#include <openssl/err.h>
#include <openssl/evp.h>
#include <libcitadel.h>
-#include "../../config.h"
// This utility function is used by the body canonicalizer
char *dkim_rtrim(char *str) {
}
-#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
--- /dev/null
+// 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 <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <assert.h>
+#include <syslog.h>
+#include <openssl/rand.h>
+#include <openssl/rsa.h>
+#include <openssl/engine.h>
+#include <openssl/sha.h>
+#include <openssl/hmac.h>
+#include <openssl/evp.h>
+#include <openssl/bio.h>
+#include <openssl/pem.h>
+#include <openssl/buffer.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <libcitadel.h>
+#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);
+}