tidying
[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);
29 static void dummy2(int x) { dummy(x-1); }
30 static void dummy(int x) { dummy2(x-1); }
31 #else
32
33
34 #include <sasl/sasl.h>
35 #include "cyrus_sasl.h"
36
37 /* Options specific to the cyrus_sasl authentication mechanism. */
38
39 optionlist auth_cyrus_sasl_options[] = {
40   { "server_hostname",      opt_stringptr,
41       (void *)(offsetof(auth_cyrus_sasl_options_block, server_hostname)) },
42   { "server_mech",          opt_stringptr,
43       (void *)(offsetof(auth_cyrus_sasl_options_block, server_mech)) },
44   { "server_realm",         opt_stringptr,
45       (void *)(offsetof(auth_cyrus_sasl_options_block, server_realm)) },
46   { "server_service",       opt_stringptr,
47       (void *)(offsetof(auth_cyrus_sasl_options_block, server_service)) }
48 };
49
50 /* Size of the options list. An extern variable has to be used so that its
51 address can appear in the tables drtables.c. */
52
53 int auth_cyrus_sasl_options_count =
54   sizeof(auth_cyrus_sasl_options)/sizeof(optionlist);
55
56 /* Default private options block for the cyrus_sasl authentication method. */
57
58 auth_cyrus_sasl_options_block auth_cyrus_sasl_option_defaults = {
59   US"smtp",         /* server_service */
60   US"$primary_hostname", /* server_hostname */
61   NULL,             /* server_realm */
62   NULL              /* server_mech */
63 };
64
65
66 /*************************************************
67 *          Initialization entry point            *
68 *************************************************/
69
70 /* Called for each instance, after its options have been read, to
71 enable consistency checks to be done, or anything else that needs
72 to be set up. */
73
74
75 /* Auxiliary function, passed in data to sasl_server_init(). */
76
77 static int
78 mysasl_config(void *context,
79               const char *plugin_name,
80               const char *option,
81               const char **result,
82               unsigned int *len)
83 {
84 if (context && !strcmp(option, "mech_list"))
85   {
86   *result = context;
87   if (len != NULL) *len = strlen(*result);
88   return SASL_OK;
89   }
90 return SASL_FAIL;
91 }
92
93 /* Here's the real function */
94
95 void
96 auth_cyrus_sasl_init(auth_instance *ablock)
97 {
98 auth_cyrus_sasl_options_block *ob =
99   (auth_cyrus_sasl_options_block *)(ablock->options_block);
100 const uschar *list, *listptr, *buffer;
101 int rc, i;
102 unsigned int len;
103 uschar *rs_point, *expanded_hostname;
104 char *realm_expanded;
105
106 sasl_conn_t *conn;
107 sasl_callback_t cbs[]={
108   {SASL_CB_GETOPT, NULL, NULL },
109   {SASL_CB_LIST_END, NULL, NULL}};
110
111 /* default the mechanism to our "public name" */
112 if(ob->server_mech == NULL)
113   ob->server_mech=string_copy(ablock->public_name);
114
115 expanded_hostname = expand_string(ob->server_hostname);
116 if (expanded_hostname == NULL)
117   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
118       "couldn't expand server_hostname [%s]: %s",
119       ablock->name, ob->server_hostname, expand_string_message);
120
121 realm_expanded=NULL;
122 if (ob->server_realm != NULL) {
123   realm_expanded = CS expand_string(ob->server_realm);
124   if (realm_expanded == NULL)
125     log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
126         "couldn't expand server_realm [%s]: %s",
127         ablock->name, ob->server_realm, expand_string_message);
128 }
129
130 /* we're going to initialise the library to check that there is an
131  * authenticator of type whatever mechanism we're using
132  */
133
134 cbs[0].proc = (int(*)(void)) &mysasl_config;
135 cbs[0].context = ob->server_mech;
136
137 rc=sasl_server_init(cbs, "exim");
138
139 if( rc != SASL_OK )
140   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
141       "couldn't initialise Cyrus SASL library.", ablock->name);
142
143 rc=sasl_server_new(CS ob->server_service, CS expanded_hostname,
144                    realm_expanded, NULL, NULL, NULL, 0, &conn);
145 if( rc != SASL_OK )
146   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
147       "couldn't initialise Cyrus SASL server connection.", ablock->name);
148
149 rc=sasl_listmech(conn, NULL, "", ":", "", (const char **)&list, &len, &i);
150 if( rc != SASL_OK )
151   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
152       "couldn't get Cyrus SASL mechanism list.", ablock->name);
153
154 i=':';
155 listptr=list;
156
157 HDEBUG(D_auth) {
158   debug_printf("Initialised Cyrus SASL service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
159       ob->server_service, expanded_hostname, realm_expanded);
160   debug_printf("Cyrus SASL knows mechanisms: %s\n", list);
161 }
162
163 /* the store_get / store_reset mechanism is hierarchical
164  * the hierarchy is stored for us behind our back. This point
165  * creates a hierarchy point for this function.
166  */
167 rs_point=store_get(0);
168
169 /* loop until either we get to the end of the list, or we match the
170  * public name of this authenticator
171  */
172 while( ( buffer = string_nextinlist(&listptr, &i, NULL, 0) ) &&
173        strcmpic(buffer,ob->server_mech) );
174
175 if(!buffer)
176   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
177       "Cyrus SASL doesn't know about mechanism %s.", ablock->name, ob->server_mech);
178
179 store_reset(rs_point);
180
181 HDEBUG(D_auth) debug_printf("Cyrus SASL driver %s: %s initialised\n", ablock->name, ablock->public_name);
182
183 /* make sure that if we get here then we're allowed to advertise. */
184 ablock->server = TRUE;
185
186 sasl_dispose(&conn);
187 sasl_done();
188 }
189
190 /*************************************************
191 *             Server entry point                 *
192 *************************************************/
193
194 /* For interface, see auths/README */
195
196 /* note, we don't care too much about memory allocation in this, because this is entirely
197  * within a shortlived child
198  */
199
200 int
201 auth_cyrus_sasl_server(auth_instance *ablock, uschar *data)
202 {
203 auth_cyrus_sasl_options_block *ob =
204   (auth_cyrus_sasl_options_block *)(ablock->options_block);
205 uschar *output, *out2, *input, *clear, *hname;
206 uschar *debug = NULL;   /* Stops compiler complaining */
207 sasl_callback_t cbs[]={{SASL_CB_LIST_END, NULL, NULL}};
208 sasl_conn_t *conn;
209 char *realm_expanded;
210 int rc, i, firsttime=1, clen, *negotiated_ssf_ptr=NULL, negotiated_ssf;
211 unsigned int inlen, outlen;
212
213 input=data;
214 inlen=Ustrlen(data);
215
216 HDEBUG(D_auth) debug=string_copy(data);
217
218 hname=expand_string(ob->server_hostname);
219 realm_expanded=NULL;
220 if (hname && ob->server_realm)
221   realm_expanded= CS expand_string(ob->server_realm);
222 if((hname == NULL) ||
223    ((realm_expanded == NULL) && (ob->server_realm != NULL)))
224   {
225   auth_defer_msg = expand_string_message;
226   return DEFER;
227   }
228
229 if(inlen)
230   {
231   clen=auth_b64decode(input, &clear);
232   if(clen < 0)
233     {
234     return BAD64;
235     }
236   input=clear;
237   inlen=clen;
238   }
239
240 rc=sasl_server_init(cbs, "exim");
241 if (rc != SASL_OK)
242   {
243   auth_defer_msg = US"couldn't initialise Cyrus SASL library";
244   return DEFER;
245   }
246
247 rc=sasl_server_new(CS ob->server_service, CS hname, realm_expanded, NULL,
248   NULL, NULL, 0, &conn);
249
250 HDEBUG(D_auth)
251   debug_printf("Initialised Cyrus SASL server connection; service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
252       ob->server_service, hname, realm_expanded);
253
254 if( rc != SASL_OK )
255   {
256   auth_defer_msg = US"couldn't initialise Cyrus SASL connection";
257   sasl_done();
258   return DEFER;
259   }
260
261 if (tls_in.cipher)
262   {
263   rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_in.bits);
264   if (rc != SASL_OK)
265     {
266     HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
267         tls_in.bits, sasl_errstring(rc, NULL, NULL));
268     auth_defer_msg = US"couldn't set Cyrus SASL EXTERNAL SSF";
269     sasl_done();
270     return DEFER;
271     }
272   else
273     HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_in.bits);
274   }
275 else
276   HDEBUG(D_auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n");
277
278 /* So sasl_setprop() documents non-shorted IPv6 addresses which is incredibly
279 annoying; looking at cyrus-imapd-2.3.x source, the IP address is constructed
280 with their iptostring() function, which just wraps
281 getnameinfo(..., NI_NUMERICHOST|NI_NUMERICSERV), which is equivalent to the
282 inet_ntop which we wrap in our host_ntoa() function.
283
284 So the docs are too strict and we shouldn't worry about :: contractions. */
285
286 /* Set properties for remote and local host-ip;port */
287 for (i=0; i < 2; ++i)
288   {
289   struct sockaddr_storage ss;
290   int (*query)(int, struct sockaddr *, socklen_t *);
291   int propnum, port;
292   const uschar *label;
293   uschar *address, *address_port;
294   const char *s_err;
295   socklen_t sslen;
296
297   if (i)
298     {
299     query = &getpeername;
300     propnum = SASL_IPREMOTEPORT;
301     label = CUS"peer";
302     }
303   else
304     {
305     query = &getsockname;
306     propnum = SASL_IPLOCALPORT;
307     label = CUS"local";
308     }
309
310   sslen = sizeof(ss);
311   rc = query(fileno(smtp_in), (struct sockaddr *) &ss, &sslen);
312   if (rc < 0)
313     {
314     HDEBUG(D_auth)
315       debug_printf("Failed to get %s address information: %s\n",
316           label, strerror(errno));
317     break;
318     }
319
320   address = host_ntoa(-1, &ss, NULL, &port);
321   address_port = string_sprintf("%s;%d", address, port);
322
323   rc = sasl_setprop(conn, propnum, address_port);
324   if (rc != SASL_OK)
325     {
326     s_err = sasl_errdetail(conn);
327     HDEBUG(D_auth)
328       debug_printf("Failed to set %s SASL property: [%d] %s\n",
329           label, rc, s_err ? s_err : "<unknown reason>");
330     break;
331     }
332   HDEBUG(D_auth) debug_printf("Cyrus SASL set %s hostport to: %s\n",
333       label, address_port);
334   }
335
336 rc=SASL_CONTINUE;
337
338 while(rc==SASL_CONTINUE)
339   {
340   if(firsttime)
341     {
342     firsttime=0;
343     HDEBUG(D_auth) debug_printf("Calling sasl_server_start(%s,\"%s\")\n", ob->server_mech, debug);
344     rc=sasl_server_start(conn, CS ob->server_mech, inlen?CS input:NULL, inlen,
345            (const char **)(&output), &outlen);
346     }
347   else
348     {
349     /* make sure that we have a null-terminated string */
350     out2=store_get(outlen+1);
351     memcpy(out2,output,outlen);
352     out2[outlen]='\0';
353     if((rc=auth_get_data(&input, out2, outlen))!=OK)
354       {
355       /* we couldn't get the data, so free up the library before
356        * returning whatever error we get */
357       sasl_dispose(&conn);
358       sasl_done();
359       return rc;
360       }
361     inlen=Ustrlen(input);
362
363     HDEBUG(D_auth) debug=string_copy(input);
364     if(inlen)
365       {
366       clen=auth_b64decode(input, &clear);
367       if(clen < 0)
368        {
369         sasl_dispose(&conn);
370         sasl_done();
371        return BAD64;
372        }
373       input=clear;
374       inlen=clen;
375       }
376
377     HDEBUG(D_auth) debug_printf("Calling sasl_server_step(\"%s\")\n", debug);
378     rc=sasl_server_step(conn, CS input, inlen, (const char **)(&output), &outlen);
379     }
380   if(rc==SASL_BADPROT)
381     {
382     sasl_dispose(&conn);
383     sasl_done();
384     return UNEXPECTED;
385     }
386   else if( rc==SASL_FAIL     || rc==SASL_BUFOVER
387        || rc==SASL_BADMAC   || rc==SASL_BADAUTH
388        || rc==SASL_NOAUTHZ  || rc==SASL_ENCRYPT
389        || rc==SASL_EXPIRED  || rc==SASL_DISABLED
390        || rc==SASL_NOUSER   )
391     {
392     /* these are considered permanent failure codes */
393     HDEBUG(D_auth)
394       debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
395     log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
396        "Cyrus SASL permanent failure: %s", ablock->name, ob->server_mech,
397        sasl_errstring(rc, NULL, NULL));
398     sasl_dispose(&conn);
399     sasl_done();
400     return FAIL;
401     }
402   else if(rc==SASL_NOMECH)
403     {
404     /* this is a temporary failure, because the mechanism is not
405      * available for this user. If it wasn't available at all, we
406      * shouldn't have got here in the first place...
407      */
408     HDEBUG(D_auth)
409       debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
410     auth_defer_msg =
411         string_sprintf("Cyrus SASL: mechanism %s not available", ob->server_mech);
412     sasl_dispose(&conn);
413     sasl_done();
414     return DEFER;
415     }
416   else if(!(rc==SASL_OK || rc==SASL_CONTINUE))
417     {
418     /* Anything else is a temporary failure, and we'll let SASL print out
419      * the error string for us
420      */
421     HDEBUG(D_auth)
422       debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
423     auth_defer_msg =
424         string_sprintf("Cyrus SASL: %s", sasl_errstring(rc, NULL, NULL));
425     sasl_dispose(&conn);
426     sasl_done();
427     return DEFER;
428     }
429   else if(rc==SASL_OK)
430     {
431     /* Get the username and copy it into $auth1 and $1. The former is now the
432     preferred variable; the latter is the original variable. */
433     rc = sasl_getprop(conn, SASL_USERNAME, (const void **)(&out2));
434     if (rc != SASL_OK)
435       {
436       HDEBUG(D_auth)
437         debug_printf("Cyrus SASL library will not tell us the username: %s\n",
438             sasl_errstring(rc, NULL, NULL));
439       log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
440          "Cyrus SASL username fetch problem: %s", ablock->name, ob->server_mech,
441          sasl_errstring(rc, NULL, NULL));
442       sasl_dispose(&conn);
443       sasl_done();
444       return FAIL;
445       }
446
447     auth_vars[0] = expand_nstring[1] = string_copy(out2);
448     expand_nlength[1] = Ustrlen(expand_nstring[1]);
449     expand_nmax = 1;
450
451     HDEBUG(D_auth)
452       debug_printf("Cyrus SASL %s authentication succeeded for %s\n",
453           ob->server_mech, auth_vars[0]);
454
455     rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf_ptr));
456     if (rc != SASL_OK)
457       {
458       HDEBUG(D_auth)
459         debug_printf("Cyrus SASL library will not tell us the SSF: %s\n",
460             sasl_errstring(rc, NULL, NULL));
461       log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
462           "Cyrus SASL SSF value not available: %s", ablock->name, ob->server_mech,
463           sasl_errstring(rc, NULL, NULL));
464       sasl_dispose(&conn);
465       sasl_done();
466       return FAIL;
467       }
468     negotiated_ssf = *negotiated_ssf_ptr;
469     HDEBUG(D_auth)
470       debug_printf("Cyrus SASL %s negotiated SSF: %d\n", ob->server_mech, negotiated_ssf);
471     if (negotiated_ssf > 0)
472       {
473       HDEBUG(D_auth)
474         debug_printf("Exim does not implement SASL wrapping (needed for SSF %d).\n", negotiated_ssf);
475       log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
476           "Cyrus SASL SSF %d not supported by Exim", ablock->name, ob->server_mech, negotiated_ssf);
477       sasl_dispose(&conn);
478       sasl_done();
479       return FAIL;
480       }
481
482     /* close down the connection, freeing up library's memory */
483     sasl_dispose(&conn);
484     sasl_done();
485
486     /* Expand server_condition as an authorization check */
487     return auth_check_serv_cond(ablock);
488     }
489   }
490 /* NOTREACHED */
491 return 0;  /* Stop compiler complaints */
492 }
493
494 /*************************************************
495 *                Diagnostic API                  *
496 *************************************************/
497
498 void
499 auth_cyrus_sasl_version_report(FILE *f)
500 {
501   const char *implementation, *version;
502   sasl_version_info(&implementation, &version, NULL, NULL, NULL, NULL);
503   fprintf(f, "Library version: Cyrus SASL: Compile: %d.%d.%d\n"
504              "                             Runtime: %s [%s]\n",
505           SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP,
506           version, implementation);
507 }
508
509 /*************************************************
510 *              Client entry point                *
511 *************************************************/
512
513 /* For interface, see auths/README */
514
515 int
516 auth_cyrus_sasl_client(
517   auth_instance *ablock,                 /* authenticator block */
518   smtp_inblock *inblock,                 /* input connection */
519   smtp_outblock *outblock,               /* output connection */
520   int timeout,                           /* command timeout */
521   uschar *buffer,                          /* for reading response */
522   int buffsize)                          /* size of buffer */
523 {
524 /* We don't support clients (yet) in this implementation of cyrus_sasl */
525 return FAIL;
526 }
527
528 #endif  /* AUTH_CYRUS_SASL */
529
530 /* End of cyrus_sasl.c */