expanded comment, noting size types and API issue
[users/jgh/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 - 2012 */
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 char *realm_expanded;
103
104 sasl_conn_t *conn;
105 sasl_callback_t cbs[]={
106   {SASL_CB_GETOPT, NULL, NULL },
107   {SASL_CB_LIST_END, NULL, NULL}};
108
109 /* default the mechanism to our "public name" */
110 if(ob->server_mech == NULL)
111   ob->server_mech=string_copy(ablock->public_name);
112
113 expanded_hostname = expand_string(ob->server_hostname);
114 if (expanded_hostname == NULL)
115   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
116       "couldn't expand server_hostname [%s]: %s",
117       ablock->name, ob->server_hostname, expand_string_message);
118
119 realm_expanded=NULL;
120 if (ob->server_realm != NULL) {
121   realm_expanded = CS expand_string(ob->server_realm);
122   if (realm_expanded == NULL)
123     log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
124         "couldn't expand server_realm [%s]: %s",
125         ablock->name, ob->server_realm, expand_string_message);
126 }
127
128 /* we're going to initialise the library to check that there is an
129  * authenticator of type whatever mechanism we're using
130  */
131
132 cbs[0].proc = (int(*)(void)) &mysasl_config;
133 cbs[0].context = ob->server_mech;
134
135 rc=sasl_server_init(cbs, "exim");
136
137 if( rc != SASL_OK )
138   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
139       "couldn't initialise Cyrus SASL library.", ablock->name);
140
141 rc=sasl_server_new(CS ob->server_service, CS expanded_hostname,
142                    realm_expanded, NULL, NULL, NULL, 0, &conn);
143 if( rc != SASL_OK )
144   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
145       "couldn't initialise Cyrus SASL server connection.", ablock->name);
146
147 rc=sasl_listmech(conn, NULL, "", ":", "", (const char **)(&list), &len, &i);
148 if( rc != SASL_OK )
149   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
150       "couldn't get Cyrus SASL mechanism list.", ablock->name);
151
152 i=':';
153 listptr=list;
154
155 HDEBUG(D_auth) {
156   debug_printf("Initialised Cyrus SASL service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
157       ob->server_service, expanded_hostname, realm_expanded);
158   debug_printf("Cyrus SASL knows mechanisms: %s\n", list);
159 }
160
161 /* the store_get / store_reset mechanism is hierarchical
162  * the hierarchy is stored for us behind our back. This point
163  * creates a hierarchy point for this function.
164  */
165 rs_point=store_get(0);
166
167 /* loop until either we get to the end of the list, or we match the
168  * public name of this authenticator
169  */
170 while( ( buffer = string_nextinlist(&listptr, &i, NULL, 0) ) &&
171        strcmpic(buffer,ob->server_mech) );
172
173 if(!buffer)
174   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
175       "Cyrus SASL doesn't know about mechanism %s.", ablock->name, ob->server_mech);
176
177 store_reset(rs_point);
178
179 HDEBUG(D_auth) debug_printf("Cyrus SASL driver %s: %s initialised\n", ablock->name, ablock->public_name);
180
181 /* make sure that if we get here then we're allowed to advertise. */
182 ablock->server = TRUE;
183
184 sasl_dispose(&conn);
185 sasl_done();
186 }
187
188 /*************************************************
189 *             Server entry point                 *
190 *************************************************/
191
192 /* For interface, see auths/README */
193
194 /* note, we don't care too much about memory allocation in this, because this is entirely
195  * within a shortlived child
196  */
197
198 int
199 auth_cyrus_sasl_server(auth_instance *ablock, uschar *data)
200 {
201 auth_cyrus_sasl_options_block *ob =
202   (auth_cyrus_sasl_options_block *)(ablock->options_block);
203 uschar *output, *out2, *input, *clear, *hname;
204 uschar *debug = NULL;   /* Stops compiler complaining */
205 sasl_callback_t cbs[]={{SASL_CB_LIST_END, NULL, NULL}};
206 sasl_conn_t *conn;
207 char *realm_expanded;
208 int rc, firsttime=1, clen, negotiated_ssf;
209 unsigned int inlen, outlen;
210
211 input=data;
212 inlen=Ustrlen(data);
213
214 HDEBUG(D_auth) debug=string_copy(data);
215
216 hname=expand_string(ob->server_hostname);
217 realm_expanded=NULL;
218 if (hname && ob->server_realm)
219   realm_expanded= CS expand_string(ob->server_realm);
220 if((hname == NULL) ||
221    ((realm_expanded == NULL) && (ob->server_realm != NULL)))
222   {
223   auth_defer_msg = expand_string_message;
224   return DEFER;
225   }
226
227 if(inlen)
228   {
229   clen=auth_b64decode(input, &clear);
230   if(clen < 0)
231     {
232     return BAD64;
233     }
234   input=clear;
235   inlen=clen;
236   }
237
238 rc=sasl_server_init(cbs, "exim");
239 if (rc != SASL_OK)
240   {
241   auth_defer_msg = US"couldn't initialise Cyrus SASL library";
242   return DEFER;
243   }
244
245 rc=sasl_server_new(CS ob->server_service, CS hname, realm_expanded, NULL,
246   NULL, NULL, 0, &conn);
247
248 HDEBUG(D_auth)
249   debug_printf("Initialised Cyrus SASL server connection; service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
250       ob->server_service, hname, realm_expanded);
251
252 if( rc != SASL_OK )
253   {
254   auth_defer_msg = US"couldn't initialise Cyrus SASL connection";
255   sasl_done();
256   return DEFER;
257   }
258
259 if (tls_cipher)
260   {
261   rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, &tls_bits);
262   if (rc != SASL_OK)
263     {
264     HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
265         tls_bits, sasl_errstring(rc, NULL, NULL));
266     auth_defer_msg = US"couldn't set Cyrus SASL EXTERNAL SSF";
267     sasl_done();
268     return DEFER;
269     }
270   else
271     HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_bits);
272   }
273 else
274   HDEBUG(D_auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n");
275
276 rc=SASL_CONTINUE;
277
278 while(rc==SASL_CONTINUE)
279   {
280   if(firsttime)
281     {
282     firsttime=0;
283     HDEBUG(D_auth) debug_printf("Calling sasl_server_start(%s,\"%s\")\n", ob->server_mech, debug);
284     rc=sasl_server_start(conn, CS ob->server_mech, inlen?CS input:NULL, inlen,
285            (const char **)(&output), &outlen);
286     }
287   else
288     {
289     /* make sure that we have a null-terminated string */
290     out2=store_get(outlen+1);
291     memcpy(out2,output,outlen);
292     out2[outlen]='\0';
293     if((rc=auth_get_data(&input, out2, outlen))!=OK)
294       {
295       /* we couldn't get the data, so free up the library before
296        * returning whatever error we get */
297       sasl_dispose(&conn);
298       sasl_done();
299       return rc;
300       }
301     inlen=Ustrlen(input);
302
303     HDEBUG(D_auth) debug=string_copy(input);
304     if(inlen)
305       {
306       clen=auth_b64decode(input, &clear);
307       if(clen < 0)
308        {
309         sasl_dispose(&conn);
310         sasl_done();
311        return BAD64;
312        }
313       input=clear;
314       inlen=clen;
315       }
316
317     HDEBUG(D_auth) debug_printf("Calling sasl_server_step(\"%s\")\n", debug);
318     rc=sasl_server_step(conn, CS input, inlen, (const char **)(&output), &outlen);
319     }
320   if(rc==SASL_BADPROT)
321     {
322     sasl_dispose(&conn);
323     sasl_done();
324     return UNEXPECTED;
325     }
326   else if( rc==SASL_FAIL     || rc==SASL_BUFOVER
327        || rc==SASL_BADMAC   || rc==SASL_BADAUTH
328        || rc==SASL_NOAUTHZ  || rc==SASL_ENCRYPT
329        || rc==SASL_EXPIRED  || rc==SASL_DISABLED
330        || rc==SASL_NOUSER   )
331     {
332     /* these are considered permanent failure codes */
333     HDEBUG(D_auth)
334       debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
335     log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
336        "Cyrus SASL permanent failure: %s", ablock->name, ob->server_mech,
337        sasl_errstring(rc, NULL, NULL));
338     sasl_dispose(&conn);
339     sasl_done();
340     return FAIL;
341     }
342   else if(rc==SASL_NOMECH)
343     {
344     /* this is a temporary failure, because the mechanism is not
345      * available for this user. If it wasn't available at all, we
346      * shouldn't have got here in the first place...
347      */
348     HDEBUG(D_auth)
349       debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
350     auth_defer_msg =
351         string_sprintf("Cyrus SASL: mechanism %s not available", ob->server_mech);
352     sasl_dispose(&conn);
353     sasl_done();
354     return DEFER;
355     }
356   else if(!(rc==SASL_OK || rc==SASL_CONTINUE))
357     {
358     /* Anything else is a temporary failure, and we'll let SASL print out
359      * the error string for us
360      */
361     HDEBUG(D_auth)
362       debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
363     auth_defer_msg =
364         string_sprintf("Cyrus SASL: %s", sasl_errstring(rc, NULL, NULL));
365     sasl_dispose(&conn);
366     sasl_done();
367     return DEFER;
368     }
369   else if(rc==SASL_OK)
370     {
371     /* Get the username and copy it into $auth1 and $1. The former is now the
372     preferred variable; the latter is the original variable. */
373     rc = sasl_getprop(conn, SASL_USERNAME, (const void **)(&out2));
374     if (rc != SASL_OK)
375       {
376       HDEBUG(D_auth)
377         debug_printf("Cyrus SASL library will not tell us the username: %s\n",
378             sasl_errstring(rc, NULL, NULL));
379       log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
380          "Cyrus SASL username fetch problem: %s", ablock->name, ob->server_mech,
381          sasl_errstring(rc, NULL, NULL));
382       sasl_dispose(&conn);
383       sasl_done();
384       return FAIL;
385       }
386
387     auth_vars[0] = expand_nstring[1] = string_copy(out2);
388     expand_nlength[1] = Ustrlen(expand_nstring[1]);
389     expand_nmax = 1;
390
391     HDEBUG(D_auth)
392       debug_printf("Cyrus SASL %s authentication succeeded for %s\n",
393           ob->server_mech, auth_vars[0]);
394
395     rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf));
396     if (rc != SASL_OK)
397       {
398       HDEBUG(D_auth)
399         debug_printf("Cyrus SASL library will not tell us the SSF: %s\n",
400             sasl_errstring(rc, NULL, NULL));
401       log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
402           "Cyrus SASL SSF value not available: %s", ablock->name, ob->server_mech,
403           sasl_errstring(rc, NULL, NULL));
404       sasl_dispose(&conn);
405       sasl_done();
406       return FAIL;
407       }
408     HDEBUG(D_auth)
409       debug_printf("Cyrus SASL %s negotiated SSF: %d\n", ob->server_mech, negotiated_ssf);
410     if (negotiated_ssf > 0)
411       {
412       HDEBUG(D_auth)
413         debug_printf("Exim does not implement SASL wrapping (needed for SSF %d).\n", negotiated_ssf);
414       log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
415           "Cyrus SASL SSF %d not supported by Exim", ablock->name, ob->server_mech, negotiated_ssf);
416       sasl_dispose(&conn);
417       sasl_done();
418       return FAIL;
419       }
420
421     /* close down the connection, freeing up library's memory */
422     sasl_dispose(&conn);
423     sasl_done();
424
425     /* Expand server_condition as an authorization check */
426     return auth_check_serv_cond(ablock);
427     }
428   }
429 /* NOTREACHED */
430 return 0;  /* Stop compiler complaints */
431 }
432
433 /*************************************************
434 *                Diagnostic API                  *
435 *************************************************/
436
437 void
438 auth_cyrus_sasl_version_report(FILE *f)
439 {
440   const char *implementation, *version;
441   sasl_version_info(&implementation, &version, NULL, NULL, NULL, NULL);
442   fprintf(f, "Library version: Cyrus SASL: Compile: %d.%d.%d\n"
443              "                             Runtime: %s [%s]\n",
444           SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP,
445           version, implementation);
446 }
447
448 /*************************************************
449 *              Client entry point                *
450 *************************************************/
451
452 /* For interface, see auths/README */
453
454 int
455 auth_cyrus_sasl_client(
456   auth_instance *ablock,                 /* authenticator block */
457   smtp_inblock *inblock,                 /* input connection */
458   smtp_outblock *outblock,               /* output connection */
459   int timeout,                           /* command timeout */
460   uschar *buffer,                          /* for reading response */
461   int buffsize)                          /* size of buffer */
462 {
463 /* We don't support clients (yet) in this implementation of cyrus_sasl */
464 return FAIL;
465 }
466
467 #endif  /* AUTH_CYRUS_SASL */
468
469 /* End of cyrus_sasl.c */