1 /* $NetBSD: smtpd_sasl_glue.c,v 1.6 2025/02/25 19:15:50 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 /* Wietse Venema
127 /* porcupine.org
128 /* Amawalk, NY 10501, USA
129 /*--*/
130
131 /* System library. */
132
133 #include <sys_defs.h>
134 #include <stdlib.h>
135 #include <string.h>
136
137 /* Utility library. */
138
139 #include <msg.h>
140 #include <mymalloc.h>
141 #include <stringops.h>
142
143 /* Global library. */
144
145 #include <mail_params.h>
146 #include <sasl_mech_filter.h>
147 #include <string_list.h>
148
149 /* XSASL library. */
150
151 #include <xsasl.h>
152
153 /* Application-specific. */
154
155 #include "smtpd.h"
156 #include "smtpd_sasl_glue.h"
157 #include "smtpd_chat.h"
158
159 #ifdef USE_SASL_AUTH
160
161 /*
162 * SASL mechanism filter.
163 */
164 static STRING_LIST *smtpd_sasl_mech_filter;
165
166 /*
167 * Silly little macros.
168 */
169 #define STR(s) vstring_str(s)
170
171 /*
172 * SASL server implementation handle.
173 */
174 static XSASL_SERVER_IMPL *smtpd_sasl_impl;
175
176 /* smtpd_sasl_initialize - per-process initialization */
177
smtpd_sasl_initialize(void)178 void smtpd_sasl_initialize(void)
179 {
180
181 /*
182 * Sanity check.
183 */
184 if (smtpd_sasl_impl)
185 msg_panic("smtpd_sasl_initialize: repeated call");
186
187 /*
188 * Initialize the SASL library.
189 */
190 if ((smtpd_sasl_impl = xsasl_server_init(var_smtpd_sasl_type,
191 var_smtpd_sasl_path)) == 0)
192 msg_fatal("SASL per-process initialization failed");
193
194 /*
195 * Initialize the SASL mechanism filter.
196 */
197 smtpd_sasl_mech_filter = string_list_init(VAR_SMTPD_SASL_MECH_FILTER,
198 MATCH_FLAG_NONE,
199 var_smtpd_sasl_mech_filter);
200 }
201
202 /* smtpd_sasl_activate - per-connection initialization */
203
smtpd_sasl_activate(SMTPD_STATE * state,const char * sasl_opts_name,const char * sasl_opts_val)204 void smtpd_sasl_activate(SMTPD_STATE *state, const char *sasl_opts_name,
205 const char *sasl_opts_val)
206 {
207 const char *mechanism_list;
208 const char *filtered_mechanism_list;
209 XSASL_SERVER_CREATE_ARGS create_args;
210 int tls_flag;
211
212 /*
213 * Sanity check.
214 */
215 if (smtpd_sasl_is_active(state))
216 msg_panic("smtpd_sasl_activate: already active");
217
218 /*
219 * Initialize SASL-specific state variables. Use long-lived storage for
220 * base 64 conversion results, rather than local variables, to avoid
221 * memory leaks when a read or write routine returns abnormally after
222 * timeout or I/O error.
223 */
224 state->sasl_reply = vstring_alloc(20);
225 state->sasl_mechanism_list = 0;
226
227 /*
228 * Set up a new server context for this connection.
229 */
230 #ifdef USE_TLS
231 tls_flag = state->tls_context != 0;
232 #else
233 tls_flag = 0;
234 #endif
235 #define ADDR_OR_EMPTY(addr, unknown) (strcmp(addr, unknown) ? addr : "")
236 #define REALM_OR_NULL(realm) (*(realm) ? (realm) : (char *) 0)
237
238 if ((state->sasl_server =
239 XSASL_SERVER_CREATE(smtpd_sasl_impl, &create_args,
240 stream = state->client,
241 addr_family = state->addr_family,
242 server_addr = ADDR_OR_EMPTY(state->dest_addr,
243 SERVER_ADDR_UNKNOWN),
244 server_port = ADDR_OR_EMPTY(state->dest_port,
245 SERVER_PORT_UNKNOWN),
246 client_addr = ADDR_OR_EMPTY(state->addr,
247 CLIENT_ADDR_UNKNOWN),
248 client_port = ADDR_OR_EMPTY(state->port,
249 CLIENT_PORT_UNKNOWN),
250 service = var_smtpd_sasl_service,
251 user_realm = REALM_OR_NULL(var_smtpd_sasl_realm),
252 security_options = sasl_opts_val,
253 tls_flag = tls_flag)) == 0)
254 msg_fatal("SASL per-connection initialization failed");
255
256 /*
257 * Get the list of authentication mechanisms.
258 */
259 if ((mechanism_list =
260 xsasl_server_get_mechanism_list(state->sasl_server)) == 0)
261 msg_fatal("no SASL authentication mechanisms");
262 filtered_mechanism_list =
263 sasl_mech_filter(smtpd_sasl_mech_filter, mechanism_list);
264 if (*filtered_mechanism_list == 0)
265 msg_fatal("%s discards all mechanisms in '%s'",
266 VAR_SMTPD_SASL_MECH_FILTER, mechanism_list);
267 state->sasl_mechanism_list = mystrdup(filtered_mechanism_list);
268 }
269
270 /* smtpd_sasl_state_init - initialize state to allow extern authentication. */
271
smtpd_sasl_state_init(SMTPD_STATE * state)272 void smtpd_sasl_state_init(SMTPD_STATE *state)
273 {
274 /* Initialization to support external authentication (e.g., XCLIENT). */
275 state->sasl_username = 0;
276 state->sasl_method = 0;
277 state->sasl_sender = 0;
278 }
279
280 /* smtpd_sasl_deactivate - per-connection cleanup */
281
smtpd_sasl_deactivate(SMTPD_STATE * state)282 void smtpd_sasl_deactivate(SMTPD_STATE *state)
283 {
284 if (state->sasl_reply) {
285 vstring_free(state->sasl_reply);
286 state->sasl_reply = 0;
287 }
288 if (state->sasl_mechanism_list) {
289 myfree(state->sasl_mechanism_list);
290 state->sasl_mechanism_list = 0;
291 }
292 if (state->sasl_username) {
293 myfree(state->sasl_username);
294 state->sasl_username = 0;
295 }
296 if (state->sasl_method) {
297 myfree(state->sasl_method);
298 state->sasl_method = 0;
299 }
300 if (state->sasl_sender) {
301 myfree(state->sasl_sender);
302 state->sasl_sender = 0;
303 }
304 if (state->sasl_server) {
305 xsasl_server_free(state->sasl_server);
306 state->sasl_server = 0;
307 }
308 }
309
310 /* smtpd_sasl_authenticate - per-session authentication */
311
smtpd_sasl_authenticate(SMTPD_STATE * state,const char * sasl_method,const char * init_response)312 int smtpd_sasl_authenticate(SMTPD_STATE *state,
313 const char *sasl_method,
314 const char *init_response)
315 {
316 int status;
317 const char *sasl_username;
318
319 /*
320 * SASL authentication protocol start-up. Process any initial client
321 * response that was sent along in the AUTH command.
322 */
323 for (status = xsasl_server_first(state->sasl_server, sasl_method,
324 init_response, state->sasl_reply);
325 status == XSASL_AUTH_MORE;
326 status = xsasl_server_next(state->sasl_server, STR(state->buffer),
327 state->sasl_reply)) {
328
329 /*
330 * Send a server challenge.
331 */
332 smtpd_chat_reply(state, "334 %s", STR(state->sasl_reply));
333
334 /*
335 * Receive the client response. "*" means that the client gives up.
336 */
337 if (!smtpd_chat_query_limit(state, var_smtpd_sasl_resp_limit)) {
338 smtpd_chat_reply(state, "500 5.5.6 SASL response limit exceeded");
339 return (-1);
340 }
341 if (strcmp(STR(state->buffer), "*") == 0) {
342 msg_warn("%s: SASL %s authentication aborted",
343 state->namaddr, sasl_method);
344 smtpd_chat_reply(state, "501 5.7.0 Authentication aborted");
345 return (-1);
346 }
347 }
348 if (status != XSASL_AUTH_DONE) {
349 const char *reason = (*STR(state->sasl_reply) ? STR(state->sasl_reply) :
350 "(reason unavailable)");
351
352 sasl_username = xsasl_server_get_username(state->sasl_server);
353 msg_warn("%s: SASL %.100s authentication failed: %s, sasl_username=%.100s",
354 state->namaddr, sasl_method, reason,
355 sasl_username ? sasl_username : "(unavailable)");
356 /* RFC 4954 Section 6. */
357 if (status == XSASL_AUTH_TEMP)
358 smtpd_chat_reply(state, "454 4.7.0 Temporary authentication failure: %s",
359 reason);
360 else
361 smtpd_chat_reply(state, "535 5.7.8 Error: authentication failed: %s",
362 reason);
363 return (-1);
364 }
365 /* RFC 4954 Section 6. */
366 smtpd_chat_reply(state, "235 2.7.0 Authentication successful");
367 if ((sasl_username = xsasl_server_get_username(state->sasl_server)) == 0)
368 msg_panic("cannot look up the authenticated SASL username");
369 state->sasl_username = mystrdup(sasl_username);
370 printable(state->sasl_username, '?');
371 state->sasl_method = mystrdup(sasl_method);
372 printable(state->sasl_method, '?');
373
374 return (0);
375 }
376
377 /* smtpd_sasl_logout - clean up after smtpd_sasl_authenticate */
378
smtpd_sasl_logout(SMTPD_STATE * state)379 void smtpd_sasl_logout(SMTPD_STATE *state)
380 {
381 if (state->sasl_username) {
382 myfree(state->sasl_username);
383 state->sasl_username = 0;
384 }
385 if (state->sasl_method) {
386 myfree(state->sasl_method);
387 state->sasl_method = 0;
388 }
389 }
390
391 /* smtpd_sasl_login - set login information */
392
smtpd_sasl_login(SMTPD_STATE * state,const char * sasl_username,const char * sasl_method)393 void smtpd_sasl_login(SMTPD_STATE *state, const char *sasl_username,
394 const char *sasl_method)
395 {
396 if (state->sasl_username)
397 myfree(state->sasl_username);
398 state->sasl_username = mystrdup(sasl_username);
399 if (state->sasl_method)
400 myfree(state->sasl_method);
401 state->sasl_method = mystrdup(sasl_method);
402 }
403
404 #endif
405