#!/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
// 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());
}
struct passwd *pw;
gid_t gid;
- syslog(LOG_DEBUG, "master_startup() started");
+ syslog(LOG_DEBUG, "citserver: master_startup() started");
time(&server_startup_time);
- syslog(LOG_INFO, "Checking directory access");
+ syslog(LOG_INFO, "citserver: checking directory access");
if ((pw = getpwuid(ctdluid)) == NULL) {
gid = getgid();
}
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
- syslog(LOG_INFO, "Initializing configuration system");
+ syslog(LOG_INFO, "citserver: initializing configuration system");
initialize_config_system();
validate_config();
migrate_legacy_control_record();
// 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);
CtdlPutRoomLock(&qrbuf);
}
- syslog(LOG_DEBUG, "master_startup() finished");
+ syslog(LOG_DEBUG, "citserver: master_startup() finished");
}
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);
}
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 {
// 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>
-/*
- * 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"
#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-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>
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) {
}
// return our module name for the log
- return "listsub";
+ return "listdeliver";
}
}
+// 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) {
- // 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"));
"<%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"
"--__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"
,
- 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");
// 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"));
"<%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"
"--__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"
,
- 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");
--- /dev/null
+// 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!
+}
+
+
--- /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);
+}
//
// 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.
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);
+
+ // If we have a source room, it's probably a mailing list message; generate an unsubscribe header
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;
- s.bytes_total = StrLength(CC->redirect_buffer);
- s.bytes_sent = 0;
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;
- // 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
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;
}
}
}
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) {
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
-/*
- * 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;
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);
);
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;
--- /dev/null
+dkimtester
--- /dev/null
+# 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
--- /dev/null
+# 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.
--- /dev/null
+// ***** 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);
+}
--- /dev/null
+#!/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');
+}
// 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);
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:
#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.
-// 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"
#endif
int BaseStrBufSize = 64;
int EnableSplice = 0;
-int ZLibCompressionRatio = -1; /* defaults to 6 */
+int ZLibCompressionRatio = -1; // defaults to 6
#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;
}
#else
-/* void it... */
+// void it...
#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;
}
-/*
- * 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 "";
}
-/*
- * 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;
}
}
-/*
- * 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;
}
-/*
- * 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;
}
-/*
- * 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;
}
-/*
- * 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;
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);
}
-/*
- * 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;
}
-/*
- * 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;
}
-/*
- * 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));
}
-/*
- * 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;
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)
#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;
*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;
}
-/*
- * 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)
}
-/*******************************************************************************
- * Simple string transformations *
- *******************************************************************************/
+//******************************************************************************
+// Simple string transformations *
+//******************************************************************************/
-// Wrapper around atol
+// Wrapper around atol
long StrTol(const StrBuf *Buf) {
if (Buf == NULL)
return 0;
}
-// Wrapper around atoi
+// Wrapper around atoi
int StrToi(const StrBuf *Buf) {
if (Buf == NULL)
return 0;
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);
Buf->buf[Buf->BufUsed] = '\0';
}
-/**
- * Cut the trailing n Chars from the string
- * Buf Buffer to modify
- * nChars how many chars should be trunkated?
- */
-void StrBufCutRight(StrBuf *Buf, int nChars)
-{
+// Cut the trailing n Chars from the string
+// Buf Buffer to modify
+// nChars how many chars should be trunkated?
+void StrBufCutRight(StrBuf *Buf, int nChars) {
if ((Buf == NULL) || (Buf->BufUsed == 0) || (Buf->buf == NULL))
return;
Buf->buf[Buf->BufUsed] = '\0';
}
-/**
- * Cut the string after n Chars
- * Buf Buffer to modify
- * AfternChars after how many chars should we trunkate the string?
- * At if non-null and points inside of our string, cut it there.
- */
-void StrBufCutAt(StrBuf *Buf, int AfternChars, const char *At)
-{
+
+// Cut the string after n Chars
+// Buf Buffer to modify
+// AfternChars after how many chars should we trunkate the string?
+// At if non-null and points inside of our string, cut it there.
+void StrBufCutAt(StrBuf *Buf, int AfternChars, const char *At) {
if ((Buf == NULL) || (Buf->BufUsed == 0)) return;
if (At != NULL){
AfternChars = At - Buf->buf;
}
-/**
- * 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;
}
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;
}
}
-void StrBufStripAllBut(StrBuf *Buf, char leftboundary, char rightboundary)
-{
+void StrBufStripAllBut(StrBuf *Buf, char leftboundary, char rightboundary) {
const char *pLeft;
const char *pRight;
}
-/**
- * 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;
}
-/**
- * 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;
}
-/*******************************************************************************
- * 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)) {
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;
}
-/**
- * Counts the numbmer of tokens in a buffer
- * source String to count tokens in
- * tok Tokenizer char to count
- * @returns numbers of tokenizer chars found
- */
+// Counts the numbmer of tokens in a buffer
+// source String to count tokens in
+// tok Tokenizer char to count
+// @returns numbers of tokenizer chars found
int StrBufNum_tokens(const StrBuf *source, char tok) {
char *pch, *pche;
long NTokens;
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;
- char *d, *s, *end; /* dest, source */
+ char *d, *s, *end; // dest, source
int count = 0;
- /* Find desired eter */
+ // Find desired eter
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;
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++;
ReducedBy = d - s;
- /* Hack and slash */
+ // Hack and slash
if (s >= end) {
return 0;
}
*--d = '\0';
Source->BufUsed += ReducedBy;
}
- /*
- while (*s) {
- *d++ = *s++;
- }
- *d = 0;
- */
+
+ //while (*s) {
+ //*d++ = *s++;
+ //}
+ //*d = 0;
+
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,
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';
}
-
-
-
-/**
- * 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];
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];
}
-/**
- * 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;
}
-
-/**
- * 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;
* 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;
* nolinebreaks if set to 1, linebreaks are removed from the string.
* if set to 2, linebreaks are replaced by <br/>
*/
-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;
* 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;
* 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;
* 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;
* 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;
* 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;
* 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;
* 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;
* 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);
* 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)
* 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;
* 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));
* 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);
* 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);
}
* 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;
* 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;
* 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;
* 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;
* 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)
* 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)) {
* 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;
#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 */