]> code.citadel.org Git - citadel.git/commitdiff
work on sixel support master
authorArt Cancro <ajc@citadel.org>
Fri, 31 May 2024 17:00:10 +0000 (17:00 +0000)
committerArt Cancro <ajc@citadel.org>
Fri, 31 May 2024 17:00:10 +0000 (17:00 +0000)
53 files changed:
citadel/configure
citadel/dumploadtest.sh
citadel/server/citadel_defs.h
citadel/server/citserver.c
citadel/server/context.c
citadel/server/context.h
citadel/server/control.c
citadel/server/internet_addressing.h
citadel/server/modules/autocompletion/serv_autocompletion.c
citadel/server/modules/bio/serv_bio.c
citadel/server/modules/crypto/serv_crypto.c
citadel/server/modules/crypto/serv_crypto.h
citadel/server/modules/ctdlproto/serv_ctdlproto.c
citadel/server/modules/expire/expire_policy.c
citadel/server/modules/expire/serv_expire.c
citadel/server/modules/imap/serv_imap.c
citadel/server/modules/inetcfg/serv_inetcfg.c
citadel/server/modules/listdeliver/serv_listdeliver.c
citadel/server/modules/listsub/serv_listsub.c
citadel/server/modules/nntp/serv_nntp.c
citadel/server/modules/pop3/serv_pop3.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_smtp.c
citadel/server/modules/smtp/serv_smtpclient.c
citadel/server/modules/smtp/smtp_util.h
citadel/server/modules/xmpp/serv_xmpp.c
citadel/server/modules/xmpp/xmpp_queue.c
citadel/server/msgbase.c
citadel/server/serv_extensions.c
citadel/server/server.h
citadel/server/sysdep.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/html_to_ascii.c
libcitadel/lib/libcitadel.h
libcitadel/lib/stringbuf.c
release_version.txt
textclient/citadel.c
textclient/citadel.rc
textclient/commands.c
textclient/messages.c
textclient/textclient.h
webcit-ng/static/js/view_calendar.js
webcit/bootstrap
webcit/configure.ac
webcit/static/t/room/edit/tab_share.html
webcit/webcit.h

