1 /* $NetBSD: smtpd_sasl_glue.c,v 1.5 2023/12/23 20:30:45 christos Exp $ */
2
3 /*++
4 /* NAME
5 /* smtpd_sasl_glue 3
6 /* SUMMARY
7 /* Postfix SMTP server, SASL support interface
8 /* SYNOPSIS
9 /* #include "smtpd_sasl_glue.h"
10 /*
11 /* void smtpd_sasl_state_init(state)
12 /* SMTPD_STATE *state;
13 /*
14 /* void smtpd_sasl_initialize()
15 /*
16 /* void smtpd_sasl_activate(state, sasl_opts_name, sasl_opts_val)
17 /* SMTPD_STATE *state;
18 /* const char *sasl_opts_name;
19 /* const char *sasl_opts_val;
20 /*
21 /* char *smtpd_sasl_authenticate(state, sasl_method, init_response)
22 /* SMTPD_STATE *state;
23 /* const char *sasl_method;
24 /* const char *init_response;
25 /*
26 /* void smtpd_sasl_logout(state)
27 /* SMTPD_STATE *state;
28 /*
29 /* void smtpd_sasl_login(state, sasl_username, sasl_method)
30 /* SMTPD_STATE *state;
31 /* const char *sasl_username;
32 /* const char *sasl_method;
33 /*
34 /* void smtpd_sasl_deactivate(state)
35 /* SMTPD_STATE *state;
36 /*
37 /* int smtpd_sasl_is_active(state)
38 /* SMTPD_STATE *state;
39 /*
40 /* int smtpd_sasl_set_inactive(state)
41 /* SMTPD_STATE *state;
42 /* DESCRIPTION
43 /* This module encapsulates most of the detail specific to SASL
44 /* authentication.
45 /*
46 /* smtpd_sasl_state_init() performs minimal server state
47 /* initialization to support external authentication (e.g.,
48 /* XCLIENT) without having to enable SASL in main.cf. This
49 /* should always be called at process startup.
50 /*
51 /* smtpd_sasl_initialize() initializes the SASL library. This
52 /* routine should be called once at process start-up. It may
53 /* need access to the file system for run-time loading of
54 /* plug-in modules. There is no corresponding cleanup routine.
55 /*
56 /* smtpd_sasl_activate() performs per-connection initialization.
57 /* This routine should be called once at the start of every
58 /* connection. The sasl_opts_name and sasl_opts_val parameters
59 /* are the postfix configuration parameters setting the security
60 /* policy of the SASL authentication.
61 /*
62 /* smtpd_sasl_authenticate() implements the authentication
63 /* dialog. The result is zero in case of success, -1 in case
64 /* of failure. smtpd_sasl_authenticate() updates the following
65 /* state structure members:
66 /* .IP sasl_method
67 /* The authentication method that was successfully applied.
68 /* This member is a null pointer in the absence of successful
69 /* authentication.
70 /* .IP sasl_username
71 /* The username that was successfully authenticated.
72 /* This member is a null pointer in the absence of successful
73 /* authentication.
74 /* .PP
75 /* smtpd_sasl_login() records the result of successful external
76 /* authentication, i.e. without invoking smtpd_sasl_authenticate(),
77 /* but produces an otherwise equivalent result.
78 /*
79 /* smtpd_sasl_logout() cleans up after smtpd_sasl_authenticate().
80 /* This routine exists for the sake of symmetry.
81 /*
82 /* smtpd_sasl_deactivate() performs per-connection cleanup.
83 /* This routine should be called at the end of every connection.
84 /*
85 /* smtpd_sasl_is_active() is a predicate that returns true
86 /* if the SMTP server session state is between smtpd_sasl_activate()
87 /* and smtpd_sasl_deactivate().
88 /*
89 /* smtpd_sasl_set_inactive() initializes the SMTP session
90 /* state before the first smtpd_sasl_activate() call.
91 /*
92 /* Arguments:
93 /* .IP state
94 /* SMTP session context.
95 /* .IP sasl_opts_name
96 /* Security options parameter name.
97 /* .IP sasl_opts_val
98 /* Security options parameter value.
99 /* .IP sasl_method
100 /* A SASL mechanism name
101 /* .IP init_reply
102 /* An optional initial client response.
103 /* DIAGNOSTICS
104 /* All errors are fatal.
105 /* LICENSE
106 /* .ad
107 /* .fi
108 /* The Secure Mailer license must be distributed with this software.
109 /* AUTHOR(S)
110 /* Initial implementation by:
111 /* Till Franke
112 /* SuSE Rhein/Main AG
113 /* 65760 Eschborn, Germany
114 /*
115 /* Adopted by:
116 /* Wietse Venema
117 /* IBM T.J. Watson Research
118 /* P.O. Box 704
119 /* Yorktown Heights, NY 10598, USA
120 /*
121 /* Wietse Venema
122 /* Google, Inc.
123 /* 111 8th Avenue
124 /* New York, NY 10011, USA
125 /*--*/
126
127 /* System library. */
128
129 #include <sys_defs.h>
130 #include <stdlib.h>
131 #include <string.h>
132
133 /* Utility library. */
134
135 #include <msg.h>
136 #include <mymalloc.h>
137 #include <stringops.h>
138
139 /* Global library. */
140
141 #include <mail_params.h>
142 #include <sasl_mech_filter.h>
143 #include <string_list.h>
144
145 /* XSASL library. */
146
147 #include <xsasl.h>
148
149 /* Application-specific. */
150
151 #include "smtpd.h"
152 #include "smtpd_sasl_glue.h"
153 #include "smtpd_chat.h"
154
155 #ifdef USE_SASL_AUTH
156
157 /*
158 * SASL mechanism filter.
159 */
160 static STRING_LIST *smtpd_sasl_mech_filter;
161
162 /*
163 * Silly little macros.
164 */
165 #define STR(s) vstring_str(s)
166
167 /*
168 * SASL server implementation handle.
169 */
170 static XSASL_SERVER_IMPL *smtpd_sasl_impl;
171
172 /* smtpd_sasl_initialize - per-process initialization */
173
smtpd_sasl_initialize(void)174 void smtpd_sasl_initialize(void)
175 {
176
177 /*
178 * Sanity check.
179 */
180 if (smtpd_sasl_impl)
181 msg_panic("smtpd_sasl_initialize: repeated call");
182
183 /*
184 * Initialize the SASL library.
185 */
186 if ((smtpd_sasl_impl = xsasl_server_init(var_smtpd_sasl_type,
187 var_smtpd_sasl_path)) == 0)
188 msg_fatal("SASL per-process initialization failed");
189
190 /*
191 * Initialize the SASL mechanism filter.
192 */
193 smtpd_sasl_mech_filter = string_list_init(VAR_SMTPD_SASL_MECH_FILTER,
194 MATCH_FLAG_NONE,
195 var_smtpd_sasl_mech_filter);
196 }
197
198 /* smtpd_sasl_activate - per-connection initialization */
199
smtpd_sasl_activate(SMTPD_STATE * state,const char * sasl_opts_name,const char * sasl_opts_val)200 void smtpd_sasl_activate(SMTPD_STATE *state, const char *sasl_opts_name,
201 const char *sasl_opts_val)
202 {
203 const char *mechanism_list;
204 const char *filtered_mechanism_list;
205 XSASL_SERVER_CREATE_ARGS create_args;
206 int tls_flag;
207
208 /*
209 * Sanity check.
210 */
211 if (smtpd_sasl_is_active(state))
212 msg_panic("smtpd_sasl_activate: already active");
213
214 /*
215 * Initialize SASL-specific state variables. Use long-lived storage for
216 * base 64 conversion results, rather than local variables, to avoid
217 * memory leaks when a read or write routine returns abnormally after
218 * timeout or I/O error.
219 */
220 state->sasl_reply = vstring_alloc(20);
221 state->sasl_mechanism_list = 0;
222
223 /*
224 * Set up a new server context for this connection.
225 */
226 #ifdef USE_TLS
227 tls_flag = state->tls_context != 0;
228 #else
229 tls_flag = 0;
230 #endif
231 #define ADDR_OR_EMPTY(addr, unknown) (strcmp(addr, unknown) ? addr : "")
232 #define REALM_OR_NULL(realm) (*(realm) ? (realm) : (char *) 0)
233
234 if ((state->sasl_server =
235 XSASL_SERVER_CREATE(smtpd_sasl_impl, &create_args,
236 stream = state->client,
237 addr_family = state->addr_family,
238 server_addr = ADDR_OR_EMPTY(state->dest_addr,
239 SERVER_ADDR_UNKNOWN),
240 server_port = ADDR_OR_EMPTY(state->dest_port,
241 SERVER_PORT_UNKNOWN),
242 client_addr = ADDR_OR_EMPTY(state->addr,
243 CLIENT_ADDR_UNKNOWN),
244 client_port = ADDR_OR_EMPTY(state->port,
245 CLIENT_PORT_UNKNOWN),
246 service = var_smtpd_sasl_service,
247 user_realm = REALM_OR_NULL(var_smtpd_sasl_realm),
248 security_options = sasl_opts_val,
249 tls_flag = tls_flag)) == 0)
250 msg_fatal("SASL per-connection initialization failed");
251
252 /*
253 * Get the list of authentication mechanisms.
254 */
255 if ((mechanism_list =
256 xsasl_server_get_mechanism_list(state->sasl_server)) == 0)
257 msg_fatal("no SASL authentication mechanisms");
258 filtered_mechanism_list =
259 sasl_mech_filter(smtpd_sasl_mech_filter, mechanism_list);
260 if (*filtered_mechanism_list == 0)
261 msg_fatal("%s discards all mechanisms in '%s'",
262 VAR_SMTPD_SASL_MECH_FILTER, mechanism_list);
263 state->sasl_mechanism_list = mystrdup(filtered_mechanism_list);
264 }
265
266 /* smtpd_sasl_state_init - initialize state to allow extern authentication. */
267
smtpd_sasl_state_init(SMTPD_STATE * state)268 void smtpd_sasl_state_init(SMTPD_STATE *state)
269 {
270 /* Initialization to support external authentication (e.g., XCLIENT). */
271 state->sasl_username = 0;
272 state->sasl_method = 0;
273 state->sasl_sender = 0;
274 }
275
276 /* smtpd_sasl_deactivate - per-connection cleanup */
277
smtpd_sasl_deactivate(SMTPD_STATE * state)278 void smtpd_sasl_deactivate(SMTPD_STATE *state)
279 {
280 if (state->sasl_reply) {
281 vstring_free(state->sasl_reply);
282 state->sasl_reply = 0;
283 }
284 if (state->sasl_mechanism_list) {
285 myfree(state->sasl_mechanism_list);
286 state->sasl_mechanism_list = 0;
287 }
288 if (state->sasl_username) {
289 myfree(state->sasl_username);
290 state->sasl_username = 0;
291 }
292 if (state->sasl_method) {
293 myfree(state->sasl_method);
294 state->sasl_method = 0;
295 }
296 if (state->sasl_sender) {
297 myfree(state->sasl_sender);
298 state->sasl_sender = 0;
299 }
300 if (state->sasl_server) {
301 xsasl_server_free(state->sasl_server);
302 state->sasl_server = 0;
303 }
304 }
305
306 /* smtpd_sasl_authenticate - per-session authentication */
307
smtpd_sasl_authenticate(SMTPD_STATE * state,const char * sasl_method,const char * init_response)308 int smtpd_sasl_authenticate(SMTPD_STATE *state,
309 const char *sasl_method,
310 const char *init_response)
311 {
312 int status;
313 const char *sasl_username;
314
315 /*
316 * SASL authentication protocol start-up. Process any initial client
317 * response that was sent along in the AUTH command.
318 */
319 for (status = xsasl_server_first(state->sasl_server, sasl_method,
320 init_response, state->sasl_reply);
321 status == XSASL_AUTH_MORE;
322 status = xsasl_server_next(state->sasl_server, STR(state->buffer),
323 state->sasl_reply)) {
324
325 /*
326 * Send a server challenge.
327 */
328 smtpd_chat_reply(state, "334 %s", STR(state->sasl_reply));
329
330 /*
331 * Receive the client response. "*" means that the client gives up.
332 */
333 if (!smtpd_chat_query_limit(state, var_smtpd_sasl_resp_limit)) {
334 smtpd_chat_reply(state, "500 5.5.6 SASL response limit exceeded");
335 return (-1);
336 }
337 if (strcmp(STR(state->buffer), "*") == 0) {
338 msg_warn("%s: SASL %s authentication aborted",
339 state->namaddr, sasl_method);
340 smtpd_chat_reply(state, "501 5.7.0 Authentication aborted");
341 return (-1);
342 }
343 }
344 if (status != XSASL_AUTH_DONE) {
345 sasl_username = xsasl_server_get_username(state->sasl_server);
346 msg_warn("%s: SASL %.100s authentication failed: %s, sasl_username=%.100s",
347 state->namaddr, sasl_method, *STR(state->sasl_reply) ?
348 STR(state->sasl_reply) : "(reason unavailable)",
349 sasl_username ? sasl_username : "(unavailable)");
350 /* RFC 4954 Section 6. */
351 if (status == XSASL_AUTH_TEMP)
352 smtpd_chat_reply(state, "454 4.7.0 Temporary authentication failure: %s",
353 STR(state->sasl_reply));
354 else
355 smtpd_chat_reply(state, "535 5.7.8 Error: authentication failed: %s",
356 STR(state->sasl_reply));
357 return (-1);
358 }
359 /* RFC 4954 Section 6. */
360 smtpd_chat_reply(state, "235 2.7.0 Authentication successful");
361 if ((sasl_username = xsasl_server_get_username(state->sasl_server)) == 0)
362 msg_panic("cannot look up the authenticated SASL username");
363 state->sasl_username = mystrdup(sasl_username);
364 printable(state->sasl_username, '?');
365 state->sasl_method = mystrdup(sasl_method);
366 printable(state->sasl_method, '?');
367
368 return (0);
369 }
370
371 /* smtpd_sasl_logout - clean up after smtpd_sasl_authenticate */
372
smtpd_sasl_logout(SMTPD_STATE * state)373 void smtpd_sasl_logout(SMTPD_STATE *state)
374 {
375 if (state->sasl_username) {
376 myfree(state->sasl_username);
377 state->sasl_username = 0;
378 }
379 if (state->sasl_method) {
380 myfree(state->sasl_method);
381 state->sasl_method = 0;
382 }
383 }
384
385 /* smtpd_sasl_login - set login information */
386
smtpd_sasl_login(SMTPD_STATE * state,const char * sasl_username,const char * sasl_method)387 void smtpd_sasl_login(SMTPD_STATE *state, const char *sasl_username,
388 const char *sasl_method)
389 {
390 if (state->sasl_username)
391 myfree(state->sasl_username);
392 state->sasl_username = mystrdup(sasl_username);
393 if (state->sasl_method)
394 myfree(state->sasl_method);
395 state->sasl_method = mystrdup(sasl_method);
396 }
397
398 #endif
399