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