2 * Copyright (C) 2000-3 Brendan Cully <brendan@kublai.com>
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.
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.
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.
19 /* common SASL helper routines */
23 #include "mutt_sasl.h"
24 #include "mutt_socket.h"
29 #include <sasl/sasl.h>
33 #include <sys/socket.h>
34 #include <netinet/in.h>
37 static int getnameinfo_err(int ret)
40 dprint (1, (debugfile, "getnameinfo: "));
44 dprint (1, (debugfile, "The name could not be resolved at this time. Future attempts may succeed.\n"));
48 dprint (1, (debugfile, "The flags had an invalid value.\n"));
52 dprint (1, (debugfile, "A non-recoverable error occurred.\n"));
56 dprint (1, (debugfile, "The address family was not recognized or the address length was invalid for the specified family.\n"));
60 dprint (1, (debugfile, "There was a memory allocation failure.\n"));
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 */
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 */
72 dprint (1, (debugfile, "Unknown error %d\n",ret));
73 err=SASL_FAIL; /* no real equivalent */
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
86 #define IP_PORT_BUFLEN 1024
89 static sasl_callback_t mutt_sasl_callbacks[5];
91 static int mutt_sasl_start (void);
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,
97 static int mutt_sasl_cb_pass (sasl_conn_t* conn, void* context, int id,
98 sasl_secret_t** psecret);
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,
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];
114 if(!addr || !out) return SASL_BADPARAM;
116 ret=getnameinfo(addr, addrlen, hbuf, sizeof(hbuf), pbuf, sizeof(pbuf),
118 #ifdef NI_WITHSCOPEID
123 return getnameinfo_err(ret);
125 if(outlen < strlen(hbuf) + strlen(pbuf) + 2)
128 snprintf(out, outlen, "%s;%s", hbuf, pbuf);
134 /* mutt_sasl_start: called before doing a SASL exchange - initialises library
135 * (if neccessary). */
136 int mutt_sasl_start (void)
138 static unsigned char sasl_init = 0;
140 static sasl_callback_t callbacks[2];
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;
151 callbacks[1].id = SASL_CB_LIST_END;
152 callbacks[1].proc = NULL;
153 callbacks[1].context = NULL;
155 rc = sasl_client_init (callbacks);
159 dprint (1, (debugfile, "mutt_sasl_start: libsasl initialisation failed.\n"));
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)
173 sasl_security_properties_t secprops;
175 struct sockaddr_storage local, remote;
177 char iplocalport[IP_PORT_BUFLEN], ipremoteport[IP_PORT_BUFLEN];
179 sasl_external_properties_t extprops;
184 if (mutt_sasl_start () != SASL_OK)
187 switch (conn->account.type)
189 case M_ACCT_TYPE_IMAP:
192 case M_ACCT_TYPE_POP:
196 dprint (1, (debugfile, "mutt_sasl_client_new: account type unset\n"));
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"));
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"));
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"));
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"));
223 dprint(1,(debugfile, "local ip: %s, remote ip:%s\n", iplocalport, ipremoteport));
225 rc = sasl_client_new (service, conn->account.host, iplocalport, ipremoteport,
226 mutt_sasl_get_callbacks (&conn->account), 0, saslconn);
229 rc = sasl_client_new (service, conn->account.host,
230 mutt_sasl_get_callbacks (&conn->account), SASL_SECURITY_LAYER, saslconn);
235 dprint (1, (debugfile,
236 "mutt_sasl_client_new: Error allocating SASL connection\n"));
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 */
246 struct sockaddr_in local, remote;
249 size = sizeof (local);
250 if (getsockname (conn->fd, (struct sockaddr*) &local, &size))
253 size = sizeof(remote);
254 if (getpeername(conn->fd, (struct sockaddr*) &remote, &size))
258 if (sasl_setprop(*saslconn, SASL_IP_LOCAL, &local) != SASL_OK)
260 dprint (1, (debugfile,
261 "mutt_sasl_client_new: Error setting local IP address\n"));
266 #ifdef SASL_IP_REMOTE
267 if (sasl_setprop(*saslconn, SASL_IP_REMOTE, &remote) != SASL_OK)
269 dprint (1, (debugfile,
270 "mutt_sasl_client_new: Error setting remote IP address\n"));
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)
288 dprint (1, (debugfile,
289 "mutt_sasl_client_new: Error setting security properties\n"));
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)
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)
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)
310 dprint (1, (debugfile, "mutt_sasl_client_new: Error setting external properties\n"));
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)
317 dprint (1, (debugfile, "mutt_sasl_client_new: Error setting external properties\n"));
327 sasl_callback_t* mutt_sasl_get_callbacks (ACCOUNT* account)
329 sasl_callback_t* callback;
331 callback = mutt_sasl_callbacks;
333 callback->id = SASL_CB_AUTHNAME;
334 callback->proc = mutt_sasl_cb_authname;
335 callback->context = account;
338 callback->id = SASL_CB_USER;
339 callback->proc = mutt_sasl_cb_authname;
340 callback->context = account;
343 callback->id = SASL_CB_PASS;
344 callback->proc = mutt_sasl_cb_pass;
345 callback->context = account;
348 callback->id = SASL_CB_GETREALM;
349 callback->proc = NULL;
350 callback->context = NULL;
353 callback->id = SASL_CB_LIST_END;
354 callback->proc = NULL;
355 callback->context = NULL;
357 return mutt_sasl_callbacks;
360 int mutt_sasl_interact (sasl_interact_t* interaction)
362 char prompt[SHORT_STRING];
363 char resp[SHORT_STRING];
365 while (interaction->id != SASL_CB_LIST_END)
367 dprint (2, (debugfile, "mutt_sasl_interact: filling in SASL interaction %ld.\n", interaction->id));
369 snprintf (prompt, sizeof (prompt), "%s: ", interaction->prompt);
371 if (mutt_get_field (prompt, resp, sizeof (resp), 0))
374 interaction->len = mutt_strlen (resp)+1;
375 interaction->result = safe_malloc (interaction->len);
376 memcpy (interaction->result, resp, interaction->len);
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. */
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)
402 SASL_DATA* sasldata = (SASL_DATA*) safe_malloc (sizeof (SASL_DATA));
404 sasldata->saslconn = saslconn;
405 /* get ssf so we know whether we have to (en|de)code read/write */
407 sasl_getprop (saslconn, SASL_SSF, (const void**) &sasldata->ssf);
409 sasl_getprop (saslconn, SASL_SSF, (void**) &sasldata->ssf);
411 dprint (3, (debugfile, "SASL protection strength: %u\n", *sasldata->ssf));
412 /* Add SASL SSF to transport SSF */
413 conn->ssf += *sasldata->ssf;
415 sasl_getprop (saslconn, SASL_MAXOUTBUF, (const void**) &sasldata->pbufsize);
417 sasl_getprop (saslconn, SASL_MAXOUTBUF, (void**) &sasldata->pbufsize);
419 dprint (3, (debugfile, "SASL protection buffer size: %u\n", *sasldata->pbufsize));
421 /* clear input buffer */
422 sasldata->buf = NULL;
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;
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;
441 /* mutt_sasl_cb_log: callback to log SASL messages */
442 static int mutt_sasl_cb_log (void* context, int priority, const char* message)
444 dprint (priority, (debugfile, "SASL: %s\n", message));
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,
454 ACCOUNT* account = (ACCOUNT*) context;
461 return SASL_BADPARAM;
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));
467 if (mutt_account_getuser (account))
470 *result = account->user;
473 *len = strlen (*result);
478 static int mutt_sasl_cb_pass (sasl_conn_t* conn, void* context, int id,
479 sasl_secret_t** psecret)
481 ACCOUNT* account = (ACCOUNT*) context;
484 if (!account || !psecret)
485 return SASL_BADPARAM;
487 dprint (2, (debugfile,
488 "mutt_sasl_cb_pass: getting password for %s@%s:%u\n", account->user,
489 account->host, account->port));
491 if (mutt_account_getpass (account))
494 len = strlen (account->pass);
496 *psecret = (sasl_secret_t*) safe_malloc (sizeof (sasl_secret_t) + len);
497 (*psecret)->len = len;
498 strcpy ((*psecret)->data, account->pass); /* __STRCPY_CHECKED__ */
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)
512 sasldata = (SASL_DATA*) conn->sockdata;
513 conn->sockdata = sasldata->sockdata;
514 rc = (sasldata->open) (conn);
515 conn->sockdata = sasldata;
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)
527 sasldata = (SASL_DATA*) conn->sockdata;
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;
536 /* release sasl resources */
537 sasl_dispose (&sasldata->saslconn);
539 FREE (&sasldata->buf);
543 /* call underlying close */
544 rc = (conn->close) (conn);
549 static int mutt_sasl_conn_read (CONNECTION* conn, char* buf, size_t len)
556 sasldata = (SASL_DATA*) conn->sockdata;
558 /* if we still have data in our read buffer, copy it into buf */
559 if (sasldata->blen > sasldata->bpos)
561 olen = (sasldata->blen - sasldata->bpos > len) ? len :
562 sasldata->blen - sasldata->bpos;
564 memcpy (buf, sasldata->buf+sasldata->bpos, olen);
565 sasldata->bpos += olen;
570 conn->sockdata = sasldata->sockdata;
573 FREE (&sasldata->buf);
578 /* and decode the result, if necessary */
583 /* call the underlying read function to fill the buffer */
584 rc = (sasldata->read) (conn, buf, len);
588 rc = sasl_decode (sasldata->saslconn, buf, rc, &sasldata->buf,
592 dprint (1, (debugfile, "SASL decode failed: %s\n",
593 sasl_errstring (rc, NULL, NULL)));
597 while (!sasldata->blen);
599 olen = (sasldata->blen - sasldata->bpos > len) ? len :
600 sasldata->blen - sasldata->bpos;
602 memcpy (buf, sasldata->buf, olen);
603 sasldata->bpos += olen;
608 rc = (sasldata->read) (conn, buf, len);
611 conn->sockdata = sasldata;
616 static int mutt_sasl_conn_write (CONNECTION* conn, const char* buf,
627 unsigned int olen, plen;
629 sasldata = (SASL_DATA*) conn->sockdata;
630 conn->sockdata = sasldata->sockdata;
632 /* encode data, if necessary */
635 /* handle data larger than MAXOUTBUF */
638 olen = (len > *sasldata->pbufsize) ? *sasldata->pbufsize : len;
640 rc = sasl_encode (sasldata->saslconn, buf, olen, &pbuf, &plen);
643 dprint (1, (debugfile, "SASL encoding failed: %s\n",
644 sasl_errstring (rc, NULL, NULL)));
648 rc = (sasldata->write) (conn, pbuf, plen);
658 while (len > *sasldata->pbufsize);
661 /* just write using the underlying socket function */
662 rc = (sasldata->write) (conn, buf, len);
664 conn->sockdata = sasldata;
669 conn->sockdata = sasldata;