Nico Golde:
[apps/madmutt.git] / mutt_sasl.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 2000-5 Brendan Cully <brendan@kublai.com>
4  *
5  * This file is part of mutt-ng, see http://www.muttng.org/.
6  * It's licensed under the GNU General Public License,
7  * please see the file GPL in the top level source directory.
8  */
9
10 /* common SASL helper routines */
11
12 #if HAVE_CONFIG_H
13 # include "config.h"
14 #endif
15
16 #include "mutt.h"
17 #include "account.h"
18 #include "mutt_sasl.h"
19 #include "mutt_socket.h"
20
21 #include "lib/mem.h"
22 #include "lib/debug.h"
23
24 #ifdef USE_SASL2
25 #include <errno.h>
26 #include <netdb.h>
27 #include <sasl/sasl.h>
28 #else
29 #include <sasl.h>
30 #endif
31 #include <sys/socket.h>
32 #include <netinet/in.h>
33
34 #ifdef USE_SASL2
35 static int getnameinfo_err (int ret)
36 {
37   int err;
38
39   switch (ret) {
40   case EAI_AGAIN:
41     debug_print (1, ("The name could not be resolved at this time. Future attempts may succeed.\n"));
42     err = SASL_TRYAGAIN;
43     break;
44   case EAI_BADFLAGS:
45     debug_print (1, ("The flags had an invalid value.\n"));
46     err = SASL_BADPARAM;
47     break;
48   case EAI_FAIL:
49     debug_print (1, ("A non-recoverable error occurred.\n"));
50     err = SASL_FAIL;
51     break;
52   case EAI_FAMILY:
53     debug_print (1, ("The address family was not recognized or the address length was invalid for the specified family.\n"));
54     err = SASL_BADPROT;
55     break;
56   case EAI_MEMORY:
57     debug_print (1, ("There was a memory allocation failure.\n"));
58     err = SASL_NOMEM;
59     break;
60   case EAI_NONAME:
61     debug_print (1, ("The name does not resolve for the supplied parameters. NI_NAMEREQD is set and the host's name cannot be located, or both nodename and servname were null.\n"));
62     err = SASL_FAIL;            /* no real equivalent */
63     break;
64   case EAI_SYSTEM:
65     debug_print (1, ("A system error occurred.  The error code can be found in errno(%d,%s)).\n", 
66                 errno, strerror (errno)));
67     err = SASL_FAIL;            /* no real equivalent */
68     break;
69   default:
70     debug_print (1, ("Unknown error %d\n", ret));
71     err = SASL_FAIL;            /* no real equivalent */
72     break;
73   }
74   return err;
75 }
76 #endif
77
78 /* arbitrary. SASL will probably use a smaller buffer anyway. OTOH it's
79  * been a while since I've had access to an SASL server which negotiated
80  * a protection buffer. */
81 #define M_SASL_MAXBUF 65536
82
83 #ifdef USE_SASL2
84 #define IP_PORT_BUFLEN 1024
85 #endif
86
87 static sasl_callback_t mutt_sasl_callbacks[5];
88
89 static int mutt_sasl_start (void);
90
91 /* callbacks */
92 static int mutt_sasl_cb_log (void *context, int priority,
93                              const char *message);
94 static int mutt_sasl_cb_authname (void *context, int id, const char **result,
95                                   unsigned int *len);
96 static int mutt_sasl_cb_pass (sasl_conn_t * conn, void *context, int id,
97                               sasl_secret_t ** psecret);
98
99 /* socket wrappers for a SASL security layer */
100 static int mutt_sasl_conn_open (CONNECTION * conn);
101 static int mutt_sasl_conn_close (CONNECTION * conn);
102 static int mutt_sasl_conn_read (CONNECTION * conn, char *buf, size_t len);
103 static int mutt_sasl_conn_write (CONNECTION * conn, const char *buf,
104                                  size_t count);
105
106 #ifdef USE_SASL2
107 /* utility function, stolen from sasl2 sample code */
108 static int iptostring (const struct sockaddr *addr, socklen_t addrlen,
109                        char *out, unsigned outlen)
110 {
111   char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV];
112   int ret;
113
114   if (!addr || !out)
115     return SASL_BADPARAM;
116
117   ret = getnameinfo (addr, addrlen, hbuf, sizeof (hbuf), pbuf, sizeof (pbuf),
118                      NI_NUMERICHOST |
119 #ifdef NI_WITHSCOPEID
120                      NI_WITHSCOPEID |
121 #endif
122                      NI_NUMERICSERV);
123   if (ret)
124     return getnameinfo_err (ret);
125
126   if (outlen < mutt_strlen (hbuf) + mutt_strlen (pbuf) + 2)
127     return SASL_BUFOVER;
128
129   snprintf (out, outlen, "%s;%s", hbuf, pbuf);
130
131   return SASL_OK;
132 }
133 #endif
134
135 /* mutt_sasl_start: called before doing a SASL exchange - initialises library
136  *   (if neccessary). */
137 int mutt_sasl_start (void)
138 {
139   static unsigned char sasl_init = 0;
140
141   static sasl_callback_t callbacks[2];
142   int rc;
143
144   if (sasl_init)
145     return SASL_OK;
146
147   /* set up default logging callback */
148   callbacks[0].id = SASL_CB_LOG;
149   callbacks[0].proc = mutt_sasl_cb_log;
150   callbacks[0].context = NULL;
151
152   callbacks[1].id = SASL_CB_LIST_END;
153   callbacks[1].proc = NULL;
154   callbacks[1].context = NULL;
155
156   rc = sasl_client_init (callbacks);
157
158   if (rc != SASL_OK) {
159     debug_print (1, ("libsasl initialisation failed.\n"));
160     return SASL_FAIL;
161   }
162
163   sasl_init = 1;
164
165   return SASL_OK;
166 }
167
168 /* mutt_sasl_client_new: wrapper for sasl_client_new which also sets various
169  * security properties. If this turns out to be fine for POP too we can
170  * probably stop exporting mutt_sasl_get_callbacks(). */
171 int mutt_sasl_client_new (CONNECTION * conn, sasl_conn_t ** saslconn)
172 {
173   sasl_security_properties_t secprops;
174
175 #ifdef USE_SASL2
176   struct sockaddr_storage local, remote;
177   socklen_t size;
178   char iplocalport[IP_PORT_BUFLEN], ipremoteport[IP_PORT_BUFLEN];
179 #else
180   sasl_external_properties_t extprops;
181 #endif
182   const char *service;
183   int rc;
184
185   if (mutt_sasl_start () != SASL_OK)
186     return -1;
187
188   switch (conn->account.type) {
189   case M_ACCT_TYPE_IMAP:
190     service = "imap";
191     break;
192   case M_ACCT_TYPE_POP:
193     service = "pop";
194     break;
195   default:
196     debug_print (1, ("account type unset\n"));
197     return -1;
198   }
199
200 #ifdef USE_SASL2
201   size = sizeof (local);
202   if (getsockname (conn->fd, (struct sockaddr *) &local, &size)) {
203     debug_print (1, ("getsockname for local failed\n"));
204     return -1;
205   }
206   else
207     if (iptostring
208         ((struct sockaddr *) &local, size, iplocalport,
209          IP_PORT_BUFLEN) != SASL_OK) {
210     debug_print (1, ("iptostring for local failed\n"));
211     return -1;
212   }
213
214   size = sizeof (remote);
215   if (getpeername (conn->fd, (struct sockaddr *) &remote, &size)) {
216     debug_print (1, ("getsockname for remote failed\n"));
217     return -1;
218   }
219   else
220     if (iptostring
221         ((struct sockaddr *) &remote, size, ipremoteport,
222          IP_PORT_BUFLEN) != SASL_OK) {
223     debug_print (1, ("iptostring for remote failed\n"));
224     return -1;
225   }
226
227   debug_print (1, ("local ip: %s, remote ip:%s\n", iplocalport, ipremoteport));
228
229   rc =
230     sasl_client_new (service, conn->account.host, iplocalport, ipremoteport,
231                      mutt_sasl_get_callbacks (&conn->account), 0, saslconn);
232
233 #else
234   rc = sasl_client_new (service, conn->account.host,
235                         mutt_sasl_get_callbacks (&conn->account),
236                         SASL_SECURITY_LAYER, saslconn);
237 #endif
238
239   if (rc != SASL_OK) {
240     debug_print (1, ("Error allocating SASL connection\n"));
241     return -1;
242   }
243
244   /*** set sasl IP properties, necessary for use with krb4 ***/
245   /* Do we need to fail if this fails? I would assume having these unset
246    * would just disable KRB4. Who wrote this code? I'm not sure how this
247    * interacts with the NSS code either, since that mucks with the fd. */
248 #ifndef USE_SASL2               /* with SASLv2 this all happens in sasl_client_new */
249   {
250     struct sockaddr_in local, remote;
251     socklen_t size;
252
253     size = sizeof (local);
254     if (getsockname (conn->fd, (struct sockaddr *) &local, &size))
255       return -1;
256
257     size = sizeof (remote);
258     if (getpeername (conn->fd, (struct sockaddr *) &remote, &size))
259       return -1;
260
261 #ifdef SASL_IP_LOCAL
262     if (sasl_setprop (*saslconn, SASL_IP_LOCAL, &local) != SASL_OK) {
263       debug_print (1, ("Error setting local IP address\n"));
264       return -1;
265     }
266 #endif
267
268 #ifdef SASL_IP_REMOTE
269     if (sasl_setprop (*saslconn, SASL_IP_REMOTE, &remote) != SASL_OK) {
270       debug_print (1, ("Error setting remote IP address\n"));
271       return -1;
272     }
273 #endif
274   }
275 #endif
276
277   /* set security properties. We use NOPLAINTEXT globally, since we can
278    * just fall back to LOGIN in the IMAP case anyway. If that doesn't
279    * work for POP, we can make it a flag or move this code into
280    * imap/auth_sasl.c */
281   memset (&secprops, 0, sizeof (secprops));
282   /* Work around a casting bug in the SASL krb4 module */
283   secprops.max_ssf = 0x7fff;
284   secprops.maxbufsize = M_SASL_MAXBUF;
285   secprops.security_flags |= SASL_SEC_NOPLAINTEXT;
286   if (sasl_setprop (*saslconn, SASL_SEC_PROPS, &secprops) != SASL_OK) {
287     debug_print (1, ("Error setting security properties\n"));
288     return -1;
289   }
290
291   /* we currently don't have an SSF finder for NSS (I don't know the API).
292    * If someone does it'd probably be trivial to write mutt_nss_get_ssf().
293    * I have a feeling more SSL code could be shared between those two files,
294    * but I haven't looked into it yet, since I still don't know the APIs. */
295 #if (defined(USE_SSL) || defined(USE_GNUTLS) && !defined(USE_NSS))
296   if (conn->account.flags & M_ACCT_SSL) {
297 #ifdef USE_SASL2                /* I'm not sure this actually has an effect, at least with SASLv2 */
298     debug_print (2, ("External SSF: %d\n", conn->ssf));
299     if (sasl_setprop (*saslconn, SASL_SSF_EXTERNAL, &(conn->ssf)) != SASL_OK)
300 #else
301     memset (&extprops, 0, sizeof (extprops));
302     extprops.ssf = conn->ssf;
303     debug_print (2, ("External SSF: %d\n", extprops.ssf));
304     if (sasl_setprop (*saslconn, SASL_SSF_EXTERNAL, &extprops) != SASL_OK)
305 #endif
306     {
307       debug_print (1, ("Error setting external properties\n"));
308       return -1;
309     }
310 #ifdef USE_SASL2
311     debug_print (2, ("External authentication name: %s\n", conn->account.user));
312     if (sasl_setprop (*saslconn, SASL_AUTH_EXTERNAL, conn->account.user) !=
313         SASL_OK) {
314       debug_print (1, ("Error setting external properties\n"));
315       return -1;
316     }
317 #endif
318   }
319 #endif
320
321   return 0;
322 }
323
324 sasl_callback_t *mutt_sasl_get_callbacks (ACCOUNT * account)
325 {
326   sasl_callback_t *callback;
327
328   callback = mutt_sasl_callbacks;
329
330   callback->id = SASL_CB_USER;
331   callback->proc = mutt_sasl_cb_authname;
332   callback->context = account;
333   callback++;
334
335   callback->id = SASL_CB_AUTHNAME;
336   callback->proc = mutt_sasl_cb_authname;
337   callback->context = account;
338   callback++;
339
340   callback->id = SASL_CB_PASS;
341   callback->proc = mutt_sasl_cb_pass;
342   callback->context = account;
343   callback++;
344
345   callback->id = SASL_CB_GETREALM;
346   callback->proc = NULL;
347   callback->context = NULL;
348   callback++;
349
350   callback->id = SASL_CB_LIST_END;
351   callback->proc = NULL;
352   callback->context = NULL;
353
354   return mutt_sasl_callbacks;
355 }
356
357 int mutt_sasl_interact (sasl_interact_t * interaction)
358 {
359   char prompt[SHORT_STRING];
360   char resp[SHORT_STRING];
361
362   while (interaction->id != SASL_CB_LIST_END) {
363     debug_print (2, ("filling in SASL interaction %ld.\n", interaction->id));
364
365     snprintf (prompt, sizeof (prompt), "%s: ", interaction->prompt);
366     resp[0] = '\0';
367     if (mutt_get_field (prompt, resp, sizeof (resp), 0))
368       return SASL_FAIL;
369
370     interaction->len = mutt_strlen (resp) + 1;
371     interaction->result = safe_malloc (interaction->len);
372     memcpy (interaction->result, resp, interaction->len);
373
374     interaction++;
375   }
376
377   return SASL_OK;
378 }
379
380 /* SASL can stack a protection layer on top of an existing connection.
381  * To handle this, we store a saslconn_t in conn->sockdata, and write
382  * wrappers which en/decode the read/write stream, then replace sockdata
383  * with an embedded copy of the old sockdata and call the underlying
384  * functions (which we've also preserved). I thought about trying to make
385  * a general stackable connection system, but it seemed like overkill -
386  * something is wrong if we have 15 filters on top of a socket. Anyway,
387  * anything else which wishes to stack can use the same method. The only
388  * disadvantage is we have to write wrappers for all the socket methods,
389  * even if we only stack over read and write. Thinking about it, the
390  * abstraction problem is that there is more in CONNECTION than there
391  * needs to be. Ideally it would have only (void*)data and methods. */
392
393 /* mutt_sasl_setup_conn: replace connection methods, sockdata with 
394  *   SASL wrappers, for protection layers. Also get ssf, as a fastpath
395  *   for the read/write methods. */
396 void mutt_sasl_setup_conn (CONNECTION * conn, sasl_conn_t * saslconn)
397 {
398   SASL_DATA *sasldata = (SASL_DATA *) safe_malloc (sizeof (SASL_DATA));
399
400   sasldata->saslconn = saslconn;
401   /* get ssf so we know whether we have to (en|de)code read/write */
402 #ifdef USE_SASL2
403   sasl_getprop (saslconn, SASL_SSF, (const void **) &sasldata->ssf);
404 #else
405   sasl_getprop (saslconn, SASL_SSF, (void **) &sasldata->ssf);
406 #endif
407   debug_print (3, ("SASL protection strength: %u\n", *sasldata->ssf));
408   /* Add SASL SSF to transport SSF */
409   conn->ssf += *sasldata->ssf;
410 #ifdef USE_SASL2
411   sasl_getprop (saslconn, SASL_MAXOUTBUF,
412                 (const void **) &sasldata->pbufsize);
413 #else
414   sasl_getprop (saslconn, SASL_MAXOUTBUF, (void **) &sasldata->pbufsize);
415 #endif
416   debug_print (3, ("SASL protection buffer size: %u\n", *sasldata->pbufsize));
417
418   /* clear input buffer */
419   sasldata->buf = NULL;
420   sasldata->bpos = 0;
421   sasldata->blen = 0;
422
423   /* preserve old functions */
424   sasldata->sockdata = conn->sockdata;
425   sasldata->msasl_open = conn->conn_open;
426   sasldata->msasl_close = conn->conn_close;
427   sasldata->msasl_read = conn->conn_read;
428   sasldata->msasl_write = conn->conn_write;
429
430   /* and set up new functions */
431   conn->sockdata = sasldata;
432   conn->conn_open = mutt_sasl_conn_open;
433   conn->conn_close = mutt_sasl_conn_close;
434   conn->conn_read = mutt_sasl_conn_read;
435   conn->conn_write = mutt_sasl_conn_write;
436 }
437
438 /* mutt_sasl_cb_log: callback to log SASL messages */
439 static int mutt_sasl_cb_log (void *context, int priority, const char *message)
440 {
441   debug_print (priority, ("SASL: %s\n", message));
442
443   return SASL_OK;
444 }
445
446 /* mutt_sasl_cb_authname: callback to retrieve authname or user from ACCOUNT */
447 static int mutt_sasl_cb_authname (void *context, int id, const char **result,
448                                   unsigned *len)
449 {
450   ACCOUNT *account = (ACCOUNT *) context;
451
452   *result = NULL;
453   if (len)
454     *len = 0;
455
456   if (!account)
457     return SASL_BADPARAM;
458
459   debug_print (2, ("getting %s for %s:%u\n",
460               id == SASL_CB_AUTHNAME ? "authname" : "user",
461               account->host, account->port));
462
463   if (id == SASL_CB_AUTHNAME) {
464     if (mutt_account_getlogin (account))
465       return SASL_FAIL;
466     *result = account->login;
467   } else {
468     if (mutt_account_getuser (account))
469       return SASL_FAIL;
470     *result = account->user;
471   }
472
473   if (len)
474     *len = mutt_strlen (*result);
475
476   return SASL_OK;
477 }
478
479 static int mutt_sasl_cb_pass (sasl_conn_t * conn, void *context, int id,
480                               sasl_secret_t ** psecret)
481 {
482   ACCOUNT *account = (ACCOUNT *) context;
483   int len;
484
485   if (!account || !psecret)
486     return SASL_BADPARAM;
487
488   debug_print (2, ("getting password for %s@%s:%u\n",
489               account->login, account->host, account->port));
490
491   if (mutt_account_getpass (account))
492     return SASL_FAIL;
493
494   len = mutt_strlen (account->pass);
495
496   *psecret = (sasl_secret_t *) safe_malloc (sizeof (sasl_secret_t) + len);
497   (*psecret)->len = len;
498   strcpy ((*psecret)->data, account->pass);     /* __STRCPY_CHECKED__ */
499
500   return SASL_OK;
501 }
502
503 /* mutt_sasl_conn_open: empty wrapper for underlying open function. We
504  *   don't know in advance that a connection will use SASL, so we
505  *   replace conn's methods with sasl methods when authentication
506  *   is successful, using mutt_sasl_setup_conn */
507 static int mutt_sasl_conn_open (CONNECTION * conn)
508 {
509   SASL_DATA *sasldata;
510   int rc;
511
512   sasldata = (SASL_DATA *) conn->sockdata;
513   conn->sockdata = sasldata->sockdata;
514   rc = (sasldata->msasl_open) (conn);
515   conn->sockdata = sasldata;
516
517   return rc;
518 }
519
520 /* mutt_sasl_conn_close: calls underlying close function and disposes of
521  *   the sasl_conn_t object, then restores connection to pre-sasl state */
522 static int mutt_sasl_conn_close (CONNECTION * conn)
523 {
524   SASL_DATA *sasldata;
525   int rc;
526
527   sasldata = (SASL_DATA *) conn->sockdata;
528
529   /* restore connection's underlying methods */
530   conn->sockdata = sasldata->sockdata;
531   conn->conn_open = sasldata->msasl_open;
532   conn->conn_close = sasldata->msasl_close;
533   conn->conn_read = sasldata->msasl_read;
534   conn->conn_write = sasldata->msasl_write;
535
536   /* release sasl resources */
537   sasl_dispose (&sasldata->saslconn);
538 #ifndef USE_SASL2
539   FREE (&sasldata->buf);
540 #endif
541   FREE (&sasldata);
542
543   /* call underlying close */
544   rc = (conn->conn_close) (conn);
545
546   return rc;
547 }
548
549 static int mutt_sasl_conn_read (CONNECTION * conn, char *buf, size_t len)
550 {
551   SASL_DATA *sasldata;
552   int rc;
553
554   unsigned int olen;
555
556   sasldata = (SASL_DATA *) conn->sockdata;
557
558   /* if we still have data in our read buffer, copy it into buf */
559   if (sasldata->blen > sasldata->bpos) {
560     olen = (sasldata->blen - sasldata->bpos > len) ? len :
561       sasldata->blen - sasldata->bpos;
562
563     memcpy (buf, sasldata->buf + sasldata->bpos, olen);
564     sasldata->bpos += olen;
565
566     return olen;
567   }
568
569   conn->sockdata = sasldata->sockdata;
570
571 #ifndef USE_SASL2
572   FREE (&sasldata->buf);
573 #endif
574   sasldata->bpos = 0;
575   sasldata->blen = 0;
576
577   /* and decode the result, if necessary */
578   if (*sasldata->ssf) {
579     do {
580       /* call the underlying read function to fill the buffer */
581       rc = (sasldata->msasl_read) (conn, buf, len);
582       if (rc <= 0)
583         goto out;
584
585       rc = sasl_decode (sasldata->saslconn, buf, rc, &sasldata->buf,
586                         &sasldata->blen);
587       if (rc != SASL_OK) {
588         debug_print (1, ("SASL decode failed: %s\n",
589                     sasl_errstring (rc, NULL, NULL)));
590         goto out;
591       }
592     }
593     while (!sasldata->blen);
594
595     olen = (sasldata->blen - sasldata->bpos > len) ? len :
596       sasldata->blen - sasldata->bpos;
597
598     memcpy (buf, sasldata->buf, olen);
599     sasldata->bpos += olen;
600
601     rc = olen;
602   }
603   else
604     rc = (sasldata->msasl_read) (conn, buf, len);
605
606 out:
607   conn->sockdata = sasldata;
608
609   return rc;
610 }
611
612 static int mutt_sasl_conn_write (CONNECTION * conn, const char *buf,
613                                  size_t len)
614 {
615   SASL_DATA *sasldata;
616   int rc;
617
618 #ifdef USE_SASL2
619   const char *pbuf;
620 #else
621   char *pbuf;
622 #endif
623   unsigned int olen, plen;
624
625   sasldata = (SASL_DATA *) conn->sockdata;
626   conn->sockdata = sasldata->sockdata;
627
628   /* encode data, if necessary */
629   if (*sasldata->ssf) {
630     /* handle data larger than MAXOUTBUF */
631     do {
632       olen = (len > *sasldata->pbufsize) ? *sasldata->pbufsize : len;
633
634       rc = sasl_encode (sasldata->saslconn, buf, olen, &pbuf, &plen);
635       if (rc != SASL_OK) {
636         debug_print (1, ("SASL encoding failed: %s\n",
637                     sasl_errstring (rc, NULL, NULL)));
638         goto fail;
639       }
640
641       rc = (sasldata->msasl_write) (conn, pbuf, plen);
642 #ifndef USE_SASL2
643       FREE (&pbuf);
644 #endif
645       if (rc != plen)
646         goto fail;
647
648       len -= olen;
649       buf += olen;
650     }
651     while (len > *sasldata->pbufsize);
652   }
653   else
654     /* just write using the underlying socket function */
655     rc = (sasldata->msasl_write) (conn, buf, len);
656
657   conn->sockdata = sasldata;
658
659   return rc;
660
661 fail:
662   conn->sockdata = sasldata;
663   return -1;
664 }