Commit 0112e3c0 0112e3c07a2b8fcfb642ace4b880575f86fcf933 by Sergey Poznyakoff

Add preauth.c

1 parent d2ff5151
1 2007-12-03 Sergey Poznyakoff <gray@gnu.org.ua> 1 2007-12-03 Sergey Poznyakoff <gray@gnu.org.ua>
2 2
3 * NEWS: Update.
3 * gnulib.modules: Add des. Sort lines. 4 * gnulib.modules: Add des. Sort lines.
4 * imap4d/Makefile.am (imap4d_SOURCES): Add preauth.c 5 * imap4d/Makefile.am (imap4d_SOURCES): Add preauth.c
6 * imap4d/preauth.c: New file.
5 * imap4d/authenticate.c (imap4d_authenticate): Use 7 * imap4d/authenticate.c (imap4d_authenticate): Use
6 imap4d_session_setup. 8 imap4d_session_setup.
7 * imap4d/imap4d.c (imap4d_session_setup) 9 * imap4d/imap4d.c (imap4d_session_setup)
......
1 GNU mailutils NEWS -- history of user-visible changes. 2007-11-30 1 GNU mailutils NEWS -- history of user-visible changes. 2007-12-03
2 Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc. 2 Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
3 See the end of file for copying conditions. 3 See the end of file for copying conditions.
4 4
...@@ -146,6 +146,33 @@ Previous versions incorrectly understood such an URL as `a/b' ...@@ -146,6 +146,33 @@ Previous versions incorrectly understood such an URL as `a/b'
146 146
147 * Fixed APOP handling. 147 * Fixed APOP handling.
148 148
149 * imap4d supports PREAUTH mode.
150
151 Three mechanisms are provided for authentifying the connection in
152 PREAUTH mode:
153
154 1. stdio - PREAUTH mode is enabled automatically if imap4d is started
155 from command line in interactive mode (-i command line
156 option). The current login name is used as the user name.
157
158 2. ident - The remote machine is asked about the requester identity
159 using the identification protocol (RFC 1413). Both plaintext and
160 DES encrypted replies are understood.
161
162 3. prog - Imap4d invokes an external program to authenticate the
163 connection. Four arguments are supplied to the program:
164
165 1) Remote IP address in dotted-quad notation;
166 2) Remote port number;
167 3) Local IP address (currently "0.0.0.0");
168 4) Local port number.
169
170 If the connection is authenticated, the program should print the
171 user name, followed by a newline character, on its standard
172 output and exit with code 0.
173
174 Otherwise, it shoud exit with a non-zero exit code.
175
149 * Remove v0.6 compatibility layer. 176 * Remove v0.6 compatibility layer.
150 177
151 178
......
1 /* GNU Mailutils -- a suite of utilities for electronic mail
2 Copyright (C) 1999, 2001, 2002, 2003, 2004,
3 2005, 2006, 2007 Free Software Foundation, Inc.
4
5 GNU Mailutils is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 3, or (at your option)
8 any later version.
9
10 GNU Mailutils is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with GNU Mailutils; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 MA 02110-1301 USA */
19
20 /* Preauth support for imap4d */
21
22 #include "imap4d.h"
23 #include "des.h"
24
25
26 /* Stdio preauth */
27
28 static char *
29 do_preauth_stdio (struct sockaddr_in *pcs)
30 {
31 struct passwd *pw = getpwuid (getuid ());
32 return pw ? strdup (pw->pw_name) : NULL;
33 }
34
35
36 /* IDENT (AUTH, RFC1413) preauth */
37 #define USERNAME_C "USERID :"
38
39 /* If the reply matches sscanf expression
40
41 "%*[^:]: USERID :%*[^:]:%s"
42
43 returns a malloced copy of the %s part. Otherwise, return NULL. */
44
45 static char *
46 ident_extract_username (char *reply)
47 {
48 char *p;
49
50 p = strchr (reply, ':');
51 if (!p)
52 return NULL;
53 if (p[1] != ' ' || strncmp (p + 2, USERNAME_C, sizeof (USERNAME_C) - 1))
54 return NULL;
55 p += 2 + sizeof (USERNAME_C) - 1;
56 p = strchr (p, ':');
57 if (!p)
58 return NULL;
59 do
60 p++;
61 while (*p == ' ');
62 return p;
63 }
64
65 static int
66 trimcrlf (char *buf)
67 {
68 int len = strlen (buf);
69 if (len == 0)
70 return 0;
71 if (buf[len-1] == '\n')
72 {
73 len--;
74 if (buf[len-1] == '\r')
75 len--;
76 buf[len] = 0;
77 }
78 return len;
79 }
80
81 static int
82 is_des_p (const char *name)
83 {
84 int len = strlen (name);
85 return len > 1 && name[0] == '[' && name[len-1] == ']';
86 }
87
88 #define smask(step) ((1<<step)-1)
89 #define pstep(x,step) (((x)&smask(step))^(((x)>>step)&smask(step)))
90 #define parity_char(x) pstep(pstep(pstep((x),4),2),1)
91
92 static void
93 des_fixup_key_parity (unsigned char key[8])
94 {
95 int i;
96 for (i = 0; i < 8; i++)
97 {
98 key[i] &= 0xfe;
99 key[i] |= 1 ^ parity_char (key[i]);
100 }
101 }
102
103 static void
104 des_cbc_cksum (gl_des_ctx *ctx, unsigned char *buf, size_t bufsize,
105 unsigned char out[8], unsigned char key[8])
106 {
107 while (bufsize > 0)
108 {
109 if (bufsize >= 8)
110 {
111 unsigned char *p = key;
112 *p++ ^= *buf++;
113 *p++ ^= *buf++;
114 *p++ ^= *buf++;
115 *p++ ^= *buf++;
116 *p++ ^= *buf++;
117 *p++ ^= *buf++;
118 *p++ ^= *buf++;
119 *p++ ^= *buf++;
120 bufsize -= 8;
121 }
122 else
123 {
124 unsigned char *p = key + bufsize;
125 buf += bufsize;
126 switch (bufsize) {
127 case 7:
128 *--p ^= *--buf;
129 case 6:
130 *--p ^= *--buf;
131 case 5:
132 *--p ^= *--buf;
133 case 4:
134 *--p ^= *--buf;
135 case 3:
136 *--p ^= *--buf;
137 case 2:
138 *--p ^= *--buf;
139 case 1:
140 *--p ^= *--buf;
141 }
142 bufsize = 0;
143 }
144 gl_des_ecb_crypt (ctx, key, key, 0);
145 }
146 }
147
148 static void
149 des_string_to_key (char *buf, size_t bufsize, unsigned char key[8])
150 {
151 size_t i;
152 int j;
153 unsigned temp;
154 unsigned char *p;
155 char *p_char;
156 char k_char[64];
157 gl_des_ctx context;
158 char *pstr;
159 int forward = 1;
160 p_char = k_char;
161 memset (k_char, 0, sizeof (k_char));
162
163 /* get next 8 bytes, strip parity, xor */
164 pstr = buf;
165 for (i = 1; i <= bufsize; i++)
166 {
167 /* get next input key byte */
168 temp = (unsigned int) *pstr++;
169 /* loop through bits within byte, ignore parity */
170 for (j = 0; j <= 6; j++)
171 {
172 if (forward)
173 *p_char++ ^= (int) temp & 01;
174 else
175 *--p_char ^= (int) temp & 01;
176 temp = temp >> 1;
177 }
178 while (--j > 0);
179
180 /* check and flip direction */
181 if ((i%8) == 0)
182 forward = !forward;
183 }
184
185 p_char = k_char;
186 p = (unsigned char *) key;
187
188 for (i = 0; i <= 7; i++)
189 {
190 temp = 0;
191 for (j = 0; j <= 6; j++)
192 temp |= *p_char++ << (1 + j);
193 *p++ = (unsigned char) temp;
194 }
195
196 des_fixup_key_parity (key);
197 gl_des_setkey (&context, key);
198 des_cbc_cksum (&context, buf, bufsize, key, key);
199 memset (&context, 0, sizeof context);
200 des_fixup_key_parity (key);
201 }
202
203 static int
204 decode64_buf (const char *name, unsigned char **pbuf, size_t *psize)
205 {
206 mu_stream_t str = NULL, flt = NULL;
207 size_t namelen;
208 unsigned char buf[512];
209 size_t size;
210
211 name++;
212 namelen = strlen (name) - 1;
213 mu_memory_stream_create (&str, NULL, MU_STREAM_NO_CHECK);
214 mu_filter_create (&flt, str, "base64", MU_FILTER_DECODE,
215 MU_STREAM_READ | MU_STREAM_NO_CHECK);
216 mu_stream_open (str);
217 mu_stream_sequential_write (str, name, namelen);
218 mu_stream_read (flt, buf, sizeof buf, 0, &size);
219 mu_stream_destroy (&flt, NULL);
220 mu_stream_destroy (&str, NULL);
221 *pbuf = malloc (size);
222 if (!*pbuf)
223 return 1;
224 memcpy (*pbuf, buf, size);
225 *psize = size;
226 return 0;
227 }
228
229 struct ident_info
230 {
231 uint32_t checksum;
232 uint16_t random;
233 uint16_t uid;
234 uint32_t date;
235 uint32_t ip_local;
236 uint32_t ip_remote;
237 uint16_t port_local;
238 uint16_t port_remote;
239 };
240
241 union ident_data
242 {
243 struct ident_info fields;
244 unsigned long longs[6];
245 unsigned char chars[24];
246 };
247
248 char *
249 ident_decrypt (const char *file, const char *name)
250 {
251 unsigned char *buf = NULL;
252 size_t size = 0;
253 int fd;
254 char keybuf[1024];
255 union ident_data id;
256
257 if (decode64_buf (name, &buf, &size))
258 return NULL;
259
260 if (size != 24)
261 {
262 mu_diag_output (MU_DIAG_ERROR,
263 _("Incorrect length of IDENT DES packet"));
264 free (buf);
265 return NULL;
266 }
267
268 fd = open (file, O_RDONLY);
269 if (fd < 0)
270 {
271 mu_diag_output (MU_DIAG_ERROR,
272 _("Cannot open file %s: %s"),
273 file, mu_strerror (errno));
274 return NULL;
275 }
276
277 while (read (fd, keybuf, sizeof (keybuf)) == sizeof (keybuf))
278 {
279 int i;
280 unsigned char key[8];
281 gl_des_ctx ctx;
282
283 des_string_to_key (keybuf, sizeof (keybuf), key);
284 gl_des_setkey (&ctx, key);
285
286 memcpy (id.chars, buf, size);
287
288 gl_des_ecb_decrypt (&ctx, (char *)&id.longs[4], (char *)&id.longs[4]);
289 id.longs[4] ^= id.longs[2];
290 id.longs[5] ^= id.longs[3];
291
292 gl_des_ecb_decrypt (&ctx, (char *)&id.longs[2], (char *)&id.longs[2]);
293 id.longs[2] ^= id.longs[0];
294 id.longs[3] ^= id.longs[1];
295
296 gl_des_ecb_decrypt (&ctx, (char *)&id.longs[0], (char *)&id.longs[0]);
297
298 for (i = 1; i < 6; i++)
299 id.longs[0] ^= id.longs[i];
300
301 if (id.fields.checksum == 0)
302 break;
303 }
304 close (fd);
305 free (buf);
306
307 if (id.fields.checksum == 0)
308 {
309 uid_t uid = ntohs (id.fields.uid);
310 auth_data = mu_get_auth_by_uid (uid);
311 if (!auth_data)
312 {
313 mu_diag_output (MU_DIAG_ERROR, _("No user with UID %u"), uid);
314 return NULL;
315 }
316 return auth_data->name;
317 }
318 else
319 mu_diag_output (MU_DIAG_ERROR, _("Failed to decrypt IDENT reply"));
320 return NULL;
321 }
322
323 static char *
324 do_preauth_ident (struct sockaddr_in *pcs)
325 {
326 mu_stream_t stream;
327 char hostaddr[16];
328 char *p = inet_ntoa (pcs->sin_addr);
329 int rc;
330 char *buf = NULL;
331 size_t size = 0;
332 char *name = NULL;
333
334 memcpy (hostaddr, p, 15);
335 hostaddr[15] = 0;
336 rc = mu_tcp_stream_create (&stream, hostaddr, ident_port,
337 MU_STREAM_RDWR | MU_STREAM_NO_CHECK);
338 if (rc)
339 {
340 mu_diag_output (MU_DIAG_INFO, _("Cannot create TCP stream: %s"),
341 mu_strerror (rc));
342 return NULL;
343 }
344
345 rc = mu_stream_open (stream);
346 if (rc)
347 {
348 mu_diag_output (MU_DIAG_INFO, _("Cannot open TCP stream to %s:%d: %s"),
349 hostaddr, ident_port, mu_strerror (rc));
350 return NULL;
351 }
352
353 mu_stream_sequential_printf (stream, "%u , %u\r\n", ntohs (pcs->sin_port),
354 mu_gocs_daemon.port);
355 mu_stream_shutdown (stream, MU_STREAM_WRITE);
356
357 rc = mu_stream_sequential_getline (stream, &buf, &size, NULL);
358 mu_stream_close (stream);
359 mu_stream_destroy (&stream, NULL);
360 if (rc)
361 {
362 mu_diag_output (MU_DIAG_INFO, _("Cannot read answer from %s:%d: %s"),
363 hostaddr, ident_port, mu_strerror (rc));
364 return NULL;
365 }
366 mu_diag_output (MU_DIAG_INFO, "Got %s", buf);
367 trimcrlf (buf);
368 name = ident_extract_username (buf);
369 if (!name)
370 mu_diag_output (MU_DIAG_INFO,
371 _("Malformed IDENT response: `%s', from %s:%d"),
372 buf, hostaddr, ident_port);
373 else if (is_des_p (name))
374 {
375 if (!ident_keyfile)
376 {
377 mu_diag_output (MU_DIAG_ERROR,
378 _("Keydile not specified in config; "
379 "use `ident-keyfile FILE'"));
380 name = NULL;
381 }
382 else
383 name = ident_decrypt (ident_keyfile, name);
384 }
385 else if (ident_encrypt_only)
386 {
387 mu_diag_output (MU_DIAG_ERROR,
388 _("Refusing unencrypted ident reply from %s:%d"),
389 hostaddr, ident_port);
390 name = NULL;
391 }
392 else
393 {
394 mu_diag_output (MU_DIAG_INFO, "USERNAME %s", name);
395 name = strdup (name);
396 }
397
398 free (buf);
399 return name;
400 }
401
402
403 /* External (program) preauth */
404 static char *
405 do_preauth_program (struct sockaddr_in *pcs)
406 {
407 FILE *fp;
408 char *p = inet_ntoa (pcs->sin_addr);
409 char *cmd = 0;
410 char *buf = NULL;
411 size_t size;
412 ssize_t rc;
413
414 asprintf (&cmd, "%s %s %u %s %u",
415 preauth_program,
416 p,
417 ntohs (pcs->sin_port),
418 "0.0.0.0", /* FIXME */
419 mu_gocs_daemon.port);
420 fp = popen (cmd, "r");
421 free (cmd);
422 rc = getline (&buf, &size, fp);
423 pclose (fp);
424 if (rc > 0)
425 {
426 if (trimcrlf (buf) == 0)
427 {
428 free (buf);
429 return NULL;
430 }
431 return buf;
432 }
433 return NULL;
434 }
435
436 int
437 imap4d_preauth_setup (int fd)
438 {
439 struct sockaddr_in cs;
440 int len = sizeof cs;
441 char *username = NULL;
442
443 mu_diag_output (MU_DIAG_INFO, _("Incoming connection opened"));
444 if (getpeername (fd, (struct sockaddr *) &cs, &len) < 0)
445 mu_diag_output (MU_DIAG_ERROR,
446 _("Cannot obtain IP address of client: %s"),
447 strerror (errno));
448 else
449 mu_diag_output (MU_DIAG_INFO, _("Connect from %s"),
450 inet_ntoa (cs.sin_addr));
451
452 auth_data = NULL;
453 switch (preauth_mode)
454 {
455 case preauth_none:
456 return 0;
457
458 case preauth_stdio:
459 username = do_preauth_stdio (&cs);
460 break;
461
462 case preauth_ident:
463 username = do_preauth_ident (&cs);
464 break;
465
466 case preauth_prog:
467 username = do_preauth_program (&cs);
468 break;
469 }
470
471 if (username)
472 {
473 int rc;
474
475 if (auth_data)
476 rc = imap4d_session_setup0 ();
477 else
478 {
479 rc = imap4d_session_setup (username);
480 free (username);
481 }
482 if (rc == 0)
483 {
484 state = STATE_AUTH;
485 return 0;
486 }
487 }
488
489 return preauth_only;
490 }