/* GNU Mailutils -- a suite of utilities for electronic mail Copyright (C) 2010-2021 Free Software Foundation, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with GNU Mailutils. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int get_implemented_mechs (Gsasl *ctx, mu_list_t *plist) { char *listmech; mu_list_t supp = NULL; int rc; struct mu_wordsplit ws; rc = gsasl_server_mechlist (ctx, &listmech); if (rc != GSASL_OK) { mu_diag_output (MU_DIAG_ERROR, "cannot get list of available SASL mechanisms: %s", gsasl_strerror (rc)); return 1; } if (mu_wordsplit (listmech, &ws, MU_WRDSF_DEFFLAGS)) { mu_error (_("cannot split line `%s': %s"), listmech, mu_wordsplit_strerror (&ws)); rc = errno; } else { size_t i; rc = mu_list_create (&supp); if (rc == 0) { mu_list_set_destroy_item (supp, mu_list_free_item); for (i = 0; i < ws.ws_wordc; i++) mu_list_append (supp, ws.ws_wordv[i]); } ws.ws_wordc = 0; mu_wordsplit_free (&ws); } free (listmech); *plist = supp; return rc; } static int _smtp_callback (Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop) { int rc = GSASL_OK; mu_smtp_t smtp = gsasl_callback_hook_get (ctx); const char *p = NULL; switch (prop) { case GSASL_PASSWORD: if (mu_smtp_get_param (smtp, MU_SMTP_PARAM_PASSWORD, &p) == 0 && p) gsasl_property_set (sctx, prop, p); else rc = GSASL_NO_PASSWORD; break; case GSASL_AUTHID: case GSASL_ANONYMOUS_TOKEN: if (mu_smtp_get_param (smtp, MU_SMTP_PARAM_USERNAME, &p) == 0 && p) gsasl_property_set (sctx, prop, p); else if (prop == GSASL_AUTHID) rc = GSASL_NO_AUTHID; else rc = GSASL_NO_ANONYMOUS_TOKEN; break; case GSASL_AUTHZID: rc = GSASL_NO_AUTHZID; break; case GSASL_SERVICE: if (mu_smtp_get_param (smtp, MU_SMTP_PARAM_SERVICE, &p) || !p) p = "smtp"; gsasl_property_set (sctx, prop, p); break; case GSASL_REALM: if ((mu_smtp_get_param (smtp, MU_SMTP_PARAM_REALM, &p) || !p) && (mu_smtp_get_param (smtp, MU_SMTP_PARAM_DOMAIN, &p) || !p)) rc = GSASL_NO_HOSTNAME; else gsasl_property_set (sctx, prop, p); break; case GSASL_HOSTNAME: if ((mu_smtp_get_param (smtp, MU_SMTP_PARAM_HOST, &p) || !p) && mu_get_host_name ((char**)&p)) { rc = GSASL_NO_HOSTNAME; break; } else gsasl_property_set (sctx, prop, p); break; default: rc = GSASL_NO_CALLBACK; mu_diag_output (MU_DIAG_NOTICE, "unsupported callback property %d", prop); break; } return rc; } static int restore_and_return (mu_smtp_t smtp, mu_stream_t *str, int code) { mu_stream_unref (str[0]); mu_stream_unref (str[1]); return code; } int insert_gsasl_stream (mu_smtp_t smtp, Gsasl_session *sess_ctx) { mu_stream_t stream[2], newstream[2]; int rc; rc = _mu_smtp_get_streams (smtp, stream); if (rc) { mu_error ("%s failed: %s", "MU_IOCTL_GET_STREAM", mu_stream_strerror (smtp->carrier, rc)); return MU_ERR_FAILURE; } rc = gsasl_encoder_stream (&newstream[0], stream[0], sess_ctx, MU_STREAM_READ); if (rc) { mu_error ("%s failed: %s", "gsasl_encoder_stream", mu_strerror (rc)); return restore_and_return (smtp, stream, MU_ERR_FAILURE); } rc = gsasl_decoder_stream (&newstream[1], stream[1], sess_ctx, MU_STREAM_WRITE); if (rc) { mu_error ("%s failed: %s", "gsasl_decoder_stream", mu_strerror (rc)); mu_stream_destroy (&newstream[0]); return restore_and_return (smtp, stream, MU_ERR_FAILURE); } mu_stream_flush (stream[1]); mu_stream_unref (stream[0]); mu_stream_unref (stream[1]); rc = _mu_smtp_set_streams (smtp, newstream); if (rc) { mu_error ("%s failed when it should not: %s", "MU_IOCTL_SET_STREAM", mu_stream_strerror (smtp->carrier, rc)); abort (); } return 0; } static int do_gsasl_auth (mu_smtp_t smtp, Gsasl *ctx, const char *mech) { Gsasl_session *sess; int rc, status; char *output = NULL; rc = gsasl_client_start (ctx, mech, &sess); if (rc != GSASL_OK) { mu_diag_output (MU_DIAG_ERROR, "SASL gsasl_client_start: %s", gsasl_strerror (rc)); return MU_ERR_FAILURE; } status = mu_smtp_write (smtp, "AUTH %s\r\n", mech); MU_SMTP_CHECK_ERROR (smtp, rc); status = mu_smtp_response (smtp); MU_SMTP_CHECK_ERROR (smtp, status); if (smtp->replcode[0] != '3') { mu_diag_output (MU_DIAG_ERROR, "GSASL handshake aborted: " "unexpected reply: %s %s", smtp->replcode, smtp->replptr); return MU_ERR_REPLY; } do { rc = gsasl_step64 (sess, smtp->replptr, &output); if (rc != GSASL_NEEDS_MORE && rc != GSASL_OK) break; status = mu_smtp_write (smtp, "%s\r\n", output); MU_SMTP_CHECK_ERROR (smtp, status); free (output); output = NULL; status = mu_smtp_response (smtp); MU_SMTP_CHECK_ERROR (smtp, status); if (smtp->replcode[0] == '2') { rc = GSASL_OK; break; } else if (smtp->replcode[0] != '3') break; } while (rc == GSASL_NEEDS_MORE); if (output) free (output); if (rc != GSASL_OK) { mu_diag_output (MU_DIAG_ERROR, "GSASL error: %s", gsasl_strerror (rc)); return 1; } if (smtp->replcode[0] != '2') { mu_diag_output (MU_DIAG_ERROR, "GSASL handshake failed: %s %s", smtp->replcode, smtp->replptr); return MU_ERR_REPLY; } /* Authentication successful */ MU_SMTP_FSET (smtp, _MU_SMTP_AUTH); return insert_gsasl_stream (smtp, sess); } int _mu_smtp_gsasl_auth (mu_smtp_t smtp) { int rc; Gsasl *ctx; mu_list_t mech_list; const char *mech; rc = gsasl_init (&ctx); if (rc != GSASL_OK) { mu_diag_output (MU_DIAG_ERROR, "cannot initialize GSASL: %s", gsasl_strerror (rc)); return MU_ERR_FAILURE; } rc = get_implemented_mechs (ctx, &mech_list); if (rc) return rc; rc = _mu_smtp_mech_impl (smtp, mech_list); if (rc) { mu_list_destroy (&mech_list); return rc; } rc = mu_smtp_mech_select (smtp, &mech); if (rc) { mu_diag_output (MU_DIAG_DEBUG, "no suitable authentication mechanism found"); return rc; } mu_diag_output (MU_DIAG_DEBUG, "selected authentication mechanism %s", mech); gsasl_callback_hook_set (ctx, smtp); gsasl_callback_set (ctx, _smtp_callback); rc = do_gsasl_auth (smtp, ctx, mech); if (rc == 0) { /* Invalidate the capability list */ mu_list_destroy (&smtp->capa); } return rc; }