From 268f90347acf630c5d0adf1a281919fb0ba86aa8 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Tue, 14 May 2024 02:53:04 +0000 Subject: [PATCH 01/16] minor comments and stuff --- citadel/server/modules/smtp/dkim.c | 4 +++- citadel/tests/dkimtester/README.md | 21 +++++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/citadel/server/modules/smtp/dkim.c b/citadel/server/modules/smtp/dkim.c index cb4f88179..639c5086d 100644 --- a/citadel/server/modules/smtp/dkim.c +++ b/citadel/server/modules/smtp/dkim.c @@ -31,6 +31,7 @@ #include +// This utility function is used by the body canonicalizer char *dkim_rtrim(char *str) { char *end; int len = strlen(str); @@ -85,6 +86,7 @@ void dkim_wrap_header_strbuf(StrBuf *header_in) { } +// This utility function is used by the body canonicalizer char *dkim_rtrim_lines(char *str) { char *end; int len = strlen(str); @@ -503,7 +505,7 @@ void dkim_sign(StrBuf *email, char *pkey_in, char *domain, char *selector) { EVP_MD_CTX_free(mdctx); // This is an optional routine to verify our own signature. - // The test program in tests/dkimtester enables it. It is not enabled during server operation. + // The test program in tests/dkimtester enables it. It is not enabled in Citadel Server. #ifdef DKIM_VERIFY_SIGNATURE mdctx = EVP_MD_CTX_new(); if (mdctx) { diff --git a/citadel/tests/dkimtester/README.md b/citadel/tests/dkimtester/README.md index 54c93c24e..5a9d9e891 100644 --- a/citadel/tests/dkimtester/README.md +++ b/citadel/tests/dkimtester/README.md @@ -1,11 +1,20 @@ # dkimtester -This is a test harness for Citadel's DKIM signing code. As this is -being written, the public DNS for dev.citadel.org has a DKIM record -that matches the private key in this test program. This is not -guaranteed to continue and if you want to run this test you might need -to set things up for yourself. +This is a test harness for Citadel's DKIM signing code. It is not likely +that you will use this program unless you're hacking on the DKIM signer +itself. The private key embedded in the test program matches the DKIM record +for `dev.citadel.org` at the time this is being written. That record looks +like this: -To run this test, you must have the `Mail::DKIM::Verifier` perl module +``` +foo._domainkey.dev.citadel.org IN TXT "v=DKIM1;k=rsa;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA37nn3HqaJEa56Ukg7MbvkA6ng/Bi/UJ2c/zeiMU3Qb+lnNdBMQXtiI0uXSiOwL4UkhCtMxhPB5KZsHnHG4nqUKnrnrXlswDGdbk+N2w95O3snu71qG4jj/ULaGBkerBtuYdluU23PdVuH3b2J8WFSY8fMCFRdH96Nub/q0LnqKNB5bxsgPf8YvRgGiS9OTpitcZzCYEr1v0PEM6u2qX9Q+lz1x7ob+bXrxJZGeY5bzNsNHE1cgMzK1IekZHJ/cTOPzT1mIIQXN4zuE6z50q6G0Bq19H3IsxPxTeiNIp9CnvGfoDDPDXhXLI7y2XvTKWPYtKcP8oCyIebdzuKqcAtSQIDAQAB" +``` +If you intend to work with this program, that key will be long gone by the +time you read this, and *you* don't control `dev.citadel.org` anyway, so you +will have to create your own host name and set your own DKIM record. You can +use this private key if you want to, though. + +To run this test, you also must have the `Mail::DKIM::Verifier` perl module installed, which can be found on CPAN. +Again -- this is not intended to be part of a regression test suite. -- 2.39.2 From 10da45b52f839d8e33abc2015fcb4f988720e3d9 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Tue, 14 May 2024 04:25:02 +0000 Subject: [PATCH 02/16] Use detection of config keys and sender domain to determine whether to dkim-sign a message. --- citadel/server/modules/smtp/serv_smtpclient.c | 19 +++++++++++++------ citadel/server/msgbase.c | 9 +++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/citadel/server/modules/smtp/serv_smtpclient.c b/citadel/server/modules/smtp/serv_smtpclient.c index ac5f3883e..7247fb19e 100644 --- a/citadel/server/modules/smtp/serv_smtpclient.c +++ b/citadel/server/modules/smtp/serv_smtpclient.c @@ -232,19 +232,26 @@ int smtp_attempt_delivery(long msgid, char *recp, char *envelope_from, char *sou CtdlOutputMsg(msgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0, NULL, &fromaddr, NULL); s.TheMessage = CC->redirect_buffer; CC->redirect_buffer = NULL; + syslog(LOG_DEBUG, "fromaddr=<%s>",fromaddr); // If we have a DKIM key, try to sign the message. char *dkim_private_key = CtdlGetConfigStr("dkim_private_key"); char *dkim_selector = CtdlGetConfigStr("dkim_selector"); char *dkim_from_domain = (strchr(fromaddr, '@') ? strchr(fromaddr, '@')+1 : NULL); - if (!IsEmptyStr(dkim_private_key) && !IsEmptyStr(dkim_selector) && !IsEmptyStr(dkim_from_domain)) { - char *pkey = strdup(dkim_private_key); - if (pkey) { + if ( + !IsEmptyStr(dkim_from_domain) // Is the sending domain non-empty? + && IsDirectory(fromaddr, 0) // and is it one of "our" domains? + && !IsEmptyStr(dkim_private_key) // Do we have a private signing key? + && !IsEmptyStr(dkim_selector) // and a selector to go with it? + ) { + char *pkey = strdup(dkim_private_key); // If you answered "yes" to all of the above questions, + if (pkey) { // congratulations! We get to DKIM-sign the message! char *sp; - while (sp = strchr(pkey, '_')) { - *sp = '\n'; + while (sp = strchr(pkey, '_')) { // The dkim_private_key record contains our RSA private key, + *sp = '\n'; // but we have to convert all the newlines back to underscores. } - syslog(LOG_DEBUG, "private key: <%s>\n", pkey); + syslog(LOG_DEBUG, "smtpclient: dkim-signing with private key for selector <%s> domain <%s>", + dkim_selector, dkim_from_domain); dkim_sign(s.TheMessage, pkey, dkim_from_domain, dkim_selector); free(pkey); } diff --git a/citadel/server/msgbase.c b/citadel/server/msgbase.c index 8582fba30..aaf4446c6 100644 --- a/citadel/server/msgbase.c +++ b/citadel/server/msgbase.c @@ -1488,16 +1488,13 @@ int CtdlOutputMsg(long msg_num, // message number (local) to fetch ); if ((Author != NULL) && (*Author == NULL)) { - long len; - CM_GetAsField(TheMessage, eAuthor, Author, &len); + *Author = strdup(TheMessage->cm_fields[eAuthor]); } if ((Address != NULL) && (*Address == NULL)) { - long len; - CM_GetAsField(TheMessage, erFc822Addr, Address, &len); + *Address = strdup(TheMessage->cm_fields[erFc822Addr]); } if ((MessageID != NULL) && (*MessageID == NULL)) { - long len; - CM_GetAsField(TheMessage, emessageId, MessageID, &len); + *MessageID = strdup(TheMessage->cm_fields[emessageId]); } CM_Free(TheMessage); TheMessage = NULL; -- 2.39.2 From aed205c388d55d4aa29c5a4aa6e25bcf252bc697 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Tue, 14 May 2024 05:02:40 +0000 Subject: [PATCH 03/16] dkim: create key and selector if absent --- citadel/server/modules/smtp/dkim.c | 61 +++++++++++++++++++ citadel/server/modules/smtp/serv_smtpclient.c | 1 + citadel/server/modules/smtp/smtp_util.h | 1 + 3 files changed, 63 insertions(+) diff --git a/citadel/server/modules/smtp/dkim.c b/citadel/server/modules/smtp/dkim.c index 639c5086d..c187916c3 100644 --- a/citadel/server/modules/smtp/dkim.c +++ b/citadel/server/modules/smtp/dkim.c @@ -29,6 +29,7 @@ #include #include #include +#include "../../config.h" // This utility function is used by the body canonicalizer @@ -545,3 +546,63 @@ void dkim_sign(StrBuf *email, char *pkey_in, char *domain, char *selector) { // And we're done! } + + +// Generate a private key and selector for DKIM if needed. This is called during server startup. +void dkim_init(void) { + + char *dkim_private_key = CtdlGetConfigStr("dkim_private_key"); + if (dkim_private_key) { + syslog(LOG_DEBUG, "dkim: private key exists and will continue to be used."); + } + else { + EVP_PKEY_CTX *ctx; + EVP_PKEY *pkey = NULL; + BIO *bio = NULL; + ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (ctx) { + if ( + (EVP_PKEY_keygen_init(ctx) == 1) + && (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048) == 1) + && (EVP_PKEY_keygen(ctx, &pkey) == 1) + ) { + syslog(LOG_DEBUG, "dkim: generated private key"); + bio = BIO_new(BIO_s_mem()); + if (bio) { + PEM_write_bio_PrivateKey(bio, pkey, NULL, NULL, 0, NULL, NULL); + char *b64key = malloc(4096); + if (b64key) { + size_t readbytes; + BIO_read_ex(bio, b64key, 4096, &readbytes); + b64key[readbytes] = 0; + char *nl = NULL; + while (nl=strchr(b64key, '\n'), nl) { // convert newlines to underscores + *nl = '_'; + } + CtdlSetConfigStr("dkim_private_key", b64key); + free(b64key); + } + free(bio); + } + } + EVP_PKEY_CTX_free(ctx); + } + } + + char *dkim_selector = CtdlGetConfigStr("dkim_selector"); + if (dkim_selector) { + syslog(LOG_DEBUG, "dkim: selector exists: %s", dkim_selector); + } + else { + // Quick and dirty algorithm to make up a five letter nonsense word as a selector + char new_selector[6]; + int i; + for (i=0; i<5; ++i) { + new_selector[i] = (rand() % 26) + 'a'; + } + new_selector[5] = 0; + syslog(LOG_DEBUG, "dkim: selector created: %s", new_selector); + CtdlSetConfigStr("dkim_selector", new_selector); + } + abort(); +} diff --git a/citadel/server/modules/smtp/serv_smtpclient.c b/citadel/server/modules/smtp/serv_smtpclient.c index 7247fb19e..3332cf4a5 100644 --- a/citadel/server/modules/smtp/serv_smtpclient.c +++ b/citadel/server/modules/smtp/serv_smtpclient.c @@ -605,6 +605,7 @@ char *ctdl_module_init_smtpclient(void) { CtdlRegisterSessionHook(smtp_do_queue_quick, EVT_HOUSE, PRIO_AGGR + 51); CtdlRegisterSessionHook(smtp_do_queue_full, EVT_TIMER, PRIO_AGGR + 51); smtp_init_spoolout(); + dkim_init(); } // return our module id for the log diff --git a/citadel/server/modules/smtp/smtp_util.h b/citadel/server/modules/smtp/smtp_util.h index 1a4e17162..497593406 100644 --- a/citadel/server/modules/smtp/smtp_util.h +++ b/citadel/server/modules/smtp/smtp_util.h @@ -30,3 +30,4 @@ enum { void smtp_do_bounce(const char *instr, int is_final); char *smtpstatus(int code); void dkim_sign(StrBuf *email, char *pkey_in, char *domain, char *selector); +void dkim_init(void); -- 2.39.2 From c4239f3379d21d0e86fdeafcda31bcba40d1a340 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Mon, 13 May 2024 22:03:42 -0700 Subject: [PATCH 04/16] dont abort --- citadel/server/modules/smtp/dkim.c | 1 - 1 file changed, 1 deletion(-) diff --git a/citadel/server/modules/smtp/dkim.c b/citadel/server/modules/smtp/dkim.c index c187916c3..96f4d03eb 100644 --- a/citadel/server/modules/smtp/dkim.c +++ b/citadel/server/modules/smtp/dkim.c @@ -604,5 +604,4 @@ void dkim_init(void) { syslog(LOG_DEBUG, "dkim: selector created: %s", new_selector); CtdlSetConfigStr("dkim_selector", new_selector); } - abort(); } -- 2.39.2 From ef6ad6420ae9cb152f8abbf2171099f9dacf2b1e Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Tue, 14 May 2024 14:58:59 -0700 Subject: [PATCH 05/16] begin config hash message --- citadel/server/modules/inetcfg/serv_inetcfg.c | 4 +- citadel/server/modules/smtp/dkim.c | 82 ++++++++++++++++++- citadel/server/modules/smtp/smtp_util.h | 1 + 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/citadel/server/modules/inetcfg/serv_inetcfg.c b/citadel/server/modules/inetcfg/serv_inetcfg.c index 19e5fd9f4..e7e9ebdb5 100644 --- a/citadel/server/modules/inetcfg/serv_inetcfg.c +++ b/citadel/server/modules/inetcfg/serv_inetcfg.c @@ -34,12 +34,13 @@ #include "../../genstamp.h" #include "../../domain.h" #include "../../ctdl_module.h" +#include "../smtp/smtp_util.h" void inetcfg_setTo(struct CtdlMessage *msg) { char *conf; char buf[SIZ]; - + if (CM_IsEmpty(msg, eMessageText)) return; conf = strdup(msg->cm_fields[eMessageText]); @@ -51,6 +52,7 @@ void inetcfg_setTo(struct CtdlMessage *msg) { if (inetcfg != NULL) free(inetcfg); inetcfg = conf; + dkim_check_advisory(); // this will check to see if we have to advise the admin about dkim } } diff --git a/citadel/server/modules/smtp/dkim.c b/citadel/server/modules/smtp/dkim.c index 96f4d03eb..beb1681e2 100644 --- a/citadel/server/modules/smtp/dkim.c +++ b/citadel/server/modules/smtp/dkim.c @@ -30,6 +30,7 @@ #include #include #include "../../config.h" +#include "../../internet_addressing.h" // This utility function is used by the body canonicalizer @@ -390,7 +391,7 @@ void dkim_sign(StrBuf *email, char *pkey_in, char *domain, char *selector) { // hash of the canonicalized body unsigned char *body_hash = malloc(SHA256_DIGEST_LENGTH); - SHA256((unsigned char*)relaxed_body, relaxed_body_len, body_hash); + SHA256((unsigned char *)relaxed_body, relaxed_body_len, body_hash); free(relaxed_body); // all we need now is the hash relaxed_body = NULL; @@ -605,3 +606,82 @@ void dkim_init(void) { CtdlSetConfigStr("dkim_selector", new_selector); } } + + +// If the DKIM key, DKIM selector, or set of signing domains has changed, we need to tell the administrator about it. +void dkim_check_advisory(void) { + + // If there is no DKIM ... there is nothing to discuss + if (IsEmptyStr(CtdlGetConfigStr("dkim_private_key"))) return; + if (IsEmptyStr(CtdlGetConfigStr("dkim_selector"))) return; + + // We're going to build a hash of the private key, the selector, and all signing domains. + // The way we build it doesn't matter, and it doesn't even have to be secure. + // This is just to let us know that we have to post an update to the administrator if the hash changes. + + StrBuf *hashsrc = NewStrBuf(); + if (!hashsrc) { + return; + } + + StrBufAppendBufPlain(hashsrc, CtdlGetConfigStr("dkim_private_key"), strlen(CtdlGetConfigStr("dkim_private_key")), 0); + StrBufAppendBufPlain(hashsrc, CtdlGetConfigStr("dkim_selector"), strlen(CtdlGetConfigStr("dkim_selector")), 0); + + char *ptr = inetcfg; + while (ptr && *ptr) { + char *sep = strchr(ptr, '|'); + if (sep && !strncasecmp(sep+1, HKEY("localhost"))) { + StrBufAppendBufPlain(hashsrc, ptr, sep-ptr, 0); + } + ptr = strchr(ptr, '\n'); + if (ptr) ++ptr; + } + + // make a hash from the string... + unsigned char *config_hash = malloc(SHA256_DIGEST_LENGTH); + SHA256((unsigned char *)ChrPtr(hashsrc), StrLength(hashsrc), config_hash); + FreeStrBuf(&hashsrc); + + // base64 encode it... + char *encoded_config_hash = malloc(SHA256_DIGEST_LENGTH * 2); + CtdlEncodeBase64(encoded_config_hash, config_hash, SHA256_DIGEST_LENGTH, BASE64_NO_LINEBREAKS); + free(config_hash); // all we need now is the encoded hash + + // Does it match the saved hash? + if ( (IsEmptyStr(CtdlGetConfigStr("dkim_config_hash"))) + || (strcmp(encoded_config_hash, CtdlGetConfigStr("dkim_config_hash"))) + ) { + // No? Post an Aide notification. + StrBuf *message = NewStrBuf(); + StrBufAppendPrintf(message, "%s", + " \n" + " Your domain configuration may have changed.\n" + " To allow the DKIM signatures of outbound mail to be verified, " + "please ensure that the following DNS records are created:\n" + " \n" + ); + + ptr = inetcfg; + while (ptr && *ptr) { + char *sep = strchr(ptr, '|'); + if (sep && !strncasecmp(sep+1, HKEY("localhost"))) { + StrBufAppendPrintf(message, " Record name : %s._domainkey.%s\n", + CtdlGetConfigStr("dkim_selector"), + "fixme.FIXME.com" + ); + StrBufAppendPrintf(message, " Record type : TXT\n"); + StrBufAppendPrintf(message, " Record value: c=dkim1 etc. etc. etc.\n"); + StrBufAppendPrintf(message, " \n"); + } + ptr = strchr(ptr, '\n'); + if (ptr) ++ptr; + } + + CtdlAideMessage(ChrPtr(message), "DKIM records"); + FreeStrBuf(&message); + } + + // Save it to the config database so we don't do this except when it changes. + CtdlSetConfigStr("dkim_config_hash", encoded_config_hash); + free(encoded_config_hash); +} diff --git a/citadel/server/modules/smtp/smtp_util.h b/citadel/server/modules/smtp/smtp_util.h index 497593406..8f3022643 100644 --- a/citadel/server/modules/smtp/smtp_util.h +++ b/citadel/server/modules/smtp/smtp_util.h @@ -31,3 +31,4 @@ void smtp_do_bounce(const char *instr, int is_final); char *smtpstatus(int code); void dkim_sign(StrBuf *email, char *pkey_in, char *domain, char *selector); void dkim_init(void); +void dkim_check_advisory(void); -- 2.39.2 From 4b62a55f3765af7042ba3c0bc6684dc11b5cca9d Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Wed, 15 May 2024 13:50:46 +0000 Subject: [PATCH 06/16] more work on alerting --- citadel/server/modules/inetcfg/serv_inetcfg.c | 2 +- citadel/server/modules/smtp/dkim.c | 104 ++++++++++++------ citadel/server/modules/smtp/serv_smtpclient.c | 17 +-- citadel/server/modules/smtp/smtp_util.h | 2 +- citadel/tests/dkimtester/Makefile | 2 +- 5 files changed, 79 insertions(+), 48 deletions(-) diff --git a/citadel/server/modules/inetcfg/serv_inetcfg.c b/citadel/server/modules/inetcfg/serv_inetcfg.c index e7e9ebdb5..1691f0f10 100644 --- a/citadel/server/modules/inetcfg/serv_inetcfg.c +++ b/citadel/server/modules/inetcfg/serv_inetcfg.c @@ -52,7 +52,7 @@ void inetcfg_setTo(struct CtdlMessage *msg) { if (inetcfg != NULL) free(inetcfg); inetcfg = conf; - dkim_check_advisory(); // this will check to see if we have to advise the admin about dkim + dkim_check_advisory(inetcfg); // this will check to see if we have to advise the admin about dkim } } diff --git a/citadel/server/modules/smtp/dkim.c b/citadel/server/modules/smtp/dkim.c index beb1681e2..b8835d36f 100644 --- a/citadel/server/modules/smtp/dkim.c +++ b/citadel/server/modules/smtp/dkim.c @@ -30,8 +30,6 @@ #include #include #include "../../config.h" -#include "../../internet_addressing.h" - // This utility function is used by the body canonicalizer char *dkim_rtrim(char *str) { @@ -362,6 +360,53 @@ void dkim_final_header_list(char *header_list, size_t header_list_size, StrBuf * } +// Supplied with a PEM-encoded PKCS#7 private key, that might also have newlines replaced with underscores, return an EVP_PKEY. +// Caller is responsible for freeing it. +EVP_PKEY *dkim_import_key(char *pkey_in) { + + if (!pkey_in) { + return(NULL); + } + + // Citadel Server stores the private key in PEM-encoded PKCS#7 format, but with all newlines replaced by underscores. + // Fix that before we try to decode it. + char *pkey_with_newlines = strdup(pkey_in); + if (!pkey_with_newlines) { + return(NULL); + } + char *sp; + while (sp = strchr(pkey_with_newlines, '_')) { + *sp = '\n'; + } + + // Load the private key into an OpenSSL "BIO" structure + BIO *bufio = BIO_new_mem_buf((void*)pkey_with_newlines, strlen(pkey_with_newlines)); + if (bufio == NULL) { + syslog(LOG_ERR, "dkim: BIO_new_mem_buf() failed"); + free(pkey_with_newlines); + return(NULL); + } + + // Now import the private key + EVP_PKEY *pkey = NULL; // Don't combine this line with the next one. It will barf. + pkey = PEM_read_bio_PrivateKey( + bufio, // BIO to read the private key from + &pkey, // pointer to EVP_PKEY structure + NULL, // password callback - can be NULL + NULL // parameter passed to callback or password if callback is NULL + ); + + free(pkey_with_newlines); + BIO_free(bufio); + + if (pkey == NULL) { + syslog(LOG_ERR, "dkim: PEM_read_bio_PrivateKey() failed"); + } + + return(pkey); +} + + // DKIM-sign an email, supplied as a full RFC2822-compliant message stored in a StrBuf void dkim_sign(StrBuf *email, char *pkey_in, char *domain, char *selector) { int i = 0; @@ -370,6 +415,12 @@ void dkim_sign(StrBuf *email, char *pkey_in, char *domain, char *selector) { return; } + // Import the private key + EVP_PKEY *pkey = dkim_import_key(pkey_in); + if (pkey == NULL) { + return; + } + // find the break between headers and body size_t msglen = StrLength(email); // total length of message (headers + body) @@ -436,27 +487,6 @@ void dkim_sign(StrBuf *email, char *pkey_in, char *domain, char *selector) { // Compute a hash of the canonicalized headers, and then sign that hash with our private key. // RFC6376 says that we hash and sign everything up to the "b=" and then we'll add the rest at the end. - // Load the private key into an OpenSSL "BIO" structure - BIO *bufio = BIO_new_mem_buf((void*)pkey_in, strlen(pkey_in)); - if (bufio == NULL) { - syslog(LOG_ERR, "dkim: BIO_new_mem_buf() failed"); - abort(); - } - - // Now import the private key - EVP_PKEY *pkey = NULL; // Don't combine this line with the next one. It will barf. - pkey = PEM_read_bio_PrivateKey( - bufio, // BIO to read the private key from - &pkey, // pointer to EVP_PKEY structure - NULL, // password callback - can be NULL - NULL // parameter passed to callback or password if callback is NULL - ); - if (pkey == NULL) { - syslog(LOG_ERR, "dkim: PEM_read_bio_PrivateKey() failed"); - abort(); - } - BIO_free(bufio); // Don't need this anymore, we have `pkey` now - // The hashing/signing library calls are documented at https://wiki.openssl.org/index.php/EVP_Signing_and_Verifying // NOTE: EVP_DigestSign*() functions are supplied with the actual data to be hashed and signed. // That means we don't hash it first, otherwise we would be signing double-hashed (and therefore wrong) data. @@ -549,11 +579,12 @@ void dkim_sign(StrBuf *email, char *pkey_in, char *domain, char *selector) { } +#ifndef DKIM_VERIFY_SIGNATURE // Generate a private key and selector for DKIM if needed. This is called during server startup. void dkim_init(void) { char *dkim_private_key = CtdlGetConfigStr("dkim_private_key"); - if (dkim_private_key) { + if (!IsEmptyStr(dkim_private_key)) { syslog(LOG_DEBUG, "dkim: private key exists and will continue to be used."); } else { @@ -609,7 +640,7 @@ void dkim_init(void) { // If the DKIM key, DKIM selector, or set of signing domains has changed, we need to tell the administrator about it. -void dkim_check_advisory(void) { +void dkim_check_advisory(char *inetcfg_in) { // If there is no DKIM ... there is nothing to discuss if (IsEmptyStr(CtdlGetConfigStr("dkim_private_key"))) return; @@ -627,7 +658,7 @@ void dkim_check_advisory(void) { StrBufAppendBufPlain(hashsrc, CtdlGetConfigStr("dkim_private_key"), strlen(CtdlGetConfigStr("dkim_private_key")), 0); StrBufAppendBufPlain(hashsrc, CtdlGetConfigStr("dkim_selector"), strlen(CtdlGetConfigStr("dkim_selector")), 0); - char *ptr = inetcfg; + char *ptr = inetcfg_in; while (ptr && *ptr) { char *sep = strchr(ptr, '|'); if (sep && !strncasecmp(sep+1, HKEY("localhost"))) { @@ -661,23 +692,27 @@ void dkim_check_advisory(void) { " \n" ); - ptr = inetcfg; + ptr = inetcfg_in; while (ptr && *ptr) { char *sep = strchr(ptr, '|'); if (sep && !strncasecmp(sep+1, HKEY("localhost"))) { - StrBufAppendPrintf(message, " Record name : %s._domainkey.%s\n", - CtdlGetConfigStr("dkim_selector"), - "fixme.FIXME.com" - ); - StrBufAppendPrintf(message, " Record type : TXT\n"); - StrBufAppendPrintf(message, " Record value: c=dkim1 etc. etc. etc.\n"); - StrBufAppendPrintf(message, " \n"); + StrBufAppendPrintf(message, " Host name : %s._domainkey.", CtdlGetConfigStr("dkim_selector")); + StrBufAppendBufPlain(message, ptr, sep-ptr, 0); + StrBufAppendBufPlain(message, HKEY("\r\n"), 0); + StrBufAppendPrintf(message, " Record type: TXT\n"); + StrBufAppendBufPlain(message, HKEY(" Value : v=DKIM1;k=rsa;p="), 0); + + // figure out the public key and get it + + StrBufAppendPrintf(message, "\n \n"); } ptr = strchr(ptr, '\n'); if (ptr) ++ptr; } +#if 0 CtdlAideMessage(ChrPtr(message), "DKIM records"); +#endif FreeStrBuf(&message); } @@ -685,3 +720,4 @@ void dkim_check_advisory(void) { CtdlSetConfigStr("dkim_config_hash", encoded_config_hash); free(encoded_config_hash); } +#endif diff --git a/citadel/server/modules/smtp/serv_smtpclient.c b/citadel/server/modules/smtp/serv_smtpclient.c index 3332cf4a5..03f54b108 100644 --- a/citadel/server/modules/smtp/serv_smtpclient.c +++ b/citadel/server/modules/smtp/serv_smtpclient.c @@ -244,17 +244,12 @@ int smtp_attempt_delivery(long msgid, char *recp, char *envelope_from, char *sou && !IsEmptyStr(dkim_private_key) // Do we have a private signing key? && !IsEmptyStr(dkim_selector) // and a selector to go with it? ) { - char *pkey = strdup(dkim_private_key); // If you answered "yes" to all of the above questions, - if (pkey) { // congratulations! We get to DKIM-sign the message! - char *sp; - while (sp = strchr(pkey, '_')) { // The dkim_private_key record contains our RSA private key, - *sp = '\n'; // but we have to convert all the newlines back to underscores. - } - syslog(LOG_DEBUG, "smtpclient: dkim-signing with private key for selector <%s> domain <%s>", - dkim_selector, dkim_from_domain); - dkim_sign(s.TheMessage, pkey, dkim_from_domain, dkim_selector); - free(pkey); - } + // If you answered "yes" to all of the above questions, congratulations! We get to sign the message! + syslog(LOG_DEBUG, "smtpclient: dkim-signing for selector <%s> in domain <%s>", dkim_selector, dkim_from_domain); + + // Remember, the dkim_sign() function is capable of handling a PEM-encoded PKCS#7 private key that + // has had all of its newlines replaced by underscores -- which is exactly how we store it. + dkim_sign(s.TheMessage,dkim_private_key, dkim_from_domain, dkim_selector); } // Prepare the buffer for transmittal diff --git a/citadel/server/modules/smtp/smtp_util.h b/citadel/server/modules/smtp/smtp_util.h index 8f3022643..041fa4dac 100644 --- a/citadel/server/modules/smtp/smtp_util.h +++ b/citadel/server/modules/smtp/smtp_util.h @@ -31,4 +31,4 @@ void smtp_do_bounce(const char *instr, int is_final); char *smtpstatus(int code); void dkim_sign(StrBuf *email, char *pkey_in, char *domain, char *selector); void dkim_init(void); -void dkim_check_advisory(void); +void dkim_check_advisory(char *inetcfg_in); diff --git a/citadel/tests/dkimtester/Makefile b/citadel/tests/dkimtester/Makefile index d308ec2fc..0ab4467da 100644 --- a/citadel/tests/dkimtester/Makefile +++ b/citadel/tests/dkimtester/Makefile @@ -1,7 +1,7 @@ # Makefile dkimtester: dkimtester.c ../../server/modules/smtp/dkim.c - cc -DDKIM_VERIFY_SIGNATURE -g dkimtester.c ../../server/modules/smtp/dkim.c -lcrypto -lcitadel -o dkimtester + cc -DCTDLDIR=/tmp -DDKIM_VERIFY_SIGNATURE -g dkimtester.c ../../server/modules/smtp/dkim.c -lcrypto -lcitadel -o dkimtester clean: rm -f dkimtester -- 2.39.2 From c8741577e79a402576de6335017a659e5a1ca4c7 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Wed, 15 May 2024 15:22:45 +0000 Subject: [PATCH 07/16] split dkim into signing and binding modules This allows citserver and dkimtester to be built cleanly --- citadel/server/modules/smtp/dkim.c | 143 ---------------- citadel/server/modules/smtp/dkim_bindings.c | 178 ++++++++++++++++++++ citadel/tests/dkimtester/Makefile | 2 +- 3 files changed, 179 insertions(+), 144 deletions(-) create mode 100644 citadel/server/modules/smtp/dkim_bindings.c diff --git a/citadel/server/modules/smtp/dkim.c b/citadel/server/modules/smtp/dkim.c index b8835d36f..f2a5d9d25 100644 --- a/citadel/server/modules/smtp/dkim.c +++ b/citadel/server/modules/smtp/dkim.c @@ -29,7 +29,6 @@ #include #include #include -#include "../../config.h" // This utility function is used by the body canonicalizer char *dkim_rtrim(char *str) { @@ -579,145 +578,3 @@ void dkim_sign(StrBuf *email, char *pkey_in, char *domain, char *selector) { } -#ifndef DKIM_VERIFY_SIGNATURE -// Generate a private key and selector for DKIM if needed. This is called during server startup. -void dkim_init(void) { - - char *dkim_private_key = CtdlGetConfigStr("dkim_private_key"); - if (!IsEmptyStr(dkim_private_key)) { - syslog(LOG_DEBUG, "dkim: private key exists and will continue to be used."); - } - else { - EVP_PKEY_CTX *ctx; - EVP_PKEY *pkey = NULL; - BIO *bio = NULL; - ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); - if (ctx) { - if ( - (EVP_PKEY_keygen_init(ctx) == 1) - && (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048) == 1) - && (EVP_PKEY_keygen(ctx, &pkey) == 1) - ) { - syslog(LOG_DEBUG, "dkim: generated private key"); - bio = BIO_new(BIO_s_mem()); - if (bio) { - PEM_write_bio_PrivateKey(bio, pkey, NULL, NULL, 0, NULL, NULL); - char *b64key = malloc(4096); - if (b64key) { - size_t readbytes; - BIO_read_ex(bio, b64key, 4096, &readbytes); - b64key[readbytes] = 0; - char *nl = NULL; - while (nl=strchr(b64key, '\n'), nl) { // convert newlines to underscores - *nl = '_'; - } - CtdlSetConfigStr("dkim_private_key", b64key); - free(b64key); - } - free(bio); - } - } - EVP_PKEY_CTX_free(ctx); - } - } - - char *dkim_selector = CtdlGetConfigStr("dkim_selector"); - if (dkim_selector) { - syslog(LOG_DEBUG, "dkim: selector exists: %s", dkim_selector); - } - else { - // Quick and dirty algorithm to make up a five letter nonsense word as a selector - char new_selector[6]; - int i; - for (i=0; i<5; ++i) { - new_selector[i] = (rand() % 26) + 'a'; - } - new_selector[5] = 0; - syslog(LOG_DEBUG, "dkim: selector created: %s", new_selector); - CtdlSetConfigStr("dkim_selector", new_selector); - } -} - - -// If the DKIM key, DKIM selector, or set of signing domains has changed, we need to tell the administrator about it. -void dkim_check_advisory(char *inetcfg_in) { - - // If there is no DKIM ... there is nothing to discuss - if (IsEmptyStr(CtdlGetConfigStr("dkim_private_key"))) return; - if (IsEmptyStr(CtdlGetConfigStr("dkim_selector"))) return; - - // We're going to build a hash of the private key, the selector, and all signing domains. - // The way we build it doesn't matter, and it doesn't even have to be secure. - // This is just to let us know that we have to post an update to the administrator if the hash changes. - - StrBuf *hashsrc = NewStrBuf(); - if (!hashsrc) { - return; - } - - StrBufAppendBufPlain(hashsrc, CtdlGetConfigStr("dkim_private_key"), strlen(CtdlGetConfigStr("dkim_private_key")), 0); - StrBufAppendBufPlain(hashsrc, CtdlGetConfigStr("dkim_selector"), strlen(CtdlGetConfigStr("dkim_selector")), 0); - - char *ptr = inetcfg_in; - while (ptr && *ptr) { - char *sep = strchr(ptr, '|'); - if (sep && !strncasecmp(sep+1, HKEY("localhost"))) { - StrBufAppendBufPlain(hashsrc, ptr, sep-ptr, 0); - } - ptr = strchr(ptr, '\n'); - if (ptr) ++ptr; - } - - // make a hash from the string... - unsigned char *config_hash = malloc(SHA256_DIGEST_LENGTH); - SHA256((unsigned char *)ChrPtr(hashsrc), StrLength(hashsrc), config_hash); - FreeStrBuf(&hashsrc); - - // base64 encode it... - char *encoded_config_hash = malloc(SHA256_DIGEST_LENGTH * 2); - CtdlEncodeBase64(encoded_config_hash, config_hash, SHA256_DIGEST_LENGTH, BASE64_NO_LINEBREAKS); - free(config_hash); // all we need now is the encoded hash - - // Does it match the saved hash? - if ( (IsEmptyStr(CtdlGetConfigStr("dkim_config_hash"))) - || (strcmp(encoded_config_hash, CtdlGetConfigStr("dkim_config_hash"))) - ) { - // No? Post an Aide notification. - StrBuf *message = NewStrBuf(); - StrBufAppendPrintf(message, "%s", - " \n" - " Your domain configuration may have changed.\n" - " To allow the DKIM signatures of outbound mail to be verified, " - "please ensure that the following DNS records are created:\n" - " \n" - ); - - ptr = inetcfg_in; - while (ptr && *ptr) { - char *sep = strchr(ptr, '|'); - if (sep && !strncasecmp(sep+1, HKEY("localhost"))) { - StrBufAppendPrintf(message, " Host name : %s._domainkey.", CtdlGetConfigStr("dkim_selector")); - StrBufAppendBufPlain(message, ptr, sep-ptr, 0); - StrBufAppendBufPlain(message, HKEY("\r\n"), 0); - StrBufAppendPrintf(message, " Record type: TXT\n"); - StrBufAppendBufPlain(message, HKEY(" Value : v=DKIM1;k=rsa;p="), 0); - - // figure out the public key and get it - - StrBufAppendPrintf(message, "\n \n"); - } - ptr = strchr(ptr, '\n'); - if (ptr) ++ptr; - } - -#if 0 - CtdlAideMessage(ChrPtr(message), "DKIM records"); -#endif - FreeStrBuf(&message); - } - - // Save it to the config database so we don't do this except when it changes. - CtdlSetConfigStr("dkim_config_hash", encoded_config_hash); - free(encoded_config_hash); -} -#endif diff --git a/citadel/server/modules/smtp/dkim_bindings.c b/citadel/server/modules/smtp/dkim_bindings.c new file mode 100644 index 000000000..e762ecf65 --- /dev/null +++ b/citadel/server/modules/smtp/dkim_bindings.c @@ -0,0 +1,178 @@ +// DKIM bindings to Citadel Server +// +// (C) 2024 by Art Cancro +// +// This program is open source software. Use, duplication, or disclosure is subject to the GNU General Public License v3. + +// Make sure we don't accidentally use any deprecated API calls +#define OPENSSL_NO_DEPRECATED_3_0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../config.h" +#include "../../msgbase.h" + + +// Generate a private key and selector for DKIM if needed. This is called during server startup. +void dkim_init(void) { + + char *dkim_private_key = CtdlGetConfigStr("dkim_private_key"); + if (!IsEmptyStr(dkim_private_key)) { + syslog(LOG_DEBUG, "dkim: private key exists and will continue to be used."); + } + else { + EVP_PKEY_CTX *ctx; + EVP_PKEY *pkey = NULL; + BIO *bio = NULL; + ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (ctx) { + if ( + (EVP_PKEY_keygen_init(ctx) == 1) + && (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048) == 1) + && (EVP_PKEY_keygen(ctx, &pkey) == 1) + ) { + syslog(LOG_DEBUG, "dkim: generated private key"); + bio = BIO_new(BIO_s_mem()); + if (bio) { + PEM_write_bio_PrivateKey(bio, pkey, NULL, NULL, 0, NULL, NULL); + char *b64key = malloc(4096); + if (b64key) { + size_t readbytes; + BIO_read_ex(bio, b64key, 4096, &readbytes); + b64key[readbytes] = 0; + char *nl = NULL; + while (nl=strchr(b64key, '\n'), nl) { // convert newlines to underscores + *nl = '_'; + } + CtdlSetConfigStr("dkim_private_key", b64key); + free(b64key); + } + free(bio); + } + } + EVP_PKEY_CTX_free(ctx); + } + } + + char *dkim_selector = CtdlGetConfigStr("dkim_selector"); + if (dkim_selector) { + syslog(LOG_DEBUG, "dkim: selector exists: %s", dkim_selector); + } + else { + // Quick and dirty algorithm to make up a five letter nonsense word as a selector + char new_selector[6]; + int i; + for (i=0; i<5; ++i) { + new_selector[i] = (rand() % 26) + 'a'; + } + new_selector[5] = 0; + syslog(LOG_DEBUG, "dkim: selector created: %s", new_selector); + CtdlSetConfigStr("dkim_selector", new_selector); + } +} + + +// If the DKIM key, DKIM selector, or set of signing domains has changed, we need to tell the administrator about it. +void dkim_check_advisory(char *inetcfg_in) { + + // If there is no DKIM ... there is nothing to discuss + if (IsEmptyStr(CtdlGetConfigStr("dkim_private_key"))) return; + if (IsEmptyStr(CtdlGetConfigStr("dkim_selector"))) return; + + // We're going to build a hash of the private key, the selector, and all signing domains. + // The way we build it doesn't matter, and it doesn't even have to be secure. + // This is just to let us know that we have to post an update to the administrator if the hash changes. + + StrBuf *hashsrc = NewStrBuf(); + if (!hashsrc) { + return; + } + + StrBufAppendBufPlain(hashsrc, CtdlGetConfigStr("dkim_private_key"), strlen(CtdlGetConfigStr("dkim_private_key")), 0); + StrBufAppendBufPlain(hashsrc, CtdlGetConfigStr("dkim_selector"), strlen(CtdlGetConfigStr("dkim_selector")), 0); + + char *ptr = inetcfg_in; + while (ptr && *ptr) { + char *sep = strchr(ptr, '|'); + if (sep && !strncasecmp(sep+1, HKEY("localhost"))) { + StrBufAppendBufPlain(hashsrc, ptr, sep-ptr, 0); + } + ptr = strchr(ptr, '\n'); + if (ptr) ++ptr; + } + + // make a hash from the string... + unsigned char *config_hash = malloc(SHA256_DIGEST_LENGTH); + SHA256((unsigned char *)ChrPtr(hashsrc), StrLength(hashsrc), config_hash); + FreeStrBuf(&hashsrc); + + // base64 encode it... + char *encoded_config_hash = malloc(SHA256_DIGEST_LENGTH * 2); + CtdlEncodeBase64(encoded_config_hash, config_hash, SHA256_DIGEST_LENGTH, BASE64_NO_LINEBREAKS); + free(config_hash); // all we need now is the encoded hash + + // Does it match the saved hash? + if ( (IsEmptyStr(CtdlGetConfigStr("dkim_config_hash"))) + || (strcmp(encoded_config_hash, CtdlGetConfigStr("dkim_config_hash"))) + ) { + // No? Post an Aide notification. + StrBuf *message = NewStrBuf(); + StrBufAppendBufPlain(message, HKEY( + "Content-type: text/plain\r\n" + "\r\n" + "Your domain configuration may have changed.\r\n" + "To allow the DKIM signatures of outbound mail to be verified,\r\n" + "please ensure that the following DNS records are created:\r\n" + "\r\n" + ),0); + + ptr = inetcfg_in; + while (ptr && *ptr) { + char *sep = strchr(ptr, '|'); + if (sep && !strncasecmp(sep+1, HKEY("localhost"))) { + StrBufAppendPrintf(message, "Host name : %s._domainkey.", CtdlGetConfigStr("dkim_selector")); + StrBufAppendBufPlain(message, ptr, sep-ptr, 0); + StrBufAppendBufPlain(message, HKEY("\r\n"), 0); + StrBufAppendPrintf(message, "Record type: TXT\r\n"); + StrBufAppendBufPlain(message, HKEY("Value : v=DKIM1;k=rsa;p="), 0); + + // figure out the public key and get it + + StrBufAppendPrintf(message, "\r\n"); + } + if (ptr) ++ptr; + } + + quickie_message("Citadel", + NULL, // from + NULL, // to + AIDEROOM, // room + ChrPtr(message), // text + FMT_RFC822, // format + "DKIM records" // subject + ); + FreeStrBuf(&message); + } + + // Save it to the config database so we don't do this except when it changes. + CtdlSetConfigStr("dkim_config_hash", encoded_config_hash); + free(encoded_config_hash); +} diff --git a/citadel/tests/dkimtester/Makefile b/citadel/tests/dkimtester/Makefile index 0ab4467da..d308ec2fc 100644 --- a/citadel/tests/dkimtester/Makefile +++ b/citadel/tests/dkimtester/Makefile @@ -1,7 +1,7 @@ # Makefile dkimtester: dkimtester.c ../../server/modules/smtp/dkim.c - cc -DCTDLDIR=/tmp -DDKIM_VERIFY_SIGNATURE -g dkimtester.c ../../server/modules/smtp/dkim.c -lcrypto -lcitadel -o dkimtester + cc -DDKIM_VERIFY_SIGNATURE -g dkimtester.c ../../server/modules/smtp/dkim.c -lcrypto -lcitadel -o dkimtester clean: rm -f dkimtester -- 2.39.2 From 1777c66ff10454d2a9783d37795907b3317541d5 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Wed, 15 May 2024 19:37:05 +0000 Subject: [PATCH 08/16] Fix bug in ctdlload import of email addresses. Go figure. A base64-encoded string sometimes doesn't include the null terminator. Fixed that. --- citadel/server/modules/smtp/dkim.c | 4 +-- citadel/server/modules/smtp/dkim_bindings.c | 15 +++++---- citadel/tests/dkimtester/dkimtester.c | 36 +++------------------ citadel/utils/ctdlload.c | 11 +++++-- 4 files changed, 24 insertions(+), 42 deletions(-) diff --git a/citadel/server/modules/smtp/dkim.c b/citadel/server/modules/smtp/dkim.c index f2a5d9d25..4099e0ea6 100644 --- a/citadel/server/modules/smtp/dkim.c +++ b/citadel/server/modules/smtp/dkim.c @@ -379,7 +379,7 @@ EVP_PKEY *dkim_import_key(char *pkey_in) { } // Load the private key into an OpenSSL "BIO" structure - BIO *bufio = BIO_new_mem_buf((void*)pkey_with_newlines, strlen(pkey_with_newlines)); + BIO *bufio = BIO_new_mem_buf((void *)pkey_with_newlines, strlen(pkey_with_newlines)); if (bufio == NULL) { syslog(LOG_ERR, "dkim: BIO_new_mem_buf() failed"); free(pkey_with_newlines); @@ -399,7 +399,7 @@ EVP_PKEY *dkim_import_key(char *pkey_in) { BIO_free(bufio); if (pkey == NULL) { - syslog(LOG_ERR, "dkim: PEM_read_bio_PrivateKey() failed"); + syslog(LOG_ERR, "dkim: PEM_read_bio_PrivateKey() failed with error 0x%lx", ERR_get_error()); } return(pkey); diff --git a/citadel/server/modules/smtp/dkim_bindings.c b/citadel/server/modules/smtp/dkim_bindings.c index e762ecf65..e7491c154 100644 --- a/citadel/server/modules/smtp/dkim_bindings.c +++ b/citadel/server/modules/smtp/dkim_bindings.c @@ -137,8 +137,8 @@ void dkim_check_advisory(char *inetcfg_in) { StrBuf *message = NewStrBuf(); StrBufAppendBufPlain(message, HKEY( "Content-type: text/plain\r\n" - "\r\n" - "Your domain configuration may have changed.\r\n" + "\r\n\r\n" + "Your domain configuration may have changed.\r\n\r\n" "To allow the DKIM signatures of outbound mail to be verified,\r\n" "please ensure that the following DNS records are created:\r\n" "\r\n" @@ -154,11 +154,14 @@ void dkim_check_advisory(char *inetcfg_in) { StrBufAppendPrintf(message, "Record type: TXT\r\n"); StrBufAppendBufPlain(message, HKEY("Value : v=DKIM1;k=rsa;p="), 0); - // figure out the public key and get it + // FIXME calculate the public key and add it here - StrBufAppendPrintf(message, "\r\n"); + StrBufAppendPrintf(message, "\r\n\r\n"); + } + ptr = strchr(ptr, '\n'); + if (ptr) { + ++ptr; } - if (ptr) ++ptr; } quickie_message("Citadel", @@ -167,7 +170,7 @@ void dkim_check_advisory(char *inetcfg_in) { AIDEROOM, // room ChrPtr(message), // text FMT_RFC822, // format - "DKIM records" // subject + "Confirm your DKIM records" // subject ); FreeStrBuf(&message); } diff --git a/citadel/tests/dkimtester/dkimtester.c b/citadel/tests/dkimtester/dkimtester.c index 271a93f81..4a4bf3391 100644 --- a/citadel/tests/dkimtester/dkimtester.c +++ b/citadel/tests/dkimtester/dkimtester.c @@ -15,7 +15,7 @@ int main(int argc, char *argv[]) { fprintf(stderr, "\033[44m\033[1m╔════════════════════════════════════════════════════════════════════════╗\033[0m\n" "\033[44m\033[1m║ DKIM signature test program for Citadel ║\033[0m\n" - "\033[44m\033[1m║ Copyright (c) 2024 by citadel.org et al. ║\033[0m\n" + "\033[44m\033[1m║ Copyright (c) 2024 by citadel.org (Art Cancro et al.) ║\033[0m\n" "\033[44m\033[1m║ This program is open source software. Use, duplication, or disclosure ║\033[0m\n" "\033[44m\033[1m║ is subject to the terms of the GNU General Public license v3. ║\033[0m\n" "\033[44m\033[1m╚════════════════════════════════════════════════════════════════════════╝\033[0m\n" @@ -23,36 +23,10 @@ int main(int argc, char *argv[]) { openlog("dkim", LOG_PERROR, LOG_USER); - char *private_key = - "-----BEGIN PRIVATE KEY-----\n" - "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDfuefcepokRrnp\n" - "SSDsxu+QDqeD8GL9QnZz/N6IxTdBv6Wc10ExBe2IjS5dKI7AvhSSEK0zGE8Hkpmw\n" - "eccbiepQqeueteWzAMZ1uT43bD3k7eye7vWobiOP9QtoYGR6sG25h2W5Tbc91W4f\n" - "dvYnxYVJjx8wIVF0f3o25v+rQueoo0HlvGyA9/xi9GAaJL05OmK1xnMJgSvW/Q8Q\n" - "zq7apf1D6XPXHuhv5tevElkZ5jlvM2w0cTVyAzMrUh6Rkcn9xM4/NPWYghBc3jO4\n" - "TrPnSrobQGrX0fcizE/FN6I0in0Ke8Z+gMM8NeFcsjvLZe9MpY9i0pw/ygLIh5t3\n" - "O4qpwC1JAgMBAAECggEAIwiTCMEAGzciDKhhagJ66BWLYMtHTP5X2zDZThSH4xlW\n" - "HznL4RfbCtuEy5y6we7h/L90x8ACPB7WRz7CkYrmsMvy9A7q0b2I1k10MyyVgqBJ\n" - "QdgMitv4YKYQK7+QbG/tNrS/lqVXUOz3iiDQSgkRpqOtUBWfkj0WD7vbhF99NDhV\n" - "dxaehFkKv3yNy0bXJlHJBJ6KtOUnDwub8TExh8dyj3kB8Qzj4I98shaXPNUSSaOw\n" - "zG6QG72yrxlMs495jkIPbF2JDidmLrX+oVISwKyaBWx+BkFV/KFAEKgaB5/nCw7+\n" - "qq/jxsmXim3HuQ3MIAjq1yw9aGRH1HMi8Gn7tYlNGwKBgQDy6EEKpuEiW9wwlI2+\n" - "GVuSkhSTTX1h6qK/ay8Jtyb8yJM/BxogAQlfjdgFixiZHy5MaomTbfeT2GDji553\n" - "+RsnZ60+g7FI9nHwabSxtuCQ+vjbFqCsdMPAiSeG0bEzo0zf5TjASdUtuZL0vXjl\n" - "yMZWDEuESoVNlYlvCOVkw2nvIwKBgQDryPuSq6PNVHRWsKRRs5ju4wKs/1ucBOg5\n" - "gCcN8lE03mFCWAlZhypE4/fAhTQ/a5KQoAzc0QZcXRueDyNsnc+QWw3/QWf8/fkV\n" - "HPfTWS3Dcuj+4RnWUucaZ/mKFlTC3+eNSlpyaPIMlCjXGsJ9GlPrsaAi9KPbD2v/\n" - "XcMq/PMOowKBgHVf7S3sfZVQthFzdxqIvksQ84hKRW/vJT1B2bTkH56+fQhTsjgM\n" - "yC64J85l7DjxbDnYsSngVWXHhOnvKV/nq0tbOcefcydCjsQREBNfvxvPajjTskgj\n" - "FAQRQlxPL0U4f4khBk9EXhJ+PZithaHjZpNl1YfTSp62x3Yz4kTSeHnpAoGAGn5m\n" - "5kArE7NdrzACBrwrfww7DL1Uyd8zSOLBgKutvEcQnqfNxSWO9la3TAarrESmH2Ic\n" - "j+Nc15wOsl/5FwdUf1/73qa2zJKtHlY28qSeo8uRqrIYeSCvnyP3wjBoLc2C8zlb\n" - "mGd6azdqr2DuYahHrcAzwjnC/6Zn+DXM7FOn7AkCgYBp1xxY88cCoF24yffkD3MC\n" - "ACUury4qRSDTGx6/qCCkIyWxg1vuiDrlPWhSwQznxHvovcfpdjdbWcFY87IK6mpG\n" - "aJHwMJ7Kw+baoxGPZWHwdg6BgvUCihe3xlcaq6rOBoLviD6FOzbogg++Tvi0LemG\n" - "y/wEs/mZkaRzW4n41ir0Xw==\n" - "-----END PRIVATE KEY-----\n" - ; + // dkim.c can handle a PEM-encoded PKCS#7 private key that has had all of the newlines replaced by underscores. + // It will convert them back to newlines before importing the key. + // This is the format that Citadel Server uses to store the key in its configuration database. + char *private_key = "-----BEGIN PRIVATE KEY-----_MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDfuefcepokRrnp_SSDsxu+QDqeD8GL9QnZz/N6IxTdBv6Wc10ExBe2IjS5dKI7AvhSSEK0zGE8Hkpmw_eccbiepQqeueteWzAMZ1uT43bD3k7eye7vWobiOP9QtoYGR6sG25h2W5Tbc91W4f_dvYnxYVJjx8wIVF0f3o25v+rQueoo0HlvGyA9/xi9GAaJL05OmK1xnMJgSvW/Q8Q_zq7apf1D6XPXHuhv5tevElkZ5jlvM2w0cTVyAzMrUh6Rkcn9xM4/NPWYghBc3jO4_TrPnSrobQGrX0fcizE/FN6I0in0Ke8Z+gMM8NeFcsjvLZe9MpY9i0pw/ygLIh5t3_O4qpwC1JAgMBAAECggEAIwiTCMEAGzciDKhhagJ66BWLYMtHTP5X2zDZThSH4xlW_HznL4RfbCtuEy5y6we7h/L90x8ACPB7WRz7CkYrmsMvy9A7q0b2I1k10MyyVgqBJ_QdgMitv4YKYQK7+QbG/tNrS/lqVXUOz3iiDQSgkRpqOtUBWfkj0WD7vbhF99NDhV_dxaehFkKv3yNy0bXJlHJBJ6KtOUnDwub8TExh8dyj3kB8Qzj4I98shaXPNUSSaOw_zG6QG72yrxlMs495jkIPbF2JDidmLrX+oVISwKyaBWx+BkFV/KFAEKgaB5/nCw7+_qq/jxsmXim3HuQ3MIAjq1yw9aGRH1HMi8Gn7tYlNGwKBgQDy6EEKpuEiW9wwlI2+_GVuSkhSTTX1h6qK/ay8Jtyb8yJM/BxogAQlfjdgFixiZHy5MaomTbfeT2GDji553_+RsnZ60+g7FI9nHwabSxtuCQ+vjbFqCsdMPAiSeG0bEzo0zf5TjASdUtuZL0vXjl_yMZWDEuESoVNlYlvCOVkw2nvIwKBgQDryPuSq6PNVHRWsKRRs5ju4wKs/1ucBOg5_gCcN8lE03mFCWAlZhypE4/fAhTQ/a5KQoAzc0QZcXRueDyNsnc+QWw3/QWf8/fkV_HPfTWS3Dcuj+4RnWUucaZ/mKFlTC3+eNSlpyaPIMlCjXGsJ9GlPrsaAi9KPbD2v/_XcMq/PMOowKBgHVf7S3sfZVQthFzdxqIvksQ84hKRW/vJT1B2bTkH56+fQhTsjgM_yC64J85l7DjxbDnYsSngVWXHhOnvKV/nq0tbOcefcydCjsQREBNfvxvPajjTskgj_FAQRQlxPL0U4f4khBk9EXhJ+PZithaHjZpNl1YfTSp62x3Yz4kTSeHnpAoGAGn5m_5kArE7NdrzACBrwrfww7DL1Uyd8zSOLBgKutvEcQnqfNxSWO9la3TAarrESmH2Ic_j+Nc15wOsl/5FwdUf1/73qa2zJKtHlY28qSeo8uRqrIYeSCvnyP3wjBoLc2C8zlb_mGd6azdqr2DuYahHrcAzwjnC/6Zn+DXM7FOn7AkCgYBp1xxY88cCoF24yffkD3MC_ACUury4qRSDTGx6/qCCkIyWxg1vuiDrlPWhSwQznxHvovcfpdjdbWcFY87IK6mpG_aJHwMJ7Kw+baoxGPZWHwdg6BgvUCihe3xlcaq6rOBoLviD6FOzbogg++Tvi0LemG_y/wEs/mZkaRzW4n41ir0Xw==_-----END PRIVATE KEY-----"; char *domain = "dev.citadel.org"; char *selector = "foo"; diff --git a/citadel/utils/ctdlload.c b/citadel/utils/ctdlload.c index a4bdb5047..cc25ef28a 100644 --- a/citadel/utils/ctdlload.c +++ b/citadel/utils/ctdlload.c @@ -1,6 +1,6 @@ // Load (restore) the Citadel database from a flat file created by ctdldump // -// Copyright (c) 2023-2024 by Art Cancro citadel.org +// Copyright (c) 2023-2024 by citadel.org (Art Cancro et al.) // // This program is open source software. Use, duplication, or disclosure // is subject to the terms of the GNU General Public License, version 3. @@ -163,8 +163,13 @@ int import_user(char *line, struct cdbkeyval *kv) { u->msgnum_pic = atol(token); break; case 12: - CtdlDecodeBase64(token, token, strlen(token)); // Decode in place - safestrncpy(u->emailaddrs, token, sizeof(u->emailaddrs)); + int dlen; + dlen = CtdlDecodeBase64(token, token, strlen(token)); // Decode in place + if (dlen >= sizeof(u->emailaddrs)) { + dlen = sizeof(u->emailaddrs) - 1; + } + memcpy(u->emailaddrs, token, dlen); + u->emailaddrs[dlen] = 0; break; case 13: u->msgnum_inboxrules = atol(token); -- 2.39.2 From 28f831c35530dc7e26a9a8254b5983614379b522 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Thu, 16 May 2024 03:44:55 +0000 Subject: [PATCH 09/16] Complete: post Aide message when DKIM records need to be updated --- citadel/server/modules/smtp/dkim.c | 41 ++++++++++++++++++++ citadel/server/modules/smtp/dkim_bindings.c | 43 +++++++++++++-------- citadel/server/modules/smtp/smtp_util.h | 2 + 3 files changed, 70 insertions(+), 16 deletions(-) diff --git a/citadel/server/modules/smtp/dkim.c b/citadel/server/modules/smtp/dkim.c index 4099e0ea6..76a7a6112 100644 --- a/citadel/server/modules/smtp/dkim.c +++ b/citadel/server/modules/smtp/dkim.c @@ -406,6 +406,47 @@ EVP_PKEY *dkim_import_key(char *pkey_in) { } +// Get the public key from our DKIM signing pair. +// Returns a string that must be freed by the caller. +char *dkim_get_public_key(EVP_PKEY *pkey) { + char *b64key = NULL; + EVP_PKEY_CTX *ctx; + ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (ctx) { + BIO *bio = NULL; + bio = BIO_new(BIO_s_mem()); + if (bio) { + PEM_write_bio_PUBKEY(bio, pkey); + b64key = malloc(4096); + if (b64key) { + size_t readbytes; + BIO_read_ex(bio, b64key, 4096, &readbytes); + b64key[readbytes] = 0; + + // strip the header + if (!strncasecmp(b64key, HKEY("-----BEGIN PUBLIC KEY-----\n"))) { + strcpy(b64key, &b64key[27]); + } + + // strip the footer + char *foot = strstr(b64key, "\n-----END PUBLIC KEY-----"); + if (foot) { + *foot = 0; + } + + // remove newlines + char *nl; + while (nl = strchr(b64key, '\n')) { + strcpy(nl, nl+1); + } + } + BIO_free(bio); + } + EVP_PKEY_CTX_free(ctx); + } + return(b64key); +} + // DKIM-sign an email, supplied as a full RFC2822-compliant message stored in a StrBuf void dkim_sign(StrBuf *email, char *pkey_in, char *domain, char *selector) { int i = 0; diff --git a/citadel/server/modules/smtp/dkim_bindings.c b/citadel/server/modules/smtp/dkim_bindings.c index e7491c154..340e574b6 100644 --- a/citadel/server/modules/smtp/dkim_bindings.c +++ b/citadel/server/modules/smtp/dkim_bindings.c @@ -29,6 +29,7 @@ #include #include "../../config.h" #include "../../msgbase.h" +#include "smtp_util.h" // Generate a private key and selector for DKIM if needed. This is called during server startup. @@ -144,24 +145,34 @@ void dkim_check_advisory(char *inetcfg_in) { "\r\n" ),0); - ptr = inetcfg_in; - while (ptr && *ptr) { - char *sep = strchr(ptr, '|'); - if (sep && !strncasecmp(sep+1, HKEY("localhost"))) { - StrBufAppendPrintf(message, "Host name : %s._domainkey.", CtdlGetConfigStr("dkim_selector")); - StrBufAppendBufPlain(message, ptr, sep-ptr, 0); - StrBufAppendBufPlain(message, HKEY("\r\n"), 0); - StrBufAppendPrintf(message, "Record type: TXT\r\n"); - StrBufAppendBufPlain(message, HKEY("Value : v=DKIM1;k=rsa;p="), 0); - - // FIXME calculate the public key and add it here + char *pubkey = NULL; + EVP_PKEY *pkey = dkim_import_key(CtdlGetConfigStr("dkim_private_key")); + if (pkey) { + pubkey = dkim_get_public_key(pkey); + EVP_PKEY_free(pkey); + } - StrBufAppendPrintf(message, "\r\n\r\n"); - } - ptr = strchr(ptr, '\n'); - if (ptr) { - ++ptr; + if (pubkey) { + ptr = inetcfg_in; + while (ptr && *ptr) { + char *sep = strchr(ptr, '|'); + if (sep && !strncasecmp(sep+1, HKEY("localhost"))) { + StrBufAppendPrintf(message, "Host name : %s._domainkey.", CtdlGetConfigStr("dkim_selector")); + StrBufAppendBufPlain(message, ptr, sep-ptr, 0); + StrBufAppendBufPlain(message, HKEY("\r\n"), 0); + StrBufAppendPrintf(message, "Record type: TXT\r\n"); + StrBufAppendPrintf(message, "Value : v=DKIM1;k=rsa;p=%s\r\n", pubkey); + StrBufAppendPrintf(message, "\r\n"); + } + ptr = strchr(ptr, '\n'); + if (ptr) { + ++ptr; + } } + free(pubkey); + } + else { + StrBufAppendBufPlain(message, HKEY("YOW! Something went wrong.\r\n\r\n"), 0); } quickie_message("Citadel", diff --git a/citadel/server/modules/smtp/smtp_util.h b/citadel/server/modules/smtp/smtp_util.h index 041fa4dac..cdaea243a 100644 --- a/citadel/server/modules/smtp/smtp_util.h +++ b/citadel/server/modules/smtp/smtp_util.h @@ -32,3 +32,5 @@ char *smtpstatus(int code); void dkim_sign(StrBuf *email, char *pkey_in, char *domain, char *selector); void dkim_init(void); void dkim_check_advisory(char *inetcfg_in); +EVP_PKEY *dkim_import_key(char *pkey_in); +char *dkim_get_public_key(EVP_PKEY *pkey); -- 2.39.2 From e3839dfff02a257676e6e82d801e0c24d3a294ef Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Thu, 16 May 2024 04:00:41 +0000 Subject: [PATCH 10/16] Release version 1000 generated by do-release.sh --- citadel/server/citadel_defs.h | 2 +- libcitadel/lib/libcitadel.h | 2 +- release_version.txt | 2 +- textclient/textclient.h | 2 +- webcit/webcit.h | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/citadel/server/citadel_defs.h b/citadel/server/citadel_defs.h index 4ee0cd060..0ed7a5a63 100644 --- a/citadel/server/citadel_defs.h +++ b/citadel/server/citadel_defs.h @@ -21,7 +21,7 @@ #include "typesize.h" #include "ipcdef.h" -#define REV_LEVEL 999 // This version +#define REV_LEVEL 1000 // This version #define REV_MIN 591 // Oldest compatible database #define EXPORT_REV_MIN 931 // Oldest compatible export files #define LIBCITADEL_MIN 951 // Minimum required version of libcitadel diff --git a/libcitadel/lib/libcitadel.h b/libcitadel/lib/libcitadel.h index 181718737..655ebe445 100644 --- a/libcitadel/lib/libcitadel.h +++ b/libcitadel/lib/libcitadel.h @@ -19,7 +19,7 @@ #include #include -#define LIBCITADEL_VERSION_NUMBER 999 +#define LIBCITADEL_VERSION_NUMBER 1000 /* * Here's a bunch of stupid magic to make the MIME parser portable. diff --git a/release_version.txt b/release_version.txt index a6905f8ba..83b33d238 100644 --- a/release_version.txt +++ b/release_version.txt @@ -1 +1 @@ -999 +1000 diff --git a/textclient/textclient.h b/textclient/textclient.h index 66583979d..576751e7f 100644 --- a/textclient/textclient.h +++ b/textclient/textclient.h @@ -6,7 +6,7 @@ #define UDS "_UDS_" #define DEFAULT_HOST "localhost" #define DEFAULT_PORT "504" -#define CLIENT_VERSION 999 +#define CLIENT_VERSION 1000 #define CLIENT_TYPE 0 // commands we can send to the stty_ctdl() routine diff --git a/webcit/webcit.h b/webcit/webcit.h index 31eaa92df..5e34bcf2c 100644 --- a/webcit/webcit.h +++ b/webcit/webcit.h @@ -118,7 +118,7 @@ extern char *ssl_cipher_list; #define PORT_NUM 80 /* port number to listen on */ #define DEVELOPER_ID 0 #define CLIENT_ID 4 -#define CLIENT_VERSION 999 /* This version of WebCit */ +#define CLIENT_VERSION 1000 /* This version of WebCit */ #define MINIMUM_CIT_VERSION 931 /* Minimum required version of Citadel server */ #define LIBCITADEL_MIN 931 /* Minimum required version of libcitadel */ #define DEFAULT_CTDLDIR "/usr/local/citadel" /* Default Citadel server directory */ -- 2.39.2 From 50d8adbcac551e6615c1e4c33dbf55e6a055cd1d Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Thu, 16 May 2024 04:05:24 +0000 Subject: [PATCH 11/16] fix dlen --- citadel/utils/ctdlload.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/citadel/utils/ctdlload.c b/citadel/utils/ctdlload.c index cc25ef28a..e69aa0873 100644 --- a/citadel/utils/ctdlload.c +++ b/citadel/utils/ctdlload.c @@ -118,6 +118,7 @@ int import_user(char *line, struct cdbkeyval *kv) { char userkey[USERNAME_SIZE]; char *token; struct ctdluser *u; + int dlen = 0; u = malloc(sizeof(struct ctdluser)); if (!u) { @@ -163,7 +164,6 @@ int import_user(char *line, struct cdbkeyval *kv) { u->msgnum_pic = atol(token); break; case 12: - int dlen; dlen = CtdlDecodeBase64(token, token, strlen(token)); // Decode in place if (dlen >= sizeof(u->emailaddrs)) { dlen = sizeof(u->emailaddrs) - 1; -- 2.39.2 From 675cd45d1e052f075aab6941e58f198dc5f44e0a Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Tue, 21 May 2024 15:15:44 +0000 Subject: [PATCH 12/16] put list-unsubscribe address in angle brackets --- citadel/dumploadtest.sh | 9 ++++---- .../autocompletion/serv_autocompletion.c | 5 +--- citadel/server/modules/bio/serv_bio.c | 4 +--- .../modules/listdeliver/serv_listdeliver.c | 23 +++++++++---------- citadel/server/modules/smtp/serv_smtpclient.c | 4 +--- 5 files changed, 19 insertions(+), 26 deletions(-) diff --git a/citadel/dumploadtest.sh b/citadel/dumploadtest.sh index 7179f3991..50488ff80 100755 --- a/citadel/dumploadtest.sh +++ b/citadel/dumploadtest.sh @@ -1,6 +1,5 @@ #!/bin/bash - # This script dumps the database, deletes the database, loads the database, dumps it again... # ...and then compares the two dumps to see if we have full fidelity between them. # @@ -13,16 +12,18 @@ ps ax | grep citserver | grep -v grep >/dev/null 2>/dev/null && { exit 1 } +echo +echo + ./ctdldump -y >dump.dat first=$(md5sum dump.dat | awk ' { print $1 } ' ) +echo MD5 of initial dump: ${first} + rm -fv data/* ./ctdlload -y dump.dat second=$(md5sum dump.dat | awk ' { print $1 } ' ) -echo -echo -echo MD5 of initial dump: ${first} echo MD5 of sequent dump: ${second} echo if [ ${first} == ${second} ] ; then diff --git a/citadel/server/modules/autocompletion/serv_autocompletion.c b/citadel/server/modules/autocompletion/serv_autocompletion.c index 413bf472b..822e01aac 100644 --- a/citadel/server/modules/autocompletion/serv_autocompletion.c +++ b/citadel/server/modules/autocompletion/serv_autocompletion.c @@ -1,9 +1,6 @@ // Autocompletion of email recipients, etc. -// // Copyright (c) 1987-2023 by the citadel.org team -// -// This program is open source software. Use, duplication, or disclosure -// is subject to the terms of the GNU General Public License version 3. +// This program is open source software. Use, duplication, or disclosure is subject to the GNU General Public License version 3. #include "../../ctdl_module.h" #include "serv_autocompletion.h" diff --git a/citadel/server/modules/bio/serv_bio.c b/citadel/server/modules/bio/serv_bio.c index 8be817ae6..6ff861551 100644 --- a/citadel/server/modules/bio/serv_bio.c +++ b/citadel/server/modules/bio/serv_bio.c @@ -3,9 +3,7 @@ // // Copyright (c) 1987-2022 by the citadel.org team // -// This program is open source software. Use, duplication, or disclosure -// is subject to the terms of the GNU General Public License, version 3. -// The program is distributed without any warranty, expressed or implied. +// This program is open source software. Use, duplication, or disclosure is subject to the GNU General Public License, version 3. #include #include diff --git a/citadel/server/modules/listdeliver/serv_listdeliver.c b/citadel/server/modules/listdeliver/serv_listdeliver.c index fd1356079..a6cb74ad4 100644 --- a/citadel/server/modules/listdeliver/serv_listdeliver.c +++ b/citadel/server/modules/listdeliver/serv_listdeliver.c @@ -1,14 +1,6 @@ // This module delivers messages to mailing lists. -// -// Copyright (c) 2002-2024 by the citadel.org team -// -// This program is open source software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. +// Copyright (c) 2002-2024 by the citadel.org team (Art Cancro et al.) +// This program is open source software. Use, duplication, or disclosure are subject to the GNU General Public License v3. #include "../../sysdep.h" #include @@ -40,7 +32,6 @@ int doing_listdeliver = 0; - // data passed back and forth between listdeliver_do_msg() and listdeliver_sweep_room() struct lddata { long msgnum; // number of most recent message processed @@ -48,7 +39,6 @@ struct lddata { }; - void listdeliver_do_msg(long msgnum, void *userdata) { struct lddata *ld = (struct lddata *) userdata; if (!ld) return; @@ -63,6 +53,8 @@ void listdeliver_do_msg(long msgnum, void *userdata) { struct CtdlMessage *TheMessage = CtdlFetchMessage(msgnum, 1); if (!TheMessage) return; + // FIXME add the list unsubscribe instructions directly to the message text. Do it right here. + // If the subject line does not contain the name of the room, add it now. if (!bmstrcasestr(TheMessage->cm_fields[eMsgSubject], CC->room.QRname)) { snprintf(buf, sizeof buf, "[%s] %s", CC->room.QRname, TheMessage->cm_fields[eMsgSubject]); @@ -78,6 +70,13 @@ void listdeliver_do_msg(long msgnum, void *userdata) { CM_SetField(TheMessage, erFc822Addr, buf); CM_SetField(TheMessage, eReplyTo, buf); + // To: likewise needs to have something in it, definitely not the name of an actual mailing list member. + // Let's use the address and name of the room. + strcat(buf, " ("); + strcat(buf, CC->room.QRname); + strcat(buf, " )"); + CM_SetField(TheMessage, eRecipient, buf); + // With that out of the way, let's figure out who this message needs to be sent to. char *recipients = malloc(strlen(ld->netconf)); if (recipients) { diff --git a/citadel/server/modules/smtp/serv_smtpclient.c b/citadel/server/modules/smtp/serv_smtpclient.c index 03f54b108..6eedc56d0 100644 --- a/citadel/server/modules/smtp/serv_smtpclient.c +++ b/citadel/server/modules/smtp/serv_smtpclient.c @@ -224,15 +224,13 @@ int smtp_attempt_delivery(long msgid, char *recp, char *envelope_from, char *sou char unsubscribe_url[SIZ]; snprintf(base_url, sizeof base_url, "https://%s/listsub", CtdlGetConfigStr("c_fqdn")); generate_one_click_url(unsubscribe_url, base_url, "unsubscribe", source_room, recp); - cprintf("List-Unsubscribe: %s\r\n", unsubscribe_url); + cprintf("List-Unsubscribe: <%s>\r\n", unsubscribe_url); // RFC 2369 cprintf("List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n"); // RFC 8058 - } CtdlOutputMsg(msgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0, NULL, &fromaddr, NULL); s.TheMessage = CC->redirect_buffer; CC->redirect_buffer = NULL; - syslog(LOG_DEBUG, "fromaddr=<%s>",fromaddr); // If we have a DKIM key, try to sign the message. char *dkim_private_key = CtdlGetConfigStr("dkim_private_key"); -- 2.39.2 From 9376c4d48e7dfd6827211e3291bf57ea88e81884 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Tue, 21 May 2024 15:23:54 +0000 Subject: [PATCH 13/16] dumploadtest.sh now requires YES_DELETE_MY_DATABASE to be set. Before this commit, it had an exit statement that the operator had to remove to run the program. Now it just bombs out telling the operator that they have to set YES_DELETE_MY_DATABASE if they really want to do this. --- citadel/dumploadtest.sh | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/citadel/dumploadtest.sh b/citadel/dumploadtest.sh index 50488ff80..912595df3 100755 --- a/citadel/dumploadtest.sh +++ b/citadel/dumploadtest.sh @@ -5,10 +5,21 @@ # # Did you read that correctly? Yes, it will DELETE your database. So don't run this. -exit 0 # In fact, here's an exit statement you must delete before you can even run it. + +if [ "${YES_DELETE_MY_DATABASE}" != '' ] ; then + echo Ah, I see you have set YES_DELETE_MY_DATABASE to a non-empty value. + echo The dump and load test will now proceed. +else + echo 'This script dumps the database, deletes the database, loads the database, dumps it again...' + echo '...and then compares the two dumps to see if we have full fidelity between them.' + echo 'Did you read that correctly? Yes, it will DELETE your database.' + echo 'If this is really what you want, set the environment variable YES_DELETE_MY_DATABASE' + echo 'to a non-empty value, and run it again.' + exit 0 +fi ps ax | grep citserver | grep -v grep >/dev/null 2>/dev/null && { - echo dont do this while the server is running + echo Do not do this while the server is running. exit 1 } -- 2.39.2 From 7b03e7177ca1afbb25477cdaf4781b2117a4ea75 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Tue, 21 May 2024 15:32:29 +0000 Subject: [PATCH 14/16] ctdlload: don't count "end|" as a bad row --- citadel/dumploadtest.sh | 7 +++---- citadel/utils/ctdlload.c | 9 +++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/citadel/dumploadtest.sh b/citadel/dumploadtest.sh index 912595df3..835986ec7 100755 --- a/citadel/dumploadtest.sh +++ b/citadel/dumploadtest.sh @@ -23,18 +23,17 @@ ps ax | grep citserver | grep -v grep >/dev/null 2>/dev/null && { exit 1 } -echo -echo - ./ctdldump -y >dump.dat first=$(md5sum dump.dat | awk ' { print $1 } ' ) -echo MD5 of initial dump: ${first} rm -fv data/* ./ctdlload -y dump.dat second=$(md5sum dump.dat | awk ' { print $1 } ' ) +echo +echo +echo MD5 of initial dump: ${first} echo MD5 of sequent dump: ${second} echo if [ ${first} == ${second} ] ; then diff --git a/citadel/utils/ctdlload.c b/citadel/utils/ctdlload.c index e69aa0873..aed19b472 100644 --- a/citadel/utils/ctdlload.c +++ b/citadel/utils/ctdlload.c @@ -696,6 +696,7 @@ void ingest_one(char *line, struct cdbkeyval *kv) { ++good_rows; } else { + fprintf(stderr, "bad row: <%s>\n", line); ++bad_rows; } @@ -736,6 +737,10 @@ void ingest(void) { } if (line_len > 0) { + if (!strncasecmp(line, HKEY("end|"))) { + fprintf(stderr, "\n"); + end_found = 1; + } if ( (begin_found) && (!end_found) ) { ingest_one(line, &kv); } @@ -743,10 +748,6 @@ void ingest(void) { begin_found = 1; fprintf(stderr, " good rows / bad rows:\n"); } - if (!strncasecmp(line, HKEY("end|"))) { - fprintf(stderr, "\n"); - end_found = 1; - } } } while (ch >= 0); -- 2.39.2 From 7f9ff763d19b6acdbb3df6166e4e36388971aaf8 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Tue, 21 May 2024 15:38:26 +0000 Subject: [PATCH 15/16] I thought Richard Stallman was a douchebag before it was cool. --- citadel/server/modules/expire/expire_policy.c | 54 ++++++------------- citadel/server/modules/expire/serv_expire.c | 5 +- 2 files changed, 18 insertions(+), 41 deletions(-) diff --git a/citadel/server/modules/expire/expire_policy.c b/citadel/server/modules/expire/expire_policy.c index 32db98ad2..ce302a6b3 100644 --- a/citadel/server/modules/expire/expire_policy.c +++ b/citadel/server/modules/expire/expire_policy.c @@ -1,15 +1,6 @@ -/* - * Functions which manage expire policy for rooms - * Copyright (c) 1987-2015 by the citadel.org team - * - * This program is open source software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ +// Functions which manage expire policy for rooms +// Copyright (c) 1987-2024 by citadel.org (Art Cancro et al.) +// This program is open source software. Use, duplication, or disclosure is subject to the GNU General Public License, version 3. #include "../../sysdep.h" #include @@ -17,7 +8,6 @@ #include #include #include - #include #include #include @@ -33,21 +23,18 @@ #include "../../ctdl_module.h" #include "../../user_ops.h" -/* - * Retrieve the applicable expire policy for a specific room - */ +// Retrieve the applicable expire policy for a specific room void GetExpirePolicy(struct ExpirePolicy *epbuf, struct ctdlroom *qrbuf) { struct floor *fl; - /* If the room has its own policy, return it */ + // If the room has its own policy, return it if (qrbuf->QRep.expire_mode != 0) { memcpy(epbuf, &qrbuf->QRep, sizeof(struct ExpirePolicy)); return; } - /* (non-mailbox rooms) - * If the floor has its own policy, return it - */ + // (non-mailbox rooms) + // If the floor has its own policy, return it if ( (qrbuf->QRflags & QR_MAILBOX) == 0) { fl = CtdlGetCachedFloor(qrbuf->QRfloor); if (fl->f_ep.expire_mode != 0) { @@ -56,9 +43,8 @@ void GetExpirePolicy(struct ExpirePolicy *epbuf, struct ctdlroom *qrbuf) { } } - /* (Mailbox rooms) - * If there is a default policy for mailbox rooms, return it - */ + // (Mailbox rooms) + // If there is a default policy for mailbox rooms, return it if (qrbuf->QRflags & QR_MAILBOX) { if (CtdlGetConfigInt("c_mbxep_mode") != 0) { epbuf->expire_mode = CtdlGetConfigInt("c_mbxep_mode"); @@ -67,15 +53,13 @@ void GetExpirePolicy(struct ExpirePolicy *epbuf, struct ctdlroom *qrbuf) { } } - /* Otherwise, fall back on the system default */ + // Otherwise, fall back on the system default epbuf->expire_mode = CtdlGetConfigInt("c_ep_mode"); epbuf->expire_value = CtdlGetConfigInt("c_ep_value"); } -/* - * Get Policy EXpire - */ +// Get Policy EXpire void cmd_gpex(char *argbuf) { struct ExpirePolicy exp; struct floor *fl; @@ -107,9 +91,7 @@ void cmd_gpex(char *argbuf) { } -/* - * Set Policy EXpire - */ +// Set Policy EXpire void cmd_spex(char *argbuf) { struct ExpirePolicy exp; struct floor flbuf; @@ -125,8 +107,7 @@ void cmd_spex(char *argbuf) { return; } - if ((!strcasecmp(which, strof(roompolicy))) || (!strcasecmp(which, "room"))) - { + if ((!strcasecmp(which, strof(roompolicy))) || (!strcasecmp(which, "room"))) { if (!is_room_aide()) { cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED); return; @@ -148,8 +129,7 @@ void cmd_spex(char *argbuf) { return; } - if ((!strcasecmp(which, strof(floorpolicy))) || (!strcasecmp(which, "floor"))) - { + if ((!strcasecmp(which, strof(floorpolicy))) || (!strcasecmp(which, "floor"))) { lgetfloor(&flbuf, CC->room.QRfloor); memcpy(&flbuf.f_ep, &exp, sizeof(struct ExpirePolicy)); lputfloor(&flbuf, CC->room.QRfloor); @@ -157,16 +137,14 @@ void cmd_spex(char *argbuf) { return; } - else if ((!strcasecmp(which, strof(mailboxespolicy))) || (!strcasecmp(which, "mailboxes"))) - { + else if ((!strcasecmp(which, strof(mailboxespolicy))) || (!strcasecmp(which, "mailboxes"))) { CtdlSetConfigInt("c_mbxep_mode", exp.expire_mode); CtdlSetConfigInt("c_mbxep_value", exp.expire_value); cprintf("%d Default expire policy for mailboxes set.\n", CIT_OK); return; } - else if ((!strcasecmp(which, strof(sitepolicy))) || (!strcasecmp(which, "site"))) - { + else if ((!strcasecmp(which, strof(sitepolicy))) || (!strcasecmp(which, "site"))) { if (exp.expire_mode == EXPIRE_NEXTLEVEL) { cprintf("%d Invalid policy (no higher level)\n", ERROR + ILLEGAL_VALUE); return; diff --git a/citadel/server/modules/expire/serv_expire.c b/citadel/server/modules/expire/serv_expire.c index fe4b60b4a..63b8fc191 100644 --- a/citadel/server/modules/expire/serv_expire.c +++ b/citadel/server/modules/expire/serv_expire.c @@ -2,10 +2,9 @@ // // You might also see this module affectionately referred to as TDAP (The Dreaded Auto-Purger). // -// Copyright (c) 1988-2023 by citadel.org (Art Cancro, Wilifried Goesgens, and others) +// Copyright (c) 1988-2024 by citadel.org (Art Cancro et al.) // -// This program is open source software. Use, duplication, or disclosure -// is subject to the terms of the GNU General Public License, version 3. +// This program is open source software. Use, duplication, or disclosure is subject to the GNU General Public License, version 3. #include "../../sysdep.h" -- 2.39.2 From f636019b302bc70eec15cd4d0b95453ddc1edced Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Wed, 22 May 2024 03:24:53 +0000 Subject: [PATCH 16/16] Remove preprocessor tests for OpenSSL. It's a requirement. --- citadel/configure | 1 - citadel/server/context.c | 3 - citadel/server/context.h | 2 - citadel/server/modules/crypto/serv_crypto.c | 6 -- citadel/server/modules/crypto/serv_crypto.h | 3 - .../server/modules/ctdlproto/serv_ctdlproto.c | 80 ++++++++----------- citadel/server/modules/imap/serv_imap.c | 8 -- citadel/server/modules/nntp/serv_nntp.c | 6 -- citadel/server/modules/pop3/serv_pop3.c | 26 +++--- citadel/server/modules/smtp/serv_smtp.c | 12 +-- citadel/server/modules/xmpp/serv_xmpp.c | 7 -- citadel/server/modules/xmpp/xmpp_queue.c | 30 +++---- citadel/server/serv_extensions.c | 2 - citadel/server/server.h | 2 - citadel/server/sysdep.c | 24 ++---- 15 files changed, 59 insertions(+), 153 deletions(-) diff --git a/citadel/configure b/citadel/configure index ad136e3ac..381a2544c 100755 --- a/citadel/configure +++ b/citadel/configure @@ -74,7 +74,6 @@ int main(int argc, char **argv) { } ! $CC $CFLAGS $CPPFLAGS $tempcc -o $tempfile $LDFLAGS -lssl -lcrypto && $tempfile >/dev/null 2>&1 && { - CFLAGS=${CFLAGS}' -DHAVE_OPENSSL' LDFLAGS=${LDFLAGS}' -lssl -lcrypto -lz' } || { echo Citadel Server requires OpenSSL which is not present. diff --git a/citadel/server/context.c b/citadel/server/context.c index 6415ce12a..81709b5b5 100644 --- a/citadel/server/context.c +++ b/citadel/server/context.c @@ -318,10 +318,7 @@ CitContext *CloneContext(CitContext *CloneMe) { me->MigrateBuf = NULL; me->sMigrateBuf = NULL; me->redirect_buffer = NULL; -#ifdef HAVE_OPENSSL me->ssl = NULL; -#endif - me->download_fp = NULL; me->upload_fp = NULL; me->ma = NULL; diff --git a/citadel/server/context.h b/citadel/server/context.h index d28dff7a7..1b207b645 100644 --- a/citadel/server/context.h +++ b/citadel/server/context.h @@ -56,10 +56,8 @@ struct CitContext { // Redirect this session's output to a memory buffer? StrBuf *redirect_buffer; // the buffer StrBuf *StatusMessage; -#ifdef HAVE_OPENSSL SSL *ssl; int redirect_ssl; -#endif char curr_user[USERNAME_SIZE]; // name of current user int logged_in; // logged in? diff --git a/citadel/server/modules/crypto/serv_crypto.c b/citadel/server/modules/crypto/serv_crypto.c index 5d41fa27c..541479af1 100644 --- a/citadel/server/modules/crypto/serv_crypto.c +++ b/citadel/server/modules/crypto/serv_crypto.c @@ -7,11 +7,9 @@ #include #include "../../sysdep.h" -#ifdef HAVE_OPENSSL #include #include #include -#endif #include @@ -32,8 +30,6 @@ #include "../../config.h" #include "../../ctdl_module.h" -#ifdef HAVE_OPENSSL - SSL_CTX *ssl_ctx = NULL; // This SSL context is used for all sessions. char *ssl_cipher_list = CIT_CIPHERS; @@ -609,5 +605,3 @@ void endtls(void) { CC->ssl = NULL; CC->redirect_ssl = 0; } - -#endif // HAVE_OPENSSL diff --git a/citadel/server/modules/crypto/serv_crypto.h b/citadel/server/modules/crypto/serv_crypto.h index b5ee85d53..4ef8033d5 100644 --- a/citadel/server/modules/crypto/serv_crypto.h +++ b/citadel/server/modules/crypto/serv_crypto.h @@ -7,7 +7,6 @@ // Which ciphers will be offered; see https://www.openssl.org/docs/manmaster/man1/ciphers.html #define CIT_CIPHERS "ALL:RC4+RSA:+SSLv2:+TLSv1:!MD5:@STRENGTH" -#ifdef HAVE_OPENSSL #define OPENSSL_NO_KRB5 /* work around redhat b0rken ssl headers */ void init_ssl(void); void client_write_ssl (const char *buf, int nbytes); @@ -19,5 +18,3 @@ void cmd_gtls(char *params); void endtls(void); void CtdlStartTLS(char *ok_response, char *nosup_response, char *error_response); extern SSL_CTX *ssl_ctx; - -#endif diff --git a/citadel/server/modules/ctdlproto/serv_ctdlproto.c b/citadel/server/modules/ctdlproto/serv_ctdlproto.c index ae267b596..d5e7ffe61 100644 --- a/citadel/server/modules/ctdlproto/serv_ctdlproto.c +++ b/citadel/server/modules/ctdlproto/serv_ctdlproto.c @@ -1,72 +1,56 @@ -/* - * Citadel protocol main dispatcher - * - * Copyright (c) 1987-2017 by the citadel.org team - * - * This program is open source software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ +// Citadel protocol main dispatcher +// Copyright (c) 1987-2024 by the citadel.org team +// This program is open source software. Use, duplication, or disclosure are subject to the GNU General Public License v3. #include #include - #include "../../citserver.h" #include "../../ctdl_module.h" #include "../../config.h" -/* - * This loop recognizes all server commands. - */ + +// This loop recognizes all server commands. void do_command_loop(void) { - struct CitContext *CCC = CC; char cmdbuf[SIZ]; - time(&CCC->lastcmd); - memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */ + time(&CC->lastcmd); + memset(cmdbuf, 0, sizeof cmdbuf); // Clear it, just in case if (client_getln(cmdbuf, sizeof cmdbuf) < 1) { syslog(LOG_INFO, "Citadel client disconnected: ending session."); - CCC->kill_me = KILLME_CLIENT_DISCONNECTED; + CC->kill_me = KILLME_CLIENT_DISCONNECTED; return; } - /* Log the server command, but don't show passwords... */ - if ( (strncasecmp(cmdbuf, "PASS", 4)) && (strncasecmp(cmdbuf, "SETP", 4)) ) { - syslog(LOG_DEBUG, "[%s(%ld)] %s", - CCC->curr_user, CCC->user.usernum, cmdbuf - ); + // Log the server command, but don't show passwords... + if ( (strncasecmp(cmdbuf, "PASS", 4)) + && (strncasecmp(cmdbuf, "SETP", 4)) + ) { + syslog(LOG_DEBUG, "[%s(%ld)] %s", CC->curr_user, CC->user.usernum, cmdbuf); } else { - syslog(LOG_DEBUG, "[%s(%ld)]