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