Auths: fix cyrus-sasl driver for gssapi use. Bug 2524
[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 - 2018 */
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       OPT_OFF(auth_cyrus_sasl_options_block, server_hostname) },
42   { "server_mech",          opt_stringptr,
43       OPT_OFF(auth_cyrus_sasl_options_block, server_mech) },
44   { "server_realm",         opt_stringptr,
45       OPT_OFF(auth_cyrus_sasl_options_block, server_realm) },
46   { "server_service",       opt_stringptr,
47       OPT_OFF(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 #ifdef MACRO_PREDEF
67
68 /* Dummy values */
69 void auth_cyrus_sasl_init(auth_instance *ablock) {}
70 int auth_cyrus_sasl_server(auth_instance *ablock, uschar *data) {return 0;}
71 int auth_cyrus_sasl_client(auth_instance *ablock, void * sx,
72   int timeout, uschar *buffer, int buffsize) {return 0;}
73 void auth_cyrus_sasl_version_report(FILE *f) {}
74
75 #else   /*!MACRO_PREDEF*/
76
77
78
79
80 /*************************************************
81 *          Initialization entry point            *
82 *************************************************/
83
84 /* Called for each instance, after its options have been read, to
85 enable consistency checks to be done, or anything else that needs
86 to be set up. */
87
88
89 /* Auxiliary function, passed in data to sasl_server_init(). */
90
91 static int
92 mysasl_config(void *context, const char *plugin_name, const char *option,
93       const char **result, unsigned int *len)
94 {
95 if (context && !strcmp(option, "mech_list"))
96   {
97   *result = context;
98   if (len) *len = strlen(*result);
99   return SASL_OK;
100   }
101 return SASL_FAIL;
102 }
103
104 /* Here's the real function */
105
106 void
107 auth_cyrus_sasl_init(auth_instance *ablock)
108 {
109 auth_cyrus_sasl_options_block *ob =
110   (auth_cyrus_sasl_options_block *)(ablock->options_block);
111 const uschar *list, *listptr, *buffer;
112 int rc, i;
113 unsigned int len;
114 rmark rs_point;
115 uschar *expanded_hostname;
116 char *realm_expanded;
117
118 sasl_conn_t *conn;
119 sasl_callback_t cbs[] = {
120   {SASL_CB_GETOPT, NULL, NULL },
121   {SASL_CB_LIST_END, NULL, NULL}};
122
123 /* default the mechanism to our "public name" */
124
125 if (!ob->server_mech) ob->server_mech = string_copy(ablock->public_name);
126
127 if (!(expanded_hostname = expand_string(ob->server_hostname)))
128   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
129       "couldn't expand server_hostname [%s]: %s",
130       ablock->name, ob->server_hostname, expand_string_message);
131
132 realm_expanded = NULL;
133 if (  ob->server_realm
134    && !(realm_expanded = CS expand_string(ob->server_realm)))
135   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
136       "couldn't expand server_realm [%s]: %s",
137       ablock->name, ob->server_realm, expand_string_message);
138
139 /* we're going to initialise the library to check that there is an
140 authenticator of type whatever mechanism we're using */
141
142 cbs[0].proc = (int(*)(void)) &mysasl_config;
143 cbs[0].context = ob->server_mech;
144
145 if ((rc = sasl_server_init(cbs, "exim")) != SASL_OK)
146   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
147       "couldn't initialise Cyrus SASL library.", ablock->name);
148
149 if ((rc = sasl_server_new(CS ob->server_service, CS expanded_hostname,
150                    realm_expanded, NULL, NULL, NULL, 0, &conn)) != SASL_OK)
151   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
152       "couldn't initialise Cyrus SASL server connection.", ablock->name);
153
154 if ((rc = sasl_listmech(conn, NULL, "", ":", "", CCSS &list, &len, &i)) != SASL_OK)
155   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
156       "couldn't get Cyrus SASL mechanism list.", ablock->name);
157
158 i = ':';
159 listptr = list;
160
161 HDEBUG(D_auth)
162   {
163   debug_printf("Initialised Cyrus SASL service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
164       ob->server_service, expanded_hostname, realm_expanded);
165   debug_printf("Cyrus SASL knows mechanisms: %s\n", list);
166   }
167
168 /* the store_get / store_reset mechanism is hierarchical
169  the hierarchy is stored for us behind our back. This point
170  creates a hierarchy point for this function.  */
171
172 rs_point = store_mark();
173
174 /* loop until either we get to the end of the list, or we match the
175 public name of this authenticator */
176
177 while (  (buffer = string_nextinlist(&listptr, &i, NULL, 0))
178       && strcmpic(buffer,ob->server_mech) );
179
180 if (!buffer)
181   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
182       "Cyrus SASL doesn't know about mechanism %s.", ablock->name, ob->server_mech);
183
184 store_reset(rs_point);
185
186 HDEBUG(D_auth) debug_printf("Cyrus SASL driver %s: %s initialised\n", ablock->name, ablock->public_name);
187
188 /* make sure that if we get here then we're allowed to advertise. */
189 ablock->server = TRUE;
190
191 sasl_dispose(&conn);
192 sasl_done();
193 }
194
195 /*************************************************
196 *             Server entry point                 *
197 *************************************************/
198
199 /* For interface, see auths/README */
200
201 /* note, we don't care too much about memory allocation in this, because this is entirely
202 within a shortlived child */
203
204 int
205 auth_cyrus_sasl_server(auth_instance *ablock, uschar *data)
206 {
207 auth_cyrus_sasl_options_block *ob =
208   (auth_cyrus_sasl_options_block *)(ablock->options_block);
209 uschar *output, *out2, *input, *clear, *hname;
210 uschar *debug = NULL;   /* Stops compiler complaining */
211 sasl_callback_t cbs[] = {{SASL_CB_LIST_END, NULL, NULL}};
212 sasl_conn_t *conn;
213 char * realm_expanded = NULL;
214 int rc, firsttime = 1, clen, *negotiated_ssf_ptr = NULL, negotiated_ssf;
215 unsigned int inlen, outlen;
216
217 input = data;
218 inlen = Ustrlen(data);
219
220 HDEBUG(D_auth) debug = string_copy(data);
221
222 hname = expand_string(ob->server_hostname);
223 if (hname && ob->server_realm)
224   realm_expanded = CS expand_string(ob->server_realm);
225 if (!hname  ||  !realm_expanded  && ob->server_realm)
226   {
227   auth_defer_msg = expand_string_message;
228   return DEFER;
229   }
230
231 if (inlen)
232   {
233   if ((clen = b64decode(input, &clear)) < 0)
234     return BAD64;
235   input = clear;
236   inlen = clen;
237   }
238
239 if ((rc = sasl_server_init(cbs, "exim")) != 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_in.cipher)
260   {
261   if ((rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_in.bits)) != SASL_OK)
262     {
263     HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
264         tls_in.bits, sasl_errstring(rc, NULL, NULL));
265     auth_defer_msg = US"couldn't set Cyrus SASL EXTERNAL SSF";
266     sasl_done();
267     return DEFER;
268     }
269   else
270     HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_in.bits);
271
272   /*XXX Set channel-binding here with sasl_channel_binding_t / SASL_CHANNEL_BINDING
273   Unclear what the "name" element does though, ditto the "critical" flag. */
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 (int i = 0; i < 2; ++i)
288   {
289   int propnum;
290   const uschar * label;
291   uschar * address_port;
292   const char *s_err;
293
294   if (i)
295     {
296     propnum = SASL_IPREMOTEPORT;
297     label = CUS"peer";
298     address_port = string_sprintf("%s;%d",
299                                   sender_host_address, sender_host_port);
300     }
301   else
302     {
303     propnum = SASL_IPLOCALPORT;
304     label = CUS"local";
305     address_port = string_sprintf("%s;%d", interface_address, interface_port);
306     }
307
308   if ((rc = sasl_setprop(conn, propnum, address_port)) != SASL_OK)
309     {
310     HDEBUG(D_auth)
311       {
312       s_err = sasl_errdetail(conn);
313       debug_printf("Failed to set %s SASL property: [%d] %s\n",
314           label, rc, s_err ? s_err : "<unknown reason>");
315       }
316     break;
317     }
318   HDEBUG(D_auth) debug_printf("Cyrus SASL set %s hostport to: %s\n",
319       label, address_port);
320   }
321
322 for (rc = SASL_CONTINUE; rc == SASL_CONTINUE; )
323   {
324   if (firsttime)
325     {
326     firsttime = 0;
327     HDEBUG(D_auth) debug_printf("Calling sasl_server_start(%s,\"%s\")\n", ob->server_mech, debug);
328     rc = sasl_server_start(conn, CS ob->server_mech, inlen ? CS input : NULL, inlen,
329            CCSS &output, &outlen);
330     }
331   else
332     {
333     /* auth_get_data() takes a length-specfied block of binary
334     which can include zeroes; no terminating NUL is needed */
335
336     if ((rc = auth_get_data(&input, output, outlen)) != OK)
337       {
338       /* we couldn't get the data, so free up the library before
339       returning whatever error we get */
340       sasl_dispose(&conn);
341       sasl_done();
342       return rc;
343       }
344     inlen = Ustrlen(input);
345
346     HDEBUG(D_auth) debug = string_copy(input);
347     if (inlen)
348       {
349       if ((clen = b64decode(input, &clear)) < 0)
350        {
351        sasl_dispose(&conn);
352        sasl_done();
353        return BAD64;
354        }
355       input = clear;
356       inlen = clen;
357       }
358
359     HDEBUG(D_auth) debug_printf("Calling sasl_server_step(\"%s\")\n", debug);
360     rc = sasl_server_step(conn, CS input, inlen, CCSS &output, &outlen);
361     }
362
363   if (rc == SASL_BADPROT)
364     {
365     sasl_dispose(&conn);
366     sasl_done();
367     return UNEXPECTED;
368     }
369   if (rc == SASL_CONTINUE)
370     continue;
371
372   /* Get the username and copy it into $auth1 and $1. The former is now the
373   preferred variable; the latter is the original variable. */
374
375   if ((sasl_getprop(conn, SASL_USERNAME, (const void **)&out2)) != SASL_OK)
376     {
377     HDEBUG(D_auth)
378       debug_printf("Cyrus SASL library will not tell us the username: %s\n",
379           sasl_errstring(rc, NULL, NULL));
380     log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
381        "Cyrus SASL username fetch problem: %s", ablock->name, ob->server_mech,
382        sasl_errstring(rc, NULL, NULL));
383     sasl_dispose(&conn);
384     sasl_done();
385     return FAIL;
386     }
387   auth_vars[0] = expand_nstring[1] = string_copy(out2);
388   expand_nlength[1] = Ustrlen(out2);
389   expand_nmax = 1;
390
391   switch (rc)
392     {
393     case SASL_FAIL: case SASL_BUFOVER: case SASL_BADMAC: case SASL_BADAUTH:
394     case SASL_NOAUTHZ: case SASL_ENCRYPT: case SASL_EXPIRED:
395     case SASL_DISABLED: case SASL_NOUSER:
396       /* these are considered permanent failure codes */
397       HDEBUG(D_auth)
398         debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
399       log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
400          "Cyrus SASL permanent failure: %s", ablock->name, ob->server_mech,
401          sasl_errstring(rc, NULL, NULL));
402       sasl_dispose(&conn);
403       sasl_done();
404       return FAIL;
405
406     case SASL_NOMECH:
407       /* this is a temporary failure, because the mechanism is not
408       available for this user. If it wasn't available at all, we
409       shouldn't have got here in the first place...  */
410
411       HDEBUG(D_auth)
412         debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
413       auth_defer_msg =
414           string_sprintf("Cyrus SASL: mechanism %s not available", ob->server_mech);
415       sasl_dispose(&conn);
416       sasl_done();
417       return DEFER;
418
419     case SASL_OK:
420       HDEBUG(D_auth)
421         debug_printf("Cyrus SASL %s authentication succeeded for %s\n",
422             ob->server_mech, auth_vars[0]);
423
424       if ((rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf_ptr)))!= SASL_OK)
425         {
426         HDEBUG(D_auth)
427           debug_printf("Cyrus SASL library will not tell us the SSF: %s\n",
428               sasl_errstring(rc, NULL, NULL));
429         log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
430             "Cyrus SASL SSF value not available: %s", ablock->name, ob->server_mech,
431             sasl_errstring(rc, NULL, NULL));
432         sasl_dispose(&conn);
433         sasl_done();
434         return FAIL;
435         }
436       negotiated_ssf = *negotiated_ssf_ptr;
437       HDEBUG(D_auth)
438         debug_printf("Cyrus SASL %s negotiated SSF: %d\n", ob->server_mech, negotiated_ssf);
439       if (negotiated_ssf > 0)
440         {
441         HDEBUG(D_auth)
442           debug_printf("Exim does not implement SASL wrapping (needed for SSF %d).\n", negotiated_ssf);
443         log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
444             "Cyrus SASL SSF %d not supported by Exim", ablock->name, ob->server_mech, negotiated_ssf);
445         sasl_dispose(&conn);
446         sasl_done();
447         return FAIL;
448         }
449
450       /* close down the connection, freeing up library's memory */
451       sasl_dispose(&conn);
452       sasl_done();
453
454       /* Expand server_condition as an authorization check */
455       return auth_check_serv_cond(ablock);
456
457     default:
458       /* Anything else is a temporary failure, and we'll let SASL print out
459        * the error string for us
460        */
461       HDEBUG(D_auth)
462         debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
463       auth_defer_msg =
464           string_sprintf("Cyrus SASL: %s", sasl_errstring(rc, NULL, NULL));
465       sasl_dispose(&conn);
466       sasl_done();
467       return DEFER;
468     }
469   }
470 /* NOTREACHED */
471 return 0;  /* Stop compiler complaints */
472 }
473
474 /*************************************************
475 *                Diagnostic API                  *
476 *************************************************/
477
478 void
479 auth_cyrus_sasl_version_report(FILE *f)
480 {
481 const char *implementation, *version;
482 sasl_version_info(&implementation, &version, NULL, NULL, NULL, NULL);
483 fprintf(f, "Library version: Cyrus SASL: Compile: %d.%d.%d\n"
484            "                             Runtime: %s [%s]\n",
485         SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP,
486         version, implementation);
487 }
488
489 /*************************************************
490 *              Client entry point                *
491 *************************************************/
492
493 /* For interface, see auths/README */
494
495 int
496 auth_cyrus_sasl_client(
497   auth_instance *ablock,                 /* authenticator block */
498   void * sx,                             /* connexction */
499   int timeout,                           /* command timeout */
500   uschar *buffer,                        /* for reading response */
501   int buffsize)                          /* size of buffer */
502 {
503 /* We don't support clients (yet) in this implementation of cyrus_sasl */
504 return FAIL;
505 }
506
507 #endif   /*!MACRO_PREDEF*/
508 #endif  /* AUTH_CYRUS_SASL */
509
510 /* End of cyrus_sasl.c */