fix dlen master
authorArt Cancro <ajc@citadel.org>
Thu, 16 May 2024 04:05:24 +0000 (04:05 +0000)
committerArt Cancro <ajc@citadel.org>
Thu, 16 May 2024 04:05:24 +0000 (04:05 +0000)
27 files changed:
citadel/server/citadel_defs.h
citadel/server/citserver.c
citadel/server/control.c
citadel/server/internet_addressing.h
citadel/server/modules/inetcfg/serv_inetcfg.c
citadel/server/modules/listdeliver/serv_listdeliver.c
citadel/server/modules/listsub/serv_listsub.c
citadel/server/modules/smtp/dkim.c [new file with mode: 0644]
citadel/server/modules/smtp/dkim_bindings.c [new file with mode: 0644]
citadel/server/modules/smtp/serv_smtpclient.c
citadel/server/modules/smtp/smtp_util.h
citadel/server/msgbase.c
citadel/tests/dkimtester/.gitignore [new file with mode: 0644]
citadel/tests/dkimtester/Makefile [new file with mode: 0644]
citadel/tests/dkimtester/README.md [new file with mode: 0644]
citadel/tests/dkimtester/dkimtester.c [new file with mode: 0644]
citadel/tests/dkimtester/tester.pl [new file with mode: 0755]
citadel/utils/ctdlload.c
libcitadel/README.txt
libcitadel/lib/libcitadel.h
libcitadel/lib/stringbuf.c
release_version.txt
textclient/textclient.h
webcit-ng/static/js/view_calendar.js
webcit/bootstrap
webcit/configure.ac
webcit/webcit.h

index 4ee0cd060f18792fdc85f9cf48abe7d6fc6bbaf7..0ed7a5a630fd7418a1d589f46107fb7407ce0795 100644 (file)
@@ -21,7 +21,7 @@
 #include "typesize.h"
 #include "ipcdef.h"
 
 #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
 #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
index da0865899516b30e7c93a4eb44d81d0493a07b3f..a6c9505967fec1c16ea2eb0874c8f83063332a4e 100644 (file)
@@ -29,24 +29,8 @@ int panic_fd;
 
 // We need pseudo-random numbers for a few things.  Seed generously.
 void seed_random_number_generator(void) {
 
 // We need pseudo-random numbers for a few things.  Seed generously.
 void seed_random_number_generator(void) {
-       FILE *urandom;
-       struct timeval tv;
-       unsigned int seed;
-
-       syslog(LOG_INFO, "Seeding the pseudo-random number generator...");
-       urandom = fopen("/dev/urandom", "r");
-       if (urandom != NULL) {
-               if (fread(&seed, sizeof seed, 1, urandom) == -1) {
-                       syslog(LOG_ERR, "citserver: failed to read random seed: %m");
-               }
-               fclose(urandom);
-       }
-       else {
-               gettimeofday(&tv, NULL);
-               seed = tv.tv_usec;
-       }
-       srand(seed);
-       srandom(seed);
+       syslog(LOG_INFO, "citserver: seeding the pseudo-random number generator");
+       srand(time(NULL) + getpid() + clock());
 }
 
 
 }
 
 
@@ -56,10 +40,10 @@ void master_startup(void) {
        struct passwd *pw;
        gid_t gid;
 
        struct passwd *pw;
        gid_t gid;
 
-       syslog(LOG_DEBUG, "master_startup() started");
+       syslog(LOG_DEBUG, "citserver: master_startup() started");
        time(&server_startup_time);
 
        time(&server_startup_time);
 
-       syslog(LOG_INFO, "Checking directory access");
+       syslog(LOG_INFO, "citserver: checking directory access");
        if ((pw = getpwuid(ctdluid)) == NULL) {
                gid = getgid();
        }
        if ((pw = getpwuid(ctdluid)) == NULL) {
                gid = getgid();
        }
@@ -77,13 +61,13 @@ void master_startup(void) {
        syslog(LOG_DEBUG, "citserver: ctdl_key_dir is %s", ctdl_key_dir);
        syslog(LOG_DEBUG, "citserver: ctdl_run_dir is %s", ctdl_run_dir);
 
        syslog(LOG_DEBUG, "citserver: ctdl_key_dir is %s", ctdl_key_dir);
        syslog(LOG_DEBUG, "citserver: ctdl_run_dir is %s", ctdl_run_dir);
 
-       syslog(LOG_INFO, "Opening databases");
+       syslog(LOG_INFO, "citserver: opening databases");
        cdb_init_backends();
        cdb_open_databases();
 
        // Load site-specific configuration
        seed_random_number_generator();                                 // must be done before config system
        cdb_init_backends();
        cdb_open_databases();
 
        // Load site-specific configuration
        seed_random_number_generator();                                 // must be done before config system
-       syslog(LOG_INFO, "Initializing configuration system");
+       syslog(LOG_INFO, "citserver: initializing configuration system");
        initialize_config_system();
        validate_config();
        migrate_legacy_control_record();
        initialize_config_system();
        validate_config();
        migrate_legacy_control_record();
@@ -98,7 +82,7 @@ void master_startup(void) {
        // Check floor reference counts
        check_ref_counts();
 
        // Check floor reference counts
        check_ref_counts();
 
-       syslog(LOG_INFO, "Creating base rooms (if necessary)");
+       syslog(LOG_INFO, "citserver: creating base rooms (if necessary)");
        CtdlCreateRoom(CtdlGetConfigStr("c_baseroom"), 0, "", 0, 1, 0, VIEW_BBS);
        CtdlCreateRoom(AIDEROOM, 3, "", 0, 1, 0, VIEW_BBS);
        CtdlCreateRoom(SYSCONFIGROOM, 3, "", 0, 1, 0, VIEW_BBS);
        CtdlCreateRoom(CtdlGetConfigStr("c_baseroom"), 0, "", 0, 1, 0, VIEW_BBS);
        CtdlCreateRoom(AIDEROOM, 3, "", 0, 1, 0, VIEW_BBS);
        CtdlCreateRoom(SYSCONFIGROOM, 3, "", 0, 1, 0, VIEW_BBS);
@@ -116,7 +100,7 @@ void master_startup(void) {
                CtdlPutRoomLock(&qrbuf);
        }
 
                CtdlPutRoomLock(&qrbuf);
        }
 
-       syslog(LOG_DEBUG, "master_startup() finished");
+       syslog(LOG_DEBUG, "citserver: master_startup() finished");
 }
 
 
 }
 
 
index b3df0ae3fbf97dd78beddf0302c493a67926413a..9be3addeb75cd95d36e9116308711f9360f86c94 100644 (file)
@@ -636,7 +636,7 @@ void cmd_conf(char *argbuf) {
                        char *valbuf = malloc(bytes + 1);
                        cprintf("%d %d\n", SEND_BINARY, bytes);
                        client_read(valbuf, bytes);
                        char *valbuf = malloc(bytes + 1);
                        cprintf("%d %d\n", SEND_BINARY, bytes);
                        client_read(valbuf, bytes);
-                       valbuf[bytes+1] = 0;
+                       valbuf[bytes] = 0;
                        CtdlSetConfigStr(confname, valbuf);
                        free(valbuf);
                }
                        CtdlSetConfigStr(confname, valbuf);
                        free(valbuf);
                }
index 16f1a016425466d24c2199f89312e7902c58744a..b0e48075c5e0b9a430e78ce3745021848d3959db 100644 (file)
@@ -22,6 +22,7 @@ int CtdlIsMe(char *addr, int addr_buf_len);
 int CtdlHostAlias(char *fqdn);
 char *harvest_collected_addresses(struct CtdlMessage *msg);
 int is_email_subscribed_to_list(char *email, char *room_name);
 int CtdlHostAlias(char *fqdn);
 char *harvest_collected_addresses(struct CtdlMessage *msg);
 int is_email_subscribed_to_list(char *email, char *room_name);
+void generate_one_click_url(char *target_buf, char *base_url, char *action, char *roomname, char *emailaddr);
 
 // Values that can be returned by CtdlHostAlias()
 enum {
 
 // Values that can be returned by CtdlHostAlias()
 enum {
index 19e5fd9f4eff5be15ec26db09dd32a8d5d9d40cd..1691f0f107a4587ec97961974bcfbd56129f6922 100644 (file)
 #include "../../genstamp.h"
 #include "../../domain.h"
 #include "../../ctdl_module.h"
 #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];
 
 
 void inetcfg_setTo(struct CtdlMessage *msg) {
        char *conf;
        char buf[SIZ];
-       
+
        if (CM_IsEmpty(msg, eMessageText)) return;
        conf = strdup(msg->cm_fields[eMessageText]);
 
        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;
 
                if (inetcfg != NULL) free(inetcfg);
                inetcfg = conf;
+               dkim_check_advisory(inetcfg);           // this will check to see if we have to advise the admin about dkim
        }
 }
 
        }
 }
 
index c342b0799d087cf6054f7115ba7ca32cb27a37f4..fd1356079f104eaa907075af27275336949b8442 100644 (file)
@@ -1,6 +1,6 @@
 // This module delivers messages to mailing lists.
 //
 // This module delivers messages to mailing lists.
 //
-// Copyright (c) 2002-2023 by the citadel.org team
+// 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 open source software; you can redistribute it and/or modify
 // it under the terms of the GNU General Public License version 3.
@@ -245,5 +245,5 @@ char *ctdl_module_init_listdeliver(void) {
        }
        
        // return our module name for the log
        }
        
        // return our module name for the log
-       return "listsub";
+       return "listdeliver";
 }
 }
index 9eec9646329756e5be07c073cc8d4f9757c9357d..08aaabadf8c00d8c17e018c46e03a5d83d60c394 100644 (file)
@@ -57,11 +57,35 @@ void generate_confirmation_token(char *token_buf, size_t token_buf_len, char *ro
 }
 
 
 }
 
 
