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