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