+// Generate a pre-authorized subscribe/unsubscribe URL for a particular email address for a particular room.
+// This can be used as the second part of a double-opt-in or double-opt-out process.
+// It can also be used to generate a "one click unsubscribe" link.
+void generate_one_click_url(char *target_buf, char *base_url, char *action, char *roomname, char *emailaddr) {
+
+       // We need a URL-safe representation of the room name
+       char encoded_roomname[ROOMNAMELEN+10];
+       urlesc(encoded_roomname, sizeof(encoded_roomname), roomname);
+
+       // The confirmation token pre-authorizes the generated URL.  It is hashed by the host key so it can't be guessed.
+       char confirmation_token[128];
+       generate_confirmation_token(confirmation_token, sizeof confirmation_token, roomname, emailaddr);
+
+       // Write to the buffer
+       snprintf(target_buf, SIZ, "%s?cmd=%s&email=%s&room=%s&token=%s",
+               base_url,
+               action,
+               emailaddr,
+               encoded_roomname,
+               confirmation_token
+       );
+}
+
+
 // This generates an email with a link the user clicks to confirm a list subscription.
 void send_subscribe_confirmation_email(char *roomname, char *emailaddr, char *url, char *confirmation_token) {
 // This generates an email with a link the user clicks to confirm a list subscription.
 void send_subscribe_confirmation_email(char *roomname, char *emailaddr, char *url, char *confirmation_token) {
-       // We need a URL-safe representation of the room name
-       char urlroom[ROOMNAMELEN+10];
-       urlesc(urlroom, sizeof(urlroom), roomname);
+
+       char confirm_subscribe_url[SIZ];
+       generate_one_click_url(confirm_subscribe_url, url, "confirm_subscribe", roomname, emailaddr);
 
        char from_address[1024];
        snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
 
        char from_address[1024];
        snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
@@ -80,7 +104,7 @@ void send_subscribe_confirmation_email(char *roomname, char *emailaddr, char *ur
                "<%s> to the <%s> mailing list.\n"
                "\n"
                "Please go here to confirm this request:\n"
                "<%s> to the <%s> mailing list.\n"
                "\n"
                "Please go here to confirm this request:\n"
-               "%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s\n"
+               "%s\n"
                "\n"
                "If this request has been submitted in error and you do not\n"
                "wish to receive the <%s> mailing list, simply do nothing,\n"
                "\n"
                "If this request has been submitted in error and you do not\n"
                "wish to receive the <%s> mailing list, simply do nothing,\n"
@@ -89,26 +113,19 @@ void send_subscribe_confirmation_email(char *roomname, char *emailaddr, char *ur
                "--__ctdlmultipart__\n"
                "Content-type: text/html\n"
                "\n"
                "--__ctdlmultipart__\n"
                "Content-type: text/html\n"
                "\n"
-               "<html><body><p>Someone (probably you) has submitted a request to subscribe "
-               "<strong>%s</strong> to the <strong>%s</strong> mailing list.</p>"
-               "<p>Please go here to confirm this request:</p>"
-               "<p><a href=\"%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s\">"
-               "%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s</a></p>"
-               "<p>If this request has been submitted in error and you do not "
-               "wish to receive the <strong>%s<strong> mailing list, simply do nothing, "
-               "and you will not receive any further mailings.</p>"
+               "<html><body><p>Someone (probably you) has submitted a request to subscribe\n"
+               "<strong>%s</strong> to the <strong>%s</strong> mailing list.</p>\n"
+               "<p>Please go here to confirm this request:</p>\n"
+               "<p><a href=\"%s\">%s</a></p>\n"
+               "<p>If this request has been submitted in error and you do not\n"
+               "wish to receive the <strong>%s</strong> mailing list, simply do nothing,\n"
+               "and you will not receive any further mailings.</p>\n"
                "</body></html>\n"
                "\n"
                "--__ctdlmultipart__--\n"
                ,
                "</body></html>\n"
                "\n"
                "--__ctdlmultipart__--\n"
                ,
-               emailaddr, roomname,
-               url, emailaddr, urlroom, confirmation_token,
-               roomname
-               ,
-               emailaddr, roomname,
-               url, emailaddr, urlroom, confirmation_token,
-               url, emailaddr, urlroom, confirmation_token,
-               roomname
+               emailaddr, roomname, confirm_subscribe_url, roomname,
+               emailaddr, roomname, confirm_subscribe_url, confirm_subscribe_url, roomname
        );
 
        quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list subscription");
        );
 
        quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list subscription");
@@ -118,9 +135,9 @@ void send_subscribe_confirmation_email(char *roomname, char *emailaddr, char *ur
 
 // This generates an email with a link the user clicks to confirm a list unsubscription.
 void send_unsubscribe_confirmation_email(char *roomname, char *emailaddr, char *url, char *confirmation_token) {
 
 // This generates an email with a link the user clicks to confirm a list unsubscription.
 void send_unsubscribe_confirmation_email(char *roomname, char *emailaddr, char *url, char *confirmation_token) {
-       // We need a URL-safe representation of the room name
-       char urlroom[ROOMNAMELEN+10];
-       urlesc(urlroom, sizeof(urlroom), roomname);
+
+       char confirm_unsubscribe_url[SIZ];
+       generate_one_click_url(confirm_unsubscribe_url, url, "confirm_unsubscribe", roomname, emailaddr);
 
        char from_address[1024];
        snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
 
        char from_address[1024];
        snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
@@ -139,7 +156,7 @@ void send_unsubscribe_confirmation_email(char *roomname, char *emailaddr, char *
                "<%s> from the <%s> mailing list.\n"
                "\n"
                "Please go here to confirm this request:\n"
                "<%s> from the <%s> mailing list.\n"
                "\n"
                "Please go here to confirm this request:\n"
-               "%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s\n"
+               "%s\n"
                "\n"
                "If this request has been submitted in error and you still\n"
                "wish to receive the <%s> mailing list, simply do nothing,\n"
                "\n"
                "If this request has been submitted in error and you still\n"
                "wish to receive the <%s> mailing list, simply do nothing,\n"
@@ -148,26 +165,19 @@ void send_unsubscribe_confirmation_email(char *roomname, char *emailaddr, char *
                "--__ctdlmultipart__\n"
                "Content-type: text/html\n"
                "\n"
                "--__ctdlmultipart__\n"
                "Content-type: text/html\n"
                "\n"
-               "<html><body><p>Someone (probably you) has submitted a request to unsubscribe "
-               "<strong>%s</strong> from the <strong>%s</strong> mailing list.</p>"
-               "<p>Please go here to confirm this request:</p>"
-               "<p><a href=\"%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s\">"
-               "%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s</a></p>"
-               "<p>If this request has been submitted in error and you still "
-               "wish to receive the <strong>%s<strong> mailing list, simply do nothing, "
-               "and you will remain subscribed.</p>"
+               "<html><body><p>Someone (probably you) has submitted a request to unsubscribe\n"
+               "<strong>%s</strong> from the <strong>%s</strong> mailing list.</p>\n"
+               "<p>Please go here to confirm this request:</p>\n"
+               "<p><a href=\"%s\">%s</a></p>\n"
+               "<p>If this request has been submitted in error and you still\n"
+               "wish to receive the <strong>%s</strong> mailing list, simply do nothing,\n"
+               "and you will remain subscribed.</p>\n"
                "</body></html>\n"
                "\n"
                "--__ctdlmultipart__--\n"
                ,
                "</body></html>\n"
                "\n"
                "--__ctdlmultipart__--\n"
                ,
-               emailaddr, roomname,
-               url, emailaddr, urlroom, confirmation_token,
-               roomname
-               ,
-               emailaddr, roomname,
-               url, emailaddr, urlroom, confirmation_token,
-               url, emailaddr, urlroom, confirmation_token,
-               roomname
+               emailaddr, roomname, confirm_unsubscribe_url, roomname,
+               emailaddr, roomname, confirm_unsubscribe_url, confirm_unsubscribe_url, roomname
        );
 
        quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list unsubscription");
        );
 
        quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list unsubscription");
diff --git a/citadel/server/modules/smtp/dkim.c b/citadel/server/modules/smtp/dkim.c
new file mode 100644 (file)
index 0000000..76a7a61
--- /dev/null
@@ -0,0 +1,621 @@
+// DKIM signature creation
+// https://www.rfc-editor.org/rfc/rfc6376.html
+//
+// Body canonicalization code (C) 2012 by Timothy E. Johansson
+// The rest is written for Citadel and OpenSSL 3.0 (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>
+
+// This utility function is used by the body canonicalizer
+char *dkim_rtrim(char *str) {
+       char *end;
+       int len = strlen(str);
+
+       while (*str && len) {
+               end = str + len-1;
+               
+               if (*end == ' ' || *end == '\t') {
+                       *end = '\0';
+               }
+               else {
+                       break;
+               }
+               
+               len = strlen(str);
+       }
+       
+       return str;
+}
+
+
+// We can use this handy function to wrap our big dkim signature header to a reasonable width
+void dkim_wrap_header_strbuf(StrBuf *header_in) {
+       char *str = (char *)ChrPtr(header_in);
+       int len = StrLength(header_in);
+
+       char *tmp = malloc(len*3+1);
+       if (!tmp) {
+               return;
+       }
+       int tmp_len = 0;
+       int i;
+       int lcount = 0;
+       
+       for (i = 0; i < len; ++i) {
+               if (str[i] == ' ' || lcount == 75) {
+                       tmp[tmp_len++] = str[i];
+                       tmp[tmp_len++] = '\r';
+                       tmp[tmp_len++] = '\n';
+                       tmp[tmp_len++] = '\t';
+                       lcount = 0;
+               }
+               else {
+                       tmp[tmp_len++] = str[i];
+                       ++lcount;
+               }
+       }
+       
+       tmp[tmp_len] = '\0';
+       StrBufPlain(header_in, tmp, tmp_len);
+       free(tmp);
+}
+
+
+// This utility function is used by the body canonicalizer
+char *dkim_rtrim_lines(char *str) {
+       char *end;
+       int len = strlen(str);
+
+       while (*str && len) {
+               end = str + len-1;
+               
+               if (*end == '\r' || *end == '\n') {
+                       *end = '\0';
+               }
+               else {
+                       break;
+               }
+               
+               len = strlen(str);
+       }
+       
+       return str;
+}
+
+
+// Canonicalize one line of the message body as per the "relaxed" algorithm
+char *dkim_canonicalize_body_line(char *line) {
+       int line_len = 0;
+       int i;
+       
+       // Ignores all whitespace at the end of lines.  Implementations MUST NOT remove the CRLF at the end of the line.
+       dkim_rtrim(line);
+       
+       // Reduces all sequences of whitespace within a line to a single space character.
+       line_len = strlen(line);
+       int new_len = 0;
+
+       for (i = 0; i < line_len; ++i) {
+               if (line[i] == '\t') {
+                       line[i] = ' ';
+               }
+       
+               if (i > 0) {
+                       if (!(line[i-1] == ' ' && line[i] == ' ')) {
+                               line[new_len++] = line[i];
+                       }
+               }
+               else {
+                       line[new_len++] = line[i];
+               }
+       }
+       
+       line[new_len] = '\0';
+       return line;
+}
+
+
+// Canonicalize the message body as per the "relaxed" algorithm
+char *dkim_canonicalize_body(char *body) {
+       int i = 0;
+       int offset = 0;
+       int body_len = strlen(body);
+
+       char *new_body = malloc(body_len*2+3);
+       int new_body_len = 0;
+
+       for (i = 0; i < body_len; ++i) {
+               int is_r = 0;
+
+               if (body[i] == '\n') {
+                       if (i > 0) {
+                               if (body[i-1] == '\r') {
+                                       i--;
+                                       is_r = 1;
+                               }
+                       }
+
+                       char *line = malloc(i - offset + 1);    
+                       memcpy(line, body+offset, i-offset);
+                       line[i-offset] = '\0';
+
+                       dkim_canonicalize_body_line(line);
+
+                       int line_len = strlen(line);
+                       memcpy(new_body+new_body_len, line, line_len);
+                       memcpy(new_body+new_body_len+line_len, "\r\n", 2);
+                       new_body_len += line_len+2;
+
+                       if (is_r) {
+                               i++;
+                       }       
+
+                       offset = i+1;
+                       free(line);
+               }
+       }
+
+       if (offset < body_len) {
+               char *line = malloc(i - offset + 1);    
+               memcpy(line, body+offset, i-offset);
+               line[i-offset] = '\0';
+
+               dkim_canonicalize_body_line(line);
+
+               int line_len = strlen(line);
+               memcpy(new_body+new_body_len, line, line_len);
+               memcpy(new_body+new_body_len+line_len, "\r\n", 2);
+               new_body_len += line_len+2;
+
+               free(line);
+       }
+
+       memcpy(new_body+new_body_len, "\0", 1);
+
+       // Ignores all empty lines at the end of the message body.  "Empty line" is defined in Section 3.4.3.
+       new_body = dkim_rtrim_lines(new_body);
+
+       // Note that a completely empty or missing body is canonicalized as a
+        // single "CRLF"; that is, the canonicalized length will be 2 octets.
+       new_body_len = strlen(new_body);
+       new_body[new_body_len++] = '\r';
+       new_body[new_body_len++] = '\n';
+       new_body[new_body_len] = '\0';
+
+       return new_body;        
+}
+
+
+// First step to canonicalize a block of headers as per the "relaxed" algorithm.
+// Unfold all headers onto single lines.
+void dkim_unfold_headers(StrBuf *unfolded_headers) {
+       char *headers_start = (char *)ChrPtr(unfolded_headers);
+       char *fold;
+
+       while (
+               fold = strstr(headers_start, "\r\n "),                          // find the first holded header
+               fold = (fold ? fold : strstr(headers_start, "\r\n\t")),         // it could be folded with tabs
+               fold != NULL                                                    // keep going until there aren't any left
+       ) {
+
+               // Replace CRLF<space> or CRLF<tab> with CRLF
+               StrBufReplaceToken(unfolded_headers, (long)(fold-headers_start), 3, HKEY("\r\n"));
+
+               // And when we've got them all, remove the CRLF as well.
+               if (
+                       (strstr(headers_start, "\r\n ") != fold)
+                       && (strstr(headers_start, "\r\n\t") != fold)
+                       && (!strncmp(fold, HKEY("\r\n")))
+               ) {
+                       StrBufReplaceToken(unfolded_headers, (long)(fold-headers_start), 2, HKEY(""));
+               }
+
+       }
+}
+
+
+// Second step to canonicalize a block of headers as per the "relaxed" algorithm.
+// Headers MUST already be unfolded with dkim_unfold_headers()
+void dkim_canonicalize_unfolded_headers(StrBuf *headers) {
+
+       char *cheaders = (char *)ChrPtr(headers);
+       char *ptr = cheaders;
+       while (*ptr) {
+
+               // We are at the beginning of a line.  Find the colon separator between field name and value.
+               char *start_of_this_line = ptr;
+               char *colon = strstr(ptr, ":");
+
+               // remove whitespace after the colon
+               while ( (*(colon+1) == ' ') || (*(colon+2) == '\t') ) {
+                       StrBufReplaceToken(headers, (long)(colon+1-cheaders), 1, HKEY(""));
+               }
+               char *end_of_this_line = strstr(ptr, "\r\n");
+
+               // replace all multiple whitespace runs with a single space
+               int replaced_something;
+               do {
+                       replaced_something = 0;
+                       char *double_space = strstr(ptr, "  ");                 // space-space?
+                       if (!double_space) {
+                               double_space = strstr(ptr, " \t");              // space-tab?
+                       }
+                       if (!double_space) {
+                               double_space = strstr(ptr, "\t ");              // tab-space?
+                       }
+                       if (!double_space) {
+                               double_space = strstr(ptr, "\t\t");             // tab-tab?
+                       }
+                       if (double_space) {
+                               StrBufReplaceToken(headers, (long)(double_space-cheaders), 2, HKEY(" "));
+                               ++replaced_something;
+                       }
+               } while (replaced_something);
+
+               // remove whitespace at the end of the line
+               do {
+                       replaced_something = 0;
+                       char *trailing_space = strstr(ptr, " \r\n");            // line ends in a space?
+                       if (!trailing_space) {                                  // no?
+                               trailing_space = strstr(ptr, "\t\r\n");         // how about a tab?
+                       }
+                       if (trailing_space) {
+                               StrBufReplaceToken(headers, (long)(trailing_space-cheaders), 3, HKEY("\r\n"));
+                               ++replaced_something;
+                       }
+               } while (replaced_something);
+
+               // Convert header field names to all lower case
+               for (char *c = start_of_this_line; c<colon; ++c) {
+                       cheaders[c-cheaders] = tolower(cheaders[c-cheaders]);
+               }
+
+               ptr = end_of_this_line + 2;                                     // Advance to the beginning of the next line
+       }
+}
+
+
+// Third step to canonicalize a block of headers as per the "relaxed" algorithm.
+// Reduce the canonicalized header block to only the fields being signed
+void dkim_reduce_canonicalized_headers(StrBuf *headers, char *header_list) {
+
+       char *cheaders = (char *)ChrPtr(headers);
+       char *ptr = cheaders;
+       while (*ptr) {
+
+               // We are at the beginning of a line.  Find the colon separator between field name and value.
+               char *start_of_this_line = ptr;
+               char *colon = strstr(ptr, ":");
+               char *end_of_this_line = strstr(ptr, "\r\n");
+
+               char relevant_headers[1024];
+               strncpy(relevant_headers, header_list, sizeof(relevant_headers));
+               char *rest = relevant_headers;
+               char *token = NULL;
+               int keep_this_header = 0;
+
+               while (token = strtok_r(rest, ":", &rest)) {
+                       if (!strncmp(start_of_this_line, token, strlen(token))) {
+                               keep_this_header = 1;
+                       }
+               }
+
+               if (keep_this_header) {                                          // Advance to the beginning of the next line
+                       ptr = end_of_this_line + 2;
+               }
+               else {
+                       StrBufReplaceToken(headers, (long)(start_of_this_line - cheaders), end_of_this_line-start_of_this_line+2, HKEY(""));
+               }
+       }
+
+}
+
+
+// Make a new header list containing only the headers actually present in the canonicalized header block.
+void dkim_final_header_list(char *header_list, size_t header_list_size, StrBuf *unfolded_headers) {
+       header_list[0] = 0;
+
+       char *cheaders = (char *)ChrPtr(unfolded_headers);
+       char *ptr = cheaders;
+       while (*ptr) {
+
+               // We are at the beginning of a line.  Find the colon separator between field name and value.
+               char *start_of_this_line = ptr;
+               char *colon = strstr(ptr, ":");
+               char *end_of_this_line = strstr(ptr, "\r\n");
+
+               if (ptr != cheaders) {
+                       strcat(header_list, ":");
+               }
+
+               strncat(header_list, start_of_this_line, (colon-start_of_this_line));
+
+               ptr = end_of_this_line + 2;                                     // Advance to the beginning of the next line
+       }
+}
+
+
+// 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 with error 0x%lx", ERR_get_error());
+       }
+
+       return(pkey);
+}
+
+
+// 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;
+
+       if (!email) {                                                           // no message was supplied
+               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)
+
+       char *body_ptr = strstr(ChrPtr(email), "\r\n\r\n");
+       if (body_ptr == NULL) {
+               syslog(LOG_ERR, "dkim: this message cannot be signed because it has no body");
+               return;
+       }
+
+       size_t body_offset = body_ptr - ChrPtr(email);                          // offset at which message body begins
+       StrBuf *header_block = NewStrBufPlain(ChrPtr(email), body_offset+2);    // headers only (the +2 makes it include final CRLF)
+
+       // This removes the headers from the supplied email buffer.  We MUST put them back in later.
+       StrBufCutLeft(email, body_offset+4);                                    // The +4 makes it NOT include the CRLFCRLF
+
+       // Apply the "relaxed" canonicalization to the message body
+       char *relaxed_body = dkim_canonicalize_body((char *)ChrPtr(email));
+       int relaxed_body_len = strlen(relaxed_body);
+
+       // hash of the canonicalized body
+       unsigned char *body_hash = malloc(SHA256_DIGEST_LENGTH);
+       SHA256((unsigned char *)relaxed_body, relaxed_body_len, body_hash);
+       free(relaxed_body);                                                     // all we need now is the hash
+       relaxed_body = NULL;
+
+       // base64 encode the body hash
+       char *encoded_body_hash = malloc(SHA256_DIGEST_LENGTH * 2);
+       CtdlEncodeBase64(encoded_body_hash, body_hash, SHA256_DIGEST_LENGTH, BASE64_NO_LINEBREAKS);
+       free(body_hash);                                                        // all we need now is the encoded hash
+
+       // "relaxed" header canonicalization, step 1 : unfold the headers
+       StrBuf *unfolded_headers = NewStrBufDup(header_block);
+       dkim_unfold_headers(unfolded_headers);
+
+       // "relaxed" header canonicalization, step 2 : lowercase the header names, remove whitespace after the colon
+       dkim_canonicalize_unfolded_headers(unfolded_headers);
+
+       // "relaxed" header canonicalization, step 3 : reduce the canonicalized header block to only the fields being signed
+       char *header_list = "from:to:cc:reply-to:subject:date:list-unsubscribe:list-unsubscribe-post";
+       dkim_reduce_canonicalized_headers(unfolded_headers, header_list);
+
+       // Make a new header list containing only the ones we actually have.
+       char final_header_list[1024];
+       dkim_final_header_list(final_header_list, sizeof(final_header_list), unfolded_headers);
+
+       // create DKIM header
+       StrBuf *dkim_header = NewStrBuf();
+       StrBufPrintf(dkim_header,
+               "v=1; a=rsa-sha256; s=%s; d=%s; l=%d; t=%d; c=relaxed/relaxed; h=%s; bh=%s; b=",
+               selector,
+               domain,
+               relaxed_body_len,
+               time(NULL),
+               final_header_list,
+               encoded_body_hash
+       );
+       free(encoded_body_hash);                                                // Hash is stored in the header now.
+
+       // Add the initial DKIM header (which is still missing the value after "b=") to the headers to be signed.
+       // RFC6376 3.7 tells us NOT to include CRLF after "b="
+       StrBufAppendBufPlain(unfolded_headers, HKEY("dkim-signature:"), 0);
+       StrBufAppendBuf(unfolded_headers, dkim_header, 0);
+
+       // 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.
+
+       // 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.
+
+       // Create the Message Digest Context
+       EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
+       if (mdctx == NULL) {
+               syslog(LOG_ERR, "dkim: EVP_MD_CTX_create() failed with error 0x%lx", ERR_get_error());
+               abort();
+       }
+
+       // Initialize the DigestSign operation using SHA-256 algorithm
+       if (EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, pkey) != 1) {
+               syslog(LOG_ERR, "dkim: EVP_DigestSignInit() failed with error 0x%lx", ERR_get_error());
+               abort();
+       }
+
+       // Call update with the "message" (the canonicalized headers)
+       if (EVP_DigestSignUpdate(mdctx, (char *)ChrPtr(unfolded_headers), StrLength(unfolded_headers)) != 1) {
+               syslog(LOG_ERR, "dkim: EVP_DigestSignUpdate() failed with error 0x%lx", ERR_get_error());
+               abort();
+       }
+
+       // Finalize the DigestSign operation.
+       // First call EVP_DigestSignFinal with a NULL sig parameter to obtain the length of the signature.
+       // Length is returned in signature_len
+       size_t signature_len;
+       if (EVP_DigestSignFinal(mdctx, NULL, &signature_len) != 1) {
+               syslog(LOG_ERR, "dkim: EVP_DigestSignFinal() failed");
+               abort();
+       }
+
+       // Sanity check.  The signature length should be the same as the size of the private key.
+       assert(signature_len == EVP_PKEY_size(pkey));
+
+       // Allocate memory for the signature based on size in signature_len
+       unsigned char *sig = OPENSSL_malloc(signature_len);
+       if (sig == NULL) {
+               syslog(LOG_ERR, "dkim: OPENSSL_malloc() failed");
+               abort();
+       }
+
+       // Obtain the signature
+       if (EVP_DigestSignFinal(mdctx, sig, &signature_len) != 1) {
+               syslog(LOG_ERR, "dkim: EVP_DigestSignFinal() failed");
+               abort();
+       }
+       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 in Citadel Server.
+#ifdef DKIM_VERIFY_SIGNATURE
+       mdctx = EVP_MD_CTX_new();
+       if (mdctx) {
+               assert(EVP_DigestVerifyInit(mdctx, NULL, EVP_sha256(), NULL, pkey) == 1);
+               assert(EVP_DigestVerifyUpdate(mdctx, (char *)ChrPtr(unfolded_headers), StrLength(unfolded_headers)) == 1);
+               assert(EVP_DigestVerifyFinal(mdctx, sig, signature_len) == 1);
+               EVP_MD_CTX_free(mdctx);
+       }
+#endif
+
+       // With the signature complete, we no longer need the private key or the unfolded headers.
+       EVP_PKEY_free(pkey);
+       FreeStrBuf(&unfolded_headers);
+
+       // base64 encode the signature
+       char *encoded_signature = malloc(signature_len * 2);
+       int encoded_signature_len = CtdlEncodeBase64(encoded_signature, sig, signature_len, BASE64_NO_LINEBREAKS);
+       free(sig);                                                      // Free the raw signature, keep the b64-encoded one.
+       StrBufAppendPrintf(dkim_header, "%s", encoded_signature);       // Make the final header.
+       free(encoded_signature);
+
+       // wrap dkim header to 72-ish columns
+       dkim_wrap_header_strbuf(dkim_header);
+
+       // Now reassemble the message.
+       StrBuf *output_msg = NewStrBuf();
+       StrBufPrintf(output_msg, "DKIM-Signature: %s\r\n", (char *)ChrPtr(dkim_header));
+       StrBufAppendBuf(output_msg, header_block, 0);
+       StrBufAppendBufPlain(output_msg, HKEY("\r\n"), 0);
+       StrBufAppendBuf(output_msg, email, 0);
+
+       // Put the combined message where the caller can find it.
+       FreeStrBuf(&dkim_header);
+       FreeStrBuf(&header_block);
+       SwapBuffers(output_msg, email);
+       FreeStrBuf(&output_msg);
+
+       // And we're done!
+}
+
+
diff --git a/citadel/server/modules/smtp/dkim_bindings.c b/citadel/server/modules/smtp/dkim_bindings.c
new file mode 100644 (file)
index 0000000..340e574
--- /dev/null
@@ -0,0 +1,192 @@
+// 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"
+#include "smtp_util.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\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"
+               ),0);
+
+               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);
+               }
+
+               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",
+                       NULL,                           // from
+                       NULL,                           // to
+                       AIDEROOM,                       // room
+                       ChrPtr(message),                // text
+                       FMT_RFC822,                     // format
+                       "Confirm your 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);
+}
index edce1f5c387088008f16b42ca651860bb6d42dbb..03f54b108b0725c794e0fb2f6c5b8dcbf4bdd5ca 100644 (file)
@@ -2,7 +2,7 @@
 //
 // This is the new, exciting, clever version that makes libcurl do all the work  :)
 //
 //
 // This is the new, exciting, clever version that makes libcurl do all the work  :)
 //
