#ifndef NO_SSLTLS #include #include #include #include #include #include #include "imapfilter.h" #include "session.h" #include "pathnames.h" #include #include #include #include extern environment env; int check_cert(X509 *pcert, unsigned char *pmd, unsigned int *pmdlen); void print_cert(X509 *cert, unsigned char *md, unsigned int *mdlen); int write_cert(X509 *cert); int mismatch_cert(void); /* * Get SSL/TLS certificate check it, maybe ask user about it and act * accordingly. */ int get_cert(session *ssn) { X509 *cert; unsigned char md[EVP_MAX_MD_SIZE]; unsigned int mdlen; mdlen = 0; if (!(cert = SSL_get_peer_certificate(ssn->ssl))) return -1; if (!(X509_digest(cert, EVP_md5(), md, &mdlen))) return -1; switch (check_cert(cert, md, &mdlen)) { case 0: if (isatty(STDIN_FILENO) == 0) fatal(ERROR_CERTIFICATE, "%s\n", "can't accept certificate in non-interactive mode"); print_cert(cert, md, &mdlen); if (write_cert(cert) == -1) goto fail; break; case -1: if (isatty(STDIN_FILENO) == 0) fatal(ERROR_CERTIFICATE, "%s\n", "certificate mismatch in non-interactive mode"); print_cert(cert, md, &mdlen); if (mismatch_cert() == -1) goto fail; break; } X509_free(cert); return 0; fail: X509_free(cert); return -1; } /* * Check if the SSL/TLS certificate exists in the certificates file. */ int check_cert(X509 *pcert, unsigned char *pmd, unsigned int *pmdlen) { int n, r; FILE *fd; char b; char *certf; X509 *cert; unsigned char md[EVP_MAX_MD_SIZE]; unsigned int mdlen; r = 0; cert = NULL; n = snprintf(&b, 1, "%s/%s", env.home, PATHNAME_CERTS); if (env.pathmax != -1 && n > env.pathmax) fatal(ERROR_PATHNAME, "pathname limit %ld exceeded: %d\n", env.pathmax, n); certf = (char *)xmalloc((n + 1) * sizeof(char)); snprintf(certf, n + 1, "%s/%s", env.home, PATHNAME_CERTS); if (!exists_file(certf)) { xfree(certf); return 0; } fd = fopen(certf, "r"); xfree(certf); if (fd == NULL) return -1; while ((cert = PEM_read_X509(fd, &cert, NULL, NULL)) != NULL) { if (X509_subject_name_cmp(cert, pcert) != 0 || X509_issuer_name_cmp(cert, pcert) != 0) continue; if (!X509_digest(cert, EVP_md5(), md, &mdlen) || *pmdlen != mdlen) continue; if (memcmp(pmd, md, mdlen) != 0) { r = -1; break; } r = 1; break; } fclose(fd); X509_free(cert); return r; } /* * Print information about the SSL/TLS certificate. */ void print_cert(X509 *cert, unsigned char *md, unsigned int *mdlen) { unsigned int i; char *c; c = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); printf("Server certificate subject: %s\n", c); xfree(c); c = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0); printf("Server certificate issuer: %s\n", c); xfree(c); printf("Server key fingerprint: "); for (i = 0; i < *mdlen; i++) printf(i != *mdlen - 1 ? "%02X:" : "%02X\n", md[i]); } /* * Write the SSL/TLS certificate after asking the user to accept/reject it. */ int write_cert(X509 *cert) { int n; FILE *fd; char b, c, buf[64]; char *certf; do { printf("(R)eject, accept (t)emporarily or " "accept (p)ermanently? "); if (fgets(buf, sizeof(buf), stdin) == NULL) return -1; c = tolower((int)(*buf)); } while (c != 'r' && c != 't' && c != 'p'); if (c == 'r') return -1; else if (c == 't') return 0; n = snprintf(&b, 1, "%s/%s", env.home, PATHNAME_CERTS); if (env.pathmax != -1 && n > env.pathmax) fatal(ERROR_PATHNAME, "pathname limit %ld exceeded: %d\n", env.pathmax, n); certf = (char *)xmalloc((n + 1) * sizeof(char)); snprintf(certf, n + 1, "%s/%s", env.home, PATHNAME_CERTS); create_file(certf, S_IRUSR | S_IWUSR); fd = fopen(certf, "a"); xfree(certf); if (fd == NULL) return -1; PEM_write_X509(fd, cert); fclose(fd); return 0; } /* * Ask user to proceed, while a fingerprint mismatch in the SSL/TLS certificate * was found. */ int mismatch_cert(void) { char c, buf[64]; do { printf("ATTENTION: SSL/TLS certificate fingerprint mismatch.\n" "Proceed with the connection (y/n)? "); if (fgets(buf, sizeof(buf), stdin) == NULL) return -1; c = tolower((int)(*buf)); } while (c != 'y' && c != 'n'); if (c == 'y') return 0; else return -1; } #endif /* NO_SSLTLS */