/** * check_certificate_expiration - Check if a certificate has expired * @param peercert Certificate to check * @param silent If true, don't notify the user if the certificate has expired * @retval true Certificate is valid * @retval false Certificate has expired (or hasn't yet become valid) */ static bool check_certificate_expiration(X509 *peercert, bool silent) { if (C_SslVerifyDates == MUTT_NO) return true; if (X509_cmp_current_time(X509_get0_notBefore(peercert)) >= 0) { if (!silent) { mutt_debug(LL_DEBUG2, "Server certificate is not yet valid\n"); mutt_error(_("Server certificate is not yet valid")); } return false; } if (X509_cmp_current_time(X509_get0_notAfter(peercert)) <= 0) { if (!silent) { mutt_debug(LL_DEBUG2, "Server certificate has expired\n"); mutt_error(_("Server certificate has expired")); } return false; } return true; }
/** * ssl_set_verify_partial - Allow verification using partial chains (with no root) * @param ctx SSL context * @retval 0 Success * @retval -1 Error */ static int ssl_set_verify_partial(SSL_CTX *ctx) { int rc = 0; #ifdef HAVE_SSL_PARTIAL_CHAIN X509_VERIFY_PARAM *param = NULL; if (C_SslVerifyPartialChains) { param = X509_VERIFY_PARAM_new(); if (param) { X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_PARTIAL_CHAIN); if (SSL_CTX_set1_param(ctx, param) == 0) { mutt_debug(LL_DEBUG2, "SSL_CTX_set1_param() failed.\n"); rc = -1; } X509_VERIFY_PARAM_free(param); } else { mutt_debug(LL_DEBUG2, "X509_VERIFY_PARAM_new() failed.\n"); rc = -1; } } #endif return rc; }
/** * cs_he_initial_set - Set the initial value of a config item * @param cs Config items * @param he HashElem representing config item * @param value Value to set * @param err Buffer for error messages * @retval int Result, e.g. #CSR_SUCCESS */ int cs_he_initial_set(const struct ConfigSet *cs, struct HashElem *he, const char *value, struct Buffer *err) { if (!cs || !he) return CSR_ERR_CODE; struct ConfigDef *cdef = NULL; const struct ConfigSetType *cst = NULL; if (he->type & DT_INHERITED) { struct Inheritance *i = he->data; cdef = i->parent->data; mutt_debug(LL_DEBUG1, "Variable '%s' is inherited type\n", cdef->name); return CSR_ERR_CODE; } cdef = he->data; cst = cs_get_type_def(cs, he->type); if (!cst) { mutt_debug(LL_DEBUG1, "Variable '%s' has an invalid type %d\n", cdef->name, he->type); return CSR_ERR_CODE; } int rc = cst->string_set(cs, NULL, cdef, value, err); if (CSR_RESULT(rc) != CSR_SUCCESS) return rc; cs_notify_listeners(cs, he, he->key.strkey, CE_INITIAL_SET); return CSR_SUCCESS; }
/** * mutt_socket_write_d - Write data to a socket * @param conn Connection to a server * @param buf Buffer with data to write * @param len Length of data to write * @param dbg Debug level for logging * @retval >0 Number of bytes written * @retval -1 Error */ int mutt_socket_write_d(struct Connection *conn, const char *buf, int len, int dbg) { int sent = 0; mutt_debug(dbg, "%d> %s", conn->fd, buf); if (conn->fd < 0) { mutt_debug(LL_DEBUG1, "attempt to write to closed connection\n"); return -1; } while (sent < len) { const int rc = conn->conn_write(conn, buf + sent, len - sent); if (rc < 0) { mutt_debug(LL_DEBUG1, "error writing (%s), closing socket\n", strerror(errno)); mutt_socket_close(conn); return -1; } if (rc < len - sent) mutt_debug(LL_DEBUG3, "short write (%d of %d bytes)\n", rc, len - sent); sent += rc; } return sent; }
/** * tls_get_client_cert - Get the client certificate for a TLS connection * @param conn Connection to a server */ static void tls_get_client_cert(struct Connection *conn) { struct TlsSockData *data = conn->sockdata; gnutls_x509_crt_t clientcrt; char *dn = NULL; char *cn = NULL; char *cnend = NULL; size_t dnlen; /* get our cert CN if we have one */ const gnutls_datum_t *crtdata = gnutls_certificate_get_ours(data->state); if (!crtdata) return; if (gnutls_x509_crt_init(&clientcrt) < 0) { mutt_debug(LL_DEBUG1, "Failed to init gnutls crt\n"); return; } if (gnutls_x509_crt_import(clientcrt, crtdata, GNUTLS_X509_FMT_DER) < 0) { mutt_debug(LL_DEBUG1, "Failed to import gnutls client crt\n"); goto err_crt; } /* get length of DN */ dnlen = 0; gnutls_x509_crt_get_dn(clientcrt, NULL, &dnlen); dn = mutt_mem_calloc(1, dnlen); gnutls_x509_crt_get_dn(clientcrt, dn, &dnlen); mutt_debug(LL_DEBUG2, "client certificate DN: %s\n", dn); /* extract CN to use as external user name */ cn = strstr(dn, "CN="); if (!cn) { mutt_debug(LL_DEBUG1, "no CN found in DN\n"); goto err_dn; } cnend = strstr(dn, ",EMAIL="); if (cnend) *cnend = '\0'; /* if we are using a client cert, SASL may expect an external auth name */ if (mutt_account_getuser(&conn->account) < 0) mutt_debug(LL_DEBUG1, "Couldn't get user info\n"); err_dn: FREE(&dn); err_crt: gnutls_x509_crt_deinit(clientcrt); }
/** * ssl_get_client_cert - Get the client certificate for an SSL connection * @param ssldata SSL socket data * @param conn Connection to a server */ static void ssl_get_client_cert(struct SslSockData *ssldata, struct Connection *conn) { if (!C_SslClientCert) return; mutt_debug(LL_DEBUG2, "Using client certificate %s\n", C_SslClientCert); SSL_CTX_set_default_passwd_cb_userdata(ssldata->sctx, &conn->account); SSL_CTX_set_default_passwd_cb(ssldata->sctx, ssl_passwd_cb); SSL_CTX_use_certificate_file(ssldata->sctx, C_SslClientCert, SSL_FILETYPE_PEM); SSL_CTX_use_PrivateKey_file(ssldata->sctx, C_SslClientCert, SSL_FILETYPE_PEM); /* if we are using a client cert, SASL may expect an external auth name */ if (mutt_account_getuser(&conn->account) < 0) mutt_debug(LL_DEBUG1, "Couldn't get user info\n"); }
/** * ssl_cache_trusted_cert - Cache a trusted certificate * @param c Certificate * @retval >0 Number of elements in the cache * @retval 0 Error */ static int ssl_cache_trusted_cert(X509 *c) { mutt_debug(LL_DEBUG1, "trusted\n"); if (!SslSessionCerts) SslSessionCerts = sk_X509_new_null(); return sk_X509_push(SslSessionCerts, X509_dup(c)); }
/** * mutt_perror_debug - Show the user an 'errno' message * @param s Additional text to show */ void mutt_perror_debug(const char *s) { char *p = strerror(errno); mutt_debug(LL_DEBUG1, "%s: %s (errno = %d)\n", s, p ? p : "unknown error", errno); mutt_error("%s: %s (errno = %d)", s, p ? p : _("unknown error"), errno); }
/** * cs_he_initial_get - Get the initial, or parent, value of a config item * @param cs Config items * @param he HashElem representing config item * @param result Buffer for results or error messages * @retval int Result, e.g. #CSR_SUCCESS * * If a config item is inherited from another, then this will get the parent's * value. Otherwise, it will get the config item's initial value. */ int cs_he_initial_get(const struct ConfigSet *cs, struct HashElem *he, struct Buffer *result) { if (!cs || !he) return CSR_ERR_CODE; struct Inheritance *i = NULL; const struct ConfigDef *cdef = NULL; const struct ConfigSetType *cst = NULL; if (he->type & DT_INHERITED) { i = he->data; cdef = i->parent->data; cst = cs_get_type_def(cs, i->parent->type); } else { cdef = he->data; cst = cs_get_type_def(cs, he->type); } if (!cst) { mutt_debug(LL_DEBUG1, "Variable '%s' has an invalid type %d\n", cdef->name, DTYPE(he->type)); return CSR_ERR_CODE; } return cst->string_get(cs, NULL, cdef, result); }
/** * mutt_socket_readln_d - Read a line from a socket * @param buf Buffer to store the line * @param buflen Length of data to write * @param conn Connection to a server * @param dbg Debug level for logging * @retval >0 Success, number of bytes read * @retval -1 Error */ int mutt_socket_readln_d(char *buf, size_t buflen, struct Connection *conn, int dbg) { char ch; int i; for (i = 0; i < buflen - 1; i++) { if (mutt_socket_readchar(conn, &ch) != 1) { buf[i] = '\0'; return -1; } if (ch == '\n') break; buf[i] = ch; } /* strip \r from \r\n termination */ if (i && (buf[i - 1] == '\r')) i--; buf[i] = '\0'; mutt_debug(dbg, "%d< %s\n", conn->fd, buf); /* number of bytes read, not strlen */ return i + 1; }
/** * mutt_socket_readchar - simple read buffering to speed things up * @param[in] conn Connection to a server * @param[out] c Character that was read * @retval 1 Success * @retval -1 Error */ int mutt_socket_readchar(struct Connection *conn, char *c) { if (conn->bufpos >= conn->available) { if (conn->fd >= 0) conn->available = conn->conn_read(conn, conn->inbuf, sizeof(conn->inbuf)); else { mutt_debug(LL_DEBUG1, "attempt to read from closed connection.\n"); return -1; } conn->bufpos = 0; if (conn->available == 0) { mutt_error(_("Connection to %s closed"), conn->account.host); } if (conn->available <= 0) { mutt_socket_close(conn); return -1; } } *c = conn->inbuf[conn->bufpos]; conn->bufpos++; return 1; }
/** * socket_connect - set up to connect to a socket fd * @param fd File descriptor to connect with * @param sa Address info * @retval 0 Success * @retval >0 An errno, e.g. EPERM * @retval -1 Error */ static int socket_connect(int fd, struct sockaddr *sa) { int sa_size; int save_errno; sigset_t set; if (sa->sa_family == AF_INET) sa_size = sizeof(struct sockaddr_in); #ifdef HAVE_GETADDRINFO else if (sa->sa_family == AF_INET6) sa_size = sizeof(struct sockaddr_in6); #endif else { mutt_debug(LL_DEBUG1, "Unknown address family!\n"); return -1; } if (C_ConnectTimeout > 0) alarm(C_ConnectTimeout); mutt_sig_allow_interrupt(1); /* FreeBSD's connect() does not respect SA_RESTART, meaning * a SIGWINCH will cause the connect to fail. */ sigemptyset(&set); sigaddset(&set, SIGWINCH); sigprocmask(SIG_BLOCK, &set, NULL); save_errno = 0; if (connect(fd, sa, sa_size) < 0) { save_errno = errno; mutt_debug(LL_DEBUG2, "Connection failed. errno: %d\n", errno); SigInt = 0; /* reset in case we caught SIGINTR while in connect() */ } if (C_ConnectTimeout > 0) alarm(0); mutt_sig_allow_interrupt(0); sigprocmask(SIG_UNBLOCK, &set, NULL); return save_errno; }
/** * socket_preconnect - Execute a command before opening a socket * @retval 0 Success * @retval >0 An errno, e.g. EPERM */ static int socket_preconnect(void) { if (!C_Preconnect) return 0; mutt_debug(LL_DEBUG2, "Executing preconnect: %s\n", C_Preconnect); const int rc = mutt_system(C_Preconnect); mutt_debug(LL_DEBUG2, "Preconnect result: %d\n", rc); if (rc != 0) { const int save_errno = errno; mutt_perror(_("Preconnect command failed")); return save_errno; } return 0; }
/** * ssl_load_certificates - Load certificates and filter out the expired ones * @param ctx SSL context * @retval 1 Success * @retval 0 Error * * ssl certificate verification can behave strangely if there are expired certs * loaded into the trusted store. This function filters out expired certs. * * Previously the code used this form: * SSL_CTX_load_verify_locations (ssldata->ctx, #C_CertificateFile, NULL); */ static int ssl_load_certificates(SSL_CTX *ctx) { FILE *fp = NULL; X509 *cert = NULL; X509_STORE *store = NULL; int rc = 1; char buf[256]; mutt_debug(LL_DEBUG2, "loading trusted certificates\n"); store = SSL_CTX_get_cert_store(ctx); if (!store) { store = X509_STORE_new(); SSL_CTX_set_cert_store(ctx, store); } fp = fopen(C_CertificateFile, "rt"); if (!fp) return 0; while (NULL != PEM_read_X509(fp, &cert, NULL, NULL)) { if ((X509_cmp_current_time(X509_get0_notBefore(cert)) >= 0) || (X509_cmp_current_time(X509_get0_notAfter(cert)) <= 0)) { mutt_debug(LL_DEBUG2, "filtering expired cert: %s\n", X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf))); } else { X509_STORE_add_cert(store, cert); } } /* PEM_read_X509 sets the error NO_START_LINE on eof */ if (ERR_GET_REASON(ERR_peek_last_error()) != PEM_R_NO_START_LINE) rc = 0; ERR_clear_error(); X509_free(cert); mutt_file_fclose(&fp); return rc; }
/** * mutt_socket_open - Simple wrapper * @param conn Connection to a server * @retval 0 Success * @retval -1 Error */ int mutt_socket_open(struct Connection *conn) { int rc; if (socket_preconnect()) return -1; rc = conn->conn_open(conn); mutt_debug(LL_DEBUG2, "Connected to %s:%d on fd=%d\n", conn->account.host, conn->account.port, conn->fd); return rc; }
/** * ssl_passwd_cb - Callback to get a password * @param buf Buffer for the password * @param buflen Length of the buffer * @param rwflag 0 if writing, 1 if reading (UNUSED) * @param userdata ConnAccount whose password is requested * @retval >0 Success, number of chars written to buf * @retval 0 Error */ static int ssl_passwd_cb(char *buf, int buflen, int rwflag, void *userdata) { struct ConnAccount *account = userdata; if (mutt_account_getuser(account) < 0) return 0; mutt_debug(LL_DEBUG2, "getting password for %s@%s:%u\n", account->user, account->host, account->port); if (mutt_account_getpass(account) < 0) return 0; return snprintf(buf, buflen, "%s", account->pass); }
/** * cs_remove_listener - Remove a listener (callback function) * @param cs Config items * @param fn Listener callback function */ void cs_remove_listener(struct ConfigSet *cs, cs_listener fn) { if (!cs || !fn) return; for (size_t i = 0; i < mutt_array_size(cs->listeners); i++) { if (cs->listeners[i] == fn) { cs->listeners[i] = NULL; return; } } mutt_debug(LL_DEBUG1, "Listener wasn't registered\n"); }
/** * cs_str_native_set - Natively set the value of a string config item * @param cs Config items * @param name Name of config item * @param value Native pointer/value to set * @param err Buffer for error messages * @retval int Result, e.g. #CSR_SUCCESS */ int cs_str_native_set(const struct ConfigSet *cs, const char *name, intptr_t value, struct Buffer *err) { if (!cs || !name) return CSR_ERR_CODE; /* LCOV_EXCL_LINE */ struct HashElem *he = cs_get_elem(cs, name); if (!he) { mutt_buffer_printf(err, "Unknown var '%s'", name); return CSR_ERR_UNKNOWN; } const struct ConfigDef *cdef = NULL; const struct ConfigSetType *cst = NULL; void *var = NULL; if (he->type & DT_INHERITED) { struct Inheritance *i = he->data; cdef = i->parent->data; var = &i->var; cst = cs_get_type_def(cs, i->parent->type); } else { cdef = he->data; var = cdef->var; cst = cs_get_type_def(cs, he->type); } if (!cst) { mutt_debug(LL_DEBUG1, "Variable '%s' has an invalid type %d\n", cdef->name, he->type); return CSR_ERR_CODE; } int rc = cst->native_set(cs, var, cdef, value, err); if (CSR_RESULT(rc) == CSR_SUCCESS) { if (he->type & DT_INHERITED) he->type = cdef->type | DT_INHERITED; if (!(rc & CSR_SUC_NO_CHANGE)) cs_notify_listeners(cs, he, cdef->name, CE_SET); } return rc; }
/** * mutt_socket_close - Close a socket * @param conn Connection to a server * @retval 0 Success * @retval -1 Error */ int mutt_socket_close(struct Connection *conn) { if (!conn) return 0; int rc = -1; if (conn->fd < 0) mutt_debug(LL_DEBUG1, "Attempt to close closed connection.\n"); else rc = conn->conn_close(conn); conn->fd = -1; conn->ssf = 0; return rc; }
/** * cs_he_string_set - Set a config item by string * @param cs Config items * @param he HashElem representing config item * @param value Value to set * @param err Buffer for error messages * @retval int Result, e.g. #CSR_SUCCESS */ int cs_he_string_set(const struct ConfigSet *cs, struct HashElem *he, const char *value, struct Buffer *err) { if (!cs || !he) return CSR_ERR_CODE; struct ConfigDef *cdef = NULL; const struct ConfigSetType *cst = NULL; void *var = NULL; if (he->type & DT_INHERITED) { struct Inheritance *i = he->data; cdef = i->parent->data; var = &i->var; cst = cs_get_type_def(cs, i->parent->type); } else { cdef = he->data; var = cdef->var; cst = cs_get_type_def(cs, he->type); } if (!cst) { mutt_debug(LL_DEBUG1, "Variable '%s' has an invalid type %d\n", cdef->name, he->type); return CSR_ERR_CODE; } if (!var) return CSR_ERR_CODE; /* LCOV_EXCL_LINE */ int rc = cst->string_set(cs, var, cdef, value, err); if (CSR_RESULT(rc) != CSR_SUCCESS) return rc; if (he->type & DT_INHERITED) { struct Inheritance *i = he->data; he->type = i->parent->type | DT_INHERITED; } if (!(rc & CSR_SUC_NO_CHANGE)) cs_notify_listeners(cs, he, he->key.strkey, CE_SET); return rc; }
/** * mutt_progress_init - Set up a progress bar * @param progress Progress bar * @param msg Message to display * @param flags Flags, e.g. #MUTT_PROGRESS_SIZE * @param inc Increments to display (0 disables updates) * @param size Total size of expected file / traffic */ void mutt_progress_init(struct Progress *progress, const char *msg, unsigned short flags, unsigned short inc, size_t size) { struct timeval tv = { 0, 0 }; if (!progress) return; if (OptNoCurses) return; memset(progress, 0, sizeof(struct Progress)); progress->inc = inc; progress->flags = flags; progress->msg = msg; progress->size = size; if (progress->size != 0) { if (progress->flags & MUTT_PROGRESS_SIZE) { mutt_str_pretty_size(progress->sizestr, sizeof(progress->sizestr), progress->size); } else snprintf(progress->sizestr, sizeof(progress->sizestr), "%zu", progress->size); } if (inc == 0) { if (size != 0) mutt_message("%s (%s)", msg, progress->sizestr); else mutt_message(msg); return; } if (gettimeofday(&tv, NULL) < 0) mutt_debug(LL_DEBUG1, "gettimeofday failed: %d\n", errno); /* if timestamp is 0 no time-based suppression is done */ if (C_TimeInc != 0) { progress->timestamp = ((unsigned int) tv.tv_sec * 1000) + (unsigned int) (tv.tv_usec / 1000); } mutt_progress_update(progress, 0, 0); }
/** * cs_register_variables - Register a set of config items * @param cs Config items * @param vars Variable definition * @param flags Flags, e.g. #CS_REG_DISABLED * @retval bool True, if all variables were registered successfully */ bool cs_register_variables(const struct ConfigSet *cs, struct ConfigDef vars[], int flags) { if (!cs || !vars) return false; struct Buffer *err = mutt_buffer_pool_get(); bool rc = true; for (size_t i = 0; vars[i].name; i++) { if (!reg_one_var(cs, &vars[i], err)) { mutt_debug(LL_DEBUG1, "%s\n", mutt_b2s(err)); rc = false; } } mutt_buffer_pool_release(&err); return rc; }
/** * cs_he_string_get - Get a config item as a string * @param cs Config items * @param he HashElem representing config item * @param result Buffer for results or error messages * @retval int Result, e.g. #CSR_SUCCESS */ int cs_he_string_get(const struct ConfigSet *cs, struct HashElem *he, struct Buffer *result) { if (!cs || !he) return CSR_ERR_CODE; struct Inheritance *i = NULL; const struct ConfigDef *cdef = NULL; const struct ConfigSetType *cst = NULL; void *var = NULL; if (he->type & DT_INHERITED) { i = he->data; cdef = i->parent->data; cst = cs_get_type_def(cs, i->parent->type); } else { cdef = he->data; cst = cs_get_type_def(cs, he->type); } if ((he->type & DT_INHERITED) && (DTYPE(he->type) != 0)) { var = &i->var; /* Local value */ } else { var = cdef->var; /* Normal var */ } if (!cst) { mutt_debug(LL_DEBUG1, "Variable '%s' has an invalid type %d\n", cdef->name, DTYPE(he->type)); return CSR_ERR_CODE; } return cst->string_get(cs, var, cdef, result); }
/** * ssl_dprint_err_stack - Dump the SSL error stack */ static void ssl_dprint_err_stack(void) { BIO *bio = NULL; char *buf = NULL; long buflen; char *output = NULL; bio = BIO_new(BIO_s_mem()); if (!bio) return; ERR_print_errors(bio); buflen = BIO_get_mem_data(bio, &buf); if (buflen > 0) { output = mutt_mem_malloc(buflen + 1); memcpy(output, buf, buflen); output[buflen] = '\0'; mutt_debug(LL_DEBUG1, "SSL error stack: %s\n", output); FREE(&output); } BIO_free(bio); }
/** * cs_add_listener - Add a listener (callback function) * @param cs Config items * @param fn Listener callback function */ void cs_add_listener(struct ConfigSet *cs, cs_listener fn) { if (!cs || !fn) return; for (size_t i = 0; i < mutt_array_size(cs->listeners); i++) { if (cs->listeners[i] == fn) { mutt_debug(LL_DEBUG1, "Listener was already registered\n"); return; } } for (size_t i = 0; i < mutt_array_size(cs->listeners); i++) { if (!cs->listeners[i]) { cs->listeners[i] = fn; return; } } }
/** * cs_register_variables - Register a set of config items * @param cs Config items * @param vars Variable definition * @param flags Flags, e.g. #CS_REG_DISABLED * @retval bool True, if all variables were registered successfully */ bool cs_register_variables(const struct ConfigSet *cs, struct ConfigDef vars[], int flags) { if (!cs || !vars) return CSR_ERR_CODE; /* LCOV_EXCL_LINE */ struct Buffer err; mutt_buffer_init(&err); err.dsize = 256; err.data = calloc(1, err.dsize); bool rc = true; for (size_t i = 0; vars[i].name; i++) { if (!reg_one_var(cs, &vars[i], &err)) { mutt_debug(LL_DEBUG1, "%s\n", err.data); rc = false; } } FREE(&err.data); return rc; }
/** * print_gss_error - Print detailed error message to the debug log * @param err_maj Error's major number * @param err_min Error's minor number */ static void print_gss_error(OM_uint32 err_maj, OM_uint32 err_min) { OM_uint32 maj_stat, min_stat; OM_uint32 msg_ctx = 0; gss_buffer_desc status_string; char buf_maj[512]; char buf_min[512]; do { maj_stat = gss_display_status(&min_stat, err_maj, GSS_C_GSS_CODE, GSS_C_NO_OID, &msg_ctx, &status_string); if (GSS_ERROR(maj_stat)) break; size_t status_len = status_string.length; if (status_len >= sizeof(buf_maj)) status_len = sizeof(buf_maj) - 1; strncpy(buf_maj, (char *) status_string.value, status_len); buf_maj[status_len] = '\0'; gss_release_buffer(&min_stat, &status_string); maj_stat = gss_display_status(&min_stat, err_min, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &status_string); if (!GSS_ERROR(maj_stat)) { status_len = status_string.length; if (status_len >= sizeof(buf_min)) status_len = sizeof(buf_min) - 1; strncpy(buf_min, (char *) status_string.value, status_len); buf_min[status_len] = '\0'; gss_release_buffer(&min_stat, &status_string); } } while (!GSS_ERROR(maj_stat) && msg_ctx != 0); mutt_debug(LL_DEBUG2, "((%s:%d )(%s:%d))\n", buf_maj, err_maj, buf_min, err_min); }
/** * ssl_err - Display an SSL error message * @param data SSL socket data * @param err SSL error code */ static void ssl_err(struct SslSockData *data, int err) { int e = SSL_get_error(data->ssl, err); switch (e) { case SSL_ERROR_NONE: return; case SSL_ERROR_ZERO_RETURN: data->isopen = 0; break; case SSL_ERROR_SYSCALL: data->isopen = 0; break; } const char *errmsg = NULL; unsigned long sslerr; switch (e) { case SSL_ERROR_SYSCALL: errmsg = "I/O error"; break; case SSL_ERROR_WANT_ACCEPT: errmsg = "retry accept"; break; case SSL_ERROR_WANT_CONNECT: errmsg = "retry connect"; break; case SSL_ERROR_WANT_READ: errmsg = "retry read"; break; case SSL_ERROR_WANT_WRITE: errmsg = "retry write"; break; case SSL_ERROR_WANT_X509_LOOKUP: errmsg = "retry x509 lookup"; break; case SSL_ERROR_ZERO_RETURN: errmsg = "SSL connection closed"; break; case SSL_ERROR_SSL: sslerr = ERR_get_error(); switch (sslerr) { case 0: switch (err) { case 0: errmsg = "EOF"; break; default: errmsg = strerror(errno); } break; default: errmsg = ERR_error_string(sslerr, NULL); } break; default: errmsg = "unknown error"; } mutt_debug(LL_DEBUG1, "SSL error: %s\n", errmsg); }
/** * interactive_check_cert - Ask the user if a certificate is valid * @param cert Certificate * @param idx Place of certificate in the chain * @param len Length of the certificate chain * @param ssl SSL state * @param allow_always If certificate may be always allowed * @retval true User selected 'skip' * @retval false Otherwise */ static bool interactive_check_cert(X509 *cert, int idx, size_t len, SSL *ssl, bool allow_always) { static const int part[] = { NID_commonName, /* CN */ NID_pkcs9_emailAddress, /* Email */ NID_organizationName, /* O */ NID_organizationalUnitName, /* OU */ NID_localityName, /* L */ NID_stateOrProvinceName, /* ST */ NID_countryName, /* C */ }; X509_NAME *x509_subject = NULL; X509_NAME *x509_issuer = NULL; char helpstr[1024]; char buf[256]; char title[256]; struct Menu *menu = mutt_menu_new(MENU_GENERIC); int done, row; FILE *fp = NULL; int ALLOW_SKIP = 0; /* All caps tells Coverity that this is effectively a preproc condition */ mutt_menu_push_current(menu); menu->max = mutt_array_size(part) * 2 + 11; menu->dialog = mutt_mem_calloc(1, menu->max * sizeof(char *)); for (int i = 0; i < menu->max; i++) menu->dialog[i] = mutt_mem_calloc(1, dialog_row_len * sizeof(char)); row = 0; mutt_str_strfcpy(menu->dialog[row], _("This certificate belongs to:"), dialog_row_len); row++; x509_subject = X509_get_subject_name(cert); for (unsigned int u = 0; u < mutt_array_size(part); u++) { snprintf(menu->dialog[row++], dialog_row_len, " %s", x509_get_part(x509_subject, part[u])); } row++; mutt_str_strfcpy(menu->dialog[row], _("This certificate was issued by:"), dialog_row_len); row++; x509_issuer = X509_get_issuer_name(cert); for (unsigned int u = 0; u < mutt_array_size(part); u++) { snprintf(menu->dialog[row++], dialog_row_len, " %s", x509_get_part(x509_issuer, part[u])); } row++; snprintf(menu->dialog[row++], dialog_row_len, "%s", _("This certificate is valid")); snprintf(menu->dialog[row++], dialog_row_len, _(" from %s"), asn1time_to_string(X509_getm_notBefore(cert))); snprintf(menu->dialog[row++], dialog_row_len, _(" to %s"), asn1time_to_string(X509_getm_notAfter(cert))); row++; buf[0] = '\0'; x509_fingerprint(buf, sizeof(buf), cert, EVP_sha1); snprintf(menu->dialog[row++], dialog_row_len, _("SHA1 Fingerprint: %s"), buf); buf[0] = '\0'; buf[40] = '\0'; /* Ensure the second printed line is null terminated */ x509_fingerprint(buf, sizeof(buf), cert, EVP_sha256); buf[39] = '\0'; /* Divide into two lines of output */ snprintf(menu->dialog[row++], dialog_row_len, "%s%s", _("SHA256 Fingerprint: "), buf); snprintf(menu->dialog[row++], dialog_row_len, "%*s%s", (int) mutt_str_strlen(_("SHA256 Fingerprint: ")), "", buf + 40); snprintf(title, sizeof(title), _("SSL Certificate check (certificate %zu of %zu in chain)"), len - idx, len); menu->title = title; /* The leaf/host certificate can't be skipped. */ #ifdef HAVE_SSL_PARTIAL_CHAIN if ((idx != 0) && C_SslVerifyPartialChains) ALLOW_SKIP = 1; #endif /* Inside ssl_verify_callback(), this function is guarded by a call to * check_certificate_by_digest(). This means if check_certificate_expiration() is * true, then check_certificate_file() must be false. Therefore we don't need * to also scan the certificate file here. */ allow_always = allow_always && C_CertificateFile && check_certificate_expiration(cert, true); /* L10N: These four letters correspond to the choices in the next four strings: (r)eject, accept (o)nce, (a)ccept always, (s)kip. These prompts are the interactive certificate confirmation prompts for an OpenSSL connection. */ menu->keys = _("roas"); if (allow_always) { if (ALLOW_SKIP) menu->prompt = _("(r)eject, accept (o)nce, (a)ccept always, (s)kip"); else menu->prompt = _("(r)eject, accept (o)nce, (a)ccept always"); } else { if (ALLOW_SKIP) menu->prompt = _("(r)eject, accept (o)nce, (s)kip"); else menu->prompt = _("(r)eject, accept (o)nce"); } helpstr[0] = '\0'; mutt_make_help(buf, sizeof(buf), _("Exit "), MENU_GENERIC, OP_EXIT); mutt_str_strcat(helpstr, sizeof(helpstr), buf); mutt_make_help(buf, sizeof(buf), _("Help"), MENU_GENERIC, OP_HELP); mutt_str_strcat(helpstr, sizeof(helpstr), buf); menu->help = helpstr; done = 0; OptIgnoreMacroEvents = true; while (done == 0) { switch (mutt_menu_loop(menu)) { case -1: /* abort */ case OP_MAX + 1: /* reject */ case OP_EXIT: done = 1; break; case OP_MAX + 3: /* accept always */ if (!allow_always) break; done = 0; fp = fopen(C_CertificateFile, "a"); if (fp) { if (PEM_write_X509(fp, cert)) done = 1; mutt_file_fclose(&fp); } if (done == 0) { mutt_error(_("Warning: Couldn't save certificate")); } else { mutt_message(_("Certificate saved")); mutt_sleep(0); } /* fallthrough */ case OP_MAX + 2: /* accept once */ done = 2; SSL_set_ex_data(ssl, SkipModeExDataIndex, NULL); ssl_cache_trusted_cert(cert); break; case OP_MAX + 4: /* skip */ if (!ALLOW_SKIP) break; done = 2; SSL_set_ex_data(ssl, SkipModeExDataIndex, &SkipModeExDataIndex); break; } } OptIgnoreMacroEvents = false; mutt_menu_pop_current(menu); mutt_menu_destroy(&menu); mutt_debug(LL_DEBUG2, "done=%d\n", done); return done == 2; }
/** * imap_auth_gss - GSS Authentication support * @param adata Imap Account data * @param method Name of this authentication method * @retval enum Result, e.g. #IMAP_AUTH_SUCCESS */ enum ImapAuthRes imap_auth_gss(struct ImapAccountData *adata, const char *method) { gss_buffer_desc request_buf, send_token; gss_buffer_t sec_token; gss_name_t target_name; gss_ctx_id_t context; gss_OID mech_name; char server_conf_flags; gss_qop_t quality; int cflags; OM_uint32 maj_stat, min_stat; unsigned long buf_size; int rc, retval = IMAP_AUTH_FAILURE; if (!(adata->capabilities & IMAP_CAP_AUTH_GSSAPI)) return IMAP_AUTH_UNAVAIL; if (mutt_account_getuser(&adata->conn->account) < 0) return IMAP_AUTH_FAILURE; struct Buffer *buf1 = mutt_buffer_pool_get(); struct Buffer *buf2 = mutt_buffer_pool_get(); /* get an IMAP service ticket for the server */ mutt_buffer_printf(buf1, "imap@%s", adata->conn->account.host); request_buf.value = buf1->data; request_buf.length = mutt_buffer_len(buf1); maj_stat = gss_import_name(&min_stat, &request_buf, gss_nt_service_name, &target_name); if (maj_stat != GSS_S_COMPLETE) { mutt_debug(LL_DEBUG2, "Couldn't get service name for [%s]\n", buf1); retval = IMAP_AUTH_UNAVAIL; goto cleanup; } else if (C_DebugLevel >= 2) { gss_display_name(&min_stat, target_name, &request_buf, &mech_name); mutt_debug(LL_DEBUG2, "Using service name [%s]\n", (char *) request_buf.value); gss_release_buffer(&min_stat, &request_buf); } /* Acquire initial credentials - without a TGT GSSAPI is UNAVAIL */ sec_token = GSS_C_NO_BUFFER; context = GSS_C_NO_CONTEXT; /* build token */ maj_stat = gss_init_sec_context(&min_stat, GSS_C_NO_CREDENTIAL, &context, target_name, GSS_C_NO_OID, GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG, 0, GSS_C_NO_CHANNEL_BINDINGS, sec_token, NULL, &send_token, (unsigned int *) &cflags, NULL); if ((maj_stat != GSS_S_COMPLETE) && (maj_stat != GSS_S_CONTINUE_NEEDED)) { print_gss_error(maj_stat, min_stat); mutt_debug(LL_DEBUG1, "Error acquiring credentials - no TGT?\n"); gss_release_name(&min_stat, &target_name); retval = IMAP_AUTH_UNAVAIL; goto cleanup; } /* now begin login */ mutt_message(_("Authenticating (GSSAPI)...")); imap_cmd_start(adata, "AUTHENTICATE GSSAPI"); /* expect a null continuation response ("+") */ do rc = imap_cmd_step(adata); while (rc == IMAP_CMD_CONTINUE); if (rc != IMAP_CMD_RESPOND) { mutt_debug(LL_DEBUG2, "Invalid response from server: %s\n", buf1); gss_release_name(&min_stat, &target_name); goto bail; } /* now start the security context initialisation loop... */ mutt_debug(LL_DEBUG2, "Sending credentials\n"); mutt_b64_buffer_encode(buf1, send_token.value, send_token.length); gss_release_buffer(&min_stat, &send_token); mutt_buffer_addstr(buf1, "\r\n"); mutt_socket_send(adata->conn, mutt_b2s(buf1)); while (maj_stat == GSS_S_CONTINUE_NEEDED) { /* Read server data */ do rc = imap_cmd_step(adata); while (rc == IMAP_CMD_CONTINUE); if (rc != IMAP_CMD_RESPOND) { mutt_debug(LL_DEBUG1, "#1 Error receiving server response\n"); gss_release_name(&min_stat, &target_name); goto bail; } if (mutt_b64_buffer_decode(buf2, adata->buf + 2) < 0) { mutt_debug(LL_DEBUG1, "Invalid base64 server response\n"); gss_release_name(&min_stat, &target_name); goto err_abort_cmd; } request_buf.value = buf2->data; request_buf.length = mutt_buffer_len(buf2); sec_token = &request_buf; /* Write client data */ maj_stat = gss_init_sec_context( &min_stat, GSS_C_NO_CREDENTIAL, &context, target_name, GSS_C_NO_OID, GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG, 0, GSS_C_NO_CHANNEL_BINDINGS, sec_token, NULL, &send_token, (unsigned int *) &cflags, NULL); if ((maj_stat != GSS_S_COMPLETE) && (maj_stat != GSS_S_CONTINUE_NEEDED)) { print_gss_error(maj_stat, min_stat); mutt_debug(LL_DEBUG1, "Error exchanging credentials\n"); gss_release_name(&min_stat, &target_name); goto err_abort_cmd; } mutt_b64_buffer_encode(buf1, send_token.value, send_token.length); gss_release_buffer(&min_stat, &send_token); mutt_buffer_addstr(buf1, "\r\n"); mutt_socket_send(adata->conn, mutt_b2s(buf1)); } gss_release_name(&min_stat, &target_name); /* get security flags and buffer size */ do rc = imap_cmd_step(adata); while (rc == IMAP_CMD_CONTINUE); if (rc != IMAP_CMD_RESPOND) { mutt_debug(LL_DEBUG1, "#2 Error receiving server response\n"); goto bail; } if (mutt_b64_buffer_decode(buf2, adata->buf + 2) < 0) { mutt_debug(LL_DEBUG1, "Invalid base64 server response\n"); goto err_abort_cmd; } request_buf.value = buf2->data; request_buf.length = mutt_buffer_len(buf2); maj_stat = gss_unwrap(&min_stat, context, &request_buf, &send_token, &cflags, &quality); if (maj_stat != GSS_S_COMPLETE) { print_gss_error(maj_stat, min_stat); mutt_debug(LL_DEBUG2, "Couldn't unwrap security level data\n"); gss_release_buffer(&min_stat, &send_token); goto err_abort_cmd; } mutt_debug(LL_DEBUG2, "Credential exchange complete\n"); /* first octet is security levels supported. We want NONE */ server_conf_flags = ((char *) send_token.value)[0]; if (!(((char *) send_token.value)[0] & GSS_AUTH_P_NONE)) { mutt_debug(LL_DEBUG2, "Server requires integrity or privacy\n"); gss_release_buffer(&min_stat, &send_token); goto err_abort_cmd; } /* we don't care about buffer size if we don't wrap content. But here it is */ ((char *) send_token.value)[0] = '\0'; buf_size = ntohl(*((long *) send_token.value)); gss_release_buffer(&min_stat, &send_token); mutt_debug(LL_DEBUG2, "Unwrapped security level flags: %c%c%c\n", (server_conf_flags & GSS_AUTH_P_NONE) ? 'N' : '-', (server_conf_flags & GSS_AUTH_P_INTEGRITY) ? 'I' : '-', (server_conf_flags & GSS_AUTH_P_PRIVACY) ? 'P' : '-'); mutt_debug(LL_DEBUG2, "Maximum GSS token size is %ld\n", buf_size); /* agree to terms (hack!) */ buf_size = htonl(buf_size); /* not relevant without integrity/privacy */ mutt_buffer_reset(buf1); mutt_buffer_addch(buf1, GSS_AUTH_P_NONE); mutt_buffer_addstr_n(buf1, ((char *) &buf_size) + 1, 3); /* server decides if principal can log in as user */ mutt_buffer_addstr(buf1, adata->conn->account.user); request_buf.value = buf1->data; request_buf.length = mutt_buffer_len(buf1); maj_stat = gss_wrap(&min_stat, context, 0, GSS_C_QOP_DEFAULT, &request_buf, &cflags, &send_token); if (maj_stat != GSS_S_COMPLETE) { mutt_debug(LL_DEBUG2, "Error creating login request\n"); goto err_abort_cmd; } mutt_b64_buffer_encode(buf1, send_token.value, send_token.length); mutt_debug(LL_DEBUG2, "Requesting authorisation as %s\n", adata->conn->account.user); mutt_buffer_addstr(buf1, "\r\n"); mutt_socket_send(adata->conn, mutt_b2s(buf1)); /* Joy of victory or agony of defeat? */ do rc = imap_cmd_step(adata); while (rc == IMAP_CMD_CONTINUE); if (rc == IMAP_CMD_RESPOND) { mutt_debug(LL_DEBUG1, "Unexpected server continuation request\n"); goto err_abort_cmd; } if (imap_code(adata->buf)) { /* flush the security context */ mutt_debug(LL_DEBUG2, "Releasing GSS credentials\n"); maj_stat = gss_delete_sec_context(&min_stat, &context, &send_token); if (maj_stat != GSS_S_COMPLETE) mutt_debug(LL_DEBUG1, "Error releasing credentials\n"); /* send_token may contain a notification to the server to flush * credentials. RFC1731 doesn't specify what to do, and since this * support is only for authentication, we'll assume the server knows * enough to flush its own credentials */ gss_release_buffer(&min_stat, &send_token); retval = IMAP_AUTH_SUCCESS; goto cleanup; } else goto bail; err_abort_cmd: mutt_socket_send(adata->conn, "*\r\n"); do rc = imap_cmd_step(adata); while (rc == IMAP_CMD_CONTINUE); bail: mutt_error(_("GSSAPI authentication failed")); retval = IMAP_AUTH_FAILURE; cleanup: mutt_buffer_pool_release(&buf1); mutt_buffer_pool_release(&buf2); return retval; }