-// Copyright (c) 1997-2023 by the citadel.org team
+// Copyright (c) 1997-2024 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 terms of the GNU General Public License, version 3.
@@ -213,29 +213,52 @@ int smtp_attempt_delivery(long msgid, char *recp, char *envelope_from, char *sou
        process_rfc822_addr(recp, user, node, name);    // split recipient address into username, hostname, displayname
        num_mx = getmx(mxes, node);
        if (num_mx < 1) {
        process_rfc822_addr(recp, user, node, name);    // split recipient address into username, hostname, displayname
        num_mx = getmx(mxes, node);
        if (num_mx < 1) {
-               return (421);
+               return(421);
        }
 
        CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
        }
 
        CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+
+       // If we have a source room, it's probably a mailing list message; generate an unsubscribe header
        if (!IsEmptyStr(source_room)) {
        if (!IsEmptyStr(source_room)) {
-               // If we have a source room, it's probably a mailing list message; generate an unsubscribe header
-               char esc_room[ROOMNAMELEN*2];
-               char esc_email[1024];
-               urlesc(esc_room, sizeof esc_room, source_room);
-               urlesc(esc_email, sizeof esc_email, recp);
-               cprintf("List-Unsubscribe: <http://%s/listsub?cmd=unsubscribe&room=%s&email=%s>\r\n",
-                       CtdlGetConfigStr("c_fqdn"),
-                       esc_room,
-                       esc_email
-               );
+               char base_url[SIZ];
+               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-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;
        CtdlOutputMsg(msgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0, NULL, &fromaddr, NULL);
        s.TheMessage = CC->redirect_buffer;
-       s.bytes_total = StrLength(CC->redirect_buffer);
-       s.bytes_sent = 0;
        CC->redirect_buffer = NULL;
        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_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?
+       ) {
+               // 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
+       s.bytes_total = StrLength(s.TheMessage);
+       s.bytes_sent = 0;
        response_code = 421;
        response_code = 421;
-                                       // keep trying MXes until one works or we run out
+
+
+       // Keep trying MXes until one works or we run out.
        for (i = 0; ((i < num_mx) && ((response_code / 100) == 4)); ++i) {
                response_code = 421;    // default 421 makes non-protocol errors transient
                s.bytes_sent = 0;       // rewind our buffer in case we try multiple MXes
        for (i = 0; ((i < num_mx) && ((response_code / 100) == 4)); ++i) {
                response_code = 421;    // default 421 makes non-protocol errors transient
                s.bytes_sent = 0;       // rewind our buffer in case we try multiple MXes
@@ -323,7 +346,7 @@ void smtp_process_one_msg(long qmsgnum) {
 
        msg = CtdlFetchMessage(qmsgnum, 1);
        if (msg == NULL) {
 
        msg = CtdlFetchMessage(qmsgnum, 1);
        if (msg == NULL) {
-               syslog(LOG_WARNING, "smtpclient: %ld does not exist", qmsgnum);
+               syslog(LOG_WARNING, "smtpclient: msg#%ld does not exist", qmsgnum);
                return;
        }
 
                return;
        }
 
@@ -463,7 +486,7 @@ void smtp_process_one_msg(long qmsgnum) {
                }
        }
        else {
                }
        }
        else {
-               syslog(LOG_DEBUG, "smtpclient: %ld retry time not reached", qmsgnum);
+               syslog(LOG_DEBUG, "smtpclient: msg#%ld retry time not reached", qmsgnum);
        }
 
        if (bounceto != NULL) {
        }
 
        if (bounceto != NULL) {
@@ -577,6 +600,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();
                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
        }
 
        // return our module id for the log
index d02a53fadcbbb91fe8e6f5ca99d6635fea22bca1..cdaea243aa45acc671308bf9a98cd3e24adda5ca 100644 (file)
@@ -1,17 +1,7 @@
-/*
- * Copyright (c) 1998-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.
- *
- */
+// Copyright (c) 1998-2024 by the citadel.org team
+// This program is open source software.  Use, duplication, or disclosure is subject to the GNU General Public License v3.
 
 
-struct citsmtp {               /* Information about the current session */
+struct citsmtp {               // Information about the current session
        int command_state;
        StrBuf *Cmd;
        StrBuf *helo_node;
        int command_state;
        StrBuf *Cmd;
        StrBuf *helo_node;
@@ -39,3 +29,8 @@ enum {
 
 void smtp_do_bounce(const char *instr, int is_final);
 char *smtpstatus(int code);
 
 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(char *inetcfg_in);
+EVP_PKEY *dkim_import_key(char *pkey_in);
+char *dkim_get_public_key(EVP_PKEY *pkey);
index b3a8f7071c66a9a3ee651dcc2e9feba089886e68..aaf4446c604f07f6b434bffa7dc905fd26e84109 100644 (file)
@@ -1488,16 +1488,13 @@ int CtdlOutputMsg(long msg_num,         // message number (local) to fetch
                        );
 
                if ((Author != NULL) && (*Author == NULL)) {
                        );
 
                if ((Author != NULL) && (*Author == NULL)) {
-                       long len;
-                       CM_GetAsField(TheMessage, eAuthor, Author, &len);
+                       *Author = strdup(TheMessage->cm_fields[eAuthor]);
                }
                if ((Address != NULL) && (*Address == NULL)) {  
                }
                if ((Address != NULL) && (*Address == NULL)) {  
-                       long len;
-                       CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
+                       *Address = strdup(TheMessage->cm_fields[erFc822Addr]);
                }
                if ((MessageID != NULL) && (*MessageID == NULL)) {      
                }
                if ((MessageID != NULL) && (*MessageID == NULL)) {      
-                       long len;
-                       CM_GetAsField(TheMessage, emessageId, MessageID, &len);
+                       *MessageID = strdup(TheMessage->cm_fields[emessageId]);
                }
                CM_Free(TheMessage);
                TheMessage = NULL;
                }
                CM_Free(TheMessage);
                TheMessage = NULL;
@@ -2011,12 +2008,13 @@ int CtdlOutputPreLoadedMsg(
        }
 
        if (mode == MT_RFC822) {
        }
 
        if (mode == MT_RFC822) {
-               // Construct a fun message id
-               cprintf("Message-ID: <%s", mid);
-               if (strchr(mid, '@')==NULL) {
-                       cprintf("@%s", snode);
-               }
-               cprintf(">%s", nl);
+               // Make the message ID RFC2822 compliant
+               cprintf("Message-ID: <%s%s%s>%s",               // put it in angle brackets
+                       mid,
+                       (strchr(mid, '@') ? "" : "@"),          // if there is no domain part,
+                       (strchr(mid, '@') ? "" : snode),        // tack on ours.
+                       nl
+               );
 
                if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
                        cprintf("From: \"----\" <x@x.org>%s", nl);
 
                if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
                        cprintf("From: \"----\" <x@x.org>%s", nl);
@@ -2320,7 +2318,7 @@ long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid) {
        }
 
        if (error_count > 0) {
        }
 
        if (error_count > 0) {
-               syslog(LOG_ERR, "msgbase: encountered %d errors storing message %ld", error_count, msgid);
+               syslog(LOG_ERR, "msgbase: encountered %ld errors storing message %ld", error_count, msgid);
        }
 
        // Free the memory we used for the serialized message
        }
 
        // Free the memory we used for the serialized message
diff --git a/citadel/tests/dkimtester/.gitignore b/citadel/tests/dkimtester/.gitignore
new file mode 100644 (file)
index 0000000..c9a9405
--- /dev/null
@@ -0,0 +1 @@
+dkimtester
diff --git a/citadel/tests/dkimtester/Makefile b/citadel/tests/dkimtester/Makefile
new file mode 100644 (file)
index 0000000..d308ec2
--- /dev/null
@@ -0,0 +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
+
+clean:
+       rm -f dkimtester
diff --git a/citadel/tests/dkimtester/README.md b/citadel/tests/dkimtester/README.md
new file mode 100644 (file)
index 0000000..5a9d9e8
--- /dev/null
@@ -0,0 +1,20 @@
+# dkimtester
+
+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:
+
+```
+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.
diff --git a/citadel/tests/dkimtester/dkimtester.c b/citadel/tests/dkimtester/dkimtester.c
new file mode 100644 (file)
index 0000000..4a4bf33
--- /dev/null
@@ -0,0 +1,75 @@
+// ***** TEST HARNESS *****
+// This contains a private key that, at the time of writing, matches the DKIM public key for dev.citadel.org
+// We can use the attached test message to validate a signature against that.
+
+#include <stdio.h>
+#include <syslog.h>
+#include <libcitadel.h>
+
+// This was easier than trying to figure out the header situation
+void dkim_sign(StrBuf *email, char *pkey_in, char *domain, char *selector);
+
+int main(int argc, char *argv[]) {
+
+       // display the greeting
+       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 (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"
+       );
+
+       openlog("dkim", LOG_PERROR, LOG_USER);
+
+       // 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";
+
+       // Sample message
+       StrBuf *email = NewStrBufPlain(HKEY(
+               "From: Fred Bloggs <bloggs@dev.citadel.org>\r\n"
+               "X-irrelevant-header: wow mom 303\r\n"
+               "To: Bread Floggs <bf@example.com>\r\n"
+               "Subject: The ultimate  test message\r\n"
+               "Message-ID: <73294856-8726543-473298@dev.citadel.org>\r\n"
+               "\r\n"
+               "Hi.\r\n"
+               "\r\n"
+               "Bhille Disassemble.  Highly recommend.\r\n"
+               "\r\n"
+               "--Fred\r\n"
+
+
+       ));
+
+       // create signature
+       dkim_sign(email, private_key, domain, selector);
+
+       // Show the user what we did
+       printf("%s", (char *)ChrPtr(email));
+
+       FILE *fp;
+       printf("\033[34m-----\033[0m\n");
+       printf("Piping original version to test program (this should pass)\n");
+       fp = popen("./tester.pl | sed s/pass/\033[32mpass\033[0m/g | sed s/fail/\033[31mfail\033[0m/g", "w");
+       fwrite((char *)ChrPtr(email), StrLength(email), 1, fp);
+       pclose(fp);
+       printf("\033[34m-----\033[0m\n");
+       printf("Piping altered version to test program (this should fail)\n");
+       fp = popen("sed s/oggs/argh/g | ./tester.pl | sed s/pass/\033[32mpass\033[0m/g | sed s/fail/\033[31mfail\033[0m/g", "w");
+       fwrite((char *)ChrPtr(email), StrLength(email), 1, fp);
+       pclose(fp);
+       printf("\033[34m-----\033[0m\n");
+
+       // free some memory
+       FreeStrBuf(&email);
+
+       // exit the test program
+       return(0);
+}
diff --git a/citadel/tests/dkimtester/tester.pl b/citadel/tests/dkimtester/tester.pl
new file mode 100755 (executable)
index 0000000..1f23339
--- /dev/null
@@ -0,0 +1,33 @@
+#!/usr/bin/perl
+
+use Mail::DKIM::Verifier;
+# create a verifier object
+my $dkim = Mail::DKIM::Verifier->new();
+# read an email from a file handle
+while (<STDIN>)
+{
+    # remove local line terminators
+    chomp;
+    s/\015$//;
+    # use SMTP line terminators
+    $dkim->PRINT("$_\015\012");
+}
+$dkim->CLOSE;
+# what is the result of the verify?
+my $result = $dkim->result;
+
+# there might be multiple signatures, what is the result per signature?
+foreach my $signature ($dkim->signatures)
+{
+    print 'signature identity: ' . $signature->identity . "\n";
+    print '     verify result: ' . $signature->result_detail . "\n";
+}
+# the alleged author of the email may specify how to handle email
+foreach my $policy ($dkim->policies)
+{
+    die 'fraudulent message' if ($policy->apply($dkim) eq 'reject');
+}
index a4bdb50478940fb2406b1d9d16a9a63147ae5770..e69aa0873745d1ea3607a08929f74147695a4849 100644 (file)
@@ -1,6 +1,6 @@
 // Load (restore) the Citadel database from a flat file created by ctdldump
 //
 // 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.
 //
 // This program is open source software.  Use, duplication, or disclosure
 // is subject to the terms of the GNU General Public License, version 3.
@@ -118,6 +118,7 @@ int import_user(char *line, struct cdbkeyval *kv) {
        char userkey[USERNAME_SIZE];
        char *token;
        struct ctdluser *u;
        char userkey[USERNAME_SIZE];
        char *token;
        struct ctdluser *u;
+       int dlen = 0;
 
        u = malloc(sizeof(struct ctdluser));
        if (!u) {
 
        u = malloc(sizeof(struct ctdluser));
        if (!u) {
@@ -163,8 +164,12 @@ int import_user(char *line, struct cdbkeyval *kv) {
                                u->msgnum_pic = atol(token);
                                break;
                        case 12:
                                u->msgnum_pic = atol(token);
                                break;
                        case 12:
-                               CtdlDecodeBase64(token, token, strlen(token));                  // Decode in place
-                               safestrncpy(u->emailaddrs, token, sizeof(u->emailaddrs));
+                               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);
                                break;
                        case 13:
                                u->msgnum_inboxrules = atol(token);
index 81336d9abe9b795e13652bb044f46daa8eaac62f..584e5363219998ea55c759ea6ac85a6bfadabde2 100644 (file)
@@ -1,12 +1,10 @@
 This is libcitadel, a library which contains code that is used in multiple
 components of the Citadel system -- the server, the text mode client, and
 WebCit.  It is not intended to be a general-purpose library for widespread
 This is libcitadel, a library which contains code that is used in multiple
 components of the Citadel system -- the server, the text mode client, and
 WebCit.  It is not intended to be a general-purpose library for widespread
-use, although there are parts of it that may be useful for that purpose,
-such as the MIME parser and the vCard data type.
+use, although there are parts of it that may be useful for that purpose.
 
 
-Copyright (c) 1987-2023 by the citadel.org development team.
-This program is distributed under the terms of the GNU General Public
-License, version 3.
+Copyright (c) 1987-2024 by the citadel.org development team.
+This program is open source software.  Use, duplication, or disclosure is subject to the GNU General Public License v3.
 
 To build and install libcitadel:
 
 
 To build and install libcitadel:
 
index 1817187371f798c931caad30603ed092496e13a6..655ebe4452baf6cb5f15653c923d03cffdb4201e 100644 (file)
@@ -19,7 +19,7 @@
 #include <sys/types.h>
 #include <netinet/in.h>
 
 #include <sys/types.h>
 #include <netinet/in.h>
 
-#define LIBCITADEL_VERSION_NUMBER 999
+#define LIBCITADEL_VERSION_NUMBER 1000
 
 /*
  * Here's a bunch of stupid magic to make the MIME parser portable.
 
 /*
  * Here's a bunch of stupid magic to make the MIME parser portable.
index b382025b957fc9d103d40877e1c30b835c56190b..7f136deb05155c3f509e498964802ba943134315 100644 (file)
@@ -1,7 +1,6 @@
-// Copyright (c) 1987-2023 by the citadel.org team
+// Copyright (c) 1987-2024 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.
 
 #define _GNU_SOURCE
 #include "sysdep.h"
 
 #define _GNU_SOURCE
 #include "sysdep.h"
@@ -37,11 +36,11 @@ int ZEXPORT compress_gzip(Bytef * dest, size_t * destLen, const Bytef * source,
 #endif
 int BaseStrBufSize = 64;
 int EnableSplice = 0;
 #endif
 int BaseStrBufSize = 64;
 int EnableSplice = 0;
-int ZLibCompressionRatio = -1; /* defaults to 6 */
+int ZLibCompressionRatio = -1; // defaults to 6
 #ifdef HAVE_ZLIB
 #ifdef HAVE_ZLIB
-#define DEF_MEM_LEVEL 8 /*< memlevel??? */
-#define OS_CODE 0x03   /*< unix */
-const int gz_magic[2] = { 0x1f, 0x8b };        /* gzip magic header */
+#define DEF_MEM_LEVEL 8 // memlevel???
+#define OS_CODE 0x03   // unix
+const int gz_magic[2] = { 0x1f, 0x8b };        // gzip magic header
 #endif
 
 const char *StrBufNOTNULL = ((char*) NULL) - 1;
 #endif
 
 const char *StrBufNOTNULL = ((char*) NULL) - 1;
@@ -164,19 +163,17 @@ void dbg_Init(StrBuf *Buf) {
 }
 
 #else
 }
 
 #else
-/* void it... */
+// void it...
 #define dbg_FreeStrBuf(a, b)
 #define dbg_IncreaseBuf(a)
 #define dbg_Init(a)
 
 #endif
 
 #define dbg_FreeStrBuf(a, b)
 #define dbg_IncreaseBuf(a)
 #define dbg_Init(a)
 
 #endif
 
-/*
- *  swaps the contents of two StrBufs
- * this is to be used to have cheap switched between a work-buffer and a target buffer 
- *  A First one
- *  B second one
- */
+// swaps the contents of two StrBufs
+// this is to be used to have cheap switched between a work-buffer and a target buffer 
+// A First one
+// B second one
 static inline void iSwapBuffers(StrBuf *A, StrBuf *B) {
        StrBuf C;
 
 static inline void iSwapBuffers(StrBuf *A, StrBuf *B) {
        StrBuf C;
 
@@ -192,15 +189,13 @@ void SwapBuffers(StrBuf *A, StrBuf *B) {
 }
 
 
 }
 
 
-/* 
- *  Cast operator to Plain String 
- * @note if the buffer is altered by StrBuf operations, this pointer may become 
- *  invalid. So don't lean on it after altering the buffer!
- *  Since this operation is considered cheap, rather call it often than risking
- *  your pointer to become invalid!
- *  Str the string we want to get the c-string representation for
- * @returns the Pointer to the Content. Don't mess with it!
- */
+// Cast operator to Plain String 
+// note: if the buffer is altered by StrBuf operations, this pointer may become 
+// invalid. So don't lean on it after altering the buffer!
+// Since this operation is considered cheap, rather call it often than risking
+// your pointer to become invalid!
+// Str the string we want to get the c-string representation for
+// returns the Pointer to the Content. Don't mess with it!
 inline const char *ChrPtr(const StrBuf *Str) {
        if (Str == NULL)
                return "";
 inline const char *ChrPtr(const StrBuf *Str) {
        if (Str == NULL)
                return "";
@@ -208,11 +203,9 @@ inline const char *ChrPtr(const StrBuf *Str) {
 }
 
 
 }
 
 
-/*
- *  since we know strlen()'s result, provide it here.
- *  Str the string to return the length to
- * @returns contentlength of the buffer
- */
+// since we know strlen()'s result, provide it here.
+// Str the string to return the length to
+// returns contentlength of the buffer
 inline int StrLength(const StrBuf *Str) {
        return (Str != NULL) ? Str->BufUsed : 0;
 }
 inline int StrLength(const StrBuf *Str) {
        return (Str != NULL) ? Str->BufUsed : 0;
 }
@@ -276,12 +269,10 @@ void ReAdjustEmptyBuf(StrBuf *Buf, long ThreshHold, long NewSize) {
 }
 
 
 }
 
 
-/*
- *  shrink long term buffers to their real size so they don't waste memory
- *  Buf buffer to shrink
- *  Force if not set, will just executed if the buffer is much to big; set for lifetime strings
- * @returns physical size of the buffer
- */
+// shrink long term buffers to their real size so they don't waste memory
+// Buf buffer to shrink
+// Force if not set, will just executed if the buffer is much to big; set for lifetime strings
+// returns physical size of the buffer
 long StrBufShrinkToFit(StrBuf *Buf, int Force) {
        if (Buf == NULL)
                return -1;
 long StrBufShrinkToFit(StrBuf *Buf, int Force) {
        if (Buf == NULL)
                return -1;
@@ -301,10 +292,8 @@ long StrBufShrinkToFit(StrBuf *Buf, int Force) {
 }
 
 
 }
 
 
-/*
- *  Allocate a new buffer with default buffer size
- * @returns the new stringbuffer
- */
+// Allocate a new buffer with default buffer size
+// returns the new stringbuffer
 StrBuf *NewStrBuf(void) {
        StrBuf *NewBuf;
 
 StrBuf *NewStrBuf(void) {
        StrBuf *NewBuf;
 
@@ -327,11 +316,9 @@ StrBuf *NewStrBuf(void) {
 }
 
 
 }
 
 
-/* 
- *  Copy Constructor; returns a duplicate of CopyMe
- *  CopyMe Buffer to faxmilate
- * @returns the new stringbuffer
- */
+// Copy Constructor; returns a duplicate of CopyMe
+// CopyMe Buffer to faxmilate
+// returns the new stringbuffer
 StrBuf *NewStrBufDup(const StrBuf *CopyMe) {
        StrBuf *NewBuf;
        
 StrBuf *NewStrBufDup(const StrBuf *CopyMe) {
        StrBuf *NewBuf;
        
@@ -359,14 +346,12 @@ StrBuf *NewStrBufDup(const StrBuf *CopyMe) {
 }
 
 
 }
 
 
-/* 
- *  Copy Constructor; CreateRelpaceMe will contain CopyFlushMe afterwards.
- *  NoMe if non-NULL, we will use that buffer as value; KeepOriginal will abused as len.
- *  CopyFlushMe Buffer to faxmilate if KeepOriginal, or to move into CreateRelpaceMe if !KeepOriginal.
- *  CreateRelpaceMe If NULL, will be created, else Flushed and filled CopyFlushMe 
- *  KeepOriginal should CopyFlushMe remain intact? or may we Steal its buffer?
- * @returns the new stringbuffer
- */
+// Copy Constructor; CreateRelpaceMe will contain CopyFlushMe afterwards.
+// NoMe if non-NULL, we will use that buffer as value; KeepOriginal will abused as len.
+// CopyFlushMe Buffer to faxmilate if KeepOriginal, or to move into CreateRelpaceMe if !KeepOriginal.
+// CreateRelpaceMe If NULL, will be created, else Flushed and filled CopyFlushMe 
+// KeepOriginal should CopyFlushMe remain intact? or may we Steal its buffer?
+// returns the new stringbuffer
 void NewStrBufDupAppendFlush(StrBuf **CreateRelpaceMe, StrBuf *CopyFlushMe, const char *NoMe, int KeepOriginal) {
        StrBuf *NewBuf;
        
 void NewStrBufDupAppendFlush(StrBuf **CreateRelpaceMe, StrBuf *CopyFlushMe, const char *NoMe, int KeepOriginal) {
        StrBuf *NewBuf;
        
@@ -389,11 +374,9 @@ void NewStrBufDupAppendFlush(StrBuf **CreateRelpaceMe, StrBuf *CopyFlushMe, cons
                return;
        }
 
                return;
        }
 
-       /* 
-        * Randomly Chosen: bigger than 64 chars is cheaper to swap the buffers instead of copying.
-        * else *CreateRelpaceMe may use more memory than needed in a longer term, CopyFlushMe might
-        * be a big IO-Buffer...
-        */
+       // Randomly Chosen: bigger than 64 chars is cheaper to swap the buffers instead of copying.
+       // else *CreateRelpaceMe may use more memory than needed in a longer term, CopyFlushMe might
+       // be a big IO-Buffer...
        if (KeepOriginal || (StrLength(CopyFlushMe) < 256)) {
                if (*CreateRelpaceMe == NULL) {
                        *CreateRelpaceMe = NewBuf = NewStrBufPlain(NULL, CopyFlushMe->BufUsed);
        if (KeepOriginal || (StrLength(CopyFlushMe) < 256)) {
                if (*CreateRelpaceMe == NULL) {
                        *CreateRelpaceMe = NewBuf = NewStrBufPlain(NULL, CopyFlushMe->BufUsed);
@@ -422,14 +405,12 @@ void NewStrBufDupAppendFlush(StrBuf **CreateRelpaceMe, StrBuf *CopyFlushMe, cons
 }
 
 
 }
 
 
-/*
- *  create a new Buffer using an existing c-string
- * this function should also be used if you want to pre-suggest
- * the buffer size to allocate in conjunction with ptr == NULL
- *  ptr the c-string to copy; may be NULL to create a blank instance
- *  nChars How many chars should we copy; -1 if we should measure the length ourselves
- * @returns the new stringbuffer
- */
+// create a new Buffer using an existing c-string
+// this function should also be used if you want to pre-suggest
+// the buffer size to allocate in conjunction with ptr == NULL
+// ptr the c-string to copy; may be NULL to create a blank instance
+// nChars How many chars should we copy; -1 if we should measure the length ourselves
+// returns the new stringbuffer
 StrBuf *NewStrBufPlain(const char* ptr, int nChars) {
        StrBuf *NewBuf;
        size_t Siz = BaseStrBufSize;
 StrBuf *NewStrBufPlain(const char* ptr, int nChars) {
        StrBuf *NewBuf;
        size_t Siz = BaseStrBufSize;
@@ -475,13 +456,13 @@ StrBuf *NewStrBufPlain(const char* ptr, int nChars) {
 }
 
 
 }
 
 
-/*
- *  Set an existing buffer from a c-string
- *  Buf buffer to load
- *  ptr c-string to put into 
- *  nChars set to -1 if we should work 0-terminated
- * @returns the new length of the string
- */
+//
+//  Set an existing buffer from a c-string
+//  Buf buffer to load
+//  ptr c-string to put into 
+//  nChars set to -1 if we should work 0-terminated
+// @returns the new length of the string
+///
 int StrBufPlain(StrBuf *Buf, const char* ptr, int nChars) {
        size_t Siz;
        size_t CopySize;
 int StrBufPlain(StrBuf *Buf, const char* ptr, int nChars) {
        size_t Siz;
        size_t CopySize;
@@ -518,13 +499,12 @@ int StrBufPlain(StrBuf *Buf, const char* ptr, int nChars) {
 }
 
 
 }
 
 
-/*
- *  use strbuf as wrapper for a string constant for easy handling
- *  StringConstant a string to wrap
- *  SizeOfStrConstant should be sizeof(StringConstant)-1
- */
-StrBuf* _NewConstStrBuf(const char* StringConstant, size_t SizeOfStrConstant)
-{
+//
+//  use strbuf as wrapper for a string constant for easy handling
+//  StringConstant a string to wrap
+//  SizeOfStrConstant should be sizeof(StringConstant)-1
+///
+StrBuf *_NewConstStrBuf(const char* StringConstant, size_t SizeOfStrConstant) {
        StrBuf *NewBuf;
 
        NewBuf = (StrBuf*) malloc(sizeof(StrBuf));
        StrBuf *NewBuf;
 
        NewBuf = (StrBuf*) malloc(sizeof(StrBuf));
@@ -541,10 +521,10 @@ StrBuf* _NewConstStrBuf(const char* StringConstant, size_t SizeOfStrConstant)
 }
 
 
 }
 
 
-/*
- *  flush the content of a Buf; keep its struct
- *  buf Buffer to flush
- */
+//
+//  flush the content of a Buf; keep its struct
+//  buf Buffer to flush
+///
 int FlushStrBuf(StrBuf *buf) {
        if ((buf == NULL) || (buf->buf == NULL)) {
                return -1;
 int FlushStrBuf(StrBuf *buf) {
        if ((buf == NULL) || (buf->buf == NULL)) {
                return -1;
@@ -557,12 +537,11 @@ int FlushStrBuf(StrBuf *buf) {
        return 0;
 }
 
        return 0;
 }
 
-/*
- *  wipe the content of a Buf thoroughly (overwrite it -> expensive); keep its struct
- *  buf Buffer to wipe
- */
-int FLUSHStrBuf(StrBuf *buf)
-{
+//
+//  wipe the content of a Buf thoroughly (overwrite it -> expensive); keep its struct
+//  buf Buffer to wipe
+///
+int FLUSHStrBuf(StrBuf *buf) {
        if (buf == NULL)
                return -1;
        if (buf->ConstBuf)
        if (buf == NULL)
                return -1;
        if (buf->ConstBuf)
@@ -577,14 +556,13 @@ int FLUSHStrBuf(StrBuf *buf)
 #ifdef SIZE_DEBUG
 int hFreeDbglog = -1;
 #endif
 #ifdef SIZE_DEBUG
 int hFreeDbglog = -1;
 #endif
-/*
- *  Release a Buffer
- * Its a double pointer, so it can NULL your pointer
- * so fancy SIG11 appear instead of random results
- *  FreeMe Pointer Pointer to the buffer to free
- */
-void FreeStrBuf (StrBuf **FreeMe)
-{
+//
+//  Release a Buffer
+// Its a double pointer, so it can NULL your pointer
+// so fancy SIG11 appear instead of random results
+//  FreeMe Pointer Pointer to the buffer to free
+///
+void FreeStrBuf (StrBuf **FreeMe) {
        if (*FreeMe == NULL)
                return;
 
        if (*FreeMe == NULL)
                return;
 
@@ -596,14 +574,14 @@ void FreeStrBuf (StrBuf **FreeMe)
        *FreeMe = NULL;
 }
 
        *FreeMe = NULL;
 }
 
-/*
- *  flatten a Buffer to the Char * we return 
- * Its a double pointer, so it can NULL your pointer
- * so fancy SIG11 appear instead of random results
- * The Callee then owns the buffer and is responsible for freeing it.
- *  SmashMe Pointer Pointer to the buffer to release Buf from and free
- * @returns the pointer of the buffer; Callee owns the memory thereafter.
- */
+//
+//  flatten a Buffer to the Char * we return 
+// Its a double pointer, so it can NULL your pointer
+// so fancy SIG11 appear instead of random results
+// The Callee then owns the buffer and is responsible for freeing it.
+//  SmashMe Pointer Pointer to the buffer to release Buf from and free
+// @returns the pointer of the buffer; Callee owns the memory thereafter.
+///
 char *SmashStrBuf (StrBuf **SmashMe) {
        char *Ret;
 
 char *SmashStrBuf (StrBuf **SmashMe) {
        char *Ret;
 
@@ -619,11 +597,9 @@ char *SmashStrBuf (StrBuf **SmashMe) {
 }
 
 
 }
 
 
-/*
- *  Release the buffer
- * If you want put your StrBuf into a Hash, use this as Destructor.
- *  VFreeMe untyped pointer to a StrBuf. be shure to do the right thing [TM]
- */
+// Release the buffer
+// If you want put your StrBuf into a Hash, use this as Destructor.
+// VFreeMe untyped pointer to a StrBuf. be shure to do the right thing [TM]
 void HFreeStrBuf (void *VFreeMe) {
        StrBuf *FreeMe = (StrBuf*)VFreeMe;
        if (FreeMe == NULL)
 void HFreeStrBuf (void *VFreeMe) {
        StrBuf *FreeMe = (StrBuf*)VFreeMe;
        if (FreeMe == NULL)
@@ -637,11 +613,11 @@ void HFreeStrBuf (void *VFreeMe) {
 }
 
 
 }
 
 
-/*******************************************************************************
- *                      Simple string transformations                          *
- *******************************************************************************/
+//******************************************************************************
+//                      Simple string transformations                          *
+//******************************************************************************/
 
 
-//  Wrapper around atol
+// Wrapper around atol
 long StrTol(const StrBuf *Buf) {
        if (Buf == NULL)
                return 0;
 long StrTol(const StrBuf *Buf) {
        if (Buf == NULL)
                return 0;
@@ -652,7 +628,7 @@ long StrTol(const StrBuf *Buf) {
 }
 
 
 }
 
 
-//  Wrapper around atoi
+// Wrapper around atoi
 int StrToi(const StrBuf *Buf) {
        if (Buf == NULL)
                return 0;
 int StrToi(const StrBuf *Buf) {
        if (Buf == NULL)
                return 0;
@@ -921,13 +897,10 @@ int StrBufSub(StrBuf *dest, const StrBuf *Source, unsigned long Offset, size_t n
        return NCharsRemain;
 }
 
        return NCharsRemain;
 }
 
-/**
- *  Cut nChars from the start of the string
- *  Buf Buffer to modify
- *  nChars how many chars should be skipped?
- */
-void StrBufCutLeft(StrBuf *Buf, int nChars)
-{
+//  Cut nChars from the start of the string
+//  Buf Buffer to modify
+//  nChars how many chars should be skipped?
+void StrBufCutLeft(StrBuf *Buf, int nChars) {
        if ((Buf == NULL) || (Buf->BufUsed == 0)) return;
        if (nChars >= Buf->BufUsed) {
                FlushStrBuf(Buf);
        if ((Buf == NULL) || (Buf->BufUsed == 0)) return;
        if (nChars >= Buf->BufUsed) {
                FlushStrBuf(Buf);
@@ -938,13 +911,10 @@ void StrBufCutLeft(StrBuf *Buf, int nChars)
        Buf->buf[Buf->BufUsed] = '\0';
 }
 
        Buf->buf[Buf->BufUsed] = '\0';
 }
 
-/**
- *  Cut the trailing n Chars from the string
- *  Buf Buffer to modify
- *  nChars how many chars should be trunkated?
- */
-void StrBufCutRight(StrBuf *Buf, int nChars)
-{
+// Cut the trailing n Chars from the string
+// Buf Buffer to modify
+// nChars how many chars should be trunkated?
+void StrBufCutRight(StrBuf *Buf, int nChars) {
        if ((Buf == NULL) || (Buf->BufUsed == 0) || (Buf->buf == NULL))
                return;
 
        if ((Buf == NULL) || (Buf->BufUsed == 0) || (Buf->buf == NULL))
                return;
 
@@ -956,14 +926,12 @@ void StrBufCutRight(StrBuf *Buf, int nChars)
        Buf->buf[Buf->BufUsed] = '\0';
 }
 
        Buf->buf[Buf->BufUsed] = '\0';
 }
 
-/**
- *  Cut the string after n Chars
- *  Buf Buffer to modify
- *  AfternChars after how many chars should we trunkate the string?
- *  At if non-null and points inside of our string, cut it there.
- */
-void StrBufCutAt(StrBuf *Buf, int AfternChars, const char *At)
-{
+
+//  Cut the string after n Chars
+//  Buf Buffer to modify
+//  AfternChars after how many chars should we trunkate the string?
+//  At if non-null and points inside of our string, cut it there.
+void StrBufCutAt(StrBuf *Buf, int AfternChars, const char *At) {
        if ((Buf == NULL) || (Buf->BufUsed == 0)) return;
        if (At != NULL){
                AfternChars = At - Buf->buf;
        if ((Buf == NULL) || (Buf->BufUsed == 0)) return;
        if (At != NULL){
                AfternChars = At - Buf->buf;
@@ -976,12 +944,9 @@ void StrBufCutAt(StrBuf *Buf, int AfternChars, const char *At)
 }
 
 
 }
 
 
-/**
- *  Strip leading and trailing spaces from a string; with premeasured and adjusted length.
- *  Buf the string to modify
- */
-void StrBufTrim(StrBuf *Buf)
-{
+//  Strip leading and trailing spaces from a string; with premeasured and adjusted length.
+//  Buf the string to modify
+void StrBufTrim(StrBuf *Buf) {
        int delta = 0;
        if ((Buf == NULL) || (Buf->BufUsed == 0)) return;
 
        int delta = 0;
        if ((Buf == NULL) || (Buf->BufUsed == 0)) return;
 
@@ -997,12 +962,11 @@ void StrBufTrim(StrBuf *Buf)
        }
        if (delta > 0) StrBufCutLeft(Buf, delta);
 }
        }
        if (delta > 0) StrBufCutLeft(Buf, delta);
 }
-/**
- *  changes all spaces in the string  (tab, linefeed...) to Blank (0x20)
- *  Buf the string to modify
- */
-void StrBufSpaceToBlank(StrBuf *Buf)
-{
+
+
+//  changes all spaces in the string  (tab, linefeed...) to Blank (0x20)
+//  Buf the string to modify
+void StrBufSpaceToBlank(StrBuf *Buf) {
        char *pche, *pch;
 
        if ((Buf == NULL) || (Buf->BufUsed == 0)) return;
        char *pche, *pch;
 
        if ((Buf == NULL) || (Buf->BufUsed == 0)) return;
@@ -1017,8 +981,7 @@ void StrBufSpaceToBlank(StrBuf *Buf)
        }
 }
 
        }
 }
 
-void StrBufStripAllBut(StrBuf *Buf, char leftboundary, char rightboundary)
-{
+void StrBufStripAllBut(StrBuf *Buf, char leftboundary, char rightboundary) {
        const char *pLeft;
        const char *pRight;
 
        const char *pLeft;
        const char *pRight;
 
@@ -1038,12 +1001,9 @@ void StrBufStripAllBut(StrBuf *Buf, char leftboundary, char rightboundary)
 }
 
 
 }
 
 
-/**
- *  uppercase the contents of a buffer
- *  Buf the buffer to translate
- */
-void StrBufUpCase(StrBuf *Buf) 
-{
+//  uppercase the contents of a buffer
+//  Buf the buffer to translate
+void StrBufUpCase(StrBuf *Buf) {
        char *pch, *pche;
 
        if ((Buf == NULL) || (Buf->BufUsed == 0)) return;
        char *pch, *pche;
 
        if ((Buf == NULL) || (Buf->BufUsed == 0)) return;
@@ -1057,12 +1017,9 @@ void StrBufUpCase(StrBuf *Buf)
 }
 
 
 }
 
 
-/**
- *  lowercase the contents of a buffer
- *  Buf the buffer to translate
- */
-void StrBufLowerCase(StrBuf *Buf) 
-{
+//  lowercase the contents of a buffer
+//  Buf the buffer to translate
+void StrBufLowerCase(StrBuf *Buf) {
        char *pch, *pche;
 
        if ((Buf == NULL) || (Buf->BufUsed == 0)) return;
        char *pch, *pche;
 
        if ((Buf == NULL) || (Buf->BufUsed == 0)) return;
@@ -1076,19 +1033,19 @@ void StrBufLowerCase(StrBuf *Buf)
 }
 
 
 }
 
 
-/*******************************************************************************
- *           a tokenizer that kills, maims, and destroys                       *
- *******************************************************************************/
+//******************************************************************************
+//           a tokenizer that kills, maims, and destroys                       *
+//******************************************************************************/
 
 
-/**
- *  Replace a token at a given place with a given length by another token with given length
- *  Buf String where to work on
- *  where where inside of the Buf is the search-token
- *  HowLong How long is the token to be replaced
- *  Repl Token to insert at 'where'
- *  ReplLen Length of repl
- * @returns -1 if fail else length of resulting Buf
- */
+//*
+//  Replace a token at a given place with a given length by another token with given length
+//  Buf String where to work on
+//  where where inside of the Buf is the search-token
+//  HowLong How long is the token to be replaced
+//  Repl Token to insert at 'where'
+//  ReplLen Length of repl
+// @returns -1 if fail else length of resulting Buf
+///
 int StrBufReplaceToken(StrBuf *Buf, long where, long HowLong, const char *Repl, long ReplLen) {
 
        if ((Buf == NULL) || (where > Buf->BufUsed) || (where + HowLong > Buf->BufUsed)) {
 int StrBufReplaceToken(StrBuf *Buf, long where, long HowLong, const char *Repl, long ReplLen) {
 
        if ((Buf == NULL) || (where > Buf->BufUsed) || (where + HowLong > Buf->BufUsed)) {
@@ -1104,15 +1061,14 @@ int StrBufReplaceToken(StrBuf *Buf, long where, long HowLong, const char *Repl,
        memmove(Buf->buf + where + ReplLen, Buf->buf + where + HowLong, Buf->BufUsed - where - HowLong);
        memcpy(Buf->buf + where, Repl, ReplLen);
        Buf->BufUsed += ReplLen - HowLong;
        memmove(Buf->buf + where + ReplLen, Buf->buf + where + HowLong, Buf->BufUsed - where - HowLong);
        memcpy(Buf->buf + where, Repl, ReplLen);
        Buf->BufUsed += ReplLen - HowLong;
+       Buf->buf[Buf->BufUsed] = 0;
        return Buf->BufUsed;
 }
 
        return Buf->BufUsed;
 }
 
-/**
- *  Counts the numbmer of tokens in a buffer
- *  source String to count tokens in
- *  tok    Tokenizer char to count
- * @returns numbers of tokenizer chars found
- */
+//  Counts the numbmer of tokens in a buffer
+//  source String to count tokens in
+//  tok    Tokenizer char to count
+// @returns numbers of tokenizer chars found
 int StrBufNum_tokens(const StrBuf *source, char tok) {
        char *pch, *pche;
        long NTokens;
 int StrBufNum_tokens(const StrBuf *source, char tok) {
        char *pch, *pche;
        long NTokens;
@@ -1132,24 +1088,22 @@ int StrBufNum_tokens(const StrBuf *source, char tok) {
        return NTokens;
 }
 
        return NTokens;
 }
 
-/**
- *  a string tokenizer
- *  Source StringBuffer to read into
- *  parmnum n'th Parameter to remove
- *  separator tokenizer character
- * @returns -1 if not found, else length of token.
- */
-int StrBufRemove_token(StrBuf *Source, int parmnum, char separator)
-{
+
+//  a string tokenizer
+//  Source StringBuffer to read into
+//  parmnum n'th Parameter to remove
+//  separator tokenizer character
+// @returns -1 if not found, else length of token.
+int StrBufRemove_token(StrBuf *Source, int parmnum, char separator) {
        int ReducedBy;
        int ReducedBy;
-       char *d, *s, *end;              /* dest, source */
+       char *d, *s, *end;              // dest, source
        int count = 0;
 
        int count = 0;
 
-       /* Find desired eter */
+       // Find desired eter
        end = Source->buf + Source->BufUsed;
        d = Source->buf;
        while ((d <= end) && (count < parmnum)) {
        end = Source->buf + Source->BufUsed;
        d = Source->buf;
        while ((d <= end) && (count < parmnum)) {
-               /* End of string, bail! */
+               // End of string, bail!
                if (!*d) {
                        d = NULL;
                        break;
                if (!*d) {
                        d = NULL;
                        break;
@@ -1160,9 +1114,9 @@ int StrBufRemove_token(StrBuf *Source, int parmnum, char separator)
                d++;
        }
        if ((d == NULL) || (d >= end))
                d++;
        }
        if ((d == NULL) || (d >= end))
-               return 0;               /* @Parameter not found */
+               return 0;               // Parameter not found
 
 
-       /* Find next eter */
+       // Find next eter
        s = d;
        while ((s <= end) && (*s && *s != separator)) {
                s++;
        s = d;
        while ((s <= end) && (*s && *s != separator)) {
                s++;
@@ -1171,7 +1125,7 @@ int StrBufRemove_token(StrBuf *Source, int parmnum, char separator)
                s++;
        ReducedBy = d - s;
 
                s++;
        ReducedBy = d - s;
 
-       /* Hack and slash */
+       // Hack and slash
        if (s >= end) {
                return 0;
        }
        if (s >= end) {
                return 0;
        }
@@ -1188,17 +1142,16 @@ int StrBufRemove_token(StrBuf *Source, int parmnum, char separator)
                *--d = '\0';
                Source->BufUsed += ReducedBy;
        }
                *--d = '\0';
                Source->BufUsed += ReducedBy;
        }
-       /*
-       while (*s) {
-               *d++ = *s++;
-       }
-       *d = 0;
-       */
+
+       //while (*s) {
+               //*d++ = *s++;
+       //}
+       //*d = 0;
+
        return ReducedBy;
 }
 
        return ReducedBy;
 }
 
-int StrBufExtract_tokenFromStr(StrBuf *dest, const char *Source, long SourceLen, int parmnum, char separator)
-{
+int StrBufExtract_tokenFromStr(StrBuf *dest, const char *Source, long SourceLen, int parmnum, char separator) {
        const StrBuf Temp = {
                (char*)Source,
                SourceLen,
        const StrBuf Temp = {
                (char*)Source,
                SourceLen,
@@ -1215,19 +1168,16 @@ int StrBufExtract_tokenFromStr(StrBuf *dest, const char *Source, long SourceLen,
        return StrBufExtract_token(dest, &Temp, parmnum, separator);
 }
 
        return StrBufExtract_token(dest, &Temp, parmnum, separator);
 }
 
-/**
- *  a string tokenizer
- *  dest Destination StringBuffer
- *  Source StringBuffer to read into
- *  parmnum n'th Parameter to extract
- *  separator tokenizer character
- * @returns -1 if not found, else length of token.
- */
-int StrBufExtract_token(StrBuf *dest, const StrBuf *Source, int parmnum, char separator)
-{
-       const char *s, *e;              //* source * /
-       int len = 0;                    //* running total length of extracted string * /
-       int current_token = 0;          //* token currently being processed * /
+// a string tokenizer
+// dest Destination StringBuffer
+// Source StringBuffer to read into
+// parmnum n'th Parameter to extract
+// separator tokenizer character
+// returns -1 if not found, else length of token.
+int StrBufExtract_token(StrBuf *dest, const StrBuf *Source, int parmnum, char separator) {
+       const char *s, *e;              // source
+       int len = 0;                    // running total length of extracted string
+       int current_token = 0;          // token currently being processed
         
        if (dest != NULL) {
                dest->buf[0] = '\0';
         
        if (dest != NULL) {
                dest->buf[0] = '\0';
@@ -1278,18 +1228,12 @@ int StrBufExtract_token(StrBuf *dest, const StrBuf *Source, int parmnum, char se
 }
 
 
 }
 
 
-
-
-
-/**
- *  a string tokenizer to fetch an integer
- *  Source String containing tokens
- *  parmnum n'th Parameter to extract
- *  separator tokenizer character
- * @returns 0 if not found, else integer representation of the token
- */
-int StrBufExtract_int(const StrBuf* Source, int parmnum, char separator)
-{
+//  a string tokenizer to fetch an integer
+//  Source String containing tokens
+//  parmnum n'th Parameter to extract
+//  separator tokenizer character
+// @returns 0 if not found, else integer representation of the token
+int StrBufExtract_int(const StrBuf* Source, int parmnum, char separator) {
        StrBuf tmp;
        char buf[64];
        
        StrBuf tmp;
        char buf[64];
        
@@ -1304,15 +1248,13 @@ int StrBufExtract_int(const StrBuf* Source, int parmnum, char separator)
                return 0;
 }
 
                return 0;
 }
 
-/**
- *  a string tokenizer to fetch a long integer
- *  Source String containing tokens
- *  parmnum n'th Parameter to extract
- *  separator tokenizer character
- * @returns 0 if not found, else long integer representation of the token
- */
-long StrBufExtract_long(const StrBuf* Source, int parmnum, char separator)
-{
+
+// a string tokenizer to fetch a long integer
+// Source String containing tokens
+// parmnum n'th Parameter to extract
+// separator tokenizer character
+// returns 0 if not found, else long integer representation of the token
+long StrBufExtract_long(const StrBuf* Source, int parmnum, char separator) {
        StrBuf tmp;
        char buf[64];
        
        StrBuf tmp;
        char buf[64];
        
@@ -1328,15 +1270,12 @@ long StrBufExtract_long(const StrBuf* Source, int parmnum, char separator)
 }
 
 
 }
 
 
-/**
- *  a string tokenizer to fetch an unsigned long
- *  Source String containing tokens
- *  parmnum n'th Parameter to extract
- *  separator tokenizer character
- * @returns 0 if not found, else unsigned long representation of the token
- */
-unsigned long StrBufExtract_unsigned_long(const StrBuf* Source, int parmnum, char separator)
-{
+// a string tokenizer to fetch an unsigned long
+// Source String containing tokens
+// parmnum n'th Parameter to extract
+// separator tokenizer character
+// returns 0 if not found, else unsigned long representation of the token
+unsigned long StrBufExtract_unsigned_long(const StrBuf* Source, int parmnum, char separator) {
        StrBuf tmp;
        char buf[64];
        char *pnum;
        StrBuf tmp;
        char buf[64];
        char *pnum;
@@ -1357,14 +1296,11 @@ unsigned long StrBufExtract_unsigned_long(const StrBuf* Source, int parmnum, cha
 }
 
 
 }
 
 
-
-/**
- *  a string tokenizer; Bounds checker
- *  function to make shure whether StrBufExtract_NextToken and friends have reached the end of the string.
- *  Source our tokenbuffer
- *  pStart the token iterator pointer to inspect
- * @returns whether the revolving pointer is inside of the search range
- */
+// a string tokenizer; Bounds checker
+// function to make shure whether StrBufExtract_NextToken and friends have reached the end of the string.
+// Source our tokenbuffer
+// pStart the token iterator pointer to inspect
+// returns whether the revolving pointer is inside of the search range
 int StrBufHaveNextToken(const StrBuf *Source, const char **pStart) {
        if ((Source == NULL) || (*pStart == StrBufNOTNULL) || (Source->BufUsed == 0)) {
                return 0;
 int StrBufHaveNextToken(const StrBuf *Source, const char **pStart) {
        if ((Source == NULL) || (*pStart == StrBufNOTNULL) || (Source->BufUsed == 0)) {
                return 0;
@@ -1812,8 +1748,7 @@ void StrBufXMLEscAppend(StrBuf *OutBuf,
  *  PlainIn way in from plain old c strings
  *  PlainInLen way in from plain old c strings; maybe you've got binary data or know the length?
  */
  *  PlainIn way in from plain old c strings
  *  PlainInLen way in from plain old c strings; maybe you've got binary data or know the length?
  */
-void StrBufHexEscAppend(StrBuf *OutBuf, const StrBuf *In, const unsigned char *PlainIn, long PlainInLen)
-{
+void StrBufHexEscAppend(StrBuf *OutBuf, const StrBuf *In, const unsigned char *PlainIn, long PlainInLen) {
        const unsigned char *pch, *pche;
        char *pt, *pte;
        int len;
        const unsigned char *pch, *pche;
        char *pt, *pte;
        int len;
@@ -1913,8 +1848,7 @@ void StrBufHexescAppend(StrBuf *OutBuf, const StrBuf *In, const char *PlainIn) {
  *  nolinebreaks       if set to 1, linebreaks are removed from the string.
  *                      if set to 2, linebreaks are replaced by &ltbr/&gt
  */
  *  nolinebreaks       if set to 1, linebreaks are removed from the string.
  *                      if set to 2, linebreaks are replaced by &ltbr/&gt
  */
-long StrEscAppend(StrBuf *Target, const StrBuf *Source, const char *PlainIn, int nbsp, int nolinebreaks)
-{
+long StrEscAppend(StrBuf *Target, const StrBuf *Source, const char *PlainIn, int nbsp, int nolinebreaks) {
        const char *aptr, *eiptr;
        char *bptr, *eptr;
        long len;
        const char *aptr, *eiptr;
        char *bptr, *eptr;
        long len;
@@ -2023,8 +1957,7 @@ long StrEscAppend(StrBuf *Target, const StrBuf *Source, const char *PlainIn, int
  *  Source     source buffer; set to NULL if you just have a C-String
  *  PlainIn       Plain-C string to append; set to NULL if unused
  */
  *  Source     source buffer; set to NULL if you just have a C-String
  *  PlainIn       Plain-C string to append; set to NULL if unused
  */
-void StrMsgEscAppend(StrBuf *Target, const StrBuf *Source, const char *PlainIn)
-{
+void StrMsgEscAppend(StrBuf *Target, const StrBuf *Source, const char *PlainIn) {
        const char *aptr, *eiptr;
        char *tptr, *eptr;
        long len;
        const char *aptr, *eiptr;
        char *tptr, *eptr;
        long len;
@@ -2089,8 +2022,7 @@ void StrMsgEscAppend(StrBuf *Target, const StrBuf *Source, const char *PlainIn)
  *  Source     source buffer; set to NULL if you just have a C-String
  *  PlainIn       Plain-C string to append; set to NULL if unused
  */
  *  Source     source buffer; set to NULL if you just have a C-String
  *  PlainIn       Plain-C string to append; set to NULL if unused
  */
-void StrIcalEscAppend(StrBuf *Target, const StrBuf *Source, const char *PlainIn)
-{
+void StrIcalEscAppend(StrBuf *Target, const StrBuf *Source, const char *PlainIn) {
        const char *aptr, *eiptr;
        char *tptr, *eptr;
        long len;
        const char *aptr, *eiptr;
        char *tptr, *eptr;
        long len;
@@ -2159,8 +2091,7 @@ void StrIcalEscAppend(StrBuf *Target, const StrBuf *Source, const char *PlainIn)
  *  PlainIn       Plain-C string to append; set to NULL if unused
  * @returns size of result or -1
  */
  *  PlainIn       Plain-C string to append; set to NULL if unused
  * @returns size of result or -1
  */
-long StrECMAEscAppend(StrBuf *Target, const StrBuf *Source, const char *PlainIn)
-{
+long StrECMAEscAppend(StrBuf *Target, const StrBuf *Source, const char *PlainIn) {
        const char *aptr, *eiptr;
        char *bptr, *eptr;
        long len;
        const char *aptr, *eiptr;
        char *bptr, *eptr;
        long len;
@@ -2852,8 +2783,7 @@ int StrBufDecodeHex(StrBuf *Buf) {
  *  Mute char to put over invalid chars
  *  Buf Buffor to transform
  */
  *  Mute char to put over invalid chars
  *  Buf Buffor to transform
  */
-int StrBufSanitizeAscii(StrBuf *Buf, const char Mute)
-{
+int StrBufSanitizeAscii(StrBuf *Buf, const char Mute) {
        unsigned char *pch;
 
        if (Buf == NULL) return -1;
        unsigned char *pch;
 
        if (Buf == NULL) return -1;
@@ -2872,8 +2802,7 @@ int StrBufSanitizeAscii(StrBuf *Buf, const char Mute)
  *  Buf Buffer to translate
  *  StripBlanks Reduce several blanks to one?
  */
  *  Buf Buffer to translate
  *  StripBlanks Reduce several blanks to one?
  */
-long StrBufUnescape(StrBuf *Buf, int StripBlanks)
-{
+long StrBufUnescape(StrBuf *Buf, int StripBlanks) {
        int a, b;
        char hex[3];
        long len;
        int a, b;
        char hex[3];
        long len;
@@ -2925,8 +2854,7 @@ long StrBufUnescape(StrBuf *Buf, int StripBlanks)
  *     source          Source string to be encoded.
  * @returns     encoded length; -1 if non success.
  */
  *     source          Source string to be encoded.
  * @returns     encoded length; -1 if non success.
  */
-int StrBufRFC2047encode(StrBuf **target, const StrBuf *source)
-{
+int StrBufRFC2047encode(StrBuf **target, const StrBuf *source) {
        const char headerStr[] = "=?UTF-8?Q?";
        int need_to_encode = 0;
        int i = 0;
        const char headerStr[] = "=?UTF-8?Q?";
        int need_to_encode = 0;
        int i = 0;
@@ -3258,8 +3186,7 @@ void StrBufReplaceChars(StrBuf *buf, char search, char replace) {
  *  removes all \\r s from the string, or replaces them with \n if its not a combination of both.
  *  buf Buffer to modify
  */
  *  removes all \\r s from the string, or replaces them with \n if its not a combination of both.
  *  buf Buffer to modify
  */
-void StrBufToUnixLF(StrBuf *buf)
-{
+void StrBufToUnixLF(StrBuf *buf) {
        char *pche, *pchS, *pchT;
        if (buf == NULL)
                return;
        char *pche, *pchS, *pchT;
        if (buf == NULL)
                return;
@@ -3297,8 +3224,7 @@ void StrBufToUnixLF(StrBuf *buf)
  *  fromcode   Source encoding
  *  pic           anonimized pointer to iconv struct
  */
  *  fromcode   Source encoding
  *  pic           anonimized pointer to iconv struct
  */
-void  ctdl_iconv_open(const char *tocode, const char *fromcode, void *pic)
-{
+void  ctdl_iconv_open(const char *tocode, const char *fromcode, void *pic) {
 #ifdef HAVE_ICONV
        iconv_t ic = (iconv_t)(-1) ;
        ic = iconv_open(tocode, fromcode);
 #ifdef HAVE_ICONV
        iconv_t ic = (iconv_t)(-1) ;
        ic = iconv_open(tocode, fromcode);
@@ -3322,8 +3248,7 @@ void  ctdl_iconv_open(const char *tocode, const char *fromcode, void *pic)
  *  bptr where to start searching
  * @returns found position, NULL if none.
  */
  *  bptr where to start searching
  * @returns found position, NULL if none.
  */
-static inline const char *FindNextEnd (const StrBuf *Buf, const char *bptr)
-{
+static inline const char *FindNextEnd (const StrBuf *Buf, const char *bptr) {
        const char * end;
        /* Find the next ?Q? */
        if (Buf->BufUsed - (bptr - Buf->buf)  < 6)
        const char * end;
        /* Find the next ?Q? */
        if (Buf->BufUsed - (bptr - Buf->buf)  < 6)
@@ -3355,8 +3280,7 @@ static inline const char *FindNextEnd (const StrBuf *Buf, const char *bptr)
  *  TmpBuf To share a workbuffer over several iterations. prepare to have it filled with useless stuff afterwards.
  *  pic Pointer to the iconv-session Object
  */
  *  TmpBuf To share a workbuffer over several iterations. prepare to have it filled with useless stuff afterwards.
  *  pic Pointer to the iconv-session Object
  */
-void StrBufConvert(StrBuf *ConvertBuf, StrBuf *TmpBuf, void *pic)
-{
+void StrBufConvert(StrBuf *ConvertBuf, StrBuf *TmpBuf, void *pic) {
 #ifdef HAVE_ICONV
        long trycount = 0;
        size_t siz;
 #ifdef HAVE_ICONV
        long trycount = 0;
        size_t siz;
@@ -3506,8 +3430,7 @@ inline static void DecodeSegment(StrBuf *Target,
  *  FoundCharset overrides DefaultCharset if non-empty; If we find a charset inside of the string, 
  *        put it here for later use where no string might be known.
  */
  *  FoundCharset overrides DefaultCharset if non-empty; If we find a charset inside of the string, 
  *        put it here for later use where no string might be known.
  */
-void StrBuf_RFC822_to_Utf8(StrBuf *Target, const StrBuf *DecodeMe, const StrBuf* DefaultCharset, StrBuf *FoundCharset)
-{
+void StrBuf_RFC822_to_Utf8(StrBuf *Target, const StrBuf *DecodeMe, const StrBuf* DefaultCharset, StrBuf *FoundCharset) {
        StrBuf *ConvertBuf;
        StrBuf *ConvertBuf2;
        ConvertBuf = NewStrBufPlain(NULL, StrLength(DecodeMe));
        StrBuf *ConvertBuf;
        StrBuf *ConvertBuf2;
        ConvertBuf = NewStrBufPlain(NULL, StrLength(DecodeMe));
@@ -3685,8 +3608,7 @@ void StrBuf_RFC822_2_Utf8(StrBuf *Target,
  *  Char the character to examine
  * @returns width of utf8 chars in bytes; if the sequence is broken 0 is returned; 1 if its simply ASCII.
  */
  *  Char the character to examine
  * @returns width of utf8 chars in bytes; if the sequence is broken 0 is returned; 1 if its simply ASCII.
  */
-static inline int Ctdl_GetUtf8SequenceLength(const char *CharS, const char *CharE)
-{
+static inline int Ctdl_GetUtf8SequenceLength(const char *CharS, const char *CharE) {
        int n = 0;
         unsigned char test = (1<<7);
 
        int n = 0;
         unsigned char test = (1<<7);
 
@@ -3709,8 +3631,7 @@ static inline int Ctdl_GetUtf8SequenceLength(const char *CharS, const char *Char
  *  Char character to inspect
  * @returns yes or no
  */
  *  Char character to inspect
  * @returns yes or no
  */
-static inline int Ctdl_IsUtf8SequenceStart(const char Char)
-{
+static inline int Ctdl_IsUtf8SequenceStart(const char Char) {
 /** 11??.???? indicates an UTF8 Sequence. */
        return ((Char & 0xC0) == 0xC0);
 }
 /** 11??.???? indicates an UTF8 Sequence. */
        return ((Char & 0xC0) == 0xC0);
 }
@@ -3720,8 +3641,7 @@ static inline int Ctdl_IsUtf8SequenceStart(const char Char)
  *  Buf string to measure
  * @returns the number of glyphs in Buf
  */
  *  Buf string to measure
  * @returns the number of glyphs in Buf
  */
-long StrBuf_Utf8StrLen(StrBuf *Buf)
-{
+long StrBuf_Utf8StrLen(StrBuf *Buf) {
        int n = 0;
        int m = 0;
        char *aptr, *eptr;
        int n = 0;
        int m = 0;
        char *aptr, *eptr;
@@ -3750,8 +3670,7 @@ long StrBuf_Utf8StrLen(StrBuf *Buf)
  *  maxlen how long may the string become?
  * @returns current length of the string
  */
  *  maxlen how long may the string become?
  * @returns current length of the string
  */
-long StrBuf_Utf8StrCut(StrBuf *Buf, int maxlen)
-{
+long StrBuf_Utf8StrCut(StrBuf *Buf, int maxlen) {
        char *aptr, *eptr;
        int n = 0, m = 0;
 
        char *aptr, *eptr;
        int n = 0, m = 0;
 
@@ -3854,8 +3773,7 @@ int ZEXPORT compress_gzip(Bytef * dest,
  * Attention! If you feed this a Const String, you must maintain the uncompressed buffer yourself!
  *  Buf buffer whose content is to be gzipped
  */
  * Attention! If you feed this a Const String, you must maintain the uncompressed buffer yourself!
  *  Buf buffer whose content is to be gzipped
  */
-int CompressBuffer(StrBuf *Buf)
-{
+int CompressBuffer(StrBuf *Buf) {
 #ifdef HAVE_ZLIB
        char *compressed_data = NULL;
        size_t compressed_len, bufsize;
 #ifdef HAVE_ZLIB
        char *compressed_data = NULL;
        size_t compressed_len, bufsize;
@@ -3895,8 +3813,7 @@ int CompressBuffer(StrBuf *Buf)
  *           File I/O; Callbacks to libevent                                   *
  *******************************************************************************/
 
  *           File I/O; Callbacks to libevent                                   *
  *******************************************************************************/
 
-long StrBuf_read_one_chunk_callback (int fd, short event, IOBuffer *FB)
-{
+long StrBuf_read_one_chunk_callback (int fd, short event, IOBuffer *FB) {
        long bufremain = 0;
        int n;
        
        long bufremain = 0;
        int n;
        
@@ -4089,8 +4006,7 @@ eReadState StrBufChunkSipLine(StrBuf *LineBuf, IOBuffer *FB) {
  *  check whether the chunk-buffer has more data waiting or not.
  *  FB Chunk-Buffer to inspect
  */
  *  check whether the chunk-buffer has more data waiting or not.
  *  FB Chunk-Buffer to inspect
  */
-eReadState StrBufCheckBuffer(IOBuffer *FB)
-{
+eReadState StrBufCheckBuffer(IOBuffer *FB) {
        if (FB == NULL)
                return eReadFail;
        if (FB->Buf->BufUsed == 0)
        if (FB == NULL)
                return eReadFail;
        if (FB->Buf->BufUsed == 0)
@@ -4126,8 +4042,7 @@ long IOBufferStrLength(IOBuffer *FB) {
  *  Error strerror() on error 
  * @returns numbers of chars read
  */
  *  Error strerror() on error 
  * @returns numbers of chars read
  */
-int StrBufTCP_read_line(StrBuf *buf, int *fd, int append, const char **Error)
-{
+int StrBufTCP_read_line(StrBuf *buf, int *fd, int append, const char **Error) {
        int len, rlen, slen;
 
        if ((buf == NULL) || (buf->buf == NULL)) {
        int len, rlen, slen;
 
        if ((buf == NULL) || (buf->buf == NULL)) {
@@ -4451,8 +4366,7 @@ static const char *ErrRBLF_BLOBPreConditionFailed="StrBufReadBLOB: Wrong argumen
  *  Error strerror() on error 
  * @returns numbers of chars read
  */
  *  Error strerror() on error 
  * @returns numbers of chars read
  */
-int StrBufReadBLOB(StrBuf *Buf, int *fd, int append, long nBytes, const char **Error)
-{
+int StrBufReadBLOB(StrBuf *Buf, int *fd, int append, long nBytes, const char **Error) {
        int fdflags;
        int rlen;
        int nSuccessLess;
        int fdflags;
        int rlen;
        int nSuccessLess;
index a6905f8ba447c0116590e38b2430151b506b1611..83b33d238dab9943201aaf267f701e8ea5fc9268 100644 (file)
@@ -1 +1 @@
-999
+1000
index 66583979d982b97c8451fb1f9ae89e2652b33bde..576751e7fd9a2be1e348a02f705368e2ae11c7d1 100644 (file)
@@ -6,7 +6,7 @@
 #define        UDS                     "_UDS_"
 #define DEFAULT_HOST           "localhost"
 #define DEFAULT_PORT           "504"
 #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
 #define CLIENT_TYPE            0
 
 // commands we can send to the stty_ctdl() routine
index f5c53fc19b4142b2341712b4e60a1b1c75beb027..28fcbd68cbb028ffc8344db3a321d9bbfd5fb4b9 100644 (file)
@@ -35,8 +35,22 @@ function view_render_calendar() {
                        throw new Error(`${response.status} ${response.statusText}`);
                }
        })
                        throw new Error(`${response.status} ${response.statusText}`);
                }
        })
-       //.then(str => new window.DOMParser().parseFromString(str, "text/xml"))
-       .then(str => document.getElementById("ctdl-main").innerHTML = escapeHTML(str))
+       //.then(str => document.getElementById("ctdl-main").innerHTML = escapeHTML(str))
+       .then(str => new window.DOMParser().parseFromString(str, "text/xml"))
+       .then(xmlcal => {
+               document.getElementById("ctdl-main").innerHTML = "calendar items:<br>";
+               let root = xmlcal.documentElement;
+               let children = root.childNodes;
+               for (let i=0; i<children.length; ++i) {
+                       let child = children[i];
+                       if (child.nodeType == Node.ELEMENT_NODE) {
+                               var getetag_e = child.getElementsByTagName("DAV:href")[0];
+                               var getetag_s = getetag_e.textContent;
+                               document.getElementById("ctdl-main").innerHTML += getetag_s + "<br>";
+                       }
+               }
+
+       })
        .catch(error => {
                console.log(error);
                document.getElementById("ctdl-main").innerHTML = `<div class="ctdl-fatal-error">${error}</div>`;
        .catch(error => {
                console.log(error);
                document.getElementById("ctdl-main").innerHTML = `<div class="ctdl-fatal-error">${error}</div>`;
index 43c30e3c2e582ef2cfd92ce23d081bf4f5fd8286..2942bace5b352f77779524148f301cffdf6ee146 100755 (executable)
@@ -6,6 +6,8 @@
 # Remove any vestiges of pre-6.05 build environments
 rm -f .libs modules *.so *.lo *.la 2>/dev/null
 
 # Remove any vestiges of pre-6.05 build environments
 rm -f .libs modules *.so *.lo *.la 2>/dev/null
 
+grep '^#define CLIENT_VERSION' webcit.h | sed 's/[^0-9]*//g' >package-version.txt
+
 echo ... running aclocal ...
 aclocal
     
 echo ... running aclocal ...
 aclocal
     
@@ -23,7 +25,6 @@ autoheader
 echo ... mk_module_init.sh ...
 ./scripts/mk_module_init.sh
 
 echo ... mk_module_init.sh ...
 ./scripts/mk_module_init.sh
 
-
 echo
 echo This script has been tested with autoconf 2.53 and
 echo automake 1.5. Other versions may work, but I recommend the latest
 echo
 echo This script has been tested with autoconf 2.53 and
 echo automake 1.5. Other versions may work, but I recommend the latest
@@ -33,4 +34,3 @@ echo Also note that autoconf and automake should be configured
 echo with the same prefix.
 echo
 
 echo with the same prefix.
 echo
 
-grep '^#define CLIENT_VERSION' webcit.h | sed 's/[^0-9]*//g' >package-version.txt
index 2ca5796b27be7fbdcc2fa21d4f655d644c81bd49..7ffaf142b725358974f64827c141f4055b5aae74 100644 (file)
@@ -1,6 +1,6 @@
 dnl Process this file with autoconf to produce a configure script.
 dnl $Id$
 dnl Process this file with autoconf to produce a configure script.
 dnl $Id$
-AC_INIT([WebCit],[m4_esyscmd_s(grep CLIENT_VERSION webcit.h | sed 's/[^0-9]*//g')],[http://uncensored.citadel.org])
+AC_INIT([WebCit],[m4_esyscmd_s(cat package-version.txt)],[http://uncensored.citadel.org])
 
 AC_SUBST(PROG_SUBDIRS)
 AC_DEFINE(PROG_SUBDIRS, [], [Program dirs])
 
 AC_SUBST(PROG_SUBDIRS)
 AC_DEFINE(PROG_SUBDIRS, [], [Program dirs])
index 31eaa92dffc36f66c36ab63abe4601d624b5d1f6..5e34bcf2c3ff8150541cf181c8671784197b6155 100644 (file)
@@ -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 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 */
 #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 */