/** * 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; }
/** * x509_get_part - Retrieve from X509 data * @param name Name of data to retrieve * @param nid ID of the item to retrieve * @retval ptr Retrieved data * * The returned pointer is to a static buffer, so it must not be free()'d. */ static char *x509_get_part(X509_NAME *name, int nid) { static char data[128]; if (!name || (X509_NAME_get_text_by_NID(name, nid, data, sizeof(data)) < 0)) mutt_str_strfcpy(data, _("Unknown"), sizeof(data)); return data; }
/** * setup_paths - Set the mailbox paths * @param m Mailbox to modify * @retval 0 Success * @retval -1 Error * * Save the compressed filename in mailbox->realpath. * Create a temporary filename and put its name in mailbox->path. * The temporary file is created to prevent symlink attacks. */ static int setup_paths(struct Mailbox *m) { if (!m) return -1; char tmp[PATH_MAX]; /* Setup the right paths */ mutt_str_strfcpy(m->realpath, m->path, sizeof(m->realpath)); /* We will uncompress to /tmp */ mutt_mktemp(tmp, sizeof(tmp)); mutt_str_strfcpy(m->path, tmp, sizeof(m->path)); FILE *fp = mutt_file_fopen(m->path, "w"); if (!fp) return -1; mutt_file_fclose(&fp); return 0; }
/** * asn1time_to_string - Convert a time to a string * @param tm Time to convert * @retval ptr Time string * * The returned pointer is to a static buffer, so it must not be free()'d. */ static char *asn1time_to_string(ASN1_UTCTIME *tm) { static char buf[64]; BIO *bio = NULL; mutt_str_strfcpy(buf, _("[invalid date]"), sizeof(buf)); bio = BIO_new(BIO_s_mem()); if (bio) { if (ASN1_TIME_print(bio, tm)) (void) BIO_read(bio, buf, sizeof(buf)); BIO_free(bio); } return buf; }
/** * 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; }
/** * 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; }
/** * group_index_format_str - Format a string for the newsgroup menu - Implements ::format_t * * | Expando | Description * |:--------|:-------------------------------------------------------- * | \%C | Current newsgroup number * | \%d | Description of newsgroup (becomes from server) * | \%f | Newsgroup name * | \%M | - if newsgroup not allowed for direct post (moderated for example) * | \%N | N if newsgroup is new, u if unsubscribed, blank otherwise * | \%n | Number of new articles in newsgroup * | \%s | Number of unread articles in newsgroup */ const char *group_index_format_str(char *buf, size_t buflen, size_t col, int cols, char op, const char *src, const char *prec, const char *if_str, const char *else_str, unsigned long data, MuttFormatFlags flags) { char fn[128], fmt[128]; struct Folder *folder = (struct Folder *) data; switch (op) { case 'C': snprintf(fmt, sizeof(fmt), "%%%sd", prec); snprintf(buf, buflen, fmt, folder->num + 1); break; case 'd': if (folder->ff->nd->desc) { char *desc = mutt_str_strdup(folder->ff->nd->desc); if (C_NewsgroupsCharset && *C_NewsgroupsCharset) mutt_ch_convert_string(&desc, C_NewsgroupsCharset, C_Charset, MUTT_ICONV_HOOK_FROM); mutt_mb_filter_unprintable(&desc); snprintf(fmt, sizeof(fmt), "%%%ss", prec); snprintf(buf, buflen, fmt, desc); FREE(&desc); } else { snprintf(fmt, sizeof(fmt), "%%%ss", prec); snprintf(buf, buflen, fmt, ""); } break; case 'f': mutt_str_strfcpy(fn, folder->ff->name, sizeof(fn)); snprintf(fmt, sizeof(fmt), "%%%ss", prec); snprintf(buf, buflen, fmt, fn); break; case 'M': snprintf(fmt, sizeof(fmt), "%%%sc", prec); if (folder->ff->nd->deleted) snprintf(buf, buflen, fmt, 'D'); else snprintf(buf, buflen, fmt, folder->ff->nd->allowed ? ' ' : '-'); break; case 'N': snprintf(fmt, sizeof(fmt), "%%%sc", prec); if (folder->ff->nd->subscribed) snprintf(buf, buflen, fmt, ' '); else snprintf(buf, buflen, fmt, folder->ff->new ? 'N' : 'u'); break; case 'n': if (Context && (Context->mailbox->mdata == folder->ff->nd)) { snprintf(fmt, sizeof(fmt), "%%%sd", prec); snprintf(buf, buflen, fmt, Context->mailbox->msg_new); } else if (C_MarkOld && (folder->ff->nd->last_cached >= folder->ff->nd->first_message) && (folder->ff->nd->last_cached <= folder->ff->nd->last_message)) { snprintf(fmt, sizeof(fmt), "%%%sd", prec); snprintf(buf, buflen, fmt, folder->ff->nd->last_message - folder->ff->nd->last_cached); } else { snprintf(fmt, sizeof(fmt), "%%%sd", prec); snprintf(buf, buflen, fmt, folder->ff->nd->unread); } break; case 's': if (flags & MUTT_FORMAT_OPTIONAL) { if (folder->ff->nd->unread != 0) { mutt_expando_format(buf, buflen, col, cols, if_str, group_index_format_str, data, flags); } else { mutt_expando_format(buf, buflen, col, cols, else_str, group_index_format_str, data, flags); } } else if (Context && (Context->mailbox->mdata == folder->ff->nd)) { snprintf(fmt, sizeof(fmt), "%%%sd", prec); snprintf(buf, buflen, fmt, Context->mailbox->msg_unread); } else { snprintf(fmt, sizeof(fmt), "%%%sd", prec); snprintf(buf, buflen, fmt, folder->ff->nd->unread); } break; } return src; }
/** * 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; }
/** * mh_mbox_check - Implements MxOps::mbox_check() * * This function handles arrival of new mail and reopening of mh/maildir * folders. Things are getting rather complex because we don't have a * well-defined "mailbox order", so the tricks from mbox.c and mx.c won't work * here. * * Don't change this code unless you _really_ understand what happens. */ int mh_mbox_check(struct Mailbox *m, int *index_hint) { if (!m) return -1; char buf[PATH_MAX]; struct stat st, st_cur; bool modified = false, occult = false, flags_changed = false; int num_new = 0; struct Maildir *md = NULL, *p = NULL; struct Maildir **last = NULL; struct MhSequences mhs = { 0 }; int count = 0; struct Hash *fnames = NULL; struct MaildirMboxData *mdata = maildir_mdata_get(m); if (!C_CheckNew) return 0; mutt_str_strfcpy(buf, m->path, sizeof(buf)); if (stat(buf, &st) == -1) return -1; /* create .mh_sequences when there isn't one. */ snprintf(buf, sizeof(buf), "%s/.mh_sequences", m->path); int i = stat(buf, &st_cur); if ((i == -1) && (errno == ENOENT)) { char *tmp = NULL; FILE *fp = NULL; if (mh_mkstemp(m, &fp, &tmp) == 0) { mutt_file_fclose(&fp); if (mutt_file_safe_rename(tmp, buf) == -1) unlink(tmp); FREE(&tmp); } } if ((i == -1) && (stat(buf, &st_cur) == -1)) modified = true; if ((mutt_file_stat_timespec_compare(&st, MUTT_STAT_MTIME, &m->mtime) > 0) || (mutt_file_stat_timespec_compare(&st_cur, MUTT_STAT_MTIME, &mdata->mtime_cur) > 0)) { modified = true; } if (!modified) return 0; /* Update the modification times on the mailbox. * * The monitor code notices changes in the open mailbox too quickly. * In practice, this sometimes leads to all the new messages not being * noticed during the SAME group of mtime stat updates. To work around * the problem, don't update the stat times for a monitor caused check. */ #ifdef USE_INOTIFY if (MonitorContextChanged) MonitorContextChanged = 0; else #endif { mutt_file_get_stat_timespec(&mdata->mtime_cur, &st_cur, MUTT_STAT_MTIME); mutt_file_get_stat_timespec(&m->mtime, &st, MUTT_STAT_MTIME); } md = NULL; last = &md; maildir_parse_dir(m, &last, NULL, &count, NULL); maildir_delayed_parsing(m, &md, NULL); if (mh_read_sequences(&mhs, m->path) < 0) return -1; mh_update_maildir(md, &mhs); mhs_free_sequences(&mhs); /* check for modifications and adjust flags */ fnames = mutt_hash_new(count, MUTT_HASH_NO_FLAGS); for (p = md; p; p = p->next) { /* the hash key must survive past the header, which is freed below. */ p->canon_fname = mutt_str_strdup(p->email->path); mutt_hash_insert(fnames, p->canon_fname, p); } for (i = 0; i < m->msg_count; i++) { m->emails[i]->active = false; p = mutt_hash_find(fnames, m->emails[i]->path); if (p && p->email && mutt_email_cmp_strict(m->emails[i], p->email)) { m->emails[i]->active = true; /* found the right message */ if (!m->emails[i]->changed) if (maildir_update_flags(m, m->emails[i], p->email)) flags_changed = true; mutt_email_free(&p->email); } else /* message has disappeared */ occult = true; } /* destroy the file name hash */ mutt_hash_free(&fnames); /* If we didn't just get new mail, update the tables. */ if (occult) mutt_mailbox_changed(m, MBN_RESORT); /* Incorporate new messages */ num_new = maildir_move_to_mailbox(m, &md); if (num_new > 0) { mutt_mailbox_changed(m, MBN_INVALID); m->changed = true; } if (occult) return MUTT_REOPENED; if (num_new > 0) return MUTT_NEW_MAIL; if (flags_changed) return MUTT_FLAGS; return 0; }
/** * mutt_draw_tree - Draw a tree of threaded emails * @param ctx Mailbox * * Since the graphics characters have a value >255, I have to resort to using * escape sequences to pass the information to print_enriched_string(). These * are the macros MUTT_TREE_* defined in mutt.h. * * ncurses should automatically use the default ASCII characters instead of * graphics chars on terminals which don't support them (see the man page for * curs_addch). */ void mutt_draw_tree(struct Context *ctx) { char *pfx = NULL, *mypfx = NULL, *arrow = NULL, *myarrow = NULL, *new_tree = NULL; enum TreeChar corner = (C_Sort & SORT_REVERSE) ? MUTT_TREE_ULCORNER : MUTT_TREE_LLCORNER; enum TreeChar vtee = (C_Sort & SORT_REVERSE) ? MUTT_TREE_BTEE : MUTT_TREE_TTEE; int depth = 0, start_depth = 0, max_depth = 0, width = C_NarrowTree ? 1 : 2; struct MuttThread *nextdisp = NULL, *pseudo = NULL, *parent = NULL, *tree = ctx->tree; /* Do the visibility calculations and free the old thread chars. * From now on we can simply ignore invisible subtrees */ calculate_visibility(ctx, &max_depth); pfx = mutt_mem_malloc(width * max_depth + 2); arrow = mutt_mem_malloc(width * max_depth + 2); while (tree) { if (depth) { myarrow = arrow + (depth - start_depth - (start_depth ? 0 : 1)) * width; if (depth && (start_depth == depth)) myarrow[0] = nextdisp ? MUTT_TREE_LTEE : corner; else if (parent->message && !C_HideLimited) myarrow[0] = MUTT_TREE_HIDDEN; else if (!parent->message && !C_HideMissing) myarrow[0] = MUTT_TREE_MISSING; else myarrow[0] = vtee; if (width == 2) { myarrow[1] = pseudo ? MUTT_TREE_STAR : (tree->duplicate_thread ? MUTT_TREE_EQUALS : MUTT_TREE_HLINE); } if (tree->visible) { myarrow[width] = MUTT_TREE_RARROW; myarrow[width + 1] = 0; new_tree = mutt_mem_malloc((2 + depth * width)); if (start_depth > 1) { strncpy(new_tree, pfx, (start_depth - 1) * width); mutt_str_strfcpy(new_tree + (start_depth - 1) * width, arrow, (1 + depth - start_depth) * width + 2); } else mutt_str_strfcpy(new_tree, arrow, 2 + depth * width); tree->message->tree = new_tree; } } if (tree->child && depth) { mypfx = pfx + (depth - 1) * width; mypfx[0] = nextdisp ? MUTT_TREE_VLINE : MUTT_TREE_SPACE; if (width == 2) mypfx[1] = MUTT_TREE_SPACE; } parent = tree; nextdisp = NULL; pseudo = NULL; do { if (tree->child && tree->subtree_visible) { if (tree->deep) depth++; if (tree->visible) start_depth = depth; tree = tree->child; /* we do this here because we need to make sure that the first child thread * of the old tree that we deal with is actually displayed if any are, * or we might set the parent variable wrong while going through it. */ while (!tree->subtree_visible && tree->next) tree = tree->next; } else { while (!tree->next && tree->parent) { if (tree == pseudo) pseudo = NULL; if (tree == nextdisp) nextdisp = NULL; if (tree->visible) start_depth = depth; tree = tree->parent; if (tree->deep) { if (start_depth == depth) start_depth--; depth--; } } if (tree == pseudo) pseudo = NULL; if (tree == nextdisp) nextdisp = NULL; if (tree->visible) start_depth = depth; tree = tree->next; if (!tree) break; } if (!pseudo && tree->fake_thread) pseudo = tree; if (!nextdisp && tree->next_subtree_visible) nextdisp = tree; } while (!tree->deep); } FREE(&pfx); FREE(&arrow); }