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