/** * 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; }
/** * raw_socket_open - Open a socket - Implements Connection::conn_open() */ int raw_socket_open(struct Connection *conn) { int rc; char *host_idna = NULL; #ifdef HAVE_GETADDRINFO /* --- IPv4/6 --- */ /* "65536\0" */ char port[6]; struct addrinfo hints; struct addrinfo *res = NULL; struct addrinfo *cur = NULL; /* we accept v4 or v6 STREAM sockets */ memset(&hints, 0, sizeof(hints)); if (C_UseIpv6) hints.ai_family = AF_UNSPEC; else hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; snprintf(port, sizeof(port), "%d", conn->account.port); #ifdef HAVE_LIBIDN if (mutt_idna_to_ascii_lz(conn->account.host, &host_idna, 1) != 0) { mutt_error(_("Bad IDN: '%s'"), conn->account.host); return -1; } #else host_idna = conn->account.host; #endif if (!OptNoCurses) mutt_message(_("Looking up %s..."), conn->account.host); rc = getaddrinfo(host_idna, port, &hints, &res); #ifdef HAVE_LIBIDN FREE(&host_idna); #endif if (rc) { mutt_error(_("Could not find the host \"%s\""), conn->account.host); return -1; } if (!OptNoCurses) mutt_message(_("Connecting to %s..."), conn->account.host); rc = -1; for (cur = res; cur; cur = cur->ai_next) { int fd = socket(cur->ai_family, cur->ai_socktype, cur->ai_protocol); if (fd >= 0) { rc = socket_connect(fd, cur->ai_addr); if (rc == 0) { fcntl(fd, F_SETFD, FD_CLOEXEC); conn->fd = fd; break; } else close(fd); } } freeaddrinfo(res); #else /* --- IPv4 only --- */ struct sockaddr_in sin; struct hostent *he = NULL; memset(&sin, 0, sizeof(sin)); sin.sin_port = htons(conn->account.port); sin.sin_family = AF_INET; #ifdef HAVE_LIBIDN if (mutt_idna_to_ascii_lz(conn->account.host, &host_idna, 1) != 0) { mutt_error(_("Bad IDN: '%s'"), conn->account.host); return -1; } #else host_idna = conn->account.host; #endif if (!OptNoCurses) mutt_message(_("Looking up %s..."), conn->account.host); he = gethostbyname(host_idna); #ifdef HAVE_LIBIDN FREE(&host_idna); #endif if (!he) { mutt_error(_("Could not find the host \"%s\""), conn->account.host); return -1; } if (!OptNoCurses) mutt_message(_("Connecting to %s..."), conn->account.host); rc = -1; for (int i = 0; he->h_addr_list[i]; i++) { memcpy(&sin.sin_addr, he->h_addr_list[i], he->h_length); int fd = socket(PF_INET, SOCK_STREAM, IPPROTO_IP); if (fd >= 0) { rc = socket_connect(fd, (struct sockaddr *) &sin); if (rc == 0) { fcntl(fd, F_SETFD, FD_CLOEXEC); conn->fd = fd; break; } else close(fd); } } #endif if (rc) { mutt_error(_("Could not connect to %s (%s)"), conn->account.host, (rc > 0) ? strerror(rc) : _("unknown error")); return -1; } return 0; }