]> code.citadel.org Git - citadel.git/blob - citadel/server/modules/smtp/dkim_bindings.c
work on sixel support
[citadel.git] / citadel / server / modules / smtp / dkim_bindings.c
1 // DKIM bindings to Citadel Server
2 //
3 // (C) 2024 by Art Cancro
4 //
5 // This program is open source software.  Use, duplication, or disclosure is subject to the GNU General Public License v3.
6
7 // Make sure we don't accidentally use any deprecated API calls
8 #define OPENSSL_NO_DEPRECATED_3_0
9
10 #include <stdlib.h>
11 #include <unistd.h>
12 #include <stdio.h>
13 #include <ctype.h>
14 #include <string.h>
15 #include <time.h>
16 #include <assert.h>
17 #include <syslog.h>
18 #include <openssl/rand.h>
19 #include <openssl/rsa.h>
20 #include <openssl/engine.h>
21 #include <openssl/sha.h>
22 #include <openssl/hmac.h>
23 #include <openssl/evp.h>
24 #include <openssl/bio.h>
25 #include <openssl/pem.h>
26 #include <openssl/buffer.h>
27 #include <openssl/err.h>
28 #include <openssl/evp.h>
29 #include <libcitadel.h>
30 #include "../../config.h"
31 #include "../../msgbase.h"
32 #include "smtp_util.h"
33
34
35 // Generate a private key and selector for DKIM if needed.  This is called during server startup.
36 void dkim_init(void) {
37
38         char *dkim_private_key = CtdlGetConfigStr("dkim_private_key");
39         if (!IsEmptyStr(dkim_private_key)) {
40                 syslog(LOG_DEBUG, "dkim: private key exists and will continue to be used.");
41         }
42         else {
43                 EVP_PKEY_CTX *ctx;
44                 EVP_PKEY *pkey = NULL;  
45                 BIO *bio = NULL;
46                 ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
47                 if (ctx) {
48                         if (
49                                 (EVP_PKEY_keygen_init(ctx) == 1)
50                                 && (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048) == 1)
51                                 && (EVP_PKEY_keygen(ctx, &pkey) == 1)
52                         ) {
53                                 syslog(LOG_DEBUG, "dkim: generated private key");
54                                 bio = BIO_new(BIO_s_mem());
55                                 if (bio) {
56                                         PEM_write_bio_PrivateKey(bio, pkey, NULL, NULL, 0, NULL, NULL);
57                                         char *b64key = malloc(4096);
58                                         if (b64key) {
59                                                 size_t readbytes;
60                                                 BIO_read_ex(bio, b64key, 4096, &readbytes);
61                                                 b64key[readbytes] = 0;
62                                                 char *nl = NULL;
63                                                 while (nl=strchr(b64key, '\n'), nl) {           // convert newlines to underscores
64                                                         *nl = '_';
65                                                 }
66                                                 CtdlSetConfigStr("dkim_private_key", b64key);
67                                                 free(b64key);
68                                         }
69                                         free(bio);
70                                 }
71                         }
72                         EVP_PKEY_CTX_free(ctx);
73                 }
74         }
75
76         char *dkim_selector = CtdlGetConfigStr("dkim_selector");
77         if (dkim_selector) {
78                 syslog(LOG_DEBUG, "dkim: selector exists: %s", dkim_selector);
79         }
80         else {
81                 // Quick and dirty algorithm to make up a five letter nonsense word as a selector
82                 char new_selector[6];
83                 int i;
84                 for (i=0; i<5; ++i) {
85                         new_selector[i] = (rand() % 26) + 'a';
86                 }
87                 new_selector[5] = 0;
88                 syslog(LOG_DEBUG, "dkim: selector created: %s", new_selector);
89                 CtdlSetConfigStr("dkim_selector", new_selector);
90         }
91 }
92
93
94 // If the DKIM key, DKIM selector, or set of signing domains has changed, we need to tell the administrator about it.
95 void dkim_check_advisory(char *inetcfg_in) {
96
97         // If there is no DKIM ... there is nothing to discuss
98         if (IsEmptyStr(CtdlGetConfigStr("dkim_private_key"))) return;
99         if (IsEmptyStr(CtdlGetConfigStr("dkim_selector"))) return;
100
101         // We're going to build a hash of the private key, the selector, and all signing domains.
102         // The way we build it doesn't matter, and it doesn't even have to be secure.
103         // This is just to let us know that we have to post an update to the administrator if the hash changes.
104
105         StrBuf *hashsrc = NewStrBuf();
106         if (!hashsrc) {
107                 return;
108         }
109
110         StrBufAppendBufPlain(hashsrc, CtdlGetConfigStr("dkim_private_key"), strlen(CtdlGetConfigStr("dkim_private_key")), 0);
111         StrBufAppendBufPlain(hashsrc, CtdlGetConfigStr("dkim_selector"), strlen(CtdlGetConfigStr("dkim_selector")), 0);
112
113         char *ptr = inetcfg_in;
114         while (ptr && *ptr) {
115                 char *sep = strchr(ptr, '|');
116                 if (sep && !strncasecmp(sep+1, HKEY("localhost"))) {
117                         StrBufAppendBufPlain(hashsrc, ptr, sep-ptr, 0);
118                 }
119                 ptr = strchr(ptr, '\n');
120                 if (ptr) ++ptr;
121         }
122
123         // make a hash from the string...
124         unsigned char *config_hash = malloc(SHA256_DIGEST_LENGTH);
125         SHA256((unsigned char *)ChrPtr(hashsrc), StrLength(hashsrc), config_hash);
126         FreeStrBuf(&hashsrc);
127
128         // base64 encode it...
129         char *encoded_config_hash = malloc(SHA256_DIGEST_LENGTH * 2);
130         CtdlEncodeBase64(encoded_config_hash, config_hash, SHA256_DIGEST_LENGTH, BASE64_NO_LINEBREAKS);
131         free(config_hash);                                                      // all we need now is the encoded hash
132
133         // Does it match the saved hash?
134         if (    (IsEmptyStr(CtdlGetConfigStr("dkim_config_hash")))
135                 || (strcmp(encoded_config_hash, CtdlGetConfigStr("dkim_config_hash")))
136         ) {
137                 // No?  Post an Aide notification.
138                 StrBuf *message = NewStrBuf();
139                 StrBufAppendBufPlain(message, HKEY(
140                         "Content-type: text/plain\r\n"
141                         "\r\n\r\n"
142                         "Your domain configuration may have changed.\r\n\r\n"
143                         "To allow the DKIM signatures of outbound mail to be verified,\r\n"
144                         "please ensure that the following DNS records are created:\r\n"
145                         "\r\n"
146                 ),0);
147
148                 char *pubkey = NULL;
149                 EVP_PKEY *pkey = dkim_import_key(CtdlGetConfigStr("dkim_private_key"));
150                 if (pkey) {
151                         pubkey = dkim_get_public_key(pkey);
152                         EVP_PKEY_free(pkey);
153                 }
154
155                 if (pubkey) {
156                         ptr = inetcfg_in;
157                         while (ptr && *ptr) {
158                                 char *sep = strchr(ptr, '|');
159                                 if (sep && !strncasecmp(sep+1, HKEY("localhost"))) {
160                                         StrBufAppendPrintf(message, "Host name  : %s._domainkey.", CtdlGetConfigStr("dkim_selector"));
161                                         StrBufAppendBufPlain(message, ptr, sep-ptr, 0);
162                                         StrBufAppendBufPlain(message, HKEY("\r\n"), 0);
163                                         StrBufAppendPrintf(message, "Record type: TXT\r\n");
164                                         StrBufAppendPrintf(message, "Value      : v=DKIM1;k=rsa;p=%s\r\n", pubkey);
165                                         StrBufAppendPrintf(message, "\r\n");
166                                 }
167                                 ptr = strchr(ptr, '\n');
168                                 if (ptr) {
169                                         ++ptr;
170                                 }
171                         }
172                         free(pubkey);
173                 }
174                 else {
175                         StrBufAppendBufPlain(message, HKEY("YOW!  Something went wrong.\r\n\r\n"), 0);
176                 }
177
178                 quickie_message("Citadel",
179                         NULL,                           // from
180                         NULL,                           // to
181                         AIDEROOM,                       // room
182                         ChrPtr(message),                // text
183                         FMT_RFC822,                     // format
184                         "Confirm your DKIM records"     // subject
185                 );
186                 FreeStrBuf(&message);
187         }
188
189         // Save it to the config database so we don't do this except when it changes.
190         CtdlSetConfigStr("dkim_config_hash", encoded_config_hash);
191         free(encoded_config_hash);
192 }