/** * mutt_is_multipart_signed - Is a message signed? * @param b Body of email * @retval num Message is signed, see #SecurityFlags * @retval 0 Message is not signed (#SEC_NO_FLAGS) */ SecurityFlags mutt_is_multipart_signed(struct Body *b) { if (!b || (b->type != TYPE_MULTIPART) || !b->subtype || (mutt_str_strcasecmp(b->subtype, "signed") != 0)) { return SEC_NO_FLAGS; } char *p = mutt_param_get(&b->parameter, "protocol"); if (!p) return SEC_NO_FLAGS; if (!(mutt_str_strcasecmp(p, "multipart/mixed") != 0)) return SEC_SIGN; if (((WithCrypto & APPLICATION_PGP) != 0) && !(mutt_str_strcasecmp(p, "application/pgp-signature") != 0)) { return PGP_SIGN; } if (((WithCrypto & APPLICATION_SMIME) != 0) && !(mutt_str_strcasecmp(p, "application/x-pkcs7-signature") != 0)) { return SMIME_SIGN; } if (((WithCrypto & APPLICATION_SMIME) != 0) && !(mutt_str_strcasecmp(p, "application/pkcs7-signature") != 0)) { return SMIME_SIGN; } return SEC_NO_FLAGS; }
/** * alias_sort_address - Compare two Addresses * @param a First Address to compare * @param b Second Address to compare * @retval -1 a precedes b * @retval 0 a and b are identical * @retval 1 b precedes a */ static int alias_sort_address(const void *a, const void *b) { struct Address *pa = (*(struct Alias **) a)->addr; struct Address *pb = (*(struct Alias **) b)->addr; int r; if (pa == pb) r = 0; else if (!pa) r = -1; else if (!pb) r = 1; else if (pa->personal) { if (pb->personal) r = mutt_str_strcasecmp(pa->personal, pb->personal); else r = 1; } else if (pb->personal) r = -1; else r = mutt_str_strcasecmp(pa->mailbox, pb->mailbox); return RSORT(r); }
/** * mutt_regexlist_add - Compile a regex string and add it to a list * @param rl RegexList to add to * @param str String to compile into a regex * @param flags Flags, e.g. REG_ICASE * @param err Buffer for error messages * @retval 0 Success, Regex compiled and added to the list * @retval -1 Error, see message in 'err' */ int mutt_regexlist_add(struct RegexList *rl, const char *str, int flags, struct Buffer *err) { if (!str || !*str) return 0; struct Regex *rx = mutt_regex_compile(str, flags); if (!rx) { mutt_buffer_printf(err, "Bad regex: %s\n", str); return -1; } /* check to make sure the item is not already on this rl */ struct RegexListNode *np = NULL; STAILQ_FOREACH(np, rl, entries) { if (mutt_str_strcasecmp(rx->pattern, np->regex->pattern) == 0) break; /* already on the rl */ } if (np) { mutt_regex_free(&rx); } else { np = mutt_regexlist_new(); np->regex = rx; STAILQ_INSERT_TAIL(rl, np, entries); } return 0; }
/** * crypt_convert_to_7bit - Convert an email to 7bit encoding * @param a Body of email to convert */ void crypt_convert_to_7bit(struct Body *a) { if (!WithCrypto) return; while (a) { if (a->type == TYPE_MULTIPART) { if (a->encoding != ENC_7BIT) { a->encoding = ENC_7BIT; crypt_convert_to_7bit(a->parts); } else if (((WithCrypto & APPLICATION_PGP) != 0) && C_PgpStrictEnc) crypt_convert_to_7bit(a->parts); } else if ((a->type == TYPE_MESSAGE) && (mutt_str_strcasecmp(a->subtype, "delivery-status") != 0)) { if (a->encoding != ENC_7BIT) mutt_message_to_7bit(a, NULL); } else if (a->encoding == ENC_8BIT) a->encoding = ENC_QUOTED_PRINTABLE; else if (a->encoding == ENC_BINARY) a->encoding = ENC_BASE64; else if (a->content && (a->encoding != ENC_BASE64) && (a->content->from || (a->content->space && C_PgpStrictEnc))) { a->encoding = ENC_QUOTED_PRINTABLE; } a = a->next; } }
/** * mutt_is_multipart_encrypted - Does the message have encrypted parts? * @param b Body of email * @retval num Message has got encrypted parts, see #SecurityFlags * @retval 0 Message hasn't got encrypted parts (#SEC_NO_FLAGS) */ SecurityFlags mutt_is_multipart_encrypted(struct Body *b) { if ((WithCrypto & APPLICATION_PGP) == 0) return SEC_NO_FLAGS; char *p = NULL; if (!b || (b->type != TYPE_MULTIPART) || !b->subtype || (mutt_str_strcasecmp(b->subtype, "encrypted") != 0) || !(p = mutt_param_get(&b->parameter, "protocol")) || (mutt_str_strcasecmp(p, "application/pgp-encrypted") != 0)) { return SEC_NO_FLAGS; } return PGP_ENCRYPT; }
/** * alias_sort_alias - Compare two Aliases * @param a First Alias to compare * @param b Second Alias to compare * @retval -1 a precedes b * @retval 0 a and b are identical * @retval 1 b precedes a */ static int alias_sort_alias(const void *a, const void *b) { struct Alias *pa = *(struct Alias **) a; struct Alias *pb = *(struct Alias **) b; int r = mutt_str_strcasecmp(pa->name, pb->name); return RSORT(r); }
/** * elem_list_sort - Sort two HashElem pointers to config * @param a First HashElem * @param b Second HashElem * @retval -1 a precedes b * @retval 0 a and b are identical * @retval 1 b precedes a */ int elem_list_sort(const void *a, const void *b) { if (!a || !b) return 0; const struct HashElem *hea = *(struct HashElem **) a; const struct HashElem *heb = *(struct HashElem **) b; return mutt_str_strcasecmp(hea->key.strkey, heb->key.strkey); }
/** * mutt_is_application_pgp - Does the message use PGP? * @param m Body of email * @retval >0 Message uses PGP, e.g. #PGP_ENCRYPT * @retval 0 Message doesn't use PGP, (#SEC_NO_FLAGS) */ SecurityFlags mutt_is_application_pgp(struct Body *m) { SecurityFlags t = SEC_NO_FLAGS; char *p = NULL; if (m->type == TYPE_APPLICATION) { if ((mutt_str_strcasecmp(m->subtype, "pgp") == 0) || (mutt_str_strcasecmp(m->subtype, "x-pgp-message") == 0)) { p = mutt_param_get(&m->parameter, "x-action"); if (p && ((mutt_str_strcasecmp(p, "sign") == 0) || (mutt_str_strcasecmp(p, "signclear") == 0))) { t |= PGP_SIGN; } p = mutt_param_get(&m->parameter, "format"); if (p && (mutt_str_strcasecmp(p, "keys-only") == 0)) { t |= PGP_KEY; } if (!t) t |= PGP_ENCRYPT; /* not necessarily correct, but... */ } if (mutt_str_strcasecmp(m->subtype, "pgp-signed") == 0) t |= PGP_SIGN; if (mutt_str_strcasecmp(m->subtype, "pgp-keys") == 0) t |= PGP_KEY; } else if ((m->type == TYPE_TEXT) && (mutt_str_strcasecmp("plain", m->subtype) == 0)) { if (((p = mutt_param_get(&m->parameter, "x-mutt-action")) || (p = mutt_param_get(&m->parameter, "x-action")) || (p = mutt_param_get(&m->parameter, "action"))) && mutt_str_startswith(p, "pgp-sign", CASE_IGNORE)) { t |= PGP_SIGN; } else if (p && mutt_str_startswith(p, "pgp-encrypt", CASE_IGNORE)) t |= PGP_ENCRYPT; else if (p && mutt_str_startswith(p, "pgp-keys", CASE_IGNORE)) t |= PGP_KEY; } if (t) t |= PGP_INLINE; return t; }
/** * mutt_is_valid_multipart_pgp_encrypted - Is this a valid multi-part encrypted message? * @param b Body of email * @retval >0 Message is valid, with encrypted parts, e.g. #PGP_ENCRYPT * @retval 0 Message hasn't got encrypted parts */ int mutt_is_valid_multipart_pgp_encrypted(struct Body *b) { if (mutt_is_multipart_encrypted(b) == SEC_NO_FLAGS) return 0; b = b->parts; if (!b || (b->type != TYPE_APPLICATION) || !b->subtype || (mutt_str_strcasecmp(b->subtype, "pgp-encrypted") != 0)) { return 0; } b = b->next; if (!b || (b->type != TYPE_APPLICATION) || !b->subtype || (mutt_str_strcasecmp(b->subtype, "octet-stream") != 0)) { return 0; } return PGP_ENCRYPT; }
/** * mutt_is_malformed_multipart_pgp_encrypted - Check for malformed layout * @param b Body of email * @retval num Success, see #SecurityFlags * @retval 0 Error, (#SEC_NO_FLAGS) * * This checks for the malformed layout caused by MS Exchange in * some cases: * ``` * <multipart/mixed> * <text/plain> * <application/pgp-encrypted> [BASE64-encoded] * <application/octet-stream> [BASE64-encoded] * ``` * See ticket #3742 */ SecurityFlags mutt_is_malformed_multipart_pgp_encrypted(struct Body *b) { if (!(WithCrypto & APPLICATION_PGP)) return SEC_NO_FLAGS; if (!b || (b->type != TYPE_MULTIPART) || !b->subtype || (mutt_str_strcasecmp(b->subtype, "mixed") != 0)) { return SEC_NO_FLAGS; } b = b->parts; if (!b || (b->type != TYPE_TEXT) || !b->subtype || (mutt_str_strcasecmp(b->subtype, "plain") != 0) || (b->length != 0)) { return SEC_NO_FLAGS; } b = b->next; if (!b || (b->type != TYPE_APPLICATION) || !b->subtype || (mutt_str_strcasecmp(b->subtype, "pgp-encrypted") != 0)) { return SEC_NO_FLAGS; } b = b->next; if (!b || (b->type != TYPE_APPLICATION) || !b->subtype || (mutt_str_strcasecmp(b->subtype, "octet-stream") != 0)) { return SEC_NO_FLAGS; } b = b->next; if (b) return SEC_NO_FLAGS; return PGP_ENCRYPT; }
/** * icmd_bind - Parse 'bind' and 'macro' commands - Implements ::icommand_t */ static enum CommandResult icmd_bind(struct Buffer *buf, struct Buffer *s, unsigned long data, struct Buffer *err) { FILE *fp_out = NULL; char tempfile[PATH_MAX]; bool dump_all = false, bind = (data == 0); if (!MoreArgs(s)) dump_all = true; else mutt_extract_token(buf, s, 0); if (MoreArgs(s)) { /* More arguments potentially means the user is using the * ::command_t :bind command thus we delegate the task. */ return MUTT_CMD_ERROR; } struct Buffer *filebuf = mutt_buffer_alloc(4096); if (dump_all || (mutt_str_strcasecmp(buf->data, "all") == 0)) { dump_all_menus(filebuf, bind); } else { const int menu_index = mutt_map_get_value(buf->data, Menus); if (menu_index == -1) { mutt_buffer_printf(err, _("%s: no such menu"), buf->data); mutt_buffer_free(&filebuf); return MUTT_CMD_ERROR; } struct Mapping menu = { buf->data, menu_index }; dump_menu(filebuf, &menu, bind); } if (mutt_buffer_is_empty(filebuf)) { mutt_buffer_printf(err, _("%s: no %s for this menu"), dump_all ? "all" : buf->data, bind ? "binds" : "macros"); mutt_buffer_free(&filebuf); return MUTT_CMD_ERROR; } mutt_mktemp(tempfile, sizeof(tempfile)); fp_out = mutt_file_fopen(tempfile, "w"); if (!fp_out) { mutt_buffer_printf(err, _("Could not create temporary file %s"), tempfile); mutt_buffer_free(&filebuf); return MUTT_CMD_ERROR; } fputs(filebuf->data, fp_out); mutt_file_fclose(&fp_out); mutt_buffer_free(&filebuf); struct Pager info = { 0 }; if (mutt_pager((bind) ? "bind" : "macro", tempfile, 0, &info) == -1) { mutt_buffer_printf(err, _("Could not create temporary file %s"), tempfile); return MUTT_CMD_ERROR; } return MUTT_CMD_SUCCESS; }
/** * 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; }
/** * mutt_protect - Encrypt and/or sign a message * @param msg Header of the message * @param keylist List of keys to encrypt to (space-separated) * @retval 0 Success * @retval -1 Error */ int mutt_protect(struct Email *msg, char *keylist) { struct Body *pbody = NULL, *tmp_pbody = NULL; struct Body *tmp_smime_pbody = NULL; struct Body *tmp_pgp_pbody = NULL; int flags = (WithCrypto & APPLICATION_PGP) ? msg->security : 0; if (!WithCrypto) return -1; if (!(msg->security & (SEC_ENCRYPT | SEC_SIGN))) return 0; if ((msg->security & SEC_SIGN) && !crypt_valid_passphrase(msg->security)) return -1; if (((WithCrypto & APPLICATION_PGP) != 0) && ((msg->security & PGP_INLINE) == PGP_INLINE)) { if ((msg->content->type != TYPE_TEXT) || (mutt_str_strcasecmp(msg->content->subtype, "plain") != 0)) { if (query_quadoption(C_PgpMimeAuto, _("Inline PGP can't be used with attachments. " "Revert to PGP/MIME?")) != MUTT_YES) { mutt_error( _("Mail not sent: inline PGP can't be used with attachments")); return -1; } } else if (mutt_str_strcasecmp("flowed", mutt_param_get(&msg->content->parameter, "format")) == 0) { if ((query_quadoption(C_PgpMimeAuto, _("Inline PGP can't be used with format=flowed. " "Revert to PGP/MIME?"))) != MUTT_YES) { mutt_error( _("Mail not sent: inline PGP can't be used with format=flowed")); return -1; } } else { /* they really want to send it inline... go for it */ if (!isendwin()) { mutt_endwin(); puts(_("Invoking PGP...")); } pbody = crypt_pgp_traditional_encryptsign(msg->content, flags, keylist); if (pbody) { msg->content = pbody; return 0; } /* otherwise inline won't work...ask for revert */ if (query_quadoption( C_PgpMimeAuto, _("Message can't be sent inline. Revert to using PGP/MIME?")) != MUTT_YES) { mutt_error(_("Mail not sent")); return -1; } } /* go ahead with PGP/MIME */ } if (!isendwin()) mutt_endwin(); if (WithCrypto & APPLICATION_SMIME) tmp_smime_pbody = msg->content; if (WithCrypto & APPLICATION_PGP) tmp_pgp_pbody = msg->content; if (C_CryptUsePka && (msg->security & SEC_SIGN)) { /* Set sender (necessary for e.g. PKA). */ const char *mailbox = NULL; struct Address *from = msg->env->from; if (!from) from = mutt_default_from(); mailbox = from->mailbox; if (!mailbox && C_EnvelopeFromAddress) mailbox = C_EnvelopeFromAddress->mailbox; if (((WithCrypto & APPLICATION_SMIME) != 0) && (msg->security & APPLICATION_SMIME)) crypt_smime_set_sender(mailbox); else if (((WithCrypto & APPLICATION_PGP) != 0) && (msg->security & APPLICATION_PGP)) crypt_pgp_set_sender(mailbox); if (!msg->env->from) mutt_addr_free(&from); } if (C_CryptProtectedHeadersWrite) { struct Envelope *protected_headers = mutt_env_new(); mutt_str_replace(&protected_headers->subject, msg->env->subject); /* Note: if other headers get added, such as to, cc, then a call to * mutt_env_to_intl() will need to be added here too. */ mutt_prepare_envelope(protected_headers, 0); mutt_env_free(&msg->content->mime_headers); msg->content->mime_headers = protected_headers; } if (msg->security & SEC_SIGN) { if (((WithCrypto & APPLICATION_SMIME) != 0) && (msg->security & APPLICATION_SMIME)) { tmp_pbody = crypt_smime_sign_message(msg->content); if (!tmp_pbody) goto bail; pbody = tmp_pbody; tmp_smime_pbody = tmp_pbody; } if (((WithCrypto & APPLICATION_PGP) != 0) && (msg->security & APPLICATION_PGP) && (!(flags & SEC_ENCRYPT) || C_PgpRetainableSigs)) { tmp_pbody = crypt_pgp_sign_message(msg->content); if (!tmp_pbody) goto bail; flags &= ~SEC_SIGN; pbody = tmp_pbody; tmp_pgp_pbody = tmp_pbody; } if ((WithCrypto != 0) && (msg->security & APPLICATION_SMIME) && (msg->security & APPLICATION_PGP)) { /* here comes the draft ;-) */ } } if (msg->security & SEC_ENCRYPT) { if (((WithCrypto & APPLICATION_SMIME) != 0) && (msg->security & APPLICATION_SMIME)) { tmp_pbody = crypt_smime_build_smime_entity(tmp_smime_pbody, keylist); if (!tmp_pbody) { /* signed ? free it! */ goto bail; } /* free tmp_body if messages was signed AND encrypted ... */ if ((tmp_smime_pbody != msg->content) && (tmp_smime_pbody != tmp_pbody)) { /* detach and don't delete msg->content, * which tmp_smime_pbody->parts after signing. */ tmp_smime_pbody->parts = tmp_smime_pbody->parts->next; msg->content->next = NULL; mutt_body_free(&tmp_smime_pbody); } pbody = tmp_pbody; } if (((WithCrypto & APPLICATION_PGP) != 0) && (msg->security & APPLICATION_PGP)) { pbody = crypt_pgp_encrypt_message(tmp_pgp_pbody, keylist, (flags & SEC_SIGN)); if (!pbody) { /* did we perform a retainable signature? */ if (flags != msg->security) { /* remove the outer multipart layer */ tmp_pgp_pbody = mutt_remove_multipart(tmp_pgp_pbody); /* get rid of the signature */ mutt_body_free(&tmp_pgp_pbody->next); } goto bail; } /* destroy temporary signature envelope when doing retainable * signatures. */ if (flags != msg->security) { tmp_pgp_pbody = mutt_remove_multipart(tmp_pgp_pbody); mutt_body_free(&tmp_pgp_pbody->next); } } } if (pbody) { msg->content = pbody; return 0; } bail: mutt_env_free(&msg->content->mime_headers); return -1; }
/** * mutt_signed_handler - Verify a "multipart/signed" body - Implements ::handler_t */ int mutt_signed_handler(struct Body *a, struct State *s) { bool inconsistent = false; struct Body *b = a; struct Body **signatures = NULL; int sigcnt = 0; int rc = 0; if (!WithCrypto) return -1; a = a->parts; SecurityFlags signed_type = mutt_is_multipart_signed(b); if (signed_type == SEC_NO_FLAGS) { /* A null protocol value is already checked for in mutt_body_handler() */ state_printf(s, _("[-- Error: " "Unknown multipart/signed protocol %s --]\n\n"), mutt_param_get(&b->parameter, "protocol")); return mutt_body_handler(a, s); } if (!(a && a->next)) inconsistent = true; else { switch (signed_type) { case SEC_SIGN: if ((a->next->type != TYPE_MULTIPART) || (mutt_str_strcasecmp(a->next->subtype, "mixed") != 0)) { inconsistent = true; } break; case PGP_SIGN: if ((a->next->type != TYPE_APPLICATION) || (mutt_str_strcasecmp(a->next->subtype, "pgp-signature") != 0)) { inconsistent = true; } break; case SMIME_SIGN: if ((a->next->type != TYPE_APPLICATION) || ((mutt_str_strcasecmp(a->next->subtype, "x-pkcs7-signature") != 0) && (mutt_str_strcasecmp(a->next->subtype, "pkcs7-signature") != 0))) { inconsistent = true; } break; default: inconsistent = true; } } if (inconsistent) { state_attach_puts(_("[-- Error: " "Missing or bad-format multipart/signed signature" " --]\n\n"), s); return mutt_body_handler(a, s); } if (s->flags & MUTT_DISPLAY) { crypt_fetch_signatures(&signatures, a->next, &sigcnt); if (sigcnt) { char tempfile[PATH_MAX]; mutt_mktemp(tempfile, sizeof(tempfile)); bool goodsig = true; if (crypt_write_signed(a, s, tempfile) == 0) { for (int i = 0; i < sigcnt; i++) { if (((WithCrypto & APPLICATION_PGP) != 0) && (signatures[i]->type == TYPE_APPLICATION) && (mutt_str_strcasecmp(signatures[i]->subtype, "pgp-signature") == 0)) { if (crypt_pgp_verify_one(signatures[i], s, tempfile) != 0) goodsig = false; continue; } if (((WithCrypto & APPLICATION_SMIME) != 0) && (signatures[i]->type == TYPE_APPLICATION) && ((mutt_str_strcasecmp(signatures[i]->subtype, "x-pkcs7-signature") == 0) || (mutt_str_strcasecmp(signatures[i]->subtype, "pkcs7-signature") == 0))) { if (crypt_smime_verify_one(signatures[i], s, tempfile) != 0) goodsig = false; continue; } state_printf(s, _("[-- Warning: " "We can't verify %s/%s signatures. --]\n\n"), TYPE(signatures[i]), signatures[i]->subtype); } } mutt_file_unlink(tempfile); b->goodsig = goodsig; b->badsig = !goodsig; /* Now display the signed body */ state_attach_puts(_("[-- The following data is signed --]\n\n"), s); mutt_protected_headers_handler(a, s); FREE(&signatures); } else state_attach_puts(_("[-- Warning: Can't find any signatures. --]\n\n"), s); } rc = mutt_body_handler(a, s); if (s->flags & MUTT_DISPLAY && sigcnt) state_attach_puts(_("\n[-- End of signed data --]\n"), s); return rc; }