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 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 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 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 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 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 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 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