/** * format_s_x - Format a string like snprintf() * @param[out] buf Buffer in which to save string * @param[in] buflen Buffer length * @param[in] prec Field precision, e.g. "-3.4" * @param[in] s String to format * @param[in] arboreal If true, string contains graphical tree characters * * This formats a string rather like: * - snprintf(fmt, sizeof(fmt), "%%%ss", prec); * - snprintf(buf, buflen, fmt, s); * except that the numbers in the conversion specification refer to * the number of character cells when printed. */ static void format_s_x(char *buf, size_t buflen, const char *prec, const char *s, int arboreal) { int justify = FMT_RIGHT; char *p = NULL; int min_width; int max_width = INT_MAX; if (*prec == '-') { prec++; justify = FMT_LEFT; } else if (*prec == '=') { prec++; justify = FMT_CENTER; } min_width = strtol(prec, &p, 10); if (*p == '.') { prec = p + 1; max_width = strtol(prec, &p, 10); if (p <= prec) max_width = INT_MAX; } mutt_simple_format(buf, buflen, min_width, max_width, justify, ' ', s, mutt_str_strlen(s), arboreal); }
/** * mutt_paddstr - Display a string on screen, padded if necessary * @param n Final width of field * @param s String to display */ void mutt_paddstr(int n, const char *s) { wchar_t wc; size_t k; size_t len = mutt_str_strlen(s); mbstate_t mbstate; memset(&mbstate, 0, sizeof(mbstate)); for (; len && (k = mbrtowc(&wc, s, len, &mbstate)); s += k, len -= k) { if ((k == (size_t)(-1)) || (k == (size_t)(-2))) { if (k == (size_t)(-1)) memset(&mbstate, 0, sizeof(mbstate)); k = (k == (size_t)(-1)) ? 1 : len; wc = ReplacementChar; } if (!IsWPrint(wc)) wc = '?'; const int w = wcwidth(wc); if (w >= 0) { if (w > n) break; addnstr((char *) s, k); n -= w; } } while (n-- > 0) addch(' '); }
/** * mutt_strwidth - Measure a string's width in screen cells * @param s String to be measured * @retval num Screen cells string would use */ int mutt_strwidth(const char *s) { wchar_t wc; int w; size_t k, n; mbstate_t mbstate; if (!s) return 0; n = mutt_str_strlen(s); memset(&mbstate, 0, sizeof(mbstate)); for (w = 0; n && (k = mbrtowc(&wc, s, n, &mbstate)); s += k, n -= k) { if (*s == MUTT_SPECIAL_INDEX) { s += 2; /* skip the index coloring sequence */ k = 0; continue; } if ((k == (size_t)(-1)) || (k == (size_t)(-2))) { if (k == (size_t)(-1)) memset(&mbstate, 0, sizeof(mbstate)); k = (k == (size_t)(-1)) ? 1 : n; wc = ReplacementChar; } if (!IsWPrint(wc)) wc = '?'; w += wcwidth(wc); } return w; }
/** * nntp_complete - Auto-complete NNTP newsgroups * @param buf Buffer containing pathname * @param buflen Length of buffer * @retval 0 Match found * @retval -1 No matches * * XXX rules */ int nntp_complete(char *buf, size_t buflen) { struct NntpAccountData *adata = CurrentNewsSrv; size_t n = 0; char filepart[PATH_MAX]; bool init = false; mutt_str_strfcpy(filepart, buf, sizeof(filepart)); /* special case to handle when there is no filepart yet * find the first subscribed newsgroup */ int len = mutt_str_strlen(filepart); if (len == 0) { for (; n < adata->groups_num; n++) { struct NntpMboxData *mdata = adata->groups_list[n]; if (mdata && mdata->subscribed) { mutt_str_strfcpy(filepart, mdata->group, sizeof(filepart)); init = true; n++; break; } } } for (; n < adata->groups_num; n++) { struct NntpMboxData *mdata = adata->groups_list[n]; if (mdata && mdata->subscribed && (mutt_str_strncmp(mdata->group, filepart, len) == 0)) { if (init) { size_t i; for (i = 0; filepart[i] && mdata->group[i]; i++) { if (filepart[i] != mdata->group[i]) { filepart[i] = '\0'; break; } } filepart[i] = '\0'; } else { mutt_str_strfcpy(filepart, mdata->group, sizeof(filepart)); init = true; } } } mutt_str_strfcpy(buf, filepart, buflen); return init ? 0 : -1; }
/** * mutt_unget_string - Return a string to the input buffer * @param s String to return * * This puts events into the `UngetKeyEvents` buffer */ void mutt_unget_string(const char *s) { const char *p = s + mutt_str_strlen(s) - 1; while (p >= s) { mutt_unget_event((unsigned char) *p--, 0); } }
/** * tls_set_priority - Set TLS algorithm priorities * @param data TLS socket data * @retval 0 Success * @retval -1 Error */ static int tls_set_priority(struct TlsSockData *data) { size_t nproto = 4; size_t priority_size; priority_size = mutt_str_strlen(C_SslCiphers) + 128; char *priority = mutt_mem_malloc(priority_size); priority[0] = '\0'; if (C_SslCiphers) mutt_str_strcat(priority, priority_size, C_SslCiphers); else mutt_str_strcat(priority, priority_size, "NORMAL"); if (!C_SslUseTlsv12) { nproto--; mutt_str_strcat(priority, priority_size, ":-VERS-TLS1.2"); } if (!C_SslUseTlsv11) { nproto--; mutt_str_strcat(priority, priority_size, ":-VERS-TLS1.1"); } if (!C_SslUseTlsv1) { nproto--; mutt_str_strcat(priority, priority_size, ":-VERS-TLS1.0"); } if (!C_SslUseSslv3) { nproto--; mutt_str_strcat(priority, priority_size, ":-VERS-SSL3.0"); } if (nproto == 0) { mutt_error(_("All available protocols for TLS/SSL connection disabled")); FREE(&priority); return -1; } int err = gnutls_priority_set_direct(data->state, priority, NULL); if (err < 0) { mutt_error("gnutls_priority_set_direct(%s): %s", priority, gnutls_strerror(err)); FREE(&priority); return -1; } FREE(&priority); return 0; }
/** * imap_auth_oauth - Authenticate an IMAP connection using OAUTHBEARER * @param adata Imap Account data * @param method Name of this authentication method (UNUSED) * @retval num Result, e.g. #IMAP_AUTH_SUCCESS */ enum ImapAuthRes imap_auth_oauth(struct ImapAccountData *adata, const char *method) { char *ibuf = NULL; char *oauthbearer = NULL; int ilen; int rc; /* For now, we only support SASL_IR also and over TLS */ if (!(adata->capabilities & IMAP_CAP_AUTH_OAUTHBEARER) || !(adata->capabilities & IMAP_CAP_SASL_IR) || !adata->conn->ssf) { return IMAP_AUTH_UNAVAIL; } /* If they did not explicitly request or configure oauth then fail quietly */ if (!(method || (C_ImapOauthRefreshCommand && *C_ImapOauthRefreshCommand))) return IMAP_AUTH_UNAVAIL; mutt_message(_("Authenticating (OAUTHBEARER)...")); /* We get the access token from the imap_oauth_refresh_command */ oauthbearer = mutt_account_getoauthbearer(&adata->conn->account); if (!oauthbearer) return IMAP_AUTH_FAILURE; ilen = mutt_str_strlen(oauthbearer) + 30; ibuf = mutt_mem_malloc(ilen); snprintf(ibuf, ilen, "AUTHENTICATE OAUTHBEARER %s", oauthbearer); /* This doesn't really contain a password, but the token is good for * an hour, so suppress it anyways. */ rc = imap_exec(adata, ibuf, IMAP_CMD_PASS); FREE(&oauthbearer); FREE(&ibuf); if (rc != IMAP_EXEC_SUCCESS) { /* The error response was in SASL continuation, so continue the SASL * to cause a failure and exit SASL input. See RFC 7628 3.2.3 */ mutt_socket_send(adata->conn, "\001"); rc = imap_exec(adata, ibuf, IMAP_CMD_NO_FLAGS); } if (rc == IMAP_EXEC_SUCCESS) { mutt_clear_error(); return IMAP_AUTH_SUCCESS; } mutt_error(_("OAUTHBEARER authentication failed.")); return IMAP_AUTH_FAILURE; }
/** * mutt_enter_fname_full - Ask the user to select a file * @param[in] prompt Prompt * @param[in] buf Buffer for the result * @param[in] buflen Length of the buffer * @param[in] mailbox If true, select mailboxes * @param[in] multiple Allow multiple selections * @param[out] files List of files selected * @param[out] numfiles Number of files selected * @param[in] flags Flags, see #SelectFileFlags * @retval 0 Success * @retval -1 Error */ int mutt_enter_fname_full(const char *prompt, char *buf, size_t buflen, bool mailbox, bool multiple, char ***files, int *numfiles, SelectFileFlags flags) { struct Event ch; SETCOLOR(MT_COLOR_PROMPT); mutt_window_mvaddstr(MuttMessageWindow, 0, 0, (char *) prompt); addstr(_(" ('?' for list): ")); NORMAL_COLOR; if (buf[0] != '\0') addstr(buf); mutt_window_clrtoeol(MuttMessageWindow); mutt_refresh(); do { ch = mutt_getch(); } while (ch.ch == -2); if (ch.ch < 0) { mutt_window_clearline(MuttMessageWindow, 0); return -1; } else if (ch.ch == '?') { mutt_refresh(); buf[0] = '\0'; if (!flags) flags = MUTT_SEL_FOLDER; if (multiple) flags |= MUTT_SEL_MULTI; if (mailbox) flags |= MUTT_SEL_MAILBOX; mutt_select_file(buf, buflen, flags, files, numfiles); } else { char *pc = mutt_mem_malloc(mutt_str_strlen(prompt) + 3); sprintf(pc, "%s: ", prompt); mutt_unget_event(ch.op ? 0 : ch.ch, ch.op ? ch.op : 0); if (mutt_get_field_full(pc, buf, buflen, (mailbox ? MUTT_EFILE : MUTT_FILE) | MUTT_CLEAR, multiple, files, numfiles) != 0) { buf[0] = '\0'; } FREE(&pc); } return 0; }
/** * mutt_wstr_trunc - Work out how to truncate a widechar string * @param[in] src String to measure * @param[in] maxlen Maximum length of string in bytes * @param[in] maxwid Maximum width in screen columns * @param[out] width Save the truncated screen column width * @retval num Bytes to use * * See how many bytes to copy from string so it's at most maxlen bytes long and * maxwid columns wide */ size_t mutt_wstr_trunc(const char *src, size_t maxlen, size_t maxwid, size_t *width) { wchar_t wc; size_t n, w = 0, l = 0, cl; int cw; mbstate_t mbstate; if (!src) goto out; n = mutt_str_strlen(src); memset(&mbstate, 0, sizeof(mbstate)); for (w = 0; n && (cl = mbrtowc(&wc, src, n, &mbstate)); src += cl, n -= cl) { if ((cl == (size_t)(-1)) || (cl == (size_t)(-2))) { if (cl == (size_t)(-1)) memset(&mbstate, 0, sizeof(mbstate)); cl = (cl == (size_t)(-1)) ? 1 : n; wc = ReplacementChar; } cw = wcwidth(wc); /* hack because MUTT_TREE symbols aren't turned into characters * until rendered by print_enriched_string (#3364) */ if ((cw < 0) && (src[0] == MUTT_SPECIAL_INDEX)) { cl = 2; /* skip the index coloring sequence */ cw = 0; } else if ((cw < 0) && (cl == 1) && (src[0] != '\0') && (src[0] < MUTT_TREE_MAX)) cw = 1; else if (cw < 0) cw = 0; /* unprintable wchar */ if ((cl + l > maxlen) || (cw + w > maxwid)) break; l += cl; w += cw; } out: if (width) *width = w; return l; }
/** * mutt_parse_icommand - Parse an informational command * @param line Command to execute * @param err Buffer for error messages * @retval #MUTT_CMD_SUCCESS Success * @retval #MUTT_CMD_ERROR Error (no message): command not found * @retval #MUTT_CMD_ERROR Error with message: command failed * @retval #MUTT_CMD_WARNING Warning with message: command failed */ enum CommandResult mutt_parse_icommand(/* const */ char *line, struct Buffer *err) { if (!line || !*line || !err) return MUTT_CMD_ERROR; enum CommandResult rc = MUTT_CMD_ERROR; struct Buffer expn, token; mutt_buffer_init(&expn); mutt_buffer_init(&token); expn.data = expn.dptr = line; expn.dsize = mutt_str_strlen(line); mutt_buffer_reset(err); SKIPWS(expn.dptr); while (*expn.dptr) { mutt_extract_token(&token, &expn, 0); for (size_t i = 0; ICommandList[i].name; i++) { if (mutt_str_strcmp(token.data, ICommandList[i].name) != 0) continue; rc = ICommandList[i].func(&token, &expn, ICommandList[i].data, err); if (rc != 0) goto finish; break; /* Continue with next command */ } } finish: if (expn.destroy) FREE(&expn.data); return rc; }
/** * tls_check_one_certificate - Check a GnuTLS certificate * @param certdata List of GnuTLS certificates * @param certstat GnuTLS certificate status * @param hostname Hostname * @param idx Index into certificate list * @param len Length of certificate list * @retval 0 Failure * @retval >0 Success */ static int tls_check_one_certificate(const gnutls_datum_t *certdata, gnutls_certificate_status_t certstat, const char *hostname, int idx, size_t len) { int certerr, savedcert; gnutls_x509_crt_t cert; char buf[128]; char fpbuf[128]; size_t buflen; char dn_common_name[128]; char dn_email[128]; char dn_organization[128]; char dn_organizational_unit[128]; char dn_locality[128]; char dn_province[128]; char dn_country[128]; time_t t; char datestr[30]; struct Menu *menu = NULL; char helpstr[1024]; char title[256]; FILE *fp = NULL; gnutls_datum_t pemdata; int row, done, ret; if (tls_check_preauth(certdata, certstat, hostname, idx, &certerr, &savedcert) == 0) return 1; /* interactive check from user */ if (gnutls_x509_crt_init(&cert) < 0) { mutt_error(_("Error initialising gnutls certificate data")); return 0; } if (gnutls_x509_crt_import(cert, certdata, GNUTLS_X509_FMT_DER) < 0) { mutt_error(_("Error processing certificate data")); gnutls_x509_crt_deinit(cert); return 0; } menu = mutt_menu_new(MENU_GENERIC); menu->max = 27; 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)); mutt_menu_push_current(menu); row = 0; mutt_str_strfcpy(menu->dialog[row], _("This certificate belongs to:"), dialog_row_len); row++; buflen = sizeof(dn_common_name); if (gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, dn_common_name, &buflen) != 0) { dn_common_name[0] = '\0'; } buflen = sizeof(dn_email); if (gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0, dn_email, &buflen) != 0) dn_email[0] = '\0'; buflen = sizeof(dn_organization); if (gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, dn_organization, &buflen) != 0) { dn_organization[0] = '\0'; } buflen = sizeof(dn_organizational_unit); if (gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0, dn_organizational_unit, &buflen) != 0) { dn_organizational_unit[0] = '\0'; } buflen = sizeof(dn_locality); if (gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, dn_locality, &buflen) != 0) { dn_locality[0] = '\0'; } buflen = sizeof(dn_province); if (gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, dn_province, &buflen) != 0) { dn_province[0] = '\0'; } buflen = sizeof(dn_country); if (gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0, dn_country, &buflen) != 0) { dn_country[0] = '\0'; } snprintf(menu->dialog[row++], dialog_row_len, " %s %s", dn_common_name, dn_email); snprintf(menu->dialog[row++], dialog_row_len, " %s", dn_organization); snprintf(menu->dialog[row++], dialog_row_len, " %s", dn_organizational_unit); snprintf(menu->dialog[row++], dialog_row_len, " %s %s %s", dn_locality, dn_province, dn_country); row++; mutt_str_strfcpy(menu->dialog[row], _("This certificate was issued by:"), dialog_row_len); row++; buflen = sizeof(dn_common_name); if (gnutls_x509_crt_get_issuer_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, dn_common_name, &buflen) != 0) { dn_common_name[0] = '\0'; } buflen = sizeof(dn_email); if (gnutls_x509_crt_get_issuer_dn_by_oid(cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0, dn_email, &buflen) != 0) { dn_email[0] = '\0'; } buflen = sizeof(dn_organization); if (gnutls_x509_crt_get_issuer_dn_by_oid(cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, dn_organization, &buflen) != 0) { dn_organization[0] = '\0'; } buflen = sizeof(dn_organizational_unit); if (gnutls_x509_crt_get_issuer_dn_by_oid(cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0, dn_organizational_unit, &buflen) != 0) { dn_organizational_unit[0] = '\0'; } buflen = sizeof(dn_locality); if (gnutls_x509_crt_get_issuer_dn_by_oid(cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, dn_locality, &buflen) != 0) { dn_locality[0] = '\0'; } buflen = sizeof(dn_province); if (gnutls_x509_crt_get_issuer_dn_by_oid(cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, dn_province, &buflen) != 0) { dn_province[0] = '\0'; } buflen = sizeof(dn_country); if (gnutls_x509_crt_get_issuer_dn_by_oid(cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0, dn_country, &buflen) != 0) { dn_country[0] = '\0'; } snprintf(menu->dialog[row++], dialog_row_len, " %s %s", dn_common_name, dn_email); snprintf(menu->dialog[row++], dialog_row_len, " %s", dn_organization); snprintf(menu->dialog[row++], dialog_row_len, " %s", dn_organizational_unit); snprintf(menu->dialog[row++], dialog_row_len, " %s %s %s", dn_locality, dn_province, dn_country); row++; snprintf(menu->dialog[row++], dialog_row_len, _("This certificate is valid")); t = gnutls_x509_crt_get_activation_time(cert); mutt_date_make_tls(datestr, sizeof(datestr), t); snprintf(menu->dialog[row++], dialog_row_len, _(" from %s"), datestr); t = gnutls_x509_crt_get_expiration_time(cert); mutt_date_make_tls(datestr, sizeof(datestr), t); snprintf(menu->dialog[row++], dialog_row_len, _(" to %s"), datestr); fpbuf[0] = '\0'; tls_fingerprint(GNUTLS_DIG_SHA, fpbuf, sizeof(fpbuf), certdata); snprintf(menu->dialog[row++], dialog_row_len, _("SHA1 Fingerprint: %s"), fpbuf); fpbuf[0] = '\0'; fpbuf[40] = '\0'; /* Ensure the second printed line is null terminated */ tls_fingerprint(GNUTLS_DIG_SHA256, fpbuf, sizeof(fpbuf), certdata); fpbuf[39] = '\0'; /* Divide into two lines of output */ snprintf(menu->dialog[row++], dialog_row_len, "%s%s", _("SHA256 Fingerprint: "), fpbuf); snprintf(menu->dialog[row++], dialog_row_len, "%*s%s", (int) mutt_str_strlen(_("SHA256 Fingerprint: ")), "", fpbuf + 40); if (certerr & CERTERR_NOTYETVALID) { row++; mutt_str_strfcpy(menu->dialog[row], _("WARNING: Server certificate is not yet valid"), dialog_row_len); } if (certerr & CERTERR_EXPIRED) { row++; mutt_str_strfcpy(menu->dialog[row], _("WARNING: Server certificate has expired"), dialog_row_len); } if (certerr & CERTERR_REVOKED) { row++; mutt_str_strfcpy(menu->dialog[row], _("WARNING: Server certificate has been revoked"), dialog_row_len); } if (certerr & CERTERR_HOSTNAME) { row++; mutt_str_strfcpy(menu->dialog[row], _("WARNING: Server hostname does not match certificate"), dialog_row_len); } if (certerr & CERTERR_SIGNERNOTCA) { row++; mutt_str_strfcpy(menu->dialog[row], _("WARNING: Signer of server certificate is not a CA"), dialog_row_len); } snprintf(title, sizeof(title), _("SSL Certificate check (certificate %zu of %zu in chain)"), len - idx, len); menu->title = title; /* certificates with bad dates, or that are revoked, must be * accepted manually each and every time */ if (C_CertificateFile && !savedcert && !(certerr & (CERTERR_EXPIRED | CERTERR_NOTYETVALID | CERTERR_REVOKED))) { menu->prompt = _("(r)eject, accept (o)nce, (a)ccept always"); /* L10N: These three letters correspond to the choices in the string: (r)eject, accept (o)nce, (a)ccept always. This is an interactive certificate confirmation prompt for a GNUTLS connection. */ menu->keys = _("roa"); } else { menu->prompt = _("(r)eject, accept (o)nce"); /* L10N: These two letters correspond to the choices in the string: (r)eject, accept (o)nce. These is an interactive certificate confirmation prompt for a GNUTLS connection. */ menu->keys = _("ro"); } 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 */ done = 0; fp = mutt_file_fopen(C_CertificateFile, "a"); if (fp) { /* save hostname if necessary */ if (certerr & CERTERR_HOSTNAME) { fpbuf[0] = '\0'; tls_fingerprint(GNUTLS_DIG_MD5, fpbuf, sizeof(fpbuf), certdata); fprintf(fp, "#H %s %s\n", hostname, fpbuf); done = 1; } /* Save the cert for all other errors */ if (certerr ^ CERTERR_HOSTNAME) { done = 0; ret = gnutls_pem_base64_encode_alloc("CERTIFICATE", certdata, &pemdata); if (ret == 0) { if (fwrite(pemdata.data, pemdata.size, 1, fp) == 1) { done = 1; } gnutls_free(pemdata.data); } } 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; break; } } OptIgnoreMacroEvents = false; mutt_menu_pop_current(menu); mutt_menu_destroy(&menu); gnutls_x509_crt_deinit(cert); return done == 2; }
/** * check_host - Check the host on the certificate * @param x509cert Certificate * @param hostname Hostname * @param err Buffer for error message * @param errlen Length of buffer * @retval 1 Hostname matches the certificate * @retval 0 Error */ static int check_host(X509 *x509cert, const char *hostname, char *err, size_t errlen) { int rc = 0; /* hostname in ASCII format: */ char *hostname_ascii = NULL; /* needed to get the common name: */ X509_NAME *x509_subject = NULL; char *buf = NULL; int bufsize; /* needed to get the DNS subjectAltNames: */ STACK_OF(GENERAL_NAME) * subj_alt_names; int subj_alt_names_count; GENERAL_NAME *subj_alt_name = NULL; /* did we find a name matching hostname? */ bool match_found; /* Check if 'hostname' matches the one of the subjectAltName extensions of * type DNS or the Common Name (CN). */ #ifdef HAVE_LIBIDN if (mutt_idna_to_ascii_lz(hostname, &hostname_ascii, 0) != 0) { hostname_ascii = mutt_str_strdup(hostname); } #else hostname_ascii = mutt_str_strdup(hostname); #endif /* Try the DNS subjectAltNames. */ match_found = false; subj_alt_names = X509_get_ext_d2i(x509cert, NID_subject_alt_name, NULL, NULL); if (subj_alt_names) { subj_alt_names_count = sk_GENERAL_NAME_num(subj_alt_names); for (int i = 0; i < subj_alt_names_count; i++) { subj_alt_name = sk_GENERAL_NAME_value(subj_alt_names, i); if (subj_alt_name->type == GEN_DNS) { if ((subj_alt_name->d.ia5->length >= 0) && (mutt_str_strlen((char *) subj_alt_name->d.ia5->data) == (size_t) subj_alt_name->d.ia5->length) && (match_found = hostname_match(hostname_ascii, (char *) (subj_alt_name->d.ia5->data)))) { break; } } } GENERAL_NAMES_free(subj_alt_names); } if (!match_found) { /* Try the common name */ x509_subject = X509_get_subject_name(x509cert); if (!x509_subject) { if (err && errlen) mutt_str_strfcpy(err, _("cannot get certificate subject"), errlen); goto out; } /* first get the space requirements */ bufsize = X509_NAME_get_text_by_NID(x509_subject, NID_commonName, NULL, 0); if (bufsize == -1) { if (err && errlen) mutt_str_strfcpy(err, _("cannot get certificate common name"), errlen); goto out; } bufsize++; /* space for the terminal nul char */ buf = mutt_mem_malloc((size_t) bufsize); if (X509_NAME_get_text_by_NID(x509_subject, NID_commonName, buf, bufsize) == -1) { if (err && errlen) mutt_str_strfcpy(err, _("cannot get certificate common name"), errlen); goto out; } /* cast is safe since bufsize is incremented above, so bufsize-1 is always * zero or greater. */ if (mutt_str_strlen(buf) == (size_t) bufsize - 1) { match_found = hostname_match(hostname_ascii, buf); } } if (!match_found) { if (err && errlen) snprintf(err, errlen, _("certificate owner does not match hostname %s"), hostname); goto out; } rc = 1; out: FREE(&buf); FREE(&hostname_ascii); return rc; }
/** * 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; }
/** * mutt_multi_choice - Offer the user a multiple choice question * @param prompt Message prompt * @param letters Allowable selection keys * @retval >=0 0-based user selection * @retval -1 Selection aborted */ int mutt_multi_choice(const char *prompt, const char *letters) { struct Event ch; int choice; bool redraw = true; int prompt_lines = 1; char *p = NULL; while (true) { if (redraw || SigWinch) { redraw = false; if (SigWinch) { SigWinch = 0; mutt_resize_screen(); clearok(stdscr, TRUE); mutt_menu_current_redraw(); } if (MuttMessageWindow->cols) { prompt_lines = (mutt_strwidth(prompt) + MuttMessageWindow->cols - 1) / MuttMessageWindow->cols; prompt_lines = MAX(1, MIN(3, prompt_lines)); } if (prompt_lines != MuttMessageWindow->rows) { mutt_window_reflow_message_rows(prompt_lines); mutt_menu_current_redraw(); } SETCOLOR(MT_COLOR_PROMPT); mutt_window_mvaddstr(MuttMessageWindow, 0, 0, prompt); NORMAL_COLOR; mutt_window_clrtoeol(MuttMessageWindow); } mutt_refresh(); /* SigWinch is not processed unless timeout is set */ mutt_getch_timeout(30 * 1000); ch = mutt_getch(); mutt_getch_timeout(-1); if (ch.ch == -2) continue; /* (ch.ch == 0) is technically possible. Treat the same as < 0 (abort) */ if ((ch.ch <= 0) || CI_is_return(ch.ch)) { choice = -1; break; } else { p = strchr(letters, ch.ch); if (p) { choice = p - letters + 1; break; } else if ((ch.ch <= '9') && (ch.ch > '0')) { choice = ch.ch - '0'; if (choice <= mutt_str_strlen(letters)) break; } } BEEP(); } if (MuttMessageWindow->rows != 1) { mutt_window_reflow_message_rows(1); mutt_menu_current_redraw(); } else mutt_window_clearline(MuttMessageWindow, 0); mutt_refresh(); return choice; }
/** * crypt_get_fingerprint_or_id - Get the fingerprint or long key ID * @param[in] p String to examine * @param[out] pphint Start of string to be passed to pgp_add_string_to_hints() or crypt_add_string_to_hints() * @param[out] ppl Start of long key ID if detected, else NULL * @param[out] pps Start of short key ID if detected, else NULL * @retval ptr Copy of fingerprint, if any, stripped of all spaces. Must be FREE'd by caller * @retval NULL Otherwise * * Obtain pointers to fingerprint or short or long key ID, if any. * * Upon return, at most one of return, *ppl and *pps pointers is non-NULL, * indicating the longest fingerprint or ID found, if any. */ const char *crypt_get_fingerprint_or_id(char *p, const char **pphint, const char **ppl, const char **pps) { const char *ps = NULL, *pl = NULL, *phint = NULL; char *pfcopy = NULL, *s1 = NULL, *s2 = NULL; char c; int isid; size_t hexdigits; /* User input may be partial name, fingerprint or short or long key ID, * independent of C_PgpLongIds. * Fingerprint without spaces is 40 hex digits (SHA-1) or 32 hex digits (MD5). * Strip leading "0x" for key ID detection and prepare pl and ps to indicate * if an ID was found and to simplify logic in the key loop's inner * condition of the caller. */ char *pf = mutt_str_skip_whitespace(p); if (mutt_str_startswith(pf, "0x", CASE_IGNORE)) pf += 2; /* Check if a fingerprint is given, must be hex digits only, blanks * separating groups of 4 hex digits are allowed. Also pre-check for ID. */ isid = 2; /* unknown */ hexdigits = 0; s1 = pf; do { c = *(s1++); if ((('0' <= c) && (c <= '9')) || (('A' <= c) && (c <= 'F')) || (('a' <= c) && (c <= 'f'))) { hexdigits++; if (isid == 2) isid = 1; /* it is an ID so far */ } else if (c) { isid = 0; /* not an ID */ if ((c == ' ') && ((hexdigits % 4) == 0)) ; /* skip blank before or after 4 hex digits */ else break; /* any other character or position */ } } while (c); /* If at end of input, check for correct fingerprint length and copy if. */ pfcopy = (!c && ((hexdigits == 40) || (hexdigits == 32)) ? mutt_str_strdup(pf) : NULL); if (pfcopy) { /* Use pfcopy to strip all spaces from fingerprint and as hint. */ s1 = pfcopy; s2 = pfcopy; do { *(s1++) = *(s2 = mutt_str_skip_whitespace(s2)); } while (*(s2++)); phint = pfcopy; ps = NULL; pl = NULL; } else { phint = p; ps = NULL; pl = NULL; if (isid == 1) { if (mutt_str_strlen(pf) == 16) pl = pf; /* long key ID */ else if (mutt_str_strlen(pf) == 8) ps = pf; /* short key ID */ } } *pphint = phint; *ppl = pl; *pps = ps; return pfcopy; }
/** * mutt_is_application_smime - Does the message use S/MIME? * @param m Body of email * @retval >0 Message uses S/MIME, e.g. #SMIME_ENCRYPT * @retval 0 Message doesn't use S/MIME, (#SEC_NO_FLAGS) */ SecurityFlags mutt_is_application_smime(struct Body *m) { if (!m) return SEC_NO_FLAGS; if (((m->type & TYPE_APPLICATION) == 0) || !m->subtype) return SEC_NO_FLAGS; char *t = NULL; bool complain = false; /* S/MIME MIME types don't need x- anymore, see RFC2311 */ if ((mutt_str_strcasecmp(m->subtype, "x-pkcs7-mime") == 0) || (mutt_str_strcasecmp(m->subtype, "pkcs7-mime") == 0)) { t = mutt_param_get(&m->parameter, "smime-type"); if (t) { if (mutt_str_strcasecmp(t, "enveloped-data") == 0) return SMIME_ENCRYPT; else if (mutt_str_strcasecmp(t, "signed-data") == 0) return SMIME_SIGN | SMIME_OPAQUE; else return SEC_NO_FLAGS; } /* Netscape 4.7 uses * Content-Description: S/MIME Encrypted Message * instead of Content-Type parameter */ if (mutt_str_strcasecmp(m->description, "S/MIME Encrypted Message") == 0) return SMIME_ENCRYPT; complain = true; } else if (mutt_str_strcasecmp(m->subtype, "octet-stream") != 0) return SEC_NO_FLAGS; t = mutt_param_get(&m->parameter, "name"); if (!t) t = m->d_filename; if (!t) t = m->filename; if (!t) { if (complain) { mutt_message( _("S/MIME messages with no hints on content are unsupported")); } return SEC_NO_FLAGS; } /* no .p7c, .p10 support yet. */ int len = mutt_str_strlen(t) - 4; if ((len > 0) && (*(t + len) == '.')) { len++; if (mutt_str_strcasecmp((t + len), "p7m") == 0) { /* Not sure if this is the correct thing to do, but * it's required for compatibility with Outlook */ return SMIME_SIGN | SMIME_OPAQUE; } else if (mutt_str_strcasecmp((t + len), "p7s") == 0) return SMIME_SIGN | SMIME_OPAQUE; } return SEC_NO_FLAGS; }
/** * crypt_get_keys - Check we have all the keys we need * @param[in] msg Message with addresses to match * @param[out] keylist Keys needed * @param[in] oppenc_mode If true, use opportunistic encryption * @retval 0 Success * @retval -1 Error * * Do a quick check to make sure that we can find all of the * encryption keys if the user has requested this service. * Return the list of keys in KEYLIST. * If oppenc_mode is true, only keys that can be determined without * prompting will be used. */ int crypt_get_keys(struct Email *msg, char **keylist, bool oppenc_mode) { struct Address *addrlist = NULL, *last = NULL; const char *fqdn = mutt_fqdn(true); char *self_encrypt = NULL; /* Do a quick check to make sure that we can find all of the encryption * keys if the user has requested this service. */ if (!WithCrypto) return 0; if (WithCrypto & APPLICATION_PGP) OptPgpCheckTrust = true; last = mutt_addr_append(&addrlist, msg->env->to, false); last = mutt_addr_append(last ? &last : &addrlist, msg->env->cc, false); mutt_addr_append(last ? &last : &addrlist, msg->env->bcc, false); if (fqdn) mutt_addr_qualify(addrlist, fqdn); addrlist = mutt_addrlist_dedupe(addrlist); *keylist = NULL; if (oppenc_mode || (msg->security & SEC_ENCRYPT)) { if (((WithCrypto & APPLICATION_PGP) != 0) && (msg->security & APPLICATION_PGP)) { *keylist = crypt_pgp_find_keys(addrlist, oppenc_mode); if (!*keylist) { mutt_addr_free(&addrlist); return -1; } OptPgpCheckTrust = false; if (C_PgpSelfEncrypt || (C_PgpEncryptSelf == MUTT_YES)) self_encrypt = C_PgpDefaultKey; } if (((WithCrypto & APPLICATION_SMIME) != 0) && (msg->security & APPLICATION_SMIME)) { *keylist = crypt_smime_find_keys(addrlist, oppenc_mode); if (!*keylist) { mutt_addr_free(&addrlist); return -1; } if (C_SmimeSelfEncrypt || (C_SmimeEncryptSelf == MUTT_YES)) self_encrypt = C_SmimeDefaultKey; } } if (!oppenc_mode && self_encrypt && *self_encrypt) { const size_t keylist_size = mutt_str_strlen(*keylist); mutt_mem_realloc(keylist, keylist_size + mutt_str_strlen(self_encrypt) + 2); sprintf(*keylist + keylist_size, " %s", self_encrypt); } mutt_addr_free(&addrlist); return 0; }
/** * tls_negotiate - Negotiate TLS connection * @param conn Connection to a server * @retval 0 Success * @retval -1 Error * * After TLS state has been initialized, attempt to negotiate TLS over the * wire, including certificate checks. */ static int tls_negotiate(struct Connection *conn) { struct TlsSockData *data = mutt_mem_calloc(1, sizeof(struct TlsSockData)); conn->sockdata = data; int err = gnutls_certificate_allocate_credentials(&data->xcred); if (err < 0) { FREE(&conn->sockdata); mutt_error("gnutls_certificate_allocate_credentials: %s", gnutls_strerror(err)); return -1; } gnutls_certificate_set_x509_trust_file(data->xcred, C_CertificateFile, GNUTLS_X509_FMT_PEM); /* ignore errors, maybe file doesn't exist yet */ if (C_SslCaCertificatesFile) { gnutls_certificate_set_x509_trust_file(data->xcred, C_SslCaCertificatesFile, GNUTLS_X509_FMT_PEM); } if (C_SslClientCert) { mutt_debug(LL_DEBUG2, "Using client certificate %s\n", C_SslClientCert); gnutls_certificate_set_x509_key_file(data->xcred, C_SslClientCert, C_SslClientCert, GNUTLS_X509_FMT_PEM); } #ifdef HAVE_DECL_GNUTLS_VERIFY_DISABLE_TIME_CHECKS /* disable checking certificate activation/expiration times * in gnutls, we do the checks ourselves */ gnutls_certificate_set_verify_flags(data->xcred, GNUTLS_VERIFY_DISABLE_TIME_CHECKS); #endif err = gnutls_init(&data->state, GNUTLS_CLIENT); if (err) { mutt_error("gnutls_handshake: %s", gnutls_strerror(err)); goto fail; } /* set socket */ gnutls_transport_set_ptr(data->state, (gnutls_transport_ptr_t)(long) conn->fd); if (gnutls_server_name_set(data->state, GNUTLS_NAME_DNS, conn->account.host, mutt_str_strlen(conn->account.host))) { mutt_error(_("Warning: unable to set TLS SNI host name")); } if (tls_set_priority(data) < 0) { goto fail; } if (C_SslMinDhPrimeBits > 0) { gnutls_dh_set_prime_bits(data->state, C_SslMinDhPrimeBits); } /* gnutls_set_cred (data->state, GNUTLS_ANON, NULL); */ gnutls_credentials_set(data->state, GNUTLS_CRD_CERTIFICATE, data->xcred); err = gnutls_handshake(data->state); while (err == GNUTLS_E_AGAIN) { err = gnutls_handshake(data->state); } if (err < 0) { if (err == GNUTLS_E_FATAL_ALERT_RECEIVED) { mutt_error("gnutls_handshake: %s(%s)", gnutls_strerror(err), gnutls_alert_get_name(gnutls_alert_get(data->state))); } else { mutt_error("gnutls_handshake: %s", gnutls_strerror(err)); } goto fail; } if (tls_check_certificate(conn) == 0) goto fail; /* set Security Strength Factor (SSF) for SASL */ /* NB: gnutls_cipher_get_key_size() returns key length in bytes */ conn->ssf = gnutls_cipher_get_key_size(gnutls_cipher_get(data->state)) * 8; tls_get_client_cert(conn); if (!OptNoCurses) { mutt_message(_("SSL/TLS connection using %s (%s/%s/%s)"), gnutls_protocol_get_name(gnutls_protocol_get_version(data->state)), gnutls_kx_get_name(gnutls_kx_get(data->state)), gnutls_cipher_get_name(gnutls_cipher_get(data->state)), gnutls_mac_get_name(gnutls_mac_get(data->state))); mutt_sleep(0); } return 0; fail: gnutls_certificate_free_credentials(data->xcred); gnutls_deinit(data->state); FREE(&conn->sockdata); return -1; }