}
-// TCP client - connect to a host/port
-int tcp_connectsock(char *host, char *service) {
- struct in6_addr serveraddr;
- struct addrinfo hints;
- struct addrinfo *res = NULL;
- struct addrinfo *ai = NULL;
- int rc = (-1);
- int s = (-1);
-
- if ((host == NULL) || IsEmptyStr(host))
- return (-1);
- if ((service == NULL) || IsEmptyStr(service))
- return (-1);
-
- syslog(LOG_DEBUG, "tcp_connectsock(%s,%s)", host, service);
-
- memset(&hints, 0x00, sizeof(hints));
- hints.ai_flags = AI_NUMERICSERV;
- hints.ai_family = AF_UNSPEC;
- hints.ai_socktype = SOCK_STREAM;
-
- // Handle numeric IPv4 and IPv6 addresses
- rc = inet_pton(AF_INET, host, &serveraddr);
- if (rc == 1) { // dotted quad
- hints.ai_family = AF_INET;
- hints.ai_flags |= AI_NUMERICHOST;
- }
- else {
- rc = inet_pton(AF_INET6, host, &serveraddr);
- if (rc == 1) { // IPv6 address
- hints.ai_family = AF_INET6;
- hints.ai_flags |= AI_NUMERICHOST;
- }
- }
-
- // Begin the connection process
-
- rc = getaddrinfo(host, service, &hints, &res);
- if (rc != 0) {
- syslog(LOG_DEBUG, "%s: %s", host, gai_strerror(rc));
- freeaddrinfo(res);
- return (-1);
- }
-
- // Try all available addresses until we connect to one or until we run out.
- for (ai = res; ai != NULL; ai = ai->ai_next) {
-
- if (ai->ai_family == AF_INET)
- syslog(LOG_DEBUG, "Trying IPv4");
- else if (ai->ai_family == AF_INET6)
- syslog(LOG_DEBUG, "Trying IPv6");
- else
- syslog(LOG_WARNING, "This is going to fail.");
-
- s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
- if (s < 0) {
- syslog(LOG_WARNING, "socket() failed: %s", strerror(errno));
- freeaddrinfo(res);
- return (-1);
- }
- rc = connect(s, ai->ai_addr, ai->ai_addrlen);
- if (rc >= 0) {
- int fdflags;
- freeaddrinfo(res);
-
- fdflags = fcntl(rc, F_GETFL);
- if (fdflags < 0) {
- syslog(LOG_ERR, "unable to get socket %d flags! %s", rc, strerror(errno));
- close(rc);
- return -1;
- }
- fdflags = fdflags | O_NONBLOCK;
- if (fcntl(rc, F_SETFL, fdflags) < 0) {
- syslog(LOG_ERR, "unable to set socket %d nonblocking flags! %s", rc, strerror(errno));
- close(s);
- return -1;
- }
-
- return (s);
- }
- else {
- syslog(LOG_WARNING, "connect() failed: %s", strerror(errno));
- close(s);
- }
- }
- freeaddrinfo(res);
- return (-1);
-}
-
-
// Extract from the headers, the username and password the client is attempting to use.
// This could be HTTP AUTH or it could be in the cookies.
void extract_auth(struct http_transaction *h, char *authbuf, int authbuflen) {
if (is_new_session) {
strcpy(my_session->room, "");
- my_session->sock = tcp_connectsock(ctdlhost, ctdlport);
+ static char *ctdl_sock_path = NULL;
+ if (!ctdl_sock_path) {
+ ctdl_sock_path = malloc(PATH_MAX);
+ snprintf(ctdl_sock_path, PATH_MAX, "%s/citadel.socket", ctdl_dir);
+ }
+ my_session->sock = uds_connectsock(ctdl_sock_path);
ctdl_readline(my_session, buf, sizeof(buf)); // skip past the server greeting banner
if (!IsEmptyStr(auth)) { // do we need to log in to Citadel?
// Functions in this module handle SSL encryption when WebCit is running
// as an HTTPS server.
//
-// Copyright (c) 1996-2021 by the citadel.org team
+// Copyright (c) 1996-2022 by the citadel.org team
//
// This program is open source software. It runs great on the
// Linux operating system (and probably elsewhere). You can use,
#include "webcit.h"
-SSL_CTX *ssl_ctx; // SSL context
+SSL_CTX *ssl_ctx; // global SSL context
+char key_file[PATH_MAX] = "";
+char cert_file[PATH_MAX] = "";
char *ssl_cipher_list = DEFAULT_SSL_CIPHER_LIST;
-// Generate a private key for SSL
-void generate_key(char *keyfilename) {
- int ret = 0;
- RSA *rsa = NULL;
- BIGNUM *bne = NULL;
- int bits = 2048;
- unsigned long e = RSA_F4;
- FILE *fp;
-
- if (access(keyfilename, R_OK) == 0) {
- return;
+// Set the private key and certificate chain for the global SSL Context.
+// This is called during initialization, and can be called again later if the certificate changes.
+void bind_to_key_and_certificate(void) {
+ if (IsEmptyStr(key_file)) {
+ snprintf(key_file, sizeof key_file, "%s/keys/citadel.key", ctdl_dir);
+ }
+ if (IsEmptyStr(cert_file)) {
+ snprintf(cert_file, sizeof key_file, "%s/keys/citadel.cer", ctdl_dir);
}
- syslog(LOG_INFO, "crypto: generating RSA key pair");
+ syslog(LOG_DEBUG, "crypto: [re]installing key \"%s\" and certificate \"%s\"", key_file, cert_file);
- // generate rsa key
- bne = BN_new();
- ret = BN_set_word(bne, e);
- if (ret != 1) {
- goto free_all;
- }
+ SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file);
+ SSL_CTX_use_PrivateKey_file(ssl_ctx, key_file, SSL_FILETYPE_PEM);
- rsa = RSA_new();
- ret = RSA_generate_key_ex(rsa, bits, bne, NULL);
- if (ret != 1) {
- goto free_all;
+ if ( !SSL_CTX_check_private_key(ssl_ctx) ) {
+ syslog(LOG_WARNING, "crypto: cannot install certificate: %s", ERR_reason_error_string(ERR_get_error()));
}
- // write the key file
- fp = fopen(keyfilename, "w");
- if (fp != NULL) {
- chmod(keyfilename, 0600);
- if (PEM_write_RSAPrivateKey(fp, // the file */
- rsa, // the key */
- NULL, // no enc */
- NULL, // no passphrase
- 0, // no passphrase
- NULL, // no callback
- NULL // no callbk
- ) != 1) {
- syslog(LOG_ERR, "crypto: cannot write key: %s", ERR_reason_error_string(ERR_get_error()));
- unlink(keyfilename);
- }
- fclose(fp);
- }
- // 4. free
- free_all:
- RSA_free(rsa);
- BN_free(bne);
}
return;
}
- // Get our certificates in order.
- // First, create the key/cert directory if it's not there already...
- mkdir(CTDL_CRYPTO_DIR, 0700);
-
- // If we still don't have a private key, generate one.
- generate_key(CTDL_KEY_PATH);
-
- // If there is no certificate file on disk, we will be generating a self-signed certificate
- // in the next step. Therefore, if we have neither a CSR nor a certificate, generate
- // the CSR in this step so that the next step may commence.
- if ((access(CTDL_CER_PATH, R_OK) != 0) && (access(CTDL_CSR_PATH, R_OK) != 0)) {
- syslog(LOG_INFO, "Generating a certificate signing request.");
-
- // Read our key from the file. No, we don't just keep this
- // in memory from the above key-generation function, because
- // there is the possibility that the key was already on disk
- // and we didn't just generate it now.
- fp = fopen(CTDL_KEY_PATH, "r");
- if (fp) {
- rsa = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL);
- fclose(fp);
- }
-
- if (rsa) {
- // Create a public key from the private key
- if (pk = EVP_PKEY_new(), pk != NULL) {
- EVP_PKEY_assign_RSA(pk, rsa);
- if (req = X509_REQ_new(), req != NULL) {
- const char *env;
- // Set the public key
- X509_REQ_set_pubkey(req, pk);
- X509_REQ_set_version(req, 0L);
- name = X509_REQ_get_subject_name(req);
- X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *) "Citadel Server", -1, -1, 0);
- X509_NAME_add_entry_by_txt(name, "OU", MBSTRING_ASC, (unsigned char *) "Default Certificate PLEASE CHANGE", -1, -1, 0);
- X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *) "*", -1, -1, 0);
- X509_REQ_set_subject_name(req, name);
-
- // Sign the CSR
- if (!X509_REQ_sign(req, pk, EVP_md5())) {
- syslog(LOG_WARNING, "X509_REQ_sign(): error");
- }
- else {
- // Write it to disk
- fp = fopen(CTDL_CSR_PATH, "w");
- if (fp != NULL) {
- chmod(CTDL_CSR_PATH, 0600);
- PEM_write_X509_REQ(fp, req);
- fclose(fp);
- }
- else {
- syslog(LOG_WARNING, "Cannot write key: %s", CTDL_CSR_PATH);
- exit(1);
- }
- }
- X509_REQ_free(req);
- }
- }
- RSA_free(rsa);
- }
- else {
- syslog(LOG_WARNING, "Unable to read private key.");
- }
- }
-
- // Generate a self-signed certificate if we don't have one.
- if (access(CTDL_CER_PATH, R_OK) != 0) {
- syslog(LOG_INFO, "Generating a self-signed certificate.");
-
- // Same deal as before: always read the key from disk because
- // it may or may not have just been generated.
- fp = fopen(CTDL_KEY_PATH, "r");
- if (fp) {
- rsa = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL);
- fclose(fp);
- }
+ // Now try to bind to the key and certificate.
+ bind_to_key_and_certificate();
+}
- // This also holds true for the CSR
- req = NULL;
- cer = NULL;
- pk = NULL;
- if (rsa) {
- if (pk = EVP_PKEY_new(), pk != NULL) {
- EVP_PKEY_assign_RSA(pk, rsa);
- }
- fp = fopen(CTDL_CSR_PATH, "r");
- if (fp) {
- req = PEM_read_X509_REQ(fp, NULL, NULL, NULL);
- fclose(fp);
- }
+// Check the modification time of the key and certificate -- reload if they changed
+void update_key_and_cert_if_needed(void) {
+ static time_t cert_mtime = 0;
+ struct stat keystat;
+ struct stat certstat;
- if (req) {
- if (cer = X509_new(), cer != NULL) {
- ASN1_INTEGER_set(X509_get_serialNumber(cer), 0);
- X509_set_issuer_name(cer, X509_REQ_get_subject_name(req));
- X509_set_subject_name(cer, X509_REQ_get_subject_name(req));
- X509_gmtime_adj(X509_get_notBefore(cer), 0);
- X509_gmtime_adj(X509_get_notAfter(cer), (long) 60 * 60 * 24 * SIGN_DAYS);
- req_pkey = X509_REQ_get_pubkey(req);
- X509_set_pubkey(cer, req_pkey);
- EVP_PKEY_free(req_pkey);
-
- // Sign the cert
- if (!X509_sign(cer, pk, EVP_md5())) {
- syslog(LOG_WARNING, "X509_sign(): error");
- }
- else { // Write it to disk
- fp = fopen(CTDL_CER_PATH, "w");
- if (fp != NULL) {
- chmod(CTDL_CER_PATH, 0600);
- PEM_write_X509(fp, cer);
- fclose(fp);
- }
- else {
- syslog(LOG_WARNING, "Cannot write key: %s", CTDL_CER_PATH);
- exit(1);
- }
- }
- X509_free(cer);
- }
- }
- RSA_free(rsa);
- }
+ if (stat(key_file, &keystat) != 0) {
+ syslog(LOG_ERR, "%s: %s", key_file, strerror(errno));
+ return;
}
-
- // Now try to bind to the key and certificate.
- // Note that we use SSL_CTX_use_certificate_chain_file() which allows
- // the certificate file to contain intermediate certificates.
- SSL_CTX_use_certificate_chain_file(ssl_ctx, CTDL_CER_PATH);
- SSL_CTX_use_PrivateKey_file(ssl_ctx, CTDL_KEY_PATH, SSL_FILETYPE_PEM);
- if (!SSL_CTX_check_private_key(ssl_ctx)) {
- syslog(LOG_WARNING, "Cannot install certificate: %s", ERR_reason_error_string(ERR_get_error()));
+ if (stat(cert_file, &certstat) != 0) {
+ syslog(LOG_ERR, "%s: %s", cert_file, strerror(errno));
+ return;
}
+ if ((keystat.st_mtime > cert_mtime) || (certstat.st_mtime > cert_mtime)) {
+ bind_to_key_and_certificate();
+ cert_mtime = certstat.st_mtime;
+ }
}
if (!ssl_ctx) {
return;
}
+
+ // Check the modification time of the key and certificate -- reload if they changed
+ update_key_and_cert_if_needed();
+
if (!(ch->ssl_handle = SSL_new(ssl_ctx))) {
syslog(LOG_WARNING, "SSL_new failed: %s", ERR_reason_error_string(ERR_get_error()));
return;
if (ssl_error_reason == NULL) {
syslog(LOG_WARNING, "SSL_accept failed: errval=%ld, retval=%d %s", errval, retval, strerror(errval));
}
- else {
- syslog(LOG_WARNING, "SSL_accept failed: %s\n", ssl_error_reason);
- }
- sleep(1);
- retval = SSL_accept(ch->ssl_handle);
- }
- if (retval < 1) {
- long errval;
- const char *ssl_error_reason = NULL;
-
- errval = SSL_get_error(ch->ssl_handle, retval);
- ssl_error_reason = ERR_reason_error_string(ERR_get_error());
- if (ssl_error_reason == NULL) {
- syslog(LOG_WARNING, "SSL_accept failed: errval=%ld, retval=%d (%s)", errval, retval, strerror(errval));
- }
else {
syslog(LOG_WARNING, "SSL_accept failed: %s", ssl_error_reason);
}
- SSL_free(ch->ssl_handle);
- ch->ssl_handle = NULL;
- return;
+ //sleep(1);
+ //retval = SSL_accept(ch->ssl_handle);
}
+ //if (retval < 1) {
+ //long errval;
+ //const char *ssl_error_reason = NULL;
+//
+ //errval = SSL_get_error(ch->ssl_handle, retval);
+ //ssl_error_reason = ERR_reason_error_string(ERR_get_error());
+ //if (ssl_error_reason == NULL) {
+ //syslog(LOG_WARNING, "SSL_accept failed: errval=%ld, retval=%d (%s)", errval, retval, strerror(errval));
+ //}
+ //else {
+ //syslog(LOG_WARNING, "SSL_accept failed: %s", ssl_error_reason);
+ //}
+ //SSL_free(ch->ssl_handle);
+ //ch->ssl_handle = NULL;
+ //return;
+ //}
else {
syslog(LOG_INFO, "SSL_accept success");
}
syslog(LOG_INFO, "SSL/TLS using %s on %s (%d of %d bits)",
SSL_CIPHER_get_name(SSL_get_current_cipher(ch->ssl_handle)),
SSL_CIPHER_get_version(SSL_get_current_cipher(ch->ssl_handle)), bits, alg_bits);
-
syslog(LOG_INFO, "SSL started");
}
sleep(1);
continue;
}
- syslog(LOG_WARNING, "SSL_write got error %ld, ret %d", errval, retval);
+ syslog(LOG_WARNING, "SSL_write: %s", ERR_reason_error_string(ERR_get_error()));
if (retval == -1) {
syslog(LOG_WARNING, "errno is %d", errno);
endtls(ch);