index ad136e3ac1b2ed79061c1688d1e648c2efc7e43b..381a2544c14090f781953154938489a6bf6ab279 100755 (executable)
@@ -74,7 +74,6 @@ int main(int argc, char **argv) {
 }
 !
 $CC $CFLAGS $CPPFLAGS $tempcc -o $tempfile $LDFLAGS -lssl -lcrypto && $tempfile >/dev/null 2>&1 && {
 }
 !
 $CC $CFLAGS $CPPFLAGS $tempcc -o $tempfile $LDFLAGS -lssl -lcrypto && $tempfile >/dev/null 2>&1 && {
-       CFLAGS=${CFLAGS}' -DHAVE_OPENSSL'
        LDFLAGS=${LDFLAGS}' -lssl -lcrypto -lz'
 } || {
        echo Citadel Server requires OpenSSL which is not present.
        LDFLAGS=${LDFLAGS}' -lssl -lcrypto -lz'
 } || {
        echo Citadel Server requires OpenSSL which is not present.
index 7179f399180e853ec0f8a55db8c27197b979139c..835986ec79463d7cc3e2af079a5837991d8bfb18 100755 (executable)
@@ -1,20 +1,31 @@
 #!/bin/bash
 
 #!/bin/bash
 
-
 # This script dumps the database, deletes the database, loads the database, dumps it again...
 # ...and then compares the two dumps to see if we have full fidelity between them.
 #
 # Did you read that correctly?  Yes, it will DELETE your database.  So don't run this.
 
 # This script dumps the database, deletes the database, loads the database, dumps it again...
 # ...and then compares the two dumps to see if we have full fidelity between them.
 #
 # Did you read that correctly?  Yes, it will DELETE your database.  So don't run this.
 
-exit 0         # In fact, here's an exit statement you must delete before you can even run it.
+
+if [ "${YES_DELETE_MY_DATABASE}" != '' ] ; then
+       echo Ah, I see you have set YES_DELETE_MY_DATABASE to a non-empty value.
+       echo The dump and load test will now proceed.
+else
+       echo 'This script dumps the database, deletes the database, loads the database, dumps it again...'
+       echo '...and then compares the two dumps to see if we have full fidelity between them.'
+       echo 'Did you read that correctly?  Yes, it will DELETE your database.'
+       echo 'If this is really what you want, set the environment variable YES_DELETE_MY_DATABASE'
+       echo 'to a non-empty value, and run it again.'
+       exit 0
+fi
 
 ps ax | grep citserver | grep -v grep >/dev/null 2>/dev/null && {
 
 ps ax | grep citserver | grep -v grep >/dev/null 2>/dev/null && {
-       echo dont do this while the server is running
+       echo Do not do this while the server is running.
        exit 1
 }
 
 ./ctdldump -y >dump.dat
 first=$(md5sum dump.dat | awk ' { print $1 } ' )
        exit 1
 }
 
 ./ctdldump -y >dump.dat
 first=$(md5sum dump.dat | awk ' { print $1 } ' )
+
 rm -fv data/*
 ./ctdlload -y <dump.dat
 ./ctdldump -y >dump.dat
 rm -fv data/*
 ./ctdlload -y <dump.dat
 ./ctdldump -y >dump.dat
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 6415ce12abd290ea52e8d846fd4dc252701195a3..81709b5b59e81ee0056bb9301df2f149e1953e33 100644 (file)
@@ -318,10 +318,7 @@ CitContext *CloneContext(CitContext *CloneMe) {
        me->MigrateBuf = NULL;
        me->sMigrateBuf = NULL;
        me->redirect_buffer = NULL;
        me->MigrateBuf = NULL;
        me->sMigrateBuf = NULL;
        me->redirect_buffer = NULL;
-#ifdef HAVE_OPENSSL
        me->ssl = NULL;
        me->ssl = NULL;
-#endif
-
        me->download_fp = NULL;
        me->upload_fp = NULL;
        me->ma = NULL;
        me->download_fp = NULL;
        me->upload_fp = NULL;
        me->ma = NULL;
index d28dff7a7f59dadf481957b5bf65d9c8947c7245..1b207b64527b9a5349ea812ec26fe641f79c0370 100644 (file)
@@ -56,10 +56,8 @@ struct CitContext {
        // Redirect this session's output to a memory buffer?
        StrBuf *redirect_buffer;                // the buffer
        StrBuf *StatusMessage;
        // Redirect this session's output to a memory buffer?
        StrBuf *redirect_buffer;                // the buffer
        StrBuf *StatusMessage;
-#ifdef HAVE_OPENSSL
        SSL *ssl;
        int redirect_ssl;
        SSL *ssl;
        int redirect_ssl;
-#endif
 
        char curr_user[USERNAME_SIZE];  // name of current user
        int logged_in;          // logged in?
 
        char curr_user[USERNAME_SIZE];  // name of current user
        int logged_in;          // logged in?
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 413bf472b739920999baeff92ae7e3768206cecb..822e01aac6ec869e7fa6a478272071f5afb01ca9 100644 (file)
@@ -1,9 +1,6 @@
 // Autocompletion of email recipients, etc.
 // Autocompletion of email recipients, etc.
-//
 // Copyright (c) 1987-2023 by the citadel.org team
 // Copyright (c) 1987-2023 by the citadel.org team
-//
-// This program is open source software.  Use, duplication, or disclosure
-// is subject to the terms of the GNU General Public License version 3.
+// This program is open source software.  Use, duplication, or disclosure is subject to the GNU General Public License version 3.
 
 #include "../../ctdl_module.h"
 #include "serv_autocompletion.h"
 
 #include "../../ctdl_module.h"
 #include "serv_autocompletion.h"
index 8be817ae687dbcdf7f999b1ad7aee2290347747d..6ff861551dd0cbbba682c10023ea51ec6f30e894 100644 (file)
@@ -3,9 +3,7 @@
 //
 // Copyright (c) 1987-2022 by the citadel.org team
 //
 //
 // Copyright (c) 1987-2022 by the citadel.org team
 //
-// This program is open source software.  Use, duplication, or disclosure
-// is subject to the terms of the GNU General Public License, version 3.
-// The program is distributed without any warranty, expressed or implied.
+// This program is open source software.  Use, duplication, or disclosure is subject to the GNU General Public License, version 3.
 
 #include <sys/types.h>
 #include <sys/stat.h>
 
 #include <sys/types.h>
 #include <sys/stat.h>
index 5d41fa27c924817cb29cc58c517417ba7daf996c..541479af14c73f4848cc789b46fd27a80f30470d 100644 (file)
@@ -7,11 +7,9 @@
 #include <sys/types.h>
 #include "../../sysdep.h"
 
 #include <sys/types.h>
 #include "../../sysdep.h"
 
-#ifdef HAVE_OPENSSL
 #include <openssl/ssl.h>
 #include <openssl/err.h>
 #include <openssl/rand.h>
 #include <openssl/ssl.h>
 #include <openssl/err.h>
 #include <openssl/rand.h>
-#endif
 
 #include <time.h>
 
 
 #include <time.h>
 
@@ -32,8 +30,6 @@
 #include "../../config.h"
 #include "../../ctdl_module.h"
 
 #include "../../config.h"
 #include "../../ctdl_module.h"
 
-#ifdef HAVE_OPENSSL
-
 SSL_CTX *ssl_ctx = NULL;               // This SSL context is used for all sessions.
 char *ssl_cipher_list = CIT_CIPHERS;
 
 SSL_CTX *ssl_ctx = NULL;               // This SSL context is used for all sessions.
 char *ssl_cipher_list = CIT_CIPHERS;
 
@@ -609,5 +605,3 @@ void endtls(void) {
        CC->ssl = NULL;
        CC->redirect_ssl = 0;
 }
        CC->ssl = NULL;
        CC->redirect_ssl = 0;
 }
-
-#endif // HAVE_OPENSSL
index b5ee85d53bd970058333422d20ce73e017134254..4ef8033d531d393b5220858171f9a48198a9e2b8 100644 (file)
@@ -7,7 +7,6 @@
 // Which ciphers will be offered; see https://www.openssl.org/docs/manmaster/man1/ciphers.html
 #define CIT_CIPHERS    "ALL:RC4+RSA:+SSLv2:+TLSv1:!MD5:@STRENGTH"
 
 // Which ciphers will be offered; see https://www.openssl.org/docs/manmaster/man1/ciphers.html
 #define CIT_CIPHERS    "ALL:RC4+RSA:+SSLv2:+TLSv1:!MD5:@STRENGTH"
 
-#ifdef HAVE_OPENSSL
 #define OPENSSL_NO_KRB5                /* work around redhat b0rken ssl headers */
 void init_ssl(void);
 void client_write_ssl (const char *buf, int nbytes);
 #define OPENSSL_NO_KRB5                /* work around redhat b0rken ssl headers */
 void init_ssl(void);
 void client_write_ssl (const char *buf, int nbytes);
@@ -19,5 +18,3 @@ void cmd_gtls(char *params);
 void endtls(void);
 void CtdlStartTLS(char *ok_response, char *nosup_response, char *error_response);
 extern SSL_CTX *ssl_ctx;  
 void endtls(void);
 void CtdlStartTLS(char *ok_response, char *nosup_response, char *error_response);
 extern SSL_CTX *ssl_ctx;  
-
-#endif
index ae267b596fc5645f1a662b159c87066f32a803c3..d5e7ffe61b0c1bf8a634a9d2acec5fe7f2207f43 100644 (file)
@@ -1,72 +1,56 @@
-/* 
- * Citadel protocol main dispatcher
- *
- * Copyright (c) 1987-2017 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
+// Citadel protocol main dispatcher
+// Copyright (c) 1987-2024 by the citadel.org team
+// This program is open source software.  Use, duplication, or disclosure are subject to the GNU General Public License v3.
 
 #include <stdio.h>
 #include <libcitadel.h>
 
 #include <stdio.h>
 #include <libcitadel.h>
-
 #include "../../citserver.h"
 #include "../../ctdl_module.h"
 #include "../../config.h"
 #include "../../citserver.h"
 #include "../../ctdl_module.h"
 #include "../../config.h"
-/*
- * This loop recognizes all server commands.
- */
+
+// This loop recognizes all server commands.
 void do_command_loop(void) {
 void do_command_loop(void) {
-       struct CitContext *CCC = CC;
        char cmdbuf[SIZ];
        
        char cmdbuf[SIZ];
        
-       time(&CCC->lastcmd);
-       memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
+       time(&CC->lastcmd);
+       memset(cmdbuf, 0, sizeof cmdbuf); // Clear it, just in case
        if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
                syslog(LOG_INFO, "Citadel client disconnected: ending session.");
        if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
                syslog(LOG_INFO, "Citadel client disconnected: ending session.");
-               CCC->kill_me = KILLME_CLIENT_DISCONNECTED;
+               CC->kill_me = KILLME_CLIENT_DISCONNECTED;
                return;
        }
 
                return;
        }
 
-       /* Log the server command, but don't show passwords... */
-       if ( (strncasecmp(cmdbuf, "PASS", 4)) && (strncasecmp(cmdbuf, "SETP", 4)) ) {
-               syslog(LOG_DEBUG, "[%s(%ld)] %s",
-                       CCC->curr_user, CCC->user.usernum, cmdbuf
-               );
+       // Log the server command, but don't show passwords...
+       if (    (strncasecmp(cmdbuf, "PASS", 4))
+               && (strncasecmp(cmdbuf, "SETP", 4))
+       ) {
+               syslog(LOG_DEBUG, "[%s(%ld)] %s", CC->curr_user, CC->user.usernum, cmdbuf);
        }
        else {
        }
        else {
-               syslog(LOG_DEBUG, "[%s(%ld)] <password command hidden from log>",
-                           CCC->curr_user, CCC->user.usernum
-               );
+               syslog(LOG_DEBUG, "[%s(%ld)] <password command hidden from log>", CC->curr_user, CC->user.usernum);
        }
 
        buffer_output();
 
        }
 
        buffer_output();
 
-       /*
-        * Let other clients see the last command we executed, and
-        * update the idle time, but not NOOP, QNOP, PEXP, GEXP, RWHO, or TIME.
-        */
-       if ( (strncasecmp(cmdbuf, "NOOP", 4))
-          && (strncasecmp(cmdbuf, "QNOP", 4))
-          && (strncasecmp(cmdbuf, "PEXP", 4))
-          && (strncasecmp(cmdbuf, "GEXP", 4))
-          && (strncasecmp(cmdbuf, "RWHO", 4))
-          && (strncasecmp(cmdbuf, "TIME", 4)) ) {
-               strcpy(CCC->lastcmdname, "    ");
-               safestrncpy(CCC->lastcmdname, cmdbuf, sizeof(CCC->lastcmdname));
-               time(&CCC->lastidle);
+       // Let other clients see the last command we executed, and
+       // update the idle time, but not NOOP, QNOP, PEXP, GEXP, RWHO, or TIME.
+       if (    (strncasecmp(cmdbuf, "NOOP", 4))
+               && (strncasecmp(cmdbuf, "QNOP", 4))
+               && (strncasecmp(cmdbuf, "PEXP", 4))
+               && (strncasecmp(cmdbuf, "GEXP", 4))
+               && (strncasecmp(cmdbuf, "RWHO", 4))
+               && (strncasecmp(cmdbuf, "TIME", 4))
+       ) {
+               strcpy(CC->lastcmdname, "    ");
+               safestrncpy(CC->lastcmdname, cmdbuf, sizeof(CC->lastcmdname));
+               time(&CC->lastidle);
        }
        
        }
        
-       if ((strncasecmp(cmdbuf, "ENT0", 4))
-          && (strncasecmp(cmdbuf, "MESG", 4))
-          && (strncasecmp(cmdbuf, "MSGS", 4)))
-       {
-          CCC->cs_flags &= ~CS_POSTING;
+       if (    (strncasecmp(cmdbuf, "ENT0", 4))
+               && (strncasecmp(cmdbuf, "MESG", 4))
+               && (strncasecmp(cmdbuf, "MSGS", 4))
+       {
+               CC->cs_flags &= ~CS_POSTING;
        }
                   
        if (!DLoader_Exec_Cmd(cmdbuf)) {
        }
                   
        if (!DLoader_Exec_Cmd(cmdbuf)) {
@@ -75,7 +59,7 @@ void do_command_loop(void) {
 
        unbuffer_output();
 
 
        unbuffer_output();
 
-       /* Run any after-each-command routines registered by modules */
+       // Run any after-each-command routines registered by modules
        PerformSessionHooks(EVT_CMD);
 }
 
        PerformSessionHooks(EVT_CMD);
 }
 
index 32db98ad2ccc62b5b4d4849b59fc19afa122c5a6..ce302a6b310dd83265c36a1957ebbc2418848f40 100644 (file)
@@ -1,15 +1,6 @@
-/* 
- * Functions which manage expire policy for rooms
- * Copyright (c) 1987-2015 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
+// Functions which manage expire policy for rooms
+// Copyright (c) 1987-2024 by citadel.org (Art Cancro et al.)
+// This program is open source software.  Use, duplication, or disclosure is subject to the GNU General Public License, version 3.
 
 #include "../../sysdep.h"
 #include <stdlib.h>
 
 #include "../../sysdep.h"
 #include <stdlib.h>
@@ -17,7 +8,6 @@
 #include <stdio.h>
 #include <sys/stat.h>
 #include <string.h>
 #include <stdio.h>
 #include <sys/stat.h>
 #include <string.h>
-
 #include <time.h>
 #include <limits.h>
 #include <libcitadel.h>
 #include <time.h>
 #include <limits.h>
 #include <libcitadel.h>
 #include "../../ctdl_module.h"
 #include "../../user_ops.h"
 
 #include "../../ctdl_module.h"
 #include "../../user_ops.h"
 
-/*
- * Retrieve the applicable expire policy for a specific room
- */
+// Retrieve the applicable expire policy for a specific room
 void GetExpirePolicy(struct ExpirePolicy *epbuf, struct ctdlroom *qrbuf) {
        struct floor *fl;
 
 void GetExpirePolicy(struct ExpirePolicy *epbuf, struct ctdlroom *qrbuf) {
        struct floor *fl;
 
-       /* If the room has its own policy, return it */ 
+       // If the room has its own policy, return it 
        if (qrbuf->QRep.expire_mode != 0) {
                memcpy(epbuf, &qrbuf->QRep, sizeof(struct ExpirePolicy));
                return;
        }
 
        if (qrbuf->QRep.expire_mode != 0) {
                memcpy(epbuf, &qrbuf->QRep, sizeof(struct ExpirePolicy));
                return;
        }
 
-       /* (non-mailbox rooms)
-        * If the floor has its own policy, return it
-        */
+       // (non-mailbox rooms)
+       // If the floor has its own policy, return it
        if ( (qrbuf->QRflags & QR_MAILBOX) == 0) {
                fl = CtdlGetCachedFloor(qrbuf->QRfloor);
                if (fl->f_ep.expire_mode != 0) {
        if ( (qrbuf->QRflags & QR_MAILBOX) == 0) {
                fl = CtdlGetCachedFloor(qrbuf->QRfloor);
                if (fl->f_ep.expire_mode != 0) {
@@ -56,9 +43,8 @@ void GetExpirePolicy(struct ExpirePolicy *epbuf, struct ctdlroom *qrbuf) {
                }
        }
 
                }
        }
 
-       /* (Mailbox rooms)
-        * If there is a default policy for mailbox rooms, return it
-        */
+       // (Mailbox rooms)
+       // If there is a default policy for mailbox rooms, return it
        if (qrbuf->QRflags & QR_MAILBOX) {
                if (CtdlGetConfigInt("c_mbxep_mode") != 0) {
                        epbuf->expire_mode = CtdlGetConfigInt("c_mbxep_mode");
        if (qrbuf->QRflags & QR_MAILBOX) {
                if (CtdlGetConfigInt("c_mbxep_mode") != 0) {
                        epbuf->expire_mode = CtdlGetConfigInt("c_mbxep_mode");
@@ -67,15 +53,13 @@ void GetExpirePolicy(struct ExpirePolicy *epbuf, struct ctdlroom *qrbuf) {
                }
        }
 
                }
        }
 
-       /* Otherwise, fall back on the system default */
+       // Otherwise, fall back on the system default
        epbuf->expire_mode = CtdlGetConfigInt("c_ep_mode");
        epbuf->expire_value = CtdlGetConfigInt("c_ep_value");
 }
 
 
        epbuf->expire_mode = CtdlGetConfigInt("c_ep_mode");
        epbuf->expire_value = CtdlGetConfigInt("c_ep_value");
 }
 
 
-/*
- * Get Policy EXpire
- */
+// Get Policy EXpire
 void cmd_gpex(char *argbuf) {
        struct ExpirePolicy exp;
        struct floor *fl;
 void cmd_gpex(char *argbuf) {
        struct ExpirePolicy exp;
        struct floor *fl;
@@ -107,9 +91,7 @@ void cmd_gpex(char *argbuf) {
 }
 
 
 }
 
 
-/*
- * Set Policy EXpire
- */
+// Set Policy EXpire
 void cmd_spex(char *argbuf) {
        struct ExpirePolicy exp;
        struct floor flbuf;
 void cmd_spex(char *argbuf) {
        struct ExpirePolicy exp;
        struct floor flbuf;
@@ -125,8 +107,7 @@ void cmd_spex(char *argbuf) {
                return;
        }
 
                return;
        }
 
-       if ((!strcasecmp(which, strof(roompolicy))) || (!strcasecmp(which, "room")))
-       {
+       if ((!strcasecmp(which, strof(roompolicy))) || (!strcasecmp(which, "room"))) {
                if (!is_room_aide()) {
                        cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
                        return;
                if (!is_room_aide()) {
                        cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
                        return;
@@ -148,8 +129,7 @@ void cmd_spex(char *argbuf) {
                return;
        }
 
                return;
        }
 
-       if ((!strcasecmp(which, strof(floorpolicy))) || (!strcasecmp(which, "floor")))
-       {
+       if ((!strcasecmp(which, strof(floorpolicy))) || (!strcasecmp(which, "floor"))) {
                lgetfloor(&flbuf, CC->room.QRfloor);
                memcpy(&flbuf.f_ep, &exp, sizeof(struct ExpirePolicy));
                lputfloor(&flbuf, CC->room.QRfloor);
                lgetfloor(&flbuf, CC->room.QRfloor);
                memcpy(&flbuf.f_ep, &exp, sizeof(struct ExpirePolicy));
                lputfloor(&flbuf, CC->room.QRfloor);
@@ -157,16 +137,14 @@ void cmd_spex(char *argbuf) {
                return;
        }
 
                return;
        }
 
-       else if ((!strcasecmp(which, strof(mailboxespolicy))) || (!strcasecmp(which, "mailboxes")))
-       {
+       else if ((!strcasecmp(which, strof(mailboxespolicy))) || (!strcasecmp(which, "mailboxes"))) {
                CtdlSetConfigInt("c_mbxep_mode", exp.expire_mode);
                CtdlSetConfigInt("c_mbxep_value", exp.expire_value);
                cprintf("%d Default expire policy for mailboxes set.\n", CIT_OK);
                return;
        }
 
                CtdlSetConfigInt("c_mbxep_mode", exp.expire_mode);
                CtdlSetConfigInt("c_mbxep_value", exp.expire_value);
                cprintf("%d Default expire policy for mailboxes set.\n", CIT_OK);
                return;
        }
 
-       else if ((!strcasecmp(which, strof(sitepolicy))) || (!strcasecmp(which, "site")))
-       {
+       else if ((!strcasecmp(which, strof(sitepolicy))) || (!strcasecmp(which, "site"))) {
                if (exp.expire_mode == EXPIRE_NEXTLEVEL) {
                        cprintf("%d Invalid policy (no higher level)\n", ERROR + ILLEGAL_VALUE);
                        return;
                if (exp.expire_mode == EXPIRE_NEXTLEVEL) {
                        cprintf("%d Invalid policy (no higher level)\n", ERROR + ILLEGAL_VALUE);
                        return;
index fe4b60b4a20375610c7d1b60954bae7e4dce4221..63b8fc19173bc8fefcca5db2fabaefaa8104a281 100644 (file)
@@ -2,10 +2,9 @@
 //
 // You might also see this module affectionately referred to as TDAP (The Dreaded Auto-Purger).
 //
 //
 // You might also see this module affectionately referred to as TDAP (The Dreaded Auto-Purger).
 //
-// Copyright (c) 1988-2023 by citadel.org (Art Cancro, Wilifried Goesgens, and others)
+// Copyright (c) 1988-2024 by citadel.org (Art Cancro et al.)
 //
 //
-// This program is open source software.  Use, duplication, or disclosure
-// is subject to the terms of the GNU General Public License, version 3.
+// This program is open source software.  Use, duplication, or disclosure is subject to the GNU General Public License, version 3.
 
 
 #include "../../sysdep.h"
 
 
 #include "../../sysdep.h"
index d55971bb5f76de414b88cd4122b3bf9ad3ea2d8a..b9d2a41af95b0efdaef904e38f40b740718a4e32 100644 (file)
@@ -429,9 +429,7 @@ void imap_cleanup_function(void) {
 void imap_output_capability_string(void) {
        IAPuts("CAPABILITY IMAP4REV1 NAMESPACE ID AUTH=PLAIN AUTH=LOGIN UIDPLUS");
 
 void imap_output_capability_string(void) {
        IAPuts("CAPABILITY IMAP4REV1 NAMESPACE ID AUTH=PLAIN AUTH=LOGIN UIDPLUS");
 
-#ifdef HAVE_OPENSSL
        if (!CC->redirect_ssl) IAPuts(" STARTTLS");
        if (!CC->redirect_ssl) IAPuts(" STARTTLS");
-#endif
 
 #ifndef DISABLE_IMAP_ACL
        IAPuts(" ACL");
 
 #ifndef DISABLE_IMAP_ACL
        IAPuts(" ACL");
@@ -516,9 +514,7 @@ void imap_greeting(void) {
  */
 void imaps_greeting(void) {
        CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
  */
 void imaps_greeting(void) {
        CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
-#ifdef HAVE_OPENSSL
        if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;          /* kill session if no crypto */
        if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;          /* kill session if no crypto */
-#endif
        imap_greeting();
 }
 
        imap_greeting();
 }
 
@@ -1544,9 +1540,7 @@ char *ctdl_module_init_imap(void) {
        RegisterImapCMD("LOGIN", "", imap_login, I_FLAG_NONE);
        RegisterImapCMD("AUTHENTICATE", "", imap_authenticate, I_FLAG_NONE);
        RegisterImapCMD("CAPABILITY", "", imap_capability, I_FLAG_NONE);
        RegisterImapCMD("LOGIN", "", imap_login, I_FLAG_NONE);
        RegisterImapCMD("AUTHENTICATE", "", imap_authenticate, I_FLAG_NONE);
        RegisterImapCMD("CAPABILITY", "", imap_capability, I_FLAG_NONE);
-#ifdef HAVE_OPENSSL
        RegisterImapCMD("STARTTLS", "", imap_starttls, I_FLAG_NONE);
        RegisterImapCMD("STARTTLS", "", imap_starttls, I_FLAG_NONE);
-#endif
 
        /* The commans below require a logged-in state */
        RegisterImapCMD("SELECT", "", imap_select, I_FLAG_LOGGED_IN);
 
        /* The commans below require a logged-in state */
        RegisterImapCMD("SELECT", "", imap_select, I_FLAG_LOGGED_IN);
@@ -1584,9 +1578,7 @@ char *ctdl_module_init_imap(void) {
 
        if (!threading) {
                CtdlRegisterServiceHook(CtdlGetConfigInt("c_imap_port"), NULL, imap_greeting, imap_command_loop, NULL, CitadelServiceIMAP);
 
        if (!threading) {
                CtdlRegisterServiceHook(CtdlGetConfigInt("c_imap_port"), NULL, imap_greeting, imap_command_loop, NULL, CitadelServiceIMAP);
-#ifdef HAVE_OPENSSL
                CtdlRegisterServiceHook(CtdlGetConfigInt("c_imaps_port"), NULL, imaps_greeting, imap_command_loop, NULL, CitadelServiceIMAPS);
                CtdlRegisterServiceHook(CtdlGetConfigInt("c_imaps_port"), NULL, imaps_greeting, imap_command_loop, NULL, CitadelServiceIMAPS);
-#endif
                CtdlRegisterSessionHook(imap_cleanup_function, EVT_STOP, PRIO_STOP + 30);
        }
        
                CtdlRegisterSessionHook(imap_cleanup_function, EVT_STOP, PRIO_STOP + 30);
        }
        
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..a6cb74ad40da811b5bbf406b5e674930e070abb1 100644 (file)
@@ -1,14 +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
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 3.
-//  
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
+// Copyright (c) 2002-2024 by the citadel.org team (Art Cancro et al.)
+// This program is open source software.  Use, duplication, or disclosure are subject to the GNU General Public License v3.
 
 #include "../../sysdep.h"
 #include <stdlib.h>
 
 #include "../../sysdep.h"
 #include <stdlib.h>
@@ -40,7 +32,6 @@
 
 int doing_listdeliver = 0;
 
 
 int doing_listdeliver = 0;
 
-
 // data passed back and forth between listdeliver_do_msg() and listdeliver_sweep_room()
 struct lddata {
        long msgnum;            // number of most recent message processed
 // data passed back and forth between listdeliver_do_msg() and listdeliver_sweep_room()
 struct lddata {
        long msgnum;            // number of most recent message processed
@@ -48,7 +39,6 @@ struct lddata {
 };
 
 
 };
 
 
-
 void listdeliver_do_msg(long msgnum, void *userdata) {
        struct lddata *ld = (struct lddata *) userdata;
        if (!ld) return;
 void listdeliver_do_msg(long msgnum, void *userdata) {
        struct lddata *ld = (struct lddata *) userdata;
        if (!ld) return;
@@ -63,6 +53,8 @@ void listdeliver_do_msg(long msgnum, void *userdata) {
        struct CtdlMessage *TheMessage = CtdlFetchMessage(msgnum, 1);
        if (!TheMessage) return;
 
        struct CtdlMessage *TheMessage = CtdlFetchMessage(msgnum, 1);
        if (!TheMessage) return;
 
+       // FIXME add the list unsubscribe instructions directly to the message text.  Do it right here.
+
        // If the subject line does not contain the name of the room, add it now.
        if (!bmstrcasestr(TheMessage->cm_fields[eMsgSubject], CC->room.QRname)) {
                snprintf(buf, sizeof buf, "[%s] %s", CC->room.QRname, TheMessage->cm_fields[eMsgSubject]);
        // If the subject line does not contain the name of the room, add it now.
        if (!bmstrcasestr(TheMessage->cm_fields[eMsgSubject], CC->room.QRname)) {
                snprintf(buf, sizeof buf, "[%s] %s", CC->room.QRname, TheMessage->cm_fields[eMsgSubject]);
@@ -78,6 +70,13 @@ void listdeliver_do_msg(long msgnum, void *userdata) {
        CM_SetField(TheMessage, erFc822Addr, buf);
        CM_SetField(TheMessage, eReplyTo, buf);
 
        CM_SetField(TheMessage, erFc822Addr, buf);
        CM_SetField(TheMessage, eReplyTo, buf);
 
+       // To: likewise needs to have something in it, definitely not the name of an actual mailing list member.
+       // Let's use the address and name of the room.
+       strcat(buf, " (");
+       strcat(buf, CC->room.QRname);
+       strcat(buf, " )");
+       CM_SetField(TheMessage, eRecipient, buf);
+
        // With that out of the way, let's figure out who this message needs to be sent to.
        char *recipients = malloc(strlen(ld->netconf));
        if (recipients) {
        // With that out of the way, let's figure out who this message needs to be sent to.
        char *recipients = malloc(strlen(ld->netconf));
        if (recipients) {
@@ -245,5 +244,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");
index 4c581db632432c11313da844022969afb446807d..231b694cddb1c8a310767d4794e1219669169ee8 100644 (file)
@@ -175,9 +175,7 @@ void nntp_greeting(void) {
 // NNTPS is just like NNTP, except it goes crypto right away.
 void nntps_greeting(void) {
        CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
 // NNTPS is just like NNTP, except it goes crypto right away.
 void nntps_greeting(void) {
        CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
-#ifdef HAVE_OPENSSL
        if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;          // kill session if no crypto
        if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;          // kill session if no crypto
-#endif
        nntp_greeting();
 }
 
        nntp_greeting();
 }
 
@@ -204,9 +202,7 @@ void nntp_capabilities(void) {
        cprintf("MODE-READER\r\n");
        cprintf("LIST ACTIVE NEWSGROUPS\r\n");
        cprintf("OVER\r\n");
        cprintf("MODE-READER\r\n");
        cprintf("LIST ACTIVE NEWSGROUPS\r\n");
        cprintf("OVER\r\n");
-#ifdef HAVE_OPENSSL
        cprintf("STARTTLS\r\n");
        cprintf("STARTTLS\r\n");
-#endif
        if (!CC->logged_in) {
                cprintf("AUTHINFO USER\r\n");
        }
        if (!CC->logged_in) {
                cprintf("AUTHINFO USER\r\n");
        }
@@ -1070,14 +1066,12 @@ char *ctdl_module_init_nntp(void) {
                                        NULL, 
                                        CitadelServiceNNTP);
 
                                        NULL, 
                                        CitadelServiceNNTP);
 
-#ifdef HAVE_OPENSSL
                CtdlRegisterServiceHook(CtdlGetConfigInt("c_nntps_port"),
                                        NULL,
                                        nntps_greeting,
                                        nntp_command_loop,
                                        NULL,
                                        CitadelServiceNNTPS);
                CtdlRegisterServiceHook(CtdlGetConfigInt("c_nntps_port"),
                                        NULL,
                                        nntps_greeting,
                                        nntp_command_loop,
                                        NULL,
                                        CitadelServiceNNTPS);
-#endif
 
                CtdlRegisterSessionHook(nntp_cleanup_function, EVT_STOP, PRIO_STOP + 250);
        }
 
                CtdlRegisterSessionHook(nntp_cleanup_function, EVT_STOP, PRIO_STOP + 250);
        }
index 322f2d826038834c47f55d21ef714339a75c98e9..d624cfe9e07ebf749efb40e488e42b3f2e04f84a 100644 (file)
 #include "../../ctdl_module.h"
 
 
 #include "../../ctdl_module.h"
 
 
-// This cleanup function blows away the temporary memory and files used by
-// the POP3 server.
+// This cleanup function blows away the temporary memory and files used by the POP3 server.
 void pop3_cleanup_function(void) {
 void pop3_cleanup_function(void) {
-       /* Don't do this stuff if this is not a POP3 session! */
+       // Don't do this stuff if this is not a POP3 session!
        if (CC->h_command_function != pop3_command_loop) return;
 
        struct citpop3 *pop3 = ((struct citpop3 *)CC->session_specific_data);
        if (CC->h_command_function != pop3_command_loop) return;
 
        struct citpop3 *pop3 = ((struct citpop3 *)CC->session_specific_data);
@@ -78,13 +77,10 @@ void pop3_greeting(void) {
 void pop3s_greeting(void) {
        CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
 
 void pop3s_greeting(void) {
        CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
 
-/* kill session if no crypto */
-#ifdef HAVE_OPENSSL
-       if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;
-#else
-       CC->kill_me = KILLME_NO_CRYPTO;
-#endif
-
+       // kill the session if TLS is not running by now
+       if (!CC->redirect_ssl) {
+               CC->kill_me = KILLME_NO_CRYPTO;
+       }
        pop3_greeting();
 }
 
        pop3_greeting();
 }
 
@@ -149,10 +145,10 @@ int pop3_grab_mailbox(void) {
 
        if (CtdlGetRoom(&CC->room, MAILROOM) != 0) return(-1);
 
 
        if (CtdlGetRoom(&CC->room, MAILROOM) != 0) return(-1);
 
-       /* Load up the messages */
+       // Load up the messages
        CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, pop3_add_message, NULL);
 
        CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, pop3_add_message, NULL);
 
-       /* Figure out which are old and which are new */
+       // Figure out which are old and which are new
         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
        POP3->lastseen = (-1);
        if (POP3->num_msgs) for (i=0; i<POP3->num_msgs; ++i) {
         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
        POP3->lastseen = (-1);
        if (POP3->num_msgs) for (i=0; i<POP3->num_msgs; ++i) {
@@ -516,11 +512,9 @@ void pop3_command_loop(void) {
                pop3_pass(&cmdbuf[5]);
        }
 
                pop3_pass(&cmdbuf[5]);
        }
 
-#ifdef HAVE_OPENSSL
        else if (!strncasecmp(cmdbuf, "STLS", 4)) {
                pop3_stls();
        }
        else if (!strncasecmp(cmdbuf, "STLS", 4)) {
                pop3_stls();
        }
-#endif
 
        else if (!CC->logged_in) {
                cprintf("-ERR Not logged in.\r\n");
 
        else if (!CC->logged_in) {
                cprintf("-ERR Not logged in.\r\n");
@@ -582,17 +576,15 @@ char *ctdl_module_init_pop3(void) {
                                        pop3_command_loop,
                                        NULL,
                                        CitadelServicePop3);
                                        pop3_command_loop,
                                        NULL,
                                        CitadelServicePop3);
-#ifdef HAVE_OPENSSL
                CtdlRegisterServiceHook(CtdlGetConfigInt("c_pop3s_port"),
                                        NULL,
                                        pop3s_greeting,
                                        pop3_command_loop,
                                        NULL,
                                        CitadelServicePop3S);
                CtdlRegisterServiceHook(CtdlGetConfigInt("c_pop3s_port"),
                                        NULL,
                                        pop3s_greeting,
                                        pop3_command_loop,
                                        NULL,
                                        CitadelServicePop3S);
-#endif
                CtdlRegisterSessionHook(pop3_cleanup_function, EVT_STOP, PRIO_STOP + 30);
        }
        
                CtdlRegisterSessionHook(pop3_cleanup_function, EVT_STOP, PRIO_STOP + 30);
        }
        
-       /* return our module name for the log */
+       // return our module name for the log
        return "pop3";
 }
        return "pop3";
 }
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 b153207567cbf0aebb36357624d57e7cb2fe5b0d..9ecf144b1a6601a802fa1502a0b631f30ca387f0 100644 (file)
@@ -134,9 +134,9 @@ void smtp_greeting(int is_msa) {
 // SMTPS is just like SMTP, except it goes crypto right away.
 void smtps_greeting(void) {
        CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
 // SMTPS is just like SMTP, except it goes crypto right away.
 void smtps_greeting(void) {
        CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
-#ifdef HAVE_OPENSSL
-       if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;          // kill session if no crypto
-#endif
+       if (!CC->redirect_ssl) {
+               CC->kill_me = KILLME_NO_CRYPTO;         // kill session if no crypto
+       }
        smtp_greeting(0);
 }
 
        smtp_greeting(0);
 }
 
@@ -214,7 +214,6 @@ void smtp_hello(int which_command) {
                cprintf("250-HELP\r\n");
                cprintf("250-SIZE %ld\r\n", CtdlGetConfigLong("c_maxmsglen"));
 
                cprintf("250-HELP\r\n");
                cprintf("250-SIZE %ld\r\n", CtdlGetConfigLong("c_maxmsglen"));
 
-#ifdef HAVE_OPENSSL
                // Offer the STARTTLS option...
                if (    (!CC->redirect_ssl)                                                     // not if we're already TLS
                        && (    (SMTP->is_msa)                                                  // Always on port 587
                // Offer the STARTTLS option...
                if (    (!CC->redirect_ssl)                                                     // not if we're already TLS
                        && (    (SMTP->is_msa)                                                  // Always on port 587
@@ -223,7 +222,6 @@ void smtp_hello(int which_command) {
                ) {
                        cprintf("250-STARTTLS\r\n");
                }
                ) {
                        cprintf("250-STARTTLS\r\n");
                }
-#endif
 
                cprintf("250-AUTH LOGIN PLAIN\r\n"
                        "250-AUTH=LOGIN PLAIN\r\n"
 
                cprintf("250-AUTH LOGIN PLAIN\r\n"
                        "250-AUTH=LOGIN PLAIN\r\n"
@@ -964,12 +962,10 @@ void smtp_command_loop(void) {
                smtp_rcpt();
                return;
        }
                smtp_rcpt();
                return;
        }
-#ifdef HAVE_OPENSSL
        if (!strncasecmp(ChrPtr(SMTP->Cmd), "STARTTLS", 8)) {
                smtp_starttls();
                return;
        }
        if (!strncasecmp(ChrPtr(SMTP->Cmd), "STARTTLS", 8)) {
                smtp_starttls();
                return;
        }
-#endif
 
        cprintf("502 I'm afraid I can't do that.\r\n");
 }
 
        cprintf("502 I'm afraid I can't do that.\r\n");
 }
@@ -1015,14 +1011,12 @@ char *ctdl_module_init_smtp(void) {
                                        NULL, 
                                        CitadelServiceSMTP_MTA);
 
                                        NULL, 
                                        CitadelServiceSMTP_MTA);
 
-#ifdef HAVE_OPENSSL
                CtdlRegisterServiceHook(CtdlGetConfigInt("c_smtps_port"),       // SMTPS MTA
                                        NULL,
                                        smtps_greeting,
                                        smtp_command_loop,
                                        NULL,
                                        CitadelServiceSMTPS_MTA);
                CtdlRegisterServiceHook(CtdlGetConfigInt("c_smtps_port"),       // SMTPS MTA
                                        NULL,
                                        smtps_greeting,
                                        smtp_command_loop,
                                        NULL,
                                        CitadelServiceSMTPS_MTA);
-#endif
 
                CtdlRegisterServiceHook(CtdlGetConfigInt("c_msa_port"),         // SMTP MSA
                                        NULL,
 
                CtdlRegisterServiceHook(CtdlGetConfigInt("c_msa_port"),         // SMTP MSA
                                        NULL,
index edce1f5c387088008f16b42ca651860bb6d42dbb..6eedc56d02bb37d3523a6cd1b282c71e077650d0 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,50 @@ 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);                 // RFC 2369
+               cprintf("List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");       // RFC 8058
        }
        }
+
        CtdlOutputMsg(msgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0, NULL, &fromaddr, NULL);
        s.TheMessage = CC->redirect_buffer;
        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;
+
+       // 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 +344,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 +484,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 +598,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 9dacbffad865639f8172a470fb4f24ba723d9d74..81b239c90ab4eebf71d8f0f5fd9483eae971b8eb 100644 (file)
@@ -192,11 +192,9 @@ void xmpp_stream_start(void *data, const char *supplied_el, const char **attr)
        /*
         * TLS encryption (but only if it isn't already active)
         */ 
        /*
         * TLS encryption (but only if it isn't already active)
         */ 
-#ifdef HAVE_OPENSSL
        if (!CC->redirect_ssl) {
                cprintf("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'></starttls>");
        }
        if (!CC->redirect_ssl) {
                cprintf("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'></starttls>");
        }
-#endif
 
        if (!CC->logged_in) {
                /* If we're not logged in yet, offer SASL as our feature set */
 
        if (!CC->logged_in) {
                /* If we're not logged in yet, offer SASL as our feature set */
@@ -487,14 +485,9 @@ void xmpp_xml_end(void *data, const char *supplied_el) {
        }
 
        else if (!strcasecmp(el, "starttls")) {
        }
 
        else if (!strcasecmp(el, "starttls")) {
-#ifdef HAVE_OPENSSL
                cprintf("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
                CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
                if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;
                cprintf("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
                CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
                if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;
-#else
-               cprintf("<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
-               CC->kill_me = KILLME_NO_CRYPTO;
-#endif
        }
 
        else if (!strcasecmp(el, "ping")) {
        }
 
        else if (!strcasecmp(el, "ping")) {
index d1929a22689810954e42930329098c8d28cc3c9d..f52a6e26f3fb48c3f530a4c0e476bace18d9db11 100644 (file)
@@ -1,16 +1,6 @@
-/*
- * XMPP event queue
- *
- * Copyright (c) 2007-2021 by Art Cancro
- *
- * 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.
- */
+// XMPP event queue
+// Copyright (c) 2007-2024 by Art Cancro
+// This program is open source software.  Use, duplication, or disclosure is subject to the GNU General Public License v3.
 
 #include "../../sysdep.h"
 #include <stdlib.h>
 
 #include "../../sysdep.h"
 #include <stdlib.h>
@@ -49,7 +39,7 @@ void xmpp_queue_event(int event_type, char *email_addr) {
 
        syslog(LOG_DEBUG, "xmpp: xmpp_queue_event(%d, %s)", event_type, email_addr);
 
 
        syslog(LOG_DEBUG, "xmpp: xmpp_queue_event(%d, %s)", event_type, email_addr);
 
-       /* Purge events more than a minute old */
+       // Purge events more than a minute old
        begin_critical_section(S_XMPP_QUEUE);
        do {
                purged_something = 0;
        begin_critical_section(S_XMPP_QUEUE);
        do {
                purged_something = 0;
@@ -64,7 +54,7 @@ void xmpp_queue_event(int event_type, char *email_addr) {
        } while(purged_something);
        end_critical_section(S_XMPP_QUEUE);
 
        } while(purged_something);
        end_critical_section(S_XMPP_QUEUE);
 
-       /* Create a new event */
+       // Create a new event
        new_event = (struct xmpp_event *) malloc(sizeof(struct xmpp_event));
        new_event->next = NULL;
        new_event->event_time = time(NULL);
        new_event = (struct xmpp_event *) malloc(sizeof(struct xmpp_event));
        new_event->next = NULL;
        new_event->event_time = time(NULL);
@@ -73,7 +63,7 @@ void xmpp_queue_event(int event_type, char *email_addr) {
        new_event->session_which_generated_this_event = CC->cs_pid;
        safestrncpy(new_event->event_jid, email_addr, sizeof new_event->event_jid);
 
        new_event->session_which_generated_this_event = CC->cs_pid;
        safestrncpy(new_event->event_jid, email_addr, sizeof new_event->event_jid);
 
-       /* Add it to the list */
+       // Add it to the list
        begin_critical_section(S_XMPP_QUEUE);
        if (xmpp_queue == NULL) {
                xmpp_queue = new_event;
        begin_critical_section(S_XMPP_QUEUE);
        if (xmpp_queue == NULL) {
                xmpp_queue = new_event;
@@ -88,7 +78,7 @@ void xmpp_queue_event(int event_type, char *email_addr) {
        }
        end_critical_section(S_XMPP_QUEUE);
 
        }
        end_critical_section(S_XMPP_QUEUE);
 
-       /* Tell the sessions that something is happening */
+       // Tell the sessions that something is happening
        begin_critical_section(S_SESSION_TABLE);
        for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
                if ((cptr->logged_in) && (cptr->h_async_function == xmpp_async_loop)) {
        begin_critical_section(S_SESSION_TABLE);
        for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
                if ((cptr->logged_in) && (cptr->h_async_function == xmpp_async_loop)) {
@@ -99,15 +89,13 @@ void xmpp_queue_event(int event_type, char *email_addr) {
 }
 
 
 }
 
 
-/* 
- * Are we interested in anything from the queue?  (Called in async loop)
- */
+// Are we interested in anything from the queue?  (Called in async loop)
 void xmpp_process_events(void) {
        struct xmpp_event *xptr = NULL;
        int highest_event = 0;
 
        for (xptr=xmpp_queue; xptr!=NULL; xptr=xptr->next) {
 void xmpp_process_events(void) {
        struct xmpp_event *xptr = NULL;
        int highest_event = 0;
 
        for (xptr=xmpp_queue; xptr!=NULL; xptr=xptr->next) {
-               if (xptr->event_seq > XMPP->last_event_processed) {             // we are getting crashes on this line?
+               if (xptr->event_seq > XMPP->last_event_processed) {
 
                        switch(xptr->event_type) {
 
 
                        switch(xptr->event_type) {
 
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
index 8787beae805027c94b5d7336cea1e755473ea8dc..260bd8dc23a6bb994fd04b9348bf4d16e0f8dd25 100644 (file)
@@ -526,9 +526,7 @@ int PerformXmsgHooks(char *sender, char *sender_email, char *recp, char *msg) {
 
 // "Start TLS" function that is (hopefully) adaptable for any protocol
 void CtdlModuleStartCryptoMsgs(char *ok_response, char *nosup_response, char *error_response) {
 
 // "Start TLS" function that is (hopefully) adaptable for any protocol
 void CtdlModuleStartCryptoMsgs(char *ok_response, char *nosup_response, char *error_response) {
-#ifdef HAVE_OPENSSL
        CtdlStartTLS (ok_response, nosup_response, error_response);
        CtdlStartTLS (ok_response, nosup_response, error_response);
-#endif
 }
 
 
 }
 
 
index 8d1f7d0ff3bbf99c93e9bfa5895d407dc53e286b..24833b737cd9bd881e8e68245c8025211f932f58 100644 (file)
 #endif
 
 #include "citadel_defs.h"
 #endif
 
 #include "citadel_defs.h"
-#ifdef HAVE_OPENSSL
 #define OPENSSL_NO_KRB5                        // work around redhat b0rken ssl headers
 #include <openssl/ssl.h>
 #define OPENSSL_NO_KRB5                        // work around redhat b0rken ssl headers
 #include <openssl/ssl.h>
-#endif
 
 
 // New format for a message in memory
 
 
 // New format for a message in memory
index 58f94624dfd5b07cf01c60465c48b6a7dcc4f281..6f3209468c2298bd91fe03fedd9cad74d7713166 100644 (file)
@@ -69,9 +69,7 @@ void init_sysdep(void) {
 #endif
 
        // If we've got OpenSSL, we're going to use it.
 #endif
 
        // If we've got OpenSSL, we're going to use it.
-#ifdef HAVE_OPENSSL
        init_ssl();
        init_ssl();
-#endif
 
        if (pthread_key_create(&MyConKey, NULL) != 0) {                         // TSD for sessions
                syslog(LOG_CRIT, "sysdep: can't create TSD key: %m");
 
        if (pthread_key_create(&MyConKey, NULL) != 0) {                         // TSD for sessions
                syslog(LOG_CRIT, "sysdep: can't create TSD key: %m");
@@ -252,19 +250,17 @@ static unsigned on = 1, off = 0;
 
 void buffer_output(void) {
 #ifdef HAVE_TCP_BUFFERING
 
 void buffer_output(void) {
 #ifdef HAVE_TCP_BUFFERING
-#ifdef HAVE_OPENSSL
-       if (!CC->redirect_ssl)
-#endif
+       if (!CC->redirect_ssl) {
                setsockopt(CC->client_socket, IPPROTO_TCP, TCP_CORK, &on, 4);
                setsockopt(CC->client_socket, IPPROTO_TCP, TCP_CORK, &on, 4);
+       }
 #endif
 }
 
 void unbuffer_output(void) {
 #ifdef HAVE_TCP_BUFFERING
 #endif
 }
 
 void unbuffer_output(void) {
 #ifdef HAVE_TCP_BUFFERING
-#ifdef HAVE_OPENSSL
-       if (!CC->redirect_ssl)
-#endif
+       if (!CC->redirect_ssl) {
                setsockopt(CC->client_socket, IPPROTO_TCP, TCP_CORK, &off, 4);
                setsockopt(CC->client_socket, IPPROTO_TCP, TCP_CORK, &off, 4);
+       }
 #endif
 }
 
 #endif
 }
 
@@ -305,12 +301,10 @@ int client_write(const char *buf, int nbytes) {
                return 0;
        }
 
                return 0;
        }
 
-#ifdef HAVE_OPENSSL
        if (Ctx->redirect_ssl) {
                client_write_ssl(buf, nbytes);
                return 0;
        }
        if (Ctx->redirect_ssl) {
                client_write_ssl(buf, nbytes);
                return 0;
        }
-#endif
        if (Ctx->client_socket == -1) return -1;
 
        fdflags = fcntl(Ctx->client_socket, F_GETFL);
        if (Ctx->client_socket == -1) return -1;
 
        fdflags = fcntl(Ctx->client_socket, F_GETFL);
@@ -397,16 +391,13 @@ int client_read_blob(StrBuf *Target, int bytes, int timeout) {
        const char *Error;
        int retval = 0;
 
        const char *Error;
        int retval = 0;
 
-#ifdef HAVE_OPENSSL
        if (CC->redirect_ssl) {
                retval = client_read_sslblob(Target, bytes, timeout);
                if (retval < 0) {
                        syslog(LOG_ERR, "sysdep: client_read_blob() failed");
                }
        }
        if (CC->redirect_ssl) {
                retval = client_read_sslblob(Target, bytes, timeout);
                if (retval < 0) {
                        syslog(LOG_ERR, "sysdep: client_read_blob() failed");
                }
        }
-       else 
-#endif
-       {
+       else {
                retval = StrBufReadBLOBBuffered(Target, 
                                                CC->RecvBuf.Buf,
                                                &CC->RecvBuf.ReadWritePointer,
                retval = StrBufReadBLOBBuffered(Target, 
                                                CC->RecvBuf.Buf,
                                                &CC->RecvBuf.ReadWritePointer,
@@ -505,14 +496,11 @@ int CtdlClientGetLine(StrBuf *Target) {
        int rc;
 
        FlushStrBuf(Target);
        int rc;
 
        FlushStrBuf(Target);
-#ifdef HAVE_OPENSSL
        if (CC->redirect_ssl) {
                rc = client_readline_sslbuffer(Target, CC->RecvBuf.Buf, &CC->RecvBuf.ReadWritePointer, 1);
                return rc;
        }
        if (CC->redirect_ssl) {
                rc = client_readline_sslbuffer(Target, CC->RecvBuf.Buf, &CC->RecvBuf.ReadWritePointer, 1);
                return rc;
        }
-       else 
-#endif
-       {
+       else {
                rc = StrBufTCP_read_buffered_line_fast(Target, 
                                                       CC->RecvBuf.Buf,
                                                       &CC->RecvBuf.ReadWritePointer,
                rc = StrBufTCP_read_buffered_line_fast(Target, 
                                                       CC->RecvBuf.Buf,
                                                       &CC->RecvBuf.ReadWritePointer,
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..aed19b472cc1ec0e9d2d73d4ef7551afa4e343e1 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);
@@ -691,6 +696,7 @@ void ingest_one(char *line, struct cdbkeyval *kv) {
                ++good_rows;
        }
        else {
                ++good_rows;
        }
        else {
+               fprintf(stderr, "bad row: <%s>\n", line);
                ++bad_rows;
        }
 
                ++bad_rows;
        }
 
@@ -731,6 +737,10 @@ void ingest(void) {
                }
 
                if (line_len > 0) {
                }
 
                if (line_len > 0) {
+                       if (!strncasecmp(line, HKEY("end|"))) {
+                               fprintf(stderr, "\n");
+                               end_found = 1;
+                       }
                        if ( (begin_found) && (!end_found) ) {
                                ingest_one(line, &kv);
                        }
                        if ( (begin_found) && (!end_found) ) {
                                ingest_one(line, &kv);
                        }
@@ -738,10 +748,6 @@ void ingest(void) {
                                begin_found = 1;
                                fprintf(stderr, "   good rows / bad rows:\n");
                        }
                                begin_found = 1;
                                fprintf(stderr, "   good rows / bad rows:\n");
                        }
-                       if (!strncasecmp(line, HKEY("end|"))) {
-                               fprintf(stderr, "\n");
-                               end_found = 1;
-                       }
                }
 
        } while (ch >= 0);
                }
 
        } while (ch >= 0);
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 ca2de8df8d9e7b0eb756ca1a7186a16d5f571641..47e0f0c8b7866dc7881b2cfd5fa48446eb4639fb 100644 (file)
@@ -1,8 +1,7 @@
-// Functions which handle translation between HTML and plain text
-// Copyright (c) 2000-2023 by the citadel.org team
+// This is an HTML to plain text converter.
+// Copyright (c) 2000-2024 by the citadel.org team (Art Cancro et al.)
 //
 //
-// This program is open source software.  Use, duplication, or disclosure
-// is subject to the terms of the GNU General Public License, version 3.
+// This program is open source software.  Use, duplication, or disclosure is subject to the GNU General Public License version 3.
 
 #include <stdlib.h>
 #include <unistd.h>
 
 #include <stdlib.h>
 #include <unistd.h>
 #include <limits.h>
 #include <time.h>
 #include "libcitadel.h"
 #include <limits.h>
 #include <time.h>
 #include "libcitadel.h"
+
+int u8_wc_toutf8(char *dest, u_int32_t ch) {
+       if (ch < 0x80) {
+               dest[0] = (char)ch;
+               return 1;
+       }
+       if (ch < 0x800) {
+               dest[0] = (ch>>6) | 0xC0;
+               dest[1] = (ch & 0x3F) | 0x80;
+               return 2;
+       }
+       if (ch < 0x10000) {
+               dest[0] = (ch>>12) | 0xE0;
+               dest[1] = ((ch>>6) & 0x3F) | 0x80;
+               dest[2] = (ch & 0x3F) | 0x80;
+               return 3;
+       }
+       if (ch < 0x110000) {
+               dest[0] = (ch>>18) | 0xF0;
+               dest[1] = ((ch>>12) & 0x3F) | 0x80;
+               dest[2] = ((ch>>6) & 0x3F) | 0x80;
+               dest[3] = (ch & 0x3F) | 0x80;
+               return 4;
+       }
+       return 0;
+}
+
+
+// Try to embed an image in the display stream.
+// out                 = the StrBuf to which we are writing the display stream
+// url                 = the URL of the image (warning: it might be a data: URL)
+// display_protocol    = currently only H2A_SIXEL is supported
+void h2a_embed_image(StrBuf *out, char *url, int display_protocol) {
+
+       char buf[4096];
+       snprintf(buf, sizeof(buf), "curl -s '%s' | img2sixel -", url);
+
+       FILE *cmd = popen(buf, "r");
+       if (!cmd) {
+               return;
+       }
+
+       size_t bytes;
+       while (bytes = fread(buf, 1, sizeof(buf), cmd), bytes>0) {
+               StrBufAppendBufPlain(out, buf, bytes, 0);
+       }
+       pclose(cmd);
+}
+
 
 // Convert HTML to plain text.
 //
 
 // Convert HTML to plain text.
 //
-// inputmsg    = pointer to raw HTML message
-// msglen      = stop reading after this many bytes
-// screenwidth = desired output screenwidth
-// ansi                = if nonzero, assume output is to a terminal that supports ANSI escape codes
+// inputmsg     = pointer to raw HTML message
+// msglen       = stop reading after this many bytes
+// screenwidth  = desired output screenwidth
+// flags        = Flags that can be set:
+//              H2A_ANSI       = Output ANSI terminal escape sequences
+//              H2A_SIXEL      = Output Sixel graphics (not yet implemented)
 //
 //
-char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, int ansi) {
-       char inbuf[SIZ];
+char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned int flags) {
+       char *inbuf = NULL;
        int inbuf_len = 0;
        int inbuf_len = 0;
-       char outbuf[SIZ];
        char tag[1024];
        char tag[1024];
-       int done_reading = 0;
-       const char *inptr;
+       char *tag_start = NULL;
+       char *tag_end = NULL;
+       StrBuf *out;
        char *outptr;
        char *outptr;
-       size_t outptr_buffer_size;
-       size_t output_len = 0;
-       int i, j, ch, did_out, rb, scanch;
-       int nest = 0;                           // Bracket nesting level
+       int j;
+       char ch;
+       int tag_nesting_level = 0;              // angle bracket nesting level
        int blockquote = 0;                     // BLOCKQUOTE nesting level
        int styletag = 0;                       // STYLE tag nesting level
        int blockquote = 0;                     // BLOCKQUOTE nesting level
        int styletag = 0;                       // STYLE tag nesting level
-       int styletag_start = 0;
-       int bytes_processed = 0;
-       char nl[128];
+       char nl[128];                           // The current value of what a "newline" looks like (changes during blockquotes)
+
+       int ansi = (flags & H2A_ANSI) ? 1 : 0;          // Output to a terminal that can accept ANSI escape sequences
+       int sixel = (flags & H2A_SIXEL) ? 1 : 0;        // Output to a terminal that can accept Sixel graphics
+
+       out = NewStrBuf();
+       if (!out) {
+               return(NULL);
+       }
 
        tag[0] = '\0';
        strcpy(nl, "\n");
 
        tag[0] = '\0';
        strcpy(nl, "\n");
-       inptr = inputmsg;
-       strcpy(inbuf, "");
-       strcpy(outbuf, "");
-       if (msglen == 0) msglen = strlen(inputmsg);
-
-       outptr_buffer_size = strlen(inptr) + SIZ;
-       outptr = malloc(outptr_buffer_size);
-       if (outptr == NULL) return NULL;
-       strcpy(outptr, "");
-       output_len = 0;
-
-       do {
-               // Fill the input buffer
-               inbuf_len = strlen(inbuf);
-               if ( (done_reading == 0) && (inbuf_len < (SIZ-128)) ) {
-
-                       ch = *inptr++;
-                       if (ch != 0) {
-                               inbuf[inbuf_len++] = ch;
-                               inbuf[inbuf_len] = 0;
-                       } 
-                       else {
-                               done_reading = 1;
-                       }
+       if (msglen == 0) {
+               msglen = strlen(inputmsg);
+       }
 
 
-                       ++bytes_processed;
-                       if (bytes_processed > msglen) {
-                               done_reading = 1;
-                       }
+       inbuf = strdup(inputmsg);
+       if (!inbuf) {
+               return NULL;
+       }
+
+       // "inbuf" ingests the unparsed HTML while we work with it.
+       inbuf_len = strlen(inbuf);
+       if (inbuf_len > msglen) {
+               inbuf[msglen] = 0;
+               inbuf_len = msglen;
+       }
 
 
+       // Do some parsing
+       if (!IsEmptyStr(inbuf)) {
+
+               // Convert newlines, carriage returns, and tabs to spaces
+               char *sp;
+               while ( (sp = strchr(inbuf, '\r'))
+                       || (sp = strchr(inbuf, '\n'))
+                       || (sp = strchr(inbuf, '\t'))
+               ) {
+                       *sp = ' ';
                }
 
                }
 
-               // Do some parsing
-               if (!IsEmptyStr(inbuf)) {
-
-                   // Fold in all the spacing
-                   for (i=0; !IsEmptyStr(&inbuf[i]); ++i) {
-                       if (inbuf[i]==10) inbuf[i]=32;
-                       if (inbuf[i]==13) inbuf[i]=32;
-                       if (inbuf[i]==9) inbuf[i]=32;
-                   }
-                   for (i=0; !IsEmptyStr(&inbuf[i]); ++i) {
-                       while ((inbuf[i]==32)&&(inbuf[i+1]==32)) {
-                               strcpy(&inbuf[i], &inbuf[i+1]);
-                       }
-                   }
+               // Convert multiple spaces to a single space.
+               while (sp = strstr(inbuf, "  "), sp!=NULL) {
+                       strcpy(sp, sp+1);
+               }
 
 
-                   for (i=0; !IsEmptyStr(&inbuf[i]); ++i) {
+               // Run through the markup performing the conversion.
+               char *inptr = inbuf;
+               int linelen = 0;
+               while (ch = inptr[0], ch != 0) {
 
 
-                       ch = inbuf[i];
+                       // Keep track of how many angle brackets were found in case someone is sloppy with them
+                       // or tries to nest tags.  If nest is 0 then we are within text; if it is nonzero then we
+                       // are within a tag.
 
 
-                       if (ch == '<') {
-                               ++nest;
+                       if (ch == '<') {                // We have hit the beginning of a tag.
+                               ++tag_nesting_level;
+                               tag_start = inptr + 1;
                                strcpy(tag, "");
                        }
 
                                strcpy(tag, "");
                        }
 
-                       else if (ch == '>') {   // We have a tag.
-                               if (nest > 0) --nest;
-
-                               // Unqualify the tag (truncate at first space)
-                               if (strchr(tag, ' ') != NULL) {
-                                       strcpy(strchr(tag, ' '), "");
-                               }
-                               
-                               if (!strcasecmp(tag, "P")) {
-                                       strcat(outbuf, nl);
-                                       strcat(outbuf, nl);
+                       else if (ch == '>') {           // We have hit the end of a tag.
+                               if (tag_nesting_level > 0) {
+                                       --tag_nesting_level;
                                }
                                }
+                               if (tag_nesting_level == 0) {
+                                       tag_end = inptr;
 
 
-                               if (!strcasecmp(tag, "/DIV")) {
-                                       strcat(outbuf, nl);
-                                       strcat(outbuf, nl);
-                               }
-
-                               if (!strcasecmp(tag, "LI")) {
-                                       strcat(outbuf, nl);
-                                       strcat(outbuf, " * ");
-                               }
-
-                               else if (!strcasecmp(tag, "/UL")) {
-                                       strcat(outbuf, nl);
-                                       strcat(outbuf, nl);
-                               }
-
-                               else if (!strcasecmp(tag, "H1")) {
-                                       strcat(outbuf, nl);
-                                       strcat(outbuf, nl);
-                               }
-
-                               else if (!strcasecmp(tag, "H2")) {
-                                       strcat(outbuf, nl);
-                                       strcat(outbuf, nl);
-                               }
-
-                               else if (!strcasecmp(tag, "H3")) {
-                                       strcat(outbuf, nl);
-                                       strcat(outbuf, nl);
-                               }
-
-                               else if (!strcasecmp(tag, "H4")) {
-                                       strcat(outbuf, nl);
-                                       strcat(outbuf, nl);
-                               }
-
-                               else if (!strcasecmp(tag, "/H1")) {
-                                       strcat(outbuf, nl);
-                               }
-
-                               else if (!strcasecmp(tag, "/H2")) {
-                                       strcat(outbuf, nl);
-                               }
-
-                               else if (!strcasecmp(tag, "/H3")) {
-                                       strcat(outbuf, nl);
-                               }
-
-                               else if (!strcasecmp(tag, "/H4")) {
-                                       strcat(outbuf, nl);
-                               }
-
-                               else if (!strcasecmp(tag, "HR")) {
-                                       strcat(outbuf, nl);
-                                       strcat(outbuf, " ");
-                                       for (j=0; j<screenwidth-2; ++j)
-                                               strcat(outbuf, "-");
-                                       strcat(outbuf, nl);
-                               }
-
-                               else if (
-                                       (!strcasecmp(tag, "B"))
-                                       || (!strcasecmp(tag, "STRONG"))
-                               ) {
-                                       if (ansi) {
-                                               strcat(outbuf, "\033[1m");
+                                       size_t tag_len = tag_end - tag_start;
+                                       if (tag_len >= sizeof(tag)) {
+                                               tag_len = sizeof(tag);
                                        }
                                        }
-                               }
-                               else if (
-                                       (!strcasecmp(tag, "/B"))
-                                       || (!strcasecmp(tag, "/STRONG"))
-                               ) {
-                                       if (ansi) {
-                                               strcat(outbuf, "\033[22m");
-                                       }
-                               }
+                                       strncpy(tag, tag_start, tag_len);
+                                       tag[tag_len] = 0;
 
 
-                               else if (
-                                       (!strcasecmp(tag, "I"))
-                                       || (!strcasecmp(tag, "EM"))
-                               ) {
-                                       if (ansi) {
-                                               strcat(outbuf, "\033[3m");
+                                       // Unqualify the tag (truncate at first space)
+                                       char *tagsp = strchr(tag, ' ');
+                                       if (tagsp) {
+                                               *tagsp = 0;
                                        }
                                        }
-                               }
 
 
-                               else if (
-                                       (!strcasecmp(tag, "/I"))
-                                       || (!strcasecmp(tag, "/EM"))
-                               ) {
-                                       if (ansi) {
-                                               strcat(outbuf, "\033[23m");
+                                       // IMG tag on sixel terminals -- try to display the image
+                                       if ( (!strcasecmp(tag, "img")) && sixel) {
+                                               char *q1, *q2;
+
+                                               // look for src attribute
+                                               char *src = bmstrcasestr(tag_start, "src=");
+                                               q1 = q2 = NULL;
+                                               if (src && src<tag_end) {
+                                                       if (q1 = strchr(src, '"'), q1 && q1<tag_end) {          // in double quotes
+                                                               ++q1;
+                                                               q2 = strchr(q1, '"');
+                                                       }
+                                                       else if (q1 = strchr(src, '\''), q1 && q1<tag_end) {    // in single quotes
+                                                               ++q1;
+                                                               q2 = strchr(q1, '\'');
+                                                       }
+                                                       if (q1 && q1<q2 && q2<tag_end) {
+                                                               char url[SIZ];
+                                                               memcpy(url, q1, q2-q1);
+                                                               url[q2-q1] = 0;
+                                                               h2a_embed_image(out, url, H2A_SIXEL);           // try to display
+                                                               linelen = 0;
+                                                       }
+                                               }
                                        }
                                        }
-                               }
 
 
-                               else if (!strcasecmp(tag, "U")) {
-                                       if (ansi) {
-                                               strcat(outbuf, "\033[4m");
+                                       // IMG tag on non-sixel terminals -- we can display the alt text
+                                       if ( (!strcasecmp(tag, "img")) && !sixel) {
+                                               char *q1, *q2;
+
+                                               // look for alt text
+                                               char *alt = bmstrcasestr(tag_start, "alt=");
+                                               q1 = q2 = NULL;
+                                               if (alt && alt<tag_end) {
+                                                       if (q1 = strchr(alt, '"'), q1 && q1<tag_end) {          // in double quotes
+                                                               ++q1;
+                                                               q2 = strchr(q1, '"');
+                                                       }
+                                                       else if (q1 = strchr(alt, '\''), q1 && q1<tag_end) {    // in single quotes
+                                                               ++q1;
+                                                               q2 = strchr(q1, '\'');
+                                                       }
+                                                       if (q1 && q1<q2 && q2<tag_end) {
+                                                               StrBufAppendBufPlain(out, q1, (long)(q2-q1), 0);
+                                                       }
+                                               }
                                        }
                                        }
-                               }
 
 
-                               else if (!strcasecmp(tag, "/U")) {
-                                       if (ansi) {
-                                               strcat(outbuf, "\033[24m");
+                                       if (!strcasecmp(tag, "P")) {
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               linelen = 0;
                                        }
                                        }
-                               }
 
 
-                               else if (!strcasecmp(tag, "BR")) {
-                                       strcat(outbuf, nl);
-                               }
-
-                               else if (!strcasecmp(tag, "TR")) {
-                                       strcat(outbuf, nl);
-                               }
-
-                               else if (!strcasecmp(tag, "/TABLE")) {
-                                       strcat(outbuf, nl);
-                               }
-
-                               else if (!strcasecmp(tag, "BLOCKQUOTE")) {
-                                       ++blockquote;
-                                       strcpy(nl, "\n");
-                                       if ( (blockquote == 1) && (ansi) ) {
-                                               strcat(nl, "\033[2m\033[2m");
+                                       if (!strcasecmp(tag, "/DIV")) {
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               linelen = 0;
                                        }
                                        }
-                                       for (j=0; j<blockquote; ++j) strcat(nl, ">");
-                                       strcat(outbuf, nl);
-                               }
 
 
-                               else if (!strcasecmp(tag, "/BLOCKQUOTE")) {
-                                       strcat(outbuf, "\n");
-                                       --blockquote;
-                                       if ( (blockquote == 0) && (ansi) ) {
-                                               strcat(outbuf, "\033[22m\033[22m");
+                                       if (!strcasecmp(tag, "LI")) {
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               StrBufAppendBufPlain(out, HKEY(" * "), 0);
+                                               linelen = 3;
                                        }
                                        }
-                                       strcpy(nl, "\n");
-                                       for (j=0; j<blockquote; ++j) strcat(nl, ">");
-                                       strcat(outbuf, nl);
-                               }
 
 
-                               else if (!strcasecmp(tag, "STYLE")) {
-                                       ++styletag;
-                                       if (styletag == 1) {
-                                               styletag_start = strlen(outbuf);
+                                       else if (!strcasecmp(tag, "/UL")) {
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               linelen = 0;
                                        }
                                        }
-                               }
 
 
-                               else if (!strcasecmp(tag, "/STYLE")) {
-                                       --styletag;
-                                       if (styletag == 0) {
-                                               outbuf[styletag_start] = 0;
+                                       else if (!strcasecmp(tag, "H1")) {
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               linelen = 0;
                                        }
                                        }
-                               }
-
-                       }
-
-                       else if ((nest > 0) && (strlen(tag)<(sizeof(tag)-1))) {
-                               tag[strlen(tag)+1] = 0;
-                               tag[strlen(tag)] = ch;
-                       }
-                               
-                       else if ((!nest) && (styletag == 0)) {
-                               outbuf[strlen(outbuf)+1] = 0;
-                               outbuf[strlen(outbuf)] = ch;
-                       }
-                   }
-                   strcpy(inbuf, &inbuf[i]);
-               }
-
-               // Convert &; tags to the forbidden characters
-               if (!IsEmptyStr(outbuf)) for (i=0; !IsEmptyStr(&outbuf[i]); ++i) {
-
-                       // Character entity references
-                       if (!strncasecmp(&outbuf[i], "&nbsp;", 6)) {
-                               outbuf[i] = ' ';
-                               strcpy(&outbuf[i+1], &outbuf[i+6]);
-                       }
-
-                       if (!strncasecmp(&outbuf[i], "&ensp;", 6)) {
-                               outbuf[i] = ' ';
-                               strcpy(&outbuf[i+1], &outbuf[i+6]);
-                       }
-
-                       if (!strncasecmp(&outbuf[i], "&emsp;", 6)) {
-                               outbuf[i] = ' ';
-                               strcpy(&outbuf[i+1], &outbuf[i+6]);
-                       }
 
 
-                       if (!strncasecmp(&outbuf[i], "&thinsp;", 8)) {
-                               outbuf[i] = ' ';
-                               strcpy(&outbuf[i+1], &outbuf[i+8]);
-                       }
-
-                       else if (!strncasecmp(&outbuf[i], "&lt;", 4)) {
-                               outbuf[i] = '<';
-                               strcpy(&outbuf[i+1], &outbuf[i+4]);
-                       }
-
-                       else if (!strncasecmp(&outbuf[i], "&gt;", 4)) {
-                               outbuf[i] = '>';
-                               strcpy(&outbuf[i+1], &outbuf[i+4]);
-                       }
-
-                       else if (!strncasecmp(&outbuf[i], "&amp;", 5)) {
-                               strcpy(&outbuf[i+1], &outbuf[i+5]);
-                       }
-
-                       else if (!strncasecmp(&outbuf[i], "&quot;", 6)) {
-                               outbuf[i] = '\"';
-                               strcpy(&outbuf[i+1], &outbuf[i+6]);
-                       }
-
-                       else if (!strncasecmp(&outbuf[i], "&lsquo;", 7)) {
-                               outbuf[i] = '`';
-                               strcpy(&outbuf[i+1], &outbuf[i+7]);
-                       }
-
-                       else if (!strncasecmp(&outbuf[i], "&rsquo;", 7)) {
-                               outbuf[i] = '\'';
-                               strcpy(&outbuf[i+1], &outbuf[i+7]);
-                       }
-
-                       else if (!strncasecmp(&outbuf[i], "&copy;", 6)) {
-                               outbuf[i] = '(';
-                               outbuf[i+1] = 'c';
-                               outbuf[i+2] = ')';
-                               strcpy(&outbuf[i+3], &outbuf[i+6]);
-                       }
-
-                       else if (!strncasecmp(&outbuf[i], "&bull;", 6)) {
-                               outbuf[i] = ' ';
-                               outbuf[i+1] = '*';
-                               outbuf[i+2] = ' ';
-                               strcpy(&outbuf[i+3], &outbuf[i+6]);
-                       }
-
-                       else if (!strncasecmp(&outbuf[i], "&hellip;", 8)) {
-                               outbuf[i] = '.';
-                               outbuf[i+1] = '.';
-                               outbuf[i+2] = '.';
-                               strcpy(&outbuf[i+3], &outbuf[i+8]);
-                       }
+                                       else if (!strcasecmp(tag, "H2")) {
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               linelen = 0;
+                                       }
 
 
-                       else if (!strncasecmp(&outbuf[i], "&trade;", 7)) {
-                               outbuf[i] = '(';
-                               outbuf[i+1] = 't';
-                               outbuf[i+2] = 'm';
-                               outbuf[i+3] = ')';
-                               strcpy(&outbuf[i+4], &outbuf[i+7]);
-                       }
+                                       else if (!strcasecmp(tag, "H3")) {
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               linelen = 0;
+                                       }
 
 
-                       else if (!strncasecmp(&outbuf[i], "&reg;", 5)) {
-                               outbuf[i] = '(';
-                               outbuf[i+1] = 'r';
-                               outbuf[i+2] = ')';
-                               strcpy(&outbuf[i+3], &outbuf[i+5]);
-                       }
+                                       else if (!strcasecmp(tag, "H4")) {
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               linelen = 0;
+                                       }
 
 
-                       else if (!strncasecmp(&outbuf[i], "&frac14;", 8)) {
-                               outbuf[i] = '1';
-                               outbuf[i+1] = '/';
-                               outbuf[i+2] = '4';
-                               strcpy(&outbuf[i+3], &outbuf[i+8]);
-                       }
+                                       else if (!strcasecmp(tag, "/H1")) {
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               linelen = 0;
+                                       }
 
 
-                       else if (!strncasecmp(&outbuf[i], "&frac12;", 8)) {
-                               outbuf[i] = '1';
-                               outbuf[i+1] = '/';
-                               outbuf[i+2] = '2';
-                               strcpy(&outbuf[i+3], &outbuf[i+8]);
-                       }
+                                       else if (!strcasecmp(tag, "/H2")) {
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               linelen = 0;
+                                       }
 
 
-                       else if (!strncasecmp(&outbuf[i], "&frac34;", 8)) {
-                               outbuf[i] = '3';
-                               outbuf[i+1] = '/';
-                               outbuf[i+2] = '4';
-                               strcpy(&outbuf[i+3], &outbuf[i+8]);
-                       }
+                                       else if (!strcasecmp(tag, "/H3")) {
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               linelen = 0;
+                                       }
 
 
-                       else if (!strncasecmp(&outbuf[i], "&ndash;", 7)) {
-                               outbuf[i] = '-';
-                               outbuf[i+1] = '-';
-                               strcpy(&outbuf[i+2], &outbuf[i+7]);
-                       }
+                                       else if (!strcasecmp(tag, "/H4")) {
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               linelen = 0;
+                                       }
 
 
-                       else if (!strncasecmp(&outbuf[i], "&mdash;", 7)) {
-                               outbuf[i] = '-';
-                               outbuf[i+1] = '-';
-                               outbuf[i+2] = '-';
-                               strcpy(&outbuf[i+3], &outbuf[i+7]);
-                       }
+                                       else if (!strcasecmp(tag, "HR")) {
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               StrBufAppendBufPlain(out, HKEY(" "), 0);
+                                               for (j = 0; j < screenwidth - 2; ++j) {
+                                                       StrBufAppendBufPlain(out, HKEY("-"), 0);
+                                               }
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               linelen = 0;
+                                       }
 
 
-                       else if (!strncmp(&outbuf[i], "&Ccedil;", 8)) {
-                               outbuf[i] = 'C';
-                               strcpy(&outbuf[i+1], &outbuf[i+8]);
-                       }
+                                       else if (       (!strcasecmp(tag, "B"))
+                                                       || (!strcasecmp(tag, "STRONG"))
+                                       ) {
+                                               if (ansi) {
+                                                       StrBufAppendBufPlain(out, HKEY("\033[1m"), 0);
+                                               }
+                                       }
+                                       else if (       (!strcasecmp(tag, "/B"))
+                                                       || (!strcasecmp(tag, "/STRONG"))
+                                       ) {
+                                               if (ansi) {
+                                                       StrBufAppendBufPlain(out, HKEY("\033[22m"), 0);
+                                               }
+                                       }
 
 
-                       else if (!strncasecmp(&outbuf[i], "&ccedil;", 8)) {
-                               outbuf[i] = 'c';
-                               strcpy(&outbuf[i+1], &outbuf[i+8]);
-                       }
+                                       else if (       (!strcasecmp(tag, "I"))
+                                                       || (!strcasecmp(tag, "EM"))
+                                       ) {
+                                               if (ansi) {
+                                                       StrBufAppendBufPlain(out, HKEY("\033[3m"), 0);
+                                               }
+                                       }
 
 
-                       else if (!strncmp(&outbuf[i], "&Egrave;", 8)) {
-                               outbuf[i] = 'E';
-                               strcpy(&outbuf[i+1], &outbuf[i+8]);
-                       }
+                                       else if (       (!strcasecmp(tag, "/I"))
+                                                       || (!strcasecmp(tag, "/EM"))
+                                       ) {
+                                               if (ansi) {
+                                                       StrBufAppendBufPlain(out, HKEY("\033[23m"), 0);
+                                               }
+                                       }
 
 
-                       else if (!strncasecmp(&outbuf[i], "&egrave;", 8)) {
-                               outbuf[i] = 'e';
-                               strcpy(&outbuf[i+1], &outbuf[i+8]);
-                       }
+                                       else if (!strcasecmp(tag, "U")) {
+                                               if (ansi) {
+                                                       StrBufAppendBufPlain(out, HKEY("\033[4m"), 0);
+                                               }
+                                       }
 
 
-                       else if (!strncmp(&outbuf[i], "&Ecirc;", 7)) {
-                               outbuf[i] = 'E';
-                               strcpy(&outbuf[i+1], &outbuf[i+7]);
-                       }
+                                       else if (!strcasecmp(tag, "/U")) {
+                                               if (ansi) {
+                                                       StrBufAppendBufPlain(out, HKEY("\033[24m"), 0);
+                                               }
+                                       }
 
 
-                       else if (!strncasecmp(&outbuf[i], "&ecirc;", 7)) {
-                               outbuf[i] = 'e';
-                               strcpy(&outbuf[i+1], &outbuf[i+7]);
-                       }
+                                       else if (!strcasecmp(tag, "BR")) {
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               linelen = 0;
+                                       }
 
 
-                       else if (!strncmp(&outbuf[i], "&Eacute;", 8)) {
-                               outbuf[i] = 'E';
-                               strcpy(&outbuf[i+1], &outbuf[i+8]);
-                       }
+                                       else if (!strcasecmp(tag, "TR")) {
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               linelen = 0;
+                                       }
 
 
-                       else if (!strncasecmp(&outbuf[i], "&eacute;", 8)) {
-                               outbuf[i] = 'e';
-                               strcpy(&outbuf[i+1], &outbuf[i+8]);
-                       }
+                                       else if (!strcasecmp(tag, "/TABLE")) {
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               linelen = 0;
+                                       }
 
 
-                       else if (!strncmp(&outbuf[i], "&Agrave;", 8)) {
-                               outbuf[i] = 'A';
-                               strcpy(&outbuf[i+1], &outbuf[i+8]);
-                       }
+                                       else if (!strcasecmp(tag, "BLOCKQUOTE")) {
+                                               ++blockquote;
+                                               strcpy(nl, "\n");
+                                               if ((blockquote == 1) && (ansi)) {
+                                                       strcat(nl, "\033[2m\033[2m");
+                                               }
+                                               for (j = 0; j < blockquote; ++j) {
+                                                       strcat(nl, ">");
+                                               }
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               linelen = 0;
+                                       }
 
 
-                       else if (!strncasecmp(&outbuf[i], "&agrave;", 8)) {
-                               outbuf[i] = 'a';
-                               strcpy(&outbuf[i+1], &outbuf[i+8]);
-                       }
+                                       else if (!strcasecmp(tag, "/BLOCKQUOTE")) {
+                                               StrBufAppendBufPlain(out, HKEY("\n"), 0);
+                                               --blockquote;
+                                               if ((blockquote == 0) && (ansi)) {
+                                                       StrBufAppendBufPlain(out, HKEY("\033[22m\033[22m"), 0);
+                                               }
+                                               strcpy(nl, "\n");
+                                               for (j = 0; j < blockquote; ++j) {
+                                                       strcat(nl, ">");
+                                               }
+                                               StrBufAppendBufPlain(out, nl, -1, 0);
+                                               linelen = 0;
+                                       }
 
 
-                       else if (!strncasecmp(&outbuf[i], "&ldquo;", 7)) {
-                               outbuf[i] = '\"';
-                               strcpy(&outbuf[i+1], &outbuf[i+7]);
-                       }
+                                       else if (!strcasecmp(tag, "STYLE")) {
+                                               ++styletag;
+                                       }
 
 
-                       else if (!strncasecmp(&outbuf[i], "&rdquo;", 7)) {
-                               outbuf[i] = '\"';
-                               strcpy(&outbuf[i+1], &outbuf[i+7]);
-                       }
+                                       else if (!strcasecmp(tag, "/STYLE")) {
+                                               --styletag;
+                                       }
+                               }
 
 
-                       else if (!strncasecmp(&outbuf[i], "&acute;", 7)) {
-                               outbuf[i] = '\'';
-                               strcpy(&outbuf[i+1], &outbuf[i+7]);
                        }
 
                        }
 
-                       else if (!strncasecmp(&outbuf[i], "&#8217;", 7)) {
-                               outbuf[i] = '\'';
-                               strcpy(&outbuf[i+1], &outbuf[i+7]);
+                       // copy non-tag text to the output buffer
+                       else if ((!tag_nesting_level) && (styletag == 0)) {
+                               StrBufAppendBufPlain(out, &ch, 1, 0);
+                               ++linelen;
                        }
 
                        }
 
-                       else if (!strncasecmp(&outbuf[i], "&#8211;", 7)) {
-                               outbuf[i] = '-';
-                               strcpy(&outbuf[i+1], &outbuf[i+7]);
-                       }
+                       // Handle numeric entities
+                       if (ch == ';') {
 
 
-                       // two-digit decimal equivalents
-                       else if (outbuf[i] == '&'       &&
-                                outbuf[i + 1] == '#'   &&
-                                isdigit(outbuf[i + 2]) && 
-                                isdigit(outbuf[i + 3]) &&
-                                (outbuf[i+4] == ';') ) 
-                       {
-                               scanch = 0;
-                               sscanf(&outbuf[i+2], "%02d", &scanch);
-                               outbuf[i] = scanch;
-                               strcpy(&outbuf[i+1], &outbuf[i+5]);
-                       }
+                               u_int32_t scanch = 0;
+                               int elen = 0;
 
 
-                       // three-digit decimal equivalents
-                       else if (outbuf[i] == '&'       &&
-                                outbuf[i + 1] == '#'   &&
-                                isdigit(outbuf[i + 2]) && 
-                                isdigit(outbuf[i + 3]) && 
-                                isdigit(outbuf[i + 4]) &&
-                                (outbuf[i + 5] == ';') ) 
-                       {
-                               scanch = 0;
-                               sscanf(&outbuf[i+2], "%03d", &scanch);
-                               outbuf[i] = scanch;
-                               strcpy(&outbuf[i+1], &outbuf[i+6]);
-                       }
+                               if ( (linelen >= 5) && (*(inptr-4) == '&') && (*(inptr-3) == '#') ) {
+                                       sscanf(inptr-2, "%02d", &scanch);
+                                       elen = 5;
+                               }
+                               else if ( (linelen >= 6) && (*(inptr-5) == '&') && (*(inptr-4) == '#') ) {
+                                       sscanf(inptr-3, "%03d", &scanch);
+                                       elen = 6;
+                               }
+                               else if ( (linelen >= 7) && (*(inptr-6) == '&') && (*(inptr-5) == '#') ) {
+                                       sscanf(inptr-3, "%04d", &scanch);
+                                       elen = 7;
+                               }
+                               else if ( (linelen >= 8) && (*(inptr-7) == '&') && (*(inptr-6) == '#') ) {
+                                       sscanf(inptr-4, "%05d", &scanch);
+                                       elen = 8;
+                               }
 
 
-                       // four-digit decimal equivalents
-                       else if (outbuf[i] == '&'       &&
-                                outbuf[i + 1] == '#'   &&
-                                isdigit(outbuf[i + 2]) && 
-                                isdigit(outbuf[i + 3]) && 
-                                isdigit(outbuf[i + 4]) &&
-                                isdigit(outbuf[i + 5]) &&
-                                (outbuf[i + 6] == ';') ) 
-                       {
-                               scanch = 0;
-                               sscanf(&outbuf[i+2], "%04d", &scanch);
-                               outbuf[i] = scanch;
-                               strcpy(&outbuf[i+1], &outbuf[i+7]);
-                       }
+                               if (scanch) {
+                                       StrBufCutRight(out, elen);
+                                       linelen -= elen;
 
 
-               }
+                                       char utf[5];
+                                       int ulen = u8_wc_toutf8(utf, scanch);
+                                       utf[ulen] = 0;
+                                       StrBufAppendBufPlain(out, utf, ulen, 0);
+                                       linelen += elen;
+                               }
 
 
-               // Make sure the output buffer is big enough
-               if ((output_len + strlen(outbuf) + SIZ) > outptr_buffer_size) {
-                       outptr_buffer_size += SIZ;
-                       outptr = realloc(outptr, outptr_buffer_size);
-                       if (outptr == NULL) {
-                               abort();
                        }
                        }
-               }
-
-               // Output any lines terminated with hard line breaks
-               do {
-                       did_out = 0;
-                       if (strlen(outbuf) > 0) {
-                           for (i = 0; i<strlen(outbuf); ++i) {
-                               if ( (i<(screenwidth-2)) && (outbuf[i]=='\n')) {
-
-                                       strncpy(&outptr[output_len], outbuf, i+1);
-                                       output_len += (i+1);
 
 
-                                       strcpy(outbuf, &outbuf[i+1]);
-                                       i = 0;
-                                       did_out = 1;
+                       // Add soft line breaks when necessary
+                       if (linelen > (screenwidth - 8)) {
+                               char *ptr = (char *)ChrPtr(out) + StrLength(out) - linelen;
+                               char *rightmost_space = strrchr(ptr, ' ');
+                               if (rightmost_space && rightmost_space > ptr) {
+                                       int space_pos = rightmost_space - ChrPtr(out);
+                                       StrBufReplaceToken(out, (long)space_pos, 1, nl, strlen(nl));
+                                       linelen = strlen(rightmost_space) - 1;
                                }
                        }
                                }
                        }
-                   }
-               } while (did_out);
-
-               // Add soft line breaks
-               if (strlen(outbuf) > (screenwidth - 2 )) {
-                       rb = (-1);
-                       for (i=0; i<(screenwidth-2); ++i) {
-                               if (outbuf[i]==32) rb = i;
-                       }
-                       if (rb>=0) {
-                               strncpy(&outptr[output_len], outbuf, rb);
-                               output_len += rb;
-                               strcpy(&outptr[output_len], nl);
-                               output_len += strlen(nl);
-                               strcpy(outbuf, &outbuf[rb+1]);
-                       }
-                       else {
-                               strncpy(&outptr[output_len], outbuf, screenwidth-2);
-                               output_len += (screenwidth-2);
-                               strcpy(&outptr[output_len], nl);
-                               output_len += strlen(nl);
-                               strcpy(outbuf, &outbuf[screenwidth-2]);
-                       }
-               }
-
-       } while (done_reading == 0);
 
 
-       strcpy(&outptr[output_len], outbuf);
-       output_len += strlen(outbuf);
-
-       // Strip leading/trailing whitespace.
+                       // Advance to the next byte of input.
+                       inptr++;
+               }
+       }
+       free(inbuf);
+
+       // Convert entity tags to printable characters
+       StrBufReplaceAllOccurrences(out, "&nbsp;", " ");
+       StrBufReplaceAllOccurrences(out, "&ensp;", " ");
+       StrBufReplaceAllOccurrences(out, "&emsp;", " ");
+       StrBufReplaceAllOccurrences(out, "&thinsp;", " ");
+       StrBufReplaceAllOccurrences(out, "&lt;", "<");
+       StrBufReplaceAllOccurrences(out, "&gt;", ">");
+       StrBufReplaceAllOccurrences(out, "&amp;", "&");
+       StrBufReplaceAllOccurrences(out, "&quot;", "\"");
+       StrBufReplaceAllOccurrences(out, "&lsquo;", "`");
+       StrBufReplaceAllOccurrences(out, "&rsquo;", "'");
+       StrBufReplaceAllOccurrences(out, "&bull;", " * ");
+       StrBufReplaceAllOccurrences(out, "&hellip;", "…");
+       StrBufReplaceAllOccurrences(out, "&copy;", "©");
+       StrBufReplaceAllOccurrences(out, "&trade;", "™");
+       StrBufReplaceAllOccurrences(out, "&reg;", "®");
+       StrBufReplaceAllOccurrences(out, "&frac14;", "¼");
+       StrBufReplaceAllOccurrences(out, "&frac12;", "½");
+       StrBufReplaceAllOccurrences(out, "&frac34;", "¾");
+       StrBufReplaceAllOccurrences(out, "&ndash;", "–");
+       StrBufReplaceAllOccurrences(out, "&mdash;", "—");
+       StrBufReplaceAllOccurrences(out, "&Ccedil;", "Ç");
+       StrBufReplaceAllOccurrences(out, "&ccedil;", "ç");
+       StrBufReplaceAllOccurrences(out, "&Egrave;", "È");
+       StrBufReplaceAllOccurrences(out, "&egrave;", "è");
+       StrBufReplaceAllOccurrences(out, "&Ecirc;", "Ê");
+       StrBufReplaceAllOccurrences(out, "&ecirc;", "ê");
+       StrBufReplaceAllOccurrences(out, "&Eacute;", "É");
+       StrBufReplaceAllOccurrences(out, "&eacute;", "é");
+       StrBufReplaceAllOccurrences(out, "&Agrave;", "À");
+       StrBufReplaceAllOccurrences(out, "&agrave;", "à");
+       StrBufReplaceAllOccurrences(out, "&ldquo;", "\"");
+       StrBufReplaceAllOccurrences(out, "&rdquo;", "\"");
+       StrBufReplaceAllOccurrences(out, "&acute;", "'");
+       StrBufReplaceAllOccurrences(out, "&#8217;", "'");
+       StrBufReplaceAllOccurrences(out, "&#8211;", "-");
+
+       // Convert from a StrBuf to a plain C string
+       int output_len = StrLength(out);
+       outptr = SmashStrBuf(&out);
+
+       // Strip leading whitespace
        while ((output_len > 0) && (isspace(outptr[0]))) {
                strcpy(outptr, &outptr[1]);
                --output_len;
        }
        while ((output_len > 0) && (isspace(outptr[0]))) {
                strcpy(outptr, &outptr[1]);
                --output_len;
        }
-       while ((output_len > 0) && (isspace(outptr[output_len-1]))) {
-               outptr[output_len-1] = 0;
+
+       // Strip trailing whitespace
+       while ((output_len > 0) && (isspace(outptr[output_len - 1]))) {
+               outptr[output_len - 1] = 0;
                --output_len;
        }
 
        // Make sure the final line ends with a newline character.
                --output_len;
        }
 
        // Make sure the final line ends with a newline character.
-       if ((output_len > 0) && (outptr[output_len-1] != '\n')) {
+       if ((output_len > 0) && (outptr[output_len - 1] != '\n')) {
                strcat(outptr, "\n");
                ++output_len;
        }
 
        return outptr;
                strcat(outptr, "\n");
                ++output_len;
        }
 
        return outptr;
-
 }
 }
index 1817187371f798c931caad30603ed092496e13a6..9601f0dc10e65242c125e7ecf6fe974b5cc7eb60 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.
@@ -252,6 +252,7 @@ int StrBufReplaceToken(StrBuf *Buf, long where, long HowLong, const char *Repl,
 int StrBufExtract_tokenFromStr(StrBuf *dest, const char *Source, long SourceLen, int parmnum, char separator);
 int StrBufExtract_token(StrBuf *dest, const StrBuf *Source, int parmnum, char separator);
 int StrBufSub(StrBuf *dest, const StrBuf *Source, unsigned long Offset, size_t nChars);
 int StrBufExtract_tokenFromStr(StrBuf *dest, const char *Source, long SourceLen, int parmnum, char separator);
 int StrBufExtract_token(StrBuf *dest, const StrBuf *Source, int parmnum, char separator);
 int StrBufSub(StrBuf *dest, const StrBuf *Source, unsigned long Offset, size_t nChars);
+int StrBufReplaceAllOccurrences(StrBuf *Buf, const char *fromstr, const char *tostr);
 
 unsigned long StrBufExtract_unsigned_long(const StrBuf* Source, int parmnum, char separator);
 long StrBufExtract_long(const StrBuf* Source, int parmnum, char separator);
 
 unsigned long StrBufExtract_unsigned_long(const StrBuf* Source, int parmnum, char separator);
 long StrBufExtract_long(const StrBuf* Source, int parmnum, char separator);
@@ -430,9 +431,15 @@ void CtdlMakeTempFileName(char *name, int len);
 char *rfc2047encode(const char *line, long length);
 int is_msg_in_mset(const char *mset, long msgnum);
 int pattern2(char *search, char *patn);
 char *rfc2047encode(const char *line, long length);
 int is_msg_in_mset(const char *mset, long msgnum);
 int pattern2(char *search, char *patn);
-char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, int ansi);
 void LoadEntityList(char *FileName);
 void utf8ify_rfc822_string(char *buf);
 void LoadEntityList(char *FileName);
 void utf8ify_rfc822_string(char *buf);
+int u8_wc_toutf8(char *dest, u_int32_t ch);
+void h2a_embed_image(StrBuf *out, char *url, int display_protocol);
+
+// flags for html_to_ascii
+#define H2A_ANSI       0x01            // it is acceptable to display ANSI graphics on this terminal
+#define H2A_SIXEL      0x02            // sixel graphics are supported (not yet fully implemented)
+char *html_to_ascii(const char *inputmsg, int msglen, int screenwidth, unsigned int flags);
 
 
 typedef struct {
 
 
 typedef struct {
index b382025b957fc9d103d40877e1c30b835c56190b..fb1eb14266f2c803216f93f76f1a58bd694a49ee 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 truncated?
+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 truncate 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,27 @@ 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
- */
+
+int StrBufReplaceAllOccurrences(StrBuf *Buf, const char *fromstr, const char *tostr) {
+
+       char *found;
+       while (found = strstr(Buf->buf, fromstr), (found != NULL)) {
+               StrBufReplaceToken(Buf, (found - Buf->buf), strlen(fromstr), tostr, strlen(tostr));
+       }
+
+       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
 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 +1101,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 +1127,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 +1138,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 +1155,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 +1181,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 +1241,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 +1261,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 +1283,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 +1309,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 +1761,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 +1861,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 +1970,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 +2035,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 +2104,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 +2796,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 +2815,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 +2867,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 +3199,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 +3237,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 +3261,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 +3293,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 +3443,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 +3621,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 +3644,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 +3654,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 +3683,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 +3786,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 +3826,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 +4019,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 +4055,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 +4379,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 ba1be6cb3ce5ce958e1fdc893543fbba34ef943a..b5682cfb28dea9fd065d4ffc9a7c59dc8db23cd2 100644 (file)
@@ -52,6 +52,7 @@ int secure;                   /* Set to nonzero when wire is encrypted */
 
 extern char instant_msgs;      /* instant messages waiting! */
 extern int rc_ansi_color;      /* ansi color value from citadel.rc */
 
 extern char instant_msgs;      /* instant messages waiting! */
 extern int rc_ansi_color;      /* ansi color value from citadel.rc */
+extern int rc_sixel;           /* sixel graphics value from citadel.rc */
 extern int next_lazy_cmd;
 
 CtdlIPC *ipc_for_signal_handlers;      /* KLUDGE cover your eyes */
 extern int next_lazy_cmd;
 
 CtdlIPC *ipc_for_signal_handlers;      /* KLUDGE cover your eyes */
index 7f184dbcf4319fbc7309917d491a4443bc317006..263577e2b1faf2bfb838d3a8b33edcc8e208aa60 100644 (file)
@@ -36,6 +36,13 @@ encrypt=default
 #
 ansi_color=user
 
 #
 ansi_color=user
 
+# Sixel graphics support (experimental)
+# Set this to "on" to output images embedded in HTML messages to the terminal
+# in Sixel graphics format.  This obviously requires a terminal that has
+# support for Sixel.  It also requires the "curl" and "img2sixel" commands to
+# be available on your system.
+use_sixel=off
+
 # USE_BACKGROUND controls Citadel's use of the background.  If it is turned
 # off, then Citadel will set the background to black.  When it is turned on,
 # the background will be unchanged.  This is most useful with "transparent"
 # USE_BACKGROUND controls Citadel's use of the background.  If it is turned
 # off, then Citadel will set the background to black.  When it is turned on,
 # the background will be unchanged.  This is most useful with "transparent"
index c241f014d70a08c55c24e164cb0df6b889564044..6d39ca9b25b952f6ae48eaf7957529f1ac50c152 100644 (file)
@@ -423,6 +423,7 @@ int rc_display_message_numbers;
 int rc_force_mail_prompts;
 int rc_remember_passwords;
 int rc_ansi_color;
 int rc_force_mail_prompts;
 int rc_remember_passwords;
 int rc_ansi_color;
+int rc_sixel;
 int rc_color_use_bg;
 int rc_prompt_control = 0;
 time_t rc_idle_threshold = (time_t) 900;
 int rc_color_use_bg;
 int rc_prompt_control = 0;
 time_t rc_idle_threshold = (time_t) 900;
@@ -889,6 +890,7 @@ void load_command_set(void) {
        rc_display_message_numbers = 0;
        rc_force_mail_prompts = 0;
        rc_ansi_color = 0;
        rc_display_message_numbers = 0;
        rc_force_mail_prompts = 0;
        rc_ansi_color = 0;
+       rc_sixel = 0;
        rc_color_use_bg = 0;
        strcpy(rc_url_cmd, "");
        strcpy(rc_open_cmd, "");
        rc_color_use_bg = 0;
        strcpy(rc_url_cmd, "");
        strcpy(rc_open_cmd, "");
@@ -997,6 +999,10 @@ void load_command_set(void) {
                        if (!strncasecmp(&buf[12], "on", 2))
                                enable_status_line = 1;
                }
                        if (!strncasecmp(&buf[12], "on", 2))
                                enable_status_line = 1;
                }
+               if (!strncasecmp(buf, "use_sixel=", 10)) {
+                       if (!strncasecmp(&buf[10], "on", 2))
+                               rc_sixel = 1;
+               }
                if (!strncasecmp(buf, "use_background=", 15)) {
                        if (!strncasecmp(&buf[15], "on", 2))
                                rc_color_use_bg = 9;
                if (!strncasecmp(buf, "use_background=", 15)) {
                        if (!strncasecmp(&buf[15], "on", 2))
                                rc_color_use_bg = 9;
index 343d2064fedad6b06315e1b1baa4fdf04d1271ca..183ed0f93db0b4caa0980c7757f1f08abb9c91e9 100644 (file)
@@ -4,7 +4,7 @@
 // late 1980s when my coding style was absolute garbage.  It
 // works, but we probably should replace most of it.
 //
 // late 1980s when my coding style was absolute garbage.  It
 // works, but we probably should replace most of it.
 //
-// Copyright (c) 1987-2022 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 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.
 
@@ -53,13 +53,14 @@ extern char printcmd[];
 extern int rc_allow_attachments;
 extern int rc_display_message_numbers;
 extern int rc_force_mail_prompts;
 extern int rc_allow_attachments;
 extern int rc_display_message_numbers;
 extern int rc_force_mail_prompts;
+extern int rc_sixel;
 extern int editor_pid;
 extern int editor_pid;
-extern CtdlIPC *ipc_for_signal_handlers;       /* KLUDGE cover your eyes */
+extern CtdlIPC *ipc_for_signal_handlers;       // KLUDGE cover your eyes
 int num_urls = 0;
 char urls[MAXURLS][SIZ];
 char imagecmd[SIZ];
 int num_urls = 0;
 char urls[MAXURLS][SIZ];
 char imagecmd[SIZ];
-int has_images = 0;            /* Current msg has images */
-struct parts *last_message_parts = NULL;       /* Parts from last msg */
+int has_images = 0;                            // Current msg has images
+struct parts *last_message_parts = NULL;       // Parts from last msg
 
 
 void ka_sigcatch(int signum) {
 
 
 void ka_sigcatch(int signum) {
@@ -69,9 +70,7 @@ void ka_sigcatch(int signum) {
 }
 
 
 }
 
 
-/*
- * server keep-alive version of wait() (needed for external editor)
- */
+// server keep-alive version of wait() (needed for external editor)
 pid_t ka_wait(int *kstatus) {
        pid_t p;
 
 pid_t ka_wait(int *kstatus) {
        pid_t p;
 
@@ -87,9 +86,7 @@ pid_t ka_wait(int *kstatus) {
 }
 
 
 }
 
 
-/*
- * version of system() that uses ka_wait()
- */
+// version of system() that uses ka_wait()
 int ka_system(char *shc) {
        pid_t childpid;
        pid_t waitpid;
 int ka_system(char *shc) {
        pid_t childpid;
        pid_t waitpid;
@@ -119,9 +116,7 @@ int ka_system(char *shc) {
 }
 
 
 }
 
 
-/*
- * add a newline to the buffer...
- */
+// add a newline to the buffer...
 void add_newline(struct cittext *textlist) {
        struct cittext *ptr;
 
 void add_newline(struct cittext *textlist) {
        struct cittext *ptr;
 
@@ -140,9 +135,7 @@ void add_newline(struct cittext *textlist) {
 }
 
 
 }
 
 
-/*
- * add a word to the buffer...
- */
+// add a word to the buffer...
 void add_word(struct cittext *textlist, char *wordbuf) {
        struct cittext *ptr;
 
 void add_word(struct cittext *textlist, char *wordbuf) {
        struct cittext *ptr;
 
@@ -163,9 +156,7 @@ void add_word(struct cittext *textlist, char *wordbuf) {
 }
 
 
 }
 
 
-/*
- * begin editing of an opened file pointed to by fp
- */
+// begin editing of an opened file pointed to by fp
 void citedit(FILE * fp) {
        int a, prev, finished, b, last_space;
        int appending = 0;
 void citedit(FILE * fp) {
        int a, prev, finished, b, last_space;
        int appending = 0;
@@ -174,7 +165,7 @@ void citedit(FILE * fp) {
        char wordbuf[MAXWORDBUF];
        int rv = 0;
 
        char wordbuf[MAXWORDBUF];
        int rv = 0;
 
-       /* first, load the text into the buffer */
+       // first, load the text into the buffer
        fseek(fp, 0L, 0);
        textlist = (struct cittext *) malloc(sizeof(struct cittext));
        textlist->next = NULL;
        fseek(fp, 0L, 0);
        textlist = (struct cittext *) malloc(sizeof(struct cittext));
        textlist->next = NULL;
@@ -204,7 +195,7 @@ void citedit(FILE * fp) {
                prev = a;
        }
 
                prev = a;
        }
 
-       /* get text */
+       // get text
        finished = 0;
        prev = (appending ? 13 : (-1));
        strcpy(wordbuf, "");
        finished = 0;
        prev = (appending ? 13 : (-1));
        strcpy(wordbuf, "");
@@ -293,7 +284,7 @@ void citedit(FILE * fp) {
                prev = a;
        } while (finished == 0);
 
                prev = a;
        } while (finished == 0);
 
-       /* write the buffer back to disk */
+       // write the buffer back to disk
        fseek(fp, 0L, 0);
        for (ptr = textlist; ptr != NULL; ptr = ptr->next) {
                fprintf(fp, "%s", ptr->text);
        fseek(fp, 0L, 0);
        for (ptr = textlist; ptr != NULL; ptr = ptr->next) {
                fprintf(fp, "%s", ptr->text);
@@ -305,7 +296,7 @@ void citedit(FILE * fp) {
                scr_printf("failed to set message buffer: %s\n", strerror(errno));
 
 
                scr_printf("failed to set message buffer: %s\n", strerror(errno));
 
 
-       /* and deallocate the memory we used */
+       // and deallocate the memory we used
        while (textlist != NULL) {
                ptr = textlist->next;
                free(textlist);
        while (textlist != NULL) {
                ptr = textlist->next;
                free(textlist);
@@ -314,9 +305,7 @@ void citedit(FILE * fp) {
 }
 
 
 }
 
 
-/*
- * Free the struct parts
- */
+// Free the struct parts
 void free_parts(struct parts *p) {
        struct parts *a_part = p;
 
 void free_parts(struct parts *p) {
        struct parts *a_part = p;
 
@@ -330,11 +319,9 @@ void free_parts(struct parts *p) {
 }
 
 
 }
 
 
-/*
- * This is a mini RFC2047 decoder.
- * It only handles strings encoded from UTF-8 as Quoted-printable.
- * We can do this "in place" because the converted string will always be smaller than the source string.
- */
+// This is a mini RFC2047 decoder.
+// It only handles strings encoded from UTF-8 as Quoted-printable.
+// We can do this "in place" because the converted string will always be smaller than the source string.
 void mini_2047_decode(char *s) {
        if (!s) {               // no null strings allowed!
                return;
 void mini_2047_decode(char *s) {
        if (!s) {               // no null strings allowed!
                return;
@@ -389,20 +376,19 @@ void mini_2047_decode(char *s) {
 }
 
 
 }
 
 
-/*
- * Read a message from the server
- */
-int read_message(CtdlIPC * ipc, long num,      /* message number */
-                int pagin,     /* 0 = normal read, 1 = read with pagination, 2 = header */
-                FILE * dest    /* Destination file, NULL for screen */
-    ) {
+// Read a message from the server
+int read_message(CtdlIPC *ipc,
+       long num,       // message number
+       int pagin,      // 0 = normal read, 1 = read with pagination, 2 = header
+       FILE *dest      // Destination file, NULL for screen
+) {
        char buf[SIZ];
        char now[256];
        int format_type = 0;
        int fr = 0;
        int nhdr = 0;
        struct ctdlipcmessage *message = NULL;
        char buf[SIZ];
        char now[256];
        int format_type = 0;
        int fr = 0;
        int nhdr = 0;
        struct ctdlipcmessage *message = NULL;
-       int r;                  /* IPC response code */
+       int r;                  // IPC response code
        char *converted_text = NULL;
        char *lineptr;
        char *nextline;
        char *converted_text = NULL;
        char *lineptr;
        char *nextline;
@@ -444,7 +430,7 @@ int read_message(CtdlIPC * ipc, long num,   /* message number */
                color(BRIGHT_CYAN);
        }
 
                color(BRIGHT_CYAN);
        }
 
-       /* View headers only */
+       // View headers only
        if (pagin == 2) {
                scr_printf("nhdr=%s\nfrom=%s\ntype=%d\nmsgn=%s\n",
                           message->nhdr ? "yes" : "no", message->author, message->type, message->msgid);
        if (pagin == 2) {
                scr_printf("nhdr=%s\nfrom=%s\ntype=%d\nmsgn=%s\n",
                           message->nhdr ? "yes" : "no", message->author, message->type, message->msgid);
@@ -598,14 +584,13 @@ int read_message(CtdlIPC * ipc, long num, /* message number */
                color(BRIGHT_WHITE);
        }
 
                color(BRIGHT_WHITE);
        }
 
-       /******* end of header output, start of message text output *******/
+       // ****** end of header output, start of message text output ******
 
 
-       /*
-        * Convert HTML to plain text, formatting for the actual width
-        * of the client screen.
-        */
+       // Convert HTML to plain text, formatting for the actual width of the client screen.
        if (!strcasecmp(message->content_type, "text/html")) {
        if (!strcasecmp(message->content_type, "text/html")) {
-               converted_text = html_to_ascii(message->text, 0, screenwidth, (enable_color ? 1 : 0));
+               converted_text = html_to_ascii(message->text, 0, screenwidth,
+                       ((enable_color ? H2A_ANSI : 0) | (rc_sixel ? H2A_SIXEL : 0))
+               );
                if (converted_text != NULL) {
                        free(message->text);
                        message->text = converted_text;
                if (converted_text != NULL) {
                        free(message->text);
                        message->text = converted_text;
@@ -613,24 +598,24 @@ int read_message(CtdlIPC * ipc, long num, /* message number */
                }
        }
 
                }
        }
 
-       /* Text/plain is a different type */
+       // Text/plain is a different type
        if (!strcasecmp(message->content_type, "text/plain")) {
                format_type = 1;
        }
 
        if (!strcasecmp(message->content_type, "text/plain")) {
                format_type = 1;
        }
 
-       /* Render text/x-markdown as plain text */
+       // Render text/x-markdown as plain text
        if (!strcasecmp(message->content_type, "text/x-markdown")) {
                format_type = 1;
        }
 
        if (!strcasecmp(message->content_type, "text/x-markdown")) {
                format_type = 1;
        }
 
-       /* Extract URL's */
+       // Extract URL's
        static char *urlprefixes[] = {
                "http://",
                "https://",
                "ftp://"
        };
        int p = 0;
        static char *urlprefixes[] = {
                "http://",
                "https://",
                "ftp://"
        };
        int p = 0;
-       num_urls = 0;           /* Start with a clean slate */
+       num_urls = 0;           // Start with a clean slate
        for (p = 0; p < (sizeof urlprefixes / sizeof(char *)); ++p) {
                searchptr = message->text;
                while ((searchptr != NULL) && (num_urls < MAXURLS)) {
        for (p = 0; p < (sizeof urlprefixes / sizeof(char *)); ++p) {
                searchptr = message->text;
                while ((searchptr != NULL) && (num_urls < MAXURLS)) {
@@ -650,17 +635,14 @@ int read_message(CtdlIPC * ipc, long num, /* message number */
                }
        }
 
                }
        }
 
-       /*
-        * Here we go
-        */
+       // Here we go
        if (format_type == 0) {
        if (format_type == 0) {
+               // renderer for legacy Citadel format
                fr = fmout(screenwidth, NULL, message->text, dest, 1);
        }
        else {
                fr = fmout(screenwidth, NULL, message->text, dest, 1);
        }
        else {
-               /* renderer for text/plain */
-
+               // renderer for text/plain
                lineptr = message->text;
                lineptr = message->text;
-
                do {
                        nextline = strchr(lineptr, '\n');
                        if (nextline != NULL) {
                do {
                        nextline = strchr(lineptr, '\n');
                        if (nextline != NULL) {
@@ -700,18 +682,18 @@ int read_message(CtdlIPC * ipc, long num, /* message number */
                }
        }
 
                }
        }
 
-       /* Enumerate any attachments */
+       // Enumerate any attachments
        if ((pagin == 1) && (message->attachments)) {
                struct parts *ptr;
 
                for (ptr = message->attachments; ptr; ptr = ptr->next) {
        if ((pagin == 1) && (message->attachments)) {
                struct parts *ptr;
 
                for (ptr = message->attachments; ptr; ptr = ptr->next) {
-                       if ((!strcasecmp(ptr->disposition, "attachment"))
-                           || (!strcasecmp(ptr->disposition, "inline"))
-                           || (!strcasecmp(ptr->disposition, ""))
-                           ) {
-                               if ((strcasecmp(ptr->number, message->mime_chosen))
-                                   && (!IsEmptyStr(ptr->mimetype))
-                                   ) {
+                       if (    (!strcasecmp(ptr->disposition, "attachment"))
+                               || (!strcasecmp(ptr->disposition, "inline"))
+                               || (!strcasecmp(ptr->disposition, ""))
+                       ) {
+                               if (    (strcasecmp(ptr->number, message->mime_chosen))
+                                       && (!IsEmptyStr(ptr->mimetype))
+                               ) {
                                        color(DIM_WHITE);
                                        scr_printf("Part ");
                                        color(BRIGHT_MAGENTA);
                                        color(DIM_WHITE);
                                        scr_printf("Part ");
                                        color(BRIGHT_MAGENTA);
@@ -730,23 +712,22 @@ int read_message(CtdlIPC * ipc, long num, /* message number */
                }
        }
 
                }
        }
 
-       /* Save the attachments info for later */
+       // Save the attachments info for later
        last_message_parts = message->attachments;
 
        last_message_parts = message->attachments;
 
-       /* Now we're done */
+       // Now we're done
        free(message->text);
        free(message);
 
        free(message->text);
        free(message);
 
-       if (pagin == 1 && !dest)
+       if (pagin == 1 && !dest) {
                color(DIM_WHITE);
                color(DIM_WHITE);
+       }
        stty_ctdl(0);
        return (fr);
 }
 
 
        stty_ctdl(0);
        return (fr);
 }
 
 
-/*
- * replace string function for the built-in editor
- */
+// replace string function for the built-in editor
 void replace_string(char *filename, long int startpos) {
        char buf[512];
        char srch_str[128];
 void replace_string(char *filename, long int startpos) {
        char buf[512];
        char srch_str[128];
@@ -791,7 +772,7 @@ void replace_string(char *filename, long int startpos) {
                        rv = fwrite((char *) buf, 128, 1, fp);
                        if (rv < 0) {
                                scr_printf("failed to replace string: %s\n", strerror(errno));
                        rv = fwrite((char *) buf, 128, 1, fp);
                        if (rv < 0) {
                                scr_printf("failed to replace string: %s\n", strerror(errno));
-                               break;  /*whoopsi! */
+                               break;          // No replacement happened; break out of the loop
                        }
                        strcpy(buf, &buf[128]);
                        wpos = ftell(fp);
                        }
                        strcpy(buf, &buf[128]);
                        wpos = ftell(fp);
@@ -810,10 +791,15 @@ void replace_string(char *filename, long int startpos) {
 
 
 // Function to begin composing a new message
 
 
 // Function to begin composing a new message
-int client_make_message(CtdlIPC * ipc, char *filename, // temporary file name
-                       char *recipient,        // NULL if it's not mail
-                       int is_anonymous, int format_type, int mode, char *subject,     // buffer to store subject line
-                       int subject_required) {
+int client_make_message(CtdlIPC *ipc,
+               char *filename,         // temporary file name
+               char *recipient,        // NULL if it's not mail
+               int is_anonymous,
+               int format_type,
+               int mode,
+               char *subject,          // buffer to store subject line
+               int subject_required
+) {
        FILE *fp;
        int a, b, e_ex_code;
        long beg;
        FILE *fp;
        int a, b, e_ex_code;
        long beg;
@@ -875,7 +861,7 @@ int client_make_message(CtdlIPC * ipc, char *filename,      // temporary file name
                }
        }
 
                }
        }
 
-      ME1:switch (mode) {
+ME1:   switch (mode) {
 
        case 0:
                fp = fopen(filename, "r+");
 
        case 0:
                fp = fopen(filename, "r+");
@@ -911,8 +897,8 @@ int client_make_message(CtdlIPC * ipc, char *filename,      // temporary file name
                break;
 
        case 2:
                break;
 
        case 2:
-       default:                /* allow 2+ modes */
-               e_ex_code = 1;  /* start with a failed exit code */
+       default:                // allow 2+ modes
+               e_ex_code = 1;  // start with a failed exit code
                stty_ctdl(SB_RESTORE);
                editor_pid = fork();
                cksum = file_checksum(filename);
                stty_ctdl(SB_RESTORE);
                editor_pid = fork();
                cksum = file_checksum(filename);
@@ -1001,9 +987,7 @@ int client_make_message(CtdlIPC * ipc, char *filename,     // temporary file name
 }
 
 
 }
 
 
-/*
- * Make sure there's room in msg_arr[] for at least one more.
- */
+// Make sure there's room in msg_arr[] for at least one more.
 void check_msg_arr_size(void) {
        if ((num_msgs + 1) > msg_arr_size) {
                msg_arr_size += 512;
 void check_msg_arr_size(void) {
        if ((num_msgs + 1) > msg_arr_size) {
                msg_arr_size += 512;
@@ -1012,10 +996,7 @@ void check_msg_arr_size(void) {
 }
 
 
 }
 
 
-/*
- * break_big_lines()  -  break up lines that are >1024 characters
- *                       otherwise the server will truncate
- */
+// break_big_lines()  -  break up lines that are >1024 characters, otherwise the server will truncate them.
 void break_big_lines(char *msg) {
        char *ptr;
        char *break_here;
 void break_big_lines(char *msg) {
        char *ptr;
        char *break_here;
@@ -1036,14 +1017,13 @@ void break_big_lines(char *msg) {
 }
 
 
 }
 
 
-/*
- * entmsg()  -  edit and create a message
- *              returns 0 if message was saved
- */
-int entmsg(CtdlIPC * ipc, int is_reply,        /* nonzero if this was a <R>eply command */
-          int c,               /* mode */
-          int masquerade       /* prompt for a non-default display name? */
-    ) {
+// entmsg()  -  edit and create a message
+//              returns 0 if message was saved
+int entmsg(CtdlIPC *ipc,
+       int is_reply,   // nonzero if this was a <R>eply command
+       int c,          // mode
+       int masquerade  // prompt for a non-default display name?
+) {
        char buf[SIZ];
        int a, b;
        int need_recp = 0;
        char buf[SIZ];
        int a, b;
        int need_recp = 0;
@@ -1053,15 +1033,13 @@ int entmsg(CtdlIPC * ipc, int is_reply, /* nonzero if this was a <R>eply command
        char subject[SIZ];
        struct ctdlipcmessage message;
        unsigned long *msgarr = NULL;
        char subject[SIZ];
        struct ctdlipcmessage message;
        unsigned long *msgarr = NULL;
-       int r;                  /* IPC response code */
+       int r;                  // IPC response code
        int subject_required = 0;
 
        int subject_required = 0;
 
-       /*
-        * First, check to see if we have permission to enter a message in
-        * this room.  The server will return an error code if we can't.
-        */
+       // First, check to see if we have permission to enter a message in
+       // this room.  The server will return an error code if we can't.
        if (entmsg_ok == ENTMSG_OK_YES) {
        if (entmsg_ok == ENTMSG_OK_YES) {
-               /* no problem, go right ahead */
+               // no problem, go right ahead
        }
        else if (entmsg_ok == ENTMSG_OK_BLOG) {
                if (!is_reply) {
        }
        else if (entmsg_ok == ENTMSG_OK_BLOG) {
                if (!is_reply) {
@@ -1092,7 +1070,7 @@ int entmsg(CtdlIPC * ipc, int is_reply,   /* nonzero if this was a <R>eply command
        strcpy(message.author, "");
        strcpy(message.subject, "");
        strcpy(message.references, "");
        strcpy(message.author, "");
        strcpy(message.subject, "");
        strcpy(message.references, "");
-       message.text = "";      /* point to "", changes later */
+       message.text = "";      // point to "", changes later
        message.anonymous = 0;
        message.type = mode;
 
        message.anonymous = 0;
        message.type = mode;
 
@@ -1111,9 +1089,8 @@ int entmsg(CtdlIPC * ipc, int is_reply,   /* nonzero if this was a <R>eply command
                        }
                }
 
                        }
                }
 
-               /* Trim down excessively long lists of thread references.  We eliminate the
-                * second one in the list so that the thread root remains intact.
-                */
+               // Trim down excessively long lists of thread references.  We eliminate the
+               // second one in the list so that the thread root remains intact.
                int rrtok = num_tokens(reply_references, '|');
                int rrlen = strlen(reply_references);
                if (((rrtok >= 3) && (rrlen > 900)) || (rrtok > 10)) {
                int rrtok = num_tokens(reply_references, '|');
                int rrlen = strlen(reply_references);
                if (((rrtok >= 3) && (rrlen > 900)) || (rrtok > 10)) {
@@ -1121,7 +1098,8 @@ int entmsg(CtdlIPC * ipc, int is_reply,   /* nonzero if this was a <R>eply command
                }
 
                snprintf(message.references, sizeof message.references, "%s%s%s",
                }
 
                snprintf(message.references, sizeof message.references, "%s%s%s",
-                        reply_references, (IsEmptyStr(reply_references) ? "" : "|"), reply_inreplyto);
+                        reply_references, (IsEmptyStr(reply_references) ? "" : "|"), reply_inreplyto
+               );
        }
 
        r = CtdlIPCPostMessage(ipc, 0, &subject_required, &message, buf);
        }
 
        r = CtdlIPCPostMessage(ipc, 0, &subject_required, &message, buf);
@@ -1131,22 +1109,20 @@ int entmsg(CtdlIPC * ipc, int is_reply, /* nonzero if this was a <R>eply command
                return (1);
        }
 
                return (1);
        }
 
-       /* Error code 570 is special.  It means that we CAN enter a message
-        * in this room, but a recipient needs to be specified.
-        */
+       // Error code 570 is special.  It means that we CAN enter a message in this room, but a recipient needs to be specified.
        need_recp = 0;
        if (r / 10 == 57) {
                need_recp = 1;
        }
 
        need_recp = 0;
        if (r / 10 == 57) {
                need_recp = 1;
        }
 
-       /* If the user is a dumbass, tell them how to type. */
+       // If the user is a dumbass, tell them how to type.
        if ((userflags & US_EXPERT) == 0) {
                scr_printf("Entering message.  Word wrap will give you soft linebreaks.  Pressing the\n");
                scr_printf("'enter' key will give you a hard linebreak and an indent.  Press 'enter' twice\n");
                scr_printf("when finished.\n");
        }
 
        if ((userflags & US_EXPERT) == 0) {
                scr_printf("Entering message.  Word wrap will give you soft linebreaks.  Pressing the\n");
                scr_printf("'enter' key will give you a hard linebreak and an indent.  Press 'enter' twice\n");
                scr_printf("when finished.\n");
        }
 
-       /* Handle the selection of a recipient, if necessary. */
+       // Handle the selection of a recipient, if necessary.
        strcpy(buf, "");
        if (need_recp == 1) {
                if (axlevel >= AxProbU) {
        strcpy(buf, "");
        if (need_recp == 1) {
                if (axlevel >= AxProbU) {
@@ -1172,7 +1148,7 @@ int entmsg(CtdlIPC * ipc, int is_reply,   /* nonzero if this was a <R>eply command
                        message.anonymous = 1;
        }
 
                        message.anonymous = 1;
        }
 
-       /* If it's mail, we've got to check the validity of the recipient... */
+       // If it's mail, we've got to check the validity of the recipient...
        if (!IsEmptyStr(message.recipient)) {
                r = CtdlIPCPostMessage(ipc, 0, &subject_required, &message, buf);
                if (r / 100 != 2) {
        if (!IsEmptyStr(message.recipient)) {
                r = CtdlIPCPostMessage(ipc, 0, &subject_required, &message, buf);
                if (r / 100 != 2) {
@@ -1181,9 +1157,7 @@ int entmsg(CtdlIPC * ipc, int is_reply,   /* nonzero if this was a <R>eply command
                }
        }
 
                }
        }
 
-       /* Learn the number of the newest message in in the room, so we can
-        * tell upon saving whether someone else has posted too.
-        */
+       // Learn the number of the newest message in in the room, so we can tell upon saving whether someone else has posted too.
        num_msgs = 0;
        r = CtdlIPCGetMessages(ipc, LastMessages, 1, NULL, &msgarr, buf);
        if (r / 100 != 1) {
        num_msgs = 0;
        r = CtdlIPCGetMessages(ipc, LastMessages, 1, NULL, &msgarr, buf);
        if (r / 100 != 1) {
@@ -1193,14 +1167,14 @@ int entmsg(CtdlIPC * ipc, int is_reply, /* nonzero if this was a <R>eply command
                for (num_msgs = 0; msgarr[num_msgs]; num_msgs++);
        }
 
                for (num_msgs = 0; msgarr[num_msgs]; num_msgs++);
        }
 
-       /* Now compose the message... */
+       // Now compose the message...
        if (client_make_message(ipc, temp, message.recipient, message.anonymous, 0, c, message.subject, subject_required) != 0) {
                if (msgarr)
                        free(msgarr);
                return (2);
        }
 
        if (client_make_message(ipc, temp, message.recipient, message.anonymous, 0, c, message.subject, subject_required) != 0) {
                if (msgarr)
                        free(msgarr);
                return (2);
        }
 
-       /* Reopen the temp file that was created, so we can send it */
+       // Reopen the temp file that was created, so we can send it
        fp = fopen(temp, "r");
 
        if (!fp || !(message.text = load_message_from_file(fp))) {
        fp = fopen(temp, "r");
 
        if (!fp || !(message.text = load_message_from_file(fp))) {
@@ -1212,19 +1186,17 @@ int entmsg(CtdlIPC * ipc, int is_reply, /* nonzero if this was a <R>eply command
        if (fp)
                fclose(fp);
 
        if (fp)
                fclose(fp);
 
-       /* Break lines that are >1024 characters, otherwise the server
-        * will truncate them.
-        */
+       // Break lines that are >1024 characters, otherwise the server will truncate them.
        break_big_lines(message.text);
 
        break_big_lines(message.text);
 
-       /* Transmit message to the server */
+       // Transmit message to the server
        r = CtdlIPCPostMessage(ipc, 1, NULL, &message, buf);
        if (r / 100 != 4) {
                scr_printf("%s\n", buf);
                return (1);
        }
 
        r = CtdlIPCPostMessage(ipc, 1, NULL, &message, buf);
        if (r / 100 != 4) {
                scr_printf("%s\n", buf);
                return (1);
        }
 
-       /* Yes, unlink it now, so it doesn't stick around if we crash */
+       // Yes, unlink it now, so it doesn't stick around if we crash
        unlink(temp);
 
        if (num_msgs >= 1)
        unlink(temp);
 
        if (num_msgs >= 1)
@@ -1241,10 +1213,10 @@ int entmsg(CtdlIPC * ipc, int is_reply, /* nonzero if this was a <R>eply command
                for (num_msgs = 0; msgarr[num_msgs]; num_msgs++);
        }
 
                for (num_msgs = 0; msgarr[num_msgs]; num_msgs++);
        }
 
-       /* get new highest message number in room to set lrp for goto... */
+       // get new highest message number in room to set lrp for goto...
        maxmsgnum = msgarr[num_msgs - 1];
 
        maxmsgnum = msgarr[num_msgs - 1];
 
-       /* now see if anyone else has posted in here */
+       // now see if anyone else has posted in here
        b = (-1);
        for (a = 0; a < num_msgs; ++a) {
                if (msgarr[a] > highmsg) {
        b = (-1);
        for (a = 0; a < num_msgs; ++a) {
                if (msgarr[a] > highmsg) {
@@ -1256,9 +1228,7 @@ int entmsg(CtdlIPC * ipc, int is_reply,   /* nonzero if this was a <R>eply command
        }
        msgarr = NULL;
 
        }
        msgarr = NULL;
 
-       /* In the Mail> room, this algorithm always counts one message
-        * higher than in public rooms, so we decrement it by one.
-        */
+       // In the Mail> room, this algorithm always counts one message higher than in public rooms, so we decrement it by one.
        if (need_recp) {
                --b;
        }
        if (need_recp) {
                --b;
        }
@@ -1270,28 +1240,24 @@ int entmsg(CtdlIPC * ipc, int is_reply, /* nonzero if this was a <R>eply command
                scr_printf("*** %d additional messages have been entered in this room by other users.\n", b);
        }
        free(message.text);
                scr_printf("*** %d additional messages have been entered in this room by other users.\n", b);
        }
        free(message.text);
-
        return (0);
 }
 
 
        return (0);
 }
 
 
-/*
- * Do editing on a quoted file
- */
+// Do editing on a quoted file
 void process_quote(void) {
        FILE *qfile, *tfile;
        char buf[128];
        int line, qstart, qend;
 
 void process_quote(void) {
        FILE *qfile, *tfile;
        char buf[128];
        int line, qstart, qend;
 
-       // Unlink the second temp file as soon as it's opened, so it'll get
-       // deleted even if the program dies
+       // Unlink the second temp file as soon as it's opened, so it'll get deleted even if the program dies
        qfile = fopen(temp2, "r");
        unlink(temp2);
 
        qfile = fopen(temp2, "r");
        unlink(temp2);
 
-       /* Display the quotable text with line numbers added */
+       // Display the quotable text with line numbers added
        line = 0;
        if (fgets(buf, 128, qfile) == NULL) {
        line = 0;
        if (fgets(buf, 128, qfile) == NULL) {
-               /* we're skipping a line here */
+               // we're skipping a line here
        }
        while (fgets(buf, 128, qfile) != NULL) {
                scr_printf("%3d %s", ++line, buf);
        }
        while (fgets(buf, 128, qfile) != NULL) {
                scr_printf("%3d %s", ++line, buf);
@@ -1303,7 +1269,7 @@ void process_quote(void) {
        rewind(qfile);
        line = 0;
        if (fgets(buf, 128, qfile) == NULL) {
        rewind(qfile);
        line = 0;
        if (fgets(buf, 128, qfile) == NULL) {
-               /* we're skipping a line here */
+               // we're skipping a line here
        }
        tfile = fopen(temp, "w");
        while (fgets(buf, 128, qfile) != NULL) {
        }
        tfile = fopen(temp, "w");
        while (fgets(buf, 128, qfile) != NULL) {
@@ -1318,9 +1284,7 @@ void process_quote(void) {
 }
 
 
 }
 
 
-/*
- * List the URLs which were embedded in the previous message
- */
+// List the URLs which were embedded in the previous message
 void list_urls(CtdlIPC * ipc) {
        int i;
        char cmd[SIZ];
 void list_urls(CtdlIPC * ipc) {
        int i;
        char cmd[SIZ];
@@ -1345,9 +1309,7 @@ void list_urls(CtdlIPC * ipc) {
 }
 
 
 }
 
 
-/*
- * Run image viewer in background
- */
+// Run image viewer in background
 int do_image_view(const char *filename) {
        char cmd[SIZ];
        pid_t childpid;
 int do_image_view(const char *filename) {
        char cmd[SIZ];
        pid_t childpid;
@@ -1406,19 +1368,18 @@ int do_image_view(const char *filename) {
 }
 
 
 }
 
 
-/*
- * View an image attached to a message
- */
+// View an image attached to a message
 void image_view(CtdlIPC * ipc, unsigned long msg) {
        struct parts *ptr = last_message_parts;
        char part[SIZ];
        int found = 0;
 
 void image_view(CtdlIPC * ipc, unsigned long msg) {
        struct parts *ptr = last_message_parts;
        char part[SIZ];
        int found = 0;
 
-       /* Run through available parts */
+       // Run through available parts
        for (ptr = last_message_parts; ptr; ptr = ptr->next) {
        for (ptr = last_message_parts; ptr; ptr = ptr->next) {
-               if ((!strcasecmp(ptr->disposition, "attachment")
-                    || !strcasecmp(ptr->disposition, "inline"))
-                   && !strncmp(ptr->mimetype, "image/", 6)) {
+               if (    (!strcasecmp(ptr->disposition, "attachment")
+                       || !strcasecmp(ptr->disposition, "inline"))
+                       && !strncmp(ptr->mimetype, "image/", 6)
+               ) {
                        found++;
                        if (found == 1) {
                                strcpy(part, ptr->number);
                        found++;
                        if (found == 1) {
                                strcpy(part, ptr->number);
@@ -1427,8 +1388,9 @@ void image_view(CtdlIPC * ipc, unsigned long msg) {
        }
 
        while (found > 0) {
        }
 
        while (found > 0) {
-               if (found > 1)
+               if (found > 1) {
                        strprompt("View which part (0 when done)", part, SIZ - 1);
                        strprompt("View which part (0 when done)", part, SIZ - 1);
+               }
                found = -found;
                for (ptr = last_message_parts; ptr; ptr = ptr->next) {
                        if ((!strcasecmp(ptr->disposition, "attachment")
                found = -found;
                for (ptr = last_message_parts; ptr; ptr = ptr->next) {
                        if ((!strcasecmp(ptr->disposition, "attachment")
@@ -1437,10 +1399,10 @@ void image_view(CtdlIPC * ipc, unsigned long msg) {
                            && !strcasecmp(ptr->number, part)) {
                                char tmp[PATH_MAX];
                                char buf[SIZ];
                            && !strcasecmp(ptr->number, part)) {
                                char tmp[PATH_MAX];
                                char buf[SIZ];
-                               void *file = NULL;      /* The downloaded file */
+                               void *file = NULL;      // The downloaded file
                                int r;
 
                                int r;
 
-                               /* view image */
+                               // view image
                                found = -found;
                                r = CtdlIPCAttachmentDownload(ipc, msg, ptr->number, &file, progress, buf);
                                if (r / 100 != 2) {
                                found = -found;
                                r = CtdlIPCAttachmentDownload(ipc, msg, ptr->number, &file, progress, buf);
                                if (r / 100 != 2) {
@@ -1467,13 +1429,12 @@ void image_view(CtdlIPC * ipc, unsigned long msg) {
 }
 
 
 }
 
 
-/*
- * Read the messages in the current room
- */
-void readmsgs(CtdlIPC * ipc, enum MessageList c,       // see listing in citadel_ipc.h
-             enum MessageDirection rdir,       // 1=Forward (-1)=Reverse
-             int q             // Number of msgs to read (if c==3)
-    ) {
+// Read the messages in the current room
+void readmsgs(CtdlIPC *ipc,
+       enum MessageList c,     // see listing in citadel_ipc.h
+       enum MessageDirection rdir,     // 1=Forward (-1)=Reverse
+       int q           // Number of msgs to read (if c==3)
+) {
        int a, e, f, g, start;
        int savedpos;
        int hold_sw = 0;
        int a, e, f, g, start;
        int savedpos;
        int hold_sw = 0;
@@ -1486,11 +1447,11 @@ void readmsgs(CtdlIPC * ipc, enum MessageList c,        // see listing in citadel_ipc.h
        char targ[ROOMNAMELEN];
        char filename[PATH_MAX];
        char save_to[PATH_MAX];
        char targ[ROOMNAMELEN];
        char filename[PATH_MAX];
        char save_to[PATH_MAX];
-       void *attachment = NULL;        /* Downloaded attachment */
-       FILE *dest = NULL;      /* Alternate destination other than screen */
-       int r;                  /* IPC response code */
-       static int att_seq = 0; /* Attachment download sequence number */
-       int rv = 0;             /* silence the stupid warn_unused_result warnings */
+       void *attachment = NULL;        // Downloaded attachment
+       FILE *dest = NULL;              // Alternate destination other than screen
+       int r;                          // IPC response code
+       static int att_seq = 0;         // Attachment download sequence number
+       int rv = 0;                     // silence the stupid warn_unused_result warnings
 
        CtdlMakeTempFileName(prtfile, sizeof prtfile);
 
 
        CtdlMakeTempFileName(prtfile, sizeof prtfile);
 
@@ -1519,7 +1480,7 @@ void readmsgs(CtdlIPC * ipc, enum MessageList c,  // see listing in citadel_ipc.h
                return;
        }
 
                return;
        }
 
-       /* this loop cycles through each message... */
+       // this loop cycles through each message...
        start = ((rdir == 1) ? 0 : (num_msgs - 1));
        for (a = start; ((a < num_msgs) && (a >= 0)); a = a + rdir) {
                while (msg_arr[a] == 0L) {
        start = ((rdir == 1) ? 0 : (num_msgs - 1));
        for (a = start; ((a < num_msgs) && (a >= 0)); a = a + rdir) {
                while (msg_arr[a] == 0L) {
@@ -1532,30 +1493,30 @@ void readmsgs(CtdlIPC * ipc, enum MessageList c,        // see listing in citadel_ipc.h
                         && (quotflag == 0)
                         && (userflags & US_PAGINATOR)) ? 1 : 0;
 
                         && (quotflag == 0)
                         && (userflags & US_PAGINATOR)) ? 1 : 0;
 
-               /* If we're doing a quote, set the screenwidth to 72 */
+               // If we're doing a quote, set the screenwidth to 72
                if (quotflag) {
                        hold_sw = screenwidth;
                        screenwidth = 72;
                }
 
                if (quotflag) {
                        hold_sw = screenwidth;
                        screenwidth = 72;
                }
 
-               /* If printing or archiving, set the screenwidth to 80 */
+               // If printing or archiving, set the screenwidth to 80
                if (arcflag) {
                        hold_sw = screenwidth;
                        screenwidth = 80;
                }
 
                if (arcflag) {
                        hold_sw = screenwidth;
                        screenwidth = 80;
                }
 
-               /* clear parts list */
+               // clear parts list
                free_parts(last_message_parts);
                last_message_parts = NULL;
 
                free_parts(last_message_parts);
                last_message_parts = NULL;
 
-               /* now read the message... */
+               // now read the message...
                e = read_message(ipc, msg_arr[a], pagin, dest);
 
                e = read_message(ipc, msg_arr[a], pagin, dest);
 
-               /* ...and set the screenwidth back if we have to */
+               // ...and set the screenwidth back if we have to
                if ((quotflag) || (arcflag)) {
                        screenwidth = hold_sw;
                }
                if ((quotflag) || (arcflag)) {
                        screenwidth = hold_sw;
                }
-             RMSGREAD:
+RMSGREAD:
                highest_msg_read = msg_arr[a];
                if (quotflag) {
                        fclose(dest);
                highest_msg_read = msg_arr[a];
                if (quotflag) {
                        fclose(dest);
@@ -1574,7 +1535,7 @@ void readmsgs(CtdlIPC * ipc, enum MessageList c,  // see listing in citadel_ipc.h
                        f = fork();
                        if (f == 0) {
                                if (freopen(prtfile, "r", stdin) == NULL) {
                        f = fork();
                        if (f == 0) {
                                if (freopen(prtfile, "r", stdin) == NULL) {
-                                       /* we probably should handle the error condition here */
+                                       // we probably should handle the error condition here
                                }
                                stty_ctdl(SB_RESTORE);
                                ka_system(printcmd);
                                }
                                stty_ctdl(SB_RESTORE);
                                ka_system(printcmd);
@@ -1614,43 +1575,46 @@ void readmsgs(CtdlIPC * ipc, enum MessageList c,        // see listing in citadel_ipc.h
                                e = (inkey() & 127);
                                e = tolower(e);
 
                                e = (inkey() & 127);
                                e = tolower(e);
 
-/* return key same as <N> */ if (e == 10)
+                                if (e == 10) {                                 // return key same as <N>
                                        e = 'n';
                                        e = 'n';
+                               }
 
 
-/* space key same as <N> */ if (e == 32)
+                                if (e == 32) {                                 // space key same as <N>
                                        e = 'n';
                                        e = 'n';
+                               }
 
 
-/* del/move for aides only */
-                               if ((!is_room_aide)
-                                   && ((room_flags & QR_MAILBOX) == 0)
-                                   && ((room_flags2 & QR2_COLLABDEL) == 0)
-                                   ) {
-                                       if ((e == 'd') || (e == 'm'))
+                               if (    (!is_room_aide)                         // delete/move are available only to admins
+                                       && ((room_flags & QR_MAILBOX) == 0)
+                                       && ((room_flags2 & QR2_COLLABDEL) == 0)
+                               ) {
+                                       if ((e == 'd') || (e == 'm')) {
                                                e = 0;
                                                e = 0;
+                                       }
                                }
 
                                }
 
-/* print only if available */
-                               if ((e == 'p') && (IsEmptyStr(printcmd)))
+                               if ((e == 'p') && (IsEmptyStr(printcmd))) {             // print, if available
                                        e = 0;
                                        e = 0;
+                               }
 
 
-/* can't file if not allowed */
-                               if ((e == 'f')
-                                   && (rc_allow_attachments == 0))
+                               if ((e == 'f') && (rc_allow_attachments == 0)) {        // file attachments, if available
                                        e = 0;
                                        e = 0;
+                               }
 
 
-/* link only if browser avail*/
-                               if ((e == 'u')
-                                   && (IsEmptyStr(rc_url_cmd)))
+                               if ((e == 'u') && (IsEmptyStr(rc_url_cmd))) {           // display urls, if a browser is available
                                        e = 0;
                                        e = 0;
-                               if ((e == 'i')
-                                   && (IsEmptyStr(imagecmd) || !has_images))
+                               }
+
+                               if ((e == 'i') && (IsEmptyStr(imagecmd) || !has_images)) {      // display images, if available
                                        e = 0;
                                        e = 0;
+                               }
+
                        } while ((e != 'a') && (e != 'n') && (e != 's')
                                 && (e != 'd') && (e != 'm') && (e != 'p')
                                 && (e != 'q') && (e != 'b') && (e != 'h')
                                 && (e != 'r') && (e != 'f') && (e != '?')
                                 && (e != 'u') && (e != 'c') && (e != 'y')
                        } while ((e != 'a') && (e != 'n') && (e != 's')
                                 && (e != 'd') && (e != 'm') && (e != 'p')
                                 && (e != 'q') && (e != 'b') && (e != 'h')
                                 && (e != 'r') && (e != 'f') && (e != '?')
                                 && (e != 'u') && (e != 'c') && (e != 'y')
-                                && (e != 'i') && (e != 'o'));
+                                && (e != 'i') && (e != 'o')
+                       );
                        switch (e) {
                        case 's':
                                scr_printf("Stop");
                        switch (e) {
                        case 's':
                                scr_printf("Stop");
@@ -1708,7 +1672,7 @@ void readmsgs(CtdlIPC * ipc, enum MessageList c,  // see listing in citadel_ipc.h
                        else
                                scr_printf("\n");
                }
                        else
                                scr_printf("\n");
                }
-             DONE_QUOTING:switch (e) {
+DONE_QUOTING:  switch (e) {
                case '?':
                        scr_printf("Options available here:\n"
                                   " ?  Help (prints this message)\n"
                case '?':
                        scr_printf("Options available here:\n"
                                   " ?  Help (prints this message)\n"
@@ -1770,8 +1734,9 @@ void readmsgs(CtdlIPC * ipc, enum MessageList c,  // see listing in citadel_ipc.h
                        else {
                                goto RMSGREAD;
                        }
                        else {
                                goto RMSGREAD;
                        }
-                       if (r / 100 != 2)       /* r will be init'ed, FIXME */
-                               goto RMSGREAD;  /* the logic here sucks */
+                       if (r / 100 != 2) {     // r will be initialized.  The logic here sucks.
+                               goto RMSGREAD;
+                       }
                        break;
                case 'o':
                case 'f':
                        break;
                case 'o':
                case 'f':
@@ -1782,23 +1747,21 @@ void readmsgs(CtdlIPC * ipc, enum MessageList c,        // see listing in citadel_ipc.h
                        }
                        else {
                                extract_token(filename, cmd, 2, '|', sizeof filename);
                        }
                        else {
                                extract_token(filename, cmd, 2, '|', sizeof filename);
-                               /*
-                                * Part 1 won't have a filename; use the
-                                * subject of the message instead. IO
-                                */
+                               // Part 1 won't have a filename; use the subject of the message instead. --IO
                                if (IsEmptyStr(filename)) {
                                        strcpy(filename, reply_subject);
                                }
                                if (IsEmptyStr(filename)) {
                                        strcpy(filename, reply_subject);
                                }
-                               if (e == 'o') { /* open attachment */
+                               if (e == 'o') {         // open attachment
                                        mkdir(tempdir, 0700);
                                        snprintf(save_to, sizeof save_to, "%s/%04x.%s", tempdir, ++att_seq, filename);
                                        save_buffer(attachment, extract_unsigned_long(cmd, 0), save_to);
                                        snprintf(cmd, sizeof cmd, rc_open_cmd, save_to);
                                        rv = system(cmd);
                                        mkdir(tempdir, 0700);
                                        snprintf(save_to, sizeof save_to, "%s/%04x.%s", tempdir, ++att_seq, filename);
                                        save_buffer(attachment, extract_unsigned_long(cmd, 0), save_to);
                                        snprintf(cmd, sizeof cmd, rc_open_cmd, save_to);
                                        rv = system(cmd);
-                                       if (rv != 0)
+                                       if (rv != 0) {
                                                scr_printf("failed to save %s Reason %d\n", cmd, rv);
                                                scr_printf("failed to save %s Reason %d\n", cmd, rv);
+                                       }
                                }
                                }
-                               else {  /* save attachment to disk */
+                               else {  // save attachment to disk
                                        destination_directory(save_to, filename);
                                        save_buffer(attachment, extract_unsigned_long(cmd, 0), save_to);
                                }
                                        destination_directory(save_to, filename);
                                        save_buffer(attachment, extract_unsigned_long(cmd, 0), save_to);
                                }
@@ -1865,9 +1828,7 @@ void readmsgs(CtdlIPC * ipc, enum MessageList c,  // see listing in citadel_ipc.h
 }                              /* end read routine */
 
 
 }                              /* end read routine */
 
 
-/*
- * View and edit a system message
- */
+// View and edit a system message
 void edit_system_message(CtdlIPC * ipc, char *which_message) {
        char desc[SIZ];
        char read_cmd[SIZ];
 void edit_system_message(CtdlIPC * ipc, char *which_message) {
        char desc[SIZ];
        char read_cmd[SIZ];
@@ -1880,10 +1841,8 @@ void edit_system_message(CtdlIPC * ipc, char *which_message) {
 }
 
 
 }
 
 
-/*
- * Loads the contents of a file into memory.  Caller must free the allocated memory.
- */
-char *load_message_from_file(FILE * src) {
+// Loads the contents of a file into memory.  Caller must free the allocated memory.
+char *load_message_from_file(FILE *src) {
        size_t i;
        size_t got = 0;
        char *dest = NULL;
        size_t i;
        size_t got = 0;
        char *dest = NULL;
@@ -1893,8 +1852,9 @@ char *load_message_from_file(FILE * src) {
        rewind(src);
 
        dest = (char *) calloc(1, i + 1);
        rewind(src);
 
        dest = (char *) calloc(1, i + 1);
-       if (!dest)
+       if (!dest) {
                return NULL;
                return NULL;
+       }
 
        while (got < i) {
                size_t g;
 
        while (got < i) {
                size_t g;
@@ -1902,10 +1862,10 @@ char *load_message_from_file(FILE * src) {
                g = fread(dest + got, 1, i - got, src);
                got += g;
                if (g < i - got) {
                g = fread(dest + got, 1, i - got, src);
                got += g;
                if (g < i - got) {
-                       /* Interrupted system call, keep going */
-                       if (errno == EINTR)
-                               continue;
-                       /* At this point we have either EOF or error */
+                       if (errno == EINTR) {
+                               continue;               // Interrupted system call, keep going
+                       }
+                       // At this point we have either EOF or error
                        i = got;
                        break;
                }
                        i = got;
                        break;
                }
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..01967701d54f1429cc8c0fd140648ed6a343fa9f 100644 (file)
@@ -6,8 +6,7 @@
 // disclosure is subject to the GNU General Public License v3.
 
 
 // disclosure is subject to the GNU General Public License v3.
 
 
-// RENDERER FOR THIS VIEW
-function view_render_calendar() {
+function XXXXXview_render_calendar() {
 
        let options = {
                method: "REPORT",
 
        let options = {
                method: "REPORT",
@@ -35,10 +34,125 @@ 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>`;
        });
 }
+
+var date_being_displayed;
+var calendar_initialized = 0;
+
+
+// Update the calendar display (we might have changed dates, or added/removed data)
+function update_calendar_display() {
+
+       // Get y-m-d to display
+       let day_of_month = date_being_displayed.getDate();
+       let month = date_being_displayed.getMonth() + 1;
+       let year = date_being_displayed.getFullYear();
+
+       document.getElementById("ctdl-main").innerHTML =
+               "Displaying " + year + "-" + month + "-" + day_of_month + "<br>"
+               + "Temporary navigation links: "
+               + "<a href=\"javascript:go_back_one_year();\">←Y</a>&nbsp;|&nbsp;"
+               + "<a href=\"javascript:go_back_one_month();\">←M</a>&nbsp;|&nbsp;"
+               + "<a href=\"javascript:go_back_one_week();\">←W</a>&nbsp;|&nbsp;"
+               + "<a href=\"javascript:go_back_one_day();\">←D</a>&nbsp;|&nbsp;"
+               + "<a href=\"javascript:go_to_today();\">today</a>&nbsp;|&nbsp;"
+               + "<a href=\"javascript:go_forward_one_day();\">D→</a>&nbsp;|&nbsp;"
+               + "<a href=\"javascript:go_forward_one_week();\">W→</a>&nbsp;|&nbsp;"
+               + "<a href=\"javascript:go_forward_one_month();\">M→</a>&nbsp;|&nbsp;"
+               + "<a href=\"javascript:go_forward_one_year();\">Y→</a>&nbsp;"
+       ;
+}
+
+
+// Go back by one year.
+function go_back_one_year() {
+       date_being_displayed.setYear(date_being_displayed.getFullYear() - 1 );
+       update_calendar_display();
+}
+
+
+// Go back by one month.
+function go_back_one_month() {
+       date_being_displayed.setMonth(date_being_displayed.getMonth() - 1 );
+       update_calendar_display();
+}
+
+
+// Go back by one week.
+function go_back_one_week() {
+       date_being_displayed.setDate(date_being_displayed.getDate() - 7 );
+       update_calendar_display();
+}
+
+
+// Go back by one day.
+function go_back_one_day() {
+       date_being_displayed.setDate(date_being_displayed.getDate() - 1);
+       update_calendar_display();
+}
+
+
+// Flip the calendar to today's date.
+function go_to_today() {
+       date_being_displayed = new Date();
+       update_calendar_display();
+}
+
+
+// Advance the calendar by one day.
+function go_forward_one_day() {
+       date_being_displayed.setDate(date_being_displayed.getDate() + 1);
+       update_calendar_display();
+}
+
+
+// Advance the calendar by one week.
+function go_forward_one_week() {
+       date_being_displayed.setDate(date_being_displayed.getDate() + 7);
+       update_calendar_display();
+}
+
+
+// Advance the calendar by one month.
+function go_forward_one_month() {
+       date_being_displayed.setMonth(date_being_displayed.getMonth() + 1 );
+       update_calendar_display();
+}
+
+
+// Advance the calendar by one year.
+function go_forward_one_year() {
+       date_being_displayed.setYear(date_being_displayed.getFullYear() + 1 );
+       update_calendar_display();
+}
+
+
+// RENDERER FOR THIS VIEW
+function view_render_calendar() {
+
+       // Calendar will persist the year/month/day where the user left it,
+       // but if it's rendering for the first time, set it to "today".
+       if (!calendar_initialized) {
+               go_to_today();
+       }
+}
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 efe04a3a668054752fb43b2d1e133bac606bbd2c..7d0eba4c0c8e7ba9b154b142c518d20a387e275c 100644 (file)
@@ -1,47 +1,2 @@
 <div class="tabcontent">
 <div class="tabcontent">
-       <br />
-       <table border="1" cellpadding="5">
-               <tr>
-                       <td><b><i><?_("Not shared with")></i></b></td>
-                       <td><b><i><?_("Shared with")></i></b></td>
-               </tr>
-               <tr>
-                       <td valign="top">
-                               <table border="0" cellpadding="5">
-                                       <tr class="tab_cell">
-                                               <td><?_("Remote node name")></td>
-                                               <td><?_("Remote room name")></td>
-                                               <td><?_("Actions")></td>
-                                       </tr>
-                                       <?ITERATE("NODECONFIG", ="room_edit_shared_room_add")>
-                               </table>
-                       </td>
-                       <td valign="top">
-                               <table border="0" cellpadding="5">
-                                       <tr class="tab_cell">
-                                               <td><?_("Remote node name")></td>
-                                               <td><?_("Remote room name")></td>
-                                               <td><?_("Actions")></td>
-                                       </tr>
-                                       <?ITERATE("ITERATE:THISROOM:GNET", ="room_edit_shared_room_removal", 0, 0, -1, #"ignet_push_share")>
-                               </table>
-                       </td>
-               </tr>
-       </table>
-       <br />
-       <div id="hints">
-               <b><?_("Notes:")></b>
-               <div><ul>
-                       <li>
-                               <?_("When sharing a room, it must be shared from both ends.  Adding a node to the 'shared' list sends messages out, but in order to receive messages, the other nodes must be configured to send messages out to your system as well.")>
-                       <li>
-                         <?_("If the remote room name is blank, it is assumed that the room name is identical on the remote node.")>
-                       </li> <li>
-                         <?_("If the remote room name is different, the remote node must also configure the name of the room here.")>
-                       </li> <li>
-                         (<img src="static/webcit_icons/essen/16x16/refresh.png" alt='<?_("resend messages to this node")>' width="16" height="16">)<?_("Re-sharing may stress your system and produce large spoolfiles that need to be transmitted; All messages in this room not originating from this node are re-spooled and re-sent with the next networker run.")>
-                       </li>
-               </ul></div>
-       </div>
-       <br />
 </div>
 </div>
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 */