Merge branch 'ConstStr_Access_Checks' into 'master'
authorArt Cancro <ajc@citadel.org>
Wed, 2 Aug 2023 00:44:02 +0000 (00:44 +0000)
committerArt Cancro <ajc@citadel.org>
Wed, 2 Aug 2023 00:44:02 +0000 (00:44 +0000)
IMAP memory issues with use of ConstStr

See merge request citadel/citadel!5

13 files changed:
citadel/Makefile
citadel/server/citadel_defs.h
citadel/server/citadel_dirs.h
citadel/server/database.c
citadel/server/makeuserkey.c
citadel/server/modules/smtp/serv_smtpclient.c
citadel/server/sysdep.c
citadel/utils/ctdldump.c
citadel/utils/ctdlload.c
libcitadel/lib/libcitadel.h
release_version.txt
textclient/textclient.h
webcit/webcit.h

index 87c3101585a7a0b889b2eb3cb090725f6849301d..5f51c9c6d054d999e58bb747cca73483eec71162 100644 (file)
@@ -10,7 +10,7 @@
 # config.mk is generated by ./configure
 include config.mk
 
-all := citserver setup sendcommand citmail chkpw chkpwd ctdldump ctdlload
+all := ctdldump ctdlload citserver setup sendcommand citmail chkpw chkpwd
 all: $(all)
 
 SRCDIRS := $(wildcard server server/modules/*)
@@ -50,10 +50,10 @@ chkpwd: utils/chkpwd.c utils/auth.c utils/*.h server/*.h
        cc ${CFLAGS} ${LDFLAGS} utils/chkpwd.c utils/auth.c -lcrypt -o chkpwd
 
 ctdldump: utils/ctdldump.c utils/*.h server/*.h
-       cc ${CFLAGS} ${LDFLAGS} utils/ctdldump.c -lcitadel -lz -ldb -o ctdldump
+       cc ${CFLAGS} ${LDFLAGS} utils/ctdldump.c -lcitadel -lz -ldb -lpthread -o ctdldump
 
 ctdlload: utils/ctdlload.c server/makeuserkey.c utils/*.h server/*.h
-       cc ${CFLAGS} ${LDFLAGS} utils/ctdlload.c server/makeuserkey.c -lcitadel -lz -ldb -o ctdlload
+       cc ${CFLAGS} ${LDFLAGS} utils/ctdlload.c server/makeuserkey.c -lcitadel -lz -ldb -lpthread -o ctdlload
 
 config.mk: configure
        ./configure
index 7a6929aaa2a4e3b06ef3e24d5bc5649aa3999e0c..d39571f905fe2a502e5fc1d3cf491f8fa0818e72 100644 (file)
@@ -21,7 +21,7 @@
 #include "typesize.h"
 #include "ipcdef.h"
 
-#define REV_LEVEL 980          // This version
+#define REV_LEVEL 988          // 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
index 5710a1f4ddbeee23eccd22d61b447307ef6bcdbd..9d00640db5603ed6bd0feef5b762092b31913c0f 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (c) 1987-2022 by the citadel.org team
+// Copyright (c) 1987-2023 by the citadel.org team
 //
 // This program is open source software.  Use, duplication, or disclosure
 // is subject to the terms of the GNU General Public License, version 3.
@@ -38,8 +38,8 @@
 #define file_pid_paniclog              "panic.log"
 #define file_crpt_file_key             "keys/citadel.key"
 #define file_crpt_file_cer             "keys/citadel.cer"
-#define file_chkpwd                    CTDLDIR "chkpwd"
-#define file_guesstimezone             CTDLDIR "guesstimezone.sh"
+#define file_chkpwd                    CTDLDIR "/chkpwd"
+#define file_guesstimezone             CTDLDIR "/guesstimezone.sh"
 
 
 // externs
index 2085dc72971bdf03949e846f1675565b7a52d698..cb5adafca46246a38ac1c036a240028034e72423 100644 (file)
@@ -296,6 +296,12 @@ void close_databases(void) {
        int i;
        int ret;
 
+       static int closing = 0;
+       while (closing == 1) {
+               syslog(LOG_INFO, "db: already closing");
+       }
+       closing = 1;
+
        syslog(LOG_INFO, "db: performing final checkpoint");
        if ((ret = dbenv->txn_checkpoint(dbenv, 0, 0, 0))) {
                syslog(LOG_ERR, "db: txn_checkpoint: %s", db_strerror(ret));
@@ -314,16 +320,10 @@ void close_databases(void) {
                if (ret) {
                        syslog(LOG_ERR, "db: db_close: %s", db_strerror(ret));
                }
-
        }
 
-       // This seemed nifty at the time but did anyone really look at it?
-       // #ifdef DB_STAT_ALL
-       // dbenv->lock_stat_print(dbenv, DB_STAT_ALL);
-       // #endif
-
        // Close the handle.
-       ret = dbenv->close(dbenv, 0);
+       ret = dbenv->close(dbenv, DB_FORCESYNC);
        if (ret) {
                syslog(LOG_ERR, "db: DBENV->close: %s", db_strerror(ret));
        }
index 5729034a504c0ed423951c03b12cae5fc8004f3b..f7f9fd19e06dd7df8233691b3491746c571ab8f1 100644 (file)
@@ -11,6 +11,7 @@
 #include <stdio.h>
 #include <sys/stat.h>
 #include <libcitadel.h>
+#include <string.h>
 #include "config.h"
 #include "user_ops.h"
 
index 9a91dc164e7c3fe1586c48f29d253d374045a9c3..5bbc77d1720007e582d8efc3423b5c34691cbd55 100644 (file)
@@ -167,12 +167,6 @@ void trim_response(long response_code, char *response) {
                return;
        }
 
-       char *t = malloc(strlen(response));
-       if (!t) {
-               return;
-       }
-       t[0] = 0;
-
        char *p;
        for (p = response; *p != 0; ++p) {
                if ( (*p != '\n') && (!isprint(*p)) ) {         // expunge any nonprintables except for newlines
index 21ba4c7b055d1eefce318cf308d1b8d95be6be2b..798e218d051dc6b6200557bc2141a795bf747dbe 100644 (file)
@@ -45,8 +45,13 @@ volatile int running_as_daemon = 0;
 
 static RETSIGTYPE signal_cleanup(int signum) {
        syslog(LOG_DEBUG, "sysdep: caught signal %d", signum);
+       signal(SIGINT, SIG_DFL);
+       signal(SIGHUP, SIG_DFL);
+       signal(SIGTERM, SIG_DFL);
+       signal(SIGSEGV, SIG_DFL);
        exit_signal = signum;
        server_shutting_down = 1;
+       master_cleanup(signum);
 }
 
 
@@ -85,11 +90,13 @@ void init_sysdep(void) {
        sigaddset(&set, SIGINT);
        sigaddset(&set, SIGHUP);
        sigaddset(&set, SIGTERM);
+       sigaddset(&set, SIGSEGV);
        sigprocmask(SIG_UNBLOCK, &set, NULL);
 
        signal(SIGINT, signal_cleanup);
        signal(SIGHUP, signal_cleanup);
        signal(SIGTERM, signal_cleanup);
+       signal(SIGSEGV, signal_cleanup);
 
        // Do not shut down the server on broken pipe signals, otherwise the
        // whole Citadel service would come down whenever a single client
@@ -573,7 +580,7 @@ void sysdep_master_cleanup(void) {
 
 
 pid_t current_child;
-void graceful_shutdown(int signum) {
+void supervisor_process_shutdown(int signum) {
        kill(current_child, signum);
        unlink(file_pid_file);
        exit(0);
@@ -614,7 +621,7 @@ void start_daemon(int unused) {
 
        do {
                current_child = fork();
-               signal(SIGTERM, graceful_shutdown);
+               signal(SIGTERM, supervisor_process_shutdown);
                if (current_child < 0) {
                        perror("fork");
                        exit(errno);
index 9bfe93c59e11a8545da2498180542755c3edcbe0..ccd6c18cc41ff254376e8a43027afd845bc3e0b7 100644 (file)
@@ -33,7 +33,7 @@ void *reallok(void *ptr, size_t size) {
        void *p = realloc(ptr, size);
        if (!p) {
                fprintf(stderr, "realloc() failed to resize %p to %ld bytes, error: %m\n", ptr, size);
-               exit(1);
+               abort();
        }
        return p;
 }
@@ -43,8 +43,6 @@ void *reallok(void *ptr, size_t size) {
 char *b64out(void *data, size_t len) {
        static char *outbuf = NULL;
        static size_t outlen = 0;
-       int i;
-       char ch;
 
        if ((outbuf == NULL) || (outlen < (len * 2))) {
                outbuf = reallok(outbuf, (len * 2));
@@ -218,8 +216,8 @@ void export_floor(int which_cdb, DBT *in_key, DBT *in_data) {
 }
 
 
-// export function for a msglist or a fulltext index record
-// (both are indexed by a long and the data is arrays of longs)
+// export function for a msglist
+// (indexed by a long and the data is arrays of longs)
 void export_msglist(int which_cdb, DBT *in_key, DBT *in_data) {
        int i;
        int num_msgs;
@@ -245,9 +243,55 @@ void export_msglist(int which_cdb, DBT *in_key, DBT *in_data) {
 }
 
 
+// export function for a full text search index record
+// (indexed by an int and the data is arrays of longs)
+void export_fulltext(int which_cdb, DBT *in_key, DBT *in_data) {
+       int i;
+       int num_msgs;
+       long msg;
+
+       // records are indexed by a single "int" and contains an array of zero or more "long"s
+       int indexnum;
+       memcpy(&indexnum, in_key->data, sizeof(int));
+
+       printf("fulltext|%d|", indexnum);
+
+       if (in_data->size > 0) {
+               num_msgs = in_data->size / sizeof(long);
+               for (i=0; i<num_msgs; ++i) {
+                       memcpy(&msg, (in_data->data + (i * sizeof(long))), sizeof(long));
+                       if (i != 0) {
+                               printf(",");
+                       }
+                       printf("%ld", msg);
+               }
+       }
+       printf("|\n");
+}
+
+
 // export function for a visit record
 void export_visit(int which_cdb, DBT *in_key, DBT *in_data) {
        struct visit *visit = (struct visit *)in_data->data;
+       int i, len;
+
+       // If there is corrupt data in the "seen" array, cut that out before exporting
+       len = strlen(visit->v_seen);
+       for (i=0; i<len; ++i) {
+               if (!isprint(visit->v_seen[i])) {
+                       visit->v_seen[i] = 0;
+               }
+       }
+
+       // If there is corrupt data in the "answered" array, cut that out before exporting
+       len = strlen(visit->v_answered);
+       for (i=0; i<len; ++i) {
+               if (!isprint(visit->v_answered[i])) {
+                       visit->v_answered[i] = 0;
+               }
+       }
+
+       // output the record
        printf("visit|%ld|%ld|%ld|%ld|%u|%s|%s|%d|\n",
                visit->v_roomnum,
                visit->v_roomgen,
@@ -262,12 +306,11 @@ void export_visit(int which_cdb, DBT *in_key, DBT *in_data) {
 
 
 // export function for a directory record
-// (This is a secondary index -- should we just regenerate the data after import?)
-// void export_dir(int which_cdb, DBT *in_key, DBT *in_data) {
-       // printf("dir|");
-       // fwrite(in_key->data, in_key->size, 1, stdout);
-       // printf("|%s|\n", (char *)in_data->data);
-// }
+void export_dir(int which_cdb, DBT *in_key, DBT *in_data) {
+       printf("dir|");
+       fwrite(in_key->data, in_key->size, 1, stdout);
+       printf("|%s|\n", (char *)in_data->data);
+}
 
 
 // export function for a use table record
@@ -287,7 +330,7 @@ void export_bigmsg(int which_cdb, DBT *in_key, DBT *in_data) {
 
 
 // export function for EUID Index records
-//void export_euidindex(int which_cdb, DBT *in_key, DBT *in_data) {
+void export_euidindex(int which_cdb, DBT *in_key, DBT *in_data) {
 
        // The structure of an euidindex record *key* is:
        // |----room_number----|----------EUID-------------|
@@ -297,28 +340,28 @@ void export_bigmsg(int which_cdb, DBT *in_key, DBT *in_data) {
        // |-----msg_number----|----room_number----|----------EUID-------------|
        //    (sizeof long)       (sizeof long)       (actual length of euid)
 
-       //long msgnum, roomnum;
-       //char *euid;
+       long msgnum, roomnum;
+       char *euid;
 
-       //memcpy(&msgnum, in_data->data, sizeof(long));
-       //memcpy(&roomnum, in_data->data+sizeof(long), sizeof(msgnum));
-       //euid = in_data->data+(sizeof(long)*2);
-//
-       //printf("euidindex|%ld|%ld|%s|\n", msgnum, roomnum, euid);
-//}
+       memcpy(&msgnum, in_data->data, sizeof(long));
+       memcpy(&roomnum, in_data->data+sizeof(long), sizeof(msgnum));
+       euid = in_data->data+(sizeof(long)*2);
+
+       printf("euidindex|%ld|%ld|%s|\n", msgnum, roomnum, euid);
+}
 
 
 // export users-by-number records
 // (This is a secondary index -- should we just regenerate the data after import?)
-//void export_usersbynumber(int which_cdb, DBT *in_key, DBT *in_data) {
+void export_usersbynumber(int which_cdb, DBT *in_key, DBT *in_data) {
 
        // key is a long
-       //long usernum;
-       //memcpy(&usernum, in_key->data, sizeof(usernum));
+       long usernum;
+       memcpy(&usernum, in_key->data, sizeof(usernum));
 
        // value is a string
-       //printf("usersbynumber|%ld|%s|\n", usernum, (char *)in_data->data);
-//}
+       printf("usersbynumber|%ld|%s|\n", usernum, (char *)in_data->data);
+}
 
 
 // export function for a config record
@@ -339,20 +382,20 @@ void zero_function(int which_cdb, DBT *in_key, DBT *in_data) {
 
 
 void (*export_functions[])(int which_cdb, DBT *in_key, DBT *in_data) = {
-       export_msgmain,         // CDB_MSGMAIN
-       export_user,            // CDB_USERS
-       export_room,            // CDB_ROOMS
-       export_floor,           // CDB_FLOORTAB
-       export_msglist,         // CDB_MSGLISTS
-       export_visit,           // CDB_VISIT
-       zero_function,          // CDB_DIRECTORY (regenerate this on the server)
-       export_usetable,        // CDB_USETABLE
-       export_bigmsg,          // CDB_BIGMSGS
-       zero_function,          // CDB_FULLTEXT (regenerate this on the server)
-       zero_function,          // CDB_EUIDINDEX (regenerate this on the server)
-       zero_function,          // CDB_USERSBYNUMBER (regenerate this on the server)
-       zero_function,          // CDB_UNUSED1 (obsolete)
-       export_config           // CDB_CONFIG
+       export_msgmain,         // 00 CDB_MSGMAIN       
+       export_user,            // 01 CDB_USERS
+       export_room,            // 02 CDB_ROOMS
+       export_floor,           // 03 CDB_FLOORTAB
+       export_msglist,         // 04 CDB_MSGLISTS
+       export_visit,           // 05 CDB_VISIT
+       export_dir,             // 06 CDB_DIRECTORY
+       export_usetable,        // 07 CDB_USETABLE
+       export_bigmsg,          // 08 CDB_BIGMSGS
+       export_fulltext,        // 09 CDB_FULLTEXT
+       export_euidindex,       // 0a CDB_EUIDINDEX
+       export_usersbynumber,   // 0b CDB_USERSBYNUMBER
+       zero_function,          // 0c CDB_UNUSED1 (obsolete)
+       export_config           // 0d CDB_CONFIG
 };
 
 
@@ -382,60 +425,60 @@ void export_table(int which_cdb, DB_ENV *src_dbenv) {
        // open the file containing the source table
        ret = src_dbp->open(src_dbp, NULL, dbfilename, NULL, DB_BTREE, 0, 0600);
        if (ret) {
-               fprintf(stderr, "ctdldump: db_open: %s\n", db_strerror(ret));
-               fprintf(stderr, "ctdldump: exit code %d\n", ret);
-               exit(CTDLEXIT_DB);
+               fprintf(stderr, "ctdldump: db_open(%s): %s (skipping empty or missing table)\n", dbfilename, db_strerror(ret));
        }
+       else {
 
-       // Acquire a cursor to read the source table
-       if ((ret = src_dbp->cursor(src_dbp, NULL, &src_dbcp, 0)) != 0) {
-               fprintf(stderr, "ctdldump: db_cursor: %s\n", db_strerror(ret));
-               fprintf(stderr, "ctdldump: exit code %d\n", ret);
-               exit(CTDLEXIT_DB);
-       }
-
-       // Zero out these database keys
-       memset(&in_key,         0, sizeof(DBT));        // input
-       memset(&in_data,        0, sizeof(DBT));
-       memset(&uncomp_data,    0, sizeof(DBT));        // decompressed input (the key doesn't change)
-
-       // Walk through the database, calling export functions as we go and clearing buffers before each call.
-       while (ret = src_dbcp->get(src_dbcp, &in_key, &in_data, DB_NEXT) == 0) {
-       
-               // If either the key or data are zero length, skip this record
-               if ((in_key.size == 0) || (in_data.size == 0)) {
-                       ++num_bad_rows;
+               // Acquire a cursor to read the source table
+               if ((ret = src_dbp->cursor(src_dbp, NULL, &src_dbcp, 0)) != 0) {
+                       fprintf(stderr, "ctdldump: db_cursor: %s\n", db_strerror(ret));
+                       fprintf(stderr, "ctdldump: exit code %d\n", ret);
+                       exit(CTDLEXIT_DB);
                }
-
-               else {  // Both key and data are >0 length so we're good to go
-
-                       // Do we need to decompress?
-                       static int32_t magic = COMPRESS_MAGIC;
-                       compressed = 0;
-                       if ((in_data.size >= sizeof(struct CtdlCompressHeader)) && (!memcmp(in_data.data, &magic, sizeof(magic)))) {
        
-                               // yes, we need to decompress
-                               compressed = 1;
-                               struct CtdlCompressHeader comp;
-                               memcpy(&comp, in_data.data, sizeof(struct CtdlCompressHeader));
-                               uncomp_data.size = comp.uncompressed_len;
-                               uncomp_data.data = reallok(uncomp_data.data, uncomp_data.size);
-                               destLen = (uLongf)comp.uncompressed_len;
+               // Zero out these database keys
+               memset(&in_key,         0, sizeof(DBT));        // input
+               memset(&in_data,        0, sizeof(DBT));
+               memset(&uncomp_data,    0, sizeof(DBT));        // decompressed input (the key doesn't change)
        
-                               ret = uncompress((Bytef *)uncomp_data.data, (uLongf *)&destLen,
-                                               (const Bytef *)in_data.data+sizeof(struct CtdlCompressHeader),
-                                               (uLong)comp.compressed_len);
-                               if (ret != Z_OK) {
-                                       fprintf(stderr, "ctdldump: uncompress() error %d\n", ret);
-                                       exit(CTDLEXIT_DB);
-                               }
+               // Walk through the database, calling export functions as we go and clearing buffers before each call.
+               while (ret = src_dbcp->get(src_dbcp, &in_key, &in_data, DB_NEXT) == 0) {
+               
+                       // If either the key or data are zero length, skip this record
+                       if ((in_key.size == 0) || (in_data.size == 0)) {
+                               ++num_bad_rows;
                        }
        
-                       // Call the export function registered to this table
-                       export_functions[which_cdb](which_cdb, &in_key, (compressed ? &uncomp_data : &in_data));
+                       else {  // Both key and data are >0 length so we're good to go
        
-                       // Knowing the total number of rows isn't critical to the program.  It's just for the user to know.
-                       fflush(stdout);
+                               // Do we need to decompress?
+                               static int32_t magic = COMPRESS_MAGIC;
+                               compressed = 0;
+                               if ((in_data.size >= sizeof(struct CtdlCompressHeader)) && (!memcmp(in_data.data, &magic, sizeof(magic)))) {
+               
+                                       // yes, we need to decompress
+                                       compressed = 1;
+                                       struct CtdlCompressHeader comp;
+                                       memcpy(&comp, in_data.data, sizeof(struct CtdlCompressHeader));
+                                       uncomp_data.size = comp.uncompressed_len;
+                                       uncomp_data.data = reallok(uncomp_data.data, uncomp_data.size);
+                                       destLen = (uLongf)comp.uncompressed_len;
+               
+                                       ret = uncompress((Bytef *)uncomp_data.data, (uLongf *)&destLen,
+                                                       (const Bytef *)in_data.data+sizeof(struct CtdlCompressHeader),
+                                                       (uLong)comp.compressed_len);
+                                       if (ret != Z_OK) {
+                                               fprintf(stderr, "ctdldump: uncompress() error %d\n", ret);
+                                               exit(CTDLEXIT_DB);
+                                       }
+                               }
+               
+                               // Call the export function registered to this table
+                               export_functions[which_cdb](which_cdb, &in_key, (compressed ? &uncomp_data : &in_data));
+               
+                               // Knowing the total number of rows isn't critical to the program.  It's just for the user to know.
+                               fflush(stdout);
+                       }
                }
        }
 
@@ -454,54 +497,60 @@ void export_table(int which_cdb, DB_ENV *src_dbenv) {
 
 int main(int argc, char **argv) {
        int i = 0;
-       char *src_dir = NULL;
-       char *dst_dir = NULL;
+       char src_dir[PATH_MAX];
        int confirmed = 0;
        static DB_ENV *src_dbenv;               // Source DB environment (global)
 
        // display the greeting
-       fprintf(stderr, "\033[44m\033[30m \033[K\033[0m\n"
-                       "\033[44m\033[30m DB Dump utility for Citadel \033[K\033[0m\n"
-                       "\033[44m\033[30m Copyright (c) 2023 by citadel.org et al.  \033[K\033[0m\n"
-                       "\033[44m\033[30m This program is open source software.  Use, duplication, or disclosure \033[K\033[0m\n"
-                       "\033[44m\033[30m is subject to the terms of the GNU General Public license v3. \033[K\033[0m\n"
-                       "\033[44m\033[30m \033[K\033[0m\n");
+       fprintf(stderr, "\033[44m\033[33m\033[1m \033[K\033[0m\n"
+                       "\033[44m\033[33m\033[1m DB Dump utility for Citadel \033[K\033[0m\n"
+                       "\033[44m\033[33m\033[1m Copyright (c) 2023 by citadel.org et al.  \033[K\033[0m\n"
+                       "\033[44m\033[33m\033[1m This program is open source software.  Use, duplication, or disclosure \033[K\033[0m\n"
+                       "\033[44m\033[33m\033[1m is subject to the terms of the GNU General Public license v3. \033[K\033[0m\n"
+                       "\033[44m\033[33m\033[1m \033[K\033[0m\n");
+
+       // Default source directory unless overridden
+       snprintf(src_dir, sizeof(src_dir), "%s/data", CTDLDIR);
 
        // Parse command line
        int a;
        while ((a = getopt(argc, argv, "h:y")) != EOF) {
                switch (a) {
                case 'h':
-                       src_dir = optarg;
+                       snprintf(src_dir, sizeof(src_dir), "%s/data", optarg);
                        break;
                case 'y':
                        confirmed = 1;
                        break;
                default:
-                       fprintf(stderr, "%s: usage: %s -s source_dir [>dumpfile]\n", argv[0], argv[0]);
+                       fprintf(stderr, "%s: usage: %s -s citadel_dir [>dumpfile]\n", argv[0], argv[0]);
                        exit(2);
                }
        }
 
+       if (src_dir == NULL) {
+               fprintf(stderr, "ctdldump: no source directory was specified.\n");
+               exit(1);
+       }
+
        if (confirmed == 1) {
                fprintf(stderr, "ctdldump: You have specified the [-y] flag, so processing will continue.\n");
        }
        else {
-               fprintf(stderr, "ctdldump: Please read [ https://www.citadel.org/dump-and-load.html ] to learn how to proceed.\n");
-               exit(0);
+               fprintf(stderr, "ctdldump: usage: ctdldump -y -h[citadel_dir] >[dump_file]\n");
+               fprintf(stderr, "          [citadel_dir] is your database directory, usually /usr/local/citadel\n");
+               fprintf(stderr, "          Please read [ https://www.citadel.org/dump-and-load.html ] to learn how to proceed.\n");
+               exit(1);
        }
 
        src_dbenv = open_dbenv(src_dir);
        printf("begin|\n");
        for (i = 0; i < MAXCDB; ++i) {
                export_table(i, src_dbenv);
-               if (i == CDB_CONFIG) {
-                       printf("config|regenerate_secondary_indices|1|\n");             // Force citserver to rebuild those tables
-                       printf("config|MM_fulltext_wordbreaker|0|\n");                  // Burn the full text search index
-               }
        }
        close_dbenv(src_dbenv);
        printf("end|\n");
 
+       fprintf(stderr, "ctdldump: \033[32m\033[1mfinished\033[0m\n");
        exit(0);
 }
index 80f00bd9866165933bc35d4f07775311af931d4b..59cc9b785ce91e8f94a97aa609cdd0f749cac610 100644 (file)
@@ -34,7 +34,7 @@ void *reallok(void *ptr, size_t size) {
        void *p = realloc(ptr, size);
        if (!p) {
                fprintf(stderr, "realloc() failed to resize %p to %ld bytes, error: %m\n", ptr, size);
-               exit(1);
+               abort();
        }
        return p;
 }
@@ -113,7 +113,7 @@ void close_dbenv(DB_ENV *dbenv) {
 
 // Convert a "msgtext" record to a message on disk.   NOT THREADSAFE
 // This also works for "bigmsg" records.
-int convert_msgtext(char *line, DBT *out_key, DBT *out_data) {
+int import_msgtext(char *line, DBT *out_key, DBT *out_data) {
 
        static char *b64_decoded_msg = NULL;
        static size_t b64_decoded_alloc = 0;
@@ -138,14 +138,29 @@ int convert_msgtext(char *line, DBT *out_key, DBT *out_data) {
 
 
 // Convert a "msgmeta" record to a message metadata record on disk.  NOT THREADSAFE
-int convert_msgmeta(char *line, DBT *out_key, DBT *out_data) {
+int import_msgmeta(char *line, DBT *out_key, DBT *out_data) {
        char *token;
        struct MetaData *m = malloc(sizeof(struct MetaData));
-       token = strtok(line, "|");
-       m->meta_msgnum = atol(strtok(NULL, "|"));
-       m->meta_refcount = atoi(strtok(NULL, "|"));
-       strncpy(m->meta_content_type, strtok(NULL, "|"), sizeof(m->meta_content_type));
-       m->meta_rfc822_length = atol(strtok(NULL, "|"));
+
+       memset(m, 0, sizeof(struct MetaData));
+       char *p = line;
+
+       for (int i=0; (token = strsep(&p, "|")); ++i) {
+               switch(i) {
+                       case 1:
+                               m->meta_msgnum = atol(token);
+                               break;
+                       case 2:
+                               m->meta_refcount = atoi(token);
+                               break;
+                       case 3:
+                               strncpy(m->meta_content_type, token, sizeof(m->meta_content_type));
+                               break;
+                       case 4:
+                               m->meta_rfc822_length = atol(token);
+                               break;
+               }
+       }
 
        // metadata records are stored in the CDB_MSGMAIN table,
        // but with the index being the *negative* of the message number.
@@ -163,7 +178,7 @@ int convert_msgmeta(char *line, DBT *out_key, DBT *out_data) {
 
 
 // Convert a "user" record to a record on disk.  (Source string is unusable after this function is called.)
-int convert_user(char *line, DBT *out_key, DBT *out_data) {
+int import_user(char *line, DBT *out_key, DBT *out_data) {
        char userkey[USERNAME_SIZE];
        char *token;
        struct ctdluser *u = malloc(sizeof(struct ctdluser));
@@ -228,7 +243,7 @@ int convert_user(char *line, DBT *out_key, DBT *out_data) {
 
 
 // Convert a "room" record to a record on disk.  (Source string is unusable after this function is called.)
-int convert_room(char *line, DBT *out_key, DBT *out_data) {
+int import_room(char *line, DBT *out_key, DBT *out_data) {
        char *token;
        struct ctdlroom *r = malloc(sizeof(struct ctdlroom));
 
@@ -306,7 +321,7 @@ int convert_room(char *line, DBT *out_key, DBT *out_data) {
 
 
 // Convert a floor record to a record on disk.
-int convert_floor(char *line, DBT *out_key, DBT *out_data) {
+int import_floor(char *line, DBT *out_key, DBT *out_data) {
        char *token;
        struct floor *f = malloc(sizeof(struct floor));
        int floor_num;
@@ -349,10 +364,11 @@ int convert_floor(char *line, DBT *out_key, DBT *out_data) {
 
 // Import a msglist record
 // msglist|26|32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51|
-int convert_msglist(char *line, DBT *out_key, DBT *out_data) {
+int import_msglist(char *line, DBT *out_key, DBT *out_data) {
        long roomnum;
-       char *token;
+       char *token, *mtoken;
        char *p = line;
+       char *q = NULL;
        int num_msgs = 0;
        long *msglist = NULL;
 
@@ -362,8 +378,7 @@ int convert_msglist(char *line, DBT *out_key, DBT *out_data) {
                                roomnum = atol(token);
                                break;
                        case 2:
-                               char *q = token;
-                               char *mtoken;
+                               q = token;
                                for (int j=0; (mtoken = strsep(&q, ",")); ++j) {
                                        msglist = realloc(msglist, (num_msgs+1) * sizeof(long));
                                        msglist[num_msgs++] = atol(mtoken);
@@ -384,7 +399,7 @@ int convert_msglist(char *line, DBT *out_key, DBT *out_data) {
 
 
 // Convert a "visit" record to a record on disk.
-int convert_visit(char *line, DBT *out_key, DBT *out_data) {
+int import_visit(char *line, DBT *out_key, DBT *out_data) {
        char *token;
        struct visit *v = malloc(sizeof(struct visit));
 
@@ -431,8 +446,37 @@ int convert_visit(char *line, DBT *out_key, DBT *out_data) {
 }
 
 
+// Convert a "dir" record to a record on disk.
+int import_dir(char *line, DBT *out_key, DBT *out_data) {
+       char dirkey[SIZ];
+       char username[USERNAME_SIZE];
+       char *token;
+
+       char *p = line;
+       for (int i=0; (token = strsep(&p, "|")); ++i) {
+               switch(i) {
+                       case 1:
+                               strncpy(dirkey, token, sizeof(dirkey));
+                               break;
+                       case 2:
+                               strncpy(username, token, sizeof(username));
+                               break;
+               }
+       }
+
+       out_key->size = strlen(dirkey);
+       out_key->data = reallok(NULL, out_key->size);
+       memcpy(out_key->data, dirkey, strlen(dirkey));
+
+       out_data->size = strlen(username) + 1;
+       out_data->data = strdup(username);
+
+       return(1);
+}
+
+
 // Convert a "usetable" record to a record on disk.
-int convert_usetable(char *line, DBT *out_key, DBT *out_data) {
+int import_usetable(char *line, DBT *out_key, DBT *out_data) {
        char *token;
        struct UseTable *u = malloc(sizeof(struct UseTable));
 
@@ -461,10 +505,116 @@ int convert_usetable(char *line, DBT *out_key, DBT *out_data) {
 }
 
 
+// Import a full text search index record.
+// It's just like a msglists record: a key and a list of message numbers, but the key is "int" instead of "long"
+int import_fulltext(char *line, DBT *out_key, DBT *out_data) {
+       int indexnum;
+       char *token, *mtoken;
+       char *p = line;
+       char *q = NULL;
+       int num_msgs = 0;
+       long *msglist = NULL;
+
+       for (int i=0; (token = strsep(&p, "|")); ++i) {
+               switch(i) {
+                       case 1:
+                               indexnum = atoi(token);
+                               break;
+                       case 2:
+                               q = token;
+                               for (int j=0; (mtoken = strsep(&q, ",")); ++j) {
+                                       msglist = realloc(msglist, (num_msgs+1) * sizeof(long));
+                                       msglist[num_msgs++] = atol(mtoken);
+                               }
+                               break;
+               }
+       }
+
+       out_key->size = sizeof(int);
+       out_key->data = malloc(out_key->size);
+       memcpy(out_key->data, &indexnum, out_key->size);
+
+       out_data->size = num_msgs * sizeof(long);
+       out_data->data = msglist;
+
+       return(1);
+}
+
+
+// Import an EUID Index record
+// euidindex|msgnum|roomnum|euid|
+int import_euidindex(char *line, DBT *out_key, DBT *out_data) {
+       char euid[SIZ];
+       long msgnum;
+       long roomnum;
+       char *token;
+
+       char *p = line;
+       for (int i=0; (token = strsep(&p, "|")); ++i) {
+               switch(i) {
+                       case 1:
+                               msgnum = atol(token);
+                               break;
+                       case 2:
+                               roomnum = atol(token);
+                               break;
+                       case 3:
+                               strncpy(euid, token, sizeof(euid));
+                               break;
+               }
+       }
+
+       // The structure of an euidindex record *key* is:
+       // |----room_number----|----------EUID-------------|
+       //    (sizeof long)       (actual length of euid)
+       out_key->size = sizeof(long) + strlen(euid) + 1;
+       out_key->data = reallok(NULL, out_key->size);
+       memcpy(out_key->data, &roomnum, sizeof(long));
+       strcpy(out_key->data + sizeof(long), euid);
+
+       // The structure of an euidindex record *value* is:
+       // |-----msg_number----|----room_number----|----------EUID-------------|
+       //    (sizeof long)       (sizeof long)       (actual length of euid)
+       out_data->size = sizeof(long) + sizeof(long) + strlen(euid) + 1;
+       out_data->data = reallok(NULL, out_data->size);
+       memcpy(out_data->data, &msgnum, sizeof(long));
+       memcpy(out_data->data + sizeof(long), &roomnum, sizeof(long));
+       strcpy(out_data->data + sizeof(long) + sizeof(long), euid);
+
+       return(1);
+}
+
+
+// Import a "users by number" (secondary index) record
+// The key is a "long"
+int import_usersbynumber(char *line, DBT *out_key, DBT *out_data) {
+       char *token;
+       long usernum;
+
+       char *p = line;
+       for (int i=0; (token = strsep(&p, "|")); ++i) {
+               switch(i) {
+                       case 1:
+                               usernum = atol(token);
+                               break;
+                       case 2:
+                               out_key->size = sizeof(long);
+                               out_key->data = reallok(NULL, sizeof(long));
+                               memcpy(out_key->data, &usernum, out_key->size);
+                               out_data->data = strdup(token);
+                               out_data->size = strlen(out_data->data) + 1;
+                               return(1);
+               }
+       }
+
+       return(0);              // should never get here unless it's a bad record
+}
+
+
 // Import a config record
 // The key is the config key
 // The data is the config key, a null, the value, and another null
-int convert_config(char *line, DBT *out_key, DBT *out_data) {
+int import_config(char *line, DBT *out_key, DBT *out_data) {
        char *token;
        char *p = line;
        char *k, *v;
@@ -512,23 +662,76 @@ void ingest_one(char *line, DB_ENV *dst_dbenv) {
                fflush(stderr);
        }
 
-       // Identify the record type we are currently working with
+       // Clear out our record buffer
+       memset(&out_key, 0, sizeof(DBT));
+       memset(&out_data, 0, sizeof(DBT));
+       row_was_good = 0;
+
+       // Identify the record type we are currently working with,
+       // then call the correct conversion function to load up our record buffer.
        extract_token(record_type, line, 0, '|', sizeof record_type);
-       if (!strcasecmp(record_type, "msgtext"))                current_cdb = CDB_MSGMAIN;
-       else if (!strcasecmp(record_type, "msgmeta"))           current_cdb = CDB_MSGMAIN;
-       else if (!strcasecmp(record_type, "user"))              current_cdb = CDB_USERS;
-       else if (!strcasecmp(record_type, "room"))              current_cdb = CDB_ROOMS;
-       else if (!strcasecmp(record_type, "floor"))             current_cdb = CDB_FLOORTAB;
-       else if (!strcasecmp(record_type, "msglist"))           current_cdb = CDB_MSGLISTS;
-       else if (!strcasecmp(record_type, "visit"))             current_cdb = CDB_VISIT;
-       else if (!strcasecmp(record_type, "dir"))               current_cdb = CDB_DIRECTORY;
-       else if (!strcasecmp(record_type, "use"))               current_cdb = CDB_USETABLE;
-       else if (!strcasecmp(record_type, "bigmsg"))            current_cdb = CDB_BIGMSGS;
-       else if (!strcasecmp(record_type, "euidindex"))         current_cdb = CDB_EUIDINDEX;
-       else if (!strcasecmp(record_type, "usersbynumber"))     current_cdb = CDB_USERSBYNUMBER;
-       else if (!strcasecmp(record_type, "config"))            current_cdb = CDB_CONFIG;
-       else                                                    current_cdb = -1 ;
+       if (!strcasecmp(record_type, "msgtext")) {
+               current_cdb = CDB_MSGMAIN;
+               row_was_good = import_msgtext(line, &out_key, &out_data);
+       }
+       else if (!strcasecmp(record_type, "msgmeta")) {
+               current_cdb = CDB_MSGMAIN;
+               row_was_good = import_msgmeta(line, &out_key, &out_data);
+       }
+       else if (!strcasecmp(record_type, "user")) {
+               current_cdb = CDB_USERS;
+               row_was_good = import_user(line, &out_key, &out_data);
+       }
+       else if (!strcasecmp(record_type, "room")) {
+               current_cdb = CDB_ROOMS;
+               row_was_good = import_room(line, &out_key, &out_data);
+       }
+       else if (!strcasecmp(record_type, "floor")) {
+               current_cdb = CDB_FLOORTAB;
+               row_was_good = import_floor(line, &out_key, &out_data);
+       }
+       else if (!strcasecmp(record_type, "msglist")) {
+               current_cdb = CDB_MSGLISTS;
+               row_was_good = import_msglist(line, &out_key, &out_data);
+       }
+       else if (!strcasecmp(record_type, "visit")) {
+               current_cdb = CDB_VISIT;
+               row_was_good = import_visit(line, &out_key, &out_data);
+       }
+       else if (!strcasecmp(record_type, "dir")) {
+               current_cdb = CDB_DIRECTORY;
+               row_was_good = import_dir(line, &out_key, &out_data);
+       }
+       else if (!strcasecmp(record_type, "use")) {
+               current_cdb = CDB_USETABLE;
+               row_was_good = import_usetable(line, &out_key, &out_data);
+       }
+       else if (!strcasecmp(record_type, "bigmsg")) {
+               current_cdb = CDB_BIGMSGS;
+               row_was_good = import_msgtext(line, &out_key, &out_data);
+       }
+       else if (!strcasecmp(record_type, "fulltext")) {
+               current_cdb = CDB_FULLTEXT;
+               row_was_good = import_fulltext(line, &out_key, &out_data);
+       }
+       else if (!strcasecmp(record_type, "euidindex")) {
+               current_cdb = CDB_EUIDINDEX;
+               row_was_good = import_euidindex(line, &out_key, &out_data);
+       }
+       else if (!strcasecmp(record_type, "usersbynumber")) {
+               current_cdb = CDB_USERSBYNUMBER;
+               row_was_good = import_usersbynumber(line, &out_key, &out_data);
+       }
+       else if (!strcasecmp(record_type, "config")) {
+               current_cdb = CDB_CONFIG;
+               row_was_good = import_config(line, &out_key, &out_data);
+       }
+       else {
+               current_cdb = -1 ;
+       }
 
+       // If the current record is a different table than the previous record,
+       // then close the other table and open the correct one.
        if (current_cdb != previous_cdb) {
                if (previous_cdb >= 0) {
                        fprintf(stderr, "\n");
@@ -556,7 +759,7 @@ void ingest_one(char *line, DB_ENV *dst_dbenv) {
                        // open the file containing the destination table
                        ret = dst_dbp->open(dst_dbp, NULL, dbfilename, NULL, DB_BTREE, (DB_CREATE | DB_TRUNCATE), 0600);
                        if (ret) {
-                               fprintf(stderr, "db: db_open: %s\n", db_strerror(ret));
+                               fprintf(stderr, "db: db_open(%s): %s\n", dbfilename, db_strerror(ret));
                                fprintf(stderr, "db: exit code %d\n", ret);
                                exit(CTDLEXIT_DB);
                        }
@@ -565,22 +768,7 @@ void ingest_one(char *line, DB_ENV *dst_dbenv) {
                previous_cdb = current_cdb;
        }
 
-       // If we have a valid record type and a target database open, dispatch the correct record type handler.
-       memset(&out_key, 0, sizeof(DBT));
-       memset(&out_data, 0, sizeof(DBT));
-       row_was_good = 0;
-       if      (!strcasecmp(record_type, "msgtext"))           row_was_good = convert_msgtext(line, &out_key, &out_data);
-       else if (!strcasecmp(record_type, "msgmeta"))           row_was_good = convert_msgmeta(line, &out_key, &out_data);
-       else if (!strcasecmp(record_type, "user"))              row_was_good = convert_user(line, &out_key, &out_data);
-       else if (!strcasecmp(record_type, "room"))              row_was_good = convert_room(line, &out_key, &out_data);
-       else if (!strcasecmp(record_type, "floor"))             row_was_good = convert_floor(line, &out_key, &out_data);
-       else if (!strcasecmp(record_type, "msglist"))           row_was_good = convert_msglist(line, &out_key, &out_data);
-       else if (!strcasecmp(record_type, "visit"))             row_was_good = convert_visit(line, &out_key, &out_data);
-       else if (!strcasecmp(record_type, "use"))               row_was_good = convert_usetable(line, &out_key, &out_data);
-       else if (!strcasecmp(record_type, "bigmsg"))            row_was_good = convert_msgtext(line, &out_key, &out_data);
-       else if (!strcasecmp(record_type, "config"))            row_was_good = convert_config(line, &out_key, &out_data);
-       else                                                    row_was_good = 0;
-
+       // If the conversion function was successful, write the record to the database.
        if (row_was_good) {
                ++good_rows;
                ret = dst_dbp->put(dst_dbp, NULL, &out_key, &out_data, 0);
@@ -603,7 +791,7 @@ void ingest(DB_ENV *dst_dbenv) {
        static size_t line_alloc = 1;
        static char *line;
        static size_t line_len = 0;
-       char ch;
+       int ch;
 
        fprintf(stderr, "\033[7mtable\033[0m \033[7mgood_rows\033[0m \033[7mbad_rows\033[0m\n");
        line = reallok(NULL, line_alloc);
@@ -634,24 +822,27 @@ void ingest(DB_ENV *dst_dbenv) {
 
 // Main entry point
 int main(int argc, char **argv) {
-       char *dst_dir = NULL;
+       char dst_dir[PATH_MAX];
        int confirmed = 0;
-       static DB_ENV *dst_dbenv;               // Source DB environment (global)
+       static DB_ENV *dst_dbenv;               // Target DB environment
 
        // display the greeting
-       fprintf(stderr, "\033[42m\033[30m \033[K\033[0m\n"
-                       "\033[42m\033[30m DB Load utility for Citadel \033[K\033[0m\n"
-                       "\033[42m\033[30m Copyright (c) 2023 by citadel.org et al.  \033[K\033[0m\n"
-                       "\033[42m\033[30m This program is open source software.  Use, duplication, or disclosure \033[K\033[0m\n"
-                       "\033[42m\033[30m is subject to the terms of the GNU General Public license v3. \033[K\033[0m\n"
-                       "\033[42m\033[30m \033[K\033[0m\n");
+       fprintf(stderr, "\033[44m\033[33m\033[1m \033[K\033[0m\n"
+                       "\033[44m\033[33m\033[1m DB Load utility for Citadel \033[K\033[0m\n"
+                       "\033[44m\033[33m\033[1m Copyright (c) 2023 by citadel.org et al.  \033[K\033[0m\n"
+                       "\033[44m\033[33m\033[1m This program is open source software.  Use, duplication, or disclosure \033[K\033[0m\n"
+                       "\033[44m\033[33m\033[1m is subject to the terms of the GNU General Public license v3. \033[K\033[0m\n"
+                       "\033[44m\033[33m\033[1m \033[K\033[0m\n");
+
+       // Default destination directory unless overridden
+       snprintf(dst_dir, sizeof(dst_dir), "%s/data", CTDLDIR);
 
        // Parse command line
        int a;
        while ((a = getopt(argc, argv, "h:y")) != EOF) {
                switch (a) {
                case 'h':
-                       dst_dir = optarg;
+                       snprintf(dst_dir, sizeof(dst_dir), "%s/data", optarg);
                        break;
                case 'y':
                        confirmed = 1;
@@ -662,15 +853,23 @@ int main(int argc, char **argv) {
                }
        }
 
+       if (dst_dir == NULL) {
+               fprintf(stderr, "ctdlload: no destination directory was specified.\n");
+               exit(1);
+       }
+
        if (confirmed == 1) {
                fprintf(stderr, "ctdlload: You have specified the [-y] flag, so processing will continue.\n");
        }
        else {
-               fprintf(stderr, "ctdlload: Please read [ https://www.citadel.org/dump-and-load.html ] to learn how to proceed.\n");
-               exit(0);
+               fprintf(stderr, "ctdlload: usage: ctdlload -y -h[data_dir] <[dump_file]\n");
+               fprintf(stderr, "          [data_dir] is your database directory, usually /usr/local/citadel/data\n");
+               fprintf(stderr, "          Please read [ https://www.citadel.org/dump-and-load.html ] to learn how to proceed.\n");
+               exit(1);
        }
 
-       char cmd[1024];
+       // Remove any database that is already in the target directory (yes, delete it, be careful)
+       char cmd[PATH_MAX];
        snprintf(cmd, sizeof cmd, "rm -fv %s/cdb.* %s/log.*", dst_dir, dst_dir);
        system(cmd);
 
@@ -678,5 +877,6 @@ int main(int argc, char **argv) {
        ingest(dst_dbenv);
        close_dbenv(dst_dbenv);
 
+       fprintf(stderr, "ctdlload: \033[32m\033[1mfinished\033[0m\n");
        exit(0);
 }
index dc96e2bb038d689d993f44723259a29bcaa95877..ddb75c3c393433da7565743c8abbb6b46d25569b 100644 (file)
@@ -19,7 +19,7 @@
 #include <sys/types.h>
 #include <netinet/in.h>
 
-#define LIBCITADEL_VERSION_NUMBER 980
+#define LIBCITADEL_VERSION_NUMBER 988
 
 /*
  * Here's a bunch of stupid magic to make the MIME parser portable.
@@ -575,6 +575,7 @@ struct vnote *vnote_new_from_str(char *s);
 void vnote_free(struct vnote *v);
 char *vnote_serialize(struct vnote *v);
 void vnote_serialize_output_field(char *append_to, char *field, char *label);
+char b64unalphabet(char ch);
 
 
 
index da8e2a42b73664ebfbca03d2f0294375d2a21fcd..60d44837bc39b4ca18a7c345dfcc5bcf763c1e8c 100644 (file)
@@ -1 +1 @@
-980
+988
index 4f6730c79f793dbb4b11429c42c8f86f7fdd6755..b0602147c71bd80dad34807bee873e973a7a3abf 100644 (file)
@@ -11,7 +11,7 @@
 #define        UDS                     "_UDS_"
 #define DEFAULT_HOST           "localhost"
 #define DEFAULT_PORT           "504"
-#define CLIENT_VERSION 980
+#define CLIENT_VERSION 988
 #define CLIENT_TYPE            0
 
 // commands we can send to the stty_ctdl() routine
index 3d20649898d6c135db72b6208f898e71efed7c81..1b525849451ad24413124b70c60a3ffc47355fe3 100644 (file)
@@ -127,7 +127,7 @@ extern char *ssl_cipher_list;
 #define PORT_NUM               80                      /* port number to listen on */
 #define DEVELOPER_ID           0
 #define CLIENT_ID              4
-#define CLIENT_VERSION 980             /* This version of WebCit */
+#define CLIENT_VERSION 988             /* 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 */