Andreas Krennmair:
[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 <errno.h>
28 #include <netdb.h>
29 #include <sasl/sasl.h>
30 #else
31 #include <sasl.h>
32 #endif
33 #include <sys/socket.h>
34 #include <netinet/in.h>
35
36 #ifdef USE_SASL2
37 static int getnameinfo_err(int ret)
38 {
39   int err;
40   dprint (1, (debugfile, "getnameinfo: "));
41   switch(ret)
42   {
43      case EAI_AGAIN:
44        dprint (1, (debugfile, "The name could not be resolved at this time.  Future attempts may succeed.\n"));
45        err=SASL_TRYAGAIN;
46        break;
47      case EAI_BADFLAGS:
48        dprint (1, (debugfile, "The flags had an invalid value.\n"));
49        err=SASL_BADPARAM;
50        break;
51      case EAI_FAIL:
52        dprint (1, (debugfile, "A non-recoverable error occurred.\n"));
53        err=SASL_FAIL;
54        break;
55      case EAI_FAMILY:
56        dprint (1, (debugfile, "The address family was not recognized or the address length was invalid for the specified family.\n"));
57        err=SASL_BADPROT;
58        break;
59      case EAI_MEMORY:
60        dprint (1, (debugfile, "There was a memory allocation failure.\n"));
61        err=SASL_NOMEM;
62        break;
63      case EAI_NONAME:
64        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"));
65        err=SASL_FAIL; /* no real equivalent */
66        break;
67      case EAI_SYSTEM:
68        dprint (1, (debugfile, "A system error occurred.  The error code can be found in errno(%d,%s)).\n",errno,strerror(errno)));
69        err=SASL_FAIL; /* no real equivalent */
70        break;
71      default:
72        dprint (1, (debugfile, "Unknown error %d\n",ret));
73        err=SASL_FAIL; /* no real equivalent */
74        break;
75   }
76   return err;
77 }
78 #endif
79
80 /* arbitrary. SASL will probably use a smaller buffer anyway. OTOH it's
81  * been a while since I've had access to an SASL server which negotiated
82  * a protection buffer. */ 
83 #define M_SASL_MAXBUF 65536
84
85 #ifdef USE_SASL2
86 #define IP_PORT_BUFLEN 1024
87 #endif
88
89 static sasl_callback_t mutt_sasl_callbacks[5];
90
91 static int mutt_sasl_start (void);
92
93 /* callbacks */
94 static int mutt_sasl_cb_log (void* context, int priority, const char* message);
95 static int mutt_sasl_cb_authname (void* context, int id, const char** result,
96   unsigned int* len);
97 static int mutt_sasl_cb_pass (sasl_conn_t* conn, void* context, int id,
98   sasl_secret_t** psecret);
99
100 /* socket wrappers for a SASL security layer */
101 static int mutt_sasl_conn_open (CONNECTION* conn);
102 static int mutt_sasl_conn_close (CONNECTION* conn);
103 static int mutt_sasl_conn_read (CONNECTION* conn, char* buf, size_t len);
104 static int mutt_sasl_conn_write (CONNECTION* conn, const char* buf,
105   size_t count);
106
107 #ifdef USE_SASL2
108 /* utility function, stolen from sasl2 sample code */
109 static int iptostring(const struct sockaddr *addr, socklen_t addrlen,
110                      char *out, unsigned outlen) {
111     char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV];
112     int ret;
113     
114     if(!addr || !out) return SASL_BADPARAM;
115
116     ret=getnameinfo(addr, addrlen, hbuf, sizeof(hbuf), pbuf, sizeof(pbuf),
117                    NI_NUMERICHOST |
118 #ifdef NI_WITHSCOPEID
119                    NI_WITHSCOPEID |
120 #endif
121                    NI_NUMERICSERV);
122     if(ret)
123       return getnameinfo_err(ret);
124
125     if(outlen < strlen(hbuf) + strlen(pbuf) + 2)
126         return SASL_BUFOVER;
127
128     snprintf(out, outlen, "%s;%s", hbuf, pbuf);
129
130     return SASL_OK;
131 }
132 #endif
133
134 /* mutt_sasl_start: called before doing a SASL exchange - initialises library
135  *   (if neccessary). */
136 int mutt_sasl_start (void)
137 {
138   static unsigned char sasl_init = 0;
139
140   static sasl_callback_t callbacks[2];
141   int rc;
142
143   if (sasl_init)
144     return SASL_OK;
145
146   /* set up default logging callback */
147   callbacks[0].id = SASL_CB_LOG;
148   callbacks[0].proc = mutt_sasl_cb_log;
149   callbacks[0].context = NULL;
150
151   callbacks[1].id = SASL_CB_LIST_END;
152   callbacks[1].proc = NULL;
153   callbacks[1].context = NULL;
154
155   rc = sasl_client_init (callbacks);
156
157   if (rc != SASL_OK)
158   {
159     dprint (1, (debugfile, "mutt_sasl_start: 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 #ifdef USE_SASL2
175   struct sockaddr_storage local, remote;
176   socklen_t size;
177   char iplocalport[IP_PORT_BUFLEN], ipremoteport[IP_PORT_BUFLEN];
178 #else
179   sasl_external_properties_t extprops;
180 #endif
181   const char* service;
182   int rc;
183
184   if (mutt_sasl_start () != SASL_OK)
185     return -1;
186
187   switch (conn->account.type)
188   {
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       dprint (1, (debugfile, "mutt_sasl_client_new: 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     dprint (1, (debugfile, "mutt_sasl_client_new: getsockname for local failed\n"));
204     return -1;
205   }
206   else 
207   if (iptostring((struct sockaddr *)&local, size, iplocalport, IP_PORT_BUFLEN) != SASL_OK){
208     dprint (1, (debugfile, "mutt_sasl_client_new: iptostring for local failed\n"));
209     return -1;
210   }
211   
212   size = sizeof (remote);
213   if (getpeername (conn->fd, (struct sockaddr *)&remote, &size)){
214     dprint (1, (debugfile, "mutt_sasl_client_new: getsockname for remote failed\n"));
215     return -1;
216   }
217   else 
218   if (iptostring((struct sockaddr *)&remote, size, ipremoteport, IP_PORT_BUFLEN) != SASL_OK){
219     dprint (1, (debugfile, "mutt_sasl_client_new: iptostring for remote failed\n"));
220     return -1;
221   }
222
223 dprint(1,(debugfile, "local ip: %s, remote ip:%s\n", iplocalport, ipremoteport));
224   
225   rc = sasl_client_new (service, conn->account.host, iplocalport, ipremoteport,
226     mutt_sasl_get_callbacks (&conn->account), 0, saslconn);
227
228 #else
229   rc = sasl_client_new (service, conn->account.host,
230     mutt_sasl_get_callbacks (&conn->account), SASL_SECURITY_LAYER, saslconn);
231 #endif
232
233   if (rc != SASL_OK)
234   {
235     dprint (1, (debugfile,
236       "mutt_sasl_client_new: Error allocating SASL connection\n"));
237     return -1;
238   }
239
240   /*** set sasl IP properties, necessary for use with krb4 ***/
241   /* Do we need to fail if this fails? I would assume having these unset
242    * would just disable KRB4. Who wrote this code? I'm not sure how this
243    * interacts with the NSS code either, since that mucks with the fd. */
244 #ifndef USE_SASL2 /* with SASLv2 this all happens in sasl_client_new */
245   {
246     struct sockaddr_in local, remote;
247     socklen_t size;
248
249     size = sizeof (local);
250     if (getsockname (conn->fd, (struct sockaddr*) &local, &size))
251       return -1;
252
253     size = sizeof(remote);
254     if (getpeername(conn->fd, (struct sockaddr*) &remote, &size))
255       return -1;
256
257 #ifdef SASL_IP_LOCAL
258     if (sasl_setprop(*saslconn, SASL_IP_LOCAL, &local) != SASL_OK)
259     {
260       dprint (1, (debugfile,
261         "mutt_sasl_client_new: Error setting local IP address\n"));
262       return -1;
263     }
264 #endif
265
266 #ifdef SASL_IP_REMOTE
267     if (sasl_setprop(*saslconn, SASL_IP_REMOTE, &remote) != SASL_OK)
268     {
269       dprint (1, (debugfile,
270         "mutt_sasl_client_new: 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   {
288     dprint (1, (debugfile,
289       "mutt_sasl_client_new: Error setting security properties\n"));
290     return -1;
291   }
292
293   /* we currently don't have an SSF finder for NSS (I don't know the API).
294    * If someone does it'd probably be trivial to write mutt_nss_get_ssf().
295    * I have a feeling more SSL code could be shared between those two files,
296    * but I haven't looked into it yet, since I still don't know the APIs. */
297 #if (defined(USE_SSL) || defined(USE_GNUTLS) && !defined(USE_NSS)
298   if (conn->account.flags & M_ACCT_SSL)
299   {
300 #ifdef USE_SASL2 /* I'm not sure this actually has an effect, at least with SASLv2 */
301     dprint (2, (debugfile, "External SSF: %d\n", conn->ssf));
302     if (sasl_setprop (*saslconn, SASL_SSF_EXTERNAL, &(conn->ssf)) != SASL_OK)
303 #else
304     memset (&extprops, 0, sizeof (extprops));
305     extprops.ssf = conn->ssf;
306     dprint (2, (debugfile, "External SSF: %d\n", extprops.ssf));
307     if (sasl_setprop (*saslconn, SASL_SSF_EXTERNAL, &extprops) != SASL_OK)
308 #endif
309     {
310       dprint (1, (debugfile, "mutt_sasl_client_new: Error setting external properties\n"));
311       return -1;
312     }
313 #ifdef USE_SASL2
314     dprint (2, (debugfile, "External authentication name: %s\n", conn->account.user));
315     if (sasl_setprop (*saslconn, SASL_AUTH_EXTERNAL, conn->account.user) != SASL_OK)
316      {
317       dprint (1, (debugfile, "mutt_sasl_client_new: Error setting external properties\n"));
318       return -1;
319     }
320 #endif
321   }
322 #endif
323
324   return 0;
325 }
326
327 sasl_callback_t* mutt_sasl_get_callbacks (ACCOUNT* account)
328 {
329   sasl_callback_t* callback;
330
331   callback = mutt_sasl_callbacks;
332
333   callback->id = SASL_CB_AUTHNAME;
334   callback->proc = mutt_sasl_cb_authname;
335   callback->context = account;
336   callback++;
337
338   callback->id = SASL_CB_USER;
339   callback->proc = mutt_sasl_cb_authname;
340   callback->context = account;
341   callback++;
342
343   callback->id = SASL_CB_PASS;
344   callback->proc = mutt_sasl_cb_pass;
345   callback->context = account;
346   callback++;
347
348   callback->id = SASL_CB_GETREALM;
349   callback->proc = NULL;
350   callback->context = NULL;
351   callback++;
352
353   callback->id = SASL_CB_LIST_END;
354   callback->proc = NULL;
355   callback->context = NULL;
356
357   return mutt_sasl_callbacks;
358 }
359
360 int mutt_sasl_interact (sasl_interact_t* interaction)
361 {
362   char prompt[SHORT_STRING];
363   char resp[SHORT_STRING];
364
365   while (interaction->id != SASL_CB_LIST_END)
366   {
367     dprint (2, (debugfile, "mutt_sasl_interact: filling in SASL interaction %ld.\n", interaction->id));
368
369     snprintf (prompt, sizeof (prompt), "%s: ", interaction->prompt);
370     resp[0] = '\0';
371     if (mutt_get_field (prompt, resp, sizeof (resp), 0))
372       return SASL_FAIL;
373
374     interaction->len = mutt_strlen (resp)+1;
375     interaction->result = safe_malloc (interaction->len);
376     memcpy (interaction->result, resp, interaction->len);
377
378     interaction++;
379   }
380
381   return SASL_OK;
382 }
383
384 /* SASL can stack a protection layer on top of an existing connection.
385  * To handle this, we store a saslconn_t in conn->sockdata, and write
386  * wrappers which en/decode the read/write stream, then replace sockdata
387  * with an embedded copy of the old sockdata and call the underlying
388  * functions (which we've also preserved). I thought about trying to make
389  * a general stackable connection system, but it seemed like overkill -
390  * something is wrong if we have 15 filters on top of a socket. Anyway,
391  * anything else which wishes to stack can use the same method. The only
392  * disadvantage is we have to write wrappers for all the socket methods,
393  * even if we only stack over read and write. Thinking about it, the
394  * abstraction problem is that there is more in CONNECTION than there
395  * needs to be. Ideally it would have only (void*)data and methods. */
396
397 /* mutt_sasl_setup_conn: replace connection methods, sockdata with 
398  *   SASL wrappers, for protection layers. Also get ssf, as a fastpath
399  *   for the read/write methods. */
400 void mutt_sasl_setup_conn (CONNECTION* conn, sasl_conn_t* saslconn)
401 {
402   SASL_DATA* sasldata = (SASL_DATA*) safe_malloc (sizeof (SASL_DATA));
403
404   sasldata->saslconn = saslconn;
405   /* get ssf so we know whether we have to (en|de)code read/write */
406 #ifdef USE_SASL2
407   sasl_getprop (saslconn, SASL_SSF, (const void**) &sasldata->ssf);
408 #else
409   sasl_getprop (saslconn, SASL_SSF, (void**) &sasldata->ssf);
410 #endif
411   dprint (3, (debugfile, "SASL protection strength: %u\n", *sasldata->ssf));
412   /* Add SASL SSF to transport SSF */
413   conn->ssf += *sasldata->ssf;
414 #ifdef USE_SASL2
415   sasl_getprop (saslconn, SASL_MAXOUTBUF, (const void**) &sasldata->pbufsize);
416 #else
417   sasl_getprop (saslconn, SASL_MAXOUTBUF, (void**) &sasldata->pbufsize);
418 #endif
419   dprint (3, (debugfile, "SASL protection buffer size: %u\n", *sasldata->pbufsize));
420
421   /* clear input buffer */
422   sasldata->buf = NULL;
423   sasldata->bpos = 0;
424   sasldata->blen = 0;
425
426   /* preserve old functions */
427   sasldata->sockdata = conn->sockdata;
428   sasldata->open = conn->open;
429   sasldata->close = conn->close;
430   sasldata->read = conn->read;
431   sasldata->write = conn->write;
432
433   /* and set up new functions */
434   conn->sockdata = sasldata;
435   conn->open = mutt_sasl_conn_open;
436   conn->close = mutt_sasl_conn_close;
437   conn->read = mutt_sasl_conn_read;
438   conn->write = mutt_sasl_conn_write;
439 }
440
441 /* mutt_sasl_cb_log: callback to log SASL messages */
442 static int mutt_sasl_cb_log (void* context, int priority, const char* message)
443 {
444   dprint (priority, (debugfile, "SASL: %s\n", message));
445
446   return SASL_OK;
447 }
448
449 /* mutt_sasl_cb_authname: callback to retrieve authname or user (mutt
450  *   doesn't distinguish, even if some SASL plugins do) from ACCOUNT */
451 static int mutt_sasl_cb_authname (void* context, int id, const char** result,
452   unsigned* len)
453 {
454   ACCOUNT* account = (ACCOUNT*) context;
455
456   *result = NULL;
457   if (len)
458     *len = 0;
459
460   if (!account)
461     return SASL_BADPARAM;
462
463   dprint (2, (debugfile, "mutt_sasl_cb_authname: getting %s for %s:%u\n",
464               id == SASL_CB_AUTHNAME ? "authname" : "user",
465               account->host, account->port));
466
467   if (mutt_account_getuser (account))
468     return SASL_FAIL;
469
470   *result = account->user;
471
472   if (len)
473     *len = strlen (*result);
474
475   return SASL_OK;
476 }
477
478 static int mutt_sasl_cb_pass (sasl_conn_t* conn, void* context, int id,
479   sasl_secret_t** psecret)
480 {
481   ACCOUNT* account = (ACCOUNT*) context;
482   int len;
483
484   if (!account || !psecret)
485     return SASL_BADPARAM;
486
487   dprint (2, (debugfile,
488     "mutt_sasl_cb_pass: getting password for %s@%s:%u\n", account->user,
489     account->host, account->port));
490
491   if (mutt_account_getpass (account))
492     return SASL_FAIL;
493
494   len = 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->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->open = sasldata->open;
532   conn->close = sasldata->close;
533   conn->read = sasldata->read;
534   conn->write = sasldata->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->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   {
561     olen = (sasldata->blen - sasldata->bpos > len) ? len :
562       sasldata->blen - sasldata->bpos;
563
564     memcpy (buf, sasldata->buf+sasldata->bpos, olen);
565     sasldata->bpos += olen;
566
567     return olen;
568   }
569   
570   conn->sockdata = sasldata->sockdata;
571
572 #ifndef USE_SASL2
573   FREE (&sasldata->buf);
574 #endif
575   sasldata->bpos = 0;
576   sasldata->blen = 0;
577
578   /* and decode the result, if necessary */
579   if (*sasldata->ssf)
580   {
581     do
582     {
583       /* call the underlying read function to fill the buffer */
584       rc = (sasldata->read) (conn, buf, len);
585       if (rc <= 0)
586         goto out;
587
588       rc = sasl_decode (sasldata->saslconn, buf, rc, &sasldata->buf,
589         &sasldata->blen);
590       if (rc != SASL_OK)
591       {
592         dprint (1, (debugfile, "SASL decode failed: %s\n",
593           sasl_errstring (rc, NULL, NULL)));
594         goto out;
595       }
596     }
597     while (!sasldata->blen);
598
599     olen = (sasldata->blen - sasldata->bpos > len) ? len :
600       sasldata->blen - sasldata->bpos;
601
602     memcpy (buf, sasldata->buf, olen);
603     sasldata->bpos += olen;
604
605     rc = olen;
606   }
607   else
608     rc = (sasldata->read) (conn, buf, len);
609
610   out:
611     conn->sockdata = sasldata;
612
613     return rc;
614 }
615
616 static int mutt_sasl_conn_write (CONNECTION* conn, const char* buf,
617   size_t len)
618 {
619   SASL_DATA* sasldata;
620   int rc;
621
622 #ifdef USE_SASL2
623   const char *pbuf;
624 #else
625   char* pbuf;
626 #endif
627   unsigned int olen, plen;
628
629   sasldata = (SASL_DATA*) conn->sockdata;
630   conn->sockdata = sasldata->sockdata;
631
632   /* encode data, if necessary */
633   if (*sasldata->ssf)
634   {
635     /* handle data larger than MAXOUTBUF */
636     do
637     {
638       olen = (len > *sasldata->pbufsize) ? *sasldata->pbufsize : len;
639
640       rc = sasl_encode (sasldata->saslconn, buf, olen, &pbuf, &plen);
641       if (rc != SASL_OK)
642       {
643         dprint (1, (debugfile, "SASL encoding failed: %s\n",
644           sasl_errstring (rc, NULL, NULL)));
645         goto fail;
646       }
647
648       rc = (sasldata->write) (conn, pbuf, plen);
649 #ifndef USE_SASL2
650       FREE (&pbuf);
651 #endif
652       if (rc != plen)
653         goto fail;
654
655       len -= olen;
656       buf += olen;
657     }
658     while (len > *sasldata->pbufsize);
659   }
660   else
661   /* just write using the underlying socket function */
662     rc = (sasldata->write) (conn, buf, len);
663   
664   conn->sockdata = sasldata;
665
666   return rc;
667
668  fail:
669   conn->sockdata = sasldata;
670   return -1;
671 }