better debug symbols
[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 #include <lib-lib/lib-lib.h>
13
14 #ifdef USE_SASL
15
16 #include <netdb.h>
17 #include <sasl/sasl.h>
18 #include <sys/socket.h>
19 #include <netinet/in.h>
20
21 #include <lib-ui/curses.h>
22 #include <lib-sys/mutt_socket.h>
23
24 #include "mutt.h"
25 #include "account.h"
26 #include "mutt_sasl.h"
27
28 /* arbitrary. SASL will probably use a smaller buffer anyway. OTOH it's
29  * been a while since I've had access to an SASL server which negotiated
30  * a protection buffer. */
31 #define M_SASL_MAXBUF 65536
32
33 #define IP_PORT_BUFLEN 1024
34
35 static sasl_callback_t mutt_sasl_callbacks[5];
36
37 /* callbacks */
38 static int mutt_sasl_cb_authname (void *context, int id, const char **result,
39                                   unsigned int *len);
40 static int mutt_sasl_cb_pass (sasl_conn_t * conn, void *context, int id,
41                               sasl_secret_t ** psecret);
42
43 /* socket wrappers for a SASL security layer */
44 static int mutt_sasl_conn_open (CONNECTION * conn);
45 static int mutt_sasl_conn_close (CONNECTION * conn);
46 static int mutt_sasl_conn_read (CONNECTION * conn, char *buf, ssize_t len);
47 static int mutt_sasl_conn_write (CONNECTION * conn, const char *buf,
48                                  ssize_t count);
49
50 static int
51 iptostring(const struct sockaddr_storage *addr, char *out, unsigned outlen)
52 {
53     char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV];
54
55     if (getnameinfo((struct sockaddr*)addr, sizeof(*addr),
56                     hbuf, sizeof(hbuf), pbuf, sizeof(pbuf),
57                     NI_NUMERICHOST | NI_NUMERICSERV))
58         return SASL_FAIL;
59
60     snprintf(out, outlen, "%s;%s", hbuf, pbuf);
61     return SASL_OK;
62 }
63
64 /* mutt_sasl_start: called before doing a SASL exchange - initialises library
65  *   (if necessary). */
66 static int mutt_sasl_start (void)
67 {
68     static int sasl_init = 0;
69
70     if (sasl_init)
71         return SASL_OK;
72
73     if (sasl_client_init(NULL) != SASL_OK)
74         return SASL_FAIL;
75
76     sasl_init = 1;
77
78     return SASL_OK;
79 }
80
81 static sasl_callback_t *mutt_sasl_get_callbacks (ACCOUNT * account)
82 {
83     sasl_callback_t *callback;
84
85     callback = mutt_sasl_callbacks;
86
87     callback->id = SASL_CB_USER;
88     callback->proc = mutt_sasl_cb_authname;
89     callback->context = account;
90     callback++;
91
92     callback->id = SASL_CB_AUTHNAME;
93     callback->proc = mutt_sasl_cb_authname;
94     callback->context = account;
95     callback++;
96
97     callback->id = SASL_CB_PASS;
98     callback->proc = mutt_sasl_cb_pass;
99     callback->context = account;
100     callback++;
101
102     callback->id = SASL_CB_GETREALM;
103     callback->proc = NULL;
104     callback->context = NULL;
105     callback++;
106
107     callback->id = SASL_CB_LIST_END;
108     callback->proc = NULL;
109     callback->context = NULL;
110
111     return mutt_sasl_callbacks;
112 }
113
114 int mutt_sasl_client_new (CONNECTION * conn, sasl_conn_t ** saslconn)
115 {
116     sasl_security_properties_t secprops;
117
118     struct sockaddr_storage local, remote;
119     socklen_t size;
120     char iplocalport[IP_PORT_BUFLEN], ipremoteport[IP_PORT_BUFLEN];
121     const char *service;
122     int rc;
123
124     if (mutt_sasl_start() != SASL_OK)
125         return -1;
126
127     switch (conn->account.type) {
128       case M_ACCT_TYPE_IMAP:
129         service = "imap";
130         break;
131
132       case M_ACCT_TYPE_POP:
133         service = "pop";
134         break;
135
136       default:
137         return -1;
138     }
139
140     size = sizeof(local);
141     if (getsockname(conn->fd, (struct sockaddr *) &local, &size)
142     ||  iptostring(&local, iplocalport, IP_PORT_BUFLEN) != SASL_OK)
143     {
144         return -1;
145     }
146
147     size = sizeof(remote);
148     if (getpeername(conn->fd, (struct sockaddr *) &remote, &size)
149     ||  iptostring(&remote, ipremoteport, IP_PORT_BUFLEN) != SASL_OK)
150     {
151         return -1;
152     }
153
154     rc = sasl_client_new(service, conn->account.host, iplocalport,
155                          ipremoteport,
156                          mutt_sasl_get_callbacks(&conn->account), 0, saslconn);
157
158     if (rc != SASL_OK) {
159         return -1;
160     }
161
162     /* set security properties. We use NOPLAINTEXT globally, since we can
163      * just fall back to LOGIN in the IMAP case anyway. If that doesn't
164      * work for POP, we can make it a flag or move this code into
165      * imap/auth_sasl.c */
166     p_clear(&secprops, 1);
167     /* Work around a casting bug in the SASL krb4 module */
168     secprops.max_ssf = 0x7fff;
169     secprops.maxbufsize = M_SASL_MAXBUF;
170     secprops.security_flags |= SASL_SEC_NOPLAINTEXT;
171     if (sasl_setprop (*saslconn, SASL_SEC_PROPS, &secprops) != SASL_OK) {
172         return -1;
173     }
174
175     if (conn->ssf) {
176         if (sasl_setprop (*saslconn, SASL_SSF_EXTERNAL, &(conn->ssf)) != SASL_OK)
177         {
178             return -1;
179         }
180         if (sasl_setprop (*saslconn, SASL_AUTH_EXTERNAL, conn->account.user) !=
181             SASL_OK) {
182             return -1;
183         }
184     }
185
186     return 0;
187 }
188
189 int mutt_sasl_interact (sasl_interact_t * interaction)
190 {
191     char prompt[STRING];
192     char resp[STRING];
193
194     while (interaction->id != SASL_CB_LIST_END) {
195         snprintf (prompt, sizeof (prompt), "%s: ", interaction->prompt);
196         resp[0] = '\0';
197         if (mutt_get_field (prompt, resp, sizeof (resp), 0))
198             return SASL_FAIL;
199
200         interaction->len = m_strlen(resp) + 1;
201         interaction->result = p_dupstr(resp, interaction->len - 1);
202         interaction++;
203     }
204
205     return SASL_OK;
206 }
207
208 /* SASL can stack a protection layer on top of an existing connection.
209  * To handle this, we store a saslconn_t in conn->sockdata, and write
210  * wrappers which en/decode the read/write stream, then replace sockdata
211  * with an embedded copy of the old sockdata and call the underlying
212  * functions (which we've also preserved). I thought about trying to make
213  * a general stackable connection system, but it seemed like overkill -
214  * something is wrong if we have 15 filters on top of a socket. Anyway,
215  * anything else which wishes to stack can use the same method. The only
216  * disadvantage is we have to write wrappers for all the socket methods,
217  * even if we only stack over read and write. Thinking about it, the
218  * abstraction problem is that there is more in CONNECTION than there
219  * needs to be. Ideally it would have only (void*)data and methods. */
220
221 /* mutt_sasl_setup_conn: replace connection methods, sockdata with 
222  *   SASL wrappers, for protection layers. Also get ssf, as a fastpath
223  *   for the read/write methods. */
224 void mutt_sasl_setup_conn (CONNECTION * conn, sasl_conn_t * saslconn)
225 {
226     SASL_DATA *sasldata = p_new(SASL_DATA, 1);
227
228     sasldata->saslconn = saslconn;
229     /* get ssf so we know whether we have to (en|de)code read/write */
230     sasl_getprop (saslconn, SASL_SSF, (const void **)(void *)&sasldata->ssf);
231
232     /* Add SASL SSF to transport SSF */
233     conn->ssf += *sasldata->ssf;
234     sasl_getprop (saslconn, SASL_MAXOUTBUF,
235                   (const void **)(void *)&sasldata->pbufsize);
236
237     /* clear input buffer */
238     sasldata->buf = NULL;
239     sasldata->bpos = 0;
240     sasldata->blen = 0;
241
242     /* preserve old functions */
243     sasldata->sockdata = conn->sockdata;
244     sasldata->msasl_open = conn->conn_open;
245     sasldata->msasl_close = conn->conn_close;
246     sasldata->msasl_read = conn->conn_read;
247     sasldata->msasl_write = conn->conn_write;
248
249     /* and set up new functions */
250     conn->sockdata = sasldata;
251     conn->conn_open = mutt_sasl_conn_open;
252     conn->conn_close = mutt_sasl_conn_close;
253     conn->conn_read = mutt_sasl_conn_read;
254     conn->conn_write = mutt_sasl_conn_write;
255 }
256
257 void mutt_sasl_done (void) {
258     sasl_done ();
259 }
260
261 /* mutt_sasl_cb_authname: callback to retrieve authname or user from ACCOUNT */
262 static int mutt_sasl_cb_authname (void *context, int id, const char **result,
263                                   unsigned *len)
264 {
265     ACCOUNT *account = (ACCOUNT *) context;
266
267     *result = NULL;
268     if (len)
269         *len = 0;
270
271     if (!account)
272         return SASL_BADPARAM;
273
274     if (id == SASL_CB_AUTHNAME) {
275         if (mutt_account_getlogin (account))
276             return SASL_FAIL;
277         *result = account->login;
278     } else {
279         if (mutt_account_getuser (account))
280             return SASL_FAIL;
281         *result = account->user;
282     }
283
284     if (len)
285         *len = m_strlen(*result);
286
287     return SASL_OK;
288 }
289
290 static int mutt_sasl_cb_pass(sasl_conn_t *conn __attribute__ ((unused)),
291                              void *context, int id __attribute__ ((unused)),
292                              sasl_secret_t **psecret)
293 {
294     ACCOUNT *account = (ACCOUNT *) context;
295     int len;
296
297     if (!account || !psecret)
298         return SASL_BADPARAM;
299
300     if (mutt_account_getpass (account))
301         return SASL_FAIL;
302
303     len = m_strlen(account->pass);
304
305     *psecret = xmalloc(sizeof(sasl_secret_t) + len);
306     (*psecret)->len = len;
307     memcpy((char*)(*psecret)->data, account->pass, len);
308
309     return SASL_OK;
310 }
311
312 /* mutt_sasl_conn_open: empty wrapper for underlying open function. We
313  *   don't know in advance that a connection will use SASL, so we
314  *   replace conn's methods with sasl methods when authentication
315  *   is successful, using mutt_sasl_setup_conn */
316 static int mutt_sasl_conn_open (CONNECTION * conn)
317 {
318     SASL_DATA *sasldata;
319     int rc;
320
321     sasldata = (SASL_DATA *) conn->sockdata;
322     conn->sockdata = sasldata->sockdata;
323     rc = (sasldata->msasl_open) (conn);
324     conn->sockdata = sasldata;
325
326     return rc;
327 }
328
329 /* mutt_sasl_conn_close: calls underlying close function and disposes of
330  *   the sasl_conn_t object, then restores connection to pre-sasl state */
331 static int mutt_sasl_conn_close(CONNECTION * conn)
332 {
333     SASL_DATA *sasldata;
334     int rc;
335
336     sasldata = (SASL_DATA *) conn->sockdata;
337
338     /* restore connection's underlying methods */
339     conn->sockdata = sasldata->sockdata;
340     conn->conn_open = sasldata->msasl_open;
341     conn->conn_close = sasldata->msasl_close;
342     conn->conn_read = sasldata->msasl_read;
343     conn->conn_write = sasldata->msasl_write;
344
345     /* release sasl resources */
346     sasl_dispose (&sasldata->saslconn);
347     p_delete(&sasldata->buf);
348     p_delete(&sasldata);
349
350     /* call underlying close */
351     rc = (conn->conn_close) (conn);
352
353     return rc;
354 }
355
356 static int mutt_sasl_conn_read (CONNECTION * conn, char *buf, ssize_t len)
357 {
358     SASL_DATA *sasldata;
359     int rc;
360
361     unsigned int olen;
362
363     sasldata = (SASL_DATA *) conn->sockdata;
364
365     /* if we still have data in our read buffer, copy it into buf */
366     if (sasldata->blen > sasldata->bpos) {
367         olen = (sasldata->blen - sasldata->bpos > len) ? len :
368             sasldata->blen - sasldata->bpos;
369
370         memcpy (buf, sasldata->buf + sasldata->bpos, olen);
371         sasldata->bpos += olen;
372
373         return olen;
374     }
375
376     conn->sockdata = sasldata->sockdata;
377
378     p_delete(&sasldata->buf);
379     sasldata->bpos = 0;
380     sasldata->blen = 0;
381
382     /* and decode the result, if necessary */
383     if (*sasldata->ssf) {
384         do {
385             /* call the underlying read function to fill the buffer */
386             rc = (sasldata->msasl_read) (conn, buf, len);
387             if (rc <= 0)
388                 goto out;
389
390             rc = sasl_decode(sasldata->saslconn, buf, rc,
391                              (const char **)&sasldata->buf, &sasldata->blen);
392             if (rc != SASL_OK) {
393                 goto out;
394             }
395         }
396         while (!sasldata->blen);
397
398         olen = (sasldata->blen - sasldata->bpos > len) ? len :
399             sasldata->blen - sasldata->bpos;
400
401         memcpy (buf, sasldata->buf, olen);
402         sasldata->bpos += olen;
403
404         rc = olen;
405     }
406     else
407         rc = (sasldata->msasl_read) (conn, buf, len);
408
409 out:
410     conn->sockdata = sasldata;
411
412     return rc;
413 }
414
415 static int
416 mutt_sasl_conn_write(CONNECTION * conn, const char *buf, ssize_t len)
417 {
418     SASL_DATA *sasldata;
419     int rc;
420
421     char *pbuf;
422     unsigned int olen, plen;
423
424     sasldata = (SASL_DATA *) conn->sockdata;
425     conn->sockdata = sasldata->sockdata;
426
427     /* encode data, if necessary */
428     if (*sasldata->ssf) {
429         /* handle data larger than MAXOUTBUF */
430         do {
431             olen = (len > *sasldata->pbufsize) ? *sasldata->pbufsize : len;
432
433             rc = sasl_encode(sasldata->saslconn, buf, olen,
434                              (const char **)&pbuf, &plen);
435             if (rc != SASL_OK) {
436                 goto fail;
437             }
438
439             plen -= (sasldata->msasl_write)(conn, pbuf, plen);
440             p_delete(&pbuf);
441             if (plen)
442                 goto fail;
443
444             len -= olen;
445             buf += olen;
446         } while (len > *sasldata->pbufsize);
447     } else {
448         /* just write using the underlying socket function */
449         rc = (sasldata->msasl_write) (conn, buf, len);
450     }
451
452     conn->sockdata = sasldata;
453
454     return rc;
455
456 fail:
457     conn->sockdata = sasldata;
458     return -1;
459 }
460
461 #endif /* USE_SASL */