}
!
$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.
#!/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.
-exit 0 # In fact, here's an exit statement you must delete before you can even run it.
+
+if [ "${YES_DELETE_MY_DATABASE}" != '' ] ; then
+ echo Ah, I see you have set YES_DELETE_MY_DATABASE to a non-empty value.
+ echo The dump and load test will now proceed.
+else
+ echo 'This script dumps the database, deletes the database, loads the database, dumps it again...'
+ echo '...and then compares the two dumps to see if we have full fidelity between them.'
+ echo 'Did you read that correctly? Yes, it will DELETE your database.'
+ echo 'If this is really what you want, set the environment variable YES_DELETE_MY_DATABASE'
+ echo 'to a non-empty value, and run it again.'
+ exit 0
+fi
ps ax | grep citserver | grep -v grep >/dev/null 2>/dev/null && {
- echo dont do this while the server is running
+ echo Do not do this while the server is running.
exit 1
}
./ctdldump -y >dump.dat
first=$(md5sum dump.dat | awk ' { print $1 } ' )
+
rm -fv data/*
./ctdlload -y <dump.dat
./ctdldump -y >dump.dat
#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
me->MigrateBuf = NULL;
me->sMigrateBuf = NULL;
me->redirect_buffer = NULL;
-#ifdef HAVE_OPENSSL
me->ssl = NULL;
-#endif
-
me->download_fp = NULL;
me->upload_fp = NULL;
me->ma = NULL;
// Redirect this session's output to a memory buffer?
StrBuf *redirect_buffer; // the buffer
StrBuf *StatusMessage;
-#ifdef HAVE_OPENSSL
SSL *ssl;
int redirect_ssl;
-#endif
char curr_user[USERNAME_SIZE]; // name of current user
int logged_in; // logged in?
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);
}
// Autocompletion of email recipients, etc.
-//
// Copyright (c) 1987-2023 by the citadel.org team
-//
-// This program is open source software. Use, duplication, or disclosure
-// is subject to the terms of the GNU General Public License version 3.
+// This program is open source software. Use, duplication, or disclosure is subject to the GNU General Public License version 3.
#include "../../ctdl_module.h"
#include "serv_autocompletion.h"
//
// 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 "../../sysdep.h"
-#ifdef HAVE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
-#endif
#include <time.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;
CC->ssl = NULL;
CC->redirect_ssl = 0;
}
-
-#endif // HAVE_OPENSSL
// 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);
void endtls(void);
void CtdlStartTLS(char *ok_response, char *nosup_response, char *error_response);
extern SSL_CTX *ssl_ctx;
-
-#endif
-/*
- * 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 "../../citserver.h"
#include "../../ctdl_module.h"
#include "../../config.h"
-/*
- * This loop recognizes all server commands.
- */
+
+// This loop recognizes all server commands.
void do_command_loop(void) {
- struct CitContext *CCC = CC;
char cmdbuf[SIZ];
- time(&CCC->lastcmd);
- memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
+ time(&CC->lastcmd);
+ memset(cmdbuf, 0, sizeof cmdbuf); // Clear it, just in case
if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
syslog(LOG_INFO, "Citadel client disconnected: ending session.");
- CCC->kill_me = KILLME_CLIENT_DISCONNECTED;
+ CC->kill_me = KILLME_CLIENT_DISCONNECTED;
return;
}
- /* Log the server command, but don't show passwords... */
- if ( (strncasecmp(cmdbuf, "PASS", 4)) && (strncasecmp(cmdbuf, "SETP", 4)) ) {
- syslog(LOG_DEBUG, "[%s(%ld)] %s",
- CCC->curr_user, CCC->user.usernum, cmdbuf
- );
+ // Log the server command, but don't show passwords...
+ if ( (strncasecmp(cmdbuf, "PASS", 4))
+ && (strncasecmp(cmdbuf, "SETP", 4))
+ ) {
+ syslog(LOG_DEBUG, "[%s(%ld)] %s", CC->curr_user, CC->user.usernum, cmdbuf);
}
else {
- syslog(LOG_DEBUG, "[%s(%ld)] <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();
- /*
- * 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)) {
unbuffer_output();
- /* Run any after-each-command routines registered by modules */
+ // Run any after-each-command routines registered by modules
PerformSessionHooks(EVT_CMD);
}
-/*
- * 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 <stdio.h>
#include <sys/stat.h>
#include <string.h>
-
#include <time.h>
#include <limits.h>
#include <libcitadel.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;
- /* If the room has its own policy, return it */
+ // If the room has its own policy, return it
if (qrbuf->QRep.expire_mode != 0) {
memcpy(epbuf, &qrbuf->QRep, sizeof(struct ExpirePolicy));
return;
}
- /* (non-mailbox rooms)
- * If the floor has its own policy, return it
- */
+ // (non-mailbox rooms)
+ // If the floor has its own policy, return it
if ( (qrbuf->QRflags & QR_MAILBOX) == 0) {
fl = CtdlGetCachedFloor(qrbuf->QRfloor);
if (fl->f_ep.expire_mode != 0) {
}
}
- /* (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");
}
}
- /* Otherwise, fall back on the system default */
+ // Otherwise, fall back on the system default
epbuf->expire_mode = CtdlGetConfigInt("c_ep_mode");
epbuf->expire_value = CtdlGetConfigInt("c_ep_value");
}
-/*
- * Get Policy EXpire
- */
+// Get Policy EXpire
void cmd_gpex(char *argbuf) {
struct ExpirePolicy exp;
struct floor *fl;
}
-/*
- * Set Policy EXpire
- */
+// Set Policy EXpire
void cmd_spex(char *argbuf) {
struct ExpirePolicy exp;
struct floor flbuf;
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;
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);
return;
}
- else if ((!strcasecmp(which, strof(mailboxespolicy))) || (!strcasecmp(which, "mailboxes")))
- {
+ else if ((!strcasecmp(which, strof(mailboxespolicy))) || (!strcasecmp(which, "mailboxes"))) {
CtdlSetConfigInt("c_mbxep_mode", exp.expire_mode);
CtdlSetConfigInt("c_mbxep_value", exp.expire_value);
cprintf("%d Default expire policy for mailboxes set.\n", CIT_OK);
return;
}
- else if ((!strcasecmp(which, strof(sitepolicy))) || (!strcasecmp(which, "site")))
- {
+ else if ((!strcasecmp(which, strof(sitepolicy))) || (!strcasecmp(which, "site"))) {
if (exp.expire_mode == EXPIRE_NEXTLEVEL) {
cprintf("%d Invalid policy (no higher level)\n", ERROR + ILLEGAL_VALUE);
return;
//
// 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"
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");
-#endif
#ifndef DISABLE_IMAP_ACL
IAPuts(" ACL");
*/
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 */
-#endif
imap_greeting();
}
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);
-#endif
/* The commans below require a logged-in state */
RegisterImapCMD("SELECT", "", imap_select, I_FLAG_LOGGED_IN);
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);
-#endif
CtdlRegisterSessionHook(imap_cleanup_function, EVT_STOP, PRIO_STOP + 30);
}
#include "../../genstamp.h"
#include "../../domain.h"
#include "../../ctdl_module.h"
+#include "../smtp/smtp_util.h"
void inetcfg_setTo(struct CtdlMessage *msg) {
char *conf;
char buf[SIZ];
-
+
if (CM_IsEmpty(msg, eMessageText)) return;
conf = strdup(msg->cm_fields[eMessageText]);
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
}
}
// This module delivers messages to mailing lists.
-//
-// Copyright (c) 2002-2024 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
+// Copyright (c) 2002-2024 by the citadel.org team (Art Cancro et al.)
+// This program is open source software. Use, duplication, or disclosure are subject to the GNU General Public License v3.
#include "../../sysdep.h"
#include <stdlib.h>
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
};
-
void listdeliver_do_msg(long msgnum, void *userdata) {
struct lddata *ld = (struct lddata *) userdata;
if (!ld) 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]);
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) {
// 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
-#endif
nntp_greeting();
}
cprintf("MODE-READER\r\n");
cprintf("LIST ACTIVE NEWSGROUPS\r\n");
cprintf("OVER\r\n");
-#ifdef HAVE_OPENSSL
cprintf("STARTTLS\r\n");
-#endif
if (!CC->logged_in) {
cprintf("AUTHINFO USER\r\n");
}
NULL,
CitadelServiceNNTP);
-#ifdef HAVE_OPENSSL
CtdlRegisterServiceHook(CtdlGetConfigInt("c_nntps_port"),
NULL,
nntps_greeting,
nntp_command_loop,
NULL,
CitadelServiceNNTPS);
-#endif
CtdlRegisterSessionHook(nntp_cleanup_function, EVT_STOP, PRIO_STOP + 250);
}
#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) {
- /* 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);
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();
}
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);
- /* 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) {
pop3_pass(&cmdbuf[5]);
}
-#ifdef HAVE_OPENSSL
else if (!strncasecmp(cmdbuf, "STLS", 4)) {
pop3_stls();
}
-#endif
else if (!CC->logged_in) {
cprintf("-ERR Not logged in.\r\n");
pop3_command_loop,
NULL,
CitadelServicePop3);
-#ifdef HAVE_OPENSSL
CtdlRegisterServiceHook(CtdlGetConfigInt("c_pop3s_port"),
NULL,
pop3s_greeting,
pop3_command_loop,
NULL,
CitadelServicePop3S);
-#endif
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";
}
#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);
}
+// This utility function is used by the body canonicalizer
char *dkim_rtrim_lines(char *str) {
char *end;
int len = strlen(str);
}
-// Second step to canonicalize a block of headers as per the "relaxed" algorithm.
+// 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 *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]);
}
+// 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;
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)
// hash of the canonicalized body
unsigned char *body_hash = malloc(SHA256_DIGEST_LENGTH);
- SHA256((unsigned char*)relaxed_body, relaxed_body_len, body_hash);
+ SHA256((unsigned char *)relaxed_body, relaxed_body_len, body_hash);
free(relaxed_body); // all we need now is the hash
relaxed_body = NULL;
// Compute a hash of the canonicalized headers, and then sign that hash with our private key.
// RFC6376 says that we hash and sign everything up to the "b=" and then we'll add the rest at the end.
- // Load the private key into an OpenSSL "BIO" structure
- BIO *bufio = BIO_new_mem_buf((void*)pkey_in, strlen(pkey_in));
- if (bufio == NULL) {
- syslog(LOG_ERR, "dkim: BIO_new_mem_buf() failed");
- abort();
- }
-
- // Now import the private key
- EVP_PKEY *pkey = NULL; // Don't combine this line with the next one. It will barf.
- pkey = PEM_read_bio_PrivateKey(
- bufio, // BIO to read the private key from
- &pkey, // pointer to EVP_PKEY structure
- NULL, // password callback - can be NULL
- NULL // parameter passed to callback or password if callback is NULL
- );
- if (pkey == NULL) {
- syslog(LOG_ERR, "dkim: PEM_read_bio_PrivateKey() failed");
- abort();
- }
- BIO_free(bufio); // Don't need this anymore, we have `pkey` now
-
// The hashing/signing library calls are documented at https://wiki.openssl.org/index.php/EVP_Signing_and_Verifying
// NOTE: EVP_DigestSign*() functions are supplied with the actual data to be hashed and signed.
// That means we don't hash it first, otherwise we would be signing double-hashed (and therefore wrong) data.
EVP_MD_CTX_free(mdctx);
// This is an optional routine to verify our own signature.
- // The test program in tests/dkimtester enables it. It is not enabled during server operation.
+ // The test program in tests/dkimtester enables it. It is not enabled in Citadel Server.
#ifdef DKIM_VERIFY_SIGNATURE
mdctx = EVP_MD_CTX_new();
if (mdctx) {
// And we're done!
}
+
+
--- /dev/null
+// DKIM bindings to Citadel Server
+//
+// (C) 2024 by Art Cancro
+//
+// This program is open source software. Use, duplication, or disclosure is subject to the GNU General Public License v3.
+
+// Make sure we don't accidentally use any deprecated API calls
+#define OPENSSL_NO_DEPRECATED_3_0
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <assert.h>
+#include <syslog.h>
+#include <openssl/rand.h>
+#include <openssl/rsa.h>
+#include <openssl/engine.h>
+#include <openssl/sha.h>
+#include <openssl/hmac.h>
+#include <openssl/evp.h>
+#include <openssl/bio.h>
+#include <openssl/pem.h>
+#include <openssl/buffer.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <libcitadel.h>
+#include "../../config.h"
+#include "../../msgbase.h"
+#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);
+}
// 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);
}
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
) {
cprintf("250-STARTTLS\r\n");
}
-#endif
cprintf("250-AUTH LOGIN PLAIN\r\n"
"250-AUTH=LOGIN PLAIN\r\n"
smtp_rcpt();
return;
}
-#ifdef HAVE_OPENSSL
if (!strncasecmp(ChrPtr(SMTP->Cmd), "STARTTLS", 8)) {
smtp_starttls();
return;
}
-#endif
cprintf("502 I'm afraid I can't do that.\r\n");
}
NULL,
CitadelServiceSMTP_MTA);
-#ifdef HAVE_OPENSSL
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,
char unsubscribe_url[SIZ];
snprintf(base_url, sizeof base_url, "https://%s/listsub", CtdlGetConfigStr("c_fqdn"));
generate_one_click_url(unsubscribe_url, base_url, "unsubscribe", source_room, recp);
- cprintf("List-Unsubscribe: %s\r\n", unsubscribe_url);
+ cprintf("List-Unsubscribe: <%s>\r\n", unsubscribe_url); // RFC 2369
cprintf("List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n"); // RFC 8058
-
}
CtdlOutputMsg(msgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0, NULL, &fromaddr, NULL);
s.TheMessage = CC->redirect_buffer;
CC->redirect_buffer = NULL;
-#if 0
- // FIXME genericize this
- char *pkey_in =
- "-----BEGIN PRIVATE KEY-----\n"
- "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDfuefcepokRrnp\n"
- "SSDsxu+QDqeD8GL9QnZz/N6IxTdBv6Wc10ExBe2IjS5dKI7AvhSSEK0zGE8Hkpmw\n"
- "eccbiepQqeueteWzAMZ1uT43bD3k7eye7vWobiOP9QtoYGR6sG25h2W5Tbc91W4f\n"
- "dvYnxYVJjx8wIVF0f3o25v+rQueoo0HlvGyA9/xi9GAaJL05OmK1xnMJgSvW/Q8Q\n"
- "zq7apf1D6XPXHuhv5tevElkZ5jlvM2w0cTVyAzMrUh6Rkcn9xM4/NPWYghBc3jO4\n"
- "TrPnSrobQGrX0fcizE/FN6I0in0Ke8Z+gMM8NeFcsjvLZe9MpY9i0pw/ygLIh5t3\n"
- "O4qpwC1JAgMBAAECggEAIwiTCMEAGzciDKhhagJ66BWLYMtHTP5X2zDZThSH4xlW\n"
- "HznL4RfbCtuEy5y6we7h/L90x8ACPB7WRz7CkYrmsMvy9A7q0b2I1k10MyyVgqBJ\n"
- "QdgMitv4YKYQK7+QbG/tNrS/lqVXUOz3iiDQSgkRpqOtUBWfkj0WD7vbhF99NDhV\n"
- "dxaehFkKv3yNy0bXJlHJBJ6KtOUnDwub8TExh8dyj3kB8Qzj4I98shaXPNUSSaOw\n"
- "zG6QG72yrxlMs495jkIPbF2JDidmLrX+oVISwKyaBWx+BkFV/KFAEKgaB5/nCw7+\n"
- "qq/jxsmXim3HuQ3MIAjq1yw9aGRH1HMi8Gn7tYlNGwKBgQDy6EEKpuEiW9wwlI2+\n"
- "GVuSkhSTTX1h6qK/ay8Jtyb8yJM/BxogAQlfjdgFixiZHy5MaomTbfeT2GDji553\n"
- "+RsnZ60+g7FI9nHwabSxtuCQ+vjbFqCsdMPAiSeG0bEzo0zf5TjASdUtuZL0vXjl\n"
- "yMZWDEuESoVNlYlvCOVkw2nvIwKBgQDryPuSq6PNVHRWsKRRs5ju4wKs/1ucBOg5\n"
- "gCcN8lE03mFCWAlZhypE4/fAhTQ/a5KQoAzc0QZcXRueDyNsnc+QWw3/QWf8/fkV\n"
- "HPfTWS3Dcuj+4RnWUucaZ/mKFlTC3+eNSlpyaPIMlCjXGsJ9GlPrsaAi9KPbD2v/\n"
- "XcMq/PMOowKBgHVf7S3sfZVQthFzdxqIvksQ84hKRW/vJT1B2bTkH56+fQhTsjgM\n"
- "yC64J85l7DjxbDnYsSngVWXHhOnvKV/nq0tbOcefcydCjsQREBNfvxvPajjTskgj\n"
- "FAQRQlxPL0U4f4khBk9EXhJ+PZithaHjZpNl1YfTSp62x3Yz4kTSeHnpAoGAGn5m\n"
- "5kArE7NdrzACBrwrfww7DL1Uyd8zSOLBgKutvEcQnqfNxSWO9la3TAarrESmH2Ic\n"
- "j+Nc15wOsl/5FwdUf1/73qa2zJKtHlY28qSeo8uRqrIYeSCvnyP3wjBoLc2C8zlb\n"
- "mGd6azdqr2DuYahHrcAzwjnC/6Zn+DXM7FOn7AkCgYBp1xxY88cCoF24yffkD3MC\n"
- "ACUury4qRSDTGx6/qCCkIyWxg1vuiDrlPWhSwQznxHvovcfpdjdbWcFY87IK6mpG\n"
- "aJHwMJ7Kw+baoxGPZWHwdg6BgvUCihe3xlcaq6rOBoLviD6FOzbogg++Tvi0LemG\n"
- "y/wEs/mZkaRzW4n41ir0Xw==\n"
- "-----END PRIVATE KEY-----\n"
- ;
- dkim_sign(s.TheMessage, pkey_in, "dev.citadel.org", "foo");
-#endif
+ // 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);
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
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);
/*
* 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>");
}
-#endif
if (!CC->logged_in) {
/* If we're not logged in yet, offer SASL as our feature set */
}
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;
-#else
- cprintf("<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
- CC->kill_me = KILLME_NO_CRYPTO;
-#endif
}
else if (!strcasecmp(el, "ping")) {
-/*
- * 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>
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;
} 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->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;
}
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)) {
}
-/*
- * 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) {
- 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) {
);
if ((Author != NULL) && (*Author == NULL)) {
- long len;
- CM_GetAsField(TheMessage, eAuthor, Author, &len);
+ *Author = strdup(TheMessage->cm_fields[eAuthor]);
}
if ((Address != NULL) && (*Address == NULL)) {
- long len;
- CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
+ *Address = strdup(TheMessage->cm_fields[erFc822Addr]);
}
if ((MessageID != NULL) && (*MessageID == NULL)) {
- long len;
- CM_GetAsField(TheMessage, emessageId, MessageID, &len);
+ *MessageID = strdup(TheMessage->cm_fields[emessageId]);
}
CM_Free(TheMessage);
TheMessage = NULL;
// "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);
-#endif
}
#endif
#include "citadel_defs.h"
-#ifdef HAVE_OPENSSL
#define OPENSSL_NO_KRB5 // work around redhat b0rken ssl headers
#include <openssl/ssl.h>
-#endif
// New format for a message in memory
#endif
// If we've got OpenSSL, we're going to use it.
-#ifdef HAVE_OPENSSL
init_ssl();
-#endif
if (pthread_key_create(&MyConKey, NULL) != 0) { // TSD for sessions
syslog(LOG_CRIT, "sysdep: can't create TSD key: %m");
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);
+ }
#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);
+ }
#endif
}
return 0;
}
-#ifdef HAVE_OPENSSL
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);
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");
}
}
- else
-#endif
- {
+ else {
retval = StrBufReadBLOBBuffered(Target,
CC->RecvBuf.Buf,
&CC->RecvBuf.ReadWritePointer,
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;
}
- else
-#endif
- {
+ else {
rc = StrBufTCP_read_buffered_line_fast(Target,
CC->RecvBuf.Buf,
&CC->RecvBuf.ReadWritePointer,
# dkimtester
-This was originally where we developed the DKIM code for Citadel
-but it is properly merged into Citadel now. What's left is just
-a test harness.
+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:
-You must have the `Mail::DKIM::Verifier` perl module installed,
-which can be found on CPAN.
+```
+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.
-Also it depends on a DKIM record for `dev.citadel.org` which is
-correct at the time I am writing this. Good luck keeping that in
-place.
+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.
// 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 <stdlib.h>
-#include <unistd.h>
#include <stdio.h>
-#include <ctype.h>
-#include <string.h>
-#include <time.h>
-#include <assert.h>
#include <syslog.h>
#include <libcitadel.h>
-// oof, a prototype where one does not belong hehe
+// 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);
- char *private_key =
- "-----BEGIN PRIVATE KEY-----\n"
- "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDfuefcepokRrnp\n"
- "SSDsxu+QDqeD8GL9QnZz/N6IxTdBv6Wc10ExBe2IjS5dKI7AvhSSEK0zGE8Hkpmw\n"
- "eccbiepQqeueteWzAMZ1uT43bD3k7eye7vWobiOP9QtoYGR6sG25h2W5Tbc91W4f\n"
- "dvYnxYVJjx8wIVF0f3o25v+rQueoo0HlvGyA9/xi9GAaJL05OmK1xnMJgSvW/Q8Q\n"
- "zq7apf1D6XPXHuhv5tevElkZ5jlvM2w0cTVyAzMrUh6Rkcn9xM4/NPWYghBc3jO4\n"
- "TrPnSrobQGrX0fcizE/FN6I0in0Ke8Z+gMM8NeFcsjvLZe9MpY9i0pw/ygLIh5t3\n"
- "O4qpwC1JAgMBAAECggEAIwiTCMEAGzciDKhhagJ66BWLYMtHTP5X2zDZThSH4xlW\n"
- "HznL4RfbCtuEy5y6we7h/L90x8ACPB7WRz7CkYrmsMvy9A7q0b2I1k10MyyVgqBJ\n"
- "QdgMitv4YKYQK7+QbG/tNrS/lqVXUOz3iiDQSgkRpqOtUBWfkj0WD7vbhF99NDhV\n"
- "dxaehFkKv3yNy0bXJlHJBJ6KtOUnDwub8TExh8dyj3kB8Qzj4I98shaXPNUSSaOw\n"
- "zG6QG72yrxlMs495jkIPbF2JDidmLrX+oVISwKyaBWx+BkFV/KFAEKgaB5/nCw7+\n"
- "qq/jxsmXim3HuQ3MIAjq1yw9aGRH1HMi8Gn7tYlNGwKBgQDy6EEKpuEiW9wwlI2+\n"
- "GVuSkhSTTX1h6qK/ay8Jtyb8yJM/BxogAQlfjdgFixiZHy5MaomTbfeT2GDji553\n"
- "+RsnZ60+g7FI9nHwabSxtuCQ+vjbFqCsdMPAiSeG0bEzo0zf5TjASdUtuZL0vXjl\n"
- "yMZWDEuESoVNlYlvCOVkw2nvIwKBgQDryPuSq6PNVHRWsKRRs5ju4wKs/1ucBOg5\n"
- "gCcN8lE03mFCWAlZhypE4/fAhTQ/a5KQoAzc0QZcXRueDyNsnc+QWw3/QWf8/fkV\n"
- "HPfTWS3Dcuj+4RnWUucaZ/mKFlTC3+eNSlpyaPIMlCjXGsJ9GlPrsaAi9KPbD2v/\n"
- "XcMq/PMOowKBgHVf7S3sfZVQthFzdxqIvksQ84hKRW/vJT1B2bTkH56+fQhTsjgM\n"
- "yC64J85l7DjxbDnYsSngVWXHhOnvKV/nq0tbOcefcydCjsQREBNfvxvPajjTskgj\n"
- "FAQRQlxPL0U4f4khBk9EXhJ+PZithaHjZpNl1YfTSp62x3Yz4kTSeHnpAoGAGn5m\n"
- "5kArE7NdrzACBrwrfww7DL1Uyd8zSOLBgKutvEcQnqfNxSWO9la3TAarrESmH2Ic\n"
- "j+Nc15wOsl/5FwdUf1/73qa2zJKtHlY28qSeo8uRqrIYeSCvnyP3wjBoLc2C8zlb\n"
- "mGd6azdqr2DuYahHrcAzwjnC/6Zn+DXM7FOn7AkCgYBp1xxY88cCoF24yffkD3MC\n"
- "ACUury4qRSDTGx6/qCCkIyWxg1vuiDrlPWhSwQznxHvovcfpdjdbWcFY87IK6mpG\n"
- "aJHwMJ7Kw+baoxGPZWHwdg6BgvUCihe3xlcaq6rOBoLviD6FOzbogg++Tvi0LemG\n"
- "y/wEs/mZkaRzW4n41ir0Xw==\n"
- "-----END PRIVATE KEY-----\n"
- ;
+ // dkim.c can handle a PEM-encoded PKCS#7 private key that has had all of the newlines replaced by underscores.
+ // It will convert them back to newlines before importing the key.
+ // This is the format that Citadel Server uses to store the key in its configuration database.
+ char *private_key = "-----BEGIN PRIVATE KEY-----_MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDfuefcepokRrnp_SSDsxu+QDqeD8GL9QnZz/N6IxTdBv6Wc10ExBe2IjS5dKI7AvhSSEK0zGE8Hkpmw_eccbiepQqeueteWzAMZ1uT43bD3k7eye7vWobiOP9QtoYGR6sG25h2W5Tbc91W4f_dvYnxYVJjx8wIVF0f3o25v+rQueoo0HlvGyA9/xi9GAaJL05OmK1xnMJgSvW/Q8Q_zq7apf1D6XPXHuhv5tevElkZ5jlvM2w0cTVyAzMrUh6Rkcn9xM4/NPWYghBc3jO4_TrPnSrobQGrX0fcizE/FN6I0in0Ke8Z+gMM8NeFcsjvLZe9MpY9i0pw/ygLIh5t3_O4qpwC1JAgMBAAECggEAIwiTCMEAGzciDKhhagJ66BWLYMtHTP5X2zDZThSH4xlW_HznL4RfbCtuEy5y6we7h/L90x8ACPB7WRz7CkYrmsMvy9A7q0b2I1k10MyyVgqBJ_QdgMitv4YKYQK7+QbG/tNrS/lqVXUOz3iiDQSgkRpqOtUBWfkj0WD7vbhF99NDhV_dxaehFkKv3yNy0bXJlHJBJ6KtOUnDwub8TExh8dyj3kB8Qzj4I98shaXPNUSSaOw_zG6QG72yrxlMs495jkIPbF2JDidmLrX+oVISwKyaBWx+BkFV/KFAEKgaB5/nCw7+_qq/jxsmXim3HuQ3MIAjq1yw9aGRH1HMi8Gn7tYlNGwKBgQDy6EEKpuEiW9wwlI2+_GVuSkhSTTX1h6qK/ay8Jtyb8yJM/BxogAQlfjdgFixiZHy5MaomTbfeT2GDji553_+RsnZ60+g7FI9nHwabSxtuCQ+vjbFqCsdMPAiSeG0bEzo0zf5TjASdUtuZL0vXjl_yMZWDEuESoVNlYlvCOVkw2nvIwKBgQDryPuSq6PNVHRWsKRRs5ju4wKs/1ucBOg5_gCcN8lE03mFCWAlZhypE4/fAhTQ/a5KQoAzc0QZcXRueDyNsnc+QWw3/QWf8/fkV_HPfTWS3Dcuj+4RnWUucaZ/mKFlTC3+eNSlpyaPIMlCjXGsJ9GlPrsaAi9KPbD2v/_XcMq/PMOowKBgHVf7S3sfZVQthFzdxqIvksQ84hKRW/vJT1B2bTkH56+fQhTsjgM_yC64J85l7DjxbDnYsSngVWXHhOnvKV/nq0tbOcefcydCjsQREBNfvxvPajjTskgj_FAQRQlxPL0U4f4khBk9EXhJ+PZithaHjZpNl1YfTSp62x3Yz4kTSeHnpAoGAGn5m_5kArE7NdrzACBrwrfww7DL1Uyd8zSOLBgKutvEcQnqfNxSWO9la3TAarrESmH2Ic_j+Nc15wOsl/5FwdUf1/73qa2zJKtHlY28qSeo8uRqrIYeSCvnyP3wjBoLc2C8zlb_mGd6azdqr2DuYahHrcAzwjnC/6Zn+DXM7FOn7AkCgYBp1xxY88cCoF24yffkD3MC_ACUury4qRSDTGx6/qCCkIyWxg1vuiDrlPWhSwQznxHvovcfpdjdbWcFY87IK6mpG_aJHwMJ7Kw+baoxGPZWHwdg6BgvUCihe3xlcaq6rOBoLviD6FOzbogg++Tvi0LemG_y/wEs/mZkaRzW4n41ir0Xw==_-----END PRIVATE KEY-----";
- // These elements are identical to the ones from previous revisions. Don't change them; we need to compare.
char *domain = "dev.citadel.org";
char *selector = "foo";
"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"
+ "Subject: The ultimate test message\r\n"
"Message-ID: <73294856-8726543-473298@dev.citadel.org>\r\n"
"\r\n"
"Hi.\r\n"
"Bhille Disassemble. Highly recommend.\r\n"
"\r\n"
"--Fred\r\n"
+
+
));
// create signature
FILE *fp;
printf("\033[34m-----\033[0m\n");
- printf("Piping original version to test program...\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...\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);
// 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.
char userkey[USERNAME_SIZE];
char *token;
struct ctdluser *u;
+ int dlen = 0;
u = malloc(sizeof(struct ctdluser));
if (!u) {
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);
++good_rows;
}
else {
+ fprintf(stderr, "bad row: <%s>\n", line);
++bad_rows;
}
}
if (line_len > 0) {
+ if (!strncasecmp(line, HKEY("end|"))) {
+ fprintf(stderr, "\n");
+ end_found = 1;
+ }
if ( (begin_found) && (!end_found) ) {
ingest_one(line, &kv);
}
begin_found = 1;
fprintf(stderr, " good rows / bad rows:\n");
}
- if (!strncasecmp(line, HKEY("end|"))) {
- fprintf(stderr, "\n");
- end_found = 1;
- }
}
} while (ch >= 0);
#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.
#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 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 */