Various SASL fixes.
[exim.git] / src / src / auths / cyrus_sasl.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) University of Cambridge 1995 - 2009 */
6 /* See the file NOTICE for conditions of use and distribution. */
7
8 /* This code was originally contributed by Matthew Byng-Maddick */
9
10 /* Copyright (c) A L Digital 2004 */
11
12 /* A generic (mechanism independent) Cyrus SASL authenticator. */
13
14
15 #include "../exim.h"
16
17
18 /* We can't just compile this code and allow the library mechanism to omit the
19 functions if they are not wanted, because we need to have the Cyrus SASL header
20 available for compiling. Therefore, compile these functions only if
21 AUTH_CYRUS_SASL is defined. However, some compilers don't like compiling empty
22 modules, so keep them happy with a dummy when skipping the rest. Make it
23 reference itself to stop picky compilers complaining that it is unused, and put
24 in a dummy argument to stop even pickier compilers complaining about infinite
25 loops. */
26
27 #ifndef AUTH_CYRUS_SASL
28 static void dummy(int x) { dummy(x-1); }
29 #else
30
31
32 #include <sasl/sasl.h>
33 #include "cyrus_sasl.h"
34
35 /* Options specific to the cyrus_sasl authentication mechanism. */
36
37 optionlist auth_cyrus_sasl_options[] = {
38   { "server_hostname",      opt_stringptr,
39       (void *)(offsetof(auth_cyrus_sasl_options_block, server_hostname)) },
40   { "server_mech",          opt_stringptr,
41       (void *)(offsetof(auth_cyrus_sasl_options_block, server_mech)) },
42   { "server_realm",         opt_stringptr,
43       (void *)(offsetof(auth_cyrus_sasl_options_block, server_realm)) },
44   { "server_service",       opt_stringptr,
45       (void *)(offsetof(auth_cyrus_sasl_options_block, server_service)) }
46 };
47
48 /* Size of the options list. An extern variable has to be used so that its
49 address can appear in the tables drtables.c. */
50
51 int auth_cyrus_sasl_options_count =
52   sizeof(auth_cyrus_sasl_options)/sizeof(optionlist);
53
54 /* Default private options block for the cyrus_sasl authentication method. */
55
56 auth_cyrus_sasl_options_block auth_cyrus_sasl_option_defaults = {
57   US"smtp",         /* server_service */
58   US"$primary_hostname", /* server_hostname */
59   NULL,             /* server_realm */
60   NULL              /* server_mech */
61 };
62
63
64 /*************************************************
65 *          Initialization entry point            *
66 *************************************************/
67
68 /* Called for each instance, after its options have been read, to
69 enable consistency checks to be done, or anything else that needs
70 to be set up. */
71
72
73 /* Auxiliary function, passed in data to sasl_server_init(). */
74
75 static int
76 mysasl_config(void *context,
77               const char *plugin_name,
78               const char *option,
79               const char **result,
80               unsigned int *len)
81 {
82 if (context && !strcmp(option, "mech_list"))
83   {
84   *result = context;
85   if (len != NULL) *len = strlen(*result);
86   return SASL_OK;
87   }
88 return SASL_FAIL;
89 }
90
91 /* Here's the real function */
92
93 void
94 auth_cyrus_sasl_init(auth_instance *ablock)
95 {
96 auth_cyrus_sasl_options_block *ob =
97   (auth_cyrus_sasl_options_block *)(ablock->options_block);
98 uschar *list, *listptr, *buffer;
99 int rc, i;
100 unsigned int len;
101 uschar *rs_point, *expanded_hostname;
102
103 sasl_conn_t *conn;
104 sasl_callback_t cbs[]={
105   {SASL_CB_GETOPT, NULL, NULL },
106   {SASL_CB_LIST_END, NULL, NULL}};
107
108 /* default the mechanism to our "public name" */
109 if(ob->server_mech == NULL)
110   ob->server_mech=string_copy(ablock->public_name);
111
112 expanded_hostname = expand_string(ob->server_hostname);
113 if (expanded_hostname == NULL)
114   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
115       "couldn't expand server_hostname [%s]: %s",
116       ablock->name, ob->server_hostname, expand_string_message);
117
118 /* we're going to initialise the library to check that there is an
119  * authenticator of type whatever mechanism we're using
120  */
121
122 cbs[0].proc = (int(*)(void)) &mysasl_config;
123 cbs[0].context = ob->server_mech;
124
125 rc=sasl_server_init(cbs, "exim");
126
127 if( rc != SASL_OK )
128   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
129       "couldn't initialise Cyrus SASL library.", ablock->name);
130
131 rc=sasl_server_new(CS ob->server_service, CS expanded_hostname,
132                    CS ob->server_realm, NULL, NULL, NULL, 0, &conn);
133 if( rc != SASL_OK )
134   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
135       "couldn't initialise Cyrus SASL server connection.", ablock->name);
136
137 rc=sasl_listmech(conn, NULL, "", ":", "", (const char **)(&list), &len, &i);
138 if( rc != SASL_OK )
139   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
140       "couldn't get Cyrus SASL mechanism list.", ablock->name);
141
142 i=':';
143 listptr=list;
144
145 HDEBUG(D_auth) {
146   debug_printf("Initialised Cyrus SASL service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
147       ob->server_service, expanded_hostname, ob->server_realm);
148   debug_printf("Cyrus SASL knows mechanisms: %s\n", list);
149 }
150
151 /* the store_get / store_reset mechanism is hierarchical
152  * the hierarchy is stored for us behind our back. This point
153  * creates a hierarchy point for this function.
154  */
155 rs_point=store_get(0);
156
157 /* loop until either we get to the end of the list, or we match the
158  * public name of this authenticator
159  */
160 while( ( buffer = string_nextinlist(&listptr, &i, NULL, 0) ) &&
161        strcmpic(buffer,ob->server_mech) );
162
163 if(!buffer)
164   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
165       "Cyrus SASL doesn't know about mechanism %s.", ablock->name, ob->server_mech);
166
167 store_reset(rs_point);
168
169 HDEBUG(D_auth) debug_printf("Cyrus SASL driver %s: %s initialised\n", ablock->name, ablock->public_name);
170
171 /* make sure that if we get here then we're allowed to advertise. */
172 ablock->server = TRUE;
173
174 sasl_dispose(&conn);
175 sasl_done();
176 }
177
178 /*************************************************
179 *             Server entry point                 *
180 *************************************************/
181
182 /* For interface, see auths/README */
183
184 /* note, we don't care too much about memory allocation in this, because this is entirely
185  * within a shortlived child
186  */
187
188 int
189 auth_cyrus_sasl_server(auth_instance *ablock, uschar *data)
190 {
191 auth_cyrus_sasl_options_block *ob =
192   (auth_cyrus_sasl_options_block *)(ablock->options_block);
193 uschar *output, *out2, *input, *clear, *hname;
194 uschar *debug = NULL;   /* Stops compiler complaining */
195 sasl_callback_t cbs[]={{SASL_CB_LIST_END, NULL, NULL}};
196 sasl_conn_t *conn;
197 int rc, firsttime=1, clen, negotiated_ssf;
198 unsigned int inlen, outlen;
199
200 input=data;
201 inlen=Ustrlen(data);
202
203 HDEBUG(D_auth) debug=string_copy(data);
204
205 hname=expand_string(ob->server_hostname);
206 if(hname == NULL)
207   {
208   auth_defer_msg = expand_string_message;
209   return DEFER;
210   }
211
212 if(inlen)
213   {
214   clen=auth_b64decode(input, &clear);
215   if(clen < 0)
216     {
217     return BAD64;
218     }
219   input=clear;
220   inlen=clen;
221   }
222
223 rc=sasl_server_init(cbs, "exim");
224 if (rc != SASL_OK)
225   {
226   auth_defer_msg = US"couldn't initialise Cyrus SASL library";
227   return DEFER;
228   }
229
230 rc=sasl_server_new(CS ob->server_service, CS hname, CS ob->server_realm, NULL,
231   NULL, NULL, 0, &conn);
232
233 HDEBUG(D_auth)
234   debug_printf("Initialised Cyrus SASL server connection; service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
235       ob->server_service, hname, ob->server_realm);
236
237 if( rc != SASL_OK )
238   {
239   auth_defer_msg = US"couldn't initialise Cyrus SASL connection";
240   sasl_done();
241   return DEFER;
242   }
243
244 if (tls_cipher)
245   {
246   rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, &tls_bits);
247   if (rc != SASL_OK)
248     {
249     HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
250         tls_bits, sasl_errstring(rc, NULL, NULL));
251     auth_defer_msg = US"couldn't set Cyrus SASL EXTERNAL SSF";
252     sasl_done();
253     return DEFER;
254     }
255   else
256     HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_bits);
257   }
258 else
259   HDEBUG(D_auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n");
260
261 rc=SASL_CONTINUE;
262
263 while(rc==SASL_CONTINUE)
264   {
265   if(firsttime)
266     {
267     firsttime=0;
268     HDEBUG(D_auth) debug_printf("Calling sasl_server_start(%s,\"%s\")\n", ob->server_mech, debug);
269     rc=sasl_server_start(conn, CS ob->server_mech, inlen?CS input:NULL, inlen,
270            (const char **)(&output), &outlen);
271     }
272   else
273     {
274     /* make sure that we have a null-terminated string */
275     out2=store_get(outlen+1);
276     memcpy(out2,output,outlen);
277     out2[outlen]='\0';
278     if((rc=auth_get_data(&input, out2, outlen))!=OK)
279       {
280       /* we couldn't get the data, so free up the library before
281        * returning whatever error we get */
282       sasl_dispose(&conn);
283       sasl_done();
284       return rc;
285       }
286     inlen=Ustrlen(input);
287
288     HDEBUG(D_auth) debug=string_copy(input);
289     if(inlen)
290       {
291       clen=auth_b64decode(input, &clear);
292       if(clen < 0)
293        {
294         sasl_dispose(&conn);
295         sasl_done();
296        return BAD64;
297        }
298       input=clear;
299       inlen=clen;
300       }
301
302     HDEBUG(D_auth) debug_printf("Calling sasl_server_step(\"%s\")\n", debug);
303     rc=sasl_server_step(conn, CS input, inlen, (const char **)(&output), &outlen);
304     }
305   if(rc==SASL_BADPROT)
306     {
307     sasl_dispose(&conn);
308     sasl_done();
309     return UNEXPECTED;
310     }
311   else if( rc==SASL_FAIL     || rc==SASL_BUFOVER
312        || rc==SASL_BADMAC   || rc==SASL_BADAUTH
313        || rc==SASL_NOAUTHZ  || rc==SASL_ENCRYPT
314        || rc==SASL_EXPIRED  || rc==SASL_DISABLED
315        || rc==SASL_NOUSER   )
316     {
317     /* these are considered permanent failure codes */
318     HDEBUG(D_auth)
319       debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
320     log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
321        "Cyrus SASL permanent failure: %s", ablock->name, ob->server_mech,
322        sasl_errstring(rc, NULL, NULL));
323     sasl_dispose(&conn);
324     sasl_done();
325     return FAIL;
326     }
327   else if(rc==SASL_NOMECH)
328     {
329     /* this is a temporary failure, because the mechanism is not
330      * available for this user. If it wasn't available at all, we
331      * shouldn't have got here in the first place...
332      */
333     HDEBUG(D_auth)
334       debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
335     auth_defer_msg =
336         string_sprintf("Cyrus SASL: mechanism %s not available", ob->server_mech);
337     sasl_dispose(&conn);
338     sasl_done();
339     return DEFER;
340     }
341   else if(!(rc==SASL_OK || rc==SASL_CONTINUE))
342     {
343     /* Anything else is a temporary failure, and we'll let SASL print out
344      * the error string for us
345      */
346     HDEBUG(D_auth)
347       debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
348     auth_defer_msg =
349         string_sprintf("Cyrus SASL: %s", sasl_errstring(rc, NULL, NULL));
350     sasl_dispose(&conn);
351     sasl_done();
352     return DEFER;
353     }
354   else if(rc==SASL_OK)
355     {
356     /* Get the username and copy it into $auth1 and $1. The former is now the
357     preferred variable; the latter is the original variable. */
358     rc = sasl_getprop(conn, SASL_USERNAME, (const void **)(&out2));
359     if (rc != SASL_OK)
360       {
361       HDEBUG(D_auth)
362         debug_printf("Cyrus SASL library will not tell us the username: %s\n",
363             sasl_errstring(rc, NULL, NULL));
364       log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
365          "Cyrus SASL username fetch problem: %s", ablock->name, ob->server_mech,
366          sasl_errstring(rc, NULL, NULL));
367       sasl_dispose(&conn);
368       sasl_done();
369       return FAIL;
370       }
371
372     auth_vars[0] = expand_nstring[1] = string_copy(out2);
373     expand_nlength[1] = Ustrlen(expand_nstring[1]);
374     expand_nmax = 1;
375
376     HDEBUG(D_auth)
377       debug_printf("Cyrus SASL %s authentication succeeded for %s\n",
378           ob->server_mech, auth_vars[0]);
379
380     rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf));
381     if (rc != SASL_OK)
382       {
383       HDEBUG(D_auth)
384         debug_printf("Cyrus SASL library will not tell us the SSF: %s\n",
385             sasl_errstring(rc, NULL, NULL));
386       log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
387           "Cyrus SASL SSF value not available: %s", ablock->name, ob->server_mech,
388           sasl_errstring(rc, NULL, NULL));
389       sasl_dispose(&conn);
390       sasl_done();
391       return FAIL;
392       }
393     HDEBUG(D_auth)
394       debug_printf("Cyrus SASL %s negotiated SSF: %d\n", ob->server_mech, negotiated_ssf);
395     if (negotiated_ssf > 0)
396       {
397       HDEBUG(D_auth)
398         debug_printf("Exim does not implement SASL wrapping (needed for SSF %d).\n", negotiated_ssf);
399       log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
400           "Cyrus SASL SSF %d not supported by Exim", ablock->name, ob->server_mech, negotiated_ssf);
401       sasl_dispose(&conn);
402       sasl_done();
403       return FAIL;
404       }
405
406     /* close down the connection, freeing up library's memory */
407     sasl_dispose(&conn);
408     sasl_done();
409
410     /* Expand server_condition as an authorization check */
411     return auth_check_serv_cond(ablock);
412     }
413   }
414 /* NOTREACHED */
415 return 0;  /* Stop compiler complaints */
416 }
417
418 /*************************************************
419 *                Diagnostic API                  *
420 *************************************************/
421
422 void
423 auth_cyrus_sasl_version_report(FILE *f)
424 {
425   const char *implementation, *version;
426   sasl_version_info(&implementation, &version, NULL, NULL, NULL, NULL);
427   fprintf(f, "Library version: Cyrus SASL: Compile: %d.%d.%d\n"
428              "                             Runtime: %s [%s]\n",
429           SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP,
430           version, implementation);
431 }
432
433 /*************************************************
434 *              Client entry point                *
435 *************************************************/
436
437 /* For interface, see auths/README */
438
439 int
440 auth_cyrus_sasl_client(
441   auth_instance *ablock,                 /* authenticator block */
442   smtp_inblock *inblock,                 /* input connection */
443   smtp_outblock *outblock,               /* output connection */
444   int timeout,                           /* command timeout */
445   uschar *buffer,                          /* for reading response */
446   int buffsize)                          /* size of buffer */
447 {
448 /* We don't support clients (yet) in this implementation of cyrus_sasl */
449 return FAIL;
450 }
451
452 #endif  /* AUTH_CYRUS_SASL */
453
454 /* End of cyrus_sasl.c */