*.d
*.gcno
*.gcda
-Make_modules
-Make_sources
-Makefile
-Makefile.in
locale
-aclocal.m4
aidepost
-base64
build-arch-stamp
build-indep-stamp
chkpw
citadel
citmail
citserver
-config.log
-config.status
-configure
-configure-stamp
ctdlmigrate
getmail
-modules_init.c
-modules_init.h
-modules_upgrade.c
msgform
panic.log
sendcommand
setup
svn_revision.c
-sysdep.h
-sysdep.h.in
userlist
-whobbs
gmon.out
-autom4te.cache
citadel.config
citadel.control
citadel.pid
software" -- and that our favorite operating system is called
"Linux" -- NOT "GNU/Linux".
-* In addition, as a special exception, it is declared without exception
- that Richard Stallman is a communist, is an asshole, and should
- shut up and go away because he contributes nothing useful to the
- open source community.
-
-* In addition, as a special exception, the copyright holders give
- permission to link the code of portions of this program with the
- OpenSSL library under certain conditions as described in each
- individual source file, and distribute linked combinations
- including the two.
- You must obey the General Public License in all respects
- for all of the code used other than OpenSSL. If you modify
- file(s) with this exception, you may extend this exception to your
- version of the file(s), but you are not obligated to do so. If you
- do not wish to do so, delete this exception statement from your
- version. If you delete this exception statement from all source
- files in the program, then also delete it here.
+* We reject and oppose Richard M. Stallman because he contributes
+ nothing but trouble to the open source community.
+
+* As a special exception, the copyright holders of the Citadel
+ system grant permission to link Citadel with non-GPL-compatible
+ libraries, including OpenSSL, in the sole case that such linking
+ is done to build the Citadel system itself.
GENERAL PUBLIC LICENSE
--- /dev/null
+# Makefile for Citadel Server
+# Copyright (c) 1987-2022 by Art Cancro and the citadel.org team
+#
+# This is the new and improved version that does not use the GNU Autotools,
+# because it is The Current Year and we aren't trying to build for weird
+# obscure systems anymore.
+#
+# This program is open source software. Use, duplication, and/or
+# disclosure are subject to the GNU General Purpose 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.
+
+
+# config.mk is generated by ./configure
+include config.mk
+
+citserver: server/*.c server/modules/*/*.c config.mk
+ cc ${CFLAGS} \
+ server/*.c server/modules/*/*.c \
+ ${LDFLAGS} -lresolv -lcitadel -lpthread -lz -lical -lldap -lcrypt -lexpat -lcurl -ldb \
+ -o citserver
+
+config.mk: configure
+ ./configure
+++ /dev/null
-// Main Citadel header file
-//
-// Copyright (c) 1987-2021 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.
-
-/* system customizations are in sysconfig.h */
-
-#ifndef CITADEL_H
-#define CITADEL_H
-/* #include <dmalloc.h> uncomment if using dmalloc */
-
-#include "sysdep.h"
-#include <limits.h>
-#include "sysconfig.h"
-#include "typesize.h"
-#include "ipcdef.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/*
- * Text description of this software
- * (We used to define this ourselves, but why bother when the build tools do it for us?)
- */
-#define CITADEL PACKAGE_STRING
-
-#define REV_LEVEL 951 // This version
-#define REV_MIN 591 // Oldest compatible database
-#define EXPORT_REV_MIN 931 // Oldest compatible export files
-#define LIBCITADEL_MIN 931 // Minimum required version of libcitadel
-#define SERVER_TYPE 0 // zero for stock Citadel; other developers please obtain SERVER_TYPE codes for your implementations
-
-#ifdef LIBCITADEL_VERSION_NUMBER
-#if LIBCITADEL_VERSION_NUMBER < LIBCITADEL_MIN
-#error libcitadel is too old. Please upgrade it before continuing.
-#endif
-#endif
-
-/*
- * This is the user name and password for the default administrator account
- * that is created when Citadel Server is started with an empty database.
- */
-#define DEFAULT_ADMIN_USERNAME "admin"
-#define DEFAULT_ADMIN_PASSWORD "citadel"
-
-/* Various length constants */
-
-#define ROOMNAMELEN 128 /* The size of a roomname string */
-#define USERNAME_SIZE 64 /* The size of a username string */
-#define MAX_EDITORS 5 /* number of external editors supported ; must be at least 1 */
-
-/*
- * Message expiration policy stuff
- */
-typedef struct ExpirePolicy ExpirePolicy;
-struct ExpirePolicy {
- int expire_mode;
- int expire_value;
-};
-
-#define EXPIRE_NEXTLEVEL 0 // Inherit expiration policy
-#define EXPIRE_MANUAL 1 // Don't expire messages at all
-#define EXPIRE_NUMMSGS 2 // Keep only latest n messages
-#define EXPIRE_AGE 3 // Expire messages after n days
-
-
-/*
- * User records.
- */
-typedef struct ctdluser ctdluser;
-struct ctdluser { // User record
- int version; // Citadel version which created this record
- uid_t uid; // Associate with a unix account?
- char password[32]; // password
- unsigned flags; // See US_ flags below
- long timescalled; // Total number of logins
- long posted; // Number of messages ever submitted
- cit_uint8_t axlevel; // Access level
- long usernum; // User number (never recycled)
- time_t lastcall; // Date/time of most recent login
- int USuserpurge; // Purge time (in days) for user
- char fullname[64]; // Display name (primary identifier)
- long msgnum_bio; // msgnum of user's profile (bio)
- long msgnum_pic; // msgnum of user's avatar (photo)
- char emailaddrs[512]; // Internet email addresses
- long msgnum_inboxrules; // msgnum of user's inbox filtering rules
- long lastproc_inboxrules; // msgnum of last message filtered
-};
-
-
-/* Bits which may appear in MMflags.
- */
-#define MM_VALID 4 // New users need validating
-
-/*
- * Room records.
- */
-typedef struct ctdlroom ctdlroom;
-struct ctdlroom {
- char QRname[ROOMNAMELEN]; // Name of room
- char QRpasswd[10]; // Only valid if it's a private rm
- long QRroomaide; // User number of room aide
- long QRhighest; // Highest message NUMBER in room
- time_t QRgen; // Generation number of room
- unsigned QRflags; // See flag values below
- char QRdirname[15]; // Directory name, if applicable
- long msgnum_info; // msgnum of room banner (info file)
- char QRfloor; // Which floor this room is on
- time_t QRmtime; // Date/time of last post
- struct ExpirePolicy QRep; // Message expiration policy
- long QRnumber; // Globally unique room number
- char QRorder; // Sort key for room listing order
- unsigned QRflags2; // Additional flags
- int QRdefaultview; // How to display the contents
- long msgnum_pic; // msgnum of room picture or icon
-};
-
-/* Private rooms are always flagged with QR_PRIVATE. If neither QR_PASSWORDED
- * or QR_GUESSNAME is set, then it is invitation-only. Passworded rooms are
- * flagged with both QR_PRIVATE and QR_PASSWORDED while guess-name rooms are
- * flagged with both QR_PRIVATE and QR_GUESSNAME. NEVER set all three flags.
- */
-
-/*
- * Miscellaneous
- */
-#define MES_NORMAL 65 // Normal message
-#define MES_ANONONLY 66 // "****" header
-#define MES_ANONOPT 67 // "Anonymous" header
-
-/****************************************************************************/
-
-/*
- * Floor record. The floor number is implicit in its location in the file.
- */
-typedef struct floor floor;
-struct floor {
- unsigned short f_flags; // flags
- char f_name[256]; // name of floor
- int f_ref_count; // reference count
- struct ExpirePolicy f_ep; // default expiration policy
-};
-
-#define F_INUSE 1 // floor is in use
-
-
-/*
- * Values used internally for function call returns, etc.
- */
-#define NEWREGISTER 0 // new user to register
-#define REREGISTER 1 // existing user reregistering
-
-/* number of items which may be handled by the CONF command */
-#define NUM_CONFIGS 71
-
-#define TRACE syslog(LOG_DEBUG, "\033[7m Checkpoint: %s : %d \033[0m", __FILE__, __LINE__)
-
-#ifndef LONG_MAX
-#define LONG_MAX 2147483647L
-#endif
-
-/*
- * Authentication modes
- */
-#define AUTHMODE_NATIVE 0 // Native (self-contained or "black box")
-#define AUTHMODE_HOST 1 // Authenticate against the host OS user database
-#define AUTHMODE_LDAP 2 // Authenticate using LDAP server with RFC 2307 schema
-#define AUTHMODE_LDAP_AD 3 // Authenticate using LDAP server with Active Directory schema
-
-#ifdef __cplusplus
-}
-#endif
-
-#if __GNUC__ >= 8
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wformat-truncation"
-#endif
-
-#endif /* CITADEL_H */
+++ /dev/null
-// citadel_dirs.c : calculate pathnames for various files used in the Citadel system
-//
-// Copyright (c) 1987-2021 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.
-
-#include <unistd.h>
-#include <stdio.h>
-#include <sys/stat.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <time.h>
-#include <errno.h>
-#include <syslog.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "citadel_dirs.h"
-
-// Generate an associated file name for a room
-size_t assoc_file_name(char *buf, size_t n, struct ctdlroom *qrbuf, const char *prefix) {
- return snprintf(buf, n, "%s%ld", prefix, qrbuf->QRnumber);
-}
-
-
-int create_dir(char *which, long ACCESS, long UID, long GID) {
- int rv;
- rv = mkdir(which, ACCESS);
- if ((rv == -1) && (errno != EEXIST)) {
- syslog(LOG_ERR,
- "failed to create directory %s: %s",
- which,
- strerror(errno));
- return rv;
- }
- rv = chmod(which, ACCESS);
- if (rv == -1) {
- syslog(LOG_ERR, "failed to set permissions for directory %s: %s", which, strerror(errno));
- return rv;
- }
- rv = chown(which, UID, GID);
- if (rv == -1) {
- syslog(LOG_ERR, "failed to set owner for directory %s: %s", which, strerror(errno));
- return rv;
- }
- return rv;
-}
-
-
-int create_run_directories(long UID, long GID) {
- int rv = 0;
- rv += create_dir(ctdl_message_dir , S_IRUSR|S_IWUSR|S_IXUSR, UID, -1);
- rv += create_dir(ctdl_file_dir , S_IRUSR|S_IWUSR|S_IXUSR, UID, -1);
- rv += create_dir(ctdl_key_dir , S_IRUSR|S_IWUSR|S_IXUSR, UID, -1);
- rv += create_dir(ctdl_run_dir , S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH, UID, GID);
- return rv;
-}
+++ /dev/null
-#ifndef __CITADEL_DIRS_H
-#define __CITADEL_DIRS_H
-
-#include <limits.h>
-
-/* Fixed directory names (some of these are obsolete and used only for migration) */
-#define ctdl_home_directory "."
-#define ctdl_db_dir "data"
-#define ctdl_file_dir "files"
-#define ctdl_shared_dir "."
-#define ctdl_image_dir "images"
-#define ctdl_info_dir "info"
-#define ctdl_key_dir "keys"
-#define ctdl_message_dir "messages"
-#define ctdl_usrpic_dir "userpics"
-#define ctdl_autoetc_dir "."
-#define ctdl_run_dir "."
-#define ctdl_netcfg_dir "netconfigs"
-#define ctdl_bbsbase_dir "."
-#define ctdl_sbin_dir "."
-#define ctdl_bin_dir "."
-#define ctdl_utilbin_dir "."
-
-/* Fixed file names (some of these are obsolete and used only for migration) */
-#define file_citadel_config "citadel.config"
-#define file_lmtp_socket "lmtp.socket"
-#define file_lmtp_unfiltered_socket "lmtp-unfiltered.socket"
-#define file_arcq "refcount_adjustments.dat"
-#define file_citadel_socket "citadel.socket"
-#define file_citadel_admin_socket "citadel-admin.socket"
-#define file_pid_file "/var/run/citserver.pid"
-#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 "chkpwd"
-#define file_guesstimezone "guesstimezone.sh"
-
-
-/* externs */
-extern int create_run_directories(long UID, long GUID);
-extern size_t assoc_file_name(char *buf, size_t n, struct ctdlroom *qrbuf, const char *prefix);
-extern FILE *create_digest_file(struct ctdlroom *room, int forceCreate);
-extern void remove_digest_file(struct ctdlroom *room);
-
-#endif /* __CITADEL_DIRS_H */
+++ /dev/null
-// Configuration for LDAP authentication. Most of this stuff gets pulled out of our site config file.
-//
-// Copyright (c) 1987-2017 by the citadel.org team
-//
-// This program is open source software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-int CtdlTryUserLDAP(char *username, char *found_dn, int found_dn_size, char *fullname, int fullname_size, uid_t *found_uid);
-int CtdlTryPasswordLDAP(char *user_dn, const char *password);
-int Ctdl_LDAP_to_vCard(char *ldap_dn, struct vCard *v);
-int extract_email_addresses_from_ldap(char *ldap_dn, char *emailaddrs);
-void CtdlSynchronizeUsersFromLDAP(void);
+++ /dev/null
-// Main source module for the Citadel server
-//
-// Copyright (c) 1987-2021 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.
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <sys/stat.h>
-#include "sysdep.h"
-#include <time.h>
-#include <libcitadel.h>
-
-#include "ctdl_module.h"
-#include "housekeeping.h"
-#include "locate_host.h"
-#include "citserver.h"
-#include "user_ops.h"
-#include "control.h"
-#include "config.h"
-
-char *unique_session_numbers;
-int ScheduledShutdown = 0;
-time_t server_startup_time;
-int panic_fd;
-int openid_level_supported = 0;
-
-
-// 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);
-}
-
-
-// Various things that need to be initialized at startup
-void master_startup(void) {
- struct ctdlroom qrbuf;
- struct passwd *pw;
- gid_t gid;
-
- syslog(LOG_DEBUG, "master_startup() started");
- time(&server_startup_time);
-
- syslog(LOG_INFO, "Checking directory access");
- if ((pw = getpwuid(ctdluid)) == NULL) {
- gid = getgid();
- }
- else {
- gid = pw->pw_gid;
- }
-
- if (create_run_directories(CTDLUID, gid) != 0) {
- syslog(LOG_ERR, "citserver: failed to access and create directories");
- exit(1);
- }
- syslog(LOG_INFO, "Opening databases");
- open_databases();
-
- // Load site-specific configuration
- seed_random_number_generator(); // must be done before config system
- syslog(LOG_INFO, "Initializing configuration system");
- initialize_config_system();
- validate_config();
- migrate_legacy_control_record();
-
- // If we have an existing database that is older than version 928, reindex the user records.
- // Unfortunately we cannot do this in serv_upgrade.c because it needs to happen VERY early during startup.
- int existing_db = CtdlGetConfigInt("MM_hosted_upgrade_level");
- if ( (existing_db > 0) && (existing_db < 928) ) {
- ForEachUser(reindex_user_928, NULL);
- }
-
- // Check floor reference counts
- check_ref_counts();
-
- syslog(LOG_INFO, "Creating base rooms (if necessary)");
- CtdlCreateRoom(CtdlGetConfigStr("c_baseroom"), 0, "", 0, 1, 0, VIEW_BBS);
- CtdlCreateRoom(AIDEROOM, 3, "", 0, 1, 0, VIEW_BBS);
- CtdlCreateRoom(SYSCONFIGROOM, 3, "", 0, 1, 0, VIEW_BBS);
- CtdlCreateRoom(CtdlGetConfigStr("c_twitroom"), 0, "", 0, 1, 0, VIEW_BBS);
-
- // The "Local System Configuration" room doesn't need to be visible
- if (CtdlGetRoomLock(&qrbuf, SYSCONFIGROOM) == 0) {
- qrbuf.QRflags2 |= QR2_SYSTEM;
- CtdlPutRoomLock(&qrbuf);
- }
-
- // Aide needs to be public postable, else we're not RFC conformant.
- if (CtdlGetRoomLock(&qrbuf, AIDEROOM) == 0) {
- qrbuf.QRflags2 |= QR2_SMTP_PUBLIC;
- CtdlPutRoomLock(&qrbuf);
- }
-
- syslog(LOG_DEBUG, "master_startup() finished");
-}
-
-
-// Cleanup routine to be called when the server is shutting down. Returns the needed exit code.
-int master_cleanup(int exitcode) {
- static int already_cleaning_up = 0;
-
- if (already_cleaning_up) {
- while (1) {
- usleep(1000000);
- }
- }
- already_cleaning_up = 1;
-
- // Do system-dependent stuff
- sysdep_master_cleanup();
-
- // Close the configuration system
- shutdown_config_system();
-
- // Close databases
- syslog(LOG_INFO, "citserver: closing databases");
- close_databases();
-
- // If the operator requested a halt but not an exit, halt here.
- if (shutdown_and_halt) {
- syslog(LOG_ERR, "citserver: Halting server without exiting.");
- fflush(stdout);
- fflush(stderr);
- while (1) {
- sleep(32767);
- }
- }
-
- // Now go away.
- syslog(LOG_ERR, "citserver: Exiting with status %d", exitcode);
- fflush(stdout);
- fflush(stderr);
-
- if (restart_server != 0) {
- exitcode = 1;
- }
- else if ((running_as_daemon != 0) && ((exitcode == 0))) {
- exitcode = CTDLEXIT_SHUTDOWN;
- }
- return (exitcode);
-}
-
-
-// returns an asterisk if there are any instant messages waiting, space otherwise.
-char CtdlCheckExpress(void) {
- if (CC->FirstExpressMessage == NULL) {
- return (' ');
- }
- else {
- return ('*');
- }
-}
-
-
-void citproto_begin_session() {
- if (CC->nologin == 1) {
- cprintf("%d Too many users are already online (maximum is %d)\n",
- ERROR + MAX_SESSIONS_EXCEEDED, CtdlGetConfigInt("c_maxsessions")
- );
- CC->kill_me = KILLME_MAX_SESSIONS_EXCEEDED;
- }
- else {
- cprintf("%d %s Citadel server ready.\n", CIT_OK, CtdlGetConfigStr("c_fqdn"));
- CC->can_receive_im = 1;
- }
-}
-
-
-void citproto_begin_admin_session() {
- CC->internal_pgm = 1;
- cprintf("%d %s Citadel server ADMIN CONNECTION ready.\n", CIT_OK, CtdlGetConfigStr("c_fqdn"));
-}
-
-
-// This loop performs all asynchronous functions.
-void do_async_loop(void) {
- PerformSessionHooks(EVT_ASYNC);
-}
+++ /dev/null
-// Copyright (c) 1987-2019 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.
-
-/* I fucking hate const and want it to die. */
-#pragma GCC diagnostic ignored "-Wcast-qual"
-
-#include "serv_extensions.h"
-#include "context.h"
-#include "ctdl_module.h"
-
-/* Simple linked list structures ... used in a bunch of different places. */
-typedef struct RoomProcList RoomProcList;
-struct RoomProcList {
- struct RoomProcList *next;
- char name[ROOMNAMELEN];
- char lcname[ROOMNAMELEN];
- long namelen;
- long lastsent;
- long key;
- long QRNum;
-};
-struct UserProcList {
- struct UserProcList *next;
- char user[USERNAME_SIZE];
-};
-
-#define CTDLUSERIP (IsEmptyStr(CC->cs_addr) ? CC->cs_clientinfo: CC->cs_addr)
-
-void master_startup (void);
-int master_cleanup (int exitcode);
-void set_wtmpsupp (char *newtext);
-void set_wtmpsupp_to_current_room(void);
-void do_command_loop(void);
-void do_async_loop(void);
-void begin_session(struct CitContext *con);
-void citproto_begin_session(void);
-void citproto_begin_admin_session(void);
-void help_subst (char *strbuf, char *source, char *dest);
-char CtdlCheckExpress(void);
-
-extern int panic_fd;
-extern time_t server_startup_time;
-extern int openid_level_supported;
+++ /dev/null
-// This module handles client-side sockets opened by the Citadel server (for
-// the client side of Internet protocols, etc.) It does _not_ handle client
-// sockets for the Citadel client; for that you must look in ipc_c_tcp.c
-// (which, uncoincidentally, bears a striking similarity to this file).
-//
-// Copyright (c) 1987-2017 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.
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <netdb.h>
-#include <stdio.h>
-#ifdef __FreeBSD__
-#include <sys/socket.h>
-#endif
-#include <libcitadel.h>
-#include "ctdl_module.h"
-#include "clientsocket.h"
-
-int sock_connect(char *host, char *service) {
- struct in6_addr serveraddr;
- struct addrinfo hints;
- struct addrinfo *res = NULL;
- struct addrinfo *ai = NULL;
- int rc = (-1);
- int sock = (-1);
-
- if ((host == NULL) || IsEmptyStr(host))
- return (-1);
- if ((service == NULL) || IsEmptyStr(service))
- return (-1);
-
- memset(&hints, 0x00, sizeof(hints));
- hints.ai_flags = AI_NUMERICSERV;
- hints.ai_family = AF_UNSPEC;
- hints.ai_socktype = SOCK_STREAM;
-
- // Handle numeric IPv4 and IPv6 addresses
- rc = inet_pton(AF_INET, host, &serveraddr);
- if (rc == 1) { // dotted quad
- hints.ai_family = AF_INET;
- hints.ai_flags |= AI_NUMERICHOST;
- }
- else {
- rc = inet_pton(AF_INET6, host, &serveraddr);
- if (rc == 1) { // IPv6 address
- hints.ai_family = AF_INET6;
- hints.ai_flags |= AI_NUMERICHOST;
- }
- }
-
- // Begin the connection process
- rc = getaddrinfo(host, service, &hints, &res);
- if (rc != 0) {
- syslog(LOG_ERR, "%s: %s", host, gai_strerror(rc));
- return(-1);
- }
-
- // Try all available addresses until we connect to one or until we run out.
- for (ai = res; ai != NULL; ai = ai->ai_next) {
- sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
- if (sock < 0) {
- syslog(LOG_ERR, "%s: %m", host);
- freeaddrinfo(res);
- return(-1);
- }
- rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
- if (rc >= 0) {
- freeaddrinfo(res);
- return(sock);
- }
- else {
- syslog(LOG_ERR, "%s: %m", host);
- close(sock);
- }
- }
- freeaddrinfo(res);
- return(-1);
-}
-
-
-// Read data from the client socket.
-//
-// sock socket fd to read from
-// buf buffer to read into
-// bytes number of bytes to read
-// timeout Number of seconds to wait before timing out
-//
-// Possible return values:
-// 1 Requested number of bytes has been read.
-// 0 Request timed out.
-// -1 Connection is broken, or other error.
-int socket_read_blob(int *Socket, StrBuf *Target, int bytes, int timeout) {
- const char *Error;
- int retval = 0;
-
- retval = StrBufReadBLOBBuffered(Target, CC->SBuf.Buf, &CC->SBuf.ReadWritePointer, Socket, 1, bytes, O_TERM, &Error);
- if (retval < 0) {
- syslog(LOG_ERR, "clientsocket: socket_read_blob() failed: %s", Error);
- }
- return retval;
-}
-
-
-int CtdlSockGetLine(int *sock, StrBuf *Target, int nSec) {
- const char *Error;
- int rc;
-
- FlushStrBuf(Target);
- rc = StrBufTCP_read_buffered_line_fast(Target,
- CC->SBuf.Buf,
- &CC->SBuf.ReadWritePointer,
- sock, nSec, 1, &Error);
- if ((rc < 0) && (Error != NULL)) {
- syslog(LOG_ERR, "clientsocket: CtdlSockGetLine() failed: %s", Error);
- }
- return rc;
-}
-
-
-// client_getln() ... Get a LF-terminated line of text from the client.
-int sock_getln(int *sock, char *buf, int bufsize) {
- int i, retval;
- const char *pCh;
-
- FlushStrBuf(CC->sMigrateBuf);
- retval = CtdlSockGetLine(sock, CC->sMigrateBuf, 5);
-
- i = StrLength(CC->sMigrateBuf);
- pCh = ChrPtr(CC->sMigrateBuf);
-
- memcpy(buf, pCh, i + 1);
-
- FlushStrBuf(CC->sMigrateBuf);
- if (retval < 0) {
- safestrncpy(&buf[i], "000", bufsize - i);
- i += 3;
- }
- return i;
-}
-
-
-// sock_write() - send binary to server.
-// Returns the number of bytes written, or -1 for error.
-int sock_write(int *sock, const char *buf, int nbytes) {
- return sock_write_timeout(sock, buf, nbytes, 50);
-}
-
-
-int sock_write_timeout(int *sock, const char *buf, int nbytes, int timeout) {
- int nSuccessLess = 0;
- int bytes_written = 0;
- int retval;
- fd_set rfds;
- int fdflags;
- int IsNonBlock;
- struct timeval tv;
- int selectresolution = 100;
-
- fdflags = fcntl(*sock, F_GETFL);
- IsNonBlock = (fdflags & O_NONBLOCK) == O_NONBLOCK;
-
- while ((nSuccessLess < timeout) && (*sock != -1) && (bytes_written < nbytes)) {
- if (IsNonBlock) {
- tv.tv_sec = selectresolution;
- tv.tv_usec = 0;
-
- FD_ZERO(&rfds);
- FD_SET(*sock, &rfds);
- if (select(*sock + 1, NULL, &rfds, NULL, &tv) == -1) {
- close (*sock);
- *sock = -1;
- return -1;
- }
- }
- if (IsNonBlock && ! FD_ISSET(*sock, &rfds)) {
- nSuccessLess ++;
- continue;
- }
- retval = write(*sock, &buf[bytes_written],
- nbytes - bytes_written);
- if (retval < 1) {
- sock_close(*sock);
- *sock = -1;
- return (-1);
- }
- bytes_written = bytes_written + retval;
- if (IsNonBlock && (bytes_written == nbytes)){
- tv.tv_sec = selectresolution;
- tv.tv_usec = 0;
-
- FD_ZERO(&rfds);
- FD_SET(*sock, &rfds);
- if (select(*sock + 1, NULL, &rfds, NULL, &tv) == -1) {
- close (*sock);
- *sock = -1;
- return -1;
- }
- }
- }
- return (bytes_written);
-}
-
-
-// client_getln() ... Get a LF-terminated line of text from the client.
-int sock_getln_err(int *sock, char *buf, int bufsize, int *rc, int nSec) {
- int i, retval;
- const char *pCh;
-
- FlushStrBuf(CC->sMigrateBuf);
- *rc = retval = CtdlSockGetLine(sock, CC->sMigrateBuf, nSec);
-
- i = StrLength(CC->sMigrateBuf);
- pCh = ChrPtr(CC->sMigrateBuf);
-
- memcpy(buf, pCh, i + 1);
-
- FlushStrBuf(CC->sMigrateBuf);
- if (retval < 0) {
- safestrncpy(&buf[i], "000", bufsize - i);
- i += 3;
- }
- return i;
-}
-
-
-// Multiline version of sock_gets() ... this is a convenience function for
-// client side protocol implementations. It only returns the first line of
-// a multiline response, discarding the rest.
-int ml_sock_gets(int *sock, char *buf, int nSec) {
- int rc = 0;
- char bigbuf[1024];
- int g;
-
- g = sock_getln_err(sock, buf, SIZ, &rc, nSec);
- if (rc < 0)
- return rc;
- if (g < 4)
- return (g);
- if (buf[3] != '-')
- return (g);
-
- do {
- g = sock_getln_err(sock, bigbuf, SIZ, &rc, nSec);
- if (rc < 0)
- return rc;
- if (g < 0)
- return (g);
- } while ((g >= 4) && (bigbuf[3] == '-'));
-
- return (strlen(buf));
-}
-
-
-// sock_puts() - send line to server - implemented in terms of serv_write()
-// Returns the number of bytes written, or -1 for error.
-int sock_puts(int *sock, char *buf) {
- int i, j;
-
- i = sock_write(sock, buf, strlen(buf));
- if (i < 0)
- return (i);
- j = sock_write(sock, "\n", 1);
- if (j < 0)
- return (j);
- return (i + j);
-}
+++ /dev/null
-// Header file for TCP client socket library
-//
-// Copyright (c) 1987-2012 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.
-
-int sock_connect(char *host, char *service);
-int sock_write(int *sock, const char *buf, int nbytes);
-int sock_write_timeout(int *sock, const char *buf, int nbytes, int timeout);
-int ml_sock_gets(int *sock, char *buf, int nSec);
-int sock_getln(int *sock, char *buf, int bufsize);
-int CtdlSockGetLine(int *sock, StrBuf *Target, int nSec);
-int sock_puts(int *sock, char *buf);
-int socket_read_blob(int *Socket, StrBuf * Target, int bytes, int timeout);
-
-
-/*
- * This looks dumb, but it's being done for future portability
- */
-#define sock_close(sock) close(sock)
-#define sock_shutdown(sock, how) shutdown(sock, how)
-
-/*
- * Default timeout for client sessions
- */
-#define CLIENT_TIMEOUT 600
+++ /dev/null
-// Read and write the system configuration database
-//
-// 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.
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <netdb.h>
-#include <crypt.h>
-#include <sys/utsname.h>
-#include <libcitadel.h>
-#include <assert.h>
-#include "config.h"
-#include "ctdl_module.h"
-
-long config_msgnum = 0;
-HashList *ctdlconfig = NULL; // new configuration
-
-
-void config_warn_if_port_unset(char *key, int default_port) {
- int p = CtdlGetConfigInt(key);
- if ((p < -1) || (p == 0) || (p > UINT16_MAX)) {
- syslog(LOG_ERR, "config: setting %s is not -1 (disabled) or a valid TCP port - setting to default %d", key, default_port);
- CtdlSetConfigInt(key, default_port);
- }
-}
-
-
-void config_warn_if_empty(char *key) {
- if (IsEmptyStr(CtdlGetConfigStr(key))) {
- syslog(LOG_ERR, "config: setting %s is empty, but must not - check your config!", key);
- }
-}
-
-
-void validate_config(void) {
-
- // these shouldn't be empty
- config_warn_if_empty("c_fqdn");
- config_warn_if_empty("c_baseroom");
- config_warn_if_empty("c_aideroom");
- config_warn_if_empty("c_twitroom");
- config_warn_if_empty("c_nodename");
-
- // Sanity check for port bindings
- config_warn_if_port_unset("c_smtp_port", 25);
- config_warn_if_port_unset("c_pop3_port", 110);
- config_warn_if_port_unset("c_imap_port", 143);
- config_warn_if_port_unset("c_msa_port", 587);
- config_warn_if_port_unset("c_port_number", 504);
- config_warn_if_port_unset("c_smtps_port", 465);
- config_warn_if_port_unset("c_pop3s_port", 995);
- config_warn_if_port_unset("c_imaps_port", 993);
- config_warn_if_port_unset("c_pftcpdict_port", -1);
- config_warn_if_port_unset("c_xmpp_c2s_port", 5222);
- config_warn_if_port_unset("c_xmpp_s2s_port", 5269);
- config_warn_if_port_unset("c_nntp_port", 119);
- config_warn_if_port_unset("c_nntps_port", 563);
-
- if (getpwuid(ctdluid) == NULL) {
- syslog(LOG_ERR, "config: uid (%d) does not exist ... citserver will run as root", ctdluid);
- }
-}
-
-
-// The "host key" is a 100-byte random string that is used to do some persistent hashing.
-// It must remain consistent throughout the lifetime of this Citadel installation
-void generate_host_key(void) {
- syslog(LOG_INFO, "config: generating a host key");
- int len = 0;
- char host_key[128];
-
- while (len < 100) {
- do {
- host_key[len] = random() % 0x7F;
- } while (!isalnum(host_key[len]));
- host_key[++len] = 0;
- }
- CtdlSetConfigStr("host_key", host_key);
-}
-
-
-// Put some sane default values into our configuration. Some will be overridden when we run setup.
-void brand_new_installation_set_defaults(void) {
-
- struct utsname my_utsname;
- struct hostent *he;
- char detected_hostname[256];
-
- // Determine our host name, in case we need to use it as a default
- uname(&my_utsname);
-
- // set some sample/default values in place of blanks...
- extract_token(detected_hostname, my_utsname.nodename, 0, '.', sizeof detected_hostname);
- CtdlSetConfigStr("c_nodename", detected_hostname);
-
- if ((he = gethostbyname(my_utsname.nodename)) != NULL) {
- CtdlSetConfigStr("c_fqdn", he->h_name);
- }
- else {
- CtdlSetConfigStr("c_fqdn", my_utsname.nodename);
- }
-
- CtdlSetConfigStr("c_humannode", "Citadel Server");
- CtdlSetConfigInt("c_initax", 4);
- CtdlSetConfigStr("c_moreprompt", "<more>");
- CtdlSetConfigStr("c_twitroom", "Trashcan");
- CtdlSetConfigStr("c_baseroom", BASEROOM);
- CtdlSetConfigStr("c_aideroom", "Aide");
- CtdlSetConfigInt("c_sleeping", 900);
-
- if (CtdlGetConfigInt("c_createax") == 0) {
- CtdlSetConfigInt("c_createax", 3);
- }
-
- // Default port numbers for various services
- CtdlSetConfigInt("c_port_number", 504);
- CtdlSetConfigInt("c_smtp_port", 25);
- CtdlSetConfigInt("c_pop3_port", 110);
- CtdlSetConfigInt("c_imap_port", 143);
- CtdlSetConfigInt("c_msa_port", 587);
- CtdlSetConfigInt("c_smtps_port", 465);
- CtdlSetConfigInt("c_pop3s_port", 995);
- CtdlSetConfigInt("c_imaps_port", 993);
- CtdlSetConfigInt("c_pftcpdict_port", -1);
- CtdlSetConfigInt("c_xmpp_c2s_port", 5222);
- CtdlSetConfigInt("c_xmpp_s2s_port", 5269);
- CtdlSetConfigInt("c_nntp_port", 119);
- CtdlSetConfigInt("c_nntps_port", 563);
-
- // Prevent the "new installation, set defaults" behavior from occurring again
- CtdlSetConfigLong("c_config_created_or_migrated", (long)time(NULL));
-}
-
-
-// Migrate a supplied legacy configuration to the new in-db format.
-// No individual site should ever have to do this more than once.
-void migrate_legacy_config(struct legacy_config *lconfig) {
- CtdlSetConfigStr( "c_nodename" , lconfig->c_nodename );
- CtdlSetConfigStr( "c_fqdn" , lconfig->c_fqdn );
- CtdlSetConfigStr( "c_humannode" , lconfig->c_humannode );
- CtdlSetConfigInt( "c_creataide" , lconfig->c_creataide );
- CtdlSetConfigInt( "c_sleeping" , lconfig->c_sleeping );
- CtdlSetConfigInt( "c_initax" , lconfig->c_initax );
- CtdlSetConfigInt( "c_regiscall" , lconfig->c_regiscall );
- CtdlSetConfigInt( "c_twitdetect" , lconfig->c_twitdetect );
- CtdlSetConfigStr( "c_twitroom" , lconfig->c_twitroom );
- CtdlSetConfigStr( "c_moreprompt" , lconfig->c_moreprompt );
- CtdlSetConfigInt( "c_restrict" , lconfig->c_restrict );
- CtdlSetConfigStr( "c_site_location" , lconfig->c_site_location );
- CtdlSetConfigStr( "c_sysadm" , lconfig->c_sysadm );
- CtdlSetConfigInt( "c_maxsessions" , lconfig->c_maxsessions );
- CtdlSetConfigStr( "c_ip_addr" , lconfig->c_ip_addr );
- CtdlSetConfigInt( "c_port_number" , lconfig->c_port_number );
- CtdlSetConfigInt( "c_ep_mode" , lconfig->c_ep.expire_mode );
- CtdlSetConfigInt( "c_ep_value" , lconfig->c_ep.expire_value );
- CtdlSetConfigInt( "c_userpurge" , lconfig->c_userpurge );
- CtdlSetConfigInt( "c_roompurge" , lconfig->c_roompurge );
- CtdlSetConfigStr( "c_logpages" , lconfig->c_logpages );
- CtdlSetConfigInt( "c_createax" , lconfig->c_createax );
- CtdlSetConfigLong( "c_maxmsglen" , lconfig->c_maxmsglen );
- CtdlSetConfigInt( "c_min_workers" , lconfig->c_min_workers );
- CtdlSetConfigInt( "c_max_workers" , lconfig->c_max_workers );
- CtdlSetConfigInt( "c_pop3_port" , lconfig->c_pop3_port );
- CtdlSetConfigInt( "c_smtp_port" , lconfig->c_smtp_port );
- CtdlSetConfigInt( "c_rfc822_strict_from" , lconfig->c_rfc822_strict_from );
- CtdlSetConfigInt( "c_aide_zap" , lconfig->c_aide_zap );
- CtdlSetConfigInt( "c_imap_port" , lconfig->c_imap_port );
- CtdlSetConfigLong( "c_net_freq" , lconfig->c_net_freq );
- CtdlSetConfigInt( "c_disable_newu" , lconfig->c_disable_newu );
- CtdlSetConfigInt( "c_enable_fulltext" , lconfig->c_enable_fulltext );
- CtdlSetConfigStr( "c_baseroom" , lconfig->c_baseroom );
- CtdlSetConfigStr( "c_aideroom" , lconfig->c_aideroom );
- CtdlSetConfigInt( "c_purge_hour" , lconfig->c_purge_hour );
- CtdlSetConfigInt( "c_mbxep_mode" , lconfig->c_mbxep.expire_mode );
- CtdlSetConfigInt( "c_mbxep_value" , lconfig->c_mbxep.expire_value );
- CtdlSetConfigStr( "c_ldap_host" , lconfig->c_ldap_host );
- CtdlSetConfigInt( "c_ldap_port" , lconfig->c_ldap_port );
- CtdlSetConfigStr( "c_ldap_base_dn" , lconfig->c_ldap_base_dn );
- CtdlSetConfigStr( "c_ldap_bind_dn" , lconfig->c_ldap_bind_dn );
- CtdlSetConfigStr( "c_ldap_bind_pw" , lconfig->c_ldap_bind_pw );
- CtdlSetConfigInt( "c_msa_port" , lconfig->c_msa_port );
- CtdlSetConfigInt( "c_imaps_port" , lconfig->c_imaps_port );
- CtdlSetConfigInt( "c_pop3s_port" , lconfig->c_pop3s_port );
- CtdlSetConfigInt( "c_smtps_port" , lconfig->c_smtps_port );
- CtdlSetConfigInt( "c_auto_cull" , lconfig->c_auto_cull );
- CtdlSetConfigInt( "c_allow_spoofing" , lconfig->c_allow_spoofing );
- CtdlSetConfigInt( "c_journal_email" , lconfig->c_journal_email );
- CtdlSetConfigInt( "c_journal_pubmsgs" , lconfig->c_journal_pubmsgs );
- CtdlSetConfigStr( "c_journal_dest" , lconfig->c_journal_dest );
- CtdlSetConfigStr( "c_default_cal_zone" , lconfig->c_default_cal_zone );
- CtdlSetConfigInt( "c_pftcpdict_port" , lconfig->c_pftcpdict_port );
- CtdlSetConfigInt( "c_auth_mode" , lconfig->c_auth_mode );
- CtdlSetConfigInt( "c_rbl_at_greeting" , lconfig->c_rbl_at_greeting );
- CtdlSetConfigStr( "c_pager_program" , lconfig->c_pager_program );
- CtdlSetConfigInt( "c_imap_keep_from" , lconfig->c_imap_keep_from );
- CtdlSetConfigInt( "c_xmpp_c2s_port" , lconfig->c_xmpp_c2s_port );
- CtdlSetConfigInt( "c_xmpp_s2s_port" , lconfig->c_xmpp_s2s_port );
- CtdlSetConfigLong( "c_pop3_fetch" , lconfig->c_pop3_fetch );
- CtdlSetConfigLong( "c_pop3_fastest" , lconfig->c_pop3_fastest );
- CtdlSetConfigInt( "c_spam_flag_only" , lconfig->c_spam_flag_only );
- CtdlSetConfigInt( "c_guest_logins" , lconfig->c_guest_logins );
- CtdlSetConfigInt( "c_nntp_port" , lconfig->c_nntp_port );
- CtdlSetConfigInt( "c_nntps_port" , lconfig->c_nntps_port );
-}
-
-
-// Called during the initialization of Citadel server.
-// It verifies the system's integrity and reads citadel.config into memory.
-void initialize_config_system(void) {
- FILE *cfp;
- int rv;
- struct legacy_config lconfig; // legacy configuration
- ctdlconfig = NewHash(1, NULL); // set up the real config system
-
- // Ensure that we are linked to the correct version of libcitadel
- if (libcitadel_version_number() < LIBCITADEL_VERSION_NUMBER) {
- fprintf(stderr, "You are running libcitadel version %d\n", libcitadel_version_number());
- fprintf(stderr, "citserver was compiled against version %d\n", LIBCITADEL_VERSION_NUMBER);
- exit(CTDLEXIT_LIBCITADEL);
- }
-
- memset(&lconfig, 0, sizeof(struct legacy_config));
- cfp = fopen(file_citadel_config, "rb");
- if (cfp != NULL) {
- if (CtdlGetConfigLong("c_config_created_or_migrated") > 0) {
- fprintf(stderr, "Citadel Server found BOTH legacy and new configurations present.\n");
- fprintf(stderr, "Exiting to prevent data corruption.\n");
- exit(CTDLEXIT_CONFIG);
- }
- rv = fread((char *) &lconfig, sizeof(struct legacy_config), 1, cfp);
- if (rv != 1) {
- fprintf(stderr,
- "Warning: Found a legacy config file %s has unexpected size. \n",
- file_citadel_config
- );
- }
-
- migrate_legacy_config(&lconfig);
-
- fclose(cfp);
- if (unlink(file_citadel_config) != 0) {
- fprintf(stderr, "Unable to remove legacy config file %s after migrating it.\n", file_citadel_config);
- fprintf(stderr, "Exiting to prevent data corruption.\n");
- exit(CTDLEXIT_CONFIG);
- }
-
- // Prevent migration/initialization from happening again.
- CtdlSetConfigLong("c_config_created_or_migrated", (long)time(NULL));
-
- }
-
- // New installation? Set up configuration
- if (CtdlGetConfigLong("c_config_created_or_migrated") <= 0) {
- brand_new_installation_set_defaults();
- }
-
- // Default maximum message length is 10 megabytes. This is site
- // configurable. Also check to make sure the limit has not been
- // set below 8192 bytes.
- if (CtdlGetConfigLong("c_maxmsglen") <= 0) CtdlSetConfigLong("c_maxmsglen", 10485760);
- if (CtdlGetConfigLong("c_maxmsglen") < 8192) CtdlSetConfigLong("c_maxmsglen", 8192);
-
- // Default lower and upper limits on number of worker threads
- if (CtdlGetConfigInt("c_min_workers") < 5) CtdlSetConfigInt("c_min_workers", 5); // min
- if (CtdlGetConfigInt("c_max_workers") == 0) CtdlSetConfigInt("c_max_workers", 256); // default max
- if (CtdlGetConfigInt("c_max_workers") < CtdlGetConfigInt("c_min_workers")) {
- CtdlSetConfigInt("c_max_workers", CtdlGetConfigInt("c_min_workers")); // max >= min
- }
-
- // Networking more than once every five minutes just isn't sane
- if (CtdlGetConfigLong("c_net_freq") == 0) CtdlSetConfigLong("c_net_freq", 3600); // once per hour default
- if (CtdlGetConfigLong("c_net_freq") < 300) CtdlSetConfigLong("c_net_freq", 300); // minimum 5 minutes
-
- // Same goes for POP3
- if (CtdlGetConfigLong("c_pop3_fetch") == 0) CtdlSetConfigLong("c_pop3_fetch", 3600); // once per hour default
- if (CtdlGetConfigLong("c_pop3_fetch") < 300) CtdlSetConfigLong("c_pop3_fetch", 300); // 5 minutes min
- if (CtdlGetConfigLong("c_pop3_fastest") == 0) CtdlSetConfigLong("c_pop3_fastest", 3600); // once per hour default
- if (CtdlGetConfigLong("c_pop3_fastest") < 300) CtdlSetConfigLong("c_pop3_fastest", 300); // 5 minutes min
-
- // LDAP sync frequency
- if (CtdlGetConfigLong("c_ldap_sync_freq") == 0) CtdlSetConfigLong("c_ldap_sync_freq", 300); // every 5 minutes default
-
- // "create new user" only works with native authentication mode
- if (CtdlGetConfigInt("c_auth_mode") != AUTHMODE_NATIVE) {
- CtdlSetConfigInt("c_disable_newu", 1);
- }
-
- // If we do not have a host key, generate one now, and save it.
- if (IsEmptyStr(CtdlGetConfigStr("host_key"))) {
- generate_host_key();
- }
-}
-
-
-// Called when Citadel server is shutting down.
-// Clears out the config hash table.
-void shutdown_config_system(void) {
- DeleteHash(&ctdlconfig);
-}
-
-
-// Set a system config value. Simple key/value here.
-void CtdlSetConfigStr(char *key, char *value) {
- int key_len = strlen(key);
- int value_len = strlen(value);
-
- // Save it in memory
- Put(ctdlconfig, key, key_len, strdup(value), NULL);
-
- // Also write it to the config database
-
- int dbv_size = key_len + value_len + 2;
- char *dbv = malloc(dbv_size);
- strcpy(dbv, key);
- strcpy(&dbv[key_len + 1], value);
- cdb_store(CDB_CONFIG, key, key_len, dbv, dbv_size);
- free(dbv);
-}
-
-
-// Set a numeric system config value (long integer)
-void CtdlSetConfigLong(char *key, long value) {
- char longstr[256];
- sprintf(longstr, "%ld", value);
- CtdlSetConfigStr(key, longstr);
-}
-
-
-// Set a numeric system config value (integer)
-void CtdlSetConfigInt(char *key, int value) {
- char intstr[256];
- sprintf(intstr, "%d", value);
- CtdlSetConfigStr(key, intstr);
-}
-
-
-// Delete a system config value.
-void CtdlDelConfig(char *key) {
- int key_len = strlen(key);
-
- if (IsEmptyStr(key)) return;
-
- // Delete from the database.
- cdb_delete(CDB_CONFIG, key, key_len);
-
- // Delete from the in-memory cache
- HashPos *Pos = GetNewHashPos(ctdlconfig, 1);
- if (GetHashPosFromKey(ctdlconfig, key, key_len, Pos)) {
- DeleteEntryFromHash(ctdlconfig, Pos);
- }
- DeleteHashPos(&Pos);
-
- assert(Pos == NULL); // no memory leaks allowed
-}
-
-
-// Fetch a system config value. Caller does *not* own the returned value and may not alter it.
-char *CtdlGetConfigStr(char *key) {
- char *value = NULL;
- struct cdbdata *cdb;
- int key_len = strlen(key);
-
- if (IsEmptyStr(key)) return(NULL);
-
- // First look in memory
- if (GetHash(ctdlconfig, key, key_len, (void *)&value)) {
- return value;
- }
-
- // Then look in the database.
- cdb = cdb_fetch(CDB_CONFIG, key, key_len);
- if (cdb == NULL) { // nope, not there either.
- return(NULL);
- }
-
- // Got it. Save it in memory for the next fetch.
- value = strdup(cdb->ptr + key_len + 1); // The key was stored there too; skip past it
- cdb_free(cdb);
- Put(ctdlconfig, key, key_len, value, NULL);
- return value;
-}
-
-
-// Fetch a system config value - integer
-int CtdlGetConfigInt(char *key) {
- char *s = CtdlGetConfigStr(key);
- if (s) return atoi(s);
- return 0;
-}
-
-
-// Fetch a system config value - long integer
-long CtdlGetConfigLong(char *key) {
- char *s = CtdlGetConfigStr(key);
- if (s) return atol(s);
- return 0;
-}
-
-
-void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
- config_msgnum = msgnum;
-}
-
-
-// This is for fetching longer configuration sets which are stored in the message base.
-char *CtdlGetSysConfig(char *sysconfname) {
- char hold_rm[ROOMNAMELEN];
- long msgnum = -1;
- char *conf;
- struct CtdlMessage *msg;
- char buf[SIZ];
-
- strcpy(hold_rm, CC->room.QRname);
- if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
- CtdlGetRoom(&CC->room, hold_rm);
- return NULL;
- }
-
- // The new way: hunt for the message number in the config database
- msgnum = CtdlGetConfigLong(sysconfname);
-
- // Legacy format: hunt through the local system configuration room for a message with a matching MIME type
- if (msgnum <= 0) {
- begin_critical_section(S_CONFIG);
- config_msgnum = -1;
- CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL, CtdlGetSysConfigBackend, NULL);
- msgnum = config_msgnum;
- end_critical_section(S_CONFIG);
- if (msgnum > 0) {
- CtdlSetConfigLong(sysconfname, msgnum); // store it the new way so we don't have to do this again
- }
- }
-
- if (msgnum <= 0) {
- conf = NULL;
- }
- else {
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg != NULL) {
- conf = strdup(msg->cm_fields[eMesageText]);
- CM_Free(msg);
- }
- else {
- conf = NULL;
- }
- }
-
- CtdlGetRoom(&CC->room, hold_rm);
-
- if (conf != NULL) { // Strip the MIME headers, leaving only the content
- do {
- extract_token(buf, conf, 0, '\n', sizeof buf);
- strcpy(conf, &conf[strlen(buf)+1]);
- } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
- }
-
- return(conf);
-}
-
-
-// This is for storing longer configuration sets which are stored in the message base.
-void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
- long old_msgnum = -1;
- long new_msgnum = -1;
-
- // Search for the previous copy of this config item, so we can delete it
- old_msgnum = CtdlGetConfigLong(sysconfname);
-
- // Go ahead and save it, and write the new msgnum to the config database so we can find it again
- new_msgnum = CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 0);
- if (new_msgnum > 0) {
- CtdlSetConfigLong(sysconfname, new_msgnum);
-
- // Now delete the old copy
- if (old_msgnum > 0) {
- CtdlDeleteMessages(SYSCONFIGROOM, &old_msgnum, 1, "");
- }
- }
-}
+++ /dev/null
-#! /bin/sh
-# Attempt to guess a canonical system name.
-# Copyright 1992-2021 Free Software Foundation, Inc.
-
-timestamp='2021-01-01'
-
-# This file is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 3 of the License, or
-# (at your option) any later version.
-#
-# 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.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, see <https://www.gnu.org/licenses/>.
-#
-# As a special exception to the GNU General Public License, if you
-# distribute this file as part of a program that contains a
-# configuration script generated by Autoconf, you may include it under
-# the same distribution terms that you use for the rest of that
-# program. This Exception is an additional permission under section 7
-# of the GNU General Public License, version 3 ("GPLv3").
-#
-# Originally written by Per Bothner; maintained since 2000 by Ben Elliston.
-#
-# You can get the latest version of this script from:
-# https://git.savannah.gnu.org/cgit/config.git/plain/config.guess
-#
-# Please send patches to <config-patches@gnu.org>.
-
-
-me=$(echo "$0" | sed -e 's,.*/,,')
-
-usage="\
-Usage: $0 [OPTION]
-
-Output the configuration name of the system \`$me' is run on.
-
-Options:
- -h, --help print this help, then exit
- -t, --time-stamp print date of last modification, then exit
- -v, --version print version number, then exit
-
-Report bugs and patches to <config-patches@gnu.org>."
-
-version="\
-GNU config.guess ($timestamp)
-
-Originally written by Per Bothner.
-Copyright 1992-2021 Free Software Foundation, Inc.
-
-This is free software; see the source for copying conditions. There is NO
-warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
-
-help="
-Try \`$me --help' for more information."
-
-# Parse command line
-while test $# -gt 0 ; do
- case $1 in
- --time-stamp | --time* | -t )
- echo "$timestamp" ; exit ;;
- --version | -v )
- echo "$version" ; exit ;;
- --help | --h* | -h )
- echo "$usage"; exit ;;
- -- ) # Stop option processing
- shift; break ;;
- - ) # Use stdin as input.
- break ;;
- -* )
- echo "$me: invalid option $1$help" >&2
- exit 1 ;;
- * )
- break ;;
- esac
-done
-
-if test $# != 0; then
- echo "$me: too many arguments$help" >&2
- exit 1
-fi
-
-# CC_FOR_BUILD -- compiler used by this script. Note that the use of a
-# compiler to aid in system detection is discouraged as it requires
-# temporary files to be created and, as you can see below, it is a
-# headache to deal with in a portable fashion.
-
-# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still
-# use `HOST_CC' if defined, but it is deprecated.
-
-# Portable tmp directory creation inspired by the Autoconf team.
-
-tmp=
-# shellcheck disable=SC2172
-trap 'test -z "$tmp" || rm -fr "$tmp"' 0 1 2 13 15
-
-set_cc_for_build() {
- # prevent multiple calls if $tmp is already set
- test "$tmp" && return 0
- : "${TMPDIR=/tmp}"
- # shellcheck disable=SC2039
- { tmp=$( (umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null) && test -n "$tmp" && test -d "$tmp" ; } ||
- { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir "$tmp" 2>/dev/null) ; } ||
- { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir "$tmp" 2>/dev/null) && echo "Warning: creating insecure temp directory" >&2 ; } ||
- { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; }
- dummy=$tmp/dummy
- case ${CC_FOR_BUILD-},${HOST_CC-},${CC-} in
- ,,) echo "int x;" > "$dummy.c"
- for driver in cc gcc c89 c99 ; do
- if ($driver -c -o "$dummy.o" "$dummy.c") >/dev/null 2>&1 ; then
- CC_FOR_BUILD="$driver"
- break
- fi
- done
- if test x"$CC_FOR_BUILD" = x ; then
- CC_FOR_BUILD=no_compiler_found
- fi
- ;;
- ,,*) CC_FOR_BUILD=$CC ;;
- ,*,*) CC_FOR_BUILD=$HOST_CC ;;
- esac
-}
-
-# This is needed to find uname on a Pyramid OSx when run in the BSD universe.
-# (ghazi@noc.rutgers.edu 1994-08-24)
-if test -f /.attbin/uname ; then
- PATH=$PATH:/.attbin ; export PATH
-fi
-
-UNAME_MACHINE=$( (uname -m) 2>/dev/null) || UNAME_MACHINE=unknown
-UNAME_RELEASE=$( (uname -r) 2>/dev/null) || UNAME_RELEASE=unknown
-UNAME_SYSTEM=$( (uname -s) 2>/dev/null) || UNAME_SYSTEM=unknown
-UNAME_VERSION=$( (uname -v) 2>/dev/null) || UNAME_VERSION=unknown
-
-case "$UNAME_SYSTEM" in
-Linux|GNU|GNU/*)
- LIBC=unknown
-
- set_cc_for_build
- cat <<-EOF > "$dummy.c"
- #include <features.h>
- #if defined(__UCLIBC__)
- LIBC=uclibc
- #elif defined(__dietlibc__)
- LIBC=dietlibc
- #elif defined(__GLIBC__)
- LIBC=gnu
- #else
- #include <stdarg.h>
- /* First heuristic to detect musl libc. */
- #ifdef __DEFINED_va_list
- LIBC=musl
- #endif
- #endif
- EOF
- eval "$($CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^LIBC' | sed 's, ,,g')"
-
- # Second heuristic to detect musl libc.
- if [ "$LIBC" = unknown ] &&
- command -v ldd >/dev/null &&
- ldd --version 2>&1 | grep -q ^musl; then
- LIBC=musl
- fi
-
- # If the system lacks a compiler, then just pick glibc.
- # We could probably try harder.
- if [ "$LIBC" = unknown ]; then
- LIBC=gnu
- fi
- ;;
-esac
-
-# Note: order is significant - the case branches are not exclusive.
-
-case "$UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION" in
- *:NetBSD:*:*)
- # NetBSD (nbsd) targets should (where applicable) match one or
- # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*,
- # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently
- # switched to ELF, *-*-netbsd* would select the old
- # object file format. This provides both forward
- # compatibility and a consistent mechanism for selecting the
- # object file format.
- #
- # Note: NetBSD doesn't particularly care about the vendor
- # portion of the name. We always set it to "unknown".
- sysctl="sysctl -n hw.machine_arch"
- UNAME_MACHINE_ARCH=$( (uname -p 2>/dev/null || \
- "/sbin/$sysctl" 2>/dev/null || \
- "/usr/sbin/$sysctl" 2>/dev/null || \
- echo unknown))
- case "$UNAME_MACHINE_ARCH" in
- aarch64eb) machine=aarch64_be-unknown ;;
- armeb) machine=armeb-unknown ;;
- arm*) machine=arm-unknown ;;
- sh3el) machine=shl-unknown ;;
- sh3eb) machine=sh-unknown ;;
- sh5el) machine=sh5le-unknown ;;
- earmv*)
- arch=$(echo "$UNAME_MACHINE_ARCH" | sed -e 's,^e\(armv[0-9]\).*$,\1,')
- endian=$(echo "$UNAME_MACHINE_ARCH" | sed -ne 's,^.*\(eb\)$,\1,p')
- machine="${arch}${endian}"-unknown
- ;;
- *) machine="$UNAME_MACHINE_ARCH"-unknown ;;
- esac
- # The Operating System including object format, if it has switched
- # to ELF recently (or will in the future) and ABI.
- case "$UNAME_MACHINE_ARCH" in
- earm*)
- os=netbsdelf
- ;;
- arm*|i386|m68k|ns32k|sh3*|sparc|vax)
- set_cc_for_build
- if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \
- | grep -q __ELF__
- then
- # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout).
- # Return netbsd for either. FIX?
- os=netbsd
- else
- os=netbsdelf
- fi
- ;;
- *)
- os=netbsd
- ;;
- esac
- # Determine ABI tags.
- case "$UNAME_MACHINE_ARCH" in
- earm*)
- expr='s/^earmv[0-9]/-eabi/;s/eb$//'
- abi=$(echo "$UNAME_MACHINE_ARCH" | sed -e "$expr")
- ;;
- esac
- # The OS release
- # Debian GNU/NetBSD machines have a different userland, and
- # thus, need a distinct triplet. However, they do not need
- # kernel version information, so it can be replaced with a
- # suitable tag, in the style of linux-gnu.
- case "$UNAME_VERSION" in
- Debian*)
- release='-gnu'
- ;;
- *)
- release=$(echo "$UNAME_RELEASE" | sed -e 's/[-_].*//' | cut -d. -f1,2)
- ;;
- esac
- # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM:
- # contains redundant information, the shorter form:
- # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used.
- echo "$machine-${os}${release}${abi-}"
- exit ;;
- *:Bitrig:*:*)
- UNAME_MACHINE_ARCH=$(arch | sed 's/Bitrig.//')
- echo "$UNAME_MACHINE_ARCH"-unknown-bitrig"$UNAME_RELEASE"
- exit ;;
- *:OpenBSD:*:*)
- UNAME_MACHINE_ARCH=$(arch | sed 's/OpenBSD.//')
- echo "$UNAME_MACHINE_ARCH"-unknown-openbsd"$UNAME_RELEASE"
- exit ;;
- *:LibertyBSD:*:*)
- UNAME_MACHINE_ARCH=$(arch | sed 's/^.*BSD\.//')
- echo "$UNAME_MACHINE_ARCH"-unknown-libertybsd"$UNAME_RELEASE"
- exit ;;
- *:MidnightBSD:*:*)
- echo "$UNAME_MACHINE"-unknown-midnightbsd"$UNAME_RELEASE"
- exit ;;
- *:ekkoBSD:*:*)
- echo "$UNAME_MACHINE"-unknown-ekkobsd"$UNAME_RELEASE"
- exit ;;
- *:SolidBSD:*:*)
- echo "$UNAME_MACHINE"-unknown-solidbsd"$UNAME_RELEASE"
- exit ;;
- *:OS108:*:*)
- echo "$UNAME_MACHINE"-unknown-os108_"$UNAME_RELEASE"
- exit ;;
- macppc:MirBSD:*:*)
- echo powerpc-unknown-mirbsd"$UNAME_RELEASE"
- exit ;;
- *:MirBSD:*:*)
- echo "$UNAME_MACHINE"-unknown-mirbsd"$UNAME_RELEASE"
- exit ;;
- *:Sortix:*:*)
- echo "$UNAME_MACHINE"-unknown-sortix
- exit ;;
- *:Twizzler:*:*)
- echo "$UNAME_MACHINE"-unknown-twizzler
- exit ;;
- *:Redox:*:*)
- echo "$UNAME_MACHINE"-unknown-redox
- exit ;;
- mips:OSF1:*.*)
- echo mips-dec-osf1
- exit ;;
- alpha:OSF1:*:*)
- case $UNAME_RELEASE in
- *4.0)
- UNAME_RELEASE=$(/usr/sbin/sizer -v | awk '{print $3}')
- ;;
- *5.*)
- UNAME_RELEASE=$(/usr/sbin/sizer -v | awk '{print $4}')
- ;;
- esac
- # According to Compaq, /usr/sbin/psrinfo has been available on
- # OSF/1 and Tru64 systems produced since 1995. I hope that
- # covers most systems running today. This code pipes the CPU
- # types through head -n 1, so we only detect the type of CPU 0.
- ALPHA_CPU_TYPE=$(/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1)
- case "$ALPHA_CPU_TYPE" in
- "EV4 (21064)")
- UNAME_MACHINE=alpha ;;
- "EV4.5 (21064)")
- UNAME_MACHINE=alpha ;;
- "LCA4 (21066/21068)")
- UNAME_MACHINE=alpha ;;
- "EV5 (21164)")
- UNAME_MACHINE=alphaev5 ;;
- "EV5.6 (21164A)")
- UNAME_MACHINE=alphaev56 ;;
- "EV5.6 (21164PC)")
- UNAME_MACHINE=alphapca56 ;;
- "EV5.7 (21164PC)")
- UNAME_MACHINE=alphapca57 ;;
- "EV6 (21264)")
- UNAME_MACHINE=alphaev6 ;;
- "EV6.7 (21264A)")
- UNAME_MACHINE=alphaev67 ;;
- "EV6.8CB (21264C)")
- UNAME_MACHINE=alphaev68 ;;
- "EV6.8AL (21264B)")
- UNAME_MACHINE=alphaev68 ;;
- "EV6.8CX (21264D)")
- UNAME_MACHINE=alphaev68 ;;
- "EV6.9A (21264/EV69A)")
- UNAME_MACHINE=alphaev69 ;;
- "EV7 (21364)")
- UNAME_MACHINE=alphaev7 ;;
- "EV7.9 (21364A)")
- UNAME_MACHINE=alphaev79 ;;
- esac
- # A Pn.n version is a patched version.
- # A Vn.n version is a released version.
- # A Tn.n version is a released field test version.
- # A Xn.n version is an unreleased experimental baselevel.
- # 1.2 uses "1.2" for uname -r.
- echo "$UNAME_MACHINE"-dec-osf"$(echo "$UNAME_RELEASE" | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz)"
- # Reset EXIT trap before exiting to avoid spurious non-zero exit code.
- exitcode=$?
- trap '' 0
- exit $exitcode ;;
- Amiga*:UNIX_System_V:4.0:*)
- echo m68k-unknown-sysv4
- exit ;;
- *:[Aa]miga[Oo][Ss]:*:*)
- echo "$UNAME_MACHINE"-unknown-amigaos
- exit ;;
- *:[Mm]orph[Oo][Ss]:*:*)
- echo "$UNAME_MACHINE"-unknown-morphos
- exit ;;
- *:OS/390:*:*)
- echo i370-ibm-openedition
- exit ;;
- *:z/VM:*:*)
- echo s390-ibm-zvmoe
- exit ;;
- *:OS400:*:*)
- echo powerpc-ibm-os400
- exit ;;
- arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*)
- echo arm-acorn-riscix"$UNAME_RELEASE"
- exit ;;
- arm*:riscos:*:*|arm*:RISCOS:*:*)
- echo arm-unknown-riscos
- exit ;;
- SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*)
- echo hppa1.1-hitachi-hiuxmpp
- exit ;;
- Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*)
- # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE.
- if test "$( (/bin/universe) 2>/dev/null)" = att ; then
- echo pyramid-pyramid-sysv3
- else
- echo pyramid-pyramid-bsd
- fi
- exit ;;
- NILE*:*:*:dcosx)
- echo pyramid-pyramid-svr4
- exit ;;
- DRS?6000:unix:4.0:6*)
- echo sparc-icl-nx6
- exit ;;
- DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*)
- case $(/usr/bin/uname -p) in
- sparc) echo sparc-icl-nx7; exit ;;
- esac ;;
- s390x:SunOS:*:*)
- echo "$UNAME_MACHINE"-ibm-solaris2"$(echo "$UNAME_RELEASE" | sed -e 's/[^.]*//')"
- exit ;;
- sun4H:SunOS:5.*:*)
- echo sparc-hal-solaris2"$(echo "$UNAME_RELEASE"|sed -e 's/[^.]*//')"
- exit ;;
- sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*)
- echo sparc-sun-solaris2"$(echo "$UNAME_RELEASE" | sed -e 's/[^.]*//')"
- exit ;;
- i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*)
- echo i386-pc-auroraux"$UNAME_RELEASE"
- exit ;;
- i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*)
- set_cc_for_build
- SUN_ARCH=i386
- # If there is a compiler, see if it is configured for 64-bit objects.
- # Note that the Sun cc does not turn __LP64__ into 1 like gcc does.
- # This test works for both compilers.
- if test "$CC_FOR_BUILD" != no_compiler_found; then
- if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \
- (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
- grep IS_64BIT_ARCH >/dev/null
- then
- SUN_ARCH=x86_64
- fi
- fi
- echo "$SUN_ARCH"-pc-solaris2"$(echo "$UNAME_RELEASE"|sed -e 's/[^.]*//')"
- exit ;;
- sun4*:SunOS:6*:*)
- # According to config.sub, this is the proper way to canonicalize
- # SunOS6. Hard to guess exactly what SunOS6 will be like, but
- # it's likely to be more like Solaris than SunOS4.
- echo sparc-sun-solaris3"$(echo "$UNAME_RELEASE"|sed -e 's/[^.]*//')"
- exit ;;
- sun4*:SunOS:*:*)
- case "$(/usr/bin/arch -k)" in
- Series*|S4*)
- UNAME_RELEASE=$(uname -v)
- ;;
- esac
- # Japanese Language versions have a version number like `4.1.3-JL'.
- echo sparc-sun-sunos"$(echo "$UNAME_RELEASE"|sed -e 's/-/_/')"
- exit ;;
- sun3*:SunOS:*:*)
- echo m68k-sun-sunos"$UNAME_RELEASE"
- exit ;;
- sun*:*:4.2BSD:*)
- UNAME_RELEASE=$( (sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null)
- test "x$UNAME_RELEASE" = x && UNAME_RELEASE=3
- case "$(/bin/arch)" in
- sun3)
- echo m68k-sun-sunos"$UNAME_RELEASE"
- ;;
- sun4)
- echo sparc-sun-sunos"$UNAME_RELEASE"
- ;;
- esac
- exit ;;
- aushp:SunOS:*:*)
- echo sparc-auspex-sunos"$UNAME_RELEASE"
- exit ;;
- # The situation for MiNT is a little confusing. The machine name
- # can be virtually everything (everything which is not
- # "atarist" or "atariste" at least should have a processor
- # > m68000). The system name ranges from "MiNT" over "FreeMiNT"
- # to the lowercase version "mint" (or "freemint"). Finally
- # the system name "TOS" denotes a system which is actually not
- # MiNT. But MiNT is downward compatible to TOS, so this should
- # be no problem.
- atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*)
- echo m68k-atari-mint"$UNAME_RELEASE"
- exit ;;
- atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*)
- echo m68k-atari-mint"$UNAME_RELEASE"
- exit ;;
- *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*)
- echo m68k-atari-mint"$UNAME_RELEASE"
- exit ;;
- milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*)
- echo m68k-milan-mint"$UNAME_RELEASE"
- exit ;;
- hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*)
- echo m68k-hades-mint"$UNAME_RELEASE"
- exit ;;
- *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*)
- echo m68k-unknown-mint"$UNAME_RELEASE"
- exit ;;
- m68k:machten:*:*)
- echo m68k-apple-machten"$UNAME_RELEASE"
- exit ;;
- powerpc:machten:*:*)
- echo powerpc-apple-machten"$UNAME_RELEASE"
- exit ;;
- RISC*:Mach:*:*)
- echo mips-dec-mach_bsd4.3
- exit ;;
- RISC*:ULTRIX:*:*)
- echo mips-dec-ultrix"$UNAME_RELEASE"
- exit ;;
- VAX*:ULTRIX*:*:*)
- echo vax-dec-ultrix"$UNAME_RELEASE"
- exit ;;
- 2020:CLIX:*:* | 2430:CLIX:*:*)
- echo clipper-intergraph-clix"$UNAME_RELEASE"
- exit ;;
- mips:*:*:UMIPS | mips:*:*:RISCos)
- set_cc_for_build
- sed 's/^ //' << EOF > "$dummy.c"
-#ifdef __cplusplus
-#include <stdio.h> /* for printf() prototype */
- int main (int argc, char *argv[]) {
-#else
- int main (argc, argv) int argc; char *argv[]; {
-#endif
- #if defined (host_mips) && defined (MIPSEB)
- #if defined (SYSTYPE_SYSV)
- printf ("mips-mips-riscos%ssysv\\n", argv[1]); exit (0);
- #endif
- #if defined (SYSTYPE_SVR4)
- printf ("mips-mips-riscos%ssvr4\\n", argv[1]); exit (0);
- #endif
- #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD)
- printf ("mips-mips-riscos%sbsd\\n", argv[1]); exit (0);
- #endif
- #endif
- exit (-1);
- }
-EOF
- $CC_FOR_BUILD -o "$dummy" "$dummy.c" &&
- dummyarg=$(echo "$UNAME_RELEASE" | sed -n 's/\([0-9]*\).*/\1/p') &&
- SYSTEM_NAME=$("$dummy" "$dummyarg") &&
- { echo "$SYSTEM_NAME"; exit; }
- echo mips-mips-riscos"$UNAME_RELEASE"
- exit ;;
- Motorola:PowerMAX_OS:*:*)
- echo powerpc-motorola-powermax
- exit ;;
- Motorola:*:4.3:PL8-*)
- echo powerpc-harris-powermax
- exit ;;
- Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*)
- echo powerpc-harris-powermax
- exit ;;
- Night_Hawk:Power_UNIX:*:*)
- echo powerpc-harris-powerunix
- exit ;;
- m88k:CX/UX:7*:*)
- echo m88k-harris-cxux7
- exit ;;
- m88k:*:4*:R4*)
- echo m88k-motorola-sysv4
- exit ;;
- m88k:*:3*:R3*)
- echo m88k-motorola-sysv3
- exit ;;
- AViiON:dgux:*:*)
- # DG/UX returns AViiON for all architectures
- UNAME_PROCESSOR=$(/usr/bin/uname -p)
- if test "$UNAME_PROCESSOR" = mc88100 || test "$UNAME_PROCESSOR" = mc88110
- then
- if test "$TARGET_BINARY_INTERFACE"x = m88kdguxelfx || \
- test "$TARGET_BINARY_INTERFACE"x = x
- then
- echo m88k-dg-dgux"$UNAME_RELEASE"
- else
- echo m88k-dg-dguxbcs"$UNAME_RELEASE"
- fi
- else
- echo i586-dg-dgux"$UNAME_RELEASE"
- fi
- exit ;;
- M88*:DolphinOS:*:*) # DolphinOS (SVR3)
- echo m88k-dolphin-sysv3
- exit ;;
- M88*:*:R3*:*)
- # Delta 88k system running SVR3
- echo m88k-motorola-sysv3
- exit ;;
- XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3)
- echo m88k-tektronix-sysv3
- exit ;;
- Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD)
- echo m68k-tektronix-bsd
- exit ;;
- *:IRIX*:*:*)
- echo mips-sgi-irix"$(echo "$UNAME_RELEASE"|sed -e 's/-/_/g')"
- exit ;;
- ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX.
- echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id
- exit ;; # Note that: echo "'$(uname -s)'" gives 'AIX '
- i*86:AIX:*:*)
- echo i386-ibm-aix
- exit ;;
- ia64:AIX:*:*)
- if test -x /usr/bin/oslevel ; then
- IBM_REV=$(/usr/bin/oslevel)
- else
- IBM_REV="$UNAME_VERSION.$UNAME_RELEASE"
- fi
- echo "$UNAME_MACHINE"-ibm-aix"$IBM_REV"
- exit ;;
- *:AIX:2:3)
- if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then
- set_cc_for_build
- sed 's/^ //' << EOF > "$dummy.c"
- #include <sys/systemcfg.h>
-
- main()
- {
- if (!__power_pc())
- exit(1);
- puts("powerpc-ibm-aix3.2.5");
- exit(0);
- }
-EOF
- if $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=$("$dummy")
- then
- echo "$SYSTEM_NAME"
- else
- echo rs6000-ibm-aix3.2.5
- fi
- elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then
- echo rs6000-ibm-aix3.2.4
- else
- echo rs6000-ibm-aix3.2
- fi
- exit ;;
- *:AIX:*:[4567])
- IBM_CPU_ID=$(/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }')
- if /usr/sbin/lsattr -El "$IBM_CPU_ID" | grep ' POWER' >/dev/null 2>&1; then
- IBM_ARCH=rs6000
- else
- IBM_ARCH=powerpc
- fi
- if test -x /usr/bin/lslpp ; then
- IBM_REV=$(/usr/bin/lslpp -Lqc bos.rte.libc |
- awk -F: '{ print $3 }' | sed s/[0-9]*$/0/)
- else
- IBM_REV="$UNAME_VERSION.$UNAME_RELEASE"
- fi
- echo "$IBM_ARCH"-ibm-aix"$IBM_REV"
- exit ;;
- *:AIX:*:*)
- echo rs6000-ibm-aix
- exit ;;
- ibmrt:4.4BSD:*|romp-ibm:4.4BSD:*)
- echo romp-ibm-bsd4.4
- exit ;;
- ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and
- echo romp-ibm-bsd"$UNAME_RELEASE" # 4.3 with uname added to
- exit ;; # report: romp-ibm BSD 4.3
- *:BOSX:*:*)
- echo rs6000-bull-bosx
- exit ;;
- DPX/2?00:B.O.S.:*:*)
- echo m68k-bull-sysv3
- exit ;;
- 9000/[34]??:4.3bsd:1.*:*)
- echo m68k-hp-bsd
- exit ;;
- hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*)
- echo m68k-hp-bsd4.4
- exit ;;
- 9000/[34678]??:HP-UX:*:*)
- HPUX_REV=$(echo "$UNAME_RELEASE"|sed -e 's/[^.]*.[0B]*//')
- case "$UNAME_MACHINE" in
- 9000/31?) HP_ARCH=m68000 ;;
- 9000/[34]??) HP_ARCH=m68k ;;
- 9000/[678][0-9][0-9])
- if test -x /usr/bin/getconf; then
- sc_cpu_version=$(/usr/bin/getconf SC_CPU_VERSION 2>/dev/null)
- sc_kernel_bits=$(/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null)
- case "$sc_cpu_version" in
- 523) HP_ARCH=hppa1.0 ;; # CPU_PA_RISC1_0
- 528) HP_ARCH=hppa1.1 ;; # CPU_PA_RISC1_1
- 532) # CPU_PA_RISC2_0
- case "$sc_kernel_bits" in
- 32) HP_ARCH=hppa2.0n ;;
- 64) HP_ARCH=hppa2.0w ;;
- '') HP_ARCH=hppa2.0 ;; # HP-UX 10.20
- esac ;;
- esac
- fi
- if test "$HP_ARCH" = ""; then
- set_cc_for_build
- sed 's/^ //' << EOF > "$dummy.c"
-
- #define _HPUX_SOURCE
- #include <stdlib.h>
- #include <unistd.h>
-
- int main ()
- {
- #if defined(_SC_KERNEL_BITS)
- long bits = sysconf(_SC_KERNEL_BITS);
- #endif
- long cpu = sysconf (_SC_CPU_VERSION);
-
- switch (cpu)
- {
- case CPU_PA_RISC1_0: puts ("hppa1.0"); break;
- case CPU_PA_RISC1_1: puts ("hppa1.1"); break;
- case CPU_PA_RISC2_0:
- #if defined(_SC_KERNEL_BITS)
- switch (bits)
- {
- case 64: puts ("hppa2.0w"); break;
- case 32: puts ("hppa2.0n"); break;
- default: puts ("hppa2.0"); break;
- } break;
- #else /* !defined(_SC_KERNEL_BITS) */
- puts ("hppa2.0"); break;
- #endif
- default: puts ("hppa1.0"); break;
- }
- exit (0);
- }
-EOF
- (CCOPTS="" $CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null) && HP_ARCH=$("$dummy")
- test -z "$HP_ARCH" && HP_ARCH=hppa
- fi ;;
- esac
- if test "$HP_ARCH" = hppa2.0w
- then
- set_cc_for_build
-
- # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating
- # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler
- # generating 64-bit code. GNU and HP use different nomenclature:
- #
- # $ CC_FOR_BUILD=cc ./config.guess
- # => hppa2.0w-hp-hpux11.23
- # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess
- # => hppa64-hp-hpux11.23
-
- if echo __LP64__ | (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) |
- grep -q __LP64__
- then
- HP_ARCH=hppa2.0w
- else
- HP_ARCH=hppa64
- fi
- fi
- echo "$HP_ARCH"-hp-hpux"$HPUX_REV"
- exit ;;
- ia64:HP-UX:*:*)
- HPUX_REV=$(echo "$UNAME_RELEASE"|sed -e 's/[^.]*.[0B]*//')
- echo ia64-hp-hpux"$HPUX_REV"
- exit ;;
- 3050*:HI-UX:*:*)
- set_cc_for_build
- sed 's/^ //' << EOF > "$dummy.c"
- #include <unistd.h>
- int
- main ()
- {
- long cpu = sysconf (_SC_CPU_VERSION);
- /* The order matters, because CPU_IS_HP_MC68K erroneously returns
- true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct
- results, however. */
- if (CPU_IS_PA_RISC (cpu))
- {
- switch (cpu)
- {
- case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break;
- case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break;
- case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break;
- default: puts ("hppa-hitachi-hiuxwe2"); break;
- }
- }
- else if (CPU_IS_HP_MC68K (cpu))
- puts ("m68k-hitachi-hiuxwe2");
- else puts ("unknown-hitachi-hiuxwe2");
- exit (0);
- }
-EOF
- $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=$("$dummy") &&
- { echo "$SYSTEM_NAME"; exit; }
- echo unknown-hitachi-hiuxwe2
- exit ;;
- 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:*)
- echo hppa1.1-hp-bsd
- exit ;;
- 9000/8??:4.3bsd:*:*)
- echo hppa1.0-hp-bsd
- exit ;;
- *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*)
- echo hppa1.0-hp-mpeix
- exit ;;
- hp7??:OSF1:*:* | hp8?[79]:OSF1:*:*)
- echo hppa1.1-hp-osf
- exit ;;
- hp8??:OSF1:*:*)
- echo hppa1.0-hp-osf
- exit ;;
- i*86:OSF1:*:*)
- if test -x /usr/sbin/sysversion ; then
- echo "$UNAME_MACHINE"-unknown-osf1mk
- else
- echo "$UNAME_MACHINE"-unknown-osf1
- fi
- exit ;;
- parisc*:Lites*:*:*)
- echo hppa1.1-hp-lites
- exit ;;
- C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*)
- echo c1-convex-bsd
- exit ;;
- C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*)
- if getsysinfo -f scalar_acc
- then echo c32-convex-bsd
- else echo c2-convex-bsd
- fi
- exit ;;
- C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*)
- echo c34-convex-bsd
- exit ;;
- C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*)
- echo c38-convex-bsd
- exit ;;
- C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*)
- echo c4-convex-bsd
- exit ;;
- CRAY*Y-MP:*:*:*)
- echo ymp-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'
- exit ;;
- CRAY*[A-Z]90:*:*:*)
- echo "$UNAME_MACHINE"-cray-unicos"$UNAME_RELEASE" \
- | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \
- -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \
- -e 's/\.[^.]*$/.X/'
- exit ;;
- CRAY*TS:*:*:*)
- echo t90-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'
- exit ;;
- CRAY*T3E:*:*:*)
- echo alphaev5-cray-unicosmk"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'
- exit ;;
- CRAY*SV1:*:*:*)
- echo sv1-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'
- exit ;;
- *:UNICOS/mp:*:*)
- echo craynv-cray-unicosmp"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'
- exit ;;
- F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*)
- FUJITSU_PROC=$(uname -m | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz)
- FUJITSU_SYS=$(uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///')
- FUJITSU_REL=$(echo "$UNAME_RELEASE" | sed -e 's/ /_/')
- echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
- exit ;;
- 5000:UNIX_System_V:4.*:*)
- FUJITSU_SYS=$(uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///')
- FUJITSU_REL=$(echo "$UNAME_RELEASE" | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/ /_/')
- echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
- exit ;;
- i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*)
- echo "$UNAME_MACHINE"-pc-bsdi"$UNAME_RELEASE"
- exit ;;
- sparc*:BSD/OS:*:*)
- echo sparc-unknown-bsdi"$UNAME_RELEASE"
- exit ;;
- *:BSD/OS:*:*)
- echo "$UNAME_MACHINE"-unknown-bsdi"$UNAME_RELEASE"
- exit ;;
- arm:FreeBSD:*:*)
- UNAME_PROCESSOR=$(uname -p)
- set_cc_for_build
- if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \
- | grep -q __ARM_PCS_VFP
- then
- echo "${UNAME_PROCESSOR}"-unknown-freebsd"$(echo ${UNAME_RELEASE}|sed -e 's/[-(].*//')"-gnueabi
- else
- echo "${UNAME_PROCESSOR}"-unknown-freebsd"$(echo ${UNAME_RELEASE}|sed -e 's/[-(].*//')"-gnueabihf
- fi
- exit ;;
- *:FreeBSD:*:*)
- UNAME_PROCESSOR=$(/usr/bin/uname -p)
- case "$UNAME_PROCESSOR" in
- amd64)
- UNAME_PROCESSOR=x86_64 ;;
- i386)
- UNAME_PROCESSOR=i586 ;;
- esac
- echo "$UNAME_PROCESSOR"-unknown-freebsd"$(echo "$UNAME_RELEASE"|sed -e 's/[-(].*//')"
- exit ;;
- i*:CYGWIN*:*)
- echo "$UNAME_MACHINE"-pc-cygwin
- exit ;;
- *:MINGW64*:*)
- echo "$UNAME_MACHINE"-pc-mingw64
- exit ;;
- *:MINGW*:*)
- echo "$UNAME_MACHINE"-pc-mingw32
- exit ;;
- *:MSYS*:*)
- echo "$UNAME_MACHINE"-pc-msys
- exit ;;
- i*:PW*:*)
- echo "$UNAME_MACHINE"-pc-pw32
- exit ;;
- *:Interix*:*)
- case "$UNAME_MACHINE" in
- x86)
- echo i586-pc-interix"$UNAME_RELEASE"
- exit ;;
- authenticamd | genuineintel | EM64T)
- echo x86_64-unknown-interix"$UNAME_RELEASE"
- exit ;;
- IA64)
- echo ia64-unknown-interix"$UNAME_RELEASE"
- exit ;;
- esac ;;
- i*:UWIN*:*)
- echo "$UNAME_MACHINE"-pc-uwin
- exit ;;
- amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*)
- echo x86_64-pc-cygwin
- exit ;;
- prep*:SunOS:5.*:*)
- echo powerpcle-unknown-solaris2"$(echo "$UNAME_RELEASE"|sed -e 's/[^.]*//')"
- exit ;;
- *:GNU:*:*)
- # the GNU system
- echo "$(echo "$UNAME_MACHINE"|sed -e 's,[-/].*$,,')-unknown-$LIBC$(echo "$UNAME_RELEASE"|sed -e 's,/.*$,,')"
- exit ;;
- *:GNU/*:*:*)
- # other systems with GNU libc and userland
- echo "$UNAME_MACHINE-unknown-$(echo "$UNAME_SYSTEM" | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]")$(echo "$UNAME_RELEASE"|sed -e 's/[-(].*//')-$LIBC"
- exit ;;
- *:Minix:*:*)
- echo "$UNAME_MACHINE"-unknown-minix
- exit ;;
- aarch64:Linux:*:*)
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
- exit ;;
- aarch64_be:Linux:*:*)
- UNAME_MACHINE=aarch64_be
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
- exit ;;
- alpha:Linux:*:*)
- case $(sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' /proc/cpuinfo 2>/dev/null) in
- EV5) UNAME_MACHINE=alphaev5 ;;
- EV56) UNAME_MACHINE=alphaev56 ;;
- PCA56) UNAME_MACHINE=alphapca56 ;;
- PCA57) UNAME_MACHINE=alphapca56 ;;
- EV6) UNAME_MACHINE=alphaev6 ;;
- EV67) UNAME_MACHINE=alphaev67 ;;
- EV68*) UNAME_MACHINE=alphaev68 ;;
- esac
- objdump --private-headers /bin/sh | grep -q ld.so.1
- if test "$?" = 0 ; then LIBC=gnulibc1 ; fi
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
- exit ;;
- arc:Linux:*:* | arceb:Linux:*:*)
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
- exit ;;
- arm*:Linux:*:*)
- set_cc_for_build
- if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \
- | grep -q __ARM_EABI__
- then
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
- else
- if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \
- | grep -q __ARM_PCS_VFP
- then
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"eabi
- else
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"eabihf
- fi
- fi
- exit ;;
- avr32*:Linux:*:*)
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
- exit ;;
- cris:Linux:*:*)
- echo "$UNAME_MACHINE"-axis-linux-"$LIBC"
- exit ;;
- crisv32:Linux:*:*)
- echo "$UNAME_MACHINE"-axis-linux-"$LIBC"
- exit ;;
- e2k:Linux:*:*)
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
- exit ;;
- frv:Linux:*:*)
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
- exit ;;
- hexagon:Linux:*:*)
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
- exit ;;
- i*86:Linux:*:*)
- echo "$UNAME_MACHINE"-pc-linux-"$LIBC"
- exit ;;
- ia64:Linux:*:*)
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
- exit ;;
- k1om:Linux:*:*)
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
- exit ;;
- loongarch32:Linux:*:* | loongarch64:Linux:*:* | loongarchx32:Linux:*:*)
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
- exit ;;
- m32r*:Linux:*:*)
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
- exit ;;
- m68*:Linux:*:*)
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
- exit ;;
- mips:Linux:*:* | mips64:Linux:*:*)
- set_cc_for_build
- IS_GLIBC=0
- test x"${LIBC}" = xgnu && IS_GLIBC=1
- sed 's/^ //' << EOF > "$dummy.c"
- #undef CPU
- #undef mips
- #undef mipsel
- #undef mips64
- #undef mips64el
- #if ${IS_GLIBC} && defined(_ABI64)
- LIBCABI=gnuabi64
- #else
- #if ${IS_GLIBC} && defined(_ABIN32)
- LIBCABI=gnuabin32
- #else
- LIBCABI=${LIBC}
- #endif
- #endif
-
- #if ${IS_GLIBC} && defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6
- CPU=mipsisa64r6
- #else
- #if ${IS_GLIBC} && !defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6
- CPU=mipsisa32r6
- #else
- #if defined(__mips64)
- CPU=mips64
- #else
- CPU=mips
- #endif
- #endif
- #endif
-
- #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL)
- MIPS_ENDIAN=el
- #else
- #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB)
- MIPS_ENDIAN=
- #else
- MIPS_ENDIAN=
- #endif
- #endif
-EOF
- eval "$($CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^CPU\|^MIPS_ENDIAN\|^LIBCABI')"
- test "x$CPU" != x && { echo "$CPU${MIPS_ENDIAN}-unknown-linux-$LIBCABI"; exit; }
- ;;
- mips64el:Linux:*:*)
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
- exit ;;
- openrisc*:Linux:*:*)
- echo or1k-unknown-linux-"$LIBC"
- exit ;;
- or32:Linux:*:* | or1k*:Linux:*:*)
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
- exit ;;
- padre:Linux:*:*)
- echo sparc-unknown-linux-"$LIBC"
- exit ;;
- parisc64:Linux:*:* | hppa64:Linux:*:*)
- echo hppa64-unknown-linux-"$LIBC"
- exit ;;
- parisc:Linux:*:* | hppa:Linux:*:*)
- # Look for CPU level
- case $(grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2) in
- PA7*) echo hppa1.1-unknown-linux-"$LIBC" ;;
- PA8*) echo hppa2.0-unknown-linux-"$LIBC" ;;
- *) echo hppa-unknown-linux-"$LIBC" ;;
- esac
- exit ;;
- ppc64:Linux:*:*)
- echo powerpc64-unknown-linux-"$LIBC"
- exit ;;
- ppc:Linux:*:*)
- echo powerpc-unknown-linux-"$LIBC"
- exit ;;
- ppc64le:Linux:*:*)
- echo powerpc64le-unknown-linux-"$LIBC"
- exit ;;
- ppcle:Linux:*:*)
- echo powerpcle-unknown-linux-"$LIBC"
- exit ;;
- riscv32:Linux:*:* | riscv32be:Linux:*:* | riscv64:Linux:*:* | riscv64be:Linux:*:*)
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
- exit ;;
- s390:Linux:*:* | s390x:Linux:*:*)
- echo "$UNAME_MACHINE"-ibm-linux-"$LIBC"
- exit ;;
- sh64*:Linux:*:*)
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
- exit ;;
- sh*:Linux:*:*)
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
- exit ;;
- sparc:Linux:*:* | sparc64:Linux:*:*)
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
- exit ;;
- tile*:Linux:*:*)
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
- exit ;;
- vax:Linux:*:*)
- echo "$UNAME_MACHINE"-dec-linux-"$LIBC"
- exit ;;
- x86_64:Linux:*:*)
- set_cc_for_build
- LIBCABI=$LIBC
- if test "$CC_FOR_BUILD" != no_compiler_found; then
- if (echo '#ifdef __ILP32__'; echo IS_X32; echo '#endif') | \
- (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
- grep IS_X32 >/dev/null
- then
- LIBCABI="$LIBC"x32
- fi
- fi
- echo "$UNAME_MACHINE"-pc-linux-"$LIBCABI"
- exit ;;
- xtensa*:Linux:*:*)
- echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
- exit ;;
- i*86:DYNIX/ptx:4*:*)
- # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there.
- # earlier versions are messed up and put the nodename in both
- # sysname and nodename.
- echo i386-sequent-sysv4
- exit ;;
- i*86:UNIX_SV:4.2MP:2.*)
- # Unixware is an offshoot of SVR4, but it has its own version
- # number series starting with 2...
- # I am not positive that other SVR4 systems won't match this,
- # I just have to hope. -- rms.
- # Use sysv4.2uw... so that sysv4* matches it.
- echo "$UNAME_MACHINE"-pc-sysv4.2uw"$UNAME_VERSION"
- exit ;;
- i*86:OS/2:*:*)
- # If we were able to find `uname', then EMX Unix compatibility
- # is probably installed.
- echo "$UNAME_MACHINE"-pc-os2-emx
- exit ;;
- i*86:XTS-300:*:STOP)
- echo "$UNAME_MACHINE"-unknown-stop
- exit ;;
- i*86:atheos:*:*)
- echo "$UNAME_MACHINE"-unknown-atheos
- exit ;;
- i*86:syllable:*:*)
- echo "$UNAME_MACHINE"-pc-syllable
- exit ;;
- i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*)
- echo i386-unknown-lynxos"$UNAME_RELEASE"
- exit ;;
- i*86:*DOS:*:*)
- echo "$UNAME_MACHINE"-pc-msdosdjgpp
- exit ;;
- i*86:*:4.*:*)
- UNAME_REL=$(echo "$UNAME_RELEASE" | sed 's/\/MP$//')
- if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then
- echo "$UNAME_MACHINE"-univel-sysv"$UNAME_REL"
- else
- echo "$UNAME_MACHINE"-pc-sysv"$UNAME_REL"
- fi
- exit ;;
- i*86:*:5:[678]*)
- # UnixWare 7.x, OpenUNIX and OpenServer 6.
- case $(/bin/uname -X | grep "^Machine") in
- *486*) UNAME_MACHINE=i486 ;;
- *Pentium) UNAME_MACHINE=i586 ;;
- *Pent*|*Celeron) UNAME_MACHINE=i686 ;;
- esac
- echo "$UNAME_MACHINE-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION}"
- exit ;;
- i*86:*:3.2:*)
- if test -f /usr/options/cb.name; then
- UNAME_REL=$(sed -n 's/.*Version //p' </usr/options/cb.name)
- echo "$UNAME_MACHINE"-pc-isc"$UNAME_REL"
- elif /bin/uname -X 2>/dev/null >/dev/null ; then
- UNAME_REL=$( (/bin/uname -X|grep Release|sed -e 's/.*= //'))
- (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486
- (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \
- && UNAME_MACHINE=i586
- (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \
- && UNAME_MACHINE=i686
- (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \
- && UNAME_MACHINE=i686
- echo "$UNAME_MACHINE"-pc-sco"$UNAME_REL"
- else
- echo "$UNAME_MACHINE"-pc-sysv32
- fi
- exit ;;
- pc:*:*:*)
- # Left here for compatibility:
- # uname -m prints for DJGPP always 'pc', but it prints nothing about
- # the processor, so we play safe by assuming i586.
- # Note: whatever this is, it MUST be the same as what config.sub
- # prints for the "djgpp" host, or else GDB configure will decide that
- # this is a cross-build.
- echo i586-pc-msdosdjgpp
- exit ;;
- Intel:Mach:3*:*)
- echo i386-pc-mach3
- exit ;;
- paragon:*:*:*)
- echo i860-intel-osf1
- exit ;;
- i860:*:4.*:*) # i860-SVR4
- if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then
- echo i860-stardent-sysv"$UNAME_RELEASE" # Stardent Vistra i860-SVR4
- else # Add other i860-SVR4 vendors below as they are discovered.
- echo i860-unknown-sysv"$UNAME_RELEASE" # Unknown i860-SVR4
- fi
- exit ;;
- mini*:CTIX:SYS*5:*)
- # "miniframe"
- echo m68010-convergent-sysv
- exit ;;
- mc68k:UNIX:SYSTEM5:3.51m)
- echo m68k-convergent-sysv
- exit ;;
- M680?0:D-NIX:5.3:*)
- echo m68k-diab-dnix
- exit ;;
- M68*:*:R3V[5678]*:*)
- test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;;
- 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0)
- OS_REL=''
- test -r /etc/.relid \
- && OS_REL=.$(sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid)
- /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
- && { echo i486-ncr-sysv4.3"$OS_REL"; exit; }
- /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
- && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;;
- 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*)
- /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
- && { echo i486-ncr-sysv4; exit; } ;;
- NCR*:*:4.2:* | MPRAS*:*:4.2:*)
- OS_REL='.3'
- test -r /etc/.relid \
- && OS_REL=.$(sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid)
- /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
- && { echo i486-ncr-sysv4.3"$OS_REL"; exit; }
- /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
- && { echo i586-ncr-sysv4.3"$OS_REL"; exit; }
- /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \
- && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;;
- m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*)
- echo m68k-unknown-lynxos"$UNAME_RELEASE"
- exit ;;
- mc68030:UNIX_System_V:4.*:*)
- echo m68k-atari-sysv4
- exit ;;
- TSUNAMI:LynxOS:2.*:*)
- echo sparc-unknown-lynxos"$UNAME_RELEASE"
- exit ;;
- rs6000:LynxOS:2.*:*)
- echo rs6000-unknown-lynxos"$UNAME_RELEASE"
- exit ;;
- PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*)
- echo powerpc-unknown-lynxos"$UNAME_RELEASE"
- exit ;;
- SM[BE]S:UNIX_SV:*:*)
- echo mips-dde-sysv"$UNAME_RELEASE"
- exit ;;
- RM*:ReliantUNIX-*:*:*)
- echo mips-sni-sysv4
- exit ;;
- RM*:SINIX-*:*:*)
- echo mips-sni-sysv4
- exit ;;
- *:SINIX-*:*:*)
- if uname -p 2>/dev/null >/dev/null ; then
- UNAME_MACHINE=$( (uname -p) 2>/dev/null)
- echo "$UNAME_MACHINE"-sni-sysv4
- else
- echo ns32k-sni-sysv
- fi
- exit ;;
- PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort
- # says <Richard.M.Bartel@ccMail.Census.GOV>
- echo i586-unisys-sysv4
- exit ;;
- *:UNIX_System_V:4*:FTX*)
- # From Gerald Hewes <hewes@openmarket.com>.
- # How about differentiating between stratus architectures? -djm
- echo hppa1.1-stratus-sysv4
- exit ;;
- *:*:*:FTX*)
- # From seanf@swdc.stratus.com.
- echo i860-stratus-sysv4
- exit ;;
- i*86:VOS:*:*)
- # From Paul.Green@stratus.com.
- echo "$UNAME_MACHINE"-stratus-vos
- exit ;;
- *:VOS:*:*)
- # From Paul.Green@stratus.com.
- echo hppa1.1-stratus-vos
- exit ;;
- mc68*:A/UX:*:*)
- echo m68k-apple-aux"$UNAME_RELEASE"
- exit ;;
- news*:NEWS-OS:6*:*)
- echo mips-sony-newsos6
- exit ;;
- R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*)
- if test -d /usr/nec; then
- echo mips-nec-sysv"$UNAME_RELEASE"
- else
- echo mips-unknown-sysv"$UNAME_RELEASE"
- fi
- exit ;;
- BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only.
- echo powerpc-be-beos
- exit ;;
- BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only.
- echo powerpc-apple-beos
- exit ;;
- BePC:BeOS:*:*) # BeOS running on Intel PC compatible.
- echo i586-pc-beos
- exit ;;
- BePC:Haiku:*:*) # Haiku running on Intel PC compatible.
- echo i586-pc-haiku
- exit ;;
- x86_64:Haiku:*:*)
- echo x86_64-unknown-haiku
- exit ;;
- SX-4:SUPER-UX:*:*)
- echo sx4-nec-superux"$UNAME_RELEASE"
- exit ;;
- SX-5:SUPER-UX:*:*)
- echo sx5-nec-superux"$UNAME_RELEASE"
- exit ;;
- SX-6:SUPER-UX:*:*)
- echo sx6-nec-superux"$UNAME_RELEASE"
- exit ;;
- SX-7:SUPER-UX:*:*)
- echo sx7-nec-superux"$UNAME_RELEASE"
- exit ;;
- SX-8:SUPER-UX:*:*)
- echo sx8-nec-superux"$UNAME_RELEASE"
- exit ;;
- SX-8R:SUPER-UX:*:*)
- echo sx8r-nec-superux"$UNAME_RELEASE"
- exit ;;
- SX-ACE:SUPER-UX:*:*)
- echo sxace-nec-superux"$UNAME_RELEASE"
- exit ;;
- Power*:Rhapsody:*:*)
- echo powerpc-apple-rhapsody"$UNAME_RELEASE"
- exit ;;
- *:Rhapsody:*:*)
- echo "$UNAME_MACHINE"-apple-rhapsody"$UNAME_RELEASE"
- exit ;;
- arm64:Darwin:*:*)
- echo aarch64-apple-darwin"$UNAME_RELEASE"
- exit ;;
- *:Darwin:*:*)
- UNAME_PROCESSOR=$(uname -p)
- case $UNAME_PROCESSOR in
- unknown) UNAME_PROCESSOR=powerpc ;;
- esac
- if command -v xcode-select > /dev/null 2> /dev/null && \
- ! xcode-select --print-path > /dev/null 2> /dev/null ; then
- # Avoid executing cc if there is no toolchain installed as
- # cc will be a stub that puts up a graphical alert
- # prompting the user to install developer tools.
- CC_FOR_BUILD=no_compiler_found
- else
- set_cc_for_build
- fi
- if test "$CC_FOR_BUILD" != no_compiler_found; then
- if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \
- (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
- grep IS_64BIT_ARCH >/dev/null
- then
- case $UNAME_PROCESSOR in
- i386) UNAME_PROCESSOR=x86_64 ;;
- powerpc) UNAME_PROCESSOR=powerpc64 ;;
- esac
- fi
- # On 10.4-10.6 one might compile for PowerPC via gcc -arch ppc
- if (echo '#ifdef __POWERPC__'; echo IS_PPC; echo '#endif') | \
- (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
- grep IS_PPC >/dev/null
- then
- UNAME_PROCESSOR=powerpc
- fi
- elif test "$UNAME_PROCESSOR" = i386 ; then
- # uname -m returns i386 or x86_64
- UNAME_PROCESSOR=$UNAME_MACHINE
- fi
- echo "$UNAME_PROCESSOR"-apple-darwin"$UNAME_RELEASE"
- exit ;;
- *:procnto*:*:* | *:QNX:[0123456789]*:*)
- UNAME_PROCESSOR=$(uname -p)
- if test "$UNAME_PROCESSOR" = x86; then
- UNAME_PROCESSOR=i386
- UNAME_MACHINE=pc
- fi
- echo "$UNAME_PROCESSOR"-"$UNAME_MACHINE"-nto-qnx"$UNAME_RELEASE"
- exit ;;
- *:QNX:*:4*)
- echo i386-pc-qnx
- exit ;;
- NEO-*:NONSTOP_KERNEL:*:*)
- echo neo-tandem-nsk"$UNAME_RELEASE"
- exit ;;
- NSE-*:NONSTOP_KERNEL:*:*)
- echo nse-tandem-nsk"$UNAME_RELEASE"
- exit ;;
- NSR-*:NONSTOP_KERNEL:*:*)
- echo nsr-tandem-nsk"$UNAME_RELEASE"
- exit ;;
- NSV-*:NONSTOP_KERNEL:*:*)
- echo nsv-tandem-nsk"$UNAME_RELEASE"
- exit ;;
- NSX-*:NONSTOP_KERNEL:*:*)
- echo nsx-tandem-nsk"$UNAME_RELEASE"
- exit ;;
- *:NonStop-UX:*:*)
- echo mips-compaq-nonstopux
- exit ;;
- BS2000:POSIX*:*:*)
- echo bs2000-siemens-sysv
- exit ;;
- DS/*:UNIX_System_V:*:*)
- echo "$UNAME_MACHINE"-"$UNAME_SYSTEM"-"$UNAME_RELEASE"
- exit ;;
- *:Plan9:*:*)
- # "uname -m" is not consistent, so use $cputype instead. 386
- # is converted to i386 for consistency with other x86
- # operating systems.
- # shellcheck disable=SC2154
- if test "$cputype" = 386; then
- UNAME_MACHINE=i386
- else
- UNAME_MACHINE="$cputype"
- fi
- echo "$UNAME_MACHINE"-unknown-plan9
- exit ;;
- *:TOPS-10:*:*)
- echo pdp10-unknown-tops10
- exit ;;
- *:TENEX:*:*)
- echo pdp10-unknown-tenex
- exit ;;
- KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*)
- echo pdp10-dec-tops20
- exit ;;
- XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*)
- echo pdp10-xkl-tops20
- exit ;;
- *:TOPS-20:*:*)
- echo pdp10-unknown-tops20
- exit ;;
- *:ITS:*:*)
- echo pdp10-unknown-its
- exit ;;
- SEI:*:*:SEIUX)
- echo mips-sei-seiux"$UNAME_RELEASE"
- exit ;;
- *:DragonFly:*:*)
- echo "$UNAME_MACHINE"-unknown-dragonfly"$(echo "$UNAME_RELEASE"|sed -e 's/[-(].*//')"
- exit ;;
- *:*VMS:*:*)
- UNAME_MACHINE=$( (uname -p) 2>/dev/null)
- case "$UNAME_MACHINE" in
- A*) echo alpha-dec-vms ; exit ;;
- I*) echo ia64-dec-vms ; exit ;;
- V*) echo vax-dec-vms ; exit ;;
- esac ;;
- *:XENIX:*:SysV)
- echo i386-pc-xenix
- exit ;;
- i*86:skyos:*:*)
- echo "$UNAME_MACHINE"-pc-skyos"$(echo "$UNAME_RELEASE" | sed -e 's/ .*$//')"
- exit ;;
- i*86:rdos:*:*)
- echo "$UNAME_MACHINE"-pc-rdos
- exit ;;
- i*86:AROS:*:*)
- echo "$UNAME_MACHINE"-pc-aros
- exit ;;
- x86_64:VMkernel:*:*)
- echo "$UNAME_MACHINE"-unknown-esx
- exit ;;
- amd64:Isilon\ OneFS:*:*)
- echo x86_64-unknown-onefs
- exit ;;
- *:Unleashed:*:*)
- echo "$UNAME_MACHINE"-unknown-unleashed"$UNAME_RELEASE"
- exit ;;
-esac
-
-# No uname command or uname output not recognized.
-set_cc_for_build
-cat > "$dummy.c" <<EOF
-#ifdef _SEQUENT_
-#include <sys/types.h>
-#include <sys/utsname.h>
-#endif
-#if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__)
-#if defined (vax) || defined (__vax) || defined (__vax__) || defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__)
-#include <signal.h>
-#if defined(_SIZE_T_) || defined(SIGLOST)
-#include <sys/utsname.h>
-#endif
-#endif
-#endif
-main ()
-{
-#if defined (sony)
-#if defined (MIPSEB)
- /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed,
- I don't know.... */
- printf ("mips-sony-bsd\n"); exit (0);
-#else
-#include <sys/param.h>
- printf ("m68k-sony-newsos%s\n",
-#ifdef NEWSOS4
- "4"
-#else
- ""
-#endif
- ); exit (0);
-#endif
-#endif
-
-#if defined (NeXT)
-#if !defined (__ARCHITECTURE__)
-#define __ARCHITECTURE__ "m68k"
-#endif
- int version;
- version=$( (hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null);
- if (version < 4)
- printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version);
- else
- printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version);
- exit (0);
-#endif
-
-#if defined (MULTIMAX) || defined (n16)
-#if defined (UMAXV)
- printf ("ns32k-encore-sysv\n"); exit (0);
-#else
-#if defined (CMU)
- printf ("ns32k-encore-mach\n"); exit (0);
-#else
- printf ("ns32k-encore-bsd\n"); exit (0);
-#endif
-#endif
-#endif
-
-#if defined (__386BSD__)
- printf ("i386-pc-bsd\n"); exit (0);
-#endif
-
-#if defined (sequent)
-#if defined (i386)
- printf ("i386-sequent-dynix\n"); exit (0);
-#endif
-#if defined (ns32000)
- printf ("ns32k-sequent-dynix\n"); exit (0);
-#endif
-#endif
-
-#if defined (_SEQUENT_)
- struct utsname un;
-
- uname(&un);
- if (strncmp(un.version, "V2", 2) == 0) {
- printf ("i386-sequent-ptx2\n"); exit (0);
- }
- if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */
- printf ("i386-sequent-ptx1\n"); exit (0);
- }
- printf ("i386-sequent-ptx\n"); exit (0);
-#endif
-
-#if defined (vax)
-#if !defined (ultrix)
-#include <sys/param.h>
-#if defined (BSD)
-#if BSD == 43
- printf ("vax-dec-bsd4.3\n"); exit (0);
-#else
-#if BSD == 199006
- printf ("vax-dec-bsd4.3reno\n"); exit (0);
-#else
- printf ("vax-dec-bsd\n"); exit (0);
-#endif
-#endif
-#else
- printf ("vax-dec-bsd\n"); exit (0);
-#endif
-#else
-#if defined(_SIZE_T_) || defined(SIGLOST)
- struct utsname un;
- uname (&un);
- printf ("vax-dec-ultrix%s\n", un.release); exit (0);
-#else
- printf ("vax-dec-ultrix\n"); exit (0);
-#endif
-#endif
-#endif
-#if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__)
-#if defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__)
-#if defined(_SIZE_T_) || defined(SIGLOST)
- struct utsname *un;
- uname (&un);
- printf ("mips-dec-ultrix%s\n", un.release); exit (0);
-#else
- printf ("mips-dec-ultrix\n"); exit (0);
-#endif
-#endif
-#endif
-
-#if defined (alliant) && defined (i860)
- printf ("i860-alliant-bsd\n"); exit (0);
-#endif
-
- exit (1);
-}
-EOF
-
-$CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null && SYSTEM_NAME=$($dummy) &&
- { echo "$SYSTEM_NAME"; exit; }
-
-# Apollos put the system type in the environment.
-test -d /usr/apollo && { echo "$ISP-apollo-$SYSTYPE"; exit; }
-
-echo "$0: unable to guess system type" >&2
-
-case "$UNAME_MACHINE:$UNAME_SYSTEM" in
- mips:Linux | mips64:Linux)
- # If we got here on MIPS GNU/Linux, output extra information.
- cat >&2 <<EOF
-
-NOTE: MIPS GNU/Linux systems require a C compiler to fully recognize
-the system type. Please install a C compiler and try again.
-EOF
- ;;
-esac
-
-cat >&2 <<EOF
-
-This script (version $timestamp), has failed to recognize the
-operating system you are using. If your script is old, overwrite *all*
-copies of config.guess and config.sub with the latest versions from:
-
- https://git.savannah.gnu.org/cgit/config.git/plain/config.guess
-and
- https://git.savannah.gnu.org/cgit/config.git/plain/config.sub
-EOF
-
-year=$(echo $timestamp | sed 's,-.*,,')
-# shellcheck disable=SC2003
-if test "$(expr "$(date +%Y)" - "$year")" -lt 3 ; then
- cat >&2 <<EOF
-
-If $0 has already been updated, send the following data and any
-information you think might be pertinent to config-patches@gnu.org to
-provide the necessary information to handle your system.
-
-config.guess timestamp = $timestamp
-
-uname -m = $( (uname -m) 2>/dev/null || echo unknown)
-uname -r = $( (uname -r) 2>/dev/null || echo unknown)
-uname -s = $( (uname -s) 2>/dev/null || echo unknown)
-uname -v = $( (uname -v) 2>/dev/null || echo unknown)
-
-/usr/bin/uname -p = $( (/usr/bin/uname -p) 2>/dev/null)
-/bin/uname -X = $( (/bin/uname -X) 2>/dev/null)
-
-hostinfo = $( (hostinfo) 2>/dev/null)
-/bin/universe = $( (/bin/universe) 2>/dev/null)
-/usr/bin/arch -k = $( (/usr/bin/arch -k) 2>/dev/null)
-/bin/arch = $( (/bin/arch) 2>/dev/null)
-/usr/bin/oslevel = $( (/usr/bin/oslevel) 2>/dev/null)
-/usr/convex/getsysinfo = $( (/usr/convex/getsysinfo) 2>/dev/null)
-
-UNAME_MACHINE = "$UNAME_MACHINE"
-UNAME_RELEASE = "$UNAME_RELEASE"
-UNAME_SYSTEM = "$UNAME_SYSTEM"
-UNAME_VERSION = "$UNAME_VERSION"
-EOF
-fi
-
-exit 1
-
-# Local variables:
-# eval: (add-hook 'before-save-hook 'time-stamp)
-# time-stamp-start: "timestamp='"
-# time-stamp-format: "%:y-%02m-%02d"
-# time-stamp-end: "'"
-# End:
+++ /dev/null
-// Copyright (c) 1987-2016 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.
-
-#include "serv_extensions.h"
-#include "citadel_dirs.h"
-
-// This is the format of the legacy config file. Do not attempt to do anything with it other
-// than migrate it into the new format. Seriously -- DO NOT CHANGE IT. The only purpose of this
-// struct is to represent the OLD configuration format.
-struct legacy_config {
- char c_nodename[16]; // short name of this node on a Citadel network
- char c_fqdn[64]; // this site's fully qualified domain name
- char c_humannode[21]; // human-readable site name
- char c_niu_7[16];
- uid_t c_niu_6;
- char c_creataide; // 1 = creating a room auto-grants room aide privileges
- int c_sleeping; // watchdog timer (seconds)
- char c_initax; // initial access level for new users
- char c_regiscall; // after c_regiscall logins user will be asked to register
- char c_twitdetect; // automatically move messages from problem users to trashcan
- char c_twitroom[ROOMNAMELEN]; // name of trashcan
- char c_moreprompt[80]; // paginator prompt
- char c_restrict; // require per-user permission to send Internet mail
- long c_niu_1;
- char c_site_location[32]; // geographic location of this Citadel site
- char c_sysadm[26]; // name of system administrator
- char c_niu_2[15];
- int c_niu_3;
- int c_maxsessions; // maximum number of concurrent sessions allowed
- char c_ip_addr[20]; // bind address for listening sockets
- int c_port_number; // port number for Citadel protocol (usually 504)
- int c_niu_4;
- struct ExpirePolicy c_ep; // default expire policy for the entire site
- int c_userpurge; // user purge time (in days)
- int c_roompurge; // room purge time (in days)
- char c_logpages[ROOMNAMELEN];
- char c_createax;
- long c_maxmsglen;
- int c_min_workers;
- int c_max_workers;
- int c_pop3_port;
- int c_smtp_port;
- int c_rfc822_strict_from;
- int c_aide_zap;
- int c_imap_port;
- time_t c_net_freq;
- char c_disable_newu;
- char c_enable_fulltext;
- char c_baseroom[ROOMNAMELEN];
- char c_aideroom[ROOMNAMELEN];
- int c_purge_hour;
- struct ExpirePolicy c_mbxep;
- char c_ldap_host[128];
- int c_ldap_port;
- char c_ldap_base_dn[256];
- char c_ldap_bind_dn[256];
- char c_ldap_bind_pw[256];
- int c_msa_port;
- int c_imaps_port;
- int c_pop3s_port;
- int c_smtps_port;
- char c_auto_cull;
- char c_niu_5;
- char c_allow_spoofing;
- char c_journal_email;
- char c_journal_pubmsgs;
- char c_journal_dest[128];
- char c_default_cal_zone[128];
- int c_pftcpdict_port;
- int c_niu_9;
- int c_auth_mode;
- char c_niu_8[256];
- int c_niu_10;
- char c_niu_11[256];
- char c_niu_12[256];
- char c_rbl_at_greeting;
- char c_master_user[32];
- char c_master_pass[32];
- char c_pager_program[256];
- char c_imap_keep_from;
- int c_xmpp_c2s_port;
- int c_xmpp_s2s_port;
- time_t c_pop3_fetch;
- time_t c_pop3_fastest;
- int c_spam_flag_only;
- int c_guest_logins;
- int c_nntp_port;
- int c_nntps_port;
-};
-
-
-void initialize_config_system(void);
-void shutdown_config_system(void);
-void put_config(void);
-void CtdlSetConfigStr(char *, char *);
-char *CtdlGetConfigStr(char *);
-int CtdlGetConfigInt(char *);
-long CtdlGetConfigLong(char *);
-void CtdlSetConfigInt(char *key, int value);
-void CtdlSetConfigLong(char *key, long value);
-void CtdlDelConfig(char *key);
-
-char *CtdlGetSysConfig(char *sysconfname);
-void CtdlPutSysConfig(char *sysconfname, char *sysconfdata);
-void validate_config(void);
-void netcfg_keyname(char *, long);
+++ /dev/null
-#! /bin/sh
-# Configuration validation subroutine script.
-# Copyright 1992-2021 Free Software Foundation, Inc.
-
-timestamp='2021-01-07'
-
-# This file is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 3 of the License, or
-# (at your option) any later version.
-#
-# 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.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, see <https://www.gnu.org/licenses/>.
-#
-# As a special exception to the GNU General Public License, if you
-# distribute this file as part of a program that contains a
-# configuration script generated by Autoconf, you may include it under
-# the same distribution terms that you use for the rest of that
-# program. This Exception is an additional permission under section 7
-# of the GNU General Public License, version 3 ("GPLv3").
-
-
-# Please send patches to <config-patches@gnu.org>.
-#
-# Configuration subroutine to validate and canonicalize a configuration type.
-# Supply the specified configuration type as an argument.
-# If it is invalid, we print an error message on stderr and exit with code 1.
-# Otherwise, we print the canonical config type on stdout and succeed.
-
-# You can get the latest version of this script from:
-# https://git.savannah.gnu.org/cgit/config.git/plain/config.sub
-
-# This file is supposed to be the same for all GNU packages
-# and recognize all the CPU types, system types and aliases
-# that are meaningful with *any* GNU software.
-# Each package is responsible for reporting which valid configurations
-# it does not support. The user should be able to distinguish
-# a failure to support a valid configuration from a meaningless
-# configuration.
-
-# The goal of this file is to map all the various variations of a given
-# machine specification into a single specification in the form:
-# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM
-# or in some cases, the newer four-part form:
-# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM
-# It is wrong to echo any other type of specification.
-
-me=$(echo "$0" | sed -e 's,.*/,,')
-
-usage="\
-Usage: $0 [OPTION] CPU-MFR-OPSYS or ALIAS
-
-Canonicalize a configuration name.
-
-Options:
- -h, --help print this help, then exit
- -t, --time-stamp print date of last modification, then exit
- -v, --version print version number, then exit
-
-Report bugs and patches to <config-patches@gnu.org>."
-
-version="\
-GNU config.sub ($timestamp)
-
-Copyright 1992-2021 Free Software Foundation, Inc.
-
-This is free software; see the source for copying conditions. There is NO
-warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
-
-help="
-Try \`$me --help' for more information."
-
-# Parse command line
-while test $# -gt 0 ; do
- case $1 in
- --time-stamp | --time* | -t )
- echo "$timestamp" ; exit ;;
- --version | -v )
- echo "$version" ; exit ;;
- --help | --h* | -h )
- echo "$usage"; exit ;;
- -- ) # Stop option processing
- shift; break ;;
- - ) # Use stdin as input.
- break ;;
- -* )
- echo "$me: invalid option $1$help" >&2
- exit 1 ;;
-
- *local*)
- # First pass through any local machine types.
- echo "$1"
- exit ;;
-
- * )
- break ;;
- esac
-done
-
-case $# in
- 0) echo "$me: missing argument$help" >&2
- exit 1;;
- 1) ;;
- *) echo "$me: too many arguments$help" >&2
- exit 1;;
-esac
-
-# Split fields of configuration type
-# shellcheck disable=SC2162
-IFS="-" read field1 field2 field3 field4 <<EOF
-$1
-EOF
-
-# Separate into logical components for further validation
-case $1 in
- *-*-*-*-*)
- echo Invalid configuration \`"$1"\': more than four components >&2
- exit 1
- ;;
- *-*-*-*)
- basic_machine=$field1-$field2
- basic_os=$field3-$field4
- ;;
- *-*-*)
- # Ambiguous whether COMPANY is present, or skipped and KERNEL-OS is two
- # parts
- maybe_os=$field2-$field3
- case $maybe_os in
- nto-qnx* | linux-* | uclinux-uclibc* \
- | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \
- | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \
- | storm-chaos* | os2-emx* | rtmk-nova*)
- basic_machine=$field1
- basic_os=$maybe_os
- ;;
- android-linux)
- basic_machine=$field1-unknown
- basic_os=linux-android
- ;;
- *)
- basic_machine=$field1-$field2
- basic_os=$field3
- ;;
- esac
- ;;
- *-*)
- # A lone config we happen to match not fitting any pattern
- case $field1-$field2 in
- decstation-3100)
- basic_machine=mips-dec
- basic_os=
- ;;
- *-*)
- # Second component is usually, but not always the OS
- case $field2 in
- # Prevent following clause from handling this valid os
- sun*os*)
- basic_machine=$field1
- basic_os=$field2
- ;;
- # Manufacturers
- dec* | mips* | sequent* | encore* | pc533* | sgi* | sony* \
- | att* | 7300* | 3300* | delta* | motorola* | sun[234]* \
- | unicom* | ibm* | next | hp | isi* | apollo | altos* \
- | convergent* | ncr* | news | 32* | 3600* | 3100* \
- | hitachi* | c[123]* | convex* | sun | crds | omron* | dg \
- | ultra | tti* | harris | dolphin | highlevel | gould \
- | cbm | ns | masscomp | apple | axis | knuth | cray \
- | microblaze* | sim | cisco \
- | oki | wec | wrs | winbond)
- basic_machine=$field1-$field2
- basic_os=
- ;;
- *)
- basic_machine=$field1
- basic_os=$field2
- ;;
- esac
- ;;
- esac
- ;;
- *)
- # Convert single-component short-hands not valid as part of
- # multi-component configurations.
- case $field1 in
- 386bsd)
- basic_machine=i386-pc
- basic_os=bsd
- ;;
- a29khif)
- basic_machine=a29k-amd
- basic_os=udi
- ;;
- adobe68k)
- basic_machine=m68010-adobe
- basic_os=scout
- ;;
- alliant)
- basic_machine=fx80-alliant
- basic_os=
- ;;
- altos | altos3068)
- basic_machine=m68k-altos
- basic_os=
- ;;
- am29k)
- basic_machine=a29k-none
- basic_os=bsd
- ;;
- amdahl)
- basic_machine=580-amdahl
- basic_os=sysv
- ;;
- amiga)
- basic_machine=m68k-unknown
- basic_os=
- ;;
- amigaos | amigados)
- basic_machine=m68k-unknown
- basic_os=amigaos
- ;;
- amigaunix | amix)
- basic_machine=m68k-unknown
- basic_os=sysv4
- ;;
- apollo68)
- basic_machine=m68k-apollo
- basic_os=sysv
- ;;
- apollo68bsd)
- basic_machine=m68k-apollo
- basic_os=bsd
- ;;
- aros)
- basic_machine=i386-pc
- basic_os=aros
- ;;
- aux)
- basic_machine=m68k-apple
- basic_os=aux
- ;;
- balance)
- basic_machine=ns32k-sequent
- basic_os=dynix
- ;;
- blackfin)
- basic_machine=bfin-unknown
- basic_os=linux
- ;;
- cegcc)
- basic_machine=arm-unknown
- basic_os=cegcc
- ;;
- convex-c1)
- basic_machine=c1-convex
- basic_os=bsd
- ;;
- convex-c2)
- basic_machine=c2-convex
- basic_os=bsd
- ;;
- convex-c32)
- basic_machine=c32-convex
- basic_os=bsd
- ;;
- convex-c34)
- basic_machine=c34-convex
- basic_os=bsd
- ;;
- convex-c38)
- basic_machine=c38-convex
- basic_os=bsd
- ;;
- cray)
- basic_machine=j90-cray
- basic_os=unicos
- ;;
- crds | unos)
- basic_machine=m68k-crds
- basic_os=
- ;;
- da30)
- basic_machine=m68k-da30
- basic_os=
- ;;
- decstation | pmax | pmin | dec3100 | decstatn)
- basic_machine=mips-dec
- basic_os=
- ;;
- delta88)
- basic_machine=m88k-motorola
- basic_os=sysv3
- ;;
- dicos)
- basic_machine=i686-pc
- basic_os=dicos
- ;;
- djgpp)
- basic_machine=i586-pc
- basic_os=msdosdjgpp
- ;;
- ebmon29k)
- basic_machine=a29k-amd
- basic_os=ebmon
- ;;
- es1800 | OSE68k | ose68k | ose | OSE)
- basic_machine=m68k-ericsson
- basic_os=ose
- ;;
- gmicro)
- basic_machine=tron-gmicro
- basic_os=sysv
- ;;
- go32)
- basic_machine=i386-pc
- basic_os=go32
- ;;
- h8300hms)
- basic_machine=h8300-hitachi
- basic_os=hms
- ;;
- h8300xray)
- basic_machine=h8300-hitachi
- basic_os=xray
- ;;
- h8500hms)
- basic_machine=h8500-hitachi
- basic_os=hms
- ;;
- harris)
- basic_machine=m88k-harris
- basic_os=sysv3
- ;;
- hp300 | hp300hpux)
- basic_machine=m68k-hp
- basic_os=hpux
- ;;
- hp300bsd)
- basic_machine=m68k-hp
- basic_os=bsd
- ;;
- hppaosf)
- basic_machine=hppa1.1-hp
- basic_os=osf
- ;;
- hppro)
- basic_machine=hppa1.1-hp
- basic_os=proelf
- ;;
- i386mach)
- basic_machine=i386-mach
- basic_os=mach
- ;;
- isi68 | isi)
- basic_machine=m68k-isi
- basic_os=sysv
- ;;
- m68knommu)
- basic_machine=m68k-unknown
- basic_os=linux
- ;;
- magnum | m3230)
- basic_machine=mips-mips
- basic_os=sysv
- ;;
- merlin)
- basic_machine=ns32k-utek
- basic_os=sysv
- ;;
- mingw64)
- basic_machine=x86_64-pc
- basic_os=mingw64
- ;;
- mingw32)
- basic_machine=i686-pc
- basic_os=mingw32
- ;;
- mingw32ce)
- basic_machine=arm-unknown
- basic_os=mingw32ce
- ;;
- monitor)
- basic_machine=m68k-rom68k
- basic_os=coff
- ;;
- morphos)
- basic_machine=powerpc-unknown
- basic_os=morphos
- ;;
- moxiebox)
- basic_machine=moxie-unknown
- basic_os=moxiebox
- ;;
- msdos)
- basic_machine=i386-pc
- basic_os=msdos
- ;;
- msys)
- basic_machine=i686-pc
- basic_os=msys
- ;;
- mvs)
- basic_machine=i370-ibm
- basic_os=mvs
- ;;
- nacl)
- basic_machine=le32-unknown
- basic_os=nacl
- ;;
- ncr3000)
- basic_machine=i486-ncr
- basic_os=sysv4
- ;;
- netbsd386)
- basic_machine=i386-pc
- basic_os=netbsd
- ;;
- netwinder)
- basic_machine=armv4l-rebel
- basic_os=linux
- ;;
- news | news700 | news800 | news900)
- basic_machine=m68k-sony
- basic_os=newsos
- ;;
- news1000)
- basic_machine=m68030-sony
- basic_os=newsos
- ;;
- necv70)
- basic_machine=v70-nec
- basic_os=sysv
- ;;
- nh3000)
- basic_machine=m68k-harris
- basic_os=cxux
- ;;
- nh[45]000)
- basic_machine=m88k-harris
- basic_os=cxux
- ;;
- nindy960)
- basic_machine=i960-intel
- basic_os=nindy
- ;;
- mon960)
- basic_machine=i960-intel
- basic_os=mon960
- ;;
- nonstopux)
- basic_machine=mips-compaq
- basic_os=nonstopux
- ;;
- os400)
- basic_machine=powerpc-ibm
- basic_os=os400
- ;;
- OSE68000 | ose68000)
- basic_machine=m68000-ericsson
- basic_os=ose
- ;;
- os68k)
- basic_machine=m68k-none
- basic_os=os68k
- ;;
- paragon)
- basic_machine=i860-intel
- basic_os=osf
- ;;
- parisc)
- basic_machine=hppa-unknown
- basic_os=linux
- ;;
- psp)
- basic_machine=mipsallegrexel-sony
- basic_os=psp
- ;;
- pw32)
- basic_machine=i586-unknown
- basic_os=pw32
- ;;
- rdos | rdos64)
- basic_machine=x86_64-pc
- basic_os=rdos
- ;;
- rdos32)
- basic_machine=i386-pc
- basic_os=rdos
- ;;
- rom68k)
- basic_machine=m68k-rom68k
- basic_os=coff
- ;;
- sa29200)
- basic_machine=a29k-amd
- basic_os=udi
- ;;
- sei)
- basic_machine=mips-sei
- basic_os=seiux
- ;;
- sequent)
- basic_machine=i386-sequent
- basic_os=
- ;;
- sps7)
- basic_machine=m68k-bull
- basic_os=sysv2
- ;;
- st2000)
- basic_machine=m68k-tandem
- basic_os=
- ;;
- stratus)
- basic_machine=i860-stratus
- basic_os=sysv4
- ;;
- sun2)
- basic_machine=m68000-sun
- basic_os=
- ;;
- sun2os3)
- basic_machine=m68000-sun
- basic_os=sunos3
- ;;
- sun2os4)
- basic_machine=m68000-sun
- basic_os=sunos4
- ;;
- sun3)
- basic_machine=m68k-sun
- basic_os=
- ;;
- sun3os3)
- basic_machine=m68k-sun
- basic_os=sunos3
- ;;
- sun3os4)
- basic_machine=m68k-sun
- basic_os=sunos4
- ;;
- sun4)
- basic_machine=sparc-sun
- basic_os=
- ;;
- sun4os3)
- basic_machine=sparc-sun
- basic_os=sunos3
- ;;
- sun4os4)
- basic_machine=sparc-sun
- basic_os=sunos4
- ;;
- sun4sol2)
- basic_machine=sparc-sun
- basic_os=solaris2
- ;;
- sun386 | sun386i | roadrunner)
- basic_machine=i386-sun
- basic_os=
- ;;
- sv1)
- basic_machine=sv1-cray
- basic_os=unicos
- ;;
- symmetry)
- basic_machine=i386-sequent
- basic_os=dynix
- ;;
- t3e)
- basic_machine=alphaev5-cray
- basic_os=unicos
- ;;
- t90)
- basic_machine=t90-cray
- basic_os=unicos
- ;;
- toad1)
- basic_machine=pdp10-xkl
- basic_os=tops20
- ;;
- tpf)
- basic_machine=s390x-ibm
- basic_os=tpf
- ;;
- udi29k)
- basic_machine=a29k-amd
- basic_os=udi
- ;;
- ultra3)
- basic_machine=a29k-nyu
- basic_os=sym1
- ;;
- v810 | necv810)
- basic_machine=v810-nec
- basic_os=none
- ;;
- vaxv)
- basic_machine=vax-dec
- basic_os=sysv
- ;;
- vms)
- basic_machine=vax-dec
- basic_os=vms
- ;;
- vsta)
- basic_machine=i386-pc
- basic_os=vsta
- ;;
- vxworks960)
- basic_machine=i960-wrs
- basic_os=vxworks
- ;;
- vxworks68)
- basic_machine=m68k-wrs
- basic_os=vxworks
- ;;
- vxworks29k)
- basic_machine=a29k-wrs
- basic_os=vxworks
- ;;
- xbox)
- basic_machine=i686-pc
- basic_os=mingw32
- ;;
- ymp)
- basic_machine=ymp-cray
- basic_os=unicos
- ;;
- *)
- basic_machine=$1
- basic_os=
- ;;
- esac
- ;;
-esac
-
-# Decode 1-component or ad-hoc basic machines
-case $basic_machine in
- # Here we handle the default manufacturer of certain CPU types. It is in
- # some cases the only manufacturer, in others, it is the most popular.
- w89k)
- cpu=hppa1.1
- vendor=winbond
- ;;
- op50n)
- cpu=hppa1.1
- vendor=oki
- ;;
- op60c)
- cpu=hppa1.1
- vendor=oki
- ;;
- ibm*)
- cpu=i370
- vendor=ibm
- ;;
- orion105)
- cpu=clipper
- vendor=highlevel
- ;;
- mac | mpw | mac-mpw)
- cpu=m68k
- vendor=apple
- ;;
- pmac | pmac-mpw)
- cpu=powerpc
- vendor=apple
- ;;
-
- # Recognize the various machine names and aliases which stand
- # for a CPU type and a company and sometimes even an OS.
- 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc)
- cpu=m68000
- vendor=att
- ;;
- 3b*)
- cpu=we32k
- vendor=att
- ;;
- bluegene*)
- cpu=powerpc
- vendor=ibm
- basic_os=cnk
- ;;
- decsystem10* | dec10*)
- cpu=pdp10
- vendor=dec
- basic_os=tops10
- ;;
- decsystem20* | dec20*)
- cpu=pdp10
- vendor=dec
- basic_os=tops20
- ;;
- delta | 3300 | motorola-3300 | motorola-delta \
- | 3300-motorola | delta-motorola)
- cpu=m68k
- vendor=motorola
- ;;
- dpx2*)
- cpu=m68k
- vendor=bull
- basic_os=sysv3
- ;;
- encore | umax | mmax)
- cpu=ns32k
- vendor=encore
- ;;
- elxsi)
- cpu=elxsi
- vendor=elxsi
- basic_os=${basic_os:-bsd}
- ;;
- fx2800)
- cpu=i860
- vendor=alliant
- ;;
- genix)
- cpu=ns32k
- vendor=ns
- ;;
- h3050r* | hiux*)
- cpu=hppa1.1
- vendor=hitachi
- basic_os=hiuxwe2
- ;;
- hp3k9[0-9][0-9] | hp9[0-9][0-9])
- cpu=hppa1.0
- vendor=hp
- ;;
- hp9k2[0-9][0-9] | hp9k31[0-9])
- cpu=m68000
- vendor=hp
- ;;
- hp9k3[2-9][0-9])
- cpu=m68k
- vendor=hp
- ;;
- hp9k6[0-9][0-9] | hp6[0-9][0-9])
- cpu=hppa1.0
- vendor=hp
- ;;
- hp9k7[0-79][0-9] | hp7[0-79][0-9])
- cpu=hppa1.1
- vendor=hp
- ;;
- hp9k78[0-9] | hp78[0-9])
- # FIXME: really hppa2.0-hp
- cpu=hppa1.1
- vendor=hp
- ;;
- hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893)
- # FIXME: really hppa2.0-hp
- cpu=hppa1.1
- vendor=hp
- ;;
- hp9k8[0-9][13679] | hp8[0-9][13679])
- cpu=hppa1.1
- vendor=hp
- ;;
- hp9k8[0-9][0-9] | hp8[0-9][0-9])
- cpu=hppa1.0
- vendor=hp
- ;;
- i*86v32)
- cpu=$(echo "$1" | sed -e 's/86.*/86/')
- vendor=pc
- basic_os=sysv32
- ;;
- i*86v4*)
- cpu=$(echo "$1" | sed -e 's/86.*/86/')
- vendor=pc
- basic_os=sysv4
- ;;
- i*86v)
- cpu=$(echo "$1" | sed -e 's/86.*/86/')
- vendor=pc
- basic_os=sysv
- ;;
- i*86sol2)
- cpu=$(echo "$1" | sed -e 's/86.*/86/')
- vendor=pc
- basic_os=solaris2
- ;;
- j90 | j90-cray)
- cpu=j90
- vendor=cray
- basic_os=${basic_os:-unicos}
- ;;
- iris | iris4d)
- cpu=mips
- vendor=sgi
- case $basic_os in
- irix*)
- ;;
- *)
- basic_os=irix4
- ;;
- esac
- ;;
- miniframe)
- cpu=m68000
- vendor=convergent
- ;;
- *mint | mint[0-9]* | *MiNT | *MiNT[0-9]*)
- cpu=m68k
- vendor=atari
- basic_os=mint
- ;;
- news-3600 | risc-news)
- cpu=mips
- vendor=sony
- basic_os=newsos
- ;;
- next | m*-next)
- cpu=m68k
- vendor=next
- case $basic_os in
- openstep*)
- ;;
- nextstep*)
- ;;
- ns2*)
- basic_os=nextstep2
- ;;
- *)
- basic_os=nextstep3
- ;;
- esac
- ;;
- np1)
- cpu=np1
- vendor=gould
- ;;
- op50n-* | op60c-*)
- cpu=hppa1.1
- vendor=oki
- basic_os=proelf
- ;;
- pa-hitachi)
- cpu=hppa1.1
- vendor=hitachi
- basic_os=hiuxwe2
- ;;
- pbd)
- cpu=sparc
- vendor=tti
- ;;
- pbb)
- cpu=m68k
- vendor=tti
- ;;
- pc532)
- cpu=ns32k
- vendor=pc532
- ;;
- pn)
- cpu=pn
- vendor=gould
- ;;
- power)
- cpu=power
- vendor=ibm
- ;;
- ps2)
- cpu=i386
- vendor=ibm
- ;;
- rm[46]00)
- cpu=mips
- vendor=siemens
- ;;
- rtpc | rtpc-*)
- cpu=romp
- vendor=ibm
- ;;
- sde)
- cpu=mipsisa32
- vendor=sde
- basic_os=${basic_os:-elf}
- ;;
- simso-wrs)
- cpu=sparclite
- vendor=wrs
- basic_os=vxworks
- ;;
- tower | tower-32)
- cpu=m68k
- vendor=ncr
- ;;
- vpp*|vx|vx-*)
- cpu=f301
- vendor=fujitsu
- ;;
- w65)
- cpu=w65
- vendor=wdc
- ;;
- w89k-*)
- cpu=hppa1.1
- vendor=winbond
- basic_os=proelf
- ;;
- none)
- cpu=none
- vendor=none
- ;;
- leon|leon[3-9])
- cpu=sparc
- vendor=$basic_machine
- ;;
- leon-*|leon[3-9]-*)
- cpu=sparc
- vendor=$(echo "$basic_machine" | sed 's/-.*//')
- ;;
-
- *-*)
- # shellcheck disable=SC2162
- IFS="-" read cpu vendor <<EOF
-$basic_machine
-EOF
- ;;
- # We use `pc' rather than `unknown'
- # because (1) that's what they normally are, and
- # (2) the word "unknown" tends to confuse beginning users.
- i*86 | x86_64)
- cpu=$basic_machine
- vendor=pc
- ;;
- # These rules are duplicated from below for sake of the special case above;
- # i.e. things that normalized to x86 arches should also default to "pc"
- pc98)
- cpu=i386
- vendor=pc
- ;;
- x64 | amd64)
- cpu=x86_64
- vendor=pc
- ;;
- # Recognize the basic CPU types without company name.
- *)
- cpu=$basic_machine
- vendor=unknown
- ;;
-esac
-
-unset -v basic_machine
-
-# Decode basic machines in the full and proper CPU-Company form.
-case $cpu-$vendor in
- # Here we handle the default manufacturer of certain CPU types in canonical form. It is in
- # some cases the only manufacturer, in others, it is the most popular.
- craynv-unknown)
- vendor=cray
- basic_os=${basic_os:-unicosmp}
- ;;
- c90-unknown | c90-cray)
- vendor=cray
- basic_os=${Basic_os:-unicos}
- ;;
- fx80-unknown)
- vendor=alliant
- ;;
- romp-unknown)
- vendor=ibm
- ;;
- mmix-unknown)
- vendor=knuth
- ;;
- microblaze-unknown | microblazeel-unknown)
- vendor=xilinx
- ;;
- rs6000-unknown)
- vendor=ibm
- ;;
- vax-unknown)
- vendor=dec
- ;;
- pdp11-unknown)
- vendor=dec
- ;;
- we32k-unknown)
- vendor=att
- ;;
- cydra-unknown)
- vendor=cydrome
- ;;
- i370-ibm*)
- vendor=ibm
- ;;
- orion-unknown)
- vendor=highlevel
- ;;
- xps-unknown | xps100-unknown)
- cpu=xps100
- vendor=honeywell
- ;;
-
- # Here we normalize CPU types with a missing or matching vendor
- dpx20-unknown | dpx20-bull)
- cpu=rs6000
- vendor=bull
- basic_os=${basic_os:-bosx}
- ;;
-
- # Here we normalize CPU types irrespective of the vendor
- amd64-*)
- cpu=x86_64
- ;;
- blackfin-*)
- cpu=bfin
- basic_os=linux
- ;;
- c54x-*)
- cpu=tic54x
- ;;
- c55x-*)
- cpu=tic55x
- ;;
- c6x-*)
- cpu=tic6x
- ;;
- e500v[12]-*)
- cpu=powerpc
- basic_os=${basic_os}"spe"
- ;;
- mips3*-*)
- cpu=mips64
- ;;
- ms1-*)
- cpu=mt
- ;;
- m68knommu-*)
- cpu=m68k
- basic_os=linux
- ;;
- m9s12z-* | m68hcs12z-* | hcs12z-* | s12z-*)
- cpu=s12z
- ;;
- openrisc-*)
- cpu=or32
- ;;
- parisc-*)
- cpu=hppa
- basic_os=linux
- ;;
- pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*)
- cpu=i586
- ;;
- pentiumpro-* | p6-* | 6x86-* | athlon-* | athalon_*-*)
- cpu=i686
- ;;
- pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*)
- cpu=i686
- ;;
- pentium4-*)
- cpu=i786
- ;;
- pc98-*)
- cpu=i386
- ;;
- ppc-* | ppcbe-*)
- cpu=powerpc
- ;;
- ppcle-* | powerpclittle-*)
- cpu=powerpcle
- ;;
- ppc64-*)
- cpu=powerpc64
- ;;
- ppc64le-* | powerpc64little-*)
- cpu=powerpc64le
- ;;
- sb1-*)
- cpu=mipsisa64sb1
- ;;
- sb1el-*)
- cpu=mipsisa64sb1el
- ;;
- sh5e[lb]-*)
- cpu=$(echo "$cpu" | sed 's/^\(sh.\)e\(.\)$/\1\2e/')
- ;;
- spur-*)
- cpu=spur
- ;;
- strongarm-* | thumb-*)
- cpu=arm
- ;;
- tx39-*)
- cpu=mipstx39
- ;;
- tx39el-*)
- cpu=mipstx39el
- ;;
- x64-*)
- cpu=x86_64
- ;;
- xscale-* | xscalee[bl]-*)
- cpu=$(echo "$cpu" | sed 's/^xscale/arm/')
- ;;
- arm64-*)
- cpu=aarch64
- ;;
-
- # Recognize the canonical CPU Types that limit and/or modify the
- # company names they are paired with.
- cr16-*)
- basic_os=${basic_os:-elf}
- ;;
- crisv32-* | etraxfs*-*)
- cpu=crisv32
- vendor=axis
- ;;
- cris-* | etrax*-*)
- cpu=cris
- vendor=axis
- ;;
- crx-*)
- basic_os=${basic_os:-elf}
- ;;
- neo-tandem)
- cpu=neo
- vendor=tandem
- ;;
- nse-tandem)
- cpu=nse
- vendor=tandem
- ;;
- nsr-tandem)
- cpu=nsr
- vendor=tandem
- ;;
- nsv-tandem)
- cpu=nsv
- vendor=tandem
- ;;
- nsx-tandem)
- cpu=nsx
- vendor=tandem
- ;;
- mipsallegrexel-sony)
- cpu=mipsallegrexel
- vendor=sony
- ;;
- tile*-*)
- basic_os=${basic_os:-linux-gnu}
- ;;
-
- *)
- # Recognize the canonical CPU types that are allowed with any
- # company name.
- case $cpu in
- 1750a | 580 \
- | a29k \
- | aarch64 | aarch64_be \
- | abacus \
- | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] \
- | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] \
- | alphapca5[67] | alpha64pca5[67] \
- | am33_2.0 \
- | amdgcn \
- | arc | arceb \
- | arm | arm[lb]e | arme[lb] | armv* \
- | avr | avr32 \
- | asmjs \
- | ba \
- | be32 | be64 \
- | bfin | bpf | bs2000 \
- | c[123]* | c30 | [cjt]90 | c4x \
- | c8051 | clipper | craynv | csky | cydra \
- | d10v | d30v | dlx | dsp16xx \
- | e2k | elxsi | epiphany \
- | f30[01] | f700 | fido | fr30 | frv | ft32 | fx80 \
- | h8300 | h8500 \
- | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \
- | hexagon \
- | i370 | i*86 | i860 | i960 | ia16 | ia64 \
- | ip2k | iq2000 \
- | k1om \
- | le32 | le64 \
- | lm32 \
- | loongarch32 | loongarch64 | loongarchx32 \
- | m32c | m32r | m32rle \
- | m5200 | m68000 | m680[012346]0 | m68360 | m683?2 | m68k \
- | m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x \
- | m88110 | m88k | maxq | mb | mcore | mep | metag \
- | microblaze | microblazeel \
- | mips | mipsbe | mipseb | mipsel | mipsle \
- | mips16 \
- | mips64 | mips64eb | mips64el \
- | mips64octeon | mips64octeonel \
- | mips64orion | mips64orionel \
- | mips64r5900 | mips64r5900el \
- | mips64vr | mips64vrel \
- | mips64vr4100 | mips64vr4100el \
- | mips64vr4300 | mips64vr4300el \
- | mips64vr5000 | mips64vr5000el \
- | mips64vr5900 | mips64vr5900el \
- | mipsisa32 | mipsisa32el \
- | mipsisa32r2 | mipsisa32r2el \
- | mipsisa32r6 | mipsisa32r6el \
- | mipsisa64 | mipsisa64el \
- | mipsisa64r2 | mipsisa64r2el \
- | mipsisa64r6 | mipsisa64r6el \
- | mipsisa64sb1 | mipsisa64sb1el \
- | mipsisa64sr71k | mipsisa64sr71kel \
- | mipsr5900 | mipsr5900el \
- | mipstx39 | mipstx39el \
- | mmix \
- | mn10200 | mn10300 \
- | moxie \
- | mt \
- | msp430 \
- | nds32 | nds32le | nds32be \
- | nfp \
- | nios | nios2 | nios2eb | nios2el \
- | none | np1 | ns16k | ns32k | nvptx \
- | open8 \
- | or1k* \
- | or32 \
- | orion \
- | picochip \
- | pdp10 | pdp11 | pj | pjl | pn | power \
- | powerpc | powerpc64 | powerpc64le | powerpcle | powerpcspe \
- | pru \
- | pyramid \
- | riscv | riscv32 | riscv32be | riscv64 | riscv64be \
- | rl78 | romp | rs6000 | rx \
- | s390 | s390x \
- | score \
- | sh | shl \
- | sh[1234] | sh[24]a | sh[24]ae[lb] | sh[23]e | she[lb] | sh[lb]e \
- | sh[1234]e[lb] | sh[12345][lb]e | sh[23]ele | sh64 | sh64le \
- | sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet \
- | sparclite \
- | sparcv8 | sparcv9 | sparcv9b | sparcv9v | sv1 | sx* \
- | spu \
- | tahoe \
- | thumbv7* \
- | tic30 | tic4x | tic54x | tic55x | tic6x | tic80 \
- | tron \
- | ubicom32 \
- | v70 | v850 | v850e | v850e1 | v850es | v850e2 | v850e2v3 \
- | vax \
- | visium \
- | w65 \
- | wasm32 | wasm64 \
- | we32k \
- | x86 | x86_64 | xc16x | xgate | xps100 \
- | xstormy16 | xtensa* \
- | ymp \
- | z8k | z80)
- ;;
-
- *)
- echo Invalid configuration \`"$1"\': machine \`"$cpu-$vendor"\' not recognized 1>&2
- exit 1
- ;;
- esac
- ;;
-esac
-
-# Here we canonicalize certain aliases for manufacturers.
-case $vendor in
- digital*)
- vendor=dec
- ;;
- commodore*)
- vendor=cbm
- ;;
- *)
- ;;
-esac
-
-# Decode manufacturer-specific aliases for certain operating systems.
-
-if test x$basic_os != x
-then
-
-# First recognize some ad-hoc caes, or perhaps split kernel-os, or else just
-# set os.
-case $basic_os in
- gnu/linux*)
- kernel=linux
- os=$(echo $basic_os | sed -e 's|gnu/linux|gnu|')
- ;;
- os2-emx)
- kernel=os2
- os=$(echo $basic_os | sed -e 's|os2-emx|emx|')
- ;;
- nto-qnx*)
- kernel=nto
- os=$(echo $basic_os | sed -e 's|nto-qnx|qnx|')
- ;;
- *-*)
- # shellcheck disable=SC2162
- IFS="-" read kernel os <<EOF
-$basic_os
-EOF
- ;;
- # Default OS when just kernel was specified
- nto*)
- kernel=nto
- os=$(echo $basic_os | sed -e 's|nto|qnx|')
- ;;
- linux*)
- kernel=linux
- os=$(echo $basic_os | sed -e 's|linux|gnu|')
- ;;
- *)
- kernel=
- os=$basic_os
- ;;
-esac
-
-# Now, normalize the OS (knowing we just have one component, it's not a kernel,
-# etc.)
-case $os in
- # First match some system type aliases that might get confused
- # with valid system types.
- # solaris* is a basic system type, with this one exception.
- auroraux)
- os=auroraux
- ;;
- bluegene*)
- os=cnk
- ;;
- solaris1 | solaris1.*)
- os=$(echo $os | sed -e 's|solaris1|sunos4|')
- ;;
- solaris)
- os=solaris2
- ;;
- unixware*)
- os=sysv4.2uw
- ;;
- # es1800 is here to avoid being matched by es* (a different OS)
- es1800*)
- os=ose
- ;;
- # Some version numbers need modification
- chorusos*)
- os=chorusos
- ;;
- isc)
- os=isc2.2
- ;;
- sco6)
- os=sco5v6
- ;;
- sco5)
- os=sco3.2v5
- ;;
- sco4)
- os=sco3.2v4
- ;;
- sco3.2.[4-9]*)
- os=$(echo $os | sed -e 's/sco3.2./sco3.2v/')
- ;;
- sco*v* | scout)
- # Don't match below
- ;;
- sco*)
- os=sco3.2v2
- ;;
- psos*)
- os=psos
- ;;
- qnx*)
- os=qnx
- ;;
- hiux*)
- os=hiuxwe2
- ;;
- lynx*178)
- os=lynxos178
- ;;
- lynx*5)
- os=lynxos5
- ;;
- lynxos*)
- # don't get caught up in next wildcard
- ;;
- lynx*)
- os=lynxos
- ;;
- mac[0-9]*)
- os=$(echo "$os" | sed -e 's|mac|macos|')
- ;;
- opened*)
- os=openedition
- ;;
- os400*)
- os=os400
- ;;
- sunos5*)
- os=$(echo "$os" | sed -e 's|sunos5|solaris2|')
- ;;
- sunos6*)
- os=$(echo "$os" | sed -e 's|sunos6|solaris3|')
- ;;
- wince*)
- os=wince
- ;;
- utek*)
- os=bsd
- ;;
- dynix*)
- os=bsd
- ;;
- acis*)
- os=aos
- ;;
- atheos*)
- os=atheos
- ;;
- syllable*)
- os=syllable
- ;;
- 386bsd)
- os=bsd
- ;;
- ctix* | uts*)
- os=sysv
- ;;
- nova*)
- os=rtmk-nova
- ;;
- ns2)
- os=nextstep2
- ;;
- # Preserve the version number of sinix5.
- sinix5.*)
- os=$(echo $os | sed -e 's|sinix|sysv|')
- ;;
- sinix*)
- os=sysv4
- ;;
- tpf*)
- os=tpf
- ;;
- triton*)
- os=sysv3
- ;;
- oss*)
- os=sysv3
- ;;
- svr4*)
- os=sysv4
- ;;
- svr3)
- os=sysv3
- ;;
- sysvr4)
- os=sysv4
- ;;
- ose*)
- os=ose
- ;;
- *mint | mint[0-9]* | *MiNT | MiNT[0-9]*)
- os=mint
- ;;
- dicos*)
- os=dicos
- ;;
- pikeos*)
- # Until real need of OS specific support for
- # particular features comes up, bare metal
- # configurations are quite functional.
- case $cpu in
- arm*)
- os=eabi
- ;;
- *)
- os=elf
- ;;
- esac
- ;;
- *)
- # No normalization, but not necessarily accepted, that comes below.
- ;;
-esac
-
-else
-
-# Here we handle the default operating systems that come with various machines.
-# The value should be what the vendor currently ships out the door with their
-# machine or put another way, the most popular os provided with the machine.
-
-# Note that if you're going to try to match "-MANUFACTURER" here (say,
-# "-sun"), then you have to tell the case statement up towards the top
-# that MANUFACTURER isn't an operating system. Otherwise, code above
-# will signal an error saying that MANUFACTURER isn't an operating
-# system, and we'll never get to this point.
-
-kernel=
-case $cpu-$vendor in
- score-*)
- os=elf
- ;;
- spu-*)
- os=elf
- ;;
- *-acorn)
- os=riscix1.2
- ;;
- arm*-rebel)
- kernel=linux
- os=gnu
- ;;
- arm*-semi)
- os=aout
- ;;
- c4x-* | tic4x-*)
- os=coff
- ;;
- c8051-*)
- os=elf
- ;;
- clipper-intergraph)
- os=clix
- ;;
- hexagon-*)
- os=elf
- ;;
- tic54x-*)
- os=coff
- ;;
- tic55x-*)
- os=coff
- ;;
- tic6x-*)
- os=coff
- ;;
- # This must come before the *-dec entry.
- pdp10-*)
- os=tops20
- ;;
- pdp11-*)
- os=none
- ;;
- *-dec | vax-*)
- os=ultrix4.2
- ;;
- m68*-apollo)
- os=domain
- ;;
- i386-sun)
- os=sunos4.0.2
- ;;
- m68000-sun)
- os=sunos3
- ;;
- m68*-cisco)
- os=aout
- ;;
- mep-*)
- os=elf
- ;;
- mips*-cisco)
- os=elf
- ;;
- mips*-*)
- os=elf
- ;;
- or32-*)
- os=coff
- ;;
- *-tti) # must be before sparc entry or we get the wrong os.
- os=sysv3
- ;;
- sparc-* | *-sun)
- os=sunos4.1.1
- ;;
- pru-*)
- os=elf
- ;;
- *-be)
- os=beos
- ;;
- *-ibm)
- os=aix
- ;;
- *-knuth)
- os=mmixware
- ;;
- *-wec)
- os=proelf
- ;;
- *-winbond)
- os=proelf
- ;;
- *-oki)
- os=proelf
- ;;
- *-hp)
- os=hpux
- ;;
- *-hitachi)
- os=hiux
- ;;
- i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent)
- os=sysv
- ;;
- *-cbm)
- os=amigaos
- ;;
- *-dg)
- os=dgux
- ;;
- *-dolphin)
- os=sysv3
- ;;
- m68k-ccur)
- os=rtu
- ;;
- m88k-omron*)
- os=luna
- ;;
- *-next)
- os=nextstep
- ;;
- *-sequent)
- os=ptx
- ;;
- *-crds)
- os=unos
- ;;
- *-ns)
- os=genix
- ;;
- i370-*)
- os=mvs
- ;;
- *-gould)
- os=sysv
- ;;
- *-highlevel)
- os=bsd
- ;;
- *-encore)
- os=bsd
- ;;
- *-sgi)
- os=irix
- ;;
- *-siemens)
- os=sysv4
- ;;
- *-masscomp)
- os=rtu
- ;;
- f30[01]-fujitsu | f700-fujitsu)
- os=uxpv
- ;;
- *-rom68k)
- os=coff
- ;;
- *-*bug)
- os=coff
- ;;
- *-apple)
- os=macos
- ;;
- *-atari*)
- os=mint
- ;;
- *-wrs)
- os=vxworks
- ;;
- *)
- os=none
- ;;
-esac
-
-fi
-
-# Now, validate our (potentially fixed-up) OS.
-case $os in
- # Sometimes we do "kernel-abi", so those need to count as OSes.
- musl* | newlib* | uclibc*)
- ;;
- # Likewise for "kernel-libc"
- eabi* | gnueabi*)
- ;;
- # Now accept the basic system types.
- # The portable systems comes first.
- # Each alternative MUST end in a * to match a version number.
- gnu* | android* | bsd* | mach* | minix* | genix* | ultrix* | irix* \
- | *vms* | esix* | aix* | cnk* | sunos | sunos[34]* \
- | hpux* | unos* | osf* | luna* | dgux* | auroraux* | solaris* \
- | sym* | plan9* | psp* | sim* | xray* | os68k* | v88r* \
- | hiux* | abug | nacl* | netware* | windows* \
- | os9* | macos* | osx* | ios* \
- | mpw* | magic* | mmixware* | mon960* | lnews* \
- | amigaos* | amigados* | msdos* | newsos* | unicos* | aof* \
- | aos* | aros* | cloudabi* | sortix* | twizzler* \
- | nindy* | vxsim* | vxworks* | ebmon* | hms* | mvs* \
- | clix* | riscos* | uniplus* | iris* | isc* | rtu* | xenix* \
- | mirbsd* | netbsd* | dicos* | openedition* | ose* \
- | bitrig* | openbsd* | solidbsd* | libertybsd* | os108* \
- | ekkobsd* | freebsd* | riscix* | lynxos* | os400* \
- | bosx* | nextstep* | cxux* | aout* | elf* | oabi* \
- | ptx* | coff* | ecoff* | winnt* | domain* | vsta* \
- | udi* | lites* | ieee* | go32* | aux* | hcos* \
- | chorusrdb* | cegcc* | glidix* \
- | cygwin* | msys* | pe* | moss* | proelf* | rtems* \
- | midipix* | mingw32* | mingw64* | mint* \
- | uxpv* | beos* | mpeix* | udk* | moxiebox* \
- | interix* | uwin* | mks* | rhapsody* | darwin* \
- | openstep* | oskit* | conix* | pw32* | nonstopux* \
- | storm-chaos* | tops10* | tenex* | tops20* | its* \
- | os2* | vos* | palmos* | uclinux* | nucleus* | morphos* \
- | scout* | superux* | sysv* | rtmk* | tpf* | windiss* \
- | powermax* | dnix* | nx6 | nx7 | sei* | dragonfly* \
- | skyos* | haiku* | rdos* | toppers* | drops* | es* \
- | onefs* | tirtos* | phoenix* | fuchsia* | redox* | bme* \
- | midnightbsd* | amdhsa* | unleashed* | emscripten* | wasi* \
- | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx*)
- ;;
- # This one is extra strict with allowed versions
- sco3.2v2 | sco3.2v[4-9]* | sco5v6*)
- # Don't forget version if it is 3.2v4 or newer.
- ;;
- none)
- ;;
- *)
- echo Invalid configuration \`"$1"\': OS \`"$os"\' not recognized 1>&2
- exit 1
- ;;
-esac
-
-# As a final step for OS-related things, validate the OS-kernel combination
-# (given a valid OS), if there is a kernel.
-case $kernel-$os in
- linux-gnu* | linux-dietlibc* | linux-android* | linux-newlib* | linux-musl* | linux-uclibc* )
- ;;
- uclinux-uclibc* )
- ;;
- -dietlibc* | -newlib* | -musl* | -uclibc* )
- # These are just libc implementations, not actual OSes, and thus
- # require a kernel.
- echo "Invalid configuration \`$1': libc \`$os' needs explicit kernel." 1>&2
- exit 1
- ;;
- kfreebsd*-gnu* | kopensolaris*-gnu*)
- ;;
- nto-qnx*)
- ;;
- os2-emx)
- ;;
- *-eabi* | *-gnueabi*)
- ;;
- -*)
- # Blank kernel with real OS is always fine.
- ;;
- *-*)
- echo "Invalid configuration \`$1': Kernel \`$kernel' not known to work with OS \`$os'." 1>&2
- exit 1
- ;;
-esac
-
-# Here we handle the case where we know the os, and the CPU type, but not the
-# manufacturer. We pick the logical manufacturer.
-case $vendor in
- unknown)
- case $cpu-$os in
- *-riscix*)
- vendor=acorn
- ;;
- *-sunos*)
- vendor=sun
- ;;
- *-cnk* | *-aix*)
- vendor=ibm
- ;;
- *-beos*)
- vendor=be
- ;;
- *-hpux*)
- vendor=hp
- ;;
- *-mpeix*)
- vendor=hp
- ;;
- *-hiux*)
- vendor=hitachi
- ;;
- *-unos*)
- vendor=crds
- ;;
- *-dgux*)
- vendor=dg
- ;;
- *-luna*)
- vendor=omron
- ;;
- *-genix*)
- vendor=ns
- ;;
- *-clix*)
- vendor=intergraph
- ;;
- *-mvs* | *-opened*)
- vendor=ibm
- ;;
- *-os400*)
- vendor=ibm
- ;;
- s390-* | s390x-*)
- vendor=ibm
- ;;
- *-ptx*)
- vendor=sequent
- ;;
- *-tpf*)
- vendor=ibm
- ;;
- *-vxsim* | *-vxworks* | *-windiss*)
- vendor=wrs
- ;;
- *-aux*)
- vendor=apple
- ;;
- *-hms*)
- vendor=hitachi
- ;;
- *-mpw* | *-macos*)
- vendor=apple
- ;;
- *-*mint | *-mint[0-9]* | *-*MiNT | *-MiNT[0-9]*)
- vendor=atari
- ;;
- *-vos*)
- vendor=stratus
- ;;
- esac
- ;;
-esac
-
-echo "$cpu-$vendor-${kernel:+$kernel-}$os"
-exit
-
-# Local variables:
-# eval: (add-hook 'before-save-hook 'time-stamp)
-# time-stamp-start: "timestamp='"
-# time-stamp-format: "%:y-%02m-%02d"
-# time-stamp-end: "'"
-# End:
--- /dev/null
+#!/bin/sh
+
+# CONFIGURE SCRIPT FOR CITADEL SERVER
+# This file is part of "conf-IG-ure"
+# Copyright (C) 2016-2022 by Art Cancro
+#
+# This program is open source software. Use, duplication, and/or
+# disclosure are subject to the GNU General Purpose 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.
+
+echo
+echo
+echo Running the configure script to create config.mk
+echo
+
+# Parse the command line arguments
+for x in $*
+do
+ a=$1
+ k=`echo $a | awk -F= ' { print $1 } '`
+ v=`echo $a | awk -F= ' { print $2 } '`
+
+ case $k in
+ --prefix)
+ PREFIX=$v
+ ;;
+ --bindir)
+ BINDIR=$v
+ ;;
+ --ctdldir)
+ CTDLDIR=$v
+ ;;
+ --with-ssl)
+ SSL=yes
+ ;;
+ --without-ssl)
+ SSL=no
+ ;;
+ *)
+ echo $0 : unknown option $k
+ echo
+ echo Valid options are:
+ echo ' --prefix=PREFIX Install files in PREFIX [/usr/local]'
+ echo ' --bindir=DIR Install executables in DIR [PREFIX/bin]'
+ echo ' --ctdldir=DIR Look for Citadel server in DIR [/usr/local/citadel]'
+ echo ' --with-ssl Force build with OpenSSL support [normally autodetected]'
+ echo ' --without-ssl Force build without OpenSSL support [normally autodetected]'
+ exit 1
+ ;;
+ esac
+ shift
+done
+
+# Set any missing values (FIXME remove the ultra-fatal -W options when finished converting from autotools)
+
+[ "$PREFIX" = "" ] && PREFIX=/usr/local/citadel
+[ "$BINDIR" = "" ] && BINDIR=${PREFIX}
+[ "$CTDLDIR" = "" ] && CTDLDIR=${PREFIX}
+[ "$CFLAGS" = "" ] && CFLAGS='-ggdb -Werror -Wfatal-errors -Wno-discarded-qualifiers'
+[ "$LDFLAGS" = "" ] && LDFLAGS=''
+
+# Test for OpenSSL
+[ "$SSL" != "yes" ] && [ "$SSL" != "no" ] && {
+ echo Testing for OpenSSL...
+ tempfile=`tempfile 2>/dev/null` || tempfile=/tmp/configure.$$
+ tempcc=${tempfile}.c
+ cat >$tempcc <<!
+#include <openssl/ssl.h>
+int main(int argc, char **argv) {
+ SSL_load_error_strings();
+ exit(0);
+}
+!
+ SSL='no';
+ cc $tempcc -lssl -lcrypto -o $tempfile && $tempfile && SSL='yes'
+ rm -f $tempfile 2>/dev/null
+ rm -f $tempcc 2>/dev/null
+}
+echo SSL: $SSL
+[ "$SSL" = "yes" ] && {
+ CFLAGS=${CFLAGS}' -DHAVE_OPENSSL'
+ LDFLAGS=${LDFLAGS}' -lssl -lcrypto -lz'
+}
+
+# FIXME do a real build id here
+CFLAGS=${CFLAGS}' -DBUILD_ID=\"unknown\"'
+
+# Output the config.mk
+
+(
+ echo "CFLAGS := ${CFLAGS}"
+ echo "LDFLAGS := ${LDFLAGS}"
+ echo "PREFIX := ${PREFIX}"
+ echo "BINDIR := ${BINDIR}"
+ echo "CTDLDIR := ${CTDLDIR}"
+) >config.mk
+
+cat config.mk
+echo
+++ /dev/null
-//
-// Citadel context management stuff.
-// Here's where we (hopefully) have all the code that manipulates contexts.
-//
-// Copyright (c) 1987-2020 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.
-
-#include "ctdl_module.h"
-#include "serv_extensions.h"
-#include "citserver.h"
-#include "user_ops.h"
-#include "locate_host.h"
-#include "context.h"
-#include "control.h"
-#include "config.h"
-
-pthread_key_t MyConKey; /* TSD key for MyContext() */
-CitContext masterCC;
-CitContext *ContextList = NULL;
-time_t last_purge = 0; /* Last dead session purge */
-int num_sessions = 0; /* Current number of sessions */
-int next_pid = 0;
-
-/* Flag for single user mode */
-static int want_single_user = 0;
-
-/* Try to go single user */
-
-int CtdlTrySingleUser(void) {
- int can_do = 0;
-
- begin_critical_section(S_SINGLE_USER);
- if (want_single_user)
- can_do = 0;
- else
- {
- can_do = 1;
- want_single_user = 1;
- }
- end_critical_section(S_SINGLE_USER);
- return can_do;
-}
-
-
-void CtdlEndSingleUser(void) {
- begin_critical_section(S_SINGLE_USER);
- want_single_user = 0;
- end_critical_section(S_SINGLE_USER);
-}
-
-
-int CtdlWantSingleUser(void) {
- return want_single_user;
-}
-
-
-int CtdlIsSingleUser(void) {
- if (want_single_user) {
- /* check for only one context here */
- if (num_sessions == 1)
- return 1;
- }
- return 0;
-}
-
-
-/*
- * Locate a context by its session number and terminate it if the user is able.
- * User can NOT terminate their current session.
- * User CAN terminate any other session that has them logged in.
- * Aide CAN terminate any session except the current one.
- */
-int CtdlTerminateOtherSession (int session_num) {
- int ret = 0;
- CitContext *ccptr;
- int aide;
-
- if (session_num == CC->cs_pid) return TERM_NOTALLOWED;
-
- aide = ( (CC->user.axlevel >= AxAideU) || (CC->internal_pgm) ) ;
-
- syslog(LOG_DEBUG, "context: locating session to kill");
- begin_critical_section(S_SESSION_TABLE);
- for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
- if (session_num == ccptr->cs_pid) {
- ret |= TERM_FOUND;
- if ((ccptr->user.usernum == CC->user.usernum) || aide) {
- ret |= TERM_ALLOWED;
- }
- break;
- }
- }
-
- if (((ret & TERM_FOUND) != 0) && ((ret & TERM_ALLOWED) != 0))
- {
- if (ccptr->user.usernum == CC->user.usernum)
- ccptr->kill_me = KILLME_ADMIN_TERMINATE;
- else
- ccptr->kill_me = KILLME_IDLE;
- end_critical_section(S_SESSION_TABLE);
- }
- else
- end_critical_section(S_SESSION_TABLE);
-
- return ret;
-}
-
-
-/*
- * Check to see if the user who we just sent mail to is logged in. If yes,
- * bump the 'new mail' counter for their session. That enables them to
- * receive a new mail notification without having to hit the database.
- */
-void BumpNewMailCounter(long which_user) {
- CtdlBumpNewMailCounter(which_user);
-}
-
-
-void CtdlBumpNewMailCounter(long which_user) {
- CitContext *ptr;
-
- begin_critical_section(S_SESSION_TABLE);
-
- for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
- if (ptr->user.usernum == which_user) {
- ptr->newmail += 1;
- }
- }
-
- end_critical_section(S_SESSION_TABLE);
-}
-
-
-/*
- * Check to see if a user is currently logged in
- * Take care with what you do as a result of this test.
- * The user may not have been logged in when this function was called BUT
- * because of threading the user might be logged in before you test the result.
- */
-int CtdlIsUserLoggedIn(char *user_name) {
- CitContext *cptr;
- int ret = 0;
-
- begin_critical_section (S_SESSION_TABLE);
- for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
- if (!strcasecmp(cptr->user.fullname, user_name)) {
- ret = 1;
- break;
- }
- }
- end_critical_section(S_SESSION_TABLE);
- return ret;
-}
-
-
-/*
- * Check to see if a user is currently logged in.
- * Basically same as CtdlIsUserLoggedIn() but uses the user number instead.
- * Take care with what you do as a result of this test.
- * The user may not have been logged in when this function was called BUT
- * because of threading the user might be logged in before you test the result.
- */
-int CtdlIsUserLoggedInByNum (long usernum) {
- CitContext *cptr;
- int ret = 0;
-
- begin_critical_section(S_SESSION_TABLE);
- for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
- if (cptr->user.usernum == usernum) {
- ret = 1;
- }
- }
- end_critical_section(S_SESSION_TABLE);
- return ret;
-}
-
-
-/*
- * Return a pointer to the CitContext structure bound to the thread which
- * called this function. If there's no such binding (for example, if it's
- * called by the housekeeper thread) then a generic 'master' CC is returned.
- *
- * This function is used *VERY* frequently and must be kept small.
- */
-CitContext *MyContext(void) {
- register CitContext *c;
- return ((c = (CitContext *) pthread_getspecific(MyConKey), c == NULL) ? &masterCC : c);
-}
-
-
-/*
- * Terminate idle sessions. This function pounds through the session table
- * comparing the current time to each session's time-of-last-command. If an
- * idle session is found it is terminated, then the search restarts at the
- * beginning because the pointer to our place in the list becomes invalid.
- */
-void terminate_idle_sessions(void) {
- CitContext *ccptr;
- time_t now;
- int killed = 0;
- int longrunners = 0;
-
- now = time(NULL);
- begin_critical_section(S_SESSION_TABLE);
- for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
- if (
- (ccptr != CC)
- && (CtdlGetConfigLong("c_sleeping") > 0)
- && (now - (ccptr->lastcmd) > CtdlGetConfigLong("c_sleeping"))
- ) {
- if (!ccptr->dont_term) {
- ccptr->kill_me = KILLME_IDLE;
- ++killed;
- }
- else {
- ++longrunners;
- }
- }
- }
- end_critical_section(S_SESSION_TABLE);
- if (killed > 0) {
- syslog(LOG_INFO, "context: scheduled %d idle sessions for termination", killed);
- }
- if (longrunners > 0) {
- syslog(LOG_INFO, "context: did not terminate %d protected idle sessions", longrunners);
- }
-}
-
-
-/*
- * During shutdown, close the sockets of any sessions still connected.
- */
-void terminate_all_sessions(void) {
- CitContext *ccptr;
- int killed = 0;
-
- begin_critical_section(S_SESSION_TABLE);
- for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
- if (ccptr->client_socket != -1)
- {
- syslog(LOG_INFO, "context: terminate_all_sessions() is murdering %s CC[%d]", ccptr->curr_user, ccptr->cs_pid);
- close(ccptr->client_socket);
- ccptr->client_socket = -1;
- killed++;
- }
- }
- end_critical_section(S_SESSION_TABLE);
- if (killed > 0) {
- syslog(LOG_INFO, "context: flushed %d stuck sessions", killed);
- }
-}
-
-
-/*
- * Terminate a session.
- */
-void RemoveContext (CitContext *con) {
- const char *c;
- if (con == NULL) {
- syslog(LOG_ERR, "context: RemoveContext() called with NULL, this should not happen");
- return;
- }
- c = con->ServiceName;
- if (c == NULL) {
- c = "WTF?";
- }
- syslog(LOG_DEBUG, "context: RemoveContext(%s) session %d", c, con->cs_pid);
-
- /* Run any cleanup routines registered by loadable modules.
- * Note: We have to "become_session()" because the cleanup functions
- * might make references to "CC" assuming it's the right one.
- */
- become_session(con);
- CtdlUserLogout();
- PerformSessionHooks(EVT_STOP);
- client_close(); /* If the client is still connected, blow 'em away. */
- become_session(NULL);
-
- syslog(LOG_INFO, "context: [%3d]SRV[%s] Session ended.", con->cs_pid, c);
-
- /*
- * If the client is still connected, blow 'em away.
- * if the socket is 0 or -1, its already gone or was never there.
- */
- if (con->client_socket > 0) {
- syslog(LOG_INFO, "context: closing socket %d", con->client_socket);
- close(con->client_socket);
- }
-
- /* If using AUTHMODE_LDAP, free the DN */
- if (con->ldap_dn) {
- free(con->ldap_dn);
- con->ldap_dn = NULL;
- }
- FreeStrBuf(&con->StatusMessage);
- FreeStrBuf(&con->MigrateBuf);
- FreeStrBuf(&con->RecvBuf.Buf);
- if (con->cached_msglist) {
- free(con->cached_msglist);
- }
-
- syslog(LOG_DEBUG, "context: done with RemoveContext()");
-}
-
-
-/*
- * Initialize a new context and place it in the list. The session number
- * used to be the PID (which is why it's called cs_pid), but that was when we
- * had one process per session. Now we just assign them sequentially, starting
- * at 1 (don't change it to 0 because masterCC uses 0).
- */
-CitContext *CreateNewContext(void) {
- CitContext *me;
-
- me = (CitContext *) malloc(sizeof(CitContext));
- if (me == NULL) {
- syslog(LOG_ERR, "citserver: malloc() failed: %m");
- return NULL;
- }
- memset(me, 0, sizeof(CitContext));
-
- /* Give the context a name. Hopefully makes it easier to track */
- strcpy (me->user.fullname, "SYS_notauth");
-
- /* The new context will be created already in the CON_EXECUTING state
- * in order to prevent another thread from grabbing it while it's
- * being set up.
- */
- me->state = CON_EXECUTING;
- /*
- * Generate a unique session number and insert this context into
- * the list.
- */
- me->MigrateBuf = NewStrBuf();
-
- me->RecvBuf.Buf = NewStrBuf();
-
- me->lastcmd = time(NULL); /* set lastcmd to now to prevent idle timer infanticide TODO: if we have a valid IO, use that to set the timer. */
-
-
- begin_critical_section(S_SESSION_TABLE);
- me->cs_pid = ++next_pid;
- me->prev = NULL;
- me->next = ContextList;
- ContextList = me;
- if (me->next != NULL) {
- me->next->prev = me;
- }
- ++num_sessions;
- end_critical_section(S_SESSION_TABLE);
- return (me);
-}
-
-
-/*
- * Initialize a new context and place it in the list. The session number
- * used to be the PID (which is why it's called cs_pid), but that was when we
- * had one process per session. Now we just assign them sequentially, starting
- * at 1 (don't change it to 0 because masterCC uses 0).
- */
-CitContext *CloneContext(CitContext *CloneMe) {
- CitContext *me;
-
- me = (CitContext *) malloc(sizeof(CitContext));
- if (me == NULL) {
- syslog(LOG_ERR, "citserver: malloc() failed: %m");
- return NULL;
- }
- memcpy(me, CloneMe, sizeof(CitContext));
-
- memset(&me->RecvBuf, 0, sizeof(IOBuffer));
- memset(&me->SendBuf, 0, sizeof(IOBuffer));
- memset(&me->SBuf, 0, sizeof(IOBuffer));
- me->MigrateBuf = NULL;
- me->sMigrateBuf = NULL;
- me->redirect_buffer = NULL;
-#ifdef HAVE_OPENSSL
- me->ssl = NULL;
-#endif
-
- me->download_fp = NULL;
- me->upload_fp = NULL;
- /// TODO: what about the room/user?
- me->ma = NULL;
- me->openid_data = NULL;
- me->ldap_dn = NULL;
- me->session_specific_data = NULL;
-
- me->CIT_ICAL = NULL;
-
- me->cached_msglist = NULL;
- me->download_fp = NULL;
- me->upload_fp = NULL;
- me->client_socket = 0;
-
- me->MigrateBuf = NewStrBuf();
- me->RecvBuf.Buf = NewStrBuf();
-
- begin_critical_section(S_SESSION_TABLE);
- {
- me->cs_pid = ++next_pid;
- me->prev = NULL;
- me->next = ContextList;
- me->lastcmd = time(NULL); /* set lastcmd to now to prevent idle timer infanticide */
- ContextList = me;
- if (me->next != NULL) {
- me->next->prev = me;
- }
- ++num_sessions;
- }
- end_critical_section(S_SESSION_TABLE);
- return (me);
-}
-
-
-/*
- * Return an array containing a copy of the context list.
- * This allows worker threads to perform "for each context" operations without
- * having to lock and traverse the live list.
- */
-CitContext *CtdlGetContextArray(int *count) {
- int nContexts, i;
- CitContext *nptr, *cptr;
-
- nContexts = num_sessions;
- nptr = malloc(sizeof(CitContext) * nContexts);
- if (!nptr) {
- *count = 0;
- return NULL;
- }
- begin_critical_section(S_SESSION_TABLE);
- for (cptr = ContextList, i=0; cptr != NULL && i < nContexts; cptr = cptr->next, i++) {
- memcpy(&nptr[i], cptr, sizeof (CitContext));
- }
- end_critical_section (S_SESSION_TABLE);
-
- *count = i;
- return nptr;
-}
-
-
-/*
- * Back-end function for starting a session
- */
-void begin_session(CitContext *con) {
- /*
- * Initialize some variables specific to our context.
- */
- con->logged_in = 0;
- con->internal_pgm = 0;
- con->download_fp = NULL;
- con->upload_fp = NULL;
- con->cached_msglist = NULL;
- con->cached_num_msgs = 0;
- con->FirstExpressMessage = NULL;
- time(&con->lastcmd);
- time(&con->lastidle);
- strcpy(con->lastcmdname, " ");
- strcpy(con->cs_clientname, "(unknown)");
- strcpy(con->curr_user, NLI);
- *con->cs_clientinfo = '\0';
- safestrncpy(con->cs_host, CtdlGetConfigStr("c_fqdn"), sizeof con->cs_host);
- safestrncpy(con->cs_addr, "", sizeof con->cs_addr);
- con->cs_UDSclientUID = -1;
- con->cs_host[sizeof con->cs_host - 1] = 0;
- if (!CC->is_local_client) {
- locate_host(con->cs_host, sizeof con->cs_host,
- con->cs_addr, sizeof con->cs_addr,
- con->client_socket
- );
- }
- else {
- con->cs_host[0] = 0;
- con->cs_addr[0] = 0;
-#ifdef HAVE_STRUCT_UCRED
- {
- /* as http://www.wsinnovations.com/softeng/articles/uds.html told us... */
- struct ucred credentials;
- socklen_t ucred_length = sizeof(struct ucred);
-
- /*fill in the user data structure */
- if(getsockopt(con->client_socket, SOL_SOCKET, SO_PEERCRED, &credentials, &ucred_length)) {
- syslog(LOG_ERR, "context: could obtain credentials from unix domain socket");
-
- }
- else {
- /* the process ID of the process on the other side of the socket */
- /* credentials.pid; */
-
- /* the effective UID of the process on the other side of the socket */
- con->cs_UDSclientUID = credentials.uid;
-
- /* the effective primary GID of the process on the other side of the socket */
- /* credentials.gid; */
-
- /* To get supplemental groups, we will have to look them up in our account
- database, after a reverse lookup on the UID to get the account name.
- We can take this opportunity to check to see if this is a legit account.
- */
- snprintf(con->cs_clientinfo, sizeof(con->cs_clientinfo),
- "PID: "F_PID_T"; UID: "F_UID_T"; GID: "F_XPID_T" ",
- credentials.pid,
- credentials.uid,
- credentials.gid);
- }
- }
-#endif
- }
- con->cs_flags = 0;
-
- con->nologin = 0;
- if (((CtdlGetConfigInt("c_maxsessions") > 0)&&(num_sessions > CtdlGetConfigInt("c_maxsessions"))) || CtdlWantSingleUser()) {
- con->nologin = 1;
- }
-
- syslog(LOG_INFO, "context: session (%s) started from %s (%s) uid=%d",
- con->ServiceName, con->cs_host, con->cs_addr, con->cs_UDSclientUID
- );
-
- /* Run any session startup routines registered by loadable modules */
- PerformSessionHooks(EVT_START);
-}
-
-
-/*
- * This function fills in a context and its user field correctly
- * Then creates/loads that user
- */
-void CtdlFillSystemContext(CitContext *context, char *name) {
- char sysname[SIZ];
- long len;
-
- memset(context, 0, sizeof(CitContext));
- context->internal_pgm = 1;
- context->cs_pid = 0;
- strcpy (sysname, "SYS_");
- strcat (sysname, name);
- len = strlen(sysname);
- memcpy(context->curr_user, sysname, len + 1);
- context->client_socket = (-1);
- context->state = CON_SYS;
- context->ServiceName = name;
-
- /* internal_create_user has the side effect of loading the user regardless of whether they
- * already existed or needed to be created
- */
- internal_create_user(sysname, &(context->user), -1) ;
-
- /* Check to see if the system user needs upgrading */
- if (context->user.usernum == 0) { /* old system user with number 0, upgrade it */
- context->user.usernum = get_new_user_number();
- syslog(LOG_INFO, "context: upgrading system user \"%s\" from user number 0 to user number %ld", context->user.fullname, context->user.usernum);
- /* add user to the database */
- CtdlPutUser(&(context->user));
- cdb_store(CDB_USERSBYNUMBER, &(context->user.usernum), sizeof(long), context->user.fullname, strlen(context->user.fullname)+1);
- }
-}
-
-
-/*
- * Cleanup any contexts that are left lying around
- */
-void context_cleanup(void) {
- CitContext *ptr = NULL;
- CitContext *rem = NULL;
-
- /*
- * Clean up the contexts.
- * There are no threads so no critical_section stuff is needed.
- */
- ptr = ContextList;
-
- /* We need to update the ContextList because some modules may want to itterate it
- * Question is should we NULL it before iterating here or should we just keep updating it
- * as we remove items?
- *
- * Answer is to NULL it first to prevent modules from doing any actions on the list at all
- */
- ContextList=NULL;
- while (ptr != NULL){
- /* Remove the session from the active list */
- rem = ptr->next;
- --num_sessions;
-
- syslog(LOG_DEBUG, "context: context_cleanup() purging session %d", ptr->cs_pid);
- RemoveContext(ptr);
- free (ptr);
- ptr = rem;
- }
-}
-
-
-/*
- * Purge all sessions which have the 'kill_me' flag set.
- * This function has code to prevent it from running more than once every
- * few seconds, because running it after every single unbind would waste a lot
- * of CPU time and keep the context list locked too much. To force it to run
- * anyway, set "force" to nonzero.
- */
-void dead_session_purge(int force) {
- CitContext *ptr, *ptr2; /* general-purpose utility pointer */
- CitContext *rem = NULL; /* list of sessions to be destroyed */
-
- if (force == 0) {
- if ( (time(NULL) - last_purge) < 5 ) {
- return; /* Too soon, go away */
- }
- }
- time(&last_purge);
-
- if (try_critical_section(S_SESSION_TABLE))
- return;
-
- ptr = ContextList;
- while (ptr) {
- ptr2 = ptr;
- ptr = ptr->next;
-
- if ( (ptr2->state == CON_IDLE) && (ptr2->kill_me) ) {
- /* Remove the session from the active list */
- if (ptr2->prev) {
- ptr2->prev->next = ptr2->next;
- }
- else {
- ContextList = ptr2->next;
- }
- if (ptr2->next) {
- ptr2->next->prev = ptr2->prev;
- }
-
- --num_sessions;
- /* And put it on our to-be-destroyed list */
- ptr2->next = rem;
- rem = ptr2;
- }
- }
- end_critical_section(S_SESSION_TABLE);
-
- /* Now that we no longer have the session list locked, we can take
- * our time and destroy any sessions on the to-be-killed list, which
- * is allocated privately on this thread's stack.
- */
- while (rem != NULL) {
- syslog(LOG_DEBUG, "context: dead_session_purge() purging session %d, reason=%d", rem->cs_pid, rem->kill_me);
- RemoveContext(rem);
- ptr = rem;
- rem = rem->next;
- free(ptr);
- }
-}
-
-
-/*
- * masterCC is the context we use when not attached to a session. This
- * function initializes it.
- */
-void InitializeMasterCC(void) {
- memset(&masterCC, 0, sizeof(struct CitContext));
- masterCC.internal_pgm = 1;
- masterCC.cs_pid = 0;
-}
-
-
-/*
- * Set the "async waiting" flag for a session, if applicable
- */
-void set_async_waiting(struct CitContext *ccptr) {
- syslog(LOG_DEBUG, "context: setting async_waiting flag for session %d", ccptr->cs_pid);
- if (ccptr->is_async) {
- ccptr->async_waiting++;
- if (ccptr->state == CON_IDLE) {
- ccptr->state = CON_READY;
- }
- }
-}
-
-
-CTDL_MODULE_INIT(session)
-{
- if (!threading) {
- }
- return "session";
-}
+++ /dev/null
-
-#ifndef CONTEXT_H
-#define CONTEXT_H
-
-#include <stdarg.h>
-#include "sysdep.h"
-#include "server.h"
-#include "sysdep_decls.h"
-#include "threads.h"
-
-
-/*
- * Values for CitContext.state
- *
- * A session that is doing nothing is in CON_IDLE state. When activity
- * is detected on the socket, it goes to CON_READY, indicating that it
- * needs to have a worker thread bound to it. When a thread binds to
- * the session, it goes to CON_EXECUTING and does its thing. When the
- * transaction is finished, the thread sets it back to CON_IDLE and lets
- * it go.
- */
-typedef enum __CCState {
- CON_IDLE, /* This context is doing nothing */
- CON_GREETING, /* This context needs to output its greeting */
- CON_STARTING, /* This context is outputting its greeting */
- CON_READY, /* This context needs attention */
- CON_EXECUTING, /* This context is bound to a thread */
- CON_SYS /* This is a system context and mustn't be purged */
-} CCState;
-
-#ifndef __CIT_CONTEXT__
-#define __CIT_CONTEXT__
-typedef struct CitContext CitContext;
-#endif
-
-/*
- * This structure keeps track of all information relating to a running
- * session on the server. We keep one of these for each session.
- */
-struct CitContext {
- CitContext *prev; /* Link to previous session in list */
- CitContext *next; /* Link to next session in the list */
-
- int cs_pid; /* session ID */
- int dont_term; /* for special activities like artv so we don't get killed */
- double created; /* time of birth */
- time_t lastcmd; /* time of last command executed */
- time_t lastidle; /* For computing idle time */
- CCState state; /* thread state (see CON_ values below) */
- int kill_me; /* Set to nonzero to flag for termination */
-
- IOBuffer SendBuf, /* Our write Buffer */
- RecvBuf, /* Our block buffered read buffer */
- SBuf; /* Our block buffered read buffer for clients */
-
- StrBuf *MigrateBuf; /* Our block buffered read buffer */
- StrBuf *sMigrateBuf; /* Our block buffered read buffer */
-
- int client_socket;
- int is_local_client; /* set to 1 if client is running on the same host */
- /* Redirect this session's output to a memory buffer? */
- StrBuf *redirect_buffer; /* the buffer */
- StrBuf *StatusMessage;
-#ifdef HAVE_OPENSSL
- SSL *ssl;
- int redirect_ssl;
-#endif
-
- char curr_user[USERNAME_SIZE]; /* name of current user */
- int logged_in; /* logged in? */
- int internal_pgm; /* authenticated as internal program? */
- int nologin; /* not allowed to log in */
- int curr_view; /* The view type for the current user/room */
-
- time_t previous_login; /* Date/time of previous login */
- char lastcmdname[5]; /* name of last command executed */
- unsigned cs_flags; /* miscellaneous flags */
- int is_async; /* Nonzero if client accepts async msgs */
- int async_waiting; /* Nonzero if there are async msgs waiting */
- int input_waiting; /* Nonzero if there is client input waiting */
- int can_receive_im; /* Session is capable of receiving instant messages */
-
- /* Client information */
- int cs_clientdev; /* client developer ID */
- int cs_clienttyp; /* client type code */
- int cs_clientver; /* client version number */
- char cs_clientinfo[256];/* if its a unix domain socket, some info for logging. */
- uid_t cs_UDSclientUID; /* the uid of the client when talking via UDS */
- char cs_clientname[32]; /* name of client software */
- char cs_host[64]; /* host logged in from */
- char cs_addr[64]; /* address logged in from */
-
- /* The Internet type of thing */
- char cs_principal_id[256]; /* User principal identity for XMPP, ActivityPub, etc. */
- char cs_inet_email[128]; /* Return address of outbound Internet mail */
- char cs_inet_other_emails[1024]; /* User's other valid Internet email addresses */
- char cs_inet_fn[128]; /* Friendly-name of outbound Internet mail */
-
- FILE *download_fp; /* Fields relating to file transfer */
- size_t download_fp_total;
- char download_desired_section[128];
- FILE *upload_fp;
- char upl_file[256];
- char upl_path[PATH_MAX];
- char upl_comment[256];
- char upl_filedir[PATH_MAX];
- char upl_mimetype[64];
-
- struct ctdluser user; /* Database record buffers */
- struct ctdlroom room;
-
- /* A linked list of all instant messages sent to us. */
- struct ExpressMessage *FirstExpressMessage;
- int disable_exp; /* Set to 1 to disable incoming pages */
- int newmail; /* Other sessions increment this */
-
- /* Preferred MIME formats */
- char preferred_formats[256];
- int msg4_dont_decode;
-
- /* Dynamically allocated session data */
- void *session_specific_data; /* Used by individual protocol modules */
- struct cit_ical *CIT_ICAL; /* calendaring data */
- struct ma_info *ma; /* multipart/alternative data */
- const char *ServiceName; /* readable purpose of this session */
- long tcp_port;
- void *openid_data; /* Data stored by the OpenID module */
- char *ldap_dn; /* DN of user when using AUTHMODE_LDAP */
-
- void (*h_command_function) (void) ; /* service command function */
- void (*h_async_function) (void) ; /* do async msgs function */
- void (*h_greeting_function) (void) ; /* greeting function for session startup */
-
- long *cached_msglist; /* results of the previous CtdlForEachMessage() */
- int cached_num_msgs;
-
- char vcard_updated_by_ldap; /* !0 iff ldap changed the vcard, treat as aide update */
-};
-
-#define CC MyContext()
-
-extern pthread_key_t MyConKey; /* TSD key for MyContext() */
-extern int num_sessions;
-extern CitContext masterCC;
-extern CitContext *ContextList;
-
-CitContext *MyContext (void);
-void RemoveContext (struct CitContext *);
-CitContext *CreateNewContext (void);
-void context_cleanup(void);
-void kill_session (int session_to_kill);
-void InitializeMasterCC(void);
-void dead_session_purge(int force);
-void set_async_waiting(struct CitContext *ccptr);
-
-CitContext *CloneContext(CitContext *CloneMe);
-
-/* forcibly close and flush fd's on shutdown */
-void terminate_all_sessions(void);
-
-/* Deprecated, user CtdlBumpNewMailCounter() instead */
-void BumpNewMailCounter(long) __attribute__ ((deprecated));
-
-void terminate_idle_sessions(void);
-int CtdlTerminateOtherSession (int session_num);
-/* bits returned by CtdlTerminateOtherSession */
-#define TERM_FOUND 0x01
-#define TERM_ALLOWED 0x02
-#define TERM_KILLED 0x03
-#define TERM_NOTALLOWED -1
-
-/*
- * Bind a thread to a context. (It's inline merely to speed things up.)
- */
-static INLINE void become_session(CitContext *which_con) {
- pthread_setspecific(MyConKey, (void *)which_con );
-}
-
-#endif /* CONTEXT_H */
+++ /dev/null
-//
-// This module handles states which are global to the entire server.
-//
-// Copyright (c) 1987-2021 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.
-
-#include <stdio.h>
-#include <sys/file.h>
-#include <libcitadel.h>
-
-#include "ctdl_module.h"
-#include "config.h"
-#include "citserver.h"
-#include "user_ops.h"
-
-long control_highest_user = 0;
-
-/*
- * This is the legacy "control record" format for the message base. If found
- * on disk, its contents will be migrated into the system configuration. Never
- * change this.
- */
-struct legacy_ctrl_format {
- long MMhighest; /* highest message number in file */
- unsigned MMflags; /* Global system flags */
- long MMnextuser; /* highest user number on system */
- long MMnextroom; /* highest room number on system */
- int MM_hosted_upgrade_level; /* Server-hosted upgrade level */
- int MM_fulltext_wordbreaker; /* ID of wordbreaker in use */
- long MMfulltext; /* highest message number indexed */
- int MMdbversion; /* Version of Berkeley DB used on previous server run */
-};
-
-
-/*
- * data that gets passed back and forth between control_find_highest() and its caller
- */
-struct cfh {
- long highest_roomnum_found;
- long highest_msgnum_found;
-};
-
-
-/*
- * Callback to get highest room number when rebuilding message base metadata
- *
- * sanity_diag_mode (can be set by -s flag at startup) may be:
- * 0 = attempt to fix inconsistencies
- * 1 = show inconsistencies but don't repair them, exit after complete
- * 2 = show inconsistencies but don't repair them, continue execution
- */
-void control_find_highest(struct ctdlroom *qrbuf, void *data) {
- struct cfh *cfh = (struct cfh *)data;
- struct cdbdata *cdbfr;
- long *msglist;
- int num_msgs=0;
- int c;
-
- if (qrbuf->QRnumber > cfh->highest_roomnum_found) {
- cfh->highest_roomnum_found = qrbuf->QRnumber;
- }
-
- /* Load the message list */
- cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
- if (cdbfr != NULL) {
- msglist = (long *) cdbfr->ptr;
- num_msgs = cdbfr->len / sizeof(long);
- }
- else {
- return; /* No messages at all? No further action. */
- }
-
- if (num_msgs > 0) {
- for (c=0; c<num_msgs; c++) {
- if (msglist[c] > cfh->highest_msgnum_found) {
- cfh->highest_msgnum_found = msglist[c];
- }
- }
- }
-
- cdb_free(cdbfr);
-}
-
-
-/*
- * Callback to get highest user number.
- */
-void control_find_user(char *username, void *out_data) {
- struct ctdluser EachUser;
-
- if (CtdlGetUser(&EachUser, username) != 0) {
- return;
- }
-
- if (EachUser.usernum > CtdlGetConfigLong("MMnextuser")) {
- syslog(LOG_DEBUG, "control: fixing MMnextuser %ld > %ld , found in %s",
- EachUser.usernum, CtdlGetConfigLong("MMnextuser"), EachUser.fullname
- );
- if (!sanity_diag_mode) {
- CtdlSetConfigLong("MMnextuser", EachUser.usernum);
- }
- }
-}
-
-
-/*
- * If we have a legacy format control record on disk, import it.
- */
-void migrate_legacy_control_record(void) {
- FILE *fp = NULL;
- struct legacy_ctrl_format c;
- memset(&c, 0, sizeof(c));
-
- fp = fopen("citadel.control", "rb+");
- if (fp != NULL) {
- syslog(LOG_INFO, "control: legacy format record found -- importing to db");
- fread(&c, sizeof(struct legacy_ctrl_format), 1, fp);
-
- CtdlSetConfigLong( "MMhighest", c.MMhighest);
- CtdlSetConfigInt( "MMflags", c.MMflags);
- CtdlSetConfigLong( "MMnextuser", c.MMnextuser);
- CtdlSetConfigLong( "MMnextroom", c.MMnextroom);
- CtdlSetConfigInt( "MM_hosted_upgrade_level", c.MM_hosted_upgrade_level);
- CtdlSetConfigInt( "MM_fulltext_wordbreaker", c.MM_fulltext_wordbreaker);
- CtdlSetConfigLong( "MMfulltext", c.MMfulltext);
-
- fclose(fp);
- if (unlink("citadel.control") != 0) {
- fprintf(stderr, "Unable to remove legacy control record after migrating it.\n");
- fprintf(stderr, "Exiting to prevent data corruption.\n");
- exit(CTDLEXIT_CONFIG);
- }
- }
-}
-
-
-/*
- * check_control - check the control record has sensible values for message, user and room numbers
- */
-void check_control(void) {
-
- syslog(LOG_INFO, "control: sanity checking the recorded highest message and room numbers");
- struct cfh cfh;
- memset(&cfh, 0, sizeof(struct cfh));
- CtdlForEachRoom(control_find_highest, &cfh);
-
- if (cfh.highest_roomnum_found > CtdlGetConfigLong("MMnextroom")) {
- syslog(LOG_DEBUG, "control: fixing MMnextroom %ld > %ld", cfh.highest_roomnum_found, CtdlGetConfigLong("MMnextroom"));
- if (!sanity_diag_mode) {
- CtdlSetConfigLong("MMnextroom", cfh.highest_roomnum_found);
- }
- }
-
- if (cfh.highest_msgnum_found > CtdlGetConfigLong("MMhighest")) {
- syslog(LOG_DEBUG, "control: fixing MMhighest %ld > %ld", cfh.highest_msgnum_found, CtdlGetConfigLong("MMhighest"));
- if (!sanity_diag_mode) {
- CtdlSetConfigLong("MMhighest", cfh.highest_msgnum_found);
- }
- }
-
- syslog(LOG_INFO, "control: sanity checking the recorded highest user number");
- ForEachUser(control_find_user, NULL);
-
- syslog(LOG_INFO, "control: sanity checks complete");
-
- if (sanity_diag_mode == 1) {
- syslog(LOG_INFO, "control: sanity check diagnostic mode is active - exiting now");
- abort();
- }
-}
-
-
-/*
- * get_new_message_number() - Obtain a new, unique ID to be used for a message.
- */
-long get_new_message_number(void)
-{
- long retval = 0L;
- begin_critical_section(S_CONTROL);
- retval = CtdlGetConfigLong("MMhighest");
- ++retval;
- CtdlSetConfigLong("MMhighest", retval);
- end_critical_section(S_CONTROL);
- return(retval);
-}
-
-
-/*
- * CtdlGetCurrentMessageNumber() - Obtain the current highest message number in the system
- * This provides a quick way to initialise a variable that might be used to indicate
- * messages that should not be processed. For example, an inbox rules script will use this
- * to record determine that messages older than this should not be processed.
- *
- * (Why is this function here? Can't we just go straight to the config variable it fetches?)
- */
-long CtdlGetCurrentMessageNumber(void)
-{
- return CtdlGetConfigLong("MMhighest");
-}
-
-
-/*
- * get_new_user_number() - Obtain a new, unique ID to be used for a user.
- */
-long get_new_user_number(void)
-{
- long retval = 0L;
- begin_critical_section(S_CONTROL);
- retval = CtdlGetConfigLong("MMnextuser");
- ++retval;
- CtdlSetConfigLong("MMnextuser", retval);
- end_critical_section(S_CONTROL);
- return(retval);
-}
-
-
-/*
- * get_new_room_number() - Obtain a new, unique ID to be used for a room.
- */
-long get_new_room_number(void)
-{
- long retval = 0L;
- begin_critical_section(S_CONTROL);
- retval = CtdlGetConfigLong("MMnextroom");
- ++retval;
- CtdlSetConfigLong("MMnextroom", retval);
- end_critical_section(S_CONTROL);
- return(retval);
-}
-
-
-/*
- * Helper function for cmd_conf() to handle boolean values
- */
-int confbool(char *v)
-{
- if (IsEmptyStr(v)) return(0);
- if (atoi(v) != 0) return(1);
- return(0);
-}
-
-
-/*
- * Get or set global configuration options
- */
-void cmd_conf(char *argbuf)
-{
- char cmd[16];
- char buf[1024];
- int a, i;
- long ii;
- char *confptr;
- char confname[128];
-
- if (CtdlAccessCheck(ac_aide)) return;
-
- extract_token(cmd, argbuf, 0, '|', sizeof cmd);
-
- // CONF GET - retrieve system configuration in legacy format
- // This is deprecated; please do not add fields or change their order.
- if (!strcasecmp(cmd, "GET")) {
- cprintf("%d Configuration...\n", LISTING_FOLLOWS);
- cprintf("%s\n", CtdlGetConfigStr("c_nodename"));
- cprintf("%s\n", CtdlGetConfigStr("c_fqdn"));
- cprintf("%s\n", CtdlGetConfigStr("c_humannode"));
- cprintf("xxx\n"); /* placeholder -- field no longer in use */
- cprintf("%d\n", CtdlGetConfigInt("c_creataide"));
- cprintf("%d\n", CtdlGetConfigInt("c_sleeping"));
- cprintf("%d\n", CtdlGetConfigInt("c_initax"));
- cprintf("%d\n", CtdlGetConfigInt("c_regiscall"));
- cprintf("%d\n", CtdlGetConfigInt("c_twitdetect"));
- cprintf("%s\n", CtdlGetConfigStr("c_twitroom"));
- cprintf("%s\n", CtdlGetConfigStr("c_moreprompt"));
- cprintf("%d\n", CtdlGetConfigInt("c_restrict"));
- cprintf("%s\n", CtdlGetConfigStr("c_site_location"));
- cprintf("%s\n", CtdlGetConfigStr("c_sysadm"));
- cprintf("%d\n", CtdlGetConfigInt("c_maxsessions"));
- cprintf("xxx\n"); /* placeholder -- field no longer in use */
- cprintf("%d\n", CtdlGetConfigInt("c_userpurge"));
- cprintf("%d\n", CtdlGetConfigInt("c_roompurge"));
- cprintf("%s\n", CtdlGetConfigStr("c_logpages"));
- cprintf("%d\n", CtdlGetConfigInt("c_createax"));
- cprintf("%ld\n", CtdlGetConfigLong("c_maxmsglen"));
- cprintf("%d\n", CtdlGetConfigInt("c_min_workers"));
- cprintf("%d\n", CtdlGetConfigInt("c_max_workers"));
- cprintf("%d\n", CtdlGetConfigInt("c_pop3_port"));
- cprintf("%d\n", CtdlGetConfigInt("c_smtp_port"));
- cprintf("%d\n", CtdlGetConfigInt("c_rfc822_strict_from"));
- cprintf("%d\n", CtdlGetConfigInt("c_aide_zap"));
- cprintf("%d\n", CtdlGetConfigInt("c_imap_port"));
- cprintf("%ld\n", CtdlGetConfigLong("c_net_freq"));
- cprintf("%d\n", CtdlGetConfigInt("c_disable_newu"));
- cprintf("1\n"); /* niu */
- cprintf("%d\n", CtdlGetConfigInt("c_purge_hour"));
- cprintf("%s\n", CtdlGetConfigStr("c_ldap_host"));
- cprintf("%d\n", CtdlGetConfigInt("c_ldap_port"));
- cprintf("%s\n", CtdlGetConfigStr("c_ldap_base_dn"));
- cprintf("%s\n", CtdlGetConfigStr("c_ldap_bind_dn"));
- cprintf("%s\n", CtdlGetConfigStr("c_ldap_bind_pw"));
- cprintf("%s\n", CtdlGetConfigStr("c_ip_addr"));
- cprintf("%d\n", CtdlGetConfigInt("c_msa_port"));
- cprintf("%d\n", CtdlGetConfigInt("c_imaps_port"));
- cprintf("%d\n", CtdlGetConfigInt("c_pop3s_port"));
- cprintf("%d\n", CtdlGetConfigInt("c_smtps_port"));
- cprintf("%d\n", CtdlGetConfigInt("c_enable_fulltext"));
- cprintf("%d\n", CtdlGetConfigInt("c_auto_cull"));
- cprintf("1\n");
- cprintf("%d\n", CtdlGetConfigInt("c_allow_spoofing"));
- cprintf("%d\n", CtdlGetConfigInt("c_journal_email"));
- cprintf("%d\n", CtdlGetConfigInt("c_journal_pubmsgs"));
- cprintf("%s\n", CtdlGetConfigStr("c_journal_dest"));
- cprintf("%s\n", CtdlGetConfigStr("c_default_cal_zone"));
- cprintf("%d\n", CtdlGetConfigInt("c_pftcpdict_port"));
- cprintf("0\n");
- cprintf("%d\n", CtdlGetConfigInt("c_auth_mode"));
- cprintf("\n");
- cprintf("\n");
- cprintf("\n");
- cprintf("\n");
- cprintf("%d\n", CtdlGetConfigInt("c_rbl_at_greeting"));
- cprintf("\n");
- cprintf("\n");
- cprintf("%s\n", CtdlGetConfigStr("c_pager_program"));
- cprintf("%d\n", CtdlGetConfigInt("c_imap_keep_from"));
- cprintf("%d\n", CtdlGetConfigInt("c_xmpp_c2s_port"));
- cprintf("%d\n", CtdlGetConfigInt("c_xmpp_s2s_port"));
- cprintf("%ld\n", CtdlGetConfigLong("c_pop3_fetch"));
- cprintf("%ld\n", CtdlGetConfigLong("c_pop3_fastest"));
- cprintf("%d\n", CtdlGetConfigInt("c_spam_flag_only"));
- cprintf("%d\n", CtdlGetConfigInt("c_guest_logins"));
- cprintf("%d\n", CtdlGetConfigInt("c_port_number"));
- cprintf("%d\n", ctdluid);
- cprintf("%d\n", CtdlGetConfigInt("c_nntp_port"));
- cprintf("%d\n", CtdlGetConfigInt("c_nntps_port"));
- cprintf("000\n");
- }
-
- // CONF SET - set system configuration in legacy format
- // This is deprecated; please do not add fields or change their order.
- else if (!strcasecmp(cmd, "SET")) {
- unbuffer_output();
- cprintf("%d Send configuration...\n", SEND_LISTING);
- a = 0;
- while (client_getln(buf, sizeof buf) >= 0 && strcmp(buf, "000")) {
- switch (a) {
- case 0:
- CtdlSetConfigStr("c_nodename", buf);
- break;
- case 1:
- CtdlSetConfigStr("c_fqdn", buf);
- break;
- case 2:
- CtdlSetConfigStr("c_humannode", buf);
- break;
- case 3:
- /* placeholder -- field no longer in use */
- break;
- case 4:
- CtdlSetConfigInt("c_creataide", confbool(buf));
- break;
- case 5:
- CtdlSetConfigInt("c_sleeping", atoi(buf));
- break;
- case 6:
- i = atoi(buf);
- if (i < 1) i = 1;
- if (i > 6) i = 6;
- CtdlSetConfigInt("c_initax", i);
- break;
- case 7:
- CtdlSetConfigInt("c_regiscall", confbool(buf));
- break;
- case 8:
- CtdlSetConfigInt("c_twitdetect", confbool(buf));
- break;
- case 9:
- CtdlSetConfigStr("c_twitroom", buf);
- break;
- case 10:
- CtdlSetConfigStr("c_moreprompt", buf);
- break;
- case 11:
- CtdlSetConfigInt("c_restrict", confbool(buf));
- break;
- case 12:
- CtdlSetConfigStr("c_site_location", buf);
- break;
- case 13:
- CtdlSetConfigStr("c_sysadm", buf);
- break;
- case 14:
- i = atoi(buf);
- if (i < 0) i = 0;
- CtdlSetConfigInt("c_maxsessions", i);
- break;
- case 15:
- /* placeholder -- field no longer in use */
- break;
- case 16:
- CtdlSetConfigInt("c_userpurge", atoi(buf));
- break;
- case 17:
- CtdlSetConfigInt("c_roompurge", atoi(buf));
- break;
- case 18:
- CtdlSetConfigStr("c_logpages", buf);
- break;
- case 19:
- i = atoi(buf);
- if (i < 1) i = 1;
- if (i > 6) i = 6;
- CtdlSetConfigInt("c_createax", i);
- break;
- case 20:
- ii = atol(buf);
- if (ii >= 8192) {
- CtdlSetConfigLong("c_maxmsglen", ii);
- }
- break;
- case 21:
- i = atoi(buf);
- if (i >= 3) { // minimum value
- CtdlSetConfigInt("c_min_workers", i);
- }
- break;
- case 22:
- i = atoi(buf);
- if (i >= CtdlGetConfigInt("c_min_workers")) { // max must be >= min
- CtdlSetConfigInt("c_max_workers", i);
- }
- break;
- case 23:
- CtdlSetConfigInt("c_pop3_port", atoi(buf));
- break;
- case 24:
- CtdlSetConfigInt("c_smtp_port", atoi(buf));
- break;
- case 25:
- CtdlSetConfigInt("c_rfc822_strict_from", atoi(buf));
- break;
- case 26:
- CtdlSetConfigInt("c_aide_zap", confbool(buf));
- break;
- case 27:
- CtdlSetConfigInt("c_imap_port", atoi(buf));
- break;
- case 28:
- CtdlSetConfigLong("c_net_freq", atol(buf));
- break;
- case 29:
- CtdlSetConfigInt("c_disable_newu", confbool(buf));
- break;
- case 30:
- /* niu */
- break;
- case 31:
- i = atoi(buf);
- if ((i >= 0) && (i <= 23)) {
- CtdlSetConfigInt("c_purge_hour", i);
- }
- break;
- case 32:
- CtdlSetConfigStr("c_ldap_host", buf);
- break;
- case 33:
- CtdlSetConfigInt("c_ldap_port", atoi(buf));
- break;
- case 34:
- CtdlSetConfigStr("c_ldap_base_dn", buf);
- break;
- case 35:
- CtdlSetConfigStr("c_ldap_bind_dn", buf);
- break;
- case 36:
- CtdlSetConfigStr("c_ldap_bind_pw", buf);
- break;
- case 37:
- CtdlSetConfigStr("c_ip_addr", buf);
- break;
- case 38:
- CtdlSetConfigInt("c_msa_port", atoi(buf));
- break;
- case 39:
- CtdlSetConfigInt("c_imaps_port", atoi(buf));
- break;
- case 40:
- CtdlSetConfigInt("c_pop3s_port", atoi(buf));
- break;
- case 41:
- CtdlSetConfigInt("c_smtps_port", atoi(buf));
- break;
- case 42:
- CtdlSetConfigInt("c_enable_fulltext", confbool(buf));
- break;
- case 43:
- CtdlSetConfigInt("c_auto_cull", confbool(buf));
- break;
- case 44:
- /* niu */
- break;
- case 45:
- CtdlSetConfigInt("c_allow_spoofing", confbool(buf));
- break;
- case 46:
- CtdlSetConfigInt("c_journal_email", confbool(buf));
- break;
- case 47:
- CtdlSetConfigInt("c_journal_pubmsgs", confbool(buf));
- break;
- case 48:
- CtdlSetConfigStr("c_journal_dest", buf);
- break;
- case 49:
- CtdlSetConfigStr("c_default_cal_zone", buf);
- break;
- case 50:
- CtdlSetConfigInt("c_pftcpdict_port", atoi(buf));
- break;
- case 51:
- /* niu */
- break;
- case 52:
- CtdlSetConfigInt("c_auth_mode", atoi(buf));
- break;
- case 53:
- /* niu */
- break;
- case 54:
- /* niu */
- break;
- case 55:
- /* niu */
- break;
- case 56:
- /* niu */
- break;
- case 57:
- CtdlSetConfigInt("c_rbl_at_greeting", confbool(buf));
- break;
- case 58:
- /* niu */
- break;
- case 59:
- /* niu */
- break;
- case 60:
- CtdlSetConfigStr("c_pager_program", buf);
- break;
- case 61:
- CtdlSetConfigInt("c_imap_keep_from", confbool(buf));
- break;
- case 62:
- CtdlSetConfigInt("c_xmpp_c2s_port", atoi(buf));
- break;
- case 63:
- CtdlSetConfigInt("c_xmpp_s2s_port", atoi(buf));
- break;
- case 64:
- CtdlSetConfigLong("c_pop3_fetch", atol(buf));
- break;
- case 65:
- CtdlSetConfigLong("c_pop3_fastest", atol(buf));
- break;
- case 66:
- CtdlSetConfigInt("c_spam_flag_only", confbool(buf));
- break;
- case 67:
- CtdlSetConfigInt("c_guest_logins", confbool(buf));
- break;
- case 68:
- CtdlSetConfigInt("c_port_number", atoi(buf));
- break;
- case 69:
- /* niu */
- break;
- case 70:
- CtdlSetConfigInt("c_nntp_port", atoi(buf));
- break;
- case 71:
- CtdlSetConfigInt("c_nntps_port", atoi(buf));
- break;
- }
- ++a;
- }
- snprintf(buf, sizeof buf,
- "The global system configuration has been edited by %s.\n",
- (CC->logged_in ? CC->curr_user : "an administrator")
- );
- CtdlAideMessage(buf, "Citadel Configuration Manager Message");
-
- if (!IsEmptyStr(CtdlGetConfigStr("c_logpages")))
- CtdlCreateRoom(CtdlGetConfigStr("c_logpages"), 3, "", 0, 1, 1, VIEW_BBS);
-
- /* If full text indexing has been disabled, invalidate the
- * index so it doesn't try to use it later.
- */
- if (CtdlGetConfigInt("c_enable_fulltext") == 0) {
- CtdlSetConfigInt("MM_fulltext_wordbreaker", 0);
- }
- }
-
- // CONF GETSYS - retrieve arbitrary system configuration stanzas stored in the message base
- else if (!strcasecmp(cmd, "GETSYS")) {
- extract_token(confname, argbuf, 1, '|', sizeof confname);
- confptr = CtdlGetSysConfig(confname);
- if (confptr != NULL) {
- long len;
-
- len = strlen(confptr);
- cprintf("%d %s\n", LISTING_FOLLOWS, confname);
- client_write(confptr, len);
- if ((len > 0) && (confptr[len - 1] != 10))
- client_write("\n", 1);
- cprintf("000\n");
- free(confptr);
- } else {
- cprintf("%d No such configuration.\n",
- ERROR + ILLEGAL_VALUE);
- }
- }
-
- // CONF PUTSYS - store arbitrary system configuration stanzas in the message base
- else if (!strcasecmp(cmd, "PUTSYS")) {
- extract_token(confname, argbuf, 1, '|', sizeof confname);
- unbuffer_output();
- cprintf("%d %s\n", SEND_LISTING, confname);
- confptr = CtdlReadMessageBody(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
- CtdlPutSysConfig(confname, confptr);
- free(confptr);
- }
-
- // CONF GETVAL - retrieve configuration variables from the database
- // CONF LOADVAL - same thing but can handle variables bigger than 1 KB (deprecated and probably safe to remove)
- else if ( (!strcasecmp(cmd, "GETVAL")) || (!strcasecmp(cmd, "LOADVAL")) ) {
- extract_token(confname, argbuf, 1, '|', sizeof confname);
- char *v = CtdlGetConfigStr(confname);
- if ( (v) && (!strcasecmp(cmd, "GETVAL")) ) {
- cprintf("%d %s|\n", CIT_OK, v);
- }
- else if ( (v) && (!strcasecmp(cmd, "LOADVAL")) ) {
- cprintf("%d %d\n", BINARY_FOLLOWS, (int)strlen(v));
- client_write(v, strlen(v));
- }
- else {
- cprintf("%d |\n", ERROR);
- }
- }
-
- // CONF PUTVAL - store configuration variables in the database
- else if (!strcasecmp(cmd, "PUTVAL")) {
- if (num_tokens(argbuf, '|') < 3) {
- cprintf("%d name and value required\n", ERROR);
- }
- else {
- extract_token(confname, argbuf, 1, '|', sizeof confname);
- extract_token(buf, argbuf, 2, '|', sizeof buf);
- CtdlSetConfigStr(confname, buf);
- cprintf("%d setting '%s' to '%s'\n", CIT_OK, confname, buf);
- }
- }
-
- // CONF STOREVAL - store configuration variables in the database bigger than 1 KB (deprecated and probably safe to remove)
- else if (!strcasecmp(cmd, "STOREVAL")) {
- if (num_tokens(argbuf, '|') < 3) {
- cprintf("%d name and length required\n", ERROR);
- }
- else {
- extract_token(confname, argbuf, 1, '|', sizeof confname);
- int bytes = extract_int(argbuf, 2);
- char *valbuf = malloc(bytes + 1);
- cprintf("%d %d\n", SEND_BINARY, bytes);
- client_read(valbuf, bytes);
- valbuf[bytes+1] = 0;
- CtdlSetConfigStr(confname, valbuf);
- free(valbuf);
- }
- }
-
- // CONF LISTVAL - list configuration variables in the database and their values
- else if (!strcasecmp(cmd, "LISTVAL")) {
- struct cdbdata *cdbcfg;
- int keylen = 0;
- char *key = NULL;
- char *value = NULL;
-
- cprintf("%d all configuration variables\n", LISTING_FOLLOWS);
- cdb_rewind(CDB_CONFIG);
- while (cdbcfg = cdb_next_item(CDB_CONFIG), cdbcfg != NULL) {
- if (cdbcfg->len < 1020) {
- keylen = strlen(cdbcfg->ptr);
- key = cdbcfg->ptr;
- value = cdbcfg->ptr + keylen + 1;
- cprintf("%s|%s\n", key, value);
- }
- cdb_free(cdbcfg);
- }
- cprintf("000\n");
- }
-
- else {
- cprintf("%d Illegal option(s) specified.\n", ERROR + ILLEGAL_VALUE);
- }
-}
-
-
-typedef struct __ConfType {
- ConstStr Name;
- long Type;
-}ConfType;
-
-ConfType CfgNames[] = {
- { {HKEY("localhost") }, 0},
- { {HKEY("directory") }, 0},
- { {HKEY("smarthost") }, 2},
- { {HKEY("fallbackhost") }, 2},
- { {HKEY("rbl") }, 3},
- { {HKEY("spamassassin") }, 3},
- { {HKEY("masqdomain") }, 1},
- { {HKEY("clamav") }, 3},
- { {HKEY("notify") }, 3},
- { {NULL, 0}, 0}
-};
-
-HashList *CfgNameHash = NULL;
-void cmd_gvdn(char *argbuf)
-{
- const ConfType *pCfg;
- char *confptr;
- long min = atol(argbuf);
- const char *Pos = NULL;
- const char *PPos = NULL;
- const char *HKey;
- long HKLen;
- StrBuf *Line;
- StrBuf *Config;
- StrBuf *Cfg;
- StrBuf *CfgToken;
- HashList *List;
- HashPos *It;
- void *vptr;
-
- List = NewHash(1, NULL);
- Cfg = NewStrBufPlain(CtdlGetConfigStr("c_fqdn"), -1);
- Put(List, SKEY(Cfg), Cfg, HFreeStrBuf);
- Cfg = NULL;
-
- confptr = CtdlGetSysConfig(INTERNETCFG);
- Config = NewStrBufPlain(confptr, -1);
- free(confptr);
-
- Line = NewStrBufPlain(NULL, StrLength(Config));
- CfgToken = NewStrBufPlain(NULL, StrLength(Config));
- while (StrBufSipLine(Line, Config, &Pos))
- {
- if (Cfg == NULL)
- Cfg = NewStrBufPlain(NULL, StrLength(Line));
- PPos = NULL;
- StrBufExtract_NextToken(Cfg, Line, &PPos, '|');
- StrBufExtract_NextToken(CfgToken, Line, &PPos, '|');
- if (GetHash(CfgNameHash, SKEY(CfgToken), &vptr) &&
- (vptr != NULL))
- {
- pCfg = (ConfType *) vptr;
- if (pCfg->Type <= min)
- {
- Put(List, SKEY(Cfg), Cfg, HFreeStrBuf);
- Cfg = NULL;
- }
- }
- }
-
- cprintf("%d Valid Domains\n", LISTING_FOLLOWS);
- It = GetNewHashPos(List, 1);
- while (GetNextHashPos(List, It, &HKLen, &HKey, &vptr))
- {
- cputbuf(vptr);
- cprintf("\n");
- }
- cprintf("000\n");
-
- DeleteHashPos(&It);
- DeleteHash(&List);
- FreeStrBuf(&Cfg);
- FreeStrBuf(&Line);
- FreeStrBuf(&CfgToken);
- FreeStrBuf(&Config);
-}
-
-/*****************************************************************************/
-/* MODULE INITIALIZATION STUFF */
-/*****************************************************************************/
-
-CTDL_MODULE_INIT(control)
-{
- if (!threading) {
- int i;
-
- CfgNameHash = NewHash(1, NULL);
- for (i = 0; CfgNames[i].Name.Key != NULL; i++)
- Put(CfgNameHash, CKEY(CfgNames[i].Name), &CfgNames[i], reference_free_handler);
-
- CtdlRegisterProtoHook(cmd_gvdn, "GVDN", "get valid domain names");
- CtdlRegisterProtoHook(cmd_conf, "CONF", "get/set system configuration");
- }
- /* return our id for the Log */
- return "control";
-}
+++ /dev/null
-/*
- * 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.
- */
-
-void get_control (void);
-void put_control (void);
-void check_control(void);
-long int get_new_message_number (void);
-long int get_new_user_number (void);
-long int get_new_room_number (void);
-void migrate_legacy_control_record(void);
+++ /dev/null
-
-#ifndef CTDL_MODULE_H
-#define CTDL_MODULE_H
-
-#include "sysdep.h"
-
-#ifdef HAVE_GC
-#define GC_THREADS
-#define GC_REDIRECT_TO_LOCAL
-#include <gc/gc_local_alloc.h>
-#else
-#define GC_MALLOC malloc
-#define GC_MALLOC_ATOMIC malloc
-#define GC_FREE free
-#define GC_REALLOC realloc
-#endif
-
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <ctype.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <syslog.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#ifdef HAVE_STRINGS_H
-#include <strings.h>
-#endif
-#include <limits.h>
-
-#include <libcitadel.h>
-
-#include "server.h"
-#include "sysdep_decls.h"
-#include "msgbase.h"
-#include "threads.h"
-#include "citadel_dirs.h"
-#include "context.h"
-
-/*
- * define macros for module init stuff
- */
-
-#define CTDL_MODULE_INIT(module_name) char *ctdl_module_##module_name##_init (int threading)
-
-#define CTDL_INIT_CALL(module_name) ctdl_module_##module_name##_init (threading)
-
-#define CTDL_MODULE_UPGRADE(module_name) char *ctdl_module_##module_name##_upgrade (void)
-
-#define CTDL_UPGRADE_CALL(module_name) ctdl_module_##module_name##_upgrade ()
-
-#define CtdlAideMessage(TEXT, SUBJECT) \
- quickie_message( \
- "Citadel", \
- NULL, \
- NULL, \
- AIDEROOM, \
- TEXT, \
- FMT_CITADEL, \
- SUBJECT)
-
-/*
- * Hook functions available to modules.
- */
-/* Priorities for */
-#define PRIO_QUEUE 500
-#define PRIO_AGGR 1000
-#define PRIO_SEND 1500
-#define PRIO_CLEANUP 2000
-/* Priorities for EVT_HOUSE */
-#define PRIO_HOUSE 3000
-/* Priorities for EVT_LOGIN */
-#define PRIO_CREATE 10000
-/* Priorities for EVT_LOGOUT */
-#define PRIO_LOGOUT 15000
-/* Priorities for EVT_LOGIN */
-#define PRIO_LOGIN 20000
-/* Priorities for EVT_START */
-#define PRIO_START 25000
-/* Priorities for EVT_STOP */
-#define PRIO_STOP 30000
-/* Priorities for EVT_ASYNC */
-#define PRIO_ASYNC 35000
-/* Priorities for EVT_SHUTDOWN */
-#define PRIO_SHUTDOWN 40000
-/* Priorities for EVT_UNSTEALTH */
-#define PRIO_UNSTEALTH 45000
-/* Priorities for EVT_STEALTH */
-#define PRIO_STEALTH 50000
-
-
-void CtdlRegisterSessionHook(void (*fcn_ptr)(void), int EventType, int Priority);
-void CtdlUnregisterSessionHook(void (*fcn_ptr)(void), int EventType);
-void CtdlShutdownServiceHooks(void);
-
-void CtdlRegisterUserHook(void (*fcn_ptr)(struct ctdluser *), int EventType);
-void CtdlUnregisterUserHook(void (*fcn_ptr)(struct ctdluser *), int EventType);
-
-void CtdlRegisterXmsgHook(int (*fcn_ptr)(char *, char *, char *, char *), int order);
-void CtdlUnregisterXmsgHook(int (*fcn_ptr)(char *, char *, char *, char *), int order);
-
-void CtdlRegisterMessageHook(int (*handler)(struct CtdlMessage *, struct recptypes *), int EventType);
-void CtdlUnregisterMessageHook(int (*handler)(struct CtdlMessage *, struct recptypes *), int EventType);
-
-void CtdlRegisterRoomHook(int (*fcn_ptr)(struct ctdlroom *) );
-void CtdlUnregisterRoomHook(int (*fnc_ptr)(struct ctdlroom *) );
-
-void CtdlRegisterDeleteHook(void (*handler)(char *, long) );
-void CtdlUnregisterDeleteHook(void (*handler)(char *, long) );
-
-void CtdlRegisterCleanupHook(void (*fcn_ptr)(void));
-void CtdlUnregisterCleanupHook(void (*fcn_ptr)(void));
-
-void CtdlRegisterEVCleanupHook(void (*fcn_ptr)(void));
-void CtdlUnregisterEVCleanupHook(void (*fcn_ptr)(void));
-
-void CtdlRegisterProtoHook(void (*handler)(char *), char *cmd, char *desc);
-
-void CtdlRegisterServiceHook(int tcp_port,
- char *sockpath,
- void (*h_greeting_function) (void),
- void (*h_command_function) (void),
- void (*h_async_function) (void),
- const char *ServiceName
-);
-void CtdlUnregisterServiceHook(int tcp_port,
- char *sockpath,
- void (*h_greeting_function) (void),
- void (*h_command_function) (void),
- void (*h_async_function) (void)
-);
-
-void CtdlRegisterFixedOutputHook(char *content_type, void (*output_function) (char *supplied_data, int len));
-void CtdlUnRegisterFixedOutputHook(char *content_type);
-void CtdlRegisterMaintenanceThread(char *name, void *(*thread_proc) (void *arg));
-void CtdlRegisterSearchFuncHook(void (*fcn_ptr)(int *, long **, const char *), char *name);
-
-/*
- * if you say a) (which may take a while)
- * don't forget to say b)
- */
-void CtdlDisableHouseKeeping(void);
-void CtdlEnableHouseKeeping(void);
-
-/* TODODRW: This needs to be changed into a hook type interface
- * for now we have this horrible hack
- */
-void CtdlModuleStartCryptoMsgs(char *ok_response, char *nosup_response, char *error_response);
-
-/* return the current context list as an array and do it in a safe manner
- * The returned data is a copy so only reading is useful
- * The number of contexts is returned in count.
- * Beware, this does not copy any of the data pointed to by the context.
- * This means that you can not rely on things like the redirect buffer being valid.
- * You must free the returned pointer when done.
- */
-struct CitContext *CtdlGetContextArray (int *count);
-void CtdlFillSystemContext(struct CitContext *context, char *name);
-int CtdlTrySingleUser(void);
-void CtdlEndSingleUser(void);
-int CtdlWantSingleUser(void);
-int CtdlIsSingleUser(void);
-
-
-int CtdlIsUserLoggedIn (char *user_name);
-int CtdlIsUserLoggedInByNum (long usernum);
-void CtdlBumpNewMailCounter(long which_user);
-
-
-/*
- * CtdlGetCurrentMessageNumber() - Obtain the current highest message number in the system
- * This provides a quick way to initialise a variable that might be used to indicate
- * messages that should not be processed. For example, a new inbox script will use this
- * to record determine that messages older than this should not be processed.
- * This function is defined in control.c
- */
-long CtdlGetCurrentMessageNumber(void);
-
-
-
-/*
- * Expose various room operation functions from room_ops.c to the modules API
- */
-
-unsigned CtdlCreateRoom(char *new_room_name,
- int new_room_type,
- char *new_room_pass,
- int new_room_floor,
- int really_create,
- int avoid_access,
- int new_room_view);
-int CtdlGetRoom(struct ctdlroom *qrbuf, const char *room_name);
-int CtdlGetRoomLock(struct ctdlroom *qrbuf, const char *room_name);
-int CtdlDoIHavePermissionToDeleteThisRoom(struct ctdlroom *qr);
-void CtdlRoomAccess(struct ctdlroom *roombuf, struct ctdluser *userbuf, int *result, int *view);
-void CtdlPutRoomLock(struct ctdlroom *qrbuf);
-typedef void (*ForEachRoomCallBack)(struct ctdlroom *EachRoom, void *out_data);
-void CtdlForEachRoom(ForEachRoomCallBack CB, void *in_data);
-char *LoadRoomNetConfigFile(long roomnum);
-void SaveRoomNetConfigFile(long roomnum, const char *raw_netconfig);
-void CtdlDeleteRoom(struct ctdlroom *qrbuf);
-int CtdlRenameRoom(char *old_name, char *new_name, int new_floor);
-void CtdlUserGoto (char *where, int display_result, int transiently, int *msgs, int *new, long *oldest, long *newest);
-struct floor *CtdlGetCachedFloor(int floor_num);
-void CtdlScheduleRoomForDeletion(struct ctdlroom *qrbuf);
-void CtdlGetFloor (struct floor *flbuf, int floor_num);
-void CtdlPutFloor (struct floor *flbuf, int floor_num);
-void CtdlPutFloorLock(struct floor *flbuf, int floor_num);
-int CtdlGetFloorByName(const char *floor_name);
-int CtdlGetFloorByNameLock(const char *floor_name);
-int CtdlGetAvailableFloor(void);
-int CtdlIsNonEditable(struct ctdlroom *qrbuf);
-void CtdlPutRoom(struct ctdlroom *);
-
-/*
- * Possible return values for CtdlRenameRoom()
- */
-enum {
- crr_ok, /* success */
- crr_room_not_found, /* room not found */
- crr_already_exists, /* new name already exists */
- crr_noneditable, /* cannot edit this room */
- crr_invalid_floor, /* target floor does not exist */
- crr_access_denied /* not allowed to edit this room */
-};
-
-
-
-/*
- * API declarations from citserver.h
- */
-int CtdlAccessCheck(int);
-/* 'required access level' values which may be passed to CtdlAccessCheck()
- */
-enum {
- ac_none,
- ac_logged_in_or_guest,
- ac_logged_in,
- ac_room_aide,
- ac_aide,
- ac_internal,
-};
-
-
-
-/*
- * API declarations from serv_extensions.h
- */
-void CtdlModuleDoSearch(int *num_msgs, long **search_msgs, const char *search_string, const char *func_name);
-
-#define NODENAME CtdlGetConfigStr("c_nodename")
-#define FQDN CtdlGetConfigStr("c_fqdn")
-#define CTDLUID ctdluid
-#define CREATAIDE CtdlGetConfigInt("c_creataide")
-#define REGISCALL CtdlGetConfigInt("c_regiscall")
-#define TWITDETECT CtdlGetConfigInt("c_twitdetect")
-#define TWITROOM CtdlGetConfigStr("c_twitroom")
-#define RESTRICT_INTERNET CtdlGetConfigInt("c_restrict")
-
-#define CtdlREGISTERRoomCfgType(a, p, uniq, nSegs, s, d) RegisterRoomCfgType(#a, sizeof(#a) - 1, a, p, uniq, nSegs, s, d);
-
-
-
-/*
- * Expose API calls from user_ops.c
- */
-int CtdlGetUser(struct ctdluser *usbuf, char *name);
-int CtdlGetUserLen(struct ctdluser *usbuf, const char *name, long len);
-int CtdlGetUserLock(struct ctdluser *usbuf, char *name);
-void CtdlPutUser(struct ctdluser *usbuf);
-void CtdlPutUserLock(struct ctdluser *usbuf);
-int CtdlLockGetCurrentUser(void);
-void CtdlPutCurrentUserLock(void);
-int CtdlGetUserByNumber(struct ctdluser *usbuf, long number);
-void CtdlGetRelationship(visit *vbuf, struct ctdluser *rel_user, struct ctdlroom *rel_room);
-void CtdlSetRelationship(visit *newvisit, struct ctdluser *rel_user, struct ctdlroom *rel_room);
-void CtdlMailboxName(char *buf, size_t n, const struct ctdluser *who, const char *prefix);
-int CtdlLoginExistingUser(const char *username);
-
-/*
- * Values which may be returned by CtdlLoginExistingUser()
- */
-enum {
- pass_ok,
- pass_already_logged_in,
- pass_no_user,
- pass_internal_error,
- pass_wrong_password
-};
-
-int CtdlTryPassword(const char *password, long len);
-/*
- * Values which may be returned by CtdlTryPassword()
- */
-enum {
- login_ok,
- login_already_logged_in,
- login_too_many_users,
- login_not_found
-};
-
-void CtdlUserLogout(void);
-
-/*
- * Expose API calls from msgbase.c
- */
-
-
-/*
- * Expose API calls from euidindex.c
- */
-long CtdlLocateMessageByEuid(char *euid, struct ctdlroom *qrbuf);
-
-
-/*
- * Expose API calls from modules/openid/serv_openid_rp.c in order to turn it into a generic external authentication driver
- */
-int attach_extauth(struct ctdluser *who, StrBuf *claimed_id);
-
-#endif /* CTDL_MODULE_H */
+++ /dev/null
-// This is a data store backend for the Citadel server which uses Berkeley DB.
-//
-// Copyright (c) 1987-2021 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.
-
-/*****************************************************************************
- Tunable configuration parameters for the Berkeley DB back end
- *****************************************************************************/
-
-/* Citadel will checkpoint the db at the end of every session, but only if
- * the specified number of kilobytes has been written, or if the specified
- * number of minutes has passed, since the last checkpoint.
- */
-#define MAX_CHECKPOINT_KBYTES 256
-#define MAX_CHECKPOINT_MINUTES 15
-
-/*****************************************************************************/
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <sys/stat.h>
-#include <stdio.h>
-#include <dirent.h>
-#include <zlib.h>
-#include <db.h>
-
-#if DB_VERSION_MAJOR < 5
-#error Citadel requires Berkeley DB v5.0 or newer. Please upgrade.
-#endif
-
-#include <libcitadel.h>
-#include "ctdl_module.h"
-#include "control.h"
-#include "citserver.h"
-#include "config.h"
-
-static DB *dbp[MAXCDB]; /* One DB handle for each Citadel database */
-static DB_ENV *dbenv; /* The DB environment (global) */
-
-
-void cdb_abort(void) {
- syslog(LOG_DEBUG, "db: citserver is stopping in order to prevent data loss. uid=%d gid=%d euid=%d egid=%d",
- getuid(), getgid(), geteuid(), getegid()
- );
- exit(CTDLEXIT_DB);
-}
-
-
-/* Verbose logging callback */
-void cdb_verbose_log(const DB_ENV * dbenv, const char *msg) {
- if (!IsEmptyStr(msg)) {
- syslog(LOG_DEBUG, "db: %s", msg);
- }
-}
-
-
-/* Verbose logging callback */
-void cdb_verbose_err(const DB_ENV * dbenv, const char *errpfx, const char *msg) {
- syslog(LOG_ERR, "db: %s", msg);
-}
-
-
-/* wrapper for txn_abort() that logs/aborts on error */
-static void txabort(DB_TXN *tid) {
- int ret;
-
- ret = tid->abort(tid);
-
- if (ret) {
- syslog(LOG_ERR, "db: txn_abort: %s", db_strerror(ret));
- cdb_abort();
- }
-}
-
-
-/* wrapper for txn_commit() that logs/aborts on error */
-static void txcommit(DB_TXN *tid) {
- int ret;
-
- ret = tid->commit(tid, 0);
-
- if (ret) {
- syslog(LOG_ERR, "db: txn_commit: %s", db_strerror(ret));
- cdb_abort();
- }
-}
-
-
-/* wrapper for txn_begin() that logs/aborts on error */
-static void txbegin(DB_TXN **tid) {
- int ret;
-
- ret = dbenv->txn_begin(dbenv, NULL, tid, 0);
-
- if (ret) {
- syslog(LOG_ERR, "db: txn_begin: %s", db_strerror(ret));
- cdb_abort();
- }
-}
-
-
-/* panic callback */
-static void dbpanic(DB_ENV * env, int errval) {
- syslog(LOG_ERR, "db: PANIC: %s", db_strerror(errval));
-}
-
-
-static void cclose(DBC * cursor) {
- int ret;
-
- if ((ret = cursor->c_close(cursor))) {
- syslog(LOG_ERR, "db: c_close: %s", db_strerror(ret));
- cdb_abort();
- }
-}
-
-
-static void bailIfCursor(DBC ** cursors, const char *msg) {
- int i;
-
- for (i = 0; i < MAXCDB; i++)
- if (cursors[i] != NULL) {
- syslog(LOG_ERR, "db: cursor still in progress on cdb %02x: %s", i, msg);
- cdb_abort();
- }
-}
-
-
-void cdb_check_handles(void) {
- bailIfCursor(TSD->cursors, "in check_handles");
-
- if (TSD->tid != NULL) {
- syslog(LOG_ERR, "db: transaction still in progress!");
- cdb_abort();
- }
-}
-
-
-/*
- * Request a checkpoint of the database. Called once per minute by the thread manager.
- */
-void cdb_checkpoint(void) {
- int ret;
-
- syslog(LOG_DEBUG, "db: -- checkpoint --");
- ret = dbenv->txn_checkpoint(dbenv, MAX_CHECKPOINT_KBYTES, MAX_CHECKPOINT_MINUTES, 0);
-
- if (ret != 0) {
- syslog(LOG_ERR, "db: cdb_checkpoint() txn_checkpoint: %s", db_strerror(ret));
- cdb_abort();
- }
-
- /* After a successful checkpoint, we can cull the unused logs */
- if (CtdlGetConfigInt("c_auto_cull")) {
- ret = dbenv->log_set_config(dbenv, DB_LOG_AUTO_REMOVE, 1);
- }
- else {
- ret = dbenv->log_set_config(dbenv, DB_LOG_AUTO_REMOVE, 0);
- }
-}
-
-
-/*
- * Open the various databases we'll be using. Any database which
- * does not exist should be created. Note that we don't need a
- * critical section here, because there aren't any active threads
- * manipulating the database yet.
- */
-void open_databases(void) {
- int ret;
- int i;
- char dbfilename[32];
- u_int32_t flags = 0;
- int dbversion_major, dbversion_minor, dbversion_patch;
-
- syslog(LOG_DEBUG, "db: open_databases() starting");
- syslog(LOG_DEBUG, "db: Compiled libdb: %s", DB_VERSION_STRING);
- syslog(LOG_DEBUG, "db: Linked libdb: %s", db_version(&dbversion_major, &dbversion_minor, &dbversion_patch));
- syslog(LOG_DEBUG, "db: Linked zlib: %s", zlibVersion());
-
- /*
- * Silently try to create the database subdirectory. If it's already there, no problem.
- */
- if ((mkdir(ctdl_db_dir, 0700) != 0) && (errno != EEXIST)) {
- syslog(LOG_ERR, "db: unable to create database directory [%s]: %m", ctdl_db_dir);
- }
- if (chmod(ctdl_db_dir, 0700) != 0) {
- syslog(LOG_ERR, "db: unable to set database directory permissions [%s]: %m", ctdl_db_dir);
- }
- if (chown(ctdl_db_dir, CTDLUID, (-1)) != 0) {
- syslog(LOG_ERR, "db: unable to set the owner for [%s]: %m", ctdl_db_dir);
- }
- syslog(LOG_DEBUG, "db: Setting up DB environment");
- // db_env_set_func_yield((int (*)(u_long, u_long))sched_yield);
- ret = db_env_create(&dbenv, 0);
- if (ret) {
- syslog(LOG_ERR, "db: db_env_create: %s", db_strerror(ret));
- syslog(LOG_ERR, "db: exit code %d", ret);
- exit(CTDLEXIT_DB);
- }
- dbenv->set_errpfx(dbenv, "citserver");
- dbenv->set_paniccall(dbenv, dbpanic);
- dbenv->set_errcall(dbenv, cdb_verbose_err);
- dbenv->set_errpfx(dbenv, "ctdl");
- dbenv->set_msgcall(dbenv, cdb_verbose_log);
- dbenv->set_verbose(dbenv, DB_VERB_DEADLOCK, 1);
- dbenv->set_verbose(dbenv, DB_VERB_RECOVERY, 1);
-
- /*
- * We want to specify the shared memory buffer pool cachesize,
- * but everything else is the default.
- */
- ret = dbenv->set_cachesize(dbenv, 0, 64 * 1024, 0);
- if (ret) {
- syslog(LOG_ERR, "db: set_cachesize: %s", db_strerror(ret));
- dbenv->close(dbenv, 0);
- syslog(LOG_ERR, "db: exit code %d", ret);
- exit(CTDLEXIT_DB);
- }
-
- if ((ret = dbenv->set_lk_detect(dbenv, DB_LOCK_DEFAULT))) {
- syslog(LOG_ERR, "db: set_lk_detect: %s", db_strerror(ret));
- dbenv->close(dbenv, 0);
- syslog(LOG_ERR, "db: exit code %d", ret);
- exit(CTDLEXIT_DB);
- }
-
- flags = DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE | DB_INIT_TXN | DB_INIT_LOCK | DB_THREAD | DB_INIT_LOG;
- syslog(LOG_DEBUG, "db: dbenv->open(dbenv, %s, %d, 0)", ctdl_db_dir, flags);
- ret = dbenv->open(dbenv, ctdl_db_dir, flags, 0); // try opening the database cleanly
- if (ret == DB_RUNRECOVERY) {
- syslog(LOG_ERR, "db: dbenv->open: %s", db_strerror(ret));
- syslog(LOG_ERR, "db: attempting recovery...");
- flags |= DB_RECOVER;
- ret = dbenv->open(dbenv, ctdl_db_dir, flags, 0); // try recovery
- }
- if (ret == DB_RUNRECOVERY) {
- syslog(LOG_ERR, "db: dbenv->open: %s", db_strerror(ret));
- syslog(LOG_ERR, "db: attempting catastrophic recovery...");
- flags &= ~DB_RECOVER;
- flags |= DB_RECOVER_FATAL;
- ret = dbenv->open(dbenv, ctdl_db_dir, flags, 0); // try catastrophic recovery
- }
- if (ret) {
- syslog(LOG_ERR, "db: dbenv->open: %s", db_strerror(ret));
- dbenv->close(dbenv, 0);
- syslog(LOG_ERR, "db: exit code %d", ret);
- exit(CTDLEXIT_DB);
- }
-
- syslog(LOG_INFO, "db: mounting databases");
- for (i = 0; i < MAXCDB; ++i) {
- ret = db_create(&dbp[i], dbenv, 0); // Create a database handle
- if (ret) {
- syslog(LOG_ERR, "db: db_create: %s", db_strerror(ret));
- syslog(LOG_ERR, "db: exit code %d", ret);
- exit(CTDLEXIT_DB);
- }
-
- snprintf(dbfilename, sizeof dbfilename, "cdb.%02x", i); // table names by number
- ret = dbp[i]->open(dbp[i], NULL, dbfilename, NULL, DB_BTREE, DB_CREATE | DB_AUTO_COMMIT | DB_THREAD, 0600);
- if (ret) {
- syslog(LOG_ERR, "db: db_open[%02x]: %s", i, db_strerror(ret));
- if (ret == ENOMEM) {
- syslog(LOG_ERR, "db: You may need to tune your database; please read http://www.citadel.org/doku.php?id=faq:troubleshooting:out_of_lock_entries for more information.");
- }
- syslog(LOG_ERR, "db: exit code %d", ret);
- exit(CTDLEXIT_DB);
- }
- }
-}
-
-
-/*
- * Make sure we own all the files, because in a few milliseconds we're going to drop root privs.
- */
-void cdb_chmod_data(void) {
- DIR *dp;
- struct dirent *d;
- char filename[PATH_MAX];
-
- dp = opendir(ctdl_db_dir);
- if (dp != NULL) {
- while (d = readdir(dp), d != NULL) {
- if (d->d_name[0] != '.') {
- snprintf(filename, sizeof filename, "%s/%s", ctdl_db_dir, d->d_name);
- syslog(LOG_DEBUG, "db: chmod(%s, 0600) returned %d", filename, chmod(filename, 0600));
- syslog(LOG_DEBUG, "db: chown(%s, CTDLUID, -1) returned %d",
- filename, chown(filename, CTDLUID, (-1))
- );
- }
- }
- closedir(dp);
- }
-}
-
-
-/*
- * Close all of the db database files we've opened. This can be done
- * in a loop, since it's just a bunch of closes.
- */
-void close_databases(void) {
- int i;
- int ret;
-
- 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));
- }
-
- syslog(LOG_INFO, "db: flushing the database logs");
- if ((ret = dbenv->log_flush(dbenv, NULL))) {
- syslog(LOG_ERR, "db: log_flush: %s", db_strerror(ret));
- }
-
- /* close the tables */
- syslog(LOG_INFO, "db: closing databases");
- for (i = 0; i < MAXCDB; ++i) {
- syslog(LOG_INFO, "db: closing database %02x", i);
- ret = dbp[i]->close(dbp[i], 0);
- 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
- // /* print some statistics... */
- // dbenv->lock_stat_print(dbenv, DB_STAT_ALL);
- // #endif
-
- /* Close the handle. */
- ret = dbenv->close(dbenv, 0);
- if (ret) {
- syslog(LOG_ERR, "db: DBENV->close: %s", db_strerror(ret));
- }
-}
-
-
-/*
- * Decompress a database item if it was compressed on disk
- */
-void cdb_decompress_if_necessary(struct cdbdata *cdb) {
- static int magic = COMPRESS_MAGIC;
-
- if ((cdb == NULL) || (cdb->ptr == NULL) || (cdb->len < sizeof(magic)) || (memcmp(cdb->ptr, &magic, sizeof(magic)))) {
- return;
- }
-
- /* At this point we know we're looking at a compressed item. */
-
- struct CtdlCompressHeader zheader;
- char *uncompressed_data;
- char *compressed_data;
- uLongf destLen, sourceLen;
- size_t cplen;
-
- memset(&zheader, 0, sizeof(struct CtdlCompressHeader));
- cplen = sizeof(struct CtdlCompressHeader);
- if (sizeof(struct CtdlCompressHeader) > cdb->len) {
- cplen = cdb->len;
- }
- memcpy(&zheader, cdb->ptr, cplen);
-
- compressed_data = cdb->ptr;
- compressed_data += sizeof(struct CtdlCompressHeader);
-
- sourceLen = (uLongf) zheader.compressed_len;
- destLen = (uLongf) zheader.uncompressed_len;
- uncompressed_data = malloc(zheader.uncompressed_len);
-
- if (uncompress((Bytef *) uncompressed_data,
- (uLongf *) & destLen, (const Bytef *) compressed_data, (uLong) sourceLen) != Z_OK) {
- syslog(LOG_ERR, "db: uncompress() error");
- cdb_abort();
- }
-
- free(cdb->ptr);
- cdb->len = (size_t) destLen;
- cdb->ptr = uncompressed_data;
-}
-
-
-/*
- * Store a piece of data. Returns 0 if the operation was successful. If a
- * key already exists it should be overwritten.
- */
-int cdb_store(int cdb, const void *ckey, int ckeylen, void *cdata, int cdatalen) {
-
- DBT dkey, ddata;
- DB_TXN *tid = NULL;
- int ret = 0;
- struct CtdlCompressHeader zheader;
- char *compressed_data = NULL;
- int compressing = 0;
- size_t buffer_len = 0;
- uLongf destLen = 0;
-
- memset(&dkey, 0, sizeof(DBT));
- memset(&ddata, 0, sizeof(DBT));
- dkey.size = ckeylen;
- dkey.data = (void *) ckey;
- ddata.size = cdatalen;
- ddata.data = cdata;
-
- /* Only compress Visit and UseTable records. Everything else is uncompressed. */
- if ((cdb == CDB_VISIT) || (cdb == CDB_USETABLE)) {
- compressing = 1;
- zheader.magic = COMPRESS_MAGIC;
- zheader.uncompressed_len = cdatalen;
- buffer_len = ((cdatalen * 101) / 100) + 100 + sizeof(struct CtdlCompressHeader);
- destLen = (uLongf) buffer_len;
- compressed_data = malloc(buffer_len);
- if (compress2((Bytef *) (compressed_data + sizeof(struct CtdlCompressHeader)),
- &destLen, (Bytef *) cdata, (uLongf) cdatalen, 1) != Z_OK) {
- syslog(LOG_ERR, "db: compress2() error");
- cdb_abort();
- }
- zheader.compressed_len = (size_t) destLen;
- memcpy(compressed_data, &zheader, sizeof(struct CtdlCompressHeader));
- ddata.size = (size_t) (sizeof(struct CtdlCompressHeader) + zheader.compressed_len);
- ddata.data = compressed_data;
- }
-
- if (TSD->tid != NULL) {
- ret = dbp[cdb]->put(dbp[cdb], // db
- TSD->tid, // transaction ID
- &dkey, // key
- &ddata, // data
- 0 // flags
- );
- if (ret) {
- syslog(LOG_ERR, "db: cdb_store(%d): %s", cdb, db_strerror(ret));
- cdb_abort();
- }
- if (compressing) {
- free(compressed_data);
- }
- return ret;
- } else {
- bailIfCursor(TSD->cursors, "attempt to write during r/o cursor");
-
- retry:
- txbegin(&tid);
-
- if ((ret = dbp[cdb]->put(dbp[cdb], // db
- tid, // transaction ID
- &dkey, // key
- &ddata, // data
- 0))) { // flags
- if (ret == DB_LOCK_DEADLOCK) {
- txabort(tid);
- goto retry;
- } else {
- syslog(LOG_ERR, "db: cdb_store(%d): %s", cdb, db_strerror(ret));
- cdb_abort();
- }
- } else {
- txcommit(tid);
- if (compressing) {
- free(compressed_data);
- }
- return ret;
- }
- }
- return ret;
-}
-
-
-/*
- * Delete a piece of data. Returns 0 if the operation was successful.
- */
-int cdb_delete(int cdb, void *key, int keylen) {
- DBT dkey;
- DB_TXN *tid;
- int ret;
-
- memset(&dkey, 0, sizeof dkey);
- dkey.size = keylen;
- dkey.data = key;
-
- if (TSD->tid != NULL) {
- ret = dbp[cdb]->del(dbp[cdb], TSD->tid, &dkey, 0);
- if (ret) {
- syslog(LOG_ERR, "db: cdb_delete(%d): %s", cdb, db_strerror(ret));
- if (ret != DB_NOTFOUND) {
- cdb_abort();
- }
- }
- } else {
- bailIfCursor(TSD->cursors, "attempt to delete during r/o cursor");
-
- retry:
- txbegin(&tid);
-
- if ((ret = dbp[cdb]->del(dbp[cdb], tid, &dkey, 0)) && ret != DB_NOTFOUND) {
- if (ret == DB_LOCK_DEADLOCK) {
- txabort(tid);
- goto retry;
- } else {
- syslog(LOG_ERR, "db: cdb_delete(%d): %s", cdb, db_strerror(ret));
- cdb_abort();
- }
- } else {
- txcommit(tid);
- }
- }
- return ret;
-}
-
-
-static DBC *localcursor(int cdb) {
- int ret;
- DBC *curs;
-
- if (TSD->cursors[cdb] == NULL) {
- ret = dbp[cdb]->cursor(dbp[cdb], TSD->tid, &curs, 0);
- }
- else {
- ret = TSD->cursors[cdb]->c_dup(TSD->cursors[cdb], &curs, DB_POSITION);
- }
-
- if (ret) {
- syslog(LOG_ERR, "db: localcursor: %s", db_strerror(ret));
- cdb_abort();
- }
-
- return curs;
-}
-
-
-/*
- * Fetch a piece of data. If not found, returns NULL. Otherwise, it returns
- * a struct cdbdata which it is the caller's responsibility to free later on
- * using the cdb_free() routine.
- */
-struct cdbdata *cdb_fetch(int cdb, const void *key, int keylen) {
-
- if (keylen == 0) { // key length zero is impossible
- return(NULL);
- }
-
- struct cdbdata *tempcdb;
- DBT dkey, dret;
- int ret;
-
- memset(&dkey, 0, sizeof(DBT));
- dkey.size = keylen;
- dkey.data = (void *) key;
-
- if (TSD->tid != NULL) {
- memset(&dret, 0, sizeof(DBT));
- dret.flags = DB_DBT_MALLOC;
- ret = dbp[cdb]->get(dbp[cdb], TSD->tid, &dkey, &dret, 0);
- }
- else {
- DBC *curs;
-
- do {
- memset(&dret, 0, sizeof(DBT));
- dret.flags = DB_DBT_MALLOC;
- curs = localcursor(cdb);
- ret = curs->c_get(curs, &dkey, &dret, DB_SET);
- cclose(curs);
- } while (ret == DB_LOCK_DEADLOCK);
- }
-
- if ((ret != 0) && (ret != DB_NOTFOUND)) {
- syslog(LOG_ERR, "db: cdb_fetch(%d): %s", cdb, db_strerror(ret));
- cdb_abort();
- }
-
- if (ret != 0) {
- return NULL;
- }
-
- tempcdb = (struct cdbdata *) malloc(sizeof(struct cdbdata));
- if (tempcdb == NULL) {
- syslog(LOG_ERR, "db: cdb_fetch() cannot allocate memory for tempcdb: %m");
- cdb_abort();
- return NULL; /* make it easier for static analysis... */
- }
- else {
- tempcdb->len = dret.size;
- tempcdb->ptr = dret.data;
- cdb_decompress_if_necessary(tempcdb);
- return (tempcdb);
- }
-}
-
-
-/*
- * Free a cdbdata item.
- *
- * Note that we only free the 'ptr' portion if it is not NULL. This allows
- * other code to assume ownership of that memory simply by storing the
- * pointer elsewhere and then setting 'ptr' to NULL. cdb_free() will then
- * avoid freeing it.
- */
-void cdb_free(struct cdbdata *cdb) {
- if (cdb->ptr) {
- free(cdb->ptr);
- }
- free(cdb);
-}
-
-
-void cdb_close_cursor(int cdb) {
- if (TSD->cursors[cdb] != NULL) {
- cclose(TSD->cursors[cdb]);
- }
-
- TSD->cursors[cdb] = NULL;
-}
-
-
-/*
- * Prepare for a sequential search of an entire database.
- * (There is guaranteed to be no more than one traversal in
- * progress per thread at any given time.)
- */
-void cdb_rewind(int cdb) {
- int ret = 0;
-
- if (TSD->cursors[cdb] != NULL) {
- syslog(LOG_ERR, "db: cdb_rewind: must close cursor on database %d before reopening", cdb);
- cdb_abort();
- /* cclose(TSD->cursors[cdb]); */
- }
-
- /*
- * Now initialize the cursor
- */
- ret = dbp[cdb]->cursor(dbp[cdb], TSD->tid, &TSD->cursors[cdb], 0);
- if (ret) {
- syslog(LOG_ERR, "db: cdb_rewind: db_cursor: %s", db_strerror(ret));
- cdb_abort();
- }
-}
-
-
-/*
- * Fetch the next item in a sequential search. Returns a pointer to a
- * cdbdata structure, or NULL if we've hit the end.
- */
-struct cdbdata *cdb_next_item(int cdb) {
- DBT key, data;
- struct cdbdata *cdbret;
- int ret = 0;
-
- /* Initialize the key/data pair so the flags aren't set. */
- memset(&key, 0, sizeof(key));
- memset(&data, 0, sizeof(data));
- data.flags = DB_DBT_MALLOC;
-
- ret = TSD->cursors[cdb]->c_get(TSD->cursors[cdb], &key, &data, DB_NEXT);
-
- if (ret) {
- if (ret != DB_NOTFOUND) {
- syslog(LOG_ERR, "db: cdb_next_item(%d): %s", cdb, db_strerror(ret));
- cdb_abort();
- }
- cdb_close_cursor(cdb);
- return NULL; /* presumably, end of file */
- }
-
- cdbret = (struct cdbdata *) malloc(sizeof(struct cdbdata));
- cdbret->len = data.size;
- cdbret->ptr = data.data;
- cdb_decompress_if_necessary(cdbret);
-
- return (cdbret);
-}
-
-
-/*
- * Transaction-based stuff. I'm writing this as I bake cookies...
- */
-void cdb_begin_transaction(void) {
- bailIfCursor(TSD->cursors, "can't begin transaction during r/o cursor");
-
- if (TSD->tid != NULL) {
- syslog(LOG_ERR, "db: cdb_begin_transaction: ERROR: nested transaction");
- cdb_abort();
- }
-
- txbegin(&TSD->tid);
-}
-
-
-void cdb_end_transaction(void) {
- int i;
-
- for (i = 0; i < MAXCDB; i++) {
- if (TSD->cursors[i] != NULL) {
- syslog(LOG_WARNING, "db: cdb_end_transaction: WARNING: cursor %d still open at transaction end", i);
- cclose(TSD->cursors[i]);
- TSD->cursors[i] = NULL;
- }
- }
-
- if (TSD->tid == NULL) {
- syslog(LOG_ERR, "db: cdb_end_transaction: ERROR: txcommit(NULL) !!");
- cdb_abort();
- }
- else {
- txcommit(TSD->tid);
- }
-
- TSD->tid = NULL;
-}
-
-
-/*
- * Truncate (delete every record)
- */
-void cdb_trunc(int cdb) {
- /* DB_TXN *tid; */
- int ret;
- u_int32_t count;
-
- if (TSD->tid != NULL) {
- syslog(LOG_ERR, "db: cdb_trunc must not be called in a transaction.");
- cdb_abort();
- }
- else {
- bailIfCursor(TSD->cursors, "attempt to write during r/o cursor");
-
- retry:
- /* txbegin(&tid); */
-
- if ((ret = dbp[cdb]->truncate(dbp[cdb], /* db */
- NULL, /* transaction ID */
- &count, /* #rows deleted */
- 0))) { /* flags */
- if (ret == DB_LOCK_DEADLOCK) {
- /* txabort(tid); */
- goto retry;
- } else {
- syslog(LOG_ERR, "db: cdb_truncate(%d): %s", cdb, db_strerror(ret));
- if (ret == ENOMEM) {
- syslog(LOG_ERR, "db: You may need to tune your database; please read http://www.citadel.org/doku.php?id=faq:troubleshooting:out_of_lock_entries for more information.");
- }
- exit(CTDLEXIT_DB);
- }
- }
- else {
- /* txcommit(tid); */
- }
- }
-}
-
-
-// compact (defragment) the database, possibly returning space back to the underlying filesystem
-void cdb_compact(void) {
- int ret;
- int i;
-
- syslog(LOG_DEBUG, "db: cdb_compact() started");
- for (i = 0; i < MAXCDB; i++) {
- syslog(LOG_DEBUG, "db: compacting database %d", i);
- ret = dbp[i]->compact(dbp[i], NULL, NULL, NULL, NULL, DB_FREE_SPACE, NULL);
- if (ret) {
- syslog(LOG_ERR, "db: compact: %s", db_strerror(ret));
- }
- }
- syslog(LOG_DEBUG, "db: cdb_compact() finished");
-}
-
-
-// Has an item already been seen (is it in the CDB_USETABLE) ?
-// Returns 0 if it hasn't, 1 if it has
-// In either case, writes the item to the database for next time.
-int CheckIfAlreadySeen(StrBuf *guid) {
- int found = 0;
- struct UseTable ut;
- struct cdbdata *cdbut;
-
- syslog(LOG_DEBUG, "db: CheckIfAlreadySeen(%s)", ChrPtr(guid));
- cdbut = cdb_fetch(CDB_USETABLE, SKEY(guid));
- if (cdbut != NULL) {
- found = 1;
- cdb_free(cdbut);
- }
-
- /* (Re)write the record, to update the timestamp. Zeroing it out makes it compress better. */
- memset(&ut, 0, sizeof(struct UseTable));
- memcpy(ut.ut_msgid, SKEY(guid));
- ut.ut_timestamp = time(NULL);
- cdb_store(CDB_USETABLE, SKEY(guid), &ut, sizeof(struct UseTable));
- return (found);
-}
-
-
-CTDL_MODULE_INIT(database)
-{
- if (!threading) {
- // nothing to do here
- }
-
- /* return our module id for the log */
- return "database";
-}
+++ /dev/null
-/*
- * Copyright (c) 1987-2017 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- */
-
-#ifndef DATABASE_H
-#define DATABASE_H
-
-
-void open_databases (void);
-void close_databases (void);
-int cdb_store (int cdb, const void *key, int keylen, void *data, int datalen);
-int cdb_delete (int cdb, void *key, int keylen);
-struct cdbdata *cdb_fetch (int cdb, const void *key, int keylen);
-void cdb_free (struct cdbdata *cdb);
-void cdb_rewind (int cdb);
-struct cdbdata *cdb_next_item (int cdb);
-void cdb_close_cursor(int cdb);
-void cdb_begin_transaction(void);
-void cdb_end_transaction(void);
-void cdb_allocate_tsd(void);
-void cdb_free_tsd(void);
-void cdb_check_handles(void);
-void cdb_trunc(int cdb);
-void *checkpoint_thread(void *arg);
-void cdb_chmod_data(void);
-void cdb_checkpoint(void);
-void check_handles(void *arg);
-void cdb_cull_logs(void);
-void cdb_compact(void);
-
-
-/*
- * Database records beginning with this magic number are assumed to
- * be compressed. In the event that a database record actually begins with
- * this magic number, we *must* compress it whether we want to or not,
- * because the fetch function will try to uncompress it anyway.
- *
- * (No need to #ifdef this stuff; it compiles ok even if zlib is not present
- * and doesn't declare anything so it won't bloat the code)
- */
-#define COMPRESS_MAGIC 0xc0ffeeee
-
-struct CtdlCompressHeader {
- int magic;
- size_t uncompressed_len;
- size_t compressed_len;
-};
-
-int CheckIfAlreadySeen(StrBuf *guid);
-
-#endif /* DATABASE_H */
-
+++ /dev/null
-/*
- * Copyright (c) 1987-2018 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.
- */
-
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <limits.h>
-#include <stdio.h>
-#include <string.h>
-#include <strings.h>
-#include <syslog.h>
-#include <libical/ical.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "sysdep_decls.h"
-#include "support.h"
-#include "config.h"
-#include "default_timezone.h"
-#include "ctdl_module.h"
-
-
-/*
- * Figure out which time zone needs to be used for timestamps that are
- * not UTC and do not have a time zone specified.
- */
-icaltimezone *get_default_icaltimezone(void) {
-
- icaltimezone *zone = NULL;
- char *default_zone_name = CtdlGetConfigStr("c_default_cal_zone");
-
- if (!zone) {
- zone = icaltimezone_get_builtin_timezone(default_zone_name);
- }
- if (!zone) {
- syslog(LOG_ERR, "ical: Unable to load '%s' time zone. Defaulting to UTC.", default_zone_name);
- zone = icaltimezone_get_utc_timezone();
- }
- if (!zone) {
- syslog(LOG_ERR, "ical: unable to load UTC time zone!");
- }
-
- return zone;
-}
+++ /dev/null
-icaltimezone *get_default_icaltimezone(void);
+++ /dev/null
-/*
- * DNS lookup for SMTP sender
- *
- * Copyright (c) 1987-2021 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.
- */
-
-#include "sysdep.h"
-#include <stdio.h>
-#include <syslog.h>
-#ifdef HAVE_RESOLV_H
-#include <arpa/nameser.h>
-#ifdef HAVE_ARPA_NAMESER_COMPAT_H
-#include <arpa/nameser_compat.h>
-#endif
-#ifdef __FreeBSD__
-#include <netinet/in.h>
-#endif
-#include <resolv.h>
-#endif
-#include <libcitadel.h>
-#include "sysdep_decls.h"
-#include "citadel.h"
-#include "domain.h"
-#include "internet_addressing.h"
-
-
-/*
- * get_hosts() checks the Internet configuration for various types of
- * entries and returns them in the same format as getmx() does -- fill the
- * buffer with a delimited list of hosts and return the number of hosts.
- *
- * This is used to fetch MX smarthosts, SpamAssassin hosts, etc.
- */
-int get_hosts(char *mxbuf, char *rectype) {
- int config_lines;
- int i;
- char buf[256];
- char host[256], type[256];
- int total_smarthosts = 0;
-
- if (inetcfg == NULL) return(0);
- strcpy(mxbuf, "");
-
- config_lines = num_tokens(inetcfg, '\n');
- for (i=0; i<config_lines; ++i) {
- extract_token(buf, inetcfg, i, '\n', sizeof buf);
- extract_token(host, buf, 0, '|', sizeof host);
- extract_token(type, buf, 1, '|', sizeof type);
-
- if (!strcasecmp(type, rectype)) {
- strcat(mxbuf, host);
- strcat(mxbuf, "|");
- ++total_smarthosts;
- }
- }
-
- return(total_smarthosts);
-}
-
-
-/*
- * Compare the preference of two MX records. First check by the actual
- * number listed in the MX record. If they're identical, randomize the
- * result.
- */
-int mx_compare_pref(const void *mx1, const void *mx2) {
- int pref1;
- int pref2;
-
- pref1 = ((const struct mx *)mx1)->pref;
- pref2 = ((const struct mx *)mx2)->pref;
-
- if (pref1 > pref2) {
- return(1);
- }
- else if (pref1 < pref2) {
- return(0);
- }
- else {
- return(rand() % 2);
- }
-}
-
-
-/*
- * getmx()
- *
- * Return one or more MX's for a mail destination.
- *
- * Upon success, it fills 'mxbuf' with one or more MX hosts, separated by
- * vertical bar characters, and returns the number of hosts as its return
- * value. If no MX's are found, it returns 0.
- *
- */
-int getmx(char *mxbuf, char *dest) {
-
-#ifdef HAVE_RESOLV_H
- union {
- u_char bytes[1024];
- HEADER header;
- } answer;
-#endif
-
- int ret;
- unsigned char *startptr, *endptr, *ptr;
- char expanded_buf[1024];
- unsigned short pref, type;
- int n = 0;
- int qdcount;
- Array *mxrecords = NULL;
- struct mx mx;
-
- // If we're configured to send all mail to a smart-host, then our job here is really easy -- just return those.
- n = get_hosts(mxbuf, "smarthost");
- if (n > 0) {
- return(n);
- }
-
- mxrecords = array_new(sizeof(struct mx));
-
- // No smart-host? Look up the best MX for a site. Make a call to the resolver library.
- ret = res_query(dest, C_IN, T_MX, (unsigned char *)answer.bytes, sizeof(answer));
-
- if (ret < 0) {
- mx.pref = 0;
- strcpy(mx.host, dest);
- array_append(mxrecords, &mx);
- }
- else {
- if (ret > sizeof(answer)) { // If we had to truncate, shrink the number to avoid fireworks
- ret = sizeof(answer);
- }
-
- startptr = &answer.bytes[0]; // start and end of buffer
- endptr = &answer.bytes[ret];
- ptr = startptr + HFIXEDSZ; // advance past header
-
- for (qdcount = ntohs(answer.header.qdcount); qdcount--; ptr += ret + QFIXEDSZ) {
- if ((ret = dn_skipname(ptr, endptr)) < 0) {
- syslog(LOG_DEBUG, "domain: dn_skipname error");
- return(0);
- }
- }
-
- while(1) {
- memset(expanded_buf, 0, sizeof(expanded_buf));
- ret = dn_expand(startptr, endptr, ptr, expanded_buf, sizeof(expanded_buf));
- if (ret < 0) break;
- ptr += ret;
-
- GETSHORT(type, ptr);
- ptr += INT16SZ + INT32SZ;
- GETSHORT(n, ptr);
-
- if (type != T_MX) {
- ptr += n;
- }
-
- else {
- GETSHORT(pref, ptr);
- ret = dn_expand(startptr, endptr, ptr, expanded_buf, sizeof(expanded_buf));
- ptr += ret;
-
- mx.pref = pref;
- strcpy(mx.host, expanded_buf);
- array_append(mxrecords, &mx);
- }
- }
- }
-
- // Sort the MX records by preference
- if (array_len(mxrecords) > 1) {
- array_sort(mxrecords, mx_compare_pref);
- }
-
- int num_mxrecs = array_len(mxrecords);
- strcpy(mxbuf, "");
- for (n=0; n<num_mxrecs; ++n) {
- strcat(mxbuf, ((struct mx *)array_get_element_at(mxrecords, n))->host);
- strcat(mxbuf, "|");
- }
- array_free(mxrecords);
-
- // Append any fallback smart hosts we have configured.
- num_mxrecs += get_hosts(&mxbuf[strlen(mxbuf)], "fallbackhost");
- return(num_mxrecs);
-}
+++ /dev/null
-/*
- * Copyright (c) 1987-2012 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.
- */
-
-struct mx {
- int pref;
- char host[1024];
-};
-
-int getmx(char *mxbuf, char *dest);
-int get_hosts(char *mxbuf, char *rectype);
-
-
-/* HP/UX has old include files...these are from arpa/nameser.h */
-
-#include "typesize.h"
-
-#ifndef HFIXEDSZ
-#define HFIXEDSZ 12 /* I hope! */
-#endif
-#ifndef INT16SZ
-#define INT16SZ sizeof(cit_int16_t)
-#endif
-#ifndef INT32SZ
-#define INT32SZ sizeof(cit_int32_t)
-#endif
+++ /dev/null
-// Index messages by EUID per room.
-//
-// Copyright (c) 1987-2022 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.
-
-#include "sysdep.h"
-#include <stdio.h>
-#include <libcitadel.h>
-
-#include "citserver.h"
-#include "room_ops.h"
-
-// The structure of an euidindex record *key* is:
-//
-// |----room_number----|----------EUID-------------|
-// (sizeof long) (actual length of euid)
-//
-//
-// The structure of an euidindex record *value* is:
-//
-// |-----msg_number----|----room_number----|----------EUID-------------|
-// (sizeof long) (sizeof long) (actual length of euid)
-
-// Return nonzero if the supplied room is one which should have
-// an EUID index.
-int DoesThisRoomNeedEuidIndexing(struct ctdlroom *qrbuf) {
-
- switch(qrbuf->QRdefaultview) {
- case VIEW_BBS: return(0);
- case VIEW_MAILBOX: return(0);
- case VIEW_ADDRESSBOOK: return(1);
- case VIEW_DRAFTS: return(0);
- case VIEW_CALENDAR: return(1);
- case VIEW_TASKS: return(1);
- case VIEW_NOTES: return(1);
- case VIEW_WIKI: return(1);
- case VIEW_BLOG: return(1);
- }
-
- return(0);
-}
-
-
-// Locate a message in a given room with a given euid, and return
-// its message number.
-long locate_message_by_euid(char *euid, struct ctdlroom *qrbuf) {
- return CtdlLocateMessageByEuid (euid, qrbuf);
-}
-
-
-long CtdlLocateMessageByEuid(char *euid, struct ctdlroom *qrbuf) {
- char *key;
- int key_len;
- struct cdbdata *cdb_euid;
- long msgnum = (-1L);
-
- syslog(LOG_DEBUG, "euidindex: searching for EUID <%s> in <%s>", euid, qrbuf->QRname);
-
- key_len = strlen(euid) + sizeof(long) + 1;
- key = malloc(key_len);
- memcpy(key, &qrbuf->QRnumber, sizeof(long));
- strcpy(&key[sizeof(long)], euid);
-
- cdb_euid = cdb_fetch(CDB_EUIDINDEX, key, key_len);
- free(key);
-
- if (cdb_euid == NULL) {
- msgnum = (-1L);
- }
- else {
- // The first (sizeof long) of the record is what we're looking for. Throw away the rest.
- memcpy(&msgnum, cdb_euid->ptr, sizeof(long));
- cdb_free(cdb_euid);
- }
- syslog(LOG_DEBUG, "euidindex: returning msgnum = %ld", msgnum);
- return(msgnum);
-}
-
-
-// Store the euid index for a message, which has presumably just been
-// stored in this room by the caller.
-void index_message_by_euid(char *euid, struct ctdlroom *qrbuf, long msgnum) {
- char *key;
- int key_len;
- char *data;
- int data_len;
-
- syslog(LOG_DEBUG, "euidindex: indexing message #%ld <%s> in <%s>", msgnum, euid, qrbuf->QRname);
-
- key_len = strlen(euid) + sizeof(long) + 1;
- key = malloc(key_len);
- memcpy(key, &qrbuf->QRnumber, sizeof(long));
- strcpy(&key[sizeof(long)], euid);
-
- data_len = sizeof(long) + key_len;
- data = malloc(data_len);
-
- memcpy(data, &msgnum, sizeof(long));
- memcpy(&data[sizeof(long)], key, key_len);
-
- cdb_store(CDB_EUIDINDEX, key, key_len, data, data_len);
- free(key);
- free(data);
-}
-
-
-// Called by rebuild_euid_index_for_room() to index one message.
-void rebuild_euid_index_for_msg(long msgnum, void *userdata) {
- struct CtdlMessage *msg = NULL;
-
- msg = CtdlFetchMessage(msgnum, 0);
- if (msg == NULL) return;
- if (!CM_IsEmpty(msg, eExclusiveID)) {
- index_message_by_euid(msg->cm_fields[eExclusiveID], &CC->room, msgnum);
- }
- CM_Free(msg);
-}
-
-
-void rebuild_euid_index_for_room(struct ctdlroom *qrbuf, void *data) {
- static struct RoomProcList *rplist = NULL;
- struct RoomProcList *ptr;
- struct ctdlroom qr;
-
- // Lazy programming here. Call this function as a CtdlForEachRoom backend
- // in order to queue up the room names, or call it with a null room
- // to make it do the processing.
- if (qrbuf != NULL) {
- ptr = (struct RoomProcList *)
- malloc(sizeof (struct RoomProcList));
- if (ptr == NULL) return;
-
- safestrncpy(ptr->name, qrbuf->QRname, sizeof ptr->name);
- ptr->next = rplist;
- rplist = ptr;
- return;
- }
-
- while (rplist != NULL) {
- if (CtdlGetRoom(&qr, rplist->name) == 0) {
- if (DoesThisRoomNeedEuidIndexing(&qr)) {
- syslog(LOG_DEBUG,
- "euidindex: rebuilding EUID index for <%s>",
- rplist->name);
- CtdlUserGoto(rplist->name, 0, 0, NULL, NULL, NULL, NULL);
- CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, rebuild_euid_index_for_msg, NULL);
- }
- }
- ptr = rplist;
- rplist = rplist->next;
- free(ptr);
- }
-}
-
-
-// Globally rebuild the EUID indices in every room.
-void rebuild_euid_index(void) {
- cdb_trunc(CDB_EUIDINDEX); // delete the old indices
- CtdlForEachRoom(rebuild_euid_index_for_room, NULL); // enumerate room names
- rebuild_euid_index_for_room(NULL, NULL); // and index them
-}
-
-
-// Server command to fetch a message number given an euid.
-void cmd_euid(char *cmdbuf) {
- char euid[256];
- long msgnum;
- struct cdbdata *cdbfr;
- long *msglist = NULL;
- int num_msgs = 0;
- int i;
-
- if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
- extract_token(euid, cmdbuf, 0, '|', sizeof euid);
- msgnum = CtdlLocateMessageByEuid(euid, &CC->room);
- if (msgnum <= 0L) {
- cprintf("%d not found\n", ERROR + MESSAGE_NOT_FOUND);
- return;
- }
-
- cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
- if (cdbfr != NULL) {
- num_msgs = cdbfr->len / sizeof(long);
- msglist = (long *) cdbfr->ptr;
- for (i = 0; i < num_msgs; ++i) {
- if (msglist[i] == msgnum) {
- cdb_free(cdbfr);
- cprintf("%d %ld\n", CIT_OK, msgnum);
- return;
- }
- }
- cdb_free(cdbfr);
- }
-
- cprintf("%d not found\n", ERROR + MESSAGE_NOT_FOUND);
-}
-
-
-CTDL_MODULE_INIT(euidindex)
-{
- if (!threading) {
- CtdlRegisterProtoHook(cmd_euid, "EUID", "Perform operations on Extended IDs for messages");
- }
- /* return our Subversion id for the Log */
- return "euidindex";
-}
+++ /dev/null
-/*
- * Index messages by EUID per room.
- */
-
-int DoesThisRoomNeedEuidIndexing(struct ctdlroom *qrbuf);
-/* locate_message_by_euid is deprecated. Use CtdlLocateMessageByEuid instead */
-long locate_message_by_euid(char *euid, struct ctdlroom *qrbuf) __attribute__ ((deprecated));
-void index_message_by_euid(char *euid, struct ctdlroom *qrbuf, long msgnum);
-void rebuild_euid_index(void);
+++ /dev/null
-/*
- * Function to generate RFC822-compliant textual time/date stamp
- */
-
-#include "sysdep.h"
-#include <stdio.h>
-#include <stdlib.h>
-#include <ctype.h>
-#include <string.h>
-#include <time.h>
-#include "genstamp.h"
-
-static char *months[] = {
- "Jan", "Feb", "Mar", "Apr", "May", "Jun",
- "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
-};
-
-static char *weekdays[] = {
- "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
-};
-
-/*
- * Supplied with a unix timestamp, generate an RFC822-compliant textual
- * time and date stamp.
- */
-long datestring(char *buf, size_t n, time_t xtime, int which_format) {
- struct tm t;
-
- long offset;
- char offsign;
-
- localtime_r(&xtime, &t);
-
- /* Convert "seconds west of GMT" to "hours/minutes offset" */
-#ifdef HAVE_STRUCT_TM_TM_GMTOFF
- offset = t.tm_gmtoff;
-#else
- offset = timezone;
-#endif
- if (offset > 0) {
- offsign = '+';
- }
- else {
- offset = 0L - offset;
- offsign = '-';
- }
- offset = ( (offset / 3600) * 100 ) + ( offset % 60 );
-
- switch(which_format) {
-
- case DATESTRING_RFC822:
- return snprintf(
- buf, n,
- "%s, %02d %s %04d %02d:%02d:%02d %c%04ld",
- weekdays[t.tm_wday],
- t.tm_mday,
- months[t.tm_mon],
- t.tm_year + 1900,
- t.tm_hour,
- t.tm_min,
- t.tm_sec,
- offsign, offset
- );
- break;
-
- case DATESTRING_IMAP:
- return snprintf(
- buf, n,
- "%02d-%s-%04d %02d:%02d:%02d %c%04ld",
- t.tm_mday,
- months[t.tm_mon],
- t.tm_year + 1900,
- t.tm_hour,
- t.tm_min,
- t.tm_sec,
- offsign, offset
- );
- break;
-
- }
- return 0;
-}
+++ /dev/null
-
-long datestring(char *buf, size_t n, time_t xtime, int which_format);
-
-enum {
- DATESTRING_RFC822,
- DATESTRING_IMAP
-};
+++ /dev/null
-/*
- * This file contains miscellaneous housekeeping tasks.
- *
- * Copyright (c) 1987-2021 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.
- */
-
-#include <stdio.h>
-#include <libcitadel.h>
-
-#include "ctdl_module.h"
-#include "serv_extensions.h"
-#include "room_ops.h"
-#include "internet_addressing.h"
-#include "config.h"
-#include "journaling.h"
-#include "citadel_ldap.h"
-
-void check_sched_shutdown(void) {
- if ((ScheduledShutdown == 1) && (ContextList == NULL)) {
- syslog(LOG_NOTICE, "housekeeping: scheduled shutdown initiating");
- server_shutting_down = 1;
- }
-}
-
-
-/*
- * Check (and fix) floor reference counts. This doesn't need to be done
- * very often, since the counts should remain correct during normal operation.
- */
-void check_ref_counts_backend(struct ctdlroom *qrbuf, void *data) {
-
- int *new_refcounts;
-
- new_refcounts = (int *) data;
-
- ++new_refcounts[(int)qrbuf->QRfloor];
-}
-
-
-void check_ref_counts(void) {
- struct floor flbuf;
- int a;
-
- int new_refcounts[MAXFLOORS];
-
- syslog(LOG_DEBUG, "housekeeping: checking floor reference counts");
- for (a=0; a<MAXFLOORS; ++a) {
- new_refcounts[a] = 0;
- }
-
- cdb_begin_transaction();
- CtdlForEachRoom(check_ref_counts_backend, (void *)new_refcounts );
- cdb_end_transaction();
-
- for (a=0; a<MAXFLOORS; ++a) {
- lgetfloor(&flbuf, a);
- flbuf.f_ref_count = new_refcounts[a];
- if (new_refcounts[a] > 0) {
- flbuf.f_flags = flbuf.f_flags | QR_INUSE;
- }
- else {
- flbuf.f_flags = flbuf.f_flags & ~QR_INUSE;
- }
- lputfloor(&flbuf, a);
- syslog(LOG_DEBUG, "housekeeping: floor %d has %d rooms", a, new_refcounts[a]);
- }
-}
-
-
-/*
- * Provide hints as to whether we have any memory leaks
- */
-void keep_an_eye_on_memory_usage(void) {
- static void *original_brk = NULL;
- if (!original_brk) original_brk = sbrk(0); // Remember the original program break so we can test for leaks
- syslog(LOG_DEBUG, "original_brk=%lx, current_brk=%lx, addl=%ld", (long)original_brk, (long)sbrk(0), (long)(sbrk(0)-original_brk)); // FIXME not so noisy please
-}
-
-
-/*
- * This is the housekeeping loop. Worker threads come through here after
- * processing client requests but before jumping back into the pool. We
- * only allow housekeeping to execute once per minute, and we only allow one
- * instance to run at a time.
- */
-static int housekeeping_in_progress = 0;
-static int housekeeping_disabled = 0;
-static time_t last_timer = 0L;
-
-void do_housekeeping(void) {
- int do_housekeeping_now = 0;
- int do_perminute_housekeeping_now = 0;
- time_t now;
-
- if (housekeeping_disabled) {
- return;
- }
-
- /*
- * We do it this way instead of wrapping the whole loop in an
- * S_HOUSEKEEPING critical section because it eliminates the need to
- * potentially have multiple concurrent mutexes in progress.
- */
- begin_critical_section(S_HOUSEKEEPING);
- if (housekeeping_in_progress == 0) {
- do_housekeeping_now = 1;
- housekeeping_in_progress = 1;
- }
- end_critical_section(S_HOUSEKEEPING);
-
- now = time(NULL);
- if ( (do_housekeeping_now == 0) && (!CtdlIsSingleUser()) ) {
- if ( (now - last_timer) > (time_t)300 ) {
- syslog(LOG_WARNING,
- "housekeeping: WARNING: housekeeping loop has not run for %ld minutes. Is something stuck?",
- ((now - last_timer) / 60)
- );
- }
- return;
- }
-
- /*
- * Ok, at this point we've made the decision to run the housekeeping
- * loop. Everything below this point is real work.
- */
-
- if ( (now - last_timer) > (time_t)60 ) {
- do_perminute_housekeeping_now = 1;
- last_timer = time(NULL);
- }
-
- /* First, do the "as often as needed" stuff... */
- JournalRunQueue();
- PerformSessionHooks(EVT_HOUSE);
-
- /* Then, do the "once per minute" stuff... */
- if (do_perminute_housekeeping_now) {
- cdb_check_handles();
- PerformSessionHooks(EVT_TIMER); // Run all registered TIMER hooks
-
- // LDAP sync isn't in a module so we can put it here
- static time_t last_ldap_sync = 0L;
- if ( (now - last_ldap_sync) > (time_t)CtdlGetConfigLong("c_ldap_sync_freq") ) {
- CtdlSynchronizeUsersFromLDAP();
- last_ldap_sync = time(NULL);
- }
-
- keep_an_eye_on_memory_usage();
- }
-
- /*
- * All done.
- */
- begin_critical_section(S_HOUSEKEEPING);
- housekeeping_in_progress = 0;
- end_critical_section(S_HOUSEKEEPING);
-}
-
-
-void CtdlDisableHouseKeeping(void) {
- syslog(LOG_INFO, "housekeeping: trying to disable");
- while ( (!housekeeping_disabled) && (!server_shutting_down) && (!housekeeping_in_progress) ) {
-
- if (housekeeping_in_progress) {
- sleep(1);
- }
- else {
- begin_critical_section(S_HOUSEKEEPING);
- if (!housekeeping_in_progress) {
- housekeeping_disabled = 1;
- }
- end_critical_section(S_HOUSEKEEPING);
- }
- }
- syslog(LOG_INFO, "housekeeping: disabled now");
-}
-
-
-void CtdlEnableHouseKeeping(void) {
- begin_critical_section(S_HOUSEKEEPING);
- housekeeping_in_progress = 0;
- end_critical_section(S_HOUSEKEEPING);
-}
+++ /dev/null
-void check_sched_shutdown(void);
-void check_ref_counts(void);
-void do_housekeeping(void);
+++ /dev/null
-// This file contains functions which handle the mapping of Internet addresses
-// to users on the Citadel system.
-//
-// Copyright (c) 1987-2022 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.
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <ctype.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "sysdep_decls.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "user_ops.h"
-#include "room_ops.h"
-#include "parsedate.h"
-#include "database.h"
-#include "ctdl_module.h"
-#ifdef HAVE_ICONV
-#include <iconv.h>
-
-// This is the non-define version in case it is needed for debugging
-#if 0
-inline void FindNextEnd (char *bptr, char *end)
-{
- /* Find the next ?Q? */
- end = strchr(bptr + 2, '?');
- if (end == NULL) return NULL;
- if (((*(end + 1) == 'B') || (*(end + 1) == 'Q')) &&
- (*(end + 2) == '?')) {
- /* skip on to the end of the cluster, the next ?= */
- end = strstr(end + 3, "?=");
- }
- else
- /* sort of half valid encoding, try to find an end. */
- end = strstr(bptr, "?=");
-}
-#endif
-
-#define FindNextEnd(bptr, end) { \
- end = strchr(bptr + 2, '?'); \
- if (end != NULL) { \
- if (((*(end + 1) == 'B') || (*(end + 1) == 'Q')) && (*(end + 2) == '?')) { \
- end = strstr(end + 3, "?="); \
- } else end = strstr(bptr, "?="); \
- } \
-}
-
-// Handle subjects with RFC2047 encoding such as:
-// =?koi8-r?B?78bP0s3Mxc7JxSDXz9rE1dvO2c3JINvB0sHNySDP?=
-void utf8ify_rfc822_string(char *buf) {
- char *start, *end, *next, *nextend, *ptr;
- char newbuf[1024];
- char charset[128];
- char encoding[16];
- char istr[1024];
- iconv_t ic = (iconv_t)(-1) ;
- char *ibuf; // Buffer of characters to be converted
- char *obuf; // Buffer for converted characters
- size_t ibuflen; // Length of input buffer
- size_t obuflen; // Length of output buffer
- char *isav; // Saved pointer to input buffer
- char *osav; // Saved pointer to output buffer
- int passes = 0;
- int i, len, delta;
- int illegal_non_rfc2047_encoding = 0;
-
- // Sometimes, badly formed messages contain strings which were simply
- // written out directly in some foreign character set instead of
- // using RFC2047 encoding. This is illegal but we will attempt to
- // handle it anyway by converting from a user-specified default
- // charset to UTF-8 if we see any nonprintable characters.
- len = strlen(buf);
- for (i=0; i<len; ++i) {
- if ((buf[i] < 32) || (buf[i] > 126)) {
- illegal_non_rfc2047_encoding = 1;
- i = len; // take a shortcut, it won't be more than one.
- }
- }
- if (illegal_non_rfc2047_encoding) {
- const char *default_header_charset = "iso-8859-1";
- if ( (strcasecmp(default_header_charset, "UTF-8")) && (strcasecmp(default_header_charset, "us-ascii")) ) {
- ctdl_iconv_open("UTF-8", default_header_charset, &ic);
- if (ic != (iconv_t)(-1) ) {
- ibuf = malloc(1024);
- isav = ibuf;
- safestrncpy(ibuf, buf, 1024);
- ibuflen = strlen(ibuf);
- obuflen = 1024;
- obuf = (char *) malloc(obuflen);
- osav = obuf;
- iconv(ic, &ibuf, &ibuflen, &obuf, &obuflen);
- osav[1024-obuflen] = 0;
- strcpy(buf, osav);
- free(osav);
- iconv_close(ic);
- free(isav);
- }
- }
- }
-
- // pre evaluate the first pair
- nextend = end = NULL;
- len = strlen(buf);
- start = strstr(buf, "=?");
- if (start != NULL)
- FindNextEnd (start, end);
-
- while ((start != NULL) && (end != NULL)) {
- next = strstr(end, "=?");
- if (next != NULL)
- FindNextEnd(next, nextend);
- if (nextend == NULL)
- next = NULL;
-
- // did we find two partitions
- if ((next != NULL) && ((next - end) > 2)) {
- ptr = end + 2;
- while ((ptr < next) &&
- (isspace(*ptr) ||
- (*ptr == '\r') ||
- (*ptr == '\n') ||
- (*ptr == '\t')))
- ptr ++;
- // did we find a gab just filled with blanks?
- if (ptr == next) {
- memmove(end + 2, next, len - (next - start));
-
- // now terminate the gab at the end
- delta = (next - end) - 2;
- len -= delta;
- buf[len] = '\0';
-
- // move next to its new location.
- next -= delta;
- nextend -= delta;
- }
- }
- // our next-pair is our new first pair now.
- start = next;
- end = nextend;
- }
-
- // Now we handle foreign character sets properly encoded in RFC2047 format.
- start = strstr(buf, "=?");
- FindNextEnd((start != NULL)? start : buf, end);
- while (start != NULL && end != NULL && end > start) {
- extract_token(charset, start, 1, '?', sizeof charset);
- extract_token(encoding, start, 2, '?', sizeof encoding);
- extract_token(istr, start, 3, '?', sizeof istr);
-
- ibuf = malloc(1024);
- isav = ibuf;
- if (!strcasecmp(encoding, "B")) { // base64
- ibuflen = CtdlDecodeBase64(ibuf, istr, strlen(istr));
- }
- else if (!strcasecmp(encoding, "Q")) { // quoted-printable
- size_t len;
- unsigned long pos;
-
- len = strlen(istr);
- pos = 0;
- while (pos < len) {
- if (istr[pos] == '_') istr[pos] = ' ';
- pos++;
- }
- ibuflen = CtdlDecodeQuotedPrintable(ibuf, istr, len);
- }
- else {
- strcpy(ibuf, istr); // unknown encoding
- ibuflen = strlen(istr);
- }
-
- ctdl_iconv_open("UTF-8", charset, &ic);
- if (ic != (iconv_t)(-1) ) {
- obuflen = 1024;
- obuf = (char *) malloc(obuflen);
- osav = obuf;
- iconv(ic, &ibuf, &ibuflen, &obuf, &obuflen);
- osav[1024-obuflen] = 0;
-
- end = start;
- end++;
- strcpy(start, "");
- remove_token(end, 0, '?');
- remove_token(end, 0, '?');
- remove_token(end, 0, '?');
- remove_token(end, 0, '?');
- strcpy(end, &end[1]);
-
- snprintf(newbuf, sizeof newbuf, "%s%s%s", buf, osav, end);
- strcpy(buf, newbuf);
- free(osav);
- iconv_close(ic);
- }
- else {
- end = start;
- end++;
- strcpy(start, "");
- remove_token(end, 0, '?');
- remove_token(end, 0, '?');
- remove_token(end, 0, '?');
- remove_token(end, 0, '?');
- strcpy(end, &end[1]);
-
- snprintf(newbuf, sizeof newbuf, "%s(unreadable)%s", buf, end);
- strcpy(buf, newbuf);
- }
-
- free(isav);
-
- // Since spammers will go to all sorts of absurd lengths to get their
- // messages through, there are LOTS of corrupt headers out there.
- // So, prevent a really badly formed RFC2047 header from throwing
- // this function into an infinite loop.
- ++passes;
- if (passes > 20) return;
-
- start = strstr(buf, "=?");
- FindNextEnd((start != NULL)? start : buf, end);
- }
-
-}
-#else
-inline void utf8ify_rfc822_string(char *a){};
-
-#endif
-
-
-char *inetcfg = NULL;
-
-// Return nonzero if the supplied name is an alias for this host.
-int CtdlHostAlias(char *fqdn) {
- int config_lines;
- int i;
- char buf[256];
- char host[256], type[256];
- int found = 0;
-
- if (fqdn == NULL) return(hostalias_nomatch);
- if (IsEmptyStr(fqdn)) return(hostalias_nomatch);
- if (!strcasecmp(fqdn, "localhost")) return(hostalias_localhost);
- if (!strcasecmp(fqdn, CtdlGetConfigStr("c_fqdn"))) return(hostalias_localhost);
- if (!strcasecmp(fqdn, CtdlGetConfigStr("c_nodename"))) return(hostalias_localhost);
- if (inetcfg == NULL) return(hostalias_nomatch);
-
- config_lines = num_tokens(inetcfg, '\n');
- for (i=0; i<config_lines; ++i) {
- extract_token(buf, inetcfg, i, '\n', sizeof buf);
- extract_token(host, buf, 0, '|', sizeof host);
- extract_token(type, buf, 1, '|', sizeof type);
-
- found = 0;
-
- // Process these in a specific order, in case there are multiple matches.
- // We want localhost to override masq, for example.
-
- if ( (!strcasecmp(type, "masqdomain")) && (!strcasecmp(fqdn, host))) {
- found = hostalias_masq;
- }
-
- if ( (!strcasecmp(type, "localhost")) && (!strcasecmp(fqdn, host))) {
- found = hostalias_localhost;
- }
-
- // "directory" used to be a distributed version of "localhost" but they're both the same now
- if ( (!strcasecmp(type, "directory")) && (!strcasecmp(fqdn, host))) {
- found = hostalias_localhost;
- }
-
- if (found) return(found);
- }
- return(hostalias_nomatch);
-}
-
-
-// Determine whether a given Internet address belongs to the current user
-int CtdlIsMe(char *addr, int addr_buf_len) {
- struct recptypes *recp;
- int i;
-
- recp = validate_recipients(addr, NULL, 0);
- if (recp == NULL) return(0);
-
- if (recp->num_local == 0) {
- free_recipients(recp);
- return(0);
- }
-
- for (i=0; i<recp->num_local; ++i) {
- extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
- if (!strcasecmp(addr, CC->user.fullname)) {
- free_recipients(recp);
- return(1);
- }
- }
-
- free_recipients(recp);
- return(0);
-}
-
-
-// If the last item in a list of recipients was truncated to a partial address,
-// remove it completely in order to avoid choking library functions.
-void sanitize_truncated_recipient(char *str) {
- if (!str) return;
- if (num_tokens(str, ',') < 2) return;
-
- int len = strlen(str);
- if (len < 900) return;
- if (len > 998) str[998] = 0;
-
- char *cptr = strrchr(str, ',');
- if (!cptr) return;
-
- char *lptr = strchr(cptr, '<');
- char *rptr = strchr(cptr, '>');
-
- if ( (lptr) && (rptr) && (rptr > lptr) ) return;
-
- *cptr = 0;
-}
-
-
-// This function is self explanatory.
-// (What can I say, I'm in a weird mood today...)
-void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name) {
- char *ptr;
- if (!name) return;
-
- for (ptr=name; *ptr; ++ptr) {
- while ( (isspace(*ptr)) && (*(ptr+1)=='@') ) {
- strcpy(ptr, ptr+1);
- if (ptr > name) --ptr;
- }
- while ( (*ptr=='@') && (*(ptr+1)!=0) && (isspace(*(ptr+1))) ) {
- strcpy(ptr+1, ptr+2);
- }
- }
-}
-
-
-// values that can be returned by expand_aliases()
-enum {
- EA_ERROR, // Can't send message due to bad address
- EA_MULTIPLE, // Alias expanded into multiple recipients -- run me again!
- EA_LOCAL, // Local message, do no network processing
- EA_INTERNET, // Convert msg and send as Internet mail
- EA_SKIP // This recipient has been invalidated -- skip it!
-};
-
-
-// Process alias and routing info for email addresses
-int expand_aliases(char *name, char *aliases) {
- int a;
- char aaa[SIZ];
- int at = 0;
-
- if (aliases) {
- int num_aliases = num_tokens(aliases, '\n');
- for (a=0; a<num_aliases; ++a) {
- extract_token(aaa, aliases, a, '\n', sizeof aaa);
- char *bar = strchr(aaa, '|');
- if (bar) {
- bar[0] = 0;
- ++bar;
- striplt(aaa);
- striplt(bar);
- if ( (!IsEmptyStr(aaa)) && (!strcasecmp(name, aaa)) ) {
- syslog(LOG_DEBUG, "internet_addressing: global alias <%s> to <%s>", name, bar);
- strcpy(name, bar);
- }
- }
- }
- if (strchr(name, ',')) {
- return(EA_MULTIPLE);
- }
- }
-
- char original_name[256]; // Now go for the regular aliases
- safestrncpy(original_name, name, sizeof original_name);
-
- // should these checks still be here, or maybe move them to split_recps() ?
- striplt(name);
- remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
- stripallbut(name, '<', '>');
-
- // Hit the email address directory
- if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
- strcpy(name, aaa);
- }
-
- if (strcasecmp(original_name, name)) {
- syslog(LOG_INFO, "internet_addressing: directory alias <%s> to <%s>", original_name, name);
- }
-
- // Change "user @ xxx" to "user" if xxx is an alias for this host
- for (a=0; name[a] != '\0'; ++a) {
- if (name[a] == '@') {
- if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
- name[a] = 0;
- syslog(LOG_DEBUG, "internet_addressing: host is local, recipient is <%s>", name);
- break;
- }
- }
- }
-
- // Is this a local or remote recipient?
- at = haschar(name, '@');
- if (at == 0) {
- return(EA_LOCAL); // no @'s = local address
- }
- else if (at == 1) {
- return(EA_INTERNET); // one @ = internet address
- }
- else {
- return(EA_ERROR); // more than one @ = badly formed address
- }
-}
-
-
-// Return a supplied list of email addresses as an array, removing superfluous information and syntax.
-// If an existing Array is supplied as "append_to" it will do so; otherwise a new Array is allocated.
-Array *split_recps(char *addresses, Array *append_to) {
-
- if (IsEmptyStr(addresses)) { // nothing supplied, nothing returned
- return(NULL);
- }
-
- // Copy the supplied address list into our own memory space, because we are going to modify it.
- char *a = strdup(addresses);
- if (a == NULL) {
- syslog(LOG_ERR, "internet_addressing: malloc() failed: %m");
- return(NULL);
- }
-
- // Strip out anything in double quotes
- char *l = NULL;
- char *r = NULL;
- do {
- l = strchr(a, '\"');
- r = strrchr(a, '\"');
- if (r > l) {
- strcpy(l, r+1);
- }
- } while (r > l);
-
- // Transform all qualifying delimiters to commas
- char *t;
- for (t=a; t[0]; ++t) {
- if ((t[0]==';') || (t[0]=='|')) {
- t[0]=',';
- }
- }
-
- // Tokenize the recipients into an array. No single recipient should be larger than 256 bytes.
- Array *recipients_array = NULL;
- if (append_to) {
- recipients_array = append_to; // Append to an existing array of recipients
- }
- else {
- recipients_array = array_new(256); // This is a new array of recipients
- }
-
- int num_addresses = num_tokens(a, ',');
- int i;
- for (i=0; i<num_addresses; ++i) {
- char this_address[256];
- extract_token(this_address, a, i, ',', sizeof this_address);
- striplt(this_address); // strip leading and trailing whitespace
- stripout(this_address, '(', ')'); // remove any portion in parentheses
- stripallbut(this_address, '<', '>'); // if angle brackets are present, keep only what is inside them
- if (!IsEmptyStr(this_address)) {
- array_append(recipients_array, this_address);
- }
- }
-
- free(a); // We don't need this buffer anymore.
- return(recipients_array); // Return the completed array to the caller.
-}
-
-
-// Validate recipients, count delivery types and errors, and handle aliasing
-//
-// Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
-// were specified, or the number of addresses found invalid.
-//
-// Caller needs to free the result using free_recipients()
-//
-struct recptypes *validate_recipients(char *supplied_recipients, const char *RemoteIdentifier, int Flags) {
- struct recptypes *ret;
- char *recipients = NULL;
- char append[SIZ];
- long len;
- int mailtype;
- int invalid;
- struct ctdluser tempUS;
- struct ctdlroom original_room;
- int err = 0;
- char errmsg[SIZ];
- char *org_recp;
- char this_recp[256];
-
- ret = (struct recptypes *) malloc(sizeof(struct recptypes)); // Initialize
- if (ret == NULL) return(NULL);
- memset(ret, 0, sizeof(struct recptypes)); // set all values to null/zero
-
- if (supplied_recipients == NULL) {
- recipients = strdup("");
- }
- else {
- recipients = strdup(supplied_recipients);
- }
-
- len = strlen(recipients) + 1024; // allocate memory
- ret->errormsg = malloc(len);
- ret->recp_local = malloc(len);
- ret->recp_internet = malloc(len);
- ret->recp_room = malloc(len);
- ret->display_recp = malloc(len);
- ret->recp_orgroom = malloc(len);
-
- ret->errormsg[0] = 0;
- ret->recp_local[0] = 0;
- ret->recp_internet[0] = 0;
- ret->recp_room[0] = 0;
- ret->recp_orgroom[0] = 0;
- ret->display_recp[0] = 0;
- ret->recptypes_magic = RECPTYPES_MAGIC;
-
- Array *recp_array = split_recps(supplied_recipients, NULL);
-
- char *aliases = CtdlGetSysConfig(GLOBAL_ALIASES); // First hit the Global Alias Table
-
- int r;
- for (r=0; (recp_array && r<array_len(recp_array)); ++r) {
- org_recp = (char *)array_get_element_at(recp_array, r);
- strncpy(this_recp, org_recp, sizeof this_recp);
-
- int i;
- for (i=0; i<3; ++i) { // pass three times through the aliaser
- mailtype = expand_aliases(this_recp, aliases);
-
- // If an alias expanded to multiple recipients, strip off those recipients and append them
- // to the end of the array. This loop will hit those again when it gets there.
- if (mailtype == EA_MULTIPLE) {
- recp_array = split_recps(this_recp, recp_array);
- }
- }
-
- // This loop searches for duplicate recipients in the final list and marks them to be skipped.
- int j;
- for (j=0; j<r; ++j) {
- if (!strcasecmp(this_recp, (char *)array_get_element_at(recp_array, j) )) {
- mailtype = EA_SKIP;
- }
- }
-
- syslog(LOG_DEBUG, "Recipient #%d of type %d is <%s>", r, mailtype, this_recp);
- invalid = 0;
- errmsg[0] = 0;
- switch(mailtype) {
- case EA_LOCAL: // There are several types of "local" recipients.
-
- // Old BBS conventions require mail to "sysop" to go somewhere. Send it to the admin room.
- if (!strcasecmp(this_recp, "sysop")) {
- ++ret->num_room;
- strcpy(this_recp, CtdlGetConfigStr("c_aideroom"));
- if (!IsEmptyStr(ret->recp_room)) {
- strcat(ret->recp_room, "|");
- }
- strcat(ret->recp_room, this_recp);
- }
-
- // This handles rooms which can receive posts via email.
- else if (!strncasecmp(this_recp, "room_", 5)) {
- original_room = CC->room; // Remember where we parked
-
- char mail_to_room[ROOMNAMELEN];
- char *m;
- strncpy(mail_to_room, &this_recp[5], sizeof mail_to_room);
- for (m = mail_to_room; *m; ++m) {
- if (m[0] == '_') m[0]=' ';
- }
- if (!CtdlGetRoom(&CC->room, mail_to_room)) { // Find the room they asked for
-
- err = CtdlDoIHavePermissionToPostInThisRoom( // check for write permissions to room
- errmsg,
- sizeof errmsg,
- RemoteIdentifier,
- Flags,
- 0 // 0 means "this is not a reply"
- );
- if (err) {
- ++ret->num_error;
- invalid = 1;
- }
- else {
- ++ret->num_room;
- if (!IsEmptyStr(ret->recp_room)) {
- strcat(ret->recp_room, "|");
- }
- strcat(ret->recp_room, CC->room.QRname);
-
- if (!IsEmptyStr(ret->recp_orgroom)) {
- strcat(ret->recp_orgroom, "|");
- }
- strcat(ret->recp_orgroom, this_recp);
-
- }
- }
- else { // no such room exists
- ++ret->num_error;
- invalid = 1;
- }
-
- // Restore this session's original room location.
- CC->room = original_room;
-
- }
-
- // This handles the most common case, which is mail to a user's inbox.
- else if (CtdlGetUser(&tempUS, this_recp) == 0) {
- ++ret->num_local;
- strcpy(this_recp, tempUS.fullname);
- if (!IsEmptyStr(ret->recp_local)) {
- strcat(ret->recp_local, "|");
- }
- strcat(ret->recp_local, this_recp);
- }
-
- // No match for this recipient
- else {
- ++ret->num_error;
- invalid = 1;
- }
- break;
- case EA_INTERNET:
- // Yes, you're reading this correctly: if the target domain points back to the local system,
- // the address is invalid. That's because if the address were valid, we would have
- // already translated it to a local address by now.
- if (IsDirectory(this_recp, 0)) {
- ++ret->num_error;
- invalid = 1;
- }
- else {
- ++ret->num_internet;
- if (!IsEmptyStr(ret->recp_internet)) {
- strcat(ret->recp_internet, "|");
- }
- strcat(ret->recp_internet, this_recp);
- }
- break;
- case EA_MULTIPLE:
- case EA_SKIP:
- // no action required, anything in this slot has already been processed elsewhere
- break;
- case EA_ERROR:
- ++ret->num_error;
- invalid = 1;
- break;
- }
- if (invalid) {
- if (IsEmptyStr(errmsg)) {
- snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
- }
- else {
- snprintf(append, sizeof append, "%s", errmsg);
- }
- if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
- if (!IsEmptyStr(ret->errormsg)) {
- strcat(ret->errormsg, "; ");
- }
- strcat(ret->errormsg, append);
- }
- }
- else {
- if (IsEmptyStr(ret->display_recp)) {
- strcpy(append, this_recp);
- }
- else {
- snprintf(append, sizeof append, ", %s", this_recp);
- }
- if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
- strcat(ret->display_recp, append);
- }
- }
- }
-
- if (aliases != NULL) { // ok, we're done with the global alias list now
- free(aliases);
- }
-
- if ( (ret->num_local + ret->num_internet + ret->num_room + ret->num_error) == 0) {
- ret->num_error = (-1);
- strcpy(ret->errormsg, "No recipients specified.");
- }
-
- syslog(LOG_DEBUG, "internet_addressing: validate_recipients() = %d local, %d room, %d SMTP, %d error",
- ret->num_local, ret->num_room, ret->num_internet, ret->num_error
- );
-
- free(recipients);
- if (recp_array) {
- array_free(recp_array);
- }
-
- return(ret);
-}
-
-
-// Destructor for recptypes
-void free_recipients(struct recptypes *valid) {
-
- if (valid == NULL) {
- return;
- }
-
- if (valid->recptypes_magic != RECPTYPES_MAGIC) {
- syslog(LOG_ERR, "internet_addressing: attempt to call free_recipients() on some other data type!");
- abort();
- }
-
- if (valid->errormsg != NULL) free(valid->errormsg);
- if (valid->recp_local != NULL) free(valid->recp_local);
- if (valid->recp_internet != NULL) free(valid->recp_internet);
- if (valid->recp_room != NULL) free(valid->recp_room);
- if (valid->recp_orgroom != NULL) free(valid->recp_orgroom);
- if (valid->display_recp != NULL) free(valid->display_recp);
- if (valid->bounce_to != NULL) free(valid->bounce_to);
- if (valid->envelope_from != NULL) free(valid->envelope_from);
- if (valid->sending_room != NULL) free(valid->sending_room);
- free(valid);
-}
-
-
-char *qp_encode_email_addrs(char *source) {
- char *user, *node, *name;
- const char headerStr[] = "=?UTF-8?Q?";
- char *Encoded;
- char *EncodedName;
- char *nPtr;
- int need_to_encode = 0;
- long SourceLen;
- long EncodedMaxLen;
- long nColons = 0;
- long *AddrPtr;
- long *AddrUtf8;
- long nAddrPtrMax = 50;
- long nmax;
- int InQuotes = 0;
- int i, n;
-
- if (source == NULL) return source;
- if (IsEmptyStr(source)) return source;
- syslog(LOG_DEBUG, "internet_addressing: qp_encode_email_addrs <%s>", source);
-
- AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
- AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
- memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
- *AddrPtr = 0;
- i = 0;
- while (!IsEmptyStr (&source[i])) {
- if (nColons >= nAddrPtrMax){
- long *ptr;
-
- ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
- memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
- free (AddrPtr), AddrPtr = ptr;
-
- ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
- memset(&ptr[nAddrPtrMax], 0, sizeof (long) * nAddrPtrMax);
-
- memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
- free (AddrUtf8), AddrUtf8 = ptr;
- nAddrPtrMax *= 2;
- }
- if (((unsigned char) source[i] < 32) || ((unsigned char) source[i] > 126)) {
- need_to_encode = 1;
- AddrUtf8[nColons] = 1;
- }
- if (source[i] == '"') {
- InQuotes = !InQuotes;
- }
- if (!InQuotes && source[i] == ',') {
- AddrPtr[nColons] = i;
- nColons++;
- }
- i++;
- }
- if (need_to_encode == 0) {
- free(AddrPtr);
- free(AddrUtf8);
- return source;
- }
-
- SourceLen = i;
- EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
- Encoded = (char*) malloc (EncodedMaxLen);
-
- for (i = 0; i < nColons; i++) {
- source[AddrPtr[i]++] = '\0';
- }
- // TODO: if libidn, this might get larger
- user = malloc(SourceLen + 1);
- node = malloc(SourceLen + 1);
- name = malloc(SourceLen + 1);
-
- nPtr = Encoded;
- *nPtr = '\0';
- for (i = 0; i < nColons && nPtr != NULL; i++) {
- nmax = EncodedMaxLen - (nPtr - Encoded);
- if (AddrUtf8[i]) {
- process_rfc822_addr(&source[AddrPtr[i]], user, node, name);
- // TODO: libIDN here !
- if (IsEmptyStr(name)) {
- n = snprintf(nPtr, nmax, (i==0)?"%s@%s" : ",%s@%s", user, node);
- }
- else {
- EncodedName = rfc2047encode(name, strlen(name));
- n = snprintf(nPtr, nmax, (i==0)?"%s <%s@%s>" : ",%s <%s@%s>", EncodedName, user, node);
- free(EncodedName);
- }
- }
- else {
- n = snprintf(nPtr, nmax, (i==0)?"%s" : ",%s", &source[AddrPtr[i]]);
- }
- if (n > 0 )
- nPtr += n;
- else {
- char *ptr, *nnPtr;
- ptr = (char*) malloc(EncodedMaxLen * 2);
- memcpy(ptr, Encoded, EncodedMaxLen);
- nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
- free(Encoded), Encoded = ptr;
- EncodedMaxLen *= 2;
- i--; // do it once more with properly lengthened buffer
- }
- }
- for (i = 0; i < nColons; i++)
- source[--AddrPtr[i]] = ',';
-
- free(user);
- free(node);
- free(name);
- free(AddrUtf8);
- free(AddrPtr);
- return Encoded;
-}
-
-
-// Unfold a multi-line field into a single line, removing multi-whitespaces
-void unfold_rfc822_field(char **field, char **FieldEnd)
-{
- int quote = 0;
- char *pField = *field;
- char *sField;
- char *pFieldEnd = *FieldEnd;
-
- while (isspace(*pField))
- pField++;
- // remove leading/trailing whitespace
- ;
-
- while (isspace(*pFieldEnd))
- pFieldEnd --;
-
- *FieldEnd = pFieldEnd;
- // convert non-space whitespace to spaces, and remove double blanks
- for (sField = *field = pField;
- sField < pFieldEnd;
- pField++, sField++)
- {
- if ((*sField=='\r') || (*sField=='\n'))
- {
- int offset = 1;
- while ( ( (*(sField + offset) == '\r') || (*(sField + offset) == '\n' )) && (sField + offset < pFieldEnd) ) {
- offset ++;
- }
- sField += offset;
- *pField = *sField;
- }
- else {
- if (*sField=='\"') quote = 1 - quote;
- if (!quote) {
- if (isspace(*sField)) {
- *pField = ' ';
- pField++;
- sField++;
-
- while ((sField < pFieldEnd) &&
- isspace(*sField))
- sField++;
- *pField = *sField;
- }
- else *pField = *sField;
- }
- else *pField = *sField;
- }
- }
- *pField = '\0';
- *FieldEnd = pField - 1;
-}
-
-
-// Split an RFC822-style address into userid, host, and full name
-//
-// Note: This still handles obsolete address syntaxes such as user%node@node and ...node!user
-// We should probably remove that.
-void process_rfc822_addr(const char *rfc822, char *user, char *node, char *name) {
- int a;
-
- strcpy(user, "");
- strcpy(node, CtdlGetConfigStr("c_fqdn"));
- strcpy(name, "");
-
- if (rfc822 == NULL) return;
-
- // extract full name - first, it's From minus <userid>
- strcpy(name, rfc822);
- stripout(name, '<', '>');
-
- // strip anything to the left of a bang
- while ((!IsEmptyStr(name)) && (haschar(name, '!') > 0))
- strcpy(name, &name[1]);
-
- // and anything to the right of a @ or %
- for (a = 0; name[a] != '\0'; ++a) {
- if (name[a] == '@') {
- name[a] = 0;
- break;
- }
- if (name[a] == '%') {
- name[a] = 0;
- break;
- }
- }
-
- // but if there are parentheses, that changes the rules...
- if ((haschar(rfc822, '(') == 1) && (haschar(rfc822, ')') == 1)) {
- strcpy(name, rfc822);
- stripallbut(name, '(', ')');
- }
-
- // but if there are a set of quotes, that supersedes everything
- if (haschar(rfc822, 34) == 2) {
- strcpy(name, rfc822);
- while ((!IsEmptyStr(name)) && (name[0] != 34)) {
- strcpy(&name[0], &name[1]);
- }
- strcpy(&name[0], &name[1]);
- for (a = 0; name[a] != '\0'; ++a)
- if (name[a] == 34) {
- name[a] = 0;
- break;
- }
- }
- // extract user id
- strcpy(user, rfc822);
-
- // first get rid of anything in parens
- stripout(user, '(', ')');
-
- // if there's a set of angle brackets, strip it down to that
- if ((haschar(user, '<') == 1) && (haschar(user, '>') == 1)) {
- stripallbut(user, '<', '>');
- }
-
- // strip anything to the left of a bang
- while ((!IsEmptyStr(user)) && (haschar(user, '!') > 0))
- strcpy(user, &user[1]);
-
- // and anything to the right of a @ or %
- for (a = 0; user[a] != '\0'; ++a) {
- if (user[a] == '@') {
- user[a] = 0;
- break;
- }
- if (user[a] == '%') {
- user[a] = 0;
- break;
- }
- }
-
-
- // extract node name
- strcpy(node, rfc822);
-
- // first get rid of anything in parens
- stripout(node, '(', ')');
-
- // if there's a set of angle brackets, strip it down to that
- if ((haschar(node, '<') == 1) && (haschar(node, '>') == 1)) {
- stripallbut(node, '<', '>');
- }
-
- // If no node specified, tack ours on instead
- if (
- (haschar(node, '@')==0)
- && (haschar(node, '%')==0)
- && (haschar(node, '!')==0)
- ) {
- strcpy(node, CtdlGetConfigStr("c_nodename"));
- }
- else {
-
- // strip anything to the left of a @
- while ((!IsEmptyStr(node)) && (haschar(node, '@') > 0))
- strcpy(node, &node[1]);
-
- // strip anything to the left of a %
- while ((!IsEmptyStr(node)) && (haschar(node, '%') > 0))
- strcpy(node, &node[1]);
-
- // reduce multiple system bang paths to node!user
- while ((!IsEmptyStr(node)) && (haschar(node, '!') > 1))
- strcpy(node, &node[1]);
-
- // now get rid of the user portion of a node!user string
- for (a = 0; node[a] != '\0'; ++a)
- if (node[a] == '!') {
- node[a] = 0;
- break;
- }
- }
-
- // strip leading and trailing spaces in all strings
- striplt(user);
- striplt(node);
- striplt(name);
-
- // If we processed a string that had the address in angle brackets
- // but no name outside the brackets, we now have an empty name. In
- // this case, use the user portion of the address as the name.
- if ((IsEmptyStr(name)) && (!IsEmptyStr(user))) {
- strcpy(name, user);
- }
-}
-
-
-// convert_field() is a helper function for convert_internet_message().
-// Given start/end positions for an rfc822 field, it converts it to a Citadel
-// field if it wants to, and unfolds it if necessary.
-//
-// Returns 1 if the field was converted and inserted into the Citadel message
-// structure, implying that the source field should be removed from the
-// message text.
-int convert_field(struct CtdlMessage *msg, const char *beg, const char *end) {
- char *key, *value, *valueend;
- long len;
- const char *pos;
- int i;
- const char *colonpos = NULL;
- int processed = 0;
- char user[1024];
- char node[1024];
- char name[1024];
- char addr[1024];
- time_t parsed_date;
- long valuelen;
-
- for (pos = end; pos >= beg; pos--) {
- if (*pos == ':') colonpos = pos;
- }
-
- if (colonpos == NULL) return(0); /* no colon? not a valid header line */
-
- len = end - beg;
- key = malloc(len + 2);
- memcpy(key, beg, len + 1);
- key[len] = '\0';
- valueend = key + len;
- * ( key + (colonpos - beg) ) = '\0';
- value = &key[(colonpos - beg) + 1];
- // printf("Header: [%s]\nValue: [%s]\n", key, value);
- unfold_rfc822_field(&value, &valueend);
- valuelen = valueend - value + 1;
- // printf("UnfoldedValue: [%s]\n", value);
-
- // Here's the big rfc822-to-citadel loop.
-
- // Date/time is converted into a unix timestamp. If the conversion
- // fails, we replace it with the time the message arrived locally.
- if (!strcasecmp(key, "Date")) {
- parsed_date = parsedate(value);
- if (parsed_date < 0L) parsed_date = time(NULL);
-
- if (CM_IsEmpty(msg, eTimestamp))
- CM_SetFieldLONG(msg, eTimestamp, parsed_date);
- processed = 1;
- }
-
- else if (!strcasecmp(key, "From")) {
- process_rfc822_addr(value, user, node, name);
- syslog(LOG_DEBUG, "internet_addressing: converted to <%s@%s> (%s)", user, node, name);
- snprintf(addr, sizeof(addr), "%s@%s", user, node);
- if (CM_IsEmpty(msg, eAuthor) && !IsEmptyStr(name)) {
- CM_SetField(msg, eAuthor, name, -1);
- }
- if (CM_IsEmpty(msg, erFc822Addr) && !IsEmptyStr(addr)) {
- CM_SetField(msg, erFc822Addr, addr, -1);
- }
- processed = 1;
- }
-
- else if (!strcasecmp(key, "Subject")) {
- if (CM_IsEmpty(msg, eMsgSubject))
- CM_SetField(msg, eMsgSubject, value, valuelen);
- processed = 1;
- }
-
- else if (!strcasecmp(key, "List-ID")) {
- if (CM_IsEmpty(msg, eListID))
- CM_SetField(msg, eListID, value, valuelen);
- processed = 1;
- }
-
- else if (!strcasecmp(key, "To")) {
- if (CM_IsEmpty(msg, eRecipient))
- CM_SetField(msg, eRecipient, value, valuelen);
- processed = 1;
- }
-
- else if (!strcasecmp(key, "CC")) {
- if (CM_IsEmpty(msg, eCarbonCopY))
- CM_SetField(msg, eCarbonCopY, value, valuelen);
- processed = 1;
- }
-
- else if (!strcasecmp(key, "Message-ID")) {
- if (!CM_IsEmpty(msg, emessageId)) {
- syslog(LOG_WARNING, "internet_addressing: duplicate message id");
- }
- else {
- char *pValue;
- long pValueLen;
-
- pValue = value;
- pValueLen = valuelen;
- // Strip angle brackets
- while (haschar(pValue, '<') > 0) {
- pValue ++;
- pValueLen --;
- }
-
- for (i = 0; i <= pValueLen; ++i)
- if (pValue[i] == '>') {
- pValueLen = i;
- break;
- }
-
- CM_SetField(msg, emessageId, pValue, pValueLen);
- }
-
- processed = 1;
- }
-
- else if (!strcasecmp(key, "Return-Path")) {
- if (CM_IsEmpty(msg, eMessagePath))
- CM_SetField(msg, eMessagePath, value, valuelen);
- processed = 1;
- }
-
- else if (!strcasecmp(key, "Envelope-To")) {
- if (CM_IsEmpty(msg, eenVelopeTo))
- CM_SetField(msg, eenVelopeTo, value, valuelen);
- processed = 1;
- }
-
- else if (!strcasecmp(key, "References")) {
- CM_SetField(msg, eWeferences, value, valuelen);
- processed = 1;
- }
-
- else if (!strcasecmp(key, "Reply-To")) {
- CM_SetField(msg, eReplyTo, value, valuelen);
- processed = 1;
- }
-
- else if (!strcasecmp(key, "In-reply-to")) {
- if (CM_IsEmpty(msg, eWeferences)) // References: supersedes In-reply-to:
- CM_SetField(msg, eWeferences, value, valuelen);
- processed = 1;
- }
-
-
-
- // Clean up and move on.
- free(key); // Don't free 'value', it's actually the same buffer
- return processed;
-}
-
-
-// Convert RFC822 references format (References) to Citadel references format (Weferences)
-void convert_references_to_wefewences(char *str) {
- int bracket_nesting = 0;
- char *ptr = str;
- char *moveptr = NULL;
- char ch;
-
- while(*ptr) {
- ch = *ptr;
- if (ch == '>') {
- --bracket_nesting;
- if (bracket_nesting < 0) bracket_nesting = 0;
- }
- if ((ch == '>') && (bracket_nesting == 0) && (*(ptr+1)) && (ptr>str) ) {
- *ptr = '|';
- ++ptr;
- }
- else if (bracket_nesting > 0) {
- ++ptr;
- }
- else {
- moveptr = ptr;
- while (*moveptr) {
- *moveptr = *(moveptr+1);
- ++moveptr;
- }
- }
- if (ch == '<') ++bracket_nesting;
- }
-
-}
-
-
-// Convert an RFC822 message (headers + body) to a CtdlMessage structure.
-// NOTE: the supplied buffer becomes part of the CtdlMessage structure, and
-// will be deallocated when CM_Free() is called. Therefore, the
-// supplied buffer should be DEREFERENCED. It should not be freed or used
-// again.
-struct CtdlMessage *convert_internet_message(char *rfc822) {
- StrBuf *RFCBuf = NewStrBufPlain(rfc822, -1);
- free (rfc822);
- return convert_internet_message_buf(&RFCBuf);
-}
-
-
-struct CtdlMessage *convert_internet_message_buf(StrBuf **rfc822)
-{
- struct CtdlMessage *msg;
- const char *pos, *beg, *end, *totalend;
- int done, alldone = 0;
- int converted;
- StrBuf *OtherHeaders;
-
- msg = malloc(sizeof(struct CtdlMessage));
- if (msg == NULL) return msg;
-
- memset(msg, 0, sizeof(struct CtdlMessage));
- msg->cm_magic = CTDLMESSAGE_MAGIC; // self check
- msg->cm_anon_type = 0; // never anonymous
- msg->cm_format_type = FMT_RFC822; // internet message
-
- pos = ChrPtr(*rfc822);
- totalend = pos + StrLength(*rfc822);
- done = 0;
- OtherHeaders = NewStrBufPlain(NULL, StrLength(*rfc822));
-
- while (!alldone) {
-
- /* Locate beginning and end of field, keeping in mind that
- * some fields might be multiline
- */
- end = beg = pos;
-
- while ((end < totalend) &&
- (end == beg) &&
- (done == 0) )
- {
-
- if ( (*pos=='\n') && ((*(pos+1))!=0x20) && ((*(pos+1))!=0x09) )
- {
- end = pos;
- }
-
- /* done with headers? */
- if ((*pos=='\n') &&
- ( (*(pos+1)=='\n') ||
- (*(pos+1)=='\r')) )
- {
- alldone = 1;
- }
-
- if (pos >= (totalend - 1) )
- {
- end = pos;
- done = 1;
- }
-
- ++pos;
-
- }
-
- /* At this point we have a field. Are we interested in it? */
- converted = convert_field(msg, beg, end);
-
- /* Strip the field out of the RFC822 header if we used it */
- if (!converted) {
- StrBufAppendBufPlain(OtherHeaders, beg, end - beg, 0);
- StrBufAppendBufPlain(OtherHeaders, HKEY("\n"), 0);
- }
-
- /* If we've hit the end of the message, bail out */
- if (pos >= totalend)
- alldone = 1;
- }
- StrBufAppendBufPlain(OtherHeaders, HKEY("\n"), 0);
- if (pos < totalend)
- StrBufAppendBufPlain(OtherHeaders, pos, totalend - pos, 0);
- FreeStrBuf(rfc822);
- CM_SetAsFieldSB(msg, eMesageText, &OtherHeaders);
-
- /* Follow-up sanity checks... */
-
- /* If there's no timestamp on this message, set it to now. */
- if (CM_IsEmpty(msg, eTimestamp)) {
- CM_SetFieldLONG(msg, eTimestamp, time(NULL));
- }
-
- /* If a W (references, or rather, Wefewences) field is present, we
- * have to convert it from RFC822 format to Citadel format.
- */
- if (!CM_IsEmpty(msg, eWeferences)) {
- /// todo: API!
- convert_references_to_wefewences(msg->cm_fields[eWeferences]);
- }
-
- return msg;
-}
-
-
-/*
- * Look for a particular header field in an RFC822 message text. If the
- * requested field is found, it is unfolded (if necessary) and returned to
- * the caller. The field name is stripped out, leaving only its contents.
- * The caller is responsible for freeing the returned buffer. If the requested
- * field is not present, or anything else goes wrong, it returns NULL.
- */
-char *rfc822_fetch_field(const char *rfc822, const char *fieldname) {
- char *fieldbuf = NULL;
- const char *end_of_headers;
- const char *field_start;
- const char *ptr;
- char *cont;
- char fieldhdr[SIZ];
-
- /* Should never happen, but sometimes we get stupid */
- if (rfc822 == NULL) return(NULL);
- if (fieldname == NULL) return(NULL);
-
- snprintf(fieldhdr, sizeof fieldhdr, "%s:", fieldname);
-
- /* Locate the end of the headers, so we don't run past that point */
- end_of_headers = cbmstrcasestr(rfc822, "\n\r\n");
- if (end_of_headers == NULL) {
- end_of_headers = cbmstrcasestr(rfc822, "\n\n");
- }
- if (end_of_headers == NULL) return (NULL);
-
- field_start = cbmstrcasestr(rfc822, fieldhdr);
- if (field_start == NULL) return(NULL);
- if (field_start > end_of_headers) return(NULL);
-
- fieldbuf = malloc(SIZ);
- strcpy(fieldbuf, "");
-
- ptr = field_start;
- ptr = cmemreadline(ptr, fieldbuf, SIZ-strlen(fieldbuf) );
- while ( (isspace(ptr[0])) && (ptr < end_of_headers) ) {
- strcat(fieldbuf, " ");
- cont = &fieldbuf[strlen(fieldbuf)];
- ptr = cmemreadline(ptr, cont, SIZ-strlen(fieldbuf) );
- striplt(cont);
- }
-
- strcpy(fieldbuf, &fieldbuf[strlen(fieldhdr)]);
- striplt(fieldbuf);
-
- return(fieldbuf);
-}
-
-
-/*****************************************************************************
- * DIRECTORY MANAGEMENT FUNCTIONS *
- *****************************************************************************/
-
-/*
- * Generate the index key for an Internet e-mail address to be looked up
- * in the database.
- */
-void directory_key(char *key, char *addr) {
- int i;
- int keylen = 0;
-
- for (i=0; !IsEmptyStr(&addr[i]); ++i) {
- if (!isspace(addr[i])) {
- key[keylen++] = tolower(addr[i]);
- }
- }
- key[keylen++] = 0;
-
- syslog(LOG_DEBUG, "internet_addressing: directory key is <%s>", key);
-}
-
-
-/*
- * Return nonzero if the supplied address is in one of "our" domains
- */
-int IsDirectory(char *addr, int allow_masq_domains) {
- char domain[256];
- int h;
-
- extract_token(domain, addr, 1, '@', sizeof domain);
- striplt(domain);
-
- h = CtdlHostAlias(domain);
-
- if ( (h == hostalias_masq) && allow_masq_domains)
- return(1);
-
- if (h == hostalias_localhost) {
- return(1);
- }
- else {
- return(0);
- }
-}
-
-
-/*
- * Add an Internet e-mail address to the directory for a user
- */
-int CtdlDirectoryAddUser(char *internet_addr, char *citadel_addr) {
- char key[SIZ];
-
- if (IsDirectory(internet_addr, 0) == 0) {
- return 0;
- }
- syslog(LOG_DEBUG, "internet_addressing: create directory entry: %s --> %s", internet_addr, citadel_addr);
- directory_key(key, internet_addr);
- cdb_store(CDB_DIRECTORY, key, strlen(key), citadel_addr, strlen(citadel_addr)+1 );
- return 1;
-}
-
-
-/*
- * Delete an Internet e-mail address from the directory.
- *
- * (NOTE: we don't actually use or need the citadel_addr variable; it's merely
- * here because the callback API expects to be able to send it.)
- */
-int CtdlDirectoryDelUser(char *internet_addr, char *citadel_addr) {
- char key[SIZ];
-
- syslog(LOG_DEBUG, "internet_addressing: delete directory entry: %s --> %s", internet_addr, citadel_addr);
- directory_key(key, internet_addr);
- return cdb_delete(CDB_DIRECTORY, key, strlen(key) ) == 0;
-}
-
-
-/*
- * Look up an Internet e-mail address in the directory.
- * On success: returns 0, and Citadel address stored in 'target'
- * On failure: returns nonzero
- */
-int CtdlDirectoryLookup(char *target, char *internet_addr, size_t targbuflen) {
- struct cdbdata *cdbrec;
- char key[SIZ];
-
- /* Dump it in there unchanged, just for kicks */
- if (target != NULL) {
- safestrncpy(target, internet_addr, targbuflen);
- }
-
- /* Only do lookups for addresses with hostnames in them */
- if (num_tokens(internet_addr, '@') != 2) return(-1);
-
- /* Only do lookups for domains in the directory */
- if (IsDirectory(internet_addr, 0) == 0) return(-1);
-
- directory_key(key, internet_addr);
- cdbrec = cdb_fetch(CDB_DIRECTORY, key, strlen(key) );
- if (cdbrec != NULL) {
- if (target != NULL) {
- safestrncpy(target, cdbrec->ptr, targbuflen);
- }
- cdb_free(cdbrec);
- return(0);
- }
-
- return(-1);
-}
-
-
-/*
- * Harvest any email addresses that someone might want to have in their
- * "collected addresses" book.
- */
-char *harvest_collected_addresses(struct CtdlMessage *msg) {
- char *coll = NULL;
- char addr[256];
- char user[256], node[256], name[256];
- int is_harvestable;
- int i, j, h;
- eMsgField field = 0;
-
- if (msg == NULL) return(NULL);
-
- is_harvestable = 1;
- strcpy(addr, "");
- if (!CM_IsEmpty(msg, eAuthor)) {
- strcat(addr, msg->cm_fields[eAuthor]);
- }
- if (!CM_IsEmpty(msg, erFc822Addr)) {
- strcat(addr, " <");
- strcat(addr, msg->cm_fields[erFc822Addr]);
- strcat(addr, ">");
- if (IsDirectory(msg->cm_fields[erFc822Addr], 0)) {
- is_harvestable = 0;
- }
- }
-
- if (is_harvestable) {
- coll = strdup(addr);
- }
- else {
- coll = strdup("");
- }
-
- if (coll == NULL) return(NULL);
-
- /* Scan both the R (To) and Y (CC) fields */
- for (i = 0; i < 2; ++i) {
- if (i == 0) field = eRecipient;
- if (i == 1) field = eCarbonCopY;
-
- if (!CM_IsEmpty(msg, field)) {
- for (j=0; j<num_tokens(msg->cm_fields[field], ','); ++j) {
- extract_token(addr, msg->cm_fields[field], j, ',', sizeof addr);
- if (strstr(addr, "=?") != NULL)
- utf8ify_rfc822_string(addr);
- process_rfc822_addr(addr, user, node, name);
- h = CtdlHostAlias(node);
- if (h != hostalias_localhost) {
- coll = realloc(coll, strlen(coll) + strlen(addr) + 4);
- if (coll == NULL) return(NULL);
- if (!IsEmptyStr(coll)) {
- strcat(coll, ",");
- }
- striplt(addr);
- strcat(coll, addr);
- }
- }
- }
- }
-
- if (IsEmptyStr(coll)) {
- free(coll);
- return(NULL);
- }
- return(coll);
-}
-
-
-/*
- * Helper function for CtdlRebuildDirectoryIndex()
- */
-void CtdlRebuildDirectoryIndex_backend(char *username, void *data) {
-
- int j = 0;
- struct ctdluser usbuf;
-
- if (CtdlGetUser(&usbuf, username) != 0) {
- return;
- }
-
- if ( (!IsEmptyStr(usbuf.fullname)) && (!IsEmptyStr(usbuf.emailaddrs)) ) {
- for (j=0; j<num_tokens(usbuf.emailaddrs, '|'); ++j) {
- char one_email[512];
- extract_token(one_email, usbuf.emailaddrs, j, '|', sizeof one_email);
- CtdlDirectoryAddUser(one_email, usbuf.fullname);
- }
- }
-}
-
-
-/*
- * Initialize the directory database (erasing anything already there)
- */
-void CtdlRebuildDirectoryIndex(void) {
- syslog(LOG_INFO, "internet_addressing: rebuilding email address directory index");
- cdb_trunc(CDB_DIRECTORY);
- ForEachUser(CtdlRebuildDirectoryIndex_backend, NULL);
-}
-
-
-// Configure Internet email addresses for a user account, updating the Directory Index in the process
-void CtdlSetEmailAddressesForUser(char *requested_user, char *new_emailaddrs) {
- struct ctdluser usbuf;
- int i;
- char buf[SIZ];
-
- if (CtdlGetUserLock(&usbuf, requested_user) != 0) { // We can lock because the DirectoryIndex functions don't lock.
- return; // Silently fail here if the specified user does not exist.
- }
-
- syslog(LOG_DEBUG, "internet_addressing: setting email addresses for <%s> to <%s>", usbuf.fullname, new_emailaddrs);
-
- // Delete all of the existing directory index records for the user (easier this way)
- for (i=0; i<num_tokens(usbuf.emailaddrs, '|'); ++i) {
- extract_token(buf, usbuf.emailaddrs, i, '|', sizeof buf);
- CtdlDirectoryDelUser(buf, requested_user);
- }
-
- strcpy(usbuf.emailaddrs, new_emailaddrs); // make it official.
-
- // Index all of the new email addresses (they've already been sanitized)
- for (i=0; i<num_tokens(usbuf.emailaddrs, '|'); ++i) {
- extract_token(buf, usbuf.emailaddrs, i, '|', sizeof buf);
- CtdlDirectoryAddUser(buf, requested_user);
- }
-
- CtdlPutUserLock(&usbuf);
-}
-
-
-/*
- * Auto-generate an Internet email address for a user account
- */
-void AutoGenerateEmailAddressForUser(struct ctdluser *user) {
- char synthetic_email_addr[1024];
- int i, j;
- int u = 0;
-
- for (i=0; u==0; ++i) {
- if (i == 0) {
- // first try just converting the user name to lowercase and replacing spaces with underscores
- snprintf(synthetic_email_addr, sizeof synthetic_email_addr, "%s@%s", user->fullname, CtdlGetConfigStr("c_fqdn"));
- for (j=0; ((synthetic_email_addr[j] != '\0')&&(synthetic_email_addr[j] != '@')); j++) {
- synthetic_email_addr[j] = tolower(synthetic_email_addr[j]);
- if (!isalnum(synthetic_email_addr[j])) {
- synthetic_email_addr[j] = '_';
- }
- }
- }
- else if (i == 1) {
- // then try 'ctdl' followed by the user number
- snprintf(synthetic_email_addr, sizeof synthetic_email_addr, "ctdl%08lx@%s", user->usernum, CtdlGetConfigStr("c_fqdn"));
- }
- else if (i > 1) {
- // oof. just keep trying other numbers until we find one
- snprintf(synthetic_email_addr, sizeof synthetic_email_addr, "ctdl%08x@%s", i, CtdlGetConfigStr("c_fqdn"));
- }
- u = CtdlDirectoryLookup(NULL, synthetic_email_addr, 0);
- syslog(LOG_DEBUG, "user_ops: address <%s> lookup returned <%d>", synthetic_email_addr, u);
- }
-
- CtdlSetEmailAddressesForUser(user->fullname, synthetic_email_addr);
- strncpy(CC->user.emailaddrs, synthetic_email_addr, sizeof(user->emailaddrs));
- syslog(LOG_DEBUG, "user_ops: auto-generated email address <%s> for <%s>", synthetic_email_addr, user->fullname);
-}
-
-
-// Determine whether the supplied email address is subscribed to the supplied room's mailing list service.
-int is_email_subscribed_to_list(char *email, char *room_name) {
- struct ctdlroom room;
- long roomnum;
- char *roomnetconfig;
- int found_it = 0;
-
- if (CtdlGetRoom(&room, room_name)) {
- return(0); // room not found, so definitely not subscribed
- }
-
- // If this room has the QR2_SMTP_PUBLIC flag set, anyone may email a post to this room, even non-subscribers.
- if (room.QRflags2 & QR2_SMTP_PUBLIC) {
- return(1);
- }
-
- roomnum = room.QRnumber;
- roomnetconfig = LoadRoomNetConfigFile(roomnum);
- if (roomnetconfig == NULL) {
- return(0);
- }
-
- // We're going to do a very sloppy match here and simply search for the specified email address
- // anywhere in the room's netconfig. If you don't like this, fix it yourself.
- if (bmstrcasestr(roomnetconfig, email)) {
- found_it = 1;
- }
- else {
- found_it = 0;
- }
-
- free(roomnetconfig);
- return(found_it);
-}
+++ /dev/null
-
-#include "server.h"
-#include "ctdl_module.h"
-
-struct recptypes *validate_recipients(char *recipients, const char *RemoteIdentifier, int Flags);
-void free_recipients(struct recptypes *);
-void process_rfc822_addr(const char *rfc822, char *user, char *node, char *name);
-char *rfc822_fetch_field(const char *rfc822, const char *fieldname);
-void sanitize_truncated_recipient(char *str);
-char *qp_encode_email_addrs(char *source);
-int alias (char *name);
-int IsDirectory(char *addr, int allow_masq_domains);
-void CtdlRebuildDirectoryIndex(void);
-int CtdlDirectoryAddUser(char *internet_addr, char *citadel_addr);
-int CtdlDirectoryDelUser(char *internet_addr, char *citadel_addr);
-int CtdlDirectoryLookup(char *target, char *internet_addr, size_t targbuflen);
-void CtdlSetEmailAddressesForUser(char *requested_user, char *new_emailaddrs);
-void AutoGenerateEmailAddressForUser(struct ctdluser *user);
-struct CtdlMessage *convert_internet_message(char *rfc822);
-struct CtdlMessage *convert_internet_message_buf(StrBuf **rfc822);
-int CtdlIsMe(char *addr, int addr_buf_len);
-int CtdlHostAlias(char *fqdn);
-char *harvest_collected_addresses(struct CtdlMessage *msg);
-int is_email_subscribed_to_list(char *email, char *room_name);
-
-/*
- * Values that can be returned by CtdlHostAlias()
- */
-enum {
- hostalias_nomatch,
- hostalias_localhost,
- hostalias_masq
-};
-
-extern char *inetcfg;
+++ /dev/null
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define LISTING_FOLLOWS 100
-#define CIT_OK 200
-#define MORE_DATA 300
-#define SEND_LISTING 400
-#define ERROR 500
-#define BINARY_FOLLOWS 600
-#define SEND_BINARY 700
-#define START_CHAT_MODE 800
-
-#define INTERNAL_ERROR 10
-#define TOO_BIG 11
-#define ILLEGAL_VALUE 12
-#define NOT_LOGGED_IN 20
-#define CMD_NOT_SUPPORTED 30
-#define SERVER_SHUTTING_DOWN 31
-#define PASSWORD_REQUIRED 40
-#define ALREADY_LOGGED_IN 41
-#define USERNAME_REQUIRED 42
-#define HIGHER_ACCESS_REQUIRED 50
-#define MAX_SESSIONS_EXCEEDED 51
-#define RESOURCE_BUSY 52
-#define RESOURCE_NOT_OPEN 53
-#define NOT_HERE 60
-#define INVALID_FLOOR_OPERATION 61
-#define NO_SUCH_USER 70
-#define FILE_NOT_FOUND 71
-#define ROOM_NOT_FOUND 72
-#define NO_SUCH_SYSTEM 73
-#define ALREADY_EXISTS 74
-#define MESSAGE_NOT_FOUND 75
-
-#define ASYNC_MSG 900
-#define ASYNC_GEXP 02
-
-#define QR_PERMANENT 1 /* Room does not purge */
-#define QR_INUSE 2 /* Set if in use, clear if avail */
-#define QR_PRIVATE 4 /* Set for any type of private room */
-#define QR_PASSWORDED 8 /* Set if there's a password too */
-#define QR_GUESSNAME 16 /* Set if it's a guessname room */
-#define QR_DIRECTORY 32 /* Directory room */
-#define QR_UPLOAD 64 /* Allowed to upload */
-#define QR_DOWNLOAD 128 /* Allowed to download */
-#define QR_VISDIR 256 /* Visible directory */
-#define QR_ANONONLY 512 /* Anonymous-Only room */
-#define QR_ANONOPT 1024 /* Anonymous-Option room */
-#define QR_NETWORK 2048 /* Shared network room */
-#define QR_PREFONLY 4096 /* Preferred status needed to enter */
-#define QR_READONLY 8192 /* Aide status required to post */
-#define QR_MAILBOX 16384 /* Set if this is a private mailbox */
-
-#define QR2_SYSTEM 1 /* System room; hide by default */
-#define QR2_SELFLIST 2 /* Self-service mailing list mgmt */
-#define QR2_COLLABDEL 4 /* Anyone who can post can delete */
-#define QR2_SUBJECTREQ 8 /* Subject strongly recommended */
-#define QR2_SMTP_PUBLIC 16 /* Listservice Subscribers may post */
-
-#define US_NEEDVALID 1 /* User needs to be validated */
-#define US_EXTEDIT 2 /* Always use external editor */
-#define US_PERM 4 /* Permanent user */
-#define US_LASTOLD 16 /* Print last old message with new */
-#define US_EXPERT 32 /* Experienced user */
-#define US_UNLISTED 64 /* Unlisted userlog entry */
-#define US_NOPROMPT 128 /* Don't prompt after each message */
-#define US_PROMPTCTL 256 /* <N>ext & <S>top work at prompt */
-#define US_DISAPPEAR 512 /* Use "disappearing msg prompts" */
-#define US_REGIS 1024 /* Registered user */
-#define US_PAGINATOR 2048 /* Pause after each screen of text */
-#define US_INTERNET 4096 /* Internet mail privileges */
-#define US_FLOORS 8192 /* User wants to see floors */
-#define US_COLOR 16384 /* User wants ANSI color support */
-#define US_USER_SET (US_LASTOLD | US_EXPERT | US_UNLISTED | \
- US_NOPROMPT | US_DISAPPEAR | US_PAGINATOR | \
- US_FLOORS | US_COLOR | US_PROMPTCTL | US_EXTEDIT)
-
-#define UA_KNOWN 2 /* Room appears in a 'known rooms' list */
-#define UA_GOTOALLOWED 4 /* User may goto this room if specified by exact name */
-#define UA_HASNEWMSGS 8 /* Unread messages exist in this room */
-#define UA_ZAPPED 16 /* User has forgotten (zapped) this room */
-#define UA_POSTALLOWED 32 /* User may post top-level messages here */
-#define UA_ADMINALLOWED 64 /* Aide or Room Aide rights exist here */
-#define UA_DELETEALLOWED 128 /* User is allowed to delete messages from this room */
-#define UA_REPLYALLOWED 256 /* User is allowed to reply to existing messages here */
-
-#ifdef __cplusplus
-}
-#endif
+++ /dev/null
-/*
- * Message journaling functions.
- *
- * Copyright (c) 1987-2020 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.
- */
-
-#include <stdio.h>
-#include <libcitadel.h>
-#include "ctdl_module.h"
-#include "citserver.h"
-#include "config.h"
-#include "user_ops.h"
-#include "serv_vcard.h" /* Needed for vcard_getuser and extract_inet_email_addrs */
-#include "internet_addressing.h"
-#include "journaling.h"
-
-struct jnlq *jnlq = NULL; /* journal queue */
-
-/*
- * Hand off a copy of a message to be journalized.
- */
-void JournalBackgroundSubmit(struct CtdlMessage *msg,
- StrBuf *saved_rfc822_version,
- struct recptypes *recps) {
-
- struct jnlq *jptr = NULL;
-
- /* Avoid double journaling! */
- if (!CM_IsEmpty(msg, eJournal)) {
- FreeStrBuf(&saved_rfc822_version);
- return;
- }
-
- jptr = (struct jnlq *)malloc(sizeof(struct jnlq));
- if (jptr == NULL) {
- FreeStrBuf(&saved_rfc822_version);
- return;
- }
- memset(jptr, 0, sizeof(struct jnlq));
- if (recps != NULL) memcpy(&jptr->recps, recps, sizeof(struct recptypes));
- if (!CM_IsEmpty(msg, eAuthor)) jptr->from = strdup(msg->cm_fields[eAuthor]);
- if (!CM_IsEmpty(msg, erFc822Addr)) jptr->rfca = strdup(msg->cm_fields[erFc822Addr]);
- if (!CM_IsEmpty(msg, eMsgSubject)) jptr->subj = strdup(msg->cm_fields[eMsgSubject]);
- if (!CM_IsEmpty(msg, emessageId)) jptr->msgn = strdup(msg->cm_fields[emessageId]);
- jptr->rfc822 = SmashStrBuf(&saved_rfc822_version);
-
- /* Add to the queue */
- begin_critical_section(S_JOURNAL_QUEUE);
- jptr->next = jnlq;
- jnlq = jptr;
- end_critical_section(S_JOURNAL_QUEUE);
-}
-
-
-/*
- * Convert a local user name to an internet email address for the journal
- * FIXME - grab the user's Internet email address from the user record, not from vCard !!!!
- */
-void local_to_inetemail(char *inetemail, char *localuser, size_t inetemail_len) {
- struct ctdluser us;
- struct vCard *v;
-
- strcpy(inetemail, "");
- if (CtdlGetUser(&us, localuser) != 0) {
- return;
- }
-
- v = vcard_get_user(&us);
- if (v == NULL) {
- return;
- }
-
- extract_inet_email_addrs(inetemail, inetemail_len, NULL, 0, v, 1);
- vcard_free(v);
-}
-
-
-/*
- * Called by JournalRunQueue() to send an individual message.
- */
-void JournalRunQueueMsg(struct jnlq *jmsg) {
-
- struct CtdlMessage *journal_msg = NULL;
- struct recptypes *journal_recps = NULL;
- StrBuf *message_text = NULL;
- char mime_boundary[256];
- long mblen;
- long rfc822len;
- char recipient[256];
- char inetemail[256];
- static int seq = 0;
- int i;
-
- if (jmsg == NULL)
- return;
- journal_recps = validate_recipients(CtdlGetConfigStr("c_journal_dest"), NULL, 0);
- if (journal_recps != NULL) {
-
- if ( (journal_recps->num_local > 0)
- || (journal_recps->num_internet > 0)
- || (journal_recps->num_room > 0)
- ) {
-
- /*
- * Construct journal message.
- * Note that we are transferring ownership of some of the memory here.
- */
- journal_msg = malloc(sizeof(struct CtdlMessage));
- memset(journal_msg, 0, sizeof(struct CtdlMessage));
- journal_msg->cm_magic = CTDLMESSAGE_MAGIC;
- journal_msg->cm_anon_type = MES_NORMAL;
- journal_msg->cm_format_type = FMT_RFC822;
- CM_SetField(journal_msg, eJournal, HKEY("is journal"));
-
- if (!IsEmptyStr(jmsg->from)) {
- CM_SetField(journal_msg, eAuthor, jmsg->from, -1);
- }
-
- if (!IsEmptyStr(jmsg->rfca)) {
- CM_SetField(journal_msg, erFc822Addr, jmsg->rfca, -1);
- }
-
- if (!IsEmptyStr(jmsg->subj)) {
- CM_SetField(journal_msg, eMsgSubject, jmsg->subj, -1);
- }
-
- mblen = snprintf(mime_boundary, sizeof(mime_boundary),
- "--Citadel-Journal-%08lx-%04x--", time(NULL), ++seq);
-
- if (!IsEmptyStr(jmsg->rfc822)) {
- rfc822len = strlen(jmsg->rfc822);
- }
- else {
- rfc822len = 0;
- }
-
- message_text = NewStrBufPlain(NULL, rfc822len + sizeof(struct recptypes) + 1024);
-
- /*
- * Here is where we begin to compose the journalized message.
- * (The "ExJournalReport" header is consumed by some email retention services which assume the journaling agent is Exchange.)
- */
- StrBufAppendBufPlain(
- message_text,
- HKEY("Content-type: multipart/mixed; boundary=\""),
- 0
- );
-
- StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0);
-
- StrBufAppendBufPlain(
- message_text,
- HKEY("\"\r\n"
- "Content-Identifier: ExJournalReport\r\n"
- "MIME-Version: 1.0\r\n"
- "\n"
- "--"),
- 0
- );
-
- StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0);
-
- StrBufAppendBufPlain(
- message_text,
- HKEY("\r\n"
- "Content-type: text/plain\r\n"
- "\r\n"
- "Sender: "), 0);
-
- if (CM_IsEmpty(journal_msg, eAuthor))
- StrBufAppendBufPlain(
- message_text,
- journal_msg->cm_fields[eAuthor], -1, 0);
- else
- StrBufAppendBufPlain(
- message_text,
- HKEY("(null)"), 0);
-
- if (!CM_IsEmpty(journal_msg, erFc822Addr)) {
- StrBufAppendPrintf(message_text, " <%s>",
- journal_msg->cm_fields[erFc822Addr]);
- }
-
- StrBufAppendBufPlain(message_text, HKEY("\r\nMessage-ID: <"), 0);
- StrBufAppendBufPlain(message_text, jmsg->msgn, -1, 0);
- StrBufAppendBufPlain(message_text, HKEY(">\r\nRecipients:\r\n"), 0);
-
- if (jmsg->recps.num_local > 0) {
- for (i=0; i<jmsg->recps.num_local; ++i) {
- extract_token(recipient, jmsg->recps.recp_local, i, '|', sizeof recipient);
- local_to_inetemail(inetemail, recipient, sizeof inetemail);
- StrBufAppendPrintf(message_text, " %s <%s>\r\n", recipient, inetemail);
- }
- }
-
- if (jmsg->recps.num_internet > 0) {
- for (i=0; i<jmsg->recps.num_internet; ++i) {
- extract_token(recipient, jmsg->recps.recp_internet, i, '|', sizeof recipient);
- StrBufAppendPrintf(message_text, " %s\r\n", recipient);
- }
- }
-
- StrBufAppendBufPlain(message_text, HKEY("\r\n" "--"), 0);
- StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0);
- StrBufAppendBufPlain(message_text, HKEY("\r\nContent-type: message/rfc822\r\n\r\n"), 0);
- StrBufAppendBufPlain(message_text, jmsg->rfc822, rfc822len, 0);
- StrBufAppendBufPlain(message_text, HKEY("--"), 0);
- StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0);
- StrBufAppendBufPlain(message_text, HKEY("--\r\n"), 0);
-
- CM_SetAsFieldSB(journal_msg, eMesageText, &message_text);
- free(jmsg->rfc822);
- free(jmsg->msgn);
- jmsg->rfc822 = NULL;
- jmsg->msgn = NULL;
-
- /* Submit journal message */
- CtdlSubmitMsg(journal_msg, journal_recps, "");
- CM_Free(journal_msg);
- }
-
- free_recipients(journal_recps);
- }
-
- /* We are responsible for freeing this memory. */
- free(jmsg);
-}
-
-
-/*
- * Run the queue.
- */
-void JournalRunQueue(void) {
- struct jnlq *jptr = NULL;
-
- while (jnlq != NULL) {
- begin_critical_section(S_JOURNAL_QUEUE);
- if (jnlq != NULL) {
- jptr = jnlq;
- jnlq = jnlq->next;
- }
- end_critical_section(S_JOURNAL_QUEUE);
- JournalRunQueueMsg(jptr);
- }
-}
-
-
+++ /dev/null
-struct jnlq {
- struct jnlq *next;
- struct recptypes recps;
- char *from;
- char *node;
- char *rfca;
- char *subj;
- char *msgn;
- char *rfc822;
-};
-
-void JournalBackgroundSubmit(struct CtdlMessage *msg,
- StrBuf *saved_rfc822_version,
- struct recptypes *recps);
-void JournalRunQueueMsg(struct jnlq *jmsg);
-void JournalRunQueue(void);
+++ /dev/null
-// These functions implement the portions of AUTHMODE_LDAP and AUTHMODE_LDAP_AD which
-// actually speak to the LDAP server.
-//
-// Copyright (c) 2011-2022 by the citadel.org development 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.
-
-// ldapsearch -D uid=admin,cn=users,cn=compat,dc=demo1,dc=freeipa,dc=org -w Secret123 -h ipa.demo1.freeipa.org
-
-int ctdl_require_ldap_version = 3;
-
-#define _GNU_SOURCE // Needed to suppress warning about vasprintf() when running on Linux/Linux
-#include <stdio.h>
-#include <libcitadel.h>
-#include "citserver.h"
-#include "citadel_ldap.h"
-#include "ctdl_module.h"
-#include "user_ops.h"
-#include "internet_addressing.h"
-#include "config.h"
-#include <ldap.h>
-
-
-// Utility function, supply a search result and get back the fullname (display name, common name, etc) from the first result
-//
-// POSIX schema: the display name will be found in "cn" (common name)
-// Active Directory: the display name will be found in "displayName"
-//
-void derive_fullname_from_ldap_result(char *fullname, int fullname_size, LDAP *ldserver, LDAPMessage *search_result) {
- struct berval **values;
-
- if (fullname == NULL) return;
- if (search_result == NULL) return;
- if (ldserver == NULL) return;
-
- if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD) {
- values = ldap_get_values_len(ldserver, search_result, "displayName");
- if (values) {
- if (ldap_count_values_len(values) > 0) {
- safestrncpy(fullname, values[0]->bv_val, fullname_size);
- syslog(LOG_DEBUG, "ldap: displayName = %s", fullname);
- }
- ldap_value_free_len(values);
- }
- }
- else {
- values = ldap_get_values_len(ldserver, search_result, "cn");
- if (values) {
- if (ldap_count_values_len(values) > 0) {
- safestrncpy(fullname, values[0]->bv_val, fullname_size);
- syslog(LOG_DEBUG, "ldap: cn = %s", fullname);
- }
- ldap_value_free_len(values);
- }
- }
-}
-
-
-// Utility function, supply a search result and get back the uid from the first result
-//
-// POSIX schema: numeric user id will be in the "uidNumber" attribute
-// Active Directory: we make a uid hashed from "objectGUID"
-//
-uid_t derive_uid_from_ldap(LDAP *ldserver, LDAPMessage *entry) {
- struct berval **values;
- uid_t uid = (-1);
-
- if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD) {
- values = ldap_get_values_len(ldserver, entry, "objectGUID");
- if (values) {
- if (ldap_count_values_len(values) > 0) {
- uid = abs(HashLittle(values[0]->bv_val, values[0]->bv_len));
- }
- ldap_value_free_len(values);
- }
- }
- else {
- values = ldap_get_values_len(ldserver, entry, "uidNumber");
- if (values) {
- if (ldap_count_values_len(values) > 0) {
- uid = atoi(values[0]->bv_val);
- }
- ldap_value_free_len(values);
- }
- }
-
- syslog(LOG_DEBUG, "ldap: uid = %d", uid);
- return(uid);
-}
-
-
-// Wrapper function for ldap_initialize() that consistently fills in the correct fields
-int ctdl_ldap_initialize(LDAP **ld) {
-
- char server_url[256];
- int ret;
-
- snprintf(server_url, sizeof server_url, "ldap://%s:%d", CtdlGetConfigStr("c_ldap_host"), CtdlGetConfigInt("c_ldap_port"));
- ret = ldap_initialize(ld, server_url);
- if (ret != LDAP_SUCCESS) {
- syslog(LOG_ERR, "ldap: could not connect to %s : %m", server_url);
- *ld = NULL;
- return(errno);
- }
-
- return(ret);
-}
-
-
-// Bind to the LDAP server and return a working handle
-LDAP *ctdl_ldap_bind(void) {
- LDAP *ldserver = NULL;
- int i;
-
- if (ctdl_ldap_initialize(&ldserver) != LDAP_SUCCESS) {
- return(NULL);
- }
-
- ldap_set_option(ldserver, LDAP_OPT_PROTOCOL_VERSION, &ctdl_require_ldap_version);
- ldap_set_option(ldserver, LDAP_OPT_REFERRALS, (void *)LDAP_OPT_OFF);
-
- striplt(CtdlGetConfigStr("c_ldap_bind_dn"));
- striplt(CtdlGetConfigStr("c_ldap_bind_pw"));
- i = ldap_simple_bind_s(ldserver,
- (!IsEmptyStr(CtdlGetConfigStr("c_ldap_bind_dn")) ? CtdlGetConfigStr("c_ldap_bind_dn") : NULL),
- (!IsEmptyStr(CtdlGetConfigStr("c_ldap_bind_pw")) ? CtdlGetConfigStr("c_ldap_bind_pw") : NULL)
- );
- if (i != LDAP_SUCCESS) {
- syslog(LOG_ERR, "ldap: Cannot bind: %s (%d)", ldap_err2string(i), i);
- return(NULL);
- }
-
- return(ldserver);
-}
-
-
-// Look up a user in the directory to see if this is an account that can be authenticated
-//
-// POSIX schema: Search all "inetOrgPerson" objects with "uid" set to the supplied username
-// Active Directory: Look for an account with "sAMAccountName" set to the supplied username
-//
-int CtdlTryUserLDAP(char *username, char *found_dn, int found_dn_size, char *fullname, int fullname_size, uid_t *uid) {
- LDAP *ldserver = NULL;
- LDAPMessage *search_result = NULL;
- LDAPMessage *entry = NULL;
- char searchstring[1024];
- struct timeval tv;
- char *user_dn = NULL;
-
- ldserver = ctdl_ldap_bind();
- if (!ldserver) return(-1);
-
- if (fullname) safestrncpy(fullname, username, fullname_size);
- tv.tv_sec = 10;
- tv.tv_usec = 0;
-
- if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD) {
- snprintf(searchstring, sizeof(searchstring), "(sAMAccountName=%s)", username);
- }
- else {
- snprintf(searchstring, sizeof(searchstring), "(&(objectclass=inetOrgPerson)(uid=%s))", username);
- }
-
- syslog(LOG_DEBUG, "ldap: search: %s", searchstring);
- syslog(LOG_DEBUG, "ldap: search results: %s", ldap_err2string(ldap_search_ext_s(
- ldserver, // ld
- CtdlGetConfigStr("c_ldap_base_dn"), // base
- LDAP_SCOPE_SUBTREE, // scope
- searchstring, // filter
- NULL, // attrs (all attributes)
- 0, // attrsonly (attrs + values)
- NULL, // serverctrls (none)
- NULL, // clientctrls (none)
- &tv, // timeout
- 1, // sizelimit (1 result max)
- &search_result // put the result here
- )));
-
- // Ignore the return value of ldap_search_ext_s(). Sometimes it returns an error even when
- // the search succeeds. Instead, we check to see whether search_result is still NULL.
- if (search_result == NULL) {
- syslog(LOG_DEBUG, "ldap: zero search results were returned");
- ldap_unbind(ldserver);
- return(2);
- }
-
- // At this point we've got at least one result from our query. If there are multiple
- // results, we still only look at the first one.
- entry = ldap_first_entry(ldserver, search_result);
- if (entry) {
-
- user_dn = ldap_get_dn(ldserver, entry);
- if (user_dn) {
- syslog(LOG_DEBUG, "ldap: dn = %s", user_dn);
- }
-
- derive_fullname_from_ldap_result(fullname, fullname_size, ldserver, search_result);
- *uid = derive_uid_from_ldap(ldserver, search_result);
- }
-
- // free the results
- ldap_msgfree(search_result);
-
- // unbind so we can go back in as the authenticating user
- ldap_unbind(ldserver);
-
- if (!user_dn) {
- syslog(LOG_DEBUG, "ldap: No such user was found.");
- return(4);
- }
-
- if (found_dn) safestrncpy(found_dn, user_dn, found_dn_size);
- ldap_memfree(user_dn);
- return(0);
-}
-
-
-// This is an extension of CtdlTryPassword() which gets called when using LDAP authentication.
-int CtdlTryPasswordLDAP(char *user_dn, const char *password) {
- LDAP *ldserver = NULL;
- int i = (-1);
-
- if (IsEmptyStr(password)) {
- syslog(LOG_DEBUG, "ldap: empty passwords are not permitted");
- return(1);
- }
-
- syslog(LOG_DEBUG, "ldap: trying to bind as %s", user_dn);
- i = ctdl_ldap_initialize(&ldserver);
- if (i == LDAP_SUCCESS) {
- ldap_set_option(ldserver, LDAP_OPT_PROTOCOL_VERSION, &ctdl_require_ldap_version);
- i = ldap_simple_bind_s(ldserver, user_dn, password);
- if (i == LDAP_SUCCESS) {
- syslog(LOG_DEBUG, "ldap: bind succeeded");
- }
- else {
- syslog(LOG_DEBUG, "ldap: Cannot bind: %s (%d)", ldap_err2string(i), i);
- }
- ldap_set_option(ldserver, LDAP_OPT_REFERRALS, (void *)LDAP_OPT_OFF);
- ldap_unbind(ldserver);
- }
-
- if (i == LDAP_SUCCESS) {
- return(0);
- }
-
- return(1);
-}
-
-
-// set multiple properties
-// returns nonzero only if property changed.
-int vcard_set_props_iff_different(struct vCard *v, char *propname, int numvals, char **vals) {
- int i;
- char *oldval = "";
- for (i=0; i<numvals; i++) {
- oldval = vcard_get_prop(v, propname, 0, i, 0);
- if (oldval == NULL) break;
- if (strcmp(vals[i],oldval)) break;
- }
- if (i != numvals) {
- syslog(LOG_DEBUG, "ldap: vcard property %s, element %d of %d changed from %s to %s", propname, i, numvals, oldval, vals[i]);
- for (i=0; i<numvals; i++) {
- vcard_set_prop(v,propname,vals[i],(i==0) ? 0 : 1);
- }
- return 1;
- }
- return 0;
-}
-
-
-// set one property
-// returns nonzero only if property changed.
-int vcard_set_one_prop_iff_different(struct vCard *v,char *propname, char *newfmt, ...) {
- va_list args;
- char *newvalue;
- int did_change = 0;
- va_start(args,newfmt);
- if (vasprintf(&newvalue, newfmt, args) < 0) {
- syslog(LOG_ERR, "ldap: out of memory");
- return 0;
- }
- did_change = vcard_set_props_iff_different(v, propname, 1, &newvalue);
- va_end(args);
- free(newvalue);
- return did_change;
-}
-
-
-// Learn LDAP attributes and stuff them into the vCard.
-// Returns nonzero if we changed anything.
-int Ctdl_LDAP_to_vCard(char *ldap_dn, struct vCard *v) {
- int changed_something = 0;
- LDAP *ldserver = NULL;
- struct timeval tv;
- LDAPMessage *search_result = NULL;
- LDAPMessage *entry = NULL;
- struct berval **givenName;
- struct berval **sn;
- struct berval **cn;
- struct berval **initials;
- struct berval **o;
- struct berval **street;
- struct berval **l;
- struct berval **st;
- struct berval **postalCode;
- struct berval **telephoneNumber;
- struct berval **mobile;
- struct berval **homePhone;
- struct berval **facsimileTelephoneNumber;
- struct berval **mail;
- struct berval **uid;
- struct berval **homeDirectory;
- struct berval **uidNumber;
- struct berval **loginShell;
- struct berval **gidNumber;
- struct berval **c;
- struct berval **title;
- struct berval **uuid;
- char *attrs[] = { "*","+",NULL};
-
- if (!ldap_dn) return(0);
- if (!v) return(0);
-
- ldserver = ctdl_ldap_bind();
- if (!ldserver) return(-1);
-
- tv.tv_sec = 10;
- tv.tv_usec = 0;
-
- syslog(LOG_DEBUG, "ldap: search: %s", ldap_dn);
- syslog(LOG_DEBUG, "ldap: search results: %s", ldap_err2string(ldap_search_ext_s(
- ldserver, // ld
- ldap_dn, // base
- LDAP_SCOPE_SUBTREE, // scope
- NULL, // filter
- attrs, // attrs (all attributes)
- 0, // attrsonly (attrs + values)
- NULL, // serverctrls (none)
- NULL, // clientctrls (none)
- &tv, // timeout
- 1, // sizelimit (1 result max)
- &search_result // res
- )));
-
- // Ignore the return value of ldap_search_ext_s(). Sometimes it returns an error even when
- // the search succeeds. Instead, we check to see whether search_result is still NULL.
- if (search_result == NULL) {
- syslog(LOG_DEBUG, "ldap: zero search results were returned");
- ldap_unbind(ldserver);
- return(0);
- }
-
- // At this point we've got at least one result from our query. If there are multiple
- // results, we still only look at the first one.
- entry = ldap_first_entry(ldserver, search_result);
- if (entry) {
- syslog(LOG_DEBUG, "ldap: search got user details for vcard.");
- givenName = ldap_get_values_len(ldserver, search_result, "givenName");
- sn = ldap_get_values_len(ldserver, search_result, "sn");
- cn = ldap_get_values_len(ldserver, search_result, "cn");
- initials = ldap_get_values_len(ldserver, search_result, "initials");
- title = ldap_get_values_len(ldserver, search_result, "title");
- o = ldap_get_values_len(ldserver, search_result, "o");
- street = ldap_get_values_len(ldserver, search_result, "street");
- l = ldap_get_values_len(ldserver, search_result, "l");
- st = ldap_get_values_len(ldserver, search_result, "st");
- postalCode = ldap_get_values_len(ldserver, search_result, "postalCode");
- telephoneNumber = ldap_get_values_len(ldserver, search_result, "telephoneNumber");
- mobile = ldap_get_values_len(ldserver, search_result, "mobile");
- homePhone = ldap_get_values_len(ldserver, search_result, "homePhone");
- facsimileTelephoneNumber = ldap_get_values_len(ldserver, search_result, "facsimileTelephoneNumber");
- mail = ldap_get_values_len(ldserver, search_result, "mail");
- uid = ldap_get_values_len(ldserver, search_result, "uid");
- homeDirectory = ldap_get_values_len(ldserver, search_result, "homeDirectory");
- uidNumber = ldap_get_values_len(ldserver, search_result, "uidNumber");
- loginShell = ldap_get_values_len(ldserver, search_result, "loginShell");
- gidNumber = ldap_get_values_len(ldserver, search_result, "gidNumber");
- c = ldap_get_values_len(ldserver, search_result, "c");
- uuid = ldap_get_values_len(ldserver, search_result, "entryUUID");
-
- if (street && l && st && postalCode && c) changed_something |= vcard_set_one_prop_iff_different(v,"adr",";;%s;%s;%s;%s;%s",street[0]->bv_val,l[0]->bv_val,st[0]->bv_val,postalCode[0]->bv_val,c[0]->bv_val);
- if (telephoneNumber) changed_something |= vcard_set_one_prop_iff_different(v,"tel;work","%s",telephoneNumber[0]->bv_val);
- if (facsimileTelephoneNumber) changed_something |= vcard_set_one_prop_iff_different(v,"tel;fax","%s",facsimileTelephoneNumber[0]->bv_val);
- if (mobile) changed_something |= vcard_set_one_prop_iff_different(v,"tel;cell","%s",mobile[0]->bv_val);
- if (homePhone) changed_something |= vcard_set_one_prop_iff_different(v,"tel;home","%s",homePhone[0]->bv_val);
- if (givenName && sn) {
- if (initials) {
- changed_something |= vcard_set_one_prop_iff_different(v,"n","%s;%s;%s",sn[0]->bv_val,givenName[0]->bv_val,initials[0]->bv_val);
- }
- else {
- changed_something |= vcard_set_one_prop_iff_different(v,"n","%s;%s",sn[0]->bv_val,givenName[0]->bv_val);
- }
- }
-
- // FIXME we need a new way to do this.
- //if (mail) {
- //changed_something |= vcard_set_props_iff_different(v,"email;internet",ldap_count_values_len(mail),mail);
- //}
-
- if (uuid) changed_something |= vcard_set_one_prop_iff_different(v,"X-uuid","%s",uuid[0]->bv_val);
- if (o) changed_something |= vcard_set_one_prop_iff_different(v,"org","%s",o[0]->bv_val);
- if (cn) changed_something |= vcard_set_one_prop_iff_different(v,"fn","%s",cn[0]->bv_val);
- if (title) changed_something |= vcard_set_one_prop_iff_different(v,"title","%s",title[0]->bv_val);
-
- if (givenName) ldap_value_free_len(givenName);
- if (initials) ldap_value_free_len(initials);
- if (sn) ldap_value_free_len(sn);
- if (cn) ldap_value_free_len(cn);
- if (o) ldap_value_free_len(o);
- if (street) ldap_value_free_len(street);
- if (l) ldap_value_free_len(l);
- if (st) ldap_value_free_len(st);
- if (postalCode) ldap_value_free_len(postalCode);
- if (telephoneNumber) ldap_value_free_len(telephoneNumber);
- if (mobile) ldap_value_free_len(mobile);
- if (homePhone) ldap_value_free_len(homePhone);
- if (facsimileTelephoneNumber) ldap_value_free_len(facsimileTelephoneNumber);
- if (mail) ldap_value_free_len(mail);
- if (uid) ldap_value_free_len(uid);
- if (homeDirectory) ldap_value_free_len(homeDirectory);
- if (uidNumber) ldap_value_free_len(uidNumber);
- if (loginShell) ldap_value_free_len(loginShell);
- if (gidNumber) ldap_value_free_len(gidNumber);
- if (c) ldap_value_free_len(c);
- if (title) ldap_value_free_len(title);
- if (uuid) ldap_value_free_len(uuid);
- }
- // free the results
- ldap_msgfree(search_result);
-
- // unbind so we can go back in as the authenticating user
- ldap_unbind(ldserver);
- return(changed_something); // tell the caller whether we made any changes
-}
-
-
-// Extract a user's Internet email addresses from LDAP.
-// Returns zero if we got a valid set of addresses; nonzero for error.
-int extract_email_addresses_from_ldap(char *ldap_dn, char *emailaddrs) {
- LDAP *ldserver = NULL;
- struct timeval tv;
- LDAPMessage *search_result = NULL;
- LDAPMessage *entry = NULL;
- struct berval **mail;
- char *attrs[] = { "*","+",NULL };
-
- if (!ldap_dn) return(1);
- if (!emailaddrs) return(1);
-
- ldserver = ctdl_ldap_bind();
- if (!ldserver) return(-1);
-
- tv.tv_sec = 10;
- tv.tv_usec = 0;
-
- syslog(LOG_DEBUG, "ldap: search: %s", ldap_dn);
- syslog(LOG_DEBUG, "ldap: search results: %s", ldap_err2string(ldap_search_ext_s(
- ldserver, // ld
- ldap_dn, // base
- LDAP_SCOPE_SUBTREE, // scope
- NULL, // filter
- attrs, // attrs (all attributes)
- 0, // attrsonly (attrs + values)
- NULL, // serverctrls (none)
- NULL, // clientctrls (none)
- &tv, // timeout
- 1, // sizelimit (1 result max)
- &search_result // res
- )));
-
- // Ignore the return value of ldap_search_ext_s(). Sometimes it returns an error even when
- // the search succeeds. Instead, we check to see whether search_result is still NULL.
- if (search_result == NULL) {
- syslog(LOG_DEBUG, "ldap: zero search results were returned");
- ldap_unbind(ldserver);
- return(4);
- }
-
- // At this point we've got at least one result from our query.
- // If there are multiple results, we still only look at the first one.
- emailaddrs[0] = 0; // clear out any previous results
- entry = ldap_first_entry(ldserver, search_result);
- if (entry) {
- syslog(LOG_DEBUG, "ldap: search got user details");
- mail = ldap_get_values_len(ldserver, search_result, "mail");
- if (mail) {
- int q;
- for (q=0; q<ldap_count_values_len(mail); ++q) {
- if (IsDirectory(mail[q]->bv_val, 0)) {
- if ((strlen(emailaddrs) + mail[q]->bv_len + 2) > 512) {
- syslog(LOG_ERR, "ldap: can't fit all email addresses into user record");
- }
- else {
- if (!IsEmptyStr(emailaddrs)) {
- strcat(emailaddrs, "|");
- }
- strcat(emailaddrs, mail[q]->bv_val);
- }
- }
- }
- }
- }
-
- // free the results
- ldap_msgfree(search_result);
-
- // unbind so we can go back in as the authenticating user
- ldap_unbind(ldserver);
- return(0);
-}
-
-
-// Remember that a particular user exists in the Citadel database.
-// As we scan the LDAP tree we will remove users from this list when we find them.
-// At the end of the scan, any users remaining in this list are stale and should be deleted.
-void ldap_note_user_in_citadel(char *username, void *data) {
- return;
-}
-
-
-// Scan LDAP for users and populate Citadel's user database with everyone
-//
-// POSIX schema: All objects of class "inetOrgPerson"
-// Active Directory: Objects that are class "user" and class "person" but NOT class "computer"
-//
-void CtdlSynchronizeUsersFromLDAP(void) {
- LDAP *ldserver = NULL;
- LDAPMessage *search_result = NULL;
- LDAPMessage *entry = NULL;
- char *user_dn = NULL;
- char searchstring[1024];
- struct timeval tv;
-
- if ((CtdlGetConfigInt("c_auth_mode") != AUTHMODE_LDAP) && (CtdlGetConfigInt("c_auth_mode") != AUTHMODE_LDAP_AD)) {
- return; // If this site is not running LDAP, stop here.
- }
-
- syslog(LOG_INFO, "ldap: synchronizing Citadel user database from LDAP");
-
- // first, scan the existing Citadel user list
- // ForEachUser(ldap_note_user_in_citadel, NULL); // FIXME finish this
-
- ldserver = ctdl_ldap_bind();
- if (!ldserver) return;
-
- tv.tv_sec = 10;
- tv.tv_usec = 0;
-
- if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD) {
- snprintf(searchstring, sizeof(searchstring), "(&(objectClass=user)(objectClass=person)(!(objectClass=computer)))");
- }
- else {
- snprintf(searchstring, sizeof(searchstring), "(objectClass=inetOrgPerson)");
- }
-
- syslog(LOG_DEBUG, "ldap: search: %s", searchstring);
- syslog(LOG_DEBUG, "ldap: search results: %s", ldap_err2string(ldap_search_ext_s(
- ldserver, // ld
- CtdlGetConfigStr("c_ldap_base_dn"), // base
- LDAP_SCOPE_SUBTREE, // scope
- searchstring, // filter
- NULL, // attrs (all attributes)
- 0, // attrsonly (attrs + values)
- NULL, // serverctrls (none)
- NULL, // clientctrls (none)
- &tv, // timeout
- INT_MAX, // sizelimit (max)
- &search_result // put the result here
- )));
-
- // Ignore the return value of ldap_search_ext_s(). Sometimes it returns an error even when
- // the search succeeds. Instead, we check to see whether search_result is still NULL.
- if (search_result == NULL) {
- syslog(LOG_DEBUG, "ldap: zero search results were returned");
- ldap_unbind(ldserver);
- return;
- }
-
- syslog(LOG_DEBUG, "ldap: %d entries returned", ldap_count_entries(ldserver, search_result));
- for (entry=ldap_first_entry(ldserver, search_result); entry!=NULL; entry=ldap_next_entry(ldserver, entry)) {
- user_dn = ldap_get_dn(ldserver, entry);
- if (user_dn) {
- syslog(LOG_DEBUG, "ldap: found %s", user_dn);
-
- int fullname_size = 256;
- char fullname[256] = { 0 } ;
- uid_t uid = (-1);
- char new_emailaddrs[512] = { 0 } ;
-
- uid = derive_uid_from_ldap(ldserver, entry);
- derive_fullname_from_ldap_result(fullname, fullname_size, ldserver, entry);
- syslog(LOG_DEBUG, "ldap: display name: <%s> , uid = <%d>", fullname, uid);
-
- // now create or update the user
- int found_user;
- struct ctdluser usbuf;
-
- found_user = getuserbyuid(&usbuf, uid);
- if (found_user != 0) {
- create_user(fullname, CREATE_USER_DO_NOT_BECOME_USER, uid);
- found_user = getuserbyuid(&usbuf, uid);
- strcpy(fullname, usbuf.fullname);
- }
-
- if (found_user == 0) { // user record exists
- // now update the account email addresses if necessary
- if (CtdlGetConfigInt("c_ldap_sync_email_addrs") > 0) {
- if (extract_email_addresses_from_ldap(user_dn, new_emailaddrs) == 0) {
- if (strcmp(usbuf.emailaddrs, new_emailaddrs)) { // update only if changed
- CtdlSetEmailAddressesForUser(usbuf.fullname, new_emailaddrs);
- }
- }
- }
- }
- ldap_memfree(user_dn);
- }
- }
-
- // free the results
- ldap_msgfree(search_result);
-
- // unbind so we can go back in as the authenticating user
- ldap_unbind(ldserver);
-}
+++ /dev/null
-/*
- * Functions which handle hostname/address lookups and resolution
- *
- * Copyright (c) 1987-2019 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.
- */
-
-#include "sysdep.h"
-#include <string.h>
-#include <stdio.h>
-#include <syslog.h>
-#include <ctype.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <libcitadel.h>
-
-#include "context.h"
-#ifdef HAVE_RESOLV_H
-#include <arpa/nameser.h>
-#ifdef HAVE_ARPA_NAMESER_COMPAT_H
-#include <arpa/nameser_compat.h>
-#endif
-#include <resolv.h>
-#endif
-
-#include "domain.h"
-#include "locate_host.h"
-
-/* START: some missing macros on OpenBSD 3.9 */
-#ifndef NS_CMPRSFLGS
-#define NS_CMPRSFLGS 0xc0
-#endif
-#if !defined(NS_MAXCDNAME) && defined (MAXCDNAME)
-#define NS_MAXCDNAME MAXCDNAME
-#endif
-#if !defined(NS_INT16SZ) && defined(INT16SZ)
-#define NS_INT16SZ INT16SZ
-#define NS_INT32SZ INT32SZ
-#endif
-#ifndef NS_GET16
-# define NS_GET16 GETSHORT
-#endif
-/* END: some missing macros on OpenBSD 3.9 */
-
-
-/*
- * Given an open client socket, return the host name and IP address at the other end.
- * (IPv4 and IPv6 compatible)
- */
-void locate_host(char *tbuf, size_t n, char *abuf, size_t na, int client_socket)
-{
- struct sockaddr_in6 clientaddr;
- unsigned int addrlen = sizeof(clientaddr);
-
- tbuf[0] = 0;
- abuf[0] = 0;
-
- getpeername(client_socket, (struct sockaddr *)&clientaddr, &addrlen);
- getnameinfo((struct sockaddr *)&clientaddr, addrlen, tbuf, n, NULL, 0, 0);
- getnameinfo((struct sockaddr *)&clientaddr, addrlen, abuf, na, NULL, 0, NI_NUMERICHOST);
-
- /* Convert IPv6-mapped IPv4 addresses back to traditional dotted quad.
- *
- * Other code here, such as the RBL check, will expect IPv4 addresses to be represented
- * as dotted-quad, even if they come in over a hybrid IPv6/IPv4 socket.
- */
- if ( (strlen(abuf) > 7) && (!strncasecmp(abuf, "::ffff:", 7)) ) {
- if (!strcmp(abuf, tbuf)) strcpy(tbuf, &tbuf[7]);
- strcpy(abuf, &abuf[7]);
- }
-}
-
-
-/*
- * RBL check written by Edward S. Marshall [http://rblcheck.sourceforge.net]
- */
-#define RESULT_SIZE 4096 /* What is the longest result text we support? */
-int rblcheck_backend(char *domain, char *txtbuf, int txtbufsize) {
- int a, b, c;
- char *result = NULL;
- u_char fixedans[ PACKETSZ ];
- u_char *answer;
- int need_to_free_answer = 0;
- const u_char *cp;
- u_char *rp;
- const u_char *cend;
- const u_char *rend;
- int len;
- char *p = NULL;
- static int res_initted = 0;
-
- if (!res_initted) { /* only have to do this once */
- res_init();
- res_initted = 1;
- }
-
- /* Make our DNS query. */
- answer = fixedans;
- if (server_shutting_down) {
- if (txtbuf != NULL) {
- snprintf(txtbuf, txtbufsize, "System shutting down");
- }
- return (1);
- }
- len = res_query(domain, C_IN, T_A, answer, PACKETSZ);
-
- /* Was there a problem? If so, the domain doesn't exist. */
- if (len == -1) {
- if (txtbuf != NULL) {
- strcpy(txtbuf, "");
- }
- return(0);
- }
-
- if (len > PACKETSZ) {
- answer = malloc(len);
- need_to_free_answer = 1;
- len = res_query(domain, C_IN, T_A, answer, len);
- if( len == -1 ) {
- if (txtbuf != NULL) {
- snprintf(txtbuf, txtbufsize, "Message rejected due to known spammer source IP address");
- }
- if (need_to_free_answer) free(answer);
- return(1);
- }
- }
- if (server_shutting_down) {
- if (txtbuf != NULL) {
- snprintf(txtbuf, txtbufsize, "System shutting down");
- }
- if (need_to_free_answer) free(answer);
- return (1);
- }
-
- result = (char *) malloc(RESULT_SIZE);
- result[0] = '\0';
-
- /* Make another DNS query for textual data; this shouldn't
- * be a performance hit, since it'll now be cached at the
- * nameserver we're using.
- */
- len = res_query(domain, C_IN, T_TXT, answer, PACKETSZ);
- if (server_shutting_down) {
- if (txtbuf != NULL) {
- snprintf(txtbuf, txtbufsize, "System shutting down");
- }
- if (need_to_free_answer) free(answer);
- free(result);
- return (1);
- }
-
- /* Just in case there's no TXT record... */
- if (len ==(-1)) {
- if (txtbuf != NULL) {
- snprintf(txtbuf, txtbufsize, "Message rejected due to known spammer source IP address");
- }
- if (need_to_free_answer) free(answer);
- free(result);
- return(1);
- }
-
- /* Skip the header and the address we queried. */
- cp = answer + sizeof( HEADER );
- while( *cp != '\0' ) {
- a = *cp++;
- while( a-- )
- cp++;
- }
-
- /* This seems to be a bit of magic data that we need to
- * skip. I wish there were good online documentation
- * for programming for libresolv, so I'd know what I'm
- * skipping here. Anyone reading this, feel free to
- * enlighten me.
- */
- cp += 1 + NS_INT16SZ + NS_INT32SZ;
-
- /* Skip the type, class and ttl. */
- cp += (NS_INT16SZ * 2) + NS_INT32SZ;
-
- /* Get the length and end of the buffer. */
- NS_GET16(c, cp);
- cend = cp + c;
-
- /* Iterate over any multiple answers we might have. In
- * this context, it's unlikely, but anyway.
- */
- rp = (u_char *) result;
- rend = (u_char *) result + RESULT_SIZE - 1;
- while (cp < cend && rp < rend) {
- a = *cp++;
- if (a != 0) {
- for (b = a; b > 0 && cp < cend && rp < rend; b--) {
- if (*cp == '\n' || *cp == '"' || *cp == '\\') {
- *rp++ = '\\';
- }
- *rp++ = *cp++;
- }
- }
- }
- *rp = '\0';
- if (txtbuf != NULL) {
- long len;
- len = snprintf(txtbuf, txtbufsize, "%s", result);
-
- /* Remove nonprintable characters */
- for (p = txtbuf; *p != '\0'; p++) {
- if (!isprint(*p)) {
- memmove (p,
- p + 1,
- len - (p - txtbuf) - 1);
- }
- }
- }
- if (need_to_free_answer) free(answer);
- free(result);
- return(1);
-}
-
-
-/*
- * Check to see if the client host is on some sort of spam list (RBL)
- * If spammer, returns nonzero and places reason in 'message_to_spammer'
- */
-int rbl_check(char *cs_addr, char *message_to_spammer)
-{
- char tbuf[256] = "";
- int suffix_pos = 0;
- int rbl;
- int rc;
- int num_rbl;
- char rbl_domains[SIZ];
- char txt_answer[1024];
- struct timeval tx_start;
- struct timeval tx_finish;
-
- rc = 0;
- strcpy(message_to_spammer, "ok");
- gettimeofday(&tx_start, NULL); /* start a stopwatch for performance timing */
-
- if ((strchr(cs_addr, '.')) && (!strchr(cs_addr, ':'))) {
- int a1, a2, a3, a4;
-
- sscanf(cs_addr, "%d.%d.%d.%d", &a1, &a2, &a3, &a4);
- snprintf(tbuf, sizeof tbuf, "%d.%d.%d.%d.", a4, a3, a2, a1);
- suffix_pos = strlen(tbuf);
- }
- else if ((!strchr(cs_addr, '.')) && (strchr(cs_addr, ':'))) {
- int num_colons = 0;
- int i = 0;
- char workbuf[sizeof tbuf];
- char *ptr;
-
- /* tedious code to expand and reverse an IPv6 address */
- safestrncpy(tbuf, cs_addr, sizeof tbuf);
- num_colons = haschar(tbuf, ':');
- if ((num_colons < 2) || (num_colons > 7))
- goto finish_rbl; /* badly formed address */
-
- /* expand the "::" shorthand */
- while (num_colons < 7) {
- ptr = strstr(tbuf, "::");
- if (!ptr)
- goto finish_rbl; /* badly formed address */
-
- ++ptr;
- strcpy(workbuf, ptr);
- strcpy(ptr, ":");
- strcat(ptr, workbuf);
- ++num_colons;
- }
-
- /* expand to 32 hex characters with no colons */
- strcpy(workbuf, tbuf);
- strcpy(tbuf, "00000000000000000000000000000000");
- for (i=0; i<8; ++i) {
- char tokbuf[5];
- extract_token(tokbuf, workbuf, i, ':', sizeof tokbuf);
- memcpy(&tbuf[ (i*4) + (4-strlen(tokbuf)) ], tokbuf, strlen(tokbuf) );
- }
- if (strlen(tbuf) != 32) {
- goto finish_rbl;
- }
-
- /* now reverse it and add dots */
- strcpy(workbuf, tbuf);
- for (i=0; i<32; ++i) {
- tbuf[i*2] = workbuf[31-i];
- tbuf[(i*2)+1] = '.';
- }
- tbuf[64] = 0;
- suffix_pos = 64;
- }
- else {
- goto finish_rbl; /* unknown address format */
- }
-
- /* See if we have any RBL domains configured */
- num_rbl = get_hosts(rbl_domains, "rbl");
- if (num_rbl < 1)
- {
- goto finish_rbl;
- }
-
- /* Try all configured RBL's */
- for (rbl=0; rbl<num_rbl; ++rbl) {
- extract_token(&tbuf[suffix_pos], rbl_domains, rbl, '|', (sizeof tbuf - suffix_pos));
-
- if (rblcheck_backend(tbuf, txt_answer, sizeof txt_answer)) {
- strcpy(message_to_spammer, txt_answer);
- syslog(LOG_INFO, "RBL: %s %s", cs_addr, txt_answer);
- rc = 1;
- }
- }
-finish_rbl:
- /* How long did this transaction take? */
- gettimeofday(&tx_finish, NULL);
-
- syslog(LOG_WARNING, "rbl: %s [%ld.%06ld] %s",
- cs_addr,
- ((tx_finish.tv_sec*1000000 + tx_finish.tv_usec) - (tx_start.tv_sec*1000000 + tx_start.tv_usec)) / 1000000,
- ((tx_finish.tv_sec*1000000 + tx_finish.tv_usec) - (tx_start.tv_sec*1000000 + tx_start.tv_usec)) % 1000000,
- (rc)?"found":"none found"
- );
-
- return rc;
-}
-
-
-/*
- * Convert a host name to a dotted quad address.
- * Returns zero on success or nonzero on failure.
- *
- * FIXME this is obviously not IPv6 compatible.
- */
-int hostname_to_dotted_quad(char *addr, char *host) {
- struct hostent *ch;
- const char *i;
- int a1, a2, a3, a4;
-
- ch = gethostbyname(host);
- if (ch == NULL) {
- strcpy(addr, "0.0.0.0");
- return(1);
- }
-
- i = (const char *) ch->h_addr_list[0];
- a1 = ((*i++) & 0xff);
- a2 = ((*i++) & 0xff);
- a3 = ((*i++) & 0xff);
- a4 = ((*i++) & 0xff);
- sprintf(addr, "%d.%d.%d.%d", a1, a2, a3, a4);
- return(0);
-}
+++ /dev/null
-void locate_host(char *tbuf, size_t n, char *abuf, size_t na, int client_socket);
-int rbl_check(char *cs_addr, char *message_to_spammer);
-int hostname_to_dotted_quad(char *addr, char *host);
-int rblcheck_backend(char *domain, char *txtbuf, int txtbufsize);
+++ /dev/null
-#! /bin/sh
-# Common stub for a few missing GNU programs while installing.
-# Copyright 1996, 1997, 1999, 2000 Free Software Foundation, Inc.
-# Originally by Fran,cois Pinard <pinard@iro.umontreal.ca>, 1996.
-
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2, or (at your option)
-# any later version.
-
-# 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.
-
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-# 02111-1307, USA.
-
-# As a special exception to the GNU General Public License, if you
-# distribute this file as part of a program that contains a
-# configuration script generated by Autoconf, you may include it under
-# the same distribution terms that you use for the rest of that program.
-
-if test $# -eq 0; then
- echo 1>&2 "Try \`$0 --help' for more information"
- exit 1
-fi
-
-run=:
-
-# In the cases where this matters, `missing' is being run in the
-# srcdir already.
-if test -f configure.ac; then
- configure_ac=configure.ac
-else
- configure_ac=configure.in
-fi
-
-case "$1" in
---run)
- # Try to run requested program, and just exit if it succeeds.
- run=
- shift
- "$@" && exit 0
- ;;
-esac
-
-# If it does not exist, or fails to run (possibly an outdated version),
-# try to emulate it.
-case "$1" in
-
- -h|--h|--he|--hel|--help)
- echo "\
-$0 [OPTION]... PROGRAM [ARGUMENT]...
-
-Handle \`PROGRAM [ARGUMENT]...' for when PROGRAM is missing, or return an
-error status if there is no known handling for PROGRAM.
-
-Options:
- -h, --help display this help and exit
- -v, --version output version information and exit
- --run try to run the given command, and emulate it if it fails
-
-Supported PROGRAM values:
- aclocal touch file \`aclocal.m4'
- autoconf touch file \`configure'
- autoheader touch file \`config.h.in'
- automake touch all \`Makefile.in' files
- bison create \`y.tab.[ch]', if possible, from existing .[ch]
- flex create \`lex.yy.c', if possible, from existing .c
- help2man touch the output file
- lex create \`lex.yy.c', if possible, from existing .c
- makeinfo touch the output file
- tar try tar, gnutar, gtar, then tar without non-portable flags
- yacc create \`y.tab.[ch]', if possible, from existing .[ch]"
- ;;
-
- -v|--v|--ve|--ver|--vers|--versi|--versio|--version)
- echo "missing 0.3 - GNU automake"
- ;;
-
- -*)
- echo 1>&2 "$0: Unknown \`$1' option"
- echo 1>&2 "Try \`$0 --help' for more information"
- exit 1
- ;;
-
- aclocal)
- echo 1>&2 "\
-WARNING: \`$1' is missing on your system. You should only need it if
- you modified \`acinclude.m4' or \`${configure_ac}'. You might want
- to install the \`Automake' and \`Perl' packages. Grab them from
- any GNU archive site."
- touch aclocal.m4
- ;;
-
- autoconf)
- echo 1>&2 "\
-WARNING: \`$1' is missing on your system. You should only need it if
- you modified \`${configure_ac}'. You might want to install the
- \`Autoconf' and \`GNU m4' packages. Grab them from any GNU
- archive site."
- touch configure
- ;;
-
- autoheader)
- echo 1>&2 "\
-WARNING: \`$1' is missing on your system. You should only need it if
- you modified \`acconfig.h' or \`${configure_ac}'. You might want
- to install the \`Autoconf' and \`GNU m4' packages. Grab them
- from any GNU archive site."
- files=`sed -n 's/^[ ]*A[CM]_CONFIG_HEADER(\([^)]*\)).*/\1/p' ${configure_ac}`
- test -z "$files" && files="config.h"
- touch_files=
- for f in $files; do
- case "$f" in
- *:*) touch_files="$touch_files "`echo "$f" |
- sed -e 's/^[^:]*://' -e 's/:.*//'`;;
- *) touch_files="$touch_files $f.in";;
- esac
- done
- touch $touch_files
- ;;
-
- automake)
- echo 1>&2 "\
-WARNING: \`$1' is missing on your system. You should only need it if
- you modified \`Makefile.am', \`acinclude.m4' or \`${configure_ac}'.
- You might want to install the \`Automake' and \`Perl' packages.
- Grab them from any GNU archive site."
- find . -type f -name Makefile.am -print |
- sed 's/\.am$/.in/' |
- while read f; do touch "$f"; done
- ;;
-
- bison|yacc)
- echo 1>&2 "\
-WARNING: \`$1' is missing on your system. You should only need it if
- you modified a \`.y' file. You may need the \`Bison' package
- in order for those modifications to take effect. You can get
- \`Bison' from any GNU archive site."
- rm -f y.tab.c y.tab.h
- if [ $# -ne 1 ]; then
- eval LASTARG="\${$#}"
- case "$LASTARG" in
- *.y)
- SRCFILE=`echo "$LASTARG" | sed 's/y$/c/'`
- if [ -f "$SRCFILE" ]; then
- cp "$SRCFILE" y.tab.c
- fi
- SRCFILE=`echo "$LASTARG" | sed 's/y$/h/'`
- if [ -f "$SRCFILE" ]; then
- cp "$SRCFILE" y.tab.h
- fi
- ;;
- esac
- fi
- if [ ! -f y.tab.h ]; then
- echo >y.tab.h
- fi
- if [ ! -f y.tab.c ]; then
- echo 'main() { return 0; }' >y.tab.c
- fi
- ;;
-
- lex|flex)
- echo 1>&2 "\
-WARNING: \`$1' is missing on your system. You should only need it if
- you modified a \`.l' file. You may need the \`Flex' package
- in order for those modifications to take effect. You can get
- \`Flex' from any GNU archive site."
- rm -f lex.yy.c
- if [ $# -ne 1 ]; then
- eval LASTARG="\${$#}"
- case "$LASTARG" in
- *.l)
- SRCFILE=`echo "$LASTARG" | sed 's/l$/c/'`
- if [ -f "$SRCFILE" ]; then
- cp "$SRCFILE" lex.yy.c
- fi
- ;;
- esac
- fi
- if [ ! -f lex.yy.c ]; then
- echo 'main() { return 0; }' >lex.yy.c
- fi
- ;;
-
- help2man)
- echo 1>&2 "\
-WARNING: \`$1' is missing on your system. You should only need it if
- you modified a dependency of a manual page. You may need the
- \`Help2man' package in order for those modifications to take
- effect. You can get \`Help2man' from any GNU archive site."
-
- file=`echo "$*" | sed -n 's/.*-o \([^ ]*\).*/\1/p'`
- if test -z "$file"; then
- file=`echo "$*" | sed -n 's/.*--output=\([^ ]*\).*/\1/p'`
- fi
- if [ -f "$file" ]; then
- touch $file
- else
- test -z "$file" || exec >$file
- echo ".ab help2man is required to generate this page"
- exit 1
- fi
- ;;
-
- makeinfo)
- if test -z "$run" && (makeinfo --version) > /dev/null 2>&1; then
- # We have makeinfo, but it failed.
- exit 1
- fi
-
- echo 1>&2 "\
-WARNING: \`$1' is missing on your system. You should only need it if
- you modified a \`.texi' or \`.texinfo' file, or any other file
- indirectly affecting the aspect of the manual. The spurious
- call might also be the consequence of using a buggy \`make' (AIX,
- DU, IRIX). You might want to install the \`Texinfo' package or
- the \`GNU make' package. Grab either from any GNU archive site."
- file=`echo "$*" | sed -n 's/.*-o \([^ ]*\).*/\1/p'`
- if test -z "$file"; then
- file=`echo "$*" | sed 's/.* \([^ ]*\) *$/\1/'`
- file=`sed -n '/^@setfilename/ { s/.* \([^ ]*\) *$/\1/; p; q; }' $file`
- fi
- touch $file
- ;;
-
- tar)
- shift
- if test -n "$run"; then
- echo 1>&2 "ERROR: \`tar' requires --run"
- exit 1
- fi
-
- # We have already tried tar in the generic part.
- # Look for gnutar/gtar before invocation to avoid ugly error
- # messages.
- if (gnutar --version > /dev/null 2>&1); then
- gnutar ${1+"$@"} && exit 0
- fi
- if (gtar --version > /dev/null 2>&1); then
- gtar ${1+"$@"} && exit 0
- fi
- firstarg="$1"
- if shift; then
- case "$firstarg" in
- *o*)
- firstarg=`echo "$firstarg" | sed s/o//`
- tar "$firstarg" ${1+"$@"} && exit 0
- ;;
- esac
- case "$firstarg" in
- *h*)
- firstarg=`echo "$firstarg" | sed s/h//`
- tar "$firstarg" ${1+"$@"} && exit 0
- ;;
- esac
- fi
-
- echo 1>&2 "\
-WARNING: I can't seem to be able to run \`tar' with the given arguments.
- You may want to install GNU tar or Free paxutils, or check the
- command line arguments."
- exit 1
- ;;
-
- *)
- echo 1>&2 "\
-WARNING: \`$1' is needed, and you do not seem to have it handy on your
- system. You might have modified some files without having the
- proper tools for further handling them. Check the \`README' file,
- it often tells you about the needed prerequirements for installing
- this package. You may also peek at any GNU archive site, in case
- some other package would contain this missing \`$1' program."
- exit 1
- ;;
-esac
-
-exit 0
+++ /dev/null
-/*
- * Autocompletion of email recipients, etc.
- *
- * Copyright (c) 1987-2020 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.
- */
-
-
-#include "ctdl_module.h"
-#include "serv_autocompletion.h"
-#include "config.h"
-
-
-/*
- * Convert a structured name into a friendly name. Caller must free the
- * returned pointer.
- */
-char *n_to_fn(char *value) {
- char *nnn = NULL;
- int i;
-
- nnn = malloc(strlen(value) + 10);
- strcpy(nnn, "");
- extract_token(&nnn[strlen(nnn)] , value, 3, ';', 999);
- strcat(nnn, " ");
- extract_token(&nnn[strlen(nnn)] , value, 1, ';', 999);
- strcat(nnn, " ");
- extract_token(&nnn[strlen(nnn)] , value, 2, ';', 999);
- strcat(nnn, " ");
- extract_token(&nnn[strlen(nnn)] , value, 0, ';', 999);
- strcat(nnn, " ");
- extract_token(&nnn[strlen(nnn)] , value, 4, ';', 999);
- strcat(nnn, " ");
- for (i=0; i<strlen(nnn); ++i) {
- if (!strncmp(&nnn[i], " ", 2)) strcpy(&nnn[i], &nnn[i+1]);
- }
- striplt(nnn);
- return(nnn);
-}
-
-
-
-
-/*
- * Back end for cmd_auto()
- */
-void hunt_for_autocomplete(long msgnum, char *search_string) {
- struct CtdlMessage *msg;
- struct vCard *v;
- char *value = NULL;
- char *value2 = NULL;
- int i = 0;
- char *nnn = NULL;
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) return;
-
- v = vcard_load(msg->cm_fields[eMesageText]);
- CM_Free(msg);
-
- /*
- * Try to match from a friendly name (the "fn" field). If there is
- * a match, return the entry in the form of:
- * Display Name <user@domain.org>
- */
- value = vcard_get_prop(v, "fn", 0, 0, 0);
- if (value != NULL) if (bmstrcasestr(value, search_string)) {
- value2 = vcard_get_prop(v, "email", 1, 0, 0);
- if (value2 == NULL) value2 = "";
- cprintf("%s <%s>\n", value, value2);
- vcard_free(v);
- return;
- }
-
- /*
- * Try to match from a structured name (the "n" field). If there is
- * a match, return the entry in the form of:
- * Display Name <user@domain.org>
- */
- value = vcard_get_prop(v, "n", 0, 0, 0);
- if (value != NULL) if (bmstrcasestr(value, search_string)) {
-
- value2 = vcard_get_prop(v, "email", 1, 0, 0);
- if (value2 == NULL) value2 = "";
- nnn = n_to_fn(value);
- cprintf("%s <%s>\n", nnn, value2);
- free(nnn);
- vcard_free(v);
- return;
- }
-
- /*
- * Try a partial match on all listed email addresses.
- */
- i = 0;
- while (value = vcard_get_prop(v, "email", 1, i++, 0), value != NULL) {
- if (bmstrcasestr(value, search_string)) {
- if (vcard_get_prop(v, "fn", 0, 0, 0)) {
- cprintf("%s <%s>\n", vcard_get_prop(v, "fn", 0, 0, 0), value);
- }
- else if (vcard_get_prop(v, "n", 0, 0, 0)) {
- nnn = n_to_fn(vcard_get_prop(v, "n", 0, 0, 0));
- cprintf("%s <%s>\n", nnn, value);
- free(nnn);
-
- }
- else {
- cprintf("%s\n", value);
- }
- vcard_free(v);
- return;
- }
- }
-
- vcard_free(v);
-}
-
-
-
-/*
- * Attempt to autocomplete an address based on a partial...
- */
-void cmd_auto(char *argbuf) {
- char hold_rm[ROOMNAMELEN];
- char search_string[256];
- long *msglist = NULL;
- int num_msgs = 0;
- long *fts_msgs = NULL;
- int fts_num_msgs = 0;
- struct cdbdata *cdbfr;
- int r = 0;
- int i = 0;
- int j = 0;
- int search_match = 0;
- char *rooms_to_try[] = { USERCONTACTSROOM, ADDRESS_BOOK_ROOM };
-
- if (CtdlAccessCheck(ac_logged_in)) return;
- extract_token(search_string, argbuf, 0, '|', sizeof search_string);
- if (IsEmptyStr(search_string)) {
- cprintf("%d You supplied an empty partial.\n",
- ERROR + ILLEGAL_VALUE);
- return;
- }
-
- strcpy(hold_rm, CC->room.QRname); /* save current room */
- cprintf("%d try these:\n", LISTING_FOLLOWS);
-
- /*
- * Gather up message pointers in rooms containing vCards
- */
- for (r=0; r < (sizeof(rooms_to_try) / sizeof(char *)); ++r) {
- if (CtdlGetRoom(&CC->room, rooms_to_try[r]) == 0) {
- cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
- if (cdbfr != NULL) {
- msglist = realloc(msglist, (num_msgs * sizeof(long)) + cdbfr->len + 1);
- memcpy(&msglist[num_msgs], cdbfr->ptr, cdbfr->len);
- num_msgs += (cdbfr->len / sizeof(long));
- cdb_free(cdbfr);
- }
- }
- }
-
- /*
- * Search-reduce the results if we have the full text index available
- */
- if (CtdlGetConfigInt("c_enable_fulltext")) {
- CtdlModuleDoSearch(&fts_num_msgs, &fts_msgs, search_string, "fulltext");
- if (fts_msgs) {
- for (i=0; i<num_msgs; ++i) {
- search_match = 0;
- for (j=0; j<fts_num_msgs; ++j) {
- if (msglist[i] == fts_msgs[j]) {
- search_match = 1;
- j = fts_num_msgs + 1; /* end the search */
- }
- }
- if (!search_match) {
- msglist[i] = 0; /* invalidate this result */
- }
- }
- free(fts_msgs);
- }
- else {
- /* If no results, invalidate the whole list */
- free(msglist);
- msglist = NULL;
- num_msgs = 0;
- }
- }
-
- /*
- * Now output the ones that look interesting
- */
- if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
- if (msglist[i] != 0) {
- hunt_for_autocomplete(msglist[i], search_string);
- }
- }
-
- cprintf("000\n");
- if (strcmp(CC->room.QRname, hold_rm)) {
- CtdlGetRoom(&CC->room, hold_rm); /* return to saved room */
- }
-
- if (msglist) {
- free(msglist);
- }
-
-}
-
-
-CTDL_MODULE_INIT(autocompletion)
-{
- if (!threading)
- {
- CtdlRegisterProtoHook(cmd_auto, "AUTO", "Do recipient autocompletion");
- }
- /* return our module name for the log */
- return "autocompletion";
-}
+++ /dev/null
-/*
- * Copyright (c) 1987-2012 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.
- */
-
-char *serv_autocompletion_init(void);
+++ /dev/null
-/*
- * This module implementsserver commands related to the display and
- * manipulation of user "bio" files.
- *
- * Copyright (c) 1987-2021 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 as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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.
- */
-
-#include "ctdl_module.h"
-#include "config.h"
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <dirent.h>
-
-
-/*
- * Command to enter user bio (profile) in plain text.
- * This is deprecated , or at least it will be when its replacement is written :)
- * I want commands to get/set bio in full MIME wonderfulness.
- */
-void cmd_ebio(char *cmdbuf) {
- char buf[SIZ];
-
- unbuffer_output();
-
- if (!(CC->logged_in)) {
- cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
- return;
- }
-
- StrBuf *NewProfile = NewStrBufPlain("Content-type: text/plain; charset=UTF-8\nContent-transfer-encoding: 8bit\n\n", -1);
-
- cprintf("%d Transmit user profile in plain text now.\n", SEND_LISTING);
- while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
- StrBufAppendBufPlain(NewProfile, buf, -1, 0);
- StrBufAppendBufPlain(NewProfile, HKEY("\n"), 0);
- }
-
- /* we have read the new profile from the user , now save it */
- long old_msgnum = CC->user.msgnum_bio;
- char userconfigroomname[ROOMNAMELEN];
- CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &CC->user, USERCONFIGROOM);
- long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, ChrPtr(NewProfile), FMT_RFC822, "Profile submitted with EBIO command");
- FreeStrBuf(&NewProfile);
- CtdlGetUserLock(&CC->user, CC->curr_user);
- CC->user.msgnum_bio = new_msgnum;
- CtdlPutUserLock(&CC->user);
- if (old_msgnum > 0) {
- syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
- CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
- }
-}
-
-
-/*
- * Command to read user bio (profile) in plain text.
- * This is deprecated , or at least it will be when its replacement is written :)
- * I want commands to get/set bio in full MIME wonderfulness.
- */
-void cmd_rbio(char *cmdbuf)
-{
- struct ctdluser ruser;
- char buf[SIZ];
-
- extract_token(buf, cmdbuf, 0, '|', sizeof buf);
- if (CtdlGetUser(&ruser, buf) != 0) {
- cprintf("%d No such user.\n",ERROR + NO_SUCH_USER);
- return;
- }
-
- cprintf("%d OK|%s|%ld|%d|%ld|%ld|%ld\n", LISTING_FOLLOWS,
- ruser.fullname, ruser.usernum, ruser.axlevel,
- (long)ruser.lastcall, ruser.timescalled, ruser.posted);
-
- struct CtdlMessage *msg = CtdlFetchMessage(ruser.msgnum_bio, 1);
- if (msg != NULL) {
- CtdlOutputPreLoadedMsg(msg, MT_CITADEL, HEADERS_NONE, 0, 0, 0);
- CM_Free(msg);
- }
- cprintf("000\n");
-}
-
-
-/*
- * Import function called by import_old_bio_files() for a single user
- */
-void import_one_bio_file(char *username, long usernum, char *path)
-{
- syslog(LOG_DEBUG, "Import legacy bio for %s, usernum=%ld, filename=%s", username, usernum, path);
-
- FILE *fp = fopen(path, "r");
- if (!fp) return;
-
- fseek(fp, 0, SEEK_END);
- long data_length = ftell(fp);
-
- if (data_length >= 1) {
- rewind(fp);
- char *unencoded_data = malloc(data_length);
- if (unencoded_data) {
- fread(unencoded_data, data_length, 1, fp);
- char *encoded_data = malloc((data_length * 2) + 100);
- if (encoded_data) {
- sprintf(encoded_data, "Content-type: text/plain; charset=UTF-8\nContent-transfer-encoding: base64\n\n");
- CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
-
- char userconfigroomname[ROOMNAMELEN];
- struct ctdluser usbuf;
-
- if (CtdlGetUser(&usbuf, username) == 0) { // no need to lock it , we are still initializing
- long old_msgnum = usbuf.msgnum_bio;
- CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &usbuf, USERCONFIGROOM);
- long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, encoded_data, FMT_RFC822, "Profile imported from bio");
- syslog(LOG_DEBUG, "Message %ld is now the profile for %s", new_msgnum, username);
- usbuf.msgnum_bio = new_msgnum;
- CtdlPutUser(&usbuf);
- unlink(path); // delete the old file , it's in the database now
- if (old_msgnum > 0) {
- syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
- CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
- }
- }
- free(encoded_data);
- }
- free(unencoded_data);
- }
- }
- fclose(fp);
-}
-
-
-/*
- * Look for old-format "bio" files and import them into the message base
- */
-void import_old_bio_files(void)
-{
- DIR *filedir = NULL;
- struct dirent *filedir_entry;
- size_t d_namelen;
- struct ctdluser usbuf;
- long usernum = 0;
- int d_type = 0;
- struct stat s;
- char path[PATH_MAX];
-
-
- syslog(LOG_DEBUG, "Importing old style bio files into the message base");
- filedir = opendir("bio");
- if (filedir == NULL) {
- return;
- }
- while ( (filedir_entry = readdir(filedir)) , (filedir_entry != NULL))
- {
-#ifdef _DIRENT_HAVE_D_NAMLEN
- d_namelen = filedir_entry->d_namlen;
-
-#else
- d_namelen = strlen(filedir_entry->d_name);
-#endif
-
-#ifdef _DIRENT_HAVE_D_TYPE
- d_type = filedir_entry->d_type;
-#else
-
-#ifndef DT_UNKNOWN
-#define DT_UNKNOWN 0
-#define DT_DIR 4
-#define DT_REG 8
-#define DT_LNK 10
-
-#define IFTODT(mode) (((mode) & 0170000) >> 12)
-#define DTTOIF(dirtype) ((dirtype) << 12)
-#endif
- d_type = DT_UNKNOWN;
-#endif
- if ((d_namelen == 1) &&
- (filedir_entry->d_name[0] == '.'))
- continue;
-
- if ((d_namelen == 2) &&
- (filedir_entry->d_name[0] == '.') &&
- (filedir_entry->d_name[1] == '.'))
- continue;
-
- snprintf(path, PATH_MAX, "bio/%s", filedir_entry->d_name);
- if (d_type == DT_UNKNOWN) {
- if (lstat(path, &s) == 0) {
- d_type = IFTODT(s.st_mode);
- }
- }
- switch (d_type)
- {
- case DT_DIR:
- break;
- case DT_LNK:
- case DT_REG:
- usernum = atol(filedir_entry->d_name);
- if (CtdlGetUserByNumber(&usbuf, usernum) == 0) {
- import_one_bio_file(usbuf.fullname, usernum, path);
- }
- }
- }
- closedir(filedir);
- rmdir("bio");
-}
-
-
-
-CTDL_MODULE_INIT(bio)
-{
- if (!threading)
- {
- import_old_bio_files();
- CtdlRegisterProtoHook(cmd_ebio, "EBIO", "Enter your bio");
- CtdlRegisterProtoHook(cmd_rbio, "RBIO", "Read a user's bio");
- }
- /* return our module name for the log */
- return "bio";
-}
+++ /dev/null
-/*
- * Support for blog rooms
- *
- * Copyright (c) 1999-2011 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 as published
- * by the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <ctype.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "serv_vcard.h"
-#include "citadel_ldap.h"
-#include "ctdl_module.h"
-
-/*
- * Pre-save hook for saving a message in a blog room.
- * (Do we want to only do this for top-level messages?)
- */
-int blog_upload_beforesave(struct CtdlMessage *msg, struct recptypes *recp) {
-
- /* Only run this hook for blog rooms */
- if (CC->room.QRdefaultview != VIEW_BLOG) {
- return(0);
- }
-
- /*
- * If the message doesn't have an EUID, give it one.
- */
- if (CM_IsEmpty(msg, eExclusiveID))
- {
- char uuid[SIZ];
- generate_uuid(uuid);
- CM_SetField(msg, eExclusiveID, uuid, strlen(uuid));
- }
-
- /*
- * We also want to define a maximum length, whether we generated it or not.
- */
- CM_CutFieldAt(msg, eExclusiveID, BLOG_EUIDBUF_SIZE - 1);
-
- /* Now allow the save to complete. */
- return(0);
-}
-
-
-CTDL_MODULE_INIT(blog)
-{
- if (!threading)
- {
- CtdlRegisterMessageHook(blog_upload_beforesave, EVT_BEFORESAVE);
- }
-
- /* return our module id for the Log */
- return "blog";
-}
+++ /dev/null
-/*
- * This module implements iCalendar object processing and the Calendar>
- * room on a Citadel server. It handles iCalendar objects using the
- * iTIP protocol. See RFCs 2445 and 2446.
- *
- * Copyright (c) 1987-2021 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.
- */
-
-#define PRODID "-//Citadel//NONSGML Citadel Calendar//EN"
-
-#include "ctdl_module.h"
-#include <libical/ical.h>
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "serv_calendar.h"
-#include "room_ops.h"
-#include "euidindex.h"
-#include "default_timezone.h"
-#include "config.h"
-
-struct ical_respond_data {
- char desired_partnum[SIZ];
- icalcomponent *cal;
-};
-
-
-/*
- * Utility function to create a new VCALENDAR component with some of the
- * required fields already set the way we like them.
- */
-icalcomponent *icalcomponent_new_citadel_vcalendar(void) {
- icalcomponent *encaps;
-
- encaps = icalcomponent_new_vcalendar();
- if (encaps == NULL) {
- syslog(LOG_ERR, "calendar: could not allocate component");
- return NULL;
- }
-
- /* Set the Product ID */
- icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
-
- /* Set the Version Number */
- icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
-
- return(encaps);
-}
-
-
-/*
- * Utility function to encapsulate a subcomponent into a full VCALENDAR
- */
-icalcomponent *ical_encapsulate_subcomponent(icalcomponent *subcomp) {
- icalcomponent *encaps;
-
- /* If we're already looking at a full VCALENDAR component,
- * don't bother ... just return itself.
- */
- if (icalcomponent_isa(subcomp) == ICAL_VCALENDAR_COMPONENT) {
- return subcomp;
- }
-
- /* Encapsulate the VEVENT component into a complete VCALENDAR */
- encaps = icalcomponent_new_citadel_vcalendar();
- if (encaps == NULL) return NULL;
-
- /* Encapsulate the subcomponent inside */
- icalcomponent_add_component(encaps, subcomp);
-
- /* Return the object we just created. */
- return(encaps);
-}
-
-
-/*
- * Write a calendar object into the specified user's calendar room.
- * If the supplied user is NULL, this function writes the calendar object
- * to the currently selected room.
- */
-void ical_write_to_cal(struct ctdluser *u, icalcomponent *cal) {
- char *ser = NULL;
- long serlen;
- icalcomponent *encaps = NULL;
- struct CtdlMessage *msg = NULL;
- icalcomponent *tmp=NULL;
-
- if (cal == NULL) return;
-
- /* If the supplied object is a subcomponent, encapsulate it in
- * a full VCALENDAR component, and save that instead.
- */
- if (icalcomponent_isa(cal) != ICAL_VCALENDAR_COMPONENT) {
- tmp = icalcomponent_new_clone(cal);
- encaps = ical_encapsulate_subcomponent(tmp);
- ical_write_to_cal(u, encaps);
- icalcomponent_free(tmp);
- icalcomponent_free(encaps);
- return;
- }
-
- ser = icalcomponent_as_ical_string_r(cal);
- if (ser == NULL) return;
-
- serlen = strlen(ser);
-
- /* If the caller supplied a user, write to that user's default calendar room */
- if (u) {
- /* This handy API function does all the work for us. */
- CtdlWriteObject(USERCALENDARROOM, /* which room */
- "text/calendar", /* MIME type */
- ser, /* data */
- serlen + 1, /* length */
- u, /* which user */
- 0, /* not binary */
- 0 /* no flags */
- );
- }
-
- /* If the caller did not supply a user, write to the currently selected room */
- if (!u) {
- struct CitContext *CCC = CC;
- StrBuf *MsgBody;
-
- msg = malloc(sizeof(struct CtdlMessage));
- memset(msg, 0, sizeof(struct CtdlMessage));
- msg->cm_magic = CTDLMESSAGE_MAGIC;
- msg->cm_anon_type = MES_NORMAL;
- msg->cm_format_type = 4;
- CM_SetField(msg, eAuthor, CCC->user.fullname, strlen(CCC->user.fullname));
- CM_SetField(msg, eOriginalRoom, CCC->room.QRname, strlen(CCC->room.QRname));
-
- MsgBody = NewStrBufPlain(NULL, serlen + 100);
- StrBufAppendBufPlain(MsgBody, HKEY("Content-type: text/calendar\r\n\r\n"), 0);
- StrBufAppendBufPlain(MsgBody, ser, serlen, 0);
-
- CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
-
- /* Now write the data */
- CtdlSubmitMsg(msg, NULL, "");
- CM_Free(msg);
- }
-
- /* In either case, now we can free the serialized calendar object */
- free(ser);
-}
-
-
-/*
- * Send a reply to a meeting invitation.
- *
- * 'request' is the invitation to reply to.
- * 'action' is the string "accept" or "decline" or "tentative".
- *
- */
-void ical_send_a_reply(icalcomponent *request, char *action) {
- icalcomponent *the_reply = NULL;
- icalcomponent *vevent = NULL;
- icalproperty *attendee = NULL;
- char attendee_string[SIZ];
- icalproperty *organizer = NULL;
- char organizer_string[SIZ];
- icalproperty *summary = NULL;
- char summary_string[SIZ];
- icalproperty *me_attend = NULL;
- struct recptypes *recp = NULL;
- icalparameter *partstat = NULL;
- char *serialized_reply = NULL;
- char *reply_message_text = NULL;
- const char *ch;
- struct CtdlMessage *msg = NULL;
- struct recptypes *valid = NULL;
-
- *organizer_string = '\0';
- strcpy(summary_string, "Calendar item");
-
- if (request == NULL) {
- syslog(LOG_ERR, "calendar: trying to reply to NULL event");
- return;
- }
-
- the_reply = icalcomponent_new_clone(request);
- if (the_reply == NULL) {
- syslog(LOG_ERR, "calendar: cannot clone request");
- return;
- }
-
- /* Change the method from REQUEST to REPLY */
- icalcomponent_set_method(the_reply, ICAL_METHOD_REPLY);
-
- vevent = icalcomponent_get_first_component(the_reply, ICAL_VEVENT_COMPONENT);
- if (vevent != NULL) {
- /* Hunt for attendees, removing ones that aren't us.
- * (Actually, remove them all, cloning our own one so we can
- * re-insert it later)
- */
- while (attendee = icalcomponent_get_first_property(vevent,
- ICAL_ATTENDEE_PROPERTY), (attendee != NULL)
- ) {
- ch = icalproperty_get_attendee(attendee);
- if ((ch != NULL) && !strncasecmp(ch, "MAILTO:", 7)) {
- safestrncpy(attendee_string, ch + 7, sizeof (attendee_string));
- striplt(attendee_string);
- recp = validate_recipients(attendee_string, NULL, 0);
- if (recp != NULL) {
- if (!strcasecmp(recp->recp_local, CC->user.fullname)) {
- if (me_attend) icalproperty_free(me_attend);
- me_attend = icalproperty_new_clone(attendee);
- }
- free_recipients(recp);
- }
- }
-
- /* Remove it... */
- icalcomponent_remove_property(vevent, attendee);
- icalproperty_free(attendee);
- }
-
- /* We found our own address in the attendee list. */
- if (me_attend) {
- /* Change the partstat from NEEDS-ACTION to ACCEPT or DECLINE */
- icalproperty_remove_parameter_by_kind(me_attend, ICAL_PARTSTAT_PARAMETER);
-
- if (!strcasecmp(action, "accept")) {
- partstat = icalparameter_new_partstat(ICAL_PARTSTAT_ACCEPTED);
- }
- else if (!strcasecmp(action, "decline")) {
- partstat = icalparameter_new_partstat(ICAL_PARTSTAT_DECLINED);
- }
- else if (!strcasecmp(action, "tentative")) {
- partstat = icalparameter_new_partstat(ICAL_PARTSTAT_TENTATIVE);
- }
-
- if (partstat) icalproperty_add_parameter(me_attend, partstat);
-
- /* Now insert it back into the vevent. */
- icalcomponent_add_property(vevent, me_attend);
- }
-
- /* Figure out who to send this thing to */
- organizer = icalcomponent_get_first_property(vevent, ICAL_ORGANIZER_PROPERTY);
- if (organizer != NULL) {
- if (icalproperty_get_organizer(organizer)) {
- strcpy(organizer_string,
- icalproperty_get_organizer(organizer) );
- }
- }
- if (!strncasecmp(organizer_string, "MAILTO:", 7)) {
- strcpy(organizer_string, &organizer_string[7]);
- striplt(organizer_string);
- } else {
- strcpy(organizer_string, "");
- }
-
- /* Extract the summary string -- we'll use it as the
- * message subject for the reply
- */
- summary = icalcomponent_get_first_property(vevent, ICAL_SUMMARY_PROPERTY);
- if (summary != NULL) {
- if (icalproperty_get_summary(summary)) {
- strcpy(summary_string,
- icalproperty_get_summary(summary) );
- }
- }
- }
-
- /* Now generate the reply message and send it out. */
- serialized_reply = icalcomponent_as_ical_string_r(the_reply);
- icalcomponent_free(the_reply); /* don't need this anymore */
- if (serialized_reply == NULL) return;
-
- reply_message_text = malloc(strlen(serialized_reply) + SIZ);
- if (reply_message_text != NULL) {
- sprintf(reply_message_text,
- "Content-type: text/calendar; charset=\"utf-8\"\r\n\r\n%s\r\n",
- serialized_reply
- );
-
- msg = CtdlMakeMessage(&CC->user,
- organizer_string, /* to */
- "", /* cc */
- CC->room.QRname, 0, FMT_RFC822,
- "",
- "",
- summary_string, /* Use summary for subject */
- NULL,
- reply_message_text,
- NULL);
-
- if (msg != NULL) {
- valid = validate_recipients(organizer_string, NULL, 0);
- CtdlSubmitMsg(msg, valid, "");
- CM_Free(msg);
- free_recipients(valid);
- }
- }
- free(serialized_reply);
-}
-
-
-/*
- * Callback function for mime parser that hunts for calendar content types
- * and turns them into calendar objects. If something is found, it is placed
- * in ird->cal, and the caller now owns that memory and is responsible for freeing it.
- */
-void ical_locate_part(char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
- char *cbid, void *cbuserdata) {
-
- struct ical_respond_data *ird = NULL;
-
- ird = (struct ical_respond_data *) cbuserdata;
-
- /* desired_partnum can be set to "_HUNT_" to have it just look for
- * the first part with a content type of text/calendar. Otherwise
- * we have to only process the right one.
- */
- if (strcasecmp(ird->desired_partnum, "_HUNT_")) {
- if (strcasecmp(partnum, ird->desired_partnum)) {
- return;
- }
- }
-
- if ( (strcasecmp(cbtype, "text/calendar"))
- && (strcasecmp(cbtype, "application/ics")) ) {
- return;
- }
-
- if (ird->cal != NULL) {
- icalcomponent_free(ird->cal);
- ird->cal = NULL;
- }
-
- ird->cal = icalcomponent_new_from_string(content);
-}
-
-
-/*
- * Respond to a meeting request.
- */
-void ical_respond(long msgnum, char *partnum, char *action) {
- struct CtdlMessage *msg = NULL;
- struct ical_respond_data ird;
-
- if (
- (strcasecmp(action, "accept"))
- && (strcasecmp(action, "decline"))
- ) {
- cprintf("%d Action must be 'accept' or 'decline'\n",
- ERROR + ILLEGAL_VALUE
- );
- return;
- }
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) {
- cprintf("%d Message %ld not found.\n",
- ERROR + ILLEGAL_VALUE,
- (long)msgnum
- );
- return;
- }
-
- memset(&ird, 0, sizeof ird);
- strcpy(ird.desired_partnum, partnum);
- mime_parser(CM_RANGE(msg, eMesageText),
- *ical_locate_part, /* callback function */
- NULL, NULL,
- (void *) &ird, /* user data */
- 0
- );
-
- /* We're done with the incoming message, because we now have a
- * calendar object in memory.
- */
- CM_Free(msg);
-
- /*
- * Here is the real meat of this function. Handle the event.
- */
- if (ird.cal != NULL) {
- /* Save this in the user's calendar if necessary */
- if (!strcasecmp(action, "accept")) {
- ical_write_to_cal(&CC->user, ird.cal);
- }
-
- /* Send a reply if necessary */
- if (icalcomponent_get_method(ird.cal) == ICAL_METHOD_REQUEST) {
- ical_send_a_reply(ird.cal, action);
- }
-
- /* We used to delete the invitation after handling it.
- * We don't do that anymore, but here is the code that handled it:
- * CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
- */
-
- /* Free the memory we allocated and return a response. */
- icalcomponent_free(ird.cal);
- ird.cal = NULL;
- cprintf("%d ok\n", CIT_OK);
- return;
- }
- else {
- cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
- return;
- }
-
- /* should never get here */
-}
-
-
-/*
- * Figure out the UID of the calendar event being referred to in a
- * REPLY object. This function is recursive.
- */
-void ical_learn_uid_of_reply(char *uidbuf, icalcomponent *cal) {
- icalcomponent *subcomponent;
- icalproperty *p;
-
- /* If this object is a REPLY, then extract the UID. */
- if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
- p = icalcomponent_get_first_property(cal, ICAL_UID_PROPERTY);
- if (p != NULL) {
- strcpy(uidbuf, icalproperty_get_comment(p));
- }
- }
-
- /* Otherwise, recurse through any VEVENT subcomponents. We do NOT want the
- * UID of the reply; we want the UID of the invitation being replied to.
- */
- for (subcomponent = icalcomponent_get_first_component(cal, ICAL_VEVENT_COMPONENT);
- subcomponent != NULL;
- subcomponent = icalcomponent_get_next_component(cal, ICAL_VEVENT_COMPONENT) ) {
- ical_learn_uid_of_reply(uidbuf, subcomponent);
- }
-}
-
-
-/*
- * ical_update_my_calendar_with_reply() refers to this callback function; when we
- * locate the message containing the calendar event we're replying to, this function
- * gets called. It basically just sticks the message number in a supplied buffer.
- */
-void ical_hunt_for_event_to_update(long msgnum, void *data) {
- long *msgnumptr;
-
- msgnumptr = (long *) data;
- *msgnumptr = msgnum;
-}
-
-
-struct original_event_container {
- icalcomponent *c;
-};
-
-/*
- * Callback function for mime parser that hunts for calendar content types
- * and turns them into calendar objects (called by ical_update_my_calendar_with_reply()
- * to fetch the object being updated)
- */
-void ical_locate_original_event(char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
- char *cbid, void *cbuserdata) {
-
- struct original_event_container *oec = NULL;
-
- if ( (strcasecmp(cbtype, "text/calendar"))
- && (strcasecmp(cbtype, "application/ics")) ) {
- return;
- }
- oec = (struct original_event_container *) cbuserdata;
- if (oec->c != NULL) {
- icalcomponent_free(oec->c);
- }
- oec->c = icalcomponent_new_from_string(content);
-}
-
-
-/*
- * Merge updated attendee information from a REPLY into an existing event.
- */
-void ical_merge_attendee_reply(icalcomponent *event, icalcomponent *reply) {
- icalcomponent *c;
- icalproperty *e_attendee, *r_attendee;
-
- /* First things first. If we're not looking at a VEVENT component,
- * recurse through subcomponents until we find one.
- */
- if (icalcomponent_isa(event) != ICAL_VEVENT_COMPONENT) {
- for (c = icalcomponent_get_first_component(event, ICAL_VEVENT_COMPONENT);
- c != NULL;
- c = icalcomponent_get_next_component(event, ICAL_VEVENT_COMPONENT) ) {
- ical_merge_attendee_reply(c, reply);
- }
- return;
- }
-
- /* Now do the same thing with the reply.
- */
- if (icalcomponent_isa(reply) != ICAL_VEVENT_COMPONENT) {
- for (c = icalcomponent_get_first_component(reply, ICAL_VEVENT_COMPONENT);
- c != NULL;
- c = icalcomponent_get_next_component(reply, ICAL_VEVENT_COMPONENT) ) {
- ical_merge_attendee_reply(event, c);
- }
- return;
- }
-
- /* Clone the reply, because we're going to rip its guts out. */
- reply = icalcomponent_new_clone(reply);
-
- /* At this point we're looking at the correct subcomponents.
- * Iterate through the attendees looking for a match.
- */
-STARTOVER:
- for (e_attendee = icalcomponent_get_first_property(event, ICAL_ATTENDEE_PROPERTY);
- e_attendee != NULL;
- e_attendee = icalcomponent_get_next_property(event, ICAL_ATTENDEE_PROPERTY)) {
-
- for (r_attendee = icalcomponent_get_first_property(reply, ICAL_ATTENDEE_PROPERTY);
- r_attendee != NULL;
- r_attendee = icalcomponent_get_next_property(reply, ICAL_ATTENDEE_PROPERTY)) {
-
- /* Check to see if these two attendees match...
- */
- const char *e, *r;
- e = icalproperty_get_attendee(e_attendee);
- r = icalproperty_get_attendee(r_attendee);
-
- if ((e != NULL) &&
- (r != NULL) &&
- !strcasecmp(e, r)) {
- /* ...and if they do, remove the attendee from the event
- * and replace it with the attendee from the reply. (The
- * reply's copy will have the same address, but an updated
- * status.)
- */
- icalcomponent_remove_property(event, e_attendee);
- icalproperty_free(e_attendee);
- icalcomponent_remove_property(reply, r_attendee);
- icalcomponent_add_property(event, r_attendee);
-
- /* Since we diddled both sets of attendees, we have to start
- * the iteration over again. This will not create an infinite
- * loop because we removed the attendee from the reply. (That's
- * why we cloned the reply, and that's what we mean by "ripping
- * its guts out.")
- */
- goto STARTOVER;
- }
-
- }
- }
-
- /* Free the *clone* of the reply. */
- icalcomponent_free(reply);
-}
-
-
-/*
- * Handle an incoming RSVP (object with method==ICAL_METHOD_REPLY) for a
- * calendar event. The object has already been deserialized for us; all
- * we have to do here is hunt for the event in our calendar, merge in the
- * updated attendee status, and save it again.
- *
- * This function returns 0 on success, 1 if the event was not found in the
- * user's calendar, or 2 if an internal error occurred.
- */
-int ical_update_my_calendar_with_reply(icalcomponent *cal) {
- char uid[SIZ];
- char hold_rm[ROOMNAMELEN];
- long msgnum_being_replaced = 0;
- struct CtdlMessage *msg = NULL;
- struct original_event_container oec;
- icalcomponent *original_event;
- char *serialized_event = NULL;
- char roomname[ROOMNAMELEN];
- char *message_text = NULL;
-
- /* Figure out just what event it is we're dealing with */
- strcpy(uid, "--==<< InVaLiD uId >>==--");
- ical_learn_uid_of_reply(uid, cal);
- syslog(LOG_DEBUG, "calendar: UID of event being replied to is <%s>", uid);
-
- strcpy(hold_rm, CC->room.QRname); /* save current room */
-
- if (CtdlGetRoom(&CC->room, USERCALENDARROOM) != 0) {
- CtdlGetRoom(&CC->room, hold_rm);
- syslog(LOG_ERR, "calendar: cannot get user calendar room");
- return(2);
- }
-
- /*
- * Look in the EUID index for a message with
- * the Citadel EUID set to the value we're looking for. Since
- * Citadel always sets the message EUID to the iCalendar UID of
- * the event, this will work.
- */
- msgnum_being_replaced = CtdlLocateMessageByEuid(uid, &CC->room);
-
- CtdlGetRoom(&CC->room, hold_rm); /* return to saved room */
-
- syslog(LOG_DEBUG, "calendar: msgnum_being_replaced == %ld", msgnum_being_replaced);
- if (msgnum_being_replaced == 0) {
- return(1); /* no calendar event found */
- }
-
- /* Now we know the ID of the message containing the event being updated.
- * We don't actually have to delete it; that'll get taken care of by the
- * server when we save another event with the same UID. This just gives
- * us the ability to load the event into memory so we can diddle the
- * attendees.
- */
- msg = CtdlFetchMessage(msgnum_being_replaced, 1);
- if (msg == NULL) {
- return(2); /* internal error */
- }
- oec.c = NULL;
- mime_parser(CM_RANGE(msg, eMesageText),
- *ical_locate_original_event, /* callback function */
- NULL, NULL,
- &oec, /* user data */
- 0
- );
- CM_Free(msg);
-
- original_event = oec.c;
- if (original_event == NULL) {
- syslog(LOG_ERR, "calendar: original_component is NULL");
- return(2);
- }
-
- /* Merge the attendee's updated status into the event */
- ical_merge_attendee_reply(original_event, cal);
-
- /* Serialize it */
- serialized_event = icalcomponent_as_ical_string_r(original_event);
- icalcomponent_free(original_event); /* Don't need this anymore. */
- if (serialized_event == NULL) return(2);
-
- CtdlMailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM);
-
- message_text = malloc(strlen(serialized_event) + SIZ);
- if (message_text != NULL) {
- sprintf(message_text,
- "Content-type: text/calendar; charset=\"utf-8\"\r\n\r\n%s\r\n",
- serialized_event
- );
-
- msg = CtdlMakeMessage(&CC->user,
- "", /* No recipient */
- "", /* No recipient */
- roomname,
- 0, FMT_RFC822,
- "",
- "",
- "", /* no subject */
- NULL,
- message_text,
- NULL);
-
- if (msg != NULL) {
- CIT_ICAL->avoid_sending_invitations = 1;
- CtdlSubmitMsg(msg, NULL, roomname);
- CM_Free(msg);
- CIT_ICAL->avoid_sending_invitations = 0;
- }
- }
- free(serialized_event);
- return(0);
-}
-
-
-/*
- * Handle an incoming RSVP for an event. (This is the server subcommand part; it
- * simply extracts the calendar object from the message, deserializes it, and
- * passes it up to ical_update_my_calendar_with_reply() for processing.
- */
-void ical_handle_rsvp(long msgnum, char *partnum, char *action) {
- struct CtdlMessage *msg = NULL;
- struct ical_respond_data ird;
- int ret;
-
- if (
- (strcasecmp(action, "update"))
- && (strcasecmp(action, "ignore"))
- ) {
- cprintf("%d Action must be 'update' or 'ignore'\n",
- ERROR + ILLEGAL_VALUE
- );
- return;
- }
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) {
- cprintf("%d Message %ld not found.\n",
- ERROR + ILLEGAL_VALUE,
- (long)msgnum
- );
- return;
- }
-
- memset(&ird, 0, sizeof ird);
- strcpy(ird.desired_partnum, partnum);
- mime_parser(CM_RANGE(msg, eMesageText),
- *ical_locate_part, /* callback function */
- NULL, NULL,
- (void *) &ird, /* user data */
- 0
- );
-
- /* We're done with the incoming message, because we now have a
- * calendar object in memory.
- */
- CM_Free(msg);
-
- /*
- * Here is the real meat of this function. Handle the event.
- */
- if (ird.cal != NULL) {
- /* Update the user's calendar if necessary */
- if (!strcasecmp(action, "update")) {
- ret = ical_update_my_calendar_with_reply(ird.cal);
- if (ret == 0) {
- cprintf("%d Your calendar has been updated with this reply.\n",
- CIT_OK);
- }
- else if (ret == 1) {
- cprintf("%d This event does not exist in your calendar.\n",
- ERROR + FILE_NOT_FOUND);
- }
- else {
- cprintf("%d An internal error occurred.\n",
- ERROR + INTERNAL_ERROR);
- }
- }
- else {
- cprintf("%d This reply has been ignored.\n", CIT_OK);
- }
-
- /* Now that we've processed this message, we don't need it
- * anymore. So delete it. (Don't do this anymore.)
- CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
- */
-
- /* Free the memory we allocated and return a response. */
- icalcomponent_free(ird.cal);
- ird.cal = NULL;
- return;
- }
- else {
- cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
- return;
- }
-
- /* should never get here */
-}
-
-
-/*
- * Search for a property in both the top level and in a VEVENT subcomponent
- */
-icalproperty *ical_ctdl_get_subprop(
- icalcomponent *cal,
- icalproperty_kind which_prop
-) {
- icalproperty *p;
- icalcomponent *c;
-
- p = icalcomponent_get_first_property(cal, which_prop);
- if (p == NULL) {
- c = icalcomponent_get_first_component(cal,
- ICAL_VEVENT_COMPONENT);
- if (c != NULL) {
- p = icalcomponent_get_first_property(c, which_prop);
- }
- }
- return p;
-}
-
-
-/*
- * Check to see if two events overlap. Returns nonzero if they do.
- * (This function is used in both Citadel and WebCit. If you change it in
- * one place, change it in the other. Better yet, put it in a library.)
- */
-int ical_ctdl_is_overlap(
- struct icaltimetype t1start,
- struct icaltimetype t1end,
- struct icaltimetype t2start,
- struct icaltimetype t2end
-) {
- if (icaltime_is_null_time(t1start)) return(0);
- if (icaltime_is_null_time(t2start)) return(0);
-
- /* if either event lacks end time, assume end = start */
- if (icaltime_is_null_time(t1end))
- memcpy(&t1end, &t1start, sizeof(struct icaltimetype));
- else {
- if (t1end.is_date && icaltime_compare(t1start, t1end)) {
- /*
- * the end date is non-inclusive so adjust it by one
- * day because our test is inclusive, note that a day is
- * not too much because we are talking about all day
- * events
- * if start = end we assume that nevertheless the whole
- * day is meant
- */
- icaltime_adjust(&t1end, -1, 0, 0, 0);
- }
- }
-
- if (icaltime_is_null_time(t2end))
- memcpy(&t2end, &t2start, sizeof(struct icaltimetype));
- else {
- if (t2end.is_date && icaltime_compare(t2start, t2end)) {
- icaltime_adjust(&t2end, -1, 0, 0, 0);
- }
- }
-
- /* First, check for all-day events */
- if (t1start.is_date || t2start.is_date) {
- /* If event 1 ends before event 2 starts, we're in the clear. */
- if (icaltime_compare_date_only(t1end, t2start) < 0) return(0);
-
- /* If event 2 ends before event 1 starts, we're also ok. */
- if (icaltime_compare_date_only(t2end, t1start) < 0) return(0);
-
- return(1);
- }
-
- /* syslog(LOG_DEBUG, "Comparing t1start %d:%d t1end %d:%d t2start %d:%d t2end %d:%d",
- t1start.hour, t1start.minute, t1end.hour, t1end.minute,
- t2start.hour, t2start.minute, t2end.hour, t2end.minute);
- */
-
- /* Now check for overlaps using date *and* time. */
-
- /* If event 1 ends before event 2 starts, we're in the clear. */
- if (icaltime_compare(t1end, t2start) <= 0) return(0);
- /* syslog(LOG_DEBUG, "calendar: first passed"); */
-
- /* If event 2 ends before event 1 starts, we're also ok. */
- if (icaltime_compare(t2end, t1start) <= 0) return(0);
- /* syslog(LOG_DEBUG, "calendar: second passed"); */
-
- /* Otherwise, they overlap. */
- return(1);
-}
-
-
-/*
- * Phase 6 of "hunt for conflicts"
- * called by ical_conflicts_phase5()
- *
- * Now both the proposed and existing events have been boiled down to start and end times.
- * Check for overlap and output any conflicts.
- *
- * Returns nonzero if a conflict was reported. This allows the caller to stop iterating.
- */
-int ical_conflicts_phase6(struct icaltimetype t1start,
- struct icaltimetype t1end,
- struct icaltimetype t2start,
- struct icaltimetype t2end,
- long existing_msgnum,
- char *conflict_event_uid,
- char *conflict_event_summary,
- char *compare_uid)
-{
- int conflict_reported = 0;
-
- /* debugging cruft *
- time_t tt;
- tt = icaltime_as_timet_with_zone(t1start, t1start.zone);
- syslog(LOG_DEBUG, "PROPOSED START: %s", ctime(&tt));
- tt = icaltime_as_timet_with_zone(t1end, t1end.zone);
- syslog(LOG_DEBUG, " PROPOSED END: %s", ctime(&tt));
- tt = icaltime_as_timet_with_zone(t2start, t2start.zone);
- syslog(LOG_DEBUG, "EXISTING START: %s", ctime(&tt));
- tt = icaltime_as_timet_with_zone(t2end, t2end.zone);
- syslog(LOG_DEBUG, " EXISTING END: %s", ctime(&tt));
- * debugging cruft */
-
- /* compare and output */
-
- if (ical_ctdl_is_overlap(t1start, t1end, t2start, t2end)) {
- cprintf("%ld||%s|%s|%d|\n",
- existing_msgnum,
- conflict_event_uid,
- conflict_event_summary,
- ( (!IsEmptyStr(compare_uid)
- &&(!strcasecmp(compare_uid,
- conflict_event_uid))) ? 1 : 0
- )
- );
- conflict_reported = 1;
- }
-
- return(conflict_reported);
-}
-
-
-/*
- * Phase 5 of "hunt for conflicts"
- * Called by ical_conflicts_phase4()
- *
- * We have the proposed event boiled down to start and end times.
- * Now check it against an existing event.
- */
-void ical_conflicts_phase5(struct icaltimetype t1start,
- struct icaltimetype t1end,
- icalcomponent *existing_event,
- long existing_msgnum,
- char *compare_uid)
-{
- char conflict_event_uid[SIZ];
- char conflict_event_summary[SIZ];
- struct icaltimetype t2start, t2end;
- icalproperty *p;
-
- /* recur variables */
- icalproperty *rrule = NULL;
- struct icalrecurrencetype recur;
- icalrecur_iterator *ritr = NULL;
- struct icaldurationtype dur;
- int num_recur = 0;
-
- /* initialization */
- strcpy(conflict_event_uid, "");
- strcpy(conflict_event_summary, "");
- t2start = icaltime_null_time();
- t2end = icaltime_null_time();
-
- /* existing event stuff */
- p = ical_ctdl_get_subprop(existing_event, ICAL_DTSTART_PROPERTY);
- if (p == NULL) return;
- if (p != NULL) t2start = icalproperty_get_dtstart(p);
- if (icaltime_is_utc(t2start)) {
- t2start.zone = icaltimezone_get_utc_timezone();
- }
- else {
- t2start.zone = icalcomponent_get_timezone(existing_event,
- icalparameter_get_tzid(
- icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
- )
- );
- if (!t2start.zone) {
- t2start.zone = get_default_icaltimezone();
- }
- }
-
- p = ical_ctdl_get_subprop(existing_event, ICAL_DTEND_PROPERTY);
- if (p != NULL) {
- t2end = icalproperty_get_dtend(p);
-
- if (icaltime_is_utc(t2end)) {
- t2end.zone = icaltimezone_get_utc_timezone();
- }
- else {
- t2end.zone = icalcomponent_get_timezone(existing_event,
- icalparameter_get_tzid(
- icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
- )
- );
- if (!t2end.zone) {
- t2end.zone = get_default_icaltimezone();
- }
- }
- dur = icaltime_subtract(t2end, t2start);
- }
- else {
- memset (&dur, 0, sizeof(struct icaldurationtype));
- }
-
- rrule = ical_ctdl_get_subprop(existing_event, ICAL_RRULE_PROPERTY);
- if (rrule) {
- recur = icalproperty_get_rrule(rrule);
- ritr = icalrecur_iterator_new(recur, t2start);
- }
-
- do {
- p = ical_ctdl_get_subprop(existing_event, ICAL_UID_PROPERTY);
- if (p != NULL) {
- strcpy(conflict_event_uid, icalproperty_get_comment(p));
- }
-
- p = ical_ctdl_get_subprop(existing_event, ICAL_SUMMARY_PROPERTY);
- if (p != NULL) {
- strcpy(conflict_event_summary, icalproperty_get_comment(p));
- }
-
- if (ical_conflicts_phase6(t1start, t1end, t2start, t2end,
- existing_msgnum, conflict_event_uid, conflict_event_summary, compare_uid))
- {
- num_recur = MAX_RECUR + 1; /* force it out of scope, no need to continue */
- }
-
- if (rrule) {
- t2start = icalrecur_iterator_next(ritr);
- if (!icaltime_is_null_time(t2end)) {
- const icaltimezone *hold_zone = t2end.zone;
- t2end = icaltime_add(t2start, dur);
- t2end.zone = hold_zone;
- }
- ++num_recur;
- }
-
- if (icaltime_compare(t2start, t1end) < 0) {
- num_recur = MAX_RECUR + 1; /* force it out of scope */
- }
-
- } while ( (rrule) && (!icaltime_is_null_time(t2start)) && (num_recur < MAX_RECUR) );
- icalrecur_iterator_free(ritr);
-}
-
-
-/*
- * Phase 4 of "hunt for conflicts"
- * Called by ical_hunt_for_conflicts_backend()
- *
- * At this point we've got it boiled down to two icalcomponent events in memory.
- * If they conflict, output something to the client.
- */
-void ical_conflicts_phase4(icalcomponent *proposed_event,
- icalcomponent *existing_event,
- long existing_msgnum)
-{
- struct icaltimetype t1start, t1end;
- icalproperty *p;
- char compare_uid[SIZ];
-
- /* recur variables */
- icalproperty *rrule = NULL;
- struct icalrecurrencetype recur;
- icalrecur_iterator *ritr = NULL;
- struct icaldurationtype dur;
- int num_recur = 0;
-
- /* initialization */
- t1end = icaltime_null_time();
- *compare_uid = '\0';
-
- /* proposed event stuff */
-
- p = ical_ctdl_get_subprop(proposed_event, ICAL_DTSTART_PROPERTY);
- if (p == NULL)
- return;
- else
- t1start = icalproperty_get_dtstart(p);
-
- if (icaltime_is_utc(t1start)) {
- t1start.zone = icaltimezone_get_utc_timezone();
- }
- else {
- t1start.zone = icalcomponent_get_timezone(proposed_event,
- icalparameter_get_tzid(
- icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
- )
- );
- if (!t1start.zone) {
- t1start.zone = get_default_icaltimezone();
- }
- }
-
- p = ical_ctdl_get_subprop(proposed_event, ICAL_DTEND_PROPERTY);
- if (p != NULL) {
- t1end = icalproperty_get_dtend(p);
-
- if (icaltime_is_utc(t1end)) {
- t1end.zone = icaltimezone_get_utc_timezone();
- }
- else {
- t1end.zone = icalcomponent_get_timezone(proposed_event,
- icalparameter_get_tzid(
- icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
- )
- );
- if (!t1end.zone) {
- t1end.zone = get_default_icaltimezone();
- }
- }
-
- dur = icaltime_subtract(t1end, t1start);
- }
- else {
- memset (&dur, 0, sizeof(struct icaldurationtype));
- }
-
- rrule = ical_ctdl_get_subprop(proposed_event, ICAL_RRULE_PROPERTY);
- if (rrule) {
- recur = icalproperty_get_rrule(rrule);
- ritr = icalrecur_iterator_new(recur, t1start);
- }
-
- p = ical_ctdl_get_subprop(proposed_event, ICAL_UID_PROPERTY);
- if (p != NULL) {
- strcpy(compare_uid, icalproperty_get_comment(p));
- }
-
- do {
- ical_conflicts_phase5(t1start, t1end, existing_event, existing_msgnum, compare_uid);
-
- if (rrule) {
- t1start = icalrecur_iterator_next(ritr);
- if (!icaltime_is_null_time(t1end)) {
- const icaltimezone *hold_zone = t1end.zone;
- t1end = icaltime_add(t1start, dur);
- t1end.zone = hold_zone;
- }
- ++num_recur;
- }
-
- } while ( (rrule) && (!icaltime_is_null_time(t1start)) && (num_recur < MAX_RECUR) );
- icalrecur_iterator_free(ritr);
-}
-
-
-/*
- * Phase 3 of "hunt for conflicts"
- * Called by ical_hunt_for_conflicts()
- */
-void ical_hunt_for_conflicts_backend(long msgnum, void *data) {
- icalcomponent *proposed_event;
- struct CtdlMessage *msg = NULL;
- struct ical_respond_data ird;
-
- proposed_event = (icalcomponent *)data;
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) return;
- memset(&ird, 0, sizeof ird);
- strcpy(ird.desired_partnum, "_HUNT_");
- mime_parser(CM_RANGE(msg, eMesageText),
- *ical_locate_part, /* callback function */
- NULL, NULL,
- (void *) &ird, /* user data */
- 0
- );
- CM_Free(msg);
-
- if (ird.cal == NULL) return;
-
- ical_conflicts_phase4(proposed_event, ird.cal, msgnum);
- icalcomponent_free(ird.cal);
-}
-
-
-/*
- * Phase 2 of "hunt for conflicts" operation.
- * At this point we have a calendar object which represents the VEVENT that
- * is proposed for addition to the calendar. Now hunt through the user's
- * calendar room, and output zero or more existing VEVENTs which conflict
- * with this one.
- */
-void ical_hunt_for_conflicts(icalcomponent *cal) {
- char hold_rm[ROOMNAMELEN];
-
- strcpy(hold_rm, CC->room.QRname); /* save current room */
-
- if (CtdlGetRoom(&CC->room, USERCALENDARROOM) != 0) {
- CtdlGetRoom(&CC->room, hold_rm);
- cprintf("%d You do not have a calendar.\n", ERROR + ROOM_NOT_FOUND);
- return;
- }
-
- cprintf("%d Conflicting events:\n", LISTING_FOLLOWS);
-
- CtdlForEachMessage(MSGS_ALL, 0, NULL,
- NULL,
- NULL,
- ical_hunt_for_conflicts_backend,
- (void *) cal
- );
-
- cprintf("000\n");
- CtdlGetRoom(&CC->room, hold_rm); /* return to saved room */
-
-}
-
-
-/*
- * Hunt for conflicts (Phase 1 -- retrieve the object and call Phase 2)
- */
-void ical_conflicts(long msgnum, char *partnum) {
- struct CtdlMessage *msg = NULL;
- struct ical_respond_data ird;
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) {
- cprintf("%d Message %ld not found\n",
- ERROR + ILLEGAL_VALUE,
- (long)msgnum
- );
- return;
- }
-
- memset(&ird, 0, sizeof ird);
- strcpy(ird.desired_partnum, partnum);
- mime_parser(CM_RANGE(msg, eMesageText),
- *ical_locate_part, /* callback function */
- NULL, NULL,
- (void *) &ird, /* user data */
- 0
- );
-
- CM_Free(msg);
-
- if (ird.cal != NULL) {
- ical_hunt_for_conflicts(ird.cal);
- icalcomponent_free(ird.cal);
- return;
- }
-
- cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
-}
-
-
-/*
- * Look for busy time in a VEVENT and add it to the supplied VFREEBUSY.
- *
- * fb The VFREEBUSY component to which we are appending
- * top_level_cal The top-level VCALENDAR component which contains a VEVENT to be added
- */
-void ical_add_to_freebusy(icalcomponent *fb, icalcomponent *top_level_cal) {
- icalcomponent *cal;
- icalproperty *p;
- icalvalue *v;
- struct icalperiodtype this_event_period = icalperiodtype_null_period();
- icaltimetype dtstart;
- icaltimetype dtend;
-
- /* recur variables */
- icalproperty *rrule = NULL;
- struct icalrecurrencetype recur;
- icalrecur_iterator *ritr = NULL;
- struct icaldurationtype dur;
- int num_recur = 0;
-
- if (!top_level_cal) return;
-
- /* Find the VEVENT component containing an event */
- cal = icalcomponent_get_first_component(top_level_cal, ICAL_VEVENT_COMPONENT);
- if (!cal) return;
-
- /* If this event is not opaque, the user isn't publishing it as
- * busy time, so don't bother doing anything else.
- */
- p = icalcomponent_get_first_property(cal, ICAL_TRANSP_PROPERTY);
- if (p != NULL) {
- v = icalproperty_get_value(p);
- if (v != NULL) {
- if (icalvalue_get_transp(v) != ICAL_TRANSP_OPAQUE) {
- return;
- }
- }
- }
-
- /*
- * Now begin calculating the event start and end times.
- */
- p = icalcomponent_get_first_property(cal, ICAL_DTSTART_PROPERTY);
- if (!p) return;
- dtstart = icalproperty_get_dtstart(p);
-
- if (icaltime_is_utc(dtstart)) {
- dtstart.zone = icaltimezone_get_utc_timezone();
- }
- else {
- dtstart.zone = icalcomponent_get_timezone(top_level_cal,
- icalparameter_get_tzid(
- icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
- )
- );
- if (!dtstart.zone) {
- dtstart.zone = get_default_icaltimezone();
- }
- }
-
- dtend = icalcomponent_get_dtend(cal);
- if (!icaltime_is_null_time(dtend)) {
- dur = icaltime_subtract(dtend, dtstart);
- }
- else {
- memset (&dur, 0, sizeof(struct icaldurationtype));
- }
-
- /* Is a recurrence specified? If so, get ready to process it... */
- rrule = ical_ctdl_get_subprop(cal, ICAL_RRULE_PROPERTY);
- if (rrule) {
- recur = icalproperty_get_rrule(rrule);
- ritr = icalrecur_iterator_new(recur, dtstart);
- }
-
- do {
- /* Convert the DTSTART and DTEND properties to an icalperiod. */
- this_event_period.start = dtstart;
-
- if (!icaltime_is_null_time(dtend)) {
- this_event_period.end = dtend;
- }
-
- /* Convert the timestamps to UTC. It's ok to do this because we've already expanded
- * recurrences and this data is never going to get used again.
- */
- this_event_period.start = icaltime_convert_to_zone(
- this_event_period.start,
- icaltimezone_get_utc_timezone()
- );
- this_event_period.end = icaltime_convert_to_zone(
- this_event_period.end,
- icaltimezone_get_utc_timezone()
- );
-
- /* Now add it. */
- icalcomponent_add_property(fb, icalproperty_new_freebusy(this_event_period));
-
- /* Make sure the DTSTART property of the freebusy *list* is set to
- * the DTSTART property of the *earliest event*.
- */
- p = icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY);
- if (p == NULL) {
- icalcomponent_set_dtstart(fb, this_event_period.start);
- }
- else {
- if (icaltime_compare(this_event_period.start, icalcomponent_get_dtstart(fb)) < 0) {
- icalcomponent_set_dtstart(fb, this_event_period.start);
- }
- }
-
- /* Make sure the DTEND property of the freebusy *list* is set to
- * the DTEND property of the *latest event*.
- */
- p = icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY);
- if (p == NULL) {
- icalcomponent_set_dtend(fb, this_event_period.end);
- }
- else {
- if (icaltime_compare(this_event_period.end, icalcomponent_get_dtend(fb)) > 0) {
- icalcomponent_set_dtend(fb, this_event_period.end);
- }
- }
-
- if (rrule) {
- dtstart = icalrecur_iterator_next(ritr);
- if (!icaltime_is_null_time(dtend)) {
- dtend = icaltime_add(dtstart, dur);
- dtend.zone = dtstart.zone;
- }
- ++num_recur;
- }
-
- } while ( (rrule) && (!icaltime_is_null_time(dtstart)) && (num_recur < MAX_RECUR) ) ;
- icalrecur_iterator_free(ritr);
-}
-
-
-/*
- * Backend for ical_freebusy()
- *
- * This function simply loads the messages in the user's calendar room,
- * which contain VEVENTs, then strips them of all non-freebusy data, and
- * adds them to the supplied VCALENDAR.
- *
- */
-void ical_freebusy_backend(long msgnum, void *data) {
- icalcomponent *fb;
- struct CtdlMessage *msg = NULL;
- struct ical_respond_data ird;
-
- fb = (icalcomponent *)data; /* User-supplied data will be the VFREEBUSY component */
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) return;
- memset(&ird, 0, sizeof ird);
- strcpy(ird.desired_partnum, "_HUNT_");
- mime_parser(CM_RANGE(msg, eMesageText),
- *ical_locate_part, /* callback function */
- NULL, NULL,
- (void *) &ird, /* user data */
- 0
- );
- CM_Free(msg);
-
- if (ird.cal) {
- ical_add_to_freebusy(fb, ird.cal); /* Add VEVENT times to VFREEBUSY */
- icalcomponent_free(ird.cal);
- }
-}
-
-
-/*
- * Grab another user's free/busy times
- */
-void ical_freebusy(char *who) {
- struct ctdluser usbuf;
- char calendar_room_name[ROOMNAMELEN];
- char hold_rm[ROOMNAMELEN];
- char *serialized_request = NULL;
- icalcomponent *encaps = NULL;
- icalcomponent *fb = NULL;
- int found_user = (-1);
- struct recptypes *recp = NULL;
- char buf[256];
- char host[256];
- char type[256];
- int i = 0;
- int config_lines = 0;
-
- /* First try an exact match. */
- found_user = CtdlGetUser(&usbuf, who);
-
- /* If not found, try it as an unqualified email address. */
- if (found_user != 0) {
- strcpy(buf, who);
- recp = validate_recipients(buf, NULL, 0);
- syslog(LOG_DEBUG, "calendar: trying <%s>", buf);
- if (recp != NULL) {
- if (recp->num_local == 1) {
- found_user = CtdlGetUser(&usbuf, recp->recp_local);
- }
- free_recipients(recp);
- }
- }
-
- /* If still not found, try it as an address qualified with the
- * primary FQDN of this Citadel node.
- */
- if (found_user != 0) {
- snprintf(buf, sizeof buf, "%s@%s", who, CtdlGetConfigStr("c_fqdn"));
- syslog(LOG_DEBUG, "calendar: trying <%s>", buf);
- recp = validate_recipients(buf, NULL, 0);
- if (recp != NULL) {
- if (recp->num_local == 1) {
- found_user = CtdlGetUser(&usbuf, recp->recp_local);
- }
- free_recipients(recp);
- }
- }
-
- /* Still not found? Try qualifying it with every domain we
- * might have addresses in.
- */
- if (found_user != 0) {
- config_lines = num_tokens(inetcfg, '\n');
- for (i=0; ((i < config_lines) && (found_user != 0)); ++i) {
- extract_token(buf, inetcfg, i, '\n', sizeof buf);
- extract_token(host, buf, 0, '|', sizeof host);
- extract_token(type, buf, 1, '|', sizeof type);
-
- if ( (!strcasecmp(type, "localhost"))
- || (!strcasecmp(type, "directory")) ) {
- snprintf(buf, sizeof buf, "%s@%s", who, host);
- syslog(LOG_DEBUG, "calendar: trying <%s>", buf);
- recp = validate_recipients(buf, NULL, 0);
- if (recp != NULL) {
- if (recp->num_local == 1) {
- found_user = CtdlGetUser(&usbuf, recp->recp_local);
- }
- free_recipients(recp);
- }
- }
- }
- }
-
- if (found_user != 0) {
- cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
- return;
- }
-
- CtdlMailboxName(calendar_room_name, sizeof calendar_room_name,
- &usbuf, USERCALENDARROOM);
-
- strcpy(hold_rm, CC->room.QRname); /* save current room */
-
- if (CtdlGetRoom(&CC->room, calendar_room_name) != 0) {
- cprintf("%d Cannot open calendar\n", ERROR + ROOM_NOT_FOUND);
- CtdlGetRoom(&CC->room, hold_rm);
- return;
- }
-
- /* Create a VFREEBUSY subcomponent */
- syslog(LOG_DEBUG, "calendar: creating VFREEBUSY component");
- fb = icalcomponent_new_vfreebusy();
- if (fb == NULL) {
- cprintf("%d Internal error: cannot allocate memory.\n", ERROR + INTERNAL_ERROR);
- CtdlGetRoom(&CC->room, hold_rm);
- return;
- }
-
- /* Set the method to PUBLISH */
- icalcomponent_set_method(fb, ICAL_METHOD_PUBLISH);
-
- /* Set the DTSTAMP to right now. */
- icalcomponent_set_dtstamp(fb, icaltime_from_timet_with_zone(time(NULL), 0, icaltimezone_get_utc_timezone()));
-
- /* Add the user's email address as ORGANIZER */
- sprintf(buf, "MAILTO:%s", who);
- if (strchr(buf, '@') == NULL) {
- strcat(buf, "@");
- strcat(buf, CtdlGetConfigStr("c_fqdn"));
- }
- for (i=0; buf[i]; ++i) {
- if (buf[i]==' ') buf[i] = '_';
- }
- icalcomponent_add_property(fb, icalproperty_new_organizer(buf));
-
- /* Add busy time from events */
- syslog(LOG_DEBUG, "calendar: adding busy time from events");
- CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL, ical_freebusy_backend, (void *)fb );
-
- /* If values for DTSTART and DTEND are still not present, set them
- * to yesterday and tomorrow as default values.
- */
- if (icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY) == NULL) {
- icalcomponent_set_dtstart(fb, icaltime_from_timet_with_zone(time(NULL)-86400L, 0, icaltimezone_get_utc_timezone()));
- }
- if (icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY) == NULL) {
- icalcomponent_set_dtend(fb, icaltime_from_timet_with_zone(time(NULL)+86400L, 0, icaltimezone_get_utc_timezone()));
- }
-
- /* Put the freebusy component into the calendar component */
- syslog(LOG_DEBUG, "calendar: encapsulating");
- encaps = ical_encapsulate_subcomponent(fb);
- if (encaps == NULL) {
- icalcomponent_free(fb);
- cprintf("%d Internal error: cannot allocate memory.\n",
- ERROR + INTERNAL_ERROR);
- CtdlGetRoom(&CC->room, hold_rm);
- return;
- }
-
- /* Set the method to PUBLISH */
- syslog(LOG_DEBUG, "calendar: setting method");
- icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
-
- /* Serialize it */
- syslog(LOG_DEBUG, "calendar: serializing");
- serialized_request = icalcomponent_as_ical_string_r(encaps);
- icalcomponent_free(encaps); /* Don't need this anymore. */
-
- cprintf("%d Free/busy for %s\n", LISTING_FOLLOWS, usbuf.fullname);
- if (serialized_request != NULL) {
- client_write(serialized_request, strlen(serialized_request));
- free(serialized_request);
- }
- cprintf("\n000\n");
-
- /* Go back to the room from which we came... */
- CtdlGetRoom(&CC->room, hold_rm);
-}
-
-
-/*
- * Backend for ical_getics()
- *
- * This is a ForEachMessage() callback function that searches the current room
- * for calendar events and adds them each into one big calendar component.
- */
-void ical_getics_backend(long msgnum, void *data) {
- icalcomponent *encaps, *c;
- struct CtdlMessage *msg = NULL;
- struct ical_respond_data ird;
-
- encaps = (icalcomponent *)data;
- if (encaps == NULL) return;
-
- /* Look for the calendar event... */
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) return;
- memset(&ird, 0, sizeof ird);
- strcpy(ird.desired_partnum, "_HUNT_");
- mime_parser(CM_RANGE(msg, eMesageText),
- *ical_locate_part, /* callback function */
- NULL, NULL,
- (void *) &ird, /* user data */
- 0
- );
- CM_Free(msg);
-
- if (ird.cal == NULL) return;
-
- /* Here we go: put the VEVENT into the VCALENDAR. We now no longer
- * are responsible for "the_request"'s memory -- it will be freed
- * when we free "encaps".
- */
-
- /* If the top-level component is *not* a VCALENDAR, we can drop it right
- * in. This will almost never happen.
- */
- if (icalcomponent_isa(ird.cal) != ICAL_VCALENDAR_COMPONENT) {
- icalcomponent_add_component(encaps, ird.cal);
- }
- /*
- * In the more likely event that we're looking at a VCALENDAR with the VEVENT
- * and other components encapsulated inside, we have to extract them.
- */
- else {
- for (c = icalcomponent_get_first_component(ird.cal, ICAL_ANY_COMPONENT);
- (c != NULL);
- c = icalcomponent_get_next_component(ird.cal, ICAL_ANY_COMPONENT)) {
-
- /* For VTIMEZONE components, suppress duplicates of the same tzid */
-
- if (icalcomponent_isa(c) == ICAL_VTIMEZONE_COMPONENT) {
- icalproperty *p = icalcomponent_get_first_property(c, ICAL_TZID_PROPERTY);
- if (p) {
- const char *tzid = icalproperty_get_tzid(p);
- if (!icalcomponent_get_timezone(encaps, tzid)) {
- icalcomponent_add_component(encaps,
- icalcomponent_new_clone(c));
- }
- }
- }
-
- /* All other types of components can go in verbatim */
- else {
- icalcomponent_add_component(encaps, icalcomponent_new_clone(c));
- }
- }
- icalcomponent_free(ird.cal);
- }
-}
-
-
-/*
- * Retrieve all of the calendar items in the current room, and output them
- * as a single icalendar object.
- */
-void ical_getics(void)
-{
- icalcomponent *encaps = NULL;
- char *ser = NULL;
-
- if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
- &&(CC->room.QRdefaultview != VIEW_TASKS) ) {
- cprintf("%d Not a calendar room\n", ERROR+NOT_HERE);
- return; /* Not an iCalendar-centric room */
- }
-
- encaps = icalcomponent_new_vcalendar();
- if (encaps == NULL) {
- syslog(LOG_ERR, "calendar: could not allocate component!");
- cprintf("%d Could not allocate memory\n", ERROR+INTERNAL_ERROR);
- return;
- }
-
- cprintf("%d one big calendar\n", LISTING_FOLLOWS);
-
- /* Set the Product ID */
- icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
-
- /* Set the Version Number */
- icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
-
- /* Set the method to PUBLISH */
- icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
-
- /* Now go through the room encapsulating all calendar items. */
- CtdlForEachMessage(MSGS_ALL, 0, NULL,
- NULL,
- NULL,
- ical_getics_backend,
- (void *) encaps
- );
-
- ser = icalcomponent_as_ical_string_r(encaps);
- icalcomponent_free(encaps); /* Don't need this anymore. */
- client_write(ser, strlen(ser));
- free(ser);
- cprintf("\n000\n");
-}
-
-
-/*
- * Helper callback function for ical_putics() to discover which TZID's we need.
- * Simply put the tzid name string into a hash table. After the callbacks are
- * done we'll go through them and attach the ones that we have.
- */
-void ical_putics_grabtzids(icalparameter *param, void *data)
-{
- const char *tzid = icalparameter_get_tzid(param);
- HashList *keys = (HashList *) data;
-
- if ( (keys) && (tzid) && (!IsEmptyStr(tzid)) ) {
- Put(keys, tzid, strlen(tzid), strdup(tzid), NULL);
- }
-}
-
-
-/*
- * Delete all of the calendar items in the current room, and replace them
- * with calendar items from a client-supplied data stream.
- */
-void ical_putics(void)
-{
- char *calstream = NULL;
- icalcomponent *cal;
- icalcomponent *c;
- icalcomponent *encaps = NULL;
- HashList *tzidlist = NULL;
- HashPos *HashPos;
- void *Value;
- const char *Key;
- long len;
-
- /* Only allow this operation if we're in a room containing a calendar or tasks view */
- if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
- &&(CC->room.QRdefaultview != VIEW_TASKS) ) {
- cprintf("%d Not a calendar room\n", ERROR+NOT_HERE);
- return;
- }
-
- /* Only allow this operation if we have permission to overwrite the existing calendar */
- if (!CtdlDoIHavePermissionToDeleteMessagesFromThisRoom()) {
- cprintf("%d Permission denied.\n", ERROR+HIGHER_ACCESS_REQUIRED);
- return;
- }
-
- cprintf("%d Transmit data now\n", SEND_LISTING);
- calstream = CtdlReadMessageBody(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
- if (calstream == NULL) {
- return;
- }
-
- cal = icalcomponent_new_from_string(calstream);
- free(calstream);
-
- /* We got our data stream -- now do something with it. */
-
- /* Delete the existing messages in the room, because we are overwriting
- * the entire calendar with an entire new (or updated) calendar.
- * (Careful: this opens an S_ROOMS critical section!)
- */
- CtdlDeleteMessages(CC->room.QRname, NULL, 0, "");
-
- /* If the top-level component is *not* a VCALENDAR, we can drop it right
- * in. This will almost never happen.
- */
- if (icalcomponent_isa(cal) != ICAL_VCALENDAR_COMPONENT) {
- ical_write_to_cal(NULL, cal);
- }
- /*
- * In the more likely event that we're looking at a VCALENDAR with the VEVENT
- * and other components encapsulated inside, we have to extract them.
- */
- else {
- for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
- (c != NULL);
- c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
-
- /* Non-VTIMEZONE components each get written as individual messages.
- * But we also need to attach the relevant VTIMEZONE components to them.
- */
- if ( (icalcomponent_isa(c) != ICAL_VTIMEZONE_COMPONENT)
- && (encaps = icalcomponent_new_vcalendar()) ) {
- icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
- icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
- icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
-
- /* Attach any needed timezones here */
- tzidlist = NewHash(1, NULL);
- if (tzidlist) {
- icalcomponent_foreach_tzid(c, ical_putics_grabtzids, tzidlist);
- }
- HashPos = GetNewHashPos(tzidlist, 0);
-
- while (GetNextHashPos(tzidlist, HashPos, &len, &Key, &Value)) {
- syslog(LOG_DEBUG, "calendar: attaching timezone '%s'", (char*) Value);
- icaltimezone *t = NULL;
-
- /* First look for a timezone attached to the original calendar */
- t = icalcomponent_get_timezone(cal, Value);
-
- /* Try built-in tzdata if the right one wasn't attached */
- if (!t) {
- t = icaltimezone_get_builtin_timezone(Value);
- }
-
- /* I've got a valid timezone to attach. */
- if (t) {
- icalcomponent_add_component(encaps,
- icalcomponent_new_clone(
- icaltimezone_get_component(t)
- )
- );
- }
-
- }
- DeleteHashPos(&HashPos);
- DeleteHash(&tzidlist);
-
- /* Now attach the component itself (usually a VEVENT or VTODO) */
- icalcomponent_add_component(encaps, icalcomponent_new_clone(c));
-
- /* Write it to the message store */
- ical_write_to_cal(NULL, encaps);
- icalcomponent_free(encaps);
- }
- }
- }
-
- icalcomponent_free(cal);
-}
-
-
-/*
- * All Citadel calendar commands from the client come through here.
- */
-void cmd_ical(char *argbuf)
-{
- char subcmd[64];
- long msgnum;
- char partnum[256];
- char action[256];
- char who[256];
-
- extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
-
- /* Allow "test" and "freebusy" subcommands without logging in. */
-
- if (!strcasecmp(subcmd, "test")) {
- cprintf("%d This server supports calendaring\n", CIT_OK);
- return;
- }
-
- if (!strcasecmp(subcmd, "freebusy")) {
- extract_token(who, argbuf, 1, '|', sizeof who);
- ical_freebusy(who);
- return;
- }
-
- if (!strcasecmp(subcmd, "sgi")) {
- CIT_ICAL->server_generated_invitations = (extract_int(argbuf, 1) ? 1 : 0) ;
- cprintf("%d %d\n", CIT_OK, CIT_ICAL->server_generated_invitations);
- return;
- }
-
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- if (!strcasecmp(subcmd, "respond")) {
- msgnum = extract_long(argbuf, 1);
- extract_token(partnum, argbuf, 2, '|', sizeof partnum);
- extract_token(action, argbuf, 3, '|', sizeof action);
- ical_respond(msgnum, partnum, action);
- return;
- }
-
- if (!strcasecmp(subcmd, "handle_rsvp")) {
- msgnum = extract_long(argbuf, 1);
- extract_token(partnum, argbuf, 2, '|', sizeof partnum);
- extract_token(action, argbuf, 3, '|', sizeof action);
- ical_handle_rsvp(msgnum, partnum, action);
- return;
- }
-
- if (!strcasecmp(subcmd, "conflicts")) {
- msgnum = extract_long(argbuf, 1);
- extract_token(partnum, argbuf, 2, '|', sizeof partnum);
- ical_conflicts(msgnum, partnum);
- return;
- }
-
- if (!strcasecmp(subcmd, "getics")) {
- ical_getics();
- return;
- }
-
- if (!strcasecmp(subcmd, "putics")) {
- ical_putics();
- return;
- }
-
- cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
-}
-
-
-/*
- * We don't know if the calendar room exists so we just create it at login
- */
-void ical_CtdlCreateRoom(void)
-{
- struct ctdlroom qr;
- visit vbuf;
-
- /* Create the calendar room if it doesn't already exist */
- CtdlCreateRoom(USERCALENDARROOM, 4, "", 0, 1, 0, VIEW_CALENDAR);
-
- /* Set expiration policy to manual; otherwise objects will be lost! */
- if (CtdlGetRoomLock(&qr, USERCALENDARROOM)) {
- syslog(LOG_ERR, "calendar: couldn't get the user calendar room");
- return;
- }
- qr.QRep.expire_mode = EXPIRE_MANUAL;
- qr.QRdefaultview = VIEW_CALENDAR; /* 3 = calendar view */
- CtdlPutRoomLock(&qr);
-
- /* Set the view to a calendar view */
- CtdlGetRelationship(&vbuf, &CC->user, &qr);
- vbuf.v_view = VIEW_CALENDAR;
- CtdlSetRelationship(&vbuf, &CC->user, &qr);
-
- /* Create the tasks list room if it doesn't already exist */
- CtdlCreateRoom(USERTASKSROOM, 4, "", 0, 1, 0, VIEW_TASKS);
-
- /* Set expiration policy to manual; otherwise objects will be lost! */
- if (CtdlGetRoomLock(&qr, USERTASKSROOM)) {
- syslog(LOG_ERR, "calendar: couldn't get the user calendar room!");
- return;
- }
- qr.QRep.expire_mode = EXPIRE_MANUAL;
- qr.QRdefaultview = VIEW_TASKS;
- CtdlPutRoomLock(&qr);
-
- /* Set the view to a task list view */
- CtdlGetRelationship(&vbuf, &CC->user, &qr);
- vbuf.v_view = VIEW_TASKS;
- CtdlSetRelationship(&vbuf, &CC->user, &qr);
-
- /* Create the notes room if it doesn't already exist */
- CtdlCreateRoom(USERNOTESROOM, 4, "", 0, 1, 0, VIEW_NOTES);
-
- /* Set expiration policy to manual; otherwise objects will be lost! */
- if (CtdlGetRoomLock(&qr, USERNOTESROOM)) {
- syslog(LOG_ERR, "calendar: couldn't get the user calendar room!");
- return;
- }
- qr.QRep.expire_mode = EXPIRE_MANUAL;
- qr.QRdefaultview = VIEW_NOTES;
- CtdlPutRoomLock(&qr);
-
- /* Set the view to a notes view */
- CtdlGetRelationship(&vbuf, &CC->user, &qr);
- vbuf.v_view = VIEW_NOTES;
- CtdlSetRelationship(&vbuf, &CC->user, &qr);
-
- return;
-}
-
-
-/*
- * ical_send_out_invitations() is called by ical_saving_vevent() when it finds a VEVENT.
- *
- * top_level_cal is the highest available level calendar object.
- * cal is the subcomponent containing the VEVENT.
- *
- * Note: if you change the encapsulation code here, change it in WebCit's ical_encapsulate_subcomponent()
- */
-void ical_send_out_invitations(icalcomponent *top_level_cal, icalcomponent *cal) {
- icalcomponent *the_request = NULL;
- char *serialized_request = NULL;
- icalcomponent *encaps = NULL;
- char *request_message_text = NULL;
- struct CtdlMessage *msg = NULL;
- struct recptypes *valid = NULL;
- char attendees_string[SIZ];
- int num_attendees = 0;
- char this_attendee[256];
- icalproperty *attendee = NULL;
- char summary_string[SIZ];
- icalproperty *summary = NULL;
- size_t reqsize;
- icalproperty *p;
- struct icaltimetype t;
- const icaltimezone *attached_zones[5] = { NULL, NULL, NULL, NULL, NULL };
- int i;
- const icaltimezone *z;
- int num_zones_attached = 0;
- int zone_already_attached;
- icalparameter *tzidp = NULL;
- const char *tzidc = NULL;
-
- if (cal == NULL) {
- syslog(LOG_ERR, "calendar: trying to reply to NULL event?");
- return;
- }
-
- /* If this is a VCALENDAR component, look for a VEVENT subcomponent. */
- if (icalcomponent_isa(cal) == ICAL_VCALENDAR_COMPONENT) {
- ical_send_out_invitations(top_level_cal,
- icalcomponent_get_first_component(
- cal, ICAL_VEVENT_COMPONENT
- )
- );
- return;
- }
-
- /* Clone the event */
- the_request = icalcomponent_new_clone(cal);
- if (the_request == NULL) {
- syslog(LOG_ERR, "calendar: cannot clone calendar object");
- return;
- }
-
- /* Extract the summary string -- we'll use it as the
- * message subject for the request
- */
- strcpy(summary_string, "Meeting request");
- summary = icalcomponent_get_first_property(the_request, ICAL_SUMMARY_PROPERTY);
- if (summary != NULL) {
- if (icalproperty_get_summary(summary)) {
- strcpy(summary_string,
- icalproperty_get_summary(summary) );
- }
- }
-
- /* Determine who the recipients of this message are (the attendees) */
- strcpy(attendees_string, "");
- for (attendee = icalcomponent_get_first_property(the_request, ICAL_ATTENDEE_PROPERTY); attendee != NULL; attendee = icalcomponent_get_next_property(the_request, ICAL_ATTENDEE_PROPERTY)) {
- const char *ch = icalproperty_get_attendee(attendee);
- if ((ch != NULL) && !strncasecmp(ch, "MAILTO:", 7)) {
- safestrncpy(this_attendee, ch + 7, sizeof(this_attendee));
-
- if (!CtdlIsMe(this_attendee, sizeof this_attendee)) { /* don't send an invitation to myself! */
- snprintf(&attendees_string[strlen(attendees_string)],
- sizeof(attendees_string) - strlen(attendees_string),
- "%s, ",
- this_attendee
- );
- ++num_attendees;
- }
- }
- }
-
- syslog(LOG_DEBUG, "calendar: <%d> attendees: <%s>", num_attendees, attendees_string);
-
- /* If there are no attendees, there are no invitations to send, so...
- * don't bother putting one together! Punch out, Maverick!
- */
- if (num_attendees == 0) {
- icalcomponent_free(the_request);
- return;
- }
-
- /* Encapsulate the VEVENT component into a complete VCALENDAR */
- encaps = icalcomponent_new_vcalendar();
- if (encaps == NULL) {
- syslog(LOG_ERR, "calendar: could not allocate component!");
- icalcomponent_free(the_request);
- return;
- }
-
- /* Set the Product ID */
- icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
-
- /* Set the Version Number */
- icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
-
- /* Set the method to REQUEST */
- icalcomponent_set_method(encaps, ICAL_METHOD_REQUEST);
-
- /* Look for properties containing timezone parameters, to see if we need to attach VTIMEZONEs */
- for (p = icalcomponent_get_first_property(the_request, ICAL_ANY_PROPERTY);
- p != NULL;
- p = icalcomponent_get_next_property(the_request, ICAL_ANY_PROPERTY))
- {
- if ( (icalproperty_isa(p) == ICAL_COMPLETED_PROPERTY)
- || (icalproperty_isa(p) == ICAL_CREATED_PROPERTY)
- || (icalproperty_isa(p) == ICAL_DATEMAX_PROPERTY)
- || (icalproperty_isa(p) == ICAL_DATEMIN_PROPERTY)
- || (icalproperty_isa(p) == ICAL_DTEND_PROPERTY)
- || (icalproperty_isa(p) == ICAL_DTSTAMP_PROPERTY)
- || (icalproperty_isa(p) == ICAL_DTSTART_PROPERTY)
- || (icalproperty_isa(p) == ICAL_DUE_PROPERTY)
- || (icalproperty_isa(p) == ICAL_EXDATE_PROPERTY)
- || (icalproperty_isa(p) == ICAL_LASTMODIFIED_PROPERTY)
- || (icalproperty_isa(p) == ICAL_MAXDATE_PROPERTY)
- || (icalproperty_isa(p) == ICAL_MINDATE_PROPERTY)
- || (icalproperty_isa(p) == ICAL_RECURRENCEID_PROPERTY)
- ) {
- t = icalproperty_get_dtstart(p); // it's safe to use dtstart for all of them
-
- /* Determine the tzid in order for some of the conditions below to work */
- tzidp = icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER);
- if (tzidp) {
- tzidc = icalparameter_get_tzid(tzidp);
- }
- else {
- tzidc = NULL;
- }
-
- /* First see if there's a timezone attached to the data structure itself */
- if (icaltime_is_utc(t)) {
- z = icaltimezone_get_utc_timezone();
- }
- else {
- z = icaltime_get_timezone(t);
- }
-
- /* If not, try to determine the tzid from the parameter using attached zones */
- if ((!z) && (tzidc)) {
- z = icalcomponent_get_timezone(top_level_cal, tzidc);
- }
-
- /* Still no good? Try our internal database */
- if ((!z) && (tzidc)) {
- z = icaltimezone_get_builtin_timezone_from_tzid(tzidc);
- }
-
- if (z) {
- /* We have a valid timezone. Good. Now we need to attach it. */
-
- zone_already_attached = 0;
- for (i=0; i<5; ++i) {
- if (z == attached_zones[i]) {
- /* We've already got this one, no need to attach another. */
- ++zone_already_attached;
- }
- }
- if ((!zone_already_attached) && (num_zones_attached < 5)) {
- /* This is a new one, so attach it. */
- attached_zones[num_zones_attached++] = z;
- }
-
- icalproperty_set_parameter(p, icalparameter_new_tzid(icaltimezone_get_tzid(z))
- );
- }
- }
- }
-
- /* Encapsulate any timezones we need */
- if (num_zones_attached > 0) for (i=0; i<num_zones_attached; ++i) {
- icalcomponent *zc;
- zc = icalcomponent_new_clone(icaltimezone_get_component(attached_zones[i]));
- icalcomponent_add_component(encaps, zc);
- }
-
- /* Here we go: encapsulate the VEVENT into the VCALENDAR. We now no longer
- * are responsible for "the_request"'s memory -- it will be freed
- * when we free "encaps".
- */
- icalcomponent_add_component(encaps, the_request);
-
- /* Serialize it */
- serialized_request = icalcomponent_as_ical_string_r(encaps);
- icalcomponent_free(encaps); /* Don't need this anymore. */
- if (serialized_request == NULL) return;
-
- reqsize = strlen(serialized_request) + SIZ;
- request_message_text = malloc(reqsize);
- if (request_message_text != NULL) {
- snprintf(request_message_text, reqsize,
- "Content-type: text/calendar\r\n\r\n%s\r\n",
- serialized_request
- );
-
- msg = CtdlMakeMessage(
- &CC->user,
- NULL, /* No single recipient here */
- NULL, /* No single recipient here */
- CC->room.QRname,
- 0,
- FMT_RFC822,
- NULL,
- NULL,
- summary_string, /* Use summary for subject */
- NULL,
- request_message_text,
- NULL
- );
-
- if (msg != NULL) {
- valid = validate_recipients(attendees_string, NULL, 0);
- CtdlSubmitMsg(msg, valid, "");
- CM_Free(msg);
- free_recipients(valid);
- }
- }
- free(serialized_request);
-}
-
-
-/*
- * When a calendar object is being saved, determine whether it's a VEVENT
- * and the user saving it is the organizer. If so, send out invitations
- * to any listed attendees.
- *
- * This function is recursive. The caller can simply supply the same object
- * as both arguments. When it recurses it will alter the second argument
- * while holding on to the top level object. This allows us to go back and
- * grab things like time zones which might be attached.
- *
- */
-void ical_saving_vevent(icalcomponent *top_level_cal, icalcomponent *cal) {
- icalcomponent *c;
- icalproperty *organizer = NULL;
- char organizer_string[SIZ];
-
- syslog(LOG_DEBUG, "calendar: ical_saving_vevent() has been called");
-
- /* Don't send out invitations unless the client wants us to. */
- if (CIT_ICAL->server_generated_invitations == 0) {
- return;
- }
-
- /* Don't send out invitations if we've been asked not to. */
- if (CIT_ICAL->avoid_sending_invitations > 0) {
- return;
- }
-
- strcpy(organizer_string, "");
- /*
- * The VEVENT subcomponent is the one we're interested in.
- * Send out invitations if, and only if, this user is the Organizer.
- */
- if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
- organizer = icalcomponent_get_first_property(cal, ICAL_ORGANIZER_PROPERTY);
- if (organizer != NULL) {
- if (icalproperty_get_organizer(organizer)) {
- strcpy(organizer_string,
- icalproperty_get_organizer(organizer));
- }
- }
- if (!strncasecmp(organizer_string, "MAILTO:", 7)) {
- strcpy(organizer_string, &organizer_string[7]);
- striplt(organizer_string);
- /*
- * If the user saving the event is listed as the
- * organizer, then send out invitations.
- */
- if (CtdlIsMe(organizer_string, sizeof organizer_string)) {
- ical_send_out_invitations(top_level_cal, cal);
- }
- }
- }
-
- /* If the component has subcomponents, recurse through them. */
- for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
- (c != NULL);
- c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
- /* Recursively process subcomponent */
- ical_saving_vevent(top_level_cal, c);
- }
-
-}
-
-
-/*
- * Back end for ical_obj_beforesave()
- * This hunts for the UID of the calendar event (becomes Citadel msg EUID),
- * the summary of the event (becomes message subject),
- * and the start time (becomes message date/time).
- */
-void ical_obj_beforesave_backend(char *name, char *filename, char *partnum,
- char *disp, void *content, char *cbtype, char *cbcharset, size_t length,
- char *encoding, char *cbid, void *cbuserdata)
-{
- const char* pch;
- icalcomponent *cal, *nested_event, *nested_todo, *whole_cal;
- icalproperty *p;
- char new_uid[256] = "";
- struct CtdlMessage *msg = (struct CtdlMessage *) cbuserdata;
-
- if (!msg) return;
-
- /* We're only interested in calendar data. */
- if ( (strcasecmp(cbtype, "text/calendar"))
- && (strcasecmp(cbtype, "application/ics")) ) {
- return;
- }
-
- /* Hunt for the UID and drop it in
- * the "user data" pointer for the MIME parser. When
- * ical_obj_beforesave() sees it there, it'll set the Exclusive msgid
- * to that string.
- */
- whole_cal = icalcomponent_new_from_string(content);
- cal = whole_cal;
- if (cal != NULL) {
- if (icalcomponent_isa(cal) == ICAL_VCALENDAR_COMPONENT) {
- nested_event = icalcomponent_get_first_component(
- cal, ICAL_VEVENT_COMPONENT);
- if (nested_event != NULL) {
- cal = nested_event;
- }
- else {
- nested_todo = icalcomponent_get_first_component(
- cal, ICAL_VTODO_COMPONENT);
- if (nested_todo != NULL) {
- cal = nested_todo;
- }
- }
- }
-
- if (cal != NULL) {
-
- /* Set the message EUID to the iCalendar UID */
-
- p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
- if (p == NULL) {
- /* If there's no uid we must generate one */
- generate_uuid(new_uid);
- icalcomponent_add_property(cal, icalproperty_new_uid(new_uid));
- p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
- }
- if (p != NULL) {
- pch = icalproperty_get_comment(p);
- if (!IsEmptyStr(pch)) {
- CM_SetField(msg, eExclusiveID, pch, strlen(pch));
- syslog(LOG_DEBUG, "calendar: saving calendar UID <%s>", pch);
- }
- }
-
- /* Set the message subject to the iCalendar summary */
-
- p = ical_ctdl_get_subprop(cal, ICAL_SUMMARY_PROPERTY);
- if (p != NULL) {
- pch = icalproperty_get_comment(p);
- if (!IsEmptyStr(pch)) {
- char *subj;
-
- subj = rfc2047encode(pch, strlen(pch));
- CM_SetAsField(msg, eMsgSubject, &subj, strlen(subj));
- }
- }
-
- /* Set the message date/time to the iCalendar start time */
-
- p = ical_ctdl_get_subprop(cal, ICAL_DTSTART_PROPERTY);
- if (p != NULL) {
- time_t idtstart;
- idtstart = icaltime_as_timet(icalproperty_get_dtstart(p));
- if (idtstart > 0) {
- CM_SetFieldLONG(msg, eTimestamp, idtstart);
- }
- }
-
- }
- icalcomponent_free(cal);
- if (whole_cal != cal) {
- icalcomponent_free(whole_cal);
- }
- }
-}
-
-
-/*
- * See if we need to prevent the object from being saved (we don't allow
- * MIME types other than text/calendar in "calendar" or "tasks" rooms).
- *
- * If the message is being saved, we also set various message header fields
- * using data found in the iCalendar object.
- */
-int ical_obj_beforesave(struct CtdlMessage *msg, struct recptypes *recp)
-{
- /* First determine if this is a calendar or tasks room */
- if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
- && (CC->room.QRdefaultview != VIEW_TASKS)
- ) {
- return(0); /* Not an iCalendar-centric room */
- }
-
- /* It must be an RFC822 message! */
- if (msg->cm_format_type != 4) {
- syslog(LOG_DEBUG, "calendar: rejecting non-RFC822 message");
- return(1); /* You tried to save a non-RFC822 message! */
- }
-
- if (CM_IsEmpty(msg, eMesageText)) {
- return(1); /* You tried to save a null message! */
- }
-
- /* Do all of our lovely back-end parsing */
- mime_parser(CM_RANGE(msg, eMesageText),
- *ical_obj_beforesave_backend,
- NULL, NULL,
- (void *)msg,
- 0
- );
-
- return(0);
-}
-
-
-/*
- * Things we need to do after saving a calendar event.
- */
-void ical_obj_aftersave_backend(char *name, char *filename, char *partnum,
- char *disp, void *content, char *cbtype, char *cbcharset, size_t length,
- char *encoding, char *cbid, void *cbuserdata)
-{
- icalcomponent *cal;
-
- /* We're only interested in calendar items here. */
- if ( (strcasecmp(cbtype, "text/calendar"))
- && (strcasecmp(cbtype, "application/ics")) ) {
- return;
- }
-
- /* Hunt for the UID and drop it in
- * the "user data" pointer for the MIME parser. When
- * ical_obj_beforesave() sees it there, it'll set the Exclusive msgid
- * to that string.
- */
- if ( (!strcasecmp(cbtype, "text/calendar"))
- || (!strcasecmp(cbtype, "application/ics")) ) {
- cal = icalcomponent_new_from_string(content);
- if (cal != NULL) {
- ical_saving_vevent(cal, cal);
- icalcomponent_free(cal);
- }
- }
-}
-
-
-/*
- * Things we need to do after saving a calendar event.
- * (This will start back end tasks such as automatic generation of invitations,
- * if such actions are appropriate.)
- */
-int ical_obj_aftersave(struct CtdlMessage *msg, struct recptypes *recp)
-{
- char roomname[ROOMNAMELEN];
-
- /*
- * If this isn't the Calendar> room, no further action is necessary.
- */
-
- /* First determine if this is our room */
- CtdlMailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM);
- if (strcasecmp(roomname, CC->room.QRname)) {
- return(0); /* Not the Calendar room -- don't do anything. */
- }
-
- /* It must be an RFC822 message! */
- if (msg->cm_format_type != 4) return(1);
-
- /* Reject null messages */
- if (CM_IsEmpty(msg, eMesageText)) return(1);
-
- /* Now recurse through it looking for our icalendar data */
- mime_parser(CM_RANGE(msg, eMesageText),
- *ical_obj_aftersave_backend,
- NULL, NULL,
- NULL,
- 0
- );
-
- return(0);
-}
-
-
-void ical_session_startup(void) {
- CIT_ICAL = malloc(sizeof(struct cit_ical));
- memset(CIT_ICAL, 0, sizeof(struct cit_ical));
-}
-
-
-void ical_session_shutdown(void) {
- free(CIT_ICAL);
-}
-
-
-/*
- * Back end for ical_fixed_output()
- */
-void ical_fixed_output_backend(icalcomponent *cal, int recursion_level) {
- icalcomponent *c;
- icalproperty *p;
- char buf[256];
- const char *ch;
-
- p = icalcomponent_get_first_property(cal, ICAL_SUMMARY_PROPERTY);
- if (p != NULL) {
- cprintf("%s\n", (const char *)icalproperty_get_comment(p));
- }
-
- p = icalcomponent_get_first_property(cal, ICAL_LOCATION_PROPERTY);
- if (p != NULL) {
- cprintf("%s\n", (const char *)icalproperty_get_comment(p));
- }
-
- p = icalcomponent_get_first_property(cal, ICAL_DESCRIPTION_PROPERTY);
- if (p != NULL) {
- cprintf("%s\n", (const char *)icalproperty_get_comment(p));
- }
-
- /* If the component has attendees, iterate through them. */
- for (p = icalcomponent_get_first_property(cal, ICAL_ATTENDEE_PROPERTY); (p != NULL); p = icalcomponent_get_next_property(cal, ICAL_ATTENDEE_PROPERTY)) {
- ch = icalproperty_get_attendee(p);
- if ((ch != NULL) &&
- !strncasecmp(ch, "MAILTO:", 7)) {
-
- /* screen name or email address */
- safestrncpy(buf, ch + 7, sizeof(buf));
- striplt(buf);
- cprintf("%s ", buf);
- }
- cprintf("\n");
- }
-
- /* If the component has subcomponents, recurse through them. */
- for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
- (c != 0);
- c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
- /* Recursively process subcomponent */
- ical_fixed_output_backend(c, recursion_level+1);
- }
-}
-
-
-/*
- * Function to output iCalendar data as plain text. Nobody uses MSG0
- * anymore, so really this is just so we expose the vCard data to the full
- * text indexer.
- */
-void ical_fixed_output(char *ptr, int len) {
- icalcomponent *cal;
- char *stringy_cal;
-
- stringy_cal = malloc(len + 1);
- safestrncpy(stringy_cal, ptr, len + 1);
- cal = icalcomponent_new_from_string(stringy_cal);
- free(stringy_cal);
-
- if (cal == NULL) {
- return;
- }
-
- ical_fixed_output_backend(cal, 0);
-
- /* Free the memory we obtained from libical's constructor */
- icalcomponent_free(cal);
-}
-
-
-/*
- * Register this module with the Citadel server.
- */
-CTDL_MODULE_INIT(calendar)
-{
- if (!threading)
- {
-
- /* Tell libical to return errors instead of aborting if it gets bad data */
-
-#ifdef LIBICAL_ICAL_EXPORT // cheap and sleazy way to detect libical >=2.0
- icalerror_set_errors_are_fatal(0);
-#else
- icalerror_errors_are_fatal = 0;
-#endif
-
- /* Use our own application prefix in tzid's generated from system tzdata */
- icaltimezone_set_tzid_prefix("/citadel.org/");
-
- /* Initialize our hook functions */
- CtdlRegisterMessageHook(ical_obj_beforesave, EVT_BEFORESAVE);
- CtdlRegisterMessageHook(ical_obj_aftersave, EVT_AFTERSAVE);
- CtdlRegisterSessionHook(ical_CtdlCreateRoom, EVT_LOGIN, PRIO_LOGIN + 1);
- CtdlRegisterProtoHook(cmd_ical, "ICAL", "Citadel iCalendar commands");
- CtdlRegisterSessionHook(ical_session_startup, EVT_START, PRIO_START + 1);
- CtdlRegisterSessionHook(ical_session_shutdown, EVT_STOP, PRIO_STOP + 80);
- CtdlRegisterFixedOutputHook("text/calendar", ical_fixed_output);
- CtdlRegisterFixedOutputHook("application/ics", ical_fixed_output);
- }
-
- /* return our module name for the log */
- return "calendar";
-}
+++ /dev/null
-/*
- * iCalendar implementation for Citadel
- *
- *
- * Copyright (c) 1987-2012 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.
- *
- *
- *
- *
- */
-
-/*
- * "server_generated_invitations" tells the Citadel server that the
- * client wants invitations to be generated and sent out by the
- * server. Set to 1 to enable this functionality.
- *
- * "avoid_sending_invitations" is a server-internal variable. It is
- * set internally during certain transactions and cleared
- * automatically.
- */
-struct cit_ical {
- int server_generated_invitations;
- int avoid_sending_invitations;
-};
-
-#define CIT_ICAL CC->CIT_ICAL
-#define MAX_RECUR 1000
+++ /dev/null
-/*
- * checkpointing module for the database
- *
- * Copyright (c) 1987-2012 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.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <ctype.h>
-#include <string.h>
-#include <errno.h>
-
-#include <libcitadel.h>
-
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "msgbase.h"
-#include "sysdep_decls.h"
-#include "config.h"
-#include "threads.h"
-
-#include "ctdl_module.h"
-#include "context.h"
-
-CTDL_MODULE_INIT(checkpoint)
-{
- if (threading)
- {
- CtdlRegisterSessionHook(cdb_checkpoint, EVT_TIMER, PRIO_CLEANUP + 10);
- }
- /* return our module name for the log */
- return "checkpoint";
-}
+++ /dev/null
-/*
- * This module allows Citadel to use clamd to filter incoming messages
- * arriving via SMTP. For more information on clamd, visit
- * http://clamav.net (the ClamAV project is not in any way
- * affiliated with the Citadel project).
- *
- * Copyright (c) 1987-2012 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.
- */
-
-#define CLAMD_PORT "3310"
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <sys/socket.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "domain.h"
-#include "clientsocket.h"
-#include "ctdl_module.h"
-
-
-/*
- * Connect to the clamd server and scan a message.
- */
-int clamd(struct CtdlMessage *msg, struct recptypes *recp) {
- int sock = (-1);
- int streamsock = (-1);
- char clamhosts[SIZ];
- int num_clamhosts;
- char buf[SIZ];
- char hostbuf[SIZ];
- char portbuf[SIZ];
- int is_virus = 0;
- int clamhost;
- StrBuf *msgtext;
- CitContext *CCC;
-
- /* See if we have any clamd hosts configured */
- num_clamhosts = get_hosts(clamhosts, "clamav");
- if (num_clamhosts < 1) {
- return(0);
- }
-
- /* Try them one by one until we get a working one */
- for (clamhost=0; clamhost<num_clamhosts; ++clamhost) {
- extract_token(buf, clamhosts, clamhost, '|', sizeof buf);
- syslog(LOG_INFO, "Connecting to clamd at <%s>\n", buf);
-
- /* Assuming a host:port entry */
- extract_token(hostbuf, buf, 0, ':', sizeof hostbuf);
- if (extract_token(portbuf, buf, 1, ':', sizeof portbuf)==-1)
- /* Didn't specify a port so we'll try the psuedo-standard 3310 */
- sock = sock_connect(hostbuf, CLAMD_PORT);
- else
- /* Port specified lets try connecting to it! */
- sock = sock_connect(hostbuf, portbuf);
-
- if (sock >= 0) syslog(LOG_DEBUG, "Connected!\n");
- }
-
- if (sock < 0) {
- /* If the service isn't running, just pass the mail
- * through. Potentially throwing away mails isn't good.
- */
- return(0);
- }
- CCC=CC;
- CCC->SBuf.Buf = NewStrBuf();
- CCC->sMigrateBuf = NewStrBuf();
- CCC->SBuf.ReadWritePointer = NULL;
-
- /* Command */
- syslog(LOG_DEBUG, "Transmitting STREAM command\n");
- sprintf(buf, "STREAM\r\n");
- sock_write(&sock, buf, strlen(buf));
-
- syslog(LOG_DEBUG, "Waiting for PORT number\n");
- if (sock_getln(&sock, buf, sizeof buf) < 0) {
- goto bail;
- }
-
- syslog(LOG_DEBUG, "<%s\n", buf);
- if (strncasecmp(buf, "PORT", 4)!=0) {
- goto bail;
- }
-
- /* Should have received a port number to connect to */
- extract_token(portbuf, buf, 1, ' ', sizeof portbuf);
-
- /* Attempt to establish connection to STREAM socket */
- streamsock = sock_connect(hostbuf, portbuf);
-
- if (streamsock < 0) {
- /* If the service isn't running, just pass the mail
- * through. Potentially throwing away mails isn't good.
- */
- FreeStrBuf(&CCC->SBuf.Buf);
- FreeStrBuf(&CCC->sMigrateBuf);
- return(0);
- }
- else {
- syslog(LOG_DEBUG, "STREAM socket connected!\n");
- }
-
-
- /* Message */
- CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
- CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, 0);
- msgtext = CC->redirect_buffer;
- CC->redirect_buffer = NULL;
-
- sock_write(&streamsock, SKEY(msgtext));
- FreeStrBuf(&msgtext);
-
- /* Close the streamsocket connection; this tells clamd
- * that we're done.
- */
- if (streamsock != -1) {
- close(streamsock);
- }
-
- /* Response */
- syslog(LOG_DEBUG, "Awaiting response\n");
- if (sock_getln(&sock, buf, sizeof buf) < 0) {
- goto bail;
- }
- syslog(LOG_DEBUG, "<%s\n", buf);
- if (strncasecmp(buf, "stream: OK", 10)!=0) {
- is_virus = 1;
- }
-
- if (is_virus) {
- CM_SetField(msg, eErrorMsg, HKEY("message rejected by virus filter"));
- }
-
-bail: close(sock);
- FreeStrBuf(&CCC->SBuf.Buf);
- FreeStrBuf(&CCC->sMigrateBuf);
- return(is_virus);
-}
-
-
-CTDL_MODULE_INIT(virus)
-{
- if (!threading)
- {
- CtdlRegisterMessageHook(clamd, EVT_SMTPSCAN);
- }
-
- /* return our module name for the log */
- return "virus";
-}
+++ /dev/null
-// Copyright (c) 1987-2022 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.
-
-#include <string.h>
-#include <unistd.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include "sysdep.h"
-
-#ifdef HAVE_OPENSSL
-#include <openssl/ssl.h>
-#include <openssl/err.h>
-#include <openssl/rand.h>
-#endif
-
-#include <time.h>
-
-#ifdef HAVE_PTHREAD_H
-#include <pthread.h>
-#endif
-
-#ifdef HAVE_SYS_SELECT_H
-#include <sys/select.h>
-#endif
-
-#include <stdio.h>
-#include <libcitadel.h>
-#include "server.h"
-#include "serv_crypto.h"
-#include "sysdep_decls.h"
-#include "citadel.h"
-#include "config.h"
-#include "ctdl_module.h"
-
-#ifdef HAVE_OPENSSL
-
-SSL_CTX *ssl_ctx = NULL; // This SSL context is used for all sessions.
-char *ssl_cipher_list = CIT_CIPHERS;
-
-// If a private key does not exist, generate one now.
-void generate_key(char *keyfilename) {
- int ret = 0;
- RSA *rsa = NULL;
- BIGNUM *bne = NULL;
- int bits = 2048;
- unsigned long e = RSA_F4;
- FILE *fp;
-
- if (access(keyfilename, R_OK) == 0) { // Already have one.
- syslog(LOG_INFO, "crypto: %s exists and is readable", keyfilename);
- return;
- }
-
- syslog(LOG_INFO, "crypto: generating RSA key pair");
-
- // generate rsa key
- bne = BN_new();
- ret = BN_set_word(bne,e);
- if (ret != 1) {
- goto free_all;
- }
-
- rsa = RSA_new();
- ret = RSA_generate_key_ex(rsa, bits, bne, NULL);
- if (ret != 1) {
- goto free_all;
- }
-
- // write the key file
- fp = fopen(keyfilename, "w");
- if (fp != NULL) {
- chmod(keyfilename, 0600);
- if (PEM_write_RSAPrivateKey(fp, // the file
- rsa, // the key
- NULL, // no enc
- NULL, // no passphrase
- 0, // no passphrase
- NULL, // no callback
- NULL // no callback
- ) != 1) {
- syslog(LOG_ERR, "crypto: cannot write key: %s", ERR_reason_error_string(ERR_get_error()));
- unlink(keyfilename);
- }
- fclose(fp);
- }
-
- // free the memory we used
-free_all:
- RSA_free(rsa);
- BN_free(bne);
-}
-
-
-// If a certificate does not exist, generate a self-signed one now.
-void generate_certificate(char *keyfilename, char *certfilename) {
- RSA *private_key = NULL;
- EVP_PKEY *public_key = NULL;
- X509_REQ *certificate_signing_request = NULL;
- X509_NAME *name = NULL;
- X509 *certificate = NULL;
- FILE *fp;
-
- if (access(certfilename, R_OK) == 0) { // already have one.
- syslog(LOG_INFO, "crypto: %s exists and is readable", certfilename);
- return;
- }
-
- syslog(LOG_INFO, "crypto: generating a self-signed certificate");
-
- // Read in our private key.
- fp = fopen(keyfilename, "r");
- if (fp) {
- private_key = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL);
- fclose(fp);
- }
-
- if (!private_key) {
- syslog(LOG_ERR, "crypto: cannot read the private key");
- return;
- }
-
- // Create a public key from the private key
- public_key = EVP_PKEY_new();
- if (!public_key) {
- syslog(LOG_ERR, "crypto: cannot allocate public key");
- RSA_free(private_key);
- return;
- }
- EVP_PKEY_assign_RSA(public_key, private_key);
-
- // Create a certificate signing request
- certificate_signing_request = X509_REQ_new();
- if (!certificate_signing_request) {
- syslog(LOG_ERR, "crypto: cannot allocate certificate signing request");
- RSA_free(private_key);
- EVP_PKEY_free(public_key);
- return;
- }
-
- // Assign the public key to the certificate signing request
- X509_REQ_set_pubkey(certificate_signing_request, public_key);
- X509_REQ_set_version(certificate_signing_request, 0L);
-
- // Tell it who we are
- name = X509_REQ_get_subject_name(certificate_signing_request);
- X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned const char *)"ZZ", -1, -1, 0);
- X509_NAME_add_entry_by_txt(name, "ST", MBSTRING_ASC, (unsigned const char *)"The World", -1, -1, 0);
- X509_NAME_add_entry_by_txt(name, "L", MBSTRING_ASC, (unsigned const char *)"My Location", -1, -1, 0);
- X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned const char *)"Generic certificate", -1, -1, 0);
- X509_NAME_add_entry_by_txt(name, "OU", MBSTRING_ASC, (unsigned const char *)"Citadel server", -1, -1, 0);
- X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned const char *)"*", -1, -1, 0);
- X509_REQ_set_subject_name(certificate_signing_request, name);
-
- // Sign the CSR
- if (!X509_REQ_sign(certificate_signing_request, public_key, EVP_md5())) {
- syslog(LOG_ERR, "crypto: X509_REQ_sign(): error");
- X509_REQ_free(certificate_signing_request);
- RSA_free(private_key);
- EVP_PKEY_free(public_key);
- return;
- }
-
- // Generate the self-signed certificate
- certificate = X509_new();
- if (!certificate) {
- syslog(LOG_ERR, "crypto: cannot allocate X.509 certificate");
- X509_REQ_free(certificate_signing_request);
- RSA_free(private_key);
- EVP_PKEY_free(public_key);
- return;
- }
-
- ASN1_INTEGER_set(X509_get_serialNumber(certificate), 0);
- X509_set_issuer_name(certificate, X509_REQ_get_subject_name(certificate_signing_request));
- X509_set_subject_name(certificate, X509_REQ_get_subject_name(certificate_signing_request));
- X509_gmtime_adj(X509_get_notBefore(certificate), 0);
- X509_gmtime_adj(X509_get_notAfter(certificate), (long)60*60*24*SIGN_DAYS);
- X509_set_pubkey(certificate, public_key);
- X509_REQ_free(certificate_signing_request); // We're done with the CSR; free it
-
- // Finally, sign the certificate with our private key.
- if (!X509_sign(certificate, public_key, EVP_md5())) {
- syslog(LOG_ERR, "crypto: X509_sign() error");
- X509_free(certificate);
- RSA_free(private_key);
- EVP_PKEY_free(public_key);
- return;
- }
-
- // Write the certificate to disk
- fp = fopen(certfilename, "w");
- if (fp != NULL) {
- chmod(certfilename, 0600);
- PEM_write_X509(fp, certificate);
- fclose(fp);
- }
- else {
- syslog(LOG_ERR, "crypto: %s: %m", certfilename);
- }
-
- X509_free(certificate);
- EVP_PKEY_free(public_key);
- // do not RSA_free(private_key); because it was freed by EVP_PKEY_free() above
-}
-
-
-// Set the private key and certificate chain for the global SSL Context.
-// This is called during initialization, and can be called again later if the certificate changes.
-void bind_to_key_and_certificate(void) {
-
- SSL_CTX *old_ctx = NULL;
- SSL_CTX *new_ctx = NULL;
-
- const SSL_METHOD *method = SSLv23_server_method();
- if (!method) {
- syslog(LOG_ERR, "crypto: SSLv23_server_method() failed: %s", ERR_reason_error_string(ERR_get_error()));
- return;
- }
-
- new_ctx = SSL_CTX_new(method);
- if (!new_ctx) {
- syslog(LOG_ERR, "crypto: SSL_CTX_new failed: %s", ERR_reason_error_string(ERR_get_error()));
- return;
- }
-
- if (!(SSL_CTX_set_cipher_list(new_ctx, ssl_cipher_list))) {
- syslog(LOG_ERR, "crypto: SSL_CTX_set_cipher_list failed: %s", ERR_reason_error_string(ERR_get_error()));
- return;
- }
-
- syslog(LOG_DEBUG, "crypto: using certificate chain %s", file_crpt_file_cer);
- if (!SSL_CTX_use_certificate_chain_file(new_ctx, file_crpt_file_cer)) {
- syslog(LOG_ERR, "crypto: SSL_CTX_use_certificate_chain_file failed: %s", ERR_reason_error_string(ERR_get_error()));
- return;
- }
-
- syslog(LOG_DEBUG, "crypto: using private key %s", file_crpt_file_key);
- if (!SSL_CTX_use_PrivateKey_file(new_ctx, file_crpt_file_key, SSL_FILETYPE_PEM)) {
- syslog(LOG_ERR, "crypto: SSL_CTX_use_PrivateKey_file failed: %s", ERR_reason_error_string(ERR_get_error()));
- return;
- }
-
- old_ctx = ssl_ctx;
- ssl_ctx = new_ctx; // All future binds will use the new certificate
-
- if (old_ctx != NULL) {
- sleep(1); // Hopefully wait until all in-progress binds to the old certificate have completed
- SSL_CTX_free(old_ctx);
- }
-}
-
-
-// Check the modification time of the key and certificate files. Reload if either one changed.
-void update_key_and_cert_if_needed(void) {
- static time_t previous_mtime = 0;
- struct stat keystat;
- struct stat certstat;
-
- if (stat(file_crpt_file_key, &keystat) != 0) {
- syslog(LOG_ERR, "%s: %s", file_crpt_file_key, strerror(errno));
- return;
- }
- if (stat(file_crpt_file_cer, &certstat) != 0) {
- syslog(LOG_ERR, "%s: %s", file_crpt_file_cer, strerror(errno));
- return;
- }
-
- if ((keystat.st_mtime + certstat.st_mtime) != previous_mtime) {
- begin_critical_section(S_OPENSSL);
- bind_to_key_and_certificate();
- end_critical_section(S_OPENSSL);
- previous_mtime = keystat.st_mtime + certstat.st_mtime;
- }
-}
-
-
-// Initialize the TLS subsystem.
-void init_ssl(void) {
-
- // Initialize the OpenSSL library
- SSL_library_init();
- SSL_load_error_strings();
-
- // Load (or generate) a key and certificate
- mkdir(ctdl_key_dir, 0700); // If the keys directory does not exist, create it
- generate_key(file_crpt_file_key); // If a private key does not exist, create it
- generate_certificate(file_crpt_file_key, file_crpt_file_cer); // If a certificate does not exist, create it
- bind_to_key_and_certificate(); // Load key and cert from disk, and bind to them.
-
- // Register some Citadel protocol commands for dealing with encrypted sessions
- CtdlRegisterProtoHook(cmd_stls, "STLS", "Start TLS session");
- CtdlRegisterProtoHook(cmd_gtls, "GTLS", "Get TLS session status");
- CtdlRegisterSessionHook(endtls, EVT_STOP, PRIO_STOP + 10);
-}
-
-
-// client_write_ssl() Send binary data to the client encrypted.
-void client_write_ssl(const char *buf, int nbytes) {
- int retval;
- int nremain;
- char junk[1];
-
- nremain = nbytes;
-
- while (nremain > 0) {
- if (SSL_want_write(CC->ssl)) {
- if ((SSL_read(CC->ssl, junk, 0)) < 1) {
- syslog(LOG_DEBUG, "crypto: SSL_read in client_write: %s", ERR_reason_error_string(ERR_get_error()));
- }
- }
- retval =
- SSL_write(CC->ssl, &buf[nbytes - nremain], nremain);
- if (retval < 1) {
- long errval;
-
- errval = SSL_get_error(CC->ssl, retval);
- if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
- sleep(1);
- continue;
- }
- syslog(LOG_DEBUG, "crypto: SSL_write got error %ld, ret %d", errval, retval);
- if (retval == -1) {
- syslog(LOG_DEBUG, "crypto: errno is %d", errno);
- }
- endtls();
- client_write(&buf[nbytes - nremain], nremain);
- return;
- }
- nremain -= retval;
- }
-}
-
-
-// read data from the encrypted layer.
-int client_read_sslbuffer(StrBuf *buf, int timeout) {
- char sbuf[16384]; // OpenSSL communicates in 16k blocks, so let's speak its native tongue.
- int rlen;
- char junk[1];
- SSL *pssl = CC->ssl;
-
- if (pssl == NULL) return(-1);
-
- while (1) {
- if (SSL_want_read(pssl)) {
- if ((SSL_write(pssl, junk, 0)) < 1) {
- syslog(LOG_DEBUG, "crypto: SSL_write in client_read");
- }
- }
- rlen = SSL_read(pssl, sbuf, sizeof(sbuf));
- if (rlen < 1) {
- long errval;
-
- errval = SSL_get_error(pssl, rlen);
- if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
- sleep(1);
- continue;
- }
- syslog(LOG_DEBUG, "crypto: SSL_read got error %ld", errval);
- endtls();
- return (-1);
- }
- StrBufAppendBufPlain(buf, sbuf, rlen, 0);
- return rlen;
- }
- return (0);
-}
-
-
-int client_readline_sslbuffer(StrBuf *Line, StrBuf *IOBuf, const char **Pos, int timeout) {
- const char *pos = NULL;
- const char *pLF;
- int len, rlen;
- int nSuccessLess = 0;
- const char *pch = NULL;
-
- if ((Line == NULL) || (Pos == NULL) || (IOBuf == NULL)) {
- if (Pos != NULL) {
- *Pos = NULL;
- }
- return -1;
- }
-
- pos = *Pos;
- if ((StrLength(IOBuf) > 0) && (pos != NULL) && (pos < ChrPtr(IOBuf) + StrLength(IOBuf))) {
- pch = pos;
- pch = strchr(pch, '\n');
-
- if (pch == NULL) {
- StrBufAppendBufPlain(Line, pos, StrLength(IOBuf) - (pos - ChrPtr(IOBuf)), 0);
- FlushStrBuf(IOBuf);
- *Pos = NULL;
- }
- else {
- int n = 0;
- if ((pch > ChrPtr(IOBuf)) && (*(pch - 1) == '\r')) {
- n = 1;
- }
- StrBufAppendBufPlain(Line, pos, (pch - pos - n), 0);
-
- if (StrLength(IOBuf) <= (pch - ChrPtr(IOBuf) + 1)) {
- FlushStrBuf(IOBuf);
- *Pos = NULL;
- }
- else {
- *Pos = pch + 1;
- }
- return StrLength(Line);
- }
- }
-
- pLF = NULL;
- while ((nSuccessLess < timeout) && (pLF == NULL) && (CC->ssl != NULL)) {
-
- rlen = client_read_sslbuffer(IOBuf, timeout);
- if (rlen < 1) {
- return -1;
- }
- else if (rlen > 0) {
- pLF = strchr(ChrPtr(IOBuf), '\n');
- }
- }
- *Pos = NULL;
- if (pLF != NULL) {
- pos = ChrPtr(IOBuf);
- len = pLF - pos;
- if (len > 0 && (*(pLF - 1) == '\r') )
- len --;
- StrBufAppendBufPlain(Line, pos, len, 0);
- if (pLF + 1 >= ChrPtr(IOBuf) + StrLength(IOBuf)) {
- FlushStrBuf(IOBuf);
- }
- else {
- *Pos = pLF + 1;
- }
- return StrLength(Line);
- }
- return -1;
-}
-
-
-int client_read_sslblob(StrBuf *Target, long bytes, int timeout) {
- long baselen;
- long RemainRead;
- int retval = 0;
-
- baselen = StrLength(Target);
-
- if (StrLength(CC->RecvBuf.Buf) > 0) {
- long RemainLen;
- long TotalLen;
- const char *pchs;
-
- if (CC->RecvBuf.ReadWritePointer == NULL) {
- CC->RecvBuf.ReadWritePointer = ChrPtr(CC->RecvBuf.Buf);
- }
- pchs = ChrPtr(CC->RecvBuf.Buf);
- TotalLen = StrLength(CC->RecvBuf.Buf);
- RemainLen = TotalLen - (pchs - CC->RecvBuf.ReadWritePointer);
- if (RemainLen > bytes) {
- RemainLen = bytes;
- }
- if (RemainLen > 0) {
- StrBufAppendBufPlain(Target, CC->RecvBuf.ReadWritePointer, RemainLen, 0);
- CC->RecvBuf.ReadWritePointer += RemainLen;
- }
- if ((ChrPtr(CC->RecvBuf.Buf) + StrLength(CC->RecvBuf.Buf)) <= CC->RecvBuf.ReadWritePointer) {
- CC->RecvBuf.ReadWritePointer = NULL;
- FlushStrBuf(CC->RecvBuf.Buf);
- }
- }
-
- if (StrLength(Target) >= bytes + baselen) {
- return 1;
- }
-
- CC->RecvBuf.ReadWritePointer = NULL;
-
- while ((StrLength(Target) < bytes + baselen) && (retval >= 0)) {
- retval = client_read_sslbuffer(CC->RecvBuf.Buf, timeout);
- if (retval >= 0) {
- RemainRead = bytes - (StrLength (Target) - baselen);
- if (RemainRead < StrLength(CC->RecvBuf.Buf)) {
- StrBufAppendBufPlain(
- Target,
- ChrPtr(CC->RecvBuf.Buf),
- RemainRead, 0);
- CC->RecvBuf.ReadWritePointer = ChrPtr(CC->RecvBuf.Buf) + RemainRead;
- break;
- }
- StrBufAppendBuf(Target, CC->RecvBuf.Buf, 0);
- FlushStrBuf(CC->RecvBuf.Buf);
- }
- else {
- FlushStrBuf(CC->RecvBuf.Buf);
- return -1;
-
- }
- }
- return 1;
-}
-
-
-// CtdlStartTLS() starts TLS encryption for the current session. It
-// must be supplied with pre-generated strings for responses of "ok," "no
-// support for TLS," and "error" so that we can use this in any protocol.
-void CtdlStartTLS(char *ok_response, char *nosup_response, char *error_response) {
- int retval, bits, alg_bits;
-
- if (CC->redirect_ssl) {
- syslog(LOG_ERR, "crypto: attempt to begin SSL on an already encrypted connection");
- if (error_response != NULL) {
- cprintf("%s", error_response);
- }
- return;
- }
-
- if (!ssl_ctx) {
- syslog(LOG_ERR, "crypto: SSL failed: context has not been initialized");
- if (nosup_response != NULL) {
- cprintf("%s", nosup_response);
- }
- return;
- }
-
- update_key_and_cert_if_needed(); // did someone update the key or cert? if so, re-bind them
-
- if (!(CC->ssl = SSL_new(ssl_ctx))) {
- syslog(LOG_ERR, "crypto: SSL_new failed: %s", ERR_reason_error_string(ERR_get_error()));
- if (error_response != NULL) {
- cprintf("%s", error_response);
- }
- return;
- }
- if (!(SSL_set_fd(CC->ssl, CC->client_socket))) {
- syslog(LOG_ERR, "crypto: SSL_set_fd failed: %s", ERR_reason_error_string(ERR_get_error()));
- SSL_free(CC->ssl);
- CC->ssl = NULL;
- if (error_response != NULL) {
- cprintf("%s", error_response);
- }
- return;
- }
- if (ok_response != NULL) {
- cprintf("%s", ok_response);
- }
- retval = SSL_accept(CC->ssl);
- if (retval < 1) {
- // Can't notify the client of an error here; they will
- // discover the problem at the SSL layer and should
- // revert to unencrypted communications.
- syslog(LOG_ERR, "crypto: SSL_accept failed: %s", ERR_reason_error_string(ERR_get_error()));
- SSL_free(CC->ssl);
- CC->ssl = NULL;
- return;
- }
- bits = SSL_CIPHER_get_bits(SSL_get_current_cipher(CC->ssl), &alg_bits);
- syslog(LOG_INFO, "crypto: TLS using %s on %s (%d of %d bits)",
- SSL_CIPHER_get_name(SSL_get_current_cipher(CC->ssl)),
- SSL_CIPHER_get_version(SSL_get_current_cipher(CC->ssl)),
- bits, alg_bits
- );
- CC->redirect_ssl = 1;
-}
-
-
-// cmd_stls() starts TLS encryption for the current session
-void cmd_stls(char *params) {
- char ok_response[SIZ];
- char nosup_response[SIZ];
- char error_response[SIZ];
-
- unbuffer_output();
-
- sprintf(ok_response, "%d Begin TLS negotiation now\n", CIT_OK);
- sprintf(nosup_response, "%d TLS not supported here\n", ERROR + CMD_NOT_SUPPORTED);
- sprintf(error_response, "%d TLS negotiation error\n", ERROR + INTERNAL_ERROR);
-
- CtdlStartTLS(ok_response, nosup_response, error_response);
-}
-
-
-// cmd_gtls() returns status info about the TLS connection
-void cmd_gtls(char *params) {
- int bits, alg_bits;
-
- if (!CC->ssl || !CC->redirect_ssl) {
- cprintf("%d Session is not encrypted.\n", ERROR);
- return;
- }
- bits = SSL_CIPHER_get_bits(SSL_get_current_cipher(CC->ssl), &alg_bits);
- cprintf("%d %s|%s|%d|%d\n", CIT_OK,
- SSL_CIPHER_get_version(SSL_get_current_cipher(CC->ssl)),
- SSL_CIPHER_get_name(SSL_get_current_cipher(CC->ssl)),
- alg_bits, bits);
-}
-
-
-// endtls() shuts down the TLS connection
-void endtls(void) {
- if (!CC->ssl) {
- CC->redirect_ssl = 0;
- return;
- }
-
- syslog(LOG_INFO, "crypto: ending TLS on this session");
- SSL_shutdown(CC->ssl);
- SSL_free(CC->ssl);
- CC->ssl = NULL;
- CC->redirect_ssl = 0;
-}
-
-#endif // HAVE_OPENSSL
+++ /dev/null
-
-/*
- * Number of days for which self-signed certs are valid.
- */
-#define SIGN_DAYS 1100 // Just over three years
-
-// Which ciphers will be offered; see https://www.openssl.org/docs/manmaster/man1/ciphers.html
-#define CIT_CIPHERS "ALL:RC4+RSA:+SSLv2:+TLSv1:!MD5:@STRENGTH"
-
-#ifdef HAVE_OPENSSL
-#define OPENSSL_NO_KRB5 /* work around redhat b0rken ssl headers */
-void init_ssl(void);
-void client_write_ssl (const char *buf, int nbytes);
-int client_read_sslbuffer(StrBuf *buf, int timeout);
-int client_readline_sslbuffer(StrBuf *Target, StrBuf *Buffer, const char **Pos, int timeout);
-int client_read_sslblob(StrBuf *Target, long want_len, int timeout);
-void cmd_stls(char *params);
-void cmd_gtls(char *params);
-void endtls(void);
-void CtdlStartTLS(char *ok_response, char *nosup_response, char *error_response);
-extern SSL_CTX *ssl_ctx;
-
-#endif
+++ /dev/null
-#ifndef FILE_OPS_H
-#define FILE_OPS_H
-
-#include "context.h"
-
-void OpenCmdResult (char *, const char *);
-void abort_upl (CitContext *who);
-
-
-#endif /* FILE_OPS_H */
+++ /dev/null
-/*
- * Citadel protocol main dispatcher
- *
- * Copyright (c) 1987-2017 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- */
-
-#include <stdio.h>
-#include <libcitadel.h>
-
-#include "citserver.h"
-#include "ctdl_module.h"
-#include "config.h"
-/*
- * This loop recognizes all server commands.
- */
-void do_command_loop(void) {
- struct CitContext *CCC = CC;
- char cmdbuf[SIZ];
-
- time(&CCC->lastcmd);
- memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
- if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
- syslog(LOG_INFO, "Citadel client disconnected: ending session.");
- CCC->kill_me = KILLME_CLIENT_DISCONNECTED;
- return;
- }
-
- /* Log the server command, but don't show passwords... */
- if ( (strncasecmp(cmdbuf, "PASS", 4)) && (strncasecmp(cmdbuf, "SETP", 4)) ) {
- syslog(LOG_DEBUG, "[%s(%ld)] %s",
- CCC->curr_user, CCC->user.usernum, cmdbuf
- );
- }
- else {
- syslog(LOG_DEBUG, "[%s(%ld)] <password command hidden from log>",
- CCC->curr_user, CCC->user.usernum
- );
- }
-
- buffer_output();
-
- /*
- * Let other clients see the last command we executed, and
- * update the idle time, but not NOOP, QNOP, PEXP, GEXP, RWHO, or TIME.
- */
- if ( (strncasecmp(cmdbuf, "NOOP", 4))
- && (strncasecmp(cmdbuf, "QNOP", 4))
- && (strncasecmp(cmdbuf, "PEXP", 4))
- && (strncasecmp(cmdbuf, "GEXP", 4))
- && (strncasecmp(cmdbuf, "RWHO", 4))
- && (strncasecmp(cmdbuf, "TIME", 4)) ) {
- strcpy(CCC->lastcmdname, " ");
- safestrncpy(CCC->lastcmdname, cmdbuf, sizeof(CCC->lastcmdname));
- time(&CCC->lastidle);
- }
-
- if ((strncasecmp(cmdbuf, "ENT0", 4))
- && (strncasecmp(cmdbuf, "MESG", 4))
- && (strncasecmp(cmdbuf, "MSGS", 4)))
- {
- CCC->cs_flags &= ~CS_POSTING;
- }
-
- if (!DLoader_Exec_Cmd(cmdbuf)) {
- cprintf("%d Unrecognized or unsupported command.\n", ERROR + CMD_NOT_SUPPORTED);
- }
-
- unbuffer_output();
-
- /* Run any after-each-command routines registered by modules */
- PerformSessionHooks(EVT_CMD);
-}
-
+++ /dev/null
-/*
- * Server functions which handle file transfers and room directories.
- *
- * Copyright (c) 1987-2018 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.
- */
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <netdb.h>
-#include <libcitadel.h>
-#include <dirent.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include "ctdl_module.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-
-
-/*
- * Server command to delete a file from a room's directory
- */
-void cmd_delf(char *filename)
-{
- char pathname[64];
- int a;
-
- if (CtdlAccessCheck(ac_room_aide))
- return;
-
- if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
- cprintf("%d No directory in this room.\n", ERROR + NOT_HERE);
- return;
- }
-
- if (IsEmptyStr(filename)) {
- cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND);
- return;
- }
- for (a = 0; !IsEmptyStr(&filename[a]); ++a) {
- if ( (filename[a] == '/') || (filename[a] == '\\') ) {
- filename[a] = '_';
- }
- }
- snprintf(pathname, sizeof pathname,
- "%s/%s/%s",
- ctdl_file_dir,
- CC->room.QRdirname, filename
- );
- a = unlink(pathname);
- if (a == 0) {
- cprintf("%d File '%s' deleted.\n", CIT_OK, pathname);
- }
- else {
- cprintf("%d File '%s' not found.\n", ERROR + FILE_NOT_FOUND, pathname);
- }
-}
-
-
-/*
- * move a file from one room directory to another
- */
-void cmd_movf(char *cmdbuf)
-{
- char filename[PATH_MAX];
- char pathname[PATH_MAX];
- char newpath[PATH_MAX];
- char newroom[ROOMNAMELEN];
- char buf[PATH_MAX];
- int a;
- struct ctdlroom qrbuf;
-
- extract_token(filename, cmdbuf, 0, '|', sizeof filename);
- extract_token(newroom, cmdbuf, 1, '|', sizeof newroom);
-
- if (CtdlAccessCheck(ac_room_aide)) return;
-
- if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
- cprintf("%d No directory in this room.\n", ERROR + NOT_HERE);
- return;
- }
-
- if (IsEmptyStr(filename)) {
- cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND);
- return;
- }
-
- for (a = 0; !IsEmptyStr(&filename[a]); ++a) {
- if ( (filename[a] == '/') || (filename[a] == '\\') ) {
- filename[a] = '_';
- }
- }
- snprintf(pathname, sizeof pathname, "./files/%s/%s", CC->room.QRdirname, filename);
- if (access(pathname, 0) != 0) {
- cprintf("%d File '%s' not found.\n", ERROR + FILE_NOT_FOUND, pathname);
- return;
- }
-
- if (CtdlGetRoom(&qrbuf, newroom) != 0) {
- cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, newroom);
- return;
- }
- if ((qrbuf.QRflags & QR_DIRECTORY) == 0) {
- cprintf("%d '%s' is not a directory room.\n", ERROR + NOT_HERE, qrbuf.QRname);
- return;
- }
- snprintf(newpath, sizeof newpath, "./files/%s/%s", qrbuf.QRdirname, filename);
- if (link(pathname, newpath) != 0) {
- cprintf("%d Couldn't move file: %s\n", ERROR + INTERNAL_ERROR, strerror(errno));
- return;
- }
- unlink(pathname);
-
- /* this is a crude method of copying the file description */
- snprintf(buf, sizeof buf, "cat ./files/%s/filedir |grep \"%s\" >>./files/%s/filedir", CC->room.QRdirname, filename, qrbuf.QRdirname);
- system(buf);
- cprintf("%d File '%s' has been moved.\n", CIT_OK, filename);
-}
-
-
-/*
- * This code is common to all commands which open a file for downloading,
- * regardless of whether it's a file from the directory, an image, a network
- * spool file, a MIME attachment, etc.
- * It examines the file and displays the OK result code and some information
- * about the file. NOTE: this stuff is Unix dependent.
- */
-void OpenCmdResult(char *filename, const char *mime_type)
-{
- struct stat statbuf;
- time_t modtime;
- long filesize;
-
- fstat(fileno(CC->download_fp), &statbuf);
- CC->download_fp_total = statbuf.st_size;
- filesize = (long) statbuf.st_size;
- modtime = (time_t) statbuf.st_mtime;
-
- cprintf("%d %ld|%ld|%s|%s\n", CIT_OK, filesize, (long)modtime, filename, mime_type);
-}
-
-
-/*
- * open a file for downloading
- */
-void cmd_open(char *cmdbuf)
-{
- char filename[256];
- char pathname[PATH_MAX];
- int a;
-
- extract_token(filename, cmdbuf, 0, '|', sizeof filename);
-
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
- cprintf("%d No directory in this room.\n", ERROR + NOT_HERE);
- return;
- }
-
- if (IsEmptyStr(filename)) {
- cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND);
- return;
- }
- if (strstr(filename, "../") != NULL)
- {
- cprintf("%d syntax error.\n", ERROR + ILLEGAL_VALUE);
- return;
- }
-
- if (CC->download_fp != NULL) {
- cprintf("%d You already have a download file open.\n", ERROR + RESOURCE_BUSY);
- return;
- }
-
- for (a = 0; !IsEmptyStr(&filename[a]); ++a) {
- if ( (filename[a] == '/') || (filename[a] == '\\') ) {
- filename[a] = '_';
- }
- }
-
- snprintf(pathname, sizeof pathname, "%s/%s/%s", ctdl_file_dir, CC->room.QRdirname, filename);
- CC->download_fp = fopen(pathname, "r");
-
- if (CC->download_fp == NULL) {
- cprintf("%d cannot open %s: %s\n", ERROR + INTERNAL_ERROR, pathname, strerror(errno));
- return;
- }
-
- OpenCmdResult(filename, "application/octet-stream");
-}
-
-
-/*
- * open an image file
- */
-void cmd_oimg(char *cmdbuf)
-{
- char filename[PATH_MAX];
- char pathname[PATH_MAX];
- char MimeTestBuf[32];
- int rv;
-
- extract_token(filename, cmdbuf, 0, '|', sizeof filename);
-
- if (IsEmptyStr(filename)) {
- cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND);
- return;
- }
-
- if (CC->download_fp != NULL) {
- cprintf("%d You already have a download file open.\n", ERROR + RESOURCE_BUSY);
- return;
- }
-
- CC->download_fp = fopen(pathname, "rb");
- if (CC->download_fp == NULL) {
- strcat(pathname, ".gif");
- CC->download_fp = fopen(pathname, "rb");
- }
- if (CC->download_fp == NULL) {
- cprintf("%d Cannot open %s: %s\n", ERROR + FILE_NOT_FOUND, pathname, strerror(errno));
- return;
- }
- rv = fread(&MimeTestBuf[0], 1, 32, CC->download_fp);
- if (rv == -1) {
- cprintf("%d Cannot access %s: %s\n", ERROR + FILE_NOT_FOUND, pathname, strerror(errno));
- return;
- }
-
- rewind (CC->download_fp);
- OpenCmdResult(pathname, GuessMimeType(&MimeTestBuf[0], 32));
-}
-
-
-/*
- * open a file for uploading
- */
-void cmd_uopn(char *cmdbuf)
-{
- int a;
-
- extract_token(CC->upl_file, cmdbuf, 0, '|', sizeof CC->upl_file);
- extract_token(CC->upl_mimetype, cmdbuf, 1, '|', sizeof CC->upl_mimetype);
- extract_token(CC->upl_comment, cmdbuf, 2, '|', sizeof CC->upl_comment);
-
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
- cprintf("%d No directory in this room.\n", ERROR + NOT_HERE);
- return;
- }
-
- if (IsEmptyStr(CC->upl_file)) {
- cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND);
- return;
- }
-
- if (CC->upload_fp != NULL) {
- cprintf("%d You already have a upload file open.\n", ERROR + RESOURCE_BUSY);
- return;
- }
-
- for (a = 0; !IsEmptyStr(&CC->upl_file[a]); ++a) {
- if ( (CC->upl_file[a] == '/') || (CC->upl_file[a] == '\\') ) {
- CC->upl_file[a] = '_';
- }
- }
- snprintf(CC->upl_path, sizeof CC->upl_path, "%s/%s/%s", ctdl_file_dir, CC->room.QRdirname, CC->upl_file);
- snprintf(CC->upl_filedir, sizeof CC->upl_filedir, "%s/%s/filedir", ctdl_file_dir, CC->room.QRdirname);
-
- CC->upload_fp = fopen(CC->upl_path, "r");
- if (CC->upload_fp != NULL) {
- fclose(CC->upload_fp);
- CC->upload_fp = NULL;
- cprintf("%d '%s' already exists\n", ERROR + ALREADY_EXISTS, CC->upl_path);
- return;
- }
-
- CC->upload_fp = fopen(CC->upl_path, "wb");
- if (CC->upload_fp == NULL) {
- cprintf("%d Cannot open %s: %s\n", ERROR + INTERNAL_ERROR, CC->upl_path, strerror(errno));
- return;
- }
- cprintf("%d Ok\n", CIT_OK);
-}
-
-
-/*
- * open an image file for uploading
- */
-void cmd_uimg(char *cmdbuf)
-{
- int is_this_for_real;
- char basenm[256];
- int a;
-
- if (num_parms(cmdbuf) < 2) {
- cprintf("%d Usage error.\n", ERROR + ILLEGAL_VALUE);
- return;
- }
-
- is_this_for_real = extract_int(cmdbuf, 0);
- extract_token(CC->upl_mimetype, cmdbuf, 1, '|', sizeof CC->upl_mimetype);
- extract_token(basenm, cmdbuf, 2, '|', sizeof basenm);
- if (CC->upload_fp != NULL) {
- cprintf("%d You already have an upload file open.\n", ERROR + RESOURCE_BUSY);
- return;
- }
-
- strcpy(CC->upl_path, "");
-
- for (a = 0; !IsEmptyStr(&basenm[a]); ++a) {
- basenm[a] = tolower(basenm[a]);
- if ( (basenm[a] == '/') || (basenm[a] == '\\') ) {
- basenm[a] = '_';
- }
- }
-
- if (CC->user.axlevel >= AxAideU) {
- snprintf(CC->upl_path, sizeof CC->upl_path, "%s/%s", ctdl_image_dir, basenm);
- }
-
- if (IsEmptyStr(CC->upl_path)) {
- cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
- return;
- }
-
- if (is_this_for_real == 0) {
- cprintf("%d Ok to send image\n", CIT_OK);
- return;
- }
-
- CC->upload_fp = fopen(CC->upl_path, "wb");
- if (CC->upload_fp == NULL) {
- cprintf("%d Cannot open %s: %s\n", ERROR + INTERNAL_ERROR, CC->upl_path, strerror(errno));
- return;
- }
- cprintf("%d Ok\n", CIT_OK);
-}
-
-
-/*
- * close the download file
- */
-void cmd_clos(char *cmdbuf)
-{
- if (CC->download_fp == NULL) {
- cprintf("%d You don't have a download file open.\n", ERROR + RESOURCE_NOT_OPEN);
- return;
- }
-
- fclose(CC->download_fp);
- CC->download_fp = NULL;
- cprintf("%d Ok\n", CIT_OK);
-}
-
-
-/*
- * abort an upload
- */
-void abort_upl(CitContext *who)
-{
- if (who->upload_fp != NULL) {
- fclose(who->upload_fp);
- who->upload_fp = NULL;
- unlink(CC->upl_path);
- }
-}
-
-
-/*
- * close the upload file
- */
-void cmd_ucls(char *cmd)
-{
- FILE *fp;
- char upload_notice[SIZ];
-
- if (CC->upload_fp == NULL) {
- cprintf("%d You don't have an upload file open.\n", ERROR + RESOURCE_NOT_OPEN);
- return;
- }
-
- fclose(CC->upload_fp);
- CC->upload_fp = NULL;
-
- if (!strcasecmp(cmd, "1")) {
- cprintf("%d File '%s' saved.\n", CIT_OK, CC->upl_path);
- fp = fopen(CC->upl_filedir, "a");
- if (fp == NULL) {
- fp = fopen(CC->upl_filedir, "w");
- }
- if (fp != NULL) {
- fprintf(fp, "%s %s %s\n", CC->upl_file, CC->upl_mimetype, CC->upl_comment);
- fclose(fp);
- }
-
- if ((CC->room.QRflags2 & QR2_NOUPLMSG) == 0) {
- /* put together an upload notice */
- snprintf(upload_notice, sizeof upload_notice,
- "NEW UPLOAD: '%s'\n %s\n%s\n",
- CC->upl_file,
- CC->upl_comment,
- CC->upl_mimetype
- );
- quickie_message(CC->curr_user, NULL, NULL, CC->room.QRname, upload_notice, 0, NULL);
- }
- } else {
- abort_upl(CC);
- cprintf("%d File '%s' aborted.\n", CIT_OK, CC->upl_path);
- }
-}
-
-
-/*
- * read from the download file
- */
-void cmd_read(char *cmdbuf)
-{
- long start_pos;
- size_t bytes;
- char buf[SIZ];
- int rc;
-
- /* The client will transmit its requested offset and byte count */
- start_pos = extract_long(cmdbuf, 0);
- bytes = extract_int(cmdbuf, 1);
- if ((start_pos < 0) || (bytes <= 0)) {
- cprintf("%d you have to specify a value > 0.\n", ERROR + ILLEGAL_VALUE);
- return;
- }
-
- if (CC->download_fp == NULL) {
- cprintf("%d You don't have a download file open.\n", ERROR + RESOURCE_NOT_OPEN);
- return;
- }
-
- /* If necessary, reduce the byte count to the size of our buffer */
- if (bytes > sizeof(buf)) {
- bytes = sizeof(buf);
- }
-
- rc = fseek(CC->download_fp, start_pos, 0);
- if (rc < 0) {
- cprintf("%d your file is smaller than %ld.\n", ERROR + ILLEGAL_VALUE, start_pos);
- syslog(LOG_ERR, "serv_file: your file %s is smaller than %ld [%s]",
- CC->upl_path,
- start_pos,
- strerror(errno)
- );
-
- return;
- }
- bytes = fread(buf, 1, bytes, CC->download_fp);
- if (bytes > 0) {
- /* Tell the client the actual byte count and transmit it */
- cprintf("%d %d\n", BINARY_FOLLOWS, (int)bytes);
- client_write(buf, bytes);
- }
- else {
- cprintf("%d %s\n", ERROR, strerror(errno));
- }
-}
-
-
-/*
- * write to the upload file
- */
-void cmd_writ(char *cmdbuf)
-{
- struct CitContext *CCC = CC;
- int bytes;
- char *buf;
- int rv;
-
- unbuffer_output();
-
- bytes = extract_int(cmdbuf, 0);
-
- if (CCC->upload_fp == NULL) {
- cprintf("%d You don't have an upload file open.\n", ERROR + RESOURCE_NOT_OPEN);
- return;
- }
- if (bytes <= 0) {
- cprintf("%d you have to specify a value > 0.\n", ERROR + ILLEGAL_VALUE);
- return;
- }
-
- if (bytes > 100000) {
- bytes = 100000;
- }
-
- cprintf("%d %d\n", SEND_BINARY, bytes);
- buf = malloc(bytes + 1);
- client_read(buf, bytes);
- rv = fwrite(buf, bytes, 1, CCC->upload_fp);
- if (rv == -1) {
- syslog(LOG_ERR, "serv_file: %s", strerror(errno));
- }
- free(buf);
-}
-
-
-void files_logout_hook(void)
-{
- CitContext *CCC = MyContext();
-
- /*
- * If there is a download in progress, abort it.
- */
- if (CCC->download_fp != NULL) {
- fclose(CCC->download_fp);
- CCC->download_fp = NULL;
- }
-
- /*
- * If there is an upload in progress, abort it.
- */
- if (CCC->upload_fp != NULL) {
- abort_upl(CCC);
- }
-
-}
-
-
-/*
- * help_subst() - support routine for help file viewer
- */
-void help_subst(char *strbuf, char *source, char *dest)
-{
- char workbuf[SIZ];
- int p;
-
- while (p = pattern2(strbuf, source), (p >= 0)) {
- strcpy(workbuf, &strbuf[p + strlen(source)]);
- strcpy(&strbuf[p], dest);
- strcat(strbuf, workbuf);
- }
-}
-
-
-void do_help_subst(char *buffer)
-{
- char buf2[16];
-
- help_subst(buffer, "^nodename", CtdlGetConfigStr("c_nodename"));
- help_subst(buffer, "^humannode", CtdlGetConfigStr("c_humannode"));
- help_subst(buffer, "^fqdn", CtdlGetConfigStr("c_fqdn"));
- help_subst(buffer, "^username", CC->user.fullname);
- snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
- help_subst(buffer, "^usernum", buf2);
- help_subst(buffer, "^sysadm", CtdlGetConfigStr("c_sysadm"));
- help_subst(buffer, "^variantname", CITADEL);
- help_subst(buffer, "^maxsessions", CtdlGetConfigStr("c_maxsessions")); // yes it's numeric but str is ok here
- help_subst(buffer, "^bbsdir", ctdl_message_dir);
-}
-
-
-typedef const char *ccharp;
-/*
- * display system messages or help
- */
-void cmd_mesg(char *mname)
-{
- FILE *mfp;
- char targ[256];
- char buf[256];
- char buf2[256];
- DIR *dp;
- struct dirent *d;
-
- extract_token(buf, mname, 0, '|', sizeof buf);
-
- snprintf(buf2, sizeof buf2, "%s.%d.%d", buf, CC->cs_clientdev, CC->cs_clienttyp);
-
- /* If the client requested "?" then produce a listing */
- if (!strcmp(buf, "?")) {
- cprintf("%d %s\n", LISTING_FOLLOWS, buf);
- dp = opendir(ctdl_message_dir);
- if (dp != NULL) {
- while (d = readdir(dp), d != NULL) {
- if (d->d_name[0] != '.') {
- cprintf(" %s\n", d->d_name);
- }
- }
- closedir(dp);
- }
- cprintf("000\n");
- return;
- }
-
- /* Otherwise, look for the requested file by name. */
- snprintf(targ, sizeof targ, "%s/%s", ctdl_message_dir, buf);
- mfp = fopen(targ, "r");
- if (mfp==NULL) {
- cprintf("%d Cannot open '%s': %s\n",
- ERROR + FILE_NOT_FOUND, targ, strerror(errno));
- return;
- }
- cprintf("%d %s\n", LISTING_FOLLOWS, buf);
-
- while (fgets(buf, (sizeof buf - 1), mfp) != NULL) {
- buf[strlen(buf)-1] = 0;
- do_help_subst(buf);
- cprintf("%s\n",buf);
- }
-
- fclose(mfp);
- cprintf("000\n");
-}
-
-
-/*
- * enter system messages or help
- */
-void cmd_emsg(char *mname)
-{
- FILE *mfp;
- char targ[256];
- char buf[256];
- int a;
-
- unbuffer_output();
-
- if (CtdlAccessCheck(ac_aide)) return;
-
- extract_token(buf, mname, 0, '|', sizeof buf);
- for (a=0; !IsEmptyStr(&buf[a]); ++a) { /* security measure */
- if (buf[a] == '/') buf[a] = '.';
- }
-
- if (IsEmptyStr(targ)) {
- snprintf(targ, sizeof targ, "%s/%s", ctdl_message_dir, buf);
- }
-
- mfp = fopen(targ, "w");
- if (mfp==NULL) {
- cprintf("%d Cannot open '%s': %s\n",
- ERROR + INTERNAL_ERROR, targ, strerror(errno));
- return;
- }
- cprintf("%d %s\n", SEND_LISTING, targ);
-
- while (client_getln(buf, sizeof buf) >=0 && strcmp(buf, "000")) {
- fprintf(mfp, "%s\n", buf);
- }
-
- fclose(mfp);
-}
-
-/*****************************************************************************/
-/* MODULE INITIALIZATION STUFF */
-/*****************************************************************************/
-
-CTDL_MODULE_INIT(file_ops)
-{
- if (!threading) {
- CtdlRegisterSessionHook(files_logout_hook, EVT_LOGOUT, PRIO_LOGOUT + 8);
- CtdlRegisterProtoHook(cmd_delf, "DELF", "Delete a file");
- CtdlRegisterProtoHook(cmd_movf, "MOVF", "Move a file");
- CtdlRegisterProtoHook(cmd_open, "OPEN", "Open a download file transfer");
- CtdlRegisterProtoHook(cmd_clos, "CLOS", "Close a download file transfer");
- CtdlRegisterProtoHook(cmd_uopn, "UOPN", "Open an upload file transfer");
- CtdlRegisterProtoHook(cmd_ucls, "UCLS", "Close an upload file transfer");
- CtdlRegisterProtoHook(cmd_read, "READ", "File transfer read operation");
- CtdlRegisterProtoHook(cmd_writ, "WRIT", "File transfer write operation");
- CtdlRegisterProtoHook(cmd_oimg, "OIMG", "Open an image file for download");
- CtdlRegisterProtoHook(cmd_uimg, "UIMG", "Upload an image file");
- CtdlRegisterProtoHook(cmd_mesg, "MESG", "fetch system banners");
- CtdlRegisterProtoHook(cmd_emsg, "EMSG", "submit system banners");
- }
- /* return our Subversion id for the Log */
- return "file_ops";
-}
+++ /dev/null
-// Message-related protocol commands for Citadel clients
-//
-// Copyright (c) 1987-2022 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.
-
-#include <stdio.h>
-#include <libcitadel.h>
-#include "citserver.h"
-#include "ctdl_module.h"
-#include "internet_addressing.h"
-#include "user_ops.h"
-#include "room_ops.h"
-#include "config.h"
-
-extern char *msgkeys[];
-
-
-// Back end for the MSGS command: output message number only.
-void simple_listing(long msgnum, void *userdata) {
- cprintf("%ld\n", msgnum);
-}
-
-
-// Back end for the MSGS command: output header summary.
-void headers_listing(long msgnum, void *userdata) {
- struct CtdlMessage *msg;
- int output_mode = *(int *)userdata;
-
- msg = CtdlFetchMessage(msgnum, 0);
- if (msg == NULL) {
- cprintf("%ld|0|||||||\n", msgnum);
- return;
- }
-
- // change all vertical bars in the subject to hyphens so it doesn't screw up the protocol
- if (!CM_IsEmpty(msg, eMsgSubject)) {
- char *p;
- for (p=msg->cm_fields[eMsgSubject]; *p; p++) {
- if (*p == '|') {
- *p = '-';
- }
- }
- }
-
- // output all fields except the references hash
- cprintf("%ld|%s|%s|%s|%s|%s",
- msgnum,
- (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"),
- (!CM_IsEmpty(msg, eAuthor) ? msg->cm_fields[eAuthor] : ""),
- CtdlGetConfigStr("c_nodename"), // no more nodenames anymore
- (!CM_IsEmpty(msg, erFc822Addr) ? msg->cm_fields[erFc822Addr] : ""),
- (!CM_IsEmpty(msg, eMsgSubject) ? msg->cm_fields[eMsgSubject] : "")
- );
-
- if (output_mode == MSG_HDRS_THREADS) { // field view with thread hashes
-
- // output the references hash
- cprintf ("|%d|",
- (!CM_IsEmpty(msg, emessageId) ? HashLittle(msg->cm_fields[emessageId],strlen(msg->cm_fields[emessageId])) : 0)
- );
-
- // output the references hash (yes it's ok that we're trashing the source buffer by doing this)
- if (!CM_IsEmpty(msg, eWeferences)) {
- char *token;
- char *rest = msg->cm_fields[eWeferences];
- char *prev = rest;
- while((token = strtok_r(rest, "|", &rest))) {
- cprintf("%d%s", HashLittle(token,rest-prev-(*rest==0?0:1)), (*rest==0?"":","));
- prev = rest;
- }
- }
-
- cprintf("|\n");
- }
-
- else { // field view with no threads, subject extends out forever
- cprintf("\n");
- }
-
- CM_Free(msg);
-}
-
-typedef struct _msg_filter {
- HashList *Filter;
- HashPos *p;
- StrBuf *buffer;
-} msg_filter;
-
-
-void headers_brief_filter(long msgnum, void *userdata) {
- long i, l;
- struct CtdlMessage *msg;
- msg_filter *flt = (msg_filter*) userdata;
-
- l = GetCount(flt->Filter);
- msg = CtdlFetchMessage(msgnum, 0);
- StrBufPrintf(flt->buffer, "%ld", msgnum);
- if (msg == NULL) {
- for (i = 0; i < l; i++) {
- StrBufAppendBufPlain(flt->buffer, HKEY("|"), 0);
- }
- }
- else {
- const char *k;
- long len;
- void *v;
- RewindHashPos(flt->Filter, flt->p, 0);
- while (GetNextHashPos(flt->Filter, flt->p, &len, &k, &v)) {
- eMsgField f = (eMsgField) v;
-
- StrBufAppendBufPlain(flt->buffer, HKEY("|"), 0);
- if (!CM_IsEmpty(msg, f)) {
- StrBufAppendBufPlain(flt->buffer, CM_KEY(msg, f), 0);
- }
- }
- }
- StrBufAppendBufPlain(flt->buffer, HKEY("\n"), 0);
- cputbuf(flt->buffer);
-}
-
-// Back end for the MSGS command: output EUID header.
-void headers_euid(long msgnum, void *userdata) {
- struct CtdlMessage *msg;
-
- msg = CtdlFetchMessage(msgnum, 0);
- if (msg == NULL) {
- cprintf("%ld||\n", msgnum);
- return;
- }
-
- cprintf("%ld|%s|%s\n",
- msgnum,
- (!CM_IsEmpty(msg, eExclusiveID) ? msg->cm_fields[eExclusiveID] : ""),
- (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"));
- CM_Free(msg);
-}
-
-
-// cmd_msgs() - get list of message #'s in this room
-// implements the MSGS server command using CtdlForEachMessage()
-void cmd_msgs(char *cmdbuf) {
- int mode = 0;
- char which[16];
- char buf[256];
- char tfield[256];
- char tvalue[256];
- int cm_ref = 0;
- int with_template = 0;
- struct CtdlMessage *template = NULL;
- msg_filter filt;
- char search_string[1024];
- ForEachMsgCallback CallBack;
-
- if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
- extract_token(which, cmdbuf, 0, '|', sizeof which);
- cm_ref = extract_int(cmdbuf, 1);
- extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
- with_template = extract_int(cmdbuf, 2);
- int output_mode = extract_int(cmdbuf, 3);
- switch (output_mode) {
- default:
- case MSG_HDRS_BRIEF:
- CallBack = simple_listing;
- break;
- case MSG_HDRS_ALL:
- case MSG_HDRS_THREADS:
- CallBack = headers_listing;
- break;
- case MSG_HDRS_EUID:
- CallBack = headers_euid;
- break;
- case MSG_HDRS_BRIEFFILTER:
- with_template = 2;
- CallBack = headers_brief_filter;
- break;
- }
-
- strcat(which, " ");
- if (!strncasecmp(which, "OLD", 3))
- mode = MSGS_OLD;
- else if (!strncasecmp(which, "NEW", 3))
- mode = MSGS_NEW;
- else if (!strncasecmp(which, "FIRST", 5))
- mode = MSGS_FIRST;
- else if (!strncasecmp(which, "LAST", 4))
- mode = MSGS_LAST;
- else if (!strncasecmp(which, "GT", 2))
- mode = MSGS_GT;
- else if (!strncasecmp(which, "LT", 2))
- mode = MSGS_LT;
- else if (!strncasecmp(which, "SEARCH", 6))
- mode = MSGS_SEARCH;
- else
- mode = MSGS_ALL;
-
- if ( (mode == MSGS_SEARCH) && (!CtdlGetConfigInt("c_enable_fulltext")) ) {
- cprintf("%d Full text index is not enabled on this server.\n",
- ERROR + CMD_NOT_SUPPORTED);
- return;
- }
-
- if (with_template == 1) {
- memset(buf, 0, 5);
- unbuffer_output();
- cprintf("%d Send template then receive message list\n",
- START_CHAT_MODE);
- template = (struct CtdlMessage *)
- malloc(sizeof(struct CtdlMessage));
- memset(template, 0, sizeof(struct CtdlMessage));
- template->cm_magic = CTDLMESSAGE_MAGIC;
- template->cm_anon_type = MES_NORMAL;
-
- while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
- eMsgField f;
- long tValueLen;
-
- tValueLen = extract_token(tfield, buf, 0, '|', sizeof tfield);
- if ((tValueLen == 4) && GetFieldFromMnemonic(&f, tfield))
- {
- tValueLen = extract_token(tvalue, buf, 1, '|', sizeof tvalue);
- if (tValueLen >= 0) {
- CM_SetField(template, f, tvalue, tValueLen);
- }
- }
- }
- buffer_output();
- }
- else if (with_template == 2) {
- long i = 0;
- memset(buf, 0, 5);
- cprintf("%d Send list of headers\n",
- START_CHAT_MODE);
- filt.Filter = NewHash(1, lFlathash);
- filt.buffer = NewStrBufPlain(NULL, 1024);
- while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
- eMsgField f;
-
- if (GetFieldFromMnemonic(&f, buf))
- {
- Put(filt.Filter, LKEY(i), (void*)f, reference_free_handler);
- i++;
- }
- }
- filt.p = GetNewHashPos(filt.Filter, 0);
- buffer_output();
- }
- else {
- cprintf("%d \n", LISTING_FOLLOWS);
- }
-
- if (with_template < 2) {
- CtdlForEachMessage(mode,
- ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
- ( (mode == MSGS_SEARCH) ? search_string : NULL ),
- NULL,
- template,
- CallBack,
- &output_mode);
- if (template != NULL) CM_Free(template);
- }
- else {
- CtdlForEachMessage(mode,
- ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
- ( (mode == MSGS_SEARCH) ? search_string : NULL ),
- NULL,
- NULL,
- CallBack,
- &filt);
- DeleteHashPos(&filt.p);
- DeleteHash(&filt.Filter);
- FreeStrBuf(&filt.buffer);
-
- }
- cprintf("000\n");
-}
-
-
-/*
- * display a message (mode 0 - Citadel proprietary)
- */
-void cmd_msg0(char *cmdbuf)
-{
- long msgid;
- int headers_only = HEADERS_ALL;
-
- msgid = extract_long(cmdbuf, 0);
- headers_only = extract_int(cmdbuf, 1);
-
- CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0, NULL, NULL, NULL);
- return;
-}
-
-
-// display a message (mode 2 - RFC822)
-void cmd_msg2(char *cmdbuf) {
- long msgid;
- int headers_only = HEADERS_ALL;
-
- msgid = extract_long(cmdbuf, 0);
- headers_only = extract_int(cmdbuf, 1);
-
- CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0, NULL, NULL, NULL);
-}
-
-
-// Display a message using MIME content types
-void cmd_msg4(char *cmdbuf) {
- long msgid;
- char section[64];
-
- msgid = extract_long(cmdbuf, 0);
- extract_token(section, cmdbuf, 1, '|', sizeof section);
- CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0, NULL, NULL, NULL);
-}
-
-
-// Client tells us its preferred message format(s)
-void cmd_msgp(char *cmdbuf) {
- if (!strcasecmp(cmdbuf, "dont_decode")) {
- CC->msg4_dont_decode = 1;
- cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
- }
- else {
- safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
- cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
- }
-}
-
-
-// Open a component of a MIME message as a download file
-void cmd_opna(char *cmdbuf) {
- long msgid;
- char desired_section[128];
-
- msgid = extract_long(cmdbuf, 0);
- extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
- safestrncpy(CC->download_desired_section, desired_section,
- sizeof CC->download_desired_section);
- CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0, NULL, NULL, NULL);
-}
-
-
-// Open a component of a MIME message and transmit it all at once
-void cmd_dlat(char *cmdbuf) {
- long msgid;
- char desired_section[128];
-
- msgid = extract_long(cmdbuf, 0);
- extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
- safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
- CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0, NULL, NULL, NULL);
-}
-
-
-// message entry - mode 0 (normal)
-void cmd_ent0(char *entargs) {
- int post = 0;
- char recp[SIZ];
- char cc[SIZ];
- char bcc[SIZ];
- char supplied_euid[128];
- int anon_flag = 0;
- int format_type = 0;
- char newusername[256];
- char newuseremail[256];
- struct CtdlMessage *msg;
- int anonymous = 0;
- char errmsg[SIZ];
- int err = 0;
- struct recptypes *valid = NULL;
- struct recptypes *valid_to = NULL;
- struct recptypes *valid_cc = NULL;
- struct recptypes *valid_bcc = NULL;
- char subject[SIZ];
- int subject_required = 0;
- int do_confirm = 0;
- long msgnum;
- int i, j;
- char buf[256];
- int newuseremail_ok = 0;
- char references[SIZ];
- char *ptr;
-
- unbuffer_output();
-
- post = extract_int(entargs, 0);
- extract_token(recp, entargs, 1, '|', sizeof recp);
- anon_flag = extract_int(entargs, 2);
- format_type = extract_int(entargs, 3);
- extract_token(subject, entargs, 4, '|', sizeof subject);
- extract_token(newusername, entargs, 5, '|', sizeof newusername);
- do_confirm = extract_int(entargs, 6);
- extract_token(cc, entargs, 7, '|', sizeof cc);
- extract_token(bcc, entargs, 8, '|', sizeof bcc);
- switch(CC->room.QRdefaultview) {
- case VIEW_NOTES:
- case VIEW_WIKI:
- extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
- break;
- default:
- supplied_euid[0] = 0;
- break;
- }
- extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
- extract_token(references, entargs, 11, '|', sizeof references);
- for (ptr=references; *ptr != 0; ++ptr) {
- if (*ptr == '!') *ptr = '|';
- }
-
- /* first check to make sure the request is valid. */
-
- err = CtdlDoIHavePermissionToPostInThisRoom(
- errmsg,
- sizeof errmsg,
- NULL,
- POST_LOGGED_IN,
- (!IsEmptyStr(references)) // is this a reply? or a top-level post?
- );
- if (err) {
- cprintf("%d %s\n", err, errmsg);
- return;
- }
-
- /* Check some other permission type things. */
-
- if (IsEmptyStr(newusername)) {
- strcpy(newusername, CC->user.fullname);
- }
- if ( (CC->user.axlevel < AxAideU)
- && (strcasecmp(newusername, CC->user.fullname))
- && (strcasecmp(newusername, CC->cs_inet_fn))
- ) {
- cprintf("%d You don't have permission to author messages as '%s'.\n",
- ERROR + HIGHER_ACCESS_REQUIRED,
- newusername
- );
- return;
- }
-
- if (IsEmptyStr(newuseremail)) {
- newuseremail_ok = 1;
- }
-
- if (!IsEmptyStr(newuseremail)) {
- if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
- newuseremail_ok = 1;
- }
- else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
- j = num_tokens(CC->cs_inet_other_emails, '|');
- for (i=0; i<j; ++i) {
- extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
- if (!strcasecmp(newuseremail, buf)) {
- newuseremail_ok = 1;
- }
- }
- }
- }
-
- if (!newuseremail_ok) {
- cprintf("%d You don't have permission to author messages as '%s'.\n",
- ERROR + HIGHER_ACCESS_REQUIRED,
- newuseremail
- );
- return;
- }
-
- CC->cs_flags |= CS_POSTING;
-
- // In mailbox rooms we have to behave a little differently --
- // make sure the user has specified at least one recipient. Then
- // validate the recipient(s). We do this for the Mail> room, as
- // well as any room which has the "Mailbox" view set - unless it
- // is the DRAFTS room which does not require recipients.
-
- if ( ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
- || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
- ) && (strcasecmp(&CC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
- if (CC->user.axlevel < AxProbU) {
- strcpy(recp, "sysop");
- strcpy(cc, "");
- strcpy(bcc, "");
- }
-
- valid_to = validate_recipients(recp, NULL, 0);
- if (valid_to->num_error > 0) {
- cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
- free_recipients(valid_to);
- return;
- }
-
- valid_cc = validate_recipients(cc, NULL, 0);
- if (valid_cc->num_error > 0) {
- cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
- free_recipients(valid_to);
- free_recipients(valid_cc);
- return;
- }
-
- valid_bcc = validate_recipients(bcc, NULL, 0);
- if (valid_bcc->num_error > 0) {
- cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
- free_recipients(valid_to);
- free_recipients(valid_cc);
- free_recipients(valid_bcc);
- return;
- }
-
- // Recipient required, but none were specified
- if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
- free_recipients(valid_to);
- free_recipients(valid_cc);
- free_recipients(valid_bcc);
- cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
- return;
- }
-
- if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
- if (CtdlCheckInternetMailPermission(&CC->user)==0) {
- cprintf("%d You do not have permission "
- "to send Internet mail.\n",
- ERROR + HIGHER_ACCESS_REQUIRED);
- free_recipients(valid_to);
- free_recipients(valid_cc);
- free_recipients(valid_bcc);
- return;
- }
- }
-
- if ( ( (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet) > 0) && (CC->user.axlevel < AxNetU) ) {
- cprintf("%d Higher access required for network mail.\n", ERROR + HIGHER_ACCESS_REQUIRED);
- free_recipients(valid_to);
- free_recipients(valid_cc);
- free_recipients(valid_bcc);
- return;
- }
-
- if ((RESTRICT_INTERNET == 1)
- && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
- && ((CC->user.flags & US_INTERNET) == 0)
- && (!CC->internal_pgm)) {
- cprintf("%d You don't have access to Internet mail.\n",
- ERROR + HIGHER_ACCESS_REQUIRED);
- free_recipients(valid_to);
- free_recipients(valid_cc);
- free_recipients(valid_bcc);
- return;
- }
-
- }
-
- // Is this a room which has anonymous-only or anonymous-option?
- anonymous = MES_NORMAL;
- if (CC->room.QRflags & QR_ANONONLY) {
- anonymous = MES_ANONONLY;
- }
- if (CC->room.QRflags & QR_ANONOPT) {
- if (anon_flag == 1) { // only if the user requested it
- anonymous = MES_ANONOPT;
- }
- }
-
- if ((CC->room.QRflags & QR_MAILBOX) == 0) {
- recp[0] = 0;
- }
-
- // Recommend to the client that the use of a message subject is
- // strongly recommended in this room, if either the SUBJECTREQ flag
- // is set, or if there is one or more Internet email recipients.
-
- if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
- if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
- if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
- if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
-
- // If we're only checking the validity of the request, return success without creating the message.
- if (post == 0) {
- cprintf("%d %s|%d\n", CIT_OK,
- ((valid_to != NULL) ? valid_to->display_recp : ""),
- subject_required);
- free_recipients(valid_to);
- free_recipients(valid_cc);
- free_recipients(valid_bcc);
- return;
- }
-
- // We don't need these anymore because we'll do it differently below
- free_recipients(valid_to);
- free_recipients(valid_cc);
- free_recipients(valid_bcc);
-
- // Read in the message from the client.
- if (do_confirm) {
- cprintf("%d send message\n", START_CHAT_MODE);
- }
- else {
- cprintf("%d send message\n", SEND_LISTING);
- }
-
- msg = CtdlMakeMessage(&CC->user, recp, cc,
- CC->room.QRname, anonymous, format_type,
- newusername, newuseremail, subject,
- ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
- NULL, references);
-
- // Put together one big recipients struct containing to/cc/bcc all in one. This is for the envelope.
- char *all_recps = malloc(SIZ * 3);
- strcpy(all_recps, recp);
- if (!IsEmptyStr(cc)) {
- if (!IsEmptyStr(all_recps)) {
- strcat(all_recps, ",");
- }
- strcat(all_recps, cc);
- }
- if (!IsEmptyStr(bcc)) {
- if (!IsEmptyStr(all_recps)) {
- strcat(all_recps, ",");
- }
- strcat(all_recps, bcc);
- }
- if (!IsEmptyStr(all_recps)) {
- valid = validate_recipients(all_recps, NULL, 0);
- }
- else {
- valid = NULL;
- }
- free(all_recps);
-
- // posting into a mailing list room? set the envelope from
- // to the actual mail address so others get a valid reply-to-header.
- if ((valid != NULL) && (valid->num_room == 1) && !IsEmptyStr(valid->recp_orgroom)) {
- CM_SetField(msg, eenVelopeTo, valid->recp_orgroom, strlen(valid->recp_orgroom));
- }
-
- if (msg != NULL) {
- msgnum = CtdlSubmitMsg(msg, valid, "");
- if (do_confirm) {
- cprintf("%ld\n", msgnum);
-
- if (StrLength(CC->StatusMessage) > 0) {
- cprintf("%s\n", ChrPtr(CC->StatusMessage));
- }
- else if (msgnum >= 0L) {
- client_write(HKEY("Message accepted.\n"));
- }
- else {
- client_write(HKEY("Internal error.\n"));
- }
-
- if (!CM_IsEmpty(msg, eExclusiveID)) {
- cprintf("%s\n", msg->cm_fields[eExclusiveID]);
- } else {
- cprintf("\n");
- }
- cprintf("000\n");
- }
-
- CM_Free(msg);
- }
- if (valid != NULL) {
- free_recipients(valid);
- }
- return;
-}
-
-
-// Delete message from current room
-void cmd_dele(char *args) {
- int num_deleted;
- int i;
- char msgset[SIZ];
- char msgtok[32];
- long *msgs;
- int num_msgs = 0;
-
- extract_token(msgset, args, 0, '|', sizeof msgset);
- num_msgs = num_tokens(msgset, ',');
- if (num_msgs < 1) {
- cprintf("%d Nothing to do.\n", CIT_OK);
- return;
- }
-
- if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
- cprintf("%d Higher access required.\n",
- ERROR + HIGHER_ACCESS_REQUIRED);
- return;
- }
-
- // Build our message set to be moved/copied
- msgs = malloc(num_msgs * sizeof(long));
- for (i=0; i<num_msgs; ++i) {
- extract_token(msgtok, msgset, i, ',', sizeof msgtok);
- msgs[i] = atol(msgtok);
- }
-
- num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
- free(msgs);
-
- if (num_deleted) {
- cprintf("%d %d message%s deleted.\n", CIT_OK,
- num_deleted, ((num_deleted != 1) ? "s" : ""));
- } else {
- cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
- }
-}
-
-
-// move or copy a message to another room
-void cmd_move(char *args) {
- char msgset[SIZ];
- char msgtok[32];
- long *msgs;
- int num_msgs = 0;
-
- char targ[ROOMNAMELEN];
- struct ctdlroom qtemp;
- int err;
- int is_copy = 0;
- int ra;
- int permit = 0;
- int i;
-
- extract_token(msgset, args, 0, '|', sizeof msgset);
- num_msgs = num_tokens(msgset, ',');
- if (num_msgs < 1) {
- cprintf("%d Nothing to do.\n", CIT_OK);
- return;
- }
-
- extract_token(targ, args, 1, '|', sizeof targ);
- convert_room_name_macros(targ, sizeof targ);
- targ[ROOMNAMELEN - 1] = 0;
- is_copy = extract_int(args, 2);
-
- if (CtdlGetRoom(&qtemp, targ) != 0) {
- cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
- return;
- }
-
- if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
- cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
- return;
- }
-
- CtdlGetUser(&CC->user, CC->curr_user);
- CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
-
- // Check for permission to perform this operation.
- // Remember: "CC->room" is source, "qtemp" is target.
- permit = 0;
-
- // Admins can move/copy
- if (CC->user.axlevel >= AxAideU) permit = 1;
-
- // Room aides can move/copy
- if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
-
- // Permit move/copy from personal rooms
- if ((CC->room.QRflags & QR_MAILBOX)
- && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
-
- // Permit only copy from public to personal room
- if ( (is_copy)
- && (!(CC->room.QRflags & QR_MAILBOX))
- && (qtemp.QRflags & QR_MAILBOX)
- ) {
- permit = 1;
- }
-
- // Permit message removal from collaborative delete rooms
- if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
-
- // Users allowed to post into the target room may move into it too.
- if ((CC->room.QRflags & QR_MAILBOX) &&
- (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
-
- // User must have access to target room
- if (!(ra & UA_KNOWN)) permit = 0;
-
- if (!permit) {
- cprintf("%d Higher access required.\n",
- ERROR + HIGHER_ACCESS_REQUIRED);
- return;
- }
-
- // Build our message set to be moved/copied
- msgs = malloc(num_msgs * sizeof(long));
- for (i=0; i<num_msgs; ++i) {
- extract_token(msgtok, msgset, i, ',', sizeof msgtok);
- msgs[i] = atol(msgtok);
- }
-
- // Do the copy
- err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
- if (err != 0) {
- cprintf("%d Cannot store message(s) in %s: error %d\n",
- err, targ, err);
- free(msgs);
- return;
- }
-
- // Now delete the message from the source room, if this is a 'move' rather than a 'copy' operation.
- if (is_copy == 0) {
- CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
- }
- free(msgs);
-
- cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
-}
-
-
-/*****************************************************************************/
-/* MODULE INITIALIZATION STUFF */
-/*****************************************************************************/
-CTDL_MODULE_INIT(ctdl_message)
-{
- if (!threading) {
-
- CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
- CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
- CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
- CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
- CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
- CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
- CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
- CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
- CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
- CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
- }
-
- /* return our Subversion id for the Log */
- return "ctdl_message";
-}
+++ /dev/null
-// Server functions which perform operations on room objects.
-//
-// Copyright (c) 1987-2022 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.
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <dirent.h> /* for cmd_rdir to read contents of the directory */
-#include <libcitadel.h>
-
-#include "citserver.h"
-#include "ctdl_module.h"
-#include "room_ops.h"
-#include "config.h"
-
-// Back-back-end for all room listing commands
-void list_roomname(struct ctdlroom *qrbuf, int ra, int current_view, int default_view) {
- char truncated_roomname[ROOMNAMELEN];
-
- // For my own mailbox rooms, chop off the owner prefix
- if ( (qrbuf->QRflags & QR_MAILBOX)
- && (atol(qrbuf->QRname) == CC->user.usernum) ) {
- safestrncpy(truncated_roomname, qrbuf->QRname, sizeof truncated_roomname);
- safestrncpy(truncated_roomname, &truncated_roomname[11], sizeof truncated_roomname);
- cprintf("%s", truncated_roomname);
- }
- // For all other rooms, just display the name in its entirety
- else {
- cprintf("%s", qrbuf->QRname);
- }
-
- /* ...and now the other parameters */
- cprintf("|%u|%d|%d|%d|%d|%d|%d|%ld|\n",
- qrbuf->QRflags,
- (int) qrbuf->QRfloor,
- (int) qrbuf->QRorder,
- (int) qrbuf->QRflags2,
- ra,
- current_view,
- default_view,
- qrbuf->QRmtime
- );
-}
-
-
-// cmd_lrms() - List all accessible rooms, known or forgotten
-void cmd_lrms_backend(struct ctdlroom *qrbuf, void *data) {
- int FloorBeingSearched = (-1);
- int ra;
- int view;
-
- FloorBeingSearched = *(int *)data;
- CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
-
- if ((( ra & (UA_KNOWN | UA_ZAPPED)))
- && ((qrbuf->QRfloor == (FloorBeingSearched))
- || ((FloorBeingSearched) < 0)))
- list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
-}
-
-
-void cmd_lrms(char *argbuf) {
- int FloorBeingSearched = (-1);
- if (!IsEmptyStr(argbuf))
- FloorBeingSearched = extract_int(argbuf, 0);
-
- if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
- CtdlGetUser(&CC->user, CC->curr_user);
- cprintf("%d Accessible rooms:\n", LISTING_FOLLOWS);
-
- CtdlForEachRoom(cmd_lrms_backend, &FloorBeingSearched);
- cprintf("000\n");
-}
-
-
-// cmd_lkra() - List all known rooms
-void cmd_lkra_backend(struct ctdlroom *qrbuf, void *data) {
- int FloorBeingSearched = (-1);
- int ra;
- int view;
-
- FloorBeingSearched = *(int *)data;
- CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
-
- if ((( ra & (UA_KNOWN))) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) {
- list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
- }
-}
-
-
-void cmd_lkra(char *argbuf) {
- int FloorBeingSearched = (-1);
- if (!IsEmptyStr(argbuf)) {
- FloorBeingSearched = extract_int(argbuf, 0);
- }
-
- if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
- CtdlGetUser(&CC->user, CC->curr_user);
- cprintf("%d Known rooms:\n", LISTING_FOLLOWS);
-
- CtdlForEachRoom(cmd_lkra_backend, &FloorBeingSearched);
- cprintf("000\n");
-}
-
-
-void cmd_lprm_backend(struct ctdlroom *qrbuf, void *data) {
- int FloorBeingSearched = (-1);
- int ra;
- int view;
-
- FloorBeingSearched = *(int *)data;
- CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
-
- if (((qrbuf->QRflags & QR_PRIVATE) == 0) && ((qrbuf->QRflags & QR_MAILBOX) == 0) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) {
- list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
- }
-}
-
-
-void cmd_lprm(char *argbuf) {
- int FloorBeingSearched = (-1);
- if (!IsEmptyStr(argbuf)) {
- FloorBeingSearched = extract_int(argbuf, 0);
- }
-
- cprintf("%d Public rooms:\n", LISTING_FOLLOWS);
-
- CtdlForEachRoom(cmd_lprm_backend, &FloorBeingSearched);
- cprintf("000\n");
-}
-
-
-// cmd_lkrn() - List all known rooms with new messages
-void cmd_lkrn_backend(struct ctdlroom *qrbuf, void *data) {
- int FloorBeingSearched = (-1);
- int ra;
- int view;
-
- FloorBeingSearched = *(int *)data;
- CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
-
- if ((ra & UA_KNOWN) && (ra & UA_HASNEWMSGS) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) {
- list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
- }
-}
-
-
-void cmd_lkrn(char *argbuf) {
- int FloorBeingSearched = (-1);
- if (!IsEmptyStr(argbuf)) {
- FloorBeingSearched = extract_int(argbuf, 0);
- }
-
- if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
- CtdlGetUser(&CC->user, CC->curr_user);
- cprintf("%d Rooms w/ new msgs:\n", LISTING_FOLLOWS);
-
- CtdlForEachRoom(cmd_lkrn_backend, &FloorBeingSearched);
- cprintf("000\n");
-}
-
-
-// cmd_lkro() - List all known rooms
-void cmd_lkro_backend(struct ctdlroom *qrbuf, void *data) {
- int FloorBeingSearched = (-1);
- int ra;
- int view;
-
- FloorBeingSearched = *(int *)data;
- CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
-
- if ((ra & UA_KNOWN) && ((ra & UA_HASNEWMSGS) == 0) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) {
- list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
- }
-}
-
-
-void cmd_lkro(char *argbuf) {
- int FloorBeingSearched = (-1);
- if (!IsEmptyStr(argbuf)) {
- FloorBeingSearched = extract_int(argbuf, 0);
- }
-
- if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
- CtdlGetUser(&CC->user, CC->curr_user);
- cprintf("%d Rooms w/o new msgs:\n", LISTING_FOLLOWS);
-
- CtdlForEachRoom(cmd_lkro_backend, &FloorBeingSearched);
- cprintf("000\n");
-}
-
-
-// cmd_lzrm() - List all forgotten rooms
-void cmd_lzrm_backend(struct ctdlroom *qrbuf, void *data) {
- int FloorBeingSearched = (-1);
- int ra;
- int view;
-
- FloorBeingSearched = *(int *)data;
- CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
-
- if ((ra & UA_GOTOALLOWED) && (ra & UA_ZAPPED) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) {
- list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
- }
-}
-
-
-void cmd_lzrm(char *argbuf) {
- int FloorBeingSearched = (-1);
- if (!IsEmptyStr(argbuf))
- FloorBeingSearched = extract_int(argbuf, 0);
-
- if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
- CtdlGetUser(&CC->user, CC->curr_user);
- cprintf("%d Zapped rooms:\n", LISTING_FOLLOWS);
-
- CtdlForEachRoom(cmd_lzrm_backend, &FloorBeingSearched);
- cprintf("000\n");
-}
-
-
-// cmd_goto() - goto a new room
-void cmd_goto(char *gargs) {
- struct CitContext *CCC = CC;
- struct ctdlroom QRscratch;
- int c;
- int ok = 0;
- int ra;
- char augmented_roomname[ROOMNAMELEN];
- char towhere[ROOMNAMELEN];
- char password[32];
- int transiently = 0;
-
- if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
- extract_token(towhere, gargs, 0, '|', sizeof towhere);
- extract_token(password, gargs, 1, '|', sizeof password);
- transiently = extract_int(gargs, 2);
-
- CtdlGetUser(&CCC->user, CCC->curr_user);
-
- // Handle some of the macro named rooms
- convert_room_name_macros(towhere, sizeof towhere);
-
- // First try a regular match
- c = CtdlGetRoom(&QRscratch, towhere);
-
- // Then try a mailbox name match
- if (c != 0) {
- CtdlMailboxName(augmented_roomname, sizeof augmented_roomname, &CCC->user, towhere);
- c = CtdlGetRoom(&QRscratch, augmented_roomname);
- if (c == 0) {
- safestrncpy(towhere, augmented_roomname, sizeof towhere);
- }
- }
-
- // And if the room was found...
- if (c == 0) {
- // Let internal programs go directly to any room.
- if (CCC->internal_pgm) {
- memcpy(&CCC->room, &QRscratch, sizeof(struct ctdlroom));
- CtdlUserGoto(NULL, 1, transiently, NULL, NULL, NULL, NULL);
- return;
- }
-
- // See if there is an existing user/room relationship
- CtdlRoomAccess(&QRscratch, &CCC->user, &ra, NULL);
-
- // normal clients have to pass through security
- if (ra & UA_GOTOALLOWED) {
- ok = 1;
- }
-
- if (ok == 1) {
- if ((QRscratch.QRflags & QR_MAILBOX) &&
- ((ra & UA_GOTOALLOWED))) {
- memcpy(&CCC->room, &QRscratch, sizeof(struct ctdlroom));
- CtdlUserGoto(NULL, 1, transiently, NULL, NULL, NULL, NULL);
- return;
- }
- else if ((QRscratch.QRflags & QR_PASSWORDED) &&
- ((ra & UA_KNOWN) == 0) &&
- (strcasecmp(QRscratch.QRpasswd, password)) &&
- (CCC->user.axlevel < AxAideU)
- ) {
- cprintf("%d wrong or missing passwd\n", ERROR + PASSWORD_REQUIRED);
- return;
- }
- else if ((QRscratch.QRflags & QR_PRIVATE) &&
- ((QRscratch.QRflags & QR_PASSWORDED) == 0) &&
- ((QRscratch.QRflags & QR_GUESSNAME) == 0) &&
- ((ra & UA_KNOWN) == 0) &&
- (CCC->user.axlevel < AxAideU)
- ) {
- syslog(LOG_DEBUG, "rooms: failed to acquire private room");
- }
- else {
- memcpy(&CCC->room, &QRscratch, sizeof(struct ctdlroom));
- CtdlUserGoto(NULL, 1, transiently, NULL, NULL, NULL, NULL);
- return;
- }
- }
- }
-
- cprintf("%d room '%s' not found\n", ERROR + ROOM_NOT_FOUND, towhere);
-}
-
-
-void cmd_whok(char *cmdbuf) {
- struct ctdluser temp;
- struct cdbdata *cdbus;
- int ra;
-
- cprintf("%d Who knows room:\n", LISTING_FOLLOWS);
- cdb_rewind(CDB_USERS);
- while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL) {
- memset(&temp, 0, sizeof temp);
- memcpy(&temp, cdbus->ptr, sizeof temp);
- cdb_free(cdbus);
-
- CtdlRoomAccess(&CC->room, &temp, &ra, NULL);
- if ((!IsEmptyStr(temp.fullname)) &&
- (CC->room.QRflags & QR_INUSE) &&
- (ra & UA_KNOWN)
- )
- cprintf("%s\n", temp.fullname);
- }
- cprintf("000\n");
-}
-
-
-// RDIR command for room directory
-void cmd_rdir(char *cmdbuf) {
- char buf[256];
- char comment[256];
- FILE *fd;
- struct stat statbuf;
- DIR *filedir = NULL;
- struct dirent *filedir_entry;
- int d_namelen;
- char buf2[SIZ];
- char mimebuf[64];
- long len;
-
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- CtdlGetRoom(&CC->room, CC->room.QRname);
- CtdlGetUser(&CC->user, CC->curr_user);
-
- if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
- cprintf("%d not here.\n", ERROR + NOT_HERE);
- return;
- }
- if (((CC->room.QRflags & QR_VISDIR) == 0)
- && (CC->user.axlevel < AxAideU)
- && (CC->user.usernum != CC->room.QRroomaide))
- {
- cprintf("%d not here.\n", ERROR + HIGHER_ACCESS_REQUIRED);
- return;
- }
-
- snprintf(buf, sizeof buf, "%s/%s", ctdl_file_dir, CC->room.QRdirname);
- filedir = opendir (buf);
-
- if (filedir == NULL) {
- cprintf("%d not here.\n", ERROR + HIGHER_ACCESS_REQUIRED);
- return;
- }
- cprintf("%d %s|%s/%s\n", LISTING_FOLLOWS, CtdlGetConfigStr("c_fqdn"), ctdl_file_dir, CC->room.QRdirname);
-
- snprintf(buf, sizeof buf, "%s/%s/filedir", ctdl_file_dir, CC->room.QRdirname);
- fd = fopen(buf, "r");
- if (fd == NULL) {
- fd = fopen("/dev/null", "r");
- }
- while ((filedir_entry = readdir(filedir))) {
- if (strcasecmp(filedir_entry->d_name, "filedir") && filedir_entry->d_name[0] != '.') {
-#ifdef _DIRENT_HAVE_D_NAMELEN
- d_namelen = filedir_entry->d_namlen;
-#else
- d_namelen = strlen(filedir_entry->d_name);
-#endif
- snprintf(buf, sizeof buf, "%s/%s/%s", ctdl_file_dir, CC->room.QRdirname, filedir_entry->d_name);
- stat(buf, &statbuf); /* stat the file */
- if (!(statbuf.st_mode & S_IFREG)) {
- snprintf(buf2, sizeof buf2,
- "\"%s\" appears in the file directory for room \"%s\" but is not a regular file. Directories, named pipes, sockets, etc. are not usable in Citadel room directories.\n",
- buf, CC->room.QRname
- );
- CtdlAideMessage(buf2, "Unusable data found in room directory");
- continue; /* not a useable file type so don't show it */
- }
- safestrncpy(comment, "", sizeof comment);
- fseek(fd, 0L, 0); /* rewind descriptions file */
- /* Get the description from the descriptions file */
- while ((fgets(buf, sizeof buf, fd) != NULL) && (IsEmptyStr(comment))) {
- buf[strlen(buf) - 1] = 0;
- if ((!strncasecmp(buf, filedir_entry->d_name, d_namelen)) && (buf[d_namelen] == ' '))
- safestrncpy(comment, &buf[d_namelen + 1], sizeof comment);
- }
- len = extract_token (mimebuf, comment, 0,' ', 64);
- if ((len <0) || strchr(mimebuf, '/') == NULL) {
- snprintf (mimebuf, 64, "application/octetstream");
- len = 0;
- }
- cprintf("%s|%ld|%s|%s\n",
- filedir_entry->d_name,
- (long)statbuf.st_size,
- mimebuf,
- &comment[len]);
- }
- }
- fclose(fd);
- closedir(filedir);
-
- cprintf("000\n");
-}
-
-
-// get room parameters (admin or room admin command)
-void cmd_getr(char *cmdbuf) {
- if (CtdlAccessCheck(ac_room_aide)) return;
-
- CtdlGetRoom(&CC->room, CC->room.QRname);
- cprintf("%d%c%s|%s|%s|%d|%d|%d|%d|%d|\n",
- CIT_OK,
- CtdlCheckExpress(),
- ((CC->room.QRflags & QR_MAILBOX) ? &CC->room.QRname[11] : CC->room.QRname),
- ((CC->room.QRflags & QR_PASSWORDED) ? CC->room.QRpasswd : ""),
- ((CC->room.QRflags & QR_DIRECTORY) ? CC->room.QRdirname : ""),
- CC->room.QRflags,
- (int) CC->room.QRfloor,
- (int) CC->room.QRorder,
- CC->room.QRdefaultview,
- CC->room.QRflags2
- );
-}
-
-
-// set room parameters (admin or room admin command)
-void cmd_setr(char *args) {
- char buf[256];
- int new_order = 0;
- int r;
- int new_floor;
- char new_name[ROOMNAMELEN];
-
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- if (num_parms(args) >= 6) {
- new_floor = extract_int(args, 5);
- }
- else {
- new_floor = (-1); /* don't change the floor */
- }
-
- /* When is a new name more than just a new name? When the old name
- * has a namespace prefix.
- */
- if (CC->room.QRflags & QR_MAILBOX) {
- sprintf(new_name, "%010ld.", atol(CC->room.QRname) );
- }
- else {
- safestrncpy(new_name, "", sizeof new_name);
- }
- extract_token(&new_name[strlen(new_name)], args, 0, '|', (sizeof new_name - strlen(new_name)));
-
- r = CtdlRenameRoom(CC->room.QRname, new_name, new_floor);
-
- if (r == crr_room_not_found) {
- cprintf("%d Internal error - room not found?\n", ERROR + INTERNAL_ERROR);
- }
- else if (r == crr_already_exists) {
- cprintf("%d '%s' already exists.\n",
- ERROR + ALREADY_EXISTS, new_name);
- }
- else if (r == crr_noneditable) {
- cprintf("%d Cannot edit this room.\n", ERROR + NOT_HERE);
- }
- else if (r == crr_invalid_floor) {
- cprintf("%d Target floor does not exist.\n",
- ERROR + INVALID_FLOOR_OPERATION);
- }
- else if (r == crr_access_denied) {
- cprintf("%d You do not have permission to edit '%s'\n",
- ERROR + HIGHER_ACCESS_REQUIRED,
- CC->room.QRname);
- }
- else if (r != crr_ok) {
- cprintf("%d Error: CtdlRenameRoom() returned %d\n",
- ERROR + INTERNAL_ERROR, r);
- }
-
- if (r != crr_ok) {
- return;
- }
-
- CtdlGetRoom(&CC->room, new_name);
-
- /* Now we have to do a bunch of other stuff */
-
- if (num_parms(args) >= 7) {
- new_order = extract_int(args, 6);
- if (new_order < 1)
- new_order = 1;
- if (new_order > 127)
- new_order = 127;
- }
-
- CtdlGetRoomLock(&CC->room, CC->room.QRname);
-
- /* Directory room */
- extract_token(buf, args, 2, '|', sizeof buf);
- buf[15] = 0;
- safestrncpy(CC->room.QRdirname, buf,
- sizeof CC->room.QRdirname);
-
- /* Default view */
- if (num_parms(args) >= 8) {
- CC->room.QRdefaultview = extract_int(args, 7);
- }
-
- /* Second set of flags */
- if (num_parms(args) >= 9) {
- CC->room.QRflags2 = extract_int(args, 8);
- }
-
- /* Misc. flags */
- CC->room.QRflags = (extract_int(args, 3) | QR_INUSE);
- /* Clean up a client boo-boo: if the client set the room to
- * guess-name or passworded, ensure that the private flag is
- * also set.
- */
- if ((CC->room.QRflags & QR_GUESSNAME)
- || (CC->room.QRflags & QR_PASSWORDED))
- CC->room.QRflags |= QR_PRIVATE;
-
- /* Some changes can't apply to BASEROOM */
- if (!strncasecmp(CC->room.QRname, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)) {
- CC->room.QRorder = 0;
- CC->room.QRpasswd[0] = '\0';
- CC->room.QRflags &= ~(QR_PRIVATE & QR_PASSWORDED &
- QR_GUESSNAME & QR_PREFONLY & QR_MAILBOX);
- CC->room.QRflags |= QR_PERMANENT;
- }
- else {
- /* March order (doesn't apply to AIDEROOM) */
- if (num_parms(args) >= 7)
- CC->room.QRorder = (char) new_order;
- /* Room password */
- extract_token(buf, args, 1, '|', sizeof buf);
- buf[10] = 0;
- safestrncpy(CC->room.QRpasswd, buf, sizeof CC->room.QRpasswd);
- /* Kick everyone out if the client requested it
- * (by changing the room's generation number)
- */
- if (extract_int(args, 4)) {
- time(&CC->room.QRgen);
- }
- }
- /* Some changes can't apply to AIDEROOM */
- if (!strncasecmp(CC->room.QRname, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)) {
- CC->room.QRorder = 0;
- CC->room.QRflags &= ~QR_MAILBOX;
- CC->room.QRflags |= QR_PERMANENT;
- }
-
- /* Write the room record back to disk */
- CtdlPutRoomLock(&CC->room);
-
- /* Create a room directory if necessary */
- if (CC->room.QRflags & QR_DIRECTORY) {
- snprintf(buf, sizeof buf,"%s/%s", ctdl_file_dir, CC->room.QRdirname);
- mkdir(buf, 0755);
- }
- snprintf(buf, sizeof buf, "The room \"%s\" has been edited by %s.\n",
- CC->room.QRname,
- (CC->logged_in ? CC->curr_user : "an administrator")
- );
- CtdlAideMessage(buf, "Room modification Message");
- cprintf("%d Ok\n", CIT_OK);
-}
-
-
-// get the name of the room admin for this room
-void cmd_geta(char *cmdbuf) {
- struct ctdluser usbuf;
-
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- if (CtdlGetUserByNumber(&usbuf, CC->room.QRroomaide) == 0) {
- cprintf("%d %s\n", CIT_OK, usbuf.fullname);
- }
- else {
- cprintf("%d \n", CIT_OK);
- }
-}
-
-
-// set the room admin for this room
-void cmd_seta(char *new_ra) {
- struct ctdluser usbuf;
- long newu;
- char buf[SIZ];
- int post_notice;
-
- if (CtdlAccessCheck(ac_room_aide)) return;
-
- if (CtdlGetUser(&usbuf, new_ra) != 0) {
- newu = (-1L);
- }
- else {
- newu = usbuf.usernum;
- }
-
- CtdlGetRoomLock(&CC->room, CC->room.QRname);
- post_notice = 0;
- if (CC->room.QRroomaide != newu) {
- post_notice = 1;
- }
- CC->room.QRroomaide = newu;
- CtdlPutRoomLock(&CC->room);
-
- // We have to post the change notice _after_ writing changes to
- // the room table, otherwise it would deadlock!
- if (post_notice == 1) {
- if (!IsEmptyStr(usbuf.fullname))
- snprintf(buf, sizeof buf,
- "%s is now the room admin for \"%s\".\n",
- usbuf.fullname, CC->room.QRname);
- else
- snprintf(buf, sizeof buf,
- "There is now no room admin for \"%s\".\n",
- CC->room.QRname);
- CtdlAideMessage(buf, "Admin Room Modification");
- }
- cprintf("%d Ok\n", CIT_OK);
-}
-
-
-// Retrieve info file for this room (this ought to be upgraded to handle non-plain-text)
-void cmd_rinf(char *argbuf) {
- struct CtdlMessage *msg = CtdlFetchMessage(CC->room.msgnum_info, 1);
- if (msg != NULL) {
- cprintf("%d Info:\n", LISTING_FOLLOWS);
- CtdlOutputPreLoadedMsg(msg, MT_CITADEL, HEADERS_NONE, 0, 0, 0);
- CM_Free(msg);
- cprintf("000\n");
- }
- else {
- cprintf("%d No info file.\n", ERROR + FILE_NOT_FOUND);
- }
-}
-
-
-// admin command: kill the current room
-void cmd_kill(char *argbuf) {
- char deleted_room_name[ROOMNAMELEN];
- char msg[SIZ];
- int kill_ok;
-
- kill_ok = extract_int(argbuf, 0);
-
- if (CtdlDoIHavePermissionToDeleteThisRoom(&CC->room) == 0) {
- cprintf("%d Can't delete this room.\n", ERROR + NOT_HERE);
- return;
- }
- if (kill_ok) {
- if (CC->room.QRflags & QR_MAILBOX) {
- safestrncpy(deleted_room_name, &CC->room.QRname[11], sizeof deleted_room_name);
- }
- else {
- safestrncpy(deleted_room_name, CC->room.QRname, sizeof deleted_room_name);
- }
-
- /* Do the dirty work */
- CtdlScheduleRoomForDeletion(&CC->room);
-
- /* Return to the Lobby */
- CtdlUserGoto(CtdlGetConfigStr("c_baseroom"), 0, 0, NULL, NULL, NULL, NULL);
-
- /* tell the world what we did */
- snprintf(msg, sizeof msg, "The room \"%s\" has been deleted by %s.\n",
- deleted_room_name,
- (CC->logged_in ? CC->curr_user : "an administrator")
- );
- CtdlAideMessage(msg, "Room Purger Message");
- cprintf("%d '%s' deleted.\n", CIT_OK, deleted_room_name);
- }
- else {
- cprintf("%d ok to delete.\n", CIT_OK);
- }
-}
-
-
-// create a new room
-void cmd_cre8(char *args) {
- int cre8_ok;
- char new_room_name[ROOMNAMELEN];
- int new_room_type;
- char new_room_pass[32];
- int new_room_floor;
- int new_room_view;
- char *notification_message = NULL;
- unsigned newflags;
- struct floor *fl;
- int avoid_access = 0;
-
- cre8_ok = extract_int(args, 0);
- extract_token(new_room_name, args, 1, '|', sizeof new_room_name);
- new_room_name[ROOMNAMELEN - 1] = 0;
- new_room_type = extract_int(args, 2);
- extract_token(new_room_pass, args, 3, '|', sizeof new_room_pass);
- avoid_access = extract_int(args, 5);
- new_room_view = extract_int(args, 6);
- new_room_pass[9] = 0;
- new_room_floor = 0;
-
- if ((IsEmptyStr(new_room_name)) && (cre8_ok == 1)) {
- cprintf("%d Invalid room name.\n", ERROR + ILLEGAL_VALUE);
- return;
- }
-
- if (!strcasecmp(new_room_name, MAILROOM)) {
- cprintf("%d '%s' already exists.\n",
- ERROR + ALREADY_EXISTS, new_room_name);
- return;
- }
-
- if (num_parms(args) >= 5) {
- fl = CtdlGetCachedFloor(extract_int(args, 4));
- if (fl == NULL) {
- cprintf("%d Invalid floor number.\n",
- ERROR + INVALID_FLOOR_OPERATION);
- return;
- }
- else if ((fl->f_flags & F_INUSE) == 0) {
- cprintf("%d Invalid floor number.\n",
- ERROR + INVALID_FLOOR_OPERATION);
- return;
- } else {
- new_room_floor = extract_int(args, 4);
- }
- }
-
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- if (CC->user.axlevel < CtdlGetConfigInt("c_createax") && !CC->internal_pgm) {
- cprintf("%d You need higher access to create rooms.\n",
- ERROR + HIGHER_ACCESS_REQUIRED);
- return;
- }
-
- if ((IsEmptyStr(new_room_name)) && (cre8_ok == 0)) {
- cprintf("%d Ok to create rooms.\n", CIT_OK);
- return;
- }
-
- if ((new_room_type < 0) || (new_room_type > 5)) {
- cprintf("%d Invalid room type.\n", ERROR + ILLEGAL_VALUE);
- return;
- }
-
- if (new_room_type == 5) {
- if (CC->user.axlevel < AxAideU) {
- cprintf("%d Higher access required\n",
- ERROR + HIGHER_ACCESS_REQUIRED);
- return;
- }
- }
-
- /* Check to make sure the requested room name doesn't already exist */
- newflags = CtdlCreateRoom(new_room_name,
- new_room_type, new_room_pass, new_room_floor,
- 0, avoid_access, new_room_view);
- if (newflags == 0) {
- cprintf("%d '%s' already exists.\n",
- ERROR + ALREADY_EXISTS, new_room_name);
- return;
- }
-
- if (cre8_ok == 0) {
- cprintf("%d OK to create '%s'\n", CIT_OK, new_room_name);
- return;
- }
-
- /* If we reach this point, the room needs to be created. */
-
- newflags = CtdlCreateRoom(new_room_name,
- new_room_type, new_room_pass, new_room_floor, 1, 0,
- new_room_view);
-
- /* post a message in Aide> describing the new room */
- notification_message = malloc(1024);
- snprintf(notification_message, 1024,
- "A new room called \"%s\" has been created by %s%s%s%s%s%s\n",
- new_room_name,
- (CC->logged_in ? CC->curr_user : "an administrator"),
- ((newflags & QR_MAILBOX) ? " [personal]" : ""),
- ((newflags & QR_PRIVATE) ? " [private]" : ""),
- ((newflags & QR_GUESSNAME) ? " [hidden]" : ""),
- ((newflags & QR_PASSWORDED) ? " Password: " : ""),
- ((newflags & QR_PASSWORDED) ? new_room_pass : "")
- );
- CtdlAideMessage(notification_message, "Room Creation Message");
- free(notification_message);
-
- cprintf("%d '%s' has been created.\n", CIT_OK, new_room_name);
-}
-
-
-// Upload the room banner text for this room.
-// This should be amended to handle content types other than plain text.
-void cmd_einf(char *ok) { /* enter info file for current room */
- char buf[SIZ];
- unbuffer_output();
-
- if (CtdlAccessCheck(ac_room_aide)) return;
-
- if (atoi(ok) == 0) {
- cprintf("%d Ok.\n", CIT_OK);
- return;
- }
-
- StrBuf *NewBanner = NewStrBufPlain("Content-type: text/plain; charset=UTF-8\nContent-transfer-encoding: 8bit\n\n", -1);
-
- cprintf("%d Transmit new banner in plain text now.\n", SEND_LISTING);
- while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
- StrBufAppendBufPlain(NewBanner, buf, -1, 0);
- StrBufAppendBufPlain(NewBanner, HKEY("\n"), 0);
- }
-
- // We have read the new banner from the user , now save it
- long new_msgnum = quickie_message("Citadel", NULL, NULL, SYSCONFIGROOM, ChrPtr(NewBanner), FMT_RFC822, "Banner submitted with EINF command");
- FreeStrBuf(&NewBanner);
-
- // Update the room record with a pointer to our new banner
- CtdlGetRoomLock(&CC->room, CC->room.QRname);
- long old_msgnum = CC->room.msgnum_info;
- CC->room.msgnum_info = new_msgnum;
- CtdlPutRoomLock(&CC->room);
-
- // Delete the old one
- CtdlDeleteMessages(SYSCONFIGROOM, &old_msgnum, 1, "");
-}
-
-
-// cmd_lflr() - List all known floors
-void cmd_lflr(char *gargs) {
- int a;
- struct floor flbuf;
-
- if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
- cprintf("%d Known floors:\n", LISTING_FOLLOWS);
-
- for (a = 0; a < MAXFLOORS; ++a) {
- CtdlGetFloor(&flbuf, a);
- if (flbuf.f_flags & F_INUSE) {
- cprintf("%d|%s|%d\n", a, flbuf.f_name, flbuf.f_ref_count);
- }
- }
- cprintf("000\n");
-}
-
-
-// create a new floor
-void cmd_cflr(char *argbuf) {
- char new_floor_name[256];
- struct floor flbuf;
- int cflr_ok;
- int free_slot = (-1);
- int a;
-
- extract_token(new_floor_name, argbuf, 0, '|', sizeof new_floor_name);
- cflr_ok = extract_int(argbuf, 1);
-
- if (CtdlAccessCheck(ac_aide)) return;
-
- if (IsEmptyStr(new_floor_name)) {
- cprintf("%d Blank floor name not allowed.\n",
- ERROR + ILLEGAL_VALUE);
- return;
- }
-
- for (a = 0; a < MAXFLOORS; ++a) {
- CtdlGetFloor(&flbuf, a);
-
- /* note any free slots while we're scanning... */
- if (((flbuf.f_flags & F_INUSE) == 0)
- && (free_slot < 0))
- free_slot = a;
-
- /* check to see if it already exists */
- if ((!strcasecmp(flbuf.f_name, new_floor_name))
- && (flbuf.f_flags & F_INUSE)) {
- cprintf("%d Floor '%s' already exists.\n",
- ERROR + ALREADY_EXISTS,
- flbuf.f_name);
- return;
- }
- }
-
- if (free_slot < 0) {
- cprintf("%d There is no space available for a new floor.\n",
- ERROR + INVALID_FLOOR_OPERATION);
- return;
- }
- if (cflr_ok == 0) {
- cprintf("%d ok to create...\n", CIT_OK);
- return;
- }
- lgetfloor(&flbuf, free_slot);
- flbuf.f_flags = F_INUSE;
- flbuf.f_ref_count = 0;
- safestrncpy(flbuf.f_name, new_floor_name, sizeof flbuf.f_name);
- lputfloor(&flbuf, free_slot);
- cprintf("%d %d\n", CIT_OK, free_slot);
-}
-
-
-// delete a floor
-void cmd_kflr(char *argbuf) {
- struct floor flbuf;
- int floor_to_delete;
- int kflr_ok;
- int delete_ok;
-
- floor_to_delete = extract_int(argbuf, 0);
- kflr_ok = extract_int(argbuf, 1);
-
- if (CtdlAccessCheck(ac_aide)) return;
-
- lgetfloor(&flbuf, floor_to_delete);
-
- delete_ok = 1;
- if ((flbuf.f_flags & F_INUSE) == 0) {
- cprintf("%d Floor %d not in use.\n", ERROR + INVALID_FLOOR_OPERATION, floor_to_delete);
- delete_ok = 0;
- } else {
- if (flbuf.f_ref_count != 0) {
- cprintf("%d Cannot delete; floor contains %d rooms.\n",
- ERROR + INVALID_FLOOR_OPERATION,
- flbuf.f_ref_count);
- delete_ok = 0;
- }
- else {
- if (kflr_ok == 1) {
- cprintf("%d Ok\n", CIT_OK);
- }
- else {
- cprintf("%d Ok to delete...\n", CIT_OK);
- }
-
- }
-
- }
-
- if ((delete_ok == 1) && (kflr_ok == 1)) {
- flbuf.f_flags = 0;
- }
- lputfloor(&flbuf, floor_to_delete);
-}
-
-
-// edit a floor
-void cmd_eflr(char *argbuf) {
- struct floor flbuf;
- int floor_num;
- int np;
-
- np = num_parms(argbuf);
- if (np < 1) {
- cprintf("%d Usage error.\n", ERROR + ILLEGAL_VALUE);
- return;
- }
-
- if (CtdlAccessCheck(ac_aide)) return;
-
- floor_num = extract_int(argbuf, 0);
- lgetfloor(&flbuf, floor_num);
- if ((flbuf.f_flags & F_INUSE) == 0) {
- lputfloor(&flbuf, floor_num);
- cprintf("%d Floor %d is not in use.\n", ERROR + INVALID_FLOOR_OPERATION, floor_num);
- return;
- }
- if (np >= 2) {
- extract_token(flbuf.f_name, argbuf, 1, '|', sizeof flbuf.f_name);
- }
- lputfloor(&flbuf, floor_num);
-
- cprintf("%d Ok\n", CIT_OK);
-}
-
-
-// cmd_stat() - return the modification time of the current room (maybe other things in the future)
-void cmd_stat(char *gargs) {
- if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
- CtdlGetRoom(&CC->room, CC->room.QRname);
- cprintf("%d %s|%ld|\n", CIT_OK, CC->room.QRname, CC->room.QRmtime);
-}
-
-
-/*****************************************************************************/
-/* MODULE INITIALIZATION STUFF */
-/*****************************************************************************/
-
-CTDL_MODULE_INIT(rooms)
-{
- if (!threading) {
- CtdlRegisterProtoHook(cmd_lrms, "LRMS", "List rooms");
- CtdlRegisterProtoHook(cmd_lkra, "LKRA", "List all known rooms");
- CtdlRegisterProtoHook(cmd_lkrn, "LKRN", "List known rooms with new messages");
- CtdlRegisterProtoHook(cmd_lkro, "LKRO", "List known rooms without new messages");
- CtdlRegisterProtoHook(cmd_lzrm, "LZRM", "List zapped rooms");
- CtdlRegisterProtoHook(cmd_lprm, "LPRM", "List public rooms");
- CtdlRegisterProtoHook(cmd_goto, "GOTO", "Goto a named room");
- CtdlRegisterProtoHook(cmd_stat, "STAT", "Get mtime of the current room");
- CtdlRegisterProtoHook(cmd_whok, "WHOK", "List users who know this room");
- CtdlRegisterProtoHook(cmd_rdir, "RDIR", "List files in room directory");
- CtdlRegisterProtoHook(cmd_getr, "GETR", "Get room parameters");
- CtdlRegisterProtoHook(cmd_setr, "SETR", "Set room parameters");
- CtdlRegisterProtoHook(cmd_geta, "GETA", "Get the room admin name");
- CtdlRegisterProtoHook(cmd_seta, "SETA", "Set the room admin for this room");
- CtdlRegisterProtoHook(cmd_rinf, "RINF", "Fetch room info file");
- CtdlRegisterProtoHook(cmd_kill, "KILL", "Kill (delete) the current room");
- CtdlRegisterProtoHook(cmd_cre8, "CRE8", "Create a new room");
- CtdlRegisterProtoHook(cmd_einf, "EINF", "Enter info file for the current room");
- CtdlRegisterProtoHook(cmd_lflr, "LFLR", "List all known floors");
- CtdlRegisterProtoHook(cmd_cflr, "CFLR", "Create a new floor");
- CtdlRegisterProtoHook(cmd_kflr, "KFLR", "Kill a floor");
- CtdlRegisterProtoHook(cmd_eflr, "EFLR", "Edit a floor");
- }
- /* return our Subversion id for the Log */
- return "rooms";
-}
+++ /dev/null
-/*
- * Server functions which perform operations on user objects.
- *
- * Copyright (c) 1987-2020 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.
- */
-
-#include <stdio.h>
-#include <libcitadel.h>
-#include "citserver.h"
-#include "svn_revision.h"
-#include "ctdl_module.h"
-#include "config.h"
-
-
-void cmd_noop(char *argbuf)
-{
- cprintf("%d%cok\n", CIT_OK, CtdlCheckExpress() );
-}
-
-
-void cmd_qnop(char *argbuf)
-{
- /* do nothing, this command returns no response */
-}
-
-
-/*
- * Set or unset asynchronous protocol mode
- */
-void cmd_asyn(char *argbuf)
-{
- int new_state;
-
- new_state = extract_int(argbuf, 0);
- if ((new_state == 0) || (new_state == 1)) {
- CC->is_async = new_state;
- }
- cprintf("%d %d\n", CIT_OK, CC->is_async);
-}
-
-
-/*
- * cmd_info() - Identify this server and its capabilities to the client
- */
-void cmd_info(char *cmdbuf) {
- cprintf("%d Server info:\n", LISTING_FOLLOWS);
- cprintf("%d\n", CC->cs_pid);
- cprintf("%s\n", CtdlGetConfigStr("c_nodename"));
- cprintf("%s\n", CtdlGetConfigStr("c_humannode"));
- cprintf("%s\n", CtdlGetConfigStr("c_fqdn"));
- cprintf("%s\n", CITADEL);
- cprintf("%d\n", REV_LEVEL);
- cprintf("%s\n", CtdlGetConfigStr("c_site_location"));
- cprintf("%s\n", CtdlGetConfigStr("c_sysadm"));
- cprintf("%d\n", SERVER_TYPE);
- cprintf("%s\n", CtdlGetConfigStr("c_moreprompt"));
- cprintf("1\n"); /* 1 = yes, this system supports floors */
- cprintf("1\n"); /* 1 = we support the extended paging options */
- cprintf("\n"); /* no longer used */
- cprintf("1\n"); /* 1 = yes, this system supports the QNOP command */
- cprintf("1\n"); /* 1 = yes, this server is LDAP-enabled */
-
- if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_NATIVE) && (CtdlGetConfigInt("c_disable_newu") == 0))
- {
- cprintf("%d\n", CtdlGetConfigInt("c_disable_newu"));
- }
- else {
- cprintf("1\n"); /* "create new user" does not work with non-native auth modes */
- }
-
- cprintf("%s\n", CtdlGetConfigStr("c_default_cal_zone"));
-
- cprintf("0\n"); /* no longer used */
- cprintf("0\n"); /* no longer used */
- cprintf("0\n"); /* no longer used */
- cprintf("0\n"); /* no longer used */
-
- cprintf("%d\n", CtdlGetConfigInt("c_enable_fulltext"));
- cprintf("%s\n", svn_revision());
-
- if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_NATIVE) {
- cprintf("%d\n", openid_level_supported); /* OpenID is enabled when using native auth */
- }
- else {
- cprintf("0\n"); /* OpenID is disabled when using non-native auth */
- }
-
- cprintf("%d\n", CtdlGetConfigInt("c_guest_logins"));
- cprintf("000\n");
-}
-
-
-/*
- * echo
- */
-void cmd_echo(char *etext)
-{
- cprintf("%d %s\n", CIT_OK, etext);
-}
-
-
-/*
- * get the paginator prompt
- */
-void cmd_more(char *argbuf) {
- cprintf("%d %s\n", CIT_OK, CtdlGetConfigStr("c_moreprompt"));
-}
-
-
-/*
- * the client is identifying itself to the server
- */
-void cmd_iden(char *argbuf)
-{
- int dev_code;
- int cli_code;
- int rev_level;
- char desc[128];
- char from_host[128];
-
- if (num_parms(argbuf)<4) {
- cprintf("%d usage error\n", ERROR + ILLEGAL_VALUE);
- return;
- }
-
- dev_code = extract_int(argbuf,0);
- cli_code = extract_int(argbuf,1);
- rev_level = extract_int(argbuf,2);
- extract_token(desc, argbuf, 3, '|', sizeof desc);
-
- safestrncpy(from_host, CtdlGetConfigStr("c_fqdn"), sizeof from_host);
- from_host[sizeof from_host - 1] = 0;
- if (num_parms(argbuf)>=5) extract_token(from_host, argbuf, 4, '|', sizeof from_host);
-
- CC->cs_clientdev = dev_code;
- CC->cs_clienttyp = cli_code;
- CC->cs_clientver = rev_level;
- safestrncpy(CC->cs_clientname, desc, sizeof CC->cs_clientname);
- CC->cs_clientname[31] = 0;
-
- /* For local sockets, allow the client to supply the user's origin address */
- if ((CC->is_local_client) || (!IsEmptyStr(CC->cs_addr) && (!strcmp(CC->cs_addr, "127.0.0.1")) || (!strcmp(CC->cs_addr, "::1")))) {
- safestrncpy(CC->cs_host, from_host, sizeof CC->cs_host);
- CC->cs_host[sizeof CC->cs_host - 1] = 0;
- CC->cs_addr[0] = 0;
- }
-
- syslog(LOG_NOTICE, "session: client %d/%d/%01d.%02d (%s) from %s",
- dev_code,
- cli_code,
- (rev_level / 100),
- (rev_level % 100),
- desc,
- CC->cs_host
- );
- cprintf("%d Ok\n",CIT_OK);
-}
-
-
-/*
- * Terminate another running session
- */
-void cmd_term(char *cmdbuf)
-{
- int session_num = extract_int(cmdbuf, 0);
- int terminated = CtdlTerminateOtherSession(session_num);
-
- if (terminated < 0) {
- cprintf("%d You can't kill your own session.\n", ERROR + ILLEGAL_VALUE);
- return;
- }
-
- if (terminated & TERM_FOUND) {
- if (terminated == TERM_KILLED) {
- cprintf("%d Session terminated.\n", CIT_OK);
- }
- else {
- cprintf("%d You are not allowed to do that.\n", ERROR + HIGHER_ACCESS_REQUIRED);
- }
- }
- else {
- cprintf("%d No such session.\n", ERROR + ILLEGAL_VALUE);
- }
-}
-
-
-void cmd_time(char *argbuf)
-{
- time_t tv;
- struct tm tmp;
-
- tv = time(NULL);
- localtime_r(&tv, &tmp);
-
- /* timezone and daylight global variables are not portable. */
-#ifdef HAVE_STRUCT_TM_TM_GMTOFF
- cprintf("%d %ld|%ld|%d|%ld\n", CIT_OK, (long)tv, tmp.tm_gmtoff, tmp.tm_isdst, server_startup_time);
-#else
- cprintf("%d %ld|%ld|%d|%ld\n", CIT_OK, (long)tv, timezone, tmp.tm_isdst, server_startup_time);
-#endif
-}
-
-
-/*****************************************************************************/
-/* MODULE INITIALIZATION STUFF */
-/*****************************************************************************/
-
-CTDL_MODULE_INIT(serv_session)
-{
- if (!threading) {
- CtdlRegisterProtoHook(cmd_noop, "NOOP", "no operation");
- CtdlRegisterProtoHook(cmd_qnop, "QNOP", "no operation with no response");
- CtdlRegisterProtoHook(cmd_asyn, "ASYN", "enable asynchronous server responses");
- CtdlRegisterProtoHook(cmd_info, "INFO", "fetch server capabilities and configuration");
- CtdlRegisterProtoHook(cmd_echo, "ECHO", "echo text back to the client");
- CtdlRegisterProtoHook(cmd_more, "MORE", "fetch the paginator prompt");
- CtdlRegisterProtoHook(cmd_iden, "IDEN", "identify the client software and location");
- CtdlRegisterProtoHook(cmd_term, "TERM", "terminate another running session");
- CtdlRegisterProtoHook(cmd_time, "TIME", "fetch the date and time from the server");
- }
- /* return our id for the Log */
- return "serv_session";
-}
+++ /dev/null
-// System management commands for Citadel server
-//
-// Copyright (c) 1987-2021 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.
-
-#include <stdio.h>
-#include <libcitadel.h>
-
-#include "serv_extensions.h"
-#include "ctdl_module.h"
-
-
-// Shut down or restart the server
-void cmd_down(char *argbuf) {
- char *Reply ="%d Shutting down server. Goodbye.\n";
-
- if (CtdlAccessCheck(ac_aide)) return;
-
- if (!IsEmptyStr(argbuf)) {
- int state = CIT_OK;
- restart_server = extract_int(argbuf, 0);
-
- if (restart_server > 0) {
- Reply = "%d citserver will now shut down and automatically restart.\n";
- }
- cprintf(Reply, state);
- }
- else {
- cprintf(Reply, CIT_OK + SERVER_SHUTTING_DOWN);
- }
- CC->kill_me = KILLME_SERVER_SHUTTING_DOWN;
- server_shutting_down = 1;
-}
-
-
-// Halt the server without exiting the server process.
-void cmd_halt(char *argbuf) {
- if (CtdlAccessCheck(ac_aide)) return;
-
- cprintf("%d Halting server. Goodbye.\n", CIT_OK);
- server_shutting_down = 1;
- shutdown_and_halt = 1;
-}
-
-
-// Schedule or cancel a server shutdown
-void cmd_scdn(char *argbuf) {
- int new_state;
- int state = CIT_OK;
- char *Reply = "%d %d\n";
-
- if (CtdlAccessCheck(ac_aide)) return;
-
- new_state = extract_int(argbuf, 0);
- if ((new_state == 2) || (new_state == 3)) {
- restart_server = 1;
- restart_server = extract_int(argbuf, 0);
- new_state -= 2;
- }
- if ((new_state == 0) || (new_state == 1)) {
- ScheduledShutdown = new_state;
- }
- cprintf(Reply, state, ScheduledShutdown);
-}
-
-
-// ****************************************************************************
-// * MODULE INITIALIZATION STUFF *
-// ****************************************************************************
-
-CTDL_MODULE_INIT(syscmd)
-{
- if (!threading) {
- CtdlRegisterProtoHook(cmd_down, "DOWN", "perform a server shutdown");
- CtdlRegisterProtoHook(cmd_halt, "HALT", "halt the server without exiting the server process");
- CtdlRegisterProtoHook(cmd_scdn, "SCDN", "schedule or cancel a server shutdown");
- }
- // return our id for the log
- return "syscmd";
-}
+++ /dev/null
-// Server functions which perform operations on user objects.
-//
-// Copyright (c) 1987-2022 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.
-
-
-#include "support.h"
-#include "control.h"
-#include "ctdl_module.h"
-#include "citserver.h"
-#include "config.h"
-#include "user_ops.h"
-#include "internet_addressing.h"
-
-
-// USER command -- attempt to log in as an existing user
-void cmd_user(char *cmdbuf) {
- char username[256];
- int a;
-
- extract_token(username, cmdbuf, 0, '|', sizeof username);
- striplt(username);
- syslog(LOG_DEBUG, "user_ops: cmd_user(%s)", username);
-
- a = CtdlLoginExistingUser(username);
- switch (a) {
- case login_already_logged_in:
- cprintf("%d Already logged in.\n", ERROR + ALREADY_LOGGED_IN);
- return;
- case login_too_many_users:
- cprintf("%d %s: Too many users are already online (maximum is %d)\n",
- ERROR + MAX_SESSIONS_EXCEEDED,
- CtdlGetConfigStr("c_nodename"), CtdlGetConfigInt("c_maxsessions")
- );
- return;
- case login_ok:
- cprintf("%d Password required for %s\n", MORE_DATA, CC->curr_user);
- return;
- case login_not_found:
- cprintf("%d %s not found.\n", ERROR + NO_SUCH_USER, username);
- return;
- default:
- cprintf("%d Internal error\n", ERROR + INTERNAL_ERROR);
- }
-}
-
-
-// PASS command -- complete logging in as an existing user (used after USER returns MORE_DATA)
-void cmd_pass(char *buf) {
- char password[SIZ];
- int a;
- long len;
-
- memset(password, 0, sizeof(password));
- len = extract_token(password, buf, 0, '|', sizeof password);
- a = CtdlTryPassword(password, len);
-
- switch (a) {
- case pass_already_logged_in:
- cprintf("%d Already logged in.\n", ERROR + ALREADY_LOGGED_IN);
- return;
- case pass_no_user:
- cprintf("%d You must send a name with USER first.\n", ERROR + USERNAME_REQUIRED);
- return;
- case pass_wrong_password:
- cprintf("%d Wrong password.\n", ERROR + PASSWORD_REQUIRED);
- return;
- case pass_ok:
- logged_in_response();
- return;
- }
-}
-
-
-// cmd_newu() - create a new user account and log in as that user
-void cmd_newu(char *cmdbuf) {
- int a;
- char username[SIZ];
-
- if (CtdlGetConfigInt("c_auth_mode") != AUTHMODE_NATIVE) {
- cprintf("%d This system does not use native mode authentication.\n",
- ERROR + NOT_HERE);
- return;
- }
-
- if (CtdlGetConfigInt("c_disable_newu")) {
- cprintf("%d Self-service user account creation is disabled on this system.\n", ERROR + NOT_HERE);
- return;
- }
-
- if (CC->logged_in) {
- cprintf("%d Already logged in.\n", ERROR + ALREADY_LOGGED_IN);
- return;
- }
- if (CC->nologin) {
- cprintf("%d %s: Too many users are already online (maximum is %d)\n",
- ERROR + MAX_SESSIONS_EXCEEDED,
- CtdlGetConfigStr("c_nodename"), CtdlGetConfigInt("c_maxsessions"));
- return;
- }
- extract_token(username, cmdbuf, 0, '|', sizeof username);
- strproc(username);
-
- if (IsEmptyStr(username)) {
- cprintf("%d You must supply a user name.\n", ERROR + USERNAME_REQUIRED);
- return;
- }
-
- if ((!strcasecmp(username, "bbs")) ||
- (!strcasecmp(username, "new")) ||
- (!strcasecmp(username, "."))
- ) {
- cprintf("%d '%s' is an invalid login name.\n", ERROR + ILLEGAL_VALUE, username);
- return;
- }
-
- a = create_user(username, CREATE_USER_BECOME_USER, NATIVE_AUTH_UID);
-
- if (a == 0) {
- logged_in_response();
- }
- else if (a == ERROR + ALREADY_EXISTS) {
- cprintf("%d '%s' already exists.\n",
- ERROR + ALREADY_EXISTS, username);
- return;
- }
- else if (a == ERROR + INTERNAL_ERROR) {
- cprintf("%d Internal error - user record disappeared?\n",
- ERROR + INTERNAL_ERROR);
- return;
- }
- else {
- cprintf("%d unknown error\n", ERROR + INTERNAL_ERROR);
- }
-}
-
-
-// set password - citadel protocol implementation
-void cmd_setp(char *new_pw) {
- if (CtdlAccessCheck(ac_logged_in)) {
- return;
- }
- if ( (CC->user.uid != CTDLUID) && (CC->user.uid != (-1)) ) {
- cprintf("%d Not allowed. Use the 'passwd' command.\n", ERROR + NOT_HERE);
- return;
- }
-
- if (!strcasecmp(new_pw, "GENERATE_RANDOM_PASSWORD")) {
- char random_password[17];
- snprintf(random_password, sizeof random_password, "%08lx%08lx", random(), random());
- CtdlSetPassword(random_password);
- cprintf("%d %s\n", CIT_OK, random_password);
- }
- else {
- strproc(new_pw);
- if (IsEmptyStr(new_pw)) {
- cprintf("%d Password unchanged.\n", CIT_OK);
- return;
- }
- CtdlSetPassword(new_pw);
- cprintf("%d Password changed.\n", CIT_OK);
- }
-}
-
-
-// cmd_creu() - administratively create a new user account (do not log in to it)
-void cmd_creu(char *cmdbuf) {
- int a;
- char username[SIZ];
- char password[SIZ];
- struct ctdluser tmp;
-
- if (CtdlAccessCheck(ac_aide)) {
- return;
- }
-
- extract_token(username, cmdbuf, 0, '|', sizeof username);
- strproc(username);
- strproc(password);
- if (IsEmptyStr(username)) {
- cprintf("%d You must supply a user name.\n", ERROR + USERNAME_REQUIRED);
- return;
- }
-
- extract_token(password, cmdbuf, 1, '|', sizeof password);
-
- a = create_user(username, CREATE_USER_DO_NOT_BECOME_USER, NATIVE_AUTH_UID);
-
- if (a == 0) {
- if (!IsEmptyStr(password)) {
- CtdlGetUserLock(&tmp, username);
- safestrncpy(tmp.password, password, sizeof(tmp.password));
- CtdlPutUserLock(&tmp);
- }
- cprintf("%d User '%s' created %s.\n", CIT_OK, username, (!IsEmptyStr(password)) ? "and password set" : "with no password");
- return;
- }
- else if (a == ERROR + ALREADY_EXISTS) {
- cprintf("%d '%s' already exists.\n", ERROR + ALREADY_EXISTS, username);
- return;
- }
- else if ( (CtdlGetConfigInt("c_auth_mode") != AUTHMODE_NATIVE) && (a == ERROR + NO_SUCH_USER) ) {
- cprintf("%d User accounts are not created within Citadel in host authentication mode.\n", ERROR + NO_SUCH_USER);
- return;
- }
- else {
- cprintf("%d An error occurred creating the user account.\n", ERROR + INTERNAL_ERROR);
- }
-}
-
-
-// get user parameters
-void cmd_getu(char *cmdbuf) {
- if (CtdlAccessCheck(ac_logged_in)) {
- return;
- }
-
- CtdlGetUser(&CC->user, CC->curr_user);
- cprintf("%d 80|24|%d|\n", CIT_OK, (CC->user.flags & US_USER_SET));
-}
-
-
-// set user parameters
-void cmd_setu(char *new_parms) {
- if (CtdlAccessCheck(ac_logged_in)) {
- return;
- }
-
- if (num_parms(new_parms) < 3) {
- cprintf("%d Usage error.\n", ERROR + ILLEGAL_VALUE);
- return;
- }
- CtdlLockGetCurrentUser();
- CC->user.flags = CC->user.flags & (~US_USER_SET);
- CC->user.flags = CC->user.flags | (extract_int(new_parms, 2) & US_USER_SET);
- CtdlPutCurrentUserLock();
- cprintf("%d Ok\n", CIT_OK);
-}
-
-
-// set last read pointer (marks all messages in the current room as read, up to the specified point)
-void cmd_slrp(char *new_ptr) {
- long newlr;
- visit vbuf;
- visit original_vbuf;
-
- if (CtdlAccessCheck(ac_logged_in)) {
- return;
- }
-
- if (!strncasecmp(new_ptr, "highest", 7)) {
- newlr = CC->room.QRhighest;
- }
- else {
- newlr = atol(new_ptr);
- }
-
- CtdlLockGetCurrentUser();
-
- CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
- memcpy(&original_vbuf, &vbuf, sizeof(visit));
- vbuf.v_lastseen = newlr;
- snprintf(vbuf.v_seen, sizeof vbuf.v_seen, "*:%ld", newlr);
-
- // Only rewrite the record if it changed
- if ( (vbuf.v_lastseen != original_vbuf.v_lastseen)
- || (strcmp(vbuf.v_seen, original_vbuf.v_seen))
- ) {
- CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
- }
-
- CtdlPutCurrentUserLock();
- cprintf("%d %ld\n", CIT_OK, newlr);
-}
-
-
-void cmd_seen(char *argbuf) {
- long target_msgnum = 0L;
- int target_setting = 0;
-
- if (CtdlAccessCheck(ac_logged_in)) {
- return;
- }
-
- if (num_parms(argbuf) != 2) {
- cprintf("%d Invalid parameters\n", ERROR + ILLEGAL_VALUE);
- return;
- }
-
- target_msgnum = extract_long(argbuf, 0);
- target_setting = extract_int(argbuf, 1);
-
- CtdlSetSeen(&target_msgnum, 1, target_setting,
- ctdlsetseen_seen, NULL, NULL);
- cprintf("%d OK\n", CIT_OK);
-}
-
-
-void cmd_gtsn(char *argbuf) {
- visit vbuf;
-
- if (CtdlAccessCheck(ac_logged_in)) {
- return;
- }
-
- // Learn about the user and room in question
- CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
-
- cprintf("%d ", CIT_OK);
- client_write(vbuf.v_seen, strlen(vbuf.v_seen));
- client_write(HKEY("\n"));
-}
-
-
-// INVT and KICK commands (grant/revoke access to an invitation-only room)
-void cmd_invt_kick(char *iuser, int op) {
-
- // These commands are only allowed by admins, room admins,
- // and room namespace owners
- if (is_room_aide()) {
- // access granted
- }
- else if ( ((atol(CC->room.QRname) == CC->user.usernum) ) && (CC->user.usernum != 0) ) {
- // access granted
- }
- else {
- // access denied
- cprintf("%d Higher access or room ownership required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
- return;
- }
-
- if (!strncasecmp(CC->room.QRname, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)) {
- cprintf("%d Can't add/remove users from this room.\n", ERROR + NOT_HERE);
- return;
- }
-
- if (CtdlInvtKick(iuser, op) != 0) {
- cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
- return;
- }
-
- cprintf("%d %s %s %s.\n",
- CIT_OK, iuser,
- ((op == 1) ? "invited to" : "kicked out of"),
- CC->room.QRname);
- return;
-}
-
-
-void cmd_invt(char *iuser) {
- cmd_invt_kick(iuser, 1);
-}
-
-
-void cmd_kick(char *iuser) {
- cmd_invt_kick(iuser, 0);
-}
-
-
-// forget (Zap) the current room
-void cmd_forg(char *argbuf) {
-
- if (CtdlAccessCheck(ac_logged_in)) {
- return;
- }
-
- if (CtdlForgetThisRoom() == 0) {
- cprintf("%d Ok\n", CIT_OK);
- }
- else {
- cprintf("%d You may not forget this room.\n", ERROR + NOT_HERE);
- }
-}
-
-
-// Get Next Unregistered User
-void cmd_gnur(char *argbuf) {
- struct cdbdata *cdbus;
- struct ctdluser usbuf;
-
- if (CtdlAccessCheck(ac_aide)) {
- return;
- }
-
- if ((CtdlGetConfigInt("MMflags") & MM_VALID) == 0) {
- cprintf("%d There are no unvalidated users.\n", CIT_OK);
- return;
- }
-
- // There are unvalidated users. Traverse the user database, and return the first user we find that needs validation.
- cdb_rewind(CDB_USERS);
- while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL) {
- memset(&usbuf, 0, sizeof(struct ctdluser));
- memcpy(&usbuf, cdbus->ptr, ((cdbus->len > sizeof(struct ctdluser)) ? sizeof(struct ctdluser) : cdbus->len));
- cdb_free(cdbus);
- if ((usbuf.flags & US_NEEDVALID) && (usbuf.axlevel > AxDeleted)) {
- cprintf("%d %s\n", MORE_DATA, usbuf.fullname);
- cdb_close_cursor(CDB_USERS);
- return;
- }
- }
-
- // If we get to this point, there are no more unvalidated users. Therefore we clear the "users need validation" flag.
- begin_critical_section(S_CONTROL);
- int flags;
- flags = CtdlGetConfigInt("MMflags");
- flags = flags & (~MM_VALID);
- CtdlSetConfigInt("MMflags", flags);
- end_critical_section(S_CONTROL);
- cprintf("%d *** End of registration.\n", CIT_OK);
-}
-
-
-// validate a user
-void cmd_vali(char *v_args) {
- char user[128];
- int newax;
- struct ctdluser userbuf;
-
- extract_token(user, v_args, 0, '|', sizeof user);
- newax = extract_int(v_args, 1);
-
- if (CtdlAccessCheck(ac_aide) || (newax > AxAideU) || (newax < AxDeleted)) {
- return;
- }
-
- if (CtdlGetUserLock(&userbuf, user) != 0) {
- cprintf("%d '%s' not found.\n", ERROR + NO_SUCH_USER, user);
- return;
- }
-
- userbuf.axlevel = newax;
- userbuf.flags = (userbuf.flags & ~US_NEEDVALID);
-
- CtdlPutUserLock(&userbuf);
-
- // If the access level was set to zero, delete the user
- if (newax == 0) {
- if (purge_user(user) == 0) {
- cprintf("%d %s Deleted.\n", CIT_OK, userbuf.fullname);
- return;
- }
- }
- cprintf("%d User '%s' validated.\n", CIT_OK, userbuf.fullname);
-}
-
-
-// List one user (this works with cmd_list)
-void ListThisUser(char *username, void *data) {
- char *searchstring;
- struct ctdluser usbuf;
-
- if (CtdlGetUser(&usbuf, username) != 0) {
- return;
- }
-
- searchstring = (char *)data;
- if (bmstrcasestr(usbuf.fullname, searchstring) == NULL) {
- return;
- }
-
- if (usbuf.axlevel > AxDeleted) {
- if ((CC->user.axlevel >= AxAideU)
- || ((usbuf.flags & US_UNLISTED) == 0)
- || ((CC->internal_pgm))) {
- cprintf("%s|%d|%ld|%ld|%ld|%ld||\n",
- usbuf.fullname,
- usbuf.axlevel,
- usbuf.usernum,
- (long)usbuf.lastcall,
- usbuf.timescalled,
- usbuf.posted);
- }
- }
-}
-
-
-// List users (searchstring may be empty to list all users)
-void cmd_list(char *cmdbuf) {
- char searchstring[256];
- extract_token(searchstring, cmdbuf, 0, '|', sizeof searchstring);
- striplt(searchstring);
- cprintf("%d \n", LISTING_FOLLOWS);
- ForEachUser(ListThisUser, (void *)searchstring );
- cprintf("000\n");
-}
-
-
-// assorted info we need to check at login
-void cmd_chek(char *argbuf) {
- int mail = 0;
- int regis = 0;
- int vali = 0;
-
- if (CtdlAccessCheck(ac_logged_in)) {
- return;
- }
-
- CtdlGetUser(&CC->user, CC->curr_user); // no lock is needed here
- if ((REGISCALL != 0) && ((CC->user.flags & US_REGIS) == 0)) {
- regis = 1;
- }
-
- if (CC->user.axlevel >= AxAideU) {
- if (CtdlGetConfigInt("MMflags") & MM_VALID) {
- vali = 1;
- }
- }
-
- mail = InitialMailCheck(); // check for mail
- cprintf("%d %d|%d|%d|%s|\n", CIT_OK, mail, regis, vali, CC->cs_inet_email);
-}
-
-
-// check to see if a user exists
-void cmd_qusr(char *who) {
- struct ctdluser usbuf;
-
- if (CtdlGetUser(&usbuf, who) == 0) {
- cprintf("%d %s\n", CIT_OK, usbuf.fullname);
- }
- else {
- cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
- }
-}
-
-
-// Administrative Get User Parameters
-void cmd_agup(char *cmdbuf) {
- struct ctdluser usbuf;
- char requested_user[128];
-
- if (CtdlAccessCheck(ac_aide)) {
- return;
- }
-
- extract_token(requested_user, cmdbuf, 0, '|', sizeof requested_user);
- if (CtdlGetUser(&usbuf, requested_user) != 0) {
- cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
- return;
- }
- cprintf("%d %s|%s|%u|%ld|%ld|%d|%ld|%ld|%d\n",
- CIT_OK,
- usbuf.fullname,
- usbuf.password,
- usbuf.flags,
- usbuf.timescalled,
- usbuf.posted,
- (int) usbuf.axlevel,
- usbuf.usernum,
- (long)usbuf.lastcall,
- usbuf.USuserpurge);
-}
-
-
-// Administrative Set User Parameters
-void cmd_asup(char *cmdbuf) {
- struct ctdluser usbuf;
- char requested_user[128];
- char notify[SIZ];
- int np;
- int newax;
- int deleted = 0;
-
- if (CtdlAccessCheck(ac_aide))
- return;
-
- extract_token(requested_user, cmdbuf, 0, '|', sizeof requested_user);
- if (CtdlGetUserLock(&usbuf, requested_user) != 0) {
- cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
- return;
- }
- np = num_parms(cmdbuf);
- if (np > 1)
- extract_token(usbuf.password, cmdbuf, 1, '|', sizeof usbuf.password);
- if (np > 2)
- usbuf.flags = extract_int(cmdbuf, 2);
- if (np > 3)
- usbuf.timescalled = extract_int(cmdbuf, 3);
- if (np > 4)
- usbuf.posted = extract_int(cmdbuf, 4);
- if (np > 5) {
- newax = extract_int(cmdbuf, 5);
- if ((newax >= AxDeleted) && (newax <= AxAideU)) {
- usbuf.axlevel = newax;
- }
- }
- if (np > 7) {
- usbuf.lastcall = extract_long(cmdbuf, 7);
- }
- if (np > 8) {
- usbuf.USuserpurge = extract_int(cmdbuf, 8);
- }
- CtdlPutUserLock(&usbuf);
- if (usbuf.axlevel == AxDeleted) {
- if (purge_user(requested_user) == 0) {
- deleted = 1;
- }
- }
-
- if (deleted) {
- snprintf(notify, SIZ,
- "User \"%s\" has been deleted by %s.\n",
- usbuf.fullname, (CC->logged_in ? CC->user.fullname : "an administrator")
- );
- CtdlAideMessage(notify, "User Deletion Message");
- }
-
- cprintf("%d Ok", CIT_OK);
- if (deleted) {
- cprintf(" (%s deleted)", requested_user);
- }
- cprintf("\n");
-}
-
-
-// Citadel protocol command to do the same
-void cmd_isme(char *argbuf) {
- char addr[256];
-
- if (CtdlAccessCheck(ac_logged_in)) return;
- extract_token(addr, argbuf, 0, '|', sizeof addr);
-
- if (CtdlIsMe(addr, sizeof addr)) {
- cprintf("%d %s\n", CIT_OK, addr);
- }
- else {
- cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
- }
-
-}
-
-
-// Retrieve all Internet email addresses/aliases for the specified user
-void cmd_agea(char *cmdbuf) {
- struct ctdluser usbuf;
- char requested_user[128];
- int i, num_e;
- char e[512];
-
- if (CtdlAccessCheck(ac_aide)) {
- return;
- }
-
- extract_token(requested_user, cmdbuf, 0, '|', sizeof requested_user);
- if (CtdlGetUser(&usbuf, requested_user) != 0) {
- cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
- return;
- }
- cprintf("%d internet email addresses for %s\n", LISTING_FOLLOWS, usbuf.fullname);
- num_e = num_tokens(usbuf.emailaddrs, '|');
- for (i=0; i<num_e; ++i) {
- extract_token(e, usbuf.emailaddrs, i, '|', sizeof e);
- cprintf("%s\n", e);
- }
- cprintf("000\n");
-}
-
-
-// Set the Internet email addresses/aliases for the specified user
-void cmd_asea(char *cmdbuf) {
- struct ctdluser usbuf;
- char requested_user[128];
- char buf[SIZ];
- char whodat[64];
- char new_emailaddrs[512] = { 0 } ;
-
- if (CtdlAccessCheck(ac_aide)) return;
-
- extract_token(requested_user, cmdbuf, 0, '|', sizeof requested_user);
- if (CtdlGetUser(&usbuf, requested_user) != 0) {
- cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
- return;
- }
-
- cprintf("%d Ok\n", SEND_LISTING);
- while (client_getln(buf, sizeof buf) >= 0 && strcmp(buf, "000")) {
- if (IsEmptyStr(buf)) {
- syslog(LOG_ERR, "user_ops: address <%s> is empty - not using", buf);
- }
- else if ((strlen(new_emailaddrs) + strlen(buf) + 2) > sizeof(new_emailaddrs)) {
- syslog(LOG_ERR, "user_ops: address <%s> does not fit in buffer - not using", buf);
- }
- else if (!IsDirectory(buf, 0)) {
- syslog(LOG_ERR, "user_ops: address <%s> is not in one of our domains - not using", buf);
- }
- else if ( (CtdlDirectoryLookup(whodat, buf, sizeof whodat) == 0) && (CtdlUserCmp(whodat, requested_user)) ) {
- syslog(LOG_ERR, "user_ops: address <%s> already belongs to <%s> - not using", buf, whodat);
- }
- else {
- syslog(LOG_DEBUG, "user_ops: address <%s> validated", buf);
- if (!IsEmptyStr(new_emailaddrs)) {
- strcat(new_emailaddrs, "|");
- }
- strcat(new_emailaddrs, buf);
- }
- }
-
- CtdlSetEmailAddressesForUser(requested_user, new_emailaddrs);
-}
-
-
-// Set the preferred view for the current user/room combination
-void cmd_view(char *cmdbuf) {
- int requested_view;
- visit vbuf;
-
- if (CtdlAccessCheck(ac_logged_in)) {
- return;
- }
-
- requested_view = extract_int(cmdbuf, 0);
-
- CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
- vbuf.v_view = requested_view;
- CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
-
- cprintf("%d ok\n", CIT_OK);
-}
-
-
-// Rename a user
-void cmd_renu(char *cmdbuf) {
- int retcode;
- char oldname[USERNAME_SIZE];
- char newname[USERNAME_SIZE];
-
- if (CtdlAccessCheck(ac_aide)) {
- return;
- }
-
- extract_token(oldname, cmdbuf, 0, '|', sizeof oldname);
- extract_token(newname, cmdbuf, 1, '|', sizeof newname);
-
- retcode = rename_user(oldname, newname);
- switch(retcode) {
- case RENAMEUSER_OK:
- cprintf("%d '%s' has been renamed to '%s'.\n", CIT_OK, oldname, newname);
- return;
- case RENAMEUSER_LOGGED_IN:
- cprintf("%d '%s' is currently logged in and cannot be renamed.\n",
- ERROR + ALREADY_LOGGED_IN , oldname
- );
- return;
- case RENAMEUSER_NOT_FOUND:
- cprintf("%d '%s' does not exist.\n", ERROR + NO_SUCH_USER, oldname);
- return;
- case RENAMEUSER_ALREADY_EXISTS:
- cprintf("%d A user named '%s' already exists.\n", ERROR + ALREADY_EXISTS, newname);
- return;
- }
-
- cprintf("%d An unknown error occurred.\n", ERROR);
-}
-
-
-void cmd_quit(char *argbuf) {
- cprintf("%d Goodbye.\n", CIT_OK);
- CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
-}
-
-
-void cmd_lout(char *argbuf) {
- if (CC->logged_in)
- CtdlUserLogout();
- cprintf("%d logged out.\n", CIT_OK);
-}
-
-
-/*****************************************************************************/
-/* MODULE INITIALIZATION STUFF */
-/*****************************************************************************/
-
-
-CTDL_MODULE_INIT(serv_user)
-{
- if (!threading) {
- CtdlRegisterProtoHook(cmd_user, "USER", "Submit username for login");
- CtdlRegisterProtoHook(cmd_pass, "PASS", "Complete login by submitting a password");
- CtdlRegisterProtoHook(cmd_quit, "QUIT", "log out and disconnect from server");
- CtdlRegisterProtoHook(cmd_lout, "LOUT", "log out but do not disconnect from server");
- CtdlRegisterProtoHook(cmd_creu, "CREU", "Create User");
- CtdlRegisterProtoHook(cmd_setp, "SETP", "Set the password for an account");
- CtdlRegisterProtoHook(cmd_getu, "GETU", "Get User parameters");
- CtdlRegisterProtoHook(cmd_setu, "SETU", "Set User parameters");
- CtdlRegisterProtoHook(cmd_slrp, "SLRP", "Set Last Read Pointer");
- CtdlRegisterProtoHook(cmd_invt, "INVT", "Invite a user to a room");
- CtdlRegisterProtoHook(cmd_kick, "KICK", "Kick a user out of a room");
- CtdlRegisterProtoHook(cmd_forg, "FORG", "Forget a room");
- CtdlRegisterProtoHook(cmd_gnur, "GNUR", "Get Next Unregistered User");
- CtdlRegisterProtoHook(cmd_vali, "VALI", "Validate new users");
- CtdlRegisterProtoHook(cmd_list, "LIST", "List users");
- CtdlRegisterProtoHook(cmd_chek, "CHEK", "assorted info we need to check at login");
- CtdlRegisterProtoHook(cmd_qusr, "QUSR", "check to see if a user exists");
- CtdlRegisterProtoHook(cmd_agup, "AGUP", "Administratively Get User Parameters");
- CtdlRegisterProtoHook(cmd_asup, "ASUP", "Administratively Set User Parameters");
- CtdlRegisterProtoHook(cmd_agea, "AGEA", "Administratively Get Email Addresses");
- CtdlRegisterProtoHook(cmd_asea, "ASEA", "Administratively Set Email Addresses");
- CtdlRegisterProtoHook(cmd_seen, "SEEN", "Manipulate seen/unread message flags");
- CtdlRegisterProtoHook(cmd_gtsn, "GTSN", "Fetch seen/unread message flags");
- CtdlRegisterProtoHook(cmd_view, "VIEW", "Set preferred view for user/room combination");
- CtdlRegisterProtoHook(cmd_renu, "RENU", "Rename a user");
- CtdlRegisterProtoHook(cmd_newu, "NEWU", "Log in as a new user");
- CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
- }
- /* return our Subversion id for the Log */
- return "user";
-}
+++ /dev/null
-/*
- * 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.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <sys/stat.h>
-#include <string.h>
-
-#include <time.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "database.h"
-#include "config.h"
-#include "room_ops.h"
-#include "sysdep_decls.h"
-#include "support.h"
-#include "msgbase.h"
-#include "citserver.h"
-#include "ctdl_module.h"
-#include "user_ops.h"
-
-/*
- * 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 (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
- */
- if ( (qrbuf->QRflags & QR_MAILBOX) == 0) {
- fl = CtdlGetCachedFloor(qrbuf->QRfloor);
- if (fl->f_ep.expire_mode != 0) {
- memcpy(epbuf, &fl->f_ep, sizeof(struct ExpirePolicy));
- return;
- }
- }
-
- /* (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");
- epbuf->expire_value = CtdlGetConfigInt("c_mbxep_value");
- return;
- }
- }
-
- /* Otherwise, fall back on the system default */
- epbuf->expire_mode = CtdlGetConfigInt("c_ep_mode");
- epbuf->expire_value = CtdlGetConfigInt("c_ep_value");
-}
-
-
-/*
- * Get Policy EXpire
- */
-void cmd_gpex(char *argbuf) {
- struct ExpirePolicy exp;
- struct floor *fl;
- char which[128];
-
- memset(&exp, 0, sizeof(struct ExpirePolicy));
- extract_token(which, argbuf, 0, '|', sizeof which);
- if (!strcasecmp(which, strof(roompolicy)) || !strcasecmp(which, "room")) {
- memcpy(&exp, &CC->room.QRep, sizeof(struct ExpirePolicy));
- }
- else if (!strcasecmp(which, strof(floorpolicy)) || !strcasecmp(which, "floor")) {
- fl = CtdlGetCachedFloor(CC->room.QRfloor);
- memcpy(&exp, &fl->f_ep, sizeof(struct ExpirePolicy));
- }
- else if (!strcasecmp(which, strof(mailboxespolicy)) || !strcasecmp(which, "mailboxes")) {
- exp.expire_mode = CtdlGetConfigInt("c_mbxep_mode");
- exp.expire_value = CtdlGetConfigInt("c_mbxep_value");
- }
- else if (!strcasecmp(which, strof(sitepolicy)) || !strcasecmp(which, "site")) {
- exp.expire_mode = CtdlGetConfigInt("c_ep_mode");
- exp.expire_value = CtdlGetConfigInt("c_ep_value");
- }
- else {
- cprintf("%d Invalid keyword \"%s\"\n", ERROR + ILLEGAL_VALUE, which);
- return;
- }
-
- cprintf("%d %d|%d\n", CIT_OK, exp.expire_mode, exp.expire_value);
-}
-
-
-/*
- * Set Policy EXpire
- */
-void cmd_spex(char *argbuf) {
- struct ExpirePolicy exp;
- struct floor flbuf;
- char which[128];
-
- memset(&exp, 0, sizeof(struct ExpirePolicy));
- extract_token(which, argbuf, 0, '|', sizeof which);
- exp.expire_mode = extract_int(argbuf, 1);
- exp.expire_value = extract_int(argbuf, 2);
-
- if ((exp.expire_mode < 0) || (exp.expire_mode > 3)) {
- cprintf("%d Invalid policy.\n", ERROR + ILLEGAL_VALUE);
- return;
- }
-
- if ((!strcasecmp(which, strof(roompolicy))) || (!strcasecmp(which, "room")))
- {
- if (!is_room_aide()) {
- cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
- return;
- }
- CtdlGetRoomLock(&CC->room, CC->room.QRname);
- memcpy(&CC->room.QRep, &exp, sizeof(struct ExpirePolicy));
- CtdlPutRoomLock(&CC->room);
- cprintf("%d Room expire policy for '%s' has been updated.\n", CIT_OK, CC->room.QRname);
- syslog(LOG_DEBUG, "Room: %s , Policy: %d , Value: %d",
- CC->room.QRname,
- exp.expire_mode,
- exp.expire_value
- );
- return;
- }
-
- if (CC->user.axlevel < AxAideU) {
- cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
- return;
- }
-
- 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);
- cprintf("%d Floor expire policy has been updated.\n", CIT_OK);
- return;
- }
-
- 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")))
- {
- if (exp.expire_mode == EXPIRE_NEXTLEVEL) {
- cprintf("%d Invalid policy (no higher level)\n", ERROR + ILLEGAL_VALUE);
- return;
- }
- CtdlSetConfigInt("c_ep_mode", exp.expire_mode);
- CtdlSetConfigInt("c_ep_value", exp.expire_value);
- cprintf("%d Site expire policy has been updated.\n", CIT_OK);
- return;
- }
-
- cprintf("%d Invalid keyword '%s'\n", ERROR + ILLEGAL_VALUE, which);
-}
+++ /dev/null
-void GetExpirePolicy(struct ExpirePolicy *epbuf, struct ctdlroom *qrbuf);
-void cmd_gpex(char *argbuf);
-void cmd_spex(char *argbuf);
+++ /dev/null
-// This module handles the expiry of old messages and the purging of old users.
-//
-// You might also see this module affectionately referred to as TDAP (The Dreaded Auto-Purger).
-//
-// Copyright (c) 1988-2022 by citadel.org (Art Cancro, Wilifried Goesgens, and others)
-//
-// This program is open source software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License as published
-// by the Free Software Foundation; either version 3 of the License, or
-// (at your option) any later version.
-//
-// 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.
-
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "policy.h"
-#include "database.h"
-#include "msgbase.h"
-#include "user_ops.h"
-#include "control.h"
-#include "threads.h"
-#include "context.h"
-
-#include "ctdl_module.h"
-
-
-struct PurgeList {
- struct PurgeList *next;
- char name[ROOMNAMELEN]; // use the larger of username or roomname
-};
-
-struct VPurgeList {
- struct VPurgeList *next;
- long vp_roomnum;
- long vp_roomgen;
- long vp_usernum;
-};
-
-struct ValidRoom {
- struct ValidRoom *next;
- long vr_roomnum;
- long vr_roomgen;
-};
-
-struct ValidUser {
- struct ValidUser *next;
- long vu_usernum;
-};
-
-struct ctdlroomref {
- struct ctdlroomref *next;
- long msgnum;
-};
-
-struct UPurgeList {
- struct UPurgeList *next;
- char up_key[256];
-};
-
-struct EPurgeList {
- struct EPurgeList *next;
- int ep_keylen;
- char *ep_key;
-};
-
-
-struct PurgeList *UserPurgeList = NULL;
-struct PurgeList *RoomPurgeList = NULL;
-struct ValidRoom *ValidRoomList = NULL;
-struct ValidUser *ValidUserList = NULL;
-int messages_purged;
-int users_not_purged;
-char *users_corrupt_msg = NULL;
-char *users_zero_msg = NULL;
-struct ctdlroomref *rr = NULL;
-int force_purge_now = 0; // set to nonzero to force a run right now
-
-
-// First phase of message purge -- gather the locations of messages which
-// qualify for purging and write them to a temp file.
-void GatherPurgeMessages(struct ctdlroom *qrbuf, void *data) {
- struct ExpirePolicy epbuf;
- long delnum;
- time_t xtime, now;
- struct CtdlMessage *msg = NULL;
- int a;
- struct cdbdata *cdbfr;
- long *msglist = NULL;
- int num_msgs = 0;
- FILE *purgelist;
-
- purgelist = (FILE *)data;
- fprintf(purgelist, "r=%s\n", qrbuf->QRname);
-
- time(&now);
- GetExpirePolicy(&epbuf, qrbuf);
-
- // If the room is set to never expire messages ... do nothing
- if (epbuf.expire_mode == EXPIRE_NEXTLEVEL) return;
- if (epbuf.expire_mode == EXPIRE_MANUAL) return;
-
- // Don't purge messages containing system configuration, dumbass.
- if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
-
- // Ok, we got this far ... now let's see what's in the room.
- cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
-
- if (cdbfr != NULL) {
- msglist = malloc(cdbfr->len);
- memcpy(msglist, cdbfr->ptr, cdbfr->len);
- num_msgs = cdbfr->len / sizeof(long);
- cdb_free(cdbfr);
- }
-
- // Nothing to do if there aren't any messages
- if (num_msgs == 0) {
- if (msglist != NULL) free(msglist);
- return;
- }
-
- // If the room is set to expire by count, do that.
- if (epbuf.expire_mode == EXPIRE_NUMMSGS) {
- if (num_msgs > epbuf.expire_value) {
- for (a=0; a<(num_msgs - epbuf.expire_value); ++a) {
- fprintf(purgelist, "m=%ld\n", msglist[a]);
- ++messages_purged;
- }
- }
- }
-
- // If the room is set to expire by age...
- if (epbuf.expire_mode == EXPIRE_AGE) {
- for (a=0; a<num_msgs; ++a) {
- delnum = msglist[a];
-
- msg = CtdlFetchMessage(delnum, 0); // don't need the body
- if (msg != NULL) {
- xtime = atol(msg->cm_fields[eTimestamp]);
- CM_Free(msg);
- }
- else {
- xtime = 0L;
- }
-
- if ((xtime > 0L) && (now - xtime > (time_t)(epbuf.expire_value * 86400L))) {
- fprintf(purgelist, "m=%ld\n", delnum);
- ++messages_purged;
- }
- }
- }
-
- if (msglist != NULL) free(msglist);
-}
-
-
-// Second phase of message purge -- read list of msgs from temp file and delete them.
-void DoPurgeMessages(FILE *purgelist) {
- char roomname[ROOMNAMELEN];
- long msgnum;
- char buf[SIZ];
-
- rewind(purgelist);
- strcpy(roomname, "nonexistent room ___ ___");
- while (fgets(buf, sizeof buf, purgelist) != NULL) {
- buf[strlen(buf)-1]=0;
- if (!strncasecmp(buf, "r=", 2)) {
- strcpy(roomname, &buf[2]);
- }
- if (!strncasecmp(buf, "m=", 2)) {
- msgnum = atol(&buf[2]);
- if (msgnum > 0L) {
- CtdlDeleteMessages(roomname, &msgnum, 1, "");
- }
- }
- }
-}
-
-
-void PurgeMessages(void) {
- FILE *purgelist;
-
- syslog(LOG_DEBUG, "PurgeMessages() called");
- messages_purged = 0;
-
- purgelist = tmpfile();
- if (purgelist == NULL) {
- syslog(LOG_CRIT, "Can't create purgelist temp file: %s", strerror(errno));
- return;
- }
-
- CtdlForEachRoom(GatherPurgeMessages, (void *)purgelist );
- DoPurgeMessages(purgelist);
- fclose(purgelist);
-}
-
-
-void AddValidUser(char *username, void *data) {
- struct ValidUser *vuptr;
- struct ctdluser usbuf;
-
- if (CtdlGetUser(&usbuf, username) != 0) {
- return;
- }
-
- vuptr = (struct ValidUser *)malloc(sizeof(struct ValidUser));
- vuptr->next = ValidUserList;
- vuptr->vu_usernum = usbuf.usernum;
- ValidUserList = vuptr;
-}
-
-
-void AddValidRoom(struct ctdlroom *qrbuf, void *data) {
- struct ValidRoom *vrptr;
-
- vrptr = (struct ValidRoom *)malloc(sizeof(struct ValidRoom));
- vrptr->next = ValidRoomList;
- vrptr->vr_roomnum = qrbuf->QRnumber;
- vrptr->vr_roomgen = qrbuf->QRgen;
- ValidRoomList = vrptr;
-}
-
-
-void DoPurgeRooms(struct ctdlroom *qrbuf, void *data) {
- time_t age, purge_secs;
- struct PurgeList *pptr;
- struct ValidUser *vuptr;
- int do_purge = 0;
-
- // For mailbox rooms, there's only one purging rule: if the user who
- // owns the room still exists, we keep the room; otherwise, we purge
- // it. Bypass any other rules.
- if (qrbuf->QRflags & QR_MAILBOX) {
- // if user not found, do_purge will be 1
- do_purge = 1;
- for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
- if (vuptr->vu_usernum == atol(qrbuf->QRname)) {
- do_purge = 0;
- }
- }
- }
- else {
- // Any of these attributes render a room non-purgable
- if (qrbuf->QRflags & QR_PERMANENT) return;
- if (qrbuf->QRflags & QR_DIRECTORY) return;
- if (qrbuf->QRflags & QR_NETWORK) return;
- if (qrbuf->QRflags2 & QR2_SYSTEM) return;
- if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
- if (CtdlIsNonEditable(qrbuf)) return;
-
- // If we don't know the modification date, be safe and don't purge
- if (qrbuf->QRmtime <= (time_t)0) return;
-
- // If no room purge time is set, be safe and don't purge
- if (CtdlGetConfigLong("c_roompurge") < 0) return;
-
- // Otherwise, check the date of last modification
- age = time(NULL) - (qrbuf->QRmtime);
- purge_secs = CtdlGetConfigLong("c_roompurge") * 86400;
- if (purge_secs <= (time_t)0) return;
- syslog(LOG_DEBUG, "<%s> is <%ld> seconds old", qrbuf->QRname, (long)age);
- if (age > purge_secs) do_purge = 1;
- } // !QR_MAILBOX
-
- if (do_purge) {
- pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
- pptr->next = RoomPurgeList;
- strcpy(pptr->name, qrbuf->QRname);
- RoomPurgeList = pptr;
- }
-
-}
-
-
-int PurgeRooms(void) {
- struct PurgeList *pptr;
- int num_rooms_purged = 0;
- struct ctdlroom qrbuf;
- struct ValidUser *vuptr;
- char *transcript = NULL;
-
- syslog(LOG_DEBUG, "PurgeRooms() called");
-
- // Load up a table full of valid user numbers so we can delete
- // user-owned rooms for users who no longer exist
- ForEachUser(AddValidUser, NULL);
-
- // Then cycle through the room file
- CtdlForEachRoom(DoPurgeRooms, NULL);
-
- // Free the valid user list
- while (ValidUserList != NULL) {
- vuptr = ValidUserList->next;
- free(ValidUserList);
- ValidUserList = vuptr;
- }
-
- transcript = malloc(SIZ);
- strcpy(transcript, "The following rooms have been auto-purged:\n");
-
- while (RoomPurgeList != NULL) {
- if (CtdlGetRoom(&qrbuf, RoomPurgeList->name) == 0) {
- transcript=realloc(transcript, strlen(transcript)+SIZ);
- snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
- qrbuf.QRname);
- CtdlDeleteRoom(&qrbuf);
- }
- pptr = RoomPurgeList->next;
- free(RoomPurgeList);
- RoomPurgeList = pptr;
- ++num_rooms_purged;
- }
-
- if (num_rooms_purged > 0) CtdlAideMessage(transcript, "Room Autopurger Message");
- free(transcript);
-
- syslog(LOG_DEBUG, "Purged %d rooms.", num_rooms_purged);
- return(num_rooms_purged);
-}
-
-
-// Back end function to check user accounts for expiration.
-void do_user_purge(char *username, void *data) {
- int purge;
- time_t now;
- time_t purge_time;
- struct PurgeList *pptr;
- struct ctdluser us;
-
- if (CtdlGetUser(&us, username) != 0) {
- return;
- }
-
- // Set purge time; if the user overrides the system default, use it
- if (us.USuserpurge > 0) {
- purge_time = ((time_t)us.USuserpurge) * 86400;
- }
- else {
- purge_time = CtdlGetConfigLong("c_userpurge") * 86400;
- }
-
- // The default rule is to not purge.
- purge = 0;
-
- // If the user has not logged in for the configured amount of time, expire the account.
- if (CtdlGetConfigLong("c_userpurge") > 0) {
- now = time(NULL);
- if ((now - us.lastcall) > purge_time) purge = 1;
- }
-
- // If the account is marked as permanent, don't purge it.
- if (us.flags & US_PERM) purge = 0;
-
- // If the account is an administrator, don't purge it.
- if (us.axlevel == 6) purge = 0;
-
- // If the access level is 0, the record should already have been
- // deleted, but maybe the user was logged in at the time or something.
- // Delete the record now.
- if (us.axlevel == 0) purge = 1;
-
- // If the user set his/her password to 'deleteme', he/she
- // wishes to be deleted, so purge the record.
- // Moved this lower down so that aides and permanent users get purged if they ask to be.
- if (!strcasecmp(us.password, "deleteme")) purge = 1;
-
- // Fewer than zero calls is impossible, indicating a corrupted record.
- if (us.timescalled < 0) purge = 1;
-
- // any negative user number, is also impossible.
- if (us.usernum < 0L) purge = 1;
-
- // Don't purge user 0. That user is there for the system
- if (us.usernum == 0L) {
- // FIXME: Temporary log message. Until we do unauth access with user 0 we should
- // try to get rid of all user 0 occurences. Many will be remnants from old code so
- // we will need to try and purge them from users data bases.Some will not have names but
- // those with names should be purged.
- syslog(LOG_DEBUG, "Auto purger found a user 0 with name <%s>", us.fullname);
- // purge = 0;
- }
-
- // If the user has no full name entry then we can't purge them since the actual purge can't find them.
- // This shouldn't happen but does somehow.
- if (IsEmptyStr(us.fullname)) {
- purge = 0;
-
- if (us.usernum > 0L) {
- purge=0;
- if (users_corrupt_msg == NULL) {
- users_corrupt_msg = malloc(SIZ);
- strcpy(users_corrupt_msg,
- "The auto-purger found the following user numbers with no name.\n"
- "The system has no way to purge a user with no name,"
- " and should not be able to create them either.\n"
- "This indicates corruption of the user DB or possibly a bug.\n"
- "It may be a good idea to restore your DB from a backup.\n"
- );
- }
-
- users_corrupt_msg=realloc(users_corrupt_msg, strlen(users_corrupt_msg)+30);
- snprintf(&users_corrupt_msg[strlen(users_corrupt_msg)], 29, " %ld\n", us.usernum);
- }
- }
-
- if (purge == 1) {
- pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
- pptr->next = UserPurgeList;
- strcpy(pptr->name, us.fullname);
- UserPurgeList = pptr;
- }
- else {
- ++users_not_purged;
- }
-
-}
-
-
-int PurgeUsers(void) {
- struct PurgeList *pptr;
- int num_users_purged = 0;
- char *transcript = NULL;
-
- syslog(LOG_DEBUG, "PurgeUsers() called");
- users_not_purged = 0;
-
- switch(CtdlGetConfigInt("c_auth_mode")) {
- case AUTHMODE_NATIVE:
- ForEachUser(do_user_purge, NULL);
- break;
- default:
- syslog(LOG_DEBUG, "User purge for auth mode %d is not implemented.", CtdlGetConfigInt("c_auth_mode"));
- break;
- }
-
- transcript = malloc(SIZ);
-
- if (users_not_purged == 0) {
- strcpy(transcript, "The auto-purger was told to purge every user. It is\n"
- "refusing to do this because it usually indicates a problem\n"
- "such as an inability to communicate with a name service.\n"
- );
- while (UserPurgeList != NULL) {
- pptr = UserPurgeList->next;
- free(UserPurgeList);
- UserPurgeList = pptr;
- ++num_users_purged;
- }
- }
-
- else {
- strcpy(transcript, "The following users have been auto-purged:\n");
- while (UserPurgeList != NULL) {
- transcript=realloc(transcript, strlen(transcript)+SIZ);
- snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
- UserPurgeList->name);
- purge_user(UserPurgeList->name);
- pptr = UserPurgeList->next;
- free(UserPurgeList);
- UserPurgeList = pptr;
- ++num_users_purged;
- }
- }
-
- if (num_users_purged > 0) CtdlAideMessage(transcript, "User Purge Message");
- free(transcript);
-
- if (users_corrupt_msg) {
- CtdlAideMessage(users_corrupt_msg, "User Corruption Message");
- free (users_corrupt_msg);
- users_corrupt_msg = NULL;
- }
-
- if(users_zero_msg) {
- CtdlAideMessage(users_zero_msg, "User Zero Message");
- free (users_zero_msg);
- users_zero_msg = NULL;
- }
-
- syslog(LOG_DEBUG, "Purged %d users.", num_users_purged);
- return(num_users_purged);
-}
-
-
-// Purge visits
-//
-// This is a really cumbersome "garbage collection" function. We have to
-// delete visits which refer to rooms and/or users which no longer exist. In
-// order to prevent endless traversals of the room and user files, we first
-// build linked lists of rooms and users which _do_ exist on the system, then
-// traverse the visit file, checking each record against those two lists and
-// purging the ones that do not have a match on _both_ lists. (Remember, if
-// either the room or user being referred to is no longer on the system, the
-// record is completely useless.)
-//
-int PurgeVisits(void) {
- struct cdbdata *cdbvisit;
- visit vbuf;
- struct VPurgeList *VisitPurgeList = NULL;
- struct VPurgeList *vptr;
- int purged = 0;
- char IndexBuf[32];
- int IndexLen;
- struct ValidRoom *vrptr;
- struct ValidUser *vuptr;
- int RoomIsValid, UserIsValid;
-
- // First, load up a table full of valid room/gen combinations
- CtdlForEachRoom(AddValidRoom, NULL);
-
- // Then load up a table full of valid user numbers
- ForEachUser(AddValidUser, NULL);
-
- // Now traverse through the visits, purging irrelevant records...
- cdb_rewind(CDB_VISIT);
- while(cdbvisit = cdb_next_item(CDB_VISIT), cdbvisit != NULL) {
- memset(&vbuf, 0, sizeof(visit));
- memcpy(&vbuf, cdbvisit->ptr,
- ( (cdbvisit->len > sizeof(visit)) ?
- sizeof(visit) : cdbvisit->len) );
- cdb_free(cdbvisit);
-
- RoomIsValid = 0;
- UserIsValid = 0;
-
- // Check to see if the room exists
- for (vrptr=ValidRoomList; vrptr!=NULL; vrptr=vrptr->next) {
- if ( (vrptr->vr_roomnum==vbuf.v_roomnum)
- && (vrptr->vr_roomgen==vbuf.v_roomgen))
- RoomIsValid = 1;
- }
-
- // Check to see if the user exists
- for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
- if (vuptr->vu_usernum == vbuf.v_usernum)
- UserIsValid = 1;
- }
-
- // Put the record on the purge list if it's dead
- if ((RoomIsValid==0) || (UserIsValid==0)) {
- vptr = (struct VPurgeList *)
- malloc(sizeof(struct VPurgeList));
- vptr->next = VisitPurgeList;
- vptr->vp_roomnum = vbuf.v_roomnum;
- vptr->vp_roomgen = vbuf.v_roomgen;
- vptr->vp_usernum = vbuf.v_usernum;
- VisitPurgeList = vptr;
- }
-
- }
-
- // Free the valid room/gen combination list
- while (ValidRoomList != NULL) {
- vrptr = ValidRoomList->next;
- free(ValidRoomList);
- ValidRoomList = vrptr;
- }
-
- // Free the valid user list
- while (ValidUserList != NULL) {
- vuptr = ValidUserList->next;
- free(ValidUserList);
- ValidUserList = vuptr;
- }
-
- // Now delete every visit on the purged list
- while (VisitPurgeList != NULL) {
- IndexLen = GenerateRelationshipIndex(IndexBuf,
- VisitPurgeList->vp_roomnum,
- VisitPurgeList->vp_roomgen,
- VisitPurgeList->vp_usernum);
- cdb_delete(CDB_VISIT, IndexBuf, IndexLen);
- vptr = VisitPurgeList->next;
- free(VisitPurgeList);
- VisitPurgeList = vptr;
- ++purged;
- }
-
- return(purged);
-}
-
-
-// Purge the use table of old entries.
-int PurgeUseTable(StrBuf *ErrMsg) {
- int purged = 0;
- struct cdbdata *cdbut;
- struct UseTable ut;
- struct UPurgeList *ul = NULL;
- struct UPurgeList *uptr;
-
- // Phase 1: traverse through the table, discovering old records...
-
- syslog(LOG_DEBUG, "Purge use table: phase 1");
- cdb_rewind(CDB_USETABLE);
- while(cdbut = cdb_next_item(CDB_USETABLE), cdbut != NULL) {
- if (cdbut->len > sizeof(struct UseTable))
- memcpy(&ut, cdbut->ptr, sizeof(struct UseTable));
- else {
- memset(&ut, 0, sizeof(struct UseTable));
- memcpy(&ut, cdbut->ptr, cdbut->len);
- }
- cdb_free(cdbut);
-
- if ( (time(NULL) - ut.ut_timestamp) > USETABLE_RETAIN ) {
- uptr = (struct UPurgeList *) malloc(sizeof(struct UPurgeList));
- if (uptr != NULL) {
- uptr->next = ul;
- safestrncpy(uptr->up_key, ut.ut_msgid, sizeof uptr->up_key);
- ul = uptr;
- }
- ++purged;
- }
-
- }
-
- // Phase 2: delete the records
- syslog(LOG_DEBUG, "Purge use table: phase 2");
- while (ul != NULL) {
- cdb_delete(CDB_USETABLE, ul->up_key, strlen(ul->up_key));
- uptr = ul->next;
- free(ul);
- ul = uptr;
- }
-
- syslog(LOG_DEBUG, "Purge use table: finished (purged %d records)", purged);
- return(purged);
-}
-
-
-// Purge the EUID Index of old records.
-int PurgeEuidIndexTable(void) {
- int purged = 0;
- struct cdbdata *cdbei;
- struct EPurgeList *el = NULL;
- struct EPurgeList *eptr;
- long msgnum;
- struct CtdlMessage *msg = NULL;
-
- // Phase 1: traverse through the table, discovering old records...
- syslog(LOG_DEBUG, "Purge EUID index: phase 1");
- cdb_rewind(CDB_EUIDINDEX);
- while(cdbei = cdb_next_item(CDB_EUIDINDEX), cdbei != NULL) {
-
- memcpy(&msgnum, cdbei->ptr, sizeof(long));
-
- msg = CtdlFetchMessage(msgnum, 0);
- if (msg != NULL) {
- CM_Free(msg); // it still exists, so do nothing
- }
- else {
- eptr = (struct EPurgeList *) malloc(sizeof(struct EPurgeList));
- if (eptr != NULL) {
- eptr->next = el;
- eptr->ep_keylen = cdbei->len - sizeof(long);
- eptr->ep_key = malloc(cdbei->len);
- memcpy(eptr->ep_key, &cdbei->ptr[sizeof(long)], eptr->ep_keylen);
- el = eptr;
- }
- ++purged;
- }
-
- cdb_free(cdbei);
-
- }
-
- // Phase 2: delete the records
- syslog(LOG_DEBUG, "Purge euid index: phase 2");
- while (el != NULL) {
- cdb_delete(CDB_EUIDINDEX, el->ep_key, el->ep_keylen);
- free(el->ep_key);
- eptr = el->next;
- free(el);
- el = eptr;
- }
-
- syslog(LOG_DEBUG, "Purge euid index: finished (purged %d records)", purged);
- return(purged);
-}
-
-
-// Purge external auth assocations for missing users (theoretically this will never delete anything)
-int PurgeStaleExtAuthAssociations(void) {
- struct cdbdata *cdboi;
- struct ctdluser usbuf;
- HashList *keys = NULL;
- HashPos *HashPos;
- char *deleteme = NULL;
- long len;
- void *Value;
- const char *Key;
- int num_deleted = 0;
- long usernum = 0L;
-
- keys = NewHash(1, NULL);
- if (!keys) return(0);
-
- cdb_rewind(CDB_EXTAUTH);
- while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
- if (cdboi->len > sizeof(long)) {
- memcpy(&usernum, cdboi->ptr, sizeof(long));
- if (CtdlGetUserByNumber(&usbuf, usernum) != 0) {
- deleteme = strdup(cdboi->ptr + sizeof(long)),
- Put(keys, deleteme, strlen(deleteme), deleteme, NULL);
- }
- }
- cdb_free(cdboi);
- }
-
- // Go through the hash list, deleting keys we stored in it
-
- HashPos = GetNewHashPos(keys, 0);
- while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0) {
- syslog(LOG_DEBUG, "Deleting associated external authenticator <%s>", (char*)Value);
- cdb_delete(CDB_EXTAUTH, Value, strlen(Value));
- // note: don't free(Value) -- deleting the hash list will handle this for us
- ++num_deleted;
- }
- DeleteHashPos(&HashPos);
- DeleteHash(&keys);
- return num_deleted;
-}
-
-
-void purge_databases(void) {
- int retval;
- static time_t last_purge = 0;
- time_t now;
- struct tm tm;
-
- // Do the auto-purge if the current hour equals the purge hour,
- // but not if the operation has already been performed in the
- // last twelve hours. This is usually enough granularity.
- now = time(NULL);
- localtime_r(&now, &tm);
- if (((tm.tm_hour != CtdlGetConfigInt("c_purge_hour")) || ((now - last_purge) < 43200)) && (force_purge_now == 0)) {
- return;
- }
-
- syslog(LOG_INFO, "Auto-purger: starting.");
-
- if (!server_shutting_down) {
- retval = PurgeUsers();
- syslog(LOG_NOTICE, "Purged %d users.", retval);
- }
-
- if (!server_shutting_down) {
- PurgeMessages();
- syslog(LOG_NOTICE, "Expired %d messages.", messages_purged);
- }
-
- if (!server_shutting_down) {
- retval = PurgeRooms();
- syslog(LOG_NOTICE, "Expired %d rooms.", retval);
- }
-
- if (!server_shutting_down) {
- retval = PurgeVisits();
- syslog(LOG_NOTICE, "Purged %d visits.", retval);
- }
-
- if (!server_shutting_down) {
- StrBuf *ErrMsg;
- ErrMsg = NewStrBuf();
- retval = PurgeUseTable(ErrMsg);
- syslog(LOG_NOTICE, "Purged %d entries from the use table.", retval);
- FreeStrBuf(&ErrMsg);
- }
-
- if (!server_shutting_down) {
- retval = PurgeEuidIndexTable();
- syslog(LOG_NOTICE, "Purged %d entries from the EUID index.", retval);
- }
-
- if (!server_shutting_down) {
- retval = PurgeStaleExtAuthAssociations();
- syslog(LOG_NOTICE, "Purged %d stale external auth associations.", retval);
- }
-
- //if (!server_shutting_down) {
- // FIXME this is where we could do a non-interactive delete of zero-refcount messages
- //}
-
- if ( (!server_shutting_down) && (CtdlGetConfigInt("c_shrink_db_files") != 0) ) {
- cdb_compact(); // Shrink the DB files on disk
- }
-
- if (!server_shutting_down) {
- syslog(LOG_INFO, "Auto-purger: finished.");
- last_purge = now; // So we don't do it again soon
- force_purge_now = 0;
- }
- else {
- syslog(LOG_INFO, "Auto-purger: STOPPED.");
- }
-}
-
-
-// Manually initiate a run of The Dreaded Auto-Purger (tm)
-void cmd_tdap(char *argbuf) {
- if (CtdlAccessCheck(ac_aide)) return;
- force_purge_now = 1;
- cprintf("%d Manually initiating a purger run now.\n", CIT_OK);
-}
-
-
-CTDL_MODULE_INIT(expire)
-{
- if (!threading)
- {
- CtdlRegisterProtoHook(cmd_tdap, "TDAP", "Manually initiate auto-purger");
- CtdlRegisterProtoHook(cmd_gpex, "GPEX", "Get expire policy");
- CtdlRegisterProtoHook(cmd_spex, "SPEX", "Set expire policy");
- CtdlRegisterSessionHook(purge_databases, EVT_TIMER, PRIO_CLEANUP + 20);
- }
-
- // return our module name for the log
- return "expire";
-}
+++ /dev/null
-/****************************************************************************
-
- Filename: crc16.c
- Description: Cyclic Redundancy Check 16 functions
- Created: 24-Feb-1999
-
- Copyright (c) 1999-2003, Indigo Systems Corporation
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
-
- Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
-
- Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-
- Neither the name of the Indigo Systems Corporation nor the names of its
- contributors may be used to endorse or promote products derived from this
- software without specific prior written permission.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- THE POSSIBILITY OF SUCH DAMAGE.
-
-****************************************************************************/
-
-#include "crc16.h"
-
-#ifdef _OPT_SIZE
-
-
-/*
- * ===== ByteCRC16 =====
- * Calculate (update) the CRC16 for a single 8-bit byte
- */
-int ByteCRC16(int value, int crcin)
-{
- int k = (((crcin >> 8) ^ value) & 255) << 8;
- int crc = 0;
- int bits = 8;
- do
- {
- if (( crc ^ k ) & 0x8000)
- crc = (crc << 1) ^ 0x1021;
- else
- crc <<= 1;
- k <<= 1;
- }
- while (--bits);
- return ((crcin << 8) ^ crc);
-}
-
-#else
-
-const CRC16 ccitt_16Table[] = {
- 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
- 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
- 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
- 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
- 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
- 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
- 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
- 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
- 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
- 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
- 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
- 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
- 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
- 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
- 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
- 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
- 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
- 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
- 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
- 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
- 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
- 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
- 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
- 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
- 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
- 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
- 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
- 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
- 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
- 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
- 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
- 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
-};
-
-#define ByteCRC16(v, crc) \
- (unsigned short)((crc << 8) ^ ccitt_16Table[((crc >> 8) ^ (v)) & 255])
-
-/*
- * ===== CalcCRC16Words =====
- * Calculate the CRC for a buffer of 16-bit words. Supports both
- * Little and Big Endian formats using conditional compilation.
- * Note: minimum count is 1 (0 case not handled)
- */
-CRC16 CalcCRC16Words(unsigned int count, short *buffer) {
-
- int crc = 0;
-
- do {
-
- int value = *buffer++;
-#ifdef _BIG_ENDIAN
- crc = ByteCRC16(value >> 8, crc);
- crc = ByteCRC16(value, crc);
-#else
- crc = ByteCRC16(value, crc);
- crc = ByteCRC16(value >> 8, crc);
-#endif
- }
- while (--count);
- return (CRC16) crc;
-}
-
-#endif /* _OPT_SIZE */
-
-#ifdef _CRC16_BYTES
-
-/*
- * ===== CalcCRC16Bytes =====
- * Calculate the CRC for a buffer of 8-bit words.
- * Note: minimum count is 1 (0 case not handled)
- */
-CRC16 CalcCRC16Bytes(unsigned int count, char *buffer) {
-
- int crc = 0;
-
- do {
-
- int value = *buffer++;
- crc = ByteCRC16(value, crc);
- }
- while (--count);
- return crc;
-}
-
-#endif /* _CRC16_BYTES */
-
-
+++ /dev/null
-/****************************************************************************
-
- Filename: crc16.h
- Description: Cyclic Redundancy Check 16 functions
- Created: 24-Feb-1999
-
- Copyright (c) 2002-2003, Indigo Systems Corporation
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
-
- Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
-
- Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-
- Neither the name of the Indigo Systems Corporation nor the names of its
- contributors may be used to endorse or promote products derived from this
- software without specific prior written permission.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- THE POSSIBILITY OF SUCH DAMAGE.
-
-****************************************************************************/
-
-#define _CRC16_BYTES 1 /* ig */
-
-#ifndef __CRC16_H__
-#define __CRC16_H__
-
-#ifdef __cplusplus
-extern "C"
-{
-#endif
-
-typedef unsigned short CRC16;
-
-#ifdef _OPT_SIZE
- int ByteCRC16(int value, int crcin);
-#else
- CRC16 CalcCRC16Words(unsigned int count, short *buffer);
-#endif
-
-#ifdef _CRC16_BYTES
- CRC16 CalcCRC16Bytes(unsigned int count, char *buffer);
-#endif
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* __CRC16_H__ */
-
-
-
+++ /dev/null
-/*
- * Default wordbreaker module for full text indexing.
- *
- * Copyright (c) 2005-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.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "sysdep_decls.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "database.h"
-#include "msgbase.h"
-#include "control.h"
-#include "ft_wordbreaker.h"
-#include "crc16.h"
-#include "ctdl_module.h"
-
-/*
- * Noise words are not included in search indices.
- * NOTE: if the noise word list is altered in any way, the FT_WORDBREAKER_ID
- * must also be changed, so that the index is rebuilt.
- */
-static char *noise_words[] = {
- "about",
- "after",
- "also",
- "another",
- "because",
- "been",
- "before",
- "being",
- "between",
- "both",
- "came",
- "come",
- "could",
- "each",
- "from",
- "have",
- "here",
- "himself",
- "into",
- "like",
- "make",
- "many",
- "might",
- "more",
- "most",
- "much",
- "must",
- "never",
- "only",
- "other",
- "over",
- "said",
- "same",
- "should",
- "since",
- "some",
- "still",
- "such",
- "take",
- "than",
- "that",
- "their",
- "them",
- "then",
- "there",
- "these",
- "they",
- "this",
- "those",
- "through",
- "under",
- "very",
- "well",
- "were",
- "what",
- "where",
- "which",
- "while",
- "with",
- "would",
- "your"
-};
-#define NUM_NOISE (sizeof(noise_words) / sizeof(char *))
-
-
-/*
- * Compare function
- */
-int intcmp(const void *rec1, const void *rec2) {
- int i1, i2;
-
- i1 = *(const int *)rec1;
- i2 = *(const int *)rec2;
-
- if (i1 > i2) return(1);
- if (i1 < i2) return(-1);
- return(0);
-}
-
-
-void wordbreaker(const char *text, int *num_tokens, int **tokens) {
-
- int wb_num_tokens = 0;
- int wb_num_alloc = 0;
- int *wb_tokens = NULL;
-
- const char *ptr;
- const char *word_start;
- const char *word_end;
- char ch;
- int word_len;
- char word[256];
- int i;
- int word_crc;
-
- if (text == NULL) { /* no NULL text please */
- *num_tokens = 0;
- *tokens = NULL;
- return;
- }
-
- if (text[0] == 0) { /* no empty text either */
- *num_tokens = 0;
- *tokens = NULL;
- return;
- }
-
- ptr = text;
- word_start = NULL;
- while (*ptr) {
- ch = *ptr;
- if (isalnum(ch)) {
- if (!word_start) {
- word_start = ptr;
- }
- }
- ++ptr;
- ch = *ptr;
- if ( (!isalnum(ch)) && (word_start) ) {
- word_end = ptr;
-
- /* extract the word */
- word_len = word_end - word_start;
- if (word_len >= sizeof word) {
- syslog(LOG_DEBUG, "wordbreaker: invalid word length: %d", word_len);
- safestrncpy(word, word_start, sizeof word);
- word[(sizeof word) - 1] = 0;
- }
- else {
- safestrncpy(word, word_start, word_len+1);
- word[word_len] = 0;
- }
- word_start = NULL;
-
- /* are we ok with the length? */
- if ( (word_len >= WB_MIN) && (word_len <= WB_MAX) ) {
- for (i=0; i<word_len; ++i) {
- word[i] = tolower(word[i]);
- }
- /* disqualify noise words */
- for (i=0; i<NUM_NOISE; ++i) {
- if (!strcmp(word, noise_words[i])) {
- word_len = 0;
- break;
- }
- }
-
- if (word_len == 0)
- continue;
-
- word_crc = (int) CalcCRC16Bytes(word_len, word);
-
- ++wb_num_tokens;
- if (wb_num_tokens > wb_num_alloc) {
- wb_num_alloc += 512;
- wb_tokens = realloc(wb_tokens, (sizeof(int) * wb_num_alloc));
- }
- wb_tokens[wb_num_tokens - 1] = word_crc;
- }
- }
- }
-
- /* sort and purge dups */
- if (wb_num_tokens > 1) {
- qsort(wb_tokens, wb_num_tokens, sizeof(int), intcmp);
- for (i=0; i<(wb_num_tokens-1); ++i) {
- if (wb_tokens[i] == wb_tokens[i+1]) {
- memmove(&wb_tokens[i], &wb_tokens[i+1],
- ((wb_num_tokens - i - 1)*sizeof(int)));
- --wb_num_tokens;
- --i;
- }
- }
- }
-
- *num_tokens = wb_num_tokens;
- *tokens = wb_tokens;
-}
-
+++ /dev/null
-/*
- * Copyright (c) 2005-2021 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.
- */
-
-/*
- * This is an ID for the wordbreaker module. If we do pluggable wordbreakers
- * later on, or even if we update this one, we can use a different ID so the
- * system knows it needs to throw away the existing index and rebuild it.
- */
-#define FT_WORDBREAKER_ID 0x0021
-
-/*
- * Minimum and maximum length of words to index
- */
-#define WB_MIN 4 // nothing with 3 or less chars
-#define WB_MAX 40
-
-void wordbreaker(const char *text, int *num_tokens, int **tokens);
+++ /dev/null
-/*
- * This module handles fulltext indexing of the message base.
- * Copyright (c) 2005-2021 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 as published
- * by the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "database.h"
-#include "msgbase.h"
-#include "control.h"
-#include "serv_fulltext.h"
-#include "ft_wordbreaker.h"
-#include "threads.h"
-#include "context.h"
-
-#include "ctdl_module.h"
-
-long ft_newhighest = 0L;
-long *ft_newmsgs = NULL;
-int ft_num_msgs = 0;
-int ft_num_alloc = 0;
-
-int ftc_num_msgs[65536];
-long *ftc_msgs[65536];
-
-
-/*
- * Compare function
- */
-int longcmp(const void *rec1, const void *rec2) {
- long i1, i2;
-
- i1 = *(const long *)rec1;
- i2 = *(const long *)rec2;
-
- if (i1 > i2) return(1);
- if (i1 < i2) return(-1);
- return(0);
-}
-
-
-/*
- * Flush our index cache out to disk.
- */
-void ft_flush_cache(void) {
- int i;
- time_t last_update = 0;
-
- for (i=0; i<65536; ++i) {
- if ((time(NULL) - last_update) >= 10) {
- syslog(LOG_INFO,
- "fulltext: flushing index cache to disk (%d%% complete)",
- (i * 100 / 65536)
- );
- last_update = time(NULL);
- }
- if (ftc_msgs[i] != NULL) {
- cdb_store(CDB_FULLTEXT, &i, sizeof(int), ftc_msgs[i],
- (ftc_num_msgs[i] * sizeof(long)));
- ftc_num_msgs[i] = 0;
- free(ftc_msgs[i]);
- ftc_msgs[i] = NULL;
- }
- }
- syslog(LOG_INFO, "fulltext: flushed index cache to disk (100%% complete)");
-}
-
-
-/*
- * Index or de-index a message. (op == 1 to index, 0 to de-index)
- */
-void ft_index_message(long msgnum, int op) {
- int num_tokens = 0;
- int *tokens = NULL;
- int i, j;
- struct cdbdata *cdb_bucket;
- StrBuf *msgtext;
- char *txt;
- int tok;
- struct CtdlMessage *msg = NULL;
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) {
- syslog(LOG_ERR, "fulltext: ft_index_message() could not load msg %ld", msgnum);
- return;
- }
-
- if (!CM_IsEmpty(msg, eSuppressIdx)) {
- syslog(LOG_DEBUG, "fulltext: ft_index_message() excluded msg %ld", msgnum);
- CM_Free(msg);
- return;
- }
-
- syslog(LOG_DEBUG, "fulltext: ft_index_message() %s msg %ld", (op ? "adding" : "removing") , msgnum);
-
- /* Output the message as text before indexing it, so we don't end up
- * indexing a bunch of encoded base64, etc.
- */
- CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
- CtdlOutputPreLoadedMsg(msg, MT_CITADEL, HEADERS_ALL, 0, 1, 0);
- CM_Free(msg);
- msgtext = CC->redirect_buffer;
- CC->redirect_buffer = NULL;
- if (msgtext != NULL) {
- syslog(LOG_DEBUG, "fulltext: wordbreaking message %ld (%d bytes)", msgnum, StrLength(msgtext));
- }
- txt = SmashStrBuf(&msgtext);
- wordbreaker(txt, &num_tokens, &tokens);
- free(txt);
-
- syslog(LOG_DEBUG, "fulltext: indexing message %ld [%d tokens]", msgnum, num_tokens);
- if (num_tokens > 0) {
- for (i=0; i<num_tokens; ++i) {
-
- /* Add the message to the relevant token bucket */
-
- /* search for tokens[i] */
- tok = tokens[i];
-
- if ( (tok >= 0) && (tok <= 65535) ) {
- /* fetch the bucket, Liza */
- if (ftc_msgs[tok] == NULL) {
- cdb_bucket = cdb_fetch(CDB_FULLTEXT, &tok, sizeof(int));
- if (cdb_bucket != NULL) {
- ftc_num_msgs[tok] = cdb_bucket->len / sizeof(long);
- ftc_msgs[tok] = (long *)cdb_bucket->ptr;
- cdb_bucket->ptr = NULL;
- cdb_free(cdb_bucket);
- }
- else {
- ftc_num_msgs[tok] = 0;
- ftc_msgs[tok] = malloc(sizeof(long));
- }
- }
-
-
- if (op == 1) { /* add to index */
- ++ftc_num_msgs[tok];
- ftc_msgs[tok] = realloc(ftc_msgs[tok],
- ftc_num_msgs[tok]*sizeof(long));
- ftc_msgs[tok][ftc_num_msgs[tok] - 1] = msgnum;
- }
-
- if (op == 0) { /* remove from index */
- if (ftc_num_msgs[tok] >= 1) {
- for (j=0; j<ftc_num_msgs[tok]; ++j) {
- if (ftc_msgs[tok][j] == msgnum) {
- memmove(&ftc_msgs[tok][j], &ftc_msgs[tok][j+1], ((ftc_num_msgs[tok] - j - 1)*sizeof(long)));
- --ftc_num_msgs[tok];
- --j;
- }
- }
- }
- }
- }
- else {
- syslog(LOG_ALERT, "fulltext: invalid token %d !!", tok);
- }
- }
-
- free(tokens);
- }
-}
-
-
-/*
- * Add a message to the list of those to be indexed.
- */
-void ft_index_msg(long msgnum, void *userdata) {
-
- if ((msgnum > CtdlGetConfigLong("MMfulltext")) && (msgnum <= ft_newhighest)) {
- ++ft_num_msgs;
- if (ft_num_msgs > ft_num_alloc) {
- ft_num_alloc += 1024;
- ft_newmsgs = realloc(ft_newmsgs, (ft_num_alloc * sizeof(long)));
- }
- ft_newmsgs[ft_num_msgs - 1] = msgnum;
- }
-
-}
-
-
-/*
- * Scan a room for messages to index.
- */
-void ft_index_room(struct ctdlroom *qrbuf, void *data)
-{
- if (server_shutting_down)
- return;
-
- CtdlGetRoom(&CC->room, qrbuf->QRname);
- CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, ft_index_msg, NULL);
-}
-
-
-/*
- * Begin the fulltext indexing process.
- */
-void do_fulltext_indexing(void) {
- int i;
- static time_t last_progress = 0L;
- static int is_running = 0;
- if (is_running) return; /* Concurrency check - only one can run */
- is_running = 1;
-
- /*
- * Don't do this if the site doesn't have it enabled.
- */
- if (!CtdlGetConfigInt("c_enable_fulltext")) {
- return;
- }
-
- /*
- * If we've switched wordbreaker modules, burn the index and start over.
- */
- begin_critical_section(S_CONTROL);
- if (CtdlGetConfigInt("MM_fulltext_wordbreaker") != FT_WORDBREAKER_ID) {
- syslog(LOG_DEBUG, "fulltext: wb ver on disk = %d, code ver = %d",
- CtdlGetConfigInt("MM_fulltext_wordbreaker"), FT_WORDBREAKER_ID
- );
- syslog(LOG_INFO, "fulltext: (re)initializing index");
- cdb_trunc(CDB_FULLTEXT);
- CtdlSetConfigLong("MMfulltext", 0);
- }
- end_critical_section(S_CONTROL);
-
- /*
- * Silently return if our fulltext index is up to date with new messages.
- */
- if ((CtdlGetConfigLong("MMfulltext") >= CtdlGetConfigLong("MMhighest"))) {
- return; /* nothing to do! */
- }
-
- /*
- * Now go through each room and find messages to index.
- */
- ft_newhighest = CtdlGetConfigLong("MMhighest");
- CtdlForEachRoom(ft_index_room, NULL); /* load all msg pointers */
-
- if (ft_num_msgs > 0) {
- qsort(ft_newmsgs, ft_num_msgs, sizeof(long), longcmp);
- for (i=0; i<(ft_num_msgs-1); ++i) { /* purge dups */
- if (ft_newmsgs[i] == ft_newmsgs[i+1]) {
- memmove(&ft_newmsgs[i], &ft_newmsgs[i+1],
- ((ft_num_msgs - i - 1)*sizeof(long)));
- --ft_num_msgs;
- --i;
- }
- }
-
- /* Here it is ... do each message! */
- for (i=0; i<ft_num_msgs; ++i) {
- if (time(NULL) != last_progress) {
- syslog(LOG_DEBUG,
- "fulltext: indexed %d of %d messages (%d%%)",
- i, ft_num_msgs,
- ((i*100) / ft_num_msgs)
- );
- last_progress = time(NULL);
- }
- ft_index_message(ft_newmsgs[i], 1);
-
- /* Check to see if we need to quit early */
- if (server_shutting_down) {
- syslog(LOG_DEBUG, "fulltext: indexer quitting early");
- ft_newhighest = ft_newmsgs[i];
- break;
- }
-
- /* Check to see if we have to maybe flush to disk */
- if (i >= FT_MAX_CACHE) {
- syslog(LOG_DEBUG, "fulltext: time to flush.");
- ft_newhighest = ft_newmsgs[i];
- break;
- }
-
- }
-
- free(ft_newmsgs);
- ft_num_msgs = 0;
- ft_num_alloc = 0;
- ft_newmsgs = NULL;
- }
-
- if (server_shutting_down) {
- is_running = 0;
- return;
- }
-
- /* Save our place so we don't have to do this again */
- ft_flush_cache();
- begin_critical_section(S_CONTROL);
- CtdlSetConfigLong("MMfulltext", ft_newhighest);
- CtdlSetConfigInt("MM_fulltext_wordbreaker", FT_WORDBREAKER_ID);
- end_critical_section(S_CONTROL);
-
- syslog(LOG_DEBUG, "fulltext: indexing finished");
- is_running = 0;
- return;
-}
-
-
-/*
- * API call to perform searches.
- * (This one does the "all of these words" search.)
- * Caller is responsible for freeing the message list.
- */
-void ft_search(int *fts_num_msgs, long **fts_msgs, const char *search_string) {
- int num_tokens = 0;
- int *tokens = NULL;
- int i, j;
- struct cdbdata *cdb_bucket;
- int num_all_msgs = 0;
- long *all_msgs = NULL;
- int num_ret_msgs = 0;
- int num_ret_alloc = 0;
- long *ret_msgs = NULL;
- int tok;
-
- wordbreaker(search_string, &num_tokens, &tokens);
- if (num_tokens > 0) {
- for (i=0; i<num_tokens; ++i) {
-
- /* search for tokens[i] */
- tok = tokens[i];
-
- /* fetch the bucket, Liza */
- if (ftc_msgs[tok] == NULL) {
- cdb_bucket = cdb_fetch(CDB_FULLTEXT, &tok, sizeof(int));
- if (cdb_bucket != NULL) {
- ftc_num_msgs[tok] = cdb_bucket->len / sizeof(long);
- ftc_msgs[tok] = (long *)cdb_bucket->ptr;
- cdb_bucket->ptr = NULL;
- cdb_free(cdb_bucket);
- }
- else {
- ftc_num_msgs[tok] = 0;
- ftc_msgs[tok] = malloc(sizeof(long));
- }
- }
-
- num_all_msgs += ftc_num_msgs[tok];
- if (num_all_msgs > 0) {
- all_msgs = realloc(all_msgs, num_all_msgs*sizeof(long) );
- memcpy(&all_msgs[num_all_msgs-ftc_num_msgs[tok]],
- ftc_msgs[tok], ftc_num_msgs[tok]*sizeof(long) );
- }
-
- }
- free(tokens);
- if (all_msgs != NULL) {
- qsort(all_msgs, num_all_msgs, sizeof(long), longcmp);
-
- /*
- * At this point, if a message appears num_tokens times in the
- * list, then it contains all of the search tokens.
- */
- if (num_all_msgs >= num_tokens)
- for (j=0; j<(num_all_msgs-num_tokens+1); ++j) {
- if (all_msgs[j] == all_msgs[j+num_tokens-1]) {
-
- ++num_ret_msgs;
- if (num_ret_msgs > num_ret_alloc) {
- num_ret_alloc += 64;
- ret_msgs = realloc(ret_msgs,
- (num_ret_alloc*sizeof(long)) );
- }
- ret_msgs[num_ret_msgs - 1] = all_msgs[j];
-
- }
- }
- free(all_msgs);
- }
- }
-
- *fts_num_msgs = num_ret_msgs;
- *fts_msgs = ret_msgs;
-}
-
-
-/*
- * This search command is for diagnostic purposes and may be removed or replaced.
- */
-void cmd_srch(char *argbuf) {
- int num_msgs = 0;
- long *msgs = NULL;
- int i;
- char search_string[256];
-
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- if (!CtdlGetConfigInt("c_enable_fulltext")) {
- cprintf("%d Full text index is not enabled on this server.\n",
- ERROR + CMD_NOT_SUPPORTED);
- return;
- }
-
- extract_token(search_string, argbuf, 0, '|', sizeof search_string);
- ft_search(&num_msgs, &msgs, search_string);
-
- cprintf("%d %d msgs match all search words:\n",
- LISTING_FOLLOWS, num_msgs);
- if (num_msgs > 0) {
- for (i=0; i<num_msgs; ++i) {
- cprintf("%ld\n", msgs[i]);
- }
- }
- if (msgs != NULL) free(msgs);
- cprintf("000\n");
-}
-
-
-/*
- * Zero out our index cache.
- */
-void initialize_ft_cache(void) {
- memset(ftc_num_msgs, 0, (65536 * sizeof(int)));
- memset(ftc_msgs, 0, (65536 * sizeof(long *)));
-}
-
-
-void ft_delete_remove(char *room, long msgnum)
-{
- if (room) return;
-
- /* Remove from fulltext index */
- if (CtdlGetConfigInt("c_enable_fulltext")) {
- ft_index_message(msgnum, 0);
- }
-}
-
-
-/*****************************************************************************/
-
-CTDL_MODULE_INIT(fulltext)
-{
- if (!threading)
- {
- initialize_ft_cache();
- CtdlRegisterProtoHook(cmd_srch, "SRCH", "Full text search");
- CtdlRegisterDeleteHook(ft_delete_remove);
- CtdlRegisterSearchFuncHook(ft_search, "fulltext");
- CtdlRegisterSessionHook(do_fulltext_indexing, EVT_TIMER, PRIO_CLEANUP + 300);
- }
- /* return our module name for the log */
- return "fulltext";
-}
+++ /dev/null
-/*
- * Copyright (c) 2005-2012 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.
- *
- *
- *
- *
- */
-
-void ft_index_message(long msgnum, int op);
-void ft_search(int *fts_num_msgs, long **fts_msgs, const char *search_string);
-void *indexer_thread(void *arg);
+++ /dev/null
-/*
- * Copyright (c) 1987-2021 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 as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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.
- */
-
-#include "ctdl_module.h"
-#include "config.h"
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <dirent.h>
-
-
-/*
- * DownLoad Room Image (see its icon or whatever)
- * If this command succeeds, it follows the same protocol as the DLAT command.
- */
-void cmd_dlri(char *cmdbuf) {
- if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
- if (CC->room.msgnum_pic < 1) {
- cprintf("%d No image found.\n", ERROR + FILE_NOT_FOUND);
- return;
- }
-
- struct CtdlMessage *msg = CtdlFetchMessage(CC->room.msgnum_pic, 1);
- if (msg != NULL) {
- // The call to CtdlOutputPreLoadedMsg() with MT_SPEW_SECTION will cause the DLRI command
- // to have the same output format as the DLAT command, because it calls the same code.
- // For example: 600 402132|-1||image/gif|
- safestrncpy(CC->download_desired_section, "1", sizeof CC->download_desired_section);
- CtdlOutputPreLoadedMsg(msg, MT_SPEW_SECTION, HEADERS_NONE, 1, 0, 0);
- CM_Free(msg);
- }
- else {
- cprintf("%d No image found.\n", ERROR + MESSAGE_NOT_FOUND);
- return;
- }
-}
-
-
-/*
- * UpLoad Room Image (avatar or photo or whatever)
- */
-void cmd_ulri(char *cmdbuf) {
- long data_length;
- char mimetype[SIZ];
-
- if (CtdlAccessCheck(ac_room_aide)) return;
-
- data_length = extract_long(cmdbuf, 0);
- extract_token(mimetype, cmdbuf, 1, '|', sizeof mimetype);
-
- if (data_length < 20) {
- cprintf("%d That's an awfully small file. Try again.\n", ERROR + ILLEGAL_VALUE);
- return;
- }
-
- if (strncasecmp(mimetype, "image/", 6)) {
- cprintf("%d Only image files are permitted.\n", ERROR + ILLEGAL_VALUE);
- return;
- }
-
- char *unencoded_data = malloc(data_length + 1);
- if (!unencoded_data) {
- cprintf("%d Could not allocate %ld bytes of memory\n", ERROR + INTERNAL_ERROR , data_length);
- return;
- }
-
- cprintf("%d %ld\n", SEND_BINARY, data_length);
- client_read(unencoded_data, data_length);
-
- // We've got the data read from the client, now save it.
- char *encoded_data = malloc((data_length * 2) + 100);
- if (encoded_data) {
- sprintf(encoded_data, "Content-type: %s\nContent-transfer-encoding: base64\n\n", mimetype);
- CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
- long new_msgnum = quickie_message("Citadel", NULL, NULL, SYSCONFIGROOM, encoded_data, FMT_RFC822, "Image uploaded by admin user");
-
- if (CtdlGetRoomLock(&CC->room, CC->room.QRname) == 0) {
- long old_msgnum = CC->room.msgnum_pic;
- syslog(LOG_DEBUG, "Message %ld is now the photo for %s", new_msgnum, CC->room.QRname);
- CC->room.msgnum_pic = new_msgnum;
- CtdlPutRoomLock(&CC->room);
- if (old_msgnum > 0) {
- syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, SYSCONFIGROOM);
- CtdlDeleteMessages(SYSCONFIGROOM, &old_msgnum, 1, "");
- }
- }
- free(encoded_data);
- }
-
- free(unencoded_data);
-}
-
-
-/*
- * DownLoad User Image (see their avatar or photo or whatever)
- * If this command succeeds, it follows the same protocol as the DLAT command.
- */
-void cmd_dlui(char *cmdbuf) {
- struct ctdluser ruser;
- char buf[SIZ];
-
- if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
- extract_token(buf, cmdbuf, 0, '|', sizeof buf);
- if (CtdlGetUser(&ruser, buf) != 0) {
- cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
- return;
- }
- if (ruser.msgnum_pic < 1) {
- cprintf("%d No image found.\n", ERROR + FILE_NOT_FOUND);
- return;
- }
-
- struct CtdlMessage *msg = CtdlFetchMessage(ruser.msgnum_pic, 1);
- if (msg != NULL) {
- // The call to CtdlOutputPreLoadedMsg() with MT_SPEW_SECTION will cause the DLUI command
- // to have the same output format as the DLAT command, because it calls the same code.
- // For example: 600 402132|-1||image/gif|
- safestrncpy(CC->download_desired_section, "1", sizeof CC->download_desired_section);
- CtdlOutputPreLoadedMsg(msg, MT_SPEW_SECTION, HEADERS_NONE, 1, 0, 0);
- CM_Free(msg);
- }
- else {
- cprintf("%d No image found.\n", ERROR + MESSAGE_NOT_FOUND);
- return;
- }
-}
-
-
-/*
- * UpLoad User Image (avatar or photo or whatever)
- */
-void cmd_ului(char *cmdbuf) {
- long data_length;
- char mimetype[SIZ];
- char username[USERNAME_SIZE];
- char userconfigroomname[ROOMNAMELEN];
-
- if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
- if (num_parms(cmdbuf) < 2) {
- cprintf("%d Usage error\n", ERROR + ILLEGAL_VALUE);
- return;
- }
-
- data_length = extract_long(cmdbuf, 0);
- extract_token(mimetype, cmdbuf, 1, '|', sizeof mimetype);
- extract_token(username, cmdbuf, 2, '|', sizeof username);
-
- if (data_length < 20) {
- cprintf("%d That's an awfully small file. Try again.\n", ERROR + ILLEGAL_VALUE);
- return;
- }
-
- if (strncasecmp(mimetype, "image/", 6)) {
- cprintf("%d Only image files are permitted.\n", ERROR + ILLEGAL_VALUE);
- return;
- }
-
- if (IsEmptyStr(username)) {
- safestrncpy(username, CC->curr_user, sizeof username);
- }
-
- // Normal users can only change their own photo
- if ( (strcasecmp(username, CC->curr_user)) && (CC->user.axlevel < AxAideU) && (!CC->internal_pgm) ) {
- cprintf("%d Higher access required to change another user's photo.\n", ERROR + HIGHER_ACCESS_REQUIRED);
- }
-
- // Check to make sure the user exists
- struct ctdluser usbuf;
- if (CtdlGetUser(&usbuf, username) != 0) { // check for existing user, don't lock it yet
- cprintf("%d %s not found.\n", ERROR + NO_SUCH_USER , username);
- return;
- }
- CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &usbuf, USERCONFIGROOM);
-
- char *unencoded_data = malloc(data_length + 1);
- if (!unencoded_data) {
- cprintf("%d Could not allocate %ld bytes of memory\n", ERROR + INTERNAL_ERROR , data_length);
- return;
- }
-
- cprintf("%d %ld\n", SEND_BINARY, data_length);
- client_read(unencoded_data, data_length);
-
- // We've got the data read from the client, now save it.
- char *encoded_data = malloc((data_length * 2) + 100);
- if (encoded_data) {
- sprintf(encoded_data, "Content-type: %s\nContent-transfer-encoding: base64\n\n", mimetype);
- CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
- long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, encoded_data, FMT_RFC822, "Photo uploaded by user");
-
- if (CtdlGetUserLock(&usbuf, username) == 0) { // lock it this time
- long old_msgnum = usbuf.msgnum_pic;
- syslog(LOG_DEBUG, "Message %ld is now the photo for %s", new_msgnum, username);
- usbuf.msgnum_pic = new_msgnum;
- CtdlPutUserLock(&usbuf);
- if (old_msgnum > 0) {
- syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
- CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
- }
- }
-
- free(encoded_data);
- }
-
- free(unencoded_data);
-}
-
-
-/*
- * Import function called by import_old_userpic_files() for a single user
- */
-void import_one_userpic_file(char *username, long usernum, char *path) {
- syslog(LOG_DEBUG, "Import legacy userpic for %s, usernum=%ld, filename=%s", username, usernum, path);
-
- FILE *fp = fopen(path, "r");
- if (!fp) return;
-
- fseek(fp, 0, SEEK_END);
- long data_length = ftell(fp);
-
- if (data_length >= 1) {
- rewind(fp);
- char *unencoded_data = malloc(data_length);
- if (unencoded_data) {
- fread(unencoded_data, data_length, 1, fp);
- char *encoded_data = malloc((data_length * 2) + 100);
- if (encoded_data) {
- sprintf(encoded_data, "Content-type: %s\nContent-transfer-encoding: base64\n\n", GuessMimeByFilename(path, strlen(path)));
- CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
-
- char userconfigroomname[ROOMNAMELEN];
- struct ctdluser usbuf;
-
- if (CtdlGetUser(&usbuf, username) == 0) { // no need to lock it , we are still initializing
- long old_msgnum = usbuf.msgnum_pic;
- CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &usbuf, USERCONFIGROOM);
- long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, encoded_data, FMT_RFC822, "Photo imported from file");
- syslog(LOG_DEBUG, "Message %ld is now the photo for %s", new_msgnum, username);
- usbuf.msgnum_pic = new_msgnum;
- CtdlPutUser(&usbuf);
- unlink(path); // delete the old file , it's in the database now
- if (old_msgnum > 0) {
- syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
- CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
- }
- }
- free(encoded_data);
- }
- free(unencoded_data);
- }
- }
- fclose(fp);
-}
-
-
-/*
- * Look for old-format "userpic" files and import them into the message base
- */
-void import_old_userpic_files(void) {
- DIR *filedir = NULL;
- struct dirent *filedir_entry;
- size_t d_namelen;
- struct ctdluser usbuf;
- long usernum = 0;
- int d_type = 0;
- struct stat s;
- char path[PATH_MAX];
-
-
- syslog(LOG_DEBUG, "Importing old style userpic files into the message base");
- filedir = opendir (ctdl_usrpic_dir);
- if (filedir == NULL) {
- return;
- }
- while ( (filedir_entry = readdir(filedir)) , (filedir_entry != NULL))
- {
-#ifdef _DIRENT_HAVE_D_NAMLEN
- d_namelen = filedir_entry->d_namlen;
-
-#else
- d_namelen = strlen(filedir_entry->d_name);
-#endif
-
-#ifdef _DIRENT_HAVE_D_TYPE
- d_type = filedir_entry->d_type;
-#else
-
-#ifndef DT_UNKNOWN
-#define DT_UNKNOWN 0
-#define DT_DIR 4
-#define DT_REG 8
-#define DT_LNK 10
-
-#define IFTODT(mode) (((mode) & 0170000) >> 12)
-#define DTTOIF(dirtype) ((dirtype) << 12)
-#endif
- d_type = DT_UNKNOWN;
-#endif
- if ((d_namelen == 1) &&
- (filedir_entry->d_name[0] == '.'))
- continue;
-
- if ((d_namelen == 2) &&
- (filedir_entry->d_name[0] == '.') &&
- (filedir_entry->d_name[1] == '.'))
- continue;
-
- snprintf(path, PATH_MAX, "%s/%s", ctdl_usrpic_dir, filedir_entry->d_name);
- if (d_type == DT_UNKNOWN) {
- if (lstat(path, &s) == 0) {
- d_type = IFTODT(s.st_mode);
- }
- }
- switch (d_type)
- {
- case DT_DIR:
- break;
- case DT_LNK:
- case DT_REG:
- usernum = atol(filedir_entry->d_name);
- if (CtdlGetUserByNumber(&usbuf, usernum) == 0) {
- import_one_userpic_file(usbuf.fullname, usernum, path);
- }
- }
- }
- closedir(filedir);
- rmdir(ctdl_usrpic_dir);
-}
-
-
-
-CTDL_MODULE_INIT(image)
-{
- if (!threading)
- {
- import_old_userpic_files();
- CtdlRegisterProtoHook(cmd_dlri, "DLRI", "DownLoad Room Image");
- CtdlRegisterProtoHook(cmd_ulri, "ULRI", "UpLoad Room Image");
- CtdlRegisterProtoHook(cmd_dlui, "DLUI", "DownLoad User Image");
- CtdlRegisterProtoHook(cmd_ului, "ULUI", "UpLoad User Image");
- }
- /* return our module name for the log */
- return "image";
-}
+++ /dev/null
-/*
- * Functions which implement RFC2086 (and maybe RFC4314) (IMAP ACL extension)
- *
- * Copyright (c) 2007-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 as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "sysdep_decls.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "serv_imap.h"
-#include "imap_tools.h"
-#include "imap_fetch.h"
-#include "imap_misc.h"
-#include "genstamp.h"
-#include "ctdl_module.h"
-
-
-/*
- * Implements the SETACL command.
- */
-void imap_setacl(int num_parms, ConstStr *Params) {
-
- IReply("BAD not yet implemented FIXME");
- return;
-}
-
-
-/*
- * Implements the DELETEACL command.
- */
-void imap_deleteacl(int num_parms, ConstStr *Params) {
-
- IReply("BAD not yet implemented FIXME");
- return;
-}
-
-
-/*
- * Given the bits returned by CtdlRoomAccess(), populate a string buffer
- * with IMAP ACL format flags. This code is common to GETACL and MYRIGHTS.
- */
-void imap_acl_flags(StrBuf *rights, int ra)
-{
- FlushStrBuf(rights);
-
- /* l - lookup (mailbox is visible to LIST/LSUB commands)
- * r - read (SELECT the mailbox, perform STATUS et al)
- * s - keep seen/unseen information across sessions (STORE SEEN flag)
- */
- if ( (ra & UA_KNOWN) /* known rooms */
- || ((ra & UA_GOTOALLOWED) && (ra & UA_ZAPPED)) /* zapped rooms */
- ) {
- StrBufAppendBufPlain(rights, HKEY("l"), 0);
- StrBufAppendBufPlain(rights, HKEY("r"), 0);
- StrBufAppendBufPlain(rights, HKEY("s"), 0);
-
- /* Only output the remaining flags if the room is known */
-
- /* w - write (set or clear flags other than SEEN or DELETED, not supported in Citadel */
-
- /* i - insert (perform APPEND, COPY into mailbox) */
- /* p - post (send mail to submission address for mailbox - not enforced) */
- /* c - create (CREATE new sub-mailboxes) */
- if (ra & UA_POSTALLOWED) {
- StrBufAppendBufPlain(rights, HKEY("i"), 0);
- StrBufAppendBufPlain(rights, HKEY("p"), 0);
- StrBufAppendBufPlain(rights, HKEY("c"), 0);
- }
-
- /* d - delete messages (STORE DELETED flag, perform EXPUNGE) */
- if (ra & UA_DELETEALLOWED) {
- StrBufAppendBufPlain(rights, HKEY("d"), 0);
- }
-
- /* a - administer (perform SETACL/DELETEACL/GETACL/LISTRIGHTS) */
- if (ra & UA_ADMINALLOWED) {
- /*
- * This is the correct place to put the "a" flag. We are leaving
- * it commented out for now, because it implies that we could
- * perform any of SETACL/DELETEACL/GETACL/LISTRIGHTS. Since these
- * commands are not yet implemented, omitting the flag should
- * theoretically prevent compliant clients from attempting to
- * perform them.
- *
- * StrBufAppendBufPlain(rights, HKEY("a"), 0);
- */
- }
- }
-}
-
-
-/*
- * Implements the GETACL command.
- */
-void imap_getacl(int num_parms, ConstStr *Params) {
- char roomname[ROOMNAMELEN];
- char savedroom[ROOMNAMELEN];
- int msgs, new;
- int ret;
- struct ctdluser temp;
- struct cdbdata *cdbus;
- int ra;
- StrBuf *rights;
-
- if (num_parms != 3) {
- IReply("BAD usage error");
- return;
- }
-
- /*
- * Search for the specified room or folder
- */
- ret = imap_grabroom(roomname, Params[2].Key, 1);
- if (ret != 0) {
- IReply("NO Invalid mailbox name or access denied");
- return;
- }
-
- /*
- * CtdlUserGoto() formally takes us to the desired room. (If another
- * folder is selected, save its name so we can return there!!!!!)
- */
- if (IMAP->selected) {
- strcpy(savedroom, CC->room.QRname);
- }
- CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
-
- IAPuts("* ACL ");
- IPutCParamStr(2);
-
- /*
- * Traverse the userlist
- */
- rights = NewStrBuf();
- cdb_rewind(CDB_USERS);
- while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL)
- {
- memset(&temp, 0, sizeof temp);
- memcpy(&temp, cdbus->ptr, sizeof temp);
- cdb_free(cdbus);
-
- CtdlRoomAccess(&CC->room, &temp, &ra, NULL);
- if (!IsEmptyStr(temp.fullname)) {
- imap_acl_flags(rights, ra);
- if (StrLength(rights) > 0) {
- IAPuts(" ");
- IPutStr(temp.fullname, strlen(temp.fullname));
- IAPuts(" ");
- iaputs(SKEY( rights));
- }
- }
- }
- FreeStrBuf(&rights);
- IAPuts("\r\n");
-
- /*
- * If another folder is selected, go back to that room so we can resume
- * our happy day without violent explosions.
- */
- if (IMAP->selected) {
- CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
- }
-
- IReply("OK GETACL completed");
-}
-
-
-/*
- * Implements the LISTRIGHTS command.
- */
-void imap_listrights(int num_parms, ConstStr *Params) {
- char roomname[ROOMNAMELEN];
- char savedroom[ROOMNAMELEN];
- int msgs, new;
- int ret;
- struct recptypes *valid;
- struct ctdluser temp;
-
- if (num_parms != 4) {
- IReply("BAD usage error");
- return;
- }
-
- /*
- * Search for the specified room/folder
- */
- ret = imap_grabroom(roomname, Params[2].Key, 1);
- if (ret != 0) {
- IReply("NO Invalid mailbox name or access denied");
- return;
- }
-
- /*
- * Search for the specified user
- */
- ret = (-1);
- valid = validate_recipients(Params[3].Key, NULL, 0);
- if (valid != NULL) {
- if (valid->num_local == 1) {
- ret = CtdlGetUser(&temp, valid->recp_local);
- }
- free_recipients(valid);
- }
- if (ret != 0) {
- IReply("NO Invalid user name or access denied");
- return;
- }
-
- /*
- * CtdlUserGoto() formally takes us to the desired room. (If another
- * folder is selected, save its name so we can return there!!!!!)
- */
- if (IMAP->selected) {
- strcpy(savedroom, CC->room.QRname);
- }
- CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
-
- /*
- * Now output the list of rights
- */
- IAPuts("* LISTRIGHTS ");
- IPutCParamStr(2);
- IAPuts(" ");
- IPutCParamStr(3);
- IAPuts(" ");
- IPutStr(HKEY("")); /* FIXME ... do something here */
- IAPuts("\r\n");
-
- /*
- * If another folder is selected, go back to that room so we can resume
- * our happy day without violent explosions.
- */
- if (IMAP->selected) {
- CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
- }
-
- IReply("OK LISTRIGHTS completed");
- return;
-}
-
-
-/*
- * Implements the MYRIGHTS command.
- */
-void imap_myrights(int num_parms, ConstStr *Params) {
- char roomname[ROOMNAMELEN];
- char savedroom[ROOMNAMELEN];
- int msgs, new;
- int ret;
- int ra;
- StrBuf *rights;
-
- if (num_parms != 3) {
- IReply("BAD usage error");
- return;
- }
-
- ret = imap_grabroom(roomname, Params[2].Key, 1);
- if (ret != 0) {
- IReply("NO Invalid mailbox name or access denied");
- return;
- }
-
- /*
- * CtdlUserGoto() formally takes us to the desired room. (If another
- * folder is selected, save its name so we can return there!!!!!)
- */
- if (IMAP->selected) {
- strcpy(savedroom, CC->room.QRname);
- }
- CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
-
- CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
- rights = NewStrBuf();
- imap_acl_flags(rights, ra);
-
- IAPuts("* MYRIGHTS ");
- IPutCParamStr(2);
- IAPuts(" ");
- IPutStr(SKEY(rights));
- IAPuts("\r\n");
-
- FreeStrBuf(&rights);
-
- /*
- * If a different folder was previously selected, return there now.
- */
- if ( (IMAP->selected) && (strcasecmp(roomname, savedroom)) ) {
- CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
- }
-
- IReply("OK MYRIGHTS completed");
- return;
-}
+++ /dev/null
-/*
- * Copyright (c) 2007-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.
- */
-
-void imap_setacl(int num_parms, ConstStr *Params);
-void imap_deleteacl(int num_parms, ConstStr *Params);
-void imap_getacl(int num_parms, ConstStr *Params);
-void imap_listrights(int num_parms, ConstStr *Params);
-void imap_myrights(int num_parms, ConstStr *Params);
-
+++ /dev/null
-/*
- * Implements the FETCH command in IMAP.
- * This is a good example of the protocol's gratuitous complexity.
- *
- * Copyright (c) 2001-2020 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 as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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.
- */
-
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "sysdep_decls.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "serv_imap.h"
-#include "imap_tools.h"
-#include "imap_fetch.h"
-#include "genstamp.h"
-#include "ctdl_module.h"
-
-
-
-/*
- * Individual field functions for imap_do_fetch_msg() ...
- */
-
-void imap_fetch_uid(int seq) {
- IAPrintf("UID %ld", IMAP->msgids[seq-1]);
-}
-
-void imap_fetch_flags(int seq)
-{
- citimap *Imap = IMAP;
- int num_flags_printed = 0;
- IAPuts("FLAGS (");
- if (Imap->flags[seq] & IMAP_DELETED) {
- if (num_flags_printed > 0)
- IAPuts(" ");
- IAPuts("\\Deleted");
- ++num_flags_printed;
- }
- if (Imap->flags[seq] & IMAP_SEEN) {
- if (num_flags_printed > 0)
- IAPuts(" ");
- IAPuts("\\Seen");
- ++num_flags_printed;
- }
- if (Imap->flags[seq] & IMAP_ANSWERED) {
- if (num_flags_printed > 0)
- IAPuts(" ");
- IAPuts("\\Answered");
- ++num_flags_printed;
- }
- if (Imap->flags[seq] & IMAP_RECENT) {
- if (num_flags_printed > 0)
- IAPuts(" ");
- IAPuts("\\Recent");
- ++num_flags_printed;
- }
- IAPuts(")");
-}
-
-
-void imap_fetch_internaldate(struct CtdlMessage *msg) {
- char datebuf[64];
- time_t msgdate;
-
- if (!msg) return;
- if (!CM_IsEmpty(msg, eTimestamp)) {
- msgdate = atol(msg->cm_fields[eTimestamp]);
- }
- else {
- msgdate = time(NULL);
- }
-
- datestring(datebuf, sizeof datebuf, msgdate, DATESTRING_IMAP);
- IAPrintf( "INTERNALDATE \"%s\"", datebuf);
-}
-
-
-/*
- * Fetch RFC822-formatted messages.
- *
- * 'whichfmt' should be set to one of:
- * "RFC822" entire message
- * "RFC822.HEADER" headers only (with trailing blank line)
- * "RFC822.SIZE" size of translated message
- * "RFC822.TEXT" body only (without leading blank line)
- */
-void imap_fetch_rfc822(long msgnum, const char *whichfmt) {
- CitContext *CCC = CC;
- citimap *Imap = CCCIMAP;
- const char *ptr = NULL;
- size_t headers_size, text_size, total_size;
- size_t bytes_to_send = 0;
- struct MetaData smi;
- int need_to_rewrite_metadata = 0;
- int need_body = 0;
-
- /* Determine whether this particular fetch operation requires
- * us to fetch the message body from disk. If not, we can save
- * on some disk operations...
- */
- if ( (!strcasecmp(whichfmt, "RFC822")) || (!strcasecmp(whichfmt, "RFC822.TEXT")) ) {
- need_body = 1;
- }
-
- /* If this is an RFC822.SIZE fetch, first look in the message's
- * metadata record to see if we've saved that information.
- */
- if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
- GetMetaData(&smi, msgnum);
- if (smi.meta_rfc822_length > 0L) {
- IAPrintf("RFC822.SIZE %ld", smi.meta_rfc822_length);
- return;
- }
- need_to_rewrite_metadata = 1;
- need_body = 1;
- }
-
- /* Cache the most recent RFC822 FETCH because some clients like to
- * fetch in pieces, and we don't want to have to go back to the
- * message store for each piece. We also burn the cache if the
- * client requests something that involves reading the message
- * body, but we haven't fetched the body yet.
- */
- if ((Imap->cached_rfc822 != NULL)
- && (Imap->cached_rfc822_msgnum == msgnum)
- && (Imap->cached_rfc822_withbody || (!need_body)) ) {
- /* Good to go! */
- }
- else if (Imap->cached_rfc822 != NULL) {
- /* Some other message is cached -- free it */
- FreeStrBuf(&Imap->cached_rfc822);
- Imap->cached_rfc822_msgnum = (-1);
- }
-
- /* At this point, we now can fetch and convert the message iff it's not
- * the one we had cached.
- */
- if (Imap->cached_rfc822 == NULL) {
- /*
- * Load the message into memory for translation & measurement
- */
- CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
- CtdlOutputMsg(msgnum, MT_RFC822,
- (need_body ? HEADERS_ALL : HEADERS_FAST),
- 0, 1, NULL, SUPPRESS_ENV_TO, NULL, NULL, NULL
- );
- if (!need_body) {
- client_write(HKEY("\r\n")); // extra trailing newline -- to the redirect_buffer, *not* to the client
- }
- Imap->cached_rfc822 = CCC->redirect_buffer;
- CCC->redirect_buffer = NULL;
- Imap->cached_rfc822_msgnum = msgnum;
- Imap->cached_rfc822_withbody = need_body;
- if ( (need_to_rewrite_metadata) &&
- (StrLength(Imap->cached_rfc822) > 0) ) {
- smi.meta_rfc822_length = StrLength(Imap->cached_rfc822);
- PutMetaData(&smi);
- }
- }
-
- /*
- * Now figure out where the headers/text break is. IMAP considers the
- * intervening blank line to be part of the headers, not the text.
- */
- headers_size = 0;
-
- if (need_body) {
- StrBuf *Line = NewStrBuf();
- ptr = NULL;
- do {
- StrBufSipLine(Line, Imap->cached_rfc822, &ptr);
-
- if ((StrLength(Line) != 0) && (ptr != StrBufNOTNULL))
- {
- StrBufTrim(Line);
- if ((StrLength(Line) != 0) &&
- (ptr != StrBufNOTNULL) )
- {
- headers_size = ptr - ChrPtr(Imap->cached_rfc822);
- }
- }
- } while ( (headers_size == 0) &&
- (ptr != StrBufNOTNULL) );
-
- total_size = StrLength(Imap->cached_rfc822);
- text_size = total_size - headers_size;
- FreeStrBuf(&Line);
- }
- else {
- headers_size = total_size = StrLength(Imap->cached_rfc822);
- text_size = 0;
- }
-
- syslog(LOG_DEBUG, "imap: RFC822 headers=" SIZE_T_FMT ", text=" SIZE_T_FMT ", total=" SIZE_T_FMT, headers_size, text_size, total_size);
-
- if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
- IAPrintf("RFC822.SIZE " SIZE_T_FMT, total_size);
- return;
- }
-
- else if (!strcasecmp(whichfmt, "RFC822")) {
- ptr = ChrPtr(Imap->cached_rfc822);
- bytes_to_send = total_size;
- }
-
- else if (!strcasecmp(whichfmt, "RFC822.HEADER")) {
- ptr = ChrPtr(Imap->cached_rfc822);
- bytes_to_send = headers_size;
- }
-
- else if (!strcasecmp(whichfmt, "RFC822.TEXT")) {
- ptr = &ChrPtr(Imap->cached_rfc822)[headers_size];
- bytes_to_send = text_size;
- }
-
- IAPrintf("%s {" SIZE_T_FMT "}\r\n", whichfmt, bytes_to_send);
- iaputs(ptr, bytes_to_send);
-}
-
-
-/*
- * Load a specific part of a message into the temp file to be output to a
- * client. FIXME we can handle parts like "2" and "2.1" and even "2.MIME"
- * but we still can't handle "2.HEADER" (which might not be a problem).
- *
- * Note: mime_parser() was called with dont_decode set to 1, so we have the
- * luxury of simply spewing without having to re-encode.
- */
-void imap_load_part(char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
- char *cbid, void *cbuserdata)
-{
- char mimebuf2[SIZ];
- StrBuf *desired_section;
-
- desired_section = (StrBuf *)cbuserdata;
- syslog(LOG_DEBUG, "imap: imap_load_part() looking for %s, found %s", ChrPtr(desired_section), partnum);
-
- if (!strcasecmp(partnum, ChrPtr(desired_section))) {
- client_write(content, length);
- }
-
- snprintf(mimebuf2, sizeof mimebuf2, "%s.MIME", partnum);
-
- if (!strcasecmp(ChrPtr(desired_section), mimebuf2)) {
- client_write(HKEY("Content-type: "));
- client_write(cbtype, strlen(cbtype));
- if (!IsEmptyStr(cbcharset)) {
- client_write(HKEY("; charset=\""));
- client_write(cbcharset, strlen(cbcharset));
- client_write(HKEY("\""));
- }
- if (!IsEmptyStr(name)) {
- client_write(HKEY("; name=\""));
- client_write(name, strlen(name));
- client_write(HKEY("\""));
- }
- client_write(HKEY("\r\n"));
- if (!IsEmptyStr(encoding)) {
- client_write(HKEY("Content-Transfer-Encoding: "));
- client_write(encoding, strlen(encoding));
- client_write(HKEY("\r\n"));
- }
- if (!IsEmptyStr(encoding)) {
- client_write(HKEY("Content-Disposition: "));
- client_write(disp, strlen(disp));
-
- if (!IsEmptyStr(filename)) {
- client_write(HKEY("; filename=\""));
- client_write(filename, strlen(filename));
- client_write(HKEY("\""));
- }
- client_write(HKEY("\r\n"));
- }
- cprintf("Content-Length: %ld\r\n\r\n", (long)length);
- }
-}
-
-
-/*
- * Called by imap_fetch_envelope() to output the "From" field.
- * This is in its own function because its logic is kind of complex. We
- * really need to make this suck less.
- */
-void imap_output_envelope_from(struct CtdlMessage *msg) {
- char user[SIZ], node[SIZ], name[SIZ];
-
- if (!msg) return;
-
- /* For anonymous messages, it's so easy! */
- if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) {
- IAPuts("((\"----\" NIL \"x\" \"x.org\")) ");
- return;
- }
- if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) {
- IAPuts("((\"anonymous\" NIL \"x\" \"x.org\")) ");
- return;
- }
-
- /* For everything else, we do stuff. */
- IAPuts("(("); // open double-parens
- IPutMsgField(eAuthor); // display name
- IAPuts(" NIL "); // source route (not used)
-
- if (!CM_IsEmpty(msg, erFc822Addr)) {
- process_rfc822_addr(msg->cm_fields[erFc822Addr], user, node, name);
- IPutStr(user, strlen(user)); /* mailbox name (user id) */
- IAPuts(" ");
- IPutStr(node, strlen(node)); /* host name */
- }
- else {
- IPutMsgField(eAuthor); /* Make up a synthetic address */
- IAPuts(" ");
- IPutStr(CtdlGetConfigStr("c_fqdn"), strlen(CtdlGetConfigStr("c_fqdn")));
- }
-
- IAPuts(")) "); /* close double-parens */
-}
-
-
-/*
- * Output an envelope address (or set of addresses) in the official,
- * convoluted, braindead format. (Note that we can't use this for
- * the "From" address because its data may come from a number of different
- * fields. But we can use it for "To" and possibly others.
- */
-void imap_output_envelope_addr(char *addr) {
- char individual_addr[256];
- int num_addrs;
- int i;
- char user[256];
- char node[256];
- char name[256];
-
- if (addr == NULL) {
- IAPuts("NIL ");
- return;
- }
-
- if (IsEmptyStr(addr)) {
- IAPuts("NIL ");
- return;
- }
-
- IAPuts("(");
-
- /* How many addresses are listed here? */
- num_addrs = num_tokens(addr, ',');
-
- /* Output them one by one. */
- for (i=0; i<num_addrs; ++i) {
- extract_token(individual_addr, addr, i, ',', sizeof individual_addr);
- striplt(individual_addr);
- process_rfc822_addr(individual_addr, user, node, name);
- IAPuts("(");
- IPutStr(name, strlen(name));
- IAPuts(" NIL ");
- IPutStr(user, strlen(user));
- IAPuts(" ");
- IPutStr(node, strlen(node));
- IAPuts(")");
- if (i < (num_addrs-1))
- IAPuts(" ");
- }
-
- IAPuts(") ");
-}
-
-
-/*
- * Implements the ENVELOPE fetch item
- *
- * Note that the imap_strout() function can cleverly output NULL fields as NIL,
- * so we don't have to check for that condition like we do elsewhere.
- */
-void imap_fetch_envelope(struct CtdlMessage *msg) {
- char datestringbuf[SIZ];
- time_t msgdate;
- char *fieldptr = NULL;
- long len;
-
- if (!msg) return;
-
- /* Parse the message date into an IMAP-format date string */
- if (!CM_IsEmpty(msg, eTimestamp)) {
- msgdate = atol(msg->cm_fields[eTimestamp]);
- }
- else {
- msgdate = time(NULL);
- }
- len = datestring(datestringbuf, sizeof datestringbuf, msgdate, DATESTRING_IMAP);
-
- /* Now start spewing data fields. The order is important, as it is
- * defined by the protocol specification. Nonexistent fields must
- * be output as NIL, existent fields must be quoted or literalled.
- * The imap_strout() function conveniently does all this for us.
- */
- IAPuts("ENVELOPE (");
-
- /* Date */
- IPutStr(datestringbuf, len);
- IAPuts(" ");
-
- /* Subject */
- IPutMsgField(eMsgSubject);
- IAPuts(" ");
-
- /* From */
- imap_output_envelope_from(msg);
-
- /* Sender (default to same as 'From' if not present) */
- fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Sender");
- if (fieldptr != NULL) {
- imap_output_envelope_addr(fieldptr);
- free(fieldptr);
- }
- else {
- imap_output_envelope_from(msg);
- }
-
- /* Reply-to */
- fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Reply-to");
- if (fieldptr != NULL) {
- imap_output_envelope_addr(fieldptr);
- free(fieldptr);
- }
- else {
- imap_output_envelope_from(msg);
- }
-
- /* To */
- imap_output_envelope_addr(msg->cm_fields[eRecipient]);
-
- /* Cc (we do it this way because there might be a legacy non-Citadel Cc: field present) */
- fieldptr = msg->cm_fields[eCarbonCopY];
- if (fieldptr != NULL) {
- imap_output_envelope_addr(fieldptr);
- }
- else {
- fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Cc");
- imap_output_envelope_addr(fieldptr);
- if (fieldptr != NULL) free(fieldptr);
- }
-
- /* Bcc */
- fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Bcc");
- imap_output_envelope_addr(fieldptr);
- if (fieldptr != NULL) free(fieldptr);
-
- /* In-reply-to */
- fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "In-reply-to");
- IPutStr(fieldptr, (fieldptr)?strlen(fieldptr):0);
- IAPuts(" ");
- if (fieldptr != NULL) free(fieldptr);
-
- /* message ID */
- len = msg->cm_lengths[emessageId];
-
- if ((len == 0) || (
- (msg->cm_fields[emessageId][0] == '<') &&
- (msg->cm_fields[emessageId][len - 1] == '>'))
- ) {
- IPutMsgField(emessageId);
- }
- else
- {
- char *Buf = malloc(len + 3);
- long pos = 0;
-
- if (msg->cm_fields[emessageId][0] != '<')
- {
- Buf[pos] = '<';
- pos ++;
- }
- memcpy(&Buf[pos], msg->cm_fields[emessageId], len);
- pos += len;
- if (msg->cm_fields[emessageId][len] != '>')
- {
- Buf[pos] = '>';
- pos++;
- }
- Buf[pos] = '\0';
- IPutStr(Buf, pos);
- free(Buf);
- }
- IAPuts(")");
-}
-
-
-/*
- * This function is called only when CC->redirect_buffer contains a set of
- * RFC822 headers with no body attached. Its job is to strip that set of
- * headers down to *only* the ones we're interested in.
- */
-void imap_strip_headers(StrBuf *section) {
- citimap_command Cmd;
- StrBuf *which_fields = NULL;
- int doing_headers = 0;
- int headers_not = 0;
- int num_parms = 0;
- int i;
- StrBuf *boiled_headers = NULL;
- StrBuf *Line;
- int ok = 0;
- int done_headers = 0;
- const char *Ptr = NULL;
- CitContext *CCC = CC;
-
- if (CCC->redirect_buffer == NULL) return;
-
- which_fields = NewStrBufDup(section);
-
- if (!strncasecmp(ChrPtr(which_fields), "HEADER.FIELDS", 13))
- doing_headers = 1;
- if (doing_headers &&
- !strncasecmp(ChrPtr(which_fields), "HEADER.FIELDS.NOT", 17))
- headers_not = 1;
-
- for (i=0; i < StrLength(which_fields); ++i) {
- if (ChrPtr(which_fields)[i]=='(')
- StrBufReplaceToken(which_fields, i, 1, HKEY(""));
- }
- for (i=0; i < StrLength(which_fields); ++i) {
- if (ChrPtr(which_fields)[i]==')') {
- StrBufCutAt(which_fields, i, NULL);
- break;
- }
- }
- memset(&Cmd, 0, sizeof(citimap_command));
- Cmd.CmdBuf = which_fields;
- num_parms = imap_parameterize(&Cmd);
-
- boiled_headers = NewStrBufPlain(NULL, StrLength(CCC->redirect_buffer));
- Line = NewStrBufPlain(NULL, SIZ);
- Ptr = NULL;
- ok = 0;
- do {
- StrBufSipLine(Line, CCC->redirect_buffer, &Ptr);
-
- if (!isspace(ChrPtr(Line)[0])) {
-
- if (doing_headers == 0) ok = 1;
- else {
- /* we're supposed to print all headers that are not matching the filter list */
- if (headers_not) for (i=0, ok = 1; (i < num_parms) && (ok == 1); ++i) {
- if ( (!strncasecmp(ChrPtr(Line),
- Cmd.Params[i].Key,
- Cmd.Params[i].len)) &&
- (ChrPtr(Line)[Cmd.Params[i].len]==':') ) {
- ok = 0;
- }
- }
- /* we're supposed to print all headers matching the filterlist */
- else for (i=0, ok = 0; ((i < num_parms) && (ok == 0)); ++i) {
- if ( (!strncasecmp(ChrPtr(Line),
- Cmd.Params[i].Key,
- Cmd.Params[i].len)) &&
- (ChrPtr(Line)[Cmd.Params[i].len]==':') ) {
- ok = 1;
- }
- }
- }
- }
-
- if (ok) {
- StrBufAppendBuf(boiled_headers, Line, 0);
- StrBufAppendBufPlain(boiled_headers, HKEY("\r\n"), 0);
- }
-
- if ((Ptr == StrBufNOTNULL) ||
- (StrLength(Line) == 0) ||
- (ChrPtr(Line)[0]=='\r') ||
- (ChrPtr(Line)[0]=='\n') ) done_headers = 1;
- } while (!done_headers);
-
- StrBufAppendBufPlain(boiled_headers, HKEY("\r\n"), 0);
-
- /* Now save it back (it'll always be smaller) */
- FreeStrBuf(&CCC->redirect_buffer);
- CCC->redirect_buffer = boiled_headers;
-
- free(Cmd.Params);
- FreeStrBuf(&which_fields);
- FreeStrBuf(&Line);
-}
-
-
-/*
- * Implements the BODY and BODY.PEEK fetch items
- */
-void imap_fetch_body(long msgnum, ConstStr item, int is_peek) {
- struct CtdlMessage *msg = NULL;
- StrBuf *section;
- StrBuf *partial;
- int is_partial = 0;
- size_t pstart, pbytes;
- int loading_body_now = 0;
- int need_body = 1;
- int burn_the_cache = 0;
- CitContext *CCC = CC;
- citimap *Imap = CCCIMAP;
-
- /* extract section */
- section = NewStrBufPlain(CKEY(item));
-
- if (strchr(ChrPtr(section), '[') != NULL) {
- StrBufStripAllBut(section, '[', ']');
- }
- syslog(LOG_DEBUG, "imap: selected section is [%s]", (StrLength(section) == 0) ? "(empty)" : ChrPtr(section));
-
- /* Burn the cache if we don't have the same section of the
- * same message again.
- */
- if (Imap->cached_body != NULL) {
- if (Imap->cached_bodymsgnum != msgnum) {
- burn_the_cache = 1;
- }
- else if ( (!Imap->cached_body_withbody) && (need_body) ) {
- burn_the_cache = 1;
- }
- else if (strcasecmp(Imap->cached_bodypart, ChrPtr(section))) {
- burn_the_cache = 1;
- }
- if (burn_the_cache) {
- /* Yup, go ahead and burn the cache. */
- free(Imap->cached_body);
- Imap->cached_body_len = 0;
- Imap->cached_body = NULL;
- Imap->cached_bodymsgnum = (-1);
- strcpy(Imap->cached_bodypart, "");
- }
- }
-
- /* extract partial */
- partial = NewStrBufPlain(CKEY(item));
- if (strchr(ChrPtr(partial), '<') != NULL) {
- StrBufStripAllBut(partial, '<', '>');
- is_partial = 1;
- }
- if ( (is_partial == 1) && (StrLength(partial) > 0) ) {
- syslog(LOG_DEBUG, "imap: selected partial is <%s>", ChrPtr(partial));
- }
-
- if (Imap->cached_body == NULL) {
- CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
- loading_body_now = 1;
- msg = CtdlFetchMessage(msgnum, (need_body ? 1 : 0));
- }
-
- /* Now figure out what the client wants, and get it */
-
- if (!loading_body_now) {
- /* What we want is already in memory */
- }
-
- else if ( (!strcmp(ChrPtr(section), "1")) && (msg->cm_format_type != 4) ) {
- CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1, SUPPRESS_ENV_TO);
- }
-
- else if (StrLength(section) == 0) {
- CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, SUPPRESS_ENV_TO);
- }
-
- /*
- * If the client asked for just headers, or just particular header
- * fields, strip it down.
- */
- else if (!strncasecmp(ChrPtr(section), "HEADER", 6)) {
- /* This used to work with HEADERS_FAST, but then Apple got stupid with their
- * IMAP library and this broke Mail.App and iPhone Mail, so we had to change it
- * to HEADERS_ONLY so the trendy hipsters with their iPhones can read mail.
- */
- CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ONLY, 0, 1, SUPPRESS_ENV_TO);
- imap_strip_headers(section);
- }
-
- /*
- * Strip it down if the client asked for everything _except_ headers.
- */
- else if (!strncasecmp(ChrPtr(section), "TEXT", 4)) {
- CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1, SUPPRESS_ENV_TO);
- }
-
- /*
- * Anything else must be a part specifier.
- * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
- */
- else {
- mime_parser(CM_RANGE(msg, eMesageText),
- *imap_load_part, NULL, NULL,
- section,
- 1
- );
- }
-
- if (loading_body_now) {
- Imap->cached_body_len = StrLength(CCC->redirect_buffer);
- Imap->cached_body = SmashStrBuf(&CCC->redirect_buffer);
- Imap->cached_bodymsgnum = msgnum;
- Imap->cached_body_withbody = need_body;
- strcpy(Imap->cached_bodypart, ChrPtr(section));
- }
-
- if (is_partial == 0) {
- IAPuts("BODY[");
- iaputs(SKEY(section));
- IAPrintf("] {" SIZE_T_FMT "}\r\n", Imap->cached_body_len);
- pstart = 0;
- pbytes = Imap->cached_body_len;
- }
- else {
- sscanf(ChrPtr(partial), SIZE_T_FMT "." SIZE_T_FMT, &pstart, &pbytes);
- if (pbytes > (Imap->cached_body_len - pstart)) {
- pbytes = Imap->cached_body_len - pstart;
- }
- IAPuts("BODY[");
- iaputs(SKEY(section));
- IAPrintf("]<" SIZE_T_FMT "> {" SIZE_T_FMT "}\r\n", pstart, pbytes);
- }
-
- FreeStrBuf(&partial);
-
- /* Here we go -- output it */
- iaputs(&Imap->cached_body[pstart], pbytes);
-
- if (msg != NULL) {
- CM_Free(msg);
- }
-
- /* Mark this message as "seen" *unless* this is a "peek" operation */
- if (is_peek == 0) {
- CtdlSetSeen(&msgnum, 1, 1, ctdlsetseen_seen, NULL, NULL);
- }
- FreeStrBuf(§ion);
-}
-
-/*
- * Called immediately before outputting a multipart bodystructure
- */
-void imap_fetch_bodystructure_pre(
- char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
- char *cbid, void *cbuserdata
- ) {
-
- IAPuts("(");
-}
-
-
-
-/*
- * Called immediately after outputting a multipart bodystructure
- */
-void imap_fetch_bodystructure_post(
- char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
- char *cbid, void *cbuserdata
- ) {
- long len;
- char subtype[128];
-
- IAPuts(" ");
-
- /* disposition */
- len = extract_token(subtype, cbtype, 1, '/', sizeof subtype);
- IPutStr(subtype, len);
-
- /* body language */
- /* IAPuts(" NIL"); We thought we needed this at one point, but maybe we don't... */
-
- IAPuts(")");
-}
-
-
-
-/*
- * Output the info for a MIME part in the format required by BODYSTRUCTURE.
- *
- */
-void imap_fetch_bodystructure_part(
- char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
- char *cbid, void *cbuserdata
- ) {
-
- int have_cbtype = 0;
- int have_encoding = 0;
- int lines = 0;
- size_t i;
- char cbmaintype[128];
- char cbsubtype[128];
- long cbmaintype_len;
- long cbsubtype_len;
-
- if (cbtype != NULL) if (!IsEmptyStr(cbtype)) have_cbtype = 1;
- if (have_cbtype) {
- cbmaintype_len = extract_token(cbmaintype, cbtype, 0, '/', sizeof cbmaintype);
- cbsubtype_len = extract_token(cbsubtype, cbtype, 1, '/', sizeof cbsubtype);
- }
- else {
- strcpy(cbmaintype, "TEXT");
- cbmaintype_len = 4;
- strcpy(cbsubtype, "PLAIN");
- cbsubtype_len = 5;
- }
-
- IAPuts("(");
- IPutStr(cbmaintype, cbmaintype_len); /* body type */
- IAPuts(" ");
- IPutStr(cbsubtype, cbsubtype_len); /* body subtype */
- IAPuts(" ");
-
- IAPuts("("); /* begin body parameter list */
-
- /* "NAME" must appear as the first parameter. This is not required by IMAP,
- * but the Asterisk voicemail application blindly assumes that NAME will be in
- * the first position. If it isn't, it rejects the message.
- */
- if ((name != NULL) && (!IsEmptyStr(name))) {
- IAPuts("\"NAME\" ");
- IPutStr(name, strlen(name));
- IAPuts(" ");
- }
-
- IAPuts("\"CHARSET\" ");
- if ((cbcharset == NULL) || (cbcharset[0] == 0)){
- IPutStr(HKEY("US-ASCII"));
- }
- else {
- IPutStr(cbcharset, strlen(cbcharset));
- }
- IAPuts(") "); /* end body parameter list */
-
- IAPuts("NIL "); /* Body ID */
- IAPuts("NIL "); /* Body description */
-
- if ((encoding != NULL) && (encoding[0] != 0)) have_encoding = 1;
- if (have_encoding) {
- IPutStr(encoding, strlen(encoding));
- }
- else {
- IPutStr(HKEY("7BIT"));
- }
- IAPuts(" ");
-
- /* The next field is the size of the part in bytes. */
- IAPrintf("%ld ", (long)length); /* bytes */
-
- /* The next field is the number of lines in the part, if and only
- * if the part is TEXT. More gratuitous complexity.
- */
- if (!strcasecmp(cbmaintype, "TEXT")) {
- if (length) for (i=0; i<length; ++i) {
- if (((char *)content)[i] == '\n') ++lines;
- }
- IAPrintf("%d ", lines);
- }
-
- /* More gratuitous complexity */
- if ((!strcasecmp(cbmaintype, "MESSAGE"))
- && (!strcasecmp(cbsubtype, "RFC822"))) {
- /* FIXME: message/rfc822 also needs to output the envelope structure,
- * body structure, and line count of the encapsulated message. Fortunately
- * there are not yet any clients depending on this, so we can get away
- * with not implementing it for now.
- */
- }
-
- /* MD5 value of body part; we can get away with NIL'ing this */
- IAPuts("NIL ");
-
- /* Disposition */
- if ((disp == NULL) || IsEmptyStr(disp)) {
- IAPuts("NIL");
- }
- else {
- IAPuts("(");
- IPutStr(disp, strlen(disp));
- if ((filename != NULL) && (!IsEmptyStr(filename))) {
- IAPuts(" (\"FILENAME\" ");
- IPutStr(filename, strlen(filename));
- IAPuts(")");
- }
- IAPuts(")");
- }
-
- /* Body language (not defined yet) */
- IAPuts(" NIL)");
-}
-
-
-
-/*
- * Spew the BODYSTRUCTURE data for a message.
- *
- */
-void imap_fetch_bodystructure (long msgnum, const char *item,
- struct CtdlMessage *msg) {
- const char *rfc822 = NULL;
- const char *rfc822_body = NULL;
- size_t rfc822_len;
- size_t rfc822_headers_len;
- size_t rfc822_body_len;
- const char *ptr = NULL;
- char *pch;
- char buf[SIZ];
- int lines = 0;
-
- /* Handle NULL message gracefully */
- if (msg == NULL) {
- IAPuts("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
- "(\"CHARSET\" \"US-ASCII\") NIL NIL "
- "\"7BIT\" 0 0)");
- return;
- }
-
- /* For non-RFC822 (ordinary Citadel) messages, this is short and
- * sweet...
- */
- if (msg->cm_format_type != FMT_RFC822) {
-
- /* *sigh* We have to RFC822-format the message just to be able
- * to measure it. FIXME use smi cached fields if possible
- */
-
- CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
- CtdlOutputPreLoadedMsg(msg, MT_RFC822, 0, 0, 1, SUPPRESS_ENV_TO);
- rfc822_len = StrLength(CC->redirect_buffer);
- rfc822 = pch = SmashStrBuf(&CC->redirect_buffer);
-
- ptr = rfc822;
- do {
- ptr = cmemreadline(ptr, buf, sizeof buf);
- ++lines;
- if ((IsEmptyStr(buf)) && (rfc822_body == NULL)) {
- rfc822_body = ptr;
- }
- } while (*ptr != 0);
-
- rfc822_headers_len = rfc822_body - rfc822;
- rfc822_body_len = rfc822_len - rfc822_headers_len;
- free(pch);
-
- IAPuts("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
- "(\"CHARSET\" \"US-ASCII\") NIL NIL "
- "\"7BIT\" ");
- IAPrintf(SIZE_T_FMT " %d)", rfc822_body_len, lines);
-
- return;
- }
-
- /* For messages already stored in RFC822 format, we have to parse. */
- IAPuts("BODYSTRUCTURE ");
- mime_parser(CM_RANGE(msg, eMesageText),
- *imap_fetch_bodystructure_part, /* part */
- *imap_fetch_bodystructure_pre, /* pre-multi */
- *imap_fetch_bodystructure_post, /* post-multi */
- NULL,
- 1); /* don't decode -- we want it as-is */
-}
-
-
-/*
- * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
- * individual message, once it has been selected for output.
- */
-void imap_do_fetch_msg(int seq, citimap_command *Cmd) {
- int i;
- citimap *Imap = IMAP;
- struct CtdlMessage *msg = NULL;
- int body_loaded = 0;
-
- /* Don't attempt to fetch bogus messages or UID's */
- if (seq < 1) return;
- if (Imap->msgids[seq-1] < 1L) return;
-
- buffer_output();
- IAPrintf("* %d FETCH (", seq);
-
- for (i=0; i<Cmd->num_parms; ++i) {
-
- /* Fetchable without going to the message store at all */
- if (!strcasecmp(Cmd->Params[i].Key, "UID")) {
- imap_fetch_uid(seq);
- }
- else if (!strcasecmp(Cmd->Params[i].Key, "FLAGS")) {
- imap_fetch_flags(seq-1);
- }
-
- /* Potentially fetchable from cache, if the client requests
- * stuff from the same message several times in a row.
- */
- else if (!strcasecmp(Cmd->Params[i].Key, "RFC822")) {
- imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
- }
- else if (!strcasecmp(Cmd->Params[i].Key, "RFC822.HEADER")) {
- imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
- }
- else if (!strcasecmp(Cmd->Params[i].Key, "RFC822.SIZE")) {
- imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
- }
- else if (!strcasecmp(Cmd->Params[i].Key, "RFC822.TEXT")) {
- imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
- }
-
- /* BODY fetches do their own fetching and caching too. */
- else if (!strncasecmp(Cmd->Params[i].Key, "BODY[", 5)) {
- imap_fetch_body(Imap->msgids[seq-1], Cmd->Params[i], 0);
- }
- else if (!strncasecmp(Cmd->Params[i].Key, "BODY.PEEK[", 10)) {
- imap_fetch_body(Imap->msgids[seq-1], Cmd->Params[i], 1);
- }
-
- /* Otherwise, load the message into memory.
- */
- else if (!strcasecmp(Cmd->Params[i].Key, "BODYSTRUCTURE")) {
- if ((msg != NULL) && (!body_loaded)) {
- CM_Free(msg); /* need the whole thing */
- msg = NULL;
- }
- if (msg == NULL) {
- msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
- body_loaded = 1;
- }
- imap_fetch_bodystructure(Imap->msgids[seq-1],
- Cmd->Params[i].Key, msg);
- }
- else if (!strcasecmp(Cmd->Params[i].Key, "ENVELOPE")) {
- if (msg == NULL) {
- msg = CtdlFetchMessage(Imap->msgids[seq-1], 0);
- body_loaded = 0;
- }
- imap_fetch_envelope(msg);
- }
- else if (!strcasecmp(Cmd->Params[i].Key, "INTERNALDATE")) {
- if (msg == NULL) {
- msg = CtdlFetchMessage(Imap->msgids[seq-1], 0);
- body_loaded = 0;
- }
- imap_fetch_internaldate(msg);
- }
-
- if (i != Cmd->num_parms-1) IAPuts(" ");
- }
-
- IAPuts(")\r\n");
- unbuffer_output();
- if (msg != NULL) {
- CM_Free(msg);
- }
-}
-
-
-
-/*
- * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
- * validated and boiled down the request a bit.
- */
-void imap_do_fetch(citimap_command *Cmd) {
- citimap *Imap = IMAP;
- int i;
-#if 0
-/* debug output the parsed vector */
- {
- int i;
- syslog(LOG_DEBUG, "imap: ----- %ld params", Cmd->num_parms);
-
- for (i=0; i < Cmd->num_parms; i++) {
- if (Cmd->Params[i].len != strlen(Cmd->Params[i].Key))
- syslog(LOG_DEBUG, "imap: *********** %ld != %ld : %s",
- Cmd->Params[i].len,
- strlen(Cmd->Params[i].Key),
- Cmd->Params[i].Key);
- else
- syslog(LOG_DEBUG, "imap: %ld : %s",
- Cmd->Params[i].len,
- Cmd->Params[i].Key);
- }}
-
-#endif
-
- if (Imap->num_msgs > 0) {
- for (i = 0; i < Imap->num_msgs; ++i) {
-
- /* Abort the fetch loop if the session breaks.
- * This is important for users who keep mailboxes
- * that are too big *and* are too impatient to
- * let them finish loading. :)
- */
- if (CC->kill_me) return;
-
- /* Get any message marked for fetch. */
- if (Imap->flags[i] & IMAP_SELECTED) {
- imap_do_fetch_msg(i+1, Cmd);
- }
- }
- }
-}
-
-
-
-/*
- * Back end for imap_handle_macros()
- * Note that this function *only* looks at the beginning of the string. It
- * is not a generic search-and-replace function.
- */
-void imap_macro_replace(StrBuf *Buf, long where,
- StrBuf *TmpBuf,
- char *find, long findlen,
- char *replace, long replacelen)
-{
-
- if (StrLength(Buf) - where > findlen)
- return;
-
- if (!strncasecmp(ChrPtr(Buf) + where, find, findlen)) {
- if (ChrPtr(Buf)[where + findlen] == ' ') {
- StrBufPlain(TmpBuf, replace, replacelen);
- StrBufAppendBufPlain(TmpBuf, HKEY(" "), 0);
- StrBufReplaceToken(Buf, where, findlen,
- SKEY(TmpBuf));
- }
- if (where + findlen == StrLength(Buf)) {
- StrBufReplaceToken(Buf, where, findlen,
- replace, replacelen);
- }
- }
-}
-
-
-
-/*
- * Handle macros embedded in FETCH data items.
- * (What the heck are macros doing in a wire protocol? Are we trying to save
- * the computer at the other end the trouble of typing a lot of characters?)
- */
-void imap_handle_macros(citimap_command *Cmd) {
- long i;
- int nest = 0;
- StrBuf *Tmp = NewStrBuf();
-
- for (i=0; i < StrLength(Cmd->CmdBuf); ++i) {
- char ch = ChrPtr(Cmd->CmdBuf)[i];
- if ((ch=='(') ||
- (ch=='[') ||
- (ch=='<') ||
- (ch=='{')) ++nest;
- else if ((ch==')') ||
- (ch==']') ||
- (ch=='>') ||
- (ch=='}')) --nest;
-
- if (nest <= 0) {
- imap_macro_replace(Cmd->CmdBuf, i,
- Tmp,
- HKEY("ALL"),
- HKEY("FLAGS INTERNALDATE RFC822.SIZE ENVELOPE")
- );
- imap_macro_replace(Cmd->CmdBuf, i,
- Tmp,
- HKEY("BODY"),
- HKEY("BODYSTRUCTURE")
- );
- imap_macro_replace(Cmd->CmdBuf, i,
- Tmp,
- HKEY("FAST"),
- HKEY("FLAGS INTERNALDATE RFC822.SIZE")
- );
- imap_macro_replace(Cmd->CmdBuf, i,
- Tmp,
- HKEY("FULL"),
- HKEY("FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY")
- );
- }
- }
- FreeStrBuf(&Tmp);
-}
-
-
-/*
- * Break out the data items requested, possibly a parenthesized list.
- * Returns the number of data items, or -1 if the list is invalid.
- * NOTE: this function alters the string it is fed, and uses it as a buffer
- * to hold the data for the pointers it returns.
- */
-int imap_extract_data_items(citimap_command *Cmd)
-{
- int nArgs;
- int nest = 0;
- const char *pch, *end;
-
- /* Convert all whitespace to ordinary space characters. */
- pch = ChrPtr(Cmd->CmdBuf);
- end = pch + StrLength(Cmd->CmdBuf);
-
- while (pch < end)
- {
- if (isspace(*pch))
- StrBufPeek(Cmd->CmdBuf, pch, 0, ' ');
- pch++;
- }
-
- /* Strip leading and trailing whitespace, then strip leading and
- * trailing parentheses if it's a list
- */
- StrBufTrim(Cmd->CmdBuf);
- pch = ChrPtr(Cmd->CmdBuf);
- if ( (pch[0]=='(') &&
- (pch[StrLength(Cmd->CmdBuf)-1]==')') )
- {
- StrBufCutRight(Cmd->CmdBuf, 1);
- StrBufCutLeft(Cmd->CmdBuf, 1);
- StrBufTrim(Cmd->CmdBuf);
- }
-
- /* Parse any macro data items */
- imap_handle_macros(Cmd);
-
- /*
- * Now break out the data items. We throw in one trailing space in
- * order to avoid having to break out the last one manually.
- */
- nArgs = StrLength(Cmd->CmdBuf) / 10 + 10;
- nArgs = CmdAdjust(Cmd, nArgs, 0);
- Cmd->num_parms = 0;
- Cmd->Params[Cmd->num_parms].Key = pch = ChrPtr(Cmd->CmdBuf);
- end = Cmd->Params[Cmd->num_parms].Key + StrLength(Cmd->CmdBuf);
-
- while (pch < end)
- {
- if ((*pch=='(') ||
- (*pch=='[') ||
- (*pch=='<') ||
- (*pch=='{'))
- ++nest;
-
- else if ((*pch==')') ||
- (*pch==']') ||
- (*pch=='>') ||
- (*pch=='}'))
- --nest;
-
- if ((nest <= 0) && (*pch==' ')) {
- StrBufPeek(Cmd->CmdBuf, pch, 0, '\0');
- Cmd->Params[Cmd->num_parms].len =
- pch - Cmd->Params[Cmd->num_parms].Key;
-
- if (Cmd->num_parms + 1 >= Cmd->avail_parms) {
- nArgs = CmdAdjust(Cmd, nArgs * 2, 1);
- }
- Cmd->num_parms++;
- Cmd->Params[Cmd->num_parms].Key = ++pch;
- }
- else if (pch + 1 == end) {
- Cmd->Params[Cmd->num_parms].len =
- pch - Cmd->Params[Cmd->num_parms].Key + 1;
-
- Cmd->num_parms++;
- }
- pch ++;
- }
- return Cmd->num_parms;
-
-}
-
-
-/*
- * One particularly hideous aspect of IMAP is that we have to allow the client
- * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
- * handles this by setting the IMAP_SELECTED flag for each message specified in
- * the ranges/sets, then looping through the message array, outputting messages
- * with the flag set. We don't bother returning an error if an out-of-range
- * number is specified (we just return quietly) because any client braindead
- * enough to request a bogus message number isn't going to notice the
- * difference anyway.
- *
- * This function clears out the IMAP_SELECTED bits, then sets that bit for each
- * message included in the specified range.
- *
- * Set is_uid to 1 to fetch by UID instead of sequence number.
- */
-void imap_pick_range(const char *supplied_range, int is_uid) {
- citimap *Imap = IMAP;
- int i;
- int num_sets;
- int s;
- char setstr[SIZ], lostr[SIZ], histr[SIZ];
- long lo, hi;
- char actual_range[SIZ];
-
- /*
- * Handle the "ALL" macro
- */
- if (!strcasecmp(supplied_range, "ALL")) {
- safestrncpy(actual_range, "1:*", sizeof actual_range);
- }
- else {
- safestrncpy(actual_range, supplied_range, sizeof actual_range);
- }
-
- /*
- * Clear out the IMAP_SELECTED flags for all messages.
- */
- for (i = 0; i < Imap->num_msgs; ++i) {
- Imap->flags[i] = Imap->flags[i] & ~IMAP_SELECTED;
- }
-
- /*
- * Now set it for all specified messages.
- */
- num_sets = num_tokens(actual_range, ',');
- for (s=0; s<num_sets; ++s) {
- extract_token(setstr, actual_range, s, ',', sizeof setstr);
-
- extract_token(lostr, setstr, 0, ':', sizeof lostr);
- if (num_tokens(setstr, ':') >= 2) {
- extract_token(histr, setstr, 1, ':', sizeof histr);
- if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
- }
- else {
- safestrncpy(histr, lostr, sizeof histr);
- }
- lo = atol(lostr);
- hi = atol(histr);
-
- /* Loop through the array, flipping bits where appropriate */
- for (i = 1; i <= Imap->num_msgs; ++i) {
- if (is_uid) { /* fetch by sequence number */
- if ( (Imap->msgids[i-1]>=lo)
- && (Imap->msgids[i-1]<=hi)) {
- Imap->flags[i-1] |= IMAP_SELECTED;
- }
- }
- else { /* fetch by uid */
- if ( (i>=lo) && (i<=hi)) {
- Imap->flags[i-1] |= IMAP_SELECTED;
- }
- }
- }
- }
-}
-
-
-
-/*
- * This function is called by the main command loop.
- */
-void imap_fetch(int num_parms, ConstStr *Params) {
- citimap_command Cmd;
- int num_items;
-
- if (num_parms < 4) {
- IReply("BAD invalid parameters");
- return;
- }
-
- imap_pick_range(Params[2].Key, 0);
-
- memset(&Cmd, 0, sizeof(citimap_command));
- Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
- MakeStringOf(Cmd.CmdBuf, 3);
-
- num_items = imap_extract_data_items(&Cmd);
- if (num_items < 1) {
- IReply("BAD invalid data item list");
- FreeStrBuf(&Cmd.CmdBuf);
- free(Cmd.Params);
- return;
- }
-
- imap_do_fetch(&Cmd);
- IReply("OK FETCH completed");
- FreeStrBuf(&Cmd.CmdBuf);
- free(Cmd.Params);
-}
-
-/*
- * This function is called by the main command loop.
- */
-void imap_uidfetch(int num_parms, ConstStr *Params) {
- citimap_command Cmd;
- int num_items;
- int i;
- int have_uid_item = 0;
-
- if (num_parms < 5) {
- IReply("BAD invalid parameters");
- return;
- }
-
- imap_pick_range(Params[3].Key, 1);
-
- memset(&Cmd, 0, sizeof(citimap_command));
- Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
-
- MakeStringOf(Cmd.CmdBuf, 4);
-#if 0
- syslog(LOG_DEBUG, "imap: -------%s--------", ChrPtr(Cmd.CmdBuf));
-#endif
- num_items = imap_extract_data_items(&Cmd);
- if (num_items < 1) {
- IReply("BAD invalid data item list");
- FreeStrBuf(&Cmd.CmdBuf);
- free(Cmd.Params);
- return;
- }
-
- /* If the "UID" item was not included, we include it implicitly
- * (at the beginning) because this is a UID FETCH command
- */
- for (i=0; i<num_items; ++i) {
- if (!strcasecmp(Cmd.Params[i].Key, "UID")) ++have_uid_item;
- }
- if (have_uid_item == 0) {
- if (Cmd.num_parms + 1 >= Cmd.avail_parms)
- CmdAdjust(&Cmd, Cmd.avail_parms + 1, 1);
- memmove(&Cmd.Params[1],
- &Cmd.Params[0],
- sizeof(ConstStr) * Cmd.num_parms);
-
- Cmd.num_parms++;
- Cmd.Params[0] = (ConstStr){HKEY("UID")};
- }
-
- imap_do_fetch(&Cmd);
- IReply("OK UID FETCH completed");
- FreeStrBuf(&Cmd.CmdBuf);
- free(Cmd.Params);
-}
-
-
+++ /dev/null
-/*
- * Copyright (c) 2001-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.
- */
-
-void imap_pick_range(const char *range, int is_uid);
-void imap_fetch(int num_parms, ConstStr *Params);
-void imap_uidfetch(int num_parms, ConstStr *Params);
-void imap_fetch_flags(int seq);
-int imap_extract_data_items(citimap_command *Cmd);
+++ /dev/null
-/*
- * Implements the LIST and LSUB commands.
- *
- * Copyright (c) 2000-2017 by Art Cancro and others.
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "sysdep_decls.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "serv_imap.h"
-#include "imap_tools.h"
-#include "imap_fetch.h"
-#include "imap_search.h"
-#include "imap_store.h"
-#include "imap_acl.h"
-#include "imap_misc.h"
-#include "imap_list.h"
-#include "ctdl_module.h"
-
-
-typedef struct __ImapRoomListFilter {
- char verb[16];
- int subscribed_rooms_only;
- int return_subscribed;
- int return_children;
-
- int num_patterns;
- int num_patterns_avail;
- StrBuf **patterns;
-}ImapRoomListFilter;
-
-/*
- * Used by LIST and LSUB to show the floors in the listing
- */
-void imap_list_floors(char *verb, int num_patterns, StrBuf **patterns)
-{
- int i;
- struct floor *fl;
- int j = 0;
- int match = 0;
-
- for (i = 0; i < MAXFLOORS; ++i) {
- fl = CtdlGetCachedFloor(i);
- if (fl->f_flags & F_INUSE) {
- match = 0;
- for (j=0; j<num_patterns; ++j) {
- if (imap_mailbox_matches_pattern (ChrPtr(patterns[j]), fl->f_name)) {
- match = 1;
- }
- }
- if (match) {
- IAPrintf("* %s (\\NoSelect \\HasChildren) \"/\" ", verb);
- IPutStr(fl->f_name, (fl->f_name)?strlen(fl->f_name):0);
- IAPuts("\r\n");
- }
- }
- }
-}
-
-
-/*
- * Back end for imap_list()
- *
- * Implementation note: IMAP "subscribed folder" is equivalent to Citadel "known room"
- *
- * The "user data" field is actually an array of pointers; see below for the breakdown
- *
- */
-void imap_listroom(struct ctdlroom *qrbuf, void *data)
-{
-#define SUBSCRIBED_STR "\\Subscribed"
-#define HASCHILD_STR "\\HasChildren"
- char MailboxName[SIZ];
- char return_options[256];
- int ra;
- int yes_output_this_room;
- ImapRoomListFilter *ImapFilter;
- int i = 0;
- int match = 0;
- int ROLen;
-
- /* Here's how we break down the array of pointers passed to us */
- ImapFilter = (ImapRoomListFilter*)data;
-
- /* Only list rooms to which the user has access!! */
- yes_output_this_room = 0;
- *return_options = '\0';
- ROLen = 0;
- CtdlRoomAccess(qrbuf, &CC->user, &ra, NULL);
-
- if (ImapFilter->return_subscribed) {
- if (ra & UA_KNOWN) {
- memcpy(return_options, HKEY(SUBSCRIBED_STR) + 1);
- ROLen += sizeof(SUBSCRIBED_STR) - 1;
- }
- }
-
- /* Warning: ugly hack.
- * We don't have any way to determine the presence of child mailboxes
- * without refactoring this entire module. So we're just going to return
- * the \HasChildren attribute for every room.
- * We'll fix this later when we have time.
- */
- if (ImapFilter->return_children) {
- if (!IsEmptyStr(return_options)) {
- memcpy(return_options + ROLen, HKEY(" "));
- ROLen ++;
- }
- memcpy(return_options + ROLen, HKEY(SUBSCRIBED_STR) + 1);
- }
-
- if (ImapFilter->subscribed_rooms_only) {
- if (ra & UA_KNOWN) {
- yes_output_this_room = 1;
- }
- }
- else {
- if ((ra & UA_KNOWN) || ((ra & UA_GOTOALLOWED) && (ra & UA_ZAPPED))) {
- yes_output_this_room = 1;
- }
- }
-
- if (yes_output_this_room) {
- long len;
- len = imap_mailboxname(MailboxName, sizeof MailboxName, qrbuf);
- match = 0;
- for (i=0; i<ImapFilter->num_patterns; ++i) {
- if (imap_mailbox_matches_pattern(ChrPtr(ImapFilter->patterns[i]), MailboxName)) {
- match = 1;
- }
- }
- if (match) {
- IAPrintf("* %s (%s) \"/\" ", ImapFilter->verb, return_options);
- IPutStr(MailboxName, len);
- IAPuts("\r\n");
- }
- }
-}
-
-
-/*
- * Implements the LIST and LSUB commands
- */
-void imap_list(int num_parms, ConstStr *Params)
-{
- struct CitContext *CCC = CC;
- citimap *Imap = CCCIMAP;
- int i, j, paren_nest;
- ImapRoomListFilter ImapFilter;
- int selection_left = (-1);
- int selection_right = (-1);
- int return_left = (-1);
- int root_pos = 2;
- int patterns_left = 3;
- int patterns_right = 3;
- int extended_list_in_use = 0;
-
- if (num_parms < 4) {
- IReply("BAD arguments invalid");
- return;
- }
-
- ImapFilter.num_patterns = 1;
- ImapFilter.return_subscribed = 0;
- ImapFilter.return_children = 0;
- ImapFilter.subscribed_rooms_only = 0;
-
-
- /* parms[1] is the IMAP verb being used (e.g. LIST or LSUB)
- * This tells us how to behave, and what verb to return back to the caller
- */
- safestrncpy(ImapFilter.verb, Params[1].Key, sizeof ImapFilter.verb);
- j = Params[1].len;
- for (i=0; i<j; ++i) {
- ImapFilter.verb[i] = toupper(ImapFilter.verb[i]);
- }
-
- if (!strcasecmp(ImapFilter.verb, "LSUB")) {
- ImapFilter.subscribed_rooms_only = 1;
- }
-
- /*
- * Partial implementation of LIST-EXTENDED (which will not get used because
- * we don't advertise it in our capabilities string). Several requirements:
- *
- * Extraction of selection options:
- * SUBSCRIBED option: done
- * RECURSIVEMATCH option: not done yet
- * REMOTE: safe to silently ignore
- *
- * Extraction of return options:
- * SUBSCRIBED option: done
- * CHILDREN option: done, but needs a non-ugly rewrite
- *
- * Multiple match patterns: done
- */
-
- /*
- * If parameter 2 begins with a '(' character, the client is specifying
- * selection options. Extract their exact position, and then modify our
- * expectation of where the root folder will be specified.
- */
- if (Params[2].Key[0] == '(') {
- extended_list_in_use = 1;
- selection_left = 2;
- paren_nest = 0;
- for (i=2; i<num_parms; ++i) {
- for (j=0; Params[i].Key[j]; ++j) {
- if (Params[i].Key[j] == '(') ++paren_nest;
- if (Params[i].Key[j] == ')') --paren_nest;
- }
- if (paren_nest == 0) {
- selection_right = i; /* found end of selection options */
- root_pos = i+1; /* folder root appears after selection options */
- i = num_parms + 1; /* break out of the loop */
- }
- }
- }
-
- /* If selection options were found, do something with them.
- */
- if ((selection_left > 0) && (selection_right >= selection_left)) {
-
- /* Strip off the outer parentheses */
- if (Params[selection_left].Key[0] == '(') {
- TokenCutLeft(&Imap->Cmd,
- &Params[selection_left],
- 1);
- }
- if (Params[selection_right].Key[Params[selection_right].len-1] == ')') {
- TokenCutRight(&Imap->Cmd,
- &Params[selection_right],
- 1);
- }
-
- for (i=selection_left; i<=selection_right; ++i) {
-
- if (!strcasecmp(Params[i].Key, "SUBSCRIBED")) {
- ImapFilter.subscribed_rooms_only = 1;
- }
-
- else if (!strcasecmp(Params[i].Key, "RECURSIVEMATCH")) {
- /* FIXME - do this! */
- }
-
- }
-
- }
-
- /* The folder root appears immediately after the selection options,
- * or in position 2 if no selection options were specified.
- */
- ImapFilter.num_patterns_avail = num_parms + 1;
- ImapFilter.patterns = malloc(ImapFilter.num_patterns_avail * sizeof(StrBuf*));
- memset(ImapFilter.patterns, 0, ImapFilter.num_patterns_avail * sizeof(StrBuf*));
-
- patterns_left = root_pos + 1;
- patterns_right = root_pos + 1;
-
- if (Params[patterns_left].Key[0] == '(') {
- extended_list_in_use = 1;
- paren_nest = 0;
- for (i=patterns_left; i<num_parms; ++i) {
- for (j=0; &Params[i].Key[j]; ++j) {
- if (Params[i].Key[j] == '(') ++paren_nest;
- if (Params[i].Key[j] == ')') --paren_nest;
- }
- if (paren_nest == 0) {
- patterns_right = i; /* found end of patterns */
- i = num_parms + 1; /* break out of the loop */
- }
- }
- ImapFilter.num_patterns = patterns_right - patterns_left + 1;
- for (i=0; i<ImapFilter.num_patterns; ++i) {
- if (i < MAX_PATTERNS) {
- ImapFilter.patterns[i] = NewStrBufPlain(NULL,
- Params[root_pos].len +
- Params[patterns_left+i].len);
- if (i == 0) {
- if (Params[root_pos].len > 1)
- StrBufAppendBufPlain(ImapFilter.patterns[i],
- 1 + CKEY(Params[root_pos]) - 1, 0);
- }
- else
- StrBufAppendBufPlain(ImapFilter.patterns[i],
- CKEY(Params[root_pos]), 0);
-
- if (i == ImapFilter.num_patterns-1) {
- if (Params[patterns_left+i].len > 1)
- StrBufAppendBufPlain(ImapFilter.patterns[i],
- CKEY(Params[patterns_left+i]) - 1, 0);
- }
- else StrBufAppendBufPlain(ImapFilter.patterns[i],
- CKEY(Params[patterns_left+i]), 0);
-
- }
-
- }
- }
- else {
- ImapFilter.num_patterns = 1;
- ImapFilter.patterns[0] = NewStrBufPlain(NULL,
- Params[root_pos].len +
- Params[patterns_left].len);
- StrBufAppendBufPlain(ImapFilter.patterns[0],
- CKEY(Params[root_pos]), 0);
- StrBufAppendBufPlain(ImapFilter.patterns[0],
- CKEY(Params[patterns_left]), 0);
- }
-
- /* If the word "RETURN" appears after the folder pattern list, then the client
- * is specifying return options.
- */
- if (num_parms - patterns_right > 2) if (!strcasecmp(Params[patterns_right+1].Key, "RETURN")) {
- return_left = patterns_right + 2;
- extended_list_in_use = 1;
- paren_nest = 0;
- for (i=return_left; i<num_parms; ++i) {
- for (j=0; Params[i].Key[j]; ++j) {
- if (Params[i].Key[j] == '(') ++paren_nest;
- if (Params[i].Key[j] == ')') --paren_nest;
- }
-
- /* Might as well look for these while we're in here... */
- if (Params[i].Key[0] == '(')
- TokenCutLeft(&Imap->Cmd,
- &Params[i],
- 1);
- if (Params[i].Key[Params[i].len-1] == ')')
- TokenCutRight(&Imap->Cmd,
- &Params[i],
- 1);
-
- syslog(LOG_DEBUG, "evaluating <%s>", Params[i].Key);
-
- if (!strcasecmp(Params[i].Key, "SUBSCRIBED")) {
- ImapFilter.return_subscribed = 1;
- }
-
- else if (!strcasecmp(Params[i].Key, "CHILDREN")) {
- ImapFilter.return_children = 1;
- }
-
- if (paren_nest == 0) {
- i = num_parms + 1; /* break out of the loop */
- }
- }
- }
-
- /* Now start setting up the data we're going to send to the CtdlForEachRoom() callback.
- */
-
- /* The non-extended LIST command is required to treat an empty
- * ("" string) mailbox name argument as a special request to return the
- * hierarchy delimiter and the root name of the name given in the
- * reference parameter.
- */
- if ( (StrLength(ImapFilter.patterns[0]) == 0) && (extended_list_in_use == 0) ) {
- IAPrintf("* %s (\\Noselect) \"/\" \"\"\r\n", ImapFilter.verb);
- }
-
- /* Non-empty mailbox names, and any form of the extended LIST command,
- * is handled by this loop.
- */
- else {
- imap_list_floors(ImapFilter.verb,
- ImapFilter.num_patterns,
- ImapFilter.patterns);
- CtdlForEachRoom(imap_listroom, (char**)&ImapFilter);
- }
-
- /*
- * Free the pattern buffers we allocated above.
- */
- for (i=0; i<ImapFilter.num_patterns; ++i) {
- FreeStrBuf(&ImapFilter.patterns[i]);
- }
- free(ImapFilter.patterns);
-
- IReplyPrintf("OK %s completed", ImapFilter.verb);
-}
+++ /dev/null
-
-/*
- * In the extended form of LIST the client is allowed to specify
- * multiple match patterns. How many will we allow?
- */
-#define MAX_PATTERNS 20
-
-void imap_list(int num_parms, ConstStr *Params);
+++ /dev/null
-/*
- * IMAP METADATA extension
- *
- * This is an implementation of the Bynari variant of the METADATA extension.
- *
- * Copyright (c) 2007-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 as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "sysdep_decls.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "serv_imap.h"
-#include "imap_tools.h"
-#include "imap_fetch.h"
-#include "imap_misc.h"
-#include "genstamp.h"
-
-#include "ctdl_module.h"
-
-/*
- * Implements the SETMETADATA command.
- *
- * Again, the only thing we're interested in setting here is the folder type.
- *
- * Attempting to set anything else calls a stub which fools the client into
- * thinking that there is no remaining space available to store annotations.
- */
-void imap_setmetadata(int num_parms, ConstStr *Params) {
- char roomname[ROOMNAMELEN];
- char savedroom[ROOMNAMELEN];
- int msgs, new;
- int ret;
- int setting_user_value = 0;
- char set_value[32];
- int set_view = VIEW_BBS;
- visit vbuf;
-
- if (num_parms != 6) {
- IReply("BAD usage error");
- return;
- }
-
- /*
- * Don't allow other types of metadata to be set
- */
- if (strcasecmp(Params[3].Key, "/vendor/kolab/folder-type")) {
- IReply("NO [METADATA TOOMANY] SETMETADATA failed");
- return;
- }
-
- if (!strcasecmp(Params[4].Key, "(value.shared")) {
- setting_user_value = 0; /* global view */
- }
- else if (!strcasecmp(Params[4].Key, "(value.priv")) {
- setting_user_value = 1; /* per-user view */
- }
- else {
- IReply("NO [METADATA TOOMANY] SETMETADATA failed");
- return;
- }
-
- /*
- * Extract the folder type without any parentheses. Then learn
- * the Citadel view type based on the supplied folder type.
- */
- extract_token(set_value, Params[5].Key, 0, ')', sizeof set_value);
- if (!strncasecmp(set_value, "mail", 4)) {
- set_view = VIEW_MAILBOX;
- }
- else if (!strncasecmp(set_value, "event", 5)) {
- set_view = VIEW_CALENDAR;
- }
- else if (!strncasecmp(set_value, "contact", 7)) {
- set_view = VIEW_ADDRESSBOOK;
- }
- else if (!strncasecmp(set_value, "journal", 7)) {
- set_view = VIEW_JOURNAL;
- }
- else if (!strncasecmp(set_value, "note", 4)) {
- set_view = VIEW_NOTES;
- }
- else if (!strncasecmp(set_value, "task", 4)) {
- set_view = VIEW_TASKS;
- }
- else {
- set_view = VIEW_MAILBOX;
- }
-
- ret = imap_grabroom(roomname, Params[2].Key, 1);
- if (ret != 0) {
- IReply("NO Invalid mailbox name or access denied");
- return;
- }
-
- /*
- * CtdlUserGoto() formally takes us to the desired room. (If another
- * folder is selected, save its name so we can return there!!!!!)
- */
- if (IMAP->selected) {
- strcpy(savedroom, CC->room.QRname);
- }
- CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
-
- /*
- * Always set the per-user view to the requested one.
- */
- CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
- vbuf.v_view = set_view;
- CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
-
- /* If this is a "value.priv" set operation, we're done. */
-
- if (setting_user_value)
- {
- IReply("OK SETANNOTATION complete");
- }
-
- /* If this is a "value.shared" set operation, we are allowed to perform it
- * under certain conditions.
- */
- else if ( (is_room_aide()) /* aide or room aide */
- || ( (CC->room.QRflags & QR_MAILBOX)
- && (CC->user.usernum == atol(CC->room.QRname)) /* mailbox owner */
- )
- || (msgs == 0) /* hack: if room is empty, assume we just created it */
- ) {
- CtdlGetRoomLock(&CC->room, CC->room.QRname);
- CC->room.QRdefaultview = set_view;
- CtdlPutRoomLock(&CC->room);
- IReply("OK SETANNOTATION complete");
- }
-
- /* If we got to this point, we don't have permission to set the default view. */
- else {
- IReply("NO [METADATA TOOMANY] SETMETADATA failed");
- }
-
- /*
- * If a different folder was previously selected, return there now.
- */
- if ( (IMAP->selected) && (strcasecmp(roomname, savedroom)) ) {
- CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
- }
- return;
-}
-
-
-/*
- * Implements the GETMETADATA command.
- *
- * Regardless of what the client asked for, we are going to supply them with
- * the folder type. It's the only metadata we have anyway.
- */
-void imap_getmetadata(int num_parms, ConstStr *Params) {
- char roomname[ROOMNAMELEN];
- char savedroom[ROOMNAMELEN];
- int msgs, new;
- int ret;
- int found = 0;
-
-/* this doesn't work if you have rooms/floors with spaces.
- we need this for the bynari connector.
- if (num_parms > 5) {
- IReply("BAD usage error");
- return;
- }
-*/
- ret = imap_grabroom(roomname, Params[2].Key, 1);
- if (ret != 0) {
- IReply("NO Invalid mailbox name or access denied");
- return;
- }
-
- /*
- * CtdlUserGoto() formally takes us to the desired room. (If another
- * folder is selected, save its name so we can return there!!!!!)
- */
- if (IMAP->selected) {
- strcpy(savedroom, CC->room.QRname);
- }
- CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
-
- IAPuts("* METADATA ");
- IPutCParamStr(2);
- IAPuts(" \"/vendor/kolab/folder-type\" (\"value.shared\" \"");
-
- /* If it's one of our hard-coded default rooms, we know what to do... */
-
- if (CC->room.QRname[10] == '.')
- {
- if (!strcasecmp(&CC->room.QRname[11], MAILROOM)) {
- found = 1;
- IAPuts("mail.inbox");
- }
- else if (!strcasecmp(&CC->room.QRname[11], SENTITEMS)) {
- found = 1;
- IAPuts("mail.sentitems");
- }
- else if (!strcasecmp(&CC->room.QRname[11], USERDRAFTROOM)) {
- found = 1;
- IAPuts("mail.drafts");
- }
- else if (!strcasecmp(&CC->room.QRname[11], USERCALENDARROOM)) {
- found = 1;
- IAPuts("event.default");
- }
- else if (!strcasecmp(&CC->room.QRname[11], USERCONTACTSROOM)) {
- found = 1;
- IAPuts("contact.default");
- }
- else if (!strcasecmp(&CC->room.QRname[11], USERNOTESROOM)) {
- found = 1;
- IAPuts("note.default");
- }
- else if (!strcasecmp(&CC->room.QRname[11], USERTASKSROOM)) {
- found = 1;
- IAPuts("task.default");
- }
- }
-
- /* Otherwise, use the view for this room to determine the type of data.
- * We are going with the default view rather than the user's view, because
- * the default view almost always defines the actual contents, while the
- * user's view might only make changes to presentation. It also saves us
- * an extra database access because we don't need to load the visit record.
- */
- if (!found)
- {
- if (CC->room.QRdefaultview == VIEW_CALENDAR) {
- IAPuts("event");
- }
- else if (CC->room.QRdefaultview == VIEW_ADDRESSBOOK) {
- IAPuts("contact");
- }
- else if (CC->room.QRdefaultview == VIEW_TASKS) {
- IAPuts("task");
- }
- else if (CC->room.QRdefaultview == VIEW_NOTES) {
- IAPuts("note");
- }
- else if (CC->room.QRdefaultview == VIEW_JOURNAL) {
- IAPuts("journal");
- }
- }
- /* If none of the above conditions were met, consider it an ordinary mailbox. */
-
- if (!found) {
- IAPuts("mail");
- }
-
- /* "mail.outbox" and "junkemail" are not implemented. */
-
- IAPuts("\")\r\n");
-
- /*
- * If a different folder was previously selected, return there now.
- */
- if ( (IMAP->selected) && (strcasecmp(roomname, savedroom)) ) {
- CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
- }
-
- IReply("OK GETMETADATA complete");
- return;
-}
-
+++ /dev/null
-/*
- * Copyright (c) 2007-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.
- */
-
-void imap_getmetadata(int num_parms, ConstStr *Params);
-void imap_setmetadata(int num_parms, ConstStr *Params);
+++ /dev/null
-/*
- * Copyright (c) 1987-2020 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 as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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.
- */
-
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "sysdep_decls.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "room_ops.h"
-#include "internet_addressing.h"
-#include "serv_imap.h"
-#include "imap_tools.h"
-#include "imap_fetch.h"
-#include "imap_misc.h"
-#include "genstamp.h"
-#include "ctdl_module.h"
-
-
-
-
-
-/*
- * imap_copy() calls imap_do_copy() to do its actual work, once it's
- * validated and boiled down the request a bit. (returns 0 on success)
- */
-int imap_do_copy(const char *destination_folder) {
- citimap *Imap = IMAP;
- int i;
- char roomname[ROOMNAMELEN];
- struct ctdlroom qrbuf;
- long *selected_msgs = NULL;
- int num_selected = 0;
-
- if (Imap->num_msgs < 1) {
- return(0);
- }
-
- i = imap_grabroom(roomname, destination_folder, 1);
- if (i != 0) return(i);
-
- /*
- * Copy all the message pointers in one shot.
- */
- selected_msgs = malloc(sizeof(long) * Imap->num_msgs);
- if (selected_msgs == NULL) return(-1);
-
- for (i = 0; i < Imap->num_msgs; ++i) {
- if (Imap->flags[i] & IMAP_SELECTED) {
- selected_msgs[num_selected++] = Imap->msgids[i];
- }
- }
-
- if (num_selected > 0) {
- CtdlSaveMsgPointersInRoom(roomname, selected_msgs, num_selected, 1, NULL, 0);
- }
- free(selected_msgs);
-
- /* Don't bother wasting any more time if there were no messages. */
- if (num_selected == 0) {
- return(0);
- }
-
- /* Enumerate lists of messages for which flags are toggled */
- long *seen_yes = NULL;
- int num_seen_yes = 0;
- long *seen_no = NULL;
- int num_seen_no = 0;
- long *answ_yes = NULL;
- int num_answ_yes = 0;
- long *answ_no = NULL;
- int num_answ_no = 0;
-
- seen_yes = malloc(num_selected * sizeof(long));
- seen_no = malloc(num_selected * sizeof(long));
- answ_yes = malloc(num_selected * sizeof(long));
- answ_no = malloc(num_selected * sizeof(long));
-
- for (i = 0; i < Imap->num_msgs; ++i) {
- if (Imap->flags[i] & IMAP_SELECTED) {
- if (Imap->flags[i] & IMAP_SEEN) {
- seen_yes[num_seen_yes++] = Imap->msgids[i];
- }
- if ((Imap->flags[i] & IMAP_SEEN) == 0) {
- seen_no[num_seen_no++] = Imap->msgids[i];
- }
- if (Imap->flags[i] & IMAP_ANSWERED) {
- answ_yes[num_answ_yes++] = Imap->msgids[i];
- }
- if ((Imap->flags[i] & IMAP_ANSWERED) == 0) {
- answ_no[num_answ_no++] = Imap->msgids[i];
- }
- }
- }
-
- /* Set the flags... */
- i = CtdlGetRoom(&qrbuf, roomname);
- if (i == 0) {
- CtdlSetSeen(seen_yes, num_seen_yes, 1, ctdlsetseen_seen, NULL, &qrbuf);
- CtdlSetSeen(seen_no, num_seen_no, 0, ctdlsetseen_seen, NULL, &qrbuf);
- CtdlSetSeen(answ_yes, num_answ_yes, 1, ctdlsetseen_answered, NULL, &qrbuf);
- CtdlSetSeen(answ_no, num_answ_no, 0, ctdlsetseen_answered, NULL, &qrbuf);
- }
-
- free(seen_yes);
- free(seen_no);
- free(answ_yes);
- free(answ_no);
-
- return(0);
-}
-
-
-/*
- * Output the [COPYUID xxx yyy] response code required by RFC2359
- * to tell the client the UID's of the messages that were copied (if any).
- * We are assuming that the IMAP_SELECTED flag is still set on any relevant
- * messages in our source room. Since the Citadel system uses UID's that
- * are both globally unique and persistent across a room-to-room copy, we
- * can get this done quite easily.
- */
-void imap_output_copyuid_response(citimap *Imap) {
- int i;
- StrBuf *MsgsCopied = NewStrBuf();
-
- for (i = 0; i < Imap->num_msgs; ++i) {
- if (Imap->flags[i] & IMAP_SELECTED) {
- if (StrLength(MsgsCopied) > 0) {
- StrBufAppendBufPlain(MsgsCopied, HKEY(","), 0);
- }
- StrBufAppendPrintf(MsgsCopied, "%ld", Imap->msgids[i]);
- }
- }
-
- if (StrLength(MsgsCopied) > 0) {
- IAPrintf("[COPYUID %ld %s %s] ", GLOBAL_UIDVALIDITY_VALUE, ChrPtr(MsgsCopied), ChrPtr(MsgsCopied));
- }
-
- FreeStrBuf(&MsgsCopied);
-}
-
-
-/*
- * This function is called by the main command loop.
- */
-void imap_copy(int num_parms, ConstStr *Params) {
- int ret;
-
- if (num_parms != 4) {
- IReply("BAD invalid parameters");
- return;
- }
-
- if (imap_is_message_set(Params[2].Key)) {
- imap_pick_range(Params[2].Key, 0);
- }
- else {
- IReply("BAD invalid parameters");
- return;
- }
-
- ret = imap_do_copy(Params[3].Key);
- if (!ret) {
- IAPrintf("%s OK ", Params[0].Key);
- imap_output_copyuid_response(IMAP);
- IAPuts("COPY completed\r\n");
- }
- else {
- IReplyPrintf("NO COPY failed (error %d)", ret);
- }
-}
-
-/*
- * This function is called by the main command loop.
- */
-void imap_uidcopy(int num_parms, ConstStr *Params) {
-
- if (num_parms != 5) {
- IReply("BAD invalid parameters");
- return;
- }
-
- if (imap_is_message_set(Params[3].Key)) {
- imap_pick_range(Params[3].Key, 1);
- }
- else {
- IReply("BAD invalid parameters");
- return;
- }
-
- if (imap_do_copy(Params[4].Key) == 0) {
- IAPrintf("%s OK ", Params[0].Key);
- imap_output_copyuid_response(IMAP);
- IAPuts("UID COPY completed\r\n");
- }
- else {
- IReply("NO UID COPY failed");
- }
-}
-
-
-/*
- * imap_do_append_flags() is called by imap_append() to set any flags that
- * the client specified at append time.
- *
- * FIXME find a way to do these in bulk so we don't max out our db journal
- */
-void imap_do_append_flags(long new_msgnum, char *new_message_flags) {
- char flags[32];
- char this_flag[sizeof flags];
- int i;
-
- if (new_message_flags == NULL) return;
- if (IsEmptyStr(new_message_flags)) return;
-
- safestrncpy(flags, new_message_flags, sizeof flags);
-
- for (i=0; i<num_tokens(flags, ' '); ++i) {
- extract_token(this_flag, flags, i, ' ', sizeof this_flag);
- if (this_flag[0] == '\\') strcpy(this_flag, &this_flag[1]);
- if (!strcasecmp(this_flag, "Seen")) {
- CtdlSetSeen(&new_msgnum, 1, 1, ctdlsetseen_seen,
- NULL, NULL);
- }
- if (!strcasecmp(this_flag, "Answered")) {
- CtdlSetSeen(&new_msgnum, 1, 1, ctdlsetseen_answered,
- NULL, NULL);
- }
- }
-}
-
-
-/*
- * This function is called by the main command loop.
- */
-void imap_append(int num_parms, ConstStr *Params) {
- long literal_length;
- struct CtdlMessage *msg = NULL;
- long new_msgnum = (-1L);
- int ret = 0;
- char roomname[ROOMNAMELEN];
- char errbuf[SIZ];
- char dummy[SIZ];
- char savedroom[ROOMNAMELEN];
- int msgs, new;
- int i;
- char new_message_flags[SIZ];
- citimap *Imap;
-
- if (num_parms < 4) {
- IReply("BAD usage error");
- return;
- }
-
- if ( (Params[num_parms-1].Key[0] != '{')
- || (Params[num_parms-1].Key[Params[num_parms-1].len-1] != '}') ) {
- IReply("BAD no message literal supplied");
- return;
- }
-
- *new_message_flags = '\0';
- if (num_parms >= 5) {
- for (i=3; i<num_parms; ++i) {
- strcat(new_message_flags, Params[i].Key);
- strcat(new_message_flags, " ");
- }
- stripallbut(new_message_flags, '(', ')');
- }
-
- /* This is how we'd do this if it were relevant in our data store.
- * if (num_parms >= 6) {
- * new_message_internaldate = parms[4];
- * }
- */
-
- literal_length = atol(&Params[num_parms-1].Key[1]);
- if (literal_length < 1) {
- IReply("BAD Message length must be at least 1.");
- return;
- }
-
- Imap = IMAP;
- imap_free_transmitted_message(); /* just in case. */
-
- Imap->TransmittedMessage = NewStrBufPlain(NULL, literal_length);
-
- if (Imap->TransmittedMessage == NULL) {
- IReply("NO Cannot allocate memory.");
- return;
- }
-
- IAPrintf("+ Transmit message now.\r\n");
-
- IUnbuffer ();
-
- client_read_blob(Imap->TransmittedMessage, literal_length, CtdlGetConfigInt("c_sleeping"));
-
- if ((ret < 0) || (StrLength(Imap->TransmittedMessage) < literal_length)) {
- IReply("NO Read failed.");
- return;
- }
-
- /* Client will transmit a trailing CRLF after the literal (the message
- * text) is received. This call to client_getln() absorbs it.
- */
- flush_output();
- client_getln(dummy, sizeof dummy);
-
- /* Convert RFC822 newlines (CRLF) to Unix newlines (LF) */
- syslog(LOG_DEBUG, "Converting CRLF to LF");
- StrBufToUnixLF(Imap->TransmittedMessage);
-
- syslog(LOG_DEBUG, "Converting message format");
- msg = convert_internet_message_buf(&Imap->TransmittedMessage);
-
- ret = imap_grabroom(roomname, Params[2].Key, 1);
- if (ret != 0) {
- IReply("NO Invalid mailbox name or access denied");
- return;
- }
-
- /*
- * CtdlUserGoto() formally takes us to the desired room. (If another
- * folder is selected, save its name so we can return there!!!!!)
- */
- if (Imap->selected) {
- strcpy(savedroom, CC->room.QRname);
- }
- CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
-
- /* If the user is locally authenticated, FORCE the From: header to
- * show up as the real sender. (Configurable setting)
- */
- if (CC->logged_in) {
- if ( ((CC->room.QRflags & QR_MAILBOX) == 0) && (CtdlGetConfigInt("c_imap_keep_from") == 0))
- {
- CM_SetField(msg, eAuthor, CC->user.fullname, strlen(CC->user.fullname));
- }
- }
-
- /*
- * Can we post here?
- */
- ret = CtdlDoIHavePermissionToPostInThisRoom(errbuf, sizeof errbuf, NULL, POST_LOGGED_IN, 0);
-
- if (ret) {
- /* Nope ... print an error message */
- IReplyPrintf("NO %s", errbuf);
- }
-
- else {
- /* Yes ... go ahead and post! */
- if (msg != NULL) {
- new_msgnum = CtdlSubmitMsg(msg, NULL, "");
- }
- if (new_msgnum >= 0L) {
- IReplyPrintf("OK [APPENDUID %ld %ld] APPEND completed",
- GLOBAL_UIDVALIDITY_VALUE, new_msgnum);
- }
- else {
- IReplyPrintf("BAD Error %ld saving message to disk.",
- new_msgnum);
- }
- }
-
- /*
- * IMAP protocol response to client has already been sent by now.
- *
- * If another folder is selected, go back to that room so we can resume
- * our happy day without violent explosions.
- */
- if (Imap->selected) {
- CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
- }
-
- /* We don't need this buffer anymore */
- CM_Free(msg);
-
- if (IsEmptyStr(new_message_flags)) {
- imap_do_append_flags(new_msgnum, new_message_flags);
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2001-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.
- */
-
-void imap_copy(int num_parms, ConstStr *Params);
-void imap_uidcopy(int num_parms, ConstStr *Params);
-void imap_append(int num_parms, ConstStr *Params);
+++ /dev/null
-/*
- * Implements IMAP's gratuitously complex SEARCH command.
- *
- * Copyright (c) 2001-2020 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.
- */
-
-#include "ctdl_module.h"
-
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "sysdep_decls.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "serv_imap.h"
-#include "imap_tools.h"
-#include "imap_fetch.h"
-#include "imap_search.h"
-#include "genstamp.h"
-
-
-/*
- * imap_do_search() calls imap_do_search_msg() to search an individual
- * message after it has been fetched from the disk. This function returns
- * nonzero if there is a match.
- *
- * supplied_msg MAY be used to pass a pointer to the message in memory,
- * if for some reason it's already been loaded. If not, the message will
- * be loaded only if one or more search criteria require it.
- */
-int imap_do_search_msg(int seq, struct CtdlMessage *supplied_msg,
- int num_items, ConstStr *itemlist, int is_uid) {
-
- citimap *Imap = IMAP;
- int match = 0;
- int is_not = 0;
- int is_or = 0;
- int pos = 0;
- int i;
- char *fieldptr;
- struct CtdlMessage *msg = NULL;
- int need_to_free_msg = 0;
-
- if (num_items == 0) {
- return(0);
- }
- msg = supplied_msg;
-
- /* Initially we start at the beginning. */
- pos = 0;
-
- /* Check for the dreaded NOT criterion. */
- if (!strcasecmp(itemlist[0].Key, "NOT")) {
- is_not = 1;
- pos = 1;
- }
-
- /* Check for the dreaded OR criterion. */
- if (!strcasecmp(itemlist[0].Key, "OR")) {
- is_or = 1;
- pos = 1;
- }
-
- /* Now look for criteria. */
- if (!strcasecmp(itemlist[pos].Key, "ALL")) {
- match = 1;
- ++pos;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "ANSWERED")) {
- if (Imap->flags[seq-1] & IMAP_ANSWERED) {
- match = 1;
- }
- ++pos;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "BCC")) {
- if (msg == NULL) {
- msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
- need_to_free_msg = 1;
- }
- if (msg != NULL) {
- fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Bcc");
- if (fieldptr != NULL) {
- if (bmstrcasestr(fieldptr, itemlist[pos+1].Key)) {
- match = 1;
- }
- free(fieldptr);
- }
- }
- pos += 2;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "BEFORE")) {
- if (msg == NULL) {
- msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
- need_to_free_msg = 1;
- }
- if (msg != NULL) {
- if (!CM_IsEmpty(msg, eTimestamp)) {
- if (imap_datecmp(itemlist[pos+1].Key,
- atol(msg->cm_fields[eTimestamp])) < 0) {
- match = 1;
- }
- }
- }
- pos += 2;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "BODY")) {
-
- /* If fulltext indexing is active, on this server,
- * all messages have already been qualified.
- */
- if (CtdlGetConfigInt("c_enable_fulltext")) {
- match = 1;
- }
-
- /* Otherwise, we have to do a slow search. */
- else {
- if (msg == NULL) {
- msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
- need_to_free_msg = 1;
- }
- if (msg != NULL) {
- if (bmstrcasestr(msg->cm_fields[eMesageText], itemlist[pos+1].Key)) {
- match = 1;
- }
- }
- }
-
- pos += 2;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "CC")) {
- if (msg == NULL) {
- msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
- need_to_free_msg = 1;
- }
- if (msg != NULL) {
- fieldptr = msg->cm_fields[eCarbonCopY];
- if (fieldptr != NULL) {
- if (bmstrcasestr(fieldptr, itemlist[pos+1].Key)) {
- match = 1;
- }
- }
- else {
- fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Cc");
- if (fieldptr != NULL) {
- if (bmstrcasestr(fieldptr, itemlist[pos+1].Key)) {
- match = 1;
- }
- free(fieldptr);
- }
- }
- }
- pos += 2;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "DELETED")) {
- if (Imap->flags[seq-1] & IMAP_DELETED) {
- match = 1;
- }
- ++pos;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "DRAFT")) {
- if (Imap->flags[seq-1] & IMAP_DRAFT) {
- match = 1;
- }
- ++pos;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "FLAGGED")) {
- if (Imap->flags[seq-1] & IMAP_FLAGGED) {
- match = 1;
- }
- ++pos;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "FROM")) {
- if (msg == NULL) {
- msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
- need_to_free_msg = 1;
- }
- if (msg != NULL) {
- if (bmstrcasestr(msg->cm_fields[eAuthor], itemlist[pos+1].Key)) {
- match = 1;
- }
- if (bmstrcasestr(msg->cm_fields[erFc822Addr], itemlist[pos+1].Key)) {
- match = 1;
- }
- }
- pos += 2;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "HEADER")) {
-
- /* We've got to do a slow search for this because the client
- * might be asking for an RFC822 header field that has not been
- * converted into a Citadel header field. That requires
- * examining the message body.
- */
- if (msg == NULL) {
- msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
- need_to_free_msg = 1;
- }
-
- if (msg != NULL) {
-
- CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
- CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_FAST, 0, 1, 0);
-
- fieldptr = rfc822_fetch_field(ChrPtr(CC->redirect_buffer), itemlist[pos+1].Key);
- if (fieldptr != NULL) {
- if (bmstrcasestr(fieldptr, itemlist[pos+2].Key)) {
- match = 1;
- }
- free(fieldptr);
- }
-
- FreeStrBuf(&CC->redirect_buffer);
- }
-
- pos += 3; /* Yes, three */
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "KEYWORD")) {
- /* not implemented */
- pos += 2;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "LARGER")) {
- if (msg == NULL) {
- msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
- need_to_free_msg = 1;
- }
- if (msg != NULL) {
- if (msg->cm_lengths[eMesageText] > atoi(itemlist[pos+1].Key)) {
- match = 1;
- }
- }
- pos += 2;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "NEW")) {
- if ( (Imap->flags[seq-1] & IMAP_RECENT) && (!(Imap->flags[seq-1] & IMAP_SEEN))) {
- match = 1;
- }
- ++pos;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "OLD")) {
- if (!(Imap->flags[seq-1] & IMAP_RECENT)) {
- match = 1;
- }
- ++pos;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "ON")) {
- if (msg == NULL) {
- msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
- need_to_free_msg = 1;
- }
- if (msg != NULL) {
- if (!CM_IsEmpty(msg, eTimestamp)) {
- if (imap_datecmp(itemlist[pos+1].Key,
- atol(msg->cm_fields[eTimestamp])) == 0) {
- match = 1;
- }
- }
- }
- pos += 2;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "RECENT")) {
- if (Imap->flags[seq-1] & IMAP_RECENT) {
- match = 1;
- }
- ++pos;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "SEEN")) {
- if (Imap->flags[seq-1] & IMAP_SEEN) {
- match = 1;
- }
- ++pos;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "SENTBEFORE")) {
- if (msg == NULL) {
- msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
- need_to_free_msg = 1;
- }
- if (msg != NULL) {
- if (!CM_IsEmpty(msg, eTimestamp)) {
- if (imap_datecmp(itemlist[pos+1].Key,
- atol(msg->cm_fields[eTimestamp])) < 0) {
- match = 1;
- }
- }
- }
- pos += 2;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "SENTON")) {
- if (msg == NULL) {
- msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
- need_to_free_msg = 1;
- }
- if (msg != NULL) {
- if (!CM_IsEmpty(msg, eTimestamp)) {
- if (imap_datecmp(itemlist[pos+1].Key,
- atol(msg->cm_fields[eTimestamp])) == 0) {
- match = 1;
- }
- }
- }
- pos += 2;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "SENTSINCE")) {
- if (msg == NULL) {
- msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
- need_to_free_msg = 1;
- }
- if (msg != NULL) {
- if (!CM_IsEmpty(msg, eTimestamp)) {
- if (imap_datecmp(itemlist[pos+1].Key,
- atol(msg->cm_fields[eTimestamp])) >= 0) {
- match = 1;
- }
- }
- }
- pos += 2;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "SINCE")) {
- if (msg == NULL) {
- msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
- need_to_free_msg = 1;
- }
- if (msg != NULL) {
- if (!CM_IsEmpty(msg, eTimestamp)) {
- if (imap_datecmp(itemlist[pos+1].Key,
- atol(msg->cm_fields[eTimestamp])) >= 0) {
- match = 1;
- }
- }
- }
- pos += 2;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "SMALLER")) {
- if (msg == NULL) {
- msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
- need_to_free_msg = 1;
- }
- if (msg != NULL) {
- if (msg->cm_lengths[eMesageText] < atoi(itemlist[pos+1].Key)) {
- match = 1;
- }
- }
- pos += 2;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "SUBJECT")) {
- if (msg == NULL) {
- msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
- need_to_free_msg = 1;
- }
- if (msg != NULL) {
- if (bmstrcasestr(msg->cm_fields[eMsgSubject], itemlist[pos+1].Key)) {
- match = 1;
- }
- }
- pos += 2;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "TEXT")) {
- if (msg == NULL) {
- msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
- need_to_free_msg = 1;
- }
- if (msg != NULL) {
- for (i='A'; i<='Z'; ++i) {
- if (bmstrcasestr(msg->cm_fields[i], itemlist[pos+1].Key)) {
- match = 1;
- }
- }
- }
- pos += 2;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "TO")) {
- if (msg == NULL) {
- msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
- need_to_free_msg = 1;
- }
- if (msg != NULL) {
- if (bmstrcasestr(msg->cm_fields[eRecipient], itemlist[pos+1].Key)) {
- match = 1;
- }
- }
- pos += 2;
- }
-
- /* FIXME this is b0rken. fix it. */
- else if (imap_is_message_set(itemlist[pos].Key)) {
- if (is_msg_in_sequence_set(itemlist[pos].Key, seq)) {
- match = 1;
- }
- pos += 1;
- }
-
- /* FIXME this is b0rken. fix it. */
- else if (!strcasecmp(itemlist[pos].Key, "UID")) {
- if (is_msg_in_sequence_set(itemlist[pos+1].Key, Imap->msgids[seq-1])) {
- match = 1;
- }
- pos += 2;
- }
-
- /* Now here come the 'UN' criteria. Why oh why do we have to
- * implement *both* the 'UN' criteria *and* the 'NOT' keyword? Why
- * can't there be *one* way to do things? More gratuitous complexity.
- */
-
- else if (!strcasecmp(itemlist[pos].Key, "UNANSWERED")) {
- if ((Imap->flags[seq-1] & IMAP_ANSWERED) == 0) {
- match = 1;
- }
- ++pos;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "UNDELETED")) {
- if ((Imap->flags[seq-1] & IMAP_DELETED) == 0) {
- match = 1;
- }
- ++pos;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "UNDRAFT")) {
- if ((Imap->flags[seq-1] & IMAP_DRAFT) == 0) {
- match = 1;
- }
- ++pos;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "UNFLAGGED")) {
- if ((Imap->flags[seq-1] & IMAP_FLAGGED) == 0) {
- match = 1;
- }
- ++pos;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "UNKEYWORD")) {
- /* FIXME */
- pos += 2;
- }
-
- else if (!strcasecmp(itemlist[pos].Key, "UNSEEN")) {
- if ((Imap->flags[seq-1] & IMAP_SEEN) == 0) {
- match = 1;
- }
- ++pos;
- }
-
- /* Remember to negate if we were told to */
- if (is_not) {
- match = !match;
- }
-
- /* Keep going if there are more criteria! */
- if (pos < num_items) {
-
- if (is_or) {
- match = (match || imap_do_search_msg(seq, msg,
- num_items - pos, &itemlist[pos], is_uid));
- }
- else {
- match = (match && imap_do_search_msg(seq, msg,
- num_items - pos, &itemlist[pos], is_uid));
- }
-
- }
-
- if (need_to_free_msg) {
- CM_Free(msg);
- }
- return(match);
-}
-
-
-/*
- * imap_search() calls imap_do_search() to do its actual work, once it's
- * validated and boiled down the request a bit.
- */
-void imap_do_search(int num_items, ConstStr *itemlist, int is_uid) {
- citimap *Imap = IMAP;
- int i, j, k;
- int fts_num_msgs = 0;
- long *fts_msgs = NULL;
- int is_in_list = 0;
- int num_results = 0;
-
- /* Strip parentheses. We realize that this method will not work
- * in all cases, but it seems to work with all currently available
- * client software. Revisit later...
- */
- for (i=0; i<num_items; ++i) {
- if (itemlist[i].Key[0] == '(') {
-
- TokenCutLeft(&Imap->Cmd,
- &itemlist[i],
- 1);
- }
- if (itemlist[i].Key[itemlist[i].len-1] == ')') {
- TokenCutRight(&Imap->Cmd,
- &itemlist[i],
- 1);
- }
- }
-
- /* If there is a BODY search criterion in the query, use our full
- * text index to disqualify messages that don't have any chance of
- * matching. (Only do this if the index is enabled!!)
- */
- if (CtdlGetConfigInt("c_enable_fulltext")) for (i=0; i<(num_items-1); ++i) {
- if (!strcasecmp(itemlist[i].Key, "BODY")) {
- CtdlModuleDoSearch(&fts_num_msgs, &fts_msgs, itemlist[i+1].Key, "fulltext");
- if (fts_num_msgs > 0) {
- for (j=0; j < Imap->num_msgs; ++j) {
- if (Imap->flags[j] & IMAP_SELECTED) {
- is_in_list = 0;
- for (k=0; k<fts_num_msgs; ++k) {
- if (Imap->msgids[j] == fts_msgs[k]) {
- ++is_in_list;
- }
- }
- }
- if (!is_in_list) {
- Imap->flags[j] = Imap->flags[j] & ~IMAP_SELECTED;
- }
- }
- }
- else { /* no hits on the index; disqualify every message */
- for (j=0; j < Imap->num_msgs; ++j) {
- Imap->flags[j] = Imap->flags[j] & ~IMAP_SELECTED;
- }
- }
- if (fts_msgs) {
- free(fts_msgs);
- }
- }
- }
-
- /* Now go through the messages and apply all search criteria. */
- buffer_output();
- IAPuts("* SEARCH ");
- if (Imap->num_msgs > 0)
- for (i = 0; i < Imap->num_msgs; ++i)
- if (Imap->flags[i] & IMAP_SELECTED) {
- if (imap_do_search_msg(i+1, NULL, num_items, itemlist, is_uid)) {
- if (num_results != 0) {
- IAPuts(" ");
- }
- if (is_uid) {
- IAPrintf("%ld", Imap->msgids[i]);
- }
- else {
- IAPrintf("%d", i+1);
- }
- ++num_results;
- }
- }
- IAPuts("\r\n");
- unbuffer_output();
-}
-
-
-/*
- * This function is called by the main command loop.
- */
-void imap_search(int num_parms, ConstStr *Params) {
- int i;
-
- if (num_parms < 3) {
- IReply("BAD invalid parameters");
- return;
- }
-
- for (i = 0; i < IMAP->num_msgs; ++i) {
- IMAP->flags[i] |= IMAP_SELECTED;
- }
-
- imap_do_search(num_parms-2, &Params[2], 0);
- IReply("OK SEARCH completed");
-}
-
-/*
- * This function is called by the main command loop.
- */
-void imap_uidsearch(int num_parms, ConstStr *Params) {
- int i;
-
- if (num_parms < 4) {
- IReply("BAD invalid parameters");
- return;
- }
-
- for (i = 0; i < IMAP->num_msgs; ++i) {
- IMAP->flags[i] |= IMAP_SELECTED;
- }
-
- imap_do_search(num_parms-3, &Params[3], 1);
- IReply("OK UID SEARCH completed");
-}
-
-
+++ /dev/null
-/*
- * Copyright (c) 2001-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.
- */
-
-void imap_search(int num_parms, ConstStr *Params);
-void imap_uidsearch(int num_parms, ConstStr *Params);
+++ /dev/null
-/*
- * Implements the STORE command in IMAP.
- *
- * Copyright (c) 2001-2009 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 as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-#include "ctdl_module.h"
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "sysdep_decls.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "room_ops.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "serv_imap.h"
-#include "imap_tools.h"
-#include "imap_fetch.h"
-#include "imap_store.h"
-#include "genstamp.h"
-
-
-/*
- * imap_do_store() calls imap_do_store_msg() to tweak the settings of
- * an individual message.
- *
- * We also implement the ".SILENT" protocol option here. :(
- */
-void imap_do_store_msg(int seq, const char *oper, unsigned int bits_to_twiddle) {
- citimap *Imap = IMAP;
-
- if (!strncasecmp(oper, "FLAGS", 5)) {
- Imap->flags[seq] &= IMAP_MASK_SYSTEM;
- Imap->flags[seq] |= bits_to_twiddle;
- }
- else if (!strncasecmp(oper, "+FLAGS", 6)) {
- Imap->flags[seq] |= bits_to_twiddle;
- }
- else if (!strncasecmp(oper, "-FLAGS", 6)) {
- Imap->flags[seq] &= (~bits_to_twiddle);
- }
-}
-
-
-/*
- * imap_store() calls imap_do_store() to perform the actual bit twiddling
- * on the flags.
- */
-void imap_do_store(citimap_command *Cmd) {
- int i, j;
- unsigned int bits_to_twiddle = 0;
- const char *oper;
- char flag[32];
- char whichflags[256];
- char num_flags;
- int silent = 0;
- long *ss_msglist;
- int num_ss = 0;
- int last_item_twiddled = (-1);
- citimap *Imap = IMAP;
-
- if (Cmd->num_parms < 2) return;
- oper = Cmd->Params[0].Key;
- if (cbmstrcasestr(oper, ".SILENT")) {
- silent = 1;
- }
-
- /*
- * ss_msglist is an array of message numbers to manipulate. We
- * are going to supply this array to CtdlSetSeen() later.
- */
- ss_msglist = malloc(Imap->num_msgs * sizeof(long));
- if (ss_msglist == NULL) return;
-
- /*
- * Ok, go ahead and parse the flags.
- */
- for (i=1; i<Cmd->num_parms; ++i) {///TODO: why strcpy?
- strcpy(whichflags, Cmd->Params[i].Key);
- if (whichflags[0]=='(') {
- safestrncpy(whichflags, &whichflags[1],
- sizeof whichflags);
- }
- if (whichflags[strlen(whichflags)-1]==')') {
- whichflags[strlen(whichflags)-1]=0;
- }
- striplt(whichflags);
-
- /* A client might twiddle more than one bit at a time.
- * Note that we check for the flag names without the leading
- * backslash because imap_parameterize() strips them out.
- */
- num_flags = num_tokens(whichflags, ' ');
- for (j=0; j<num_flags; ++j) {
- extract_token(flag, whichflags, j, ' ', sizeof flag);
-
- if ((!strcasecmp(flag, "\\Deleted"))
- || (!strcasecmp(flag, "Deleted"))) {
- if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom()) {
- bits_to_twiddle |= IMAP_DELETED;
- }
- }
- if ((!strcasecmp(flag, "\\Seen"))
- || (!strcasecmp(flag, "Seen"))) {
- bits_to_twiddle |= IMAP_SEEN;
- }
- if ((!strcasecmp(flag, "\\Answered"))
- || (!strcasecmp(flag, "Answered"))) {
- bits_to_twiddle |= IMAP_ANSWERED;
- }
- }
- }
-
- if (Imap->num_msgs > 0) {
- for (i = 0; i < Imap->num_msgs; ++i) {
- if (Imap->flags[i] & IMAP_SELECTED) {
- last_item_twiddled = i;
-
- ss_msglist[num_ss++] = Imap->msgids[i];
- imap_do_store_msg(i, oper, bits_to_twiddle);
-
- if (!silent) {
- IAPrintf("* %d FETCH (", i+1);
- imap_fetch_flags(i);
- IAPuts(")\r\n");
- }
-
- }
- }
- }
-
- /*
- * Now manipulate the database -- all in one shot.
- */
- if ( (last_item_twiddled >= 0) && (num_ss > 0) ) {
-
- if (bits_to_twiddle & IMAP_SEEN) {
- CtdlSetSeen(ss_msglist, num_ss,
- ((Imap->flags[last_item_twiddled] & IMAP_SEEN) ? 1 : 0),
- ctdlsetseen_seen,
- NULL, NULL
- );
- }
-
- if (bits_to_twiddle & IMAP_ANSWERED) {
- CtdlSetSeen(ss_msglist, num_ss,
- ((Imap->flags[last_item_twiddled] & IMAP_ANSWERED) ? 1 : 0),
- ctdlsetseen_answered,
- NULL, NULL
- );
- }
-
- }
-
- free(ss_msglist);
- imap_do_expunge(); // Citadel always expunges immediately.
- imap_rescan_msgids();
-}
-
-
-/*
- * This function is called by the main command loop.
- */
-void imap_store(int num_parms, ConstStr *Params) {
- citimap_command Cmd;
- int num_items;
-
- if (num_parms < 3) {
- IReply("BAD invalid parameters");
- return;
- }
-
- if (imap_is_message_set(Params[2].Key)) {
- imap_pick_range(Params[2].Key, 0);
- }
- else {
- IReply("BAD invalid parameters");
- return;
- }
-
- memset(&Cmd, 0, sizeof(citimap_command));
- Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
- MakeStringOf(Cmd.CmdBuf, 3);
-
- num_items = imap_extract_data_items(&Cmd);
- if (num_items < 1) {
- IReply("BAD invalid data item list");
- FreeStrBuf(&Cmd.CmdBuf);
- free(Cmd.Params);
- return;
- }
-
- imap_do_store(&Cmd);
- IReply("OK STORE completed");
- FreeStrBuf(&Cmd.CmdBuf);
- free(Cmd.Params);
-}
-
-/*
- * This function is called by the main command loop.
- */
-void imap_uidstore(int num_parms, ConstStr *Params) {
- citimap_command Cmd;
- int num_items;
-
- if (num_parms < 4) {
- IReply("BAD invalid parameters");
- return;
- }
-
- if (imap_is_message_set(Params[3].Key)) {
- imap_pick_range(Params[3].Key, 1);
- }
- else {
- IReply("BAD invalid parameters");
- return;
- }
-
- memset(&Cmd, 0, sizeof(citimap_command));
- Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
- MakeStringOf(Cmd.CmdBuf, 4);
-
- num_items = imap_extract_data_items(&Cmd);
- if (num_items < 1) {
- IReply("BAD invalid data item list");
- FreeStrBuf(&Cmd.CmdBuf);
- free(Cmd.Params);
- return;
- }
-
- imap_do_store(&Cmd);
- IReply("OK UID STORE completed");
- FreeStrBuf(&Cmd.CmdBuf);
- free(Cmd.Params);
-}
-
-
+++ /dev/null
-/*
- * Copyright (c) 2001-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.
- */
-
-void imap_store(int num_parms, ConstStr *Params);
-void imap_uidstore(int num_parms, ConstStr *Params);
+++ /dev/null
-/*
- * Utility functions for the IMAP module.
- *
- * Copyright (c) 2001-2017 by the citadel.org team and others, except for
- * most of the UTF7 and UTF8 handling code which was lifted from Evolution.
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-#define SHOW_ME_VAPPEND_PRINTF
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <ctype.h>
-#include <string.h>
-#include <stdarg.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "sysdep_decls.h"
-#include "internet_addressing.h"
-#include "serv_imap.h"
-#include "imap_tools.h"
-#include "ctdl_module.h"
-
-/* String handling helpers */
-
-/* This code uses some pretty nasty string manipulation. To make everything
- * manageable, we use this semi-high-level string manipulation API. Strings are
- * always \0-terminated, despite the fact that we keep track of the size.
- */
-struct string {
- char* buffer;
- int maxsize;
- int size;
-};
-
-static void string_init(struct string* s, char* buf, int bufsize)
-{
- s->buffer = buf;
- s->maxsize = bufsize-1;
- s->size = strlen(buf);
-}
-
-static char* string_end(struct string* s)
-{
- return s->buffer + s->size;
-}
-
-/* Append a UTF8 string of a particular length (in bytes). -1 to autocalculate. */
-
-static void string_append_sn(struct string* s, char* p, int len)
-{
- if (len == -1)
- len = strlen(p);
- if ((s->size+len) > s->maxsize)
- len = s->maxsize - s->size;
- memcpy(s->buffer + s->size, p, len);
- s->size += len;
- s->buffer[s->size] = '\0';
-}
-
-/* As above, always autocalculate. */
-
-#define string_append_s(s, p) string_append_sn((s), (p), -1)
-
-/* Appends a UTF8 character --- which may make the size change by more than 1!
- * If the string overflows, the last character may become invalid. */
-
-static void string_append_c(struct string* s, int c)
-{
- char UmlChar[5];
- int len = 0;
-
- /* Don't do anything if there's no room. */
-
- if (s->size == s->maxsize)
- return;
-
- if (c <= 0x7F)
- {
- /* This is the most common case, so we optimise it. */
-
- s->buffer[s->size++] = c;
- s->buffer[s->size] = 0;
- return;
- }
- else if (c <= 0x7FF)
- {
- UmlChar[0] = 0xC0 | (c >> 6);
- UmlChar[1] = 0x80 | (c & 0x3F);
- len = 2;
- }
- else if (c <= 0xFFFF)
- {
- UmlChar[0] = 0xE0 | (c >> 12);
- UmlChar[1] = 0x80 | ((c >> 6) & 0x3f);
- UmlChar[2] = 0x80 | (c & 0x3f);
- len = 3;
- }
- else
- {
- UmlChar[0] = 0xf0 | c >> 18;
- UmlChar[1] = 0x80 | ((c >> 12) & 0x3f);
- UmlChar[2] = 0x80 | ((c >> 6) & 0x3f);
- UmlChar[3] = 0x80 | (c & 0x3f);
- len = 4;
- }
-
- string_append_sn(s, UmlChar, len);
-}
-
-/* Reads a UTF8 character from a char*, advancing the pointer. */
-
-int utf8_getc(char** ptr)
-{
- unsigned char* p = (unsigned char*) *ptr;
- unsigned char c, r;
- int v, m;
-
- for (;;)
- {
- r = *p++;
- loop:
- if (r < 0x80)
- {
- *ptr = (char*) p;
- v = r;
- break;
- }
- else if (r < 0xf8)
- {
- /* valid start char? (max 4 octets) */
- v = r;
- m = 0x7f80; /* used to mask out the length bits */
- do {
- c = *p++;
- if ((c & 0xc0) != 0x80)
- {
- r = c;
- goto loop;
- }
- v = (v<<6) | (c & 0x3f);
- r<<=1;
- m<<=5;
- } while (r & 0x40);
-
- *ptr = (char*)p;
-
- v &= ~m;
- break;
- }
- }
-
- return v;
-}
-
-/* IMAP name safety */
-
-/* IMAP has certain special requirements in its character set, which means we
- * have to do a fair bit of work to convert Citadel's UTF8 strings to IMAP
- * strings. The next two routines (and their data tables) do that.
- */
-
-static char *utf7_alphabet =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
-
-static unsigned char utf7_rank[256] = {
- 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
- 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
- 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3E,0x3F,0xFF,0xFF,0xFF,
- 0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
- 0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,
- 0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF,
- 0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,
- 0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0xFF,0xFF,0xFF,0xFF,0xFF,
- 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
- 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
- 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
- 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
- 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
- 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
- 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
- 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
-};
-
-/* Base64 helpers. */
-
-static void utf7_closeb64(struct string* out, int v, int i)
-{
- int x;
-
- if (i > 0)
- {
- x = (v << (6-i)) & 0x3F;
- string_append_c(out, utf7_alphabet[x]);
- }
- string_append_c(out, '-');
-}
-
-/* Convert from a Citadel name to an IMAP-safe name. Returns the end
- * of the destination.
- */
-static char* toimap(char* destp, char* destend, char* src)
-{
- struct string dest;
- int state = 0;
- int v = 0;
- int i = 0;
-
- *destp = 0;
- string_init(&dest, destp, destend-destp);
- /* syslog(LOG_DEBUG, "toimap %s", src); */
-
- for (;;)
- {
- int c = utf8_getc(&src);
- if (c == '\0')
- break;
-
- if (c >= 0x20 && c <= 0x7e)
- {
- if (state == 1)
- {
- utf7_closeb64(&dest, v, i);
- state = 0;
- i = 0;
- }
-
- switch (c)
- {
- case '&':
- string_append_sn(&dest, "&-", 2);
- break;
-
- case '/':
- /* Citadel extension: / becomes |, because /
- * isn't valid as part of an IMAP name. */
-
- c = '|';
- goto defaultcase;
-
- case '\\':
- /* Citadel extension: backslashes mark folder
- * seperators in the IMAP subfolder emulation
- * hack. We turn them into / characters,
- * *except* if it's the last character in the
- * string. */
-
- if (*src != '\0')
- c = '/';
- /* fall through */
-
- default:
- defaultcase:
- string_append_c(&dest, c);
- }
- }
- else
- {
- if (state == 0)
- {
- string_append_c(&dest, '&');
- state = 1;
- }
- v = (v << 16) | c;
- i += 16;
- while (i >= 6)
- {
- int x = (v >> (i-6)) & 0x3f;
- string_append_c(&dest, utf7_alphabet[x]);
- i -= 6;
- }
- }
- }
-
- if (state == 1)
- utf7_closeb64(&dest, v, i);
- /* syslog(LOG_DEBUG, " -> %s", destp); */
- return string_end(&dest);
-}
-
-/* Convert from an IMAP-safe name back into a Citadel name. Returns the end of the destination. */
-
-static int cfrommap(int c);
-static char* fromimap(char* destp, char* destend, const char* src)
-{
- struct string dest;
- unsigned const char *p = (unsigned const char*) src;
- int v = 0;
- int i = 0;
- int state = 0;
- int c;
-
- *destp = 0;
- string_init(&dest, destp, destend-destp);
- /* syslog(LOG_DEBUG, "fromimap %s", src); */
-
- do {
- c = *p++;
- switch (state)
- {
- case 0:
- /* US-ASCII characters. */
-
- if (c == '&')
- state = 1;
- else
- string_append_c(&dest, cfrommap(c));
- break;
-
- case 1:
- if (c == '-')
- {
- string_append_c(&dest, '&');
- state = 0;
- }
- else if (utf7_rank[c] != 0xff)
- {
- v = utf7_rank[c];
- i = 6;
- state = 2;
- }
- else
- {
- /* invalid char */
- string_append_sn(&dest, "&-", 2);
- state = 0;
- }
- break;
-
- case 2:
- if (c == '-')
- state = 0;
- else if (utf7_rank[c] != 0xFF)
- {
- v = (v<<6) | utf7_rank[c];
- i += 6;
- if (i >= 16)
- {
- int x = (v >> (i-16)) & 0xFFFF;
- string_append_c(&dest, cfrommap(x));
- i -= 16;
- }
- }
- else
- {
- string_append_c(&dest, cfrommap(c));
- state = 0;
- }
- break;
- }
- } while (c != '\0');
-
- /* syslog(LOG_DEBUG, " -> %s", destp); */
- return string_end(&dest);
-}
-
-/* Undoes the special character conversion. */
-static int cfrommap(int c)
-{
- switch (c)
- {
- case '|': return '/';
- case '/': return '\\';
- }
- return c;
-}
-
-
-/* Break a command down into tokens, unquoting any escaped characters. */
-void MakeStringOf(StrBuf *Buf, int skip)
-{
- int i;
- citimap_command *Cmd = &IMAP->Cmd;
-
- for (i=skip; i<Cmd->num_parms; ++i) {
- StrBufAppendBufPlain(Buf, Cmd->Params[i].Key, Cmd->Params[i].len, 0);
- if (i < (Cmd->num_parms-1)) StrBufAppendBufPlain(Buf, HKEY(" "), 0);
- }
-}
-
-
-void TokenCutRight(citimap_command *Cmd,
- ConstStr *CutMe,
- int n)
-{
- const char *CutAt;
-
- if (CutMe->len < n) {
- CutAt = CutMe->Key;
- CutMe->len = 0;
- }
- else {
- CutAt = CutMe->Key + CutMe->len - n;
- CutMe->len -= n;
- }
- StrBufPeek(Cmd->CmdBuf, CutAt, -1, '\0');
-}
-
-void TokenCutLeft(citimap_command *Cmd,
- ConstStr *CutMe,
- int n)
-{
- if (CutMe->len < n) {
- CutMe->Key += CutMe->len;
- CutMe->len = 0;
- }
- else {
- CutMe->Key += n;
- CutMe->len -= n;
- }
-}
-
-
-
-int CmdAdjust(citimap_command *Cmd,
- int nArgs,
- int Realloc)
-{
- ConstStr *Params;
- if (nArgs > Cmd->avail_parms) {
- Params = (ConstStr*) malloc(sizeof(ConstStr) * nArgs);
- if (Realloc) {
- memcpy(Params,
- Cmd->Params,
- sizeof(ConstStr) * Cmd->avail_parms);
-
- memset(Cmd->Params +
- sizeof(ConstStr) * Cmd->avail_parms,
- 0,
- sizeof(ConstStr) * nArgs -
- sizeof(ConstStr) * Cmd->avail_parms
- );
- }
- else {
- Cmd->num_parms = 0;
- memset(Params, 0,
- sizeof(ConstStr) * nArgs);
- }
- Cmd->avail_parms = nArgs;
- if (Cmd->Params != NULL)
- free (Cmd->Params);
- Cmd->Params = Params;
- }
- else {
- if (!Realloc) {
- memset(Cmd->Params,
- 0,
- sizeof(ConstStr) * Cmd->avail_parms);
- Cmd->num_parms = 0;
- }
- }
- return Cmd->avail_parms;
-}
-
-int imap_parameterize(citimap_command *Cmd)
-{
- int nArgs;
- const char *In, *End;
-
- In = ChrPtr(Cmd->CmdBuf);
- End = In + StrLength(Cmd->CmdBuf);
-
- /* we start with 10 chars per arg, maybe we need to realloc later. */
- nArgs = StrLength(Cmd->CmdBuf) / 10 + 10;
- nArgs = CmdAdjust(Cmd, nArgs, 0);
- while (In < End)
- {
- /* Skip whitespace. */
- while (isspace(*In))
- In++;
- if (*In == '\0')
- break;
-
- /* Found the start of a token. */
-
- Cmd->Params[Cmd->num_parms].Key = In;
-
- /* Read in the token. */
-
- for (;;)
- {
- if (isspace(*In))
- break;
-
- if (*In == '\"')
- {
- /* Found a quoted section. */
-
- Cmd->Params[Cmd->num_parms].Key++;
- //In++;
- for (;;)
- {
- In++;
- if (*In == '\"') {
- StrBufPeek(Cmd->CmdBuf, In, -1, '\0');
- break;
- }
- else if (*In == '\\')
- In++;
-
- if (*In == '\0') {
- Cmd->Params[Cmd->num_parms].len =
- In - Cmd->Params[Cmd->num_parms].Key;
- Cmd->num_parms++;
- return Cmd->num_parms;
- }
- }
- break;
- }
- else if (*In == '\\')
- {
- In++;
- }
-
- if (*In == '\0') {
- Cmd->Params[Cmd->num_parms].len =
- In - Cmd->Params[Cmd->num_parms].Key;
- Cmd->num_parms++;
- return Cmd->num_parms;
- }
- In++;
- }
- StrBufPeek(Cmd->CmdBuf, In, -1, '\0');
- Cmd->Params[Cmd->num_parms].len =
- In - Cmd->Params[Cmd->num_parms].Key;
- if (Cmd->num_parms + 1 >= Cmd->avail_parms) {
- nArgs = CmdAdjust(Cmd, nArgs * 2, 1);
- }
- Cmd->num_parms ++;
- In++;
- }
- return Cmd->num_parms;
-}
-
-
-/* Convert a struct ctdlroom to an IMAP-compatible mailbox name. */
-long imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf)
-{
- char* bufend = buf+bufsize;
- struct floor *fl;
- char* p = buf;
- const char *pend;
-
- /* For mailboxes, just do it straight.
- * Do the Cyrus-compatible thing: all private folders are
- * subfolders of INBOX. */
-
- if (qrbuf->QRflags & QR_MAILBOX)
- {
- if (strcasecmp(qrbuf->QRname+11, MAILROOM) == 0)
- {
- pend = toimap(p, bufend, "INBOX");
- return pend - buf;
- }
- else
- {
- p = toimap(p, bufend, "INBOX");
- if (p < bufend)
- *p++ = '/';
- pend = toimap(p, bufend, qrbuf->QRname+11);
- return pend - buf;
- }
- }
- else
- {
- /* Otherwise, prefix the floor name as a "public folders" moniker. */
-
- fl = CtdlGetCachedFloor(qrbuf->QRfloor);
- p = toimap(p, bufend, fl->f_name);
- if (p < bufend)
- *p++ = '/';
- pend = toimap(p, bufend, qrbuf->QRname);
- return pend - buf;
- }
-}
-
-/*
- * Convert an inputted folder name to our best guess as to what an equivalent
- * room name should be.
- *
- * If an error occurs, it returns -1. Otherwise...
- *
- * The lower eight bits of the return value are the floor number on which the
- * room most likely resides. The upper eight bits may contain flags,
- * including IR_MAILBOX if we're dealing with a personal room.
- *
- */
-int imap_roomname(char *rbuf, int bufsize, const char *foldername)
-{
- int levels;
- char floorname[ROOMNAMELEN*2];
- char roomname[ROOMNAMELEN];
- int i;
- struct floor *fl;
- int ret = (-1);
-
- if (foldername == NULL)
- return(-1);
-
- /* Unmunge the entire string into the output buffer. */
-
- fromimap(rbuf, rbuf+bufsize, foldername);
-
- /* Is this an IMAP inbox? */
-
- if (strncasecmp(rbuf, "INBOX", 5) == 0)
- {
- if (rbuf[5] == 0)
- {
- /* It's the system inbox. */
-
- safestrncpy(rbuf, MAILROOM, bufsize);
- ret = (0 | IR_MAILBOX);
- goto exit;
- }
- else if (rbuf[5] == FDELIM)
- {
- /* It's another personal mail folder. */
-
- safestrncpy(rbuf, rbuf+6, bufsize);
- ret = (0 | IR_MAILBOX);
- goto exit;
- }
-
- /* If we get here, the folder just happens to start with INBOX
- * --- fall through. */
- }
-
- /* Is this a multi-level room name? */
-
- levels = num_tokens(rbuf, FDELIM);
- if (levels > 1)
- {
- long len;
- /* Extract the main room name. */
-
- len = extract_token(floorname, rbuf, 0, FDELIM, sizeof floorname);
- if (len < 0) len = 0;
- safestrncpy(roomname, &rbuf[len + 1], sizeof(roomname));
-
- /* Try and find it on any floor. */
-
- for (i = 0; i < MAXFLOORS; ++i)
- {
- fl = CtdlGetCachedFloor(i);
- if (fl->f_flags & F_INUSE)
- {
- if (strcasecmp(floorname, fl->f_name) == 0)
- {
- /* Got it! */
-
- safestrncpy(rbuf, roomname, bufsize);
- ret = i;
- goto exit;
- }
- }
- }
- }
-
- /* Meh. It's either not a multi-level room name, or else we
- * couldn't find it.
- */
- ret = (0 | IR_MAILBOX);
-
-exit:
- syslog(LOG_DEBUG, "(That translates to \"%s\")", rbuf);
- return(ret);
-}
-
-
-/*
- * Determine whether the supplied string is a valid message set.
- * If the string contains only numbers, colons, commas, and asterisks,
- * return 1 for a valid message set. If any other character is found,
- * return 0.
- */
-int imap_is_message_set(const char *buf)
-{
- int i;
-
- if (buf == NULL)
- return (0); /* stupidity checks */
- if (IsEmptyStr(buf))
- return (0);
-
- if (!strcasecmp(buf, "ALL"))
- return (1); /* macro? why? */
-
- for (i = 0; buf[i]; ++i) { /* now start the scan */
- if (
- (!isdigit(buf[i]))
- && (buf[i] != ':')
- && (buf[i] != ',')
- && (buf[i] != '*')
- )
- return (0);
- }
-
- return (1); /* looks like we're good */
-}
-
-
-/*
- * imap_match.c, based on wildmat.c from INN
- * hacked for Citadel/IMAP by Daniel Malament
- */
-
-/* note: not all return statements use these; don't change them */
-#define WILDMAT_TRUE 1
-#define WILDMAT_FALSE 0
-#define WILDMAT_ABORT -1
-#define WILDMAT_DELIM '/'
-
-/*
- * Match text and p, return TRUE, FALSE, or ABORT.
- */
-static int do_imap_match(const char *supplied_text, const char *supplied_p)
-{
- int matched, i;
- char lcase_text[SIZ], lcase_p[SIZ];
- char *text;
- char *p;
-
- /* Copy both strings and lowercase them, in order to
- * make this entire operation case-insensitive.
- */
- for (i=0;
- ((supplied_text[i] != '\0') &&
- (i < sizeof(lcase_text)));
- ++i)
- lcase_text[i] = tolower(supplied_text[i]);
- lcase_text[i] = '\0';
-
- for (i=0;
- ((supplied_p[i] != '\0') &&
- (i < sizeof(lcase_p)));
- ++i)
- lcase_p[i] = tolower(supplied_p[i]);
- lcase_p[i] = '\0';
-
- /* Start matching */
- for (p = lcase_p, text = lcase_text;
- !IsEmptyStr(p) && !IsEmptyStr(text);
- text++, p++) {
- if ((*text == '\0') && (*p != '*') && (*p != '%')) {
- return WILDMAT_ABORT;
- }
- switch (*p) {
- default:
- if (*text != *p) {
- return WILDMAT_FALSE;
- }
- continue;
- case '*':
-star:
- while (++p, ((*p == '*') || (*p == '%'))) {
- /* Consecutive stars or %'s act
- * just like one star.
- */
- continue;
- }
- if (*p == '\0') {
- /* Trailing star matches everything. */
- return WILDMAT_TRUE;
- }
- while (*text) {
- if ((matched = do_imap_match(text++, p))
- != WILDMAT_FALSE) {
- return matched;
- }
- }
- return WILDMAT_ABORT;
- case '%':
- while (++p, (!IsEmptyStr(p) && ((*p == '*') || (*p == '%'))))
- {
- /* Consecutive %'s act just like one, but even
- * a single star makes the sequence act like
- * one star, instead.
- */
- if (*p == '*') {
- goto star;
- }
- continue;
- }
- if (*p == '\0') {
- /*
- * Trailing % matches everything
- * without a delimiter.
- */
- while (!IsEmptyStr(text)) {
- if (*text == WILDMAT_DELIM) {
- return WILDMAT_FALSE;
- }
- text++;
- }
- return WILDMAT_TRUE;
- }
- while (!IsEmptyStr(text) &&
- /* make sure text - 1 isn't before lcase_p */
- ((text == lcase_text) || (*(text - 1) != WILDMAT_DELIM)))
- {
- if ((matched = do_imap_match(text++, p))
- != WILDMAT_FALSE) {
- return matched;
- }
- }
- return WILDMAT_ABORT;
- }
- }
-
- if ((*text == '\0') && (*p == '\0')) return WILDMAT_TRUE;
- else return WILDMAT_FALSE;
-}
-
-
-/*
- * Support function for mailbox pattern name matching in LIST and LSUB
- * Returns nonzero if the supplied mailbox name matches the supplied pattern.
- */
-int imap_mailbox_matches_pattern(const char *pattern, char *mailboxname)
-{
- /* handle just-star case quickly */
- if ((pattern[0] == '*') && (pattern[1] == '\0')) {
- return WILDMAT_TRUE;
- }
- return (do_imap_match(mailboxname, pattern) == WILDMAT_TRUE);
-}
-
-
-
-/*
- * Compare an IMAP date string (date only, no time) to the date found in
- * a Unix timestamp.
- */
-int imap_datecmp(const char *datestr, time_t msgtime) {
- char daystr[256];
- char monthstr[256];
- char yearstr[256];
- int i;
- int day, month, year;
- int msgday, msgmonth, msgyear;
- struct tm msgtm;
-
- char *imap_datecmp_ascmonths[12] = {
- "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
- };
-
- if (datestr == NULL) return(0);
-
- /* Expecting a date in the form dd-Mmm-yyyy */
- extract_token(daystr, datestr, 0, '-', sizeof daystr);
- extract_token(monthstr, datestr, 1, '-', sizeof monthstr);
- extract_token(yearstr, datestr, 2, '-', sizeof yearstr);
-
- day = atoi(daystr);
- year = atoi(yearstr);
- month = 0;
- for (i=0; i<12; ++i) {
- if (!strcasecmp(monthstr, imap_datecmp_ascmonths[i])) {
- month = i;
- }
- }
-
- /* Extract day/month/year from message timestamp */
- localtime_r(&msgtime, &msgtm);
- msgday = msgtm.tm_mday;
- msgmonth = msgtm.tm_mon;
- msgyear = msgtm.tm_year + 1900;
-
- /* Now start comparing */
-
- if (year < msgyear) return(+1);
- if (year > msgyear) return(-1);
-
- if (month < msgmonth) return(+1);
- if (month > msgmonth) return(-1);
-
- if (day < msgday) return(+1);
- if (day > msgday) return(-1);
-
- return(0);
-}
-
-
-void IAPrintf(const char *Format, ...)
-{
- va_list arg_ptr;
-
- va_start(arg_ptr, Format);
- StrBufVAppendPrintf(IMAP->Reply, Format, arg_ptr);
- va_end(arg_ptr);
-}
-
-
-void iaputs(const char *Str, long Len)
-{
- StrBufAppendBufPlain(IMAP->Reply, Str, Len, 0);
-}
-
-
-void ireply(const char *Msg, long len)
-{
- citimap *Imap = IMAP;
-
- StrBufAppendBufPlain(Imap->Reply,
- CKEY(Imap->Cmd.Params[0]), 0);
- StrBufAppendBufPlain(Imap->Reply,
- HKEY(" "), 0);
- StrBufAppendBufPlain(Imap->Reply,
- Msg, len, 0);
-
- StrBufAppendBufPlain(Imap->Reply,
- HKEY("\r\n"), 0);
-
-}
-
-
-void IReplyPrintf(const char *Format, ...)
-{
- citimap *Imap = IMAP;
- va_list arg_ptr;
-
-
- StrBufAppendBufPlain(Imap->Reply,
- CKEY(Imap->Cmd.Params[0]), 0);
-
- StrBufAppendBufPlain(Imap->Reply,
- HKEY(" "), 0);
-
- va_start(arg_ptr, Format);
- StrBufVAppendPrintf(IMAP->Reply, Format, arg_ptr);
- va_end(arg_ptr);
-
- StrBufAppendBufPlain(Imap->Reply,
- HKEY("\r\n"), 0);
-
-}
-
-
-/* Output a string to the IMAP client, either as a literal or quoted.
- * (We do a literal if it has any double-quotes or backslashes.) */
-void IPutStr(const char *Msg, long Len)
-{
- int i;
- int is_literal = 0;
- citimap *Imap = IMAP;
-
-
- if ((Msg == NULL) || (Len == 0))
- { /* yeah, we handle this */
- StrBufAppendBufPlain(Imap->Reply, HKEY("NIL"), 0);
- return;
- }
-
- for (i = 0; i < Len; ++i) {
- if ((Msg[i] == '\"') || (Msg[i] == '\\'))
- is_literal = 1;
- }
-
- if (is_literal) {
- StrBufAppendPrintf(Imap->Reply, "{%ld}\r\n", Len);
- StrBufAppendBufPlain(Imap->Reply, Msg, Len, 0);
- } else {
- StrBufAppendBufPlain(Imap->Reply,
- HKEY("\""), 0);
- StrBufAppendBufPlain(Imap->Reply,
- Msg, Len, 0);
- StrBufAppendBufPlain(Imap->Reply,
- HKEY("\""), 0);
- }
-}
-
-void IUnbuffer (void)
-{
- citimap *Imap = IMAP;
-
- cputbuf(Imap->Reply);
- FlushStrBuf(Imap->Reply);
-}
+++ /dev/null
-
-/*
- * since we work with shifted pointers to ConstStrs in some places,
- * we can't just say we want to cut the n'th of Cmd, we have to pass it in
- * and rely on that CutMe references Cmd->CmdBuf; else peek won't work out
- * and len will differ.
- */
-void TokenCutRight(citimap_command *Cmd,
- ConstStr *CutMe,
- int n);
-/*
- * since we just move Key around here, Cmd is just here so the syntax is identical.
- */
-void TokenCutLeft(citimap_command *Cmd,
- ConstStr *CutMe,
- int n);
-void MakeStringOf(StrBuf *Buf, int skip);
-
-int CmdAdjust(citimap_command *Cmd,
- int nArgs,
- int Realloc);
-
-
-void imap_strout(ConstStr *args);
-void imap_strbuffer(StrBuf *Reply, ConstStr *args);
-void plain_imap_strbuffer(StrBuf *Reply, char *buf);
-int imap_parameterize(citimap_command *Cmd);
-long imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf);
-int imap_roomname(char *buf, int bufsize, const char *foldername);
-int imap_is_message_set(const char *);
-int imap_mailbox_matches_pattern(const char *pattern, char *mailboxname);
-int imap_datecmp(const char *datestr, time_t msgtime);
-
-
-/* Imap Append Printf, send to the outbuffer */
-void IAPrintf(const char *Format, ...) __attribute__((__format__(__printf__,1,2)));
-
-void iaputs(const char *Str, long Len);
-#define IAPuts(Msg) iaputs(HKEY(Msg))
-/* give it a naughty name since its ugly. */
-#define _iaputs(Msg) iaputs(Msg, strlen(Msg))
-
-/* outputs a static message prepended by the sequence no */
-void ireply(const char *Msg, long len);
-#define IReply(msg) ireply(HKEY(msg))
-/* outputs a dynamic message prepended by the sequence no */
-void IReplyPrintf(const char *Format, ...);
-
-
-/* output a string like that {%ld}%s */
-void IPutStr(const char *Msg, long Len);
-#define IPutCStr(_ConstStr) IPutStr(CKEY(_ConstStr))
-#define IPutCParamStr(n) IPutStr(CKEY(Params[n]))
-#define IPutMsgField(Which) IPutStr(CM_KEY(msg, Which))
-void IUnbuffer (void);
+++ /dev/null
-/*
- * IMAP server for the Citadel system
- *
- * Copyright (C) 2000-2021 by Art Cancro and others.
- * This code is released under the terms of the GNU General Public License.
- *
- * WARNING: the IMAP protocol is badly designed. No implementation of it
- * is perfect. Indeed, with so much gratuitous complexity, *all* IMAP
- * implementations have bugs.
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "serv_imap.h"
-#include "imap_tools.h"
-#include "imap_list.h"
-#include "imap_fetch.h"
-#include "imap_search.h"
-#include "imap_store.h"
-#include "imap_acl.h"
-#include "imap_metadata.h"
-#include "imap_misc.h"
-
-#include "ctdl_module.h"
-HashList *ImapCmds = NULL;
-void registerImapCMD(const char *First, long FLen,
- const char *Second, long SLen,
- imap_handler H,
- int Flags)
-{
- imap_handler_hook *h;
-
- h = (imap_handler_hook*) malloc(sizeof(imap_handler_hook));
- memset(h, 0, sizeof(imap_handler_hook));
-
- h->Flags = Flags;
- h->h = H;
- if (SLen == 0) {
- Put(ImapCmds, First, FLen, h, NULL);
- }
- else {
- char CMD[SIZ];
- memcpy(CMD, First, FLen);
- memcpy(CMD+FLen, Second, SLen);
- CMD[FLen+SLen] = '\0';
- Put(ImapCmds, CMD, FLen + SLen, h, NULL);
- }
-}
-
-
-const imap_handler_hook *imap_lookup(int num_parms, ConstStr *Params)
-{
- struct CitContext *CCC = CC;
- void *v;
- citimap *Imap = CCCIMAP;
-
- if (num_parms < 1)
- return NULL;
-
- /* we abuse the Reply-buffer for uppercasing... */
- StrBufPlain(Imap->Reply, CKEY(Params[1]));
- StrBufUpCase(Imap->Reply);
-
- syslog(LOG_DEBUG, "---- Looking up [%s] -----", ChrPtr(Imap->Reply));
- if (GetHash(ImapCmds, SKEY(Imap->Reply), &v))
- {
- syslog(LOG_DEBUG, "Found.");
- FlushStrBuf(Imap->Reply);
- return (imap_handler_hook *) v;
- }
-
- if (num_parms == 1)
- {
- syslog(LOG_DEBUG, "NOT Found.");
- FlushStrBuf(Imap->Reply);
- return NULL;
- }
-
- syslog(LOG_DEBUG, "---- Looking up [%s] -----", ChrPtr(Imap->Reply));
- StrBufAppendBufPlain(Imap->Reply, CKEY(Params[2]), 0);
- StrBufUpCase(Imap->Reply);
- if (GetHash(ImapCmds, SKEY(Imap->Reply), &v))
- {
- syslog(LOG_DEBUG, "Found.");
- FlushStrBuf(Imap->Reply);
- return (imap_handler_hook *) v;
- }
- syslog(LOG_DEBUG, "NOT Found.");
- FlushStrBuf(Imap->Reply);
- return NULL;
-}
-
-/* imap_rename() uses this struct containing list of rooms to rename */
-struct irl {
- struct irl *next;
- char irl_oldroom[ROOMNAMELEN];
- char irl_newroom[ROOMNAMELEN];
- int irl_newfloor;
-};
-
-/* Data which is passed between imap_rename() and imap_rename_backend() */
-typedef struct __irlparms {
- const char *oldname;
- long oldnamelen;
- const char *newname;
- long newnamelen;
- struct irl **irl;
-}irlparms;
-
-
-/*
- * If there is a message ID map in memory, free it
- */
-void imap_free_msgids(void)
-{
- citimap *Imap = IMAP;
- if (Imap->msgids != NULL) {
- free(Imap->msgids);
- Imap->msgids = NULL;
- Imap->num_msgs = 0;
- Imap->num_alloc = 0;
- }
- if (Imap->flags != NULL) {
- free(Imap->flags);
- Imap->flags = NULL;
- }
- Imap->last_mtime = (-1);
-}
-
-
-/*
- * If there is a transmitted message in memory, free it
- */
-void imap_free_transmitted_message(void)
-{
- FreeStrBuf(&IMAP->TransmittedMessage);
-}
-
-
-/*
- * Set the \Seen, \Recent. and \Answered flags, based on the sequence
- * sets stored in the visit record for this user/room. Note that we have
- * to parse each sequence set manually here, because calling the utility
- * function is_msg_in_sequence_set() over and over again is too expensive.
- *
- * first_msg should be set to 0 to rescan the flags for every message in the
- * room, or some other value if we're only interested in an incremental
- * update.
- */
-void imap_set_seen_flags(int first_msg)
-{
- citimap *Imap = IMAP;
- visit vbuf;
- int i;
- int num_sets;
- int s;
- char setstr[64], lostr[64], histr[64];
- long lo, hi;
-
- if (Imap->num_msgs < 1) return;
- CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
-
- for (i = first_msg; i < Imap->num_msgs; ++i) {
- Imap->flags[i] = Imap->flags[i] & ~IMAP_SEEN;
- Imap->flags[i] |= IMAP_RECENT;
- Imap->flags[i] = Imap->flags[i] & ~IMAP_ANSWERED;
- }
-
- /*
- * Do the "\Seen" flag.
- * (Any message not "\Seen" is considered "\Recent".)
- */
- num_sets = num_tokens(vbuf.v_seen, ',');
- for (s=0; s<num_sets; ++s) {
- extract_token(setstr, vbuf.v_seen, s, ',', sizeof setstr);
-
- extract_token(lostr, setstr, 0, ':', sizeof lostr);
- if (num_tokens(setstr, ':') >= 2) {
- extract_token(histr, setstr, 1, ':', sizeof histr);
- if (!strcmp(histr, "*")) {
- snprintf(histr, sizeof histr, "%ld", LONG_MAX);
- }
- }
- else {
- strcpy(histr, lostr);
- }
- lo = atol(lostr);
- hi = atol(histr);
-
- for (i = first_msg; i < Imap->num_msgs; ++i) {
- if ((Imap->msgids[i] >= lo) && (Imap->msgids[i] <= hi)){
- Imap->flags[i] |= IMAP_SEEN;
- Imap->flags[i] = Imap->flags[i] & ~IMAP_RECENT;
- }
- }
- }
-
- /* Do the ANSWERED flag */
- num_sets = num_tokens(vbuf.v_answered, ',');
- for (s=0; s<num_sets; ++s) {
- extract_token(setstr, vbuf.v_answered, s, ',', sizeof setstr);
-
- extract_token(lostr, setstr, 0, ':', sizeof lostr);
- if (num_tokens(setstr, ':') >= 2) {
- extract_token(histr, setstr, 1, ':', sizeof histr);
- if (!strcmp(histr, "*")) {
- snprintf(histr, sizeof histr, "%ld", LONG_MAX);
- }
- }
- else {
- strcpy(histr, lostr);
- }
- lo = atol(lostr);
- hi = atol(histr);
-
- for (i = first_msg; i < Imap->num_msgs; ++i) {
- if ((Imap->msgids[i] >= lo) && (Imap->msgids[i] <= hi)){
- Imap->flags[i] |= IMAP_ANSWERED;
- }
- }
- }
-
-}
-
-
-
-/*
- * Back end for imap_load_msgids()
- *
- * Optimization: instead of calling realloc() to add each message, we
- * allocate space in the list for REALLOC_INCREMENT messages at a time. This
- * allows the mapping to proceed much faster.
- */
-void imap_add_single_msgid(long msgnum, void *userdata)
-{
- citimap *Imap = IMAP;
-
- ++Imap->num_msgs;
- if (Imap->num_msgs > Imap->num_alloc) {
- Imap->num_alloc += REALLOC_INCREMENT;
- Imap->msgids = realloc(Imap->msgids, (Imap->num_alloc * sizeof(long)) );
- Imap->flags = realloc(Imap->flags, (Imap->num_alloc * sizeof(unsigned int)) );
- }
- Imap->msgids[Imap->num_msgs - 1] = msgnum;
- Imap->flags[Imap->num_msgs - 1] = 0;
-}
-
-
-
-/*
- * Set up a message ID map for the current room (folder)
- */
-void imap_load_msgids(void)
-{
- struct CitContext *CCC = CC;
- struct cdbdata *cdbfr;
- citimap *Imap = CCCIMAP;
-
- if (Imap->selected == 0) {
- syslog(LOG_ERR, "imap_load_msgids() can't run; no room selected");
- return;
- }
-
- imap_free_msgids(); /* If there was already a map, free it */
-
- /* Load the message list */
- cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
- if (cdbfr != NULL) {
- Imap->msgids = (long*)cdbfr->ptr;
- Imap->num_msgs = cdbfr->len / sizeof(long);
- Imap->num_alloc = cdbfr->len / sizeof(long);
- cdbfr->ptr = NULL;
- cdbfr->len = 0;
- cdb_free(cdbfr);
- }
-
- if (Imap->num_msgs) {
- Imap->flags = malloc(Imap->num_alloc * sizeof(unsigned int));
- memset(Imap->flags, 0, (Imap->num_alloc * sizeof(unsigned int)) );
- }
-
- imap_set_seen_flags(0);
-}
-
-
-/*
- * Re-scan the selected room (folder) and see if it's been changed at all
- */
-void imap_rescan_msgids(void)
-{
- struct CitContext *CCC = CC;
- citimap *Imap = CCCIMAP;
- int original_num_msgs = 0;
- long original_highest = 0L;
- int i, j, jstart;
- int message_still_exists;
- struct cdbdata *cdbfr;
- long *msglist = NULL;
- int num_msgs = 0;
- int num_recent = 0;
-
- if (Imap->selected == 0) {
- syslog(LOG_ERR, "imap_load_msgids() can't run; no room selected");
- return;
- }
-
- /*
- * Check to see if the room's contents have changed.
- * If not, we can avoid this rescan.
- */
- CtdlGetRoom(&CC->room, CC->room.QRname);
- if (Imap->last_mtime == CC->room.QRmtime) { /* No changes! */
- return;
- }
-
- /* Load the *current* message list from disk, so we can compare it
- * to what we have in memory.
- */
- cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
- if (cdbfr != NULL) {
- msglist = (long*)cdbfr->ptr;
- cdbfr->ptr = NULL;
- num_msgs = cdbfr->len / sizeof(long);
- cdbfr->len = 0;
- cdb_free(cdbfr);
- } else {
- num_msgs = 0;
- }
-
- /*
- * Check to see if any of the messages we know about have been expunged
- */
- if (Imap->num_msgs > 0) {
- jstart = 0;
- for (i = 0; i < Imap->num_msgs; ++i) {
-
- message_still_exists = 0;
- if (num_msgs > 0) {
- for (j = jstart; j < num_msgs; ++j) {
- if (msglist[j] == Imap->msgids[i]) {
- message_still_exists = 1;
- jstart = j;
- break;
- }
- }
- }
-
- if (message_still_exists == 0) {
- IAPrintf("* %d EXPUNGE\r\n", i + 1);
-
- /* Here's some nice stupid nonsense. When a
- * message is expunged, we have to slide all
- * the existing messages up in the message
- * array.
- */
- --Imap->num_msgs;
- memmove(&Imap->msgids[i],
- &Imap->msgids[i + 1],
- (sizeof(long) *
- (Imap->num_msgs - i)));
- memmove(&Imap->flags[i],
- &Imap->flags[i + 1],
- (sizeof(unsigned int) *
- (Imap->num_msgs - i)));
- --i;
- }
-
- }
- }
-
- /*
- * Remember how many messages were here before we re-scanned.
- */
- original_num_msgs = Imap->num_msgs;
- if (Imap->num_msgs > 0) {
- original_highest = Imap->msgids[Imap->num_msgs - 1];
- } else {
- original_highest = 0L;
- }
-
- /*
- * Now peruse the room for *new* messages only.
- * This logic is probably the cause of Bug # 368
- * [ http://bugzilla.citadel.org/show_bug.cgi?id=368 ]
- */
- if (num_msgs > 0) {
- for (j = 0; j < num_msgs; ++j) {
- if (msglist[j] > original_highest) {
- imap_add_single_msgid(msglist[j], NULL);
- }
- }
- }
- imap_set_seen_flags(original_num_msgs);
-
- /*
- * If new messages have arrived, tell the client about them.
- */
- if (Imap->num_msgs > original_num_msgs) {
-
- for (j = 0; j < num_msgs; ++j) {
- if (Imap->flags[j] & IMAP_RECENT) {
- ++num_recent;
- }
- }
-
- IAPrintf("* %d EXISTS\r\n", Imap->num_msgs);
- IAPrintf("* %d RECENT\r\n", num_recent);
- }
-
- if (msglist != NULL) {
- free(msglist);
- }
- Imap->last_mtime = CC->room.QRmtime;
-}
-
-
-/*
- * This cleanup function blows away the temporary memory and files used by
- * the IMAP server.
- */
-void imap_cleanup_function(void)
-{
- struct CitContext *CCC = CC;
- citimap *Imap = CCCIMAP;
-
- /* Don't do this stuff if this is not a Imap session! */
- if (CC->h_command_function != imap_command_loop)
- return;
-
- /* If there is a mailbox selected, auto-expunge it. */
- if (Imap->selected) {
- imap_do_expunge();
- }
-
- syslog(LOG_DEBUG, "Performing IMAP cleanup hook");
- imap_free_msgids();
- imap_free_transmitted_message();
-
- if (Imap->cached_rfc822 != NULL) {
- FreeStrBuf(&Imap->cached_rfc822);
- Imap->cached_rfc822_msgnum = (-1);
- Imap->cached_rfc822_withbody = 0;
- }
-
- if (Imap->cached_body != NULL) {
- free(Imap->cached_body);
- Imap->cached_body = NULL;
- Imap->cached_body_len = 0;
- Imap->cached_bodymsgnum = (-1);
- }
- FreeStrBuf(&Imap->Cmd.CmdBuf);
- FreeStrBuf(&Imap->Reply);
- if (Imap->Cmd.Params != NULL) free(Imap->Cmd.Params);
- free(Imap);
- syslog(LOG_DEBUG, "Finished IMAP cleanup hook");
-}
-
-
-/*
- * Does the actual work of the CAPABILITY command (because we need to
- * output this stuff in other places as well)
- */
-void imap_output_capability_string(void) {
- IAPuts("CAPABILITY IMAP4REV1 NAMESPACE ID AUTH=PLAIN AUTH=LOGIN UIDPLUS");
-
-#ifdef HAVE_OPENSSL
- if (!CC->redirect_ssl) IAPuts(" STARTTLS");
-#endif
-
-#ifndef DISABLE_IMAP_ACL
- IAPuts(" ACL");
-#endif
-
- /* We are building a partial implementation of METADATA for the sole purpose
- * of interoperating with the ical/vcard version of the Bynari Insight Connector.
- * It is not a full RFC5464 implementation, but it should refuse non-Bynari
- * metadata in a compatible and graceful way.
- */
- IAPuts(" METADATA");
-
- /*
- * LIST-EXTENDED was originally going to be required by the METADATA extension.
- * It was mercifully removed prior to the finalization of RFC5464. We started
- * implementing this but stopped when we learned that it would not be needed.
- * If you uncomment this declaration you are responsible for writing a lot of new
- * code.
- *
- * IAPuts(" LIST-EXTENDED")
- */
-}
-
-
-/*
- * implements the CAPABILITY command
- */
-void imap_capability(int num_parms, ConstStr *Params)
-{
- IAPuts("* ");
- imap_output_capability_string();
- IAPuts("\r\n");
- IReply("OK CAPABILITY completed");
-}
-
-
-/*
- * Implements the ID command (specified by RFC2971)
- *
- * We ignore the client-supplied information, and output a NIL response.
- * Although this is technically a valid implementation of the extension, it
- * is quite useless. It exists only so that we may see which clients are
- * making use of this extension.
- *
- */
-void imap_id(int num_parms, ConstStr *Params)
-{
- IAPuts("* ID NIL\r\n");
- IReply("OK ID completed");
-}
-
-
-/*
- * Here's where our IMAP session begins its happy day.
- */
-void imap_greeting(void)
-{
- citimap *Imap;
- CitContext *CCC = CC;
-
- strcpy(CCC->cs_clientname, "IMAP session");
- CCC->session_specific_data = malloc(sizeof(citimap));
- Imap = (citimap *)CCC->session_specific_data;
- memset(Imap, 0, sizeof(citimap));
- Imap->authstate = imap_as_normal;
- Imap->cached_rfc822_msgnum = (-1);
- Imap->cached_rfc822_withbody = 0;
- Imap->Reply = NewStrBufPlain(NULL, SIZ * 10); /* 40k */
-
- if (CCC->nologin)
- {
- IAPuts("* BYE; Server busy, try later\r\n");
- CCC->kill_me = KILLME_NOLOGIN;
- IUnbuffer();
- return;
- }
-
- IAPuts("* OK [");
- imap_output_capability_string();
- IAPrintf("] %s IMAP4rev1 %s ready\r\n", CtdlGetConfigStr("c_fqdn"), CITADEL);
- IUnbuffer();
-}
-
-
-/*
- * IMAPS is just like IMAP, except it goes crypto right away.
- */
-void imaps_greeting(void) {
- CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
-#ifdef HAVE_OPENSSL
- if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO; /* kill session if no crypto */
-#endif
- imap_greeting();
-}
-
-
-/*
- * implements the LOGIN command (ordinary username/password login)
- */
-void imap_login(int num_parms, ConstStr *Params)
-{
-
- switch (num_parms) {
- case 3:
- if (Params[2].Key[0] == '{') {
- IAPuts("+ go ahead\r\n");
- IMAP->authstate = imap_as_expecting_multilineusername;
- strcpy(IMAP->authseq, Params[0].Key);
- return;
- }
- else {
- IReply("BAD incorrect number of parameters");
- return;
- }
- case 4:
- if (CtdlLoginExistingUser(Params[2].Key) == login_ok) {
- if (CtdlTryPassword(Params[3].Key, Params[3].len) == pass_ok) {
- /* hm, thats not doable by IReply :-( */
- IAPrintf("%s OK [", Params[0].Key);
- imap_output_capability_string();
- IAPrintf("] Hello, %s\r\n", CC->user.fullname);
- return;
- }
- else
- {
- IReplyPrintf("NO AUTHENTICATE %s failed", Params[3].Key);
- return;
- }
- }
-
- IReply("BAD Login incorrect");
- return;
- default:
- IReply("BAD incorrect number of parameters");
- return;
- }
-
-}
-
-
-/*
- * Implements the AUTHENTICATE command
- */
-void imap_authenticate(int num_parms, ConstStr *Params)
-{
- char UsrBuf[SIZ];
-
- if (num_parms != 3) {
- IReply("BAD incorrect number of parameters");
- return;
- }
-
- if (CC->logged_in) {
- IReply("BAD Already logged in.");
- return;
- }
-
- if (!strcasecmp(Params[2].Key, "LOGIN")) {
- size_t len = CtdlEncodeBase64(UsrBuf, "Username:", 9, 0);
- if (UsrBuf[len - 1] == '\n') {
- UsrBuf[len - 1] = '\0';
- }
-
- IAPrintf("+ %s\r\n", UsrBuf);
- IMAP->authstate = imap_as_expecting_username;
- strcpy(IMAP->authseq, Params[0].Key);
- return;
- }
-
- if (!strcasecmp(Params[2].Key, "PLAIN")) {
- // size_t len = CtdlEncodeBase64(UsrBuf, "Username:", 9, 0);
- // if (UsrBuf[len - 1] == '\n') {
- // UsrBuf[len - 1] = '\0';
- // }
- // IAPuts("+ %s\r\n", UsrBuf);
- IAPuts("+ \r\n");
- IMAP->authstate = imap_as_expecting_plainauth;
- strcpy(IMAP->authseq, Params[0].Key);
- return;
- }
-
- else {
- IReplyPrintf("NO AUTHENTICATE %s failed",
- Params[1].Key);
- }
-}
-
-
-void imap_auth_plain(void)
-{
- citimap *Imap = IMAP;
- const char *decoded_authstring;
- char ident[256] = "";
- char user[256] = "";
- char pass[256] = "";
- int result;
- long decoded_len;
- long len = 0;
- long plen = 0;
-
- memset(pass, 0, sizeof(pass));
- decoded_len = StrBufDecodeBase64(Imap->Cmd.CmdBuf);
-
- if (decoded_len > 0)
- {
- decoded_authstring = ChrPtr(Imap->Cmd.CmdBuf);
-
- len = safestrncpy(ident, decoded_authstring, sizeof ident);
-
- decoded_len -= len - 1;
- decoded_authstring += len + 1;
-
- if (decoded_len > 0)
- {
- len = safestrncpy(user, decoded_authstring, sizeof user);
-
- decoded_authstring += len + 1;
- decoded_len -= len - 1;
- }
-
- if (decoded_len > 0)
- {
- plen = safestrncpy(pass, decoded_authstring, sizeof pass);
-
- if (plen < 0)
- plen = sizeof(pass) - 1;
- }
- }
- Imap->authstate = imap_as_normal;
-
- if (!IsEmptyStr(ident)) {
- result = CtdlLoginExistingUser(ident);
- }
- else {
- result = CtdlLoginExistingUser(user);
- }
-
- if (result == login_ok) {
- if (CtdlTryPassword(pass, plen) == pass_ok) {
- IAPrintf("%s OK authentication succeeded\r\n", Imap->authseq);
- return;
- }
- }
- IAPrintf("%s NO authentication failed\r\n", Imap->authseq);
-}
-
-
-void imap_auth_login_user(long state)
-{
- char PWBuf[SIZ];
- citimap *Imap = IMAP;
-
- switch (state){
- case imap_as_expecting_username:
- StrBufDecodeBase64(Imap->Cmd.CmdBuf);
- CtdlLoginExistingUser(ChrPtr(Imap->Cmd.CmdBuf));
- size_t len = CtdlEncodeBase64(PWBuf, "Password:", 9, 0);
- if (PWBuf[len - 1] == '\n') {
- PWBuf[len - 1] = '\0';
- }
-
- IAPrintf("+ %s\r\n", PWBuf);
-
- Imap->authstate = imap_as_expecting_password;
- return;
- case imap_as_expecting_multilineusername:
- extract_token(PWBuf, ChrPtr(Imap->Cmd.CmdBuf), 1, ' ', sizeof(PWBuf));
- CtdlLoginExistingUser(ChrPtr(Imap->Cmd.CmdBuf));
- IAPuts("+ go ahead\r\n");
- Imap->authstate = imap_as_expecting_multilinepassword;
- return;
- }
-}
-
-
-void imap_auth_login_pass(long state)
-{
- citimap *Imap = IMAP;
- const char *pass = NULL;
- long len = 0;
-
- switch (state) {
- default:
- case imap_as_expecting_password:
- StrBufDecodeBase64(Imap->Cmd.CmdBuf);
- pass = ChrPtr(Imap->Cmd.CmdBuf);
- len = StrLength(Imap->Cmd.CmdBuf);
- break;
- case imap_as_expecting_multilinepassword:
- pass = ChrPtr(Imap->Cmd.CmdBuf);
- len = StrLength(Imap->Cmd.CmdBuf);
- break;
- }
- if (len > USERNAME_SIZE)
- StrBufCutAt(Imap->Cmd.CmdBuf, USERNAME_SIZE, NULL);
-
- if (CtdlTryPassword(pass, len) == pass_ok) {
- IAPrintf("%s OK authentication succeeded\r\n", Imap->authseq);
- } else {
- IAPrintf("%s NO authentication failed\r\n", Imap->authseq);
- }
- Imap->authstate = imap_as_normal;
- return;
-}
-
-
-/*
- * implements the STARTTLS command (Citadel API version)
- */
-void imap_starttls(int num_parms, ConstStr *Params)
-{
- char ok_response[SIZ];
- char nosup_response[SIZ];
- char error_response[SIZ];
-
- snprintf(ok_response, SIZ, "%s OK begin TLS negotiation now\r\n", Params[0].Key);
- snprintf(nosup_response, SIZ, "%s NO TLS not supported here\r\n", Params[0].Key);
- snprintf(error_response, SIZ, "%s BAD Internal error\r\n", Params[0].Key);
- CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
-}
-
-
-/*
- * implements the SELECT command
- */
-void imap_select(int num_parms, ConstStr *Params)
-{
- citimap *Imap = IMAP;
- char towhere[ROOMNAMELEN];
- char augmented_roomname[ROOMNAMELEN];
- int c = 0;
- int ok = 0;
- int ra = 0;
- struct ctdlroom QRscratch;
- int msgs, new;
- int i;
-
- /* Convert the supplied folder name to a roomname */
- i = imap_roomname(towhere, sizeof towhere, Params[2].Key);
- if (i < 0) {
- IReply("NO Invalid mailbox name.");
- Imap->selected = 0;
- return;
- }
-
- /* First try a regular match */
- c = CtdlGetRoom(&QRscratch, towhere);
-
- /* Then try a mailbox name match */
- if (c != 0) {
- CtdlMailboxName(augmented_roomname, sizeof augmented_roomname, &CC->user, towhere);
- c = CtdlGetRoom(&QRscratch, augmented_roomname);
- if (c == 0) {
- safestrncpy(towhere, augmented_roomname, sizeof(towhere));
- }
- }
-
- /* If the room exists, check security/access */
- if (c == 0) {
- /* See if there is an existing user/room relationship */
- CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL);
-
- /* normal clients have to pass through security */
- if (ra & UA_KNOWN) {
- ok = 1;
- }
- }
-
- /* Fail here if no such room */
- if (!ok) {
- IReply("NO ... no such room, or access denied");
- return;
- }
-
- /* If we already had some other folder selected, auto-expunge it */
- imap_do_expunge();
-
- /*
- * CtdlUserGoto() formally takes us to the desired room, happily returning
- * the number of messages and number of new messages.
- */
- memcpy(&CC->room, &QRscratch, sizeof(struct ctdlroom));
- CtdlUserGoto(NULL, 0, 0, &msgs, &new, NULL, NULL);
- Imap->selected = 1;
-
- if (!strcasecmp(Params[1].Key, "EXAMINE")) {
- Imap->readonly = 1;
- } else {
- Imap->readonly = 0;
- }
-
- imap_load_msgids();
- Imap->last_mtime = CC->room.QRmtime;
-
- IAPrintf("* %d EXISTS\r\n", msgs);
- IAPrintf("* %d RECENT\r\n", new);
-
- IAPrintf("* OK [UIDVALIDITY %ld] UID validity status\r\n", GLOBAL_UIDVALIDITY_VALUE);
- IAPrintf("* OK [UIDNEXT %ld] Predicted next UID\r\n", CtdlGetConfigLong("MMhighest") + 1);
-
- /* Technically, \Deleted is a valid flag, but not a permanent flag,
- * because we don't maintain its state across sessions. Citadel
- * automatically expunges mailboxes when they are de-selected.
- *
- * Unfortunately, omitting \Deleted as a PERMANENTFLAGS flag causes
- * some clients (particularly Thunderbird) to misbehave -- they simply
- * elect not to transmit the flag at all. So we have to advertise
- * \Deleted as a PERMANENTFLAGS flag, even though it technically isn't.
- */
- IAPuts("* FLAGS (\\Deleted \\Seen \\Answered)\r\n");
- IAPuts("* OK [PERMANENTFLAGS (\\Deleted \\Seen \\Answered)] permanent flags\r\n");
-
- IReplyPrintf("OK [%s] %s completed",
- (Imap->readonly ? "READ-ONLY" : "READ-WRITE"), Params[1].Key
- );
-}
-
-
-/*
- * Does the real work for expunge.
- */
-int imap_do_expunge(void)
-{
- struct CitContext *CCC = CC;
- citimap *Imap = CCCIMAP;
- int i;
- int num_expunged = 0;
- long *delmsgs = NULL;
- int num_delmsgs = 0;
-
- syslog(LOG_DEBUG, "imap_do_expunge() called");
- if (Imap->selected == 0) {
- return (0);
- }
-
- if (Imap->num_msgs > 0) {
- delmsgs = malloc(Imap->num_msgs * sizeof(long));
- for (i = 0; i < Imap->num_msgs; ++i) {
- if (Imap->flags[i] & IMAP_DELETED) {
- delmsgs[num_delmsgs++] = Imap->msgids[i];
- }
- }
- if (num_delmsgs > 0) {
- CtdlDeleteMessages(CC->room.QRname, delmsgs, num_delmsgs, "");
- }
- num_expunged += num_delmsgs;
- free(delmsgs);
- }
-
- if (num_expunged > 0) {
- imap_rescan_msgids();
- }
-
- syslog(LOG_DEBUG, "Expunged %d messages from <%s>", num_expunged, CC->room.QRname);
- return (num_expunged);
-}
-
-
-/*
- * implements the EXPUNGE command syntax
- */
-void imap_expunge(int num_parms, ConstStr *Params)
-{
- int num_expunged = 0;
-
- num_expunged = imap_do_expunge();
- IReplyPrintf("OK expunged %d messages.", num_expunged);
-}
-
-
-/*
- * implements the CLOSE command
- */
-void imap_close(int num_parms, ConstStr *Params)
-{
-
- /* Yes, we always expunge on close. */
- if (IMAP->selected) {
- imap_do_expunge();
- }
-
- IMAP->selected = 0;
- IMAP->readonly = 0;
- imap_free_msgids();
- IReply("OK CLOSE completed");
-}
-
-
-/*
- * Implements the NAMESPACE command.
- */
-void imap_namespace(int num_parms, ConstStr *Params)
-{
- long len;
- int i;
- struct floor *fl;
- int floors = 0;
- char Namespace[SIZ];
-
- IAPuts("* NAMESPACE ");
-
- /* All personal folders are subordinate to INBOX. */
- IAPuts("((\"INBOX/\" \"/\")) ");
-
- /* Other users' folders ... coming soon! FIXME */
- IAPuts("NIL ");
-
- /* Show all floors as shared namespaces. Neato! */
- IAPuts("(");
- for (i = 0; i < MAXFLOORS; ++i) {
- fl = CtdlGetCachedFloor(i);
- if (fl->f_flags & F_INUSE) {
- /* if (floors > 0) IAPuts(" "); samjam says this confuses javamail */
- IAPuts("(");
- len = snprintf(Namespace, sizeof(Namespace), "%s/", fl->f_name);
- IPutStr(Namespace, len);
- IAPuts(" \"/\")");
- ++floors;
- }
- }
- IAPuts(")");
-
- /* Wind it up with a newline and a completion message. */
- IAPuts("\r\n");
- IReply("OK NAMESPACE completed");
-}
-
-
-/*
- * Implements the CREATE command
- *
- */
-void imap_create(int num_parms, ConstStr *Params)
-{
- int ret;
- char roomname[ROOMNAMELEN];
- int floornum;
- int flags;
- int newroomtype = 0;
- int newroomview = 0;
- char *notification_message = NULL;
-
- if (num_parms < 3) {
- IReply("NO A foder name must be specified");
- return;
- }
-
- if (strchr(Params[2].Key, '\\') != NULL) {
- IReply("NO Invalid character in folder name");
- syslog(LOG_ERR, "invalid character in folder name");
- return;
- }
-
- ret = imap_roomname(roomname, sizeof roomname, Params[2].Key);
- if (ret < 0) {
- IReply("NO Invalid mailbox name or location");
- syslog(LOG_ERR, "invalid mailbox name or location");
- return;
- }
- floornum = (ret & 0x00ff); /* lower 8 bits = floor number */
- flags = (ret & 0xff00); /* upper 8 bits = flags */
-
- if (flags & IR_MAILBOX) {
- if (strncasecmp(Params[2].Key, "INBOX/", 6)) {
- IReply("NO Personal folders must be created under INBOX");
- syslog(LOG_ERR, "not subordinate to inbox");
- return;
- }
- }
-
- if (flags & IR_MAILBOX) {
- newroomtype = 4; /* private mailbox */
- newroomview = VIEW_MAILBOX;
- } else {
- newroomtype = 0; /* public folder */
- newroomview = VIEW_BBS;
- }
-
- syslog(LOG_INFO, "Create new room <%s> on floor <%d> with type <%d>",
- roomname, floornum, newroomtype);
-
- ret = CtdlCreateRoom(roomname, newroomtype, "", floornum, 1, 0, newroomview);
- if (ret == 0) {
- /*** DO NOT CHANGE THIS ERROR MESSAGE IN ANY WAY! BYNARI CONNECTOR DEPENDS ON IT! ***/
- IReply("NO Mailbox already exists, or create failed");
- } else {
- IReply("OK CREATE completed");
- /* post a message in Aide> describing the new room */
- notification_message = malloc(1024);
- snprintf(notification_message, 1024,
- "A new room called \"%s\" has been created by %s%s%s%s\n",
- roomname,
- CC->user.fullname,
- ((ret & QR_MAILBOX) ? " [personal]" : ""),
- ((ret & QR_PRIVATE) ? " [private]" : ""),
- ((ret & QR_GUESSNAME) ? " [hidden]" : "")
- );
- CtdlAideMessage(notification_message, "Room Creation Message");
- free(notification_message);
- }
- syslog(LOG_DEBUG, "imap_create() completed");
-}
-
-
-/*
- * Locate a room by its IMAP folder name, and check access to it.
- * If zapped_ok is nonzero, we can also look for the room in the zapped list.
- */
-int imap_grabroom(char *returned_roomname, const char *foldername, int zapped_ok)
-{
- int ret;
- char augmented_roomname[ROOMNAMELEN];
- char roomname[ROOMNAMELEN];
- int c;
- struct ctdlroom QRscratch;
- int ra;
- int ok = 0;
-
- ret = imap_roomname(roomname, sizeof roomname, foldername);
- if (ret < 0) {
- return (1);
- }
-
- /* First try a regular match */
- c = CtdlGetRoom(&QRscratch, roomname);
-
- /* Then try a mailbox name match */
- if (c != 0) {
- CtdlMailboxName(augmented_roomname, sizeof augmented_roomname,
- &CC->user, roomname);
- c = CtdlGetRoom(&QRscratch, augmented_roomname);
- if (c == 0)
- safestrncpy(roomname, augmented_roomname, sizeof(roomname));
- }
-
- /* If the room exists, check security/access */
- if (c == 0) {
- /* See if there is an existing user/room relationship */
- CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL);
-
- /* normal clients have to pass through security */
- if (ra & UA_KNOWN) {
- ok = 1;
- }
- if ((zapped_ok) && (ra & UA_ZAPPED)) {
- ok = 1;
- }
- }
-
- /* Fail here if no such room */
- if (!ok) {
- strcpy(returned_roomname, "");
- return (2);
- } else {
- safestrncpy(returned_roomname, QRscratch.QRname, ROOMNAMELEN);
- return (0);
- }
-}
-
-
-/*
- * Implements the STATUS command (sort of)
- *
- */
-void imap_status(int num_parms, ConstStr *Params)
-{
- long len;
- int ret;
- char roomname[ROOMNAMELEN];
- char imaproomname[SIZ];
- char savedroom[ROOMNAMELEN];
- int msgs, new;
-
- ret = imap_grabroom(roomname, Params[2].Key, 1);
- if (ret != 0) {
- IReply("NO Invalid mailbox name or location, or access denied");
- return;
- }
-
- /*
- * CtdlUserGoto() formally takes us to the desired room, happily returning
- * the number of messages and number of new messages. (If another
- * folder is selected, save its name so we can return there!!!!!)
- */
- if (IMAP->selected) {
- strcpy(savedroom, CC->room.QRname);
- }
- CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
-
- /*
- * Tell the client what it wants to know. In fact, tell it *more* than
- * it wants to know. We happily IGnore the supplied status data item
- * names and simply spew all possible data items. It's far easier to
- * code and probably saves us some processing time too.
- */
- len = imap_mailboxname(imaproomname, sizeof imaproomname, &CC->room);
- IAPuts("* STATUS ");
- IPutStr(imaproomname, len);
- IAPrintf(" (MESSAGES %d ", msgs);
- IAPrintf("RECENT %d ", new); /* Initially, new==recent */
- IAPrintf("UIDNEXT %ld ", CtdlGetConfigLong("MMhighest") + 1);
- IAPrintf("UNSEEN %d)\r\n", new);
-
- /*
- * If another folder is selected, go back to that room so we can resume
- * our happy day without violent explosions.
- */
- if (IMAP->selected) {
- CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
- }
-
- /*
- * Oooh, look, we're done!
- */
- IReply("OK STATUS completed");
-}
-
-
-/*
- * Implements the SUBSCRIBE command
- *
- */
-void imap_subscribe(int num_parms, ConstStr *Params)
-{
- int ret;
- char roomname[ROOMNAMELEN];
- char savedroom[ROOMNAMELEN];
- int msgs, new;
-
- ret = imap_grabroom(roomname, Params[2].Key, 1);
- if (ret != 0) {
- IReplyPrintf(
- "NO Error %d: invalid mailbox name or location, or access denied",
- ret
- );
- return;
- }
-
- /*
- * CtdlUserGoto() formally takes us to the desired room, which has the side
- * effect of marking the room as not-zapped ... exactly the effect
- * we're looking for.
- */
- if (IMAP->selected) {
- strcpy(savedroom, CC->room.QRname);
- }
- CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
-
- /*
- * If another folder is selected, go back to that room so we can resume
- * our happy day without violent explosions.
- */
- if (IMAP->selected) {
- CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
- }
-
- IReply("OK SUBSCRIBE completed");
-}
-
-
-/*
- * Implements the UNSUBSCRIBE command
- *
- */
-void imap_unsubscribe(int num_parms, ConstStr *Params)
-{
- int ret;
- char roomname[ROOMNAMELEN];
- char savedroom[ROOMNAMELEN];
- int msgs, new;
-
- ret = imap_grabroom(roomname, Params[2].Key, 1);
- if (ret != 0) {
- IReply("NO Invalid mailbox name or location, or access denied");
- return;
- }
-
- /*
- * CtdlUserGoto() formally takes us to the desired room.
- */
- if (IMAP->selected) {
- strcpy(savedroom, CC->room.QRname);
- }
- CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
-
- /*
- * Now make the API call to zap the room
- */
- if (CtdlForgetThisRoom() == 0) {
- IReply("OK UNSUBSCRIBE completed");
- } else {
- IReply("NO You may not unsubscribe from this folder.");
- }
-
- /*
- * If another folder is selected, go back to that room so we can resume
- * our happy day without violent explosions.
- */
- if (IMAP->selected) {
- CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
- }
-}
-
-
-/*
- * Implements the DELETE command
- *
- */
-void imap_delete(int num_parms, ConstStr *Params)
-{
- int ret;
- char roomname[ROOMNAMELEN];
- char savedroom[ROOMNAMELEN];
- int msgs, new;
-
- ret = imap_grabroom(roomname, Params[2].Key, 1);
- if (ret != 0) {
- IReply("NO Invalid mailbox name, or access denied");
- return;
- }
-
- /*
- * CtdlUserGoto() formally takes us to the desired room, happily returning
- * the number of messages and number of new messages. (If another
- * folder is selected, save its name so we can return there!!!!!)
- */
- if (IMAP->selected) {
- strcpy(savedroom, CC->room.QRname);
- }
- CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
-
- /*
- * Now delete the room.
- */
- if (CtdlDoIHavePermissionToDeleteThisRoom(&CC->room)) {
- CtdlScheduleRoomForDeletion(&CC->room);
- IReply("OK DELETE completed");
- } else {
- IReply("NO Can't delete this folder.");
- }
-
- /*
- * If another folder is selected, go back to that room so we can resume
- * our happy day without violent explosions.
- */
- if (IMAP->selected) {
- CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
- }
-}
-
-
-/*
- * Back end function for imap_rename()
- */
-void imap_rename_backend(struct ctdlroom *qrbuf, void *data)
-{
- char foldername[SIZ];
- char newfoldername[SIZ];
- char newroomname[ROOMNAMELEN];
- int newfloor = 0;
- struct irl *irlp = NULL; /* scratch pointer */
- irlparms *myirlparms;
-
- myirlparms = (irlparms *) data;
- imap_mailboxname(foldername, sizeof foldername, qrbuf);
-
- /* Rename subfolders */
- if ((!strncasecmp(foldername, myirlparms->oldname,
- myirlparms->oldnamelen)
- && (foldername[myirlparms->oldnamelen] == '/'))) {
-
- sprintf(newfoldername, "%s/%s",
- myirlparms->newname,
- &foldername[myirlparms->oldnamelen + 1]
- );
-
- newfloor = imap_roomname(newroomname,
- sizeof newroomname,
- newfoldername) & 0xFF;
-
- irlp = (struct irl *) malloc(sizeof(struct irl));
- strcpy(irlp->irl_newroom, newroomname);
- strcpy(irlp->irl_oldroom, qrbuf->QRname);
- irlp->irl_newfloor = newfloor;
- irlp->next = *(myirlparms->irl);
- *(myirlparms->irl) = irlp;
- }
-}
-
-
-/*
- * Implements the RENAME command
- *
- */
-void imap_rename(int num_parms, ConstStr *Params)
-{
- char old_room[ROOMNAMELEN];
- char new_room[ROOMNAMELEN];
- int newr;
- int new_floor;
- int r;
- struct irl *irl = NULL; /* the list */
- struct irl *irlp = NULL; /* scratch pointer */
- irlparms irlparms;
- char aidemsg[1024];
-
- if (strchr(Params[3].Key, '\\') != NULL) {
- IReply("NO Invalid character in folder name");
- return;
- }
-
- imap_roomname(old_room, sizeof old_room, Params[2].Key);
- newr = imap_roomname(new_room, sizeof new_room, Params[3].Key);
- new_floor = (newr & 0xFF);
-
- r = CtdlRenameRoom(old_room, new_room, new_floor);
-
- if (r == crr_room_not_found) {
- IReply("NO Could not locate this folder");
- return;
- }
- if (r == crr_already_exists) {
- IReplyPrintf("NO '%s' already exists.");
- return;
- }
- if (r == crr_noneditable) {
- IReply("NO This folder is not editable.");
- return;
- }
- if (r == crr_invalid_floor) {
- IReply("NO Folder root does not exist.");
- return;
- }
- if (r == crr_access_denied) {
- IReply("NO You do not have permission to edit this folder.");
- return;
- }
- if (r != crr_ok) {
- IReplyPrintf("NO Rename failed - undefined error %d", r);
- return;
- }
-
- /* If this is the INBOX, then RFC2060 says we have to just move the
- * contents. In a Citadel environment it's easier to rename the room
- * (already did that) and create a new inbox.
- */
- if (!strcasecmp(Params[2].Key, "INBOX")) {
- CtdlCreateRoom(MAILROOM, 4, "", 0, 1, 0, VIEW_MAILBOX);
- }
-
- /* Otherwise, do the subfolders. Build a list of rooms to rename... */
- else {
- irlparms.oldname = Params[2].Key;
- irlparms.oldnamelen = Params[2].len;
- irlparms.newname = Params[3].Key;
- irlparms.newnamelen = Params[3].len;
- irlparms.irl = &irl;
- CtdlForEachRoom(imap_rename_backend, (void *) &irlparms);
-
- /* ... and now rename them. */
- while (irl != NULL) {
- r = CtdlRenameRoom(irl->irl_oldroom,
- irl->irl_newroom,
- irl->irl_newfloor);
- if (r != crr_ok) {
- /* FIXME handle error returns better */
- syslog(LOG_ERR, "CtdlRenameRoom() error %d", r);
- }
- irlp = irl;
- irl = irl->next;
- free(irlp);
- }
- }
-
- snprintf(aidemsg, sizeof aidemsg, "IMAP folder \"%s\" renamed to \"%s\" by %s\n",
- Params[2].Key,
- Params[3].Key,
- CC->curr_user
- );
- CtdlAideMessage(aidemsg, "IMAP folder rename");
-
- IReply("OK RENAME completed");
-}
-
-
-/*
- * Main command loop for IMAP sessions.
- */
-void imap_command_loop(void)
-{
- struct CitContext *CCC = CC;
- struct timeval tv1, tv2;
- suseconds_t total_time = 0;
- citimap *Imap;
- const char *pchs, *pche;
- const imap_handler_hook *h;
-
- gettimeofday(&tv1, NULL);
- CCC->lastcmd = time(NULL);
- Imap = CCCIMAP;
-
- flush_output();
- if (Imap->Cmd.CmdBuf == NULL)
- Imap->Cmd.CmdBuf = NewStrBufPlain(NULL, SIZ);
- else
- FlushStrBuf(Imap->Cmd.CmdBuf);
-
- if (CtdlClientGetLine(Imap->Cmd.CmdBuf) < 1) {
- syslog(LOG_ERR, "client disconnected: ending session.");
- CC->kill_me = KILLME_CLIENT_DISCONNECTED;
- return;
- }
-
- if (Imap->authstate == imap_as_expecting_password) {
- syslog(LOG_INFO, "<password>");
- }
- else if (Imap->authstate == imap_as_expecting_plainauth) {
- syslog(LOG_INFO, "<plain_auth>");
- }
- else if ((Imap->authstate == imap_as_expecting_multilineusername) ||
- cbmstrcasestr(ChrPtr(Imap->Cmd.CmdBuf), " LOGIN ")) {
- syslog(LOG_INFO, "LOGIN...");
- }
- else {
- syslog(LOG_DEBUG, "%s", ChrPtr(Imap->Cmd.CmdBuf));
- }
-
- pchs = ChrPtr(Imap->Cmd.CmdBuf);
- pche = pchs + StrLength(Imap->Cmd.CmdBuf);
-
- while ((pche > pchs) &&
- ((*pche == '\n') ||
- (*pche == '\r')))
- {
- pche --;
- StrBufCutRight(Imap->Cmd.CmdBuf, 1);
- }
- StrBufTrim(Imap->Cmd.CmdBuf);
-
- /* If we're in the middle of a multi-line command, handle that */
- switch (Imap->authstate){
- case imap_as_expecting_username:
- imap_auth_login_user(imap_as_expecting_username);
- IUnbuffer();
- return;
- case imap_as_expecting_multilineusername:
- imap_auth_login_user(imap_as_expecting_multilineusername);
- IUnbuffer();
- return;
- case imap_as_expecting_plainauth:
- imap_auth_plain();
- IUnbuffer();
- return;
- case imap_as_expecting_password:
- imap_auth_login_pass(imap_as_expecting_password);
- IUnbuffer();
- return;
- case imap_as_expecting_multilinepassword:
- imap_auth_login_pass(imap_as_expecting_multilinepassword);
- IUnbuffer();
- return;
- default:
- break;
- }
-
- /* Ok, at this point we're in normal command mode.
- * If the command just submitted does not contain a literal, we
- * might think about delivering some untagged stuff...
- */
-
- /* Grab the tag, command, and parameters. */
- imap_parameterize(&Imap->Cmd);
-#if 0
-/* debug output the parsed vector */
- {
- int i;
- syslog(LOG_DEBUG, "----- %ld params", Imap->Cmd.num_parms);
-
- for (i=0; i < Imap->Cmd.num_parms; i++) {
- if (Imap->Cmd.Params[i].len != strlen(Imap->Cmd.Params[i].Key))
- syslog(LOG_DEBUG, "*********** %ld != %ld : %s",
- Imap->Cmd.Params[i].len,
- strlen(Imap->Cmd.Params[i].Key),
- Imap->Cmd.Params[i].Key);
- else
- syslog(LOG_DEBUG, "%ld : %s",
- Imap->Cmd.Params[i].len,
- Imap->Cmd.Params[i].Key);
- }}
-#endif
-
- /* Now for the command set. */
- h = imap_lookup(Imap->Cmd.num_parms, Imap->Cmd.Params);
-
- if (h == NULL)
- {
- IReply("BAD command unrecognized");
- goto BAIL;
- }
-
- /* RFC3501 says that we cannot output untagged data during these commands */
- if ((h->Flags & I_FLAG_UNTAGGED) == 0) {
-
- /* we can put any additional untagged stuff right here in the future */
-
- /*
- * Before processing the command that was just entered... if we happen
- * to have a folder selected, we'd like to rescan that folder for new
- * messages, and for deletions/changes of existing messages. This
- * could probably be optimized better with some deep thought...
- */
- if (Imap->selected) {
- imap_rescan_msgids();
- }
- }
-
- /* does our command require a logged-in state */
- if ((!CC->logged_in) && ((h->Flags & I_FLAG_LOGGED_IN) != 0)) {
- IReply("BAD Not logged in.");
- goto BAIL;
- }
-
- /* does our command require the SELECT state on a mailbox */
- if ((Imap->selected == 0) && ((h->Flags & I_FLAG_SELECT) != 0)){
- IReply("BAD no folder selected");
- goto BAIL;
- }
- h->h(Imap->Cmd.num_parms, Imap->Cmd.Params);
-
- /* If the client transmitted a message we can free it now */
-
-BAIL:
- IUnbuffer();
-
- imap_free_transmitted_message();
-
- gettimeofday(&tv2, NULL);
- total_time = (tv2.tv_usec + (tv2.tv_sec * 1000000)) - (tv1.tv_usec + (tv1.tv_sec * 1000000));
- syslog(LOG_DEBUG, "IMAP command completed in %ld.%ld seconds",
- (total_time / 1000000),
- (total_time % 1000000)
- );
-}
-
-void imap_noop (int num_parms, ConstStr *Params)
-{
- IReply("OK No operation");
-}
-
-void imap_logout(int num_parms, ConstStr *Params)
-{
- if (IMAP->selected) {
- imap_do_expunge(); /* yes, we auto-expunge at logout */
- }
- IAPrintf("* BYE %s logging out\r\n", CtdlGetConfigStr("c_fqdn"));
- IReply("OK Citadel IMAP session ended.");
- CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
- return;
-}
-
-const char *CitadelServiceIMAP="IMAP";
-const char *CitadelServiceIMAPS="IMAPS";
-
-/*
- * This function is called to register the IMAP extension with Citadel.
- */
-CTDL_MODULE_INIT(imap)
-{
- if (ImapCmds == NULL)
- ImapCmds = NewHash(1, NULL);
-
- RegisterImapCMD("NOOP", "", imap_noop, I_FLAG_NONE);
- RegisterImapCMD("CHECK", "", imap_noop, I_FLAG_NONE);
- RegisterImapCMD("ID", "", imap_id, I_FLAG_NONE);
- RegisterImapCMD("LOGOUT", "", imap_logout, I_FLAG_NONE);
- RegisterImapCMD("LOGIN", "", imap_login, I_FLAG_NONE);
- RegisterImapCMD("AUTHENTICATE", "", imap_authenticate, I_FLAG_NONE);
- RegisterImapCMD("CAPABILITY", "", imap_capability, I_FLAG_NONE);
-#ifdef HAVE_OPENSSL
- RegisterImapCMD("STARTTLS", "", imap_starttls, I_FLAG_NONE);
-#endif
-
- /* The commans below require a logged-in state */
- RegisterImapCMD("SELECT", "", imap_select, I_FLAG_LOGGED_IN);
- RegisterImapCMD("EXAMINE", "", imap_select, I_FLAG_LOGGED_IN);
- RegisterImapCMD("LSUB", "", imap_list, I_FLAG_LOGGED_IN);
- RegisterImapCMD("LIST", "", imap_list, I_FLAG_LOGGED_IN);
- RegisterImapCMD("CREATE", "", imap_create, I_FLAG_LOGGED_IN);
- RegisterImapCMD("DELETE", "", imap_delete, I_FLAG_LOGGED_IN);
- RegisterImapCMD("RENAME", "", imap_rename, I_FLAG_LOGGED_IN);
- RegisterImapCMD("STATUS", "", imap_status, I_FLAG_LOGGED_IN);
- RegisterImapCMD("SUBSCRIBE", "", imap_subscribe, I_FLAG_LOGGED_IN);
- RegisterImapCMD("UNSUBSCRIBE", "", imap_unsubscribe, I_FLAG_LOGGED_IN);
- RegisterImapCMD("APPEND", "", imap_append, I_FLAG_LOGGED_IN);
- RegisterImapCMD("NAMESPACE", "", imap_namespace, I_FLAG_LOGGED_IN);
- RegisterImapCMD("SETACL", "", imap_setacl, I_FLAG_LOGGED_IN);
- RegisterImapCMD("DELETEACL", "", imap_deleteacl, I_FLAG_LOGGED_IN);
- RegisterImapCMD("GETACL", "", imap_getacl, I_FLAG_LOGGED_IN);
- RegisterImapCMD("LISTRIGHTS", "", imap_listrights, I_FLAG_LOGGED_IN);
- RegisterImapCMD("MYRIGHTS", "", imap_myrights, I_FLAG_LOGGED_IN);
- RegisterImapCMD("GETMETADATA", "", imap_getmetadata, I_FLAG_LOGGED_IN);
- RegisterImapCMD("SETMETADATA", "", imap_setmetadata, I_FLAG_LOGGED_IN);
-
- /* The commands below require the SELECT state on a mailbox */
- RegisterImapCMD("FETCH", "", imap_fetch, I_FLAG_LOGGED_IN | I_FLAG_SELECT | I_FLAG_UNTAGGED);
- RegisterImapCMD("UID", "FETCH", imap_uidfetch, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
- RegisterImapCMD("SEARCH", "", imap_search, I_FLAG_LOGGED_IN | I_FLAG_SELECT | I_FLAG_UNTAGGED);
- RegisterImapCMD("UID", "SEARCH", imap_uidsearch, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
- RegisterImapCMD("STORE", "", imap_store, I_FLAG_LOGGED_IN | I_FLAG_SELECT | I_FLAG_UNTAGGED);
- RegisterImapCMD("UID", "STORE", imap_uidstore, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
- RegisterImapCMD("COPY", "", imap_copy, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
- RegisterImapCMD("UID", "COPY", imap_uidcopy, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
- RegisterImapCMD("EXPUNGE", "", imap_expunge, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
- RegisterImapCMD("UID", "EXPUNGE", imap_expunge, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
- RegisterImapCMD("CLOSE", "", imap_close, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
-
- if (!threading)
- {
- CtdlRegisterServiceHook(CtdlGetConfigInt("c_imap_port"),
- NULL, imap_greeting, imap_command_loop, NULL, CitadelServiceIMAP);
-#ifdef HAVE_OPENSSL
- CtdlRegisterServiceHook(CtdlGetConfigInt("c_imaps_port"),
- NULL, imaps_greeting, imap_command_loop, NULL, CitadelServiceIMAPS);
-#endif
- CtdlRegisterSessionHook(imap_cleanup_function, EVT_STOP, PRIO_STOP + 30);
- }
-
- /* return our module name for the log */
- return "imap";
-}
+++ /dev/null
-#define GLOBAL_UIDVALIDITY_VALUE 1L
-
-
-void imap_cleanup_function(void);
-void imap_greeting(void);
-void imap_command_loop(void);
-int imap_grabroom(char *returned_roomname, const char *foldername, int zapped_ok);
-void imap_free_transmitted_message(void);
-int imap_do_expunge(void);
-void imap_rescan_msgids(void);
-
-/*
- * FDELIM defines which character we want to use as a folder delimiter
- * in room names. Originally we used a forward slash, but that caused
- * rooms with names like "Sent/Received Pages" to get delimited, so we
- * changed it to a backslash. This is completely irrelevant to how Citadel
- * speaks to IMAP clients -- the delimiter used in the IMAP protocol is
- * a vertical bar, which is illegal in Citadel room names anyway.
- */
-
-typedef void (*imap_handler)(int num_parms, ConstStr *Params);
-
-typedef struct _imap_handler_hook {
- imap_handler h;
- int Flags;
-} imap_handler_hook;
-
-typedef struct __citimap_command {
- StrBuf *CmdBuf; /* our current commandline; gets chopped into: */
- ConstStr *Params; /* Commandline tokens */
- int num_parms; /* Number of Commandline tokens available */
- int avail_parms; /* Number of ConstStr args is big */
- const imap_handler_hook *hh;
-} citimap_command;
-
-
-typedef struct __citimap {
- StrBuf *Reply;
- int authstate;
- char authseq[SIZ];
- int selected; /* set to 1 if in the SELECTED state */
- int readonly; /* mailbox is open read only */
- int num_msgs; /* Number of messages being mapped */
- int num_alloc; /* Number of messages for which we've allocated space */
- time_t last_mtime; /* For checking whether the room was modified... */
- long *msgids;
- unsigned int *flags;
-
- StrBuf *TransmittedMessage; /* for APPEND command... */
-
- citimap_command Cmd; /* our current commandline */
-
- /* Cache most recent RFC822 FETCH because client might load in pieces */
- StrBuf *cached_rfc822;
- long cached_rfc822_msgnum;
- char cached_rfc822_withbody; /* 1 = body cached; 0 = only headers cached */
-
- /* Cache most recent BODY FETCH because client might load in pieces */
- char *cached_body;
- size_t cached_body_len;
- char cached_bodypart[SIZ];
- long cached_bodymsgnum;
- char cached_body_withbody; /* 1 = body cached; 0 = only headers cached */
-} citimap;
-
-/*
- * values of 'authstate'
- */
-enum {
- imap_as_normal,
- imap_as_expecting_username,
- imap_as_expecting_password,
- imap_as_expecting_plainauth,
- imap_as_expecting_multilineusername,
- imap_as_expecting_multilinepassword
-};
-
-/* Flags for the above struct. Note that some of these are for internal use,
- * and are not to be reported to IMAP clients.
- */
-#define IMAP_ANSWERED 1 /* reportable and setable */
-#define IMAP_FLAGGED 2 /* reportable and setable */
-#define IMAP_DELETED 4 /* reportable and setable */
-#define IMAP_DRAFT 8 /* reportable and setable */
-#define IMAP_SEEN 16 /* reportable and setable */
-
-#define IMAP_MASK_SETABLE 0x1f
-#define IMAP_MASK_SYSTEM 0xe0
-
-#define IMAP_SELECTED 32 /* neither reportable nor setable */
-#define IMAP_RECENT 64 /* reportable but not setable */
-
-
-/*
- * Flags that may be returned by imap_roomname()
- * (the lower eight bits will be the floor number)
- */
-#define IR_MAILBOX 0x0100 /* Mailbox */
-#define IR_EXISTS 0x0200 /* Room exists (not implemented) */
-#define IR_BABOON 0x0000 /* Just had to put this here :) */
-
-#define FDELIM '\\'
-
-#define IMAP ((citimap *)CC->session_specific_data)
-#define CCCIMAP ((citimap *)CCC->session_specific_data)
-
-#define IMAPDBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (IMAPDebugEnabled != 0))
-
-#define I_FLAG_NONE (0)
-#define I_FLAG_LOGGED_IN (1<<0)
-#define I_FLAG_SELECT (1<<1)
-/* RFC3501 says that we cannot output untagged data during these commands */
-#define I_FLAG_UNTAGGED (1<<2)
-
-/*
- * When loading arrays of message ID's into memory, increase the buffer to
- * hold this many additional messages instead of calling realloc() each time.
- */
-#define REALLOC_INCREMENT 100
-
-
-void registerImapCMD(const char *First, long FLen,
- const char *Second, long SLen,
- imap_handler H,
- int Flags);
-
-#define RegisterImapCMD(First, Second, H, Flags) \
- registerImapCMD(HKEY(First), HKEY(Second), H, Flags)
+++ /dev/null
-// Inbox handling rules
-//
-// Copyright (c) 1987-2022 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.
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <ctype.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "ctdl_module.h"
-
-
-// The next sections are enums and keys that drive the serialize/deserialize functions for the inbox rules/state configuration.
-
-// Fields to be compared
-enum {
- field_from,
- field_tocc,
- field_subject,
- field_replyto,
- field_sender,
- field_resentfrom,
- field_resentto,
- field_envfrom,
- field_envto,
- field_xmailer,
- field_xspamflag,
- field_xspamstatus,
- field_listid,
- field_size,
- field_all
-};
-char *field_keys[] = {
- "from",
- "tocc",
- "subject",
- "replyto",
- "sender",
- "resentfrom",
- "resentto",
- "envfrom",
- "envto",
- "xmailer",
- "xspamflag",
- "xspamstatus",
- "listid",
- "size",
- "all"
-};
-
-// Field comparison operators
-enum {
- fcomp_contains,
- fcomp_notcontains,
- fcomp_is,
- fcomp_isnot,
- fcomp_matches,
- fcomp_notmatches
-};
-char *fcomp_keys[] = {
- "contains",
- "notcontains",
- "is",
- "isnot",
- "matches",
- "notmatches"
-};
-
-// Actions
-enum {
- action_keep,
- action_discard,
- action_reject,
- action_fileinto,
- action_redirect,
- action_vacation
-};
-char *action_keys[] = {
- "keep",
- "discard",
- "reject",
- "fileinto",
- "redirect",
- "vacation"
-};
-
-// Size comparison operators
-enum {
- scomp_larger,
- scomp_smaller
-};
-char *scomp_keys[] = {
- "larger",
- "smaller"
-};
-
-// Final actions
-enum {
- final_continue,
- final_stop
-};
-char *final_keys[] = {
- "continue",
- "stop"
-};
-
-// This data structure represents ONE inbox rule within the configuration.
-struct irule {
- int compared_field;
- int field_compare_op;
- char compared_value[128];
- int size_compare_op;
- long compared_size;
- int action;
- char file_into[ROOMNAMELEN];
- char redirect_to[1024];
- char autoreply_message[SIZ];
- int final_action;
-};
-
-// This data structure represents the entire inbox rules configuration AND current state for a single user.
-struct inboxrules {
- long lastproc;
- int num_rules;
- struct irule *rules;
-};
-
-
-// Destructor for 'struct inboxrules'
-void free_inbox_rules(struct inboxrules *ibr) {
- free(ibr->rules);
- free(ibr);
-}
-
-
-// Constructor for 'struct inboxrules' that deserializes the configuration from text input.
-struct inboxrules *deserialize_inbox_rules(char *serialized_rules) {
- int i;
-
- if (!serialized_rules) {
- return NULL;
- }
-
- // Make a copy of the supplied buffer because we're going to shit all over it with strtok_r()
- char *sr = strdup(serialized_rules);
- if (!sr) {
- return NULL;
- }
-
- struct inboxrules *ibr = malloc(sizeof(struct inboxrules));
- if (ibr == NULL) {
- return NULL;
- }
- memset(ibr, 0, sizeof(struct inboxrules));
-
- char *token;
- char *rest = sr;
- while ((token = strtok_r(rest, "\n", &rest))) {
-
- // For backwards compatibility, "# WEBCIT_RULE" is an alias for "rule".
- // Prior to version 930, WebCit converted its rules to Sieve scripts, but saved the rules as comments for
- // later re-editing. Now, the rules hidden in the comments become the real rules.
- if (!strncasecmp(token, "# WEBCIT_RULE|", 14)) {
- strcpy(token, "rule|");
- strcpy(&token[5], &token[14]);
- }
-
- // Lines containing actual rules are double-serialized with Base64. It seemed like a good idea at the time :(
- if (!strncasecmp(token, "rule|", 5)) {
- remove_token(&token[5], 0, '|');
- char *decoded_rule = malloc(strlen(token));
- CtdlDecodeBase64(decoded_rule, &token[5], strlen(&token[5]));
- ibr->num_rules++;
- ibr->rules = realloc(ibr->rules, (sizeof(struct irule) * ibr->num_rules));
- struct irule *new_rule = &ibr->rules[ibr->num_rules - 1];
- memset(new_rule, 0, sizeof(struct irule));
-
- // We have a rule , now parse it
- char rtoken[SIZ];
- int nt = num_tokens(decoded_rule, '|');
- int t = 0;
- for (t=0; t<nt; ++t) {
- extract_token(rtoken, decoded_rule, t, '|', sizeof(rtoken));
- striplt(rtoken);
- switch(t) {
- case 1: // field to compare
- for (i=0; i<=field_all; ++i) {
- if (!strcasecmp(rtoken, field_keys[i])) {
- new_rule->compared_field = i;
- }
- }
- break;
- case 2: // field comparison operation
- for (i=0; i<=fcomp_notmatches; ++i) {
- if (!strcasecmp(rtoken, fcomp_keys[i])) {
- new_rule->field_compare_op = i;
- }
- }
- break;
- case 3: // field comparison value
- safestrncpy(new_rule->compared_value, rtoken, sizeof(new_rule->compared_value));
- break;
- case 4: // size comparison operation
- for (i=0; i<=scomp_smaller; ++i) {
- if (!strcasecmp(rtoken, scomp_keys[i])) {
- new_rule->size_compare_op = i;
- }
- }
- break;
- case 5: // size comparison value
- new_rule->compared_size = atol(rtoken);
- break;
- case 6: // action
- for (i=0; i<=action_vacation; ++i) {
- if (!strcasecmp(rtoken, action_keys[i])) {
- new_rule->action = i;
- }
- }
- break;
- case 7: // file into (target room)
- safestrncpy(new_rule->file_into, rtoken, sizeof(new_rule->file_into));
- break;
- case 8: // redirect to (target address)
- safestrncpy(new_rule->redirect_to, rtoken, sizeof(new_rule->redirect_to));
- break;
- case 9: // autoreply message
- safestrncpy(new_rule->autoreply_message, rtoken, sizeof(new_rule->autoreply_message));
- break;
- case 10: // final_action;
- for (i=0; i<=final_stop; ++i) {
- if (!strcasecmp(rtoken, final_keys[i])) {
- new_rule->final_action = i;
- }
- }
- break;
- default:
- break;
- }
- }
- free(decoded_rule);
- }
-
- // "lastproc" indicates the newest message number in the inbox that was previously processed by our inbox rules.
- // This is a legacy location for this value and will only be used if it's the only one present.
- else if (!strncasecmp(token, "lastproc|", 5)) {
- ibr->lastproc = atol(&token[9]);
- }
-
- // Lines which do not contain a recognizable token must be IGNORED. These lines may be left over
- // from a previous version and will disappear when we rewrite the config.
-
- }
-
- free(sr); // free our copy of the source buffer that has now been trashed with null bytes...
- return(ibr); // and return our complex data type to the caller.
-}
-
-
-// Perform the "fileinto" action (save the message in another room)
-// Returns: 1 or 0 to tell the caller to keep (1) or delete (0) the inbox copy of the message.
-//
-int inbox_do_fileinto(struct irule *rule, long msgnum) {
- char *dest_folder = rule->file_into;
- char original_room_name[ROOMNAMELEN];
- char foldername[ROOMNAMELEN];
- int c;
-
- // Situations where we want to just keep the message in the inbox:
- if (
- (IsEmptyStr(dest_folder)) // no destination room was specified
- || (!strcasecmp(dest_folder, "INBOX")) // fileinto inbox is the same as keep
- || (!strcasecmp(dest_folder, MAILROOM)) // fileinto "Mail" is the same as keep
- ) {
- return(1); // don't delete the inbox copy if this failed
- }
-
- // Remember what room we came from
- safestrncpy(original_room_name, CC->room.QRname, sizeof original_room_name);
-
- // First try a mailbox name match (check personal mail folders first)
- strcpy(foldername, original_room_name); // This keeps the user namespace of the inbox
- snprintf(&foldername[10], sizeof(foldername)-10, ".%s", dest_folder); // And this tacks on the target room name
- c = CtdlGetRoom(&CC->room, foldername);
-
- // Then a regular room name match (public and private rooms)
- if (c != 0) {
- safestrncpy(foldername, dest_folder, sizeof foldername);
- c = CtdlGetRoom(&CC->room, foldername);
- }
-
- if (c != 0) {
- syslog(LOG_WARNING, "inboxrules: target <%s> does not exist", dest_folder);
- return(1); // don't delete the inbox copy if this failed
- }
-
- // Yes, we actually have to go there
- CtdlUserGoto(NULL, 0, 0, NULL, NULL, NULL, NULL);
-
- c = CtdlSaveMsgPointersInRoom(NULL, &msgnum, 1, 0, NULL, 0);
-
- // Go back to the room we came from
- if (strcasecmp(original_room_name, CC->room.QRname)) {
- CtdlUserGoto(original_room_name, 0, 0, NULL, NULL, NULL, NULL);
- }
-
- return(0); // delete the inbox copy
-}
-
-
-// Perform the "redirect" action (divert the message to another email address)
-// Returns: 1 or 0 to tell the caller to keep (1) or delete (0) the inbox copy of the message.
-//
-int inbox_do_redirect(struct irule *rule, long msgnum) {
- if (IsEmptyStr(rule->redirect_to)) {
- syslog(LOG_WARNING, "inboxrules: inbox_do_redirect() invalid recipient <%s>", rule->redirect_to);
- return(1); // don't delete the inbox copy if this failed
- }
-
- struct recptypes *valid = validate_recipients(rule->redirect_to, NULL, 0);
- if (valid == NULL) {
- syslog(LOG_WARNING, "inboxrules: inbox_do_redirect() invalid recipient <%s>", rule->redirect_to);
- return(1); // don't delete the inbox copy if this failed
- }
- if (valid->num_error > 0) {
- free_recipients(valid);
- syslog(LOG_WARNING, "inboxrules: inbox_do_redirect() invalid recipient <%s>", rule->redirect_to);
- return(1); // don't delete the inbox copy if this failed
- }
-
- struct CtdlMessage *msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) {
- free_recipients(valid);
- syslog(LOG_WARNING, "inboxrules: cannot reload message %ld for forwarding", msgnum);
- return(1); // don't delete the inbox copy if this failed
- }
-
- CtdlSubmitMsg(msg, valid, NULL); // send the message to the new recipient
- free_recipients(valid);
- CM_Free(msg);
- return(0); // delete the inbox copy
-}
-
-
-// Perform the "reject" action (delete the message, and tell the sender we deleted it)
-void inbox_do_reject(struct irule *rule, struct CtdlMessage *msg) {
- syslog(LOG_DEBUG, "inbox_do_reject: sender: <%s>, reject", msg->cm_fields[erFc822Addr]);
-
- // If we can't determine who sent the message, reject silently.
- char *sender;
- if (!IsEmptyStr(msg->cm_fields[eMessagePath])) {
- sender = msg->cm_fields[eMessagePath];
- }
- else if (!IsEmptyStr(msg->cm_fields[erFc822Addr])) {
- sender = msg->cm_fields[erFc822Addr];
- }
- else {
- return;
- }
-
- // Assemble the reject message.
- char *reject_text = malloc(strlen(rule->autoreply_message) + 1024);
- if (reject_text == NULL) {
- return;
- }
- sprintf(reject_text,
- "Content-type: text/plain\n"
- "\n"
- "The message was refused by the recipient's mail filtering program.\n"
- "The reason given was as follows:\n"
- "\n"
- "%s\n"
- "\n"
- ,
- rule->autoreply_message
- );
-
- // Deliver the message
- quickie_message(
- " ",
- msg->cm_fields[eenVelopeTo],
- sender,
- MAILROOM,
- reject_text,
- FMT_RFC822,
- "Delivery status notification"
- );
- free(reject_text);
-}
-
-
-// Perform the "vacation" action (send an automatic response)
-void inbox_do_vacation(struct irule *rule, struct CtdlMessage *msg) {
- syslog(LOG_DEBUG, "inbox_do_vacation: sender: <%s>, vacation", msg->cm_fields[erFc822Addr]);
-
- // If we can't determine who sent the message, no auto-reply can be sent.
- char *sender;
- if (!IsEmptyStr(msg->cm_fields[eMessagePath])) {
- sender = msg->cm_fields[eMessagePath];
- }
- else if (!IsEmptyStr(msg->cm_fields[erFc822Addr])) {
- sender = msg->cm_fields[erFc822Addr];
- }
- else {
- return;
- }
-
- // Avoid repeatedly sending auto-replies to the same correspondent over and over again by creating
- // a hash of the user, correspondent, and reply text, and hitting the S_USETABLE database.
- StrBuf *u = NewStrBuf();
- StrBufPrintf(u, "vacation/%x/%x/%x",
- HashLittle(sender, strlen(sender)),
- HashLittle(msg->cm_fields[eenVelopeTo], msg->cm_lengths[eenVelopeTo]),
- HashLittle(rule->autoreply_message, strlen(rule->autoreply_message))
- );
- int already_seen = CheckIfAlreadySeen(u);
- FreeStrBuf(&u);
-
- if (!already_seen) {
- // Assemble the auto-reply message.
- StrBuf *reject_text = NewStrBuf();
- if (reject_text == NULL) {
- return;
- }
-
- StrBufPrintf(reject_text,
- "Content-type: text/plain\n"
- "\n"
- "%s\n"
- "\n"
- ,
- rule->autoreply_message
- );
-
- // Deliver the auto-reply.
- quickie_message(
- "",
- msg->cm_fields[eenVelopeTo],
- sender,
- MAILROOM,
- ChrPtr(reject_text),
- FMT_RFC822,
- "Delivery status notification"
- );
- FreeStrBuf(&reject_text);
- }
-}
-
-
-/*
- * Process a single message. We know the room, the user, the rules, the message number, etc.
- */
-void inbox_do_msg(long msgnum, void *userdata) {
- struct inboxrules *ii = (struct inboxrules *) userdata;
- struct CtdlMessage *msg = NULL; // If we are loading a message to process, put it here.
- int headers_loaded = 0; // Did we load the headers yet? Do it only once.
- int body_loaded = 0; // Did we load the message body yet? Do it only once.
- int metadata_loaded = 0; // Did we load the metadata yet? Do it only once.
- struct MetaData smi; // If we are loading the metadata to compare, put it here.
- int rule_activated = 0; // On each rule, this is set if the compare succeeds and the rule activates.
- char compare_me[SIZ]; // On each rule, we will store the field to be compared here.
- int compare_compound = 0; // Set to 1 when we are comparing both display name and email address
- int keep_message = 1; // Nonzero to keep the message in the inbox after processing, 0 to delete it.
- int i;
-
- syslog(LOG_DEBUG, "inboxrules: processing message #%ld which is higher than %ld, we are in %s", msgnum, ii->lastproc, CC->room.QRname);
-
- if (ii->num_rules <= 0) {
- syslog(LOG_DEBUG, "inboxrules: rule set is empty");
- return;
- }
-
- for (i=0; i<ii->num_rules; ++i) {
- syslog(LOG_DEBUG, "inboxrules: processing rule %d is %s", i, field_keys[ ii->rules[i].compared_field ]);
- rule_activated = 0;
-
- // Before doing a field compare, check to see if we have the correct parts of the message in memory.
-
- switch(ii->rules[i].compared_field) {
- // These fields require loading only the top-level headers
- case field_from: // From:
- case field_tocc: // To: or Cc:
- case field_subject: // Subject:
- case field_replyto: // Reply-to:
- case field_listid: // List-ID:
- case field_envto: // Envelope-to:
- case field_envfrom: // Return-path:
- if (!headers_loaded) {
- syslog(LOG_DEBUG, "inboxrules: loading headers for message %ld", msgnum);
- msg = CtdlFetchMessage(msgnum, 0);
- if (!msg) {
- return;
- }
- headers_loaded = 1;
- }
- break;
- // These fields are not stored as Citadel headers, and therefore require a full message load.
- case field_sender:
- case field_resentfrom:
- case field_resentto:
- case field_xmailer:
- case field_xspamflag:
- case field_xspamstatus:
- if (!body_loaded) {
- syslog(LOG_DEBUG, "inboxrules: loading all of message %ld", msgnum);
- if (msg != NULL) {
- CM_Free(msg);
- }
- msg = CtdlFetchMessage(msgnum, 1);
- if (!msg) {
- return;
- }
- headers_loaded = 1;
- body_loaded = 1;
- }
- break;
- case field_size:
- if (!metadata_loaded) {
- syslog(LOG_DEBUG, "inboxrules: loading metadata for message %ld", msgnum);
- GetMetaData(&smi, msgnum);
- metadata_loaded = 1;
- }
- break;
- case field_all:
- syslog(LOG_DEBUG, "inboxrules: this is an always-on rule");
- break;
- default:
- syslog(LOG_DEBUG, "inboxrules: unknown rule key");
- }
-
- // If the rule involves a field comparison, load the field to be compared.
- compare_me[0] = 0;
- compare_compound = 0;
- switch(ii->rules[i].compared_field) {
- case field_from: // From:
- if ( (!IsEmptyStr(msg->cm_fields[erFc822Addr])) && (!IsEmptyStr(msg->cm_fields[erFc822Addr])) ) {
- snprintf(compare_me, sizeof compare_me, "%s|%s",
- msg->cm_fields[eAuthor],
- msg->cm_fields[erFc822Addr]
- );
- compare_compound = 1; // there will be two fields to compare "name|address"
- }
- else if (!IsEmptyStr(msg->cm_fields[erFc822Addr])) {
- safestrncpy(compare_me, msg->cm_fields[erFc822Addr], sizeof compare_me);
- }
- else if (!IsEmptyStr(msg->cm_fields[eAuthor])) {
- safestrncpy(compare_me, msg->cm_fields[eAuthor], sizeof compare_me);
- }
- break;
- case field_tocc: // To: or Cc:
- if (!IsEmptyStr(msg->cm_fields[eRecipient])) {
- safestrncpy(compare_me, msg->cm_fields[eRecipient], sizeof compare_me);
- }
- if (!IsEmptyStr(msg->cm_fields[eCarbonCopY])) {
- if (!IsEmptyStr(compare_me)) {
- strcat(compare_me, ",");
- }
- safestrncpy(&compare_me[strlen(compare_me)], msg->cm_fields[eCarbonCopY], (sizeof compare_me - strlen(compare_me)));
- }
- break;
- case field_subject: // Subject:
- if (!IsEmptyStr(msg->cm_fields[eMsgSubject])) {
- safestrncpy(compare_me, msg->cm_fields[eMsgSubject], sizeof compare_me);
- }
- break;
- case field_replyto: // Reply-to:
- if (!IsEmptyStr(msg->cm_fields[eReplyTo])) {
- safestrncpy(compare_me, msg->cm_fields[eReplyTo], sizeof compare_me);
- }
- break;
- case field_listid: // List-ID:
- if (!IsEmptyStr(msg->cm_fields[eListID])) {
- safestrncpy(compare_me, msg->cm_fields[eListID], sizeof compare_me);
- }
- break;
- case field_envto: // Envelope-to:
- if (!IsEmptyStr(msg->cm_fields[eenVelopeTo])) {
- safestrncpy(compare_me, msg->cm_fields[eenVelopeTo], sizeof compare_me);
- }
- break;
- case field_envfrom: // Return-path:
- if (!IsEmptyStr(msg->cm_fields[eMessagePath])) {
- safestrncpy(compare_me, msg->cm_fields[eMessagePath], sizeof compare_me);
- }
- break;
-
- case field_sender:
- case field_resentfrom:
- case field_resentto:
- case field_xmailer:
- case field_xspamflag:
- case field_xspamstatus:
-
- default:
- break;
- }
-
- // Message data to compare is loaded, now do something.
- switch(ii->rules[i].compared_field) {
- case field_from: // From:
- case field_tocc: // To: or Cc:
- case field_subject: // Subject:
- case field_replyto: // Reply-to:
- case field_listid: // List-ID:
- case field_envto: // Envelope-to:
- case field_envfrom: // Return-path:
- case field_sender:
- case field_resentfrom:
- case field_resentto:
- case field_xmailer:
- case field_xspamflag:
- case field_xspamstatus:
-
- // For all of the above fields, we can compare the field we've loaded into the buffer.
- syslog(LOG_DEBUG, "Value of field to compare is: <%s>", compare_me);
- int substring_match = (bmstrcasestr(compare_me, ii->rules[i].compared_value) ? 1 : 0);
- int exact_match = 0;
- if (compare_compound) {
- char *sep = strchr(compare_me, '|');
- if (sep) {
- *sep = 0;
- exact_match =
- (strcasecmp(compare_me, ii->rules[i].compared_value) ? 0 : 1)
- + (strcasecmp(++sep, ii->rules[i].compared_value) ? 0 : 1)
- ;
- }
- }
- else {
- exact_match = (strcasecmp(compare_me, ii->rules[i].compared_value) ? 0 : 1);
- }
- syslog(LOG_DEBUG, "substring match: %d", substring_match);
- syslog(LOG_DEBUG, "exact match: %d", exact_match);
- switch(ii->rules[i].field_compare_op) {
- case fcomp_contains:
- case fcomp_matches:
- rule_activated = substring_match;
- break;
- case fcomp_notcontains:
- case fcomp_notmatches:
- rule_activated = !substring_match;
- break;
- case fcomp_is:
- rule_activated = exact_match;
- break;
- case fcomp_isnot:
- rule_activated = !exact_match;
- break;
- }
- break;
-
- case field_size:
- rule_activated = 0;
- switch(ii->rules[i].field_compare_op) {
- case scomp_larger:
- rule_activated = ((smi.meta_rfc822_length > ii->rules[i].compared_size) ? 1 : 0);
- break;
- case scomp_smaller:
- rule_activated = ((smi.meta_rfc822_length < ii->rules[i].compared_size) ? 1 : 0);
- break;
- }
- break;
- case field_all: // The "all messages" rule ALWAYS triggers
- rule_activated = 1;
- break;
- default: // no matches, fall through and do nothing
- syslog(LOG_WARNING, "inboxrules: an unknown field comparison was encountered");
- rule_activated = 0;
- break;
- }
-
- // If the rule matched, perform the requested action.
- if (rule_activated) {
- syslog(LOG_DEBUG, "inboxrules: rule activated");
-
- // Perform the requested action
- switch(ii->rules[i].action) {
- case action_keep:
- keep_message = 1;
- break;
- case action_discard:
- keep_message = 0;
- break;
- case action_reject:
- inbox_do_reject(&ii->rules[i], msg);
- keep_message = 0;
- break;
- case action_fileinto:
- keep_message = inbox_do_fileinto(&ii->rules[i], msgnum);
- break;
- case action_redirect:
- keep_message = inbox_do_redirect(&ii->rules[i], msgnum);
- break;
- case action_vacation:
- inbox_do_vacation(&ii->rules[i], msg);
- keep_message = 1;
- break;
- }
-
- // Now perform the "final" action (anything other than "stop" means continue)
- if (ii->rules[i].final_action == final_stop) {
- syslog(LOG_DEBUG, "inboxrules: stop processing");
- i = ii->num_rules + 1; // throw us out of scope to stop
- }
-
-
- }
- else {
- syslog(LOG_DEBUG, "inboxrules: rule not activated");
- }
- }
-
- if (msg != NULL) { // Delete the copy of the message that is currently in memory. We don't need it anymore.
- CM_Free(msg);
- }
-
- if (!keep_message) { // Delete the copy of the message that is currently in the inbox, if rules dictated that.
- syslog(LOG_DEBUG, "inboxrules: delete %ld from inbox", msgnum);
- CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, ""); // we're in the inbox already
- }
-
- ii->lastproc = msgnum; // make note of the last message we processed, so we don't scan the whole inbox again
-}
-
-
-/*
- * A user account is identified as requring inbox processing.
- * Do it.
- */
-void do_inbox_processing_for_user(long usernum) {
- struct CtdlMessage *msg;
- struct inboxrules *ii;
- char roomname[ROOMNAMELEN];
- char username[64];
-
- if (CtdlGetUserByNumber(&CC->user, usernum) != 0) { // grab the user record
- return; // and bail out if we were given an invalid user
- }
-
- strcpy(username, CC->user.fullname); // save the user name so we can fetch it later and lock it
-
- if (CC->user.msgnum_inboxrules <= 0) {
- return; // this user has no inbox rules
- }
-
- msg = CtdlFetchMessage(CC->user.msgnum_inboxrules, 1);
- if (msg == NULL) {
- return; // config msgnum is set but that message does not exist
- }
-
- ii = deserialize_inbox_rules(msg->cm_fields[eMesageText]);
- CM_Free(msg);
-
- if (ii == NULL) {
- return; // config message exists but body is null
- }
-
- if (ii->lastproc > CC->user.lastproc_inboxrules) { // There might be a "last message processed" number left over
- CC->user.lastproc_inboxrules = ii->lastproc; // in the ruleset from a previous version. Use this if it is
- } // a higher number.
- else {
- ii->lastproc = CC->user.lastproc_inboxrules;
- }
-
- long original_lastproc = ii->lastproc;
- syslog(LOG_DEBUG, "inboxrules: for %s, messages newer than %ld", CC->user.fullname, original_lastproc);
-
- // Go to the user's inbox room and process all new messages
- snprintf(roomname, sizeof roomname, "%010ld.%s", usernum, MAILROOM);
- if (CtdlGetRoom(&CC->room, roomname) == 0) {
- CtdlForEachMessage(MSGS_GT, ii->lastproc, NULL, NULL, NULL, inbox_do_msg, (void *) ii);
- }
-
- // Record the number of the last message we processed
- if (ii->lastproc > original_lastproc) {
- CtdlGetUserLock(&CC->user, username);
- CC->user.lastproc_inboxrules = ii->lastproc; // Avoid processing the entire inbox next time
- CtdlPutUserLock(&CC->user);
- }
-
- // And free the memory.
- free_inbox_rules(ii);
-}
-
-
-/*
- * Here is an array of users (by number) who have received messages in their inbox and may require processing.
- */
-long *users_requiring_inbox_processing = NULL;
-int num_urip = 0;
-int num_urip_alloc = 0;
-
-
-/*
- * Perform inbox processing for all rooms which require it
- */
-void perform_inbox_processing(void) {
- int i = 0;
-
- if (num_urip == 0) {
- return; // no action required
- }
-
- for (i=0; i<num_urip; ++i) {
- do_inbox_processing_for_user(users_requiring_inbox_processing[i]);
- }
-
- free(users_requiring_inbox_processing);
- users_requiring_inbox_processing = NULL;
- num_urip = 0;
- num_urip_alloc = 0;
-}
-
-
-/*
- * This function is called after a message is saved to a room.
- * If it's someone's inbox, we have to check for inbox rules
- */
-int serv_inboxrules_roomhook(struct ctdlroom *room) {
- int i = 0;
-
- // Is this someone's inbox?
- if (!strcasecmp(&room->QRname[11], MAILROOM)) {
- long usernum = atol(room->QRname);
- if (usernum > 0) {
-
- // first check to see if this user is already on the list
- if (num_urip > 0) {
- for (i=0; i<=num_urip; ++i) {
- if (users_requiring_inbox_processing[i] == usernum) { // already on the list!
- return(0);
- }
- }
- }
-
- // make room if we need to
- if (num_urip_alloc == 0) {
- num_urip_alloc = 100;
- users_requiring_inbox_processing = malloc(sizeof(long) * num_urip_alloc);
- }
- else if (num_urip >= num_urip_alloc) {
- num_urip_alloc += 100;
- users_requiring_inbox_processing = realloc(users_requiring_inbox_processing, (sizeof(long) * num_urip_alloc));
- }
-
- // now add the user to the list
- users_requiring_inbox_processing[num_urip++] = usernum;
- }
- }
-
- // No errors are possible from this function.
- return(0);
-}
-
-
-
-/*
- * Get InBox Rules
- *
- * This is a client-facing function which fetches the user's inbox rules -- it omits all lines containing anything other than a rule.
- *
- * hmmmmm ... should we try to rebuild this in terms of deserialize_inbox_rules() instread?
- */
-void cmd_gibr(char *argbuf) {
-
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- cprintf("%d inbox rules for %s\n", LISTING_FOLLOWS, CC->user.fullname);
-
- struct CtdlMessage *msg = CtdlFetchMessage(CC->user.msgnum_inboxrules, 1);
- if (msg != NULL) {
- if (!CM_IsEmpty(msg, eMesageText)) {
- char *token;
- char *rest = msg->cm_fields[eMesageText];
- while ((token = strtok_r(rest, "\n", &rest))) {
-
- // for backwards compatibility, "# WEBCIT_RULE" is an alias for "rule"
- if (!strncasecmp(token, "# WEBCIT_RULE|", 14)) {
- strcpy(token, "rule|");
- strcpy(&token[5], &token[14]);
- }
-
- // Output only lines containing rules.
- if (!strncasecmp(token, "rule|", 5)) {
- cprintf("%s\n", token);
- }
- else {
- cprintf("# invalid rule found : %s\n", token);
- }
- }
- }
- CM_Free(msg);
- }
- cprintf("000\n");
-}
-
-
-/*
- * Rewrite the rule set to disk after it has been modified
- * Called by cmd_pibr()
- * Returns the msgnum of the new rules message
- */
-void rewrite_rules_to_disk(const char *new_config) {
- long old_msgnum = CC->user.msgnum_inboxrules;
- char userconfigroomname[ROOMNAMELEN];
- CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &CC->user, USERCONFIGROOM);
- long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, new_config, FMT_RFC822, "inbox rules configuration");
- CtdlGetUserLock(&CC->user, CC->curr_user);
- CC->user.msgnum_inboxrules = new_msgnum; // Now we know where to get the rules next time
- CC->user.lastproc_inboxrules = new_msgnum; // Avoid processing the entire inbox next time
- CtdlPutUserLock(&CC->user);
- if (old_msgnum > 0) {
- syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
- CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
- }
-}
-
-
-/*
- * Put InBox Rules
- *
- * User transmits the new inbox rules for the account. They are inserted into the account, replacing the ones already there.
- */
-void cmd_pibr(char *argbuf) {
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- unbuffer_output();
- cprintf("%d send new rules\n", SEND_LISTING);
- char *newrules = CtdlReadMessageBody(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
- StrBuf *NewConfig = NewStrBufPlain("Content-type: application/x-citadel-sieve-config; charset=UTF-8\nContent-transfer-encoding: 8bit\n\n", -1);
-
- char *token;
- char *rest = newrules;
- while ((token = strtok_r(rest, "\n", &rest))) {
- // Accept only lines containing rules
- if (!strncasecmp(token, "rule|", 5)) {
- StrBufAppendBufPlain(NewConfig, token, -1, 0);
- StrBufAppendBufPlain(NewConfig, HKEY("\n"), 0);
- }
- }
- free(newrules);
- rewrite_rules_to_disk(ChrPtr(NewConfig));
- FreeStrBuf(&NewConfig);
-}
-
-
-CTDL_MODULE_INIT(sieve)
-{
- if (!threading)
- {
- CtdlRegisterProtoHook(cmd_gibr, "GIBR", "Get InBox Rules");
- CtdlRegisterProtoHook(cmd_pibr, "PIBR", "Put InBox Rules");
- CtdlRegisterRoomHook(serv_inboxrules_roomhook);
- CtdlRegisterSessionHook(perform_inbox_processing, EVT_HOUSE, PRIO_HOUSE + 10);
- }
-
- /* return our module name for the log */
- return "inboxrules";
-}
+++ /dev/null
-/*
- * This module handles the loading/saving and maintenance of the
- * system's Internet configuration. It's not an optional component; I
- * wrote it as a module merely to keep things as clean and loosely coupled
- * as possible.
- *
- * Copyright (c) 1987-2021 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.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "genstamp.h"
-#include "domain.h"
-#include "ctdl_module.h"
-
-
-void inetcfg_setTo(struct CtdlMessage *msg) {
- char *conf;
- char buf[SIZ];
-
- if (CM_IsEmpty(msg, eMesageText)) return;
- conf = strdup(msg->cm_fields[eMesageText]);
-
- if (conf != NULL) {
- do {
- extract_token(buf, conf, 0, '\n', sizeof buf);
- strcpy(conf, &conf[strlen(buf)+1]);
- } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
-
- if (inetcfg != NULL) free(inetcfg);
- inetcfg = conf;
- }
-}
-
-
-/*
- * This handler detects changes being made to the system's Internet
- * configuration.
- */
-int inetcfg_aftersave(struct CtdlMessage *msg, struct recptypes *recp) {
- char *ptr;
- int linelen;
-
- /* If this isn't the configuration room, or if this isn't a MIME
- * message, don't bother.
- */
- if ((msg->cm_fields[eOriginalRoom]) && (strcasecmp(msg->cm_fields[eOriginalRoom], SYSCONFIGROOM))) {
- return(0);
- }
- if (msg->cm_format_type != 4) {
- return(0);
- }
-
- ptr = msg->cm_fields[eMesageText];
- while (ptr != NULL) {
-
- linelen = strcspn(ptr, "\n");
- if (linelen == 0) {
- return(0); /* end of headers */
- }
-
- if (!strncasecmp(ptr, "Content-type: ", 14)) {
- if (!strncasecmp(&ptr[14], INTERNETCFG, strlen(INTERNETCFG))) {
- inetcfg_setTo(msg); /* changing configs */
- }
- }
-
- ptr = strchr((char *)ptr, '\n');
- if (ptr != NULL) ++ptr;
- }
-
- return(0);
-}
-
-
-void inetcfg_init_backend(long msgnum, void *userdata) {
- struct CtdlMessage *msg;
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg != NULL) {
- inetcfg_setTo(msg);
- CM_Free(msg);
- }
-}
-
-
-void inetcfg_init(void) {
- syslog(LOG_DEBUG, "EVQ: called inetcfg_init()");
- if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
- return;
- }
- CtdlForEachMessage(MSGS_LAST, 1, NULL, INTERNETCFG, NULL, inetcfg_init_backend, NULL);
-}
-
-
-/*****************************************************************************/
-/* MODULE INITIALIZATION STUFF */
-/*****************************************************************************/
-
-
-CTDL_MODULE_INIT(inetcfg)
-{
- if (!threading)
- {
- CtdlRegisterMessageHook(inetcfg_aftersave, EVT_AFTERSAVE);
- inetcfg_init();
- }
-
- /* return our module name for the log */
- return "inetcfg";
-}
+++ /dev/null
-/*
- * This module handles instant messaging between users.
- *
- * Copyright (c) 1987-2020 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.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "serv_instmsg.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "msgbase.h"
-#include "user_ops.h"
-#include "ctdl_module.h"
-
-struct imlog {
- struct imlog *next;
- long usernums[2];
- char usernames[2][128];
- time_t lastmsg;
- int last_serial;
- StrBuf *conversation;
-};
-
-struct imlog *imlist = NULL;
-
-/*
- * This function handles the logging of instant messages to disk.
- */
-void log_instant_message(struct CitContext *me, struct CitContext *them, char *msgtext, int serial_number)
-{
- long usernums[2];
- long t;
- struct imlog *iptr = NULL;
- struct imlog *this_im = NULL;
-
- memset(usernums, 0, sizeof usernums);
- usernums[0] = me->user.usernum;
- usernums[1] = them->user.usernum;
-
- /* Always put the lower user number first, so we can use the array as a hash value which
- * represents a pair of users. For a broadcast message one of the users will be 0.
- */
- if (usernums[0] > usernums[1]) {
- t = usernums[0];
- usernums[0] = usernums[1];
- usernums[1] = t;
- }
-
- begin_critical_section(S_IM_LOGS);
-
- /* Look for an existing conversation in the hash table.
- * If not found, create a new one.
- */
-
- this_im = NULL;
- for (iptr = imlist; iptr != NULL; iptr = iptr->next) {
- if ((iptr->usernums[0] == usernums[0]) && (iptr->usernums[1] == usernums[1])) {
- /* Existing conversation */
- this_im = iptr;
- }
- }
- if (this_im == NULL) {
- /* New conversation */
- this_im = malloc(sizeof(struct imlog));
- memset(this_im, 0, sizeof (struct imlog));
- this_im->usernums[0] = usernums[0];
- this_im->usernums[1] = usernums[1];
- /* usernames[] and usernums[] might not be in the same order. This is not an error. */
- if (me) {
- safestrncpy(this_im->usernames[0], me->user.fullname, sizeof this_im->usernames[0]);
- }
- if (them) {
- safestrncpy(this_im->usernames[1], them->user.fullname, sizeof this_im->usernames[1]);
- }
- this_im->conversation = NewStrBuf();
- this_im->next = imlist;
- imlist = this_im;
- StrBufAppendBufPlain(this_im->conversation, HKEY("<html><body>\r\n"), 0);
- }
-
- /* Since it's possible for this function to get called more than once if a user is logged
- * in on multiple sessions, we use the message's serial number to keep track of whether
- * we've already logged it.
- */
- if (this_im->last_serial != serial_number)
- {
- this_im->lastmsg = time(NULL); /* Touch the timestamp so we know when to flush */
- this_im->last_serial = serial_number;
- StrBufAppendBufPlain(this_im->conversation, HKEY("<p><b>"), 0);
- StrBufAppendBufPlain(this_im->conversation, me->user.fullname, -1, 0);
- StrBufAppendBufPlain(this_im->conversation, HKEY(":</b> "), 0);
- StrEscAppend(this_im->conversation, NULL, msgtext, 0, 0);
- StrBufAppendBufPlain(this_im->conversation, HKEY("</p>\r\n"), 0);
- }
- end_critical_section(S_IM_LOGS);
-}
-
-
-/*
- * Delete any remaining instant messages
- */
-void delete_instant_messages(void) {
- struct ExpressMessage *ptr;
-
- begin_critical_section(S_SESSION_TABLE);
- while (CC->FirstExpressMessage != NULL) {
- ptr = CC->FirstExpressMessage->next;
- if (CC->FirstExpressMessage->text != NULL)
- free(CC->FirstExpressMessage->text);
- free(CC->FirstExpressMessage);
- CC->FirstExpressMessage = ptr;
- }
- end_critical_section(S_SESSION_TABLE);
-}
-
-
-/*
- * Retrieve instant messages
- */
-void cmd_gexp(char *argbuf) {
- struct ExpressMessage *ptr;
-
- if (CC->FirstExpressMessage == NULL) {
- cprintf("%d No instant messages waiting.\n", ERROR + MESSAGE_NOT_FOUND);
- return;
- }
-
- begin_critical_section(S_SESSION_TABLE);
- ptr = CC->FirstExpressMessage;
- CC->FirstExpressMessage = CC->FirstExpressMessage->next;
- end_critical_section(S_SESSION_TABLE);
-
- cprintf("%d %d|%ld|%d|%s|%s|%s\n",
- LISTING_FOLLOWS,
- ((ptr->next != NULL) ? 1 : 0), /* more msgs? */
- (long)ptr->timestamp, /* time sent */
- ptr->flags, /* flags */
- ptr->sender, /* sender of msg */
- CtdlGetConfigStr("c_nodename"), /* static for now (and possibly deprecated) */
- ptr->sender_email /* email or jid of sender */
- );
-
- if (ptr->text != NULL) {
- memfmout(ptr->text, "\n");
- free(ptr->text);
- }
-
- cprintf("000\n");
- free(ptr);
-}
-
-
-/*
- * Asynchronously deliver instant messages
- */
-void cmd_gexp_async(void) {
-
- /* Only do this if the session can handle asynchronous protocol */
- if (CC->is_async == 0) return;
-
- /* And don't do it if there's nothing to send. */
- if (CC->FirstExpressMessage == NULL) return;
-
- cprintf("%d instant msg\n", ASYNC_MSG + ASYNC_GEXP);
-}
-
-
-/*
- * Back end support function for send_instant_message() and company
- */
-void add_xmsg_to_context(struct CitContext *ccptr, struct ExpressMessage *newmsg)
-{
- struct ExpressMessage *findend;
-
- if (ccptr->FirstExpressMessage == NULL) {
- ccptr->FirstExpressMessage = newmsg;
- }
- else {
- findend = ccptr->FirstExpressMessage;
- while (findend->next != NULL) {
- findend = findend->next;
- }
- findend->next = newmsg;
- }
-
- /* If the target context is a session which can handle asynchronous
- * messages, go ahead and set the flag for that.
- */
- set_async_waiting(ccptr);
-}
-
-
-/*
- * This is the back end to the instant message sending function.
- * Returns the number of users to which the message was sent.
- * Sending a zero-length message tests for recipients without sending messages.
- */
-int send_instant_message(char *lun, char *lem, char *x_user, char *x_msg)
-{
- int message_sent = 0; /* number of successful sends */
- struct CitContext *ccptr;
- struct ExpressMessage *newmsg = NULL;
- int do_send = 0; /* 1 = send message; 0 = only check for valid recipient */
- static int serial_number = 0; /* this keeps messages from getting logged twice */
-
- if (!IsEmptyStr(x_msg)) {
- do_send = 1;
- }
-
- /* find the target user's context and append the message */
- begin_critical_section(S_SESSION_TABLE);
- ++serial_number;
- for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
-
- if ( ((!strcasecmp(ccptr->user.fullname, x_user))
- || (!strcasecmp(x_user, "broadcast")))
- && (ccptr->can_receive_im)
- && ((ccptr->disable_exp == 0)
- || (CC->user.axlevel >= AxAideU)) ) {
- if (do_send) {
- newmsg = (struct ExpressMessage *) malloc(sizeof (struct ExpressMessage));
- memset(newmsg, 0, sizeof (struct ExpressMessage));
- time(&(newmsg->timestamp));
- safestrncpy(newmsg->sender, lun, sizeof newmsg->sender);
- safestrncpy(newmsg->sender_email, lem, sizeof newmsg->sender_email);
- if (!strcasecmp(x_user, "broadcast")) {
- newmsg->flags |= EM_BROADCAST;
- }
- newmsg->text = strdup(x_msg);
-
- add_xmsg_to_context(ccptr, newmsg);
-
- /* and log it ... */
- if (ccptr != CC) {
- log_instant_message(CC, ccptr, newmsg->text, serial_number);
- }
- }
- ++message_sent;
- }
- }
- end_critical_section(S_SESSION_TABLE);
- return (message_sent);
-}
-
-
-/*
- * send instant messages
- */
-void cmd_sexp(char *argbuf)
-{
- int message_sent = 0;
- char x_user[USERNAME_SIZE];
- char x_msg[1024];
- char *lem;
- char *x_big_msgbuf = NULL;
-
- if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
- cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
- return;
- }
-
- lem = CC->cs_principal_id;
-
- extract_token(x_user, argbuf, 0, '|', sizeof x_user);
- extract_token(x_msg, argbuf, 1, '|', sizeof x_msg);
-
- if (!x_user[0]) {
- cprintf("%d You were not previously paged.\n", ERROR + NO_SUCH_USER);
- return;
- }
- if ((!strcasecmp(x_user, "broadcast")) && (CC->user.axlevel < AxAideU)) {
- cprintf("%d Higher access required to send a broadcast.\n",
- ERROR + HIGHER_ACCESS_REQUIRED);
- return;
- }
- /* This loop handles text-transfer pages */
- if (!strcmp(x_msg, "-")) {
- message_sent = PerformXmsgHooks(CC->user.fullname, lem, x_user, "");
- if (message_sent == 0) {
- if (CtdlGetUser(NULL, x_user))
- cprintf("%d '%s' does not exist.\n",
- ERROR + NO_SUCH_USER, x_user);
- else
- cprintf("%d '%s' is not logged in "
- "or is not accepting pages.\n",
- ERROR + RESOURCE_NOT_OPEN, x_user);
- return;
- }
- unbuffer_output();
- cprintf("%d Transmit message (will deliver to %d users)\n",
- SEND_LISTING, message_sent);
- x_big_msgbuf = malloc(SIZ);
- memset(x_big_msgbuf, 0, SIZ);
- while (client_getln(x_msg, sizeof x_msg) >= 0 && strcmp(x_msg, "000")) {
- x_big_msgbuf = realloc(x_big_msgbuf,
- strlen(x_big_msgbuf) + strlen(x_msg) + 4);
- if (!IsEmptyStr(x_big_msgbuf))
- if (x_big_msgbuf[strlen(x_big_msgbuf)] != '\n')
- strcat(x_big_msgbuf, "\n");
- strcat(x_big_msgbuf, x_msg);
- }
- PerformXmsgHooks(CC->user.fullname, lem, x_user, x_big_msgbuf);
- free(x_big_msgbuf);
-
- /* This loop handles inline pages */
- } else {
- message_sent = PerformXmsgHooks(CC->user.fullname, lem, x_user, x_msg);
-
- if (message_sent > 0) {
- if (!IsEmptyStr(x_msg)) {
- cprintf("%d Message sent", CIT_OK);
- }
- else {
- cprintf("%d Ok to send message", CIT_OK);
- }
- if (message_sent > 1) {
- cprintf(" to %d users", message_sent);
- }
- cprintf(".\n");
- } else {
- if (CtdlGetUser(NULL, x_user)) {
- cprintf("%d '%s' does not exist.\n", ERROR + NO_SUCH_USER, x_user);
- }
- else {
- cprintf("%d '%s' is not logged in or is not accepting instant messages.\n",
- ERROR + RESOURCE_NOT_OPEN, x_user);
- }
- }
-
-
- }
-}
-
-
-/*
- * Enter or exit paging-disabled mode
- */
-void cmd_dexp(char *argbuf)
-{
- int new_state;
-
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- new_state = extract_int(argbuf, 0);
- if ((new_state == 0) || (new_state == 1)) {
- CC->disable_exp = new_state;
- }
-
- cprintf("%d %d\n", CIT_OK, CC->disable_exp);
-}
-
-
-/*
- * Request client termination
- */
-void cmd_reqt(char *argbuf) {
- struct CitContext *ccptr;
- int sessions = 0;
- int which_session;
- struct ExpressMessage *newmsg;
-
- if (CtdlAccessCheck(ac_aide)) return;
- which_session = extract_int(argbuf, 0);
-
- begin_critical_section(S_SESSION_TABLE);
- for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
- if ((ccptr->cs_pid == which_session) || (which_session == 0)) {
-
- newmsg = (struct ExpressMessage *)
- malloc(sizeof (struct ExpressMessage));
- memset(newmsg, 0,
- sizeof (struct ExpressMessage));
- time(&(newmsg->timestamp));
- safestrncpy(newmsg->sender, CC->user.fullname,
- sizeof newmsg->sender);
- newmsg->flags |= EM_GO_AWAY;
- newmsg->text = strdup("Automatic logoff requested.");
-
- add_xmsg_to_context(ccptr, newmsg);
- ++sessions;
-
- }
- }
- end_critical_section(S_SESSION_TABLE);
- cprintf("%d Sent termination request to %d sessions.\n", CIT_OK, sessions);
-}
-
-
-/*
- * This is the back end for flush_conversations_to_disk()
- * At this point we've isolated a single conversation (struct imlog)
- * and are ready to write it to disk.
- */
-void flush_individual_conversation(struct imlog *im) {
- struct CtdlMessage *msg;
- long msgnum = 0;
- char roomname[ROOMNAMELEN];
- StrBuf *MsgBuf, *FullMsgBuf;
-
- StrBufAppendBufPlain(im->conversation, HKEY(
- "</body>\r\n"
- "</html>\r\n"
- ), 0
- );
-
- MsgBuf = StrBufRFC2047encodeMessage(im->conversation);
- FlushStrBuf(im->conversation);
- FullMsgBuf = NewStrBufPlain(NULL, StrLength(im->conversation) + 100);
-
- StrBufAppendBufPlain(FullMsgBuf, HKEY(
- "Content-type: text/html; charset=UTF-8\r\n"
- "Content-Transfer-Encoding: quoted-printable\r\n"
- "\r\n"
- ), 0
- );
- StrBufAppendBuf (FullMsgBuf, MsgBuf, 0);
- FreeStrBuf(&MsgBuf);
-
- msg = malloc(sizeof(struct CtdlMessage));
- memset(msg, 0, sizeof(struct CtdlMessage));
- msg->cm_magic = CTDLMESSAGE_MAGIC;
- msg->cm_anon_type = MES_NORMAL;
- msg->cm_format_type = FMT_RFC822;
- if (!IsEmptyStr(im->usernames[0])) {
- CM_SetField(msg, eAuthor, im->usernames[0], strlen(im->usernames[0]));
- } else {
- CM_SetField(msg, eAuthor, HKEY("Citadel"));
- }
- if (!IsEmptyStr(im->usernames[1])) {
- CM_SetField(msg, eRecipient, im->usernames[1], strlen(im->usernames[1]));
- }
-
- CM_SetField(msg, eOriginalRoom, HKEY(PAGELOGROOM));
- CM_SetAsFieldSB(msg, eMesageText, &FullMsgBuf); /* we own this memory now */
-
- /* Start with usernums[1] because it's guaranteed to be higher than usernums[0],
- * so if there's only one party, usernums[0] will be zero but usernums[1] won't.
- * Create the room if necessary. Note that we create as a type 5 room rather
- * than 4, which indicates that it's a personal room but we've already supplied
- * the namespace prefix.
- *
- * In the unlikely event that usernums[1] is zero, a room with an invalid namespace
- * prefix will be created. That's ok because the auto-purger will clean it up later.
- */
- snprintf(roomname, sizeof roomname, "%010ld.%s", im->usernums[1], PAGELOGROOM);
- CtdlCreateRoom(roomname, 5, "", 0, 1, 1, VIEW_BBS);
- msgnum = CtdlSubmitMsg(msg, NULL, roomname);
- CM_Free(msg);
-
- /* If there is a valid user number in usernums[0], save a copy for them too. */
- if (im->usernums[0] > 0) {
- snprintf(roomname, sizeof roomname, "%010ld.%s", im->usernums[0], PAGELOGROOM);
- CtdlCreateRoom(roomname, 5, "", 0, 1, 1, VIEW_BBS);
- CtdlSaveMsgPointerInRoom(roomname, msgnum, 0, NULL);
- }
-
- /* Finally, if we're logging instant messages globally, do that now. */
- if (!IsEmptyStr(CtdlGetConfigStr("c_logpages"))) {
- CtdlCreateRoom(CtdlGetConfigStr("c_logpages"), 3, "", 0, 1, 1, VIEW_BBS);
- CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_logpages"), msgnum, 0, NULL);
- }
-
-}
-
-/*
- * Locate instant message conversations which have gone idle
- * (or, if the server is shutting down, locate *all* conversations)
- * and flush them to disk (in the participants' log rooms, etc.)
- */
-void flush_conversations_to_disk(time_t if_older_than) {
-
- struct imlog *flush_these = NULL;
- struct imlog *dont_flush_these = NULL;
- struct imlog *imptr = NULL;
- struct CitContext *nptr;
- int nContexts, i;
-
- nptr = CtdlGetContextArray(&nContexts) ; /* Make a copy of the current wholist */
-
- begin_critical_section(S_IM_LOGS);
- while (imlist)
- {
- imptr = imlist;
- imlist = imlist->next;
-
- /* For a two party conversation, if one party has logged out, force flush. */
- if (nptr) {
- int user0_is_still_online = 0;
- int user1_is_still_online = 0;
- for (i=0; i<nContexts; i++) {
- if (nptr[i].user.usernum == imptr->usernums[0]) ++user0_is_still_online;
- if (nptr[i].user.usernum == imptr->usernums[1]) ++user1_is_still_online;
- }
- if (imptr->usernums[0] != imptr->usernums[1]) { /* two party conversation */
- if ((!user0_is_still_online) || (!user1_is_still_online)) {
- imptr->lastmsg = 0L; /* force flush */
- }
- }
- else { /* one party conversation (yes, people do IM themselves) */
- if (!user0_is_still_online) {
- imptr->lastmsg = 0L; /* force flush */
- }
- }
- }
-
- /* Now test this conversation to see if it qualifies for flushing. */
- if ((time(NULL) - imptr->lastmsg) > if_older_than)
- {
- /* This conversation qualifies. Move it to the list of ones to flush. */
- imptr->next = flush_these;
- flush_these = imptr;
- }
- else {
- /* Move it to the list of ones not to flush. */
- imptr->next = dont_flush_these;
- dont_flush_these = imptr;
- }
- }
- imlist = dont_flush_these;
- end_critical_section(S_IM_LOGS);
- free(nptr);
-
- /* We are now outside of the critical section, and we are the only thread holding a
- * pointer to a linked list of conversations to be flushed to disk.
- */
- while (flush_these) {
-
- flush_individual_conversation(flush_these); /* This will free the string buffer */
- imptr = flush_these;
- flush_these = flush_these->next;
- free(imptr);
- }
-}
-
-
-
-void instmsg_timer(void) {
- flush_conversations_to_disk(300); /* Anything that hasn't peeped in more than 5 minutes */
-}
-
-void instmsg_shutdown(void) {
- flush_conversations_to_disk(0); /* Get it ALL onto disk NOW. */
-}
-
-CTDL_MODULE_INIT(instmsg)
-{
- if (!threading)
- {
- CtdlRegisterProtoHook(cmd_gexp, "GEXP", "Get instant messages");
- CtdlRegisterProtoHook(cmd_sexp, "SEXP", "Send an instant message");
- CtdlRegisterProtoHook(cmd_dexp, "DEXP", "Disable instant messages");
- CtdlRegisterProtoHook(cmd_reqt, "REQT", "Request client termination");
- CtdlRegisterSessionHook(cmd_gexp_async, EVT_ASYNC, PRIO_ASYNC + 1);
- CtdlRegisterSessionHook(delete_instant_messages, EVT_STOP, PRIO_STOP + 1);
- CtdlRegisterXmsgHook(send_instant_message, XMSG_PRI_LOCAL);
- CtdlRegisterSessionHook(instmsg_timer, EVT_TIMER, PRIO_CLEANUP + 400);
- CtdlRegisterSessionHook(instmsg_shutdown, EVT_SHUTDOWN, PRIO_SHUTDOWN + 10);
- }
-
- /* return our module name for the log */
- return "instmsg";
-}
+++ /dev/null
-#include "ctdl_module.h"
-
-void ChatUnloadingTest(void);
-void allwrite (char *cmdbuf, int flag, char *username);
-CitContext *find_context (char **unstr);
-void cmd_pexp (char *argbuf); /* arg unused */
-void cmd_sexp (char *argbuf);
-void delete_instant_messages(void);
-void cmd_gexp(char *);
-int send_instant_message(char *, char *, char *, char *);
+++ /dev/null
-// This module delivers messages to mailing lists.
-//
-// Copyright (c) 2002-2022 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.
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <ctype.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <dirent.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "clientsocket.h"
-#include "ctdl_module.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
- char *netconf; // netconfig for this room (contains the recipients)
-};
-
-
-
-void listdeliver_do_msg(long msgnum, void *userdata) {
- struct lddata *ld = (struct lddata *) userdata;
- if (!ld) return;
- char buf[SIZ];
- char *ch;
- char bounce_to[256];
- int i = 0;
-
- ld->msgnum = msgnum;
- if (msgnum <= 0) return;
-
- struct CtdlMessage *TheMessage = CtdlFetchMessage(msgnum, 1);
- if (!TheMessage) return;
-
- // 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, eMsgSubject, buf, strlen(buf));
- }
-
- // From: should be set to the list address because doing otherwise makes DKIM parsers angry.
- // Reply-to: should be set to the list address so that replies come to the list.
- snprintf(buf, sizeof buf, "room_%s@%s", CC->room.QRname, CtdlGetConfigStr("c_fqdn"));
- for (ch=buf; *ch; ++ch) {
- if (isspace(*ch)) *ch = '_';
- }
- CM_SetField(TheMessage, erFc822Addr, buf, strlen(buf));
- CM_SetField(TheMessage, eReplyTo, buf, strlen(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) {
- recipients[0] = 0;
-
- int config_lines = num_tokens(ld->netconf, '\n');
- for (i=0; i<config_lines; ++i) {
- extract_token(buf, ld->netconf, i, '\n', sizeof buf);
- if (!strncasecmp(buf, "listrecp|", 9)) {
- if (recipients[0] != 0) {
- strcat(recipients, ",");
- }
- strcat(recipients, &buf[9]);
- }
- if (!strncasecmp(buf, "digestrecp|", 11)) {
- if (recipients[0] != 0) {
- strcat(recipients, ",");
- }
- strcat(recipients, &buf[11]);
- }
- }
-
- // Where do we want bounces and other noise to be sent? Certainly not to the list members!
- snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", CtdlGetConfigStr("c_fqdn"));
-
- // Now submit the message
- struct recptypes *valid = validate_recipients(recipients, NULL, 0);
- if (valid) {
- valid->bounce_to = strdup(bounce_to);
- valid->envelope_from = strdup(bounce_to);
- valid->sending_room = strdup(CC->room.QRname);
- CtdlSubmitMsg(TheMessage, valid, "");
- free_recipients(valid);
- }
- }
- CM_Free(TheMessage);
-}
-
-
-// Sweep through one room looking for mailing list deliveries to do
-void listdeliver_sweep_room(char *roomname) {
- char *netconfig = NULL;
- char *newnetconfig = NULL;
- long lastsent = 0;
- char buf[SIZ];
- int config_lines;
- int i;
- int number_of_messages_processed = 0;
- int number_of_recipients = 0;
- struct lddata ld;
-
- if (CtdlGetRoom(&CC->room, roomname)) {
- syslog(LOG_DEBUG, "listdeliver: no room <%s>", roomname);
- return;
- }
-
- netconfig = LoadRoomNetConfigFile(CC->room.QRnumber);
- if (!netconfig) {
- return; // no netconfig, no processing, no problem
- }
-
- config_lines = num_tokens(netconfig, '\n');
- for (i=0; i<config_lines; ++i) {
- extract_token(buf, netconfig, i, '\n', sizeof buf);
-
- if (!strncasecmp(buf, "lastsent|", 9)) {
- lastsent = atol(&buf[9]);
- }
- else if ( (!strncasecmp(buf, "listrecp|", 9)) || (!strncasecmp(buf, "digestrecp|", 11)) ) {
- ++number_of_recipients;
- }
- }
-
- if (number_of_recipients > 0) {
- syslog(LOG_DEBUG, "listdeliver: processing new messages in <%s> for <%d> recipients", CC->room.QRname, number_of_recipients);
- ld.netconf = netconfig;
- number_of_messages_processed = CtdlForEachMessage(MSGS_GT, lastsent, NULL, NULL, NULL, listdeliver_do_msg, &ld);
- syslog(LOG_INFO, "listdeliver: processed <%d> messages in <%s> for <%d> recipients", number_of_messages_processed, CC->room.QRname, number_of_recipients);
-
- if (number_of_messages_processed > 0) {
- syslog(LOG_DEBUG, "listdeliver: new lastsent is %ld", ld.msgnum);
-
- // Update this room's netconfig with the updated lastsent
- begin_critical_section(S_NETCONFIGS);
- netconfig = LoadRoomNetConfigFile(CC->room.QRnumber);
- if (!netconfig) {
- netconfig = strdup("");
- }
-
- // The new netconfig begins with the new lastsent directive
- newnetconfig = malloc(strlen(netconfig) + 1024);
- sprintf(newnetconfig, "lastsent|%ld\n", ld.msgnum);
-
- // And then we append all of the old netconfig, minus the old lastsent. Also omit blank lines.
- config_lines = num_tokens(netconfig, '\n');
- for (i=0; i<config_lines; ++i) {
- extract_token(buf, netconfig, i, '\n', sizeof buf);
- if ( (!IsEmptyStr(buf)) && (strncasecmp(buf, "lastsent|", 9)) ) {
- sprintf(&newnetconfig[strlen(newnetconfig)], "%s\n", buf);
- }
- }
-
- // Write the new netconfig back to disk
- SaveRoomNetConfigFile(CC->room.QRnumber, newnetconfig);
- end_critical_section(S_NETCONFIGS);
- free(newnetconfig); // this was the new netconfig, free it because we're done with it
- }
- }
- free(netconfig); // this was the old netconfig, free it even if we didn't do anything
-}
-
-
-// Callback for listdeliver_sweep()
-// Adds one room to the queue
-void listdeliver_queue_room(struct ctdlroom *qrbuf, void *data) {
- Array *roomlistarr = (Array *)data;
- array_append(roomlistarr, qrbuf->QRname);
-}
-
-
-// Queue up the list of rooms so we can sweep them for mailing list delivery instructions
-void listdeliver_sweep(void) {
- static time_t last_run = 0L;
- int i = 0;
- time_t now = time(NULL);
-
- // Run mailing list delivery no more frequently than once every 15 minutes (we should make this configurable)
- if ( (now - last_run) < 900 ) {
- syslog(LOG_DEBUG,
- "listdeliver: delivery interval not yet reached; last run was %ldm%lds ago",
- ((now - last_run) / 60),
- ((now - last_run) % 60)
- );
- return;
- }
-
- // This is a simple concurrency check to make sure only one listdeliver
- // run is done at a time. We could do this with a mutex, but since we
- // don't really require extremely fine granularity here, we'll do it
- // with a static variable instead.
- if (doing_listdeliver) return;
- doing_listdeliver = 1;
-
- // Go through each room looking for mailing lists to process
- syslog(LOG_DEBUG, "listdeliver: sweep started");
-
- Array *roomlistarr = array_new(ROOMNAMELEN); // we have to queue them
- CtdlForEachRoom(listdeliver_queue_room, roomlistarr); // otherwise we get multiple cursors in progress
-
- for (i=0; i<array_len(roomlistarr); ++i) {
- listdeliver_sweep_room((char *)array_get_element_at(roomlistarr, i));
- }
-
- array_free(roomlistarr);
- syslog(LOG_DEBUG, "listdeliver: ended");
- last_run = time(NULL);
- doing_listdeliver = 0;
-}
-
-
-// Module entry point
-CTDL_MODULE_INIT(listdeliver)
-{
- if (!threading) {
- CtdlRegisterSessionHook(listdeliver_sweep, EVT_TIMER, PRIO_AGGR + 50);
- }
-
- // return our module name for the log
- return "listsub";
-}
+++ /dev/null
-// This module handles self-service subscription/unsubscription to mail lists.
-//
-// Copyright (c) 2002-2022 by the citadel.org team
-//
-// This program is open source software. It runs great on the
-// Linux operating system (and probably elsewhere). You can use,
-// copy, and run 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.
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <ctype.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <dirent.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <crypt.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "clientsocket.h"
-#include "ctdl_module.h"
-
-
-enum { // one of these gets passed to do_subscribe_or_unsubscribe() so it knows what we asked for
- UNSUBSCRIBE,
- SUBSCRIBE
-};
-
-
-// The confirmation token will be generated by combining the room name and email address with the host key,
-// and then generating an encrypted hash of that string. The encrypted hash is included as part of the
-// confirmation link.
-void generate_confirmation_token(char *token_buf, size_t token_buf_len, char *roomname, char *emailaddr) {
- char string_to_hash[1024];
- struct crypt_data cd;
- char *ptr;
-
- snprintf(string_to_hash, sizeof string_to_hash, "%s|%s|%s", roomname, emailaddr, CtdlGetConfigStr("host_key"));
- memset(&cd, 0, sizeof cd);
-
- strncpy(token_buf, crypt_r(string_to_hash, "$1$ctdl", &cd), token_buf_len);
-
- for (ptr=token_buf; *ptr; ++ptr) {
- if (!isalnum((char)*ptr)) *ptr='X';
- }
-}
-
-
-// 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 from_address[1024];
- snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
-
- char emailtext[SIZ];
- snprintf(emailtext, sizeof emailtext,
- "MIME-Version: 1.0\n"
- "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
- "\n"
- "This is a multipart message in MIME format.\n"
- "\n"
- "--__ctdlmultipart__\n"
- "Content-type: text/plain\n"
- "\n"
- "Someone (probably you) has submitted a request to subscribe\n"
- "<%s> to the <%s> mailing list.\n"
- "\n"
- "Please go here to confirm this request:\n"
- "%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s\n"
- "\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"
- "and you will not receive any further mailings.\n"
- "\n"
- "--__ctdlmultipart__\n"
- "Content-type: text/html\n"
- "\n"
- "<html><body><p>Someone (probably you) has submitted a request to subscribe "
- "<strong>%s</strong> to the <strong>%s</strong> mailing list.</p>"
- "<p>Please go here to confirm this request:</p>"
- "<p><a href=\"%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s\">"
- "%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s</a></p>"
- "<p>If this request has been submitted in error and you do not "
- "wish to receive the <strong>%s<strong> mailing list, simply do nothing, "
- "and you will not receive any further mailings.</p>"
- "</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
- );
-
- quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list subscription");
- cprintf("%d confirmation email sent\n", CIT_OK);
-}
-
-
-// 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 from_address[1024];
- snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
-
- char emailtext[SIZ];
- snprintf(emailtext, sizeof emailtext,
- "MIME-Version: 1.0\n"
- "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
- "\n"
- "This is a multipart message in MIME format.\n"
- "\n"
- "--__ctdlmultipart__\n"
- "Content-type: text/plain\n"
- "\n"
- "Someone (probably you) has submitted a request to unsubscribe\n"
- "<%s> from the <%s> mailing list.\n"
- "\n"
- "Please go here to confirm this request:\n"
- "%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s\n"
- "\n"
- "If this request has been submitted in error and you still\n"
- "wish to receive the <%s> mailing list, simply do nothing,\n"
- "and you will remain subscribed.\n"
- "\n"
- "--__ctdlmultipart__\n"
- "Content-type: text/html\n"
- "\n"
- "<html><body><p>Someone (probably you) has submitted a request to unsubscribe "
- "<strong>%s</strong> from the <strong>%s</strong> mailing list.</p>"
- "<p>Please go here to confirm this request:</p>"
- "<p><a href=\"%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s\">"
- "%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s</a></p>"
- "<p>If this request has been submitted in error and you still "
- "wish to receive the <strong>%s<strong> mailing list, simply do nothing, "
- "and you will remain subscribed.</p>"
- "</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
- );
-
- quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list unsubscription");
- cprintf("%d confirmation email sent\n", CIT_OK);
-}
-
-
-// Confirm a list subscription or unsubscription
-void do_confirm(int cmd, char *roomname, char *emailaddr, char *url, char *generated_token, char *supplied_token) {
- int i;
- char buf[1024];
- int config_lines = 0;
- char *oldnetconfig, *newnetconfig;
-
- // During opt #1, the server generated a persistent confirmation token for the user+room combination.
- // Let's see if the user has supplied the same token during opt #2.
- if (strcmp(generated_token, supplied_token)) {
- cprintf("%d This request could not be authenticated.\n", ERROR + PASSWORD_REQUIRED);
- return;
- }
-
- // If the generated token matches the supplied token, the request is authentic. Do what it says.
-
- // Load the room's network configuration...
- oldnetconfig = LoadRoomNetConfigFile(CC->room.QRnumber);
- if (!oldnetconfig) {
- oldnetconfig = strdup("");
- }
-
- // The new netconfig begins with an empty buffer...
- begin_critical_section(S_NETCONFIGS);
- newnetconfig = malloc(strlen(oldnetconfig) + 1024);
- newnetconfig[0] = 0;
-
- // Load the config lines in one by one, skipping any that reference this subscriber. Also remove blank lines.
- config_lines = num_tokens(oldnetconfig, '\n');
- for (i=0; i<config_lines; ++i) {
- char buf_email[256];
- extract_token(buf, oldnetconfig, i, '\n', sizeof buf);
- extract_token(buf_email, buf, 1, '|', sizeof buf_email);
- if ( !IsEmptyStr(buf) && (strcasecmp(buf_email, emailaddr)) ) {
- sprintf(&newnetconfig[strlen(newnetconfig)], "%s\n", buf);
- }
- }
-
- // We have now removed all lines containing the subscriber's email address. This deletes any pending requests.
- // If this was an unsubscribe operation, they're now gone from the list.
- // But if this was a subscribe operation, we now need to add them.
- if (cmd == SUBSCRIBE) {
- sprintf(&newnetconfig[strlen(newnetconfig)], "listrecp|%s\n", emailaddr);
- }
-
- // write it back to disk
- SaveRoomNetConfigFile(CC->room.QRnumber, newnetconfig);
- end_critical_section(S_NETCONFIGS);
- free(oldnetconfig);
- free(newnetconfig);
- cprintf("%d The pending request was confirmed.\n", CIT_OK);
-}
-
-
-// process subscribe/unsubscribe requests and confirmations
-void cmd_lsub(char *cmdbuf) {
- char cmd[20];
- char roomname[ROOMNAMELEN];
- char emailaddr[1024];
- char url[1024];
- char generated_token[128];
- char supplied_token[128];
-
- extract_token(cmd, cmdbuf, 0, '|', sizeof cmd); // token 0 is the sub-command being sent
- extract_token(roomname, cmdbuf, 1, '|', sizeof roomname); // token 1 is always a room name
- extract_token(emailaddr, cmdbuf, 2, '|', sizeof emailaddr); // token 2 is the subscriber's email address
- extract_token(url, cmdbuf, 3, '|', sizeof url); // token 3 is the URL at which we subscribed
- extract_token(supplied_token, cmdbuf, 4, '|', sizeof supplied_token); // token 4 is the token supplied by the caller
-
- // First confirm that the caller is referencing a room that actually exists.
- if (CtdlGetRoom(&CC->room, roomname) != 0) {
- cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, roomname);
- return;
- }
-
- if ((CC->room.QRflags2 & QR2_SELFLIST) == 0) {
- cprintf("%d '%s' does not accept subscribe/unsubscribe requests.\n", ERROR + ROOM_NOT_FOUND, roomname);
- return;
- }
-
- // Generate a confirmation token -- either to supply to the user for opt #1 or to compare for opt #2
- generate_confirmation_token(generated_token, sizeof generated_token, roomname, emailaddr);
-
- // Now parse the command.
- if (!strcasecmp(cmd, "subscribe")) {
- send_subscribe_confirmation_email(roomname, emailaddr, url, generated_token);
- }
-
- else if (!strcasecmp(cmd, "unsubscribe")) {
- send_unsubscribe_confirmation_email(roomname, emailaddr, url, generated_token);
- }
-
- else if (!strcasecmp(cmd, "confirm_subscribe")) {
- do_confirm(SUBSCRIBE, roomname, emailaddr, url, generated_token, supplied_token);
- }
-
- else if (!strcasecmp(cmd, "confirm_unsubscribe")) {
- do_confirm(UNSUBSCRIBE, roomname, emailaddr, url, generated_token, supplied_token);
- }
-
- else { // sorry man, I can't deal with that
- cprintf("%d Invalid command '%s'\n", ERROR + ILLEGAL_VALUE, cmd);
- }
-}
-
-
-/*
- * Module entry point
- */
-CTDL_MODULE_INIT(listsub)
-{
- if (!threading)
- {
- CtdlRegisterProtoHook(cmd_lsub, "LSUB", "List subscribe/unsubscribe");
- }
-
- /* return our module name for the log */
- return "listsub";
-}
+++ /dev/null
-// This module dumps and/or loads the Citadel database in XML format.
-//
-// Copyright (c) 1987-2021 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.
-//
-// Explanation of <progress> tags:
-//
-// 0% started
-// 2% finished exporting configuration
-// 7% finished exporting users
-// 12% finished exporting openids
-// 17% finished exporting rooms
-// 18% finished exporting floors
-// 25% finished exporting visits
-// 26-99% exporting messages
-// 100% finished exporting messages
-//
-// These tags are inserted into the XML stream to give the reader an approximation of its progress.
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <ctype.h>
-#include <limits.h>
-#include <expat.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "database.h"
-#include "msgbase.h"
-#include "user_ops.h"
-#include "euidindex.h"
-#include "ctdl_module.h"
-
-char migr_tempfilename1[PATH_MAX];
-char migr_tempfilename2[PATH_MAX];
-FILE *migr_global_message_list;
-int total_msgs = 0;
-char *ikey = NULL; // If we're importing a config key we store it here.
-
-//*****************************************************************************
-//* Code which implements the export appears in this section *
-//*****************************************************************************
-
-// Output a string to the client with these characters escaped: & < > " '
-void xml_strout(char *str) {
-
- char *c = str;
-
- if (str == NULL) {
- return;
- }
-
- while (*c != 0) {
- if (*c == '\"') {
- client_write(HKEY("""));
- }
- else if (*c == '\'') {
- client_write(HKEY("'"));
- }
- else if (*c == '<') {
- client_write(HKEY("<"));
- }
- else if (*c == '>') {
- client_write(HKEY(">"));
- }
- else if (*c == '&') {
- client_write(HKEY("&"));
- }
- else {
- client_write(c, 1);
- }
- ++c;
- }
-}
-
-
-// Export a user record as XML
-void migr_export_users_backend(char *username, void *data) {
-
- struct ctdluser u;
- if (CtdlGetUser(&u, username) != 0) {
- return;
- }
-
- client_write(HKEY("<user>\n"));
- cprintf("<u_version>%d</u_version>\n", u.version);
- cprintf("<u_uid>%ld</u_uid>\n", (long)u.uid);
- client_write(HKEY("<u_password>")); xml_strout(u.password); client_write(HKEY("</u_password>\n"));
- cprintf("<u_flags>%u</u_flags>\n", u.flags);
- cprintf("<u_timescalled>%ld</u_timescalled>\n", u.timescalled);
- cprintf("<u_posted>%ld</u_posted>\n", u.posted);
- cprintf("<u_axlevel>%d</u_axlevel>\n", u.axlevel);
- cprintf("<u_usernum>%ld</u_usernum>\n", u.usernum);
- cprintf("<u_lastcall>%ld</u_lastcall>\n", (long)u.lastcall);
- cprintf("<u_USuserpurge>%d</u_USuserpurge>\n", u.USuserpurge);
- client_write(HKEY("<u_fullname>")); xml_strout(u.fullname); client_write(HKEY("</u_fullname>\n"));
- cprintf("<u_msgnum_bio>%ld</u_msgnum_bio>\n", u.msgnum_bio);
- cprintf("<u_msgnum_pic>%ld</u_msgnum_pic>\n", u.msgnum_pic);
- cprintf("<u_emailaddrs>%s</u_emailaddrs>\n", u.emailaddrs);
- cprintf("<u_msgnum_inboxrules>%ld</u_msgnum_inboxrules>\n", u.msgnum_inboxrules);
- cprintf("<u_lastproc_inboxrules>%ld</u_lastproc_inboxrules>\n", u.lastproc_inboxrules);
- client_write(HKEY("</user>\n"));
-}
-
-
-void migr_export_users(void) {
- ForEachUser(migr_export_users_backend, NULL);
-}
-
-
-void migr_export_room_msg(long msgnum, void *userdata) {
- static int count = 0;
-
- cprintf("%ld,", msgnum);
- if (++count%10==0) {
- cprintf("\n");
- }
- fprintf(migr_global_message_list, "%ld\n", msgnum);
-}
-
-
-void migr_export_rooms_backend(struct ctdlroom *buf, void *data) {
- client_write(HKEY("<room>\n"));
- client_write(HKEY("<QRname>"));
- xml_strout(buf->QRname);
- client_write(HKEY("</QRname>\n"));
- client_write(HKEY("<QRpasswd>"));
- xml_strout(buf->QRpasswd);
- client_write(HKEY("</QRpasswd>\n"));
- cprintf("<QRroomaide>%ld</QRroomaide>\n", buf->QRroomaide);
- cprintf("<QRhighest>%ld</QRhighest>\n", buf->QRhighest);
- cprintf("<QRgen>%ld</QRgen>\n", (long)buf->QRgen);
- cprintf("<QRflags>%u</QRflags>\n", buf->QRflags);
- if (buf->QRflags & QR_DIRECTORY) {
- client_write(HKEY("<QRdirname>"));
- xml_strout(buf->QRdirname);
- client_write(HKEY("</QRdirname>\n"));
- }
- cprintf("<QRfloor>%d</QRfloor>\n", buf->QRfloor);
- cprintf("<QRmtime>%ld</QRmtime>\n", (long)buf->QRmtime);
- cprintf("<QRexpire_mode>%d</QRexpire_mode>\n", buf->QRep.expire_mode);
- cprintf("<QRexpire_value>%d</QRexpire_value>\n", buf->QRep.expire_value);
- cprintf("<QRnumber>%ld</QRnumber>\n", buf->QRnumber);
- cprintf("<QRorder>%d</QRorder>\n", buf->QRorder);
- cprintf("<QRflags2>%u</QRflags2>\n", buf->QRflags2);
- cprintf("<QRdefaultview>%d</QRdefaultview>\n", buf->QRdefaultview);
- cprintf("<msgnum_info>%ld</msgnum_info>\n", buf->msgnum_info);
- cprintf("<msgnum_pic>%ld</msgnum_pic>\n", buf->msgnum_pic);
- client_write(HKEY("</room>\n"));
-
- // message list goes inside this tag
- CtdlGetRoom(&CC->room, buf->QRname);
- client_write(HKEY("<room_messages>"));
- client_write(HKEY("<FRname>"));
- xml_strout(buf->QRname); // buf->QRname rather than CC->room.QRname to guarantee consistency
- client_write(HKEY("</FRname>\n"));
- client_write(HKEY("<FRmsglist>\n"));
- CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, migr_export_room_msg, NULL);
- client_write(HKEY("</FRmsglist>\n"));
- client_write(HKEY("</room_messages>\n"));
-}
-
-
-void migr_export_rooms(void) {
- char cmd[SIZ];
- migr_global_message_list = fopen(migr_tempfilename1, "w");
- if (migr_global_message_list != NULL) {
- CtdlForEachRoom(migr_export_rooms_backend, NULL);
- fclose(migr_global_message_list);
- }
-
- // Process the 'global' message list. (Sort it and remove dups.
- // Dups are ok because a message may be in more than one room, but
- // this will be handled by exporting the reference count, not by
- // exporting the message multiple times.)
- snprintf(cmd, sizeof cmd, "sort -n <%s >%s", migr_tempfilename1, migr_tempfilename2);
- if (system(cmd) != 0) {
- syslog(LOG_ERR, "migrate: error %d", errno);
- }
- snprintf(cmd, sizeof cmd, "uniq <%s >%s", migr_tempfilename2, migr_tempfilename1);
- if (system(cmd) != 0) {
- syslog(LOG_ERR, "migrate: error %d", errno);
- }
-
- snprintf(cmd, sizeof cmd, "wc -l %s", migr_tempfilename1);
- FILE *fp = popen(cmd, "r");
- if (fp) {
- fgets(cmd, sizeof cmd, fp);
- pclose(fp);
- total_msgs = atoi(cmd);
- }
- else {
- total_msgs = 1; // any nonzero just to keep it from barfing
- }
- syslog(LOG_DEBUG, "migrate: total messages to be exported: %d", total_msgs);
-}
-
-
-void migr_export_floors(void) {
- struct floor qfbuf, *buf;
- int i;
-
- for (i=0; i < MAXFLOORS; ++i) {
- client_write(HKEY("<floor>\n"));
- cprintf("<f_num>%d</f_num>\n", i);
- CtdlGetFloor(&qfbuf, i);
- buf = &qfbuf;
- cprintf("<f_flags>%u</f_flags>\n", buf->f_flags);
- client_write(HKEY("<f_name>")); xml_strout(buf->f_name); client_write(HKEY("</f_name>\n"));
- cprintf("<f_ref_count>%d</f_ref_count>\n", buf->f_ref_count);
- cprintf("<f_ep_expire_mode>%d</f_ep_expire_mode>\n", buf->f_ep.expire_mode);
- cprintf("<f_ep_expire_value>%d</f_ep_expire_value>\n", buf->f_ep.expire_value);
- client_write(HKEY("</floor>\n"));
- }
-}
-
-
-// Return nonzero if the supplied string contains only characters which are valid in a sequence set.
-int is_sequence_set(char *s) {
- if (!s) return(0);
-
- char *c = s;
- char ch;
- while (ch = *c++, ch) {
- if (!strchr("0123456789*,:", ch)) {
- return(0);
- }
- }
- return(1);
-}
-
-
-// Traverse the visits file...
-void migr_export_visits(void) {
- visit vbuf;
- struct cdbdata *cdbv;
-
- cdb_rewind(CDB_VISIT);
-
- while (cdbv = cdb_next_item(CDB_VISIT), cdbv != NULL) {
- memset(&vbuf, 0, sizeof(visit));
- memcpy(&vbuf, cdbv->ptr,
- ((cdbv->len > sizeof(visit)) ?
- sizeof(visit) : cdbv->len));
- cdb_free(cdbv);
-
- client_write(HKEY("<visit>\n"));
- cprintf("<v_roomnum>%ld</v_roomnum>\n", vbuf.v_roomnum);
- cprintf("<v_roomgen>%ld</v_roomgen>\n", vbuf.v_roomgen);
- cprintf("<v_usernum>%ld</v_usernum>\n", vbuf.v_usernum);
-
- client_write(HKEY("<v_seen>"));
- if ( (!IsEmptyStr(vbuf.v_seen)) && (is_sequence_set(vbuf.v_seen)) ) {
- xml_strout(vbuf.v_seen);
- }
- else {
- cprintf("%ld", vbuf.v_lastseen);
- }
- client_write(HKEY("</v_seen>"));
-
- if ( (!IsEmptyStr(vbuf.v_answered)) && (is_sequence_set(vbuf.v_answered)) ) {
- client_write(HKEY("<v_answered>"));
- xml_strout(vbuf.v_answered);
- client_write(HKEY("</v_answered>\n"));
- }
-
- cprintf("<v_flags>%u</v_flags>\n", vbuf.v_flags);
- cprintf("<v_view>%d</v_view>\n", vbuf.v_view);
- client_write(HKEY("</visit>\n"));
- }
-}
-
-
-void migr_export_message(long msgnum) {
- struct MetaData smi;
- struct CtdlMessage *msg;
- struct ser_ret smr;
- long bytes_written = 0;
- long this_block = 0;
-
- // We can use a static buffer here because there will never be more than
- // one of this operation happening at any given time, and it's really best
- // to just keep it allocated once instead of torturing malloc/free.
- // Call this function with msgnum "-1" to free the buffer when finished.
-
- static int encoded_alloc = 0;
- static char *encoded_msg = NULL;
-
- if (msgnum < 0) {
- if ((encoded_alloc == 0) && (encoded_msg != NULL)) {
- free(encoded_msg);
- encoded_alloc = 0;
- encoded_msg = NULL;
- }
- return;
- }
-
- // Ok, here we go ...
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) return; // fail silently
-
- client_write(HKEY("<message>\n"));
- GetMetaData(&smi, msgnum);
- cprintf("<msg_msgnum>%ld</msg_msgnum>\n", msgnum);
- cprintf("<msg_meta_refcount>%d</msg_meta_refcount>\n", smi.meta_refcount);
- cprintf("<msg_meta_rfc822_length>%ld</msg_meta_rfc822_length>\n", smi.meta_rfc822_length);
- client_write(HKEY("<msg_meta_content_type>")); xml_strout(smi.meta_content_type); client_write(HKEY("</msg_meta_content_type>\n"));
-
- client_write(HKEY("<msg_text>"));
- CtdlSerializeMessage(&smr, msg);
- CM_Free(msg);
-
- // Predict the buffer size we need. Expand the buffer if necessary.
- int encoded_len = smr.len * 15 / 10 ;
- if (encoded_len > encoded_alloc) {
- encoded_alloc = encoded_len;
- encoded_msg = realloc(encoded_msg, encoded_alloc);
- }
-
- if (encoded_msg == NULL) {
- // Questionable hack that hopes it'll work next time and we only lose one message
- encoded_alloc = 0;
- }
- else {
- // Once we do the encoding we know the exact size
- encoded_len = CtdlEncodeBase64(encoded_msg, (char *)smr.ser, smr.len, 1);
-
- // Careful now. If the message is gargantuan, trying to write multiple gigamegs in one
- // big write operation can make our transport unhappy. So we'll chunk it up 10 KB at a time.
- bytes_written = 0;
- while ( (bytes_written < encoded_len) && (!server_shutting_down) ) {
- this_block = encoded_len - bytes_written;
- if (this_block > 10240) {
- this_block = 10240;
- }
- client_write(&encoded_msg[bytes_written], this_block);
- bytes_written += this_block;
- }
- }
-
- free(smr.ser);
-
- client_write(HKEY("</msg_text>\n"));
- client_write(HKEY("</message>\n"));
-}
-
-
-void migr_export_openids(void) {
- struct cdbdata *cdboi;
- long usernum;
- char url[512];
-
- cdb_rewind(CDB_EXTAUTH);
- while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
- if (cdboi->len > sizeof(long)) {
- client_write(HKEY("<openid>\n"));
- memcpy(&usernum, cdboi->ptr, sizeof(long));
- snprintf(url, sizeof url, "%s", (cdboi->ptr)+sizeof(long) );
- client_write(HKEY("<oid_url>"));
- xml_strout(url);
- client_write(HKEY("</oid_url>\n"));
- cprintf("<oid_usernum>%ld</oid_usernum>\n", usernum);
- client_write(HKEY("</openid>\n"));
- }
- cdb_free(cdboi);
- }
-}
-
-
-void migr_export_configs(void) {
- struct cdbdata *cdbcfg;
- int keylen = 0;
- char *key = NULL;
- char *value = NULL;
-
- cdb_rewind(CDB_CONFIG);
- while (cdbcfg = cdb_next_item(CDB_CONFIG), cdbcfg != NULL) {
-
- keylen = strlen(cdbcfg->ptr);
- key = cdbcfg->ptr;
- value = cdbcfg->ptr + keylen + 1;
-
- client_write("<config key=\"", 13);
- xml_strout(key);
- client_write("\">", 2);
- xml_strout(value);
- client_write("</config>\n", 10);
- cdb_free(cdbcfg);
- }
-}
-
-
-void migr_export_messages(void) {
- char buf[SIZ];
- long msgnum;
- int count = 0;
- int progress = 0;
- int prev_progress = 0;
- CitContext *Ctx;
-
- Ctx = CC;
- migr_global_message_list = fopen(migr_tempfilename1, "r");
- if (migr_global_message_list != NULL) {
- syslog(LOG_INFO, "migrate: opened %s", migr_tempfilename1);
- while ((Ctx->kill_me == 0) &&
- (fgets(buf, sizeof(buf), migr_global_message_list) != NULL)) {
- msgnum = atol(buf);
- if (msgnum > 0L) {
- migr_export_message(msgnum);
- ++count;
- }
- progress = (count * 74 / total_msgs) + 25 ;
- if ((progress > prev_progress) && (progress < 100)) {
- cprintf("<progress>%d</progress>\n", progress);
- }
- prev_progress = progress;
- }
- fclose(migr_global_message_list);
- }
- if (Ctx->kill_me == 0) {
- syslog(LOG_INFO, "migrate: exported %d messages.", count);
- }
- else {
- syslog(LOG_ERR, "migrate: export aborted due to client disconnect!");
- }
-
- migr_export_message(-1L); // This frees the encoding buffer
-}
-
-
-void migr_do_export(void) {
- CitContext *Ctx;
-
- Ctx = CC;
- cprintf("%d Exporting all Citadel databases.\n", LISTING_FOLLOWS);
- Ctx->dont_term = 1;
-
- client_write(HKEY("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"));
- client_write(HKEY("<citadel_migrate_data>\n"));
- cprintf("<version>%d</version>\n", REV_LEVEL);
- cprintf("<progress>%d</progress>\n", 0);
-
- // export the configuration database
- migr_export_configs();
- cprintf("<progress>%d</progress>\n", 2);
-
- if (Ctx->kill_me == 0) migr_export_users();
- cprintf("<progress>%d</progress>\n", 7);
- if (Ctx->kill_me == 0) migr_export_openids();
- cprintf("<progress>%d</progress>\n", 12);
- if (Ctx->kill_me == 0) migr_export_rooms();
- cprintf("<progress>%d</progress>\n", 17);
- if (Ctx->kill_me == 0) migr_export_floors();
- cprintf("<progress>%d</progress>\n", 18);
- if (Ctx->kill_me == 0) migr_export_visits();
- cprintf("<progress>%d</progress>\n", 25);
- if (Ctx->kill_me == 0) migr_export_messages();
- cprintf("<progress>%d</progress>\n", 100);
- client_write(HKEY("</citadel_migrate_data>\n"));
- client_write(HKEY("000\n"));
- Ctx->dont_term = 0;
-}
-
-
-// Import code
-// Here's the code that implements the import side. It's going to end up
-// being one big loop with lots of global variables. I don't care.
-// You wouldn't run multiple concurrent imports anyway. If this offends your
-// delicate sensibilities then go rewrite it in Ruby on Rails or something.
-
-
-int citadel_migrate_data = 0; // Are we inside a <citadel_migrate_data> tag pair?
-StrBuf *migr_chardata = NULL;
-StrBuf *migr_MsgData = NULL;
-struct ctdluser usbuf;
-struct ctdlroom qrbuf;
-char openid_url[512];
-long openid_usernum = 0;
-char FRname[ROOMNAMELEN];
-struct floor flbuf;
-int floornum = 0;
-visit vbuf;
-struct MetaData smi;
-long import_msgnum = 0;
-
-// This callback stores up the data which appears in between tags.
-void migr_xml_chardata(void *data, const XML_Char *s, int len) {
- StrBufAppendBufPlain(migr_chardata, s, len, 0);
-}
-
-
-void migr_xml_start(void *data, const char *el, const char **attr) {
- int i;
-
- // *** GENERAL STUFF ***
-
- // Reset chardata_len to zero and init buffer
- FlushStrBuf(migr_chardata);
- FlushStrBuf(migr_MsgData);
-
- if (!strcasecmp(el, "citadel_migrate_data")) {
- ++citadel_migrate_data;
-
- // As soon as it looks like the input data is a genuine Citadel XML export,
- // whack the existing database on disk to make room for the new one.
- if (citadel_migrate_data == 1) {
- syslog(LOG_INFO, "migrate: erasing existing databases so we can receive the incoming import");
- for (i = 0; i < MAXCDB; ++i) {
- cdb_trunc(i);
- }
- }
- return;
- }
-
- if (citadel_migrate_data != 1) {
- syslog(LOG_ERR, "migrate: out-of-sequence tag <%s> detected. Warning: ODD-DATA!", el);
- return;
- }
-
- // When we begin receiving XML for one of these record types, clear out the associated
- // buffer so we don't accidentally carry over any data from a previous record.
- if (!strcasecmp(el, "user")) memset(&usbuf, 0, sizeof (struct ctdluser));
- else if (!strcasecmp(el, "openid")) memset(openid_url, 0, sizeof openid_url);
- else if (!strcasecmp(el, "room")) memset(&qrbuf, 0, sizeof (struct ctdlroom));
- else if (!strcasecmp(el, "room_messages")) memset(FRname, 0, sizeof FRname);
- else if (!strcasecmp(el, "floor")) memset(&flbuf, 0, sizeof (struct floor));
- else if (!strcasecmp(el, "visit")) memset(&vbuf, 0, sizeof (visit));
-
- else if (!strcasecmp(el, "message")) {
- memset(&smi, 0, sizeof (struct MetaData));
- import_msgnum = 0;
- }
- else if (!strcasecmp(el, "config")) {
- if (ikey != NULL) {
- free(ikey);
- ikey = NULL;
- }
- while (*attr) {
- if (!strcasecmp(attr[0], "key")) {
- ikey = strdup(attr[1]);
- }
- attr += 2;
- }
- }
-
-}
-
-
-int migr_userrecord(void *data, const char *el) {
- if (!strcasecmp(el, "u_version")) usbuf.version = atoi(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "u_uid")) usbuf.uid = atol(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "u_password")) safestrncpy(usbuf.password, ChrPtr(migr_chardata), sizeof usbuf.password);
- else if (!strcasecmp(el, "u_flags")) usbuf.flags = atoi(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "u_timescalled")) usbuf.timescalled = atol(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "u_posted")) usbuf.posted = atol(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "u_axlevel")) usbuf.axlevel = atoi(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "u_usernum")) usbuf.usernum = atol(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "u_lastcall")) usbuf.lastcall = atol(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "u_USuserpurge")) usbuf.USuserpurge = atoi(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "u_fullname")) safestrncpy(usbuf.fullname, ChrPtr(migr_chardata), sizeof usbuf.fullname);
- else if (!strcasecmp(el, "u_msgnum_bio")) usbuf.msgnum_bio = atol(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "u_msgnum_pic")) usbuf.msgnum_pic = atol(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "u_emailaddrs")) safestrncpy(usbuf.emailaddrs, ChrPtr(migr_chardata), sizeof usbuf.emailaddrs);
- else if (!strcasecmp(el, "u_msgnum_inboxrules")) usbuf.msgnum_inboxrules = atol(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "u_lastproc_inboxrules")) usbuf.lastproc_inboxrules = atol(ChrPtr(migr_chardata));
- else return 0;
- return 1;
-}
-
-
-int migr_roomrecord(void *data, const char *el) {
- if (!strcasecmp(el, "QRname")) safestrncpy(qrbuf.QRname, ChrPtr(migr_chardata), sizeof qrbuf.QRname);
- else if (!strcasecmp(el, "QRpasswd")) safestrncpy(qrbuf.QRpasswd, ChrPtr(migr_chardata), sizeof qrbuf.QRpasswd);
- else if (!strcasecmp(el, "QRroomaide")) qrbuf.QRroomaide = atol(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "QRhighest")) qrbuf.QRhighest = atol(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "QRgen")) qrbuf.QRgen = atol(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "QRflags")) qrbuf.QRflags = atoi(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "QRdirname")) safestrncpy(qrbuf.QRdirname, ChrPtr(migr_chardata), sizeof qrbuf.QRdirname);
- else if (!strcasecmp(el, "QRfloor")) qrbuf.QRfloor = atoi(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "QRmtime")) qrbuf.QRmtime = atol(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "QRexpire_mode")) qrbuf.QRep.expire_mode = atoi(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "QRexpire_value")) qrbuf.QRep.expire_value = atoi(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "QRnumber")) qrbuf.QRnumber = atol(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "QRorder")) qrbuf.QRorder = atoi(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "QRflags2")) qrbuf.QRflags2 = atoi(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "QRdefaultview")) qrbuf.QRdefaultview = atoi(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "msgnum_info")) qrbuf.msgnum_info = atol(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "msgnum_pic")) qrbuf.msgnum_pic = atol(ChrPtr(migr_chardata));
- else return 0;
- return 1;
-}
-
-
-int migr_floorrecord(void *data, const char *el) {
- if (!strcasecmp(el, "f_num")) floornum = atoi(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "f_flags")) flbuf.f_flags = atoi(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "f_name")) safestrncpy(flbuf.f_name, ChrPtr(migr_chardata), sizeof flbuf.f_name);
- else if (!strcasecmp(el, "f_ref_count")) flbuf.f_ref_count = atoi(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "f_ep_expire_mode")) flbuf.f_ep.expire_mode = atoi(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "f_ep_expire_value")) flbuf.f_ep.expire_value = atoi(ChrPtr(migr_chardata));
- else return 0;
- return 1;
-}
-
-
-int migr_visitrecord(void *data, const char *el) {
- if (!strcasecmp(el, "v_roomnum")) vbuf.v_roomnum = atol(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "v_roomgen")) vbuf.v_roomgen = atol(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "v_usernum")) vbuf.v_usernum = atol(ChrPtr(migr_chardata));
-
- else if (!strcasecmp(el, "v_seen")) {
- int is_textual_seen = 0;
- int i;
- int max = StrLength(migr_chardata);
-
- vbuf.v_lastseen = atol(ChrPtr(migr_chardata));
- is_textual_seen = 0;
- for (i=0; i < max; ++i)
- if (!isdigit(ChrPtr(migr_chardata)[i]))
- is_textual_seen = 1;
- if (is_textual_seen)
- safestrncpy(vbuf.v_seen, ChrPtr(migr_chardata), sizeof vbuf.v_seen);
- }
-
- else if (!strcasecmp(el, "v_answered")) safestrncpy(vbuf.v_answered, ChrPtr(migr_chardata), sizeof vbuf.v_answered);
- else if (!strcasecmp(el, "v_flags")) vbuf.v_flags = atoi(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "v_view")) vbuf.v_view = atoi(ChrPtr(migr_chardata));
- else return 0;
- return 1;
-}
-
-
-void migr_xml_end(void *data, const char *el) {
- const char *ptr;
- int msgcount = 0;
- long msgnum = 0L;
- long *msglist = NULL;
- int msglist_alloc = 0;
- // *** GENERAL STUFF ***
-
- if (!strcasecmp(el, "citadel_migrate_data")) {
- --citadel_migrate_data;
- return;
- }
-
- if (citadel_migrate_data != 1) {
- syslog(LOG_ERR, "migrate: out-of-sequence tag <%s> detected. Warning: ODD-DATA!", el);
- return;
- }
-
- // syslog(LOG_DEBUG, "END TAG: <%s> DATA: <%s>\n", el, (migr_chardata_len ? migr_chardata : ""));
-
- // *** CONFIG ***
-
- if (!strcasecmp(el, "config")) {
- syslog(LOG_DEBUG, "migrate: imported config key=%s", ikey);
-
- if (ikey != NULL) {
- CtdlSetConfigStr(ikey, (char *)ChrPtr(migr_chardata));
- free(ikey);
- ikey = NULL;
- }
- else {
- syslog(LOG_INFO, "migrate: closed a <config> tag but no key name was supplied.");
- }
- }
-
- // *** USER ***
- else if ((!strncasecmp(el, HKEY("u_"))) &&
- migr_userrecord(data, el))
- ; /* Nothing to do anymore */
- else if (!strcasecmp(el, "user")) {
- CtdlPutUser(&usbuf);
- syslog(LOG_INFO, "migrate: imported user: %s", usbuf.fullname);
- }
-
- // *** OPENID ***
-
- else if (!strcasecmp(el, "oid_url")) safestrncpy(openid_url, ChrPtr(migr_chardata), sizeof openid_url);
- else if (!strcasecmp(el, "oid_usernum")) openid_usernum = atol(ChrPtr(migr_chardata));
-
- else if (!strcasecmp(el, "openid")) { // see serv_openid_rp.c for a description of the record format
- char *oid_data;
- int oid_data_len;
- oid_data_len = sizeof(long) + strlen(openid_url) + 1;
- oid_data = malloc(oid_data_len);
- memcpy(oid_data, &openid_usernum, sizeof(long));
- memcpy(&oid_data[sizeof(long)], openid_url, strlen(openid_url) + 1);
- cdb_store(CDB_EXTAUTH, openid_url, strlen(openid_url), oid_data, oid_data_len);
- free(oid_data);
- syslog(LOG_INFO, "migrate: imported OpenID: %s (%ld)", openid_url, openid_usernum);
- }
-
- // *** ROOM ***
- else if ((!strncasecmp(el, HKEY("QR"))) &&
- migr_roomrecord(data, el))
- ; // Nothing to do anymore
- else if (!strcasecmp(el, "room")) {
- CtdlPutRoom(&qrbuf);
- syslog(LOG_INFO, "migrate: imported room: %s", qrbuf.QRname);
- }
-
- // *** ROOM MESSAGE POINTERS ***
-
- else if (!strcasecmp(el, "FRname")) {
- safestrncpy(FRname, ChrPtr(migr_chardata), sizeof FRname);
- }
-
- else if (!strcasecmp(el, "FRmsglist")) {
- if (!IsEmptyStr(FRname)) {
- msgcount = 0;
- msglist_alloc = 1000;
- msglist = malloc(sizeof(long) * msglist_alloc);
-
- syslog(LOG_DEBUG, "migrate: message list for: %s", FRname);
-
- ptr = ChrPtr(migr_chardata);
- while (*ptr != 0) {
- while ((*ptr != 0) && (!isdigit(*ptr))) {
- ++ptr;
- }
- if ((*ptr != 0) && (isdigit(*ptr))) {
- msgnum = atol(ptr);
- if (msgnum > 0L) {
- if (msgcount >= msglist_alloc) {
- msglist_alloc *= 2;
- msglist = realloc(msglist, sizeof(long) * msglist_alloc);
- }
- msglist[msgcount++] = msgnum;
- }
- }
- while ((*ptr != 0) && (isdigit(*ptr))) {
- ++ptr;
- }
- }
- }
- if (msgcount > 0) {
- CtdlSaveMsgPointersInRoom(FRname, msglist, msgcount, 0, NULL, 1);
- }
- free(msglist);
- msglist = NULL;
- msglist_alloc = 0;
- syslog(LOG_DEBUG, "migrate: imported %d messages.", msgcount);
- if (server_shutting_down) {
- return;
- }
- }
-
- // *** FLOORS ***
- else if ((!strncasecmp(el, HKEY("f_"))) &&
- migr_floorrecord(data, el))
- ; // Nothing to do anymore
-
- else if (!strcasecmp(el, "floor")) {
- CtdlPutFloor(&flbuf, floornum);
- syslog(LOG_INFO, "migrate: imported floor #%d (%s)", floornum, flbuf.f_name);
- }
-
- // *** VISITS ***
- else if ((!strncasecmp(el, HKEY("v_"))) && migr_visitrecord(data, el)) {
- ; // Nothing to do anymore
- }
- else if (!strcasecmp(el, "visit")) {
- put_visit(&vbuf);
- syslog(LOG_INFO, "migrate: imported visit: %ld/%ld/%ld", vbuf.v_roomnum, vbuf.v_roomgen, vbuf.v_usernum);
- }
-
- // *** MESSAGES ***
-
- else if (!strcasecmp(el, "msg_msgnum")) smi.meta_msgnum = import_msgnum = atol(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "msg_meta_refcount")) smi.meta_refcount = atoi(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "msg_meta_rfc822_length")) smi.meta_rfc822_length = atoi(ChrPtr(migr_chardata));
- else if (!strcasecmp(el, "msg_meta_content_type")) safestrncpy(smi.meta_content_type, ChrPtr(migr_chardata), sizeof smi.meta_content_type);
-
- else if (!strcasecmp(el, "msg_text")) {
- long rc;
- struct CtdlMessage *msg;
-
- FlushStrBuf(migr_MsgData);
- StrBufDecodeBase64To(migr_chardata, migr_MsgData);
-
- msg = CtdlDeserializeMessage(import_msgnum, -1,
- ChrPtr(migr_MsgData),
- StrLength(migr_MsgData));
- if (msg != NULL) {
- rc = CtdlSaveThisMessage(msg, import_msgnum, 0);
- if (rc == 0) {
- PutMetaData(&smi);
- }
- CM_Free(msg);
- }
- else {
- rc = -1;
- }
-
- syslog(LOG_INFO,
- "migrate: %s message #%ld, size=%d, ref=%d, body=%ld, content-type: %s",
- (rc!= 0)?"failed to import":"imported",
- import_msgnum,
- StrLength(migr_MsgData),
- smi.meta_refcount,
- smi.meta_rfc822_length,
- smi.meta_content_type
- );
- memset(&smi, 0, sizeof(smi));
- }
-
- // *** MORE GENERAL STUFF ***
-
- FlushStrBuf(migr_chardata);
-}
-
-
-// Import begins here
-void migr_do_import(void) {
- XML_Parser xp;
- char buf[SIZ];
-
- unbuffer_output();
- migr_chardata = NewStrBufPlain(NULL, SIZ * 20);
- migr_MsgData = NewStrBufPlain(NULL, SIZ * 20);
- xp = XML_ParserCreate(NULL);
- if (!xp) {
- cprintf("%d Failed to create XML parser instance\n", ERROR+INTERNAL_ERROR);
- return;
- }
- XML_SetElementHandler(xp, migr_xml_start, migr_xml_end);
- XML_SetCharacterDataHandler(xp, migr_xml_chardata);
-
- CC->dont_term = 1;
-
- cprintf("%d sock it to me\n", SEND_LISTING);
- unbuffer_output();
-
- client_set_inbound_buf(SIZ * 10);
-
- while (client_getln(buf, sizeof buf) >= 0 && strcmp(buf, "000")) {
- XML_Parse(xp, buf, strlen(buf), 0);
- if (server_shutting_down)
- break; // Should we break or return?
- }
-
- XML_Parse(xp, "", 0, 1);
- XML_ParserFree(xp);
- FreeStrBuf(&migr_chardata);
- FreeStrBuf(&migr_MsgData);
- rebuild_euid_index();
- rebuild_usersbynumber();
- CtdlSetConfigInt("MM_fulltext_wordbreaker", -1); // Set an invalid wordbreaker to force re-indexing
- CC->dont_term = 0;
-}
-
-
-// ******************************************************************************
-// * Dispatcher, Common code *
-// ******************************************************************************
-
-// Dump out the pathnames of directories which can be copied "as is"
-void migr_do_listdirs(void) {
- cprintf("%d Don't forget these:\n", LISTING_FOLLOWS);
- cprintf("files|%s\n", ctdl_file_dir);
- cprintf("messages|%s\n", ctdl_message_dir);
- cprintf("keys|%s\n", ctdl_key_dir);
- cprintf("000\n");
-}
-
-
-// ******************************************************************************
-// * Repair database integrity *
-// ******************************************************************************
-
-StrBuf *PlainMessageBuf = NULL;
-HashList *UsedMessageIDS = NULL;
-
-int migr_restore_message_metadata(long msgnum, int refcount) {
- struct MetaData smi;
- struct CtdlMessage *msg;
- char *mptr = NULL;
-
- // We can use a static buffer here because there will never be more than
- // one of this operation happening at any given time, and it's really best
- // to just keep it allocated once instead of torturing malloc/free.
- // Call this function with msgnum "-1" to free the buffer when finished.
- static int encoded_alloc = 0;
- static char *encoded_msg = NULL;
-
- if (msgnum < 0) {
- if ((encoded_alloc == 0) && (encoded_msg != NULL)) {
- free(encoded_msg);
- encoded_alloc = 0;
- encoded_msg = NULL;
- // todo FreeStrBuf(&PlainMessageBuf); PlainMessageBuf = NULL;
- }
- return 0;
- }
-
- if (PlainMessageBuf == NULL) {
- PlainMessageBuf = NewStrBufPlain(NULL, 10*SIZ);
- }
-
- // Ok, here we go ...
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) {
- return 1;
- }
-
- GetMetaData(&smi, msgnum);
- smi.meta_msgnum = msgnum;
- smi.meta_refcount = refcount;
-
- // restore the content type from the message body:
- mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
- if (mptr != NULL) {
- char *aptr;
- safestrncpy(smi.meta_content_type, &mptr[13], sizeof smi.meta_content_type);
- striplt(smi.meta_content_type);
- aptr = smi.meta_content_type;
- while (!IsEmptyStr(aptr)) {
- if ((*aptr == ';')
- || (*aptr == ' ')
- || (*aptr == 13)
- || (*aptr == 10)) {
- memset(aptr, 0, sizeof(smi.meta_content_type) - (aptr - smi.meta_content_type));
- }
- else aptr++;
- }
- }
-
- CC->redirect_buffer = PlainMessageBuf;
- CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
- smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
- CC->redirect_buffer = NULL;
-
- syslog(LOG_INFO,
- "migrate: setting message #%ld meta data to: refcount=%d, bodylength=%ld, content-type: %s",
- smi.meta_msgnum,
- smi.meta_refcount,
- smi.meta_rfc822_length,
- smi.meta_content_type
- );
-
- PutMetaData(&smi);
- CM_Free(msg);
- return 0;
-}
-
-
-void migr_check_room_msg(long msgnum, void *userdata) {
- fprintf(migr_global_message_list, "%ld %s\n", msgnum, CC->room.QRname);
-}
-
-
-void migr_check_rooms_backend(struct ctdlroom *buf, void *data) {
- // message list goes inside this tag
- CtdlGetRoom(&CC->room, buf->QRname);
- CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, migr_check_room_msg, NULL);
-}
-
-
-void RemoveMessagesFromRooms(StrBuf *RoomNameVec, long msgnum) {
- struct MetaData smi;
- const char *Pos = NULL;
- StrBuf *oneRoom = NewStrBuf();
-
- syslog(LOG_INFO, "migrate: removing message pointer %ld from these rooms: %s", msgnum, ChrPtr(RoomNameVec));
-
- while (Pos != StrBufNOTNULL){
- StrBufExtract_NextToken(oneRoom, RoomNameVec, &Pos, '|');
- CtdlDeleteMessages(ChrPtr(oneRoom), &msgnum, 1, "");
- };
- GetMetaData(&smi, msgnum);
- AdjRefCount(msgnum, -smi.meta_refcount);
-}
-
-
-void migr_do_restore_meta(void) {
- char buf[SIZ];
- int failGetMessage;
- long msgnum = 0;
- int lastnum = 0;
- int refcount = 0;
- CitContext *Ctx;
- char *prn;
- StrBuf *RoomNames;
- char cmd[SIZ];
-
- migr_global_message_list = fopen(migr_tempfilename1, "w");
- if (migr_global_message_list != NULL) {
- CtdlForEachRoom(migr_check_rooms_backend, NULL);
- fclose(migr_global_message_list);
- }
-
- // Process the 'global' message list. (Sort it and remove dups.
- // Dups are ok because a message may be in more than one room, but
- // this will be handled by exporting the reference count, not by
- // exporting the message multiple times.)
- snprintf(cmd, sizeof cmd, "sort -n <%s >%s", migr_tempfilename1, migr_tempfilename2);
- if (system(cmd) != 0) {
- syslog(LOG_ERR, "migrate: error %d", errno);
- }
-
- RoomNames = NewStrBuf();
- Ctx = CC;
- migr_global_message_list = fopen(migr_tempfilename2, "r");
- if (migr_global_message_list != NULL) {
- syslog(LOG_INFO, "migrate: opened %s", migr_tempfilename1);
- while ((Ctx->kill_me == 0) &&
- (fgets(buf, sizeof(buf), migr_global_message_list) != NULL)) {
- msgnum = atol(buf);
- if (msgnum == 0L)
- continue;
- if (lastnum == 0) {
- lastnum = msgnum;
- }
- prn = strchr(buf, ' ');
- if (lastnum != msgnum) {
- failGetMessage = migr_restore_message_metadata(lastnum, refcount);
- if (failGetMessage) {
- RemoveMessagesFromRooms(RoomNames, lastnum);
- }
- refcount = 1;
- lastnum = msgnum;
- if (prn != NULL) {
- StrBufPlain(RoomNames, prn + 1, -1);
- }
- StrBufTrim(RoomNames);
- }
- else {
- if (prn != NULL) {
- if (StrLength(RoomNames) > 0) {
- StrBufAppendBufPlain(RoomNames, HKEY("|"), 0);
- }
- StrBufAppendBufPlain(RoomNames, prn, -1, 1);
- StrBufTrim(RoomNames);
- }
- refcount ++;
- }
- lastnum = msgnum;
- }
- failGetMessage = migr_restore_message_metadata(msgnum, refcount);
- if (failGetMessage) {
- RemoveMessagesFromRooms(RoomNames, lastnum);
- }
- fclose(migr_global_message_list);
- }
-
- migr_restore_message_metadata(-1L, -1); // This frees the encoding buffer
- cprintf("%d system analysis completed", CIT_OK);
- Ctx->kill_me = 1;
-}
-
-
-// ******************************************************************************
-// * Dispatcher, Common code *
-// ******************************************************************************
-void cmd_migr(char *cmdbuf) {
- char cmd[32];
-
- if (CtdlAccessCheck(ac_aide)) return;
-
- if (CtdlTrySingleUser()) {
- CtdlDisableHouseKeeping();
- CtdlMakeTempFileName(migr_tempfilename1, sizeof migr_tempfilename1);
- CtdlMakeTempFileName(migr_tempfilename2, sizeof migr_tempfilename2);
-
- extract_token(cmd, cmdbuf, 0, '|', sizeof cmd);
- if (!strcasecmp(cmd, "export")) {
- migr_do_export();
- }
- else if (!strcasecmp(cmd, "import")) {
- migr_do_import();
- }
- else if (!strcasecmp(cmd, "listdirs")) {
- migr_do_listdirs();
- }
- else if (!strcasecmp(cmd, "restoremeta")) {
- migr_do_restore_meta();
- }
- else {
- cprintf("%d illegal command\n", ERROR + ILLEGAL_VALUE);
- }
-
- unlink(migr_tempfilename1);
- unlink(migr_tempfilename2);
-
- CtdlEnableHouseKeeping();
- CtdlEndSingleUser();
- }
- else {
- cprintf("%d The migrator is already running.\n", ERROR + RESOURCE_BUSY);
- }
-}
-
-
-// ******************************************************************************
-// * Module Hook *
-// ******************************************************************************
-
-CTDL_MODULE_INIT(migrate)
-{
- if (!threading)
- {
- CtdlRegisterProtoHook(cmd_migr, "MIGR", "Across-the-wire migration");
- }
-
- // return our module name for the log
- return "migrate";
-}
+++ /dev/null
-/*
- * Automatically copies the contents of a "New User Greetings" room to the
- * inbox of any new user upon account creation.
- *
- * Copyright (c) 1987-2012 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.
- */
-
-/*
- * Name of the New User Greetings room.
- */
-#define NEWUSERGREETINGS "New User Greetings"
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include "ctdl_module.h"
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-
-/*
- * Copy the contents of the New User Greetings> room to the user's Mail> room.
- */
-void CopyNewUserGreetings(void) {
- struct cdbdata *cdbfr;
- long *msglist = NULL;
- int num_msgs = 0;
- char mailboxname[ROOMNAMELEN];
-
-
- /* Only do this for new users. */
- if (CC->user.timescalled != 1) return;
-
- /* This user's mailbox. */
- CtdlMailboxName(mailboxname, sizeof mailboxname, &CC->user, MAILROOM);
-
- /* Go to the source room ... bail out silently if it's not there,
- * or if it's not private.
- */
- if (CtdlGetRoom(&CC->room, NEWUSERGREETINGS) != 0) return;
- if ((CC->room.QRflags & QR_PRIVATE) == 0) return;
-
- cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
-
- if (cdbfr != NULL) {
- msglist = malloc(cdbfr->len);
- memcpy(msglist, cdbfr->ptr, cdbfr->len);
- num_msgs = cdbfr->len / sizeof(long);
- cdb_free(cdbfr);
- }
-
- if (num_msgs > 0) {
- CtdlSaveMsgPointersInRoom(mailboxname, msglist, num_msgs, 1, NULL, 0);
- }
-
- /* Now free the memory we used, and go away. */
- if (msglist != NULL) free(msglist);
-}
-
-
-CTDL_MODULE_INIT(newuser)
-{
- if (!threading)
- {
- CtdlRegisterSessionHook(CopyNewUserGreetings, EVT_LOGIN, PRIO_LOGIN + 1);
- }
-
- /* return our module name for the log */
- return "newuser";
-}
+++ /dev/null
-//
-// NNTP server module (RFC 3977)
-//
-// Copyright (c) 2014-2020 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.
-//
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <termios.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <syslog.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "user_ops.h"
-#include "room_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "genstamp.h"
-#include "domain.h"
-#include "clientsocket.h"
-#include "locate_host.h"
-#include "citadel_dirs.h"
-#include "ctdl_module.h"
-#include "serv_nntp.h"
-
-#ifndef __FreeBSD__
-extern long timezone;
-#endif
-
-//
-// Tests whether the supplied string is a valid newsgroup name
-// Returns true (nonzero) or false (0)
-//
-int is_valid_newsgroup_name(char *name) {
- char *ptr = name;
- int has_a_letter = 0;
- int num_dots = 0;
-
- if (!ptr) return(0);
- if (!strncasecmp(name, "ctdl.", 5)) return(0);
-
- while (*ptr != 0) {
-
- if (isalpha(ptr[0])) {
- has_a_letter = 1;
- }
-
- if (ptr[0] == '.') {
- ++num_dots;
- }
-
- if ( (isalnum(ptr[0]))
- || (ptr[0] == '.')
- || (ptr[0] == '+')
- || (ptr[0] == '-')
- ) {
- ++ptr;
- }
- else {
- return(0);
- }
- }
- return( (has_a_letter) && (num_dots >= 1) ) ;
-}
-
-
-//
-// Convert a Citadel room name to a valid newsgroup name
-//
-void room_to_newsgroup(char *target, char *source, size_t target_size) {
-
- if (!target) return;
- if (!source) return;
-
- if (is_valid_newsgroup_name(source)) {
- strncpy(target, source, target_size);
- return;
- }
-
- strcpy(target, "ctdl.");
- int len = 5;
- char *ptr = source;
- char ch;
-
- while (ch=*ptr++, ch!=0) {
- if (len >= target_size) return;
- if ( (isalnum(ch))
- || (ch == '.')
- || (ch == '-')
- ) {
- target[len++] = tolower(ch);
- target[len] = 0;
- }
- else {
- target[len++] = '+' ;
- sprintf(&target[len], "%02x", ch);
- len += 2;
- target[len] = 0;
- }
- }
-}
-
-
-//
-// Convert a newsgroup name to a Citadel room name.
-// This function recognizes names converted with room_to_newsgroup() and restores them with full fidelity.
-//
-void newsgroup_to_room(char *target, char *source, size_t target_size) {
-
- if (!target) return;
- if (!source) return;
-
- if (strncasecmp(source, "ctdl.", 5)) { // not a converted room name; pass through as-is
- strncpy(target, source, target_size);
- return;
- }
-
- target[0] = 0;
- int len = 0;
- char *ptr = &source[5];
- char ch;
-
- while (ch=*ptr++, ch!=0) {
- if (len >= target_size) return;
- if (ch == '+') {
- char hex[3];
- long digit;
- hex[0] = *ptr++;
- hex[1] = *ptr++;
- hex[2] = 0;
- digit = strtol(hex, NULL, 16);
- ch = (char)digit;
- }
- target[len++] = ch;
- target[len] = 0;
- }
-}
-
-
-//
-// Here's where our NNTP session begins its happy day.
-//
-void nntp_greeting(void) {
- strcpy(CC->cs_clientname, "NNTP session");
- CC->cs_flags |= CS_STEALTH;
-
- CC->session_specific_data = malloc(sizeof(citnntp));
- citnntp *nntpstate = (citnntp *) CC->session_specific_data;
- memset(nntpstate, 0, sizeof(citnntp));
-
- if (CC->nologin==1) {
- cprintf("451 Too many connections are already open; please try again later.\r\n");
- CC->kill_me = KILLME_MAX_SESSIONS_EXCEEDED;
- return;
- }
-
- // Display the standard greeting
- cprintf("200 %s NNTP Citadel server is not finished yet\r\n", CtdlGetConfigStr("c_fqdn"));
-}
-
-
-//
-// NNTPS is just like NNTP, except it goes crypto right away.
-//
-void nntps_greeting(void) {
- CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
-#ifdef HAVE_OPENSSL
- if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO; /* kill session if no crypto */
-#endif
- nntp_greeting();
-}
-
-
-//
-// implements the STARTTLS command
-//
-void nntp_starttls(void) {
- char ok_response[SIZ];
- char nosup_response[SIZ];
- char error_response[SIZ];
-
- sprintf(ok_response, "382 Begin TLS negotiation now\r\n");
- sprintf(nosup_response, "502 Can not initiate TLS negotiation\r\n");
- sprintf(error_response, "580 Internal error\r\n");
- CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
-}
-
-
-//
-// Implements the CAPABILITY command
-//
-void nntp_capabilities(void) {
- cprintf("101 Capability list:\r\n");
- cprintf("IMPLEMENTATION Citadel %d\r\n", REV_LEVEL);
- cprintf("VERSION 2\r\n");
- cprintf("READER\r\n");
- cprintf("MODE-READER\r\n");
- cprintf("LIST ACTIVE NEWSGROUPS\r\n");
- cprintf("OVER\r\n");
-#ifdef HAVE_OPENSSL
- cprintf("STARTTLS\r\n");
-#endif
- if (!CC->logged_in) {
- cprintf("AUTHINFO USER\r\n");
- }
- cprintf(".\r\n");
-}
-
-
-//
-// Implements the QUIT command
-//
-void nntp_quit(void) {
- cprintf("221 Goodbye...\r\n");
- CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
-}
-
-
-//
-// Implements the AUTHINFO USER command (RFC 4643)
-//
-void nntp_authinfo_user(const char *username) {
- int a = CtdlLoginExistingUser(username);
- switch (a) {
- case login_already_logged_in:
- cprintf("482 Already logged in\r\n");
- return;
- case login_too_many_users:
- cprintf("481 Too many users are already online (maximum is %d)\r\n", CtdlGetConfigInt("c_maxsessions"));
- return;
- case login_ok:
- cprintf("381 Password required for %s\r\n", CC->curr_user);
- return;
- case login_not_found:
- cprintf("481 %s not found\r\n", username);
- return;
- default:
- cprintf("502 Internal error\r\n");
- }
-}
-
-
-//
-// Implements the AUTHINFO PASS command (RFC 4643)
-//
-void nntp_authinfo_pass(const char *buf) {
- int a;
-
- a = CtdlTryPassword(buf, strlen(buf));
-
- switch (a) {
- case pass_already_logged_in:
- cprintf("482 Already logged in\r\n");
- return;
- case pass_no_user:
- cprintf("482 Authentication commands issued out of sequence\r\n");
- return;
- case pass_wrong_password:
- cprintf("481 Authentication failed\r\n");
- return;
- case pass_ok:
- cprintf("281 Authentication accepted\r\n");
- return;
- }
-}
-
-
-//
-// Implements the AUTHINFO extension (RFC 4643) in USER/PASS mode
-//
-void nntp_authinfo(const char *cmd) {
-
- if (!strncasecmp(cmd, "authinfo user ", 14)) {
- nntp_authinfo_user(&cmd[14]);
- }
-
- else if (!strncasecmp(cmd, "authinfo pass ", 14)) {
- nntp_authinfo_pass(&cmd[14]);
- }
-
- else {
- cprintf("502 command unavailable\r\n");
- }
-}
-
-
-//
-// Utility function to fetch the current list of message numbers in a room
-//
-struct nntp_msglist nntp_fetch_msglist(struct ctdlroom *qrbuf) {
- struct nntp_msglist nm;
- struct cdbdata *cdbfr;
-
- cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
- if (cdbfr != NULL) {
- nm.msgnums = (long*)cdbfr->ptr;
- cdbfr->ptr = NULL;
- nm.num_msgs = cdbfr->len / sizeof(long);
- cdbfr->len = 0;
- cdb_free(cdbfr);
- } else {
- nm.num_msgs = 0;
- nm.msgnums = NULL;
- }
- return(nm);
-}
-
-
-//
-// Output a room name (newsgroup name) in formats required for LIST and NEWGROUPS command
-//
-void output_roomname_in_list_format(struct ctdlroom *qrbuf, int which_format, char *wildmat_pattern) {
- char n_name[1024];
- struct nntp_msglist nm;
- long low_water_mark = 0;
- long high_water_mark = 0;
-
- room_to_newsgroup(n_name, qrbuf->QRname, sizeof n_name);
-
- if ((wildmat_pattern != NULL) && (!IsEmptyStr(wildmat_pattern))) {
- if (!wildmat(n_name, wildmat_pattern)) {
- return;
- }
- }
-
- nm = nntp_fetch_msglist(qrbuf);
- if ((nm.num_msgs > 0) && (nm.msgnums != NULL)) {
- low_water_mark = nm.msgnums[0];
- high_water_mark = nm.msgnums[nm.num_msgs - 1];
- }
-
- // Only the mandatory formats are supported
- switch(which_format) {
- case NNTP_LIST_ACTIVE:
- // FIXME we have hardcoded "n" for "no posting allowed" -- fix when we add posting
- cprintf("%s %ld %ld n\r\n", n_name, high_water_mark, low_water_mark);
- break;
- case NNTP_LIST_NEWSGROUPS:
- cprintf("%s %s\r\n", n_name, qrbuf->QRname);
- break;
- }
-
- if (nm.msgnums != NULL) {
- free(nm.msgnums);
- }
-}
-
-
-//
-// Called once per room by nntp_newgroups() to qualify and possibly output a single room
-//
-void nntp_newgroups_backend(struct ctdlroom *qrbuf, void *data) {
- int ra;
- int view;
- time_t thetime = *(time_t *)data;
-
- CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
-
- /*
- * The "created after <date/time>" heuristics depend on the happy coincidence
- * that for a very long time we have used a unix timestamp as the room record's
- * generation number (QRgen). When this module is merged into the master
- * source tree we should rename QRgen to QR_create_time or something like that.
- */
-
- if (ra & UA_KNOWN) {
- if (qrbuf->QRgen >= thetime) {
- output_roomname_in_list_format(qrbuf, NNTP_LIST_ACTIVE, NULL);
- }
- }
-}
-
-
-//
-// Implements the NEWGROUPS command
-//
-void nntp_newgroups(const char *cmd) {
- if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
- char stringy_date[16];
- char stringy_time[16];
- char stringy_gmt[16];
- struct tm tm;
- time_t thetime;
-
- extract_token(stringy_date, cmd, 1, ' ', sizeof stringy_date);
- extract_token(stringy_time, cmd, 2, ' ', sizeof stringy_time);
- extract_token(stringy_gmt, cmd, 3, ' ', sizeof stringy_gmt);
-
- memset(&tm, 0, sizeof tm);
- if (strlen(stringy_date) == 6) {
- sscanf(stringy_date, "%2d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday);
- tm.tm_year += 100;
- }
- else {
- sscanf(stringy_date, "%4d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday);
- tm.tm_year -= 1900;
- }
- tm.tm_mon -= 1; // tm_mon is zero based (0=January)
- tm.tm_isdst = (-1); // let the C library figure out whether DST is in effect
- sscanf(stringy_time, "%2d%2d%2d", &tm.tm_hour, &tm.tm_min ,&tm.tm_sec);
- thetime = mktime(&tm);
- if (!strcasecmp(stringy_gmt, "GMT")) {
- tzset();
-#ifdef __FreeBSD__
- thetime += &tm.tm_gmtoff;
-#else
- thetime += timezone;
-#endif
- }
-
-
- cprintf("231 list of new newsgroups follows\r\n");
- CtdlGetUser(&CC->user, CC->curr_user);
- CtdlForEachRoom(nntp_newgroups_backend, &thetime);
- cprintf(".\r\n");
-}
-
-
-//
-// Called once per room by nntp_list() to qualify and possibly output a single room
-//
-void nntp_list_backend(struct ctdlroom *qrbuf, void *data) {
- int ra;
- int view;
- struct nntp_list_data *nld = (struct nntp_list_data *)data;
-
- CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
- if (ra & UA_KNOWN) {
- output_roomname_in_list_format(qrbuf, nld->list_format, nld->wildmat_pattern);
- }
-}
-
-
-//
-// Implements the LIST commands
-//
-void nntp_list(const char *cmd) {
- if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
- char list_format[64];
- char wildmat_pattern[1024];
- struct nntp_list_data nld;
-
- extract_token(list_format, cmd, 1, ' ', sizeof list_format);
- extract_token(wildmat_pattern, cmd, 2, ' ', sizeof wildmat_pattern);
-
- if (strlen(wildmat_pattern) > 0) {
- nld.wildmat_pattern = wildmat_pattern;
- }
- else {
- nld.wildmat_pattern = NULL;
- }
-
- if ( (strlen(cmd) < 6) || (!strcasecmp(list_format, "ACTIVE")) ) {
- nld.list_format = NNTP_LIST_ACTIVE;
- }
- else if (!strcasecmp(list_format, "NEWSGROUPS")) {
- nld.list_format = NNTP_LIST_NEWSGROUPS;
- }
- else if (!strcasecmp(list_format, "OVERVIEW.FMT")) {
- nld.list_format = NNTP_LIST_OVERVIEW_FMT;
- }
- else {
- cprintf("501 syntax error , unsupported list format\r\n");
- return;
- }
-
- // OVERVIEW.FMT delivers a completely different type of data than all of the
- // other LIST commands. It's a stupid place to put it. But that's how it's
- // written into RFC3977, so we have to handle it here.
- if (nld.list_format == NNTP_LIST_OVERVIEW_FMT) {
- cprintf("215 Order of fields in overview database.\r\n");
- cprintf("Subject:\r\n");
- cprintf("From:\r\n");
- cprintf("Date:\r\n");
- cprintf("Message-ID:\r\n");
- cprintf("References:\r\n");
- cprintf("Bytes:\r\n");
- cprintf("Lines:\r\n");
- cprintf(".\r\n");
- return;
- }
-
- cprintf("215 list of newsgroups follows\r\n");
- CtdlGetUser(&CC->user, CC->curr_user);
- CtdlForEachRoom(nntp_list_backend, &nld);
- cprintf(".\r\n");
-}
-
-
-//
-// Implement HELP command.
-//
-void nntp_help(void) {
- cprintf("100 This is the Citadel NNTP service.\r\n");
- cprintf("RTFM http://www.ietf.org/rfc/rfc3977.txt\r\n");
- cprintf(".\r\n");
-}
-
-
-//
-// Implement DATE command.
-//
-void nntp_date(void) {
- time_t now;
- struct tm nowLocal;
- struct tm nowUtc;
- char tsFromUtc[32];
-
- now = time(NULL);
- localtime_r(&now, &nowLocal);
- gmtime_r(&now, &nowUtc);
-
- strftime(tsFromUtc, sizeof(tsFromUtc), "%Y%m%d%H%M%S", &nowUtc);
-
- cprintf("111 %s\r\n", tsFromUtc);
-}
-
-
-//
-// back end for the LISTGROUP command , called for each message number
-//
-void nntp_listgroup_backend(long msgnum, void *userdata) {
-
- struct listgroup_range *lr = (struct listgroup_range *)userdata;
-
- // check range if supplied
- if (msgnum < lr->lo) return;
- if ((lr->hi != 0) && (msgnum > lr->hi)) return;
-
- cprintf("%ld\r\n", msgnum);
-}
-
-
-//
-// Implements the GROUP and LISTGROUP commands
-//
-void nntp_group(const char *cmd) {
- if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
- citnntp *nntpstate = (citnntp *) CC->session_specific_data;
- char verb[16];
- char requested_group[1024];
- char message_range[256];
- char range_lo[256];
- char range_hi[256];
- char requested_room[ROOMNAMELEN];
- char augmented_roomname[ROOMNAMELEN];
- int c = 0;
- int ok = 0;
- int ra = 0;
- struct ctdlroom QRscratch;
- int msgs, new;
- long oldest,newest;
- struct listgroup_range lr;
-
- extract_token(verb, cmd, 0, ' ', sizeof verb);
- extract_token(requested_group, cmd, 1, ' ', sizeof requested_group);
- extract_token(message_range, cmd, 2, ' ', sizeof message_range);
- extract_token(range_lo, message_range, 0, '-', sizeof range_lo);
- extract_token(range_hi, message_range, 1, '-', sizeof range_hi);
- lr.lo = atoi(range_lo);
- lr.hi = atoi(range_hi);
-
- /* In LISTGROUP mode we can specify an empty name for 'currently selected' */
- if ((!strcasecmp(verb, "LISTGROUP")) && (IsEmptyStr(requested_group))) {
- room_to_newsgroup(requested_group, CC->room.QRname, sizeof requested_group);
- }
-
- /* First try a regular match */
- newsgroup_to_room(requested_room, requested_group, sizeof requested_room);
- c = CtdlGetRoom(&QRscratch, requested_room);
-
- /* Then try a mailbox name match */
- if (c != 0) {
- CtdlMailboxName(augmented_roomname, sizeof augmented_roomname, &CC->user, requested_room);
- c = CtdlGetRoom(&QRscratch, augmented_roomname);
- if (c == 0) {
- safestrncpy(requested_room, augmented_roomname, sizeof(requested_room));
- }
- }
-
- /* If the room exists, check security/access */
- if (c == 0) {
- /* See if there is an existing user/room relationship */
- CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL);
-
- /* normal clients have to pass through security */
- if (ra & UA_KNOWN) {
- ok = 1;
- }
- }
-
- /* Fail here if no such room */
- if (!ok) {
- cprintf("411 no such newsgroup\r\n");
- return;
- }
-
-
- /*
- * CtdlUserGoto() formally takes us to the desired room, happily returning
- * the number of messages and number of new messages.
- */
- memcpy(&CC->room, &QRscratch, sizeof(struct ctdlroom));
- CtdlUserGoto(NULL, 0, 0, &msgs, &new, &oldest, &newest);
- cprintf("211 %d %ld %ld %s\r\n", msgs, oldest, newest, requested_group);
-
- // If this is a GROUP command, set the "current article number" to zero, and then stop here.
- if (!strcasecmp(verb, "GROUP")) {
- nntpstate->current_article_number = oldest;
- return;
- }
-
- // If we get to this point we are running a LISTGROUP command. Fetch those message numbers.
- CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, nntp_listgroup_backend, &lr);
- cprintf(".\r\n");
-}
-
-
-//
-// Implements the MODE command
-//
-void nntp_mode(const char *cmd) {
-
- char which_mode[16];
-
- extract_token(which_mode, cmd, 1, ' ', sizeof which_mode);
-
- if (!strcasecmp(which_mode, "reader")) {
- // FIXME implement posting and change to 200
- cprintf("201 Reader mode activated\r\n");
- }
- else {
- cprintf("501 unknown mode\r\n");
- }
-}
-
-
-//
-// Implements the ARTICLE, HEAD, BODY, and STAT commands.
-// (These commands all accept the same parameters; they differ only in how they output the retrieved message.)
-//
-void nntp_article(const char *cmd) {
- if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
- citnntp *nntpstate = (citnntp *) CC->session_specific_data;
- char which_command[16];
- int acmd = 0;
- char requested_article[256];
- long requested_msgnum = 0;
- char *lb, *rb = NULL;
- int must_change_currently_selected_article = 0;
-
- // We're going to store one of these values in the variable 'acmd' so that
- // we can quickly check later which version of the output we want.
- enum {
- ARTICLE,
- HEAD,
- BODY,
- STAT
- };
-
- extract_token(which_command, cmd, 0, ' ', sizeof which_command);
-
- if (!strcasecmp(which_command, "article")) {
- acmd = ARTICLE;
- }
- else if (!strcasecmp(which_command, "head")) {
- acmd = HEAD;
- }
- else if (!strcasecmp(which_command, "body")) {
- acmd = BODY;
- }
- else if (!strcasecmp(which_command, "stat")) {
- acmd = STAT;
- }
- else {
- cprintf("500 I'm afraid I can't do that.\r\n");
- return;
- }
-
- // Which NNTP command was issued, determines whether we will fetch headers, body, or both.
- int headers_only = HEADERS_ALL;
- if (acmd == HEAD) headers_only = HEADERS_FAST;
- else if (acmd == BODY) headers_only = HEADERS_NONE;
- else if (acmd == STAT) headers_only = HEADERS_FAST;
-
- // now figure out what the client is asking for.
- extract_token(requested_article, cmd, 1, ' ', sizeof requested_article);
- lb = strchr(requested_article, '<');
- rb = strchr(requested_article, '>');
- requested_msgnum = atol(requested_article);
-
- // If no article number or message-id is specified, the client wants the "currently selected article"
- if (IsEmptyStr(requested_article)) {
- if (nntpstate->current_article_number < 1) {
- cprintf("420 No current article selected\r\n");
- return;
- }
- requested_msgnum = nntpstate->current_article_number;
- must_change_currently_selected_article = 1;
- // got it -- now fall through and keep going
- }
-
- // If the requested article is numeric, it maps directly to a message number. Good.
- else if (requested_msgnum > 0) {
- must_change_currently_selected_article = 1;
- // good -- fall through and keep going
- }
-
- // If the requested article has angle brackets, the client wants a specific message-id.
- // We don't know how to do that yet.
- else if ( (lb != NULL) && (rb != NULL) && (lb < rb) ) {
- must_change_currently_selected_article = 0;
- cprintf("500 I don't know how to fetch by message-id yet.\r\n"); // FIXME
- return;
- }
-
- // Anything else is noncompliant gobbledygook and should die in a car fire.
- else {
- must_change_currently_selected_article = 0;
- cprintf("500 syntax error\r\n");
- return;
- }
-
- // At this point we know the message number of the "article" being requested.
- // We have an awesome API call that does all the heavy lifting for us.
- char *fetched_message_id = NULL;
- CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
- int fetch = CtdlOutputMsg(requested_msgnum,
- MT_RFC822, // output in RFC822 format ... sort of
- headers_only, // headers, body, or both?
- 0, // don't do Citadel protocol responses
- 1, // CRLF newlines
- NULL, // teh whole thing, not just a section
- 0, // no flags yet ... maybe new ones for Path: etc ?
- NULL,
- NULL,
- &fetched_message_id // extract the message ID from the message as we go...
- );
- StrBuf *msgtext = CC->redirect_buffer;
- CC->redirect_buffer = NULL;
-
- if (fetch != om_ok) {
- cprintf("423 no article with that number\r\n");
- FreeStrBuf(&msgtext);
- return;
- }
-
- // RFC3977 6.2.1.2 specifes conditions under which the "currently selected article"
- // MUST or MUST NOT be set to the message we just referenced.
- if (must_change_currently_selected_article) {
- nntpstate->current_article_number = requested_msgnum;
- }
-
- // Now give the client what it asked for.
- if (acmd == ARTICLE) {
- cprintf("220 %ld <%s>\r\n", requested_msgnum, fetched_message_id);
- }
- if (acmd == HEAD) {
- cprintf("221 %ld <%s>\r\n", requested_msgnum, fetched_message_id);
- }
- if (acmd == BODY) {
- cprintf("222 %ld <%s>\r\n", requested_msgnum, fetched_message_id);
- }
- if (acmd == STAT) {
- cprintf("223 %ld <%s>\r\n", requested_msgnum, fetched_message_id);
- FreeStrBuf(&msgtext);
- return;
- }
-
- client_write(SKEY(msgtext));
- cprintf(".\r\n"); // this protocol uses a dot terminator
- FreeStrBuf(&msgtext);
- if (fetched_message_id) free(fetched_message_id);
-}
-
-
-//
-// Utility function for nntp_last_next() that turns a msgnum into a message ID.
-// The memory for the returned string is pwnz0red by the caller.
-//
-char *message_id_from_msgnum(long msgnum) {
- char *fetched_message_id = NULL;
- CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
- CtdlOutputMsg(msgnum,
- MT_RFC822, // output in RFC822 format ... sort of
- HEADERS_FAST, // headers, body, or both?
- 0, // don't do Citadel protocol responses
- 1, // CRLF newlines
- NULL, // teh whole thing, not just a section
- 0, // no flags yet ... maybe new ones for Path: etc ?
- NULL,
- NULL,
- &fetched_message_id // extract the message ID from the message as we go...
- );
- StrBuf *msgtext = CC->redirect_buffer;
- CC->redirect_buffer = NULL;
-
- FreeStrBuf(&msgtext);
- return(fetched_message_id);
-}
-
-
-//
-// The LAST and NEXT commands are so similar that they are handled by a single function.
-//
-void nntp_last_next(const char *cmd) {
- if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
- citnntp *nntpstate = (citnntp *) CC->session_specific_data;
- char which_command[16];
- int acmd = 0;
-
- // We're going to store one of these values in the variable 'acmd' so that
- // we can quickly check later which version of the output we want.
- enum {
- NNTP_LAST,
- NNTP_NEXT
- };
-
- extract_token(which_command, cmd, 0, ' ', sizeof which_command);
-
- if (!strcasecmp(which_command, "last")) {
- acmd = NNTP_LAST;
- }
- else if (!strcasecmp(which_command, "next")) {
- acmd = NNTP_NEXT;
- }
- else {
- cprintf("500 I'm afraid I can't do that.\r\n");
- return;
- }
-
- // ok, here we go ... fetch the msglist so we can figure out our place in the universe
- struct nntp_msglist nm;
- int i = 0;
- long selected_msgnum = 0;
- char *message_id = NULL;
-
- nm = nntp_fetch_msglist(&CC->room);
- if ((nm.num_msgs < 0) || (nm.msgnums == NULL)) {
- cprintf("500 something bad happened\r\n");
- return;
- }
-
- if ( (acmd == NNTP_LAST) && (nm.num_msgs == 0) ) {
- cprintf("422 no previous article in this group\r\n"); // nothing here
- }
-
- else if ( (acmd == NNTP_LAST) && (nntpstate->current_article_number <= nm.msgnums[0]) ) {
- cprintf("422 no previous article in this group\r\n"); // already at the beginning
- }
-
- else if (acmd == NNTP_LAST) {
- for (i=0; ((i<nm.num_msgs)&&(selected_msgnum<=0)); ++i) {
- if ( (nm.msgnums[i] >= nntpstate->current_article_number) && (i > 0) ) {
- selected_msgnum = nm.msgnums[i-1];
- }
- }
- if (selected_msgnum > 0) {
- nntpstate->current_article_number = selected_msgnum;
- message_id = message_id_from_msgnum(nntpstate->current_article_number);
- cprintf("223 %ld <%s>\r\n", nntpstate->current_article_number, message_id);
- if (message_id) free(message_id);
- }
- else {
- cprintf("422 no previous article in this group\r\n");
- }
- }
-
- else if ( (acmd == NNTP_NEXT) && (nm.num_msgs == 0) ) {
- cprintf("421 no next article in this group\r\n"); // nothing here
- }
-
- else if ( (acmd == NNTP_NEXT) && (nntpstate->current_article_number >= nm.msgnums[nm.num_msgs-1]) ) {
- cprintf("421 no next article in this group\r\n"); // already at the end
- }
-
- else if (acmd == NNTP_NEXT) {
- for (i=0; ((i<nm.num_msgs)&&(selected_msgnum<=0)); ++i) {
- if (nm.msgnums[i] > nntpstate->current_article_number) {
- selected_msgnum = nm.msgnums[i];
- }
- }
- if (selected_msgnum > 0) {
- nntpstate->current_article_number = selected_msgnum;
- message_id = message_id_from_msgnum(nntpstate->current_article_number);
- cprintf("223 %ld <%s>\r\n", nntpstate->current_article_number, message_id);
- if (message_id) free(message_id);
- }
- else {
- cprintf("421 no next article in this group\r\n");
- }
- }
-
- // should never get here
- else {
- cprintf("500 internal error\r\n");
- }
-
- if (nm.msgnums != NULL) {
- free(nm.msgnums);
- }
-
-}
-
-
-//
-// back end for the XOVER command , called for each message number
-//
-void nntp_xover_backend(long msgnum, void *userdata) {
-
- struct listgroup_range *lr = (struct listgroup_range *)userdata;
-
- // check range if supplied
- if (msgnum < lr->lo) return;
- if ((lr->hi != 0) && (msgnum > lr->hi)) return;
-
- struct CtdlMessage *msg = CtdlFetchMessage(msgnum, 0);
- if (msg == NULL) {
- return;
- }
-
- // Teh RFC says we need:
- // -------------------------
- // Subject header content
- // From header content
- // Date header content
- // Message-ID header content
- // References header content
- // :bytes metadata item
- // :lines metadata item
-
- time_t msgtime = atol(msg->cm_fields[eTimestamp]);
- char strtimebuf[26];
- ctime_r(&msgtime, strtimebuf);
-
- // here we go -- print the line o'data
- cprintf("%ld\t%s\t%s <%s>\t%s\t%s\t%s\t100\t10\r\n",
- msgnum,
- msg->cm_fields[eMsgSubject],
- msg->cm_fields[eAuthor],
- msg->cm_fields[erFc822Addr],
- strtimebuf,
- msg->cm_fields[emessageId],
- msg->cm_fields[eWeferences]
- );
-
- CM_Free(msg);
-}
-
-
-//
-//
-// XOVER is used by some clients, even if we don't offer it
-//
-void nntp_xover(const char *cmd) {
- if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
-
- citnntp *nntpstate = (citnntp *) CC->session_specific_data;
- char range[256];
- struct listgroup_range lr;
-
- extract_token(range, cmd, 1, ' ', sizeof range);
- lr.lo = atol(range);
- if (lr.lo <= 0) {
- lr.lo = nntpstate->current_article_number;
- lr.hi = nntpstate->current_article_number;
- }
- else {
- char *dash = strchr(range, '-');
- if (dash != NULL) {
- ++dash;
- lr.hi = atol(dash);
- if (lr.hi == 0) {
- lr.hi = LONG_MAX;
- }
- if (lr.hi < lr.lo) {
- lr.hi = lr.lo;
- }
- }
- else {
- lr.hi = lr.lo;
- }
- }
-
- cprintf("224 Overview information follows\r\n");
- CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, nntp_xover_backend, &lr);
- cprintf(".\r\n");
-}
-
-
-//
-// Main command loop for NNTP server sessions.
-//
-void nntp_command_loop(void) {
- StrBuf *Cmd = NewStrBuf();
- char cmdname[16];
-
- time(&CC->lastcmd);
- if (CtdlClientGetLine(Cmd) < 1) {
- syslog(LOG_CRIT, "NNTP: client disconnected: ending session.\n");
- CC->kill_me = KILLME_CLIENT_DISCONNECTED;
- FreeStrBuf(&Cmd);
- return;
- }
- syslog(LOG_DEBUG, "NNTP: %s\n", ((!strncasecmp(ChrPtr(Cmd), "AUTHINFO", 8)) ? "AUTHINFO ..." : ChrPtr(Cmd)));
- extract_token(cmdname, ChrPtr(Cmd), 0, ' ', sizeof cmdname);
-
- // Rumpelstiltskin lookups are *awesome*
-
- if (!strcasecmp(cmdname, "quit")) {
- nntp_quit();
- }
-
- else if (!strcasecmp(cmdname, "help")) {
- nntp_help();
- }
-
- else if (!strcasecmp(cmdname, "date")) {
- nntp_date();
- }
-
- else if (!strcasecmp(cmdname, "capabilities")) {
- nntp_capabilities();
- }
-
- else if (!strcasecmp(cmdname, "starttls")) {
- nntp_starttls();
- }
-
- else if (!strcasecmp(cmdname, "authinfo")) {
- nntp_authinfo(ChrPtr(Cmd));
- }
-
- else if (!strcasecmp(cmdname, "newgroups")) {
- nntp_newgroups(ChrPtr(Cmd));
- }
-
- else if (!strcasecmp(cmdname, "list")) {
- nntp_list(ChrPtr(Cmd));
- }
-
- else if (!strcasecmp(cmdname, "group")) {
- nntp_group(ChrPtr(Cmd));
- }
-
- else if (!strcasecmp(cmdname, "listgroup")) {
- nntp_group(ChrPtr(Cmd));
- }
-
- else if (!strcasecmp(cmdname, "mode")) {
- nntp_mode(ChrPtr(Cmd));
- }
-
- else if (
- (!strcasecmp(cmdname, "article"))
- || (!strcasecmp(cmdname, "head"))
- || (!strcasecmp(cmdname, "body"))
- || (!strcasecmp(cmdname, "stat"))
- )
- {
- nntp_article(ChrPtr(Cmd));
- }
-
- else if (
- (!strcasecmp(cmdname, "last"))
- || (!strcasecmp(cmdname, "next"))
- )
- {
- nntp_last_next(ChrPtr(Cmd));
- }
-
- else if (
- (!strcasecmp(cmdname, "xover"))
- || (!strcasecmp(cmdname, "over"))
- )
- {
- nntp_xover(ChrPtr(Cmd));
- }
-
- else {
- cprintf("500 I'm afraid I can't do that.\r\n");
- }
-
- FreeStrBuf(&Cmd);
-}
-
-
-// ****************************************************************************
-// MODULE INITIALIZATION STUFF
-// ****************************************************************************
-
-
-//
-// This cleanup function blows away the temporary memory used by
-// the NNTP server.
-//
-void nntp_cleanup_function(void) {
- /* Don't do this stuff if this is not an NNTP session! */
- if (CC->h_command_function != nntp_command_loop) return;
-
- syslog(LOG_DEBUG, "Performing NNTP cleanup hook\n");
- citnntp *nntpstate = (citnntp *) CC->session_specific_data;
- if (nntpstate != NULL) {
- free(nntpstate);
- nntpstate = NULL;
- }
-}
-
-const char *CitadelServiceNNTP="NNTP";
-const char *CitadelServiceNNTPS="NNTPS";
-
-CTDL_MODULE_INIT(nntp)
-{
- if (!threading)
- {
- CtdlRegisterServiceHook(CtdlGetConfigInt("c_nntp_port"),
- NULL,
- nntp_greeting,
- nntp_command_loop,
- NULL,
- CitadelServiceNNTP);
-
-#ifdef HAVE_OPENSSL
- CtdlRegisterServiceHook(CtdlGetConfigInt("c_nntps_port"),
- NULL,
- nntps_greeting,
- nntp_command_loop,
- NULL,
- CitadelServiceNNTPS);
-#endif
-
- CtdlRegisterSessionHook(nntp_cleanup_function, EVT_STOP, PRIO_STOP + 250);
- }
-
- /* return our module name for the log */
- return "nntp";
-}
+++ /dev/null
-//
-// Header file for NNTP server module
-//
-// Copyright (c) 2014 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.
-//
-
-
-// data returned by a message list fetch
-struct nntp_msglist {
- int num_msgs;
- long *msgnums;
-};
-
-
-// data passed by the LIST commands to its helper function
-struct nntp_list_data {
- int list_format;
- char *wildmat_pattern;
-};
-
-
-//
-// data passed between nntp_listgroup() and nntp_listgroup_backend()
-//
-struct listgroup_range {
- long lo;
- long hi;
-};
-
-
-typedef struct _citnntp { // Information about the current session
- long current_article_number;
-} citnntp;
-
-
-//
-// Various output formats for the LIST commands
-//
-enum {
- NNTP_LIST_ACTIVE,
- NNTP_LIST_ACTIVE_TIMES,
- NNTP_LIST_DISTRIB_PATS,
- NNTP_LIST_HEADERS,
- NNTP_LIST_NEWSGROUPS,
- NNTP_LIST_OVERVIEW_FMT
-};
-
-
-int wildmat(const char *text, const char *p);
-
+++ /dev/null
-/* wildmat.h - NNTP wildmat processing functions
- *
- * Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in
- * the documentation and/or other materials provided with the
- * distribution.
- *
- * 3. The name "Carnegie Mellon University" must not be used to
- * endorse or promote products derived from this software without
- * prior written permission. For permission or any legal
- * details, please contact
- * Carnegie Mellon University
- * Center for Technology Transfer and Enterprise Creation
- * 4615 Forbes Avenue
- * Suite 302
- * Pittsburgh, PA 15213
- * (412) 268-7393, fax: (412) 268-7395
- * innovation@andrew.cmu.edu
- *
- * 4. Redistributions of any form whatsoever must retain the following
- * acknowledgment:
- * "This product includes software developed by Computing Services
- * at Carnegie Mellon University (http://www.cmu.edu/computing/)."
- *
- * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
- * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
- * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
- * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
- * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- *
- * $Id: wildmat.c,v 1.4 2010/01/06 17:01:47 murch Exp $
- */
-
-/*
-**
-** Do shell-style pattern matching for ?, \, [], and * characters.
-** Might not be robust in face of malformed patterns; e.g., "foo[a-"
-** could cause a segmentation violation. It is 8bit clean.
-**
-** Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986.
-** Rich $alz is now <rsalz@osf.org>.
-** April, 1991: Replaced mutually-recursive calls with in-line code
-** for the star character.
-**
-** Special thanks to Lars Mathiesen <thorinn@diku.dk> for the ABORT code.
-** This can greatly speed up failing wildcard patterns. For example:
-** pattern: -*-*-*-*-*-*-12-*-*-*-m-*-*-*
-** text 1: -adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1
-** text 2: -adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1
-** Text 1 matches with 51 calls, while text 2 fails with 54 calls. Without
-** the ABORT code, it takes 22310 calls to fail. Ugh. The following
-** explanation is from Lars:
-** The precondition that must be fulfilled is that DoMatch will consume
-** at least one character in text. This is true if *p is neither '*' nor
-** '\0'.) The last return has ABORT instead of FALSE to avoid quadratic
-** behaviour in cases like pattern "*a*b*c*d" with text "abcxxxxx". With
-** FALSE, each star-loop has to run to the end of the text; with ABORT
-** only the last one does.
-**
-** Once the control of one instance of DoMatch enters the star-loop, that
-** instance will return either TRUE or ABORT, and any calling instance
-** will therefore return immediately after (without calling recursively
-** again). In effect, only one star-loop is ever active. It would be
-** possible to modify the code to maintain this context explicitly,
-** eliminating all recursive calls at the cost of some complication and
-** loss of clarity (and the ABORT stuff seems to be unclear enough by
-** itself). I think it would be unwise to try to get this into a
-** released version unless you have a good test data base to try it out
-** on.
-*/
-#include <stdio.h>
-#include <sys/types.h>
-
-
-
-#define TRUE 1
-#define FALSE 0
-#define ABORT -1
-
-
- /* What character marks an inverted character class? */
-#define NEGATE_CLASS '^'
- /* Is "*" a common pattern? */
-#define OPTIMIZE_JUST_STAR
- /* Do tar(1) matching rules, which ignore a trailing slash? */
-#undef MATCH_TAR_PATTERN
-
-
-/*
-** Match text and p, return TRUE, FALSE, or ABORT.
-*/
-static int DoMatch(const char *text, const char *p)
-{
- int last;
- int matched;
- int reverse;
-
- for ( ; *p; text++, p++) {
- if (*text == '\0' && *p != '*')
- return ABORT;
- switch (*p) {
- case '\\':
- /* Literal match with following character. */
- p++;
- /* FALLTHROUGH */
- default:
- if (*text != *p)
- return FALSE;
- continue;
- case '?':
- /* Match anything. */
- continue;
- case '*':
- while (*++p == '*')
- /* Consecutive stars act just like one. */
- continue;
- if (*p == '\0')
- /* Trailing star matches everything. */
- return TRUE;
- while (*text)
- if ((matched = DoMatch(text++, p)) != FALSE)
- return matched;
- return ABORT;
- case '[':
- reverse = p[1] == NEGATE_CLASS ? TRUE : FALSE;
- if (reverse)
- /* Inverted character class. */
- p++;
- matched = FALSE;
- if (p[1] == ']' || p[1] == '-')
- if (*++p == *text)
- matched = TRUE;
- for (last = *p; *++p && *p != ']'; last = *p)
- /* This next line requires a good C compiler. */
- if (*p == '-' && p[1] != ']'
- ? *text <= *++p && *text >= last : *text == *p)
- matched = TRUE;
- if (matched == reverse)
- return FALSE;
- continue;
- }
- }
-
-#ifdef MATCH_TAR_PATTERN
- if (*text == '/')
- return TRUE;
-#endif /* MATCH_TAR_ATTERN */
- return *text == '\0';
-}
-
-
-/*
-** User-level routine. Returns TRUE or FALSE.
-*/
-int wildmat(const char *text, const char *p)
-{
-#ifdef OPTIMIZE_JUST_STAR
- if (p[0] == '*' && p[1] == '\0')
- return TRUE;
-#endif /* OPTIMIZE_JUST_STAR */
- return DoMatch(text, p) == TRUE;
-}
+++ /dev/null
-/*
- * Handles functions related to yellow sticky notes.
- *
- * Copyright (c) 2007-2012 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.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "ctdl_module.h"
-
-
-/*
- * Callback function for serv_notes_beforesave() hunts for a vNote in the MIME structure
- */
-void notes_extract_vnote(char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length,
- char *encoding, char *cbid, void *cbuserdata)
-{
- struct vnote **v = (struct vnote **) cbuserdata;
-
- if (!strcasecmp(cbtype, "text/vnote")) {
-
- syslog(LOG_DEBUG, "Part %s contains a vNote! Loading...\n", partnum);
- if (*v != NULL) {
- vnote_free(*v);
- }
- *v = vnote_new_from_str(content);
- }
-}
-
-
-/*
- * Before-save hook searches for two different types of notes (legacy Kolab/Aethera notes
- * and modern vNote format notes) and does its best to learn the subject (summary)
- * and EUID (uid) of the note for Citadel's own nefarious purposes.
- */
-int serv_notes_beforesave(struct CtdlMessage *msg, struct recptypes *recp)
-{
- char *p;
- int a, i;
- char uuid[512];
- struct vnote *v = NULL;
-
- /* First determine if this room has the "notes" view set */
-
- if (CC->room.QRdefaultview != VIEW_NOTES) {
- return(0); /* not notes; do nothing */
- }
-
- /* It must be an RFC822 message! */
- if (msg->cm_format_type != 4) {
- return(0); /* You tried to save a non-RFC822 message! */
- }
-
- /*
- * If we are in a "notes" view room, and the client has sent an RFC822
- * message containing an X-KOrg-Note-Id: field (Aethera does this, as
- * do some Kolab clients) then set both the Subject and the Exclusive ID
- * of the message to that. It's going to be a UUID so we want to replace
- * any existing message containing that UUID.
- */
- strcpy(uuid, "");
- p = msg->cm_fields[eMesageText];
- a = msg->cm_lengths[eMesageText];
- while (--a > 0) {
- if (!strncasecmp(p, "X-KOrg-Note-Id: ", 16)) { /* Found it */
- safestrncpy(uuid, p + 16, sizeof(uuid));
- for (i = 0; uuid[i]; ++i) {
- if ( (uuid[i] == '\r') || (uuid[i] == '\n') ) {
- uuid[i] = 0;
- break;
- }
- }
-
- syslog(LOG_DEBUG, "UUID of note is: %s\n", uuid);
- if (!IsEmptyStr(uuid)) {
- CM_SetField(msg, eExclusiveID, uuid, strlen(uuid));
-
- CM_CopyField(msg, eMsgSubject, eExclusiveID);
- }
- }
- p++;
- }
-
- /* Modern clients are using vNote format. Check for one... */
-
- mime_parser(CM_RANGE(msg, eMesageText),
- *notes_extract_vnote,
- NULL, NULL,
- &v, /* user data ptr - put the vnote here */
- 0
- );
-
- if (v == NULL) return(0); /* no vNotes were found in this message */
-
- /* Set the message EUID to the vNote UID */
-
- if ((v->uid) && (!IsEmptyStr(v->uid))) {
- syslog(LOG_DEBUG, "UID of vNote is: %s\n", v->uid);
- CM_SetField(msg, eExclusiveID, v->uid, strlen(v->uid));
- }
-
- /* Set the message Subject to the vNote Summary */
-
- if ((v->summary) && (!IsEmptyStr(v->summary))) {
- CM_SetField(msg, eMsgSubject, v->summary, strlen(v->summary));
-
- if (msg->cm_lengths[eMsgSubject] > 72) {
- strcpy(&msg->cm_fields[eMsgSubject][68], "...");
- CM_CutFieldAt(msg, eMsgSubject, 72);
- }
- }
-
- vnote_free(v);
-
- return(0);
-}
-
-
-CTDL_MODULE_INIT(notes)
-{
- if (!threading)
- {
- CtdlRegisterMessageHook(serv_notes_beforesave, EVT_BEFORESAVE);
- }
-
- /* return our module name for the log */
- return "notes";
-}
+++ /dev/null
-/*
- * This is an implementation of OpenID 2.0 relying party support in stateless mode.
- *
- * Copyright (c) 2007-2020 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 as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <curl/curl.h>
-#include <expat.h>
-#include "ctdl_module.h"
-#include "config.h"
-#include "citserver.h"
-#include "user_ops.h"
-
-typedef struct _ctdl_openid {
- StrBuf *op_url; /* OpenID Provider Endpoint URL */
- StrBuf *claimed_id; /* Claimed Identifier */
- int verified;
- HashList *sreg_keys;
-} ctdl_openid;
-
-enum {
- openid_disco_none,
- openid_disco_xrds,
- openid_disco_html
-};
-
-
-void Free_ctdl_openid(ctdl_openid **FreeMe)
-{
- if (*FreeMe == NULL) {
- return;
- }
- FreeStrBuf(&(*FreeMe)->op_url);
- FreeStrBuf(&(*FreeMe)->claimed_id);
- DeleteHash(&(*FreeMe)->sreg_keys);
- free(*FreeMe);
- *FreeMe = NULL;
-}
-
-
-/*
- * This cleanup function blows away the temporary memory used by this module.
- */
-void openid_cleanup_function(void) {
- struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
-
- if (CCC->openid_data != NULL) {
- syslog(LOG_DEBUG, "openid: Clearing OpenID session state");
- Free_ctdl_openid((ctdl_openid **) &CCC->openid_data);
- }
-}
-
-
-/**************************************************************************/
-/* */
-/* Functions in this section handle Citadel internal OpenID mapping stuff */
-/* */
-/**************************************************************************/
-
-
-/*
- * The structure of an openid record *key* is:
- *
- * |--------------claimed_id-------------|
- * (actual length of claimed id)
- *
- *
- * The structure of an openid record *value* is:
- *
- * |-----user_number----|------------claimed_id---------------|
- * (sizeof long) (actual length of claimed id)
- *
- */
-
-
-
-/*
- * Attach an external authenticator (such as an OpenID) to a Citadel account
- */
-int attach_extauth(struct ctdluser *who, StrBuf *claimed_id)
-{
- struct cdbdata *cdboi;
- long fetched_usernum;
- char *data;
- int data_len;
- char buf[2048];
-
- if (!who) return(1);
- if (StrLength(claimed_id)==0) return(1);
-
- /* Check to see if this authenticator is already in the database */
-
- cdboi = cdb_fetch(CDB_EXTAUTH, ChrPtr(claimed_id), StrLength(claimed_id));
- if (cdboi != NULL) {
- memcpy(&fetched_usernum, cdboi->ptr, sizeof(long));
- cdb_free(cdboi);
-
- if (fetched_usernum == who->usernum) {
- syslog(LOG_INFO, "openid: %s already associated; no action is taken", ChrPtr(claimed_id));
- return(0);
- }
- else {
- syslog(LOG_INFO, "openid: %s already belongs to another user", ChrPtr(claimed_id));
- return(3);
- }
- }
-
- /* Not already in the database, so attach it now */
-
- data_len = sizeof(long) + StrLength(claimed_id) + 1;
- data = malloc(data_len);
-
- memcpy(data, &who->usernum, sizeof(long));
- memcpy(&data[sizeof(long)], ChrPtr(claimed_id), StrLength(claimed_id) + 1);
-
- cdb_store(CDB_EXTAUTH, ChrPtr(claimed_id), StrLength(claimed_id), data, data_len);
- free(data);
-
- snprintf(buf, sizeof buf, "User <%s> (#%ld) is now associated with %s\n", who->fullname, who->usernum, ChrPtr(claimed_id));
- CtdlAideMessage(buf, "External authenticator claim");
- syslog(LOG_INFO, "openid: %s", buf);
- return(0);
-}
-
-
-/*
- * When a user is being deleted, we have to delete any OpenID associations
- */
-void extauth_purge(struct ctdluser *usbuf) {
- struct cdbdata *cdboi;
- HashList *keys = NULL;
- HashPos *HashPos;
- char *deleteme = NULL;
- long len;
- void *Value;
- const char *Key;
- long usernum = 0L;
-
- keys = NewHash(1, NULL);
- if (!keys) return;
-
- cdb_rewind(CDB_EXTAUTH);
- while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
- if (cdboi->len > sizeof(long)) {
- memcpy(&usernum, cdboi->ptr, sizeof(long));
- if (usernum == usbuf->usernum) {
- deleteme = strdup(cdboi->ptr + sizeof(long)),
- Put(keys, deleteme, strlen(deleteme), deleteme, NULL);
- }
- }
- cdb_free(cdboi);
- }
-
- /* Go through the hash list, deleting keys we stored in it */
-
- HashPos = GetNewHashPos(keys, 0);
- while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0)
- {
- syslog(LOG_DEBUG, "openid: deleting associated external authenticator <%s>", (char*)Value);
- cdb_delete(CDB_EXTAUTH, Value, strlen(Value));
- /* note: don't free(Value) -- deleting the hash list will handle this for us */
- }
- DeleteHashPos(&HashPos);
- DeleteHash(&keys);
-}
-
-
-/*
- * List the OpenIDs associated with the currently logged in account
- */
-void cmd_oidl(char *argbuf) {
- struct cdbdata *cdboi;
- long usernum = 0L;
-
- if (CtdlGetConfigInt("c_disable_newu"))
- {
- cprintf("%d this system does not support openid.\n", ERROR + CMD_NOT_SUPPORTED);
- return;
- }
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- cdb_rewind(CDB_EXTAUTH);
- cprintf("%d Associated external authenticators:\n", LISTING_FOLLOWS);
-
- while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
- if (cdboi->len > sizeof(long)) {
- memcpy(&usernum, cdboi->ptr, sizeof(long));
- if (usernum == CC->user.usernum) {
- cprintf("%s\n", cdboi->ptr + sizeof(long));
- }
- }
- cdb_free(cdboi);
- }
- cprintf("000\n");
-}
-
-
-/*
- * List ALL OpenIDs in the database
- */
-void cmd_oida(char *argbuf) {
- struct cdbdata *cdboi;
- long usernum;
- struct ctdluser usbuf;
-
- if (CtdlGetConfigInt("c_disable_newu"))
- {
- cprintf("%d this system does not support openid.\n",
- ERROR + CMD_NOT_SUPPORTED);
- return;
- }
- if (CtdlAccessCheck(ac_aide)) return;
- cdb_rewind(CDB_EXTAUTH);
- cprintf("%d List of all OpenIDs in the database:\n", LISTING_FOLLOWS);
-
- while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
- if (cdboi->len > sizeof(long)) {
- memcpy(&usernum, cdboi->ptr, sizeof(long));
- if (CtdlGetUserByNumber(&usbuf, usernum) != 0) {
- usbuf.fullname[0] = 0;
- }
- cprintf("%s|%ld|%s\n",
- cdboi->ptr + sizeof(long),
- usernum,
- usbuf.fullname
- );
- }
- cdb_free(cdboi);
- }
- cprintf("000\n");
-}
-
-
-/*
- * Create a new user account, manually specifying the name, after successfully
- * verifying an OpenID (which will of course be attached to the account)
- */
-void cmd_oidc(char *argbuf) {
- ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
-
- if (CtdlGetConfigInt("c_disable_newu"))
- {
- cprintf("%d this system does not support openid.\n",
- ERROR + CMD_NOT_SUPPORTED);
- return;
- }
- if ( (!oiddata) || (!oiddata->verified) ) {
- cprintf("%d You have not verified an OpenID yet.\n", ERROR);
- return;
- }
-
- /* We can make the semantics of OIDC exactly the same as NEWU, simply
- * by _calling_ cmd_newu() and letting it run. Very clever!
- */
- cmd_newu(argbuf);
-
- /* Now, if this logged us in, we have to attach the OpenID */
- if (CC->logged_in) {
- attach_extauth(&CC->user, oiddata->claimed_id);
- }
-
-}
-
-
-/*
- * Detach an OpenID from the currently logged in account
- */
-void cmd_oidd(char *argbuf) {
- struct cdbdata *cdboi;
- char id_to_detach[1024];
- int this_is_mine = 0;
- long usernum = 0L;
-
- if (CtdlGetConfigInt("c_disable_newu"))
- {
- cprintf("%d this system does not support openid.\n",
- ERROR + CMD_NOT_SUPPORTED);
- return;
- }
- if (CtdlAccessCheck(ac_logged_in)) return;
- extract_token(id_to_detach, argbuf, 0, '|', sizeof id_to_detach);
- if (IsEmptyStr(id_to_detach)) {
- cprintf("%d An empty OpenID URL is not allowed.\n", ERROR + ILLEGAL_VALUE);
- }
-
- cdb_rewind(CDB_EXTAUTH);
- while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
- if (cdboi->len > sizeof(long)) {
- memcpy(&usernum, cdboi->ptr, sizeof(long));
- if (usernum == CC->user.usernum) {
- this_is_mine = 1;
- }
- }
- cdb_free(cdboi);
- }
-
- if (!this_is_mine) {
- cprintf("%d That OpenID was not found or not associated with your account.\n",
- ERROR + ILLEGAL_VALUE);
- return;
- }
-
- cdb_delete(CDB_EXTAUTH, id_to_detach, strlen(id_to_detach));
- cprintf("%d %s detached from your account.\n", CIT_OK, id_to_detach);
-}
-
-
-/*
- * Attempt to auto-create a new Citadel account using the nickname from Attribute Exchange
- */
-int openid_create_user_via_ax(StrBuf *claimed_id, HashList *sreg_keys)
-{
- char *nickname = NULL;
- char *firstname = NULL;
- char *lastname = NULL;
- char new_password[32];
- long len;
- const char *Key;
- void *Value;
-
- if (CtdlGetConfigInt("c_auth_mode") != AUTHMODE_NATIVE) return(1);
- if (CtdlGetConfigInt("c_disable_newu")) return(2);
- if (CC->logged_in) return(3);
-
- HashPos *HashPos = GetNewHashPos(sreg_keys, 0);
- while (GetNextHashPos(sreg_keys, HashPos, &len, &Key, &Value) != 0) {
- syslog(LOG_DEBUG, "openid: %s = %s", Key, (char *)Value);
-
- if (cbmstrcasestr(Key, "value.nickname") != NULL) {
- nickname = (char *)Value;
- }
- else if ( (nickname == NULL) && (cbmstrcasestr(Key, "value.nickname") != NULL)) {
- nickname = (char *)Value;
- }
- else if (cbmstrcasestr(Key, "value.firstname") != NULL) {
- firstname = (char *)Value;
- }
- else if (cbmstrcasestr(Key, "value.lastname") != NULL) {
- lastname = (char *)Value;
- }
-
- }
- DeleteHashPos(&HashPos);
-
- if (nickname == NULL) {
- if ((firstname != NULL) || (lastname != NULL)) {
- char fullname[1024] = "";
- if (firstname) strcpy(fullname, firstname);
- if (firstname && lastname) strcat(fullname, " ");
- if (lastname) strcat(fullname, lastname);
- nickname = fullname;
- }
- }
-
- if (nickname == NULL) {
- return(4);
- }
- syslog(LOG_DEBUG, "openid: the desired account name is <%s>", nickname);
-
- if (!CtdlGetUser(&CC->user, nickname)) {
- syslog(LOG_DEBUG, "openid: <%s> is already taken by another user.", nickname);
- memset(&CC->user, 0, sizeof(struct ctdluser));
- return(5);
- }
-
- /* The desired account name is available. Create the account and log it in! */
- if (create_user(nickname, CREATE_USER_BECOME_USER, NATIVE_AUTH_UID)) return(6);
-
- /* Generate a random password.
- * The user doesn't care what the password is since he is using OpenID.
- */
- snprintf(new_password, sizeof new_password, "%08lx%08lx", random(), random());
- CtdlSetPassword(new_password);
-
- /* Now attach the verified OpenID to this account. */
- attach_extauth(&CC->user, claimed_id);
-
- return(0);
-}
-
-
-/*
- * If a user account exists which is associated with the Claimed ID, log it in and return zero.
- * Otherwise it returns nonzero.
- */
-int login_via_extauth(StrBuf *claimed_id)
-{
- struct cdbdata *cdboi;
- long usernum = 0;
-
- cdboi = cdb_fetch(CDB_EXTAUTH, ChrPtr(claimed_id), StrLength(claimed_id));
- if (cdboi == NULL) {
- return(-1);
- }
-
- memcpy(&usernum, cdboi->ptr, sizeof(long));
- cdb_free(cdboi);
-
- if (!CtdlGetUserByNumber(&CC->user, usernum)) {
- /* Now become the user we just created */
- safestrncpy(CC->curr_user, CC->user.fullname, sizeof CC->curr_user);
- do_login();
- return(0);
- }
- else {
- memset(&CC->user, 0, sizeof(struct ctdluser));
- return(-1);
- }
-}
-
-
-
-
-/**************************************************************************/
-/* */
-/* Functions in this section handle OpenID protocol */
-/* */
-/**************************************************************************/
-
-
-/*
- * Locate a <link> tag and, given its 'rel=' parameter, return its 'href' parameter
- */
-void extract_link(StrBuf *target_buf, const char *rel, long repllen, StrBuf *source_buf)
-{
- int i;
- const char *ptr;
- const char *href_start = NULL;
- const char *href_end = NULL;
- const char *link_tag_start = NULL;
- const char *link_tag_end = NULL;
- const char *rel_start = NULL;
- const char *rel_end = NULL;
-
- if (!target_buf) return;
- if (!rel) return;
- if (!source_buf) return;
-
- ptr = ChrPtr(source_buf);
-
- FlushStrBuf(target_buf);
- while (ptr = cbmstrcasestr(ptr, "<link"), ptr != NULL) {
-
- link_tag_start = ptr;
- link_tag_end = strchr(ptr, '>');
- if (link_tag_end == NULL)
- break;
- for (i=0; i < 1; i++ ){
- rel_start = cbmstrcasestr(link_tag_start, "rel=");
- if ((rel_start == NULL) ||
- (rel_start > link_tag_end))
- continue;
-
- rel_start = strchr(rel_start, '\"');
- if ((rel_start == NULL) ||
- (rel_start > link_tag_end))
- continue;
- ++rel_start;
- rel_end = strchr(rel_start, '\"');
- if ((rel_end == NULL) ||
- (rel_end == rel_start) ||
- (rel_end >= link_tag_end) )
- continue;
- if (strncasecmp(rel, rel_start, repllen)!= 0)
- continue; /* didn't match? never mind... */
-
- href_start = cbmstrcasestr(link_tag_start, "href=");
- if ((href_start == NULL) ||
- (href_start >= link_tag_end))
- continue;
- href_start = strchr(href_start, '\"');
- if ((href_start == NULL) |
- (href_start >= link_tag_end))
- continue;
- ++href_start;
- href_end = strchr(href_start, '\"');
- if ((href_end == NULL) ||
- (href_end == href_start) ||
- (href_start >= link_tag_end))
- continue;
- StrBufPlain(target_buf, href_start, href_end - href_start);
- }
- ptr = link_tag_end;
- }
-}
-
-
-/*
- * Wrapper for curl_easy_init() that includes the options common to all calls
- * used in this module.
- */
-CURL *ctdl_openid_curl_easy_init(char *errmsg) {
- CURL *curl;
-
- curl = curl_easy_init();
- if (!curl) {
- return(curl);
- }
-
- curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
- curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
-
- if (errmsg) {
- curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errmsg);
- }
- curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
-#ifdef CURLOPT_HTTP_CONTENT_DECODING
- curl_easy_setopt(curl, CURLOPT_HTTP_CONTENT_DECODING, 1);
- curl_easy_setopt(curl, CURLOPT_ENCODING, "");
-#endif
- curl_easy_setopt(curl, CURLOPT_USERAGENT, CITADEL);
- curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30); /* die after 30 seconds */
-
- if (
- (!IsEmptyStr(CtdlGetConfigStr("c_ip_addr")))
- && (strcmp(CtdlGetConfigStr("c_ip_addr"), "*"))
- && (strcmp(CtdlGetConfigStr("c_ip_addr"), "::"))
- && (strcmp(CtdlGetConfigStr("c_ip_addr"), "0.0.0.0"))
- ) {
- curl_easy_setopt(curl, CURLOPT_INTERFACE, CtdlGetConfigStr("c_ip_addr"));
- }
-
- return(curl);
-}
-
-
-struct xrds {
- StrBuf *CharData;
- int nesting_level;
- int in_xrd;
- int current_service_priority;
- int selected_service_priority;
- StrBuf *current_service_uri;
- StrBuf *selected_service_uri;
- int current_service_is_oid2auth;
-};
-
-
-void xrds_xml_start(void *data, const char *supplied_el, const char **attr) {
- struct xrds *xrds = (struct xrds *) data;
- int i;
-
- ++xrds->nesting_level;
-
- if (!strcasecmp(supplied_el, "XRD")) {
- ++xrds->in_xrd;
- }
-
- else if (!strcasecmp(supplied_el, "service")) {
- xrds->current_service_priority = 0;
- xrds->current_service_is_oid2auth = 0;
- for (i=0; attr[i] != NULL; i+=2) {
- if (!strcasecmp(attr[i], "priority")) {
- xrds->current_service_priority = atoi(attr[i+1]);
- }
- }
- }
-
- FlushStrBuf(xrds->CharData);
-}
-
-
-void xrds_xml_end(void *data, const char *supplied_el) {
- struct xrds *xrds = (struct xrds *) data;
-
- --xrds->nesting_level;
-
- if (!strcasecmp(supplied_el, "XRD")) {
- --xrds->in_xrd;
- }
-
- else if (!strcasecmp(supplied_el, "type")) {
- if ( (xrds->in_xrd)
- && (!strcasecmp(ChrPtr(xrds->CharData), "http://specs.openid.net/auth/2.0/server"))
- ) {
- xrds->current_service_is_oid2auth = 1;
- }
- if ( (xrds->in_xrd)
- && (!strcasecmp(ChrPtr(xrds->CharData), "http://specs.openid.net/auth/2.0/signon"))
- ) {
- xrds->current_service_is_oid2auth = 1;
- /* FIXME in this case, the Claimed ID should be considered immutable */
- }
- }
-
- else if (!strcasecmp(supplied_el, "uri")) {
- if (xrds->in_xrd) {
- FlushStrBuf(xrds->current_service_uri);
- StrBufAppendBuf(xrds->current_service_uri, xrds->CharData, 0);
- }
- }
-
- else if (!strcasecmp(supplied_el, "service")) {
- if ( (xrds->in_xrd)
- && (xrds->current_service_priority < xrds->selected_service_priority)
- && (xrds->current_service_is_oid2auth)
- ) {
- xrds->selected_service_priority = xrds->current_service_priority;
- FlushStrBuf(xrds->selected_service_uri);
- StrBufAppendBuf(xrds->selected_service_uri, xrds->current_service_uri, 0);
- }
-
- }
-
- FlushStrBuf(xrds->CharData);
-}
-
-
-void xrds_xml_chardata(void *data, const XML_Char *s, int len) {
- struct xrds *xrds = (struct xrds *) data;
-
- StrBufAppendBufPlain (xrds->CharData, s, len, 0);
-}
-
-
-/*
- * Parse an XRDS document.
- * If an OpenID Provider URL is discovered, op_url to that value and return nonzero.
- * If nothing useful happened, return 0.
- */
-int parse_xrds_document(StrBuf *ReplyBuf) {
- ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
- struct xrds xrds;
- int return_value = 0;
-
- memset(&xrds, 0, sizeof (struct xrds));
- xrds.selected_service_priority = INT_MAX;
- xrds.CharData = NewStrBuf();
- xrds.current_service_uri = NewStrBuf();
- xrds.selected_service_uri = NewStrBuf();
- XML_Parser xp = XML_ParserCreate(NULL);
- if (xp) {
- XML_SetUserData(xp, &xrds);
- XML_SetElementHandler(xp, xrds_xml_start, xrds_xml_end);
- XML_SetCharacterDataHandler(xp, xrds_xml_chardata);
- XML_Parse(xp, ChrPtr(ReplyBuf), StrLength(ReplyBuf), 0);
- XML_Parse(xp, "", 0, 1);
- XML_ParserFree(xp);
- }
- else {
- syslog(LOG_ERR, "openid: cannot create XML parser");
- }
-
- if (xrds.selected_service_priority < INT_MAX) {
- if (oiddata->op_url == NULL) {
- oiddata->op_url = NewStrBuf();
- }
- FlushStrBuf(oiddata->op_url);
- StrBufAppendBuf(oiddata->op_url, xrds.selected_service_uri, 0);
- return_value = openid_disco_xrds;
- }
-
- FreeStrBuf(&xrds.CharData);
- FreeStrBuf(&xrds.current_service_uri);
- FreeStrBuf(&xrds.selected_service_uri);
-
- return(return_value);
-}
-
-
-/*
- * Callback function for perform_openid2_discovery()
- * We're interested in the X-XRDS-Location: header.
- */
-size_t yadis_headerfunction(void *ptr, size_t size, size_t nmemb, void *userdata) {
- char hdr[1024];
- StrBuf **x_xrds_location = (StrBuf **) userdata;
-
- memcpy(hdr, ptr, (size*nmemb));
- hdr[size*nmemb] = 0;
-
- if (!strncasecmp(hdr, "X-XRDS-Location:", 16)) {
- *x_xrds_location = NewStrBufPlain(&hdr[16], ((size*nmemb)-16));
- StrBufTrim(*x_xrds_location);
- }
-
- return(size * nmemb);
-}
-
-
-/* Attempt to perform Yadis discovery as specified in Yadis 1.0 section 6.2.5.
- *
- * If Yadis fails, we then attempt HTML discovery using the same document.
- *
- * If successful, returns nonzero and calls parse_xrds_document() to act upon the received data.
- * If fails, returns 0 and does nothing else.
- */
-int perform_openid2_discovery(StrBuf *SuppliedURL) {
- ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
- int docbytes = (-1);
- StrBuf *ReplyBuf = NULL;
- int return_value = 0;
- CURL *curl;
- CURLcode result;
- char errmsg[1024] = "";
- struct curl_slist *my_headers = NULL;
- StrBuf *x_xrds_location = NULL;
-
- if (!SuppliedURL) return(0);
- syslog(LOG_DEBUG, "openid: perform_openid2_discovery(%s)", ChrPtr(SuppliedURL));
- if (StrLength(SuppliedURL) == 0) return(0);
-
- ReplyBuf = NewStrBuf();
- if (!ReplyBuf) return(0);
-
- curl = ctdl_openid_curl_easy_init(errmsg);
- if (!curl) return(0);
-
- curl_easy_setopt(curl, CURLOPT_URL, ChrPtr(SuppliedURL));
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, ReplyBuf);
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlFillStrBuf_callback);
-
- my_headers = curl_slist_append(my_headers, "Accept:"); /* disable the default Accept: header */
- my_headers = curl_slist_append(my_headers, "Accept: application/xrds+xml");
- curl_easy_setopt(curl, CURLOPT_HTTPHEADER, my_headers);
-
- curl_easy_setopt(curl, CURLOPT_WRITEHEADER, &x_xrds_location);
- curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, yadis_headerfunction);
-
- result = curl_easy_perform(curl);
- if (result) {
- syslog(LOG_DEBUG, "openid: libcurl error %d: %s", result, errmsg);
- }
- curl_slist_free_all(my_headers);
- curl_easy_cleanup(curl);
- docbytes = StrLength(ReplyBuf);
-
- /*
- * The response from the server will be one of:
- *
- * Option 1: An HTML document with a <head> element that includes a <meta> element with http-equiv
- * attribute, X-XRDS-Location,
- *
- * Does any provider actually do this? If so then we will implement it in the future.
- */
-
- /*
- * Option 2: HTTP response-headers that include an X-XRDS-Location response-header,
- * together with a document.
- * Option 3: HTTP response-headers only, which MAY include an X-XRDS-Location response-header,
- * a contenttype response-header specifying MIME media type,
- * application/xrds+xml, or both.
- *
- * If the X-XRDS-Location header was delivered, we know about it at this point...
- */
- if ( (x_xrds_location)
- && (strcmp(ChrPtr(x_xrds_location), ChrPtr(SuppliedURL)))
- ) {
- syslog(LOG_DEBUG, "openid: X-XRDS-Location: %s ... recursing!", ChrPtr(x_xrds_location));
- return_value = perform_openid2_discovery(x_xrds_location);
- FreeStrBuf(&x_xrds_location);
- }
-
- /*
- * Option 4: the returned web page may *be* an XRDS document. Try to parse it.
- */
- if ( (return_value == 0) && (docbytes >= 0)) {
- return_value = parse_xrds_document(ReplyBuf);
- }
-
- /*
- * Option 5: if all else fails, attempt HTML based discovery.
- */
- if ( (return_value == 0) && (docbytes >= 0)) {
- if (oiddata->op_url == NULL) {
- oiddata->op_url = NewStrBuf();
- }
- extract_link(oiddata->op_url, HKEY("openid2.provider"), ReplyBuf);
- if (StrLength(oiddata->op_url) > 0) {
- return_value = openid_disco_html;
- }
- }
-
- if (ReplyBuf != NULL) {
- FreeStrBuf(&ReplyBuf);
- }
- return(return_value);
-}
-
-
-/*
- * Setup an OpenID authentication
- */
-void cmd_oids(char *argbuf) {
- struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
- const char *Pos = NULL;
- StrBuf *ArgBuf = NULL;
- StrBuf *ReplyBuf = NULL;
- StrBuf *return_to = NULL;
- StrBuf *RedirectUrl = NULL;
- ctdl_openid *oiddata;
- int discovery_succeeded = 0;
-
- if (CtdlGetConfigInt("c_disable_newu"))
- {
- cprintf("%d this system does not support openid.\n",
- ERROR + CMD_NOT_SUPPORTED);
- return;
- }
- Free_ctdl_openid ((ctdl_openid**)&CCC->openid_data);
-
- CCC->openid_data = oiddata = malloc(sizeof(ctdl_openid));
- if (oiddata == NULL) {
- syslog(LOG_ERR, "openid: malloc() failed: %m");
- cprintf("%d malloc failed\n", ERROR + INTERNAL_ERROR);
- return;
- }
- memset(oiddata, 0, sizeof(ctdl_openid));
-
- ArgBuf = NewStrBufPlain(argbuf, -1);
-
- oiddata->verified = 0;
- oiddata->claimed_id = NewStrBufPlain(NULL, StrLength(ArgBuf));
- return_to = NewStrBufPlain(NULL, StrLength(ArgBuf));
-
- StrBufExtract_NextToken(oiddata->claimed_id, ArgBuf, &Pos, '|');
- StrBufExtract_NextToken(return_to, ArgBuf, &Pos, '|');
-
- syslog(LOG_DEBUG, "openid: user-Supplied Identifier is: %s", ChrPtr(oiddata->claimed_id));
-
- /********** OpenID 2.0 section 7.3 - Discovery **********/
-
- /* Section 7.3.1 says we have to attempt XRI based discovery.
- * No one is using this, no one is asking for it, no one wants it.
- * So we're not even going to bother attempting this mode.
- */
-
- /* Attempt section 7.3.2 (Yadis discovery) and section 7.3.3 (HTML discovery);
- */
- discovery_succeeded = perform_openid2_discovery(oiddata->claimed_id);
-
- if (discovery_succeeded == 0) {
- cprintf("%d There is no OpenID identity provider at this location.\n", ERROR);
- }
-
- else {
- /*
- * If we get to this point we are in possession of a valid OpenID Provider URL.
- */
- syslog(LOG_DEBUG, "openid: OP URI '%s' discovered using method %d",
- ChrPtr(oiddata->op_url),
- discovery_succeeded
- );
-
- /* We have to "normalize" our Claimed ID otherwise it will cause some OP's to barf */
- if (cbmstrcasestr(ChrPtr(oiddata->claimed_id), "://") == NULL) {
- StrBuf *cid = oiddata->claimed_id;
- oiddata->claimed_id = NewStrBufPlain(HKEY("http://"));
- StrBufAppendBuf(oiddata->claimed_id, cid, 0);
- FreeStrBuf(&cid);
- }
-
- /*
- * OpenID 2.0 section 9: request authentication
- * Assemble a URL to which the user-agent will be redirected.
- */
-
- RedirectUrl = NewStrBufDup(oiddata->op_url);
-
- StrBufAppendBufPlain(RedirectUrl, HKEY("?openid.ns="), 0);
- StrBufUrlescAppend(RedirectUrl, NULL, "http://specs.openid.net/auth/2.0");
-
- StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.mode=checkid_setup"), 0);
-
- StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.claimed_id="), 0);
- StrBufUrlescAppend(RedirectUrl, oiddata->claimed_id, NULL);
-
- StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.identity="), 0);
- StrBufUrlescAppend(RedirectUrl, oiddata->claimed_id, NULL);
-
- /* return_to tells the provider how to complete the round trip back to our site */
- StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.return_to="), 0);
- StrBufUrlescAppend(RedirectUrl, return_to, NULL);
-
- /* Attribute Exchange
- * See:
- * http://openid.net/specs/openid-attribute-exchange-1_0.html
- * http://code.google.com/apis/accounts/docs/OpenID.html#endpoint
- * http://test-id.net/OP/AXFetch.aspx
- */
-
- StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ns.ax="), 0);
- StrBufUrlescAppend(RedirectUrl, NULL, "http://openid.net/srv/ax/1.0");
-
- StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.mode=fetch_request"), 0);
-
- StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.required=firstname,lastname,friendly,nickname"), 0);
-
- StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.firstname="), 0);
- StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/first");
-
- StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.lastname="), 0);
- StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/last");
-
- StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.friendly="), 0);
- StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/friendly");
-
- StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.nickname="), 0);
- StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/nickname");
-
- syslog(LOG_DEBUG, "openid: redirecting client to %s", ChrPtr(RedirectUrl));
- cprintf("%d %s\n", CIT_OK, ChrPtr(RedirectUrl));
- }
-
- FreeStrBuf(&ArgBuf);
- FreeStrBuf(&ReplyBuf);
- FreeStrBuf(&return_to);
- FreeStrBuf(&RedirectUrl);
-}
-
-
-/*
- * Finalize an OpenID authentication
- */
-void cmd_oidf(char *argbuf) {
- long len;
- char buf[2048];
- char thiskey[1024];
- char thisdata[1024];
- HashList *keys = NULL;
- const char *Key;
- void *Value;
- ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
-
- if (CtdlGetConfigInt("c_disable_newu"))
- {
- cprintf("%d this system does not support openid.\n",
- ERROR + CMD_NOT_SUPPORTED);
- return;
- }
- if (oiddata == NULL) {
- cprintf("%d run OIDS first.\n", ERROR + INTERNAL_ERROR);
- return;
- }
- if (StrLength(oiddata->op_url) == 0){
- cprintf("%d No OpenID Endpoint URL has been obtained.\n", ERROR + ILLEGAL_VALUE);
- return;
- }
- keys = NewHash(1, NULL);
- if (!keys) {
- cprintf("%d NewHash() failed\n", ERROR + INTERNAL_ERROR);
- return;
- }
- cprintf("%d Transmit OpenID data now\n", START_CHAT_MODE);
-
- while (client_getln(buf, sizeof buf), strcmp(buf, "000")) {
- len = extract_token(thiskey, buf, 0, '|', sizeof thiskey);
- if (len < 0) {
- len = sizeof(thiskey) - 1;
- }
- extract_token(thisdata, buf, 1, '|', sizeof thisdata);
- Put(keys, thiskey, len, strdup(thisdata), NULL);
- }
-
- /* Check to see if this is a correct response.
- * Start with verified=1 but then set it to 0 if anything looks wrong.
- */
- oiddata->verified = 1;
-
- char *openid_ns = NULL;
- if ( (!GetHash(keys, "ns", 2, (void *) &openid_ns))
- || (strcasecmp(openid_ns, "http://specs.openid.net/auth/2.0"))
- ) {
- syslog(LOG_DEBUG, "openid: this is not an an OpenID assertion");
- oiddata->verified = 0;
- }
-
- char *openid_mode = NULL;
- if ( (!GetHash(keys, "mode", 4, (void *) &openid_mode))
- || (strcasecmp(openid_mode, "id_res"))
- ) {
- oiddata->verified = 0;
- }
-
- char *openid_claimed_id = NULL;
- if (GetHash(keys, "claimed_id", 10, (void *) &openid_claimed_id)) {
- FreeStrBuf(&oiddata->claimed_id);
- oiddata->claimed_id = NewStrBufPlain(openid_claimed_id, -1);
- syslog(LOG_DEBUG, "openid: provider is asserting the Claimed ID '%s'", ChrPtr(oiddata->claimed_id));
- }
-
- /* Validate the assertion against the server */
- syslog(LOG_DEBUG, "openid: validating...");
-
- CURL *curl;
- CURLcode res;
- struct curl_httppost *formpost = NULL;
- struct curl_httppost *lastptr = NULL;
- char errmsg[1024] = "";
- StrBuf *ReplyBuf = NewStrBuf();
-
- curl_formadd(&formpost, &lastptr,
- CURLFORM_COPYNAME, "openid.mode",
- CURLFORM_COPYCONTENTS, "check_authentication",
- CURLFORM_END
- );
-
- HashPos *HashPos = GetNewHashPos(keys, 0);
- while (GetNextHashPos(keys, HashPos, &len, &Key, &Value) != 0) {
- if (strcasecmp(Key, "mode")) {
- char k_o_keyname[1024];
- snprintf(k_o_keyname, sizeof k_o_keyname, "openid.%s", (const char *)Key);
- curl_formadd(&formpost, &lastptr,
- CURLFORM_COPYNAME, k_o_keyname,
- CURLFORM_COPYCONTENTS, (char *)Value,
- CURLFORM_END
- );
- }
- }
- DeleteHashPos(&HashPos);
-
- curl = ctdl_openid_curl_easy_init(errmsg);
- curl_easy_setopt(curl, CURLOPT_URL, ChrPtr(oiddata->op_url));
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, ReplyBuf);
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlFillStrBuf_callback);
- curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
-
- res = curl_easy_perform(curl);
- if (res) {
- syslog(LOG_DEBUG, "openid: cmd_oidf() libcurl error %d: %s", res, errmsg);
- oiddata->verified = 0;
- }
- curl_easy_cleanup(curl);
- curl_formfree(formpost);
-
- if (cbmstrcasestr(ChrPtr(ReplyBuf), "is_valid:true") == NULL) {
- oiddata->verified = 0;
- }
- FreeStrBuf(&ReplyBuf);
-
- syslog(LOG_DEBUG, "openid: authentication %s", (oiddata->verified ? "succeeded" : "failed") );
-
- /* Respond to the client */
-
- if (oiddata->verified) {
-
- /* If we were already logged in, attach the OpenID to the user's account */
- if (CC->logged_in) {
- if (attach_extauth(&CC->user, oiddata->claimed_id) == 0) {
- cprintf("attach\n");
- syslog(LOG_DEBUG, "openid: attach succeeded");
- }
- else {
- cprintf("fail\n");
- syslog(LOG_DEBUG, "openid: attach failed");
- }
- }
-
- /* Otherwise, a user is attempting to log in using the verified OpenID */
- else {
- /*
- * Existing user who has claimed this OpenID?
- *
- * Note: if you think that sending the password back over the wire is insecure,
- * check your assumptions. If someone has successfully asserted an OpenID that
- * is associated with the account, they already have password equivalency and can
- * login, so they could just as easily change the password, etc.
- */
- if (login_via_extauth(oiddata->claimed_id) == 0) {
- cprintf("authenticate\n%s\n%s\n", CC->user.fullname, CC->user.password);
- logged_in_response();
- syslog(LOG_DEBUG, "openid: logged in using previously claimed OpenID");
- }
-
- /*
- * If this system does not allow self-service new user registration, the
- * remaining modes do not apply, so fail here and now.
- */
- else if (CtdlGetConfigInt("c_disable_newu")) {
- cprintf("fail\n");
- syslog(LOG_DEBUG, "openid: creating user failed due to local policy");
- }
-
- /*
- * New user whose OpenID is verified and Attribute Exchange gave us a name?
- */
- else if (openid_create_user_via_ax(oiddata->claimed_id, keys) == 0) {
- cprintf("authenticate\n%s\n%s\n", CC->user.fullname, CC->user.password);
- logged_in_response();
- syslog(LOG_DEBUG, "openid: successfully auto-created new user");
- }
-
- /*
- * OpenID is verified, but the desired username either was not specified or
- * conflicts with an existing user. Manual account creation is required.
- */
- else {
- char *desired_name = NULL;
- cprintf("verify_only\n");
- cprintf("%s\n", ChrPtr(oiddata->claimed_id));
- if (GetHash(keys, "sreg.nickname", 13, (void *) &desired_name)) {
- cprintf("%s\n", desired_name);
- }
- else {
- cprintf("\n");
- }
- syslog(LOG_DEBUG, "openid: the desired display name is already taken.");
- }
- }
- }
- else {
- cprintf("fail\n");
- }
- cprintf("000\n");
-
- if (oiddata->sreg_keys != NULL) {
- DeleteHash(&oiddata->sreg_keys);
- oiddata->sreg_keys = NULL;
- }
- oiddata->sreg_keys = keys;
-}
-
-
-
-/**************************************************************************/
-/* */
-/* Functions in this section handle module initialization and shutdown */
-/* */
-/**************************************************************************/
-
-
-
-
-CTDL_MODULE_INIT(openid_rp)
-{
- if (!threading) {
-
- /* Only enable the OpenID command set when native mode authentication is in use. */
- if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_NATIVE) {
- CtdlRegisterProtoHook(cmd_oids, "OIDS", "Setup OpenID authentication");
- CtdlRegisterProtoHook(cmd_oidf, "OIDF", "Finalize OpenID authentication");
- CtdlRegisterProtoHook(cmd_oidl, "OIDL", "List OpenIDs associated with an account");
- CtdlRegisterProtoHook(cmd_oidd, "OIDD", "Detach an OpenID from an account");
- CtdlRegisterProtoHook(cmd_oidc, "OIDC", "Create new user after validating OpenID");
- CtdlRegisterProtoHook(cmd_oida, "OIDA", "List all OpenIDs in the database");
- }
- CtdlRegisterSessionHook(openid_cleanup_function, EVT_LOGOUT, PRIO_LOGOUT + 10);
- CtdlRegisterUserHook(extauth_purge, EVT_PURGEUSER);
- openid_level_supported = 1; /* This module supports OpenID 1.0 only */
- }
-
- /* return our module name for the log */
- return "openid_rp";
-}
+++ /dev/null
-// POP3 service for the Citadel system
-//
-// Copyright (c) 1998-2021 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.
-//
-// Current status of standards conformance:
-//
-// -> All required POP3 commands described in RFC1939 are implemented.
-// -> All optional POP3 commands described in RFC1939 are also implemented.
-// -> The deprecated "LAST" command is included in this implementation, because
-// there exist mail clients which insist on using it (such as Bynari
-// TradeMail, and certain versions of Eudora).
-// -> Capability detection via the method described in RFC2449 is implemented.
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <ctype.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "serv_pop3.h"
-#include "ctdl_module.h"
-
-
-// This cleanup function blows away the temporary memory and files used by
-// the POP3 server.
-void pop3_cleanup_function(void) {
- /* Don't do this stuff if this is not a POP3 session! */
- if (CC->h_command_function != pop3_command_loop) return;
-
- struct citpop3 *pop3 = ((struct citpop3 *)CC->session_specific_data);
- syslog(LOG_DEBUG, "pop3: performing cleanup hook");
- if (pop3->msgs != NULL) {
- free(pop3->msgs);
- }
-
- free(pop3);
-}
-
-
-// Here's where our POP3 session begins its happy day.
-void pop3_greeting(void) {
- strcpy(CC->cs_clientname, "POP3 session");
- CC->internal_pgm = 1;
- CC->session_specific_data = malloc(sizeof(struct citpop3));
- memset(POP3, 0, sizeof(struct citpop3));
-
- cprintf("+OK Citadel POP3 server ready.\r\n");
-}
-
-
-// POP3S is just like POP3, except it goes crypto right away.
-void pop3s_greeting(void) {
- CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
-
-/* kill session if no crypto */
-#ifdef HAVE_OPENSSL
- if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;
-#else
- CC->kill_me = KILLME_NO_CRYPTO;
-#endif
-
- pop3_greeting();
-}
-
-
-// Specify user name (implements POP3 "USER" command)
-void pop3_user(char *argbuf) {
- char username[SIZ];
-
- if (CC->logged_in) {
- cprintf("-ERR You are already logged in.\r\n");
- return;
- }
-
- strcpy(username, argbuf);
- striplt(username);
-
- if (CtdlLoginExistingUser(username) == login_ok) {
- cprintf("+OK Password required for %s\r\n", username);
- }
- else {
- cprintf("-ERR No such user.\r\n");
- }
-}
-
-
-// Back end for pop3_grab_mailbox()
-void pop3_add_message(long msgnum, void *userdata) {
- struct MetaData smi;
-
- ++POP3->num_msgs;
- if (POP3->num_msgs < 2) {
- POP3->msgs = malloc(sizeof(struct pop3msg));
- }
- else {
- POP3->msgs = realloc(POP3->msgs, (POP3->num_msgs * sizeof(struct pop3msg)) ) ;
- }
- POP3->msgs[POP3->num_msgs-1].msgnum = msgnum;
- POP3->msgs[POP3->num_msgs-1].deleted = 0;
-
- // We need to know the length of this message when it is printed in
- // RFC822 format. Perhaps we have cached this length in the message's
- // metadata record. If so, great; if not, measure it and then cache
- // it for next time.
- GetMetaData(&smi, msgnum);
- if (smi.meta_rfc822_length <= 0L) {
- CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
- CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, SUPPRESS_ENV_TO, NULL, NULL, NULL);
- smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
- FreeStrBuf(&CC->redirect_buffer);
- PutMetaData(&smi);
- }
- POP3->msgs[POP3->num_msgs-1].rfc822_length = smi.meta_rfc822_length;
-}
-
-
-// Open the inbox and read its contents.
-// (This should be called only once, by pop3_pass(), and returns the number
-// of messages in the inbox, or -1 for error)
-int pop3_grab_mailbox(void) {
- visit vbuf;
- int i;
-
- if (CtdlGetRoom(&CC->room, MAILROOM) != 0) return(-1);
-
- /* Load up the messages */
- CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, pop3_add_message, NULL);
-
- /* Figure out which are old and which are new */
- CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
- POP3->lastseen = (-1);
- if (POP3->num_msgs) for (i=0; i<POP3->num_msgs; ++i) {
- if (is_msg_in_sequence_set(vbuf.v_seen, (POP3->msgs[POP3->num_msgs-1].msgnum) )) {
- POP3->lastseen = i;
- }
- }
-
- return(POP3->num_msgs);
-}
-
-
-void pop3_login(void) {
- int msgs;
-
- msgs = pop3_grab_mailbox();
- if (msgs >= 0) {
- cprintf("+OK %s is logged in (%d messages)\r\n",
- CC->user.fullname, msgs);
- syslog(LOG_DEBUG, "pop3: authenticated %s", CC->user.fullname);
- }
- else {
- cprintf("-ERR Can't open your mailbox\r\n");
- }
-
-}
-
-
-// Authorize with password (implements POP3 "PASS" command)
-void pop3_pass(char *argbuf) {
- char password[SIZ];
-
- safestrncpy(password, argbuf, sizeof password);
- striplt(password);
-
- if (CtdlTryPassword(password, strlen(password)) == pass_ok) {
- pop3_login();
- }
- else {
- cprintf("-ERR That is NOT the password.\r\n");
- }
-}
-
-
-// list available msgs
-void pop3_list(char *argbuf) {
- int i;
- int which_one;
-
- which_one = atoi(argbuf);
-
- // "list one" mode
- if (which_one > 0) {
- if (which_one > POP3->num_msgs) {
- cprintf("-ERR no such message, only %d are here\r\n", POP3->num_msgs);
- return;
- }
- else if (POP3->msgs[which_one-1].deleted) {
- cprintf("-ERR Sorry, you deleted that message.\r\n");
- return;
- }
- else {
- cprintf("+OK %d %ld\r\n", which_one, (long)POP3->msgs[which_one-1].rfc822_length);
- return;
- }
- }
-
- // "list all" (scan listing) mode
- else {
- cprintf("+OK Here's your mail:\r\n");
- if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
- if (! POP3->msgs[i].deleted) {
- cprintf("%d %ld\r\n", i+1, (long)POP3->msgs[i].rfc822_length);
- }
- }
- cprintf(".\r\n");
- }
-}
-
-
-// STAT (tally up the total message count and byte count) command
-void pop3_stat(char *argbuf) {
- int total_msgs = 0;
- size_t total_octets = 0;
- int i;
-
- if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
- if (! POP3->msgs[i].deleted) {
- ++total_msgs;
- total_octets += POP3->msgs[i].rfc822_length;
- }
- }
-
- cprintf("+OK %d %ld\r\n", total_msgs, (long)total_octets);
-}
-
-
-// RETR command (fetch a message)
-void pop3_retr(char *argbuf) {
- int which_one;
-
- which_one = atoi(argbuf);
- if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
- cprintf("-ERR No such message.\r\n");
- return;
- }
-
- if (POP3->msgs[which_one - 1].deleted) {
- cprintf("-ERR Sorry, you deleted that message.\r\n");
- return;
- }
-
- cprintf("+OK Message %d:\r\n", which_one);
- CtdlOutputMsg(POP3->msgs[which_one - 1].msgnum, MT_RFC822,
- HEADERS_ALL, 0, 1, NULL,
- (ESC_DOT|SUPPRESS_ENV_TO), NULL, NULL, NULL
- );
- cprintf(".\r\n");
-}
-
-
-// TOP command (dumb way of fetching a partial message or headers-only)
-void pop3_top(char *argbuf) {
- int which_one;
- int lines_requested = 0;
- int lines_dumped = 0;
- char buf[1024];
- StrBuf *msgtext;
- const char *ptr;
- int in_body = 0;
- int done = 0;
-
- sscanf(argbuf, "%d %d", &which_one, &lines_requested);
- if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
- cprintf("-ERR No such message.\r\n");
- return;
- }
-
- if (POP3->msgs[which_one - 1].deleted) {
- cprintf("-ERR Sorry, you deleted that message.\r\n");
- return;
- }
-
- CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
-
- CtdlOutputMsg(POP3->msgs[which_one - 1].msgnum,
- MT_RFC822,
- HEADERS_ALL,
- 0, 1, NULL,
- SUPPRESS_ENV_TO,
- NULL, NULL, NULL);
-
- msgtext = CC->redirect_buffer;
- CC->redirect_buffer = NULL;
-
- cprintf("+OK Message %d:\r\n", which_one);
-
- ptr = ChrPtr(msgtext);
- while (ptr = cmemreadline(ptr, buf, (sizeof buf - 2)),
- ( (*ptr != 0) && (done == 0))) {
- strcat(buf, "\r\n");
- if (in_body == 1) {
- if (lines_dumped >= lines_requested) {
- done = 1;
- }
- }
- if ((in_body == 0) || (done == 0)) {
- client_write(buf, strlen(buf));
- }
- if (in_body) {
- ++lines_dumped;
- }
- if ((buf[0]==13)||(buf[0]==10)) in_body = 1;
- }
-
- if (buf[strlen(buf)-1] != 10) cprintf("\n");
- FreeStrBuf(&msgtext);
-
- cprintf(".\r\n");
-}
-
-
-// DELE (delete message from mailbox)
-void pop3_dele(char *argbuf) {
- int which_one;
-
- which_one = atoi(argbuf);
- if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
- cprintf("-ERR No such message.\r\n");
- return;
- }
-
- if (POP3->msgs[which_one - 1].deleted) {
- cprintf("-ERR You already deleted that message.\r\n");
- return;
- }
-
- // Flag the message as deleted. Will expunge during QUIT command.
- POP3->msgs[which_one - 1].deleted = 1;
- cprintf("+OK Message %d deleted.\r\n",
- which_one);
-}
-
-
-// Perform "UPDATE state" stuff
-void pop3_update(void) {
- int i;
- visit vbuf;
-
- long *deletemsgs = NULL;
- int num_deletemsgs = 0;
-
- // Remove messages marked for deletion
- if (POP3->num_msgs > 0) {
- deletemsgs = malloc(POP3->num_msgs * sizeof(long));
- for (i=0; i<POP3->num_msgs; ++i) {
- if (POP3->msgs[i].deleted) {
- deletemsgs[num_deletemsgs++] = POP3->msgs[i].msgnum;
- }
- }
- if (num_deletemsgs > 0) {
- CtdlDeleteMessages(MAILROOM, deletemsgs, num_deletemsgs, "");
- }
- free(deletemsgs);
- }
-
- // Set last read pointer
- if (POP3->num_msgs > 0) {
- CtdlLockGetCurrentUser();
- CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
- snprintf(vbuf.v_seen, sizeof vbuf.v_seen, "*:%ld", POP3->msgs[POP3->num_msgs-1].msgnum);
- CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
- CtdlPutCurrentUserLock();
- }
-
-}
-
-
-// RSET (reset, i.e. undelete any deleted messages) command
-void pop3_rset(char *argbuf) {
- int i;
-
- if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
- if (POP3->msgs[i].deleted) {
- POP3->msgs[i].deleted = 0;
- }
- }
- cprintf("+OK Reset completed.\r\n");
-}
-
-
-// LAST (Determine which message is the last unread message)
-void pop3_last(char *argbuf) {
- cprintf("+OK %d\r\n", POP3->lastseen + 1);
-}
-
-
-// CAPA is a command which tells the client which POP3 extensions are supported.
-void pop3_capa(void) {
- cprintf("+OK Capability list follows\r\n"
- "TOP\r\n"
- "USER\r\n"
- "UIDL\r\n"
- "IMPLEMENTATION %s\r\n"
- ".\r\n"
- ,
- CITADEL
- );
-}
-
-
-// UIDL (Universal IDentifier Listing) is easy. Our 'unique' message
-// identifiers are simply the Citadel message numbers in the database.
-void pop3_uidl(char *argbuf) {
- int i;
- int which_one;
-
- which_one = atoi(argbuf);
-
- // "list one" mode
- if (which_one > 0) {
- if (which_one > POP3->num_msgs) {
- cprintf("-ERR no such message, only %d are here\r\n", POP3->num_msgs);
- return;
- }
- else if (POP3->msgs[which_one-1].deleted) {
- cprintf("-ERR Sorry, you deleted that message.\r\n");
- return;
- }
- else {
- cprintf("+OK %d %ld\r\n", which_one, POP3->msgs[which_one-1].msgnum);
- return;
- }
- }
-
- // "list all" (scan listing) mode
- else {
- cprintf("+OK Here's your mail:\r\n");
- if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
- if (! POP3->msgs[i].deleted) {
- cprintf("%d %ld\r\n", i+1, POP3->msgs[i].msgnum);
- }
- }
- cprintf(".\r\n");
- }
-}
-
-
-// implements the STLS command (Citadel API version)
-void pop3_stls(void) {
- char ok_response[SIZ];
- char nosup_response[SIZ];
- char error_response[SIZ];
-
- sprintf(ok_response, "+OK Begin TLS negotiation now\r\n");
- sprintf(nosup_response, "-ERR TLS not supported here\r\n");
- sprintf(error_response, "-ERR Internal error\r\n");
- CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
-}
-
-
-// Main command loop for POP3 sessions.
-void pop3_command_loop(void) {
- char cmdbuf[SIZ];
-
- time(&CC->lastcmd);
- memset(cmdbuf, 0, sizeof cmdbuf); // Clear it, just in case
- if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
- syslog(LOG_INFO, "pop3: client disconnected; ending session.");
- CC->kill_me = KILLME_CLIENT_DISCONNECTED;
- return;
- }
- if (!strncasecmp(cmdbuf, "PASS", 4)) {
- syslog(LOG_DEBUG, "pop3: PASS...");
- }
- else {
- syslog(LOG_DEBUG, "pop3: %s", cmdbuf);
- }
- while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
-
- if (!strncasecmp(cmdbuf, "NOOP", 4)) {
- cprintf("+OK No operation.\r\n");
- }
-
- else if (!strncasecmp(cmdbuf, "CAPA", 4)) {
- pop3_capa();
- }
-
- else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
- cprintf("+OK Goodbye...\r\n");
- pop3_update();
- CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
- return;
- }
-
- else if (!strncasecmp(cmdbuf, "USER", 4)) {
- pop3_user(&cmdbuf[5]);
- }
-
- else if (!strncasecmp(cmdbuf, "PASS", 4)) {
- pop3_pass(&cmdbuf[5]);
- }
-
-#ifdef HAVE_OPENSSL
- else if (!strncasecmp(cmdbuf, "STLS", 4)) {
- pop3_stls();
- }
-#endif
-
- else if (!CC->logged_in) {
- cprintf("-ERR Not logged in.\r\n");
- }
-
- else if (CC->nologin) {
- cprintf("-ERR System busy, try later.\r\n");
- CC->kill_me = KILLME_NOLOGIN;
- }
-
- else if (!strncasecmp(cmdbuf, "LIST", 4)) {
- pop3_list(&cmdbuf[5]);
- }
-
- else if (!strncasecmp(cmdbuf, "STAT", 4)) {
- pop3_stat(&cmdbuf[5]);
- }
-
- else if (!strncasecmp(cmdbuf, "RETR", 4)) {
- pop3_retr(&cmdbuf[5]);
- }
-
- else if (!strncasecmp(cmdbuf, "DELE", 4)) {
- pop3_dele(&cmdbuf[5]);
- }
-
- else if (!strncasecmp(cmdbuf, "RSET", 4)) {
- pop3_rset(&cmdbuf[5]);
- }
-
- else if (!strncasecmp(cmdbuf, "UIDL", 4)) {
- pop3_uidl(&cmdbuf[5]);
- }
-
- else if (!strncasecmp(cmdbuf, "TOP", 3)) {
- pop3_top(&cmdbuf[4]);
- }
-
- else if (!strncasecmp(cmdbuf, "LAST", 4)) {
- pop3_last(&cmdbuf[4]);
- }
-
- else {
- cprintf("-ERR I'm afraid I can't do that.\r\n");
- }
-
-}
-
-const char *CitadelServicePop3="POP3";
-const char *CitadelServicePop3S="POP3S";
-
-
-CTDL_MODULE_INIT(pop3)
-{
- if(!threading)
- {
- CtdlRegisterServiceHook(CtdlGetConfigInt("c_pop3_port"),
- NULL,
- pop3_greeting,
- pop3_command_loop,
- NULL,
- CitadelServicePop3);
-#ifdef HAVE_OPENSSL
- CtdlRegisterServiceHook(CtdlGetConfigInt("c_pop3s_port"),
- NULL,
- pop3s_greeting,
- pop3_command_loop,
- NULL,
- CitadelServicePop3S);
-#endif
- CtdlRegisterSessionHook(pop3_cleanup_function, EVT_STOP, PRIO_STOP + 30);
- }
-
- /* return our module name for the log */
- return "pop3";
-}
+++ /dev/null
-/*
- * 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.
- */
-
-struct pop3msg {
- long msgnum;
- size_t rfc822_length;
- int deleted;
-};
-
-struct citpop3 { /* Information about the current session */
- struct pop3msg *msgs; /* Array of message pointers */
- int num_msgs; /* Number of messages in array */
- int lastseen; /* Offset of last-read message in array */
-};
- /* Note: the "lastseen" is represented as the
- * offset in this array (zero-based), so when
- * displaying it to a POP3 client, it must be
- * incremented by one.
- */
-
-#define POP3 ((struct citpop3 *)CC->session_specific_data)
-
-void pop3_cleanup_function(void);
-void pop3_greeting(void);
-void pop3_user(char *argbuf);
-void pop3_pass(char *argbuf);
-void pop3_list(char *argbuf);
-void pop3_command_loop(void);
-void pop3_login(void);
-
+++ /dev/null
-/*
- * Consolidate mail from remote POP3 accounts.
- *
- * Copyright (c) 2007-2021 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 as published
- * by the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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.
- */
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <sysconfig.h>
-#include <time.h>
-#include <ctype.h>
-#include <string.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <libcitadel.h>
-#include <curl/curl.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "ctdl_module.h"
-#include "clientsocket.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "database.h"
-#include "citadel_dirs.h"
-
-struct p3cq { // module-local queue of pop3 client work that needs processing
- struct p3cq *next;
- char *room;
- char *host;
- char *user;
- char *pass;
- int keep;
- long interval;
-};
-
-static int doing_pop3client = 0;
-struct p3cq *p3cq = NULL;
-
-/*
- * Process one mailbox.
- */
-void pop3client_one_mailbox(char *room, const char *host, const char *user, const char *pass, int keep, long interval) {
- syslog(LOG_DEBUG, "pop3client: room=<%s> host=<%s> user=<%s> keep=<%d> interval=<%ld>", room, host, user, keep, interval);
-
- char url[SIZ];
- CURL *curl;
- CURLcode res = CURLE_OK;
- StrBuf *Uidls = NULL;
- int i;
- char cmd[1024];
-
- curl = curl_easy_init();
- if (!curl) {
- return;
- }
-
- Uidls = NewStrBuf();
-
- curl_easy_setopt(curl, CURLOPT_USERNAME, user);
- curl_easy_setopt(curl, CURLOPT_PASSWORD, pass);
- curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
- curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
- curl_easy_setopt(curl, CURLOPT_TIMEOUT, 15);
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlFillStrBuf_callback); // What to do with downloaded data
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, Uidls); // Give it our StrBuf to work with
- curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "UIDL");
-
- /* Try POP3S (SSL encrypted) first */
- snprintf(url, sizeof url, "pop3s://%s", host);
- curl_easy_setopt(curl, CURLOPT_URL, url);
- res = curl_easy_perform(curl);
- if (res == CURLE_OK) {
- }
- else {
- syslog(LOG_DEBUG, "pop3client: POP3S connection failed: %s , trying POP3 next", curl_easy_strerror(res));
- snprintf(url, sizeof url, "pop3://%s", host); // try unencrypted next
- curl_easy_setopt(curl, CURLOPT_URL, url);
- FlushStrBuf(Uidls);
- res = curl_easy_perform(curl);
- }
-
- if (res != CURLE_OK) {
- syslog(LOG_DEBUG, "pop3client: POP3 connection failed: %s", curl_easy_strerror(res));
- curl_easy_cleanup(curl);
- FreeStrBuf(&Uidls);
- return;
- }
-
- // If we got this far, a connection was established, we know whether it's pop3s or pop3, and UIDL is supported.
- // Now go through the UIDL list and look for messages.
-
- int num_msgs = num_tokens(ChrPtr(Uidls), '\n');
- syslog(LOG_DEBUG, "pop3client: there are %d messages", num_msgs);
- for (i=0; i<num_msgs; ++i) {
- char oneuidl[1024];
- extract_token(oneuidl, ChrPtr(Uidls), i, '\n', sizeof oneuidl);
- if (strlen(oneuidl) > 2) {
- if (oneuidl[strlen(oneuidl)-1] == '\r') {
- oneuidl[strlen(oneuidl)-1] = 0;
- }
- int this_msg = atoi(oneuidl);
- char *c = strchr(oneuidl, ' ');
- if (c) strcpy(oneuidl, ++c);
-
- // Make up the Use Table record so we can check if we've already seen this message.
- StrBuf *UT = NewStrBuf();
- StrBufPrintf(UT, "pop3/%s/%s:%s@%s", room, oneuidl, user, host);
- int already_seen = CheckIfAlreadySeen(UT);
- FreeStrBuf(&UT);
-
- // Only fetch the message if we haven't seen it before.
- if (already_seen == 0) {
- StrBuf *TheMsg = NewStrBuf();
- snprintf(cmd, sizeof cmd, "RETR %d", this_msg);
- curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, cmd);
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, TheMsg);
- res = curl_easy_perform(curl);
- if (res == CURLE_OK) {
- struct CtdlMessage *msg = convert_internet_message_buf(&TheMsg);
- CtdlSubmitMsg(msg, NULL, room);
- CM_Free(msg);
- }
- else {
- FreeStrBuf(&TheMsg);
- }
-
- // Unless the configuration says to keep the message on the server, delete it.
- if (keep == 0) {
- snprintf(cmd, sizeof cmd, "DELE %d", this_msg);
- curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, cmd);
- res = curl_easy_perform(curl);
- }
- }
- else {
- syslog(LOG_DEBUG, "pop3client: %s has already been retrieved", oneuidl);
- }
- }
- }
-
- curl_easy_cleanup(curl);
- FreeStrBuf(&Uidls);
- return;
-}
-
-
-// Scan a room's netconfig looking for RSS feed parsing requests
-//
-void pop3client_scan_room(struct ctdlroom *qrbuf, void *data)
-{
- char *serialized_config = NULL;
- int num_configs = 0;
- char cfgline[SIZ];
- char cfgelement[SIZ];
- int i = 0;
-
- serialized_config = LoadRoomNetConfigFile(qrbuf->QRnumber);
- if (!serialized_config) {
- return;
- }
-
- num_configs = num_tokens(serialized_config, '\n');
- for (i=0; i<num_configs; ++i) {
- extract_token(cfgline, serialized_config, i, '\n', sizeof cfgline);
- if (!strncasecmp(cfgline, HKEY("pop3client|"))) {
- struct p3cq *pptr = malloc(sizeof(struct p3cq));
- pptr->next = p3cq;
- p3cq = pptr;
- p3cq->room = strdup(qrbuf->QRname);
- extract_token(cfgelement, cfgline, 1, '|', sizeof cfgelement);
- p3cq->host = strdup(cfgelement);
- extract_token(cfgelement, cfgline, 2, '|', sizeof cfgelement);
- p3cq->user = strdup(cfgelement);
- extract_token(cfgelement, cfgline, 3, '|', sizeof cfgelement);
- p3cq->pass = strdup(cfgelement);
- p3cq->keep = extract_int(cfgline, 4);
- p3cq->interval = extract_long(cfgline, 5);
- }
- }
-
- free(serialized_config);
-}
-
-
-void pop3client_scan(void) {
- static time_t last_run = 0L;
- time_t fastest_scan;
- struct p3cq *pptr = NULL;
-
- if (CtdlGetConfigLong("c_pop3_fastest") < CtdlGetConfigLong("c_pop3_fetch")) {
- fastest_scan = CtdlGetConfigLong("c_pop3_fastest");
- }
- else {
- fastest_scan = CtdlGetConfigLong("c_pop3_fetch");
- }
-
- /*
- * Run POP3 aggregation no more frequently than once every n seconds
- */
- if ( (time(NULL) - last_run) < fastest_scan ) {
- return;
- }
-
- /*
- * This is a simple concurrency check to make sure only one pop3client
- * run is done at a time. We could do this with a mutex, but since we
- * don't really require extremely fine granularity here, we'll do it
- * with a static variable instead.
- */
- if (doing_pop3client) return;
- doing_pop3client = 1;
-
- syslog(LOG_DEBUG, "pop3client: scan started");
- CtdlForEachRoom(pop3client_scan_room, NULL);
-
- /*
- * We have to queue and process in separate phases, otherwise we leave a cursor open
- */
- syslog(LOG_DEBUG, "pop3client: processing started");
- while (p3cq != NULL) {
- pptr = p3cq;
- p3cq = p3cq->next;
-
- pop3client_one_mailbox(pptr->room, pptr->host, pptr->user, pptr->pass, pptr->keep, pptr->interval);
-
- free(pptr->room);
- free(pptr->host);
- free(pptr->user);
- free(pptr->pass);
- free(pptr);
- }
-
- syslog(LOG_DEBUG, "pop3client: ended");
- last_run = time(NULL);
- doing_pop3client = 0;
-}
-
-
-CTDL_MODULE_INIT(pop3client)
-{
- if (!threading)
- {
- CtdlRegisterSessionHook(pop3client_scan, EVT_TIMER, PRIO_AGGR + 50);
- }
-
- /* return our module id for the log */
- return "pop3client";
-}
+++ /dev/null
-/*
- * This module handles instant messaging between users.
- *
- * Copyright (c) 2012 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.
- */
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <assert.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "msgbase.h"
-#include "user_ops.h"
-#include "ctdl_module.h"
-
-struct chatmsg {
- struct chatmsg *next;
- time_t timestamp;
- int seq;
- long roomnum;
- char *sender;
- char *msgtext;
-};
-
-struct chatmsg *first_chat_msg = NULL;
-struct chatmsg *last_chat_msg = NULL;
-
-
-/*
- * Periodically called for housekeeping. Expire old chat messages so they don't take up memory forever.
- */
-void roomchat_timer(void) {
- struct chatmsg *ptr;
-
- begin_critical_section(S_CHATQUEUE);
-
- while ((first_chat_msg != NULL) && ((time(NULL) - first_chat_msg->timestamp) > 300)) {
- ptr = first_chat_msg->next;
- free(first_chat_msg->sender);
- free(first_chat_msg->msgtext);
- free(first_chat_msg);
- first_chat_msg = ptr;
- if (first_chat_msg == NULL) {
- last_chat_msg = NULL;
- }
- }
-
- end_critical_section(S_CHATQUEUE);
-}
-
-
-/*
- * Perform shutdown-related activities...
- */
-void roomchat_shutdown(void) {
- /* if we ever start logging chats, we have to flush them to disk here .*/
-}
-
-
-/*
- * Add a message into the chat queue
- */
-void add_to_chat_queue(char *msg) {
- static int seq = 0;
-
- struct chatmsg *m = malloc(sizeof(struct chatmsg));
- if (!m) return;
-
- m->next = NULL;
- m->timestamp = time(NULL);
- m->roomnum = CC->room.QRnumber;
- m->sender = strdup(CC->user.fullname);
- m->msgtext = strdup(msg);
-
- if ((m->sender == NULL) || (m->msgtext == NULL)) {
- free(m->sender);
- free(m->msgtext);
- free(m);
- return;
- }
-
- begin_critical_section(S_CHATQUEUE);
- m->seq = ++seq;
-
- if (first_chat_msg == NULL) {
- assert(last_chat_msg == NULL);
- first_chat_msg = m;
- last_chat_msg = m;
- }
- else {
- assert(last_chat_msg != NULL);
- assert(last_chat_msg->next == NULL);
- last_chat_msg->next = m;
- last_chat_msg = m;
- }
-
- end_critical_section(S_CHATQUEUE);
-}
-
-
-/*
- * Transmit a message into a room chat
- */
-void roomchat_send(char *argbuf) {
- char buf[1024];
-
- if ((CC->cs_flags & CS_CHAT) == 0) {
- cprintf("%d Session is not in chat mode.\n", ERROR);
- return;
- }
-
- cprintf("%d send now\n", SEND_LISTING);
- while (client_getln(buf, sizeof buf) >= 0 && strcmp(buf, "000")) {
- add_to_chat_queue(buf);
- }
-}
-
-
-/*
- * Poll room for incoming chat messages
- */
-void roomchat_poll(char *argbuf) {
- int newer_than = 0;
- struct chatmsg *found = NULL;
- struct chatmsg *ptr = NULL;
-
- newer_than = extract_int(argbuf, 1);
-
- if ((CC->cs_flags & CS_CHAT) == 0) {
- cprintf("%d Session is not in chat mode.\n", ERROR);
- return;
- }
-
- begin_critical_section(S_CHATQUEUE);
- for (ptr = first_chat_msg; ((ptr != NULL) && (found == NULL)); ptr = ptr->next) {
- if ((ptr->seq > newer_than) && (ptr->roomnum == CC->room.QRnumber)) {
- found = ptr;
- }
- }
- end_critical_section(S_CHATQUEUE);
-
- if (found == NULL) {
- cprintf("%d no messages\n", ERROR + MESSAGE_NOT_FOUND);
- return;
- }
-
- cprintf("%d %d|%ld|%s\n", LISTING_FOLLOWS, found->seq, found->timestamp, found->sender);
- cprintf("%s\n", found->msgtext);
- cprintf("000\n");
-}
-
-
-
-/*
- * list users in chat in this room
- */
-void roomchat_rwho(char *argbuf) {
- struct CitContext *nptr;
- int nContexts, i;
-
- if ((CC->cs_flags & CS_CHAT) == 0) {
- cprintf("%d Session is not in chat mode.\n", ERROR);
- return;
- }
-
- cprintf("%d%c \n", LISTING_FOLLOWS, CtdlCheckExpress() );
-
- nptr = CtdlGetContextArray(&nContexts) ; // grab a copy of the wholist
- if (nptr) {
- for (i=0; i<nContexts; i++) { // list the users
- if ( (nptr[i].room.QRnumber == CC->room.QRnumber)
- && (nptr[i].cs_flags & CS_CHAT)
- ) {
- cprintf("%s\n", nptr[i].user.fullname);
- }
- }
- free(nptr); // free our copy
- }
-
- cprintf("000\n");
-}
-
-
-
-/*
- * Participate in real time chat in a room
- */
-void cmd_rcht(char *argbuf)
-{
- char subcmd[16];
-
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
-
- if (!strcasecmp(subcmd, "enter")) {
- CC->cs_flags |= CS_CHAT;
- cprintf("%d Entering chat mode.\n", CIT_OK);
- }
- else if (!strcasecmp(subcmd, "exit")) {
- CC->cs_flags &= ~CS_CHAT;
- cprintf("%d Exiting chat mode.\n", CIT_OK);
- }
- else if (!strcasecmp(subcmd, "send")) {
- roomchat_send(argbuf);
- }
- else if (!strcasecmp(subcmd, "poll")) {
- roomchat_poll(argbuf);
- }
- else if (!strcasecmp(subcmd, "rwho")) {
- roomchat_rwho(argbuf);
- }
- else {
- cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
- }
-}
-
-
-CTDL_MODULE_INIT(roomchat)
-{
- if (!threading)
- {
- CtdlRegisterProtoHook(cmd_rcht, "RCHT", "Participate in real time chat in a room");
- CtdlRegisterSessionHook(roomchat_timer, EVT_TIMER, PRIO_CLEANUP + 400);
- CtdlRegisterSessionHook(roomchat_shutdown, EVT_SHUTDOWN, PRIO_SHUTDOWN + 55);
- }
-
- /* return our module name for the log */
- return "roomchat";
-}
+++ /dev/null
-/*
- * Bring external RSS and/or Atom feeds into rooms. This module implements a
- * very loose parser that scrapes both kinds of feeds and is not picky about
- * the standards compliance of the source data.
- *
- * Copyright (c) 2007-2021 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.
- */
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <time.h>
-#include <ctype.h>
-#include <string.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <expat.h>
-#include <curl/curl.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "threads.h"
-#include "ctdl_module.h"
-#include "msgbase.h"
-#include "parsedate.h"
-#include "database.h"
-#include "citadel_dirs.h"
-#include "context.h"
-#include "internet_addressing.h"
-
-struct rssroom {
- struct rssroom *next;
- char *room;
-};
-
-struct rssurl {
- struct rssurl *next;
- char *url;
- struct rssroom *rooms;
-};
-
-struct rssparser {
- StrBuf *CData;
- struct CtdlMessage *msg;
- char *link;
- char *description;
- char *item_id;
- struct rssroom *rooms;
-};
-
-time_t last_run = 0L;
-struct rssurl *rsstodo = NULL;
-
-
-// This handler is called whenever an XML tag opens.
-//
-void rss_start_element(void *data, const char *el, const char **attribute) {
- struct rssparser *r = (struct rssparser *)data;
- int i;
-
- if (server_shutting_down) return; // shunt the whole operation if we're exiting
-
- if (
- (!strcasecmp(el, "entry"))
- || (!strcasecmp(el, "item"))
- ) {
- // this is the start of a new item(rss) or entry(atom)
- if (r->msg != NULL) {
- CM_Free(r->msg);
- r->msg = NULL;
- }
- r->msg = malloc(sizeof(struct CtdlMessage));
- memset(r->msg, 0, sizeof(struct CtdlMessage));
- r->msg->cm_magic = CTDLMESSAGE_MAGIC;
- r->msg->cm_anon_type = MES_NORMAL;
- r->msg->cm_format_type = FMT_RFC822;
- }
-
- else if (!strcasecmp(el, "link")) { // atom feeds have the link as an attribute
- for(i = 0; attribute[i]; i += 2) {
- if (!strcasecmp(attribute[i], "href")) {
- if (r->link != NULL) {
- free(r->link);
- r->link = NULL;
- }
- r->link = strdup(attribute[i+1]);
- striplt(r->link);
- }
- }
- }
-}
-
-
-// This handler is called whenever an XML tag closes.
-//
-void rss_end_element(void *data, const char *el) {
- struct rssparser *r = (struct rssparser *)data;
- StrBuf *encoded_field;
-
- if (server_shutting_down) return; // shunt the whole operation if we're exiting
-
- if (StrLength(r->CData) > 0) { // strip leading/trailing whitespace from field
- StrBufTrim(r->CData);
- }
-
- if ( // end of a new item(rss) or entry(atom)
- (!strcasecmp(el, "entry"))
- || (!strcasecmp(el, "item"))
- ) {
- if (r->msg != NULL) { // Save the message to the rooms
-
- // use the link as an item id if nothing else is available
- if ((r->item_id == NULL) && (r->link != NULL)) {
- r->item_id = strdup(r->link);
- }
-
- // check the use table
- StrBuf *u = NewStrBuf();
- StrBufAppendPrintf(u, "rss/%s", r->item_id);
- int already_seen = CheckIfAlreadySeen(u);
- FreeStrBuf(&u);
-
- if (already_seen == 0) {
-
- // Compose the message text
- StrBuf *TheMessage = NewStrBuf();
- StrBufAppendPrintf(TheMessage,
- "Content-type: text/html\n\n"
- "\n\n"
- "<html><head></head><body>"
- );
-
- if (r->description != NULL) {
- StrBufAppendPrintf(TheMessage, "%s<br><br>\r\n", r->description);
- free(r->description);
- r->description = NULL;
- }
-
- if (r->link != NULL) {
- StrBufAppendPrintf(TheMessage, "<a href=\"%s\">%s</a>\r\n", r->link, r->link);
- free(r->link);
- r->link = NULL;
- }
-
- StrBufAppendPrintf(TheMessage, "</body></html>\r\n");
- CM_SetField(r->msg, eMesageText, ChrPtr(TheMessage), StrLength(TheMessage));
- FreeStrBuf(&TheMessage);
-
- if (CM_IsEmpty(r->msg, eAuthor)) {
- CM_SetField(r->msg, eAuthor, HKEY("rss"));
- }
-
- if (CM_IsEmpty(r->msg, eTimestamp)) {
- CM_SetFieldLONG(r->msg, eTimestamp, time(NULL));
- }
-
- // Save it to the room(s)
- struct rssroom *rr = NULL;
- long msgnum = (-1);
- for (rr=r->rooms; rr!=NULL; rr=rr->next) {
- if (rr == r->rooms) {
- msgnum = CtdlSubmitMsg(r->msg, NULL, rr->room); // in first room, save msg
- }
- else {
- CtdlSaveMsgPointerInRoom(rr->room, msgnum, 0, NULL); // elsewhere, save a pointer
- }
- syslog(LOG_DEBUG, "rssclient: saved message %ld to %s", msgnum, rr->room);
- }
- }
- else {
- syslog(LOG_DEBUG, "rssclient: already seen %s", r->item_id);
- }
-
- CM_Free(r->msg);
- r->msg = NULL;
- }
-
- if (r->item_id != NULL) {
- free(r->item_id);
- r->item_id = NULL;
- }
- }
-
- else if (!strcasecmp(el, "title")) { // item subject (rss and atom)
- if ((r->msg != NULL) && (CM_IsEmpty(r->msg, eMsgSubject))) {
- encoded_field = NewStrBuf();
- StrBufRFC2047encode(&encoded_field, r->CData);
- CM_SetAsFieldSB(r->msg, eMsgSubject, &encoded_field);
- }
- }
-
- else if (!strcasecmp(el, "creator")) { // <creator> can be used if <author> is not present
- if ((r->msg != NULL) && (CM_IsEmpty(r->msg, eAuthor))) {
- encoded_field = NewStrBuf();
- StrBufRFC2047encode(&encoded_field, r->CData);
- CM_SetAsFieldSB(r->msg, eAuthor, &encoded_field);
- }
- }
-
- else if (!strcasecmp(el, "author")) { // <author> supercedes <creator> if both are present
- if (r->msg != NULL) {
- encoded_field = NewStrBuf();
- StrBufRFC2047encode(&encoded_field, r->CData);
- CM_SetAsFieldSB(r->msg, eAuthor, &encoded_field);
- }
- }
-
- else if (!strcasecmp(el, "pubdate")) { // date/time stamp (rss) Sat, 25 Feb 2017 14:28:01 EST
- if ((r->msg)&&(r->msg->cm_fields[eTimestamp]==NULL)) {
- CM_SetFieldLONG(r->msg, eTimestamp, parsedate(ChrPtr(r->CData)));
- }
- }
-
- else if (!strcasecmp(el, "updated")) { // date/time stamp (atom) 2003-12-13T18:30:02Z
- if ((r->msg)&&(r->msg->cm_fields[eTimestamp]==NULL)) {
- struct tm t;
- char zulu;
- memset(&t, 0, sizeof t);
- sscanf(ChrPtr(r->CData), "%d-%d-%dT%d:%d:%d%c", &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec, &zulu);
- t.tm_year -= 1900;
- t.tm_mon -= 1;
- CM_SetFieldLONG(r->msg, eTimestamp, mktime(&t));
- }
- }
-
- else if (!strcasecmp(el, "link")) { // link to story (rss)
- if (r->link != NULL) {
- free(r->link);
- r->link = NULL;
- }
- r->link = strdup(ChrPtr(r->CData));
- }
-
- else if (
- (!strcasecmp(el, "guid")) // unique item id (rss)
- || (!strcasecmp(el, "id")) // unique item id (atom)
- ) {
- if (r->item_id != NULL) {
- free(r->item_id);
- r->item_id = NULL;
- }
- r->item_id = strdup(ChrPtr(r->CData));
- }
-
- else if (
- (!strcasecmp(el, "description")) // message text (rss)
- || (!strcasecmp(el, "summary")) // message text (atom)
- || (!strcasecmp(el, "content")) // message text (atom)
- ) {
- if (r->description != NULL) {
- free(r->description);
- r->description = NULL;
- }
- r->description = strdup(ChrPtr(r->CData));
- }
-
- if (r->CData != NULL) {
- FreeStrBuf(&r->CData);
- r->CData = NULL;
- }
-}
-
-
-// This handler is called whenever data appears between opening and closing tags.
-//
-void rss_handle_data(void *data, const char *content, int length)
-{
- struct rssparser *r = (struct rssparser *)data;
-
- if (r->CData == NULL) {
- r->CData = NewStrBuf();
- }
-
- StrBufAppendBufPlain(r->CData, content, length, 0);
-}
-
-
-// Feed has been downloaded, now parse it.
-//
-void rss_parse_feed(StrBuf *Feed, struct rssroom *rooms)
-{
- struct rssparser r;
-
- memset(&r, 0, sizeof r);
- r.rooms = rooms;
- XML_Parser p = XML_ParserCreate("UTF-8");
- XML_SetElementHandler(p, rss_start_element, rss_end_element);
- XML_SetCharacterDataHandler(p, rss_handle_data);
- XML_SetUserData(p, (void *)&r);
- XML_Parse(p, ChrPtr(Feed), StrLength(Feed), XML_TRUE);
- XML_ParserFree(p);
-}
-
-
-// Add a feed/room pair into the todo list
-//
-void rssclient_push_todo(char *rssurl, char *roomname)
-{
- struct rssurl *r = NULL;
- struct rssurl *thisone = NULL;
- struct rssroom *newroom = NULL;
-
- syslog(LOG_DEBUG, "rssclient: will fetch %s to %s", rssurl, roomname);
-
- for (r=rsstodo; r!=NULL; r=r->next) {
- if (!strcasecmp(r->url, rssurl)) {
- thisone = r;
- }
- }
-
- if (thisone == NULL) {
- thisone = malloc(sizeof(struct rssurl));
- thisone->url = strdup(rssurl);
- thisone->rooms = NULL;
- thisone->next = rsstodo;
- rsstodo = thisone;
- }
-
- newroom = malloc(sizeof(struct rssroom));
- newroom->room = strdup(roomname);
- newroom->next = thisone->rooms;
- thisone->rooms = newroom;
-}
-
-
-// pull one feed (possibly multiple rooms)
-//
-void rss_pull_one_feed(struct rssurl *url)
-{
- CURL *curl;
- CURLcode res;
- StrBuf *Downloaded = NULL;
-
- syslog(LOG_DEBUG, "rssclient: fetching %s", url->url);
-
- curl = curl_easy_init();
- if (!curl) {
- return;
- }
-
- Downloaded = NewStrBuf();
-
- curl_easy_setopt(curl, CURLOPT_URL, url->url);
- curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
- curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
- curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // Follow redirects
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlFillStrBuf_callback); // What to do with downloaded data
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, Downloaded); // Give it our StrBuf to work with
- curl_easy_setopt(curl, CURLOPT_TIMEOUT, 20L); // Time out after 20 seconds
- res = curl_easy_perform(curl); // Perform the request
- if (res != CURLE_OK) {
- syslog(LOG_WARNING, "rssclient: failed to load feed: %s", curl_easy_strerror(res));
- }
- curl_easy_cleanup(curl);
-
- rss_parse_feed(Downloaded, url->rooms); // parse the feed
- FreeStrBuf(&Downloaded); // free the downloaded feed data
-}
-
-
-// We have a list, now download the feeds
-//
-void rss_pull_feeds(void)
-{
- struct rssurl *r;
- struct rssroom *rr;
-
- while ((rsstodo != NULL) && (!server_shutting_down)) {
- rss_pull_one_feed(rsstodo);
- r = rsstodo;
- rsstodo = rsstodo->next;
- while (r->rooms != NULL) {
- rr = r->rooms;
- r->rooms = r->rooms->next;
- free(rr->room);
- free(rr);
- }
- free(r->url);
- free(r);
- }
-}
-
-
-// Scan a room's netconfig looking for RSS feed parsing requests
-//
-void rssclient_scan_room(struct ctdlroom *qrbuf, void *data)
-{
- char *serialized_config = NULL;
- int num_configs = 0;
- char cfgline[SIZ];
- int i = 0;
-
- if (server_shutting_down) return;
-
- serialized_config = LoadRoomNetConfigFile(qrbuf->QRnumber);
- if (!serialized_config) {
- return;
- }
-
- num_configs = num_tokens(serialized_config, '\n');
- for (i=0; i<num_configs; ++i) {
- extract_token(cfgline, serialized_config, i, '\n', sizeof cfgline);
- if (!strncasecmp(cfgline, HKEY("rssclient|"))) {
- strcpy(cfgline, &cfgline[10]);
- char *vbar = strchr(cfgline, '|');
- if (vbar != NULL) {
- *vbar = 0;
- }
- rssclient_push_todo(cfgline, qrbuf->QRname);
- }
- }
-
- free(serialized_config);
-}
-
-
-/*
- * Scan for rooms that have RSS client requests configured
- */
-void rssclient_scan(void) {
- time_t now = time(NULL);
-
- /* Run no more than once every 15 minutes. */
- if ((now - last_run) < 900) {
- syslog(LOG_DEBUG,
- "rssclient: polling interval not yet reached; last run was %ldm%lds ago",
- ((now - last_run) / 60),
- ((now - last_run) % 60)
- );
- return;
- }
-
- syslog(LOG_DEBUG, "rssclient: started");
- CtdlForEachRoom(rssclient_scan_room, NULL);
- rss_pull_feeds();
- syslog(LOG_DEBUG, "rssclient: ended");
- last_run = time(NULL);
- return;
-}
-
-
-CTDL_MODULE_INIT(rssclient)
-{
- if (!threading)
- {
- syslog(LOG_INFO, "rssclient: using %s", curl_version());
- CtdlRegisterSessionHook(rssclient_scan, EVT_TIMER, PRIO_AGGR + 300);
- }
- return "rssclient";
-}
+++ /dev/null
-/*
- * This module implements server commands related to the display and
- * manipulation of the "Who's online" list.
- *
- * Copyright (c) 1987-2019 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.
- *
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "ctdl_module.h"
-
-/* Don't show the names of private rooms unless the viewing
- * user also knows the rooms.
- */
-void GenerateRoomDisplay(char *real_room,
- CitContext *viewed,
- CitContext *viewer) {
-
- int ra;
-
- strcpy(real_room, viewed->room.QRname);
- if (viewed->room.QRflags & QR_MAILBOX) {
- strcpy(real_room, &real_room[11]);
- }
- if (viewed->room.QRflags & QR_PRIVATE) {
- CtdlRoomAccess(&viewed->room, &viewer->user, &ra, NULL);
- if ( (ra & UA_KNOWN) == 0) {
- strcpy(real_room, " ");
- }
- }
-
- if (viewed->cs_flags & CS_CHAT) {
- while (strlen(real_room) < 14) {
- strcat(real_room, " ");
- }
- strcpy(&real_room[14], "<chat>");
- }
-
-}
-
-
-
-/*
- * display who's online
- */
-void cmd_rwho(char *argbuf) {
- struct CitContext *nptr;
- int nContexts, i;
- int spoofed = 0;
- int aide;
- char room[ROOMNAMELEN];
- char flags[5];
-
- /* So that we don't keep the context list locked for a long time
- * we create a copy of it first
- */
- nptr = CtdlGetContextArray(&nContexts) ;
- if (!nptr)
- {
- /* Couldn't malloc so we have to bail but stick to the protocol */
- cprintf("%d%c \n", LISTING_FOLLOWS, CtdlCheckExpress() );
- cprintf("000\n");
- return;
- }
-
- aide = ( (CC->user.axlevel >= AxAideU) || (CC->internal_pgm) ) ;
- cprintf("%d%c \n", LISTING_FOLLOWS, CtdlCheckExpress() );
-
- for (i=0; i<nContexts; i++) {
- flags[0] = '\0';
- spoofed = 0;
-
- if (!aide && nptr[i].state == CON_SYS)
- continue;
-
- if (!aide && nptr[i].kill_me != 0)
- continue;
-
- if (nptr[i].cs_flags & CS_POSTING) {
- strcat(flags, "*");
- }
- else {
- strcat(flags, ".");
- }
-
- GenerateRoomDisplay(room, &nptr[i], CC);
-
- if ((aide) && (spoofed)) {
- strcat(flags, "+");
- }
-
- if ((nptr[i].cs_flags & CS_STEALTH) && (aide)) {
- strcat(flags, "-");
- }
-
- if (((nptr[i].cs_flags&CS_STEALTH)==0) || (aide)) {
-
- cprintf("%d|%s|%s|%s|%s|%ld|%s|%s|",
- nptr[i].cs_pid, nptr[i].curr_user, room,
- nptr[i].cs_host, nptr[i].cs_clientname,
- (long)(nptr[i].lastidle),
- nptr[i].lastcmdname, flags
- );
-
- cprintf("|"); // no spoofed user names anymore
- cprintf("|"); // no spoofed room names anymore
- cprintf("|"); // no spoofed host names anymore
-
- cprintf("%d\n", nptr[i].logged_in);
- }
- }
-
- /* release our copy of the context list */
- free(nptr);
-
- /* Now it's magic time. Before we finish, call any EVT_RWHO hooks
- * so that external paging modules such as serv_icq can add more
- * content to the Wholist.
- */
- PerformSessionHooks(EVT_RWHO);
- cprintf("000\n");
-}
-
-
-/*
- * enter or exit "stealth mode"
- */
-void cmd_stel(char *cmdbuf)
-{
- int requested_mode;
-
- requested_mode = extract_int(cmdbuf,0);
-
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- if (requested_mode == 1) {
- CC->cs_flags = CC->cs_flags | CS_STEALTH;
- PerformSessionHooks(EVT_STEALTH);
- }
- if (requested_mode == 0) {
- CC->cs_flags = CC->cs_flags & ~CS_STEALTH;
- PerformSessionHooks(EVT_UNSTEALTH);
- }
-
- cprintf("%d %d\n", CIT_OK,
- ((CC->cs_flags & CS_STEALTH) ? 1 : 0) );
-}
-
-
-CTDL_MODULE_INIT(rwho)
-{
- if(!threading)
- {
- CtdlRegisterProtoHook(cmd_rwho, "RWHO", "Display who is online");
- CtdlRegisterProtoHook(cmd_stel, "STEL", "Enter/exit stealth mode");
- //CtdlRegisterSessionHook(dead_io_check, EVT_TIMER, PRIO_QUEUE + 50);
-
- }
-
- /* return our module name for the log */
- return "rwho";
-}
+++ /dev/null
-// This module is an SMTP and ESMTP server for the Citadel system.
-// It is compliant with all of the following:
-//
-// RFC 821 - Simple Mail Transfer Protocol
-// RFC 876 - Survey of SMTP Implementations
-// RFC 1047 - Duplicate messages and SMTP
-// RFC 1652 - 8 bit MIME
-// RFC 1869 - Extended Simple Mail Transfer Protocol
-// RFC 1870 - SMTP Service Extension for Message Size Declaration
-// RFC 2033 - Local Mail Transfer Protocol
-// RFC 2197 - SMTP Service Extension for Command Pipelining
-// RFC 2476 - Message Submission
-// RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
-// RFC 2554 - SMTP Service Extension for Authentication
-// RFC 2821 - Simple Mail Transfer Protocol
-// RFC 2822 - Internet Message Format
-// RFC 2920 - SMTP Service Extension for Command Pipelining
-//
-// The VRFY and EXPN commands have been removed from this implementation
-// because nobody uses these commands anymore, except for spammers.
-//
-// Copyright (c) 1998-2022 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.
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <termios.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <syslog.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "user_ops.h"
-#include "room_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "genstamp.h"
-#include "domain.h"
-#include "clientsocket.h"
-#include "locate_host.h"
-#include "citadel_dirs.h"
-#include "ctdl_module.h"
-
-#include "smtp_util.h"
-
-enum { // Command states for login authentication
- smtp_command,
- smtp_user,
- smtp_password,
- smtp_plain
-};
-
-enum SMTP_FLAGS {
- HELO,
- EHLO,
- LHLO
-};
-
-
-// Here's where our SMTP session begins its happy day.
-void smtp_greeting(int is_msa) {
- char message_to_spammer[1024];
-
- strcpy(CC->cs_clientname, "SMTP session");
- CC->internal_pgm = 1;
- CC->cs_flags |= CS_STEALTH;
- CC->session_specific_data = malloc(sizeof(struct citsmtp));
- memset(SMTP, 0, sizeof(struct citsmtp));
- SMTP->is_msa = is_msa;
- SMTP->Cmd = NewStrBufPlain(NULL, SIZ);
- SMTP->helo_node = NewStrBuf();
- SMTP->from = NewStrBufPlain(NULL, SIZ);
- SMTP->recipients = NewStrBufPlain(NULL, SIZ);
- SMTP->OneRcpt = NewStrBufPlain(NULL, SIZ);
- SMTP->preferred_sender_email = NULL;
- SMTP->preferred_sender_name = NULL;
-
- // If this config option is set, reject connections from problem
- // addresses immediately instead of after they execute a RCPT
- if ( (CtdlGetConfigInt("c_rbl_at_greeting")) && (SMTP->is_msa == 0) ) {
- if (rbl_check(CC->cs_addr, message_to_spammer)) {
- if (server_shutting_down)
- cprintf("421 %s\r\n", message_to_spammer);
- else
- cprintf("550 %s\r\n", message_to_spammer);
- CC->kill_me = KILLME_SPAMMER;
- /* no need to free_recipients(valid), it's not allocated yet */
- return;
- }
- }
-
- // Otherwise we're either clean or we check later.
-
- if (CC->nologin==1) {
- cprintf("451 Too many connections are already open; please try again later.\r\n");
- CC->kill_me = KILLME_MAX_SESSIONS_EXCEEDED;
- // no need to free_recipients(valid), it's not allocated yet
- return;
- }
-
- // Note: the FQDN *must* appear as the first thing after the 220 code.
- // Some clients (including citmail.c) depend on it being there.
- cprintf("220 %s ESMTP Citadel server ready.\r\n", CtdlGetConfigStr("c_fqdn"));
-}
-
-
-// SMTPS is just like SMTP, except it goes crypto right away.
-void smtps_greeting(void) {
- CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
-#ifdef HAVE_OPENSSL
- if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO; // kill session if no crypto
-#endif
- smtp_greeting(0);
-}
-
-
-// SMTP MSA port requires authentication.
-void smtp_msa_greeting(void) {
- smtp_greeting(1);
-}
-
-
-// LMTP is like SMTP but with some extra bonus footage added.
-void lmtp_greeting(void) {
-
- smtp_greeting(0);
- SMTP->is_lmtp = 1;
-}
-
-
-// Generic SMTP MTA greeting
-void smtp_mta_greeting(void) {
- smtp_greeting(0);
-}
-
-
-// We also have an unfiltered LMTP socket that bypasses spam filters.
-void lmtp_unfiltered_greeting(void) {
- smtp_greeting(0);
- SMTP->is_lmtp = 1;
- SMTP->is_unfiltered = 1;
-}
-
-
-// Login greeting common to all auth methods
-void smtp_auth_greeting(void) {
- cprintf("235 Hello, %s\r\n", CC->user.fullname);
- syslog(LOG_INFO, "serv_smtp: SMTP authenticated %s", CC->user.fullname);
- CC->internal_pgm = 0;
- CC->cs_flags &= ~CS_STEALTH;
-}
-
-
-// Implement HELO and EHLO commands.
-// which_command: 0=HELO, 1=EHLO, 2=LHLO
-void smtp_hello(int which_command) {
-
- if (StrLength(SMTP->Cmd) >= 6) {
- FlushStrBuf(SMTP->helo_node);
- StrBufAppendBuf(SMTP->helo_node, SMTP->Cmd, 5);
- }
-
- if ( (which_command != LHLO) && (SMTP->is_lmtp) ) {
- cprintf("500 Only LHLO is allowed when running LMTP\r\n");
- return;
- }
-
- if ( (which_command == LHLO) && (SMTP->is_lmtp == 0) ) {
- cprintf("500 LHLO is only allowed when running LMTP\r\n");
- return;
- }
-
- if (which_command == HELO) {
- cprintf("250 Hello %s (%s [%s])\r\n",
- ChrPtr(SMTP->helo_node),
- CC->cs_host,
- CC->cs_addr
- );
- }
- else {
- if (which_command == EHLO) {
- cprintf("250-Hello %s (%s [%s])\r\n",
- ChrPtr(SMTP->helo_node),
- CC->cs_host,
- CC->cs_addr
- );
- }
- else {
- cprintf("250-Greetings and joyous salutations.\r\n");
- }
- cprintf("250-HELP\r\n");
- cprintf("250-SIZE %ld\r\n", CtdlGetConfigLong("c_maxmsglen"));
-
-#ifdef HAVE_OPENSSL
- // Offer TLS, but only if TLS is not already active.
- // Furthermore, only offer TLS when running on
- // the SMTP-MSA port, not on the SMTP-MTA port, due to
- // questionable reliability of TLS in certain sending MTA's.
- if ( (!CC->redirect_ssl) && (SMTP->is_msa) ) {
- cprintf("250-STARTTLS\r\n");
- }
-#endif
-
- cprintf("250-AUTH LOGIN PLAIN\r\n"
- "250-AUTH=LOGIN PLAIN\r\n"
- "250 8BITMIME\r\n"
- );
- }
-}
-
-
-// Backend function for smtp_webcit_preferences_hack().
-// Look at a message and determine if it's the preferences file.
-void smtp_webcit_preferences_hack_backend(long msgnum, void *userdata) {
- struct CtdlMessage *msg;
- char **webcit_conf = (char **) userdata;
-
- if (*webcit_conf) {
- return; // already got it
- }
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) {
- return;
- }
-
- if ( !CM_IsEmpty(msg, eMsgSubject) && (!strcasecmp(msg->cm_fields[eMsgSubject], "__ WebCit Preferences __"))) {
- // This is it! Change ownership of the message text so it doesn't get freed.
- *webcit_conf = (char *)msg->cm_fields[eMesageText];
- msg->cm_fields[eMesageText] = NULL;
- }
- CM_Free(msg);
-}
-
-
-// The configuration item for the user's preferred display name for outgoing email is, unfortunately,
-// stored in the account's WebCit configuration. We have to fetch it now.
-void smtp_webcit_preferences_hack(void) {
- char config_roomname[ROOMNAMELEN];
- char *webcit_conf = NULL;
-
- snprintf(config_roomname, sizeof config_roomname, "%010ld.%s", CC->user.usernum, USERCONFIGROOM);
- if (CtdlGetRoom(&CC->room, config_roomname) != 0) {
- return;
- }
-
- // Find the WebCit configuration message
- CtdlForEachMessage(MSGS_ALL, 1, NULL, NULL, NULL, smtp_webcit_preferences_hack_backend, (void *)&webcit_conf);
-
- if (!webcit_conf) {
- return;
- }
-
- // Parse the webcit configuration and attempt to do something useful with it
- char *str = webcit_conf;
- char *saveptr = str;
- char *this_line = NULL;
- while (this_line = strtok_r(str, "\n", &saveptr), this_line != NULL) {
- str = NULL;
- if (!strncasecmp(this_line, "defaultfrom|", 12)) {
- SMTP->preferred_sender_email = NewStrBufPlain(&this_line[12], -1);
- }
- if (!strncasecmp(this_line, "defaultname|", 12)) {
- SMTP->preferred_sender_name = NewStrBufPlain(&this_line[12], -1);
- }
- if ((!strncasecmp(this_line, "defaultname|", 12)) && (SMTP->preferred_sender_name == NULL)) {
- SMTP->preferred_sender_name = NewStrBufPlain(&this_line[12], -1);
- }
-
- }
- free(webcit_conf);
-}
-
-
-// Implement HELP command.
-void smtp_help(void) {
- cprintf("214 RTFM http://www.ietf.org/rfc/rfc2821.txt\r\n");
-}
-
-
-void smtp_get_user(int offset) {
- char buf[SIZ];
-
- StrBuf *UserName = NewStrBufDup(SMTP->Cmd);
- StrBufCutLeft(UserName, offset);
- StrBufDecodeBase64(UserName);
-
- if (CtdlLoginExistingUser(ChrPtr(UserName)) == login_ok) {
- size_t len = CtdlEncodeBase64(buf, "Password:", 9, 0);
-
- if (buf[len - 1] == '\n') {
- buf[len - 1] = '\0';
- }
- cprintf("334 %s\r\n", buf);
- SMTP->command_state = smtp_password;
- }
- else {
- cprintf("500 No such user.\r\n");
- SMTP->command_state = smtp_command;
- }
- FreeStrBuf(&UserName);
-}
-
-
-void smtp_get_pass(void) {
- char password[SIZ];
-
- memset(password, 0, sizeof(password));
- StrBufDecodeBase64(SMTP->Cmd);
- syslog(LOG_DEBUG, "serv_smtp: trying <%s>", password);
- if (CtdlTryPassword(SKEY(SMTP->Cmd)) == pass_ok) {
- smtp_auth_greeting();
- }
- else {
- cprintf("535 Authentication failed.\r\n");
- }
- SMTP->command_state = smtp_command;
-}
-
-
-// Back end for PLAIN auth method (either inline or multistate)
-void smtp_try_plain(void) {
- const char*decoded_authstring;
- char ident[256] = "";
- char user[256] = "";
- char pass[256] = "";
- int result;
-
- long decoded_len;
- long len = 0;
- long plen = 0;
-
- memset(pass, 0, sizeof(pass));
- decoded_len = StrBufDecodeBase64(SMTP->Cmd);
-
- if (decoded_len > 0) {
- decoded_authstring = ChrPtr(SMTP->Cmd);
-
- len = safestrncpy(ident, decoded_authstring, sizeof ident);
-
- decoded_len -= len - 1;
- decoded_authstring += len + 1;
-
- if (decoded_len > 0) {
- len = safestrncpy(user, decoded_authstring, sizeof user);
-
- decoded_authstring += len + 1;
- decoded_len -= len - 1;
- }
-
- if (decoded_len > 0) {
- plen = safestrncpy(pass, decoded_authstring, sizeof pass);
-
- if (plen < 0)
- plen = sizeof(pass) - 1;
- }
- }
-
- SMTP->command_state = smtp_command;
-
- if (!IsEmptyStr(ident)) {
- result = CtdlLoginExistingUser(ident);
- }
- else {
- result = CtdlLoginExistingUser(user);
- }
-
- if (result == login_ok) {
- if (CtdlTryPassword(pass, plen) == pass_ok) {
- smtp_webcit_preferences_hack();
- smtp_auth_greeting();
- return;
- }
- }
- cprintf("504 Authentication failed.\r\n");
-}
-
-
-// Attempt to perform authenticated SMTP
-void smtp_auth(void) {
- char username_prompt[64];
- char method[64];
- char encoded_authstring[1024];
-
- if (CC->logged_in) {
- cprintf("504 Already logged in.\r\n");
- return;
- }
-
- if (StrLength(SMTP->Cmd) < 6) {
- cprintf("501 Syntax error\r\n");
- return;
- }
-
- extract_token(method, ChrPtr(SMTP->Cmd) + 5, 0, ' ', sizeof method);
-
- if (!strncasecmp(method, "login", 5) ) {
- if (StrLength(SMTP->Cmd) >= 12) {
- syslog(LOG_DEBUG, "serv_smtp: username <%s> supplied inline", ChrPtr(SMTP->Cmd)+11);
- smtp_get_user(11);
- }
- else {
- size_t len = CtdlEncodeBase64(username_prompt, "Username:", 9, 0);
- if (username_prompt[len - 1] == '\n') {
- username_prompt[len - 1] = '\0';
- }
- cprintf("334 %s\r\n", username_prompt);
- SMTP->command_state = smtp_user;
- }
- return;
- }
-
- if (!strncasecmp(method, "plain", 5) ) {
- long len;
- if (num_tokens(ChrPtr(SMTP->Cmd) + 5, ' ') < 2) {
- cprintf("334 \r\n");
- SMTP->command_state = smtp_plain;
- return;
- }
-
- len = extract_token(encoded_authstring,
- ChrPtr(SMTP->Cmd) + 5,
- 1, ' ',
- sizeof encoded_authstring);
- StrBufPlain(SMTP->Cmd, encoded_authstring, len);
- smtp_try_plain();
- return;
- }
-
- cprintf("504 Unknown authentication method.\r\n");
- return;
-}
-
-
-// Implements the RSET (reset state) command.
-// Currently this just zeroes out the state buffer. If pointers to data
-// allocated with malloc() are ever placed in the state buffer, we have to
-// be sure to free() them first!
-//
-// Set do_response to nonzero to output the SMTP RSET response code.
-void smtp_rset(int do_response) {
- FlushStrBuf(SMTP->Cmd);
- FlushStrBuf(SMTP->helo_node);
- FlushStrBuf(SMTP->from);
- FlushStrBuf(SMTP->recipients);
- FlushStrBuf(SMTP->OneRcpt);
-
- SMTP->command_state = 0;
- SMTP->number_of_recipients = 0;
- SMTP->delivery_mode = 0;
- SMTP->message_originated_locally = 0;
- SMTP->is_msa = 0;
- // is_lmtp and is_unfiltered should not be cleared.
-
- if (do_response) {
- cprintf("250 Zap!\r\n");
- }
-}
-
-
-// Clear out the portions of the state buffer that need to be cleared out
-// after the DATA command finishes.
-void smtp_data_clear(void) {
- FlushStrBuf(SMTP->from);
- FlushStrBuf(SMTP->recipients);
- FlushStrBuf(SMTP->OneRcpt);
- SMTP->number_of_recipients = 0;
- SMTP->delivery_mode = 0;
- SMTP->message_originated_locally = 0;
-}
-
-
-// Implements the "MAIL FROM:" command
-void smtp_mail(void) {
- char user[SIZ];
- char node[SIZ];
- char name[SIZ];
-
- if (StrLength(SMTP->from) > 0) {
- cprintf("503 Only one sender permitted\r\n");
- return;
- }
-
- if (StrLength(SMTP->Cmd) < 6) {
- cprintf("501 Syntax error\r\n");
- return;
- }
-
- if (strncasecmp(ChrPtr(SMTP->Cmd) + 5, "From:", 5)) {
- cprintf("501 Syntax error\r\n");
- return;
- }
-
- StrBufAppendBuf(SMTP->from, SMTP->Cmd, 5);
- StrBufTrim(SMTP->from);
- if (strchr(ChrPtr(SMTP->from), '<') != NULL) {
- StrBufStripAllBut(SMTP->from, '<', '>');
- }
-
- // We used to reject empty sender names, until it was brought to our
- // attention that RFC1123 5.2.9 requires that this be allowed. So now
- // we allow it, but replace the empty string with a fake
- // address so we don't have to contend with the empty string causing
- // other code to fail when it's expecting something there.
- if (StrLength(SMTP->from) == 0) {
- StrBufPlain(SMTP->from, HKEY("someone@example.com"));
- }
-
- // If this SMTP connection is from a logged-in user, force the 'from'
- // to be the user's Internet e-mail address as Citadel knows it.
- if (CC->logged_in) {
- StrBufPlain(SMTP->from, CC->cs_inet_email, -1);
- cprintf("250 Sender ok <%s>\r\n", ChrPtr(SMTP->from));
- SMTP->message_originated_locally = 1;
- return;
- }
-
- else if (SMTP->is_lmtp) {
- // Bypass forgery checking for LMTP
- }
-
- // Otherwise, make sure outsiders aren't trying to forge mail from
- // this system (unless, of course, c_allow_spoofing is enabled)
- else if (CtdlGetConfigInt("c_allow_spoofing") == 0) {
- process_rfc822_addr(ChrPtr(SMTP->from), user, node, name);
- syslog(LOG_DEBUG, "serv_smtp: claimed envelope sender is '%s' == '%s' @ '%s' ('%s')",
- ChrPtr(SMTP->from), user, node, name
- );
- if (CtdlHostAlias(node) != hostalias_nomatch) {
- cprintf("550 You must log in to send mail from %s\r\n", node);
- FlushStrBuf(SMTP->from);
- syslog(LOG_DEBUG, "serv_smtp: rejecting unauthenticated mail from %s", node);
- return;
- }
- }
-
- cprintf("250 Sender ok\r\n");
-}
-
-
-// Implements the "RCPT To:" command
-void smtp_rcpt(void) {
- char message_to_spammer[SIZ];
- struct recptypes *valid = NULL;
-
- if (StrLength(SMTP->from) == 0) {
- cprintf("503 Need MAIL before RCPT\r\n");
- return;
- }
-
- if (StrLength(SMTP->Cmd) < 6) {
- cprintf("501 Syntax error\r\n");
- return;
- }
-
- if (strncasecmp(ChrPtr(SMTP->Cmd) + 5, "To:", 3)) {
- cprintf("501 Syntax error\r\n");
- return;
- }
-
- if ( (SMTP->is_msa) && (!CC->logged_in) ) {
- cprintf("550 You must log in to send mail on this port.\r\n");
- FlushStrBuf(SMTP->from);
- return;
- }
-
- FlushStrBuf(SMTP->OneRcpt);
- StrBufAppendBuf(SMTP->OneRcpt, SMTP->Cmd, 8);
- StrBufTrim(SMTP->OneRcpt);
- StrBufStripAllBut(SMTP->OneRcpt, '<', '>');
-
- if ( (StrLength(SMTP->OneRcpt) + StrLength(SMTP->recipients)) >= SIZ) {
- cprintf("452 Too many recipients\r\n");
- return;
- }
-
- // RBL check
- if (
- (!CC->logged_in) // Don't RBL authenticated users
- && (!SMTP->is_lmtp) // Don't RBL LMTP clients
- && (CtdlGetConfigInt("c_rbl_at_greeting") == 0) // Don't RBL if we did it at connection time
- && (rbl_check(CC->cs_addr, message_to_spammer))
- ) {
- cprintf("550 %s\r\n", message_to_spammer);
- return; // no need to free_recipients(valid)
- } // because it hasn't been allocated yet
-
- // This is a *preliminary* call to validate_recipients() to evaluate one recipient.
- valid = validate_recipients(
- (char *)ChrPtr(SMTP->OneRcpt),
- smtp_get_Recipients(),
- (SMTP->is_lmtp)? POST_LMTP: (CC->logged_in)? POST_LOGGED_IN: POST_EXTERNAL
- );
-
- // Any type of error thrown by validate_recipients() will make the SMTP transaction fail at this point.
- if (valid->num_error != 0) {
- cprintf("550 %s\r\n", valid->errormsg);
- free_recipients(valid);
- return;
- }
-
- if (
- (valid->num_internet > 0) // If it's outbound Internet mail...
- && (CC->logged_in) // ...and we're a logged-in user...
- && (CtdlCheckInternetMailPermission(&CC->user)==0) // ...who does not have Internet mail rights...
- ) {
- cprintf("551 <%s> - you do not have permission to send Internet mail\r\n", ChrPtr(SMTP->OneRcpt));
- free_recipients(valid);
- return;
- }
-
- if (
- (valid->num_internet > 0) // If it's outbound Internet mail...
- && (SMTP->message_originated_locally == 0) // ...and also inbound Internet mail...
- && (SMTP->is_lmtp == 0) /// ...and didn't arrive via LMTP...
- ) {
- cprintf("551 <%s> - relaying denied\r\n", ChrPtr(SMTP->OneRcpt));
- free_recipients(valid);
- return;
- }
-
- if (
- (valid->num_room > 0) // If it's mail to a room (mailing list)...
- && (SMTP->message_originated_locally == 0) // ...and also inbound Internet mail...
- && (is_email_subscribed_to_list((char *)ChrPtr(SMTP->from), valid->recp_room) == 0) // ...and not a subscriber
- ) {
- cprintf("551 <%s> - The message is not from a list member\r\n", ChrPtr(SMTP->OneRcpt));
- free_recipients(valid);
- return;
- }
-
- cprintf("250 RCPT ok <%s>\r\n", ChrPtr(SMTP->OneRcpt));
- if (StrLength(SMTP->recipients) > 0) {
- StrBufAppendBufPlain(SMTP->recipients, HKEY(","), 0);
- }
- StrBufAppendBuf(SMTP->recipients, SMTP->OneRcpt, 0);
- SMTP->number_of_recipients ++;
- if (valid != NULL) {
- free_recipients(valid);
- }
-}
-
-
-// Implements the DATA command
-void smtp_data(void) {
- StrBuf *body;
- StrBuf *defbody;
- struct CtdlMessage *msg = NULL;
- long msgnum = (-1L);
- char nowstamp[SIZ];
- struct recptypes *valid;
- int scan_errors;
- int i;
-
- if (StrLength(SMTP->from) == 0) {
- cprintf("503 Need MAIL command first.\r\n");
- return;
- }
-
- if (SMTP->number_of_recipients < 1) {
- cprintf("503 Need RCPT command first.\r\n");
- return;
- }
-
- cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
-
- datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
- defbody = NewStrBufPlain(NULL, SIZ);
-
- if (defbody != NULL) {
- if (SMTP->is_lmtp && (CC->cs_UDSclientUID != -1)) {
- StrBufPrintf(
- defbody,
- "Received: from %s (Citadel from userid %ld)\n"
- " by %s; %s\n",
- ChrPtr(SMTP->helo_node),
- (long int) CC->cs_UDSclientUID,
- CtdlGetConfigStr("c_fqdn"),
- nowstamp);
- }
- else {
- StrBufPrintf(
- defbody,
- "Received: from %s (%s [%s])\n"
- " by %s; %s\n",
- ChrPtr(SMTP->helo_node),
- CC->cs_host,
- CC->cs_addr,
- CtdlGetConfigStr("c_fqdn"),
- nowstamp);
- }
- }
- body = CtdlReadMessageBodyBuf(HKEY("."), CtdlGetConfigLong("c_maxmsglen"), defbody, 1);
- FreeStrBuf(&defbody);
- if (body == NULL) {
- cprintf("550 Unable to save message: internal error.\r\n");
- return;
- }
-
- syslog(LOG_DEBUG, "serv_smtp: converting message...");
- msg = convert_internet_message_buf(&body);
-
- // If the user is locally authenticated, FORCE the From: header to
- // show up as the real sender. Yes, this violates the RFC standard,
- // but IT MAKES SENSE. If you prefer strict RFC adherence over
- // common sense, you can disable this in the configuration.
- //
- // We also set the "message room name" ('O' field) to MAILROOM
- // (which is Mail> on most systems) to prevent it from getting set
- // to something ugly like "0000058008.Sent Items>" when the message
- // is read with a Citadel client.
-
- if ( (CC->logged_in) && (CtdlGetConfigInt("c_rfc822_strict_from") != CFG_SMTP_FROM_NOFILTER) ) {
- int validemail = 0;
-
- if (!CM_IsEmpty(msg, erFc822Addr) &&
- ((CtdlGetConfigInt("c_rfc822_strict_from") == CFG_SMTP_FROM_CORRECT) ||
- (CtdlGetConfigInt("c_rfc822_strict_from") == CFG_SMTP_FROM_REJECT) ) )
- {
- if (!IsEmptyStr(CC->cs_inet_email))
- validemail = strcmp(CC->cs_inet_email, msg->cm_fields[erFc822Addr]) == 0;
- if ((!validemail) &&
- (!IsEmptyStr(CC->cs_inet_other_emails)))
- {
- int num_secondary_emails = 0;
- int i;
- num_secondary_emails = num_tokens(CC->cs_inet_other_emails, '|');
- for (i=0; i < num_secondary_emails && !validemail; ++i) {
- char buf[256];
- extract_token(buf, CC->cs_inet_other_emails,i,'|',sizeof CC->cs_inet_other_emails);
- validemail = strcmp(buf, msg->cm_fields[erFc822Addr]) == 0;
- }
- }
- }
-
- if (!validemail && (CtdlGetConfigInt("c_rfc822_strict_from") == CFG_SMTP_FROM_REJECT)) {
- syslog(LOG_ERR, "serv_smtp: invalid sender '%s' - rejecting this message", msg->cm_fields[erFc822Addr]);
- cprintf("550 Invalid sender '%s' - rejecting this message.\r\n", msg->cm_fields[erFc822Addr]);
- return;
- }
-
- CM_SetField(msg, eOriginalRoom, HKEY(MAILROOM));
- if (SMTP->preferred_sender_name != NULL)
- CM_SetField(msg, eAuthor, SKEY(SMTP->preferred_sender_name));
- else
- CM_SetField(msg, eAuthor, CC->user.fullname, strlen(CC->user.fullname));
-
- if (!validemail) {
- if (SMTP->preferred_sender_email != NULL) {
- CM_SetField(msg, erFc822Addr, SKEY(SMTP->preferred_sender_email));
- }
- else {
- CM_SetField(msg, erFc822Addr, CC->cs_inet_email, strlen(CC->cs_inet_email));
- }
- }
- }
-
- // Set the "envelope from" address
- CM_SetField(msg, eMessagePath, SKEY(SMTP->from));
-
- // Set the "envelope to" address
- CM_SetField(msg, eenVelopeTo, SKEY(SMTP->recipients));
-
- // Submit the message into the Citadel system.
- valid = validate_recipients(
- (char *)ChrPtr(SMTP->recipients),
- smtp_get_Recipients(),
- (SMTP->is_lmtp)? POST_LMTP: (CC->logged_in)? POST_LOGGED_IN: POST_EXTERNAL
- );
-
- // If there are modules that want to scan this message before final
- // submission (such as virus checkers or spam filters), call them now
- // and give them an opportunity to reject the message.
- if (SMTP->is_unfiltered) {
- scan_errors = 0;
- }
- else {
- scan_errors = PerformMessageHooks(msg, valid, EVT_SMTPSCAN);
- }
-
- if (scan_errors > 0) { // We don't want this message!
-
- if (CM_IsEmpty(msg, eErrorMsg)) {
- CM_SetField(msg, eErrorMsg, HKEY("Message rejected by filter"));
- }
-
- StrBufPrintf(SMTP->OneRcpt, "550 %s\r\n", msg->cm_fields[eErrorMsg]);
- }
-
- else { // Ok, we'll accept this message.
- msgnum = CtdlSubmitMsg(msg, valid, "");
- if (msgnum > 0L) {
- StrBufPrintf(SMTP->OneRcpt, "250 Message accepted.\r\n");
- }
- else {
- StrBufPrintf(SMTP->OneRcpt, "550 Internal delivery error\r\n");
- }
- }
-
- // For SMTP and ESMTP, just print the result message. For LMTP, we
- // have to print one result message for each recipient. Since there
- // is nothing in Citadel which would cause different recipients to
- // have different results, we can get away with just spitting out the
- // same message once for each recipient.
- if (SMTP->is_lmtp) {
- for (i=0; i<SMTP->number_of_recipients; ++i) {
- cputbuf(SMTP->OneRcpt);
- }
- }
- else {
- cputbuf(SMTP->OneRcpt);
- }
-
- // Write something to the syslog(which may or may not be where the
- // rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
- syslog((LOG_MAIL | LOG_INFO),
- "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
- msgnum,
- ChrPtr(SMTP->from),
- SMTP->number_of_recipients,
- CC->cs_host,
- CC->cs_addr,
- ChrPtr(SMTP->OneRcpt)
- );
-
- // Clean up
- CM_Free(msg);
- free_recipients(valid);
- smtp_data_clear(); // clear out the buffers now
-}
-
-
-// Implements the STARTTLS command
-void smtp_starttls(void) {
- char ok_response[SIZ];
- char nosup_response[SIZ];
- char error_response[SIZ];
-
- sprintf(ok_response, "220 Begin TLS negotiation now\r\n");
- sprintf(nosup_response, "554 TLS not supported here\r\n");
- sprintf(error_response, "554 Internal error\r\n");
- CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
- smtp_rset(0);
-}
-
-
-// Implements the NOOP (NO OPeration) command
-void smtp_noop(void) {
- cprintf("250 NOOP\r\n");
-}
-
-
-// Implements the QUIT command
-void smtp_quit(void) {
- cprintf("221 Goodbye...\r\n");
- CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
-}
-
-
-// Main command loop for SMTP server sessions.
-void smtp_command_loop(void) {
- static const ConstStr AuthPlainStr = {HKEY("AUTH PLAIN")};
-
- if (SMTP == NULL) {
- syslog(LOG_ERR, "serv_smtp: Session SMTP data is null. WTF? We will crash now.");
- abort();
- }
-
- time(&CC->lastcmd);
- if (CtdlClientGetLine(SMTP->Cmd) < 1) {
- syslog(LOG_INFO, "SMTP: client disconnected: ending session.");
- CC->kill_me = KILLME_CLIENT_DISCONNECTED;
- return;
- }
-
- if (SMTP->command_state == smtp_user) {
- if (!strncmp(ChrPtr(SMTP->Cmd), AuthPlainStr.Key, AuthPlainStr.len)) {
- smtp_try_plain();
- }
- else {
- smtp_get_user(0);
- }
- return;
- }
-
- else if (SMTP->command_state == smtp_password) {
- smtp_get_pass();
- return;
- }
-
- else if (SMTP->command_state == smtp_plain) {
- smtp_try_plain();
- return;
- }
-
- syslog(LOG_DEBUG, "serv_smtp: client sent command <%s>", ChrPtr(SMTP->Cmd));
-
- if (!strncasecmp(ChrPtr(SMTP->Cmd), "NOOP", 4)) {
- smtp_noop();
- return;
- }
-
- if (!strncasecmp(ChrPtr(SMTP->Cmd), "QUIT", 4)) {
- smtp_quit();
- return;
- }
-
- if (!strncasecmp(ChrPtr(SMTP->Cmd), "HELO", 4)) {
- smtp_hello(HELO);
- return;
- }
-
- if (!strncasecmp(ChrPtr(SMTP->Cmd), "EHLO", 4)) {
- smtp_hello(EHLO);
- return;
- }
-
- if (!strncasecmp(ChrPtr(SMTP->Cmd), "LHLO", 4)) {
- smtp_hello(LHLO);
- return;
- }
-
- if (!strncasecmp(ChrPtr(SMTP->Cmd), "RSET", 4)) {
- smtp_rset(1);
- return;
- }
-
- if (!strncasecmp(ChrPtr(SMTP->Cmd), "AUTH", 4)) {
- smtp_auth();
- return;
- }
-
- if (!strncasecmp(ChrPtr(SMTP->Cmd), "DATA", 4)) {
- smtp_data();
- return;
- }
-
- if (!strncasecmp(ChrPtr(SMTP->Cmd), "HELP", 4)) {
- smtp_help();
- return;
- }
-
- if (!strncasecmp(ChrPtr(SMTP->Cmd), "MAIL", 4)) {
- smtp_mail();
- return;
- }
-
- if (!strncasecmp(ChrPtr(SMTP->Cmd), "RCPT", 4)) {
- smtp_rcpt();
- return;
- }
-#ifdef HAVE_OPENSSL
- if (!strncasecmp(ChrPtr(SMTP->Cmd), "STARTTLS", 8)) {
- smtp_starttls();
- return;
- }
-#endif
-
- cprintf("502 I'm afraid I can't do that.\r\n");
-}
-
-
-/*****************************************************************************/
-/* MODULE INITIALIZATION STUFF */
-/*****************************************************************************/
-/*
- * This cleanup function blows away the temporary memory used by
- * the SMTP server.
- */
-void smtp_cleanup_function(void)
-{
- /* Don't do this stuff if this is not an SMTP session! */
- if (CC->h_command_function != smtp_command_loop) return;
-
- syslog(LOG_DEBUG, "Performing SMTP cleanup hook");
-
- FreeStrBuf(&SMTP->Cmd);
- FreeStrBuf(&SMTP->helo_node);
- FreeStrBuf(&SMTP->from);
- FreeStrBuf(&SMTP->recipients);
- FreeStrBuf(&SMTP->OneRcpt);
- FreeStrBuf(&SMTP->preferred_sender_email);
- FreeStrBuf(&SMTP->preferred_sender_name);
-
- free(SMTP);
-}
-
-const char *CitadelServiceSMTP_MTA="SMTP-MTA";
-const char *CitadelServiceSMTPS_MTA="SMTPs-MTA";
-const char *CitadelServiceSMTP_MSA="SMTP-MSA";
-const char *CitadelServiceSMTP_LMTP="LMTP";
-const char *CitadelServiceSMTP_LMTP_UNF="LMTP-UnF";
-
-
-CTDL_MODULE_INIT(smtp)
-{
- if (!threading) {
- CtdlRegisterServiceHook(CtdlGetConfigInt("c_smtp_port"), /* SMTP MTA */
- NULL,
- smtp_mta_greeting,
- smtp_command_loop,
- NULL,
- CitadelServiceSMTP_MTA);
-
-#ifdef HAVE_OPENSSL
- CtdlRegisterServiceHook(CtdlGetConfigInt("c_smtps_port"), /* SMTPS MTA */
- NULL,
- smtps_greeting,
- smtp_command_loop,
- NULL,
- CitadelServiceSMTPS_MTA);
-#endif
-
- CtdlRegisterServiceHook(CtdlGetConfigInt("c_msa_port"), /* SMTP MSA */
- NULL,
- smtp_msa_greeting,
- smtp_command_loop,
- NULL,
- CitadelServiceSMTP_MSA);
-
- CtdlRegisterServiceHook(0, /* local LMTP */
- file_lmtp_socket,
- lmtp_greeting,
- smtp_command_loop,
- NULL,
- CitadelServiceSMTP_LMTP);
-
- CtdlRegisterServiceHook(0, /* local LMTP */
- file_lmtp_unfiltered_socket,
- lmtp_unfiltered_greeting,
- smtp_command_loop,
- NULL,
- CitadelServiceSMTP_LMTP_UNF);
-
- CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP, PRIO_STOP + 250);
- }
-
- /* return our module name for the log */
- return "smtp";
-}
+++ /dev/null
-// Transmit outbound SMTP mail to the big wide world of the Internet
-//
-// This is the new, exciting, clever version that makes libcurl do all the work :)
-//
-// Copyright (c) 1997-2022 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 as published
-// by the Free Software Foundation; either version 3 of the License, or
-// (at your option) any later version.
-//
-// 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.
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <sysconfig.h>
-#include <time.h>
-#include <ctype.h>
-#include <string.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <libcitadel.h>
-#include <curl/curl.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "ctdl_module.h"
-#include "clientsocket.h"
-#include "msgbase.h"
-#include "domain.h"
-#include "internet_addressing.h"
-#include "citadel_dirs.h"
-#include "modules/smtp/smtp_util.h"
-
-struct smtpmsgsrc { // Data passed in and out of libcurl for message upload
- StrBuf *TheMessage;
- int bytes_total;
- int bytes_sent;
-};
-
-static int doing_smtpclient = 0;
-long *smtpq = NULL; // array of msgnums containing queue instructions
-int smtpq_count = 0; // number of queue messages in smtpq
-int smtpq_alloc = 0; // current allocation size for smtpq
-
-
-// Initialize the SMTP outbound queue
-void smtp_init_spoolout(void) {
- struct ctdlroom qrbuf;
-
- // Create the room. This will silently fail if the room already
- // exists, and that's perfectly ok, because we want it to exist.
- CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_QUEUE);
-
- // Make sure it's set to be a "system room" so it doesn't show up
- // in the <K>nown rooms list for administrators.
- if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
- qrbuf.QRflags2 |= QR2_SYSTEM;
- CtdlPutRoomLock(&qrbuf);
- }
-}
-
-
-// For internet mail, generate delivery instructions.
-// Yes, this is recursive. Deal with it. Infinite recursion does
-// not happen because the delivery instructions message does not
-// contain a recipient.
-int smtp_aftersave(struct CtdlMessage *msg, struct recptypes *recps) {
- if ((recps != NULL) && (recps->num_internet > 0)) {
- struct CtdlMessage *imsg = NULL;
- char recipient[SIZ];
- StrBuf *SpoolMsg = NewStrBuf();
- long nTokens;
- int i;
-
- syslog(LOG_DEBUG, "smtpclient: generating delivery instructions");
-
- StrBufPrintf(SpoolMsg,
- "Content-type: " SPOOLMIME "\n"
- "\n"
- "msgid|%s\n"
- "submitted|%ld\n" "bounceto|%s\n", msg->cm_fields[eVltMsgNum], (long) time(NULL), recps->bounce_to);
-
- if (recps->envelope_from != NULL) {
- StrBufAppendBufPlain(SpoolMsg, HKEY("envelope_from|"), 0);
- StrBufAppendBufPlain(SpoolMsg, recps->envelope_from, -1, 0);
- StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
- }
- if (recps->sending_room != NULL) {
- StrBufAppendBufPlain(SpoolMsg, HKEY("source_room|"), 0);
- StrBufAppendBufPlain(SpoolMsg, recps->sending_room, -1, 0);
- StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
- }
-
- nTokens = num_tokens(recps->recp_internet, '|');
- for (i = 0; i < nTokens; i++) {
- long len;
- len = extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
- if (len > 0) {
- StrBufAppendBufPlain(SpoolMsg, HKEY("remote|"), 0);
- StrBufAppendBufPlain(SpoolMsg, recipient, len, 0);
- StrBufAppendBufPlain(SpoolMsg, HKEY("|0||\n"), 0);
- }
- }
-
- imsg = malloc(sizeof(struct CtdlMessage));
- memset(imsg, 0, sizeof(struct CtdlMessage));
- imsg->cm_magic = CTDLMESSAGE_MAGIC;
- imsg->cm_anon_type = MES_NORMAL;
- imsg->cm_format_type = FMT_RFC822;
- CM_SetField(imsg, eMsgSubject, HKEY("QMSG"));
- CM_SetField(imsg, eAuthor, HKEY("Citadel"));
- CM_SetField(imsg, eJournal, HKEY("do not journal"));
- CM_SetAsFieldSB(imsg, eMesageText, &SpoolMsg);
- CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
- CM_Free(imsg);
- }
- return 0;
-}
-
-
-// Callback for smtp_attempt_delivery() to supply libcurl with upload data.
-static size_t upload_source(void *ptr, size_t size, size_t nmemb, void *userp) {
- struct smtpmsgsrc *s = (struct smtpmsgsrc *) userp;
- int sendbytes = 0;
- const char *send_this = NULL;
-
- sendbytes = (size * nmemb);
-
- if (s->bytes_sent >= s->bytes_total) {
- return (0); // no data remaining; we are done
- }
-
- if (sendbytes > (s->bytes_total - s->bytes_sent)) {
- sendbytes = s->bytes_total - s->bytes_sent; // can't send more than we have
- }
-
- send_this = ChrPtr(s->TheMessage);
- send_this += s->bytes_sent; // start where we last left off
-
- memcpy(ptr, send_this, sendbytes);
- s->bytes_sent += sendbytes;
- return(sendbytes); // return the number of bytes _actually_ copied
-}
-
-
-// The libcurl API doesn't provide a way to capture the actual SMTP result message returned
-// by the remote server. This is an ugly way to extract it, by capturing debug data from
-// the library and filtering on the lines we want.
-int ctdl_libcurl_smtp_debug_callback(CURL *handle, curl_infotype type, char *data, size_t size, void *userptr) {
- if (type != CURLINFO_HEADER_IN)
- return 0;
- if (!userptr)
- return 0;
- char *debugbuf = (char *) userptr;
-
- int len = strlen(debugbuf);
- if (len + size > SIZ)
- return 0;
-
- memcpy(&debugbuf[len], data, size);
- debugbuf[len + size] = 0;
- return 0;
-}
-
-
-// Go through the debug output of an SMTP transaction, and boil it down to just the final success or error response message.
-void trim_response(long response_code, char *response) {
- if ((response_code < 100) || (response_code > 999) || (IsEmptyStr(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
- *p = ' ';
- }
- }
-
- char response_code_str[4];
- snprintf(response_code_str, sizeof response_code_str, "%ld", response_code);
- char *respstart = strstr(response, response_code_str);
- if (respstart == NULL) { // If we have a response code but no response text,
- strcpy(response, smtpstatus(response_code)); // use one of our canned messages.
- return;
- }
- strcpy(response, respstart);
-
- p = strstr(response, "\n");
- if (p != NULL) {
- *p = 0;
- }
-}
-
-
-// Attempt a delivery to one recipient.
-// Returns a three-digit SMTP status code.
-int smtp_attempt_delivery(long msgid, char *recp, char *envelope_from, char *source_room, char *response) {
- struct smtpmsgsrc s;
- char *fromaddr = NULL;
- CURL *curl;
- CURLcode res = CURLE_OK;
- struct curl_slist *recipients = NULL;
- long response_code = 421;
- int num_mx = 0;
- char mxes[SIZ];
- char user[1024];
- char node[1024];
- char name[1024];
- char try_this_mx[256];
- char smtp_url[512];
- int i;
-
- syslog(LOG_DEBUG, "smtpclient: smtp_attempt_delivery(%ld, %s)", msgid, recp);
-
- 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);
- }
-
- CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
- 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
- );
- }
- 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;
- response_code = 421;
- // 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
-
- curl = curl_easy_init();
- if (curl) {
- response[0] = 0;
-
- if (!IsEmptyStr(envelope_from)) {
- curl_easy_setopt(curl, CURLOPT_MAIL_FROM, envelope_from);
- }
- else {
- curl_easy_setopt(curl, CURLOPT_MAIL_FROM, fromaddr);
- }
-
- recipients = curl_slist_append(recipients, recp);
- curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);
- curl_easy_setopt(curl, CURLOPT_READFUNCTION, upload_source);
- curl_easy_setopt(curl, CURLOPT_READDATA, &s);
- curl_easy_setopt(curl, CURLOPT_UPLOAD, 1); // tell libcurl we are uploading
- curl_easy_setopt(curl, CURLOPT_TIMEOUT, 20L); // Time out after 20 seconds
- if (CtdlGetConfigInt("c_smtpclient_disable_starttls") == 0) {
- curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_TRY); // Attempt STARTTLS if offered
- }
- curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
- curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
- curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, ctdl_libcurl_smtp_debug_callback);
- curl_easy_setopt(curl, CURLOPT_DEBUGDATA, (void *) response);
- curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
-
- // Construct an SMTP URL in the form of:
- // smtp[s]://target_host/source_host
- // This looks weird but libcurl uses that last part to set our name for EHLO or HELO.
- // We check for "smtp://" and "smtps://" because the admin may have put those prefixes in a smart-host entry.
- // If there is no prefix we add "smtp://"
- extract_token(try_this_mx, mxes, i, '|', (sizeof try_this_mx - 7));
- snprintf(smtp_url, sizeof smtp_url,
- "%s%s/%s",
- (((!strncasecmp(try_this_mx, HKEY("smtp://")))
- || (!strncasecmp(try_this_mx, HKEY("smtps://")))) ? "" : "smtp://"),
- try_this_mx, CtdlGetConfigStr("c_fqdn")
- );
- curl_easy_setopt(curl, CURLOPT_URL, smtp_url);
- syslog(LOG_DEBUG, "smtpclient: trying MX %d of %d <%s>", i+1, num_mx, smtp_url); // send the message
- res = curl_easy_perform(curl);
- curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
- syslog(LOG_DEBUG,
- "smtpclient: libcurl returned %d (%s) , SMTP response %ld",
- res, curl_easy_strerror(res), response_code
- );
-
- if ((res != CURLE_OK) && (response_code == 0)) { // check for errors
- response_code = 421;
- }
-
- curl_slist_free_all(recipients);
- recipients = NULL; // this gets reused; avoid double-free
- curl_easy_cleanup(curl);
- curl = NULL; // this gets reused; avoid double-free
-
- // Trim the error message buffer down to just the actual message
- trim_response(response_code, response);
- }
- }
-
- FreeStrBuf(&s.TheMessage);
- if (fromaddr) {
- free(fromaddr);
- }
- return ((int) response_code);
-}
-
-
-// Process one outbound message.
-void smtp_process_one_msg(long qmsgnum) {
- struct CtdlMessage *msg = NULL;
- char *instr = NULL;
- int i;
- int num_success = 0;
- int num_fail = 0;
- int num_delayed = 0;
- long deletes[2];
- int delete_this_queue = 0;
- char server_response[SIZ];
-
- msg = CtdlFetchMessage(qmsgnum, 1);
- if (msg == NULL) {
- syslog(LOG_WARNING, "smtpclient: %ld does not exist", qmsgnum);
- return;
- }
-
- instr = msg->cm_fields[eMesageText];
- msg->cm_fields[eMesageText] = NULL;
- CM_Free(msg);
-
- // if the queue message has any CRLF's convert them to LF's
- char *crlf = NULL;
- while (crlf = strstr(instr, "\r\n"), crlf != NULL) {
- strcpy(crlf, crlf + 1);
- }
-
- // Strip out the headers and we are now left with just the instructions.
- char *soi = strstr(instr, "\n\n");
- if (soi) {
- strcpy(instr, soi + 2);
- }
-
- long msgid = 0;
- time_t submitted = time(NULL);
- time_t attempted = 0;
- char *bounceto = NULL;
- char *envelope_from = NULL;
- char *source_room = NULL;
-
- char cfgline[SIZ];
- for (i = 0; i < num_tokens(instr, '\n'); ++i) {
- extract_token(cfgline, instr, i, '\n', sizeof cfgline);
- if (!strncasecmp(cfgline, HKEY("msgid|")))
- msgid = atol(&cfgline[6]);
- if (!strncasecmp(cfgline, HKEY("submitted|")))
- submitted = atol(&cfgline[10]);
- if (!strncasecmp(cfgline, HKEY("attempted|")))
- attempted = atol(&cfgline[10]);
- if (!strncasecmp(cfgline, HKEY("bounceto|")))
- bounceto = strdup(&cfgline[9]);
- if (!strncasecmp(cfgline, HKEY("envelope_from|")))
- envelope_from = strdup(&cfgline[14]);
- if (!strncasecmp(cfgline, HKEY("source_room|")))
- source_room = strdup(&cfgline[12]);
- }
-
- int should_try_now = 0;
- if (attempted < submitted) { // If no attempts have been made yet, try now
- should_try_now = 1;
- }
- else if ((attempted - submitted) <= 14400) {
- if ((time(NULL) - attempted) > 1800) { // First four hours, retry every 30 minutes
- should_try_now = 1;
- }
- }
- else {
- if ((time(NULL) - attempted) > 14400) { // After that, retry once every 4 hours
- should_try_now = 1;
- }
- }
-
- if (should_try_now) {
- syslog(LOG_DEBUG, "smtpclient: attempting delivery of message <%ld> now", qmsgnum);
- if (source_room) {
- syslog(LOG_DEBUG, "smtpclient: this message originated in <%s>", source_room);
- }
- StrBuf *NewInstr = NewStrBuf();
- StrBufAppendPrintf(NewInstr, "Content-type: " SPOOLMIME "\n\n");
- StrBufAppendPrintf(NewInstr, "msgid|%ld\n", msgid);
- StrBufAppendPrintf(NewInstr, "submitted|%ld\n", submitted);
- if (bounceto) {
- StrBufAppendPrintf(NewInstr, "bounceto|%s\n", bounceto);
- }
- if (envelope_from) {
- StrBufAppendPrintf(NewInstr, "envelope_from|%s\n", envelope_from);
- }
- for (i = 0; i < num_tokens(instr, '\n'); ++i) {
- extract_token(cfgline, instr, i, '\n', sizeof cfgline);
- if (!strncasecmp(cfgline, HKEY("remote|"))) {
- char recp[SIZ];
- int previous_result = extract_int(cfgline, 2);
- if ((previous_result == 0)
- || (previous_result == 4)) {
- int new_result = 421;
- extract_token(recp, cfgline, 1, '|', sizeof recp);
- new_result = smtp_attempt_delivery(msgid, recp, envelope_from, source_room, server_response);
- syslog(LOG_DEBUG, "smtpclient: recp: <%s> , result: %d (%s)", recp, new_result, server_response);
- if ((new_result / 100) == 2) {
- ++num_success;
- }
- else {
- if ((new_result / 100) == 5) {
- ++num_fail;
- }
- else {
- ++num_delayed;
- }
- StrBufAppendPrintf
- (NewInstr,
- "remote|%s|%ld|%ld (%s)\n",
- recp, (new_result / 100), new_result, server_response);
- }
- }
- }
- }
-
- StrBufAppendPrintf(NewInstr, "attempted|%ld\n", time(NULL));
-
- // All deliveries have now been attempted. Now determine the disposition of this queue entry.
-
- time_t age = time(NULL) - submitted;
- syslog(LOG_DEBUG,
- "smtpclient: submission age: %ldd%ldh%ldm%lds",
- (age / 86400), ((age % 86400) / 3600), ((age % 3600) / 60), (age % 60));
- syslog(LOG_DEBUG, "smtpclient: num_success=%d , num_fail=%d , num_delayed=%d", num_success, num_fail, num_delayed);
-
- // If there are permanent fails on this attempt, deliver a bounce to the user.
- // The 5XX fails will be recorded in the rewritten queue, but they will be removed before the next attempt.
- if (num_fail > 0) {
- smtp_do_bounce(ChrPtr(NewInstr), SDB_BOUNCE_FATALS);
- }
- // If all deliveries have either succeeded or failed, we are finished with this queue entry.
- if (num_delayed == 0) {
- delete_this_queue = 1;
- }
- // If it's been more than five days, give up and tell the sender that delivery failed
- else if ((time(NULL) - submitted) > SMTP_DELIVER_FAIL) {
- smtp_do_bounce(ChrPtr(NewInstr), SDB_BOUNCE_ALL);
- delete_this_queue = 1;
- }
- // If it's been more than four hours but less than five days, warn the sender that delivery is delayed
- else if (((attempted - submitted) < SMTP_DELIVER_WARN)
- && ((time(NULL) - submitted) >= SMTP_DELIVER_WARN)) {
- smtp_do_bounce(ChrPtr(NewInstr), SDB_WARN);
- }
-
- if (delete_this_queue) {
- syslog(LOG_DEBUG, "smtpclient: %ld deleting", qmsgnum);
- deletes[0] = qmsgnum;
- deletes[1] = msgid;
- CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, deletes, 2, "");
- FreeStrBuf(&NewInstr); // We have to free NewInstr here, no longer needed
- }
- else {
- // replace the old queue entry with the new one
- syslog(LOG_DEBUG, "smtpclient: %ld rewriting", qmsgnum);
- msg = convert_internet_message_buf(&NewInstr); // This function will free NewInstr for us
- CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
- CM_Free(msg);
- CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &qmsgnum, 1, "");
- }
- }
- else {
- syslog(LOG_DEBUG, "smtpclient: %ld retry time not reached", qmsgnum);
- }
-
- if (bounceto != NULL) {
- free(bounceto);
- }
- if (envelope_from != NULL) {
- free(envelope_from);
- }
- if (source_room != NULL) {
- free(source_room);
- }
- free(instr);
-}
-
-
-// Callback for smtp_do_queue()
-void smtp_add_msg(long msgnum, void *userdata) {
-
- if (smtpq == NULL) {
- smtpq_count = 0;
- smtpq_alloc = 100;
- smtpq = malloc(smtpq_alloc * sizeof(long));
- }
-
- if (smtpq_alloc >= smtpq_count) {
- smtpq_alloc += 100;
- smtpq = realloc(smtpq, (smtpq_alloc * sizeof(long)));
- }
-
- smtpq[smtpq_count++] = msgnum;
-}
-
-
-// Run through the queue sending out messages.
-void smtp_do_queue(void) {
- int i = 0;
-
- // This is a simple concurrency check to make sure only one smtpclient
- // run is done at a time. We could do this with a mutex, but since we
- // don't really require extremely fine granularity here, we'll do it
- // with a static variable instead.
- if (doing_smtpclient) {
- return;
- }
- doing_smtpclient = 1;
-
- syslog(LOG_DEBUG, "smtpclient: start queue run");
-
- if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
- syslog(LOG_WARNING, "Cannot find room <%s>", SMTP_SPOOLOUT_ROOM);
- doing_smtpclient = 0;
- return;
- }
- // Put the queue in memory so we can close the db cursor
- CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_add_msg, NULL);
-
- // We are ready to run through the queue now.
- for (i = 0; i < smtpq_count; ++i) {
- smtp_process_one_msg(smtpq[i]);
- }
-
- smtpq_count = 0; // don't free it, we will use this memory on the next run
- doing_smtpclient = 0;
- syslog(LOG_DEBUG, "smtpclient: end queue run");
-}
-
-
-// Module entry point
-CTDL_MODULE_INIT(smtpclient)
-{
- if (!threading) {
- CtdlRegisterMessageHook(smtp_aftersave, EVT_AFTERSAVE);
- CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER, PRIO_AGGR + 51);
- smtp_init_spoolout();
- }
-
- // return our module id for the log
- return "smtpclient";
-}
+++ /dev/null
-/*
- * Utility functions for the Citadel SMTP implementation
- *
- * Copyright (c) 1998-2021 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.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <termios.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <syslog.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "genstamp.h"
-#include "domain.h"
-#include "clientsocket.h"
-#include "locate_host.h"
-#include "citadel_dirs.h"
-#include "ctdl_module.h"
-#include "smtp_util.h"
-
-const char *smtp_get_Recipients(void) {
- struct citsmtp *sSMTP = SMTP;
-
- if (sSMTP == NULL)
- return NULL;
- else return ChrPtr(sSMTP->from);
-}
-
-
-/*
- * smtp_do_bounce() is caled by smtp_process_one_msg() to scan a set of delivery
- * instructions for errors and produce/deliver a "bounce" message (delivery
- * status notification).
- *
- * is_final should be set to:
- * SDB_BOUNCE_FATALS Advise the sender of all 5XX (permanent failures)
- * SDB_BOUNCE_ALL Advise the sender that all deliveries have failed and will not be retried
- * SDB_WARN Warn the sender about all 4XX transient delays
- */
-void smtp_do_bounce(const char *instr, int is_final) {
- int i;
- int lines;
- int status;
- char buf[1024];
- char key[1024];
- char addr[1024];
- char dsn[1024];
- char bounceto[1024];
- StrBuf *boundary;
- int num_bounces = 0;
- int bounce_this = 0;
- struct CtdlMessage *bmsg = NULL;
- struct recptypes *valid;
- int successful_bounce = 0;
- static int seq = 0;
- StrBuf *BounceMB;
- long omsgid = (-1);
-
- syslog(LOG_DEBUG, "smtp_do_bounce() called");
- strcpy(bounceto, "");
- boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
-
- StrBufAppendPrintf(boundary, "%s_%04x%04x", CtdlGetConfigStr("c_fqdn"), getpid(), ++seq);
-
- /* Start building our bounce message */
-
- bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
- if (bmsg == NULL) return;
- memset(bmsg, 0, sizeof(struct CtdlMessage));
- BounceMB = NewStrBufPlain(NULL, 1024);
-
- bmsg->cm_magic = CTDLMESSAGE_MAGIC;
- bmsg->cm_anon_type = MES_NORMAL;
- bmsg->cm_format_type = FMT_RFC822;
- CM_SetField(bmsg, eAuthor, HKEY("Citadel"));
- CM_SetField(bmsg, eOriginalRoom, HKEY(MAILROOM));
- CM_SetField(bmsg, eMsgSubject, HKEY("Delivery Status Notification (Failure)"));
- StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
- StrBufAppendBuf(BounceMB, boundary, 0);
- StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
- StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
- StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
- StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
- StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
- StrBufAppendBuf(BounceMB, boundary, 0);
- StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
- StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
-
- if (is_final == SDB_BOUNCE_ALL) {
- StrBufAppendBufPlain(
- BounceMB,
- HKEY( "A message you sent could not be delivered "
- "to some or all of its recipients\ndue to "
- "prolonged unavailability of its destination(s).\n"
- "Giving up on the following addresses:\n\n"),
- 0);
- }
- else if (is_final == SDB_BOUNCE_FATALS) {
- StrBufAppendBufPlain(
- BounceMB,
- HKEY( "A message you sent could not be delivered "
- "to some or all of its recipients.\n"
- "The following addresses were undeliverable:\n\n"),
- 0);
- }
- else if (is_final == SDB_WARN) {
- StrBufAppendBufPlain(
- BounceMB,
- HKEY("A message you sent has not been delivered "
- "to some or all of its recipients.\n"
- "Citadel will continue attempting delivery for five days.\n"
- "The following addresses were undeliverable:\n\n"),
- 0);
- }
- else { // should never get here
- StrBufAppendBufPlain(BounceMB, HKEY("This message should never occur.\n\n"), 0);
- }
-
- /*
- * Now go through the instructions checking for stuff.
- */
- lines = num_tokens(instr, '\n');
- for (i=0; i<lines; ++i) {
- long addrlen;
- long dsnlen;
- extract_token(buf, instr, i, '\n', sizeof buf);
- extract_token(key, buf, 0, '|', sizeof key);
- addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
- status = extract_int(buf, 2);
- dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
- bounce_this = 0;
-
- syslog(LOG_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>", key, addr, status, dsn);
-
- if (!strcasecmp(key, "bounceto")) {
- strcpy(bounceto, addr);
- }
-
- if (!strcasecmp(key, "msgid")) {
- omsgid = atol(addr);
- }
-
- if (!strcasecmp(key, "remote")) {
- if ((is_final == SDB_BOUNCE_FATALS) && (status == 5)) bounce_this = 1;
- if ((is_final == SDB_BOUNCE_ALL) && (status != 2)) bounce_this = 1;
- if ((is_final == SDB_WARN) && (status == 4)) bounce_this = 1;
- }
-
- if (bounce_this) {
- ++num_bounces;
- StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
- StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
- StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
- StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
- }
- }
-
- /* Attach the original message */
- if (omsgid >= 0) {
- StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
- StrBufAppendBuf(BounceMB, boundary, 0);
- StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
- StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
- StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
- StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
- StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
-
- CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
- CtdlOutputMsg(omsgid,
- MT_RFC822,
- HEADERS_ALL,
- 0, 1, NULL, 0,
- NULL, NULL, NULL
- );
- StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
- FreeStrBuf(&CC->redirect_buffer);
- }
-
- /* Close the multipart MIME scope */
- StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
- StrBufAppendBuf(BounceMB, boundary, 0);
- StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
- CM_SetAsFieldSB(bmsg, eMesageText, &BounceMB);
-
- /* Deliver the bounce if there's anything worth mentioning */
- syslog(LOG_DEBUG, "num_bounces = %d", num_bounces);
- if (num_bounces > 0) {
-
- /* First try the user who sent the message */
- if (IsEmptyStr(bounceto)) {
- syslog(LOG_ERR, "No bounce address specified");
- }
- else {
- syslog(LOG_DEBUG, "bounce to user <%s>", bounceto);
- }
- /* Can we deliver the bounce to the original sender? */
- valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
- if (valid != NULL) {
- if (valid->num_error == 0) {
- CtdlSubmitMsg(bmsg, valid, "");
- successful_bounce = 1;
- }
- }
-
- /* If not, post it in the Aide> room */
- if (successful_bounce == 0) {
- CtdlSubmitMsg(bmsg, NULL, CtdlGetConfigStr("c_aideroom"));
- }
-
- /* Free up the memory we used */
- if (valid != NULL) {
- free_recipients(valid);
- }
- }
- FreeStrBuf(&boundary);
- CM_Free(bmsg);
- syslog(LOG_DEBUG, "Done processing bounces");
-}
-
-
-char *smtpcodes[][2] = {
- { "211 - System status / system help reply" },
- { "214", "Help message" },
- { "220", "Domain service ready" },
- { "221", "Domain service closing transmission channel" },
- { "250", "Requested mail action completed and OK" },
- { "251", "Not Local User, forward email to forward path" },
- { "252", "Cannot Verify user, will attempt delivery later" },
- { "253", "Pending messages for node started" },
- { "354", "Start mail input; end with ." },
- { "355", "Octet-offset is the transaction offset" },
- { "421", "Domain service not available, closing transmission channel" },
- { "432", "Domain service not available, closing transmission channel" },
- { "450", "Requested mail action not taken: mailbox unavailable. request refused" },
- { "451", "Requested action aborted: local error in processing Request is unable to be processed, try again" },
- { "452", "Requested action not taken: insufficient system storage" },
- { "453", "No mail" },
- { "454", "TLS not available due to temporary reason. Encryption required for requested authentication mechanism" },
- { "458", "Unable to queue messages for node" },
- { "459", "Node not allowed: reason" },
- { "500", "Syntax error, command unrecognized" },
- { "501", "Syntax error in parameters or arguments" },
- { "502", "Command not implemented" },
- { "503", "Bad sequence of commands" },
- { "504", "Command parameter not implemented" },
- { "510", "Check the recipient address" },
- { "512", "Domain can not be found. Unknown host." },
- { "515", "Destination mailbox address invalid" },
- { "517", "Problem with senders mail attribute, check properties" },
- { "521", "Domain does not accept mail" },
- { "522", "Recipient has exceeded mailbox limit" },
- { "523", "Server limit exceeded. Message too large" },
- { "530", "Access Denied. Authentication required" },
- { "531", "Mail system Full" },
- { "533", "Remote server has insufficient disk space to hold email" },
- { "534", "Authentication mechanism is too weak. Message too big" },
- { "535", "Multiple servers using same IP. Required Authentication" },
- { "538", "Encryption required for requested authentication mechanism" },
- { "540", "Email address has no DNS Server" },
- { "541", "No response from host" },
- { "542", "Bad Connection" },
- { "543", "Routing server failure. No available route" },
- { "546", "Email looping" },
- { "547", "Delivery time-out" },
- { "550", "Requested action not taken: mailbox unavailable or relaying denied" },
- { "551", "User not local; please try forward path" },
- { "552", "Requested mail action aborted: exceeded storage allocation" },
- { "553", "Requested action not taken: mailbox name not allowed" },
- { "554", "Transaction failed" }
-};
-
-
-char *smtpstatus(int code) {
- int i;
-
- for (i=0; i<(sizeof(smtpcodes)/sizeof(char *)/2); ++i) {
- if (atoi(smtpcodes[i][0]) == code) {
- return(smtpcodes[i][1]);
- }
- }
-
- return("Unknown or other SMTP status");
-}
+++ /dev/null
-/*
- * 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.
- *
- */
-
-const char *smtp_get_Recipients(void);
-
-struct citsmtp { /* Information about the current session */
- int command_state;
- StrBuf *Cmd;
- StrBuf *helo_node;
- StrBuf *from;
- StrBuf *recipients;
- StrBuf *OneRcpt;
- int number_of_recipients;
- int delivery_mode;
- int message_originated_locally;
- int is_lmtp;
- int is_unfiltered;
- int is_msa;
- StrBuf *preferred_sender_email;
- StrBuf *preferred_sender_name;
-};
-
-#define SMTP ((struct citsmtp *)CC->session_specific_data)
-
-// These are all the values that can be passed to the is_final parameter of smtp_do_bounce()
-enum {
- SDB_BOUNCE_FATALS,
- SDB_BOUNCE_ALL,
- SDB_WARN
-};
-
-void smtp_do_bounce(const char *instr, int is_final);
-char *smtpstatus(int code);
+++ /dev/null
-/*
- * This module allows Citadel to use SpamAssassin to filter incoming messages
- * arriving via SMTP. For more information on SpamAssassin, visit
- * http://www.spamassassin.org (the SpamAssassin project is not in any way
- * affiliated with the Citadel project).
- *
- * Copyright (c) 1998-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.
- */
-
-#define SPAMASSASSIN_PORT "783"
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <sys/socket.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "domain.h"
-#include "clientsocket.h"
-
-
-#include "ctdl_module.h"
-
-
-
-/*
- * Connect to the SpamAssassin server and scan a message.
- */
-int spam_assassin(struct CtdlMessage *msg, struct recptypes *recp) {
- int sock = (-1);
- char sahosts[SIZ];
- int num_sahosts;
- char buf[SIZ];
- int is_spam = 0;
- int sa;
- StrBuf *msgtext;
- CitContext *CCC=CC;
-
- /* For users who have authenticated to this server we never want to
- * apply spam filtering, because presumably they're trustworthy.
- */
- if (CC->logged_in) return(0);
-
- /* See if we have any SpamAssassin hosts configured */
- num_sahosts = get_hosts(sahosts, "spamassassin");
- if (num_sahosts < 1) return(0);
-
- /* Try them one by one until we get a working one */
- for (sa=0; sa<num_sahosts; ++sa) {
- extract_token(buf, sahosts, sa, '|', sizeof buf);
- syslog(LOG_INFO, "Connecting to SpamAssassin at <%s>\n", buf);
- sock = sock_connect(buf, SPAMASSASSIN_PORT);
- if (sock >= 0) syslog(LOG_DEBUG, "Connected!\n");
- }
-
- if (sock < 0) {
- /* If the service isn't running, just pass the mail
- * through. Potentially throwing away mails isn't good.
- */
- return(0);
- }
-
- CCC->SBuf.Buf = NewStrBuf();
- CCC->sMigrateBuf = NewStrBuf();
- CCC->SBuf.ReadWritePointer = NULL;
-
- /* Command */
- syslog(LOG_DEBUG, "Transmitting command\n");
- sprintf(buf, "CHECK SPAMC/1.2\r\n\r\n");
- sock_write(&sock, buf, strlen(buf));
-
- /* Message */
- CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
- CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, 0);
- msgtext = CC->redirect_buffer;
- CC->redirect_buffer = NULL;
-
- sock_write(&sock, SKEY(msgtext));
- FreeStrBuf(&msgtext);
-
- /* Close one end of the socket connection; this tells SpamAssassin
- * that we're done.
- */
- if (sock != -1)
- sock_shutdown(sock, SHUT_WR);
-
- /* Response */
- syslog(LOG_DEBUG, "Awaiting response\n");
- if (sock_getln(&sock, buf, sizeof buf) < 0) {
- goto bail;
- }
- syslog(LOG_DEBUG, "<%s\n", buf);
- if (strncasecmp(buf, "SPAMD", 5)) {
- goto bail;
- }
- if (sock_getln(&sock, buf, sizeof buf) < 0) {
- goto bail;
- }
- syslog(LOG_DEBUG, "<%s\n", buf);
- syslog(LOG_DEBUG, "c_spam_flag_only setting %d\n", CtdlGetConfigInt("c_spam_flag_only"));
- if (CtdlGetConfigInt("c_spam_flag_only")) {
- int headerlen;
- char *cur;
- char sastatus[10];
- char sascore[10];
- char saoutof[10];
- int numscore;
-
- syslog(LOG_DEBUG, "flag spam code used");
-
- extract_token(sastatus, buf, 1, ' ', sizeof sastatus);
- extract_token(sascore, buf, 3, ' ', sizeof sascore);
- extract_token(saoutof, buf, 5, ' ', sizeof saoutof);
-
- memcpy(buf, HKEY("X-Spam-Level: "));
- cur = buf + 14;
- for (numscore = atoi(sascore); numscore>0; numscore--)
- *(cur++) = '*';
- *cur = '\0';
-
- headerlen = cur - buf;
- headerlen += snprintf(cur, (sizeof(buf) - headerlen),
- "\r\nX-Spam-Status: %s, score=%s required=%s\r\n",
- sastatus, sascore, saoutof);
-
- CM_PrependToField(msg, eMesageText, buf, headerlen);
-
- } else {
- syslog(LOG_DEBUG, "reject spam code used");
- if (!strncasecmp(buf, "Spam: True", 10)) {
- is_spam = 1;
- }
-
- if (is_spam) {
- CM_SetField(msg, eErrorMsg, HKEY("message rejected by spam filter"));
- }
- }
-
-bail: close(sock);
- FreeStrBuf(&CCC->SBuf.Buf);
- FreeStrBuf(&CCC->sMigrateBuf);
- return(is_spam);
-}
-
-
-
-CTDL_MODULE_INIT(spam)
-{
- if (!threading)
- {
- CtdlRegisterMessageHook(spam_assassin, EVT_SMTPSCAN);
- }
-
- /* return our module name for the log */
- return "spam";
-}
+++ /dev/null
-/*
- * This is an empty skeleton of a Citadel server module, included to demonstrate
- * how to add a new module to the system and how to activate it in the server.
- *
- * Copyright (c) 1998-2016 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.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include "ctdl_module.h"
-
-
-void CleanupTest(void) {
- syslog(LOG_DEBUG, "--- test of adding an unload hook --- \n");
-}
-
-void NewRoomTest(void) {
- syslog(LOG_DEBUG, "--- test module was told we're now in a new room ---\n");
-}
-
-void SessionStartTest(void) {
- syslog(LOG_DEBUG, "--- starting up session %d ---\n", CC->cs_pid);
-}
-
-void SessionStopTest(void) {
- syslog(LOG_DEBUG, "--- ending session %d ---\n", CC->cs_pid);
-}
-
-void LoginTest(void) {
- syslog(LOG_DEBUG, "--- Hello, %s ---\n", CC->curr_user);
-}
-
-/* To insert this module into the server activate the next block by changing the #if 0 to #if 1 */
-CTDL_MODULE_INIT(test)
-{
-#if 0
- if (!threading)
- {
- CtdlRegisterCleanupHook(CleanupTest);
- CtdlRegisterSessionHook(NewRoomTest, EVT_NEWROOM, 1);
- CtdlRegisterSessionHook(SessionStartTest, EVT_START, 1);
- CtdlRegisterSessionHook(SessionStopTest, EVT_STOP, 1);
- CtdlRegisterSessionHook(LoginTest, EVT_LOGIN, 1);
- }
-#endif
-
-/* return our module name for the log */
-return "test";
-}
+++ /dev/null
-// Transparently handle the upgrading of server data formats. If we see
-// an existing version number of our database, we can make some intelligent
-// guesses about what kind of data format changes need to be applied, and
-// we apply them transparently.
-//
-// Copyright (c) 1987-2022 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.
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "database.h"
-#include "user_ops.h"
-#include "msgbase.h"
-#include "serv_upgrade.h"
-#include "euidindex.h"
-#include "ctdl_module.h"
-#include "serv_vcard.h"
-#include "internet_addressing.h"
-
-// oldver is the version number of Citadel Server which was active on the previous run of the program, learned from the system configuration.
-// If we are running a new Citadel Server for the first time, oldver will be 0.
-// We keep this value around for the entire duration of the program run because we'll need it during several stages of startup.
-int oldver = 0;
-
-// Try to remove any extra users with number 0
-void fix_sys_user_name(void) {
- struct ctdluser usbuf;
- char usernamekey[USERNAME_SIZE];
-
- while (CtdlGetUserByNumber(&usbuf, 0) == 0) {
- // delete user with number 0 and no name
- if (IsEmptyStr(usbuf.fullname)) {
- cdb_delete(CDB_USERS, "", 0);
- }
- else {
- // temporarily set this user to -1
- usbuf.usernum = -1;
- CtdlPutUser(&usbuf);
- }
- }
-
- // Delete any "user 0" accounts
- while (CtdlGetUserByNumber(&usbuf, -1) == 0) {
- makeuserkey(usernamekey, usbuf.fullname);
- cdb_delete(CDB_USERS, usernamekey, strlen(usernamekey));
- }
-}
-
-
-// Back end processing function for reindex_uids()
-void reindex_uids_backend(char *username, void *data) {
-
- struct ctdluser us;
-
- if (CtdlGetUserLock(&us, username) == 0) {
- syslog(LOG_DEBUG, "Processing <%s> (%d)", us.fullname, us.uid);
- if (us.uid == CTDLUID) {
- us.uid = NATIVE_AUTH_UID;
- }
- CtdlPutUserLock(&us);
- if ((us.uid > 0) && (us.uid != NATIVE_AUTH_UID)) { // if non-native auth , index by uid
- StrBuf *claimed_id = NewStrBuf();
- StrBufPrintf(claimed_id, "uid:%d", us.uid);
- attach_extauth(&us, claimed_id);
- FreeStrBuf(&claimed_id);
- }
- }
-}
-
-
-// Build extauth index of all users with uid-based join (system auth, LDAP auth)
-// Also changes all users with a uid of CTDLUID to NATIVE_AUTH_UID (-1)
-void reindex_uids(void) {
- syslog(LOG_WARNING, "upgrade: reindexing and applying uid changes");
- ForEachUser(reindex_uids_backend, NULL);
- return;
-}
-
-
-// These accounts may have been created by code that ran between mid 2008 and early 2011.
-// If present they are no longer in use and may be deleted.
-void remove_thread_users(void) {
- char *deleteusers[] = {
- "SYS_checkpoint",
- "SYS_extnotify",
- "SYS_IGnet Queue",
- "SYS_indexer",
- "SYS_network",
- "SYS_popclient",
- "SYS_purger",
- "SYS_rssclient",
- "SYS_select_on_master",
- "SYS_SMTP Send"
- };
-
- int i;
- struct ctdluser usbuf;
- for (i=0; i<(sizeof(deleteusers)/sizeof(char *)); ++i) {
- if (CtdlGetUser(&usbuf, deleteusers[i]) == 0) {
- usbuf.axlevel = 0;
- strcpy(usbuf.password, "deleteme");
- CtdlPutUser(&usbuf);
- syslog(LOG_INFO,
- "System user account <%s> is no longer in use and will be deleted.",
- deleteusers[i]
- );
- }
- }
-}
-
-
-// Attempt to guess the name of the time zone currently in use
-// on the underlying host system.
-void guess_time_zone(void) {
- FILE *fp;
- char buf[PATH_MAX];
-
- fp = popen(file_guesstimezone, "r");
- if (fp) {
- if (fgets(buf, sizeof buf, fp) && (strlen(buf) > 2)) {
- buf[strlen(buf)-1] = 0;
- CtdlSetConfigStr("c_default_cal_zone", buf);
- syslog(LOG_INFO, "Configuring timezone: %s", buf);
- }
- fclose(fp);
- }
-}
-
-
-// Per-room callback function for ingest_old_roominfo_and_roompic_files()
-// This is the second pass, where we process the list of rooms with info or pic files.
-void iorarf_oneroom(char *roomname, char *infofile, char *picfile) {
- FILE *fp;
- long data_length;
- char *unencoded_data;
- char *encoded_data;
- long info_msgnum = 0;
- long pic_msgnum = 0;
- char subject[SIZ];
-
- // Test for the presence of a legacy "room info file"
- if (!IsEmptyStr(infofile)) {
- fp = fopen(infofile, "r");
- }
- else {
- fp = NULL;
- }
- if (fp) {
- fseek(fp, 0, SEEK_END);
- data_length = ftell(fp);
-
- if (data_length >= 1) {
- rewind(fp);
- unencoded_data = malloc(data_length);
- if (unencoded_data) {
- fread(unencoded_data, data_length, 1, fp);
- encoded_data = malloc((data_length * 2) + 100);
- if (encoded_data) {
- sprintf(encoded_data, "Content-type: text/plain\nContent-transfer-encoding: base64\n\n");
- CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
- snprintf(subject, sizeof subject, "Imported room banner for %s", roomname);
- info_msgnum = quickie_message("Citadel", NULL, NULL, SYSCONFIGROOM, encoded_data, FMT_RFC822, subject);
- free(encoded_data);
- }
- free(unencoded_data);
- }
- }
- fclose(fp);
- if (info_msgnum > 0) unlink(infofile);
- }
-
- // Test for the presence of a legacy "room picture file" and import it.
- if (!IsEmptyStr(picfile)) {
- fp = fopen(picfile, "r");
- }
- else {
- fp = NULL;
- }
- if (fp) {
- fseek(fp, 0, SEEK_END);
- data_length = ftell(fp);
-
- if (data_length >= 1) {
- rewind(fp);
- unencoded_data = malloc(data_length);
- if (unencoded_data) {
- fread(unencoded_data, data_length, 1, fp);
- encoded_data = malloc((data_length * 2) + 100);
- if (encoded_data) {
- sprintf(encoded_data, "Content-type: image/gif\nContent-transfer-encoding: base64\n\n");
- CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
- snprintf(subject, sizeof subject, "Imported room icon for %s", roomname);
- pic_msgnum = quickie_message("Citadel", NULL, NULL, SYSCONFIGROOM, encoded_data, FMT_RFC822, subject);
- free(encoded_data);
- }
- free(unencoded_data);
- }
- }
- fclose(fp);
- if (pic_msgnum > 0) unlink(picfile);
- }
-
- // Now we have the message numbers of our new banner and icon. Record them in the room record.
- // NOTE: we are not deleting the old msgnum_info because that position in the record was previously
- // a pointer to the highest message number which existed in the room when the info file was saved,
- // and we don't want to delete messages that are not *actually* old banners.
- struct ctdlroom qrbuf;
- if (CtdlGetRoomLock(&qrbuf, roomname) == 0) {
- qrbuf.msgnum_info = info_msgnum;
- qrbuf.msgnum_pic = pic_msgnum;
- CtdlPutRoomLock(&qrbuf);
- }
-
-}
-
-
-struct iorarf_list {
- struct iorarf_list *next;
- char name[ROOMNAMELEN];
- char info[PATH_MAX];
- char pic[PATH_MAX];
-};
-
-
-// Per-room callback function for ingest_old_roominfo_and_roompic_files()
-// This is the first pass, where the list of qualifying rooms is gathered.
-void iorarf_backend(struct ctdlroom *qrbuf, void *data) {
- FILE *fp;
- struct iorarf_list **iorarf_list = (struct iorarf_list **)data;
-
- struct iorarf_list *i = malloc(sizeof(struct iorarf_list));
- i->next = *iorarf_list;
- strcpy(i->name, qrbuf->QRname);
- strcpy(i->info, "");
- strcpy(i->pic, "");
-
- // Test for the presence of a legacy "room info file"
- assoc_file_name(i->info, sizeof i->info, qrbuf, ctdl_info_dir);
- fp = fopen(i->info, "r");
- if (fp) {
- fclose(fp);
- }
- else {
- i->info[0] = 0;
- }
-
- // Test for the presence of a legacy "room picture file"
- assoc_file_name(i->pic, sizeof i->pic, qrbuf, ctdl_image_dir);
- fp = fopen(i->pic, "r");
- if (fp) {
- fclose(fp);
- }
- else {
- i->pic[0] = 0;
- }
-
- if ( (!IsEmptyStr(i->info)) || (!IsEmptyStr(i->pic)) ) {
- *iorarf_list = i;
- }
- else {
- free(i);
- }
-}
-
-
-// Prior to Citadel Server version 902, room info and pictures (which comprise the
-// displayed banner for each room) were stored in the filesystem. If we are upgrading
-// from version >000 to version >=902, ingest those files into the database.
-void ingest_old_roominfo_and_roompic_files(void) {
- struct iorarf_list *il = NULL;
-
- CtdlForEachRoom(iorarf_backend, &il);
-
- struct iorarf_list *p;
- while (il) {
- iorarf_oneroom(il->name, il->info, il->pic);
- p = il->next;
- free(il);
- il = p;
- }
-
- unlink(ctdl_info_dir);
-}
-
-
-// For upgrades in which a new config setting appears for the first time, set default values.
-// For new installations (oldver == 0) also set default values.
-void update_config(void) {
-
- if (oldver < 606) {
- CtdlSetConfigInt("c_rfc822_strict_from", 0);
- }
-
- if (oldver < 609) {
- CtdlSetConfigInt("c_purge_hour", 3);
- }
-
- if (oldver < 615) {
- CtdlSetConfigInt("c_ldap_port", 389);
- }
-
- if (oldver < 623) {
- CtdlSetConfigStr("c_ip_addr", "*");
- }
-
- if (oldver < 650) {
- CtdlSetConfigInt("c_enable_fulltext", 1);
- }
-
- if (oldver < 652) {
- CtdlSetConfigInt("c_auto_cull", 1);
- }
-
- if (oldver < 725) {
- CtdlSetConfigInt("c_xmpp_c2s_port", 5222);
- CtdlSetConfigInt("c_xmpp_s2s_port", 5269);
- }
-
- if (oldver < 830) {
- CtdlSetConfigInt("c_nntp_port", 119);
- CtdlSetConfigInt("c_nntps_port", 563);
- }
-
- if (IsEmptyStr(CtdlGetConfigStr("c_default_cal_zone"))) {
- guess_time_zone();
- }
-}
-
-
-// Helper function for move_inet_addrs_from_vcards_to_user_records()
-//
-// Call this function as a ForEachUser backend in order to queue up
-// user names, or call it with a null user to make it do the processing.
-// This allows us to maintain the list as a static instead of passing
-// pointers around.
-void miafvtur_backend(char *username, void *data) {
- struct ctdluser usbuf;
- char primary_inet_email[512] = { 0 };
- char other_inet_emails[512] = { 0 };
- char combined_inet_emails[512] = { 0 };
-
- if (CtdlGetUser(&usbuf, username) != 0) {
- return;
- }
-
- struct vCard *v = vcard_get_user(&usbuf);
- if (!v) return;
- extract_inet_email_addrs(primary_inet_email, sizeof primary_inet_email, other_inet_emails, sizeof other_inet_emails, v, 1);
- vcard_free(v);
-
- if ( (IsEmptyStr(primary_inet_email)) && (IsEmptyStr(other_inet_emails)) ) {
- return;
- }
-
- snprintf(combined_inet_emails, 512, "%s%s%s",
- (!IsEmptyStr(primary_inet_email) ? primary_inet_email : ""),
- ((!IsEmptyStr(primary_inet_email)&&(!IsEmptyStr(other_inet_emails))) ? "|" : ""),
- (!IsEmptyStr(other_inet_emails) ? other_inet_emails : "")
- );
-
- CtdlSetEmailAddressesForUser(usbuf.fullname, combined_inet_emails);
-}
-
-
-// If our system still has a "refcount_adjustments.dat" sitting around from an old version, ingest it now.
-int ProcessOldStyleAdjRefCountQueue(void) {
- int r;
- FILE *fp;
- struct arcq arcq_rec;
- int num_records_processed = 0;
-
- fp = fopen(file_arcq, "rb");
- if (fp == NULL) {
- return(num_records_processed);
- }
-
- syslog(LOG_INFO, "msgbase: ingesting %s", file_arcq);
-
- while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
- AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
- ++num_records_processed;
- }
-
- fclose(fp);
- r = unlink(file_arcq);
- if (r != 0) {
- syslog(LOG_ERR, "%s: %m", file_arcq);
- }
-
- return(num_records_processed);
-}
-
-
-// Prior to version 912 we kept a user's various Internet email addresses in their vCards.
-// This function moves them over to the user record, which is where we keep them now.
-void move_inet_addrs_from_vcards_to_user_records(void) {
- ForEachUser(miafvtur_backend, NULL);
- CtdlRebuildDirectoryIndex();
-}
-
-
-// We found the legacy sieve config in the user's config room. Store the message number in the user record.
-void mifm_found_config(long msgnum, void *userdata) {
- struct ctdluser *us = (struct ctdluser *)userdata;
-
- us->msgnum_inboxrules = msgnum;
- syslog(LOG_DEBUG, "user: <%s> inbox filter msgnum: <%ld>", us->fullname, us->msgnum_inboxrules);
-}
-
-
-// Helper function for migrate_inbox_filter_msgnums()
-void mifm_backend(char *username, void *data) {
- struct ctdluser us;
- char roomname[ROOMNAMELEN];
-
- if (CtdlGetUserLock(&us, username) == 0) {
- // Take a spin through the user's personal config room
- syslog(LOG_DEBUG, "Processing <%s> (%ld)", us.fullname, us.usernum);
- snprintf(roomname, sizeof roomname, "%010ld.%s", us.usernum, USERCONFIGROOM);
- if (CtdlGetRoom(&CC->room, roomname) == 0) {
- CtdlForEachMessage(MSGS_LAST, 1, NULL, SIEVECONFIG, NULL, mifm_found_config, (void *)&us );
- }
- CtdlPutUserLock(&us);
- }
-}
-
-
-// Prior to version 930 we used a MIME type search to locate the user's inbox filter rules.
-// This function locates those ruleset messages and simply stores the message number in the user record.
-void migrate_inbox_filter_msgnums(void) {
- ForEachUser(mifm_backend, NULL);
-}
-
-
-// Create a default administrator account so we can log in to a new installation
-void create_default_admin_account(void) {
- struct ctdluser usbuf;
-
- create_user(DEFAULT_ADMIN_USERNAME, CREATE_USER_DO_NOT_BECOME_USER, (-1));
- CtdlGetUser(&usbuf, DEFAULT_ADMIN_USERNAME);
- safestrncpy(usbuf.password, DEFAULT_ADMIN_PASSWORD, sizeof(usbuf.password));
- usbuf.axlevel = AxAideU;
- CtdlPutUser(&usbuf);
- CtdlSetConfigStr("c_sysadm", DEFAULT_ADMIN_USERNAME);
-}
-
-
-// Based on the server version number reported by the existing database,
-// run in-place data format upgrades until everything is up to date.
-void pre_startup_upgrades(void) {
-
- oldver = CtdlGetConfigInt("MM_hosted_upgrade_level");
- syslog(LOG_INFO, "Existing database version on disk is %d", oldver);
- update_config();
-
- if (oldver < REV_LEVEL) {
- syslog(LOG_WARNING, "Running pre-startup database upgrades.");
- }
- else {
- return;
- }
-
- if ((oldver > 000) && (oldver < 591)) {
- syslog(LOG_EMERG, "This database is too old to be upgraded. Citadel server will exit.");
- exit(EXIT_FAILURE);
- }
- if ((oldver > 000) && (oldver < 913)) {
- reindex_uids();
- }
- if ((oldver > 000) && (oldver < 659)) {
- rebuild_euid_index();
- }
- if (oldver < 735) {
- fix_sys_user_name();
- }
- if (oldver < 736) {
- rebuild_usersbynumber();
- }
- if (oldver < 790) {
- remove_thread_users();
- }
- if (oldver < 810) {
- struct ctdlroom QRoom;
- if (!CtdlGetRoom(&QRoom, SMTP_SPOOLOUT_ROOM)) {
- QRoom.QRdefaultview = VIEW_QUEUE;
- CtdlPutRoom(&QRoom);
- }
- }
-
- if ((oldver > 000) && (oldver < 902)) {
- ingest_old_roominfo_and_roompic_files();
- }
-
- CtdlSetConfigInt("MM_hosted_upgrade_level", REV_LEVEL);
-
- // Negative values for maxsessions are not allowed.
- if (CtdlGetConfigInt("c_maxsessions") < 0) {
- CtdlSetConfigInt("c_maxsessions", 0);
- }
-
- // We need a system default message expiry policy, because this is
- // the top level and there's no 'higher' policy to fall back on.
- // By default, do not expire messages at all.
- if (CtdlGetConfigInt("c_ep_mode") == 0) {
- CtdlSetConfigInt("c_ep_mode", EXPIRE_MANUAL);
- CtdlSetConfigInt("c_ep_value", 0);
- }
-
- // If this is the first run on an empty database, create a default administrator
- if (oldver == 0) {
- create_default_admin_account();
- }
-}
-
-
-// Based on the server version number reported by the existing database,
-// run in-place data format upgrades until everything is up to date.
-void post_startup_upgrades(void) {
-
- syslog(LOG_INFO, "Existing database version on disk is %d", oldver);
-
- if (oldver < REV_LEVEL) {
- syslog(LOG_WARNING, "Running post-startup database upgrades.");
- }
- else {
- return;
- }
-
- if ((oldver > 000) && (oldver < 912)) {
- move_inet_addrs_from_vcards_to_user_records();
- }
-
- if ((oldver > 000) && (oldver < 922)) {
- ProcessOldStyleAdjRefCountQueue();
- }
-
- if ((oldver > 000) && (oldver < 930)) {
- migrate_inbox_filter_msgnums();
- }
-
-}
-
-
-CTDL_MODULE_UPGRADE(upgrade)
-{
- pre_startup_upgrades();
-
- /* return our module id for the Log */
- return "upgrade";
-}
-
-
-CTDL_MODULE_INIT(upgrade)
-{
- if (!threading) {
- post_startup_upgrades();
- }
-
- /* return our module name for the log */
- return "upgrade";
-}
+++ /dev/null
-/*
- * Copyright (c) 1987-2012 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.
- *
- *
- *
- *
- */
-
+++ /dev/null
-/*
- * A server-side module for Citadel which supports address book information
- * using the standard vCard format.
- *
- * Copyright (c) 1999-2021 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.
- */
-
-/*
- * Format of the "Exclusive ID" field of the message containing a user's
- * vCard. Doesn't matter what it really looks like as long as it's both
- * unique and consistent (because we use it for replication checking to
- * delete the old vCard network-wide when the user enters a new one).
- */
-#define VCARD_EXT_FORMAT "Citadel vCard: personal card for %s at %s"
-
-/*
- * Citadel will accept either text/vcard or text/x-vcard as the MIME type
- * for a vCard. The following definition determines which one it *generates*
- * when serializing.
- */
-#define VCARD_MIME_TYPE "text/x-vcard"
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <ctype.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "room_ops.h"
-#include "internet_addressing.h"
-#include "serv_vcard.h"
-#include "citadel_ldap.h"
-#include "ctdl_module.h"
-
-/*
- * set global flag calling for an aide to validate new users
- */
-void set_mm_valid(void) {
- int flags = 0;
-
- begin_critical_section(S_CONTROL);
- flags = CtdlGetConfigInt("MMflags");
- flags = flags | MM_VALID ;
- CtdlSetConfigInt("MMflags", flags);
- end_critical_section(S_CONTROL);
-}
-
-
-///TODO: gettext!
-#define _(a) a
-
-/*
- * See if there is a valid Internet address in a vCard to use for outbound
- * Internet messages. If there is, stick it in the buffer.
- */
-void extract_inet_email_addrs(char *emailaddrbuf, size_t emailaddrbuf_len,
- char *secemailaddrbuf, size_t secemailaddrbuf_len,
- struct vCard *v,
- int local_addrs_only
-) {
- char *s, *k, *addr;
- int instance = 0;
- int IsDirectoryAddress;
- int saved_instance = 0;
-
- /* Go through the vCard searching for *all* Internet email addresses
- */
- while (s = vcard_get_prop(v, "email", 1, instance, 0), s != NULL) {
- k = vcard_get_prop(v, "email", 1, instance, 1);
- if ( (s != NULL) && (k != NULL) && (bmstrcasestr(k, "internet")) ) {
- addr = strdup(s);
- striplt(addr);
- if (!IsEmptyStr(addr)) {
- IsDirectoryAddress = IsDirectory(addr, 1);
-
- syslog(LOG_DEBUG, "EVQ: addr=<%s> IsDirectoryAddress=<%d> local_addrs_only=<%d>", addr, IsDirectoryAddress, local_addrs_only);
-
- if ( IsDirectoryAddress || !local_addrs_only)
- {
- ++saved_instance;
- if ((saved_instance == 1) && (emailaddrbuf != NULL)) {
- safestrncpy(emailaddrbuf, addr, emailaddrbuf_len);
- }
- else if ((saved_instance == 2) && (secemailaddrbuf != NULL)) {
- safestrncpy(secemailaddrbuf, addr, secemailaddrbuf_len);
- }
- else if ((saved_instance > 2) && (secemailaddrbuf != NULL)) {
- if ( (strlen(addr) + strlen(secemailaddrbuf) + 2)
- < secemailaddrbuf_len ) {
- strcat(secemailaddrbuf, "|");
- strcat(secemailaddrbuf, addr);
- }
- }
- }
- if (!IsDirectoryAddress && local_addrs_only)
- {
- StrBufAppendPrintf(CC->StatusMessage, "\n%d|", ERROR+ ILLEGAL_VALUE);
- StrBufAppendBufPlain(CC->StatusMessage, addr, -1, 0);
- StrBufAppendBufPlain(CC->StatusMessage, HKEY("|"), 0);
- StrBufAppendBufPlain(CC->StatusMessage, _("unable to add this emailaddress; its not matching our domain."), -1, 0);
- }
- }
- free(addr);
- }
- ++instance;
- }
-}
-
-
-/*
- * See if there is a name / screen name / friendly name in a vCard to use for outbound
- * Internet messages. If there is, stick it in the buffer.
- */
-void extract_friendly_name(char *namebuf, size_t namebuf_len, struct vCard *v)
-{
- char *s;
-
- s = vcard_get_prop(v, "fn", 1, 0, 0);
- if (s == NULL) {
- s = vcard_get_prop(v, "n", 1, 0, 0);
- }
-
- if (s != NULL) {
- safestrncpy(namebuf, s, namebuf_len);
- }
-}
-
-
-/*
- * Callback function for vcard_upload_beforesave() hunts for the real vcard in the MIME structure
- */
-void vcard_extract_vcard(char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length,
- char *encoding, char *cbid, void *cbuserdata)
-{
- struct vCard **v = (struct vCard **) cbuserdata;
-
- if ( (!strcasecmp(cbtype, "text/x-vcard"))
- || (!strcasecmp(cbtype, "text/vcard")) ) {
-
- syslog(LOG_DEBUG, "vcard: part %s contains a vCard! Loading...", partnum);
- if (*v != NULL) {
- vcard_free(*v);
- }
- *v = vcard_load(content);
- }
-}
-
-
-/*
- * This handler detects whether the user is attempting to save a new
- * vCard as part of his/her personal configuration, and handles the replace
- * function accordingly (delete the user's existing vCard in the config room
- * and in the global address book).
- */
-int vcard_upload_beforesave(struct CtdlMessage *msg, struct recptypes *recp) {
- char *s;
- char buf[SIZ];
- struct ctdluser usbuf;
- long what_user;
- struct vCard *v = NULL;
- char *ser = NULL;
- int i = 0;
- int yes_my_citadel_config = 0;
- int yes_any_vcard_room = 0;
-
- if ((!CC->logged_in) && (CC->vcard_updated_by_ldap==0)) return(0); /* Only do this if logged in, or if ldap changed the vcard. */
-
- /* Is this some user's "My Citadel Config" room? */
- if (((CC->room.QRflags & QR_MAILBOX) != 0) &&
- (!strcasecmp(&CC->room.QRname[11], USERCONFIGROOM)) ) {
- /* Yes, we want to do this */
- yes_my_citadel_config = 1;
-#ifdef VCARD_SAVES_BY_AIDES_ONLY
- /* Prevent non-aides from performing registration changes, but ldap is ok. */
- if ((CC->user.axlevel < AxAideU) && (CC->vcard_updated_by_ldap==0)) {
- return(1);
- }
-#endif
-
- }
-
- /* Is this a room with an address book in it? */
- if (CC->room.QRdefaultview == VIEW_ADDRESSBOOK) {
- yes_any_vcard_room = 1;
- }
-
- /* If neither condition exists, don't run this hook. */
- if ( (!yes_my_citadel_config) && (!yes_any_vcard_room) ) {
- return(0);
- }
-
- /* If this isn't a MIME message, don't bother. */
- if (msg->cm_format_type != 4) return(0);
-
- /* Ok, if we got this far, look into the situation further... */
-
- if (CM_IsEmpty(msg, eMesageText)) return(0);
-
- mime_parser(CM_RANGE(msg, eMesageText),
- *vcard_extract_vcard,
- NULL, NULL,
- &v, /* user data ptr - put the vcard here */
- 0
- );
-
- if (v == NULL) return(0); /* no vCards were found in this message */
-
- /* If users cannot create their own accounts, they cannot re-register either. */
- if ( (yes_my_citadel_config) &&
- (CtdlGetConfigInt("c_disable_newu")) &&
- (CC->user.axlevel < AxAideU) &&
- (CC->vcard_updated_by_ldap==0) )
- {
- return(1);
- }
-
- vcard_get_prop(v, "fn", 1, 0, 0);
-
-
- if (yes_my_citadel_config) {
- /* Bingo! The user is uploading a new vCard, so
- * delete the old one. First, figure out which user
- * is being re-registered...
- */
- what_user = atol(CC->room.QRname);
-
- if (what_user == CC->user.usernum) {
- /* It's the logged in user. That was easy. */
- memcpy(&usbuf, &CC->user, sizeof(struct ctdluser));
- }
-
- else if (CtdlGetUserByNumber(&usbuf, what_user) == 0) {
- /* We fetched a valid user record */
- }
-
- else {
- /* somebody set up us the bomb! */
- yes_my_citadel_config = 0;
- }
- }
-
- if (yes_my_citadel_config) {
- /* Delete the user's old vCard. This would probably
- * get taken care of by the replication check, but we
- * want to make sure there is absolutely only one
- * vCard in the user's config room at all times.
- *
- */
- CtdlDeleteMessages(CC->room.QRname, NULL, 0, "[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$");
-
- /* Make the author of the message the name of the user. */
- if (!IsEmptyStr(usbuf.fullname)) {
- CM_SetField(msg, eAuthor, usbuf.fullname, strlen(usbuf.fullname));
- }
- }
-
- /* Insert or replace RFC2739-compliant free/busy URL */
- if (yes_my_citadel_config) {
- sprintf(buf, "http://%s/%s.vfb",
- CtdlGetConfigStr("c_fqdn"),
- usbuf.fullname);
- for (i=0; buf[i]; ++i) {
- if (buf[i] == ' ') buf[i] = '_';
- }
- vcard_set_prop(v, "FBURL;PREF", buf, 0);
- }
-
-
- s = vcard_get_prop(v, "UID", 1, 0, 0);
- if (s == NULL) { /* Note LDAP auth sets UID from the LDAP UUID, use that if it exists. */
- /* Enforce local UID policy if applicable */
- if (yes_my_citadel_config) {
- snprintf(buf, sizeof buf, VCARD_EXT_FORMAT, msg->cm_fields[eAuthor], NODENAME);
- } else {
- /* If the vCard has no UID, then give it one. */
- generate_uuid(buf);
- }
- vcard_set_prop(v, "UID", buf, 0);
- }
-
-
- /*
- * Set the EUID of the message to the UID of the vCard.
- */
- CM_FlushField(msg, eExclusiveID);
-
- s = vcard_get_prop(v, "UID", 1, 0, 0);
- if (!IsEmptyStr(s)) {
- CM_SetField(msg, eExclusiveID, s, strlen(s));
- if (CM_IsEmpty(msg, eMsgSubject)) {
- CM_CopyField(msg, eMsgSubject, eExclusiveID);
- }
- }
-
- /*
- * Set the Subject to the name in the vCard.
- */
- s = vcard_get_prop(v, "FN", 1, 0, 0);
- if (s == NULL) {
- s = vcard_get_prop(v, "N", 1, 0, 0);
- }
- if (!IsEmptyStr(s)) {
- CM_SetField(msg, eMsgSubject, s, strlen(s));
- }
-
- /* Re-serialize it back into the msg body */
- ser = vcard_serialize(v);
- if (!IsEmptyStr(ser)) {
- StrBuf *buf;
- long serlen;
-
- serlen = strlen(ser);
- buf = NewStrBufPlain(NULL, serlen + 1024);
-
- StrBufAppendBufPlain(buf, HKEY("Content-type: " VCARD_MIME_TYPE "\r\n\r\n"), 0);
- StrBufAppendBufPlain(buf, ser, serlen, 0);
- StrBufAppendBufPlain(buf, HKEY("\r\n"), 0);
- CM_SetAsFieldSB(msg, eMesageText, &buf);
- free(ser);
- }
-
- /* Now allow the save to complete. */
- vcard_free(v);
- return(0);
-}
-
-
-/*
- * This handler detects whether the user is attempting to save a new
- * vCard as part of his/her personal configuration, and handles the replace
- * function accordingly (copy the vCard from the config room to the global
- * address book).
- */
-int vcard_upload_aftersave(struct CtdlMessage *msg, struct recptypes *recp) {
- char *ptr;
- int linelen;
- long I;
- struct vCard *v;
- int is_UserConf=0;
- int is_MY_UserConf=0;
- int is_GAB=0;
- char roomname[ROOMNAMELEN];
-
- if (msg->cm_format_type != 4) return(0);
- if ((!CC->logged_in) && (CC->vcard_updated_by_ldap==0)) return(0); /* Only do this if logged in, or if ldap changed the vcard. */
-
- /* We're interested in user config rooms only. */
-
- if ( !IsEmptyStr(CC->room.QRname) &&
- (strlen(CC->room.QRname) >= 12) &&
- (!strcasecmp(&CC->room.QRname[11], USERCONFIGROOM)) ) {
- is_UserConf = 1; /* It's someone's config room */
- }
- CtdlMailboxName(roomname, sizeof roomname, &CC->user, USERCONFIGROOM);
- if (!strcasecmp(CC->room.QRname, roomname)) {
- is_UserConf = 1;
- is_MY_UserConf = 1; /* It's MY config room */
- }
- if (!strcasecmp(CC->room.QRname, ADDRESS_BOOK_ROOM)) {
- is_GAB = 1; /* It's the Global Address Book */
- }
-
- if (!is_UserConf && !is_GAB) return(0);
-
- if (CM_IsEmpty(msg, eMesageText))
- return 0;
-
- ptr = msg->cm_fields[eMesageText];
-
- CC->vcard_updated_by_ldap=0; /* As this will write LDAP's previous changes, disallow LDAP change auth until next LDAP change. */
-
- NewStrBufDupAppendFlush(&CC->StatusMessage, NULL, NULL, 0);
-
- StrBufPrintf(CC->StatusMessage, "%d\n", LISTING_FOLLOWS);
-
- while (ptr != NULL) {
-
- linelen = strcspn(ptr, "\n");
- if (linelen == 0) return(0); /* end of headers */
-
- if ( (!strncasecmp(ptr, "Content-type: text/x-vcard", 26))
- || (!strncasecmp(ptr, "Content-type: text/vcard", 24)) ) {
- /*
- * Bingo! The user is uploading a new vCard, so
- * copy it to the Global Address Book room.
- */
-
- I = atol(msg->cm_fields[eVltMsgNum]);
- if (I <= 0L) return(0);
-
- /* Store our friendly/display name in memory */
- if (is_MY_UserConf) {
- v = vcard_load(msg->cm_fields[eMesageText]);
- extract_friendly_name(CC->cs_inet_fn, sizeof CC->cs_inet_fn, v);
- vcard_free(v);
- }
-
- if (!is_GAB)
- { // This is not the GAB
- /* Put it in the Global Address Book room... */
- CtdlSaveMsgPointerInRoom(ADDRESS_BOOK_ROOM, I, 1, msg);
- }
-
- /* Some sites want an Aide to be notified when a
- * user registers or re-registers
- * But if the user was an Aide or was edited by an Aide then we can
- * Assume they don't need validating.
- */
- if (CC->user.axlevel >= AxAideU) {
- CtdlLockGetCurrentUser();
- CC->user.flags |= US_REGIS;
- CtdlPutCurrentUserLock();
- return (0);
- }
-
- set_mm_valid();
-
- /* ...which also means we need to flag the user */
- CtdlLockGetCurrentUser();
- CC->user.flags |= (US_REGIS|US_NEEDVALID);
- CtdlPutCurrentUserLock();
-
- return(0);
- }
-
- ptr = strchr((char *)ptr, '\n');
- if (ptr != NULL) ++ptr;
- }
-
- return(0);
-}
-
-
-
-/*
- * back end function used for callbacks
- */
-void vcard_gu_backend(long supplied_msgnum, void *userdata) {
- long *msgnum;
-
- msgnum = (long *) userdata;
- *msgnum = supplied_msgnum;
-}
-
-
-/*
- * If this user has a vcard on disk, read it into memory, otherwise allocate
- * and return an empty vCard.
- */
-struct vCard *vcard_get_user(struct ctdluser *u) {
- char hold_rm[ROOMNAMELEN];
- char config_rm[ROOMNAMELEN];
- struct CtdlMessage *msg = NULL;
- struct vCard *v;
- long VCmsgnum;
-
- strcpy(hold_rm, CC->room.QRname); /* save current room */
- CtdlMailboxName(config_rm, sizeof config_rm, u, USERCONFIGROOM);
-
- if (CtdlGetRoom(&CC->room, config_rm) != 0) {
- CtdlGetRoom(&CC->room, hold_rm);
- return vcard_new();
- }
-
- /* We want the last (and probably only) vcard in this room */
- VCmsgnum = (-1);
- CtdlForEachMessage(MSGS_LAST, 1, NULL, "[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$",
- NULL, vcard_gu_backend, (void *)&VCmsgnum );
- CtdlGetRoom(&CC->room, hold_rm); /* return to saved room */
-
- if (VCmsgnum < 0L) return vcard_new();
-
- msg = CtdlFetchMessage(VCmsgnum, 1);
- if (msg == NULL) return vcard_new();
-
- v = vcard_load(msg->cm_fields[eMesageText]);
- CM_Free(msg);
- return v;
-}
-
-
-/*
- * Store this user's vCard in the appropriate place
- */
-/*
- * Write our config to disk
- */
-void vcard_write_user(struct ctdluser *u, struct vCard *v) {
- char *ser;
-
- ser = vcard_serialize(v);
- if (ser == NULL) {
- ser = strdup("begin:vcard\r\nend:vcard\r\n");
- }
- if (ser == NULL) return;
-
- /* This handy API function does all the work for us.
- * NOTE: normally we would want to set that last argument to 1, to
- * force the system to delete the user's old vCard. But it doesn't
- * have to, because the vcard_upload_beforesave() hook above
- * is going to notice what we're trying to do, and delete the old vCard.
- */
- CtdlWriteObject(USERCONFIGROOM, /* which room */
- VCARD_MIME_TYPE, /* MIME type */
- ser, /* data */
- strlen(ser)+1, /* length */
- u, /* which user */
- 0, /* not binary */
- 0); /* no flags */
-
- free(ser);
-}
-
-
-
-/*
- * Old style "enter registration info" command. This function simply honors
- * the REGI protocol command, translates the entered parameters into a vCard,
- * and enters the vCard into the user's configuration.
- */
-void cmd_regi(char *argbuf) {
- int a,b,c;
- char buf[SIZ];
- struct vCard *my_vcard;
-
- char tmpaddr[SIZ];
- char tmpcity[SIZ];
- char tmpstate[SIZ];
- char tmpzip[SIZ];
- char tmpaddress[SIZ];
- char tmpcountry[SIZ];
-
- unbuffer_output();
-
- if (!(CC->logged_in)) {
- cprintf("%d Not logged in.\n",ERROR + NOT_LOGGED_IN);
- return;
- }
-
- /* If users cannot create their own accounts, they cannot re-register either. */
- if ( (CtdlGetConfigInt("c_disable_newu")) && (CC->user.axlevel < AxAideU) ) {
- cprintf("%d Self-service registration is not allowed here.\n",
- ERROR + HIGHER_ACCESS_REQUIRED);
- }
-
- my_vcard = vcard_get_user(&CC->user);
- strcpy(tmpaddr, "");
- strcpy(tmpcity, "");
- strcpy(tmpstate, "");
- strcpy(tmpzip, "");
- strcpy(tmpcountry, "USA");
-
- cprintf("%d Send registration...\n", SEND_LISTING);
- a=0;
- while (client_getln(buf, sizeof buf), strcmp(buf,"000")) {
- if (a==0) vcard_set_prop(my_vcard, "n", buf, 0);
- if (a==1) strcpy(tmpaddr, buf);
- if (a==2) strcpy(tmpcity, buf);
- if (a==3) strcpy(tmpstate, buf);
- if (a==4) {
- for (c=0; buf[c]; ++c) {
- if ((buf[c]>='0') && (buf[c]<='9')) {
- b = strlen(tmpzip);
- tmpzip[b] = buf[c];
- tmpzip[b+1] = 0;
- }
- }
- }
- if (a==5) vcard_set_prop(my_vcard, "tel", buf, 0);
- if (a==6) vcard_set_prop(my_vcard, "email;internet", buf, 0);
- if (a==7) strcpy(tmpcountry, buf);
- ++a;
- }
-
- snprintf(tmpaddress, sizeof tmpaddress, ";;%s;%s;%s;%s;%s",
- tmpaddr, tmpcity, tmpstate, tmpzip, tmpcountry);
- vcard_set_prop(my_vcard, "adr", tmpaddress, 0);
- vcard_write_user(&CC->user, my_vcard);
- vcard_free(my_vcard);
-}
-
-
-/*
- * Protocol command to fetch registration info for a user
- */
-void cmd_greg(char *argbuf)
-{
- struct ctdluser usbuf;
- struct vCard *v;
- char *s;
- char who[USERNAME_SIZE];
- char adr[256];
- char buf[256];
-
- extract_token(who, argbuf, 0, '|', sizeof who);
-
- if (!(CC->logged_in)) {
- cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
- return;
- }
-
- if (!strcasecmp(who,"_SELF_")) strcpy(who,CC->curr_user);
-
- if ((CC->user.axlevel < AxAideU) && (strcasecmp(who,CC->curr_user))) {
- cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
- return;
- }
-
- if (CtdlGetUser(&usbuf, who) != 0) {
- cprintf("%d '%s' not found.\n", ERROR + NO_SUCH_USER, who);
- return;
- }
-
- v = vcard_get_user(&usbuf);
-
- cprintf("%d %s\n", LISTING_FOLLOWS, usbuf.fullname);
- cprintf("%ld\n", usbuf.usernum);
- cprintf("%s\n", usbuf.password);
- s = vcard_get_prop(v, "n", 1, 0, 0);
- cprintf("%s\n", s ? s : " "); /* name */
- s = vcard_get_prop(v, "adr", 1, 0, 0);
- snprintf(adr, sizeof adr, "%s", s ? s : " "); /* address */
- extract_token(buf, adr, 2, ';', sizeof buf);
- cprintf("%s\n", buf); /* street */
- extract_token(buf, adr, 3, ';', sizeof buf);
- cprintf("%s\n", buf); /* city */
- extract_token(buf, adr, 4, ';', sizeof buf);
- cprintf("%s\n", buf); /* state */
- extract_token(buf, adr, 5, ';', sizeof buf);
- cprintf("%s\n", buf); /* zip */
-
- s = vcard_get_prop(v, "tel", 1, 0, 0);
- if (s == NULL) s = vcard_get_prop(v, "tel", 1, 0, 0);
- if (s != NULL) {
- cprintf("%s\n", s);
- }
- else {
- cprintf(" \n");
- }
-
- cprintf("%d\n", usbuf.axlevel);
-
- s = vcard_get_prop(v, "email;internet", 0, 0, 0);
- cprintf("%s\n", s ? s : " ");
- s = vcard_get_prop(v, "adr", 0, 0, 0);
- snprintf(adr, sizeof adr, "%s", s ? s : " ");/* address... */
-
- extract_token(buf, adr, 6, ';', sizeof buf);
- cprintf("%s\n", buf); /* country */
- cprintf("000\n");
- vcard_free(v);
-}
-
-
-
-/*
- * When a user is being created, create his/her vCard.
- */
-void vcard_newuser(struct ctdluser *usbuf) {
- char vname[256];
- char buf[256];
- int i;
- struct vCard *v;
- int need_default_vcard;
-
- need_default_vcard =1;
- vcard_fn_to_n(vname, usbuf->fullname, sizeof vname);
- syslog(LOG_DEBUG, "vcard: converted <%s> to <%s>", usbuf->fullname, vname);
-
- /* Create and save the vCard */
- v = vcard_new();
- if (v == NULL) return;
- vcard_add_prop(v, "fn", usbuf->fullname);
- vcard_add_prop(v, "n", vname);
- vcard_add_prop(v, "adr", "adr:;;_;_;_;00000;__");
-
-#ifdef HAVE_GETPWUID_R
- /* If using host auth mode, we add an email address based on the login */
- if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_HOST) {
- struct passwd pwd;
- char pwd_buffer[SIZ];
-
-#ifdef SOLARIS_GETPWUID
- if (getpwuid_r(usbuf->uid, &pwd, pwd_buffer, sizeof pwd_buffer) != NULL) {
-#else // SOLARIS_GETPWUID
- struct passwd *result = NULL;
- syslog(LOG_DEBUG, "vcard: searching for uid %d", usbuf->uid);
- if (getpwuid_r(usbuf->uid, &pwd, pwd_buffer, sizeof pwd_buffer, &result) == 0) {
-#endif // HAVE_GETPWUID_R
- snprintf(buf, sizeof buf, "%s@%s", pwd.pw_name, CtdlGetConfigStr("c_fqdn"));
- vcard_add_prop(v, "email;internet", buf);
- need_default_vcard = 0;
- }
- }
-#endif
-
- /*
- * Is this an LDAP session? If so, copy various LDAP attributes from the directory entry
- * into the user's vCard.
- */
- if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) {
- //uid_t ldap_uid;
- int found_user;
- char ldap_cn[512];
- char ldap_dn[512];
-
- syslog(LOG_DEBUG, "\033[31m FIXME BORK BORK BORK try lookup by uid , or maybe dn?\033[0m");
-
- found_user = CtdlTryUserLDAP(usbuf->fullname, ldap_dn, sizeof ldap_dn, ldap_cn, sizeof ldap_cn, &usbuf->uid);
- if (found_user == 0) {
- if (Ctdl_LDAP_to_vCard(ldap_dn, v)) {
- /* Allow global address book and internet directory update without login long enough to write this. */
- CC->vcard_updated_by_ldap++; /* Otherwise we'll only update the user config. */
- need_default_vcard = 0;
- syslog(LOG_DEBUG, "vcard: LDAP Created Initial vCard for %s\n",usbuf->fullname);
- }
- }
- }
-
- if (need_default_vcard!=0) {
- /* Everyone gets an email address based on their display name */
- snprintf(buf, sizeof buf, "%s@%s", usbuf->fullname, CtdlGetConfigStr("c_fqdn"));
- for (i=0; buf[i]; ++i) {
- if (buf[i] == ' ') buf[i] = '_';
- }
- vcard_add_prop(v, "email;internet", buf);
- }
- vcard_write_user(usbuf, v);
- vcard_free(v);
-}
-
-
-/*
- * Get Valid Screen Names
- */
-void cmd_gvsn(char *argbuf)
-{
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- cprintf("%d valid screen names:\n", LISTING_FOLLOWS);
- cprintf("%s\n", CC->user.fullname);
- if ( (!IsEmptyStr(CC->cs_inet_fn)) && (strcasecmp(CC->user.fullname, CC->cs_inet_fn)) ) {
- cprintf("%s\n", CC->cs_inet_fn);
- }
- cprintf("000\n");
-}
-
-
-/*
- * Get Valid Email Addresses
- * FIXME this doesn't belong in serv_vcard.c anymore , maybe move it to internet_addressing.c
- */
-void cmd_gvea(char *argbuf)
-{
- int num_secondary_emails = 0;
- int i;
- char buf[256];
-
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- cprintf("%d valid email addresses:\n", LISTING_FOLLOWS);
- if (!IsEmptyStr(CC->cs_inet_email)) {
- cprintf("%s\n", CC->cs_inet_email);
- }
- if (!IsEmptyStr(CC->cs_inet_other_emails)) {
- num_secondary_emails = num_tokens(CC->cs_inet_other_emails, '|');
- for (i=0; i<num_secondary_emails; ++i) {
- extract_token(buf, CC->cs_inet_other_emails,i,'|',sizeof CC->cs_inet_other_emails);
- cprintf("%s\n", buf);
- }
- }
- cprintf("000\n");
-}
-
-
-/*
- * Callback function for cmd_dvca() that hunts for vCard content types
- * and outputs any email addresses found within.
- */
-void dvca_mime_callback(char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
- char *cbid, void *cbuserdata) {
-
- struct vCard *v;
- char displayname[256] = "";
- int displayname_len;
- char emailaddr[256] = "";
- int i;
- int has_commas = 0;
-
- if ( (strcasecmp(cbtype, "text/vcard")) && (strcasecmp(cbtype, "text/x-vcard")) ) {
- return;
- }
-
- v = vcard_load(content);
- if (v == NULL) return;
-
- extract_friendly_name(displayname, sizeof displayname, v);
- extract_inet_email_addrs(emailaddr, sizeof emailaddr, NULL, 0, v, 0);
-
- displayname_len = strlen(displayname);
- for (i=0; i<displayname_len; ++i) {
- if (displayname[i] == '\"') displayname[i] = ' ';
- if (displayname[i] == ';') displayname[i] = ',';
- if (displayname[i] == ',') has_commas = 1;
- }
- striplt(displayname);
-
- cprintf("%s%s%s <%s>\n",
- (has_commas ? "\"" : ""),
- displayname,
- (has_commas ? "\"" : ""),
- emailaddr
- );
-
- vcard_free(v);
-}
-
-
-/*
- * Back end callback function for cmd_dvca()
- *
- * It's basically just passed a list of message numbers, which we're going
- * to fetch off the disk and then pass along to the MIME parser via another
- * layer of callback...
- */
-void dvca_callback(long msgnum, void *userdata) {
- struct CtdlMessage *msg = NULL;
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) return;
- mime_parser(CM_RANGE(msg, eMesageText),
- *dvca_mime_callback, /* callback function */
- NULL, NULL,
- NULL, /* user data */
- 0
- );
- CM_Free(msg);
-}
-
-
-/*
- * Dump VCard Addresses
- */
-void cmd_dvca(char *argbuf)
-{
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- cprintf("%d addresses:\n", LISTING_FOLLOWS);
- CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL, dvca_callback, NULL);
- cprintf("000\n");
-}
-
-
-/*
- * Query Directory
- */
-void cmd_qdir(char *argbuf) {
- char citadel_addr[256];
- char internet_addr[256];
-
- if (CtdlAccessCheck(ac_logged_in)) return;
-
- extract_token(internet_addr, argbuf, 0, '|', sizeof internet_addr);
-
- if (CtdlDirectoryLookup(citadel_addr, internet_addr, sizeof citadel_addr) != 0) {
- cprintf("%d %s was not found.\n",
- ERROR + NO_SUCH_USER, internet_addr);
- return;
- }
-
- cprintf("%d %s\n", CIT_OK, citadel_addr);
-}
-
-
-/*
- * Query Directory, in fact an alias to match postfix tcp auth.
- */
-void check_get(void) {
- char internet_addr[256];
-
- char cmdbuf[SIZ];
-
- time(&CC->lastcmd);
- memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
- if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
- syslog(LOG_ERR, "vcard: client disconnected: ending session.");
- CC->kill_me = KILLME_CLIENT_DISCONNECTED;
- return;
- }
- syslog(LOG_INFO, ": %s", cmdbuf);
- while (strlen(cmdbuf) < 3) strcat(cmdbuf, " ");
- syslog(LOG_INFO, "[ %s]", cmdbuf);
-
- if (strncasecmp(cmdbuf, "GET ", 4)==0)
- {
- struct recptypes *rcpt;
- char *argbuf = &cmdbuf[4];
-
- extract_token(internet_addr, argbuf, 0, '|', sizeof internet_addr);
- rcpt = validate_recipients(internet_addr, NULL, CHECK_EXIST);
- if ( (rcpt != NULL) &&
- (
- (*rcpt->recp_local != '\0') ||
- (*rcpt->recp_room != '\0')
- )
- ) {
- cprintf("200 OK %s\n", internet_addr);
- syslog(LOG_INFO, "vcard: sending 200 OK for the room %s", rcpt->display_recp);
- }
- else
- {
- cprintf("500 REJECT no one here by that name.\n");
-
- syslog(LOG_INFO, "vcard: sending 500 REJECT no one here by that name: %s", internet_addr);
- }
- if (rcpt != NULL)
- free_recipients(rcpt);
- }
- else {
- cprintf("500 REJECT invalid Query.\n");
- syslog(LOG_INFO, "vcard: sending 500 REJECT invalid query: %s", internet_addr);
- }
-}
-
-
-void check_get_greeting(void) {
-/* dummy function, we have no greeting in this very simple protocol. */
-}
-
-
-/*
- * We don't know if the Contacts room exists so we just create it at login
- */
-void vcard_CtdlCreateRoom(void)
-{
- struct ctdlroom qr;
- visit vbuf;
-
- /* Create the calendar room if it doesn't already exist */
- CtdlCreateRoom(USERCONTACTSROOM, 4, "", 0, 1, 0, VIEW_ADDRESSBOOK);
-
- /* Set expiration policy to manual; otherwise objects will be lost! */
- if (CtdlGetRoomLock(&qr, USERCONTACTSROOM)) {
- syslog(LOG_ERR, "vcard: couldn't get the user CONTACTS room!");
- return;
- }
- qr.QRep.expire_mode = EXPIRE_MANUAL;
- qr.QRdefaultview = VIEW_ADDRESSBOOK; /* 2 = address book view */
- CtdlPutRoomLock(&qr);
-
- /* Set the view to a calendar view */
- CtdlGetRelationship(&vbuf, &CC->user, &qr);
- vbuf.v_view = 2; /* 2 = address book view */
- CtdlSetRelationship(&vbuf, &CC->user, &qr);
-
- return;
-}
-
-
-
-
-/*
- * When a user logs in...
- */
-void vcard_session_login_hook(void) {
- struct vCard *v = NULL;
-
- /*
- * Is this an LDAP session? If so, copy various LDAP attributes from the directory entry
- * into the user's vCard.
- */
- if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) {
- v = vcard_get_user(&CC->user);
- if (v) {
- if (Ctdl_LDAP_to_vCard(CC->ldap_dn, v)) {
- CC->vcard_updated_by_ldap++; /* Make sure changes make it to the global address book and internet directory, not just the user config. */
- syslog(LOG_DEBUG, "vcard: LDAP Detected vcard change");
- vcard_write_user(&CC->user, v);
- }
- }
- }
-
- /*
- * Extract the user's friendly/screen name
- * These are inserted into the session data for various message entry commands to use.
- */
- v = vcard_get_user(&CC->user);
- if (v) {
- extract_friendly_name(CC->cs_inet_fn, sizeof CC->cs_inet_fn, v);
- vcard_free(v);
- }
-
- /*
- * Create the user's 'Contacts' room (personal address book) if it doesn't already exist.
- */
- vcard_CtdlCreateRoom();
-}
-
-
-/*
- * Turn an arbitrary RFC822 address into a struct vCard for possible
- * inclusion into an address book.
- */
-struct vCard *vcard_new_from_rfc822_addr(char *addr) {
- struct vCard *v;
- char user[256], node[256], name[256], email[256], n[256], uid[256];
- int i;
-
- v = vcard_new();
- if (v == NULL) return(NULL);
-
- process_rfc822_addr(addr, user, node, name);
- vcard_set_prop(v, "fn", name, 0);
-
- vcard_fn_to_n(n, name, sizeof n);
- vcard_set_prop(v, "n", n, 0);
-
- snprintf(email, sizeof email, "%s@%s", user, node);
- vcard_set_prop(v, "email;internet", email, 0);
-
- snprintf(uid, sizeof uid, "collected: %s %s@%s", name, user, node);
- for (i=0; uid[i]; ++i) {
- if (isspace(uid[i])) uid[i] = '_';
- uid[i] = tolower(uid[i]);
- }
- vcard_set_prop(v, "UID", uid, 0);
-
- return(v);
-}
-
-
-/*
- * This is called by store_harvested_addresses() to remove from the
- * list any addresses we already have in our address book.
- */
-void strip_addresses_already_have(long msgnum, void *userdata) {
- char *collected_addresses;
- struct CtdlMessage *msg = NULL;
- struct vCard *v;
- char *value = NULL;
- int i, j;
- char addr[256], user[256], node[256], name[256];
-
- collected_addresses = (char *)userdata;
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) return;
- v = vcard_load(msg->cm_fields[eMesageText]);
- CM_Free(msg);
-
- i = 0;
- while (value = vcard_get_prop(v, "email", 1, i++, 0), value != NULL) {
-
- for (j=0; j<num_tokens(collected_addresses, ','); ++j) {
- extract_token(addr, collected_addresses, j, ',', sizeof addr);
-
- /* Remove the address if we already have it! */
- process_rfc822_addr(addr, user, node, name);
- snprintf(addr, sizeof addr, "%s@%s", user, node);
- if (!strcasecmp(value, addr)) {
- remove_token(collected_addresses, j, ',');
- }
- }
-
- }
-
- vcard_free(v);
-}
-
-
-
-/*
- * Back end function for store_harvested_addresses()
- */
-void store_this_ha(struct addresses_to_be_filed *aptr) {
- struct CtdlMessage *vmsg = NULL;
- char *ser = NULL;
- struct vCard *v = NULL;
- char recipient[256];
- int i;
-
- /* First remove any addresses we already have in the address book */
- CtdlUserGoto(aptr->roomname, 0, 0, NULL, NULL, NULL, NULL);
- CtdlForEachMessage(MSGS_ALL, 0, NULL, "[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$", NULL,
- strip_addresses_already_have, aptr->collected_addresses);
-
- if (!IsEmptyStr(aptr->collected_addresses))
- for (i=0; i<num_tokens(aptr->collected_addresses, ','); ++i) {
-
- /* Make a vCard out of each address */
- extract_token(recipient, aptr->collected_addresses, i, ',', sizeof recipient);
- striplt(recipient);
- v = vcard_new_from_rfc822_addr(recipient);
- if (v != NULL) {
- const char *s;
- vmsg = malloc(sizeof(struct CtdlMessage));
- memset(vmsg, 0, sizeof(struct CtdlMessage));
- vmsg->cm_magic = CTDLMESSAGE_MAGIC;
- vmsg->cm_anon_type = MES_NORMAL;
- vmsg->cm_format_type = FMT_RFC822;
- CM_SetField(vmsg, eAuthor, HKEY("Citadel"));
- s = vcard_get_prop(v, "UID", 1, 0, 0);
- if (!IsEmptyStr(s)) {
- CM_SetField(vmsg, eExclusiveID, s, strlen(s));
- }
- ser = vcard_serialize(v);
- if (ser != NULL) {
- StrBuf *buf;
- long serlen;
-
- serlen = strlen(ser);
- buf = NewStrBufPlain(NULL, serlen + 1024);
-
- StrBufAppendBufPlain(buf, HKEY("Content-type: " VCARD_MIME_TYPE "\r\n\r\n"), 0);
- StrBufAppendBufPlain(buf, ser, serlen, 0);
- StrBufAppendBufPlain(buf, HKEY("\r\n"), 0);
- CM_SetAsFieldSB(vmsg, eMesageText, &buf);
- free(ser);
- }
- vcard_free(v);
-
- syslog(LOG_DEBUG, "vcard: adding contact: %s", recipient);
- CtdlSubmitMsg(vmsg, NULL, aptr->roomname);
- CM_Free(vmsg);
- }
- }
-
- free(aptr->roomname);
- free(aptr->collected_addresses);
- free(aptr);
-}
-
-
-/*
- * When a user sends a message, we may harvest one or more email addresses
- * from the recipient list to be added to the user's address book. But we
- * want to do this asynchronously so it doesn't keep the user waiting.
- */
-void store_harvested_addresses(void) {
-
- struct addresses_to_be_filed *aptr = NULL;
-
- if (atbf == NULL) return;
-
- begin_critical_section(S_ATBF);
- while (atbf != NULL) {
- aptr = atbf;
- atbf = atbf->next;
- end_critical_section(S_ATBF);
- store_this_ha(aptr);
- begin_critical_section(S_ATBF);
- }
- end_critical_section(S_ATBF);
-}
-
-
-/*
- * Function to output vCard data as plain text. Nobody uses MSG0 anymore, so
- * really this is just so we expose the vCard data to the full text indexer.
- */
-void vcard_fixed_output(char *ptr, int len) {
- char *serialized_vcard;
- struct vCard *v;
- char *key, *value;
- int i = 0;
-
- serialized_vcard = malloc(len + 1);
- safestrncpy(serialized_vcard, ptr, len+1);
- v = vcard_load(serialized_vcard);
- free(serialized_vcard);
-
- i = 0;
- while (key = vcard_get_prop(v, "", 0, i, 1), key != NULL) {
- value = vcard_get_prop(v, "", 0, i++, 0);
- cprintf("%s\n", value);
- }
-
- vcard_free(v);
-}
-
-
-const char *CitadelServiceDICT_TCP="DICT_TCP";
-
-CTDL_MODULE_INIT(vcard)
-{
- struct ctdlroom qr;
-
- if (!threading)
- {
- CtdlRegisterSessionHook(vcard_session_login_hook, EVT_LOGIN, PRIO_LOGIN + 70);
- CtdlRegisterMessageHook(vcard_upload_beforesave, EVT_BEFORESAVE);
- CtdlRegisterMessageHook(vcard_upload_aftersave, EVT_AFTERSAVE);
- CtdlRegisterProtoHook(cmd_regi, "REGI", "Enter registration info");
- CtdlRegisterProtoHook(cmd_greg, "GREG", "Get registration info");
- CtdlRegisterProtoHook(cmd_qdir, "QDIR", "Query Directory");
- CtdlRegisterProtoHook(cmd_gvsn, "GVSN", "Get Valid Screen Names");
- CtdlRegisterProtoHook(cmd_gvea, "GVEA", "Get Valid Email Addresses");
- CtdlRegisterProtoHook(cmd_dvca, "DVCA", "Dump VCard Addresses");
- CtdlRegisterUserHook(vcard_newuser, EVT_NEWUSER);
- CtdlRegisterSessionHook(store_harvested_addresses, EVT_TIMER, PRIO_CLEANUP + 470);
- CtdlRegisterFixedOutputHook("text/x-vcard", vcard_fixed_output);
- CtdlRegisterFixedOutputHook("text/vcard", vcard_fixed_output);
-
- /* Create the Global Address Book room if necessary */
- CtdlCreateRoom(ADDRESS_BOOK_ROOM, 3, "", 0, 1, 0, VIEW_ADDRESSBOOK);
-
- /* Set expiration policy to manual; otherwise objects will be lost! */
- if (!CtdlGetRoomLock(&qr, ADDRESS_BOOK_ROOM)) {
- qr.QRep.expire_mode = EXPIRE_MANUAL;
- qr.QRdefaultview = VIEW_ADDRESSBOOK; // 2 = address book view
- CtdlPutRoomLock(&qr);
- }
-
- /* for postfix tcpdict */
- CtdlRegisterServiceHook(CtdlGetConfigInt("c_pftcpdict_port"), // Postfix
- NULL,
- check_get_greeting,
- check_get,
- NULL,
- CitadelServiceDICT_TCP
- );
- }
-
- /* return our module name for the log */
- return "vcard";
-}
+++ /dev/null
-/*
- * Server-side module for Wiki rooms. This handles things like version control.
- *
- * Copyright (c) 2009-2021 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.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <ctype.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "user_ops.h"
-#include "room_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "euidindex.h"
-#include "ctdl_module.h"
-
-/*
- * Data passed back and forth between wiki_rev() and its MIME parser callback
- */
-struct HistoryEraserCallBackData {
- char *tempfilename; /* name of temp file being patched */
- char *stop_when; /* stop when we hit this uuid */
- int done; /* set to nonzero when we're done patching */
-};
-
-/*
- * Name of the temporary room we create to store old revisions when someone requests them.
- * We put it in an invalid namespace so the DAP cleans up after us later.
- */
-char *wwm = "9999999999.WikiWaybackMachine";
-
-/*
- * Before allowing a wiki page save to execute, we have to perform version control.
- * This involves fetching the old version of the page if it exists.
- */
-int wiki_upload_beforesave(struct CtdlMessage *msg, struct recptypes *recp) {
- long old_msgnum = (-1L);
- struct CtdlMessage *old_msg = NULL;
- long history_msgnum = (-1L);
- struct CtdlMessage *history_msg = NULL;
- char diff_old_filename[PATH_MAX];
- char diff_new_filename[PATH_MAX];
- char diff_out_filename[PATH_MAX];
- char diff_cmd[PATH_MAX];
- FILE *fp;
- int rv;
- char history_page[1024];
- long history_page_len;
- char boundary[256];
- char prefixed_boundary[258];
- char buf[1024];
- char *diffbuf = NULL;
- size_t diffbuf_len = 0;
- char *ptr = NULL;
- long newmsgid;
- StrBuf *msgidbuf;
-
- if (!CC->logged_in) return(0); /* Only do this if logged in. */
-
- /* Is this a room with a Wiki in it, don't run this hook. */
- if (CC->room.QRdefaultview != VIEW_WIKI) {
- return(0);
- }
-
- /* If this isn't a MIME message, don't bother. */
- if (msg->cm_format_type != 4) return(0);
-
- /* If there's no EUID we can't do this. Reject the post. */
- if (CM_IsEmpty(msg, eExclusiveID)) return(1);
-
- newmsgid = get_new_message_number();
- msgidbuf = NewStrBuf();
- StrBufPrintf(msgidbuf, "%08lX-%08lX@%s/%s",
- (long unsigned int) time(NULL),
- (long unsigned int) newmsgid,
- CtdlGetConfigStr("c_fqdn"),
- msg->cm_fields[eExclusiveID]
- );
-
- CM_SetAsFieldSB(msg, emessageId, &msgidbuf);
-
- history_page_len = snprintf(history_page, sizeof history_page,
- "%s_HISTORY_", msg->cm_fields[eExclusiveID]);
-
- /* Make sure we're saving a real wiki page rather than a wiki history page.
- * This is important in order to avoid recursing infinitely into this hook.
- */
- if ( (msg->cm_lengths[eExclusiveID] >= 9)
- && (!strcasecmp(&msg->cm_fields[eExclusiveID][msg->cm_lengths[eExclusiveID]-9], "_HISTORY_"))
- ) {
- syslog(LOG_DEBUG, "History page not being historied\n");
- return(0);
- }
-
- /* If there's no message text, obviously this is all b0rken and shouldn't happen at all */
- if (CM_IsEmpty(msg, eMesageText)) return(0);
-
- /* Set the message subject identical to the page name */
- CM_CopyField(msg, eMsgSubject, eExclusiveID);
-
- /* See if we can retrieve the previous version. */
- old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CC->room);
- if (old_msgnum > 0L) {
- old_msg = CtdlFetchMessage(old_msgnum, 1);
- }
- else {
- old_msg = NULL;
- }
-
- if ((old_msg != NULL) && (CM_IsEmpty(old_msg, eMesageText))) { /* old version is corrupt? */
- CM_Free(old_msg);
- old_msg = NULL;
- }
-
- /* If no changes were made, don't bother saving it again */
- if ((old_msg != NULL) && (!strcmp(msg->cm_fields[eMesageText], old_msg->cm_fields[eMesageText]))) {
- CM_Free(old_msg);
- return(1);
- }
-
- /*
- * Generate diffs
- */
- CtdlMakeTempFileName(diff_old_filename, sizeof diff_old_filename);
- CtdlMakeTempFileName(diff_new_filename, sizeof diff_new_filename);
- CtdlMakeTempFileName(diff_out_filename, sizeof diff_out_filename);
-
- if (old_msg != NULL) {
- fp = fopen(diff_old_filename, "w");
- rv = fwrite(old_msg->cm_fields[eMesageText], old_msg->cm_lengths[eMesageText], 1, fp);
- fclose(fp);
- CM_Free(old_msg);
- }
-
- fp = fopen(diff_new_filename, "w");
- rv = fwrite(msg->cm_fields[eMesageText], msg->cm_lengths[eMesageText], 1, fp);
- fclose(fp);
-
- snprintf(diff_cmd, sizeof diff_cmd,
- DIFF " -u %s %s >%s",
- diff_new_filename,
- ((old_msg != NULL) ? diff_old_filename : "/dev/null"),
- diff_out_filename
- );
- syslog(LOG_DEBUG, "diff cmd: %s", diff_cmd);
- rv = system(diff_cmd);
- syslog(LOG_DEBUG, "diff cmd returned %d", rv);
-
- diffbuf_len = 0;
- diffbuf = NULL;
- fp = fopen(diff_out_filename, "r");
- if (fp == NULL) {
- fp = fopen("/dev/null", "r");
- }
- if (fp != NULL) {
- fseek(fp, 0L, SEEK_END);
- diffbuf_len = ftell(fp);
- fseek(fp, 0L, SEEK_SET);
- diffbuf = malloc(diffbuf_len + 1);
- fread(diffbuf, diffbuf_len, 1, fp);
- diffbuf[diffbuf_len] = '\0';
- fclose(fp);
- }
-
- syslog(LOG_DEBUG, "diff length is "SIZE_T_FMT" bytes", diffbuf_len);
-
- unlink(diff_old_filename);
- unlink(diff_new_filename);
- unlink(diff_out_filename);
-
- /* Determine whether this was a bogus (empty) edit */
- if ((diffbuf_len = 0) && (diffbuf != NULL)) {
- free(diffbuf);
- diffbuf = NULL;
- }
- if (diffbuf == NULL) {
- return(1); /* No changes at all? Abandon the post entirely! */
- }
-
- /* Now look for the existing edit history */
-
- history_msgnum = CtdlLocateMessageByEuid(history_page, &CC->room);
- history_msg = NULL;
- if (history_msgnum > 0L) {
- history_msg = CtdlFetchMessage(history_msgnum, 1);
- }
-
- /* Create a new history message if necessary */
- if (history_msg == NULL) {
- char *buf;
- long len;
-
- history_msg = malloc(sizeof(struct CtdlMessage));
- memset(history_msg, 0, sizeof(struct CtdlMessage));
- history_msg->cm_magic = CTDLMESSAGE_MAGIC;
- history_msg->cm_anon_type = MES_NORMAL;
- history_msg->cm_format_type = FMT_RFC822;
- CM_SetField(history_msg, eAuthor, HKEY("Citadel"));
- if (!IsEmptyStr(CC->room.QRname)){
- CM_SetField(history_msg, eRecipient, CC->room.QRname, strlen(CC->room.QRname));
- }
- CM_SetField(history_msg, eExclusiveID, history_page, history_page_len);
- CM_SetField(history_msg, eMsgSubject, history_page, history_page_len);
- CM_SetField(history_msg, eSuppressIdx, HKEY("1")); /* suppress full text indexing */
- snprintf(boundary, sizeof boundary, "Citadel--Multipart--%04x--%08lx", getpid(), time(NULL));
- buf = (char*) malloc(1024);
- len = snprintf(buf, 1024,
- "Content-type: multipart/mixed; boundary=\"%s\"\n\n"
- "This is a Citadel wiki history encoded as multipart MIME.\n"
- "Each part is comprised of a diff script representing one change set.\n"
- "\n"
- "--%s--\n",
- boundary, boundary
- );
- CM_SetAsField(history_msg, eMesageText, &buf, len);
- }
-
- /* Update the history message (regardless of whether it's new or existing) */
-
- /* Remove the Message-ID from the old version of the history message. This will cause a brand
- * new one to be generated, avoiding an uninitentional hit of the loop zapper when we replicate.
- */
- CM_FlushField(history_msg, emessageId);
-
- /* Figure out the boundary string. We do this even when we generated the
- * boundary string in the above code, just to be safe and consistent.
- */
- *boundary = '\0';
-
- ptr = history_msg->cm_fields[eMesageText];
- do {
- ptr = memreadline(ptr, buf, sizeof buf);
- if (*ptr != 0) {
- striplt(buf);
- if (!IsEmptyStr(buf) && (!strncasecmp(buf, "Content-type:", 13))) {
- if (
- (bmstrcasestr(buf, "multipart") != NULL)
- && (bmstrcasestr(buf, "boundary=") != NULL)
- ) {
- safestrncpy(boundary, bmstrcasestr(buf, "\""), sizeof boundary);
- char *qu;
- qu = strchr(boundary, '\"');
- if (qu) {
- strcpy(boundary, ++qu);
- }
- qu = strchr(boundary, '\"');
- if (qu) {
- *qu = 0;
- }
- }
- }
- }
- } while ( (IsEmptyStr(boundary)) && (*ptr != 0) );
-
- /*
- * Now look for the first boundary. That is where we need to insert our fun.
- */
- if (!IsEmptyStr(boundary)) {
- char *MsgText;
- long MsgTextLen;
- time_t Now = time(NULL);
-
- snprintf(prefixed_boundary, sizeof(prefixed_boundary), "--%s", boundary);
-
- CM_GetAsField(history_msg, eMesageText, &MsgText, &MsgTextLen);
-
- ptr = bmstrcasestr(MsgText, prefixed_boundary);
- if (ptr != NULL) {
- StrBuf *NewMsgText;
- char uuid[64];
- char memo[512];
- long memolen;
- char encoded_memo[1024];
-
- NewMsgText = NewStrBufPlain(NULL, MsgTextLen + diffbuf_len + 1024);
-
- generate_uuid(uuid);
- memolen = snprintf(memo, sizeof(memo), "%s|%ld|%s|%s",
- uuid,
- Now,
- CC->user.fullname,
- CtdlGetConfigStr("c_nodename"));
-
- memolen = CtdlEncodeBase64(encoded_memo, memo, memolen, 0);
-
- StrBufAppendBufPlain(NewMsgText, HKEY("--"), 0);
- StrBufAppendBufPlain(NewMsgText, boundary, -1, 0);
- StrBufAppendBufPlain(
- NewMsgText,
- HKEY("\n"
- "Content-type: text/plain\n"
- "Content-Disposition: inline; filename=\""), 0);
-
- StrBufAppendBufPlain(NewMsgText, encoded_memo, memolen, 0);
-
- StrBufAppendBufPlain(
- NewMsgText,
- HKEY("\"\n"
- "Content-Transfer-Encoding: 8bit\n"
- "\n"), 0);
-
- StrBufAppendBufPlain(NewMsgText, diffbuf, diffbuf_len, 0);
- StrBufAppendBufPlain(NewMsgText, HKEY("\n"), 0);
-
- StrBufAppendBufPlain(NewMsgText, ptr, MsgTextLen - (ptr - MsgText), 0);
- free(MsgText);
- CM_SetAsFieldSB(history_msg, eMesageText, &NewMsgText);
- }
- else
- {
- CM_SetAsField(history_msg, eMesageText, &MsgText, MsgTextLen);
- }
-
- CM_SetFieldLONG(history_msg, eTimestamp, Now);
-
- CtdlSubmitMsg(history_msg, NULL, "");
- }
- else {
- syslog(LOG_ALERT, "Empty boundary string in history message. No history!\n");
- }
-
- free(diffbuf);
- CM_Free(history_msg);
- return(0);
-}
-
-
-/*
- * MIME Parser callback for wiki_history()
- *
- * The "filename" field will contain a memo field. All we have to do is decode
- * the base64 and output it. The data is already in a delimited format suitable
- * for our client protocol.
- */
-void wiki_history_callback(char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length,
- char *encoding, char *cbid, void *cbuserdata)
-{
- char memo[1024];
-
- CtdlDecodeBase64(memo, filename, strlen(filename));
- cprintf("%s\n", memo);
-}
-
-
-/*
- * Fetch a list of revisions for a particular wiki page
- */
-void wiki_history(char *pagename) {
- int r;
- char history_page_name[270];
- long msgnum;
- struct CtdlMessage *msg;
-
- r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
- if (r != om_ok) {
- if (r == om_not_logged_in) {
- cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
- }
- else {
- cprintf("%d An unknown error has occurred.\n", ERROR);
- }
- return;
- }
-
- snprintf(history_page_name, sizeof history_page_name, "%s_HISTORY_", pagename);
- msgnum = CtdlLocateMessageByEuid(history_page_name, &CC->room);
- if (msgnum > 0L) {
- msg = CtdlFetchMessage(msgnum, 1);
- }
- else {
- msg = NULL;
- }
-
- if ((msg != NULL) && CM_IsEmpty(msg, eMesageText)) {
- CM_Free(msg);
- msg = NULL;
- }
-
- if (msg == NULL) {
- cprintf("%d Revision history for '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename);
- return;
- }
-
-
- cprintf("%d Revision history for '%s'\n", LISTING_FOLLOWS, pagename);
- mime_parser(CM_RANGE(msg, eMesageText), *wiki_history_callback, NULL, NULL, NULL, 0);
- cprintf("000\n");
-
- CM_Free(msg);
- return;
-}
-
-/*
- * MIME Parser callback for wiki_rev()
- *
- * The "filename" field will contain a memo field, which includes (among other things)
- * the uuid of this revision. After we hit the desired revision, we stop processing.
- *
- * The "content" filed will contain "diff" output suitable for applying via "patch"
- * to our temporary file.
- */
-void wiki_rev_callback(char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length,
- char *encoding, char *cbid, void *cbuserdata)
-{
- struct HistoryEraserCallBackData *hecbd = (struct HistoryEraserCallBackData *)cbuserdata;
- char memo[1024];
- char this_rev[256];
- FILE *fp;
- char *ptr = NULL;
- char buf[1024];
-
- /* Did a previous callback already indicate that we've reached our target uuid?
- * If so, don't process anything else.
- */
- if (hecbd->done) {
- return;
- }
-
- CtdlDecodeBase64(memo, filename, strlen(filename));
- extract_token(this_rev, memo, 0, '|', sizeof this_rev);
- striplt(this_rev);
-
- /* Perform the patch */
- fp = popen(PATCH " -f -s -p0 -r /dev/null >/dev/null 2>/dev/null", "w");
- if (fp) {
- /* Replace the filenames in the patch with the tempfilename we're actually tweaking */
- fprintf(fp, "--- %s\n", hecbd->tempfilename);
- fprintf(fp, "+++ %s\n", hecbd->tempfilename);
-
- ptr = (char *)content;
- int linenum = 0;
- do {
- ++linenum;
- ptr = memreadline(ptr, buf, sizeof buf);
- if (*ptr != 0) {
- if (linenum <= 2) {
- /* skip the first two lines; they contain bogus filenames */
- }
- else {
- fprintf(fp, "%s\n", buf);
- }
- }
- } while ((*ptr != 0) && (ptr < ((char*)content + length)));
- if (pclose(fp) != 0) {
- syslog(LOG_ERR, "pclose() returned an error - patch failed\n");
- }
- }
-
- if (!strcasecmp(this_rev, hecbd->stop_when)) {
- /* Found our target rev. Tell any subsequent callbacks to suppress processing. */
- syslog(LOG_DEBUG, "Target revision has been reached -- stop patching.\n");
- hecbd->done = 1;
- }
-}
-
-
-/*
- * Fetch a specific revision of a wiki page. The "operation" string may be set to "fetch" in order
- * to simply fetch the desired revision and store it in a temporary location for viewing, or "revert"
- * to revert the currently active page to that revision.
- */
-void wiki_rev(char *pagename, char *rev, char *operation)
-{
- int r;
- char history_page_name[270];
- long msgnum;
- char temp[PATH_MAX];
- struct CtdlMessage *msg;
- FILE *fp;
- struct HistoryEraserCallBackData hecbd;
- long len = 0L;
- int rv;
-
- r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
- if (r != om_ok) {
- if (r == om_not_logged_in) {
- cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
- }
- else {
- cprintf("%d An unknown error has occurred.\n", ERROR);
- }
- return;
- }
-
- if (!strcasecmp(operation, "revert")) {
- r = CtdlDoIHavePermissionToPostInThisRoom(temp, sizeof temp, NULL, POST_LOGGED_IN, 0);
- if (r != 0) {
- cprintf("%d %s\n", r, temp);
- return;
- }
- }
-
- /* Begin by fetching the current version of the page. We're going to patch
- * backwards through the diffs until we get the one we want.
- */
- msgnum = CtdlLocateMessageByEuid(pagename, &CC->room);
- if (msgnum > 0L) {
- msg = CtdlFetchMessage(msgnum, 1);
- }
- else {
- msg = NULL;
- }
-
- if ((msg != NULL) && CM_IsEmpty(msg, eMesageText)) {
- CM_Free(msg);
- msg = NULL;
- }
-
- if (msg == NULL) {
- cprintf("%d Page '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename);
- return;
- }
-
- /* Output it to a temporary file */
-
- CtdlMakeTempFileName(temp, sizeof temp);
- fp = fopen(temp, "w");
- if (fp != NULL) {
- r = fwrite(msg->cm_fields[eMesageText], msg->cm_lengths[eMesageText], 1, fp);
- fclose(fp);
- }
- else {
- syslog(LOG_ERR, "%s: %m", temp);
- }
- CM_Free(msg);
-
- /* Get the revision history */
-
- snprintf(history_page_name, sizeof history_page_name, "%s_HISTORY_", pagename);
- msgnum = CtdlLocateMessageByEuid(history_page_name, &CC->room);
- if (msgnum > 0L) {
- msg = CtdlFetchMessage(msgnum, 1);
- }
- else {
- msg = NULL;
- }
-
- if ((msg != NULL) && CM_IsEmpty(msg, eMesageText)) {
- CM_Free(msg);
- msg = NULL;
- }
-
- if (msg == NULL) {
- cprintf("%d Revision history for '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename);
- return;
- }
-
- /* Start patching backwards (newest to oldest) through the revision history until we
- * hit the revision uuid requested by the user. (The callback will perform each one.)
- */
-
- memset(&hecbd, 0, sizeof(struct HistoryEraserCallBackData));
- hecbd.tempfilename = temp;
- hecbd.stop_when = rev;
- striplt(hecbd.stop_when);
-
- mime_parser(CM_RANGE(msg, eMesageText), *wiki_rev_callback, NULL, NULL, (void *)&hecbd, 0);
- CM_Free(msg);
-
- /* Were we successful? */
- if (hecbd.done == 0) {
- cprintf("%d Revision '%s' of page '%s' was not found.\n",
- ERROR + MESSAGE_NOT_FOUND, rev, pagename
- );
- }
-
- /* We have the desired revision on disk. Now do something with it. */
-
- else if ( (!strcasecmp(operation, "fetch")) || (!strcasecmp(operation, "revert")) ) {
- msg = malloc(sizeof(struct CtdlMessage));
- memset(msg, 0, sizeof(struct CtdlMessage));
- msg->cm_magic = CTDLMESSAGE_MAGIC;
- msg->cm_anon_type = MES_NORMAL;
- msg->cm_format_type = FMT_RFC822;
- fp = fopen(temp, "r");
- if (fp) {
- char *msgbuf;
- fseek(fp, 0L, SEEK_END);
- len = ftell(fp);
- fseek(fp, 0L, SEEK_SET);
- msgbuf = malloc(len + 1);
- rv = fread(msgbuf, len, 1, fp);
- syslog(LOG_DEBUG, "did %d blocks of %ld bytes\n", rv, len);
- msgbuf[len] = '\0';
- CM_SetAsField(msg, eMesageText, &msgbuf, len);
- fclose(fp);
- }
- if (len <= 0) {
- msgnum = (-1L);
- }
- else if (!strcasecmp(operation, "fetch")) {
- CM_SetField(msg, eAuthor, HKEY("Citadel"));
- CtdlCreateRoom(wwm, 5, "", 0, 1, 1, VIEW_BBS); /* Not an error if already exists */
- msgnum = CtdlSubmitMsg(msg, NULL, wwm); /* Store the revision here */
-
- /*
- * WARNING: VILE SLEAZY HACK
- * This will avoid the 'message xxx is not in this room' security error,
- * but only if the client fetches the message we just generated immediately
- * without first trying to perform other fetch operations.
- */
- if (CC->cached_msglist != NULL) {
- free(CC->cached_msglist);
- CC->cached_msglist = NULL;
- CC->cached_num_msgs = 0;
- }
- CC->cached_msglist = malloc(sizeof(long));
- if (CC->cached_msglist != NULL) {
- CC->cached_num_msgs = 1;
- CC->cached_msglist[0] = msgnum;
- }
-
- }
- else if (!strcasecmp(operation, "revert")) {
- CM_SetFieldLONG(msg, eTimestamp, time(NULL));
- if (!IsEmptyStr(CC->user.fullname)) {
- CM_SetField(msg, eAuthor, CC->user.fullname, strlen(CC->user.fullname));
- }
-
- if (!IsEmptyStr(CC->cs_inet_email)) {
- CM_SetField(msg, erFc822Addr, CC->cs_inet_email, strlen(CC->cs_inet_email));
- }
-
- if (!IsEmptyStr(CC->room.QRname)) {
- CM_SetField(msg, eOriginalRoom, CC->room.QRname, strlen(CC->room.QRname));
- }
-
- if (!IsEmptyStr(pagename)) {
- CM_SetField(msg, eExclusiveID, pagename, strlen(pagename));
- }
- msgnum = CtdlSubmitMsg(msg, NULL, ""); /* Replace the current revision */
- }
- else {
- /* Theoretically it is impossible to get here, but throw an error anyway */
- msgnum = (-1L);
- }
- CM_Free(msg);
- if (msgnum >= 0L) {
- cprintf("%d %ld\n", CIT_OK, msgnum); /* Give the client a msgnum */
- }
- else {
- cprintf("%d Error %ld has occurred.\n", ERROR+INTERNAL_ERROR, msgnum);
- }
- }
-
- /* We did all this work for nothing. Express anguish to the caller. */
- else {
- cprintf("%d An unknown operation was requested.\n", ERROR+CMD_NOT_SUPPORTED);
- }
-
- unlink(temp);
- return;
-}
-
-
-
-/*
- * commands related to wiki management
- */
-void cmd_wiki(char *argbuf) {
- char subcmd[32];
- char pagename[256];
- char rev[128];
- char operation[16];
-
- extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
-
- if (!strcasecmp(subcmd, "history")) {
- extract_token(pagename, argbuf, 1, '|', sizeof pagename);
- wiki_history(pagename);
- return;
- }
-
- if (!strcasecmp(subcmd, "rev")) {
- extract_token(pagename, argbuf, 1, '|', sizeof pagename);
- extract_token(rev, argbuf, 2, '|', sizeof rev);
- extract_token(operation, argbuf, 3, '|', sizeof operation);
- wiki_rev(pagename, rev, operation);
- return;
- }
-
- cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
-}
-
-
-
-/*
- * Module initialization
- */
-CTDL_MODULE_INIT(wiki)
-{
- if (!threading)
- {
- CtdlRegisterMessageHook(wiki_upload_beforesave, EVT_BEFORESAVE);
- CtdlRegisterProtoHook(cmd_wiki, "WIKI", "Commands related to Wiki management");
- }
-
- /* return our module name for the log */
- return "wiki";
-}
+++ /dev/null
-/*
- * XMPP (Jabber) service for the Citadel system
- * Copyright (c) 2007-2021 by Art Cancro and citadel.org
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <ctype.h>
-#include <libcitadel.h>
-#include <expat.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "ctdl_module.h"
-#include "serv_xmpp.h"
-
-/* uncomment for more verbosity - it will log all received XML tags */
-// #define XMPP_XML_DEBUG
-
-/* XML_StopParser is present in expat 2.x */
-#if XML_MAJOR_VERSION > 1
-#define HAVE_XML_STOPPARSER
-#endif
-
-struct xmpp_event *xmpp_queue = NULL;
-
-#ifdef HAVE_XML_STOPPARSER
-/* Stop the parser if an entity declaration is hit. */
-static void xmpp_entity_declaration(void *userData, const XML_Char *entityName,
- int is_parameter_entity, const XML_Char *value,
- int value_length, const XML_Char *base,
- const XML_Char *systemId, const XML_Char *publicId,
- const XML_Char *notationName
-) {
- syslog(LOG_WARNING, "xmpp: illegal entity declaration encountered; stopping parser.");
- XML_StopParser(XMPP->xp, XML_FALSE);
-}
-#endif
-
-
-/*
- * support function for xmlesc() which helps with UTF-8 strings
- */
-static inline int Ctdl_GetUtf8SequenceLength(const char *CharS, const char *CharE)
-{
- int n = 0;
- unsigned char test = (1<<7);
-
- if ((*CharS & 0xC0) != 0xC0) {
- return 1;
- }
-
- while ((n < 8) && ((test & ((unsigned char)*CharS)) != 0)) {
- test = test >> 1;
- n ++;
- }
- if ((n > 6) || ((CharE - CharS) < n)) {
- n = 0;
- }
- return n;
-}
-
-
-/*
- * Given a source string and a target buffer, returns the string
- * properly escaped for insertion into an XML stream. Returns a
- * pointer to the target buffer for convenience.
- */
-char *xmlesc(char *buf, char *str, int bufsiz)
-{
- int IsUtf8Sequence;
- char *ptr, *pche;
- unsigned char ch;
- int inlen;
- int len = 0;
-
- if (!buf) return(NULL);
- buf[0] = 0;
- len = 0;
- if (!str) {
- return(buf);
- }
- inlen = strlen(str);
- pche = str + inlen;
-
- for (ptr=str; *ptr; ptr++) {
- ch = *ptr;
- if (ch == '<') {
- strcpy(&buf[len], "<");
- len += 4;
- }
- else if (ch == '>') {
- strcpy(&buf[len], ">");
- len += 4;
- }
- else if (ch == '&') {
- strcpy(&buf[len], "&");
- len += 5;
- }
- else if ((ch >= 0x20) && (ch <= 0x7F)) {
- buf[len++] = ch;
- buf[len] = 0;
- }
- else if (ch < 0x20) {
- buf[len++] = '_'; // we probably shouldn't be doing this
- buf[len] = 0;
- }
- else {
- IsUtf8Sequence = Ctdl_GetUtf8SequenceLength(ptr, pche);
- if (IsUtf8Sequence)
- {
- while ((IsUtf8Sequence > 0) &&
- (ptr < pche))
- {
- buf[len] = *ptr;
- ptr ++;
- --IsUtf8Sequence;
- }
- }
- else
- {
- char oct[10];
- sprintf(oct, "&#%o;", ch);
- strcpy(&buf[len], oct);
- len += strlen(oct);
- }
- }
- if ((len + 6) > bufsiz) {
- return(buf);
- }
- }
- return(buf);
-}
-
-
-/*
- * We have just received a <stream> tag from the client, so send them ours
- */
-void xmpp_stream_start(void *data, const char *supplied_el, const char **attr)
-{
- char xmlbuf[256];
-
- while (*attr) {
- if (!strcasecmp(attr[0], "to")) {
- safestrncpy(XMPP->server_name, attr[1], sizeof XMPP->server_name);
- }
- attr += 2;
- }
-
- cprintf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
- cprintf("<stream:stream ");
- cprintf("from=\"%s\" ", xmlesc(xmlbuf, XMPP->server_name, sizeof xmlbuf));
- cprintf("id=\"%08x\" ", CC->cs_pid);
- cprintf("version=\"1.0\" ");
- cprintf("xmlns:stream=\"http://etherx.jabber.org/streams\" ");
- cprintf("xmlns=\"jabber:client\">");
-
- /* The features of this stream are... */
- cprintf("<stream:features>");
-
- /*
- * TLS encryption (but only if it isn't already active)
- */
-#ifdef HAVE_OPENSSL
- if (!CC->redirect_ssl) {
- cprintf("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'></starttls>");
- }
-#endif
-
- if (!CC->logged_in) {
- /* If we're not logged in yet, offer SASL as our feature set */
- xmpp_output_auth_mechs();
-
- /* Also offer non-SASL authentication */
- cprintf("<auth xmlns=\"http://jabber.org/features/iq-auth\"/>");
- }
-
- /* Offer binding and sessions as part of our feature set */
- cprintf("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>");
- cprintf("<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>");
- cprintf("</stream:features>");
-
- CC->is_async = 1; /* XMPP sessions are inherently async-capable */
-}
-
-
-void xmpp_xml_start(void *data, const char *supplied_el, const char **attr) {
- char el[256];
- char *sep = NULL;
- int i;
-
- /* Create a version of the element with the namespace removed.
- * Now we can access "el" or "supplied_el" depending on whether we want to see the whole namespace.
- */
- safestrncpy(el, supplied_el, sizeof el);
- while (sep = strchr(el, ':'), sep) {
- strcpy(el, ++sep);
- }
-
-#ifdef XMPP_XML_DEBUG
- syslog(LOG_DEBUG, "xmpp: ELEMENT START: <%s>", el);
- for (i=0; attr[i] != NULL; i+=2) {
- syslog(LOG_DEBUG, "xmpp: Attribute '%s' = '%s'", attr[i], attr[i+1]);
- }
-#endif
-
- if (!strcasecmp(el, "stream")) {
- xmpp_stream_start(data, supplied_el, attr);
- }
-
- else if (!strcasecmp(el, "query")) {
- XMPP->iq_query_xmlns[0] = 0;
- safestrncpy(XMPP->iq_query_xmlns, supplied_el, sizeof XMPP->iq_query_xmlns);
- }
-
- else if (!strcasecmp(el, "bind")) {
- XMPP->bind_requested = 1;
- }
-
- else if (!strcasecmp(el, "iq")) {
- for (i=0; attr[i] != NULL; i+=2) {
- if (!strcasecmp(attr[i], "type")) {
- safestrncpy(XMPP->iq_type, attr[i+1], sizeof XMPP->iq_type);
- }
- else if (!strcasecmp(attr[i], "id")) {
- safestrncpy(XMPP->iq_id, attr[i+1], sizeof XMPP->iq_id);
- }
- else if (!strcasecmp(attr[i], "from")) {
- safestrncpy(XMPP->iq_from, attr[i+1], sizeof XMPP->iq_from);
- }
- else if (!strcasecmp(attr[i], "to")) {
- safestrncpy(XMPP->iq_to, attr[i+1], sizeof XMPP->iq_to);
- }
- }
- }
-
- else if (!strcasecmp(el, "auth")) {
- XMPP->sasl_auth_mech[0] = 0;
- for (i=0; attr[i] != NULL; i+=2) {
- if (!strcasecmp(attr[i], "mechanism")) {
- safestrncpy(XMPP->sasl_auth_mech, attr[i+1], sizeof XMPP->sasl_auth_mech);
- }
- }
- }
-
- else if (!strcasecmp(el, "message")) {
- for (i=0; attr[i] != NULL; i+=2) {
- if (!strcasecmp(attr[i], "to")) {
- safestrncpy(XMPP->message_to, attr[i+1], sizeof XMPP->message_to);
- }
- }
- }
-
- else if (!strcasecmp(el, "html")) {
- ++XMPP->html_tag_level;
- }
-}
-
-
-void xmpp_xml_end(void *data, const char *supplied_el) {
- char el[256];
- char *sep = NULL;
- char xmlbuf[256];
-
- /* Create a version of the element with the namespace removed.
- * Now we can access "el" or "supplied_el" depending on whether we want to see the whole namespace.
- */
- safestrncpy(el, supplied_el, sizeof el);
- while (sep = strchr(el, ':'), sep) {
- strcpy(el, ++sep);
- }
-
-#ifdef XMPP_XML_DEBUG
- syslog(LOG_DEBUG, "xmpp: ELEMENT END : <%s>", el);
- if (XMPP->chardata_len > 0) {
- syslog(LOG_DEBUG, "xmpp: chardata: %s", XMPP->chardata);
- }
-#endif
-
- if (!strcasecmp(el, "resource")) {
- if (XMPP->chardata_len > 0) {
- safestrncpy(XMPP->iq_client_resource, XMPP->chardata, sizeof XMPP->iq_client_resource);
- striplt(XMPP->iq_client_resource);
- }
- }
-
- else if (!strcasecmp(el, "username")) { /* NON SASL ONLY */
- if (XMPP->chardata_len > 0) {
- safestrncpy(XMPP->iq_client_username, XMPP->chardata, sizeof XMPP->iq_client_username);
- striplt(XMPP->iq_client_username);
- }
- }
-
- else if (!strcasecmp(el, "password")) { /* NON SASL ONLY */
- if (XMPP->chardata_len > 0) {
- safestrncpy(XMPP->iq_client_password, XMPP->chardata, sizeof XMPP->iq_client_password);
- striplt(XMPP->iq_client_password);
- }
- }
-
- else if (!strcasecmp(el, "iq")) {
-
- /*
- * iq type="get" (handle queries)
- */
- if (!strcasecmp(XMPP->iq_type, "get")) {
-
- /*
- * Query on a namespace
- */
- if (!IsEmptyStr(XMPP->iq_query_xmlns)) {
- xmpp_query_namespace(XMPP->iq_id, XMPP->iq_from, XMPP->iq_to, XMPP->iq_query_xmlns);
- }
-
- /*
- * ping ( http://xmpp.org/extensions/xep-0199.html )
- */
- else if (XMPP->ping_requested) {
- cprintf("<iq type=\"result\" ");
- if (!IsEmptyStr(XMPP->iq_from)) {
- cprintf("to=\"%s\" ", xmlesc(xmlbuf, XMPP->iq_from, sizeof xmlbuf));
- }
- if (!IsEmptyStr(XMPP->iq_to)) {
- cprintf("from=\"%s\" ", xmlesc(xmlbuf, XMPP->iq_to, sizeof xmlbuf));
- }
- cprintf("id=\"%s\"/>", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
- }
-
- /*
- * Client is requesting its own vCard
- * (If we make this more elaborate, move it to a separate function)
- */
- else if (XMPP->iq_vcard) {
- cprintf("<iq type=\"result\" id=\"%s\" ", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
- cprintf("to=\"%s\">", xmlesc(xmlbuf, XMPP->iq_from, sizeof xmlbuf));
- cprintf("<vCard xmlns=\"vcard-temp\">");
- cprintf("<fn>%s</fn>", xmlesc(xmlbuf, CC->user.fullname, sizeof xmlbuf));
- cprintf("<nickname>%s</nickname>", xmlesc(xmlbuf, CC->user.fullname, sizeof xmlbuf));
- cprintf("</vCard>");
- cprintf("</iq>");
- }
-
- /*
- * Unknown query ... return the XML equivalent of a blank stare
- */
- else {
- syslog(LOG_DEBUG, "xmpp: Unknown query <%s> - returning <service-unavailable/>", el);
- cprintf("<iq type=\"error\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
- cprintf("<error code=\"503\" type=\"cancel\">"
- "<service-unavailable xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
- "</error>"
- );
- cprintf("</iq>");
- }
- }
-
- /*
- * Non SASL authentication
- */
- else if (
- (!strcasecmp(XMPP->iq_type, "set"))
- && (!strcasecmp(XMPP->iq_query_xmlns, "jabber:iq:auth:query"))
- ) {
- xmpp_non_sasl_authenticate(XMPP->iq_id, XMPP->iq_client_username, XMPP->iq_client_password);
- }
-
- /*
- * If this <iq> stanza was a "bind" attempt, process it ...
- */
- else if (
- (XMPP->bind_requested)
- && (!IsEmptyStr(XMPP->iq_id))
- && (CC->logged_in)
- ) {
- /* If the client has not specified a client resource, generate one */
- if (IsEmptyStr(XMPP->iq_client_resource)) {
- snprintf(XMPP->iq_client_resource, sizeof XMPP->iq_client_resource, "%d", CC->cs_pid);
- }
-
- /* Generate the "full JID" of the client (user@host/resource) */
- snprintf(XMPP->client_jid, sizeof XMPP->client_jid, "%s/%s", CC->cs_principal_id, XMPP->iq_client_resource);
-
- /* Tell the client what its JID is */
- cprintf("<iq type=\"result\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
- cprintf("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">");
- cprintf("<jid>%s</jid>", xmlesc(xmlbuf, XMPP->client_jid, sizeof xmlbuf));
- cprintf("</bind>");
- cprintf("</iq>");
- }
-
- else if (XMPP->iq_session) {
- cprintf("<iq type=\"result\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
- cprintf("</iq>");
- }
-
- else {
- cprintf("<iq type=\"error\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
- cprintf("<error>Don't know how to do '%s'!</error>", xmlesc(xmlbuf, XMPP->iq_type, sizeof xmlbuf));
- cprintf("</iq>");
- syslog(LOG_DEBUG, "XMPP: don't know how to do iq_type='%s' with iq_query_xmlns='%s'", XMPP->iq_type, XMPP->iq_query_xmlns);
- }
-
- /* Now clear these fields out so they don't get used by a future stanza */
- XMPP->iq_id[0] = 0;
- XMPP->iq_from[0] = 0;
- XMPP->iq_to[0] = 0;
- XMPP->iq_type[0] = 0;
- XMPP->iq_client_resource[0] = 0;
- XMPP->iq_session = 0;
- XMPP->iq_vcard = 0;
- XMPP->iq_query_xmlns[0] = 0;
- XMPP->bind_requested = 0;
- XMPP->ping_requested = 0;
- }
-
- else if (!strcasecmp(el, "auth")) {
- /* Try to authenticate (this function is responsible for the output stanza) */
- xmpp_sasl_auth(XMPP->sasl_auth_mech, (XMPP->chardata != NULL ? XMPP->chardata : "") );
-
- /* Now clear these fields out so they don't get used by a future stanza */
- XMPP->sasl_auth_mech[0] = 0;
- }
-
- else if (!strcasecmp(el, "session")) {
- XMPP->iq_session = 1;
- }
-
- else if (!strcasecmp(supplied_el, "vcard-temp:vCard")) {
- XMPP->iq_vcard = 1;
- }
-
- else if (!strcasecmp(el, "presence")) {
- /* Respond to a <presence> update by firing back with presence information
- * on the entire wholist. Check this assumption, it's probably wrong.
- */
- xmpp_wholist_presence_dump();
- }
-
- else if ( (!strcasecmp(el, "body")) && (XMPP->html_tag_level == 0) ) {
- if (XMPP->message_body != NULL) {
- free(XMPP->message_body);
- XMPP->message_body = NULL;
- }
- if (XMPP->chardata_len > 0) {
- XMPP->message_body = strdup(XMPP->chardata);
- }
- }
-
- else if (!strcasecmp(el, "message")) {
- xmpp_send_message(XMPP->message_to, XMPP->message_body);
- XMPP->html_tag_level = 0;
- }
-
- else if (!strcasecmp(el, "html")) {
- --XMPP->html_tag_level;
- }
-
- else if (!strcasecmp(el, "starttls")) {
-#ifdef HAVE_OPENSSL
- cprintf("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
- CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
- if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;
-#else
- cprintf("<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
- CC->kill_me = KILLME_NO_CRYPTO;
-#endif
- }
-
- else if (!strcasecmp(el, "ping")) {
- XMPP->ping_requested = 1;
- }
-
- else if (!strcasecmp(el, "stream")) {
- syslog(LOG_DEBUG, "xmpp: client shut down their stream");
- xmpp_massacre_roster();
- cprintf("</stream>\n");
- CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
- }
-
- else if (!strcasecmp(el, "query")) {
- /* already processed , no further action needed here */
- }
-
- else if (!strcasecmp(el, "bind")) {
- /* already processed , no further action needed here */
- }
-
- else {
- syslog(LOG_DEBUG, "xmpp: ignoring unknown tag <%s>", el);
- }
-
- XMPP->chardata_len = 0;
- if (XMPP->chardata_alloc > 0) {
- XMPP->chardata[0] = 0;
- }
-}
-
-
-void xmpp_xml_chardata(void *data, const XML_Char *s, int len)
-{
- citxmpp *X = XMPP;
-
- if (X->chardata_alloc == 0) {
- X->chardata_alloc = SIZ;
- X->chardata = malloc(X->chardata_alloc);
- }
- if ((X->chardata_len + len + 1) > X->chardata_alloc) {
- X->chardata_alloc = X->chardata_len + len + 1024;
- X->chardata = realloc(X->chardata, X->chardata_alloc);
- }
- memcpy(&X->chardata[X->chardata_len], s, len);
- X->chardata_len += len;
- X->chardata[X->chardata_len] = 0;
-}
-
-
-/*
- * This cleanup function blows away the temporary memory and files used by the XMPP service.
- */
-void xmpp_cleanup_function(void) {
-
- /* Don't do this stuff if this is not a XMPP session! */
- if (CC->h_command_function != xmpp_command_loop) return;
-
- if (XMPP->chardata != NULL) {
- free(XMPP->chardata);
- XMPP->chardata = NULL;
- XMPP->chardata_len = 0;
- XMPP->chardata_alloc = 0;
- if (XMPP->message_body != NULL) {
- free(XMPP->message_body);
- }
- }
- XML_ParserFree(XMPP->xp);
- free(XMPP);
-}
-
-
-/*
- * Here's where our XMPP session begins its happy day.
- */
-void xmpp_greeting(void) {
- client_set_inbound_buf(4);
- strcpy(CC->cs_clientname, "XMPP session");
- CC->session_specific_data = malloc(sizeof(citxmpp));
- memset(XMPP, 0, sizeof(citxmpp));
- XMPP->last_event_processed = queue_event_seq;
-
- /* XMPP does not use a greeting, but we still have to initialize some things. */
-
- XMPP->xp = XML_ParserCreateNS("UTF-8", ':');
- if (XMPP->xp == NULL) {
- syslog(LOG_ERR, "xmpp: cannot create XML parser");
- CC->kill_me = KILLME_XML_PARSER;
- return;
- }
-
- XML_SetElementHandler(XMPP->xp, xmpp_xml_start, xmpp_xml_end);
- XML_SetCharacterDataHandler(XMPP->xp, xmpp_xml_chardata);
- // XML_SetUserData(XMPP->xp, something...);
-
- /* Prevent the "billion laughs" attack against expat by disabling
- * internal entity expansion. With 2.x, forcibly stop the parser
- * if an entity is declared - this is safer and a more obvious
- * failure mode. With older versions, simply prevent expansion
- * of such entities. */
-#ifdef HAVE_XML_STOPPARSER
- XML_SetEntityDeclHandler(XMPP->xp, xmpp_entity_declaration);
-#else
- XML_SetDefaultHandler(XMPP->xp, NULL);
-#endif
-
- CC->can_receive_im = 1; /* This protocol is capable of receiving instant messages */
-}
-
-
-/*
- * Main command loop for XMPP sessions.
- */
-void xmpp_command_loop(void) {
- int rc;
- StrBuf *stream_input = NewStrBuf();
-
- time(&CC->lastcmd);
- rc = client_read_random_blob(stream_input, 30);
- if (rc > 0) {
- XML_Parse(XMPP->xp, ChrPtr(stream_input), rc, 0);
- }
- else {
- syslog(LOG_ERR, "xmpp: client disconnected: ending session.");
- CC->kill_me = KILLME_CLIENT_DISCONNECTED;
- }
- FreeStrBuf(&stream_input);
-}
-
-
-/*
- * Async loop for XMPP sessions (handles the transmission of unsolicited stanzas)
- */
-void xmpp_async_loop(void) {
- xmpp_process_events();
- xmpp_output_incoming_messages();
-}
-
-
-/*
- * Login hook for XMPP sessions
- */
-void xmpp_login_hook(void) {
- xmpp_queue_event(XMPP_EVT_LOGIN, CC->cs_principal_id);
-}
-
-
-/*
- * Logout hook for XMPP sessions
- */
-void xmpp_logout_hook(void) {
- xmpp_queue_event(XMPP_EVT_LOGOUT, CC->cs_principal_id);
-}
-
-
-const char *CitadelServiceXMPP="XMPP";
-CTDL_MODULE_INIT(xmpp)
-{
- if (!threading) {
- CtdlRegisterServiceHook(CtdlGetConfigInt("c_xmpp_c2s_port"),
- NULL,
- xmpp_greeting,
- xmpp_command_loop,
- xmpp_async_loop,
- CitadelServiceXMPP
- );
- CtdlRegisterSessionHook(xmpp_cleanup_function, EVT_STOP, PRIO_STOP + 70);
- CtdlRegisterSessionHook(xmpp_login_hook, EVT_LOGIN, PRIO_LOGIN + 90);
- CtdlRegisterSessionHook(xmpp_logout_hook, EVT_LOGOUT, PRIO_LOGOUT + 90);
- CtdlRegisterSessionHook(xmpp_login_hook, EVT_UNSTEALTH, PRIO_UNSTEALTH + 1);
- CtdlRegisterSessionHook(xmpp_logout_hook, EVT_STEALTH, PRIO_STEALTH + 1);
-
- }
-
- /* return our module name for the log */
- return "xmpp";
-}
+++ /dev/null
-/*
- * Copyright (c) 2007-2019 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.
- */
-
-typedef struct _citxmpp { /* Information about the current session */
- XML_Parser xp; /* XML parser instance for incoming client stream */
- char server_name[256]; /* who they think we are */
- char *chardata;
- int chardata_len;
- int chardata_alloc;
- char client_jid[256]; /* "full JID" of the client */
- int last_event_processed;
-
- char iq_type[256]; /* for <iq> stanzas */
- char iq_id[256];
- char iq_from[256];
- char iq_to[256];
- char iq_client_username[256]; /* username requested by the client (NON SASL ONLY) */
- char iq_client_password[256]; /* password requested by the client (NON SASL ONLY) */
- char iq_client_resource[256]; /* resource name requested by the client */
- int iq_session; /* nonzero == client is requesting a session */
- int iq_vcard; /* nonzero == client is requesting its vCard */
- char iq_query_xmlns[256]; /* Namespace of <query> */
-
- char sasl_auth_mech[32]; /* SASL auth mechanism requested by the client */
-
- char message_to[256];
- char *message_body; /* Message body in transit */
- int html_tag_level; /* <html> tag nesting level */
-
- int bind_requested; /* In this stanza, client is asking server to bind a resource. */
- int ping_requested; /* In this stanza, client is pinging the server. */
-} citxmpp;
-
-#define XMPP ((citxmpp *)CC->session_specific_data)
-
-struct xmpp_event {
- struct xmpp_event *next;
- int event_seq;
- time_t event_time;
- int event_type;
- char event_jid[256];
- int session_which_generated_this_event;
-};
-
-extern struct xmpp_event *xmpp_queue;
-extern int queue_event_seq;
-
-enum {
- XMPP_EVT_LOGIN,
- XMPP_EVT_LOGOUT
-};
-
-void xmpp_cleanup_function(void);
-void xmpp_greeting(void);
-void xmpp_command_loop(void);
-void xmpp_async_loop(void);
-void xmpp_sasl_auth(char *, char *);
-void xmpp_output_auth_mechs(void);
-void xmpp_query_namespace(char *, char *, char *, char *);
-void xmpp_wholist_presence_dump(void);
-void xmpp_output_incoming_messages(void);
-void xmpp_queue_event(int, char *);
-void xmpp_process_events(void);
-void xmpp_presence_notify(char *, int);
-void xmpp_roster_item(struct CitContext *);
-void xmpp_send_message(char *, char *);
-void xmpp_non_sasl_authenticate(char *, char *, char *);
-void xmpp_massacre_roster(void);
-void xmpp_delete_old_buddies_who_no_longer_exist_from_the_client_roster(void);
-int xmpp_is_visible(struct CitContext *from, struct CitContext *to_whom);
-char *xmlesc(char *buf, char *str, int bufsiz);
+++ /dev/null
-/*
- * Handle messages sent and received using XMPP (Jabber) protocol
- *
- * Copyright (c) 2007-2010 by Art Cancro
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <ctype.h>
-#include <expat.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "internet_addressing.h"
-#include "ctdl_module.h"
-#include "serv_xmpp.h"
-
-
-/*
- * This function is called by the XMPP service's async loop.
- * If the client session has instant messages waiting, it outputs
- * unsolicited XML stanzas containing them.
- */
-void xmpp_output_incoming_messages(void) {
-
- struct ExpressMessage *ptr;
- char xmlbuf1[4096];
- char xmlbuf2[4096];
-
- while (CC->FirstExpressMessage != NULL) {
-
- begin_critical_section(S_SESSION_TABLE);
- ptr = CC->FirstExpressMessage;
- CC->FirstExpressMessage = CC->FirstExpressMessage->next;
- end_critical_section(S_SESSION_TABLE);
-
- cprintf("<message to=\"%s\" from=\"%s\" type=\"chat\">",
- xmlesc(xmlbuf1, XMPP->client_jid, sizeof xmlbuf1),
- xmlesc(xmlbuf2, ptr->sender_email, sizeof xmlbuf2)
- );
- if (ptr->text != NULL) {
- striplt(ptr->text);
- cprintf("<body>%s</body>", xmlesc(xmlbuf1, ptr->text, sizeof xmlbuf1));
- free(ptr->text);
- }
- cprintf("</message>");
- free(ptr);
- }
-}
-
-
-/*
- * Client is sending a message.
- */
-void xmpp_send_message(char *message_to, char *message_body) {
- char *recp = NULL;
- struct CitContext *cptr;
-
- if (message_body == NULL) return;
- if (message_to == NULL) return;
- if (IsEmptyStr(message_to)) return;
- if (!CC->logged_in) return;
-
- for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
- if ( (cptr->logged_in)
- && (cptr->can_receive_im)
- && (!strcasecmp(cptr->cs_principal_id, message_to))
- ) {
- recp = cptr->user.fullname;
- }
- }
-
- if (recp) {
- PerformXmsgHooks(CC->user.fullname, CC->cs_principal_id, recp, message_body);
- }
-
- free(XMPP->message_body);
- XMPP->message_body = NULL;
- XMPP->message_to[0] = 0;
- time(&CC->lastidle);
-}
-
+++ /dev/null
-/*
- * Handle XMPP presence exchanges
- *
- * Copyright (c) 2007-2021 by Art Cancro and citadel.org
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- *
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <assert.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <ctype.h>
-#include <expat.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "internet_addressing.h"
-#include "ctdl_module.h"
-#include "serv_xmpp.h"
-
-
-/*
- * Indicate the presence of another user to the client
- * (used in several places)
- */
-void xmpp_indicate_presence(char *presence_jid) {
- char xmlbuf[256];
-
- syslog(LOG_DEBUG, "xmpp: indicating presence of <%s> to <%s>", presence_jid, XMPP->client_jid);
- cprintf("<presence from=\"%s\" ", xmlesc(xmlbuf, presence_jid, sizeof xmlbuf));
- cprintf("to=\"%s\"></presence>", xmlesc(xmlbuf, XMPP->client_jid, sizeof xmlbuf));
-}
-
-
-/*
- * Convenience function to determine whether any given session is 'visible' to any other given session,
- * and is capable of receiving instant messages from that session.
- */
-int xmpp_is_visible(struct CitContext *cptr, struct CitContext *to_whom) {
- int aide = (to_whom->user.axlevel >= AxAideU);
-
- if ( (cptr->logged_in)
- && (((cptr->cs_flags&CS_STEALTH)==0) || (aide)) /* aides see everyone */
- && (cptr->user.usernum != to_whom->user.usernum) /* don't show myself */
- && (cptr->can_receive_im) /* IM-capable session */
- ) {
- return(1);
- }
- else {
- return(0);
- }
-}
-
-
-/*
- * Initial dump of the entire wholist
- */
-void xmpp_wholist_presence_dump(void) {
- struct CitContext *cptr = NULL;
- int nContexts, i;
-
- cptr = CtdlGetContextArray(&nContexts);
- if (!cptr) {
- return;
- }
-
- for (i=0; i<nContexts; i++) {
- if (xmpp_is_visible(&cptr[i], CC)) {
- xmpp_indicate_presence(cptr[i].cs_principal_id);
- }
- }
- free(cptr);
-}
-
-
-/*
- * Function to remove a buddy subscription and delete from the roster
- * (used in several places)
- */
-void xmpp_destroy_buddy(char *presence_jid, int aggressively) {
- static int unsolicited_id = 1;
- char xmlbuf1[256];
- char xmlbuf2[256];
-
- if (!presence_jid) return;
- if (!XMPP) return;
- if (!XMPP->client_jid) return;
-
- /* Transmit non-presence information */
- cprintf("<presence type=\"unavailable\" from=\"%s\" to=\"%s\"></presence>",
- xmlesc(xmlbuf1, presence_jid, sizeof xmlbuf1),
- xmlesc(xmlbuf2, XMPP->client_jid, sizeof xmlbuf2)
- );
-
- /*
- * Setting the "aggressively" flag also sends an "unsubscribed" presence update.
- * We only ask for this when flushing the client side roster, because if we do it
- * in the middle of a session when another user logs off, some clients (Jitsi) interpret
- * it as a rejection of a subscription request.
- */
- if (aggressively) {
- cprintf("<presence type=\"unsubscribed\" from=\"%s\" to=\"%s\"></presence>",
- xmlesc(xmlbuf1, presence_jid, sizeof xmlbuf1),
- xmlesc(xmlbuf2, XMPP->client_jid, sizeof xmlbuf2)
- );
- }
-
- // note: we should implement xmpp_indicate_nonpresence so we can use it elsewhere
-
- /* Do an unsolicited roster update that deletes the contact. */
- cprintf("<iq from=\"%s\" to=\"%s\" id=\"unbuddy_%x\" type=\"result\">",
- xmlesc(xmlbuf1, CC->cs_principal_id, sizeof xmlbuf1),
- xmlesc(xmlbuf2, XMPP->client_jid, sizeof xmlbuf2),
- ++unsolicited_id
- );
- cprintf("<query xmlns=\"jabber:iq:roster\">");
- cprintf("<item jid=\"%s\" subscription=\"remove\">", xmlesc(xmlbuf1, presence_jid, sizeof xmlbuf1));
- cprintf("<group>%s</group>", xmlesc(xmlbuf1, CtdlGetConfigStr("c_humannode"), sizeof xmlbuf1));
- cprintf("</item>");
- cprintf("</query>"
- "</iq>"
- );
-}
-
-
-/*
- * When a user logs in or out of the local Citadel system, notify all XMPP sessions about it.
- * THIS FUNCTION HAS A BUG IN IT THAT ENUMERATES THE SESSIONS WRONG.
- */
-void xmpp_presence_notify(char *presence_jid, int event_type) {
- struct CitContext *cptr;
- static int unsolicited_id = 12345;
- int visible_sessions = 0;
- int nContexts, i;
- int which_cptr_is_relevant = (-1);
-
- if (IsEmptyStr(presence_jid)) return;
- if (CC->kill_me) return;
-
- cptr = CtdlGetContextArray(&nContexts);
- if (!cptr) {
- return;
- }
-
- /* Count the visible sessions for this user */
- for (i=0; i<nContexts; i++) {
- if ( (!strcasecmp(cptr[i].cs_principal_id, presence_jid))
- && (xmpp_is_visible(&cptr[i], CC))
- ) {
- ++visible_sessions;
- which_cptr_is_relevant = i;
- }
- }
-
- syslog(LOG_DEBUG, "xmpp: %d sessions for <%s> are now visible to session %d", visible_sessions, presence_jid, CC->cs_pid);
-
- if ( (event_type == XMPP_EVT_LOGIN) && (visible_sessions == 1) ) {
-
- syslog(LOG_DEBUG, "xmpp: telling session %d that <%s> logged in", CC->cs_pid, presence_jid);
-
- /* Do an unsolicited roster update that adds a new contact. */
- assert(which_cptr_is_relevant >= 0);
- cprintf("<iq id=\"unsolicited_%x\" type=\"result\">", ++unsolicited_id);
- cprintf("<query xmlns=\"jabber:iq:roster\">");
- xmpp_roster_item(&cptr[which_cptr_is_relevant]);
- cprintf("</query></iq>");
-
- /* Transmit presence information */
- xmpp_indicate_presence(presence_jid);
- }
-
- if (visible_sessions == 0) {
- syslog(LOG_DEBUG, "xmpp: telling session %d that <%s> logged out", CC->cs_pid, presence_jid);
- xmpp_destroy_buddy(presence_jid, 0); /* non aggressive presence update */
- }
-
- free(cptr);
-}
-
-
-void xmpp_fetch_mortuary_backend(long msgnum, void *userdata) {
- HashList *mortuary = (HashList *) userdata;
- struct CtdlMessage *msg;
- char *ptr = NULL;
- char *lasts = NULL;
-
- msg = CtdlFetchMessage(msgnum, 1);
- if (msg == NULL) {
- return;
- }
-
- /* now add anyone we find into the hashlist */
-
- /* skip past the headers */
- ptr = strstr(msg->cm_fields[eMesageText], "\n\n");
- if (ptr != NULL) {
- ptr += 2;
- }
- else {
- ptr = strstr(msg->cm_fields[eMesageText], "\n\r\n");
- if (ptr != NULL) {
- ptr += 3;
- }
- }
-
- /* the remaining lines are addresses */
- if (ptr != NULL) {
- ptr = strtok_r(ptr, "\n", &lasts);
- while (ptr != NULL) {
- char *pch = strdup(ptr);
- Put(mortuary, pch, strlen(pch), pch, NULL);
- ptr = strtok_r(NULL, "\n", &lasts);
- }
- }
-
- CM_Free(msg);
-}
-
-
-/*
- * Fetch the "mortuary" - a list of dead buddies which we keep around forever
- * so we can remove them from any client's roster that still has them listed
- */
-HashList *xmpp_fetch_mortuary(void) {
- HashList *mortuary = NewHash(1, NULL);
- if (!mortuary) {
- syslog(LOG_ALERT, "xmpp: NewHash() failed!");
- return(NULL);
- }
-
- if (CtdlGetRoom(&CC->room, USERCONFIGROOM) != 0) {
- /* no config room exists - no further processing is required. */
- return(mortuary);
- }
- CtdlForEachMessage(MSGS_LAST, 1, NULL, XMPPMORTUARY, NULL,
- xmpp_fetch_mortuary_backend, (void *)mortuary );
-
- return(mortuary);
-}
-
-
-/*
- * Fetch the "mortuary" - a list of dead buddies which we keep around forever
- * so we can remove them from any client's roster that still has them listed
- */
-void xmpp_store_mortuary(HashList *mortuary) {
- HashPos *HashPos;
- long len;
- void *Value;
- const char *Key;
- StrBuf *themsg;
-
- themsg = NewStrBuf();
- StrBufPrintf(themsg, "Content-type: " XMPPMORTUARY "\n"
- "Content-transfer-encoding: 7bit\n"
- "\n"
- );
-
- HashPos = GetNewHashPos(mortuary, 0);
- while (GetNextHashPos(mortuary, HashPos, &len, &Key, &Value) != 0) {
- StrBufAppendPrintf(themsg, "%s\n", (char *)Value);
- }
- DeleteHashPos(&HashPos);
-
- /* Delete the old mortuary */
- CtdlDeleteMessages(USERCONFIGROOM, NULL, 0, XMPPMORTUARY);
-
- /* And save the new one to disk */
- quickie_message(CC->user.fullname, NULL, NULL, USERCONFIGROOM, ChrPtr(themsg), 4, "XMPP Mortuary");
- FreeStrBuf(&themsg);
-}
-
-
-/*
- * Upon logout we make an attempt to delete the whole roster, in order to
- * try to keep "ghost" buddies from remaining in the client-side roster.
- *
- * Since the client is probably not still alive, also remember the current
- * roster for next time so we can delete dead buddies then.
- */
-void xmpp_massacre_roster(void) {
- struct CitContext *cptr;
- int nContexts, i;
- HashList *mortuary = xmpp_fetch_mortuary();
-
- cptr = CtdlGetContextArray(&nContexts);
- if (cptr) {
- for (i=0; i<nContexts; i++) {
- if (xmpp_is_visible(&cptr[i], CC)) {
- if (mortuary) {
- char *buddy = strdup(cptr[i].cs_principal_id);
- Put(mortuary, buddy, strlen(buddy), buddy, NULL);
- }
- }
- }
- free (cptr);
- }
-
- if (mortuary) {
- xmpp_store_mortuary(mortuary);
- DeleteHash(&mortuary);
- }
-}
-
-
-/*
- * Stupidly, XMPP does not specify a way to tell the client to flush its client-side roster
- * and prepare to receive a new one. So instead we remember every buddy we've ever told the
- * client about, and push delete operations out at the beginning of a session.
- *
- * We omit any users who happen to be online right now, but we still keep them in the mortuary,
- * which needs to be maintained as a list of every buddy the user has ever seen. We don't know
- * when they're connecting from the same client and when they're connecting from a different client,
- * so we have no guarantee of what is in the client side roster at connect time.
- */
-void xmpp_delete_old_buddies_who_no_longer_exist_from_the_client_roster(void) {
- long len;
- void *Value;
- const char *Key;
- struct CitContext *cptr;
- int nContexts, i;
- int online_now = 0;
- HashList *mortuary = xmpp_fetch_mortuary();
- HashPos *HashPos = GetNewHashPos(mortuary, 0);
-
- /* we need to omit anyone who is currently online */
- cptr = CtdlGetContextArray(&nContexts);
-
- /* go through the list of users in the mortuary... */
- while (GetNextHashPos(mortuary, HashPos, &len, &Key, &Value) != 0)
- {
-
- online_now = 0;
- if (cptr) for (i=0; i<nContexts; i++) {
- if (xmpp_is_visible(&cptr[i], CC)) {
- if (!strcasecmp(cptr[i].cs_principal_id, (char *)Value)) {
- online_now = 1;
- }
- }
- }
-
- if (!online_now) {
- xmpp_destroy_buddy((char *)Value, 1); /* aggressive presence update */
- }
-
- }
- DeleteHashPos(&HashPos);
- DeleteHash(&mortuary);
- free(cptr);
-}
+++ /dev/null
-/*
- * Handle <iq> <get> <query> type situations (namespace queries)
- *
- * Copyright (c) 2007-2015 by Art Cancro and citadel.org
- *
- * 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.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <ctype.h>
-#include <expat.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "internet_addressing.h"
-#include "ctdl_module.h"
-#include "serv_xmpp.h"
-
-
-/*
- * Output a single roster item, for roster queries or pushes
- */
-void xmpp_roster_item(struct CitContext *cptr) {
- char xmlbuf1[256];
- char xmlbuf2[256];
-
- cprintf("<item jid=\"%s\" name=\"%s\" subscription=\"both\">",
- xmlesc(xmlbuf1, cptr->cs_principal_id, sizeof xmlbuf1),
- xmlesc(xmlbuf2, cptr->user.fullname, sizeof xmlbuf2)
- );
- cprintf("<group>%s</group>", xmlesc(xmlbuf1, CtdlGetConfigStr("c_humannode"), sizeof xmlbuf1));
- cprintf("</item>");
-}
-
-
-/*
- * Return the results for a "jabber:iq:roster:query"
- *
- * Since we are not yet managing a roster, we simply return the entire wholist
- * (minus any entries for this user -- don't tell me about myself)
- *
- */
-void xmpp_iq_roster_query(void)
-{
- struct CitContext *cptr;
- int nContexts, i;
-
- syslog(LOG_DEBUG, "xmpp: roster push!");
- cprintf("<query xmlns=\"jabber:iq:roster\">");
- cptr = CtdlGetContextArray(&nContexts);
- if (cptr) {
- for (i=0; i<nContexts; i++) {
- if (xmpp_is_visible(&cptr[i], CC)) {
- syslog(LOG_DEBUG, "xmpp: rosterizing %s", cptr[i].user.fullname);
- xmpp_roster_item(&cptr[i]);
- }
- }
- free (cptr);
- }
- cprintf("</query>");
-}
-
-
-/*
- * Client is doing a namespace query. These are all handled differently.
- */
-void xmpp_query_namespace(char *iq_id, char *iq_from, char *iq_to, char *query_xmlns)
-{
- int supported_namespace = 0;
- int roster_query = 0;
- char xmlbuf[256];
- int reply_must_be_from_my_jid = 0;
-
- /* We need to know before we begin the response whether this is a supported namespace, so
- * unfortunately all supported namespaces need to be defined here *and* down below where
- * they are handled.
- */
- if (
- (!strcasecmp(query_xmlns, "jabber:iq:roster:query"))
- || (!strcasecmp(query_xmlns, "jabber:iq:auth:query"))
- || (!strcasecmp(query_xmlns, "http://jabber.org/protocol/disco#items:query"))
- || (!strcasecmp(query_xmlns, "http://jabber.org/protocol/disco#info:query"))
- ) {
- supported_namespace = 1;
- }
-
- syslog(LOG_DEBUG, "xmpp: xmpp_query_namespace(id=%s, from=%s, to=%s, xmlns=%s)", iq_id, iq_from, iq_to, query_xmlns);
-
- /*
- * Beginning of query result.
- */
-
- if (!strcasecmp(query_xmlns, "jabber:iq:roster:query")) {
- reply_must_be_from_my_jid = 1;
- }
-
- char dom[1024]; // client is expecting to see the reply
- if (reply_must_be_from_my_jid) { // coming "from" the user's jid
- safestrncpy(dom, XMPP->client_jid, sizeof(dom));
- char *slash = strchr(dom, '/');
- if (slash) {
- *slash = 0;
- }
- }
- else {
- safestrncpy(dom, XMPP->client_jid, sizeof(dom)); // client is expecting to see the reply
- if (IsEmptyStr(dom)) { // coming "from" the domain of the user's jid
- safestrncpy(dom, XMPP->server_name, sizeof(dom));
- }
- char *at = strrchr(dom, '@');
- if (at) {
- strcpy(dom, ++at);
- }
- char *slash = strchr(dom, '/');
- if (slash) {
- *slash = 0;
- }
- }
-
- if (supported_namespace) {
- cprintf("<iq type=\"result\" from=\"%s\" ", xmlesc(xmlbuf, dom, sizeof xmlbuf) );
- }
- else {
- cprintf("<iq type=\"error\" from=\"%s\" ", xmlesc(xmlbuf, dom, sizeof xmlbuf) );
- }
- if (!IsEmptyStr(iq_from)) {
- cprintf("to=\"%s\" ", xmlesc(xmlbuf, iq_from, sizeof xmlbuf));
- }
- cprintf("id=\"%s\">", xmlesc(xmlbuf, iq_id, sizeof xmlbuf));
-
- /*
- * Is this a query we know how to handle?
- */
-
- if (!strcasecmp(query_xmlns, "jabber:iq:roster:query")) {
- roster_query = 1;
- xmpp_iq_roster_query();
- }
-
- else if (!strcasecmp(query_xmlns, "jabber:iq:auth:query")) {
- cprintf("<query xmlns=\"jabber:iq:auth\">"
- "<username/><password/><resource/>"
- "</query>"
- );
- }
-
- // Extension "xep-0030" (http://xmpp.org/extensions/xep-0030.html) (return an empty set of results)
- else if (!strcasecmp(query_xmlns, "http://jabber.org/protocol/disco#items:query")) {
- cprintf("<query xmlns=\"%s\"/>", xmlesc(xmlbuf, query_xmlns, sizeof xmlbuf));
- }
-
- // Extension "xep-0030" (http://xmpp.org/extensions/xep-0030.html) (return an empty set of results)
- else if (!strcasecmp(query_xmlns, "http://jabber.org/protocol/disco#info:query")) {
- cprintf("<query xmlns=\"%s\"/>", xmlesc(xmlbuf, query_xmlns, sizeof xmlbuf));
- }
-
- /*
- * If we didn't hit any known query namespaces then we should deliver a
- * "service unavailable" error (see RFC3921 section 2.4 and 11.1.5.4)
- */
-
- else {
- syslog(LOG_DEBUG, "xmpp: unknown query namespace '%s' - returning <service-unavailable/>", query_xmlns);
- cprintf("<error code=\"503\" type=\"cancel\">"
- "<service-unavailable xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
- "</error>"
- );
- }
-
- cprintf("</iq>");
-
- /* If we told the client who is on the roster, we also need to tell the client
- * who is *not* on the roster. (It's down here because we can't do it in the same
- * stanza; this will be an unsolicited push.)
- */
- if (roster_query) {
- xmpp_delete_old_buddies_who_no_longer_exist_from_the_client_roster();
- }
-}
+++ /dev/null
-/*
- * XMPP event queue
- *
- * Copyright (c) 2007-2021 by Art Cancro
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <ctype.h>
-#include <expat.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "internet_addressing.h"
-#include "ctdl_module.h"
-#include "serv_xmpp.h"
-
-int queue_event_seq = 0;
-
-void xmpp_queue_event(int event_type, char *email_addr) {
-
- struct xmpp_event *xptr = NULL;
- struct xmpp_event *new_event = NULL;
- struct xmpp_event *last = NULL;
- int purged_something = 0;
- struct CitContext *cptr;
-
- syslog(LOG_DEBUG, "xmpp: xmpp_queue_event(%d, %s)", event_type, email_addr);
-
- /* Purge events more than a minute old */
- begin_critical_section(S_XMPP_QUEUE);
- do {
- purged_something = 0;
- if (xmpp_queue != NULL) {
- if ((time(NULL) - xmpp_queue->event_time) > 60) {
- xptr = xmpp_queue->next;
- free(xmpp_queue);
- xmpp_queue = xptr;
- purged_something = 1;
- }
- }
- } while(purged_something);
- end_critical_section(S_XMPP_QUEUE);
-
- /* Create a new event */
- new_event = (struct xmpp_event *) malloc(sizeof(struct xmpp_event));
- new_event->next = NULL;
- new_event->event_time = time(NULL);
- new_event->event_seq = ++queue_event_seq;
- new_event->event_type = event_type;
- new_event->session_which_generated_this_event = CC->cs_pid;
- safestrncpy(new_event->event_jid, email_addr, sizeof new_event->event_jid);
-
- /* Add it to the list */
- begin_critical_section(S_XMPP_QUEUE);
- if (xmpp_queue == NULL) {
- xmpp_queue = new_event;
- }
- else {
- for (xptr = xmpp_queue; xptr != NULL; xptr = xptr->next) {
- if (xptr->next == NULL) {
- last = xptr;
- }
- }
- last->next = new_event;
- }
- end_critical_section(S_XMPP_QUEUE);
-
- /* Tell the sessions that something is happening */
- begin_critical_section(S_SESSION_TABLE);
- for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
- if ((cptr->logged_in) && (cptr->h_async_function == xmpp_async_loop)) {
- set_async_waiting(cptr);
- }
- }
- end_critical_section(S_SESSION_TABLE);
-}
-
-
-/*
- * Are we interested in anything from the queue? (Called in async loop)
- */
-void xmpp_process_events(void) {
- struct xmpp_event *xptr = NULL;
- int highest_event = 0;
-
- for (xptr=xmpp_queue; xptr!=NULL; xptr=xptr->next) {
- if (xptr->event_seq > XMPP->last_event_processed) {
-
- switch(xptr->event_type) {
-
- case XMPP_EVT_LOGIN:
- case XMPP_EVT_LOGOUT:
- if (xptr->session_which_generated_this_event != CC->cs_pid) {
- xmpp_presence_notify(xptr->event_jid, xptr->event_type);
- }
- break;
- }
-
- if (xptr->event_seq > highest_event) {
- highest_event = xptr->event_seq;
- }
- }
- }
-
- XMPP->last_event_processed = highest_event;
-}
+++ /dev/null
-/*
- * Barebones SASL authentication service for XMPP (Jabber) clients.
- *
- * Note: RFC3920 says we "must" support DIGEST-MD5 but we only support PLAIN.
- *
- * Copyright (c) 2007-2019 by Art Cancro
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <time.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <ctype.h>
-#include <expat.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "user_ops.h"
-#include "internet_addressing.h"
-#include "ctdl_module.h"
-#include "serv_xmpp.h"
-
-
-/*
- * PLAIN authentication. Returns zero on success, nonzero on failure.
- */
-int xmpp_auth_plain(char *authstring)
-{
- char decoded_authstring[1024];
- char ident[256];
- char user[256];
- char pass[256];
- int result;
- long len;
-
- /* Take apart the authentication string */
- memset(pass, 0, sizeof(pass));
-
- CtdlDecodeBase64(decoded_authstring, authstring, strlen(authstring));
- safestrncpy(ident, decoded_authstring, sizeof ident);
- safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
- len = safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
- if (len < 0) {
- len = -len;
- }
-
- if (!IsEmptyStr(ident)) {
- result = CtdlLoginExistingUser(ident);
- }
- else {
- result = CtdlLoginExistingUser(user);
- }
-
- if (result == login_ok) {
- if (CtdlTryPassword(pass, len) == pass_ok) {
- return(0); /* success */
- }
- }
-
- return(1); /* failure */
-}
-
-
-/*
- * Output the list of SASL mechanisms offered by this stream.
- */
-void xmpp_output_auth_mechs(void) {
- cprintf("<mechanisms xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
- cprintf("<mechanism>PLAIN</mechanism>");
- cprintf("</mechanisms>");
-}
-
-
-/*
- * Here we go ... client is trying to authenticate.
- */
-void xmpp_sasl_auth(char *sasl_auth_mech, char *authstring) {
-
- if (strcasecmp(sasl_auth_mech, "PLAIN")) {
- cprintf("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
- cprintf("<invalid-mechanism/>");
- cprintf("</failure>");
- return;
- }
-
- if (CC->logged_in) {
- CtdlUserLogout(); /* Client may try to log in twice. Handle this. */
- }
-
- if (CC->nologin) {
- cprintf("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
- cprintf("<system-shutdown/>");
- cprintf("</failure>");
- }
-
- else if (xmpp_auth_plain(authstring) == 0) {
- cprintf("<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>");
- }
-
- else {
- cprintf("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
- cprintf("<not-authorized/>");
- cprintf("</failure>");
- }
-}
-
-
-/*
- * Non-SASL authentication
- */
-void xmpp_non_sasl_authenticate(char *iq_id, char *username, char *password) {
- int result;
- char xmlbuf[256];
-
- if (CC->logged_in) {
- CtdlUserLogout(); /* Client may try to log in twice. Handle this. */
- }
-
- result = CtdlLoginExistingUser(username);
- if (result == login_ok) {
- result = CtdlTryPassword(password, strlen(password));
- if (result == pass_ok) {
- cprintf("<iq type=\"result\" id=\"%s\"></iq>", xmlesc(xmlbuf, iq_id, sizeof xmlbuf)); /* success */
- return;
- }
- }
-
- /* failure */
- cprintf("<iq type=\"error\" id=\"%s\">", xmlesc(xmlbuf, iq_id, sizeof xmlbuf));
- cprintf("<error code=\"401\" type=\"auth\">"
- "<not-authorized xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
- "</error>"
- "</iq>"
- );
-}
+++ /dev/null
-// Implements the message store.
-//
-// Copyright (c) 1987-2022 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.
-
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <regex.h>
-#include <sys/stat.h>
-#include <assert.h>
-#include <libcitadel.h>
-#include "ctdl_module.h"
-#include "citserver.h"
-#include "control.h"
-#include "config.h"
-#include "clientsocket.h"
-#include "genstamp.h"
-#include "room_ops.h"
-#include "user_ops.h"
-#include "internet_addressing.h"
-#include "euidindex.h"
-#include "msgbase.h"
-#include "journaling.h"
-
-struct addresses_to_be_filed *atbf = NULL;
-
-// These are the four-character field headers we use when outputting
-// messages in Citadel format (as opposed to RFC822 format).
-char *msgkeys[] = {
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL,
- "from", // A -> eAuthor
- NULL, // B -> eBig_message
- NULL, // C (formerly used as eRemoteRoom)
- NULL, // D (formerly used as eDestination)
- "exti", // E -> eXclusivID
- "rfca", // F -> erFc822Addr
- NULL, // G
- "hnod", // H (formerly used as eHumanNode)
- "msgn", // I -> emessageId
- "jrnl", // J -> eJournal
- "rep2", // K -> eReplyTo
- "list", // L -> eListID
- "text", // M -> eMesageText
- NULL, // N (formerly used as eNodename)
- "room", // O -> eOriginalRoom
- "path", // P -> eMessagePath
- NULL, // Q
- "rcpt", // R -> eRecipient
- NULL, // S (formerly used as eSpecialField)
- "time", // T -> eTimestamp
- "subj", // U -> eMsgSubject
- "nvto", // V -> eenVelopeTo
- "wefw", // W -> eWeferences
- NULL, // X
- "cccc", // Y -> eCarbonCopY
- NULL // Z
-};
-
-
-HashList *msgKeyLookup = NULL;
-
-int GetFieldFromMnemonic(eMsgField *f, const char* c) {
- void *v = NULL;
- if (GetHash(msgKeyLookup, c, 4, &v)) {
- *f = (eMsgField) v;
- return 1;
- }
- return 0;
-}
-
-void FillMsgKeyLookupTable(void) {
- long i;
-
- msgKeyLookup = NewHash (1, FourHash);
-
- for (i=0; i < 91; i++) {
- if (msgkeys[i] != NULL) {
- Put(msgKeyLookup, msgkeys[i], 4, (void*)i, reference_free_handler);
- }
- }
-}
-
-
-eMsgField FieldOrder[] = {
-/* Important fields */
- emessageId ,
- eMessagePath ,
- eTimestamp ,
- eAuthor ,
- erFc822Addr ,
- eOriginalRoom,
- eRecipient ,
-/* Semi-important fields */
- eBig_message ,
- eExclusiveID ,
- eWeferences ,
- eJournal ,
-/* G is not used yet */
- eReplyTo ,
- eListID ,
-/* Q is not used yet */
- eenVelopeTo ,
-/* X is not used yet */
-/* Z is not used yet */
- eCarbonCopY ,
- eMsgSubject ,
-/* internal only */
- eErrorMsg ,
- eSuppressIdx ,
- eExtnotify ,
-/* Message text (MUST be last) */
- eMesageText
-/* Not saved to disk:
- eVltMsgNum
-*/
-};
-
-static const long NDiskFields = sizeof(FieldOrder) / sizeof(eMsgField);
-
-
-int CM_IsEmpty(struct CtdlMessage *Msg, eMsgField which) {
- return !((Msg->cm_fields[which] != NULL) && (Msg->cm_fields[which][0] != '\0'));
-}
-
-
-void CM_SetField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length) {
- if (Msg->cm_fields[which] != NULL) {
- free (Msg->cm_fields[which]);
- }
- if (length < 0) { // You can set the length to -1 to have CM_SetField measure it for you
- length = strlen(buf);
- }
- Msg->cm_fields[which] = malloc(length + 1);
- memcpy(Msg->cm_fields[which], buf, length);
- Msg->cm_fields[which][length] = '\0';
- Msg->cm_lengths[which] = length;
-}
-
-
-void CM_SetFieldLONG(struct CtdlMessage *Msg, eMsgField which, long lvalue) {
- char buf[128];
- long len;
- len = snprintf(buf, sizeof(buf), "%ld", lvalue);
- CM_SetField(Msg, which, buf, len);
-}
-
-
-void CM_CutFieldAt(struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen) {
- if (Msg->cm_fields[WhichToCut] == NULL)
- return;
-
- if (Msg->cm_lengths[WhichToCut] > maxlen)
- {
- Msg->cm_fields[WhichToCut][maxlen] = '\0';
- Msg->cm_lengths[WhichToCut] = maxlen;
- }
-}
-
-
-void CM_FlushField(struct CtdlMessage *Msg, eMsgField which) {
- if (Msg->cm_fields[which] != NULL)
- free (Msg->cm_fields[which]);
- Msg->cm_fields[which] = NULL;
- Msg->cm_lengths[which] = 0;
-}
-
-
-void CM_Flush(struct CtdlMessage *Msg) {
- int i;
-
- if (CM_IsValidMsg(Msg) == 0) {
- return;
- }
-
- for (i = 0; i < 256; ++i) {
- CM_FlushField(Msg, i);
- }
-}
-
-
-void CM_CopyField(struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy) {
- long len;
- if (Msg->cm_fields[WhichToPutTo] != NULL) {
- free (Msg->cm_fields[WhichToPutTo]);
- }
-
- if (Msg->cm_fields[WhichtToCopy] != NULL) {
- len = Msg->cm_lengths[WhichtToCopy];
- Msg->cm_fields[WhichToPutTo] = malloc(len + 1);
- memcpy(Msg->cm_fields[WhichToPutTo], Msg->cm_fields[WhichtToCopy], len);
- Msg->cm_fields[WhichToPutTo][len] = '\0';
- Msg->cm_lengths[WhichToPutTo] = len;
- }
- else {
- Msg->cm_fields[WhichToPutTo] = NULL;
- Msg->cm_lengths[WhichToPutTo] = 0;
- }
-}
-
-
-void CM_PrependToField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length) {
- if (Msg->cm_fields[which] != NULL) {
- long oldmsgsize;
- long newmsgsize;
- char *new;
-
- oldmsgsize = Msg->cm_lengths[which] + 1;
- newmsgsize = length + oldmsgsize;
-
- new = malloc(newmsgsize);
- memcpy(new, buf, length);
- memcpy(new + length, Msg->cm_fields[which], oldmsgsize);
- free(Msg->cm_fields[which]);
- Msg->cm_fields[which] = new;
- Msg->cm_lengths[which] = newmsgsize - 1;
- }
- else {
- Msg->cm_fields[which] = malloc(length + 1);
- memcpy(Msg->cm_fields[which], buf, length);
- Msg->cm_fields[which][length] = '\0';
- Msg->cm_lengths[which] = length;
- }
-}
-
-
-void CM_SetAsField(struct CtdlMessage *Msg, eMsgField which, char **buf, long length) {
- if (Msg->cm_fields[which] != NULL) {
- free (Msg->cm_fields[which]);
- }
-
- Msg->cm_fields[which] = *buf;
- *buf = NULL;
- if (length < 0) { // You can set the length to -1 to have CM_SetField measure it for you
- Msg->cm_lengths[which] = strlen(Msg->cm_fields[which]);
- }
- else {
- Msg->cm_lengths[which] = length;
- }
-}
-
-
-void CM_SetAsFieldSB(struct CtdlMessage *Msg, eMsgField which, StrBuf **buf) {
- if (Msg->cm_fields[which] != NULL) {
- free (Msg->cm_fields[which]);
- }
-
- Msg->cm_lengths[which] = StrLength(*buf);
- Msg->cm_fields[which] = SmashStrBuf(buf);
-}
-
-
-void CM_GetAsField(struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen) {
- if (Msg->cm_fields[which] != NULL) {
- *retlen = Msg->cm_lengths[which];
- *ret = Msg->cm_fields[which];
- Msg->cm_fields[which] = NULL;
- Msg->cm_lengths[which] = 0;
- }
- else {
- *ret = NULL;
- *retlen = 0;
- }
-}
-
-
-// Returns 1 if the supplied pointer points to a valid Citadel message.
-// If the pointer is NULL or the magic number check fails, returns 0.
-int CM_IsValidMsg(struct CtdlMessage *msg) {
- if (msg == NULL) {
- return 0;
- }
- if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
- syslog(LOG_WARNING, "msgbase: CM_IsValidMsg() self-check failed");
- return 0;
- }
- return 1;
-}
-
-
-void CM_FreeContents(struct CtdlMessage *msg) {
- int i;
-
- for (i = 0; i < 256; ++i)
- if (msg->cm_fields[i] != NULL) {
- free(msg->cm_fields[i]);
- msg->cm_lengths[i] = 0;
- }
-
- msg->cm_magic = 0; // just in case
-}
-
-
-// 'Destructor' for struct CtdlMessage
-void CM_Free(struct CtdlMessage *msg) {
- if (CM_IsValidMsg(msg) == 0) {
- if (msg != NULL) free (msg);
- return;
- }
- CM_FreeContents(msg);
- free(msg);
-}
-
-
-int CM_DupField(eMsgField i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg) {
- long len;
- len = OrgMsg->cm_lengths[i];
- NewMsg->cm_fields[i] = malloc(len + 1);
- if (NewMsg->cm_fields[i] == NULL) {
- return 0;
- }
- memcpy(NewMsg->cm_fields[i], OrgMsg->cm_fields[i], len);
- NewMsg->cm_fields[i][len] = '\0';
- NewMsg->cm_lengths[i] = len;
- return 1;
-}
-
-
-struct CtdlMessage *CM_Duplicate(struct CtdlMessage *OrgMsg) {
- int i;
- struct CtdlMessage *NewMsg;
-
- if (CM_IsValidMsg(OrgMsg) == 0) {
- return NULL;
- }
- NewMsg = (struct CtdlMessage *)malloc(sizeof(struct CtdlMessage));
- if (NewMsg == NULL) {
- return NULL;
- }
-
- memcpy(NewMsg, OrgMsg, sizeof(struct CtdlMessage));
-
- memset(&NewMsg->cm_fields, 0, sizeof(char*) * 256);
-
- for (i = 0; i < 256; ++i) {
- if (OrgMsg->cm_fields[i] != NULL) {
- if (!CM_DupField(i, OrgMsg, NewMsg)) {
- CM_Free(NewMsg);
- return NULL;
- }
- }
- }
-
- return NewMsg;
-}
-
-
-// Determine if a given message matches the fields in a message template.
-// Return 0 for a successful match.
-int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
- int i;
-
- // If there aren't any fields in the template, all messages will match.
- if (template == NULL) return(0);
-
- // Null messages are bogus.
- if (msg == NULL) return(1);
-
- for (i='A'; i<='Z'; ++i) {
- if (template->cm_fields[i] != NULL) {
- if (msg->cm_fields[i] == NULL) {
- // Considered equal if temmplate is empty string
- if (IsEmptyStr(template->cm_fields[i])) continue;
- return 1;
- }
- if ((template->cm_lengths[i] != msg->cm_lengths[i]) ||
- (strcasecmp(msg->cm_fields[i], template->cm_fields[i])))
- return 1;
- }
- }
-
- // All compares succeeded: we have a match!
- return 0;
-}
-
-
-// Retrieve the "seen" message list for the current room.
-void CtdlGetSeen(char *buf, int which_set) {
- visit vbuf;
-
- // Learn about the user and room in question
- CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
-
- if (which_set == ctdlsetseen_seen) {
- safestrncpy(buf, vbuf.v_seen, SIZ);
- }
- if (which_set == ctdlsetseen_answered) {
- safestrncpy(buf, vbuf.v_answered, SIZ);
- }
-}
-
-
-// Manipulate the "seen msgs" string (or other message set strings)
-void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
- int target_setting, int which_set,
- struct ctdluser *which_user, struct ctdlroom *which_room) {
- struct cdbdata *cdbfr;
- int i, k;
- int is_seen = 0;
- int was_seen = 0;
- long lo = (-1L);
- long hi = (-1L);
- visit vbuf;
- long *msglist;
- int num_msgs = 0;
- StrBuf *vset;
- StrBuf *setstr;
- StrBuf *lostr;
- StrBuf *histr;
- const char *pvset;
- char *is_set; // actually an array of booleans
-
- // Don't bother doing *anything* if we were passed a list of zero messages
- if (num_target_msgnums < 1) {
- return;
- }
-
- // If no room was specified, we go with the current room.
- if (!which_room) {
- which_room = &CC->room;
- }
-
- // If no user was specified, we go with the current user.
- if (!which_user) {
- which_user = &CC->user;
- }
-
- syslog(LOG_DEBUG, "msgbase: CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>",
- num_target_msgnums, target_msgnums[0],
- (target_setting ? "SET" : "CLEAR"),
- which_set,
- which_room->QRname);
-
- // Learn about the user and room in question
- CtdlGetRelationship(&vbuf, which_user, which_room);
-
- // Load the message list
- cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
- if (cdbfr != NULL) {
- msglist = (long *) cdbfr->ptr;
- cdbfr->ptr = NULL; // CtdlSetSeen() now owns this memory
- num_msgs = cdbfr->len / sizeof(long);
- cdb_free(cdbfr);
- }
- else {
- return; // No messages at all? No further action.
- }
-
- is_set = malloc(num_msgs * sizeof(char));
- memset(is_set, 0, (num_msgs * sizeof(char)) );
-
- // Decide which message set we're manipulating
- switch(which_set) {
- case ctdlsetseen_seen:
- vset = NewStrBufPlain(vbuf.v_seen, -1);
- break;
- case ctdlsetseen_answered:
- vset = NewStrBufPlain(vbuf.v_answered, -1);
- break;
- default:
- vset = NewStrBuf();
- }
-
-
-#if 0 // This is a special diagnostic section. Do not allow it to run during normal operation.
- syslog(LOG_DEBUG, "There are %d messages in the room.\n", num_msgs);
- for (i=0; i<num_msgs; ++i) {
- if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
- }
- syslog(LOG_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
- for (k=0; k<num_target_msgnums; ++k) {
- if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
- }
-#endif
-
- // Translate the existing sequence set into an array of booleans
- setstr = NewStrBuf();
- lostr = NewStrBuf();
- histr = NewStrBuf();
- pvset = NULL;
- while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
-
- StrBufExtract_token(lostr, setstr, 0, ':');
- if (StrBufNum_tokens(setstr, ':') >= 2) {
- StrBufExtract_token(histr, setstr, 1, ':');
- }
- else {
- FlushStrBuf(histr);
- StrBufAppendBuf(histr, lostr, 0);
- }
- lo = StrTol(lostr);
- if (!strcmp(ChrPtr(histr), "*")) {
- hi = LONG_MAX;
- }
- else {
- hi = StrTol(histr);
- }
-
- for (i = 0; i < num_msgs; ++i) {
- if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
- is_set[i] = 1;
- }
- }
- }
- FreeStrBuf(&setstr);
- FreeStrBuf(&lostr);
- FreeStrBuf(&histr);
-
- // Now translate the array of booleans back into a sequence set
- FlushStrBuf(vset);
- was_seen = 0;
- lo = (-1);
- hi = (-1);
-
- for (i=0; i<num_msgs; ++i) {
- is_seen = is_set[i];
-
- // Apply changes
- for (k=0; k<num_target_msgnums; ++k) {
- if (msglist[i] == target_msgnums[k]) {
- is_seen = target_setting;
- }
- }
-
- if ((was_seen == 0) && (is_seen == 1)) {
- lo = msglist[i];
- }
- else if ((was_seen == 1) && (is_seen == 0)) {
- hi = msglist[i-1];
-
- if (StrLength(vset) > 0) {
- StrBufAppendBufPlain(vset, HKEY(","), 0);
- }
- if (lo == hi) {
- StrBufAppendPrintf(vset, "%ld", hi);
- }
- else {
- StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
- }
- }
-
- if ((is_seen) && (i == num_msgs - 1)) {
- if (StrLength(vset) > 0) {
- StrBufAppendBufPlain(vset, HKEY(","), 0);
- }
- if ((i==0) || (was_seen == 0)) {
- StrBufAppendPrintf(vset, "%ld", msglist[i]);
- }
- else {
- StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
- }
- }
-
- was_seen = is_seen;
- }
-
- // We will have to stuff this string back into a 4096 byte buffer, so if it's
- // larger than that now, truncate it by removing tokens from the beginning.
- // The limit of 100 iterations is there to prevent an infinite loop in case
- // something unexpected happens.
- int number_of_truncations = 0;
- while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
- StrBufRemove_token(vset, 0, ',');
- ++number_of_truncations;
- }
-
- // If we're truncating the sequence set of messages marked with the 'seen' flag,
- // we want the earliest messages (the truncated ones) to be marked, not unmarked.
- // Otherwise messages at the beginning will suddenly appear to be 'unseen'.
- if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
- StrBuf *first_tok;
- first_tok = NewStrBuf();
- StrBufExtract_token(first_tok, vset, 0, ',');
- StrBufRemove_token(vset, 0, ',');
-
- if (StrBufNum_tokens(first_tok, ':') > 1) {
- StrBufRemove_token(first_tok, 0, ':');
- }
-
- StrBuf *new_set;
- new_set = NewStrBuf();
- StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
- StrBufAppendBuf(new_set, first_tok, 0);
- StrBufAppendBufPlain(new_set, HKEY(":"), 0);
- StrBufAppendBuf(new_set, vset, 0);
-
- FreeStrBuf(&vset);
- FreeStrBuf(&first_tok);
- vset = new_set;
- }
-
- // Decide which message set we're manipulating
- switch (which_set) {
- case ctdlsetseen_seen:
- safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
- break;
- case ctdlsetseen_answered:
- safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
- break;
- }
-
- free(is_set);
- free(msglist);
- CtdlSetRelationship(&vbuf, which_user, which_room);
- FreeStrBuf(&vset);
-}
-
-
-// API function to perform an operation for each qualifying message in the
-// current room. (Returns the number of messages processed.)
-int CtdlForEachMessage(int mode, long ref, char *search_string,
- char *content_type,
- struct CtdlMessage *compare,
- ForEachMsgCallback CallBack,
- void *userdata)
-{
- int a, i, j;
- visit vbuf;
- struct cdbdata *cdbfr;
- long *msglist = NULL;
- int num_msgs = 0;
- int num_processed = 0;
- long thismsg;
- struct MetaData smi;
- struct CtdlMessage *msg = NULL;
- int is_seen = 0;
- long lastold = 0L;
- int printed_lastold = 0;
- int num_search_msgs = 0;
- long *search_msgs = NULL;
- regex_t re;
- int need_to_free_re = 0;
- regmatch_t pm;
-
- if ((content_type) && (!IsEmptyStr(content_type))) {
- regcomp(&re, content_type, 0);
- need_to_free_re = 1;
- }
-
- // Learn about the user and room in question
- if (server_shutting_down) {
- if (need_to_free_re) regfree(&re);
- return -1;
- }
- CtdlGetUser(&CC->user, CC->curr_user);
-
- if (server_shutting_down) {
- if (need_to_free_re) regfree(&re);
- return -1;
- }
- CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
-
- if (server_shutting_down) {
- if (need_to_free_re) regfree(&re);
- return -1;
- }
-
- // Load the message list
- cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
- if (cdbfr == NULL) {
- if (need_to_free_re) regfree(&re);
- return 0; // No messages at all? No further action.
- }
-
- msglist = (long *) cdbfr->ptr;
- num_msgs = cdbfr->len / sizeof(long);
-
- cdbfr->ptr = NULL; // clear this so that cdb_free() doesn't free it
- cdb_free(cdbfr); // we own this memory now
-
- /*
- * Now begin the traversal.
- */
- if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
-
- /* If the caller is looking for a specific MIME type, filter
- * out all messages which are not of the type requested.
- */
- if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
-
- /* This call to GetMetaData() sits inside this loop
- * so that we only do the extra database read per msg
- * if we need to. Doing the extra read all the time
- * really kills the server. If we ever need to use
- * metadata for another search criterion, we need to
- * move the read somewhere else -- but still be smart
- * enough to only do the read if the caller has
- * specified something that will need it.
- */
- if (server_shutting_down) {
- if (need_to_free_re) regfree(&re);
- free(msglist);
- return -1;
- }
- GetMetaData(&smi, msglist[a]);
-
- /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
- if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
- msglist[a] = 0L;
- }
- }
- }
-
- num_msgs = sort_msglist(msglist, num_msgs);
-
- /* If a template was supplied, filter out the messages which
- * don't match. (This could induce some delays!)
- */
- if (num_msgs > 0) {
- if (compare != NULL) {
- for (a = 0; a < num_msgs; ++a) {
- if (server_shutting_down) {
- if (need_to_free_re) regfree(&re);
- free(msglist);
- return -1;
- }
- msg = CtdlFetchMessage(msglist[a], 1);
- if (msg != NULL) {
- if (CtdlMsgCmp(msg, compare)) {
- msglist[a] = 0L;
- }
- CM_Free(msg);
- }
- }
- }
- }
-
- /* If a search string was specified, get a message list from
- * the full text index and remove messages which aren't on both
- * lists.
- *
- * How this works:
- * Since the lists are sorted and strictly ascending, and the
- * output list is guaranteed to be shorter than or equal to the
- * input list, we overwrite the bottom of the input list. This
- * eliminates the need to memmove big chunks of the list over and
- * over again.
- */
- if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
-
- /* Call search module via hook mechanism.
- * NULL means use any search function available.
- * otherwise replace with a char * to name of search routine
- */
- CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
-
- if (num_search_msgs > 0) {
-
- int orig_num_msgs;
-
- orig_num_msgs = num_msgs;
- num_msgs = 0;
- for (i=0; i<orig_num_msgs; ++i) {
- for (j=0; j<num_search_msgs; ++j) {
- if (msglist[i] == search_msgs[j]) {
- msglist[num_msgs++] = msglist[i];
- }
- }
- }
- }
- else {
- num_msgs = 0; /* No messages qualify */
- }
- if (search_msgs != NULL) free(search_msgs);
-
- /* Now that we've purged messages which don't contain the search
- * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
- * point on.
- */
- mode = MSGS_ALL;
- }
-
- /*
- * Now iterate through the message list, according to the
- * criteria supplied by the caller.
- */
- if (num_msgs > 0)
- for (a = 0; a < num_msgs; ++a) {
- if (server_shutting_down) {
- if (need_to_free_re) regfree(&re);
- free(msglist);
- return num_processed;
- }
- thismsg = msglist[a];
- if (mode == MSGS_ALL) {
- is_seen = 0;
- }
- else {
- is_seen = is_msg_in_sequence_set(vbuf.v_seen, thismsg);
- if (is_seen) lastold = thismsg;
- }
- if (
- (thismsg > 0L)
- && (
- (mode == MSGS_ALL)
- || ((mode == MSGS_OLD) && (is_seen))
- || ((mode == MSGS_NEW) && (!is_seen))
- || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
- || ((mode == MSGS_FIRST) && (a < ref))
- || ((mode == MSGS_GT) && (thismsg > ref))
- || ((mode == MSGS_LT) && (thismsg < ref))
- || ((mode == MSGS_EQ) && (thismsg == ref))
- )
- ) {
- if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
- if (CallBack) {
- CallBack(lastold, userdata);
- }
- printed_lastold = 1;
- ++num_processed;
- }
- if (CallBack) {
- CallBack(thismsg, userdata);
- }
- ++num_processed;
- }
- }
- if (need_to_free_re) regfree(&re);
-
- /*
- * We cache the most recent msglist in order to do security checks later
- */
- if (CC->client_socket > 0) {
- if (CC->cached_msglist != NULL) {
- free(CC->cached_msglist);
- }
- CC->cached_msglist = msglist;
- CC->cached_num_msgs = num_msgs;
- }
- else {
- free(msglist);
- }
-
- return num_processed;
-}
-
-
-/*
- * memfmout() - Citadel text formatter and paginator.
- * Although the original purpose of this routine was to format
- * text to the reader's screen width, all we're really using it
- * for here is to format text out to 80 columns before sending it
- * to the client. The client software may reformat it again.
- */
-void memfmout(
- char *mptr, /* where are we going to get our text from? */
- const char *nl /* string to terminate lines with */
-) {
- int column = 0;
- unsigned char ch = 0;
- char outbuf[1024];
- int len = 0;
- int nllen = 0;
-
- if (!mptr) return;
- nllen = strlen(nl);
- while (ch=*(mptr++), ch != 0) {
-
- if (ch == '\n') {
- if (client_write(outbuf, len) == -1) {
- syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
- return;
- }
- len = 0;
- if (client_write(nl, nllen) == -1) {
- syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
- return;
- }
- column = 0;
- }
- else if (ch == '\r') {
- /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
- }
- else if (isspace(ch)) {
- if (column > 72) { /* Beyond 72 columns, break on the next space */
- if (client_write(outbuf, len) == -1) {
- syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
- return;
- }
- len = 0;
- if (client_write(nl, nllen) == -1) {
- syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
- return;
- }
- column = 0;
- }
- else {
- outbuf[len++] = ch;
- ++column;
- }
- }
- else {
- outbuf[len++] = ch;
- ++column;
- if (column > 1000) { /* Beyond 1000 columns, break anywhere */
- if (client_write(outbuf, len) == -1) {
- syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
- return;
- }
- len = 0;
- if (client_write(nl, nllen) == -1) {
- syslog(LOG_ERR, "msgbase: memfmout(): aborting due to write failure");
- return;
- }
- column = 0;
- }
- }
- }
- if (len) {
- if (client_write(outbuf, len) == -1) {
- syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
- return;
- }
- client_write(nl, nllen);
- column = 0;
- }
-}
-
-
-/*
- * Callback function for mime parser that simply lists the part
- */
-void list_this_part(char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
- char *cbid, void *cbuserdata)
-{
- struct ma_info *ma;
-
- ma = (struct ma_info *)cbuserdata;
- if (ma->is_ma == 0) {
- cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
- name,
- filename,
- partnum,
- disp,
- cbtype,
- (long)length,
- cbid,
- cbcharset);
- }
-}
-
-
-/*
- * Callback function for multipart prefix
- */
-void list_this_pref(char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
- char *cbid, void *cbuserdata)
-{
- struct ma_info *ma;
-
- ma = (struct ma_info *)cbuserdata;
- if (!strcasecmp(cbtype, "multipart/alternative")) {
- ++ma->is_ma;
- }
-
- if (ma->is_ma == 0) {
- cprintf("pref=%s|%s\n", partnum, cbtype);
- }
-}
-
-
-/*
- * Callback function for multipart sufffix
- */
-void list_this_suff(char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
- char *cbid, void *cbuserdata)
-{
- struct ma_info *ma;
-
- ma = (struct ma_info *)cbuserdata;
- if (ma->is_ma == 0) {
- cprintf("suff=%s|%s\n", partnum, cbtype);
- }
- if (!strcasecmp(cbtype, "multipart/alternative")) {
- --ma->is_ma;
- }
-}
-
-
-/*
- * Callback function for mime parser that opens a section for downloading
- * we use serv_files function here:
- */
-extern void OpenCmdResult(char *filename, const char *mime_type);
-void mime_download(char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length,
- char *encoding, char *cbid, void *cbuserdata)
-{
- int rv = 0;
-
- /* Silently go away if there's already a download open. */
- if (CC->download_fp != NULL)
- return;
-
- if (
- (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
- || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
- ) {
- CC->download_fp = tmpfile();
- if (CC->download_fp == NULL) {
- syslog(LOG_ERR, "msgbase: mime_download() couldn't write: %m");
- cprintf("%d cannot open temporary file: %s\n", ERROR + INTERNAL_ERROR, strerror(errno));
- return;
- }
-
- rv = fwrite(content, length, 1, CC->download_fp);
- if (rv <= 0) {
- syslog(LOG_ERR, "msgbase: mime_download() Couldn't write: %m");
- cprintf("%d unable to write tempfile.\n", ERROR + TOO_BIG);
- fclose(CC->download_fp);
- CC->download_fp = NULL;
- return;
- }
- fflush(CC->download_fp);
- rewind(CC->download_fp);
-
- OpenCmdResult(filename, cbtype);
- }
-}
-
-
-/*
- * Callback function for mime parser that outputs a section all at once.
- * We can specify the desired section by part number *or* content-id.
- */
-void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length,
- char *encoding, char *cbid, void *cbuserdata)
-{
- int *found_it = (int *)cbuserdata;
-
- if (
- (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
- || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
- ) {
- *found_it = 1;
- cprintf("%d %d|-1|%s|%s|%s\n",
- BINARY_FOLLOWS,
- (int)length,
- filename,
- cbtype,
- cbcharset
- );
- client_write(content, length);
- }
-}
-
-
-struct CtdlMessage *CtdlDeserializeMessage(long msgnum, int with_body, const char *Buffer, long Length) {
- struct CtdlMessage *ret = NULL;
- const char *mptr;
- const char *upper_bound;
- cit_uint8_t ch;
- cit_uint8_t field_header;
- eMsgField which;
-
- mptr = Buffer;
- upper_bound = Buffer + Length;
- if (msgnum <= 0) {
- return NULL;
- }
-
- // Parse the three bytes that begin EVERY message on disk.
- // The first is always 0xFF, the on-disk magic number.
- // The second is the anonymous/public type byte.
- // The third is the format type byte (vari, fixed, or MIME).
- //
- ch = *mptr++;
- if (ch != 255) {
- syslog(LOG_ERR, "msgbase: message %ld appears to be corrupted", msgnum);
- return NULL;
- }
- ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
- memset(ret, 0, sizeof(struct CtdlMessage));
-
- ret->cm_magic = CTDLMESSAGE_MAGIC;
- ret->cm_anon_type = *mptr++; // Anon type byte
- ret->cm_format_type = *mptr++; // Format type byte
-
- // The rest is zero or more arbitrary fields. Load them in.
- // We're done when we encounter either a zero-length field or
- // have just processed the 'M' (message text) field.
- //
- do {
- field_header = '\0';
- long len;
-
- while (field_header == '\0') { // work around possibly buggy messages
- if (mptr >= upper_bound) {
- break;
- }
- field_header = *mptr++;
- }
- if (mptr >= upper_bound) {
- break;
- }
- which = field_header;
- len = strlen(mptr);
-
- CM_SetField(ret, which, mptr, len);
-
- mptr += len + 1; // advance to next field
-
- } while ((mptr < upper_bound) && (field_header != 'M'));
- return (ret);
-}
-
-
-// Load a message from disk into memory.
-// This is used by CtdlOutputMsg() and other fetch functions.
-//
-// NOTE: Caller is responsible for freeing the returned CtdlMessage struct
-// using the CM_Free(); function.
-//
-struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body) {
- struct cdbdata *dmsgtext;
- struct CtdlMessage *ret = NULL;
-
- syslog(LOG_DEBUG, "msgbase: CtdlFetchMessage(%ld, %d)", msgnum, with_body);
- dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
- if (dmsgtext == NULL) {
- syslog(LOG_ERR, "msgbase: message #%ld was not found", msgnum);
- return NULL;
- }
-
- if (dmsgtext->ptr[dmsgtext->len - 1] != '\0') {
- syslog(LOG_ERR, "msgbase: CtdlFetchMessage(%ld, %d) Forcefully terminating message!!", msgnum, with_body);
- dmsgtext->ptr[dmsgtext->len - 1] = '\0';
- }
-
- ret = CtdlDeserializeMessage(msgnum, with_body, dmsgtext->ptr, dmsgtext->len);
-
- cdb_free(dmsgtext);
-
- if (ret == NULL) {
- return NULL;
- }
-
- // Always make sure there's something in the msg text field. If
- // it's NULL, the message text is most likely stored separately,
- // so go ahead and fetch that. Failing that, just set a dummy
- // body so other code doesn't barf.
- //
- if ( (CM_IsEmpty(ret, eMesageText)) && (with_body) ) {
- dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
- if (dmsgtext != NULL) {
- CM_SetAsField(ret, eMesageText, &dmsgtext->ptr, dmsgtext->len - 1);
- cdb_free(dmsgtext);
- }
- }
- if (CM_IsEmpty(ret, eMesageText)) {
- CM_SetField(ret, eMesageText, HKEY("\r\n\r\n (no text)\r\n"));
- }
-
- return (ret);
-}
-
-
-// Pre callback function for multipart/alternative
-//
-// NOTE: this differs from the standard behavior for a reason. Normally when
-// displaying multipart/alternative you want to show the _last_ usable
-// format in the message. Here we show the _first_ one, because it's
-// usually text/plain. Since this set of functions is designed for text
-// output to non-MIME-aware clients, this is the desired behavior.
-//
-void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
- char *cbid, void *cbuserdata)
-{
- struct ma_info *ma;
-
- ma = (struct ma_info *)cbuserdata;
- syslog(LOG_DEBUG, "msgbase: fixed_output_pre() type=<%s>", cbtype);
- if (!strcasecmp(cbtype, "multipart/alternative")) {
- ++ma->is_ma;
- ma->did_print = 0;
- }
- if (!strcasecmp(cbtype, "message/rfc822")) {
- ++ma->freeze;
- }
-}
-
-
-//
-// Post callback function for multipart/alternative
-//
-void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length,
- char *encoding, char *cbid, void *cbuserdata)
-{
- struct ma_info *ma;
-
- ma = (struct ma_info *)cbuserdata;
- syslog(LOG_DEBUG, "msgbase: fixed_output_post() type=<%s>", cbtype);
- if (!strcasecmp(cbtype, "multipart/alternative")) {
- --ma->is_ma;
- ma->did_print = 0;
- }
- if (!strcasecmp(cbtype, "message/rfc822")) {
- --ma->freeze;
- }
-}
-
-
-// Inline callback function for mime parser that wants to display text
-void fixed_output(char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length,
- char *encoding, char *cbid, void *cbuserdata)
-{
- char *ptr;
- char *wptr;
- size_t wlen;
- struct ma_info *ma;
-
- ma = (struct ma_info *)cbuserdata;
-
- syslog(LOG_DEBUG,
- "msgbase: fixed_output() part %s: %s (%s) (%ld bytes)",
- partnum, filename, cbtype, (long)length
- );
-
- // If we're in the middle of a multipart/alternative scope and
- // we've already printed another section, skip this one.
- if ( (ma->is_ma) && (ma->did_print) ) {
- syslog(LOG_DEBUG, "msgbase: skipping part %s (%s)", partnum, cbtype);
- return;
- }
- ma->did_print = 1;
-
- if ( (!strcasecmp(cbtype, "text/plain"))
- || (IsEmptyStr(cbtype)) ) {
- wptr = content;
- if (length > 0) {
- client_write(wptr, length);
- if (wptr[length-1] != '\n') {
- cprintf("\n");
- }
- }
- return;
- }
-
- if (!strcasecmp(cbtype, "text/html")) {
- ptr = html_to_ascii(content, length, 80);
- wlen = strlen(ptr);
- client_write(ptr, wlen);
- if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
- cprintf("\n");
- }
- free(ptr);
- return;
- }
-
- if (ma->use_fo_hooks) {
- if (PerformFixedOutputHooks(cbtype, content, length)) { // returns nonzero if it handled the part
- return;
- }
- }
-
- if (strncasecmp(cbtype, "multipart/", 10)) {
- cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
- partnum, filename, cbtype, (long)length);
- return;
- }
-}
-
-
-// The client is elegant and sophisticated and wants to be choosy about
-// MIME content types, so figure out which multipart/alternative part
-// we're going to send.
-//
-// We use a system of weights. When we find a part that matches one of the
-// MIME types we've declared as preferential, we can store it in ma->chosen_part
-// and then set ma->chosen_pref to that MIME type's position in our preference
-// list. If we then hit another match, we only replace the first match if
-// the preference value is lower.
-void choose_preferred(char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length,
- char *encoding, char *cbid, void *cbuserdata)
-{
- char buf[1024];
- int i;
- struct ma_info *ma;
-
- ma = (struct ma_info *)cbuserdata;
-
- for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
- extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
- if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
- if (i < ma->chosen_pref) {
- syslog(LOG_DEBUG, "msgbase: setting chosen part to <%s>", partnum);
- safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
- ma->chosen_pref = i;
- }
- }
- }
-}
-
-
-// Now that we've chosen our preferred part, output it.
-void output_preferred(char *name,
- char *filename,
- char *partnum,
- char *disp,
- void *content,
- char *cbtype,
- char *cbcharset,
- size_t length,
- char *encoding,
- char *cbid,
- void *cbuserdata)
-{
- int i;
- char buf[128];
- int add_newline = 0;
- char *text_content;
- struct ma_info *ma;
- char *decoded = NULL;
- size_t bytes_decoded;
- int rc = 0;
-
- ma = (struct ma_info *)cbuserdata;
-
- // This is not the MIME part you're looking for...
- if (strcasecmp(partnum, ma->chosen_part)) return;
-
- // If the content-type of this part is in our preferred formats
- // list, we can simply output it verbatim.
- for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
- extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
- if (!strcasecmp(buf, cbtype)) {
- /* Yeah! Go! W00t!! */
- if (ma->dont_decode == 0)
- rc = mime_decode_now (content,
- length,
- encoding,
- &decoded,
- &bytes_decoded);
- if (rc < 0)
- break; // Give us the chance, maybe theres another one.
-
- if (rc == 0) text_content = (char *)content;
- else {
- text_content = decoded;
- length = bytes_decoded;
- }
-
- if (text_content[length-1] != '\n') {
- ++add_newline;
- }
- cprintf("Content-type: %s", cbtype);
- if (!IsEmptyStr(cbcharset)) {
- cprintf("; charset=%s", cbcharset);
- }
- cprintf("\nContent-length: %d\n",
- (int)(length + add_newline) );
- if (!IsEmptyStr(encoding)) {
- cprintf("Content-transfer-encoding: %s\n", encoding);
- }
- else {
- cprintf("Content-transfer-encoding: 7bit\n");
- }
- cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
- cprintf("\n");
- if (client_write(text_content, length) == -1)
- {
- syslog(LOG_ERR, "msgbase: output_preferred() aborting due to write failure");
- return;
- }
- if (add_newline) cprintf("\n");
- if (decoded != NULL) free(decoded);
- return;
- }
- }
-
- // No translations required or possible: output as text/plain
- cprintf("Content-type: text/plain\n\n");
- rc = 0;
- if (ma->dont_decode == 0)
- rc = mime_decode_now (content,
- length,
- encoding,
- &decoded,
- &bytes_decoded);
- if (rc < 0)
- return; // Give us the chance, maybe theres another one.
-
- if (rc == 0) text_content = (char *)content;
- else {
- text_content = decoded;
- length = bytes_decoded;
- }
-
- fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset, length, encoding, cbid, cbuserdata);
- if (decoded != NULL) free(decoded);
-}
-
-
-struct encapmsg {
- char desired_section[64];
- char *msg;
- size_t msglen;
-};
-
-
-// Callback function
-void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
- void *content, char *cbtype, char *cbcharset, size_t length,
- char *encoding, char *cbid, void *cbuserdata)
-{
- struct encapmsg *encap;
-
- encap = (struct encapmsg *)cbuserdata;
-
- // Only proceed if this is the desired section...
- if (!strcasecmp(encap->desired_section, partnum)) {
- encap->msglen = length;
- encap->msg = malloc(length + 2);
- memcpy(encap->msg, content, length);
- return;
- }
-}
-
-
-// Determine whether the specified message exists in the cached_msglist
-// (This is a security check)
-int check_cached_msglist(long msgnum) {
-
- // cases in which we skip the check
- if (!CC) return om_ok; // not a session
- if (CC->client_socket <= 0) return om_ok; // not a client session
- if (CC->cached_msglist == NULL) return om_access_denied; // no msglist fetched
- if (CC->cached_num_msgs == 0) return om_access_denied; // nothing to check
-
- // Do a binary search within the cached_msglist for the requested msgnum
- int min = 0;
- int max = (CC->cached_num_msgs - 1);
-
- while (max >= min) {
- int middle = min + (max-min) / 2 ;
- if (msgnum == CC->cached_msglist[middle]) {
- return om_ok;
- }
- if (msgnum > CC->cached_msglist[middle]) {
- min = middle + 1;
- }
- else {
- max = middle - 1;
- }
- }
-
- return om_access_denied;
-}
-
-
-// Get a message off disk. (returns om_* values found in msgbase.h)
-int CtdlOutputMsg(long msg_num, // message number (local) to fetch
- int mode, // how would you like that message?
- int headers_only, // eschew the message body?
- int do_proto, // do Citadel protocol responses?
- int crlf, // Use CRLF newlines instead of LF?
- char *section, // NULL or a message/rfc822 section
- int flags, // various flags; see msgbase.h
- char **Author,
- char **Address,
- char **MessageID
-) {
- struct CtdlMessage *TheMessage = NULL;
- int retcode = CIT_OK;
- struct encapmsg encap;
- int r;
-
- syslog(LOG_DEBUG, "msgbase: CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)",
- msg_num, mode,
- (section ? section : "<>")
- );
-
- r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
- if (r != om_ok) {
- if (do_proto) {
- if (r == om_not_logged_in) {
- cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
- }
- else {
- cprintf("%d An unknown error has occurred.\n", ERROR);
- }
- }
- return(r);
- }
-
- /*
- * Check to make sure the message is actually IN this room
- */
- r = check_cached_msglist(msg_num);
- if (r == om_access_denied) {
- /* Not in the cache? We get ONE shot to check it again. */
- CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
- r = check_cached_msglist(msg_num);
- }
- if (r != om_ok) {
- syslog(LOG_DEBUG, "msgbase: security check fail; message %ld is not in %s",
- msg_num, CC->room.QRname
- );
- if (do_proto) {
- if (r == om_access_denied) {
- cprintf("%d message %ld was not found in this room\n",
- ERROR + HIGHER_ACCESS_REQUIRED,
- msg_num
- );
- }
- }
- return(r);
- }
-
- /*
- * Fetch the message from disk. If we're in HEADERS_FAST mode,
- * request that we don't even bother loading the body into memory.
- */
- if (headers_only == HEADERS_FAST) {
- TheMessage = CtdlFetchMessage(msg_num, 0);
- }
- else {
- TheMessage = CtdlFetchMessage(msg_num, 1);
- }
-
- if (TheMessage == NULL) {
- if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
- ERROR + MESSAGE_NOT_FOUND, msg_num);
- return(om_no_such_msg);
- }
-
- /* Here is the weird form of this command, to process only an
- * encapsulated message/rfc822 section.
- */
- if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
- memset(&encap, 0, sizeof encap);
- safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
- mime_parser(CM_RANGE(TheMessage, eMesageText),
- *extract_encapsulated_message,
- NULL, NULL, (void *)&encap, 0
- );
-
- if ((Author != NULL) && (*Author == NULL))
- {
- long len;
- CM_GetAsField(TheMessage, eAuthor, Author, &len);
- }
- if ((Address != NULL) && (*Address == NULL))
- {
- long len;
- CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
- }
- if ((MessageID != NULL) && (*MessageID == NULL))
- {
- long len;
- CM_GetAsField(TheMessage, emessageId, MessageID, &len);
- }
- CM_Free(TheMessage);
- TheMessage = NULL;
-
- if (encap.msg) {
- encap.msg[encap.msglen] = 0;
- TheMessage = convert_internet_message(encap.msg);
- encap.msg = NULL; /* no free() here, TheMessage owns it now */
-
- /* Now we let it fall through to the bottom of this
- * function, because TheMessage now contains the
- * encapsulated message instead of the top-level
- * message. Isn't that neat?
- */
- }
- else {
- if (do_proto) {
- cprintf("%d msg %ld has no part %s\n",
- ERROR + MESSAGE_NOT_FOUND,
- msg_num,
- section);
- }
- retcode = om_no_such_msg;
- }
-
- }
-
- /* Ok, output the message now */
- if (retcode == CIT_OK)
- retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
- if ((Author != NULL) && (*Author == NULL))
- {
- long len;
- CM_GetAsField(TheMessage, eAuthor, Author, &len);
- }
- if ((Address != NULL) && (*Address == NULL))
- {
- long len;
- CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
- }
- if ((MessageID != NULL) && (*MessageID == NULL))
- {
- long len;
- CM_GetAsField(TheMessage, emessageId, MessageID, &len);
- }
-
- CM_Free(TheMessage);
-
- return(retcode);
-}
-
-
-void OutputCtdlMsgHeaders(struct CtdlMessage *TheMessage, int do_proto) {
- int i;
- char buf[SIZ];
- char display_name[256];
-
- /* begin header processing loop for Citadel message format */
- safestrncpy(display_name, "<unknown>", sizeof display_name);
- if (!CM_IsEmpty(TheMessage, eAuthor)) {
- strcpy(buf, TheMessage->cm_fields[eAuthor]);
- if (TheMessage->cm_anon_type == MES_ANONONLY) {
- safestrncpy(display_name, "****", sizeof display_name);
- }
- else if (TheMessage->cm_anon_type == MES_ANONOPT) {
- safestrncpy(display_name, "anonymous", sizeof display_name);
- }
- else {
- safestrncpy(display_name, buf, sizeof display_name);
- }
- if ((is_room_aide())
- && ((TheMessage->cm_anon_type == MES_ANONONLY)
- || (TheMessage->cm_anon_type == MES_ANONOPT))) {
- size_t tmp = strlen(display_name);
- snprintf(&display_name[tmp],
- sizeof display_name - tmp,
- " [%s]", buf);
- }
- }
-
- /* Now spew the header fields in the order we like them. */
- for (i=0; i< NDiskFields; ++i) {
- eMsgField Field;
- Field = FieldOrder[i];
- if (Field != eMesageText) {
- if ( (!CM_IsEmpty(TheMessage, Field)) && (msgkeys[Field] != NULL) ) {
- if ((Field == eenVelopeTo) || (Field == eRecipient) || (Field == eCarbonCopY)) {
- sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
- }
- if (Field == eAuthor) {
- if (do_proto) {
- cprintf("%s=%s\n", msgkeys[Field], display_name);
- }
- }
- /* Masquerade display name if needed */
- else {
- if (do_proto) {
- cprintf("%s=%s\n", msgkeys[Field], TheMessage->cm_fields[Field]);
- }
- }
- /* Give the client a hint about whether the message originated locally */
- if (Field == erFc822Addr) {
- if (IsDirectory(TheMessage->cm_fields[Field] ,0)) {
- cprintf("locl=yes\n"); // message originated locally.
- }
-
-
-
- }
- }
- }
- }
-}
-
-
-void OutputRFC822MsgHeaders(
- struct CtdlMessage *TheMessage,
- int flags, /* should the message be exported clean */
- const char *nl, int nlen,
- char *mid, long sizeof_mid,
- char *suser, long sizeof_suser,
- char *luser, long sizeof_luser,
- char *fuser, long sizeof_fuser,
- char *snode, long sizeof_snode)
-{
- char datestamp[100];
- int subject_found = 0;
- char buf[SIZ];
- int i, j, k;
- char *mptr = NULL;
- char *mpptr = NULL;
- char *hptr;
-
- for (i = 0; i < NDiskFields; ++i) {
- if (TheMessage->cm_fields[FieldOrder[i]]) {
- mptr = mpptr = TheMessage->cm_fields[FieldOrder[i]];
- switch (FieldOrder[i]) {
- case eAuthor:
- safestrncpy(luser, mptr, sizeof_luser);
- safestrncpy(suser, mptr, sizeof_suser);
- break;
- case eCarbonCopY:
- if ((flags & QP_EADDR) != 0) {
- mptr = qp_encode_email_addrs(mptr);
- }
- sanitize_truncated_recipient(mptr);
- cprintf("CC: %s%s", mptr, nl);
- break;
- case eMessagePath:
- cprintf("Return-Path: %s%s", mptr, nl);
- break;
- case eListID:
- cprintf("List-ID: %s%s", mptr, nl);
- break;
- case eenVelopeTo:
- if ((flags & QP_EADDR) != 0)
- mptr = qp_encode_email_addrs(mptr);
- hptr = mptr;
- while ((*hptr != '\0') && isspace(*hptr))
- hptr ++;
- if (!IsEmptyStr(hptr))
- cprintf("Envelope-To: %s%s", hptr, nl);
- break;
- case eMsgSubject:
- cprintf("Subject: %s%s", mptr, nl);
- subject_found = 1;
- break;
- case emessageId:
- safestrncpy(mid, mptr, sizeof_mid);
- break;
- case erFc822Addr:
- safestrncpy(fuser, mptr, sizeof_fuser);
- break;
- case eRecipient:
- if (haschar(mptr, '@') == 0) {
- sanitize_truncated_recipient(mptr);
- cprintf("To: %s@%s", mptr, CtdlGetConfigStr("c_fqdn"));
- cprintf("%s", nl);
- }
- else {
- if ((flags & QP_EADDR) != 0) {
- mptr = qp_encode_email_addrs(mptr);
- }
- sanitize_truncated_recipient(mptr);
- cprintf("To: %s", mptr);
- cprintf("%s", nl);
- }
- break;
- case eTimestamp:
- datestring(datestamp, sizeof datestamp, atol(mptr), DATESTRING_RFC822);
- cprintf("Date: %s%s", datestamp, nl);
- break;
- case eWeferences:
- cprintf("References: ");
- k = num_tokens(mptr, '|');
- for (j=0; j<k; ++j) {
- extract_token(buf, mptr, j, '|', sizeof buf);
- cprintf("<%s>", buf);
- if (j == (k-1)) {
- cprintf("%s", nl);
- }
- else {
- cprintf(" ");
- }
- }
- break;
- case eReplyTo:
- hptr = mptr;
- while ((*hptr != '\0') && isspace(*hptr))
- hptr ++;
- if (!IsEmptyStr(hptr))
- cprintf("Reply-To: %s%s", mptr, nl);
- break;
-
- case eExclusiveID:
- case eJournal:
- case eMesageText:
- case eBig_message:
- case eOriginalRoom:
- case eErrorMsg:
- case eSuppressIdx:
- case eExtnotify:
- case eVltMsgNum:
- /* these don't map to mime message headers. */
- break;
- }
- if (mptr != mpptr) {
- free (mptr);
- }
- }
- }
- if (subject_found == 0) {
- cprintf("Subject: (no subject)%s", nl);
- }
-}
-
-
-void Dump_RFC822HeadersBody(
- struct CtdlMessage *TheMessage,
- int headers_only, /* eschew the message body? */
- int flags, /* should the bessage be exported clean? */
- const char *nl, int nlen)
-{
- cit_uint8_t prev_ch;
- int eoh = 0;
- const char *StartOfText = StrBufNOTNULL;
- char outbuf[1024];
- int outlen = 0;
- int nllen = strlen(nl);
- char *mptr;
- int lfSent = 0;
-
- mptr = TheMessage->cm_fields[eMesageText];
-
- prev_ch = '\0';
- while (*mptr != '\0') {
- if (*mptr == '\r') {
- /* do nothing */
- }
- else {
- if ((!eoh) &&
- (*mptr == '\n'))
- {
- eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
- if (!eoh)
- eoh = *(mptr+1) == '\n';
- if (eoh)
- {
- StartOfText = mptr;
- StartOfText = strchr(StartOfText, '\n');
- StartOfText = strchr(StartOfText, '\n');
- }
- }
- if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
- ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
- ((headers_only != HEADERS_NONE) &&
- (headers_only != HEADERS_ONLY))
- ) {
- if (*mptr == '\n') {
- memcpy(&outbuf[outlen], nl, nllen);
- outlen += nllen;
- outbuf[outlen] = '\0';
- }
- else {
- outbuf[outlen++] = *mptr;
- }
- }
- }
- if (flags & ESC_DOT) {
- if ((prev_ch == '\n') && (*mptr == '.') && ((*(mptr+1) == '\r') || (*(mptr+1) == '\n'))) {
- outbuf[outlen++] = '.';
- }
- prev_ch = *mptr;
- }
- ++mptr;
- if (outlen > 1000) {
- if (client_write(outbuf, outlen) == -1) {
- syslog(LOG_ERR, "msgbase: Dump_RFC822HeadersBody() aborting due to write failure");
- return;
- }
- lfSent = (outbuf[outlen - 1] == '\n');
- outlen = 0;
- }
- }
- if (outlen > 0) {
- client_write(outbuf, outlen);
- lfSent = (outbuf[outlen - 1] == '\n');
- }
- if (!lfSent)
- client_write(nl, nlen);
-}
-
-
-/* If the format type on disk is 1 (fixed-format), then we want
- * everything to be output completely literally ... regardless of
- * what message transfer format is in use.
- */
-void DumpFormatFixed(
- struct CtdlMessage *TheMessage,
- int mode, /* how would you like that message? */
- const char *nl, int nllen)
-{
- cit_uint8_t ch;
- char buf[SIZ];
- int buflen;
- int xlline = 0;
- char *mptr;
-
- mptr = TheMessage->cm_fields[eMesageText];
-
- if (mode == MT_MIME) {
- cprintf("Content-type: text/plain\n\n");
- }
- *buf = '\0';
- buflen = 0;
- while (ch = *mptr++, ch > 0) {
- if (ch == '\n')
- ch = '\r';
-
- if ((buflen > 250) && (!xlline)){
- int tbuflen;
- tbuflen = buflen;
-
- while ((buflen > 0) &&
- (!isspace(buf[buflen])))
- buflen --;
- if (buflen == 0) {
- xlline = 1;
- }
- else {
- mptr -= tbuflen - buflen;
- buf[buflen] = '\0';
- ch = '\r';
- }
- }
-
- /* if we reach the outer bounds of our buffer, abort without respect for what we purge. */
- if (xlline && ((isspace(ch)) || (buflen > SIZ - nllen - 2))) {
- ch = '\r';
- }
-
- if (ch == '\r') {
- memcpy (&buf[buflen], nl, nllen);
- buflen += nllen;
- buf[buflen] = '\0';
-
- if (client_write(buf, buflen) == -1) {
- syslog(LOG_ERR, "msgbase: DumpFormatFixed() aborting due to write failure");
- return;
- }
- *buf = '\0';
- buflen = 0;
- xlline = 0;
- } else {
- buf[buflen] = ch;
- buflen++;
- }
- }
- buf[buflen] = '\0';
- if (!IsEmptyStr(buf)) {
- cprintf("%s%s", buf, nl);
- }
-}
-
-
-/*
- * Get a message off disk. (returns om_* values found in msgbase.h)
- */
-int CtdlOutputPreLoadedMsg(
- struct CtdlMessage *TheMessage,
- int mode, /* how would you like that message? */
- int headers_only, /* eschew the message body? */
- int do_proto, /* do Citadel protocol responses? */
- int crlf, /* Use CRLF newlines instead of LF? */
- int flags /* should the bessage be exported clean? */
-) {
- int i;
- const char *nl; /* newline string */
- int nlen;
- struct ma_info ma;
-
- /* Buffers needed for RFC822 translation. These are all filled
- * using functions that are bounds-checked, and therefore we can
- * make them substantially smaller than SIZ.
- */
- char suser[1024];
- char luser[1024];
- char fuser[1024];
- char snode[1024];
- char mid[1024];
-
- syslog(LOG_DEBUG, "msgbase: CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d",
- ((TheMessage == NULL) ? "NULL" : "not null"),
- mode, headers_only, do_proto, crlf
- );
-
- strcpy(mid, "unknown");
- nl = (crlf ? "\r\n" : "\n");
- nlen = crlf ? 2 : 1;
-
- if (!CM_IsValidMsg(TheMessage)) {
- syslog(LOG_ERR, "msgbase: error; invalid preloaded message for output");
- return(om_no_such_msg);
- }
-
- /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
- * Pad it with spaces in order to avoid changing the RFC822 length of the message.
- */
- if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
- memset(TheMessage->cm_fields[eenVelopeTo], ' ', TheMessage->cm_lengths[eenVelopeTo]);
- }
-
- /* Are we downloading a MIME component? */
- if (mode == MT_DOWNLOAD) {
- if (TheMessage->cm_format_type != FMT_RFC822) {
- if (do_proto)
- cprintf("%d This is not a MIME message.\n",
- ERROR + ILLEGAL_VALUE);
- } else if (CC->download_fp != NULL) {
- if (do_proto) cprintf(
- "%d You already have a download open.\n",
- ERROR + RESOURCE_BUSY);
- } else {
- /* Parse the message text component */
- mime_parser(CM_RANGE(TheMessage, eMesageText),
- *mime_download, NULL, NULL, NULL, 0);
- /* If there's no file open by this time, the requested
- * section wasn't found, so print an error
- */
- if (CC->download_fp == NULL) {
- if (do_proto) cprintf(
- "%d Section %s not found.\n",
- ERROR + FILE_NOT_FOUND,
- CC->download_desired_section);
- }
- }
- return((CC->download_fp != NULL) ? om_ok : om_mime_error);
- }
-
- // MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
- // in a single server operation instead of opening a download file.
- if (mode == MT_SPEW_SECTION) {
- if (TheMessage->cm_format_type != FMT_RFC822) {
- if (do_proto)
- cprintf("%d This is not a MIME message.\n",
- ERROR + ILLEGAL_VALUE);
- }
- else {
- // Locate and parse the component specified by the caller
- int found_it = 0;
- mime_parser(CM_RANGE(TheMessage, eMesageText), *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
-
- // If section wasn't found, print an error
- if (!found_it) {
- if (do_proto) {
- cprintf( "%d Section %s not found.\n", ERROR + FILE_NOT_FOUND, CC->download_desired_section);
- }
- }
- }
- return((CC->download_fp != NULL) ? om_ok : om_mime_error);
- }
-
- // now for the user-mode message reading loops
- if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
-
- // Does the caller want to skip the headers?
- if (headers_only == HEADERS_NONE) goto START_TEXT;
-
- // Tell the client which format type we're using.
- if ( (mode == MT_CITADEL) && (do_proto) ) {
- cprintf("type=%d\n", TheMessage->cm_format_type); // Tell the client which format type we're using.
- }
-
- // nhdr=yes means that we're only displaying headers, no body
- if ( (TheMessage->cm_anon_type == MES_ANONONLY)
- && ((mode == MT_CITADEL) || (mode == MT_MIME))
- && (do_proto)
- ) {
- cprintf("nhdr=yes\n");
- }
-
- if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
- OutputCtdlMsgHeaders(TheMessage, do_proto);
- }
-
- // begin header processing loop for RFC822 transfer format
- strcpy(suser, "");
- strcpy(luser, "");
- strcpy(fuser, "");
- strcpy(snode, "");
- if (mode == MT_RFC822)
- OutputRFC822MsgHeaders(
- TheMessage,
- flags,
- nl, nlen,
- mid, sizeof(mid),
- suser, sizeof(suser),
- luser, sizeof(luser),
- fuser, sizeof(fuser),
- snode, sizeof(snode)
- );
-
-
- for (i=0; !IsEmptyStr(&suser[i]); ++i) {
- suser[i] = tolower(suser[i]);
- if (!isalnum(suser[i])) suser[i]='_';
- }
-
- if (mode == MT_RFC822) {
- /* Construct a fun message id */
- cprintf("Message-ID: <%s", mid);
- if (strchr(mid, '@')==NULL) {
- cprintf("@%s", snode);
- }
- cprintf(">%s", nl);
-
- if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
- cprintf("From: \"----\" <x@x.org>%s", nl);
- }
- else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
- cprintf("From: \"anonymous\" <x@x.org>%s", nl);
- }
- else if (!IsEmptyStr(fuser)) {
- cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
- }
- else {
- cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
- }
-
- /* Blank line signifying RFC822 end-of-headers */
- if (TheMessage->cm_format_type != FMT_RFC822) {
- cprintf("%s", nl);
- }
- }
-
- // end header processing loop ... at this point, we're in the text
-START_TEXT:
- if (headers_only == HEADERS_FAST) goto DONE;
-
- // Tell the client about the MIME parts in this message
- if (TheMessage->cm_format_type == FMT_RFC822) {
- if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
- memset(&ma, 0, sizeof(struct ma_info));
- mime_parser(CM_RANGE(TheMessage, eMesageText),
- (do_proto ? *list_this_part : NULL),
- (do_proto ? *list_this_pref : NULL),
- (do_proto ? *list_this_suff : NULL),
- (void *)&ma, 1);
- }
- else if (mode == MT_RFC822) { // unparsed RFC822 dump
- Dump_RFC822HeadersBody(
- TheMessage,
- headers_only,
- flags,
- nl, nlen);
- goto DONE;
- }
- }
-
- if (headers_only == HEADERS_ONLY) {
- goto DONE;
- }
-
- // signify start of msg text
- if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
- if (do_proto) cprintf("text\n");
- }
-
- if (TheMessage->cm_format_type == FMT_FIXED)
- DumpFormatFixed(
- TheMessage,
- mode, // how would you like that message?
- nl, nlen);
-
- // If the message on disk is format 0 (Citadel vari-format), we
- // output using the formatter at 80 columns. This is the final output
- // form if the transfer format is RFC822, but if the transfer format
- // is Citadel proprietary, it'll still work, because the indentation
- // for new paragraphs is correct and the client will reformat the
- // message to the reader's screen width.
- //
- if (TheMessage->cm_format_type == FMT_CITADEL) {
- if (mode == MT_MIME) {
- cprintf("Content-type: text/x-citadel-variformat\n\n");
- }
- memfmout(TheMessage->cm_fields[eMesageText], nl);
- }
-
- // If the message on disk is format 4 (MIME), we've gotta hand it
- // off to the MIME parser. The client has already been told that
- // this message is format 1 (fixed format), so the callback function
- // we use will display those parts as-is.
- //
- if (TheMessage->cm_format_type == FMT_RFC822) {
- memset(&ma, 0, sizeof(struct ma_info));
-
- if (mode == MT_MIME) {
- ma.use_fo_hooks = 0;
- strcpy(ma.chosen_part, "1");
- ma.chosen_pref = 9999;
- ma.dont_decode = CC->msg4_dont_decode;
- mime_parser(CM_RANGE(TheMessage, eMesageText),
- *choose_preferred, *fixed_output_pre,
- *fixed_output_post, (void *)&ma, 1);
- mime_parser(CM_RANGE(TheMessage, eMesageText),
- *output_preferred, NULL, NULL, (void *)&ma, 1);
- }
- else {
- ma.use_fo_hooks = 1;
- mime_parser(CM_RANGE(TheMessage, eMesageText),
- *fixed_output, *fixed_output_pre,
- *fixed_output_post, (void *)&ma, 0);
- }
-
- }
-
-DONE: /* now we're done */
- if (do_proto) cprintf("000\n");
- return(om_ok);
-}
-
-// Save one or more message pointers into a specified room
-// (Returns 0 for success, nonzero for failure)
-// roomname may be NULL to use the current room
-//
-// Note that the 'supplied_msg' field may be set to NULL, in which case
-// the message will be fetched from disk, by number, if we need to perform
-// replication checks. This adds an additional database read, so if the
-// caller already has the message in memory then it should be supplied. (Obviously
-// this mode of operation only works if we're saving a single message.)
-//
-int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
- int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
-) {
- int i, j, unique;
- char hold_rm[ROOMNAMELEN];
- struct cdbdata *cdbfr;
- int num_msgs;
- long *msglist;
- long highest_msg = 0L;
-
- long msgid = 0;
- struct CtdlMessage *msg = NULL;
-
- long *msgs_to_be_merged = NULL;
- int num_msgs_to_be_merged = 0;
-
- syslog(LOG_DEBUG,
- "msgbase: CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)",
- roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
- );
-
- strcpy(hold_rm, CC->room.QRname);
-
- /* Sanity checks */
- if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
- if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
- if (num_newmsgs > 1) supplied_msg = NULL;
-
- /* Now the regular stuff */
- if (CtdlGetRoomLock(&CC->room,
- ((roomname != NULL) ? roomname : CC->room.QRname) )
- != 0) {
- syslog(LOG_ERR, "msgbase: no such room <%s>", roomname);
- return(ERROR + ROOM_NOT_FOUND);
- }
-
-
- msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
- num_msgs_to_be_merged = 0;
-
-
- cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
- if (cdbfr == NULL) {
- msglist = NULL;
- num_msgs = 0;
- } else {
- msglist = (long *) cdbfr->ptr;
- cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
- num_msgs = cdbfr->len / sizeof(long);
- cdb_free(cdbfr);
- }
-
-
- /* Create a list of msgid's which were supplied by the caller, but do
- * not already exist in the target room. It is absolutely taboo to
- * have more than one reference to the same message in a room.
- */
- for (i=0; i<num_newmsgs; ++i) {
- unique = 1;
- if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
- if (msglist[j] == newmsgidlist[i]) {
- unique = 0;
- }
- }
- if (unique) {
- msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
- }
- }
-
- syslog(LOG_DEBUG, "msgbase: %d unique messages to be merged", num_msgs_to_be_merged);
-
- /*
- * Now merge the new messages
- */
- msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
- if (msglist == NULL) {
- syslog(LOG_ALERT, "msgbase: ERROR; can't realloc message list!");
- free(msgs_to_be_merged);
- return (ERROR + INTERNAL_ERROR);
- }
- memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
- num_msgs += num_msgs_to_be_merged;
-
- /* Sort the message list, so all the msgid's are in order */
- num_msgs = sort_msglist(msglist, num_msgs);
-
- /* Determine the highest message number */
- highest_msg = msglist[num_msgs - 1];
-
- /* Write it back to disk. */
- cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
- msglist, (int)(num_msgs * sizeof(long)));
-
- /* Free up the memory we used. */
- free(msglist);
-
- /* Update the highest-message pointer and unlock the room. */
- CC->room.QRhighest = highest_msg;
- CtdlPutRoomLock(&CC->room);
-
- /* Perform replication checks if necessary */
- if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
- syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() doing repl checks");
-
- for (i=0; i<num_msgs_to_be_merged; ++i) {
- msgid = msgs_to_be_merged[i];
-
- if (supplied_msg != NULL) {
- msg = supplied_msg;
- }
- else {
- msg = CtdlFetchMessage(msgid, 0);
- }
-
- if (msg != NULL) {
- ReplicationChecks(msg);
-
- /* If the message has an Exclusive ID, index that... */
- if (!CM_IsEmpty(msg, eExclusiveID)) {
- index_message_by_euid(msg->cm_fields[eExclusiveID], &CC->room, msgid);
- }
-
- /* Free up the memory we may have allocated */
- if (msg != supplied_msg) {
- CM_Free(msg);
- }
- }
-
- }
- }
-
- else {
- syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() skips repl checks");
- }
-
- /* Submit this room for processing by hooks */
- int total_roomhook_errors = PerformRoomHooks(&CC->room);
- if (total_roomhook_errors) {
- syslog(LOG_WARNING, "msgbase: room hooks returned %d errors", total_roomhook_errors);
- }
-
- /* Go back to the room we were in before we wandered here... */
- CtdlGetRoom(&CC->room, hold_rm);
-
- /* Bump the reference count for all messages which were merged */
- if (!suppress_refcount_adj) {
- AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
- }
-
- /* Free up memory... */
- if (msgs_to_be_merged != NULL) {
- free(msgs_to_be_merged);
- }
-
- /* Return success. */
- return (0);
-}
-
-
-/*
- * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
- * a single message.
- */
-int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
- int do_repl_check, struct CtdlMessage *supplied_msg)
-{
- return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
-}
-
-
-/*
- * Message base operation to save a new message to the message store
- * (returns new message number)
- *
- * This is the back end for CtdlSubmitMsg() and should not be directly
- * called by server-side modules.
- *
- */
-long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply) {
- long retval;
- struct ser_ret smr;
- int is_bigmsg = 0;
- char *holdM = NULL;
- long holdMLen = 0;
-
- /*
- * If the message is big, set its body aside for storage elsewhere
- * and we hide the message body from the serializer
- */
- if (!CM_IsEmpty(msg, eMesageText) && msg->cm_lengths[eMesageText] > BIGMSG) {
- is_bigmsg = 1;
- holdM = msg->cm_fields[eMesageText];
- msg->cm_fields[eMesageText] = NULL;
- holdMLen = msg->cm_lengths[eMesageText];
- msg->cm_lengths[eMesageText] = 0;
- }
-
- /* Serialize our data structure for storage in the database */
- CtdlSerializeMessage(&smr, msg);
-
- if (is_bigmsg) {
- /* put the message body back into the message */
- msg->cm_fields[eMesageText] = holdM;
- msg->cm_lengths[eMesageText] = holdMLen;
- }
-
- if (smr.len == 0) {
- if (Reply) {
- cprintf("%d Unable to serialize message\n",
- ERROR + INTERNAL_ERROR);
- }
- else {
- syslog(LOG_ERR, "msgbase: CtdlSaveMessage() unable to serialize message");
-
- }
- return (-1L);
- }
-
- /* Write our little bundle of joy into the message base */
- retval = cdb_store(CDB_MSGMAIN, &msgid, (int)sizeof(long), smr.ser, smr.len);
- if (retval < 0) {
- syslog(LOG_ERR, "msgbase: can't store message %ld: %ld", msgid, retval);
- }
- else {
- if (is_bigmsg) {
- retval = cdb_store(CDB_BIGMSGS,
- &msgid,
- (int)sizeof(long),
- holdM,
- (holdMLen + 1)
- );
- if (retval < 0) {
- syslog(LOG_ERR, "msgbase: failed to store message body for msgid %ld: %ld", msgid, retval);
- }
- }
- }
-
- /* Free the memory we used for the serialized message */
- free(smr.ser);
-
- return(retval);
-}
-
-
-long send_message(struct CtdlMessage *msg) {
- long newmsgid;
- long retval;
- char msgidbuf[256];
- long msgidbuflen;
-
- /* Get a new message number */
- newmsgid = get_new_message_number();
-
- /* Generate an ID if we don't have one already */
- if (CM_IsEmpty(msg, emessageId)) {
- msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
- (long unsigned int) time(NULL),
- (long unsigned int) newmsgid,
- CtdlGetConfigStr("c_fqdn")
- );
-
- CM_SetField(msg, emessageId, msgidbuf, msgidbuflen);
- }
-
- retval = CtdlSaveThisMessage(msg, newmsgid, 1);
-
- if (retval == 0) {
- retval = newmsgid;
- }
-
- /* Return the *local* message ID to the caller
- * (even if we're storing an incoming network message)
- */
- return(retval);
-}
-
-
-/*
- * Serialize a struct CtdlMessage into the format used on disk.
- *
- * This function loads up a "struct ser_ret" (defined in server.h) which
- * contains the length of the serialized message and a pointer to the
- * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
- */
-void CtdlSerializeMessage(struct ser_ret *ret, /* return values */
- struct CtdlMessage *msg) /* unserialized msg */
-{
- size_t wlen;
- int i;
-
- /*
- * Check for valid message format
- */
- if (CM_IsValidMsg(msg) == 0) {
- syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() aborting due to invalid message");
- ret->len = 0;
- ret->ser = NULL;
- return;
- }
-
- ret->len = 3;
- for (i=0; i < NDiskFields; ++i)
- if (msg->cm_fields[FieldOrder[i]] != NULL)
- ret->len += msg->cm_lengths[FieldOrder[i]] + 2;
-
- ret->ser = malloc(ret->len);
- if (ret->ser == NULL) {
- syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() malloc(%ld) failed: %m", (long)ret->len);
- ret->len = 0;
- ret->ser = NULL;
- return;
- }
-
- ret->ser[0] = 0xFF;
- ret->ser[1] = msg->cm_anon_type;
- ret->ser[2] = msg->cm_format_type;
- wlen = 3;
-
- for (i=0; i < NDiskFields; ++i) {
- if (msg->cm_fields[FieldOrder[i]] != NULL) {
- ret->ser[wlen++] = (char)FieldOrder[i];
-
- memcpy(&ret->ser[wlen],
- msg->cm_fields[FieldOrder[i]],
- msg->cm_lengths[FieldOrder[i]] + 1);
-
- wlen = wlen + msg->cm_lengths[FieldOrder[i]] + 1;
- }
- }
-
- if (ret->len != wlen) {
- syslog(LOG_ERR, "msgbase: ERROR; len=%ld wlen=%ld", (long)ret->len, (long)wlen);
- }
-
- return;
-}
-
-
-/*
- * Check to see if any messages already exist in the current room which
- * carry the same Exclusive ID as this one. If any are found, delete them.
- */
-void ReplicationChecks(struct CtdlMessage *msg) {
- long old_msgnum = (-1L);
-
- if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
-
- syslog(LOG_DEBUG, "msgbase: performing replication checks in <%s>", CC->room.QRname);
-
- /* No exclusive id? Don't do anything. */
- if (msg == NULL) return;
- if (CM_IsEmpty(msg, eExclusiveID)) return;
-
- /*syslog(LOG_DEBUG, "msgbase: exclusive ID: <%s> for room <%s>",
- msg->cm_fields[eExclusiveID], CC->room.QRname);*/
-
- old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CC->room);
- if (old_msgnum > 0L) {
- syslog(LOG_DEBUG, "msgbase: ReplicationChecks() replacing message %ld", old_msgnum);
- CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
- }
-}
-
-
-/*
- * Save a message to disk and submit it into the delivery system.
- */
-long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
- struct recptypes *recps, /* recipients (if mail) */
- const char *force /* force a particular room? */
-) {
- char hold_rm[ROOMNAMELEN];
- char actual_rm[ROOMNAMELEN];
- char force_room[ROOMNAMELEN];
- char content_type[SIZ]; /* We have to learn this */
- char recipient[SIZ];
- char bounce_to[1024];
- const char *room;
- long newmsgid;
- const char *mptr = NULL;
- struct ctdluser userbuf;
- int a, i;
- struct MetaData smi;
- char *collected_addresses = NULL;
- struct addresses_to_be_filed *aptr = NULL;
- StrBuf *saved_rfc822_version = NULL;
- int qualified_for_journaling = 0;
-
- syslog(LOG_DEBUG, "msgbase: CtdlSubmitMsg() called");
- if (CM_IsValidMsg(msg) == 0) return(-1); /* self check */
-
- /* If this message has no timestamp, we take the liberty of
- * giving it one, right now.
- */
- if (CM_IsEmpty(msg, eTimestamp)) {
- CM_SetFieldLONG(msg, eTimestamp, time(NULL));
- }
-
- /* If this message has no path, we generate one.
- */
- if (CM_IsEmpty(msg, eMessagePath)) {
- if (!CM_IsEmpty(msg, eAuthor)) {
- CM_CopyField(msg, eMessagePath, eAuthor);
- for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
- if (isspace(msg->cm_fields[eMessagePath][a])) {
- msg->cm_fields[eMessagePath][a] = ' ';
- }
- }
- }
- else {
- CM_SetField(msg, eMessagePath, HKEY("unknown"));
- }
- }
-
- if (force == NULL) {
- force_room[0] = '\0';
- }
- else {
- strcpy(force_room, force);
- }
-
- /* Learn about what's inside, because it's what's inside that counts */
- if (CM_IsEmpty(msg, eMesageText)) {
- syslog(LOG_ERR, "msgbase: ERROR; attempt to save message with NULL body");
- return(-2);
- }
-
- switch (msg->cm_format_type) {
- case 0:
- strcpy(content_type, "text/x-citadel-variformat");
- break;
- case 1:
- strcpy(content_type, "text/plain");
- break;
- case 4:
- strcpy(content_type, "text/plain");
- mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
- if (mptr != NULL) {
- char *aptr;
- safestrncpy(content_type, &mptr[13], sizeof content_type);
- striplt(content_type);
- aptr = content_type;
- while (!IsEmptyStr(aptr)) {
- if ((*aptr == ';')
- || (*aptr == ' ')
- || (*aptr == 13)
- || (*aptr == 10)) {
- *aptr = 0;
- }
- else aptr++;
- }
- }
- }
-
- /* Goto the correct room */
- room = (recps) ? CC->room.QRname : SENTITEMS;
- syslog(LOG_DEBUG, "msgbase: selected room %s", room);
- strcpy(hold_rm, CC->room.QRname);
- strcpy(actual_rm, CC->room.QRname);
- if (recps != NULL) {
- strcpy(actual_rm, SENTITEMS);
- }
-
- /* If the user is a twit, move to the twit room for posting */
- if (TWITDETECT) {
- if (CC->user.axlevel == AxProbU) {
- strcpy(hold_rm, actual_rm);
- strcpy(actual_rm, CtdlGetConfigStr("c_twitroom"));
- syslog(LOG_DEBUG, "msgbase: diverting to twit room");
- }
- }
-
- /* ...or if this message is destined for Aide> then go there. */
- if (!IsEmptyStr(force_room)) {
- strcpy(actual_rm, force_room);
- }
-
- syslog(LOG_DEBUG, "msgbase: final selection: %s (%s)", actual_rm, room);
- if (strcasecmp(actual_rm, CC->room.QRname)) {
- CtdlUserGoto(actual_rm, 0, 1, NULL, NULL, NULL, NULL);
- }
-
- /*
- * If this message has no O (room) field, generate one.
- */
- if (CM_IsEmpty(msg, eOriginalRoom) && !IsEmptyStr(CC->room.QRname)) {
- CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
- }
-
- /* Perform "before save" hooks (aborting if any return nonzero) */
- syslog(LOG_DEBUG, "msgbase: performing before-save hooks");
- if (PerformMessageHooks(msg, recps, EVT_BEFORESAVE) > 0) return(-3);
-
- /*
- * If this message has an Exclusive ID, and the room is replication
- * checking enabled, then do replication checks.
- */
- if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
- ReplicationChecks(msg);
- }
-
- /* Save it to disk */
- syslog(LOG_DEBUG, "msgbase: saving to disk");
- newmsgid = send_message(msg);
- if (newmsgid <= 0L) return(-5);
-
- /* Write a supplemental message info record. This doesn't have to
- * be a critical section because nobody else knows about this message
- * yet.
- */
- syslog(LOG_DEBUG, "msgbase: creating metadata record");
- memset(&smi, 0, sizeof(struct MetaData));
- smi.meta_msgnum = newmsgid;
- smi.meta_refcount = 0;
- safestrncpy(smi.meta_content_type, content_type, sizeof smi.meta_content_type);
-
- /*
- * Measure how big this message will be when rendered as RFC822.
- * We do this for two reasons:
- * 1. We need the RFC822 length for the new metadata record, so the
- * POP and IMAP services don't have to calculate message lengths
- * while the user is waiting (multiplied by potentially hundreds
- * or thousands of messages).
- * 2. If journaling is enabled, we will need an RFC822 version of the
- * message to attach to the journalized copy.
- */
- if (CC->redirect_buffer != NULL) {
- syslog(LOG_ALERT, "msgbase: CC->redirect_buffer is not NULL during message submission!");
- abort();
- }
- CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
- CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
- smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
- saved_rfc822_version = CC->redirect_buffer;
- CC->redirect_buffer = NULL;
-
- PutMetaData(&smi);
-
- /* Now figure out where to store the pointers */
- syslog(LOG_DEBUG, "msgbase: storing pointers");
-
- /* If this is being done by the networker delivering a private
- * message, we want to BYPASS saving the sender's copy (because there
- * is no local sender; it would otherwise go to the Trashcan).
- */
- if ((!CC->internal_pgm) || (recps == NULL)) {
- if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
- syslog(LOG_ERR, "msgbase: ERROR saving message pointer %ld in %s", newmsgid, actual_rm);
- CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
- }
- }
-
- /* For internet mail, drop a copy in the outbound queue room */
- if ((recps != NULL) && (recps->num_internet > 0)) {
- CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
- }
-
- /* If other rooms are specified, drop them there too. */
- if ((recps != NULL) && (recps->num_room > 0)) {
- for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
- extract_token(recipient, recps->recp_room, i, '|', sizeof recipient);
- syslog(LOG_DEBUG, "msgbase: delivering to room <%s>", recipient);
- CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
- }
- }
-
- /* Bump this user's messages posted counter. */
- syslog(LOG_DEBUG, "msgbase: updating user");
- CtdlLockGetCurrentUser();
- CC->user.posted = CC->user.posted + 1;
- CtdlPutCurrentUserLock();
-
- /* Decide where bounces need to be delivered */
- if ((recps != NULL) && (recps->bounce_to == NULL)) {
- if (CC->logged_in) {
- strcpy(bounce_to, CC->user.fullname);
- }
- else if (!IsEmptyStr(msg->cm_fields[eAuthor])){
- strcpy(bounce_to, msg->cm_fields[eAuthor]);
- }
- recps->bounce_to = bounce_to;
- }
-
- CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
-
- /* If this is private, local mail, make a copy in the
- * recipient's mailbox and bump the reference count.
- */
- if ((recps != NULL) && (recps->num_local > 0)) {
- char *pch;
- int ntokens;
-
- pch = recps->recp_local;
- recps->recp_local = recipient;
- ntokens = num_tokens(pch, '|');
- for (i=0; i<ntokens; ++i) {
- extract_token(recipient, pch, i, '|', sizeof recipient);
- syslog(LOG_DEBUG, "msgbase: delivering private local mail to <%s>", recipient);
- if (CtdlGetUser(&userbuf, recipient) == 0) {
- CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
- CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
- CtdlBumpNewMailCounter(userbuf.usernum);
- PerformMessageHooks(msg, recps, EVT_AFTERUSRMBOXSAVE);
- }
- else {
- syslog(LOG_DEBUG, "msgbase: no user <%s>", recipient);
- CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
- }
- }
- recps->recp_local = pch;
- }
-
- /* Perform "after save" hooks */
- syslog(LOG_DEBUG, "msgbase: performing after-save hooks");
-
- PerformMessageHooks(msg, recps, EVT_AFTERSAVE);
- CM_FlushField(msg, eVltMsgNum);
-
- /* Go back to the room we started from */
- syslog(LOG_DEBUG, "msgbase: returning to original room %s", hold_rm);
- if (strcasecmp(hold_rm, CC->room.QRname)) {
- CtdlUserGoto(hold_rm, 0, 1, NULL, NULL, NULL, NULL);
- }
-
- /*
- * Any addresses to harvest for someone's address book?
- */
- if ( (CC->logged_in) && (recps != NULL) ) {
- collected_addresses = harvest_collected_addresses(msg);
- }
-
- if (collected_addresses != NULL) {
- aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed));
- CtdlMailboxName(actual_rm, sizeof actual_rm, &CC->user, USERCONTACTSROOM);
- aptr->roomname = strdup(actual_rm);
- aptr->collected_addresses = collected_addresses;
- begin_critical_section(S_ATBF);
- aptr->next = atbf;
- atbf = aptr;
- end_critical_section(S_ATBF);
- }
-
- /*
- * Determine whether this message qualifies for journaling.
- */
- if (!CM_IsEmpty(msg, eJournal)) {
- qualified_for_journaling = 0;
- }
- else {
- if (recps == NULL) {
- qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
- }
- else if (recps->num_local + recps->num_internet > 0) {
- qualified_for_journaling = CtdlGetConfigInt("c_journal_email");
- }
- else {
- qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
- }
- }
-
- /*
- * Do we have to perform journaling? If so, hand off the saved
- * RFC822 version will be handed off to the journaler for background
- * submit. Otherwise, we have to free the memory ourselves.
- */
- if (saved_rfc822_version != NULL) {
- if (qualified_for_journaling) {
- JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
- }
- else {
- FreeStrBuf(&saved_rfc822_version);
- }
- }
-
- if ((recps != NULL) && (recps->bounce_to == bounce_to))
- recps->bounce_to = NULL;
-
- /* Done. */
- return(newmsgid);
-}
-
-
-/*
- * Convenience function for generating small administrative messages.
- */
-long quickie_message(const char *from,
- const char *fromaddr,
- const char *to,
- char *room,
- const char *text,
- int format_type,
- const char *subject)
-{
- struct CtdlMessage *msg;
- struct recptypes *recp = NULL;
-
- msg = malloc(sizeof(struct CtdlMessage));
- memset(msg, 0, sizeof(struct CtdlMessage));
- msg->cm_magic = CTDLMESSAGE_MAGIC;
- msg->cm_anon_type = MES_NORMAL;
- msg->cm_format_type = format_type;
-
- if (!IsEmptyStr(from)) {
- CM_SetField(msg, eAuthor, from, -1);
- }
- else if (!IsEmptyStr(fromaddr)) {
- char *pAt;
- CM_SetField(msg, eAuthor, fromaddr, -1);
- pAt = strchr(msg->cm_fields[eAuthor], '@');
- if (pAt != NULL) {
- CM_CutFieldAt(msg, eAuthor, pAt - msg->cm_fields[eAuthor]);
- }
- }
- else {
- msg->cm_fields[eAuthor] = strdup("Citadel");
- }
-
- if (!IsEmptyStr(fromaddr)) CM_SetField(msg, erFc822Addr, fromaddr, -1);
- if (!IsEmptyStr(room)) CM_SetField(msg, eOriginalRoom, room, -1);
- if (!IsEmptyStr(to)) {
- CM_SetField(msg, eRecipient, to, -1);
- recp = validate_recipients(to, NULL, 0);
- }
- if (!IsEmptyStr(subject)) {
- CM_SetField(msg, eMsgSubject, subject, -1);
- }
- if (!IsEmptyStr(text)) {
- CM_SetField(msg, eMesageText, text, -1);
- }
-
- long msgnum = CtdlSubmitMsg(msg, recp, room);
- CM_Free(msg);
- if (recp != NULL) free_recipients(recp);
- return msgnum;
-}
-
-
-/*
- * Back end function used by CtdlMakeMessage() and similar functions
- */
-StrBuf *CtdlReadMessageBodyBuf(char *terminator, // token signalling EOT
- long tlen,
- size_t maxlen, // maximum message length
- StrBuf *exist, // if non-null, append to it; exist is ALWAYS freed
- int crlf // CRLF newlines instead of LF
-) {
- StrBuf *Message;
- StrBuf *LineBuf;
- int flushing = 0;
- int finished = 0;
- int dotdot = 0;
-
- LineBuf = NewStrBufPlain(NULL, SIZ);
- if (exist == NULL) {
- Message = NewStrBufPlain(NULL, 4 * SIZ);
- }
- else {
- Message = NewStrBufDup(exist);
- }
-
- /* Do we need to change leading ".." to "." for SMTP escaping? */
- if ((tlen == 1) && (*terminator == '.')) {
- dotdot = 1;
- }
-
- /* read in the lines of message text one by one */
- do {
- if (CtdlClientGetLine(LineBuf) < 0) {
- finished = 1;
- }
- if ((StrLength(LineBuf) == tlen) && (!strcmp(ChrPtr(LineBuf), terminator))) {
- finished = 1;
- }
- if ( (!flushing) && (!finished) ) {
- if (crlf) {
- StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
- }
- else {
- StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
- }
-
- /* Unescape SMTP-style input of two dots at the beginning of the line */
- if ((dotdot) && (StrLength(LineBuf) > 1) && (ChrPtr(LineBuf)[0] == '.')) {
- StrBufCutLeft(LineBuf, 1);
- }
- StrBufAppendBuf(Message, LineBuf, 0);
- }
-
- /* if we've hit the max msg length, flush the rest */
- if (StrLength(Message) >= maxlen) {
- flushing = 1;
- }
-
- } while (!finished);
- FreeStrBuf(&LineBuf);
-
- if (flushing) {
- syslog(LOG_ERR, "msgbase: exceeded maximum message length of %ld - message was truncated", maxlen);
- }
-
- return Message;
-}
-
-
-// Back end function used by CtdlMakeMessage() and similar functions
-char *CtdlReadMessageBody(char *terminator, // token signalling EOT
- long tlen,
- size_t maxlen, // maximum message length
- StrBuf *exist, // if non-null, append to it; exist is ALWAYS freed
- int crlf // CRLF newlines instead of LF
-) {
- StrBuf *Message;
-
- Message = CtdlReadMessageBodyBuf(terminator,
- tlen,
- maxlen,
- exist,
- crlf
- );
- if (Message == NULL) {
- return NULL;
- }
- else {
- return SmashStrBuf(&Message);
- }
-}
-
-
-struct CtdlMessage *CtdlMakeMessage(
- struct ctdluser *author, /* author's user structure */
- char *recipient, /* NULL if it's not mail */
- char *recp_cc, /* NULL if it's not mail */
- char *room, /* room where it's going */
- int type, /* see MES_ types in header file */
- int format_type, /* variformat, plain text, MIME... */
- char *fake_name, /* who we're masquerading as */
- char *my_email, /* which of my email addresses to use (empty is ok) */
- char *subject, /* Subject (optional) */
- char *supplied_euid, /* ...or NULL if this is irrelevant */
- char *preformatted_text, /* ...or NULL to read text from client */
- char *references /* Thread references */
-) {
- return CtdlMakeMessageLen(
- author, /* author's user structure */
- recipient, /* NULL if it's not mail */
- (recipient)?strlen(recipient) : 0,
- recp_cc, /* NULL if it's not mail */
- (recp_cc)?strlen(recp_cc): 0,
- room, /* room where it's going */
- (room)?strlen(room): 0,
- type, /* see MES_ types in header file */
- format_type, /* variformat, plain text, MIME... */
- fake_name, /* who we're masquerading as */
- (fake_name)?strlen(fake_name): 0,
- my_email, /* which of my email addresses to use (empty is ok) */
- (my_email)?strlen(my_email): 0,
- subject, /* Subject (optional) */
- (subject)?strlen(subject): 0,
- supplied_euid, /* ...or NULL if this is irrelevant */
- (supplied_euid)?strlen(supplied_euid):0,
- preformatted_text, /* ...or NULL to read text from client */
- (preformatted_text)?strlen(preformatted_text) : 0,
- references, /* Thread references */
- (references)?strlen(references):0);
-
-}
-
-
-/*
- * Build a binary message to be saved on disk.
- * (NOTE: if you supply 'preformatted_text', the buffer you give it
- * will become part of the message. This means you are no longer
- * responsible for managing that memory -- it will be freed along with
- * the rest of the fields when CM_Free() is called.)
- */
-struct CtdlMessage *CtdlMakeMessageLen(
- struct ctdluser *author, /* author's user structure */
- char *recipient, /* NULL if it's not mail */
- long rcplen,
- char *recp_cc, /* NULL if it's not mail */
- long cclen,
- char *room, /* room where it's going */
- long roomlen,
- int type, /* see MES_ types in header file */
- int format_type, /* variformat, plain text, MIME... */
- char *fake_name, /* who we're masquerading as */
- long fnlen,
- char *my_email, /* which of my email addresses to use (empty is ok) */
- long myelen,
- char *subject, /* Subject (optional) */
- long subjlen,
- char *supplied_euid, /* ...or NULL if this is irrelevant */
- long euidlen,
- char *preformatted_text, /* ...or NULL to read text from client */
- long textlen,
- char *references, /* Thread references */
- long reflen
-) {
- long blen;
- char buf[1024];
- struct CtdlMessage *msg;
- StrBuf *FakeAuthor;
- StrBuf *FakeEncAuthor = NULL;
-
- msg = malloc(sizeof(struct CtdlMessage));
- memset(msg, 0, sizeof(struct CtdlMessage));
- msg->cm_magic = CTDLMESSAGE_MAGIC;
- msg->cm_anon_type = type;
- msg->cm_format_type = format_type;
-
- if (recipient != NULL) rcplen = striplt(recipient);
- if (recp_cc != NULL) cclen = striplt(recp_cc);
-
- /* Path or Return-Path */
- if (myelen > 0) {
- CM_SetField(msg, eMessagePath, my_email, myelen);
- }
- else if (!IsEmptyStr(author->fullname)) {
- CM_SetField(msg, eMessagePath, author->fullname, -1);
- }
- convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
-
- blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
- CM_SetField(msg, eTimestamp, buf, blen);
-
- if (fnlen > 0) {
- FakeAuthor = NewStrBufPlain (fake_name, fnlen);
- }
- else {
- FakeAuthor = NewStrBufPlain (author->fullname, -1);
- }
- StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
- CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor);
- FreeStrBuf(&FakeAuthor);
-
- if (!!IsEmptyStr(CC->room.QRname)) {
- if (CC->room.QRflags & QR_MAILBOX) { /* room */
- CM_SetField(msg, eOriginalRoom, &CC->room.QRname[11], -1);
- }
- else {
- CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
- }
- }
-
- if (rcplen > 0) {
- CM_SetField(msg, eRecipient, recipient, rcplen);
- }
- if (cclen > 0) {
- CM_SetField(msg, eCarbonCopY, recp_cc, cclen);
- }
-
- if (myelen > 0) {
- CM_SetField(msg, erFc822Addr, my_email, myelen);
- }
- else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
- CM_SetField(msg, erFc822Addr, CC->cs_inet_email, -1);
- }
-
- if (subject != NULL) {
- long length;
- length = striplt(subject);
- if (length > 0) {
- long i;
- long IsAscii;
- IsAscii = -1;
- i = 0;
- while ((subject[i] != '\0') &&
- (IsAscii = isascii(subject[i]) != 0 ))
- i++;
- if (IsAscii != 0)
- CM_SetField(msg, eMsgSubject, subject, subjlen);
- else /* ok, we've got utf8 in the string. */
- {
- char *rfc2047Subj;
- rfc2047Subj = rfc2047encode(subject, length);
- CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj));
- }
-
- }
- }
-
- if (euidlen > 0) {
- CM_SetField(msg, eExclusiveID, supplied_euid, euidlen);
- }
-
- if (reflen > 0) {
- CM_SetField(msg, eWeferences, references, reflen);
- }
-
- if (preformatted_text != NULL) {
- CM_SetField(msg, eMesageText, preformatted_text, textlen);
- }
- else {
- StrBuf *MsgBody;
- MsgBody = CtdlReadMessageBodyBuf(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
- if (MsgBody != NULL) {
- CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
- }
- }
-
- return(msg);
-}
-
-
-/*
- * API function to delete messages which match a set of criteria
- * (returns the actual number of messages deleted)
- */
-int CtdlDeleteMessages(const char *room_name, // which room
- long *dmsgnums, // array of msg numbers to be deleted
- int num_dmsgnums, // number of msgs to be deleted, or 0 for "any"
- char *content_type // or "" for any. regular expressions expected.
-) {
- struct ctdlroom qrbuf;
- struct cdbdata *cdbfr;
- long *msglist = NULL;
- long *dellist = NULL;
- int num_msgs = 0;
- int i, j;
- int num_deleted = 0;
- int delete_this;
- struct MetaData smi;
- regex_t re;
- regmatch_t pm;
- int need_to_free_re = 0;
-
- if (content_type) if (!IsEmptyStr(content_type)) {
- regcomp(&re, content_type, 0);
- need_to_free_re = 1;
- }
- syslog(LOG_DEBUG, "msgbase: CtdlDeleteMessages(%s, %d msgs, %s)", room_name, num_dmsgnums, content_type);
-
- /* get room record, obtaining a lock... */
- if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
- syslog(LOG_ERR, "msgbase: CtdlDeleteMessages(): Room <%s> not found", room_name);
- if (need_to_free_re) regfree(&re);
- return(0); /* room not found */
- }
- cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
-
- if (cdbfr != NULL) {
- dellist = malloc(cdbfr->len);
- msglist = (long *) cdbfr->ptr;
- cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
- num_msgs = cdbfr->len / sizeof(long);
- cdb_free(cdbfr);
- }
- if (num_msgs > 0) {
- int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
- int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
- int have_more_del = 1;
-
- num_msgs = sort_msglist(msglist, num_msgs);
- if (num_dmsgnums > 1)
- num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
-/*
- {
- StrBuf *dbg = NewStrBuf();
- for (i = 0; i < num_dmsgnums; i++)
- StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
- syslog(LOG_DEBUG, "msgbase: Deleting before: %s", ChrPtr(dbg));
- FreeStrBuf(&dbg);
- }
-*/
- i = 0; j = 0;
- while ((i < num_msgs) && (have_more_del)) {
- delete_this = 0x00;
-
- /* Set/clear a bit for each criterion */
-
- /* 0 messages in the list or a null list means that we are
- * interested in deleting any messages which meet the other criteria.
- */
- if (have_delmsgs) {
- delete_this |= 0x01;
- }
- else {
- while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
-
- if (i >= num_msgs)
- continue;
-
- if (msglist[i] == dmsgnums[j]) {
- delete_this |= 0x01;
- }
- j++;
- have_more_del = (j < num_dmsgnums);
- }
-
- if (have_contenttype) {
- GetMetaData(&smi, msglist[i]);
- if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
- delete_this |= 0x02;
- }
- } else {
- delete_this |= 0x02;
- }
-
- /* Delete message only if all bits are set */
- if (delete_this == 0x03) {
- dellist[num_deleted++] = msglist[i];
- msglist[i] = 0L;
- }
- i++;
- }
-/*
- {
- StrBuf *dbg = NewStrBuf();
- for (i = 0; i < num_deleted; i++)
- StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
- syslog(LOG_DEBUG, "msgbase: Deleting: %s", ChrPtr(dbg));
- FreeStrBuf(&dbg);
- }
-*/
- num_msgs = sort_msglist(msglist, num_msgs);
- cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
- msglist, (int)(num_msgs * sizeof(long)));
-
- if (num_msgs > 0)
- qrbuf.QRhighest = msglist[num_msgs - 1];
- else
- qrbuf.QRhighest = 0;
- }
- CtdlPutRoomLock(&qrbuf);
-
- /* Go through the messages we pulled out of the index, and decrement
- * their reference counts by 1. If this is the only room the message
- * was in, the reference count will reach zero and the message will
- * automatically be deleted from the database. We do this in a
- * separate pass because there might be plug-in hooks getting called,
- * and we don't want that happening during an S_ROOMS critical
- * section.
- */
- if (num_deleted) {
- for (i=0; i<num_deleted; ++i) {
- PerformDeleteHooks(qrbuf.QRname, dellist[i]);
- }
- AdjRefCountList(dellist, num_deleted, -1);
- }
- /* Now free the memory we used, and go away. */
- if (msglist != NULL) free(msglist);
- if (dellist != NULL) free(dellist);
- syslog(LOG_DEBUG, "msgbase: %d message(s) deleted", num_deleted);
- if (need_to_free_re) regfree(&re);
- return (num_deleted);
-}
-
-
-/*
- * GetMetaData() - Get the supplementary record for a message
- */
-void GetMetaData(struct MetaData *smibuf, long msgnum)
-{
- struct cdbdata *cdbsmi;
- long TheIndex;
-
- memset(smibuf, 0, sizeof(struct MetaData));
- smibuf->meta_msgnum = msgnum;
- smibuf->meta_refcount = 1; /* Default reference count is 1 */
-
- /* Use the negative of the message number for its supp record index */
- TheIndex = (0L - msgnum);
-
- cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
- if (cdbsmi == NULL) {
- return; /* record not found; leave it alone */
- }
- memcpy(smibuf, cdbsmi->ptr,
- ((cdbsmi->len > sizeof(struct MetaData)) ?
- sizeof(struct MetaData) : cdbsmi->len)
- );
- cdb_free(cdbsmi);
- return;
-}
-
-
-/*
- * PutMetaData() - (re)write supplementary record for a message
- */
-void PutMetaData(struct MetaData *smibuf)
-{
- long TheIndex;
-
- /* Use the negative of the message number for the metadata db index */
- TheIndex = (0L - smibuf->meta_msgnum);
-
- cdb_store(CDB_MSGMAIN,
- &TheIndex, (int)sizeof(long),
- smibuf, (int)sizeof(struct MetaData)
- );
-}
-
-
-/*
- * Convenience function to process a big block of AdjRefCount() operations
- */
-void AdjRefCountList(long *msgnum, long nmsg, int incr)
-{
- long i;
-
- for (i = 0; i < nmsg; i++) {
- AdjRefCount(msgnum[i], incr);
- }
-}
-
-
-/*
- * AdjRefCount - adjust the reference count for a message. We need to delete from disk any message whose reference count reaches zero.
- */
-void AdjRefCount(long msgnum, int incr)
-{
- struct MetaData smi;
- long delnum;
-
- /* This is a *tight* critical section; please keep it that way, as
- * it may get called while nested in other critical sections.
- * Complicating this any further will surely cause deadlock!
- */
- begin_critical_section(S_SUPPMSGMAIN);
- GetMetaData(&smi, msgnum);
- smi.meta_refcount += incr;
- PutMetaData(&smi);
- end_critical_section(S_SUPPMSGMAIN);
- syslog(LOG_DEBUG, "msgbase: AdjRefCount() msg %ld ref count delta %+d, is now %d", msgnum, incr, smi.meta_refcount);
-
- /* If the reference count is now zero, delete both the message and its metadata record.
- */
- if (smi.meta_refcount == 0) {
- syslog(LOG_DEBUG, "msgbase: deleting message <%ld>", msgnum);
-
- /* Call delete hooks with NULL room to show it has gone altogether */
- PerformDeleteHooks(NULL, msgnum);
-
- /* Remove from message base */
- delnum = msgnum;
- cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
- cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
-
- /* Remove metadata record */
- delnum = (0L - msgnum);
- cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
- }
-}
-
-
-/*
- * Write a generic object to this room
- *
- * Returns the message number of the written object, in case you need it.
- */
-long CtdlWriteObject(char *req_room, /* Room to stuff it in */
- char *content_type, /* MIME type of this object */
- char *raw_message, /* Data to be written */
- off_t raw_length, /* Size of raw_message */
- struct ctdluser *is_mailbox, /* Mailbox room? */
- int is_binary, /* Is encoding necessary? */
- unsigned int flags /* Internal save flags */
-) {
- struct ctdlroom qrbuf;
- char roomname[ROOMNAMELEN];
- struct CtdlMessage *msg;
- StrBuf *encoded_message = NULL;
-
- if (is_mailbox != NULL) {
- CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
- }
- else {
- safestrncpy(roomname, req_room, sizeof(roomname));
- }
-
- syslog(LOG_DEBUG, "msfbase: raw length is %ld", (long)raw_length);
-
- if (is_binary) {
- encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) );
- }
- else {
- encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096));
- }
-
- StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0);
- StrBufAppendBufPlain(encoded_message, content_type, -1, 0);
- StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0);
-
- if (is_binary) {
- StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0);
- }
- else {
- StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0);
- }
-
- if (is_binary) {
- StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0);
- }
- else {
- StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0);
- }
-
- syslog(LOG_DEBUG, "msgbase: allocating");
- msg = malloc(sizeof(struct CtdlMessage));
- memset(msg, 0, sizeof(struct CtdlMessage));
- msg->cm_magic = CTDLMESSAGE_MAGIC;
- msg->cm_anon_type = MES_NORMAL;
- msg->cm_format_type = 4;
- CM_SetField(msg, eAuthor, CC->user.fullname, -1);
- CM_SetField(msg, eOriginalRoom, req_room, -1);
- msg->cm_flags = flags;
-
- CM_SetAsFieldSB(msg, eMesageText, &encoded_message);
-
- /* Create the requested room if we have to. */
- if (CtdlGetRoom(&qrbuf, roomname) != 0) {
- CtdlCreateRoom(roomname, ( (is_mailbox != NULL) ? 5 : 3 ), "", 0, 1, 0, VIEW_BBS);
- }
-
- /* Now write the data */
- long new_msgnum = CtdlSubmitMsg(msg, NULL, roomname);
- CM_Free(msg);
- return new_msgnum;
-}
-
-
-/************************************************************************/
-/* MODULE INITIALIZATION */
-/************************************************************************/
-
-CTDL_MODULE_INIT(msgbase)
-{
- if (!threading) {
- FillMsgKeyLookupTable();
- }
-
- /* return our module id for the log */
- return "msgbase";
-}
+++ /dev/null
-
-#ifndef MSGBASE_H
-#define MSGBASE_H
-
-enum {
- MSGS_ALL,
- MSGS_OLD,
- MSGS_NEW,
- MSGS_FIRST,
- MSGS_LAST,
- MSGS_GT,
- MSGS_EQ,
- MSGS_SEARCH,
- MSGS_LT
-};
-
-enum {
- MSG_HDRS_BRIEF = 0,
- MSG_HDRS_ALL = 1,
- MSG_HDRS_EUID = 4,
- MSG_HDRS_BRIEFFILTER = 8,
- MSG_HDRS_THREADS = 9
-};
-
-/*
- * Possible return codes from CtdlOutputMsg()
- */
-enum {
- om_ok,
- om_not_logged_in,
- om_no_such_msg,
- om_mime_error,
- om_access_denied
-};
-
-/*
- * Values of "headers_only" when calling message output routines
- */
-#define HEADERS_ALL 0 /* Headers and body */
-#define HEADERS_ONLY 1 /* Headers only */
-#define HEADERS_NONE 2 /* Body only */
-#define HEADERS_FAST 3 /* Headers only with no MIME info */
-
-
-struct ma_info {
- int is_ma; /* Set to 1 if we are using this stuff */
- int freeze; /* Freeze the replacement chain because we're
- * digging through a subsection */
- int did_print; /* One alternative has been displayed */
- char chosen_part[128]; /* Which part of a m/a did we choose? */
- int chosen_pref; /* Chosen part preference level (lower is better) */
- int use_fo_hooks; /* Use fixed output hooks */
- int dont_decode; /* should we call the decoder or not? */
-};
-
-
-struct repl { /* Info for replication checking */
- char exclusive_id[SIZ];
- time_t highest;
-};
-
-
-/*
- * This is a list of "harvested" email addresses that we might want to
- * stick into someone's address book. But we defer this operaiton so
- * it can be done asynchronously.
- */
-struct addresses_to_be_filed {
- struct addresses_to_be_filed *next;
- char *roomname;
- char *collected_addresses;
-};
-
-extern struct addresses_to_be_filed *atbf;
-
-int GetFieldFromMnemonic(eMsgField *f, const char* c);
-void memfmout (char *mptr, const char *nl);
-void output_mime_parts(char *);
-long send_message (struct CtdlMessage *);
-void loadtroom (void);
-long CtdlSubmitMsg(struct CtdlMessage *, struct recptypes *, const char *);
-long quickie_message(const char *from, const char *fromaddr, const char *to, char *room, const char *text, int format_type, const char *subject);
-void GetMetaData(struct MetaData *, long);
-void PutMetaData(struct MetaData *);
-void AdjRefCount(long, int);
-void TDAP_AdjRefCount(long, int);
-int TDAP_ProcessAdjRefCountQueue(void);
-void simple_listing(long, void *);
-int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template);
-typedef void (*ForEachMsgCallback)(long MsgNumber, void *UserData);
-int CtdlForEachMessage(int mode,
- long ref,
- char *searchstring,
- char *content_type,
- struct CtdlMessage *compare,
- ForEachMsgCallback CallBack,
- void *userdata);
-int CtdlDeleteMessages(const char *, long *, int, char *);
-long CtdlWriteObject(char *req_room, /* Room to stuff it in */
- char *content_type, /* MIME type of this object */
- char *raw_message, /* Data to be written */
- off_t raw_length, /* Size of raw_message */
- struct ctdluser *is_mailbox, /* Mailbox room? */
- int is_binary, /* Is encoding necessary? */
- unsigned int flags /* Internal save flags */
-);
-struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body);
-struct CtdlMessage * CM_Duplicate
- (struct CtdlMessage *OrgMsg);
-int CM_IsEmpty (struct CtdlMessage *Msg, eMsgField which);
-void CM_SetField (struct CtdlMessage *Msg, eMsgField which, const char *buf, long length);
-void CM_SetFieldLONG (struct CtdlMessage *Msg, eMsgField which, long lvalue);
-void CM_CopyField (struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy);
-void CM_CutFieldAt (struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen);
-void CM_FlushField (struct CtdlMessage *Msg, eMsgField which);
-void CM_Flush (struct CtdlMessage *Msg);
-void CM_SetAsField (struct CtdlMessage *Msg, eMsgField which, char **buf, long length);
-void CM_SetAsFieldSB (struct CtdlMessage *Msg, eMsgField which, StrBuf **buf);
-void CM_GetAsField (struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen);
-void CM_PrependToField (struct CtdlMessage *Msg, eMsgField which, const char *buf, long length);
-
-void CM_Free (struct CtdlMessage *msg);
-void CM_FreeContents (struct CtdlMessage *msg);
-int CM_IsValidMsg (struct CtdlMessage *msg);
-
-#define CM_KEY(Message, Which) Message->cm_fields[Which], Message->cm_lengths[Which]
-#define CM_RANGE(Message, Which) Message->cm_fields[Which], \
- Message->cm_fields[Which] + Message->cm_lengths[Which]
-
-void CtdlSerializeMessage(struct ser_ret *, struct CtdlMessage *);
-struct CtdlMessage *CtdlDeserializeMessage(long msgnum, int with_body, const char *Buffer, long Length);
-void ReplicationChecks(struct CtdlMessage *);
-int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
- int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
-);
-int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check, struct CtdlMessage *msg);
-long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply);
-char *CtdlReadMessageBody(char *terminator, long tlen, size_t maxlen, StrBuf *exist, int crlf);
-StrBuf *CtdlReadMessageBodyBuf(
- char *terminator, /* token signalling EOT */
- long tlen,
- size_t maxlen, /* maximum message length */
- StrBuf *exist, /* if non-null, append to it; exist is ALWAYS freed */
- int crlf /* CRLF newlines instead of LF */
-);
-
-int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
- int mode, /* how would you like that message? */
- int headers_only, /* eschew the message body? */
- int do_proto, /* do Citadel protocol responses? */
- int crlf, /* 0=LF, 1=CRLF */
- char *section, /* output a message/rfc822 section */
- int flags, /* should the bessage be exported clean? */
- char **Author, /* if you want to know the author of the message... */
- char **Address, /* if you want to know the sender address of the message... */
- char **MessageID /* if you want to know the Message-ID of the message... */
-);
-
-/* Flags which may be passed to CtdlOutputMsg() and CtdlOutputPreLoadedMsg() */
-#define QP_EADDR (1<<0) /* quoted-printable encode email addresses */
-#define CRLF (1<<1)
-#define ESC_DOT (1<<2) /* output a line containing only "." as ".." instead */
-#define SUPPRESS_ENV_TO (1<<3) /* suppress Envelope-to: header (warning: destructive!) */
-
-int CtdlOutputPreLoadedMsg(struct CtdlMessage *,
- int mode, /* how would you like that message? */
- int headers_only, /* eschew the message body? */
- int do_proto, /* do Citadel protocol responses? */
- int crlf, /* 0=LF, 1=CRLF */
- int flags /* should the bessage be exported clean? */
-);
-
-
-/* values for which_set */
-enum {
- ctdlsetseen_seen,
- ctdlsetseen_answered
-};
-
-void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
- int target_setting, int which_set,
- struct ctdluser *which_user, struct ctdlroom *which_room
-);
-
-void CtdlGetSeen(char *buf, int which_set);
-
-
-struct CtdlMessage *CtdlMakeMessage(
- struct ctdluser *author, /* author's user structure */
- char *recipient, /* NULL if it's not mail */
- char *recp_cc, /* NULL if it's not mail */
- char *room, /* room where it's going */
- int type, /* see MES_ types in header file */
- int format_type, /* variformat, plain text, MIME... */
- char *fake_name, /* who we're masquerading as */
- char *my_email, /* which of my email addresses to use (empty is ok) */
- char *subject, /* Subject (optional) */
- char *supplied_euid, /* ...or NULL if this is irrelevant */
- char *preformatted_text, /* ...or NULL to read text from client */
- char *references /* Thread references */
-);
-
-struct CtdlMessage *CtdlMakeMessageLen(
- struct ctdluser *author, /* author's user structure */
- char *recipient, /* NULL if it's not mail */
- long rcplen,
- char *recp_cc, /* NULL if it's not mail */
- long cclen,
- char *room, /* room where it's going */
- long roomlen,
- int type, /* see MES_ types in header file */
- int format_type, /* variformat, plain text, MIME... */
- char *fake_name, /* who we're masquerading as */
- long fnlen,
- char *my_email, /* which of my email addresses to use (empty is ok) */
- long myelen,
- char *subject, /* Subject (optional) */
- long subjlen,
- char *supplied_euid, /* ...or NULL if this is irrelevant */
- long euidlen,
- char *preformatted_text, /* ...or NULL to read text from client */
- long textlen,
- char *references, /* Thread references */
- long reflen
-);
-
-void AdjRefCountList(long *msgnum, long nmsg, int incr);
-
-#endif /* MSGBASE_H */
+++ /dev/null
-// This module handles loading, saving, and parsing of room network configurations.
-//
-// Copyright (c) 2000-2021 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.
-
-#include "sysdep.h"
-#include <stdio.h>
-#include <sys/types.h>
-#include <dirent.h>
-
-#ifdef HAVE_SYSCALL_H
-# include <syscall.h>
-#else
-# if HAVE_SYS_SYSCALL_H
-# include <sys/syscall.h>
-# endif
-#endif
-#include <dirent.h>
-#include <assert.h>
-
-#include <libcitadel.h>
-
-#include "ctdl_module.h"
-#include "serv_extensions.h"
-#include "config.h"
-
-
-// Create a config key for a room's netconfig entry
-void netcfg_keyname(char *keybuf, long roomnum) {
- if (!keybuf) return;
- sprintf(keybuf, "c_netconfig_%010ld", roomnum);
-}
-
-
-// Given a room number and a textual netconfig, convert to base64 and write to the configdb
-void SaveRoomNetConfigFile(long roomnum, const char *raw_netconfig) {
- char keyname[25];
- char *enc;
- int enc_len;
- int len;
-
- len = strlen(raw_netconfig);
- netcfg_keyname(keyname, roomnum);
- enc = malloc(len * 2);
-
- if (enc) {
- enc_len = CtdlEncodeBase64(enc, raw_netconfig, len, 0);
- if ((enc_len > 1) && (enc[enc_len-2] == 13)) enc[enc_len-2] = 0;
- if ((enc_len > 0) && (enc[enc_len-1] == 10)) enc[enc_len-1] = 0;
- enc[enc_len] = 0;
- syslog(LOG_DEBUG, "netconfig: writing key '%s' (length=%d)", keyname, enc_len);
- CtdlSetConfigStr(keyname, enc);
- free(enc);
- }
-}
-
-
-// Given a room number, attempt to load the netconfig configdb entry for that room.
-// If it returns NULL, there is no netconfig.
-// Otherwise the caller owns the returned memory and is responsible for freeing it.
-char *LoadRoomNetConfigFile(long roomnum) {
- char keyname[25];
- char *encoded_netconfig = NULL;
- char *decoded_netconfig = NULL;
-
- netcfg_keyname(keyname, roomnum);
- encoded_netconfig = CtdlGetConfigStr(keyname);
- if (!encoded_netconfig) return NULL;
-
- decoded_netconfig = malloc(strlen(encoded_netconfig)); // yeah, way bigger than it needs to be, but safe
- CtdlDecodeBase64(decoded_netconfig, encoded_netconfig, strlen(encoded_netconfig));
- return decoded_netconfig;
-}
-
-
-//-----------------------------------------------------------------------------
-// Per room network configs : exchange with client
-//-----------------------------------------------------------------------------
-void cmd_gnet(char *argbuf) {
- if ( (CC->room.QRflags & QR_MAILBOX) && (CC->user.usernum == atol(CC->room.QRname)) ) {
- // users can edit the netconfigs for their own mailbox rooms
- }
- else if (CtdlAccessCheck(ac_room_aide)) return;
-
- cprintf("%d Network settings for room #%ld <%s>\n", LISTING_FOLLOWS, CC->room.QRnumber, CC->room.QRname);
-
- char *c = LoadRoomNetConfigFile(CC->room.QRnumber);
- if (c) {
- int len = strlen(c);
- client_write(c, len); // Can't use cprintf() here, it has a limit of 1024 bytes
- if (c[len] != '\n') {
- client_write(HKEY("\n"));
- }
- free(c);
- }
- cprintf("000\n");
-}
-
-
-void cmd_snet(char *argbuf) {
- StrBuf *Line = NULL;
- StrBuf *TheConfig = NULL;
- int rc;
-
- unbuffer_output();
- Line = NewStrBuf();
- TheConfig = NewStrBuf();
- cprintf("%d send new netconfig now\n", SEND_LISTING);
-
- while (rc = CtdlClientGetLine(Line), (rc >= 0)) {
- if ((rc == 3) && (strcmp(ChrPtr(Line), "000") == 0))
- break;
-
- StrBufAppendBuf(TheConfig, Line, 0);
- StrBufAppendBufPlain(TheConfig, HKEY("\n"), 0);
- }
- FreeStrBuf(&Line);
-
- begin_critical_section(S_NETCONFIGS);
- SaveRoomNetConfigFile(CC->room.QRnumber, ChrPtr(TheConfig));
- end_critical_section(S_NETCONFIGS);
- FreeStrBuf(&TheConfig);
-}
-
-
-// Convert any legacy configuration files in the "netconfigs" directory
-void convert_legacy_netcfg_files(void) {
- DIR *dh = NULL;
- struct dirent *dit = NULL;
- char filename[PATH_MAX];
- long roomnum;
- FILE *fp;
- long len;
- char *v;
-
- dh = opendir(ctdl_netcfg_dir);
- if (!dh) return;
-
- syslog(LOG_INFO, "netconfig: legacy netconfig files exist - converting them!");
-
- while (dit = readdir(dh), dit != NULL) { // yes, we use the non-reentrant version; we're not in threaded mode yet
- roomnum = atol(dit->d_name);
- if (roomnum > 0) {
- snprintf(filename, sizeof filename, "%s/%ld", ctdl_netcfg_dir, roomnum);
- fp = fopen(filename, "r");
- if (fp) {
- fseek(fp, 0L, SEEK_END);
- len = ftell(fp);
- if (len > 0) {
- v = malloc(len);
- if (v) {
- rewind(fp);
- if (fread(v, len, 1, fp)) {
- SaveRoomNetConfigFile(roomnum, v);
- unlink(filename);
- }
- free(v);
- }
- }
- else {
- unlink(filename); // zero length netconfig, just delete it
- }
- fclose(fp);
- }
- }
- }
-
- closedir(dh);
- rmdir(ctdl_netcfg_dir);
-}
-
-
-/*
- * Module entry point
- */
-CTDL_MODULE_INIT(netconfig)
-{
- if (!threading)
- {
- convert_legacy_netcfg_files();
- CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
- CtdlRegisterProtoHook(cmd_snet, "SNET", "Set network config");
- }
- return "netconfig";
-}
+++ /dev/null
-/* A Bison parser, made by GNU Bison 3.0.4. */
-
-/* Bison implementation for Yacc-like parsers in C
-
- Copyright (C) 1984, 1989-1990, 2000-2015 Free Software Foundation, Inc.
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- 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.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-/* As a special exception, you may create a larger work that contains
- part or all of the Bison parser skeleton and distribute that work
- under terms of your choice, so long as that work isn't itself a
- parser generator using the skeleton or a modified version thereof
- as a parser skeleton. Alternatively, if you modify or redistribute
- the parser skeleton itself, you may (at your option) remove this
- special exception, which will cause the skeleton and the resulting
- Bison output files to be licensed under the GNU General Public
- License without this special exception.
-
- This special exception was added by the Free Software Foundation in
- version 2.2 of Bison. */
-
-/* C LALR(1) parser skeleton written by Richard Stallman, by
- simplifying the original so-called "semantic" parser. */
-
-/* All symbols defined below should begin with yy or YY, to avoid
- infringing on user name space. This should be done even for local
- variables, as they might otherwise be expanded by user macros.
- There are some unavoidable exceptions within include files to
- define necessary library symbols; they are noted "INFRINGES ON
- USER NAME SPACE" below. */
-
-/* Identify Bison output. */
-#define YYBISON 1
-
-/* Bison version. */
-#define YYBISON_VERSION "3.0.4"
-
-/* Skeleton name. */
-#define YYSKELETON_NAME "yacc.c"
-
-/* Pure parsers. */
-#define YYPURE 0
-
-/* Push parsers. */
-#define YYPUSH 0
-
-/* Pull parsers. */
-#define YYPULL 1
-
-
-
-
-/* Copy the first part of user declarations. */
-#line 1 "parsedate.y" /* yacc.c:339 */
-
-/* $Revision$
-**
-** Originally written by Steven M. Bellovin <smb@research.att.com> while
-** at the University of North Carolina at Chapel Hill. Later tweaked by
-** a couple of people on Usenet. Completely overhauled by Rich $alz
-** <rsalz@osf.org> and Jim Berets <jberets@bbn.com> in August, 1990.
-** Further revised (removed obsolete constructs and cleaned up timezone
-** names) in August, 1991, by Rich. Paul Eggert <eggert@twinsun.com>
-** helped in September, 1992. Art Cancro <ajc@citadel.org> cleaned
-** it up for ANSI C in December, 1999.
-**
-** This grammar has six shift/reduce conflicts.
-**
-** This code is in the public domain and has no copyright.
-*/
-/* SUPPRESS 530 *//* Empty body for statement */
-/* SUPPRESS 593 on yyerrlab *//* Label was not used */
-/* SUPPRESS 593 on yynewstate *//* Label was not used */
-/* SUPPRESS 595 on yypvt *//* Automatic variable may be used before set */
-
-#include "sysdep.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/types.h>
-#include <ctype.h>
-#include <time.h>
-#if HAVE_STRING_H
-# if !STDC_HEADERS && HAVE_MEMORY_H
-# include <memory.h>
-# endif
-# include <string.h>
-#endif
-#if HAVE_STRINGS_H
-# include <strings.h>
-#endif
-
-#include "parsedate.h"
-
-int date_lex(void);
-
-#define yyparse date_parse
-#define yylex date_lex
-#define yyerror date_error
-
-
- /* See the LeapYears table in Convert. */
-#define EPOCH 1970
-#define END_OF_TIME 2038
- /* Constants for general time calculations. */
-#define DST_OFFSET 1
-#define SECSPERDAY (24L * 60L * 60L)
- /* Readability for TABLE stuff. */
-#define HOUR(x) (x * 60)
-
-#define LPAREN '('
-#define RPAREN ')'
-#define IS7BIT(x) ((unsigned int)(x) < 0200)
-
-#define SIZEOF(array) ((int)(sizeof array / sizeof array[0]))
-#define ENDOF(array) (&array[SIZEOF(array)])
-
-
-/*
-** An entry in the lexical lookup table.
-*/
-typedef struct _TABLE {
- char *name;
- int type;
- time_t value;
-} TABLE;
-
-/*
-** Daylight-savings mode: on, off, or not yet known.
-*/
-typedef enum _DSTMODE {
- DSTon, DSToff, DSTmaybe
-} DSTMODE;
-
-/*
-** Meridian: am, pm, or 24-hour style.
-*/
-typedef enum _MERIDIAN {
- MERam, MERpm, MER24
-} MERIDIAN;
-
-
-/*
-** Global variables. We could get rid of most of them by using a yacc
-** union, but this is more efficient. (This routine predates the
-** yacc %union construct.)
-*/
-static const char *yyInput;
-static DSTMODE yyDSTmode;
-static int yyHaveDate;
-static int yyHaveRel;
-static int yyHaveTime;
-static time_t yyTimezone;
-static time_t yyDay;
-static time_t yyHour;
-static time_t yyMinutes;
-static time_t yyMonth;
-static time_t yySeconds;
-static time_t yyYear;
-static MERIDIAN yyMeridian;
-static time_t yyRelMonth;
-static time_t yyRelSeconds;
-
-
-static void date_error(char *);
-
-#line 179 "y.tab.c" /* yacc.c:339 */
-
-# ifndef YY_NULLPTR
-# if defined __cplusplus && 201103L <= __cplusplus
-# define YY_NULLPTR nullptr
-# else
-# define YY_NULLPTR 0
-# endif
-# endif
-
-/* Enabling verbose error messages. */
-#ifdef YYERROR_VERBOSE
-# undef YYERROR_VERBOSE
-# define YYERROR_VERBOSE 1
-#else
-# define YYERROR_VERBOSE 0
-#endif
-
-
-/* Debug traces. */
-#ifndef YYDEBUG
-# define YYDEBUG 0
-#endif
-#if YYDEBUG
-extern int yydebug;
-#endif
-
-/* Token type. */
-#ifndef YYTOKENTYPE
-# define YYTOKENTYPE
- enum yytokentype
- {
- tDAY = 258,
- tDAYZONE = 259,
- tMERIDIAN = 260,
- tMONTH = 261,
- tMONTH_UNIT = 262,
- tSEC_UNIT = 263,
- tSNUMBER = 264,
- tUNUMBER = 265,
- tZONE = 266
- };
-#endif
-/* Tokens. */
-#define tDAY 258
-#define tDAYZONE 259
-#define tMERIDIAN 260
-#define tMONTH 261
-#define tMONTH_UNIT 262
-#define tSEC_UNIT 263
-#define tSNUMBER 264
-#define tUNUMBER 265
-#define tZONE 266
-
-/* Value type. */
-#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
-
-union YYSTYPE
-{
-#line 114 "parsedate.y" /* yacc.c:355 */
-
- time_t Number;
- enum _MERIDIAN Meridian;
-
-#line 243 "y.tab.c" /* yacc.c:355 */
-};
-
-typedef union YYSTYPE YYSTYPE;
-# define YYSTYPE_IS_TRIVIAL 1
-# define YYSTYPE_IS_DECLARED 1
-#endif
-
-
-extern YYSTYPE yylval;
-
-int yyparse (void);
-
-
-
-/* Copy the second part of user declarations. */
-
-#line 260 "y.tab.c" /* yacc.c:358 */
-
-#ifdef short
-# undef short
-#endif
-
-#ifdef YYTYPE_UINT8
-typedef YYTYPE_UINT8 yytype_uint8;
-#else
-typedef unsigned char yytype_uint8;
-#endif
-
-#ifdef YYTYPE_INT8
-typedef YYTYPE_INT8 yytype_int8;
-#else
-typedef signed char yytype_int8;
-#endif
-
-#ifdef YYTYPE_UINT16
-typedef YYTYPE_UINT16 yytype_uint16;
-#else
-typedef unsigned short int yytype_uint16;
-#endif
-
-#ifdef YYTYPE_INT16
-typedef YYTYPE_INT16 yytype_int16;
-#else
-typedef short int yytype_int16;
-#endif
-
-#ifndef YYSIZE_T
-# ifdef __SIZE_TYPE__
-# define YYSIZE_T __SIZE_TYPE__
-# elif defined size_t
-# define YYSIZE_T size_t
-# elif ! defined YYSIZE_T
-# include <stddef.h> /* INFRINGES ON USER NAME SPACE */
-# define YYSIZE_T size_t
-# else
-# define YYSIZE_T unsigned int
-# endif
-#endif
-
-#define YYSIZE_MAXIMUM ((YYSIZE_T) -1)
-
-#ifndef YY_
-# if defined YYENABLE_NLS && YYENABLE_NLS
-# if ENABLE_NLS
-# include <libintl.h> /* INFRINGES ON USER NAME SPACE */
-# define YY_(Msgid) dgettext ("bison-runtime", Msgid)
-# endif
-# endif
-# ifndef YY_
-# define YY_(Msgid) Msgid
-# endif
-#endif
-
-#ifndef YY_ATTRIBUTE
-# if (defined __GNUC__ \
- && (2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__))) \
- || defined __SUNPRO_C && 0x5110 <= __SUNPRO_C
-# define YY_ATTRIBUTE(Spec) __attribute__(Spec)
-# else
-# define YY_ATTRIBUTE(Spec) /* empty */
-# endif
-#endif
-
-#ifndef YY_ATTRIBUTE_PURE
-# define YY_ATTRIBUTE_PURE YY_ATTRIBUTE ((__pure__))
-#endif
-
-#ifndef YY_ATTRIBUTE_UNUSED
-# define YY_ATTRIBUTE_UNUSED YY_ATTRIBUTE ((__unused__))
-#endif
-
-#if !defined _Noreturn \
- && (!defined __STDC_VERSION__ || __STDC_VERSION__ < 201112)
-# if defined _MSC_VER && 1200 <= _MSC_VER
-# define _Noreturn __declspec (noreturn)
-# else
-# define _Noreturn YY_ATTRIBUTE ((__noreturn__))
-# endif
-#endif
-
-/* Suppress unused-variable warnings by "using" E. */
-#if ! defined lint || defined __GNUC__
-# define YYUSE(E) ((void) (E))
-#else
-# define YYUSE(E) /* empty */
-#endif
-
-#if defined __GNUC__ && 407 <= __GNUC__ * 100 + __GNUC_MINOR__
-/* Suppress an incorrect diagnostic about yylval being uninitialized. */
-# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
- _Pragma ("GCC diagnostic push") \
- _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")\
- _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
-# define YY_IGNORE_MAYBE_UNINITIALIZED_END \
- _Pragma ("GCC diagnostic pop")
-#else
-# define YY_INITIAL_VALUE(Value) Value
-#endif
-#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
-# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
-# define YY_IGNORE_MAYBE_UNINITIALIZED_END
-#endif
-#ifndef YY_INITIAL_VALUE
-# define YY_INITIAL_VALUE(Value) /* Nothing. */
-#endif
-
-
-#if ! defined yyoverflow || YYERROR_VERBOSE
-
-/* The parser invokes alloca or malloc; define the necessary symbols. */
-
-# ifdef YYSTACK_USE_ALLOCA
-# if YYSTACK_USE_ALLOCA
-# ifdef __GNUC__
-# define YYSTACK_ALLOC __builtin_alloca
-# elif defined __BUILTIN_VA_ARG_INCR
-# include <alloca.h> /* INFRINGES ON USER NAME SPACE */
-# elif defined _AIX
-# define YYSTACK_ALLOC __alloca
-# elif defined _MSC_VER
-# include <malloc.h> /* INFRINGES ON USER NAME SPACE */
-# define alloca _alloca
-# else
-# define YYSTACK_ALLOC alloca
-# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS
-# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
- /* Use EXIT_SUCCESS as a witness for stdlib.h. */
-# ifndef EXIT_SUCCESS
-# define EXIT_SUCCESS 0
-# endif
-# endif
-# endif
-# endif
-# endif
-
-# ifdef YYSTACK_ALLOC
- /* Pacify GCC's 'empty if-body' warning. */
-# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0)
-# ifndef YYSTACK_ALLOC_MAXIMUM
- /* The OS might guarantee only one guard page at the bottom of the stack,
- and a page size can be as small as 4096 bytes. So we cannot safely
- invoke alloca (N) if N exceeds 4096. Use a slightly smaller number
- to allow for a few compiler-allocated temporary stack slots. */
-# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */
-# endif
-# else
-# define YYSTACK_ALLOC YYMALLOC
-# define YYSTACK_FREE YYFREE
-# ifndef YYSTACK_ALLOC_MAXIMUM
-# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM
-# endif
-# if (defined __cplusplus && ! defined EXIT_SUCCESS \
- && ! ((defined YYMALLOC || defined malloc) \
- && (defined YYFREE || defined free)))
-# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
-# ifndef EXIT_SUCCESS
-# define EXIT_SUCCESS 0
-# endif
-# endif
-# ifndef YYMALLOC
-# define YYMALLOC malloc
-# if ! defined malloc && ! defined EXIT_SUCCESS
-void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */
-# endif
-# endif
-# ifndef YYFREE
-# define YYFREE free
-# if ! defined free && ! defined EXIT_SUCCESS
-void free (void *); /* INFRINGES ON USER NAME SPACE */
-# endif
-# endif
-# endif
-#endif /* ! defined yyoverflow || YYERROR_VERBOSE */
-
-
-#if (! defined yyoverflow \
- && (! defined __cplusplus \
- || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL)))
-
-/* A type that is properly aligned for any stack member. */
-union yyalloc
-{
- yytype_int16 yyss_alloc;
- YYSTYPE yyvs_alloc;
-};
-
-/* The size of the maximum gap between one aligned stack and the next. */
-# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1)
-
-/* The size of an array large to enough to hold all stacks, each with
- N elements. */
-# define YYSTACK_BYTES(N) \
- ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE)) \
- + YYSTACK_GAP_MAXIMUM)
-
-# define YYCOPY_NEEDED 1
-
-/* Relocate STACK from its old location to the new one. The
- local variables YYSIZE and YYSTACKSIZE give the old and new number of
- elements in the stack, and YYPTR gives the new location of the
- stack. Advance YYPTR to a properly aligned location for the next
- stack. */
-# define YYSTACK_RELOCATE(Stack_alloc, Stack) \
- do \
- { \
- YYSIZE_T yynewbytes; \
- YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \
- Stack = &yyptr->Stack_alloc; \
- yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \
- yyptr += yynewbytes / sizeof (*yyptr); \
- } \
- while (0)
-
-#endif
-
-#if defined YYCOPY_NEEDED && YYCOPY_NEEDED
-/* Copy COUNT objects from SRC to DST. The source and destination do
- not overlap. */
-# ifndef YYCOPY
-# if defined __GNUC__ && 1 < __GNUC__
-# define YYCOPY(Dst, Src, Count) \
- __builtin_memcpy (Dst, Src, (Count) * sizeof (*(Src)))
-# else
-# define YYCOPY(Dst, Src, Count) \
- do \
- { \
- YYSIZE_T yyi; \
- for (yyi = 0; yyi < (Count); yyi++) \
- (Dst)[yyi] = (Src)[yyi]; \
- } \
- while (0)
-# endif
-# endif
-#endif /* !YYCOPY_NEEDED */
-
-/* YYFINAL -- State number of the termination state. */
-#define YYFINAL 2
-/* YYLAST -- Last index in YYTABLE. */
-#define YYLAST 40
-
-/* YYNTOKENS -- Number of terminals. */
-#define YYNTOKENS 15
-/* YYNNTS -- Number of nonterminals. */
-#define YYNNTS 9
-/* YYNRULES -- Number of rules. */
-#define YYNRULES 30
-/* YYNSTATES -- Number of states. */
-#define YYNSTATES 44
-
-/* YYTRANSLATE[YYX] -- Symbol number corresponding to YYX as returned
- by yylex, with out-of-bounds checking. */
-#define YYUNDEFTOK 2
-#define YYMAXUTOK 266
-
-#define YYTRANSLATE(YYX) \
- ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK)
-
-/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM
- as returned by yylex, without out-of-bounds checking. */
-static const yytype_uint8 yytranslate[] =
-{
- 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 14, 2, 2, 13, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 12, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 1, 2, 3, 4,
- 5, 6, 7, 8, 9, 10, 11
-};
-
-#if YYDEBUG
- /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */
-static const yytype_uint16 yyrline[] =
-{
- 0, 128, 128, 129, 132, 141, 145, 148, 153, 165,
- 171, 178, 184, 194, 198, 202, 210, 216, 237, 241,
- 253, 257, 262, 266, 271, 278, 281, 284, 287, 292,
- 295
-};
-#endif
-
-#if YYDEBUG || YYERROR_VERBOSE || 0
-/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
- First, the terminals, then, starting at YYNTOKENS, nonterminals. */
-static const char *const yytname[] =
-{
- "$end", "error", "$undefined", "tDAY", "tDAYZONE", "tMERIDIAN",
- "tMONTH", "tMONTH_UNIT", "tSEC_UNIT", "tSNUMBER", "tUNUMBER", "tZONE",
- "':'", "'/'", "','", "$accept", "spec", "item", "time", "zone",
- "numzone", "date", "rel", "o_merid", YY_NULLPTR
-};
-#endif
-
-# ifdef YYPRINT
-/* YYTOKNUM[NUM] -- (External) token number corresponding to the
- (internal) symbol number NUM (which must be that of a token). */
-static const yytype_uint16 yytoknum[] =
-{
- 0, 256, 257, 258, 259, 260, 261, 262, 263, 264,
- 265, 266, 58, 47, 44
-};
-# endif
-
-#define YYPACT_NINF -29
-
-#define yypact_value_is_default(Yystate) \
- (!!((Yystate) == (-29)))
-
-#define YYTABLE_NINF -1
-
-#define yytable_value_is_error(Yytable_value) \
- 0
-
- /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
- STATE-NUM. */
-static const yytype_int8 yypact[] =
-{
- -29, 1, -29, -11, 11, 20, 12, -29, 4, -29,
- -29, 13, 16, -29, -29, -29, 21, -29, -29, 22,
- 23, -29, -29, -29, 5, -29, -29, 28, 25, -29,
- 17, 24, -29, 26, -29, 29, -29, -29, 30, -29,
- 0, -29, -29, -29
-};
-
- /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM.
- Performed when YYTABLE does not specify something else to do. Zero
- means the default is an error. */
-static const yytype_uint8 yydefact[] =
-{
- 2, 0, 1, 0, 0, 0, 29, 3, 4, 6,
- 7, 0, 20, 27, 25, 30, 22, 28, 26, 0,
- 0, 8, 14, 17, 13, 5, 16, 0, 0, 23,
- 29, 18, 15, 0, 21, 0, 10, 9, 0, 24,
- 29, 19, 12, 11
-};
-
- /* YYPGOTO[NTERM-NUM]. */
-static const yytype_int8 yypgoto[] =
-{
- -29, -29, -29, -29, -29, -24, -29, -29, -28
-};
-
- /* YYDEFGOTO[NTERM-NUM]. */
-static const yytype_int8 yydefgoto[] =
-{
- -1, 1, 7, 8, 25, 26, 9, 10, 21
-};
-
- /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If
- positive, shift that token. If negative, reduce the rule whose
- number is the opposite. If YYTABLE_NINF, syntax error. */
-static const yytype_uint8 yytable[] =
-{
- 32, 2, 37, 11, 3, 15, 36, 4, 22, 23,
- 5, 6, 43, 23, 23, 24, 42, 15, 16, 17,
- 18, 12, 15, 27, 19, 20, 23, 13, 14, 35,
- 28, 29, 30, 31, 33, 34, 39, 38, 0, 40,
- 41
-};
-
-static const yytype_int8 yycheck[] =
-{
- 24, 0, 30, 14, 3, 5, 30, 6, 4, 9,
- 9, 10, 40, 9, 9, 11, 40, 5, 6, 7,
- 8, 10, 5, 10, 12, 13, 9, 7, 8, 12,
- 14, 10, 10, 10, 6, 10, 10, 13, -1, 10,
- 10
-};
-
- /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing
- symbol of state STATE-NUM. */
-static const yytype_uint8 yystos[] =
-{
- 0, 16, 0, 3, 6, 9, 10, 17, 18, 21,
- 22, 14, 10, 7, 8, 5, 6, 7, 8, 12,
- 13, 23, 4, 9, 11, 19, 20, 10, 14, 10,
- 10, 10, 20, 6, 10, 12, 20, 23, 13, 10,
- 10, 10, 20, 23
-};
-
- /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */
-static const yytype_uint8 yyr1[] =
-{
- 0, 15, 16, 16, 17, 17, 17, 17, 18, 18,
- 18, 18, 18, 19, 19, 19, 19, 20, 21, 21,
- 21, 21, 21, 21, 21, 22, 22, 22, 22, 23,
- 23
-};
-
- /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */
-static const yytype_uint8 yyr2[] =
-{
- 0, 2, 0, 2, 1, 2, 1, 1, 2, 4,
- 4, 6, 6, 1, 1, 2, 1, 1, 3, 5,
- 2, 4, 2, 3, 5, 2, 2, 2, 2, 0,
- 1
-};
-
-
-#define yyerrok (yyerrstatus = 0)
-#define yyclearin (yychar = YYEMPTY)
-#define YYEMPTY (-2)
-#define YYEOF 0
-
-#define YYACCEPT goto yyacceptlab
-#define YYABORT goto yyabortlab
-#define YYERROR goto yyerrorlab
-
-
-#define YYRECOVERING() (!!yyerrstatus)
-
-#define YYBACKUP(Token, Value) \
-do \
- if (yychar == YYEMPTY) \
- { \
- yychar = (Token); \
- yylval = (Value); \
- YYPOPSTACK (yylen); \
- yystate = *yyssp; \
- goto yybackup; \
- } \
- else \
- { \
- yyerror (YY_("syntax error: cannot back up")); \
- YYERROR; \
- } \
-while (0)
-
-/* Error token number */
-#define YYTERROR 1
-#define YYERRCODE 256
-
-
-
-/* Enable debugging if requested. */
-#if YYDEBUG
-
-# ifndef YYFPRINTF
-# include <stdio.h> /* INFRINGES ON USER NAME SPACE */
-# define YYFPRINTF fprintf
-# endif
-
-# define YYDPRINTF(Args) \
-do { \
- if (yydebug) \
- YYFPRINTF Args; \
-} while (0)
-
-/* This macro is provided for backward compatibility. */
-#ifndef YY_LOCATION_PRINT
-# define YY_LOCATION_PRINT(File, Loc) ((void) 0)
-#endif
-
-
-# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \
-do { \
- if (yydebug) \
- { \
- YYFPRINTF (stderr, "%s ", Title); \
- yy_symbol_print (stderr, \
- Type, Value); \
- YYFPRINTF (stderr, "\n"); \
- } \
-} while (0)
-
-
-/*----------------------------------------.
-| Print this symbol's value on YYOUTPUT. |
-`----------------------------------------*/
-
-static void
-yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep)
-{
- FILE *yyo = yyoutput;
- YYUSE (yyo);
- if (!yyvaluep)
- return;
-# ifdef YYPRINT
- if (yytype < YYNTOKENS)
- YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep);
-# endif
- YYUSE (yytype);
-}
-
-
-/*--------------------------------.
-| Print this symbol on YYOUTPUT. |
-`--------------------------------*/
-
-static void
-yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep)
-{
- YYFPRINTF (yyoutput, "%s %s (",
- yytype < YYNTOKENS ? "token" : "nterm", yytname[yytype]);
-
- yy_symbol_value_print (yyoutput, yytype, yyvaluep);
- YYFPRINTF (yyoutput, ")");
-}
-
-/*------------------------------------------------------------------.
-| yy_stack_print -- Print the state stack from its BOTTOM up to its |
-| TOP (included). |
-`------------------------------------------------------------------*/
-
-static void
-yy_stack_print (yytype_int16 *yybottom, yytype_int16 *yytop)
-{
- YYFPRINTF (stderr, "Stack now");
- for (; yybottom <= yytop; yybottom++)
- {
- int yybot = *yybottom;
- YYFPRINTF (stderr, " %d", yybot);
- }
- YYFPRINTF (stderr, "\n");
-}
-
-# define YY_STACK_PRINT(Bottom, Top) \
-do { \
- if (yydebug) \
- yy_stack_print ((Bottom), (Top)); \
-} while (0)
-
-
-/*------------------------------------------------.
-| Report that the YYRULE is going to be reduced. |
-`------------------------------------------------*/
-
-static void
-yy_reduce_print (yytype_int16 *yyssp, YYSTYPE *yyvsp, int yyrule)
-{
- unsigned long int yylno = yyrline[yyrule];
- int yynrhs = yyr2[yyrule];
- int yyi;
- YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n",
- yyrule - 1, yylno);
- /* The symbols being reduced. */
- for (yyi = 0; yyi < yynrhs; yyi++)
- {
- YYFPRINTF (stderr, " $%d = ", yyi + 1);
- yy_symbol_print (stderr,
- yystos[yyssp[yyi + 1 - yynrhs]],
- &(yyvsp[(yyi + 1) - (yynrhs)])
- );
- YYFPRINTF (stderr, "\n");
- }
-}
-
-# define YY_REDUCE_PRINT(Rule) \
-do { \
- if (yydebug) \
- yy_reduce_print (yyssp, yyvsp, Rule); \
-} while (0)
-
-/* Nonzero means print parse trace. It is left uninitialized so that
- multiple parsers can coexist. */
-int yydebug;
-#else /* !YYDEBUG */
-# define YYDPRINTF(Args)
-# define YY_SYMBOL_PRINT(Title, Type, Value, Location)
-# define YY_STACK_PRINT(Bottom, Top)
-# define YY_REDUCE_PRINT(Rule)
-#endif /* !YYDEBUG */
-
-
-/* YYINITDEPTH -- initial size of the parser's stacks. */
-#ifndef YYINITDEPTH
-# define YYINITDEPTH 200
-#endif
-
-/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only
- if the built-in stack extension method is used).
-
- Do not make this value too large; the results are undefined if
- YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH)
- evaluated with infinite-precision integer arithmetic. */
-
-#ifndef YYMAXDEPTH
-# define YYMAXDEPTH 10000
-#endif
-
-
-#if YYERROR_VERBOSE
-
-# ifndef yystrlen
-# if defined __GLIBC__ && defined _STRING_H
-# define yystrlen strlen
-# else
-/* Return the length of YYSTR. */
-static YYSIZE_T
-yystrlen (const char *yystr)
-{
- YYSIZE_T yylen;
- for (yylen = 0; yystr[yylen]; yylen++)
- continue;
- return yylen;
-}
-# endif
-# endif
-
-# ifndef yystpcpy
-# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE
-# define yystpcpy stpcpy
-# else
-/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in
- YYDEST. */
-static char *
-yystpcpy (char *yydest, const char *yysrc)
-{
- char *yyd = yydest;
- const char *yys = yysrc;
-
- while ((*yyd++ = *yys++) != '\0')
- continue;
-
- return yyd - 1;
-}
-# endif
-# endif
-
-# ifndef yytnamerr
-/* Copy to YYRES the contents of YYSTR after stripping away unnecessary
- quotes and backslashes, so that it's suitable for yyerror. The
- heuristic is that double-quoting is unnecessary unless the string
- contains an apostrophe, a comma, or backslash (other than
- backslash-backslash). YYSTR is taken from yytname. If YYRES is
- null, do not copy; instead, return the length of what the result
- would have been. */
-static YYSIZE_T
-yytnamerr (char *yyres, const char *yystr)
-{
- if (*yystr == '"')
- {
- YYSIZE_T yyn = 0;
- char const *yyp = yystr;
-
- for (;;)
- switch (*++yyp)
- {
- case '\'':
- case ',':
- goto do_not_strip_quotes;
-
- case '\\':
- if (*++yyp != '\\')
- goto do_not_strip_quotes;
- /* Fall through. */
- default:
- if (yyres)
- yyres[yyn] = *yyp;
- yyn++;
- break;
-
- case '"':
- if (yyres)
- yyres[yyn] = '\0';
- return yyn;
- }
- do_not_strip_quotes: ;
- }
-
- if (! yyres)
- return yystrlen (yystr);
-
- return yystpcpy (yyres, yystr) - yyres;
-}
-# endif
-
-/* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message
- about the unexpected token YYTOKEN for the state stack whose top is
- YYSSP.
-
- Return 0 if *YYMSG was successfully written. Return 1 if *YYMSG is
- not large enough to hold the message. In that case, also set
- *YYMSG_ALLOC to the required number of bytes. Return 2 if the
- required number of bytes is too large to store. */
-static int
-yysyntax_error (YYSIZE_T *yymsg_alloc, char **yymsg,
- yytype_int16 *yyssp, int yytoken)
-{
- YYSIZE_T yysize0 = yytnamerr (YY_NULLPTR, yytname[yytoken]);
- YYSIZE_T yysize = yysize0;
- enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 };
- /* Internationalized format string. */
- const char *yyformat = YY_NULLPTR;
- /* Arguments of yyformat. */
- char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM];
- /* Number of reported tokens (one for the "unexpected", one per
- "expected"). */
- int yycount = 0;
-
- /* There are many possibilities here to consider:
- - If this state is a consistent state with a default action, then
- the only way this function was invoked is if the default action
- is an error action. In that case, don't check for expected
- tokens because there are none.
- - The only way there can be no lookahead present (in yychar) is if
- this state is a consistent state with a default action. Thus,
- detecting the absence of a lookahead is sufficient to determine
- that there is no unexpected or expected token to report. In that
- case, just report a simple "syntax error".
- - Don't assume there isn't a lookahead just because this state is a
- consistent state with a default action. There might have been a
- previous inconsistent state, consistent state with a non-default
- action, or user semantic action that manipulated yychar.
- - Of course, the expected token list depends on states to have
- correct lookahead information, and it depends on the parser not
- to perform extra reductions after fetching a lookahead from the
- scanner and before detecting a syntax error. Thus, state merging
- (from LALR or IELR) and default reductions corrupt the expected
- token list. However, the list is correct for canonical LR with
- one exception: it will still contain any token that will not be
- accepted due to an error action in a later state.
- */
- if (yytoken != YYEMPTY)
- {
- int yyn = yypact[*yyssp];
- yyarg[yycount++] = yytname[yytoken];
- if (!yypact_value_is_default (yyn))
- {
- /* Start YYX at -YYN if negative to avoid negative indexes in
- YYCHECK. In other words, skip the first -YYN actions for
- this state because they are default actions. */
- int yyxbegin = yyn < 0 ? -yyn : 0;
- /* Stay within bounds of both yycheck and yytname. */
- int yychecklim = YYLAST - yyn + 1;
- int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS;
- int yyx;
-
- for (yyx = yyxbegin; yyx < yyxend; ++yyx)
- if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR
- && !yytable_value_is_error (yytable[yyx + yyn]))
- {
- if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM)
- {
- yycount = 1;
- yysize = yysize0;
- break;
- }
- yyarg[yycount++] = yytname[yyx];
- {
- YYSIZE_T yysize1 = yysize + yytnamerr (YY_NULLPTR, yytname[yyx]);
- if (! (yysize <= yysize1
- && yysize1 <= YYSTACK_ALLOC_MAXIMUM))
- return 2;
- yysize = yysize1;
- }
- }
- }
- }
-
- switch (yycount)
- {
-# define YYCASE_(N, S) \
- case N: \
- yyformat = S; \
- break
- YYCASE_(0, YY_("syntax error"));
- YYCASE_(1, YY_("syntax error, unexpected %s"));
- YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s"));
- YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s"));
- YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s"));
- YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"));
-# undef YYCASE_
- }
-
- {
- YYSIZE_T yysize1 = yysize + yystrlen (yyformat);
- if (! (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM))
- return 2;
- yysize = yysize1;
- }
-
- if (*yymsg_alloc < yysize)
- {
- *yymsg_alloc = 2 * yysize;
- if (! (yysize <= *yymsg_alloc
- && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM))
- *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM;
- return 1;
- }
-
- /* Avoid sprintf, as that infringes on the user's name space.
- Don't have undefined behavior even if the translation
- produced a string with the wrong number of "%s"s. */
- {
- char *yyp = *yymsg;
- int yyi = 0;
- while ((*yyp = *yyformat) != '\0')
- if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount)
- {
- yyp += yytnamerr (yyp, yyarg[yyi++]);
- yyformat += 2;
- }
- else
- {
- yyp++;
- yyformat++;
- }
- }
- return 0;
-}
-#endif /* YYERROR_VERBOSE */
-
-/*-----------------------------------------------.
-| Release the memory associated to this symbol. |
-`-----------------------------------------------*/
-
-static void
-yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep)
-{
- YYUSE (yyvaluep);
- if (!yymsg)
- yymsg = "Deleting";
- YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp);
-
- YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
- YYUSE (yytype);
- YY_IGNORE_MAYBE_UNINITIALIZED_END
-}
-
-
-
-
-/* The lookahead symbol. */
-int yychar;
-
-/* The semantic value of the lookahead symbol. */
-YYSTYPE yylval;
-/* Number of syntax errors so far. */
-int yynerrs;
-
-
-/*----------.
-| yyparse. |
-`----------*/
-
-int
-yyparse (void)
-{
- int yystate;
- /* Number of tokens to shift before error messages enabled. */
- int yyerrstatus;
-
- /* The stacks and their tools:
- 'yyss': related to states.
- 'yyvs': related to semantic values.
-
- Refer to the stacks through separate pointers, to allow yyoverflow
- to reallocate them elsewhere. */
-
- /* The state stack. */
- yytype_int16 yyssa[YYINITDEPTH];
- yytype_int16 *yyss;
- yytype_int16 *yyssp;
-
- /* The semantic value stack. */
- YYSTYPE yyvsa[YYINITDEPTH];
- YYSTYPE *yyvs;
- YYSTYPE *yyvsp;
-
- YYSIZE_T yystacksize;
-
- int yyn;
- int yyresult;
- /* Lookahead token as an internal (translated) token number. */
- int yytoken = 0;
- /* The variables used to return semantic value and location from the
- action routines. */
- YYSTYPE yyval;
-
-#if YYERROR_VERBOSE
- /* Buffer for error messages, and its allocated size. */
- char yymsgbuf[128];
- char *yymsg = yymsgbuf;
- YYSIZE_T yymsg_alloc = sizeof yymsgbuf;
-#endif
-
-#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N))
-
- /* The number of symbols on the RHS of the reduced rule.
- Keep to zero when no symbol should be popped. */
- int yylen = 0;
-
- yyssp = yyss = yyssa;
- yyvsp = yyvs = yyvsa;
- yystacksize = YYINITDEPTH;
-
- YYDPRINTF ((stderr, "Starting parse\n"));
-
- yystate = 0;
- yyerrstatus = 0;
- yynerrs = 0;
- yychar = YYEMPTY; /* Cause a token to be read. */
- goto yysetstate;
-
-/*------------------------------------------------------------.
-| yynewstate -- Push a new state, which is found in yystate. |
-`------------------------------------------------------------*/
- yynewstate:
- /* In all cases, when you get here, the value and location stacks
- have just been pushed. So pushing a state here evens the stacks. */
- yyssp++;
-
- yysetstate:
- *yyssp = yystate;
-
- if (yyss + yystacksize - 1 <= yyssp)
- {
- /* Get the current used size of the three stacks, in elements. */
- YYSIZE_T yysize = yyssp - yyss + 1;
-
-#ifdef yyoverflow
- {
- /* Give user a chance to reallocate the stack. Use copies of
- these so that the &'s don't force the real ones into
- memory. */
- YYSTYPE *yyvs1 = yyvs;
- yytype_int16 *yyss1 = yyss;
-
- /* Each stack pointer address is followed by the size of the
- data in use in that stack, in bytes. This used to be a
- conditional around just the two extra args, but that might
- be undefined if yyoverflow is a macro. */
- yyoverflow (YY_("memory exhausted"),
- &yyss1, yysize * sizeof (*yyssp),
- &yyvs1, yysize * sizeof (*yyvsp),
- &yystacksize);
-
- yyss = yyss1;
- yyvs = yyvs1;
- }
-#else /* no yyoverflow */
-# ifndef YYSTACK_RELOCATE
- goto yyexhaustedlab;
-# else
- /* Extend the stack our own way. */
- if (YYMAXDEPTH <= yystacksize)
- goto yyexhaustedlab;
- yystacksize *= 2;
- if (YYMAXDEPTH < yystacksize)
- yystacksize = YYMAXDEPTH;
-
- {
- yytype_int16 *yyss1 = yyss;
- union yyalloc *yyptr =
- (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize));
- if (! yyptr)
- goto yyexhaustedlab;
- YYSTACK_RELOCATE (yyss_alloc, yyss);
- YYSTACK_RELOCATE (yyvs_alloc, yyvs);
-# undef YYSTACK_RELOCATE
- if (yyss1 != yyssa)
- YYSTACK_FREE (yyss1);
- }
-# endif
-#endif /* no yyoverflow */
-
- yyssp = yyss + yysize - 1;
- yyvsp = yyvs + yysize - 1;
-
- YYDPRINTF ((stderr, "Stack size increased to %lu\n",
- (unsigned long int) yystacksize));
-
- if (yyss + yystacksize - 1 <= yyssp)
- YYABORT;
- }
-
- YYDPRINTF ((stderr, "Entering state %d\n", yystate));
-
- if (yystate == YYFINAL)
- YYACCEPT;
-
- goto yybackup;
-
-/*-----------.
-| yybackup. |
-`-----------*/
-yybackup:
-
- /* Do appropriate processing given the current state. Read a
- lookahead token if we need one and don't already have one. */
-
- /* First try to decide what to do without reference to lookahead token. */
- yyn = yypact[yystate];
- if (yypact_value_is_default (yyn))
- goto yydefault;
-
- /* Not known => get a lookahead token if don't already have one. */
-
- /* YYCHAR is either YYEMPTY or YYEOF or a valid lookahead symbol. */
- if (yychar == YYEMPTY)
- {
- YYDPRINTF ((stderr, "Reading a token: "));
- yychar = yylex ();
- }
-
- if (yychar <= YYEOF)
- {
- yychar = yytoken = YYEOF;
- YYDPRINTF ((stderr, "Now at end of input.\n"));
- }
- else
- {
- yytoken = YYTRANSLATE (yychar);
- YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc);
- }
-
- /* If the proper action on seeing token YYTOKEN is to reduce or to
- detect an error, take that action. */
- yyn += yytoken;
- if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken)
- goto yydefault;
- yyn = yytable[yyn];
- if (yyn <= 0)
- {
- if (yytable_value_is_error (yyn))
- goto yyerrlab;
- yyn = -yyn;
- goto yyreduce;
- }
-
- /* Count tokens shifted since error; after three, turn off error
- status. */
- if (yyerrstatus)
- yyerrstatus--;
-
- /* Shift the lookahead token. */
- YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc);
-
- /* Discard the shifted token. */
- yychar = YYEMPTY;
-
- yystate = yyn;
- YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
- *++yyvsp = yylval;
- YY_IGNORE_MAYBE_UNINITIALIZED_END
-
- goto yynewstate;
-
-
-/*-----------------------------------------------------------.
-| yydefault -- do the default action for the current state. |
-`-----------------------------------------------------------*/
-yydefault:
- yyn = yydefact[yystate];
- if (yyn == 0)
- goto yyerrlab;
- goto yyreduce;
-
-
-/*-----------------------------.
-| yyreduce -- Do a reduction. |
-`-----------------------------*/
-yyreduce:
- /* yyn is the number of a rule to reduce with. */
- yylen = yyr2[yyn];
-
- /* If YYLEN is nonzero, implement the default value of the action:
- '$$ = $1'.
-
- Otherwise, the following line sets YYVAL to garbage.
- This behavior is undocumented and Bison
- users should not rely upon it. Assigning to YYVAL
- unconditionally makes the parser a bit smaller, and it avoids a
- GCC warning that YYVAL may be used uninitialized. */
- yyval = yyvsp[1-yylen];
-
-
- YY_REDUCE_PRINT (yyn);
- switch (yyn)
- {
- case 4:
-#line 132 "parsedate.y" /* yacc.c:1646 */
- {
- yyHaveTime++;
-#ifdef lint
- /* I am compulsive about lint natterings... */
- if (yyHaveTime == -1) {
- YYERROR;
- }
-#endif /* lint */
- }
-#line 1366 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 5:
-#line 141 "parsedate.y" /* yacc.c:1646 */
- {
- yyHaveTime++;
- yyTimezone = (yyvsp[0].Number);
- }
-#line 1375 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 6:
-#line 145 "parsedate.y" /* yacc.c:1646 */
- {
- yyHaveDate++;
- }
-#line 1383 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 7:
-#line 148 "parsedate.y" /* yacc.c:1646 */
- {
- yyHaveRel = 1;
- }
-#line 1391 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 8:
-#line 153 "parsedate.y" /* yacc.c:1646 */
- {
- if ((yyvsp[-1].Number) < 100) {
- yyHour = (yyvsp[-1].Number);
- yyMinutes = 0;
- }
- else {
- yyHour = (yyvsp[-1].Number) / 100;
- yyMinutes = (yyvsp[-1].Number) % 100;
- }
- yySeconds = 0;
- yyMeridian = (yyvsp[0].Meridian);
- }
-#line 1408 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 9:
-#line 165 "parsedate.y" /* yacc.c:1646 */
- {
- yyHour = (yyvsp[-3].Number);
- yyMinutes = (yyvsp[-1].Number);
- yySeconds = 0;
- yyMeridian = (yyvsp[0].Meridian);
- }
-#line 1419 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 10:
-#line 171 "parsedate.y" /* yacc.c:1646 */
- {
- yyHour = (yyvsp[-3].Number);
- yyMinutes = (yyvsp[-1].Number);
- yyTimezone = (yyvsp[0].Number);
- yyMeridian = MER24;
- yyDSTmode = DSToff;
- }
-#line 1431 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 11:
-#line 178 "parsedate.y" /* yacc.c:1646 */
- {
- yyHour = (yyvsp[-5].Number);
- yyMinutes = (yyvsp[-3].Number);
- yySeconds = (yyvsp[-1].Number);
- yyMeridian = (yyvsp[0].Meridian);
- }
-#line 1442 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 12:
-#line 184 "parsedate.y" /* yacc.c:1646 */
- {
- yyHour = (yyvsp[-5].Number);
- yyMinutes = (yyvsp[-3].Number);
- yySeconds = (yyvsp[-1].Number);
- yyTimezone = (yyvsp[0].Number);
- yyMeridian = MER24;
- yyDSTmode = DSToff;
- }
-#line 1455 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 13:
-#line 194 "parsedate.y" /* yacc.c:1646 */
- {
- (yyval.Number) = (yyvsp[0].Number);
- yyDSTmode = DSToff;
- }
-#line 1464 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 14:
-#line 198 "parsedate.y" /* yacc.c:1646 */
- {
- (yyval.Number) = (yyvsp[0].Number);
- yyDSTmode = DSTon;
- }
-#line 1473 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 15:
-#line 202 "parsedate.y" /* yacc.c:1646 */
- {
- /* Only allow "GMT+300" and "GMT-0800" */
- if ((yyvsp[-1].Number) != 0) {
- YYABORT;
- }
- (yyval.Number) = (yyvsp[0].Number);
- yyDSTmode = DSToff;
- }
-#line 1486 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 16:
-#line 210 "parsedate.y" /* yacc.c:1646 */
- {
- (yyval.Number) = (yyvsp[0].Number);
- yyDSTmode = DSToff;
- }
-#line 1495 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 17:
-#line 216 "parsedate.y" /* yacc.c:1646 */
- {
- int i;
-
- /* Unix and GMT and numeric timezones -- a little confusing. */
- if ((yyvsp[0].Number) < 0) {
- /* Don't work with negative modulus. */
- (yyvsp[0].Number) = -(yyvsp[0].Number);
- if ((yyvsp[0].Number) > 9999 || (i = (yyvsp[0].Number) % 100) >= 60) {
- YYABORT;
- }
- (yyval.Number) = ((yyvsp[0].Number) / 100) * 60 + i;
- }
- else {
- if ((yyvsp[0].Number) > 9999 || (i = (yyvsp[0].Number) % 100) >= 60) {
- YYABORT;
- }
- (yyval.Number) = -(((yyvsp[0].Number) / 100) * 60 + i);
- }
- }
-#line 1519 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 18:
-#line 237 "parsedate.y" /* yacc.c:1646 */
- {
- yyMonth = (yyvsp[-2].Number);
- yyDay = (yyvsp[0].Number);
- }
-#line 1528 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 19:
-#line 241 "parsedate.y" /* yacc.c:1646 */
- {
- if ((yyvsp[-4].Number) > 100) {
- yyYear = (yyvsp[-4].Number);
- yyMonth = (yyvsp[-2].Number);
- yyDay = (yyvsp[0].Number);
- }
- else {
- yyMonth = (yyvsp[-4].Number);
- yyDay = (yyvsp[-2].Number);
- yyYear = (yyvsp[0].Number);
- }
- }
-#line 1545 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 20:
-#line 253 "parsedate.y" /* yacc.c:1646 */
- {
- yyMonth = (yyvsp[-1].Number);
- yyDay = (yyvsp[0].Number);
- }
-#line 1554 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 21:
-#line 257 "parsedate.y" /* yacc.c:1646 */
- {
- yyMonth = (yyvsp[-3].Number);
- yyDay = (yyvsp[-2].Number);
- yyYear = (yyvsp[0].Number);
- }
-#line 1564 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 22:
-#line 262 "parsedate.y" /* yacc.c:1646 */
- {
- yyDay = (yyvsp[-1].Number);
- yyMonth = (yyvsp[0].Number);
- }
-#line 1573 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 23:
-#line 266 "parsedate.y" /* yacc.c:1646 */
- {
- yyDay = (yyvsp[-2].Number);
- yyMonth = (yyvsp[-1].Number);
- yyYear = (yyvsp[0].Number);
- }
-#line 1583 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 24:
-#line 271 "parsedate.y" /* yacc.c:1646 */
- {
- yyDay = (yyvsp[-2].Number);
- yyMonth = (yyvsp[-1].Number);
- yyYear = (yyvsp[0].Number);
- }
-#line 1593 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 25:
-#line 278 "parsedate.y" /* yacc.c:1646 */
- {
- yyRelSeconds += (yyvsp[-1].Number) * (yyvsp[0].Number);
- }
-#line 1601 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 26:
-#line 281 "parsedate.y" /* yacc.c:1646 */
- {
- yyRelSeconds += (yyvsp[-1].Number) * (yyvsp[0].Number);
- }
-#line 1609 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 27:
-#line 284 "parsedate.y" /* yacc.c:1646 */
- {
- yyRelMonth += (yyvsp[-1].Number) * (yyvsp[0].Number);
- }
-#line 1617 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 28:
-#line 287 "parsedate.y" /* yacc.c:1646 */
- {
- yyRelMonth += (yyvsp[-1].Number) * (yyvsp[0].Number);
- }
-#line 1625 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 29:
-#line 292 "parsedate.y" /* yacc.c:1646 */
- {
- (yyval.Meridian) = MER24;
- }
-#line 1633 "y.tab.c" /* yacc.c:1646 */
- break;
-
- case 30:
-#line 295 "parsedate.y" /* yacc.c:1646 */
- {
- (yyval.Meridian) = (yyvsp[0].Meridian);
- }
-#line 1641 "y.tab.c" /* yacc.c:1646 */
- break;
-
-
-#line 1645 "y.tab.c" /* yacc.c:1646 */
- default: break;
- }
- /* User semantic actions sometimes alter yychar, and that requires
- that yytoken be updated with the new translation. We take the
- approach of translating immediately before every use of yytoken.
- One alternative is translating here after every semantic action,
- but that translation would be missed if the semantic action invokes
- YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or
- if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an
- incorrect destructor might then be invoked immediately. In the
- case of YYERROR or YYBACKUP, subsequent parser actions might lead
- to an incorrect destructor call or verbose syntax error message
- before the lookahead is translated. */
- YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc);
-
- YYPOPSTACK (yylen);
- yylen = 0;
- YY_STACK_PRINT (yyss, yyssp);
-
- *++yyvsp = yyval;
-
- /* Now 'shift' the result of the reduction. Determine what state
- that goes to, based on the state we popped back to and the rule
- number reduced by. */
-
- yyn = yyr1[yyn];
-
- yystate = yypgoto[yyn - YYNTOKENS] + *yyssp;
- if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp)
- yystate = yytable[yystate];
- else
- yystate = yydefgoto[yyn - YYNTOKENS];
-
- goto yynewstate;
-
-
-/*--------------------------------------.
-| yyerrlab -- here on detecting error. |
-`--------------------------------------*/
-yyerrlab:
- /* Make sure we have latest lookahead translation. See comments at
- user semantic actions for why this is necessary. */
- yytoken = yychar == YYEMPTY ? YYEMPTY : YYTRANSLATE (yychar);
-
- /* If not already recovering from an error, report this error. */
- if (!yyerrstatus)
- {
- ++yynerrs;
-#if ! YYERROR_VERBOSE
- yyerror (YY_("syntax error"));
-#else
-# define YYSYNTAX_ERROR yysyntax_error (&yymsg_alloc, &yymsg, \
- yyssp, yytoken)
- {
- char const *yymsgp = YY_("syntax error");
- int yysyntax_error_status;
- yysyntax_error_status = YYSYNTAX_ERROR;
- if (yysyntax_error_status == 0)
- yymsgp = yymsg;
- else if (yysyntax_error_status == 1)
- {
- if (yymsg != yymsgbuf)
- YYSTACK_FREE (yymsg);
- yymsg = (char *) YYSTACK_ALLOC (yymsg_alloc);
- if (!yymsg)
- {
- yymsg = yymsgbuf;
- yymsg_alloc = sizeof yymsgbuf;
- yysyntax_error_status = 2;
- }
- else
- {
- yysyntax_error_status = YYSYNTAX_ERROR;
- yymsgp = yymsg;
- }
- }
- yyerror (yymsgp);
- if (yysyntax_error_status == 2)
- goto yyexhaustedlab;
- }
-# undef YYSYNTAX_ERROR
-#endif
- }
-
-
-
- if (yyerrstatus == 3)
- {
- /* If just tried and failed to reuse lookahead token after an
- error, discard it. */
-
- if (yychar <= YYEOF)
- {
- /* Return failure if at end of input. */
- if (yychar == YYEOF)
- YYABORT;
- }
- else
- {
- yydestruct ("Error: discarding",
- yytoken, &yylval);
- yychar = YYEMPTY;
- }
- }
-
- /* Else will try to reuse lookahead token after shifting the error
- token. */
- goto yyerrlab1;
-
-
-/*---------------------------------------------------.
-| yyerrorlab -- error raised explicitly by YYERROR. |
-`---------------------------------------------------*/
-yyerrorlab:
-
- /* Pacify compilers like GCC when the user code never invokes
- YYERROR and the label yyerrorlab therefore never appears in user
- code. */
- if (/*CONSTCOND*/ 0)
- goto yyerrorlab;
-
- /* Do not reclaim the symbols of the rule whose action triggered
- this YYERROR. */
- YYPOPSTACK (yylen);
- yylen = 0;
- YY_STACK_PRINT (yyss, yyssp);
- yystate = *yyssp;
- goto yyerrlab1;
-
-
-/*-------------------------------------------------------------.
-| yyerrlab1 -- common code for both syntax error and YYERROR. |
-`-------------------------------------------------------------*/
-yyerrlab1:
- yyerrstatus = 3; /* Each real token shifted decrements this. */
-
- for (;;)
- {
- yyn = yypact[yystate];
- if (!yypact_value_is_default (yyn))
- {
- yyn += YYTERROR;
- if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR)
- {
- yyn = yytable[yyn];
- if (0 < yyn)
- break;
- }
- }
-
- /* Pop the current state because it cannot handle the error token. */
- if (yyssp == yyss)
- YYABORT;
-
-
- yydestruct ("Error: popping",
- yystos[yystate], yyvsp);
- YYPOPSTACK (1);
- yystate = *yyssp;
- YY_STACK_PRINT (yyss, yyssp);
- }
-
- YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
- *++yyvsp = yylval;
- YY_IGNORE_MAYBE_UNINITIALIZED_END
-
-
- /* Shift the error token. */
- YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp);
-
- yystate = yyn;
- goto yynewstate;
-
-
-/*-------------------------------------.
-| yyacceptlab -- YYACCEPT comes here. |
-`-------------------------------------*/
-yyacceptlab:
- yyresult = 0;
- goto yyreturn;
-
-/*-----------------------------------.
-| yyabortlab -- YYABORT comes here. |
-`-----------------------------------*/
-yyabortlab:
- yyresult = 1;
- goto yyreturn;
-
-#if !defined yyoverflow || YYERROR_VERBOSE
-/*-------------------------------------------------.
-| yyexhaustedlab -- memory exhaustion comes here. |
-`-------------------------------------------------*/
-yyexhaustedlab:
- yyerror (YY_("memory exhausted"));
- yyresult = 2;
- /* Fall through. */
-#endif
-
-yyreturn:
- if (yychar != YYEMPTY)
- {
- /* Make sure we have latest lookahead translation. See comments at
- user semantic actions for why this is necessary. */
- yytoken = YYTRANSLATE (yychar);
- yydestruct ("Cleanup: discarding lookahead",
- yytoken, &yylval);
- }
- /* Do not reclaim the symbols of the rule whose action triggered
- this YYABORT or YYACCEPT. */
- YYPOPSTACK (yylen);
- YY_STACK_PRINT (yyss, yyssp);
- while (yyssp != yyss)
- {
- yydestruct ("Cleanup: popping",
- yystos[*yyssp], yyvsp);
- YYPOPSTACK (1);
- }
-#ifndef yyoverflow
- if (yyss != yyssa)
- YYSTACK_FREE (yyss);
-#endif
-#if YYERROR_VERBOSE
- if (yymsg != yymsgbuf)
- YYSTACK_FREE (yymsg);
-#endif
- return yyresult;
-}
-#line 300 "parsedate.y" /* yacc.c:1906 */
-
-
-/* Month and day table. */
-static TABLE MonthDayTable[] = {
- { "january", tMONTH, 1 },
- { "february", tMONTH, 2 },
- { "march", tMONTH, 3 },
- { "april", tMONTH, 4 },
- { "may", tMONTH, 5 },
- { "june", tMONTH, 6 },
- { "july", tMONTH, 7 },
- { "august", tMONTH, 8 },
- { "september", tMONTH, 9 },
- { "october", tMONTH, 10 },
- { "november", tMONTH, 11 },
- { "december", tMONTH, 12 },
- /* The value of the day isn't used... */
- { "sunday", tDAY, 0 },
- { "monday", tDAY, 0 },
- { "tuesday", tDAY, 0 },
- { "wednesday", tDAY, 0 },
- { "thursday", tDAY, 0 },
- { "friday", tDAY, 0 },
- { "saturday", tDAY, 0 },
-};
-
-/* Time units table. */
-static TABLE UnitsTable[] = {
- { "year", tMONTH_UNIT, 12 },
- { "month", tMONTH_UNIT, 1 },
- { "week", tSEC_UNIT, 7L * 24 * 60 * 60 },
- { "day", tSEC_UNIT, 1L * 24 * 60 * 60 },
- { "hour", tSEC_UNIT, 60 * 60 },
- { "minute", tSEC_UNIT, 60 },
- { "min", tSEC_UNIT, 60 },
- { "second", tSEC_UNIT, 1 },
- { "sec", tSEC_UNIT, 1 },
-};
-
-/* Timezone table. */
-static TABLE TimezoneTable[] = {
- { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */
- { "ut", tZONE, HOUR( 0) }, /* Universal */
- { "utc", tZONE, HOUR( 0) }, /* Universal Coordinated */
- { "cut", tZONE, HOUR( 0) }, /* Coordinated Universal */
- { "z", tZONE, HOUR( 0) }, /* Greenwich Mean */
- { "wet", tZONE, HOUR( 0) }, /* Western European */
- { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */
- { "nst", tZONE, HOUR(3)+30 }, /* Newfoundland Standard */
- { "ndt", tDAYZONE, HOUR(3)+30 }, /* Newfoundland Daylight */
- { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */
- { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */
- { "est", tZONE, HOUR( 5) }, /* Eastern Standard */
- { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */
- { "cst", tZONE, HOUR( 6) }, /* Central Standard */
- { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */
- { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */
- { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */
- { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */
- { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */
- { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */
- { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */
- { "akst", tZONE, HOUR( 9) }, /* Alaska Standard */
- { "akdt", tDAYZONE, HOUR( 9) }, /* Alaska Daylight */
- { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */
- { "hast", tZONE, HOUR(10) }, /* Hawaii-Aleutian Standard */
- { "hadt", tDAYZONE, HOUR(10) }, /* Hawaii-Aleutian Daylight */
- { "ces", tDAYZONE, -HOUR(1) }, /* Central European Summer */
- { "cest", tDAYZONE, -HOUR(1) }, /* Central European Summer */
- { "mez", tZONE, -HOUR(1) }, /* Middle European */
- { "mezt", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
- { "cet", tZONE, -HOUR(1) }, /* Central European */
- { "met", tZONE, -HOUR(1) }, /* Middle European */
- { "eet", tZONE, -HOUR(2) }, /* Eastern Europe */
- { "msk", tZONE, -HOUR(3) }, /* Moscow Winter */
- { "msd", tDAYZONE, -HOUR(3) }, /* Moscow Summer */
- { "wast", tZONE, -HOUR(8) }, /* West Australian Standard */
- { "wadt", tDAYZONE, -HOUR(8) }, /* West Australian Daylight */
- { "hkt", tZONE, -HOUR(8) }, /* Hong Kong */
- { "cct", tZONE, -HOUR(8) }, /* China Coast */
- { "jst", tZONE, -HOUR(9) }, /* Japan Standard */
- { "kst", tZONE, -HOUR(9) }, /* Korean Standard */
- { "kdt", tZONE, -HOUR(9) }, /* Korean Daylight */
- { "cast", tZONE, -(HOUR(9)+30) }, /* Central Australian Standard */
- { "cadt", tDAYZONE, -(HOUR(9)+30) }, /* Central Australian Daylight */
- { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */
- { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */
- { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */
- { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */
-
- /* For completeness we include the following entries. */
-#if 0
-
- /* Duplicate names. Either they conflict with a zone listed above
- * (which is either more likely to be seen or just been in circulation
- * longer), or they conflict with another zone in this section and
- * we could not reasonably choose one over the other. */
- { "fst", tZONE, HOUR( 2) }, /* Fernando De Noronha Standard */
- { "fdt", tDAYZONE, HOUR( 2) }, /* Fernando De Noronha Daylight */
- { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */
- { "est", tZONE, HOUR( 3) }, /* Eastern Standard (Brazil) */
- { "edt", tDAYZONE, HOUR( 3) }, /* Eastern Daylight (Brazil) */
- { "wst", tZONE, HOUR( 4) }, /* Western Standard (Brazil) */
- { "wdt", tDAYZONE, HOUR( 4) }, /* Western Daylight (Brazil) */
- { "cst", tZONE, HOUR( 5) }, /* Chile Standard */
- { "cdt", tDAYZONE, HOUR( 5) }, /* Chile Daylight */
- { "ast", tZONE, HOUR( 5) }, /* Acre Standard */
- { "adt", tDAYZONE, HOUR( 5) }, /* Acre Daylight */
- { "cst", tZONE, HOUR( 5) }, /* Cuba Standard */
- { "cdt", tDAYZONE, HOUR( 5) }, /* Cuba Daylight */
- { "est", tZONE, HOUR( 6) }, /* Easter Island Standard */
- { "edt", tDAYZONE, HOUR( 6) }, /* Easter Island Daylight */
- { "sst", tZONE, HOUR(11) }, /* Samoa Standard */
- { "ist", tZONE, -HOUR(2) }, /* Israel Standard */
- { "idt", tDAYZONE, -HOUR(2) }, /* Israel Daylight */
- { "idt", tDAYZONE, -(HOUR(3)+30) }, /* Iran Daylight */
- { "ist", tZONE, -(HOUR(3)+30) }, /* Iran Standard */
- { "cst", tZONE, -HOUR(8) }, /* China Standard */
- { "cdt", tDAYZONE, -HOUR(8) }, /* China Daylight */
- { "sst", tZONE, -HOUR(8) }, /* Singapore Standard */
-
- /* Dubious (e.g., not in Olson's TIMEZONE package) or obsolete. */
- { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */
- { "wat", tZONE, -HOUR(1) }, /* West Africa */
- { "at", tZONE, HOUR( 2) }, /* Azores */
- { "gst", tZONE, -HOUR(10) }, /* Guam Standard */
- { "nft", tZONE, HOUR(3)+30 }, /* Newfoundland */
- { "idlw", tZONE, HOUR(12) }, /* International Date Line West */
- { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */
- { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
- { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */
- { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */
- { "fwt", tZONE, -HOUR(1) }, /* French Winter */
- { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */
- { "bt", tZONE, -HOUR(3) }, /* Baghdad */
- { "it", tZONE, -(HOUR(3)+30) }, /* Iran */
- { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */
- { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */
- { "ist", tZONE, -(HOUR(5)+30) }, /* Indian Standard */
- { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */
- { "nst", tZONE, -HOUR(7) }, /* North Sumatra */
- { "sst", tZONE, -HOUR(7) }, /* South Sumatra */
- { "jt", tZONE, -(HOUR(7)+30) }, /* Java (3pm in Cronusland!) */
- { "nzt", tZONE, -HOUR(12) }, /* New Zealand */
- { "idle", tZONE, -HOUR(12) }, /* International Date Line East */
- { "cat", tZONE, HOUR(10) }, /* -- expired 1967 */
- { "nt", tZONE, HOUR(11) }, /* -- expired 1967 */
- { "ahst", tZONE, HOUR(10) }, /* -- expired 1983 */
- { "hdt", tDAYZONE, HOUR(10) }, /* -- expired 1986 */
-#endif /* 0 */
-};
-
-
-/* ARGSUSED */
-static void
-date_error(char *s)
-{
- /* NOTREACHED */
-}
-
-
-static time_t
-ToSeconds(time_t Hours, time_t Minutes, time_t Seconds, MERIDIAN Meridian)
-{
- if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 61)
- return -1;
- if (Meridian == MER24) {
- if (Hours < 0 || Hours > 23)
- return -1;
- }
- else {
- if (Hours < 1 || Hours > 12)
- return -1;
- if (Hours == 12)
- Hours = 0;
- if (Meridian == MERpm)
- Hours += 12;
- }
- return (Hours * 60L + Minutes) * 60L + Seconds;
-}
-
-
-static time_t
-Convert(time_t Month, time_t Day, time_t Year,
- time_t Hours, time_t Minutes, time_t Seconds,
- MERIDIAN Meridian, DSTMODE dst)
-{
- static int DaysNormal[13] = {
- 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
- };
- static int DaysLeap[13] = {
- 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
- };
- static int LeapYears[] = {
- 1972, 1976, 1980, 1984, 1988, 1992, 1996,
- 2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036
- };
- register int *yp;
- register int *mp;
- register time_t Julian;
- register int i;
- time_t tod;
-
- if (Year < 0)
- Year = -Year;
- if (Year < 100)
- Year += 1900;
- if (Year < EPOCH)
- Year += 100;
- for (mp = DaysNormal, yp = LeapYears; yp < ENDOF(LeapYears); yp++)
- if (Year == *yp) {
- mp = DaysLeap;
- break;
- }
- if (Year < EPOCH || Year > END_OF_TIME
- || Month < 1 || Month > 12
- /* NOSTRICT *//* conversion from long may lose accuracy */
- || Day < 1 || Day > mp[(int)Month])
- return -1;
-
- Julian = Day - 1 + (Year - EPOCH) * 365;
- for (yp = LeapYears; yp < ENDOF(LeapYears); yp++, Julian++)
- if (Year <= *yp)
- break;
- for (i = 1; i < Month; i++)
- Julian += *++mp;
- Julian *= SECSPERDAY;
- Julian += yyTimezone * 60L;
- if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
- return -1;
- Julian += tod;
- tod = Julian;
- if (dst == DSTon || (dst == DSTmaybe && localtime(&tod)->tm_isdst))
- Julian -= DST_OFFSET * 60L * 60L;
- return Julian;
-}
-
-
-static time_t
-DSTcorrect(time_t Start, time_t Future)
-{
- time_t StartDay;
- time_t FutureDay;
-
- StartDay = (localtime(&Start)->tm_hour + 1) % 24;
- FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
- return (Future - Start) + (StartDay - FutureDay) * DST_OFFSET * 60L * 60L;
-}
-
-
-static time_t
-RelativeMonth(time_t Start, time_t RelMonth)
-{
- struct tm *tm;
- time_t Month;
- time_t Year;
-
- tm = localtime(&Start);
- Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
- Year = Month / 12;
- Month = Month % 12 + 1;
- return DSTcorrect(Start,
- Convert(Month, (time_t)tm->tm_mday, Year,
- (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
- MER24, DSTmaybe));
-}
-
-
-static int
-LookupWord(char *buff, register int length)
-{
- register char *p;
- register char *q;
- register TABLE *tp;
- register int c;
-
- p = buff;
- c = p[0];
-
- /* See if we have an abbreviation for a month. */
- if (length == 3 || (length == 4 && p[3] == '.'))
- for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++) {
- q = tp->name;
- if (c == q[0] && p[1] == q[1] && p[2] == q[2]) {
- yylval.Number = tp->value;
- return tp->type;
- }
- }
- else
- for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++)
- if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
- yylval.Number = tp->value;
- return tp->type;
- }
-
- /* Try for a timezone. */
- for (tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
- if (c == tp->name[0] && p[1] == tp->name[1]
- && strcmp(p, tp->name) == 0) {
- yylval.Number = tp->value;
- return tp->type;
- }
-
- /* Try the units table. */
- for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
- if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
- yylval.Number = tp->value;
- return tp->type;
- }
-
- /* Strip off any plural and try the units table again. */
- if (--length > 0 && p[length] == 's') {
- p[length] = '\0';
- for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
- if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
- p[length] = 's';
- yylval.Number = tp->value;
- return tp->type;
- }
- p[length] = 's';
- }
- length++;
-
- /* Drop out any periods. */
- for (p = buff, q = (char*)buff; *q; q++)
- if (*q != '.')
- *p++ = *q;
- *p = '\0';
-
- /* Try the meridians. */
- if (buff[1] == 'm' && buff[2] == '\0') {
- if (buff[0] == 'a') {
- yylval.Meridian = MERam;
- return tMERIDIAN;
- }
- if (buff[0] == 'p') {
- yylval.Meridian = MERpm;
- return tMERIDIAN;
- }
- }
-
- /* If we saw any periods, try the timezones again. */
- if (p - buff != length) {
- c = buff[0];
- for (p = buff, tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
- if (c == tp->name[0] && p[1] == tp->name[1]
- && strcmp(p, tp->name) == 0) {
- yylval.Number = tp->value;
- return tp->type;
- }
- }
-
- /* Unknown word -- assume GMT timezone. */
- yylval.Number = 0;
- return tZONE;
-}
-
-
-int
-date_lex(void)
-{
- register char c;
- register char *p;
- char buff[20];
- register int sign;
- register int i;
- register int nesting;
-
- for ( ; ; ) {
- /* Get first character after the whitespace. */
- for ( ; ; ) {
- while (isspace(*yyInput))
- yyInput++;
- c = *yyInput;
-
- /* Ignore RFC 822 comments, typically time zone names. */
- if (c != LPAREN)
- break;
- for (nesting = 1; (c = *++yyInput) != RPAREN || --nesting; )
- if (c == LPAREN)
- nesting++;
- else if (!IS7BIT(c) || c == '\0' || c == '\r'
- || (c == '\\' && ((c = *++yyInput) == '\0' || !IS7BIT(c))))
- /* Lexical error: bad comment. */
- return '?';
- yyInput++;
- }
-
- /* A number? */
- if (isdigit(c) || c == '-' || c == '+') {
- if (c == '-' || c == '+') {
- sign = c == '-' ? -1 : 1;
- yyInput++;
- if (!isdigit(*yyInput))
- /* Skip the plus or minus sign. */
- continue;
- }
- else
- sign = 0;
- for (i = 0; (c = *yyInput++) != '\0' && isdigit(c); )
- i = 10 * i + c - '0';
- yyInput--;
- yylval.Number = sign < 0 ? -i : i;
- return sign ? tSNUMBER : tUNUMBER;
- }
-
- /* A word? */
- if (isalpha(c)) {
- for (p = buff; (c = *yyInput++) == '.' || isalpha(c); )
- if (p < &buff[sizeof buff - 1])
- *p++ = isupper(c) ? tolower(c) : c;
- *p = '\0';
- yyInput--;
- return LookupWord(buff, p - buff);
- }
-
- return *yyInput++;
- }
-}
-
-
-time_t
-parsedate(const char *p)
-{
- extern int date_parse(void);
- time_t Start;
-
- yyInput = p; /* well, its supposed to be const... */
-
- yyYear = 0;
- yyMonth = 0;
- yyDay = 0;
- yyTimezone = 0;
- yyDSTmode = DSTmaybe;
- yyHour = 0;
- yyMinutes = 0;
- yySeconds = 0;
- yyMeridian = MER24;
- yyRelSeconds = 0;
- yyRelMonth = 0;
- yyHaveDate = 0;
- yyHaveRel = 0;
- yyHaveTime = 0;
-
- if (date_parse() || yyHaveTime > 1 || yyHaveDate > 1)
- return -1;
-
- if (yyHaveDate || yyHaveTime) {
- Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
- yyMeridian, yyDSTmode);
- if (Start < 0)
- return -1;
- }
- else
- return -1;
-
- Start += yyRelSeconds;
- if (yyRelMonth)
- Start += RelativeMonth(Start, yyRelMonth);
-
- /* Have to do *something* with a legitimate -1 so it's distinguishable
- * from the error return value. (Alternately could set errno on error.) */
- return Start == -1 ? 0 : Start;
-}
-
-
-#ifdef TEST
-
-#if YYDEBUG
-extern int yydebug;
-#endif /* YYDEBUG */
-
-/* ARGSUSED */
-int
-main(int ac, char *av[])
-{
- char buff[128];
- time_t d;
-
-#if YYDEBUG
- yydebug = 1;
-#endif /* YYDEBUG */
-
- (void)printf("Enter date, or blank line to exit.\n\t> ");
- for ( ; ; ) {
- (void)printf("\t> ");
- (void)fflush(stdout);
- if (fgets(buff, sizeof buff, stdin) == NULL || buff[0] == '\n')
- break;
-#if YYDEBUG
- if (strcmp(buff, "yydebug") == 0) {
- yydebug = !yydebug;
- printf("yydebug = %s\n", yydebug ? "on" : "off");
- continue;
- }
-#endif /* YYDEBUG */
- d = parsedate(buff, (TIMEINFO *)NULL);
- if (d == -1)
- (void)printf("Bad format - couldn't convert.\n");
- else
- (void)printf("%s", ctime(&d));
- }
-
- exit(0);
- /* NOTREACHED */
-}
-#endif /* TEST */
+++ /dev/null
-
-time_t parsedate(const char *);
+++ /dev/null
-%{
-/* $Revision$
-**
-** Originally written by Steven M. Bellovin <smb@research.att.com> while
-** at the University of North Carolina at Chapel Hill. Later tweaked by
-** a couple of people on Usenet. Completely overhauled by Rich $alz
-** <rsalz@osf.org> and Jim Berets <jberets@bbn.com> in August, 1990.
-** Further revised (removed obsolete constructs and cleaned up timezone
-** names) in August, 1991, by Rich. Paul Eggert <eggert@twinsun.com>
-** helped in September, 1992. Art Cancro <ajc@citadel.org> cleaned
-** it up for ANSI C in December, 1999.
-**
-** This grammar has six shift/reduce conflicts.
-**
-** This code is in the public domain and has no copyright.
-*/
-/* SUPPRESS 530 *//* Empty body for statement */
-/* SUPPRESS 593 on yyerrlab *//* Label was not used */
-/* SUPPRESS 593 on yynewstate *//* Label was not used */
-/* SUPPRESS 595 on yypvt *//* Automatic variable may be used before set */
-
-#include "sysdep.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/types.h>
-#include <ctype.h>
-#include <time.h>
-#if HAVE_STRING_H
-# if !STDC_HEADERS && HAVE_MEMORY_H
-# include <memory.h>
-# endif
-# include <string.h>
-#endif
-#if HAVE_STRINGS_H
-# include <strings.h>
-#endif
-
-#include "parsedate.h"
-
-int date_lex(void);
-
-#define yyparse date_parse
-#define yylex date_lex
-#define yyerror date_error
-
-
- /* See the LeapYears table in Convert. */
-#define EPOCH 1970
-#define END_OF_TIME 2038
- /* Constants for general time calculations. */
-#define DST_OFFSET 1
-#define SECSPERDAY (24L * 60L * 60L)
- /* Readability for TABLE stuff. */
-#define HOUR(x) (x * 60)
-
-#define LPAREN '('
-#define RPAREN ')'
-#define IS7BIT(x) ((unsigned int)(x) < 0200)
-
-#define SIZEOF(array) ((int)(sizeof array / sizeof array[0]))
-#define ENDOF(array) (&array[SIZEOF(array)])
-
-
-/*
-** An entry in the lexical lookup table.
-*/
-typedef struct _TABLE {
- char *name;
- int type;
- time_t value;
-} TABLE;
-
-/*
-** Daylight-savings mode: on, off, or not yet known.
-*/
-typedef enum _DSTMODE {
- DSTon, DSToff, DSTmaybe
-} DSTMODE;
-
-/*
-** Meridian: am, pm, or 24-hour style.
-*/
-typedef enum _MERIDIAN {
- MERam, MERpm, MER24
-} MERIDIAN;
-
-
-/*
-** Global variables. We could get rid of most of them by using a yacc
-** union, but this is more efficient. (This routine predates the
-** yacc %union construct.)
-*/
-static const char *yyInput;
-static DSTMODE yyDSTmode;
-static int yyHaveDate;
-static int yyHaveRel;
-static int yyHaveTime;
-static time_t yyTimezone;
-static time_t yyDay;
-static time_t yyHour;
-static time_t yyMinutes;
-static time_t yyMonth;
-static time_t yySeconds;
-static time_t yyYear;
-static MERIDIAN yyMeridian;
-static time_t yyRelMonth;
-static time_t yyRelSeconds;
-
-
-static void date_error(char *);
-%}
-
-%union {
- time_t Number;
- enum _MERIDIAN Meridian;
-}
-
-%token tDAY tDAYZONE tMERIDIAN tMONTH tMONTH_UNIT tSEC_UNIT tSNUMBER
-%token tUNUMBER tZONE
-
-%type <Number> tDAYZONE tMONTH tMONTH_UNIT tSEC_UNIT
-%type <Number> tSNUMBER tUNUMBER tZONE numzone zone
-%type <Meridian> tMERIDIAN o_merid
-
-%%
-
-spec : /* NULL */
- | spec item
- ;
-
-item : time {
- yyHaveTime++;
-#ifdef lint
- /* I am compulsive about lint natterings... */
- if (yyHaveTime == -1) {
- YYERROR;
- }
-#endif /* lint */
- }
- | time zone {
- yyHaveTime++;
- yyTimezone = $2;
- }
- | date {
- yyHaveDate++;
- }
- | rel {
- yyHaveRel = 1;
- }
- ;
-
-time : tUNUMBER o_merid {
- if ($1 < 100) {
- yyHour = $1;
- yyMinutes = 0;
- }
- else {
- yyHour = $1 / 100;
- yyMinutes = $1 % 100;
- }
- yySeconds = 0;
- yyMeridian = $2;
- }
- | tUNUMBER ':' tUNUMBER o_merid {
- yyHour = $1;
- yyMinutes = $3;
- yySeconds = 0;
- yyMeridian = $4;
- }
- | tUNUMBER ':' tUNUMBER numzone {
- yyHour = $1;
- yyMinutes = $3;
- yyTimezone = $4;
- yyMeridian = MER24;
- yyDSTmode = DSToff;
- }
- | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
- yyHour = $1;
- yyMinutes = $3;
- yySeconds = $5;
- yyMeridian = $6;
- }
- | tUNUMBER ':' tUNUMBER ':' tUNUMBER numzone {
- yyHour = $1;
- yyMinutes = $3;
- yySeconds = $5;
- yyTimezone = $6;
- yyMeridian = MER24;
- yyDSTmode = DSToff;
- }
- ;
-
-zone : tZONE {
- $$ = $1;
- yyDSTmode = DSToff;
- }
- | tDAYZONE {
- $$ = $1;
- yyDSTmode = DSTon;
- }
- | tZONE numzone {
- /* Only allow "GMT+300" and "GMT-0800" */
- if ($1 != 0) {
- YYABORT;
- }
- $$ = $2;
- yyDSTmode = DSToff;
- }
- | numzone {
- $$ = $1;
- yyDSTmode = DSToff;
- }
- ;
-
-numzone : tSNUMBER {
- int i;
-
- /* Unix and GMT and numeric timezones -- a little confusing. */
- if ($1 < 0) {
- /* Don't work with negative modulus. */
- $1 = -$1;
- if ($1 > 9999 || (i = $1 % 100) >= 60) {
- YYABORT;
- }
- $$ = ($1 / 100) * 60 + i;
- }
- else {
- if ($1 > 9999 || (i = $1 % 100) >= 60) {
- YYABORT;
- }
- $$ = -(($1 / 100) * 60 + i);
- }
- }
- ;
-
-date : tUNUMBER '/' tUNUMBER {
- yyMonth = $1;
- yyDay = $3;
- }
- | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
- if ($1 > 100) {
- yyYear = $1;
- yyMonth = $3;
- yyDay = $5;
- }
- else {
- yyMonth = $1;
- yyDay = $3;
- yyYear = $5;
- }
- }
- | tMONTH tUNUMBER {
- yyMonth = $1;
- yyDay = $2;
- }
- | tMONTH tUNUMBER ',' tUNUMBER {
- yyMonth = $1;
- yyDay = $2;
- yyYear = $4;
- }
- | tUNUMBER tMONTH {
- yyDay = $1;
- yyMonth = $2;
- }
- | tUNUMBER tMONTH tUNUMBER {
- yyDay = $1;
- yyMonth = $2;
- yyYear = $3;
- }
- | tDAY ',' tUNUMBER tMONTH tUNUMBER {
- yyDay = $3;
- yyMonth = $4;
- yyYear = $5;
- }
- ;
-
-rel : tSNUMBER tSEC_UNIT {
- yyRelSeconds += $1 * $2;
- }
- | tUNUMBER tSEC_UNIT {
- yyRelSeconds += $1 * $2;
- }
- | tSNUMBER tMONTH_UNIT {
- yyRelMonth += $1 * $2;
- }
- | tUNUMBER tMONTH_UNIT {
- yyRelMonth += $1 * $2;
- }
- ;
-
-o_merid : /* NULL */ {
- $$ = MER24;
- }
- | tMERIDIAN {
- $$ = $1;
- }
- ;
-
-%%
-
-/* Month and day table. */
-static TABLE MonthDayTable[] = {
- { "january", tMONTH, 1 },
- { "february", tMONTH, 2 },
- { "march", tMONTH, 3 },
- { "april", tMONTH, 4 },
- { "may", tMONTH, 5 },
- { "june", tMONTH, 6 },
- { "july", tMONTH, 7 },
- { "august", tMONTH, 8 },
- { "september", tMONTH, 9 },
- { "october", tMONTH, 10 },
- { "november", tMONTH, 11 },
- { "december", tMONTH, 12 },
- /* The value of the day isn't used... */
- { "sunday", tDAY, 0 },
- { "monday", tDAY, 0 },
- { "tuesday", tDAY, 0 },
- { "wednesday", tDAY, 0 },
- { "thursday", tDAY, 0 },
- { "friday", tDAY, 0 },
- { "saturday", tDAY, 0 },
-};
-
-/* Time units table. */
-static TABLE UnitsTable[] = {
- { "year", tMONTH_UNIT, 12 },
- { "month", tMONTH_UNIT, 1 },
- { "week", tSEC_UNIT, 7L * 24 * 60 * 60 },
- { "day", tSEC_UNIT, 1L * 24 * 60 * 60 },
- { "hour", tSEC_UNIT, 60 * 60 },
- { "minute", tSEC_UNIT, 60 },
- { "min", tSEC_UNIT, 60 },
- { "second", tSEC_UNIT, 1 },
- { "sec", tSEC_UNIT, 1 },
-};
-
-/* Timezone table. */
-static TABLE TimezoneTable[] = {
- { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */
- { "ut", tZONE, HOUR( 0) }, /* Universal */
- { "utc", tZONE, HOUR( 0) }, /* Universal Coordinated */
- { "cut", tZONE, HOUR( 0) }, /* Coordinated Universal */
- { "z", tZONE, HOUR( 0) }, /* Greenwich Mean */
- { "wet", tZONE, HOUR( 0) }, /* Western European */
- { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */
- { "nst", tZONE, HOUR(3)+30 }, /* Newfoundland Standard */
- { "ndt", tDAYZONE, HOUR(3)+30 }, /* Newfoundland Daylight */
- { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */
- { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */
- { "est", tZONE, HOUR( 5) }, /* Eastern Standard */
- { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */
- { "cst", tZONE, HOUR( 6) }, /* Central Standard */
- { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */
- { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */
- { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */
- { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */
- { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */
- { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */
- { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */
- { "akst", tZONE, HOUR( 9) }, /* Alaska Standard */
- { "akdt", tDAYZONE, HOUR( 9) }, /* Alaska Daylight */
- { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */
- { "hast", tZONE, HOUR(10) }, /* Hawaii-Aleutian Standard */
- { "hadt", tDAYZONE, HOUR(10) }, /* Hawaii-Aleutian Daylight */
- { "ces", tDAYZONE, -HOUR(1) }, /* Central European Summer */
- { "cest", tDAYZONE, -HOUR(1) }, /* Central European Summer */
- { "mez", tZONE, -HOUR(1) }, /* Middle European */
- { "mezt", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
- { "cet", tZONE, -HOUR(1) }, /* Central European */
- { "met", tZONE, -HOUR(1) }, /* Middle European */
- { "eet", tZONE, -HOUR(2) }, /* Eastern Europe */
- { "msk", tZONE, -HOUR(3) }, /* Moscow Winter */
- { "msd", tDAYZONE, -HOUR(3) }, /* Moscow Summer */
- { "wast", tZONE, -HOUR(8) }, /* West Australian Standard */
- { "wadt", tDAYZONE, -HOUR(8) }, /* West Australian Daylight */
- { "hkt", tZONE, -HOUR(8) }, /* Hong Kong */
- { "cct", tZONE, -HOUR(8) }, /* China Coast */
- { "jst", tZONE, -HOUR(9) }, /* Japan Standard */
- { "kst", tZONE, -HOUR(9) }, /* Korean Standard */
- { "kdt", tZONE, -HOUR(9) }, /* Korean Daylight */
- { "cast", tZONE, -(HOUR(9)+30) }, /* Central Australian Standard */
- { "cadt", tDAYZONE, -(HOUR(9)+30) }, /* Central Australian Daylight */
- { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */
- { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */
- { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */
- { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */
-
- /* For completeness we include the following entries. */
-#if 0
-
- /* Duplicate names. Either they conflict with a zone listed above
- * (which is either more likely to be seen or just been in circulation
- * longer), or they conflict with another zone in this section and
- * we could not reasonably choose one over the other. */
- { "fst", tZONE, HOUR( 2) }, /* Fernando De Noronha Standard */
- { "fdt", tDAYZONE, HOUR( 2) }, /* Fernando De Noronha Daylight */
- { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */
- { "est", tZONE, HOUR( 3) }, /* Eastern Standard (Brazil) */
- { "edt", tDAYZONE, HOUR( 3) }, /* Eastern Daylight (Brazil) */
- { "wst", tZONE, HOUR( 4) }, /* Western Standard (Brazil) */
- { "wdt", tDAYZONE, HOUR( 4) }, /* Western Daylight (Brazil) */
- { "cst", tZONE, HOUR( 5) }, /* Chile Standard */
- { "cdt", tDAYZONE, HOUR( 5) }, /* Chile Daylight */
- { "ast", tZONE, HOUR( 5) }, /* Acre Standard */
- { "adt", tDAYZONE, HOUR( 5) }, /* Acre Daylight */
- { "cst", tZONE, HOUR( 5) }, /* Cuba Standard */
- { "cdt", tDAYZONE, HOUR( 5) }, /* Cuba Daylight */
- { "est", tZONE, HOUR( 6) }, /* Easter Island Standard */
- { "edt", tDAYZONE, HOUR( 6) }, /* Easter Island Daylight */
- { "sst", tZONE, HOUR(11) }, /* Samoa Standard */
- { "ist", tZONE, -HOUR(2) }, /* Israel Standard */
- { "idt", tDAYZONE, -HOUR(2) }, /* Israel Daylight */
- { "idt", tDAYZONE, -(HOUR(3)+30) }, /* Iran Daylight */
- { "ist", tZONE, -(HOUR(3)+30) }, /* Iran Standard */
- { "cst", tZONE, -HOUR(8) }, /* China Standard */
- { "cdt", tDAYZONE, -HOUR(8) }, /* China Daylight */
- { "sst", tZONE, -HOUR(8) }, /* Singapore Standard */
-
- /* Dubious (e.g., not in Olson's TIMEZONE package) or obsolete. */
- { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */
- { "wat", tZONE, -HOUR(1) }, /* West Africa */
- { "at", tZONE, HOUR( 2) }, /* Azores */
- { "gst", tZONE, -HOUR(10) }, /* Guam Standard */
- { "nft", tZONE, HOUR(3)+30 }, /* Newfoundland */
- { "idlw", tZONE, HOUR(12) }, /* International Date Line West */
- { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */
- { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
- { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */
- { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */
- { "fwt", tZONE, -HOUR(1) }, /* French Winter */
- { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */
- { "bt", tZONE, -HOUR(3) }, /* Baghdad */
- { "it", tZONE, -(HOUR(3)+30) }, /* Iran */
- { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */
- { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */
- { "ist", tZONE, -(HOUR(5)+30) }, /* Indian Standard */
- { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */
- { "nst", tZONE, -HOUR(7) }, /* North Sumatra */
- { "sst", tZONE, -HOUR(7) }, /* South Sumatra */
- { "jt", tZONE, -(HOUR(7)+30) }, /* Java (3pm in Cronusland!) */
- { "nzt", tZONE, -HOUR(12) }, /* New Zealand */
- { "idle", tZONE, -HOUR(12) }, /* International Date Line East */
- { "cat", tZONE, HOUR(10) }, /* -- expired 1967 */
- { "nt", tZONE, HOUR(11) }, /* -- expired 1967 */
- { "ahst", tZONE, HOUR(10) }, /* -- expired 1983 */
- { "hdt", tDAYZONE, HOUR(10) }, /* -- expired 1986 */
-#endif /* 0 */
-};
-
-
-/* ARGSUSED */
-static void
-date_error(char *s)
-{
- /* NOTREACHED */
-}
-
-
-static time_t
-ToSeconds(time_t Hours, time_t Minutes, time_t Seconds, MERIDIAN Meridian)
-{
- if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 61)
- return -1;
- if (Meridian == MER24) {
- if (Hours < 0 || Hours > 23)
- return -1;
- }
- else {
- if (Hours < 1 || Hours > 12)
- return -1;
- if (Hours == 12)
- Hours = 0;
- if (Meridian == MERpm)
- Hours += 12;
- }
- return (Hours * 60L + Minutes) * 60L + Seconds;
-}
-
-
-static time_t
-Convert(time_t Month, time_t Day, time_t Year,
- time_t Hours, time_t Minutes, time_t Seconds,
- MERIDIAN Meridian, DSTMODE dst)
-{
- static int DaysNormal[13] = {
- 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
- };
- static int DaysLeap[13] = {
- 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
- };
- static int LeapYears[] = {
- 1972, 1976, 1980, 1984, 1988, 1992, 1996,
- 2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036
- };
- register int *yp;
- register int *mp;
- register time_t Julian;
- register int i;
- time_t tod;
-
- if (Year < 0)
- Year = -Year;
- if (Year < 100)
- Year += 1900;
- if (Year < EPOCH)
- Year += 100;
- for (mp = DaysNormal, yp = LeapYears; yp < ENDOF(LeapYears); yp++)
- if (Year == *yp) {
- mp = DaysLeap;
- break;
- }
- if (Year < EPOCH || Year > END_OF_TIME
- || Month < 1 || Month > 12
- /* NOSTRICT *//* conversion from long may lose accuracy */
- || Day < 1 || Day > mp[(int)Month])
- return -1;
-
- Julian = Day - 1 + (Year - EPOCH) * 365;
- for (yp = LeapYears; yp < ENDOF(LeapYears); yp++, Julian++)
- if (Year <= *yp)
- break;
- for (i = 1; i < Month; i++)
- Julian += *++mp;
- Julian *= SECSPERDAY;
- Julian += yyTimezone * 60L;
- if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
- return -1;
- Julian += tod;
- tod = Julian;
- if (dst == DSTon || (dst == DSTmaybe && localtime(&tod)->tm_isdst))
- Julian -= DST_OFFSET * 60L * 60L;
- return Julian;
-}
-
-
-static time_t
-DSTcorrect(time_t Start, time_t Future)
-{
- time_t StartDay;
- time_t FutureDay;
-
- StartDay = (localtime(&Start)->tm_hour + 1) % 24;
- FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
- return (Future - Start) + (StartDay - FutureDay) * DST_OFFSET * 60L * 60L;
-}
-
-
-static time_t
-RelativeMonth(time_t Start, time_t RelMonth)
-{
- struct tm *tm;
- time_t Month;
- time_t Year;
-
- tm = localtime(&Start);
- Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
- Year = Month / 12;
- Month = Month % 12 + 1;
- return DSTcorrect(Start,
- Convert(Month, (time_t)tm->tm_mday, Year,
- (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
- MER24, DSTmaybe));
-}
-
-
-static int
-LookupWord(char *buff, register int length)
-{
- register char *p;
- register char *q;
- register TABLE *tp;
- register int c;
-
- p = buff;
- c = p[0];
-
- /* See if we have an abbreviation for a month. */
- if (length == 3 || (length == 4 && p[3] == '.'))
- for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++) {
- q = tp->name;
- if (c == q[0] && p[1] == q[1] && p[2] == q[2]) {
- yylval.Number = tp->value;
- return tp->type;
- }
- }
- else
- for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++)
- if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
- yylval.Number = tp->value;
- return tp->type;
- }
-
- /* Try for a timezone. */
- for (tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
- if (c == tp->name[0] && p[1] == tp->name[1]
- && strcmp(p, tp->name) == 0) {
- yylval.Number = tp->value;
- return tp->type;
- }
-
- /* Try the units table. */
- for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
- if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
- yylval.Number = tp->value;
- return tp->type;
- }
-
- /* Strip off any plural and try the units table again. */
- if (--length > 0 && p[length] == 's') {
- p[length] = '\0';
- for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
- if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
- p[length] = 's';
- yylval.Number = tp->value;
- return tp->type;
- }
- p[length] = 's';
- }
- length++;
-
- /* Drop out any periods. */
- for (p = buff, q = (char*)buff; *q; q++)
- if (*q != '.')
- *p++ = *q;
- *p = '\0';
-
- /* Try the meridians. */
- if (buff[1] == 'm' && buff[2] == '\0') {
- if (buff[0] == 'a') {
- yylval.Meridian = MERam;
- return tMERIDIAN;
- }
- if (buff[0] == 'p') {
- yylval.Meridian = MERpm;
- return tMERIDIAN;
- }
- }
-
- /* If we saw any periods, try the timezones again. */
- if (p - buff != length) {
- c = buff[0];
- for (p = buff, tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
- if (c == tp->name[0] && p[1] == tp->name[1]
- && strcmp(p, tp->name) == 0) {
- yylval.Number = tp->value;
- return tp->type;
- }
- }
-
- /* Unknown word -- assume GMT timezone. */
- yylval.Number = 0;
- return tZONE;
-}
-
-
-int
-date_lex(void)
-{
- register char c;
- register char *p;
- char buff[20];
- register int sign;
- register int i;
- register int nesting;
-
- for ( ; ; ) {
- /* Get first character after the whitespace. */
- for ( ; ; ) {
- while (isspace(*yyInput))
- yyInput++;
- c = *yyInput;
-
- /* Ignore RFC 822 comments, typically time zone names. */
- if (c != LPAREN)
- break;
- for (nesting = 1; (c = *++yyInput) != RPAREN || --nesting; )
- if (c == LPAREN)
- nesting++;
- else if (!IS7BIT(c) || c == '\0' || c == '\r'
- || (c == '\\' && ((c = *++yyInput) == '\0' || !IS7BIT(c))))
- /* Lexical error: bad comment. */
- return '?';
- yyInput++;
- }
-
- /* A number? */
- if (isdigit(c) || c == '-' || c == '+') {
- if (c == '-' || c == '+') {
- sign = c == '-' ? -1 : 1;
- yyInput++;
- if (!isdigit(*yyInput))
- /* Skip the plus or minus sign. */
- continue;
- }
- else
- sign = 0;
- for (i = 0; (c = *yyInput++) != '\0' && isdigit(c); )
- i = 10 * i + c - '0';
- yyInput--;
- yylval.Number = sign < 0 ? -i : i;
- return sign ? tSNUMBER : tUNUMBER;
- }
-
- /* A word? */
- if (isalpha(c)) {
- for (p = buff; (c = *yyInput++) == '.' || isalpha(c); )
- if (p < &buff[sizeof buff - 1])
- *p++ = isupper(c) ? tolower(c) : c;
- *p = '\0';
- yyInput--;
- return LookupWord(buff, p - buff);
- }
-
- return *yyInput++;
- }
-}
-
-
-time_t
-parsedate(const char *p)
-{
- extern int date_parse(void);
- time_t Start;
-
- yyInput = p; /* well, its supposed to be const... */
-
- yyYear = 0;
- yyMonth = 0;
- yyDay = 0;
- yyTimezone = 0;
- yyDSTmode = DSTmaybe;
- yyHour = 0;
- yyMinutes = 0;
- yySeconds = 0;
- yyMeridian = MER24;
- yyRelSeconds = 0;
- yyRelMonth = 0;
- yyHaveDate = 0;
- yyHaveRel = 0;
- yyHaveTime = 0;
-
- if (date_parse() || yyHaveTime > 1 || yyHaveDate > 1)
- return -1;
-
- if (yyHaveDate || yyHaveTime) {
- Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
- yyMeridian, yyDSTmode);
- if (Start < 0)
- return -1;
- }
- else
- return -1;
-
- Start += yyRelSeconds;
- if (yyRelMonth)
- Start += RelativeMonth(Start, yyRelMonth);
-
- /* Have to do *something* with a legitimate -1 so it's distinguishable
- * from the error return value. (Alternately could set errno on error.) */
- return Start == -1 ? 0 : Start;
-}
-
-
-#ifdef TEST
-
-#if YYDEBUG
-extern int yydebug;
-#endif /* YYDEBUG */
-
-/* ARGSUSED */
-int
-main(int ac, char *av[])
-{
- char buff[128];
- time_t d;
-
-#if YYDEBUG
- yydebug = 1;
-#endif /* YYDEBUG */
-
- (void)printf("Enter date, or blank line to exit.\n\t> ");
- for ( ; ; ) {
- (void)printf("\t> ");
- (void)fflush(stdout);
- if (fgets(buff, sizeof buff, stdin) == NULL || buff[0] == '\n')
- break;
-#if YYDEBUG
- if (strcmp(buff, "yydebug") == 0) {
- yydebug = !yydebug;
- printf("yydebug = %s\n", yydebug ? "on" : "off");
- continue;
- }
-#endif /* YYDEBUG */
- d = parsedate(buff, (TIMEINFO *)NULL);
- if (d == -1)
- (void)printf("Bad format - couldn't convert.\n");
- else
- (void)printf("%s", ctime(&d));
- }
-
- exit(0);
- /* NOTREACHED */
-}
-#endif /* TEST */
+++ /dev/null
-// Server functions which perform operations on room objects.
-//
-// Copyright (c) 1987-2022 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.
-
-#include <stdio.h>
-#include <libcitadel.h>
-
-#include "citserver.h"
-#include "ctdl_module.h"
-#include "config.h"
-#include "control.h"
-#include "user_ops.h"
-#include "room_ops.h"
-
-struct floor *floorcache[MAXFLOORS];
-
-// Determine whether the currently logged in session has permission to read
-// messages in the current room.
-int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
- if ( (!(CC->logged_in))
- && (!(CC->internal_pgm))
- && (!CtdlGetConfigInt("c_guest_logins"))
- ) {
- return(om_not_logged_in);
- }
- return(om_ok);
-}
-
-
-// Check to see whether we have permission to post a message in the current
-// room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
-// returns 0 on success.
-int CtdlDoIHavePermissionToPostInThisRoom(
- char *errmsgbuf,
- size_t n,
- const char* RemoteIdentifier,
- PostType PostPublic,
- int is_reply
-) {
- int ra;
-
- if (!(CC->logged_in) && (PostPublic == POST_LOGGED_IN)) {
- snprintf(errmsgbuf, n, "Not logged in.");
- return (ERROR + NOT_LOGGED_IN);
- }
- else if (PostPublic == CHECK_EXIST) {
- return (0); // evaluate whether a recipient exists
- }
- else if (!(CC->logged_in)) {
- if ((CC->room.QRflags & QR_READONLY)) {
- snprintf(errmsgbuf, n, "Not logged in.");
- return (ERROR + NOT_LOGGED_IN);
- }
- return (0);
- }
-
- if ((CC->user.axlevel < AxProbU) && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
- snprintf(errmsgbuf, n, "Need to be validated to enter (except in %s> to sysop)", MAILROOM);
- return (ERROR + HIGHER_ACCESS_REQUIRED);
- }
-
- CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
-
- if (ra & UA_POSTALLOWED) {
- strcpy(errmsgbuf, "OK to post or reply here");
- return(0);
- }
-
- if ( (ra & UA_REPLYALLOWED) && (is_reply) ) {
- // To be thorough, we ought to check to see if the message they are
- // replying to is actually a valid one in this room, but unless this
- // actually becomes a problem we'll go with high performance instead.
- strcpy(errmsgbuf, "OK to reply here");
- return(0);
- }
-
- if ( (ra & UA_REPLYALLOWED) && (!is_reply) ) {
- // Clarify what happened with a better error message
- snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
- return (ERROR + HIGHER_ACCESS_REQUIRED);
- }
-
- snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
- return (ERROR + HIGHER_ACCESS_REQUIRED);
-
-}
-
-
-// Check whether the current user has permission to delete messages from
-// the current room (returns 1 for yes, 0 for no)
-int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
- int ra;
- CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
- if (ra & UA_DELETEALLOWED) return(1);
- return(0);
-}
-
-
-// Retrieve access control information for any user/room pair.
-// Yes, it has a couple of gotos. If you don't like that, go die in a car fire.
-void CtdlRoomAccess(struct ctdlroom *roombuf, struct ctdluser *userbuf, int *result, int *view) {
- int retval = 0;
- visit vbuf;
- int is_me = 0;
- int is_guest = 0;
-
- if (userbuf == &CC->user) {
- is_me = 1;
- }
-
- if ((is_me) && (CtdlGetConfigInt("c_guest_logins")) && (!CC->logged_in)) {
- is_guest = 1;
- }
-
- // for internal programs, always do everything
- if (((CC->internal_pgm)) && (roombuf->QRflags & QR_INUSE)) {
- retval = (UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED);
- vbuf.v_view = 0;
- goto SKIP_EVERYTHING;
- }
-
- // If guest mode is enabled, always grant access to the Lobby
- if ((is_guest) && (!strcasecmp(roombuf->QRname, BASEROOM))) {
- retval = (UA_KNOWN | UA_GOTOALLOWED);
- vbuf.v_view = 0;
- goto SKIP_EVERYTHING;
- }
-
- // Locate any applicable user/room relationships
- if (is_guest) {
- memset(&vbuf, 0, sizeof vbuf);
- }
- else {
- CtdlGetRelationship(&vbuf, userbuf, roombuf);
- }
-
- // Force the properties of the Aide room
- if (!strcasecmp(roombuf->QRname, CtdlGetConfigStr("c_aideroom"))) {
- if (userbuf->axlevel >= AxAideU) {
- retval = UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED;
- } else {
- retval = 0;
- }
- goto NEWMSG;
- }
-
- // If this is a public room, it's accessible...
- if ( ((roombuf->QRflags & QR_PRIVATE) == 0)
- && ((roombuf->QRflags & QR_MAILBOX) == 0)
- ) {
- retval = retval | UA_KNOWN | UA_GOTOALLOWED;
- }
-
- // If this is a preferred users only room, check access level
- if (roombuf->QRflags & QR_PREFONLY) {
- if (userbuf->axlevel < AxPrefU) {
- retval = retval & ~UA_KNOWN & ~UA_GOTOALLOWED;
- }
- }
-
- // For private rooms, check the generation number matchups
- if ( (roombuf->QRflags & QR_PRIVATE)
- && ((roombuf->QRflags & QR_MAILBOX) == 0)
- ) {
-
- // An explicit match means the user belongs in this room
- if (vbuf.v_flags & V_ACCESS) {
- retval = retval | UA_KNOWN | UA_GOTOALLOWED;
- }
- // Otherwise, check if this is a guess-name or passworded
- // room. If it is, a goto may at least be attempted
- else if ( (roombuf->QRflags & QR_PRIVATE)
- || (roombuf->QRflags & QR_PASSWORDED)
- ) {
- retval = retval & ~UA_KNOWN;
- retval = retval | UA_GOTOALLOWED;
- }
- }
-
- // For mailbox rooms, also check the namespace. Also, mailbox owners can delete their messages
- if ( (roombuf->QRflags & QR_MAILBOX) && (atol(roombuf->QRname) != 0)) {
- if (userbuf->usernum == atol(roombuf->QRname)) {
- retval = retval | UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED;
- }
- // An explicit match means the user belongs in this room
- if (vbuf.v_flags & V_ACCESS) {
- retval = retval | UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED;
- }
- }
-
- // For non-mailbox rooms...
- else {
- // User is allowed to post in the room unless:
- // - User is not validated
- // - User has no net privileges and it is a shared network room
- // - It is a read-only room
- // - It is a blog room (in which case we only allow replies to existing messages)
- int post_allowed = 1;
- int reply_allowed = 1;
- if (userbuf->axlevel < AxProbU) {
- post_allowed = 0;
- reply_allowed = 0;
- }
- if ((userbuf->axlevel < AxNetU) && (roombuf->QRflags & QR_NETWORK)) {
- post_allowed = 0;
- reply_allowed = 0;
- }
- if (roombuf->QRflags & QR_READONLY) {
- post_allowed = 0;
- reply_allowed = 0;
- }
- if (roombuf->QRdefaultview == VIEW_BLOG) {
- post_allowed = 0;
- }
- if (post_allowed) {
- retval = retval | UA_POSTALLOWED | UA_REPLYALLOWED;
- }
- if (reply_allowed) {
- retval = retval | UA_REPLYALLOWED;
- }
-
- // If "collaborative deletion" is active for this room, any user who can post
- // is also allowed to delete
- if (roombuf->QRflags2 & QR2_COLLABDEL) {
- if (retval & UA_POSTALLOWED) {
- retval = retval | UA_DELETEALLOWED;
- }
- }
-
- }
-
- // Check to see if the user has forgotten this room
- if (vbuf.v_flags & V_FORGET) {
- retval = retval & ~UA_KNOWN;
- if ( ( ((roombuf->QRflags & QR_PRIVATE) == 0)
- && ((roombuf->QRflags & QR_MAILBOX) == 0)
- ) || ( (roombuf->QRflags & QR_MAILBOX)
- && (atol(roombuf->QRname) == CC->user.usernum))
- ) {
- retval = retval | UA_ZAPPED;
- }
- }
-
- // If user is explicitly locked out of this room, deny everything
- if (vbuf.v_flags & V_LOCKOUT) {
- retval = retval & ~UA_KNOWN & ~UA_GOTOALLOWED & ~UA_POSTALLOWED & ~UA_REPLYALLOWED;
- }
-
- // Aides get access to all private rooms
- if ( (userbuf->axlevel >= AxAideU)
- && ((roombuf->QRflags & QR_MAILBOX) == 0)
- ) {
- if (vbuf.v_flags & V_FORGET) {
- retval = retval | UA_GOTOALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED;
- }
- else {
- retval = retval | UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED;
- }
- }
-
- // Aides can gain access to mailboxes as well, but they don't show by default.
- if ( (userbuf->axlevel >= AxAideU)
- && (roombuf->QRflags & QR_MAILBOX)
- ) {
- retval = retval | UA_GOTOALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED;
- }
-
- // Aides and Room Aides have admin privileges
- if ( (userbuf->axlevel >= AxAideU)
- || (userbuf->usernum == roombuf->QRroomaide)
- ) {
- retval = retval | UA_ADMINALLOWED | UA_DELETEALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED;
- }
-
-NEWMSG: // By the way, we also check for the presence of new messages
- if (is_msg_in_sequence_set(vbuf.v_seen, roombuf->QRhighest) == 0) {
- retval = retval | UA_HASNEWMSGS;
- }
-
- // System rooms never show up in the list.
- if (roombuf->QRflags2 & QR2_SYSTEM) {
- retval = retval & ~UA_KNOWN;
- }
-
-SKIP_EVERYTHING:
- // Now give the caller the information it wants.
- if (result != NULL) *result = retval;
- if (view != NULL) *view = vbuf.v_view;
-}
-
-
-// Self-checking stuff for a room record read into memory
-void room_sanity_check(struct ctdlroom *qrbuf) {
- // Mailbox rooms are always on the lowest floor
- if (qrbuf->QRflags & QR_MAILBOX) {
- qrbuf->QRfloor = 0;
- }
- // Listing order of 0 is illegal except for base rooms
- if (qrbuf->QRorder == 0) {
- if ( !(qrbuf->QRflags & QR_MAILBOX)
- && strncasecmp(qrbuf->QRname, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)
- && strncasecmp(qrbuf->QRname, CtdlGetConfigStr("c_aideroom"), ROOMNAMELEN)
- ) {
- qrbuf->QRorder = 64;
- }
- }
-}
-
-
-// CtdlGetRoom() - retrieve room data from disk
-int CtdlGetRoom(struct ctdlroom *qrbuf, const char *room_name) {
- struct cdbdata *cdbqr;
- char lowercase_name[ROOMNAMELEN];
- char personal_lowercase_name[ROOMNAMELEN];
- const char *sptr;
- char *dptr, *eptr;
-
- dptr = lowercase_name;
- sptr = room_name;
- eptr = (dptr + (sizeof lowercase_name - 1));
- while (!IsEmptyStr(sptr) && (dptr < eptr)) {
- *dptr = tolower(*sptr);
- sptr++; dptr++;
- }
- *dptr = '\0';
-
- memset(qrbuf, 0, sizeof(struct ctdlroom));
-
- if (IsEmptyStr(lowercase_name)) {
- return(1); // empty room name , not valid
- }
-
- // First, try the public namespace
- cdbqr = cdb_fetch(CDB_ROOMS, lowercase_name, strlen(lowercase_name));
-
- // If that didn't work, try the user's personal namespace
- if (cdbqr == NULL) {
- snprintf(personal_lowercase_name, sizeof personal_lowercase_name, "%010ld.%s", CC->user.usernum, lowercase_name);
- cdbqr = cdb_fetch(CDB_ROOMS, personal_lowercase_name, strlen(personal_lowercase_name));
- }
- if (cdbqr != NULL) {
- memcpy(qrbuf, cdbqr->ptr, ((cdbqr->len > sizeof(struct ctdlroom)) ? sizeof(struct ctdlroom) : cdbqr->len));
- cdb_free(cdbqr);
- room_sanity_check(qrbuf);
- return (0);
- }
- else {
- return (1);
- }
-}
-
-
-// CtdlGetRoomLock() - same as getroom() but locks the record (if supported)
-int CtdlGetRoomLock(struct ctdlroom *qrbuf, const char *room_name) {
- register int retval;
- retval = CtdlGetRoom(qrbuf, room_name);
- if (retval == 0) begin_critical_section(S_ROOMS);
- return(retval);
-}
-
-
-// b_putroom() - back end to putroom() and b_deleteroom()
-// (if the supplied buffer is NULL, delete the room record)
-void b_putroom(struct ctdlroom *qrbuf, char *room_name) {
- char lowercase_name[ROOMNAMELEN];
- char *aptr, *bptr;
- long len;
-
- aptr = room_name;
- bptr = lowercase_name;
- while (!IsEmptyStr(aptr)) {
- *bptr = tolower(*aptr);
- aptr++;
- bptr++;
- }
- *bptr='\0';
-
- len = bptr - lowercase_name;
- if (qrbuf == NULL) {
- cdb_delete(CDB_ROOMS, lowercase_name, len);
- }
- else {
- time(&qrbuf->QRmtime);
- cdb_store(CDB_ROOMS, lowercase_name, len, qrbuf, sizeof(struct ctdlroom));
- }
-}
-
-
-// CtdlPutRoom() - store room data to disk
-void CtdlPutRoom(struct ctdlroom *qrbuf) {
- b_putroom(qrbuf, qrbuf->QRname);
-}
-
-
-// b_deleteroom() - delete a room record from disk
-void b_deleteroom(char *room_name) {
- b_putroom(NULL, room_name);
-}
-
-
-// CtdlPutRoomLock() - same as CtdlPutRoom() but unlocks the record (if supported)
-void CtdlPutRoomLock(struct ctdlroom *qrbuf) {
- CtdlPutRoom(qrbuf);
- end_critical_section(S_ROOMS);
-}
-
-
-// CtdlGetFloorByName() - retrieve the number of the named floor
-// return < 0 if not found else return floor number
-int CtdlGetFloorByName(const char *floor_name) {
- int a;
- struct floor *flbuf = NULL;
-
- for (a = 0; a < MAXFLOORS; ++a) {
- flbuf = CtdlGetCachedFloor(a);
-
- // check to see if it already exists
- if ((!strcasecmp(flbuf->f_name, floor_name)) && (flbuf->f_flags & F_INUSE)) {
- return a;
- }
- }
- return -1;
-}
-
-
-// CtdlGetFloorByNameLock() - retrieve floor number for given floor and lock the floor list.
-int CtdlGetFloorByNameLock(const char *floor_name) {
- begin_critical_section(S_FLOORTAB);
- return CtdlGetFloorByName(floor_name);
-}
-
-
-/*
- * CtdlGetAvailableFloor() - Return number of first unused floor
- * return < 0 if none available
- */
-int CtdlGetAvailableFloor(void) {
- int a;
- struct floor *flbuf = NULL;
-
- for (a = 0; a < MAXFLOORS; a++) {
- flbuf = CtdlGetCachedFloor(a);
-
- /* check to see if it already exists */
- if ((flbuf->f_flags & F_INUSE) == 0) {
- return a;
- }
- }
- return -1;
-}
-
-
-/*
- * CtdlGetFloor() - retrieve floor data from disk
- */
-void CtdlGetFloor(struct floor *flbuf, int floor_num) {
- struct cdbdata *cdbfl;
-
- memset(flbuf, 0, sizeof(struct floor));
- cdbfl = cdb_fetch(CDB_FLOORTAB, &floor_num, sizeof(int));
- if (cdbfl != NULL) {
- memcpy(flbuf, cdbfl->ptr, ((cdbfl->len > sizeof(struct floor)) ? sizeof(struct floor) : cdbfl->len));
- cdb_free(cdbfl);
- } else {
- if (floor_num == 0) {
- safestrncpy(flbuf->f_name, "Main Floor", sizeof flbuf->f_name);
- flbuf->f_flags = F_INUSE;
- flbuf->f_ref_count = 3;
- }
- }
-}
-
-
-/*
- * lgetfloor() - same as CtdlGetFloor() but locks the record (if supported)
- */
-void lgetfloor(struct floor *flbuf, int floor_num) {
- begin_critical_section(S_FLOORTAB);
- CtdlGetFloor(flbuf, floor_num);
-}
-
-
-/*
- * CtdlGetCachedFloor() - Get floor record from *cache* (loads from disk if needed)
- *
- * This is strictly a performance hack.
- */
-struct floor *CtdlGetCachedFloor(int floor_num) {
- static int initialized = 0;
- int i;
- int fetch_new = 0;
- struct floor *fl = NULL;
-
- begin_critical_section(S_FLOORCACHE);
- if (initialized == 0) {
- for (i=0; i<MAXFLOORS; ++i) {
- floorcache[floor_num] = NULL;
- }
- initialized = 1;
- }
- if (floorcache[floor_num] == NULL) {
- fetch_new = 1;
- }
- end_critical_section(S_FLOORCACHE);
-
- if (fetch_new) {
- fl = malloc(sizeof(struct floor));
- CtdlGetFloor(fl, floor_num);
- begin_critical_section(S_FLOORCACHE);
- if (floorcache[floor_num] != NULL) {
- free(floorcache[floor_num]);
- }
- floorcache[floor_num] = fl;
- end_critical_section(S_FLOORCACHE);
- }
-
- return(floorcache[floor_num]);
-}
-
-
-/*
- * CtdlPutFloor() - store floor data on disk
- */
-void CtdlPutFloor(struct floor *flbuf, int floor_num) {
- /* If we've cached this, clear it out, 'cuz it's WRONG now! */
- begin_critical_section(S_FLOORCACHE);
- if (floorcache[floor_num] != NULL) {
- free(floorcache[floor_num]);
- floorcache[floor_num] = malloc(sizeof(struct floor));
- memcpy(floorcache[floor_num], flbuf, sizeof(struct floor));
- }
- end_critical_section(S_FLOORCACHE);
-
- cdb_store(CDB_FLOORTAB, &floor_num, sizeof(int),
- flbuf, sizeof(struct floor));
-}
-
-
-/*
- * CtdlPutFloorLock() - same as CtdlPutFloor() but unlocks the record (if supported)
- */
-void CtdlPutFloorLock(struct floor *flbuf, int floor_num) {
- CtdlPutFloor(flbuf, floor_num);
- end_critical_section(S_FLOORTAB);
-
-}
-
-
-/*
- * lputfloor() - same as CtdlPutFloor() but unlocks the record (if supported)
- */
-void lputfloor(struct floor *flbuf, int floor_num) {
- CtdlPutFloorLock(flbuf, floor_num);
-}
-
-
-/*
- * Iterate through the room table, performing a callback for each room.
- */
-void CtdlForEachRoom(ForEachRoomCallBack callback_func, void *in_data) {
- struct ctdlroom qrbuf;
- struct cdbdata *cdbqr;
-
- cdb_rewind(CDB_ROOMS);
-
- while (cdbqr = cdb_next_item(CDB_ROOMS), cdbqr != NULL) {
- memset(&qrbuf, 0, sizeof(struct ctdlroom));
- memcpy(&qrbuf, cdbqr->ptr, ((cdbqr->len > sizeof(struct ctdlroom)) ? sizeof(struct ctdlroom) : cdbqr->len) );
- cdb_free(cdbqr);
- room_sanity_check(&qrbuf);
- if (qrbuf.QRflags & QR_INUSE) {
- callback_func(&qrbuf, in_data);
- }
- }
-}
-
-
-/*
- * delete_msglist() - delete room message pointers
- */
-void delete_msglist(struct ctdlroom *whichroom) {
- struct cdbdata *cdbml;
-
- /* Make sure the msglist we're deleting actually exists, otherwise
- * libdb will complain when we try to delete an invalid record
- */
- cdbml = cdb_fetch(CDB_MSGLISTS, &whichroom->QRnumber, sizeof(long));
- if (cdbml != NULL) {
- cdb_free(cdbml);
-
- /* Go ahead and delete it */
- cdb_delete(CDB_MSGLISTS, &whichroom->QRnumber, sizeof(long));
- }
-}
-
-
-/*
- * Message pointer compare function for sort_msglist()
- */
-int sort_msglist_cmp(const void *m1, const void *m2) {
- if ((*(const long *)m1) > (*(const long *)m2)) return(1);
- if ((*(const long *)m1) < (*(const long *)m2)) return(-1);
- return(0);
-}
-
-
-/*
- * sort message pointers
- * (returns new msg count)
- */
-int sort_msglist(long listptrs[], int oldcount) {
- int numitems;
- int i = 0;
-
- numitems = oldcount;
- if (numitems < 2) {
- return (oldcount);
- }
-
- /* do the sort */
- qsort(listptrs, numitems, sizeof(long), sort_msglist_cmp);
-
- /* and yank any nulls */
- while ((i < numitems) && (listptrs[i] == 0L)) i++;
-
- if (i > 0) {
- memmove(&listptrs[0], &listptrs[i], (sizeof(long) * (numitems - i)));
- numitems-=i;
- }
-
- return (numitems);
-}
-
-
-/*
- * Determine whether a given room is non-editable.
- */
-int CtdlIsNonEditable(struct ctdlroom *qrbuf) {
-
- /* Mail> rooms are non-editable */
- if ( (qrbuf->QRflags & QR_MAILBOX) && (!strcasecmp(&qrbuf->QRname[11], MAILROOM)) ) {
- return (1);
- }
-
- /* Everything else is editable */
- return (0);
-}
-
-
-/*
- * Make the specified room the current room for this session. No validation
- * or access control is done here -- the caller should make sure that the
- * specified room exists and is ok to access.
- */
-void CtdlUserGoto(char *where, int display_result, int transiently,
- int *retmsgs, int *retnew, long *retoldest, long *retnewest)
-{
- int a;
- int new_messages = 0;
- int old_messages = 0;
- int total_messages = 0;
- long oldest_message = 0;
- long newest_message = 0;
- int info = 0;
- int rmailflag;
- int raideflag;
- int newmailcount = 0;
- visit vbuf;
- char truncated_roomname[ROOMNAMELEN];
- struct cdbdata *cdbfr;
- long *msglist = NULL;
- int num_msgs = 0;
- unsigned int original_v_flags;
- int num_sets;
- int s;
- char setstr[128], lostr[64], histr[64];
- long lo, hi;
- int is_trash = 0;
-
- /* If the supplied room name is NULL, the caller wants us to know that
- * it has already copied the room record into CC->room, so
- * we can skip the extra database fetch.
- */
- if (where != NULL) {
- safestrncpy(CC->room.QRname, where, sizeof CC->room.QRname);
- CtdlGetRoom(&CC->room, where);
- }
-
- /* Take care of all the formalities. */
-
- begin_critical_section(S_USERS);
- CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
- original_v_flags = vbuf.v_flags;
-
- /* Know the room ... but not if it's the page log room, or if the
- * caller specified that we're only entering this room transiently.
- */
- int add_room_to_known_list = 1;
- if (transiently == 1) {
- add_room_to_known_list = 0;
- }
- char *c_logpages = CtdlGetConfigStr("c_logpages");
- if ( (c_logpages != NULL) && (!strcasecmp(CC->room.QRname, c_logpages)) ) {
- add_room_to_known_list = 0;
- }
- if (add_room_to_known_list) {
- vbuf.v_flags = vbuf.v_flags & ~V_FORGET & ~V_LOCKOUT;
- vbuf.v_flags = vbuf.v_flags | V_ACCESS;
- }
-
- /* Only rewrite the database record if we changed something */
- if (vbuf.v_flags != original_v_flags) {
- CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
- }
- end_critical_section(S_USERS);
-
- /* Check for new mail */
- newmailcount = NewMailCount();
-
- /* Set info to 1 if the room banner is new since our last visit.
- * Some clients only want to display it when it changes.
- */
- if (CC->room.msgnum_info > vbuf.v_lastseen) {
- info = 1;
- }
-
- cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
- if (cdbfr != NULL) {
- msglist = (long *) cdbfr->ptr;
- cdbfr->ptr = NULL; /* CtdlUserGoto() now owns this memory */
- num_msgs = cdbfr->len / sizeof(long);
- cdb_free(cdbfr);
- }
-
- total_messages = 0;
- for (a=0; a<num_msgs; ++a) {
- if (msglist[a] > 0L) ++total_messages;
- }
-
- if (total_messages > 0) {
- oldest_message = msglist[0];
- newest_message = msglist[num_msgs - 1];
- }
-
- num_sets = num_tokens(vbuf.v_seen, ',');
- for (s=0; s<num_sets; ++s) {
- extract_token(setstr, vbuf.v_seen, s, ',', sizeof setstr);
-
- extract_token(lostr, setstr, 0, ':', sizeof lostr);
- if (num_tokens(setstr, ':') >= 2) {
- extract_token(histr, setstr, 1, ':', sizeof histr);
- if (!strcmp(histr, "*")) {
- snprintf(histr, sizeof histr, "%ld", LONG_MAX);
- }
- }
- else {
- strcpy(histr, lostr);
- }
- lo = atol(lostr);
- hi = atol(histr);
-
- for (a=0; a<num_msgs; ++a) if (msglist[a] > 0L) {
- if ((msglist[a] >= lo) && (msglist[a] <= hi)) {
- ++old_messages;
- msglist[a] = 0L;
- }
- }
- }
- new_messages = total_messages - old_messages;
-
- if (msglist != NULL) free(msglist);
-
- if (CC->room.QRflags & QR_MAILBOX)
- rmailflag = 1;
- else
- rmailflag = 0;
-
- if ((CC->room.QRroomaide == CC->user.usernum) || (CC->user.axlevel >= AxAideU))
- raideflag = 1;
- else
- raideflag = 0;
-
- safestrncpy(truncated_roomname, CC->room.QRname, sizeof truncated_roomname);
- if ( (CC->room.QRflags & QR_MAILBOX) && (atol(CC->room.QRname) == CC->user.usernum) ) {
- safestrncpy(truncated_roomname, &truncated_roomname[11], sizeof truncated_roomname);
- }
-
- if (!strcasecmp(truncated_roomname, USERTRASHROOM)) {
- is_trash = 1;
- }
-
- if (retmsgs != NULL) *retmsgs = total_messages;
- if (retnew != NULL) *retnew = new_messages;
- if (retoldest != NULL) *retoldest = oldest_message;
- if (retnewest != NULL) *retnewest = newest_message;
- syslog(LOG_DEBUG, "room_ops: %s : %d new of %d total messages, oldest=%ld, newest=%ld",
- CC->room.QRname, new_messages, total_messages, oldest_message, newest_message
- );
-
- CC->curr_view = (int)vbuf.v_view;
-
- if (display_result) {
- cprintf("%d%c%s|%d|%d|%d|%d|%ld|%ld|%d|%d|%d|%d|%d|%d|%d|%d|%ld|\n",
- CIT_OK, CtdlCheckExpress(),
- truncated_roomname,
- (int)new_messages,
- (int)total_messages,
- (int)info,
- (int)CC->room.QRflags,
- (long)CC->room.QRhighest,
- (long)vbuf.v_lastseen,
- (int)rmailflag,
- (int)raideflag,
- (int)newmailcount,
- (int)CC->room.QRfloor,
- (int)vbuf.v_view,
- (int)CC->room.QRdefaultview,
- (int)is_trash,
- (int)CC->room.QRflags2,
- (long)CC->room.QRmtime
- );
- }
-}
-
-
-/*
- * Handle some of the macro named rooms
- */
-void convert_room_name_macros(char *towhere, size_t maxlen) {
- if (!strcasecmp(towhere, "_BASEROOM_")) {
- safestrncpy(towhere, CtdlGetConfigStr("c_baseroom"), maxlen);
- }
- else if (!strcasecmp(towhere, "_MAIL_")) {
- safestrncpy(towhere, MAILROOM, maxlen);
- }
- else if (!strcasecmp(towhere, "_TRASH_")) {
- safestrncpy(towhere, USERTRASHROOM, maxlen);
- }
- else if (!strcasecmp(towhere, "_DRAFTS_")) {
- safestrncpy(towhere, USERDRAFTROOM, maxlen);
- }
- else if (!strcasecmp(towhere, "_BITBUCKET_")) {
- safestrncpy(towhere, CtdlGetConfigStr("c_twitroom"), maxlen);
- }
- else if (!strcasecmp(towhere, "_CALENDAR_")) {
- safestrncpy(towhere, USERCALENDARROOM, maxlen);
- }
- else if (!strcasecmp(towhere, "_TASKS_")) {
- safestrncpy(towhere, USERTASKSROOM, maxlen);
- }
- else if (!strcasecmp(towhere, "_CONTACTS_")) {
- safestrncpy(towhere, USERCONTACTSROOM, maxlen);
- }
- else if (!strcasecmp(towhere, "_NOTES_")) {
- safestrncpy(towhere, USERNOTESROOM, maxlen);
- }
-}
-
-
-/*
- * Back end function to rename a room.
- * You can also specify which floor to move the room to, or specify -1 to
- * keep the room on the same floor it was on.
- *
- * If you are renaming a mailbox room, you must supply the namespace prefix
- * in *at least* the old name!
- */
-int CtdlRenameRoom(char *old_name, char *new_name, int new_floor) {
- int old_floor = 0;
- struct ctdlroom qrbuf;
- struct ctdlroom qrtmp;
- int ret = 0;
- struct floor *fl;
- struct floor flbuf;
- long owner = 0L;
- char actual_old_name[ROOMNAMELEN];
-
- syslog(LOG_DEBUG, "room_ops: CtdlRenameRoom(%s, %s, %d)", old_name, new_name, new_floor);
-
- if (new_floor >= 0) {
- fl = CtdlGetCachedFloor(new_floor);
- if ((fl->f_flags & F_INUSE) == 0) {
- return(crr_invalid_floor);
- }
- }
-
- begin_critical_section(S_ROOMS);
-
- if ( (CtdlGetRoom(&qrtmp, new_name) == 0) && (strcasecmp(new_name, old_name)) ) {
- ret = crr_already_exists;
- }
-
- else if (CtdlGetRoom(&qrbuf, old_name) != 0) {
- ret = crr_room_not_found;
- }
-
- else if ( (CC->user.axlevel < AxAideU) && (!CC->internal_pgm)
- && (CC->user.usernum != qrbuf.QRroomaide)
- && ( (((qrbuf.QRflags & QR_MAILBOX) == 0) || (atol(qrbuf.QRname) != CC->user.usernum))) ) {
- ret = crr_access_denied;
- }
-
- else if (CtdlIsNonEditable(&qrbuf)) {
- ret = crr_noneditable;
- }
-
- else {
- /* Rename it */
- safestrncpy(actual_old_name, qrbuf.QRname, sizeof actual_old_name);
- if (qrbuf.QRflags & QR_MAILBOX) {
- owner = atol(qrbuf.QRname);
- }
- if ( (owner > 0L) && (atol(new_name) == 0L) ) {
- snprintf(qrbuf.QRname, sizeof(qrbuf.QRname),
- "%010ld.%s", owner, new_name);
- }
- else {
- safestrncpy(qrbuf.QRname, new_name,
- sizeof(qrbuf.QRname));
- }
-
- /* Reject change of floor for baseroom/aideroom */
- if (!strncasecmp(old_name, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN) ||
- !strncasecmp(old_name, CtdlGetConfigStr("c_aideroom"), ROOMNAMELEN))
- {
- new_floor = 0;
- }
-
- /* Take care of floor stuff */
- old_floor = qrbuf.QRfloor;
- if (new_floor < 0) {
- new_floor = old_floor;
- }
- qrbuf.QRfloor = new_floor;
- CtdlPutRoom(&qrbuf);
-
- begin_critical_section(S_CONFIG);
-
- /* If baseroom/aideroom name changes, update config */
- if (!strncasecmp(old_name, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)) {
- CtdlSetConfigStr("c_baseroom", new_name);
- }
- if (!strncasecmp(old_name, CtdlGetConfigStr("c_aideroom"), ROOMNAMELEN)) {
- CtdlSetConfigStr("c_aideroom", new_name);
- }
-
- end_critical_section(S_CONFIG);
-
- /* If the room name changed, then there are now two room
- * records, so we have to delete the old one.
- */
- if (strcasecmp(new_name, old_name)) {
- b_deleteroom(actual_old_name);
- }
-
- ret = crr_ok;
- }
-
- end_critical_section(S_ROOMS);
-
- /* Adjust the floor reference counts if necessary */
- if (new_floor != old_floor) {
- lgetfloor(&flbuf, old_floor);
- --flbuf.f_ref_count;
- lputfloor(&flbuf, old_floor);
- syslog(LOG_DEBUG, "room_ops: reference count for floor %d is now %d", old_floor, flbuf.f_ref_count);
- lgetfloor(&flbuf, new_floor);
- ++flbuf.f_ref_count;
- lputfloor(&flbuf, new_floor);
- syslog(LOG_DEBUG, "room_ops: reference count for floor %d is now %d", new_floor, flbuf.f_ref_count);
- }
-
- /* ...and everybody say "YATTA!" */
- return(ret);
-}
-
-
-/*
- * Asynchronously schedule a room for deletion. By placing the room into an invalid private namespace,
- * the room will appear deleted to the user(s), but the session doesn't need to block while waiting for
- * database operations to complete. Instead, the room gets purged when THE DREADED AUTO-PURGER makes
- * its next run. Aren't we so clever?!!
- */
-void CtdlScheduleRoomForDeletion(struct ctdlroom *qrbuf) {
- char old_name[ROOMNAMELEN];
- static int seq = 0;
-
- syslog(LOG_NOTICE, "room_ops: scheduling room <%s> for deletion", qrbuf->QRname);
-
- safestrncpy(old_name, qrbuf->QRname, sizeof old_name);
- CtdlGetRoom(qrbuf, qrbuf->QRname);
-
- /* Turn the room into a private mailbox owned by a user who doesn't
- * exist. This will immediately make the room invisible to everyone,
- * and qualify the room for purging.
- */
- snprintf(qrbuf->QRname, sizeof qrbuf->QRname, "9999999999.%08lx.%04d.%s",
- time(NULL),
- ++seq,
- old_name
- );
- qrbuf->QRflags |= QR_MAILBOX;
- time(&qrbuf->QRgen); /* Use a timestamp as the new generation number */
- CtdlPutRoom(qrbuf);
- b_deleteroom(old_name);
-}
-
-
-/*
- * Back end processing to delete a room and everything associated with it
- * (This one is synchronous and should only get called by THE DREADED
- * AUTO-PURGER in serv_expire.c. All user-facing code should call
- * the asynchronous schedule_room_for_deletion() instead.)
- */
-void CtdlDeleteRoom(struct ctdlroom *qrbuf) {
- struct floor flbuf;
- char configdbkeyname[25];
-
- syslog(LOG_NOTICE, "room_ops: deleting room <%s>", qrbuf->QRname);
-
- // Delete the room's network configdb entry
- netcfg_keyname(configdbkeyname, qrbuf->QRnumber);
- CtdlDelConfig(configdbkeyname);
-
- // Delete the messages in the room
- // (Careful: this opens an S_ROOMS critical section!)
- CtdlDeleteMessages(qrbuf->QRname, NULL, 0, "");
-
- // Flag the room record as not in use
- CtdlGetRoomLock(qrbuf, qrbuf->QRname);
- qrbuf->QRflags = 0;
- CtdlPutRoomLock(qrbuf);
-
- // then decrement the reference count for the floor
- lgetfloor(&flbuf, (int) (qrbuf->QRfloor));
- flbuf.f_ref_count = flbuf.f_ref_count - 1;
- lputfloor(&flbuf, (int) (qrbuf->QRfloor));
-
- // Delete the room record from the database!
- b_deleteroom(qrbuf->QRname);
-}
-
-
-/*
- * Check access control for deleting a room
- */
-int CtdlDoIHavePermissionToDeleteThisRoom(struct ctdlroom *qr) {
-
- if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
- return(0);
- }
-
- if (CtdlIsNonEditable(qr)) {
- return(0);
- }
-
- /*
- * For mailboxes, check stuff
- */
- if (qr->QRflags & QR_MAILBOX) {
-
- if (strlen(qr->QRname) < 12) return(0); /* bad name */
-
- if (atol(qr->QRname) != CC->user.usernum) {
- return(0); /* not my room */
- }
-
- /* Can't delete your Mail> room */
- if (!strcasecmp(&qr->QRname[11], MAILROOM)) return(0);
-
- /* Otherwise it's ok */
- return(1);
- }
-
- /*
- * For normal rooms, just check for admin or room admin status.
- */
- return(is_room_aide());
-}
-
-
-/*
- * Internal code to create a new room (returns room flags)
- *
- * Room types: 0=public, 1=hidden, 2=passworded, 3=invitation-only,
- * 4=mailbox, 5=mailbox, but caller supplies namespace
- */
-unsigned CtdlCreateRoom(char *new_room_name,
- int new_room_type,
- char *new_room_pass,
- int new_room_floor,
- int really_create,
- int avoid_access,
- int new_room_view)
-{
- struct ctdlroom qrbuf;
- struct floor flbuf;
- visit vbuf;
-
- syslog(LOG_DEBUG, "room_ops: CtdlCreateRoom(name=%s, type=%d, view=%d)", new_room_name, new_room_type, new_room_view);
-
- if (CtdlGetRoom(&qrbuf, new_room_name) == 0) {
- syslog(LOG_DEBUG, "room_ops: cannot create room <%s> - already exists", new_room_name);
- return(0);
- }
-
- memset(&qrbuf, 0, sizeof(struct ctdlroom));
- safestrncpy(qrbuf.QRpasswd, new_room_pass, sizeof qrbuf.QRpasswd);
- qrbuf.QRflags = QR_INUSE;
- if (new_room_type > 0)
- qrbuf.QRflags = (qrbuf.QRflags | QR_PRIVATE);
- if (new_room_type == 1)
- qrbuf.QRflags = (qrbuf.QRflags | QR_GUESSNAME);
- if (new_room_type == 2)
- qrbuf.QRflags = (qrbuf.QRflags | QR_PASSWORDED);
- if ( (new_room_type == 4) || (new_room_type == 5) ) {
- qrbuf.QRflags = (qrbuf.QRflags | QR_MAILBOX);
- /* qrbuf.QRflags2 |= QR2_SUBJECTREQ; */
- }
-
- /* If the user is requesting a personal room, set up the room
- * name accordingly (prepend the user number)
- */
- if (new_room_type == 4) {
- CtdlMailboxName(qrbuf.QRname, sizeof qrbuf.QRname, &CC->user, new_room_name);
- }
- else {
- safestrncpy(qrbuf.QRname, new_room_name, sizeof qrbuf.QRname);
- }
-
- /* If the room is private, and the system administrator has elected
- * to automatically grant room admin privileges, do so now.
- */
- if ((qrbuf.QRflags & QR_PRIVATE) && (CREATAIDE == 1)) {
- qrbuf.QRroomaide = CC->user.usernum;
- }
- /* Blog owners automatically become room admins of their blogs.
- * (In the future we will offer a site-wide configuration setting to suppress this behavior.)
- */
- else if (new_room_view == VIEW_BLOG) {
- qrbuf.QRroomaide = CC->user.usernum;
- }
- /* Otherwise, set the room admin to undefined.
- */
- else {
- qrbuf.QRroomaide = (-1L);
- }
-
- /*
- * If the caller is only interested in testing whether this will work,
- * return now without creating the room.
- */
- if (!really_create) return (qrbuf.QRflags);
-
- qrbuf.QRnumber = get_new_room_number();
- qrbuf.QRhighest = 0L; /* No messages in this room yet */
- time(&qrbuf.QRgen); /* Use a timestamp as the generation number */
- qrbuf.QRfloor = new_room_floor;
- qrbuf.QRdefaultview = new_room_view;
-
- /* save what we just did... */
- CtdlPutRoom(&qrbuf);
-
- /* bump the reference count on whatever floor the room is on */
- lgetfloor(&flbuf, (int) qrbuf.QRfloor);
- flbuf.f_ref_count = flbuf.f_ref_count + 1;
- lputfloor(&flbuf, (int) qrbuf.QRfloor);
-
- /* Grant the creator access to the room unless the avoid_access
- * parameter was specified.
- */
- if ( (CC->logged_in) && (avoid_access == 0) ) {
- CtdlGetRelationship(&vbuf, &CC->user, &qrbuf);
- vbuf.v_flags = vbuf.v_flags & ~V_FORGET & ~V_LOCKOUT;
- vbuf.v_flags = vbuf.v_flags | V_ACCESS;
- CtdlSetRelationship(&vbuf, &CC->user, &qrbuf);
- }
-
- /* resume our happy day */
- return (qrbuf.QRflags);
-}
+++ /dev/null
-int is_known (struct ctdlroom *roombuf, int roomnum, struct ctdluser *userbuf);
-int has_newmsgs (struct ctdlroom *roombuf, int roomnum, struct ctdluser *userbuf);
-int is_zapped (struct ctdlroom *roombuf, int roomnum, struct ctdluser *userbuf);
-void b_putroom(struct ctdlroom *qrbuf, char *room_name);
-void b_deleteroom(char *);
-void lgetfloor (struct floor *flbuf, int floor_num);
-void lputfloor (struct floor *flbuf, int floor_num);
-int sort_msglist (long int *listptrs, int oldcount);
-void list_roomname(struct ctdlroom *qrbuf, int ra, int current_view, int default_view);
-void convert_room_name_macros(char *towhere, size_t maxlen);
-
-typedef enum _POST_TYPE{
- POST_LOGGED_IN,
- POST_EXTERNAL,
- CHECK_EXIST,
- POST_LMTP
-} PostType;
-
-int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
- size_t n,
- const char* RemoteIdentifier,
- PostType PostPublic,
- int is_reply
-);
-int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void);
-int CtdlDoIHavePermissionToReadMessagesInThisRoom(void);
+++ /dev/null
-#!/bin/sh
-#
-# Script to generate svn_revision.c
-#
-
-ECHO=/usr/bin/printf
-
-
-SCRIPT_DIR=`dirname $0`
-SRC_DIR=`dirname $SCRIPT_DIR`
-CUR_DIR=`pwd`
-C_FILE="$CUR_DIR/svn_revision.c"
-H_FILE="$CUR_DIR/svn_revision.h"
-
-# determine if this code base came from subversion.
-if test -d $SRC_DIR/.svn ; then
- echo "have subversion repository"
- SVNVERSION=`which svnversion`
- if test -x $SVNVERSION ; then
- echo "have svnversion at $SVNVERSION"
- BUILD=`svnversion -n .`
- echo "This code base svn-revision: $BUILD"
- CAN_BUILD_SVN_REVISION="yes"
- fi
-else
- if test -d $SRC_DIR/../.git ; then
- echo "have Git repository."
- BUILD=`/usr/bin/env git log -1 --pretty=%h . `
- echo "This code base git-revision: $BUILD"
- CAN_BUILD_SVN_REVISION="yes"
- else
- if test -f $C_FILE; then
- exit
- fi
- fi
-fi
-
-if [ "$CAN_BUILD_SVN_REVISION" = "yes" ] ; then
-
-cat <<EOF > $C_FILE
-/*
- * Subversion / GIT revision functions
- *
- * Autogenerated at make/release time
- *
- * Do not modify this file
- *
- */
-
-const char *svn_revision (void)
-{
- const char *SVN_Version = "$BUILD";
- return SVN_Version;
-}
-EOF
-
-elif test ! -f $C_FILE ; then
-
-cat <<EOF > $C_FILE
-/*
- * Subversion / GIT revision functions
- *
- * Autogenerated at make time
- *
- * There should have been one with your source distribution
- *
- * Do not modify this file
- *
- */
-
-const char *svn_revision (void)
-{
- const char *SVN_Version = "(unknown)";
- return SVN_Version;
-}
-EOF
-
-fi
+++ /dev/null
-/*
- * Citadel Extension Loader
- * Originally written by Brian Costello <btx@calyx.net>
- *
- * Copyright (c) 1987-2021 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.
- */
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <sys/stat.h>
-#include <libcitadel.h>
-#include "sysdep_decls.h"
-#include "modules/crypto/serv_crypto.h" /* Needed until a universal crypto startup hook is implimented for CtdlStartTLS */
-#include "serv_extensions.h"
-#include "ctdl_module.h"
-#include "config.h"
-
-
-/*
- * Structure defentitions for hook tables
- */
-
-typedef struct FixedOutputHook FixedOutputHook;
-struct FixedOutputHook {
- FixedOutputHook *next;
- char content_type[64];
- void (*h_function_pointer) (char *, int);
-};
-FixedOutputHook *FixedOutputTable = NULL;
-
-
-/*
- * SessionFunctionHook extensions are used for any type of hook for which
- * the context in which it's being called (which is determined by the event
- * type) will make it obvious for the hook function to know where to look for
- * pertinent data.
- */
-typedef struct SessionFunctionHook SessionFunctionHook;
-struct SessionFunctionHook {
- SessionFunctionHook *next;
- int Priority;
- void (*h_function_pointer) (void);
- int eventtype;
-};
-SessionFunctionHook *SessionHookTable = NULL;
-
-
-/*
- * UserFunctionHook extensions are used for any type of hook which implements
- * an operation on a user or username (potentially) other than the one
- * operating the current session.
- */
-typedef struct UserFunctionHook UserFunctionHook;
-struct UserFunctionHook {
- UserFunctionHook *next;
- void (*h_function_pointer) (struct ctdluser *usbuf);
- int eventtype;
-};
-UserFunctionHook *UserHookTable = NULL;
-
-
-/*
- * MessageFunctionHook extensions are used for hooks which implement handlers
- * for various types of message operations (save, read, etc.)
- */
-typedef struct MessageFunctionHook MessageFunctionHook;
-struct MessageFunctionHook {
- MessageFunctionHook *next;
- int (*h_function_pointer) (struct CtdlMessage *msg, struct recptypes *recps);
- int eventtype;
-};
-MessageFunctionHook *MessageHookTable = NULL;
-
-
-/*
- * DeleteFunctionHook extensions are used for hooks which get called when a
- * message is about to be deleted.
- */
-typedef struct DeleteFunctionHook DeleteFunctionHook;
-struct DeleteFunctionHook {
- DeleteFunctionHook *next;
- void (*h_function_pointer) (char *target_room, long msgnum);
-};
-DeleteFunctionHook *DeleteHookTable = NULL;
-
-
-/*
- * ExpressMessageFunctionHook extensions are used for hooks which implement
- * the sending of an instant message through various channels. Any function
- * registered should return the number of recipients to whom the message was
- * successfully transmitted.
- */
-typedef struct XmsgFunctionHook XmsgFunctionHook;
-struct XmsgFunctionHook {
- XmsgFunctionHook *next;
- int (*h_function_pointer) (char *, char *, char *, char *);
- int order;
-};
-XmsgFunctionHook *XmsgHookTable = NULL;
-
-
-/*
- * RoomFunctionHook extensions are used for hooks which impliment room
- * processing functions when new messages are added.
- */
-typedef struct RoomFunctionHook RoomFunctionHook;
-struct RoomFunctionHook {
- RoomFunctionHook *next;
- int (*fcn_ptr) (struct ctdlroom *);
-};
-RoomFunctionHook *RoomHookTable = NULL;
-
-
-typedef struct SearchFunctionHook SearchFunctionHook;
-struct SearchFunctionHook {
- SearchFunctionHook *next;
- void (*fcn_ptr) (int *, long **, const char *);
- char *name;
-};
-SearchFunctionHook *SearchFunctionHookTable = NULL;
-
-ServiceFunctionHook *ServiceHookTable = NULL;
-
-typedef struct ProtoFunctionHook ProtoFunctionHook;
-struct ProtoFunctionHook {
- void (*handler) (char *cmdbuf);
- const char *cmd;
- const char *desc;
-};
-
-HashList *ProtoHookList = NULL;
-
-
-#define ERR_PORT (1 << 1)
-
-
-static StrBuf *portlist = NULL;
-static StrBuf *errormessages = NULL;
-
-
-long DetailErrorFlags;
-ConstStr Empty = {HKEY("")};
-char *ErrSubject = "Startup Problems";
-ConstStr ErrGeneral[] = {
- {HKEY("Citadel had trouble on starting up. ")},
- {HKEY(" This means, Citadel won't be the service provider for a specific service you configured it to.\n\n"
- "If you don't want Citadel to provide these services, turn them off in WebCit via: ")},
- {HKEY("To make both ways actualy take place restart the citserver with \"sendcommand down\"\n\n"
- "The errors returned by the system were:\n")},
- {HKEY("You can recheck the above if you follow this faq item:\n"
- "http://www.citadel.org/doku.php?id=faq:mastering_your_os:net#netstat")}
-};
-
-ConstStr ErrPortShort = { HKEY("We couldn't bind all ports you configured to be provided by Citadel Server.\n")};
-ConstStr ErrPortWhere = { HKEY("\"Admin->System Preferences->Network\".\n\nThe failed ports and sockets are: ")};
-ConstStr ErrPortHint = { HKEY("If you want Citadel to provide you with that functionality, "
- "check the output of \"netstat -lnp\" on Linux, or \"netstat -na\" on BSD"
- " and disable the program that binds these ports.\n")};
-
-
-void LogPrintMessages(long err)
-{
- StrBuf *Message;
- StrBuf *List, *DetailList;
- ConstStr *Short, *Where, *Hint;
-
-
- Message = NewStrBufPlain(NULL, StrLength(portlist) + StrLength(errormessages));
-
- DetailErrorFlags = DetailErrorFlags & ~err;
-
- switch (err)
- {
- case ERR_PORT:
- Short = &ErrPortShort;
- Where = &ErrPortWhere;
- Hint = &ErrPortHint;
- List = portlist;
- DetailList = errormessages;
- break;
- default:
- Short = &Empty;
- Where = &Empty;
- Hint = &Empty;
- List = NULL;
- DetailList = NULL;
- }
-
- StrBufAppendBufPlain(Message, CKEY(ErrGeneral[0]), 0);
- StrBufAppendBufPlain(Message, CKEY(*Short), 0);
- StrBufAppendBufPlain(Message, CKEY(ErrGeneral[1]), 0);
- StrBufAppendBufPlain(Message, CKEY(*Where), 0);
- StrBufAppendBuf(Message, List, 0);
- StrBufAppendBufPlain(Message, HKEY("\n\n"), 0);
- StrBufAppendBufPlain(Message, CKEY(*Hint), 0);
- StrBufAppendBufPlain(Message, HKEY("\n\n"), 0);
- StrBufAppendBufPlain(Message, CKEY(ErrGeneral[2]), 0);
- StrBufAppendBuf(Message, DetailList, 0);
- StrBufAppendBufPlain(Message, HKEY("\n\n"), 0);
- StrBufAppendBufPlain(Message, CKEY(ErrGeneral[3]), 0);
-
- syslog(LOG_ERR, "extensions: %s", ChrPtr(Message));
- syslog(LOG_ERR, "extensions: %s", ErrSubject);
- quickie_message("Citadel", NULL, NULL, AIDEROOM, ChrPtr(Message), FMT_FIXED, ErrSubject);
-
- FreeStrBuf(&Message);
- FreeStrBuf(&List);
- FreeStrBuf(&DetailList);
-}
-
-
-void AddPortError(char *Port, char *ErrorMessage)
-{
- long len;
-
- DetailErrorFlags |= ERR_PORT;
-
- len = StrLength(errormessages);
- if (len > 0) StrBufAppendBufPlain(errormessages, HKEY("; "), 0);
- else errormessages = NewStrBuf();
- StrBufAppendBufPlain(errormessages, ErrorMessage, -1, 0);
-
-
- len = StrLength(portlist);
- if (len > 0) StrBufAppendBufPlain(portlist, HKEY(";"), 0);
- else portlist = NewStrBuf();
- StrBufAppendBufPlain(portlist, Port, -1, 0);
-}
-
-
-int DLoader_Exec_Cmd(char *cmdbuf)
-{
- void *vP;
- ProtoFunctionHook *p;
-
- if (GetHash(ProtoHookList, cmdbuf, 4, &vP) && (vP != NULL)) {
- p = (ProtoFunctionHook*) vP;
- p->handler(&cmdbuf[5]);
- return 1;
- }
- return 0;
-}
-
-
-void CtdlRegisterProtoHook(void (*handler) (char *), char *cmd, char *desc)
-{
- ProtoFunctionHook *p;
-
- if (ProtoHookList == NULL)
- ProtoHookList = NewHash (1, FourHash);
-
-
- p = (ProtoFunctionHook *)
- malloc(sizeof(ProtoFunctionHook));
-
- if (p == NULL) {
- fprintf(stderr, "can't malloc new ProtoFunctionHook\n");
- exit(EXIT_FAILURE);
- }
- p->handler = handler;
- p->cmd = cmd;
- p->desc = desc;
-
- Put(ProtoHookList, cmd, 4, p, NULL);
- syslog(LOG_DEBUG, "extensions: registered server command %s (%s)", cmd, desc);
-}
-
-
-void CtdlRegisterSessionHook(void (*fcn_ptr) (void), int EventType, int Priority)
-{
- SessionFunctionHook *newfcn;
-
- newfcn = (SessionFunctionHook *)
- malloc(sizeof(SessionFunctionHook));
- newfcn->Priority = Priority;
- newfcn->h_function_pointer = fcn_ptr;
- newfcn->eventtype = EventType;
-
- SessionFunctionHook **pfcn;
- pfcn = &SessionHookTable;
- while ((*pfcn != NULL) &&
- ((*pfcn)->Priority < newfcn->Priority) &&
- ((*pfcn)->next != NULL))
- pfcn = &(*pfcn)->next;
-
- newfcn->next = *pfcn;
- *pfcn = newfcn;
-
- syslog(LOG_DEBUG, "extensions: registered a new session function (type %d Priority %d)", EventType, Priority);
-}
-
-
-void CtdlUnregisterSessionHook(void (*fcn_ptr) (void), int EventType)
-{
- SessionFunctionHook *cur, *p, *last;
- last = NULL;
- cur = SessionHookTable;
- while (cur != NULL) {
- if ((fcn_ptr == cur->h_function_pointer) &&
- (EventType == cur->eventtype))
- {
- syslog(LOG_DEBUG, "extensions: unregistered session function (type %d)", EventType);
- p = cur->next;
-
- free(cur);
- cur = NULL;
-
- if (last != NULL)
- last->next = p;
- else
- SessionHookTable = p;
- cur = p;
- }
- else {
- last = cur;
- cur = cur->next;
- }
- }
-}
-
-
-void CtdlRegisterUserHook(void (*fcn_ptr) (ctdluser *), int EventType)
-{
-
- UserFunctionHook *newfcn;
-
- newfcn = (UserFunctionHook *)
- malloc(sizeof(UserFunctionHook));
- newfcn->next = UserHookTable;
- newfcn->h_function_pointer = fcn_ptr;
- newfcn->eventtype = EventType;
- UserHookTable = newfcn;
-
- syslog(LOG_DEBUG, "extensions: registered a new user function (type %d)",
- EventType);
-}
-
-
-void CtdlUnregisterUserHook(void (*fcn_ptr) (struct ctdluser *), int EventType)
-{
- UserFunctionHook *cur, *p, *last;
- last = NULL;
- cur = UserHookTable;
- while (cur != NULL) {
- if ((fcn_ptr == cur->h_function_pointer) &&
- (EventType == cur->eventtype))
- {
- syslog(LOG_DEBUG, "extensions: unregistered user function (type %d)", EventType);
- p = cur->next;
-
- free(cur);
- cur = NULL;
-
- if (last != NULL)
- last->next = p;
- else
- UserHookTable = p;
- cur = p;
- }
- else {
- last = cur;
- cur = cur->next;
- }
- }
-}
-
-
-void CtdlRegisterMessageHook(int (*handler)(struct CtdlMessage *, struct recptypes *), int EventType)
-{
-
- MessageFunctionHook *newfcn;
-
- newfcn = (MessageFunctionHook *)
- malloc(sizeof(MessageFunctionHook));
- newfcn->next = MessageHookTable;
- newfcn->h_function_pointer = handler;
- newfcn->eventtype = EventType;
- MessageHookTable = newfcn;
-
- syslog(LOG_DEBUG, "extensions: registered a new message function (type %d)", EventType);
-}
-
-
-void CtdlUnregisterMessageHook(int (*handler)(struct CtdlMessage *, struct recptypes *), int EventType)
-{
- MessageFunctionHook *cur, *p, *last;
- last = NULL;
- cur = MessageHookTable;
- while (cur != NULL) {
- if ((handler == cur->h_function_pointer) &&
- (EventType == cur->eventtype))
- {
- syslog(LOG_DEBUG, "extensions: unregistered message function (type %d)", EventType);
- p = cur->next;
- free(cur);
- cur = NULL;
-
- if (last != NULL)
- last->next = p;
- else
- MessageHookTable = p;
- cur = p;
- }
- else {
- last = cur;
- cur = cur->next;
- }
- }
-}
-
-
-void CtdlRegisterRoomHook(int (*fcn_ptr)(struct ctdlroom *))
-{
- RoomFunctionHook *newfcn;
-
- newfcn = (RoomFunctionHook *)
- malloc(sizeof(RoomFunctionHook));
- newfcn->next = RoomHookTable;
- newfcn->fcn_ptr = fcn_ptr;
- RoomHookTable = newfcn;
-
- syslog(LOG_DEBUG, "extensions: registered a new room function");
-}
-
-
-void CtdlUnregisterRoomHook(int (*fcn_ptr)(struct ctdlroom *))
-{
- RoomFunctionHook *cur, *p, *last;
- last = NULL;
- cur = RoomHookTable;
- while (cur != NULL)
- {
- if (fcn_ptr == cur->fcn_ptr) {
- syslog(LOG_DEBUG, "extensions: unregistered room function");
- p = cur->next;
-
- free(cur);
- cur = NULL;
-
- if (last != NULL)
- last->next = p;
- else
- RoomHookTable = p;
- cur = p;
- }
- else {
- last = cur;
- cur = cur->next;
- }
- }
-}
-
-
-void CtdlRegisterDeleteHook(void (*handler)(char *, long) )
-{
- DeleteFunctionHook *newfcn;
-
- newfcn = (DeleteFunctionHook *)
- malloc(sizeof(DeleteFunctionHook));
- newfcn->next = DeleteHookTable;
- newfcn->h_function_pointer = handler;
- DeleteHookTable = newfcn;
-
- syslog(LOG_DEBUG, "extensions: registered a new delete function");
-}
-
-
-void CtdlUnregisterDeleteHook(void (*handler)(char *, long) )
-{
- DeleteFunctionHook *cur, *p, *last;
-
- last = NULL;
- cur = DeleteHookTable;
- while (cur != NULL) {
- if (handler == cur->h_function_pointer )
- {
- syslog(LOG_DEBUG, "extensions: unregistered delete function");
- p = cur->next;
- free(cur);
-
- if (last != NULL)
- last->next = p;
- else
- DeleteHookTable = p;
-
- cur = p;
- }
- else {
- last = cur;
- cur = cur->next;
- }
- }
-}
-
-
-void CtdlRegisterFixedOutputHook(char *content_type, void (*handler)(char *, int) )
-{
- FixedOutputHook *newfcn;
-
- newfcn = (FixedOutputHook *)
- malloc(sizeof(FixedOutputHook));
- newfcn->next = FixedOutputTable;
- newfcn->h_function_pointer = handler;
- safestrncpy(newfcn->content_type, content_type, sizeof newfcn->content_type);
- FixedOutputTable = newfcn;
-
- syslog(LOG_DEBUG, "extensions: registered a new fixed output function for %s", newfcn->content_type);
-}
-
-
-void CtdlUnregisterFixedOutputHook(char *content_type)
-{
- FixedOutputHook *cur, *p, *last;
-
- last = NULL;
- cur = FixedOutputTable;
- while (cur != NULL) {
- /* This will also remove duplicates if any */
- if (!strcasecmp(content_type, cur->content_type)) {
- syslog(LOG_DEBUG, "extensions: unregistered fixed output function for %s", content_type);
- p = cur->next;
- free(cur);
-
- if (last != NULL)
- last->next = p;
- else
- FixedOutputTable = p;
-
- cur = p;
- }
- else
- {
- last = cur;
- cur = cur->next;
- }
- }
-}
-
-
-/* returns nonzero if we found a hook and used it */
-int PerformFixedOutputHooks(char *content_type, char *content, int content_length)
-{
- FixedOutputHook *fcn;
-
- for (fcn = FixedOutputTable; fcn != NULL; fcn = fcn->next) {
- if (!strcasecmp(content_type, fcn->content_type)) {
- (*fcn->h_function_pointer) (content, content_length);
- return(1);
- }
- }
- return(0);
-}
-
-
-void CtdlRegisterXmsgHook(int (*fcn_ptr) (char *, char *, char *, char *), int order)
-{
-
- XmsgFunctionHook *newfcn;
-
- newfcn = (XmsgFunctionHook *) malloc(sizeof(XmsgFunctionHook));
- newfcn->next = XmsgHookTable;
- newfcn->order = order;
- newfcn->h_function_pointer = fcn_ptr;
- XmsgHookTable = newfcn;
- syslog(LOG_DEBUG, "extensions: registered a new x-msg function (priority %d)", order);
-}
-
-
-void CtdlUnregisterXmsgHook(int (*fcn_ptr) (char *, char *, char *, char *), int order)
-{
- XmsgFunctionHook *cur, *p, *last;
-
- last = NULL;
- cur = XmsgHookTable;
- while (cur != NULL) {
- /* This will also remove duplicates if any */
- if (fcn_ptr == cur->h_function_pointer &&
- order == cur->order) {
- syslog(LOG_DEBUG, "extensions: unregistered x-msg function (priority %d)", order);
- p = cur->next;
- free(cur);
-
- if (last != NULL) {
- last->next = p;
- }
- else {
- XmsgHookTable = p;
- }
- cur = p;
- }
- else {
- last = cur;
- cur = cur->next;
- }
- }
-}
-
-
-void CtdlRegisterServiceHook(int tcp_port,
- char *sockpath,
- void (*h_greeting_function) (void),
- void (*h_command_function) (void),
- void (*h_async_function) (void),
- const char *ServiceName)
-{
- ServiceFunctionHook *newfcn;
- char *message;
-
- newfcn = (ServiceFunctionHook *) malloc(sizeof(ServiceFunctionHook));
- message = (char*) malloc (SIZ + SIZ);
-
- newfcn->next = ServiceHookTable;
- newfcn->tcp_port = tcp_port;
- newfcn->sockpath = sockpath;
- newfcn->h_greeting_function = h_greeting_function;
- newfcn->h_command_function = h_command_function;
- newfcn->h_async_function = h_async_function;
- newfcn->ServiceName = ServiceName;
-
- if (sockpath != NULL) {
- newfcn->msock = ctdl_uds_server(sockpath, CtdlGetConfigInt("c_maxsessions"));
- snprintf(message, SIZ, "extensions: unix domain socket '%s': ", sockpath);
- }
- else if (tcp_port <= 0) { /* port -1 to disable */
- syslog(LOG_INFO, "extensions: service %s has been manually disabled, skipping", ServiceName);
- free (message);
- free(newfcn);
- return;
- }
- else {
- newfcn->msock = ctdl_tcp_server(CtdlGetConfigStr("c_ip_addr"), tcp_port, CtdlGetConfigInt("c_maxsessions"));
- snprintf(message, SIZ, "extensions: TCP port %s:%d: (%s) ",
- CtdlGetConfigStr("c_ip_addr"), tcp_port, ServiceName);
- }
-
- if (newfcn->msock > 0) {
- ServiceHookTable = newfcn;
- strcat(message, "registered.");
- syslog(LOG_INFO, "%s", message);
- }
- else {
- AddPortError(message, "failed");
- strcat(message, "FAILED.");
- syslog(LOG_ERR, "%s", message);
- free(newfcn);
- }
- free(message);
-}
-
-
-void CtdlUnregisterServiceHook(int tcp_port, char *sockpath,
- void (*h_greeting_function) (void),
- void (*h_command_function) (void),
- void (*h_async_function) (void)
- )
-{
- ServiceFunctionHook *cur, *p, *last;
-
- last = NULL;
- cur = ServiceHookTable;
- while (cur != NULL) {
- /* This will also remove duplicates if any */
- if (h_greeting_function == cur->h_greeting_function &&
- h_command_function == cur->h_command_function &&
- h_async_function == cur->h_async_function &&
- tcp_port == cur->tcp_port &&
- !(sockpath && cur->sockpath && strcmp(sockpath, cur->sockpath)) )
- {
- if (cur->msock > 0)
- close(cur->msock);
- if (sockpath) {
- syslog(LOG_INFO, "extensions: closed UNIX domain socket %s", sockpath);
- unlink(sockpath);
- } else if (tcp_port) {
- syslog(LOG_INFO, "extensions: closed TCP port %d", tcp_port);
- } else {
- syslog(LOG_INFO, "extensions: unregistered service \"%s\"", cur->ServiceName);
- }
- p = cur->next;
- free(cur);
- if (last != NULL)
- last->next = p;
- else
- ServiceHookTable = p;
- cur = p;
- }
- else {
- last = cur;
- cur = cur->next;
- }
- }
-}
-
-
-void CtdlShutdownServiceHooks(void)
-{
- /* sort of a duplicate of close_masters() but called earlier */
- ServiceFunctionHook *cur;
-
- cur = ServiceHookTable;
- while (cur != NULL)
- {
- if (cur->msock != -1)
- {
- close(cur->msock);
- cur->msock = -1;
- if (cur->sockpath != NULL){
- syslog(LOG_INFO, "extensions: [%s] Closed UNIX domain socket %s", cur->ServiceName, cur->sockpath);
- unlink(cur->sockpath);
- } else {
- syslog(LOG_INFO, "extensions: [%s] closing service", cur->ServiceName);
- }
- }
- cur = cur->next;
- }
-}
-
-
-void CtdlRegisterSearchFuncHook(void (*fcn_ptr)(int *, long **, const char *), char *name)
-{
- SearchFunctionHook *newfcn;
-
- if (!name || !fcn_ptr) {
- return;
- }
-
- newfcn = (SearchFunctionHook *)
- malloc(sizeof(SearchFunctionHook));
- newfcn->next = SearchFunctionHookTable;
- newfcn->name = name;
- newfcn->fcn_ptr = fcn_ptr;
- SearchFunctionHookTable = newfcn;
-
- syslog(LOG_DEBUG, "extensions: registered a new search function (%s)", name);
-}
-
-void CtdlUnregisterSearchFuncHook(void (*fcn_ptr)(int *, long **, const char *), char *name)
-{
- SearchFunctionHook *cur, *p, *last;
-
- last = NULL;
- cur = SearchFunctionHookTable;
- while (cur != NULL) {
- if (fcn_ptr &&
- (cur->fcn_ptr == fcn_ptr) &&
- name && !strcmp(name, cur->name))
- {
- syslog(LOG_DEBUG, "extensions: unregistered search function(%s)", name);
- p = cur->next;
- free (cur);
- if (last != NULL)
- last->next = p;
- else
- SearchFunctionHookTable = p;
- cur = p;
- }
- else {
- last = cur;
- cur = cur->next;
- }
- }
-}
-
-
-void CtdlModuleDoSearch(int *num_msgs, long **search_msgs, const char *search_string, const char *func_name)
-{
- SearchFunctionHook *fcn = NULL;
-
- for (fcn = SearchFunctionHookTable; fcn != NULL; fcn = fcn->next) {
- if (!func_name || !strcmp(func_name, fcn->name)) {
- (*fcn->fcn_ptr) (num_msgs, search_msgs, search_string);
- return;
- }
- }
- *num_msgs = 0;
-}
-
-
-void PerformSessionHooks(int EventType)
-{
- SessionFunctionHook *fcn = NULL;
-
- for (fcn = SessionHookTable; fcn != NULL; fcn = fcn->next) {
- if (fcn->eventtype == EventType) {
- if (EventType == EVT_TIMER) {
- pthread_setspecific(MyConKey, NULL); /* for every hook */
- }
- (*fcn->h_function_pointer) ();
- }
- }
-}
-
-void PerformUserHooks(ctdluser *usbuf, int EventType)
-{
- UserFunctionHook *fcn = NULL;
-
- for (fcn = UserHookTable; fcn != NULL; fcn = fcn->next) {
- if (fcn->eventtype == EventType) {
- (*fcn->h_function_pointer) (usbuf);
- }
- }
-}
-
-int PerformMessageHooks(struct CtdlMessage *msg, struct recptypes *recps, int EventType)
-{
- MessageFunctionHook *fcn = NULL;
- int total_retval = 0;
-
- /* Other code may elect to protect this message from server-side
- * handlers; if this is the case, don't do anything.
- */
- if (msg->cm_flags & CM_SKIP_HOOKS) {
- return(0);
- }
-
- /* Otherwise, run all the hooks appropriate to this event type.
- */
- for (fcn = MessageHookTable; fcn != NULL; fcn = fcn->next) {
- if (fcn->eventtype == EventType) {
- total_retval = total_retval + (*fcn->h_function_pointer) (msg, recps);
- }
- }
-
- /* Return the sum of the return codes from the hook functions. If
- * this is an EVT_BEFORESAVE event, a nonzero return code will cause
- * the save operation to abort.
- */
- return total_retval;
-}
-
-
-int PerformRoomHooks(struct ctdlroom *target_room)
-{
- RoomFunctionHook *fcn;
- int total_retval = 0;
-
- syslog(LOG_DEBUG, "extensions: performing room hooks for <%s>", target_room->QRname);
-
- for (fcn = RoomHookTable; fcn != NULL; fcn = fcn->next) {
- total_retval = total_retval + (*fcn->fcn_ptr) (target_room);
- }
-
- /* Return the sum of the return codes from the hook functions.
- */
- return total_retval;
-}
-
-
-void PerformDeleteHooks(char *room, long msgnum)
-{
- DeleteFunctionHook *fcn;
-
- for (fcn = DeleteHookTable; fcn != NULL; fcn = fcn->next) {
- (*fcn->h_function_pointer) (room, msgnum);
- }
-}
-
-
-int PerformXmsgHooks(char *sender, char *sender_email, char *recp, char *msg)
-{
- XmsgFunctionHook *fcn;
- int total_sent = 0;
- int p;
-
- for (p=0; p<MAX_XMSG_PRI; ++p) {
- for (fcn = XmsgHookTable; fcn != NULL; fcn = fcn->next) {
- if (fcn->order == p) {
- total_sent +=
- (*fcn->h_function_pointer)
- (sender, sender_email, recp, msg);
- }
- }
- /* Break out of the loop if a higher-priority function
- * successfully delivered the message. This prevents duplicate
- * deliveries to local users simultaneously signed onto
- * remote services.
- */
- if (total_sent) break;
- }
- return total_sent;
-}
-
-
-/*
- * Dirty hack until we impliment a hook mechanism for this
- */
-void CtdlModuleStartCryptoMsgs(char *ok_response, char *nosup_response, char *error_response)
-{
-#ifdef HAVE_OPENSSL
- CtdlStartTLS (ok_response, nosup_response, error_response);
-#endif
-}
-
-
-CTDL_MODULE_INIT(modules)
-{
- if (!threading) {
- }
- return "modules";
-}
+++ /dev/null
-
-#ifndef SERV_EXTENSIONS_H
-#define SERV_EXTENSIONS_H
-
-#include "server.h"
-
-/*
- * ServiceFunctionHook extensions are used for hooks which implement various
- * protocols (either on TCP or on unix domain sockets) directly in the Citadel server.
- */
-typedef struct ServiceFunctionHook ServiceFunctionHook;
-struct ServiceFunctionHook {
- ServiceFunctionHook *next;
- int tcp_port;
- char *sockpath;
- void (*h_greeting_function) (void) ;
- void (*h_command_function) (void) ;
- void (*h_async_function) (void) ;
- int msock;
- const char* ServiceName; /* this is just for debugging and logging purposes. */
-};
-extern ServiceFunctionHook *ServiceHookTable;
-
-void initialize_server_extensions(void);
-int DLoader_Exec_Cmd(char *cmdbuf);
-char *Dynamic_Module_Init(void);
-
-void PerformSessionHooks(int EventType);
-
-void PerformUserHooks(struct ctdluser *usbuf, int EventType);
-
-int PerformXmsgHooks(char *, char *, char *, char *);
-
-int PerformMessageHooks(struct CtdlMessage *, struct recptypes *recps, int EventType);
-
-int PerformRoomHooks(struct ctdlroom *);
-
-void PerformDeleteHooks(char *, long);
-
-
-
-
-
-int PerformFixedOutputHooks(char *, char *, int);
-
-void netcfg_keyname(char *keybuf, long roomnum);
-
-#endif /* SERV_EXTENSIONS_H */
+++ /dev/null
-void extract_inet_email_addrs(char *, size_t, char *, size_t, struct vCard *v, int local_addrs_only);
-struct vCard *vcard_get_user(struct ctdluser *u);
-
-enum {
- V2L_WRITE,
- V2L_DELETE
-};
-
+++ /dev/null
-/*
- * Main declarations file for the Citadel server
- *
- * Copyright (c) 1987-2022 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.
- */
-
-#ifndef SERVER_H
-#define SERVER_H
-
-#ifdef __GNUC__
-#define INLINE __inline__
-#else
-#define INLINE
-#endif
-
-#include "citadel.h"
-#ifdef HAVE_OPENSSL
-#define OPENSSL_NO_KRB5 /* work around redhat b0rken ssl headers */
-#include <openssl/ssl.h>
-#endif
-
-/*
- * New format for a message in memory
- */
-struct CtdlMessage {
- int cm_magic; /* Self-check (NOT SAVED TO DISK) */
- char cm_anon_type; /* Anonymous or author-visible */
- char cm_format_type; /* Format type */
- char *cm_fields[256]; /* Data fields */
- long cm_lengths[256]; /* size of datafields */
- unsigned int cm_flags; /* How to handle (NOT SAVED TO DISK) */
-};
-
-#define CTDLMESSAGE_MAGIC 0x159d
-#define CM_SKIP_HOOKS 0x01 /* Don't run server-side handlers */
-
-
-/* Data structure returned by validate_recipients() */
-struct recptypes {
- int recptypes_magic;
- int num_local;
- int num_internet;
- int num_room;
- int num_error;
- char *errormsg;
- char *recp_local;
- char *recp_internet;
- char *recp_room;
- char *recp_orgroom;
- char *display_recp;
- char *bounce_to;
- char *envelope_from;
- char *sending_room;
-};
-
-#define RECPTYPES_MAGIC 0xfeeb
-
-#define CTDLEXIT_SHUTDOWN 0 // Normal shutdown; do NOT auto-restart
-
-/*
- * Exit codes 101 through 109 are used for conditions in which
- * we deliberately do NOT want the service to automatically
- * restart.
- */
-#define CTDLEXIT_CONFIG 101 // Could not read system configuration
-#define CTDLEXIT_HOME 103 // Citadel home directory not found
-#define CTDLEXIT_DB 105 // Unable to initialize database
-#define CTDLEXIT_LIBCITADEL 106 // Incorrect version of libcitadel
-#define CTDL_EXIT_UNSUP_AUTH 107 // Unsupported auth mode configured
-#define CTDLEXIT_UNUSER 108 // Could not determine uid to run as
-#define CTDLEXIT_CRYPTO 109 // Problem initializing SSL or TLS
-
-/*
- * Reasons why a session would be terminated (set CC->kill_me to these values)
- */
-enum {
- KILLME_NOT,
- KILLME_UNKNOWN,
- KILLME_CLIENT_LOGGED_OUT,
- KILLME_IDLE,
- KILLME_CLIENT_DISCONNECTED,
- KILLME_AUTHFAILED,
- KILLME_SERVER_SHUTTING_DOWN,
- KILLME_MAX_SESSIONS_EXCEEDED,
- KILLME_ADMIN_TERMINATE,
- KILLME_SELECT_INTERRUPTED,
- KILLME_SELECT_FAILED,
- KILLME_WRITE_FAILED,
- KILLME_SIMULATION_WORKER,
- KILLME_NOLOGIN,
- KILLME_NO_CRYPTO,
- KILLME_READSTRING_FAILED,
- KILLME_MALLOC_FAILED,
- KILLME_QUOTA,
- KILLME_READ_FAILED,
- KILLME_SPAMMER,
- KILLME_XML_PARSER
-};
-
-
-#define CS_STEALTH 1 /* stealth mode */
-#define CS_CHAT 2 /* chat mode */
-#define CS_POSTING 4 /* Posting */
-
-extern int ScheduledShutdown;
-extern uid_t ctdluid;
-extern int sanity_diag_mode;
-
-struct ExpressMessage {
- struct ExpressMessage *next;
- time_t timestamp; /* When this message was sent */
- unsigned flags; /* Special instructions */
- char sender[256]; /* Name of sending user */
- char sender_email[256]; /* Email or JID of sending user */
- char *text; /* Message text (if applicable) */
-};
-
-#define EM_BROADCAST 1 /* Broadcast message */
-#define EM_GO_AWAY 2 /* Server requests client log off */
-#define EM_CHAT 4 /* Server requests client enter chat */
-
-/*
- * Various things we need to lock and unlock
- */
-enum {
- S_USERS,
- S_ROOMS,
- S_SESSION_TABLE,
- S_FLOORTAB,
- S_CHATQUEUE,
- S_CONTROL,
- S_NETDB,
- S_SUPPMSGMAIN,
- S_CONFIG,
- S_HOUSEKEEPING,
- S_DIRECTORY,
- S_NETCONFIGS,
- S_FLOORCACHE,
- S_ATBF,
- S_JOURNAL_QUEUE,
- S_CHKPWD,
- S_LOG,
- S_NETSPOOL,
- S_XMPP_QUEUE,
- S_SCHEDULE_LIST,
- S_SINGLE_USER,
- S_LDAP,
- S_IM_LOGS,
- S_OPENSSL,
- MAX_SEMAPHORES
-};
-
-
-/*
- * message transfer formats
- */
-enum {
- MT_CITADEL, /* Citadel proprietary */
- MT_RFC822, /* RFC822 */
- MT_MIME, /* MIME-formatted message */
- MT_DOWNLOAD, /* Download a component */
- MT_SPEW_SECTION /* Download a component in a single operation */
-};
-
-/*
- * Message format types in the database
- */
-#define FMT_CITADEL 0 /* Citadel vari-format (proprietary) */
-#define FMT_FIXED 1 /* Fixed format (proprietary) */
-#define FMT_RFC822 4 /* Standard (headers are in M field) */
-
-
-/*
- * Citadel DataBases (define one for each cdb we need to open)
- */
-enum {
- CDB_MSGMAIN, /* message base */
- CDB_USERS, /* user file */
- CDB_ROOMS, /* room index */
- CDB_FLOORTAB, /* floor index */
- CDB_MSGLISTS, /* room message lists */
- CDB_VISIT, /* user/room relationships */
- CDB_DIRECTORY, /* address book directory */
- CDB_USETABLE, /* network use table */
- CDB_BIGMSGS, /* larger message bodies */
- CDB_FULLTEXT, /* full text search index */
- CDB_EUIDINDEX, /* locate msgs by EUID */
- CDB_USERSBYNUMBER, /* index of users by number */
- CDB_EXTAUTH, /* associates OpenIDs with users */
- CDB_CONFIG, /* system configuration database */
- MAXCDB /* total number of CDB's defined */
-};
-
-struct cdbdata {
- size_t len;
- char *ptr;
-};
-
-
-/*
- * Event types can't be enum'ed, because they must remain consistent between
- * builds (to allow for binary modules built somewhere else)
- */
-#define EVT_STOP 0 /* Session is terminating */
-#define EVT_START 1 /* Session is starting */
-#define EVT_LOGIN 2 /* A user is logging in */
-#define EVT_NEWROOM 3 /* Changing rooms */
-#define EVT_LOGOUT 4 /* A user is logging out */
-#define EVT_SETPASS 5 /* Setting or changing password */
-#define EVT_CMD 6 /* Called after each server command */
-#define EVT_RWHO 7 /* An RWHO command is being executed */
-#define EVT_ASYNC 8 /* Doing asynchronous messages */
-#define EVT_STEALTH 9 /* Entering stealth mode */
-#define EVT_UNSTEALTH 10 /* Exiting stealth mode */
-
-#define EVT_TIMER 50 /* Timer events are called once per minute
- and are not tied to any session */
-#define EVT_HOUSE 51 /* as needed houskeeping stuff */
-#define EVT_SHUTDOWN 52 /* Server is shutting down */
-
-#define EVT_PURGEUSER 100 /* Deleting a user */
-#define EVT_NEWUSER 102 /* Creating a user */
-
-#define EVT_BEFORESAVE 201
-#define EVT_AFTERSAVE 202
-#define EVT_SMTPSCAN 203 /* called before submitting a msg from SMTP */
-#define EVT_AFTERUSRMBOXSAVE 204 /* called afte a message was saved into a users inbox */
-/* Priority levels for paging functions (lower is better) */
-enum {
- XMSG_PRI_LOCAL, /* Other users on -this- server */
- XMSG_PRI_REMOTE, /* Other users on a Citadel network (future) */
- XMSG_PRI_FOREIGN, /* Contacts on foreign instant message hosts */
- MAX_XMSG_PRI
-};
-
-
-/* Defines the relationship of a user to a particular room */
-typedef struct __visit {
- long v_roomnum;
- long v_roomgen;
- long v_usernum;
- long v_lastseen;
- unsigned int v_flags;
- char v_seen[SIZ];
- char v_answered[SIZ];
- int v_view;
-} visit;
-
-#define V_FORGET 1 /* User has zapped this room */
-#define V_LOCKOUT 2 /* User is locked out of this room */
-#define V_ACCESS 4 /* Access is granted to this room */
-
-
-/* Supplementary data for a message on disk
- * These are kept separate from the message itself for one of two reasons:
- * 1. Either their values may change at some point after initial save, or
- * 2. They are merely caches of data which exist somewhere else, for speed.
- * DO NOT PUT BIG DATA IN HERE ... we need this struct to be tiny for lots of quick r/w
- */
-struct MetaData {
- long meta_msgnum; /* Message number in *local* message base */
- int meta_refcount; /* Number of rooms pointing to this msg */
- char meta_content_type[64]; /* Cached MIME content-type */
- long meta_rfc822_length; /* Cache of RFC822-translated msg length */
-};
-
-
-/* Calls to AdjRefCount() are queued and deferred, so the user doesn't
- * have to wait for various disk-intensive operations to complete synchronously.
- * This is the record format.
- */
-struct arcq {
- long arcq_msgnum; /* Message number being adjusted */
- int arcq_delta; /* Adjustment ( usually 1 or -1 ) */
-};
-
-
-/*
- * Serialization routines use this struct to return a pointer and a length
- */
-struct ser_ret {
- size_t len;
- unsigned char *ser;
-};
-
-
-/*
- * The S_USETABLE database is used in several modules now, so we define its format here.
- */
-struct UseTable {
- char ut_msgid[SIZ];
- time_t ut_timestamp;
-};
-
-
-/*
- * These one-byte field headers are found in the Citadel message store.
- */
-typedef enum _MsgField {
- eAuthor = 'A',
- eBig_message = 'B',
- eExclusiveID = 'E',
- erFc822Addr = 'F',
- emessageId = 'I',
- eJournal = 'J',
- eReplyTo = 'K',
- eListID = 'L',
- eMesageText = 'M',
- eOriginalRoom = 'O',
- eMessagePath = 'P',
- eRecipient = 'R',
- eTimestamp = 'T',
- eMsgSubject = 'U',
- eenVelopeTo = 'V',
- eWeferences = 'W',
- eCarbonCopY = 'Y',
- eErrorMsg = '0',
- eSuppressIdx = '1',
- eExtnotify = '2',
- eVltMsgNum = '3'
-} eMsgField;
-
-#endif /* SERVER_H */
--- /dev/null
+// Main Citadel header file
+//
+// Copyright (c) 1987-2021 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.
+
+/* system customizations are in sysconfig.h */
+
+#ifndef CITADEL_H
+#define CITADEL_H
+/* #include <dmalloc.h> uncomment if using dmalloc */
+
+#include "sysdep.h"
+#include <limits.h>
+#include "sysconfig.h"
+#include "typesize.h"
+#include "ipcdef.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Text description of this software
+ * (We used to define this ourselves, but why bother when the build tools do it for us?)
+ */
+#define CITADEL PACKAGE_STRING
+
+#define REV_LEVEL 951 // This version
+#define REV_MIN 591 // Oldest compatible database
+#define EXPORT_REV_MIN 931 // Oldest compatible export files
+#define LIBCITADEL_MIN 931 // Minimum required version of libcitadel
+#define SERVER_TYPE 0 // zero for stock Citadel; other developers please obtain SERVER_TYPE codes for your implementations
+
+#ifdef LIBCITADEL_VERSION_NUMBER
+#if LIBCITADEL_VERSION_NUMBER < LIBCITADEL_MIN
+#error libcitadel is too old. Please upgrade it before continuing.
+#endif
+#endif
+
+/*
+ * This is the user name and password for the default administrator account
+ * that is created when Citadel Server is started with an empty database.
+ */
+#define DEFAULT_ADMIN_USERNAME "admin"
+#define DEFAULT_ADMIN_PASSWORD "citadel"
+
+/* Various length constants */
+
+#define ROOMNAMELEN 128 /* The size of a roomname string */
+#define USERNAME_SIZE 64 /* The size of a username string */
+#define MAX_EDITORS 5 /* number of external editors supported ; must be at least 1 */
+
+/*
+ * Message expiration policy stuff
+ */
+typedef struct ExpirePolicy ExpirePolicy;
+struct ExpirePolicy {
+ int expire_mode;
+ int expire_value;
+};
+
+#define EXPIRE_NEXTLEVEL 0 // Inherit expiration policy
+#define EXPIRE_MANUAL 1 // Don't expire messages at all
+#define EXPIRE_NUMMSGS 2 // Keep only latest n messages
+#define EXPIRE_AGE 3 // Expire messages after n days
+
+
+/*
+ * User records.
+ */
+typedef struct ctdluser ctdluser;
+struct ctdluser { // User record
+ int version; // Citadel version which created this record
+ uid_t uid; // Associate with a unix account?
+ char password[32]; // password
+ unsigned flags; // See US_ flags below
+ long timescalled; // Total number of logins
+ long posted; // Number of messages ever submitted
+ cit_uint8_t axlevel; // Access level
+ long usernum; // User number (never recycled)
+ time_t lastcall; // Date/time of most recent login
+ int USuserpurge; // Purge time (in days) for user
+ char fullname[64]; // Display name (primary identifier)
+ long msgnum_bio; // msgnum of user's profile (bio)
+ long msgnum_pic; // msgnum of user's avatar (photo)
+ char emailaddrs[512]; // Internet email addresses
+ long msgnum_inboxrules; // msgnum of user's inbox filtering rules
+ long lastproc_inboxrules; // msgnum of last message filtered
+};
+
+
+/* Bits which may appear in MMflags.
+ */
+#define MM_VALID 4 // New users need validating
+
+/*
+ * Room records.
+ */
+typedef struct ctdlroom ctdlroom;
+struct ctdlroom {
+ char QRname[ROOMNAMELEN]; // Name of room
+ char QRpasswd[10]; // Only valid if it's a private rm
+ long QRroomaide; // User number of room aide
+ long QRhighest; // Highest message NUMBER in room
+ time_t QRgen; // Generation number of room
+ unsigned QRflags; // See flag values below
+ char QRdirname[15]; // Directory name, if applicable
+ long msgnum_info; // msgnum of room banner (info file)
+ char QRfloor; // Which floor this room is on
+ time_t QRmtime; // Date/time of last post
+ struct ExpirePolicy QRep; // Message expiration policy
+ long QRnumber; // Globally unique room number
+ char QRorder; // Sort key for room listing order
+ unsigned QRflags2; // Additional flags
+ int QRdefaultview; // How to display the contents
+ long msgnum_pic; // msgnum of room picture or icon
+};
+
+/* Private rooms are always flagged with QR_PRIVATE. If neither QR_PASSWORDED
+ * or QR_GUESSNAME is set, then it is invitation-only. Passworded rooms are
+ * flagged with both QR_PRIVATE and QR_PASSWORDED while guess-name rooms are
+ * flagged with both QR_PRIVATE and QR_GUESSNAME. NEVER set all three flags.
+ */
+
+/*
+ * Miscellaneous
+ */
+#define MES_NORMAL 65 // Normal message
+#define MES_ANONONLY 66 // "****" header
+#define MES_ANONOPT 67 // "Anonymous" header
+
+/****************************************************************************/
+
+/*
+ * Floor record. The floor number is implicit in its location in the file.
+ */
+typedef struct floor floor;
+struct floor {
+ unsigned short f_flags; // flags
+ char f_name[256]; // name of floor
+ int f_ref_count; // reference count
+ struct ExpirePolicy f_ep; // default expiration policy
+};
+
+#define F_INUSE 1 // floor is in use
+
+
+/*
+ * Values used internally for function call returns, etc.
+ */
+#define NEWREGISTER 0 // new user to register
+#define REREGISTER 1 // existing user reregistering
+
+/* number of items which may be handled by the CONF command */
+#define NUM_CONFIGS 71
+
+#define TRACE syslog(LOG_DEBUG, "\033[7m Checkpoint: %s : %d \033[0m", __FILE__, __LINE__)
+
+#ifndef LONG_MAX
+#define LONG_MAX 2147483647L
+#endif
+
+/*
+ * Authentication modes
+ */
+#define AUTHMODE_NATIVE 0 // Native (self-contained or "black box")
+#define AUTHMODE_HOST 1 // Authenticate against the host OS user database
+#define AUTHMODE_LDAP 2 // Authenticate using LDAP server with RFC 2307 schema
+#define AUTHMODE_LDAP_AD 3 // Authenticate using LDAP server with Active Directory schema
+
+#ifdef __cplusplus
+}
+#endif
+
+#if __GNUC__ >= 8
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-truncation"
+#endif
+
+#endif /* CITADEL_H */
--- /dev/null
+// citadel_dirs.c : calculate pathnames for various files used in the Citadel system
+//
+// Copyright (c) 1987-2021 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.
+
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <time.h>
+#include <errno.h>
+#include <syslog.h>
+#include <libcitadel.h>
+#include "citadel.h"
+#include "citadel_dirs.h"
+
+// Generate an associated file name for a room
+size_t assoc_file_name(char *buf, size_t n, struct ctdlroom *qrbuf, const char *prefix) {
+ return snprintf(buf, n, "%s%ld", prefix, qrbuf->QRnumber);
+}
+
+
+int create_dir(char *which, long ACCESS, long UID, long GID) {
+ int rv;
+ rv = mkdir(which, ACCESS);
+ if ((rv == -1) && (errno != EEXIST)) {
+ syslog(LOG_ERR,
+ "failed to create directory %s: %s",
+ which,
+ strerror(errno));
+ return rv;
+ }
+ rv = chmod(which, ACCESS);
+ if (rv == -1) {
+ syslog(LOG_ERR, "failed to set permissions for directory %s: %s", which, strerror(errno));
+ return rv;
+ }
+ rv = chown(which, UID, GID);
+ if (rv == -1) {
+ syslog(LOG_ERR, "failed to set owner for directory %s: %s", which, strerror(errno));
+ return rv;
+ }
+ return rv;
+}
+
+
+int create_run_directories(long UID, long GID) {
+ int rv = 0;
+ rv += create_dir(ctdl_message_dir , S_IRUSR|S_IWUSR|S_IXUSR, UID, -1);
+ rv += create_dir(ctdl_file_dir , S_IRUSR|S_IWUSR|S_IXUSR, UID, -1);
+ rv += create_dir(ctdl_key_dir , S_IRUSR|S_IWUSR|S_IXUSR, UID, -1);
+ rv += create_dir(ctdl_run_dir , S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH, UID, GID);
+ return rv;
+}
--- /dev/null
+#ifndef __CITADEL_DIRS_H
+#define __CITADEL_DIRS_H
+
+#include <limits.h>
+
+/* Fixed directory names (some of these are obsolete and used only for migration) */
+#define ctdl_home_directory "."
+#define ctdl_db_dir "data"
+#define ctdl_file_dir "files"
+#define ctdl_shared_dir "."
+#define ctdl_image_dir "images"
+#define ctdl_info_dir "info"
+#define ctdl_key_dir "keys"
+#define ctdl_message_dir "messages"
+#define ctdl_usrpic_dir "userpics"
+#define ctdl_autoetc_dir "."
+#define ctdl_run_dir "."
+#define ctdl_netcfg_dir "netconfigs"
+#define ctdl_bbsbase_dir "."
+#define ctdl_sbin_dir "."
+#define ctdl_bin_dir "."
+#define ctdl_utilbin_dir "."
+
+/* Fixed file names (some of these are obsolete and used only for migration) */
+#define file_citadel_config "citadel.config"
+#define file_lmtp_socket "lmtp.socket"
+#define file_lmtp_unfiltered_socket "lmtp-unfiltered.socket"
+#define file_arcq "refcount_adjustments.dat"
+#define file_citadel_socket "citadel.socket"
+#define file_citadel_admin_socket "citadel-admin.socket"
+#define file_pid_file "/var/run/citserver.pid"
+#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 "chkpwd"
+#define file_guesstimezone "guesstimezone.sh"
+
+
+/* externs */
+extern int create_run_directories(long UID, long GUID);
+extern size_t assoc_file_name(char *buf, size_t n, struct ctdlroom *qrbuf, const char *prefix);
+extern FILE *create_digest_file(struct ctdlroom *room, int forceCreate);
+extern void remove_digest_file(struct ctdlroom *room);
+
+#endif /* __CITADEL_DIRS_H */
--- /dev/null
+// Configuration for LDAP authentication. Most of this stuff gets pulled out of our site config file.
+//
+// Copyright (c) 1987-2017 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+int CtdlTryUserLDAP(char *username, char *found_dn, int found_dn_size, char *fullname, int fullname_size, uid_t *found_uid);
+int CtdlTryPasswordLDAP(char *user_dn, const char *password);
+int Ctdl_LDAP_to_vCard(char *ldap_dn, struct vCard *v);
+int extract_email_addresses_from_ldap(char *ldap_dn, char *emailaddrs);
+void CtdlSynchronizeUsersFromLDAP(void);
--- /dev/null
+// Main source module for the Citadel server
+//
+// 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.
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include "sysdep.h"
+#include <time.h>
+#include <libcitadel.h>
+
+#include "ctdl_module.h"
+#include "housekeeping.h"
+#include "locate_host.h"
+#include "citserver.h"
+#include "user_ops.h"
+#include "control.h"
+#include "config.h"
+
+char *unique_session_numbers;
+int ScheduledShutdown = 0;
+time_t server_startup_time;
+int panic_fd;
+int openid_level_supported = 0;
+
+
+// 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);
+}
+
+
+// Various things that need to be initialized at startup
+void master_startup(void) {
+ struct ctdlroom qrbuf;
+ struct passwd *pw;
+ gid_t gid;
+
+ syslog(LOG_DEBUG, "master_startup() started");
+ time(&server_startup_time);
+
+ syslog(LOG_INFO, "Checking directory access");
+ if ((pw = getpwuid(ctdluid)) == NULL) {
+ gid = getgid();
+ }
+ else {
+ gid = pw->pw_gid;
+ }
+
+ if (create_run_directories(CTDLUID, gid) != 0) {
+ syslog(LOG_ERR, "citserver: failed to access and create directories");
+ exit(1);
+ }
+ syslog(LOG_INFO, "Opening databases");
+ open_databases();
+
+ // Load site-specific configuration
+ seed_random_number_generator(); // must be done before config system
+ syslog(LOG_INFO, "Initializing configuration system");
+ initialize_config_system();
+ validate_config();
+ migrate_legacy_control_record();
+
+ // If we have an existing database that is older than version 928, reindex the user records.
+ // Unfortunately we cannot do this in serv_upgrade.c because it needs to happen VERY early during startup.
+ int existing_db = CtdlGetConfigInt("MM_hosted_upgrade_level");
+ if ( (existing_db > 0) && (existing_db < 928) ) {
+ ForEachUser(reindex_user_928, NULL);
+ }
+
+ // Check floor reference counts
+ check_ref_counts();
+
+ syslog(LOG_INFO, "Creating base rooms (if necessary)");
+ CtdlCreateRoom(CtdlGetConfigStr("c_baseroom"), 0, "", 0, 1, 0, VIEW_BBS);
+ CtdlCreateRoom(AIDEROOM, 3, "", 0, 1, 0, VIEW_BBS);
+ CtdlCreateRoom(SYSCONFIGROOM, 3, "", 0, 1, 0, VIEW_BBS);
+ CtdlCreateRoom(CtdlGetConfigStr("c_twitroom"), 0, "", 0, 1, 0, VIEW_BBS);
+
+ // The "Local System Configuration" room doesn't need to be visible
+ if (CtdlGetRoomLock(&qrbuf, SYSCONFIGROOM) == 0) {
+ qrbuf.QRflags2 |= QR2_SYSTEM;
+ CtdlPutRoomLock(&qrbuf);
+ }
+
+ // Aide needs to be public postable, else we're not RFC conformant.
+ if (CtdlGetRoomLock(&qrbuf, AIDEROOM) == 0) {
+ qrbuf.QRflags2 |= QR2_SMTP_PUBLIC;
+ CtdlPutRoomLock(&qrbuf);
+ }
+
+ syslog(LOG_DEBUG, "master_startup() finished");
+}
+
+
+// Cleanup routine to be called when the server is shutting down. Returns the needed exit code.
+int master_cleanup(int exitcode) {
+ static int already_cleaning_up = 0;
+
+ if (already_cleaning_up) {
+ while (1) {
+ usleep(1000000);
+ }
+ }
+ already_cleaning_up = 1;
+
+ // Do system-dependent stuff
+ sysdep_master_cleanup();
+
+ // Close the configuration system
+ shutdown_config_system();
+
+ // Close databases
+ syslog(LOG_INFO, "citserver: closing databases");
+ close_databases();
+
+ // If the operator requested a halt but not an exit, halt here.
+ if (shutdown_and_halt) {
+ syslog(LOG_ERR, "citserver: Halting server without exiting.");
+ fflush(stdout);
+ fflush(stderr);
+ while (1) {
+ sleep(32767);
+ }
+ }
+
+ // Now go away.
+ syslog(LOG_ERR, "citserver: Exiting with status %d", exitcode);
+ fflush(stdout);
+ fflush(stderr);
+
+ if (restart_server != 0) {
+ exitcode = 1;
+ }
+ else if ((running_as_daemon != 0) && ((exitcode == 0))) {
+ exitcode = CTDLEXIT_SHUTDOWN;
+ }
+ return (exitcode);
+}
+
+
+// returns an asterisk if there are any instant messages waiting, space otherwise.
+char CtdlCheckExpress(void) {
+ if (CC->FirstExpressMessage == NULL) {
+ return (' ');
+ }
+ else {
+ return ('*');
+ }
+}
+
+
+void citproto_begin_session() {
+ if (CC->nologin == 1) {
+ cprintf("%d Too many users are already online (maximum is %d)\n",
+ ERROR + MAX_SESSIONS_EXCEEDED, CtdlGetConfigInt("c_maxsessions")
+ );
+ CC->kill_me = KILLME_MAX_SESSIONS_EXCEEDED;
+ }
+ else {
+ cprintf("%d %s Citadel server ready.\n", CIT_OK, CtdlGetConfigStr("c_fqdn"));
+ CC->can_receive_im = 1;
+ }
+}
+
+
+void citproto_begin_admin_session() {
+ CC->internal_pgm = 1;
+ cprintf("%d %s Citadel server ADMIN CONNECTION ready.\n", CIT_OK, CtdlGetConfigStr("c_fqdn"));
+}
+
+
+// This loop performs all asynchronous functions.
+void do_async_loop(void) {
+ PerformSessionHooks(EVT_ASYNC);
+}
--- /dev/null
+// Copyright (c) 1987-2019 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.
+
+/* I fucking hate const and want it to die. */
+#pragma GCC diagnostic ignored "-Wcast-qual"
+
+#include "serv_extensions.h"
+#include "context.h"
+#include "ctdl_module.h"
+
+/* Simple linked list structures ... used in a bunch of different places. */
+typedef struct RoomProcList RoomProcList;
+struct RoomProcList {
+ struct RoomProcList *next;
+ char name[ROOMNAMELEN];
+ char lcname[ROOMNAMELEN];
+ long namelen;
+ long lastsent;
+ long key;
+ long QRNum;
+};
+struct UserProcList {
+ struct UserProcList *next;
+ char user[USERNAME_SIZE];
+};
+
+#define CTDLUSERIP (IsEmptyStr(CC->cs_addr) ? CC->cs_clientinfo: CC->cs_addr)
+
+void master_startup (void);
+int master_cleanup (int exitcode);
+void set_wtmpsupp (char *newtext);
+void set_wtmpsupp_to_current_room(void);
+void do_command_loop(void);
+void do_async_loop(void);
+void begin_session(struct CitContext *con);
+void citproto_begin_session(void);
+void citproto_begin_admin_session(void);
+void help_subst (char *strbuf, char *source, char *dest);
+char CtdlCheckExpress(void);
+
+extern int panic_fd;
+extern time_t server_startup_time;
+extern int openid_level_supported;
--- /dev/null
+// This module handles client-side sockets opened by the Citadel server (for
+// the client side of Internet protocols, etc.) It does _not_ handle client
+// sockets for the Citadel client; for that you must look in ipc_c_tcp.c
+// (which, uncoincidentally, bears a striking similarity to this file).
+//
+// Copyright (c) 1987-2017 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.
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <stdio.h>
+#ifdef __FreeBSD__
+#include <sys/socket.h>
+#endif
+#include <libcitadel.h>
+#include "ctdl_module.h"
+#include "clientsocket.h"
+
+int sock_connect(char *host, char *service) {
+ struct in6_addr serveraddr;
+ struct addrinfo hints;
+ struct addrinfo *res = NULL;
+ struct addrinfo *ai = NULL;
+ int rc = (-1);
+ int sock = (-1);
+
+ if ((host == NULL) || IsEmptyStr(host))
+ return (-1);
+ if ((service == NULL) || IsEmptyStr(service))
+ return (-1);
+
+ memset(&hints, 0x00, sizeof(hints));
+ hints.ai_flags = AI_NUMERICSERV;
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+
+ // Handle numeric IPv4 and IPv6 addresses
+ rc = inet_pton(AF_INET, host, &serveraddr);
+ if (rc == 1) { // dotted quad
+ hints.ai_family = AF_INET;
+ hints.ai_flags |= AI_NUMERICHOST;
+ }
+ else {
+ rc = inet_pton(AF_INET6, host, &serveraddr);
+ if (rc == 1) { // IPv6 address
+ hints.ai_family = AF_INET6;
+ hints.ai_flags |= AI_NUMERICHOST;
+ }
+ }
+
+ // Begin the connection process
+ rc = getaddrinfo(host, service, &hints, &res);
+ if (rc != 0) {
+ syslog(LOG_ERR, "%s: %s", host, gai_strerror(rc));
+ return(-1);
+ }
+
+ // Try all available addresses until we connect to one or until we run out.
+ for (ai = res; ai != NULL; ai = ai->ai_next) {
+ sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ if (sock < 0) {
+ syslog(LOG_ERR, "%s: %m", host);
+ freeaddrinfo(res);
+ return(-1);
+ }
+ rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
+ if (rc >= 0) {
+ freeaddrinfo(res);
+ return(sock);
+ }
+ else {
+ syslog(LOG_ERR, "%s: %m", host);
+ close(sock);
+ }
+ }
+ freeaddrinfo(res);
+ return(-1);
+}
+
+
+// Read data from the client socket.
+//
+// sock socket fd to read from
+// buf buffer to read into
+// bytes number of bytes to read
+// timeout Number of seconds to wait before timing out
+//
+// Possible return values:
+// 1 Requested number of bytes has been read.
+// 0 Request timed out.
+// -1 Connection is broken, or other error.
+int socket_read_blob(int *Socket, StrBuf *Target, int bytes, int timeout) {
+ const char *Error;
+ int retval = 0;
+
+ retval = StrBufReadBLOBBuffered(Target, CC->SBuf.Buf, &CC->SBuf.ReadWritePointer, Socket, 1, bytes, O_TERM, &Error);
+ if (retval < 0) {
+ syslog(LOG_ERR, "clientsocket: socket_read_blob() failed: %s", Error);
+ }
+ return retval;
+}
+
+
+int CtdlSockGetLine(int *sock, StrBuf *Target, int nSec) {
+ const char *Error;
+ int rc;
+
+ FlushStrBuf(Target);
+ rc = StrBufTCP_read_buffered_line_fast(Target,
+ CC->SBuf.Buf,
+ &CC->SBuf.ReadWritePointer,
+ sock, nSec, 1, &Error);
+ if ((rc < 0) && (Error != NULL)) {
+ syslog(LOG_ERR, "clientsocket: CtdlSockGetLine() failed: %s", Error);
+ }
+ return rc;
+}
+
+
+// client_getln() ... Get a LF-terminated line of text from the client.
+int sock_getln(int *sock, char *buf, int bufsize) {
+ int i, retval;
+ const char *pCh;
+
+ FlushStrBuf(CC->sMigrateBuf);
+ retval = CtdlSockGetLine(sock, CC->sMigrateBuf, 5);
+
+ i = StrLength(CC->sMigrateBuf);
+ pCh = ChrPtr(CC->sMigrateBuf);
+
+ memcpy(buf, pCh, i + 1);
+
+ FlushStrBuf(CC->sMigrateBuf);
+ if (retval < 0) {
+ safestrncpy(&buf[i], "000", bufsize - i);
+ i += 3;
+ }
+ return i;
+}
+
+
+// sock_write() - send binary to server.
+// Returns the number of bytes written, or -1 for error.
+int sock_write(int *sock, const char *buf, int nbytes) {
+ return sock_write_timeout(sock, buf, nbytes, 50);
+}
+
+
+int sock_write_timeout(int *sock, const char *buf, int nbytes, int timeout) {
+ int nSuccessLess = 0;
+ int bytes_written = 0;
+ int retval;
+ fd_set rfds;
+ int fdflags;
+ int IsNonBlock;
+ struct timeval tv;
+ int selectresolution = 100;
+
+ fdflags = fcntl(*sock, F_GETFL);
+ IsNonBlock = (fdflags & O_NONBLOCK) == O_NONBLOCK;
+
+ while ((nSuccessLess < timeout) && (*sock != -1) && (bytes_written < nbytes)) {
+ if (IsNonBlock) {
+ tv.tv_sec = selectresolution;
+ tv.tv_usec = 0;
+
+ FD_ZERO(&rfds);
+ FD_SET(*sock, &rfds);
+ if (select(*sock + 1, NULL, &rfds, NULL, &tv) == -1) {
+ close (*sock);
+ *sock = -1;
+ return -1;
+ }
+ }
+ if (IsNonBlock && ! FD_ISSET(*sock, &rfds)) {
+ nSuccessLess ++;
+ continue;
+ }
+ retval = write(*sock, &buf[bytes_written],
+ nbytes - bytes_written);
+ if (retval < 1) {
+ sock_close(*sock);
+ *sock = -1;
+ return (-1);
+ }
+ bytes_written = bytes_written + retval;
+ if (IsNonBlock && (bytes_written == nbytes)){
+ tv.tv_sec = selectresolution;
+ tv.tv_usec = 0;
+
+ FD_ZERO(&rfds);
+ FD_SET(*sock, &rfds);
+ if (select(*sock + 1, NULL, &rfds, NULL, &tv) == -1) {
+ close (*sock);
+ *sock = -1;
+ return -1;
+ }
+ }
+ }
+ return (bytes_written);
+}
+
+
+// client_getln() ... Get a LF-terminated line of text from the client.
+int sock_getln_err(int *sock, char *buf, int bufsize, int *rc, int nSec) {
+ int i, retval;
+ const char *pCh;
+
+ FlushStrBuf(CC->sMigrateBuf);
+ *rc = retval = CtdlSockGetLine(sock, CC->sMigrateBuf, nSec);
+
+ i = StrLength(CC->sMigrateBuf);
+ pCh = ChrPtr(CC->sMigrateBuf);
+
+ memcpy(buf, pCh, i + 1);
+
+ FlushStrBuf(CC->sMigrateBuf);
+ if (retval < 0) {
+ safestrncpy(&buf[i], "000", bufsize - i);
+ i += 3;
+ }
+ return i;
+}
+
+
+// Multiline version of sock_gets() ... this is a convenience function for
+// client side protocol implementations. It only returns the first line of
+// a multiline response, discarding the rest.
+int ml_sock_gets(int *sock, char *buf, int nSec) {
+ int rc = 0;
+ char bigbuf[1024];
+ int g;
+
+ g = sock_getln_err(sock, buf, SIZ, &rc, nSec);
+ if (rc < 0)
+ return rc;
+ if (g < 4)
+ return (g);
+ if (buf[3] != '-')
+ return (g);
+
+ do {
+ g = sock_getln_err(sock, bigbuf, SIZ, &rc, nSec);
+ if (rc < 0)
+ return rc;
+ if (g < 0)
+ return (g);
+ } while ((g >= 4) && (bigbuf[3] == '-'));
+
+ return (strlen(buf));
+}
+
+
+// sock_puts() - send line to server - implemented in terms of serv_write()
+// Returns the number of bytes written, or -1 for error.
+int sock_puts(int *sock, char *buf) {
+ int i, j;
+
+ i = sock_write(sock, buf, strlen(buf));
+ if (i < 0)
+ return (i);
+ j = sock_write(sock, "\n", 1);
+ if (j < 0)
+ return (j);
+ return (i + j);
+}
--- /dev/null
+// Header file for TCP client socket library
+//
+// Copyright (c) 1987-2012 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.
+
+int sock_connect(char *host, char *service);
+int sock_write(int *sock, const char *buf, int nbytes);
+int sock_write_timeout(int *sock, const char *buf, int nbytes, int timeout);
+int ml_sock_gets(int *sock, char *buf, int nSec);
+int sock_getln(int *sock, char *buf, int bufsize);
+int CtdlSockGetLine(int *sock, StrBuf *Target, int nSec);
+int sock_puts(int *sock, char *buf);
+int socket_read_blob(int *Socket, StrBuf * Target, int bytes, int timeout);
+
+
+/*
+ * This looks dumb, but it's being done for future portability
+ */
+#define sock_close(sock) close(sock)
+#define sock_shutdown(sock, how) shutdown(sock, how)
+
+/*
+ * Default timeout for client sessions
+ */
+#define CLIENT_TIMEOUT 600
--- /dev/null
+// Read and write the system configuration database
+//
+// 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.
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <crypt.h>
+#include <sys/utsname.h>
+#include <libcitadel.h>
+#include <assert.h>
+#include "config.h"
+#include "ctdl_module.h"
+
+long config_msgnum = 0;
+HashList *ctdlconfig = NULL; // new configuration
+
+
+void config_warn_if_port_unset(char *key, int default_port) {
+ int p = CtdlGetConfigInt(key);
+ if ((p < -1) || (p == 0) || (p > UINT16_MAX)) {
+ syslog(LOG_ERR, "config: setting %s is not -1 (disabled) or a valid TCP port - setting to default %d", key, default_port);
+ CtdlSetConfigInt(key, default_port);
+ }
+}
+
+
+void config_warn_if_empty(char *key) {
+ if (IsEmptyStr(CtdlGetConfigStr(key))) {
+ syslog(LOG_ERR, "config: setting %s is empty, but must not - check your config!", key);
+ }
+}
+
+
+void validate_config(void) {
+
+ // these shouldn't be empty
+ config_warn_if_empty("c_fqdn");
+ config_warn_if_empty("c_baseroom");
+ config_warn_if_empty("c_aideroom");
+ config_warn_if_empty("c_twitroom");
+ config_warn_if_empty("c_nodename");
+
+ // Sanity check for port bindings
+ config_warn_if_port_unset("c_smtp_port", 25);
+ config_warn_if_port_unset("c_pop3_port", 110);
+ config_warn_if_port_unset("c_imap_port", 143);
+ config_warn_if_port_unset("c_msa_port", 587);
+ config_warn_if_port_unset("c_port_number", 504);
+ config_warn_if_port_unset("c_smtps_port", 465);
+ config_warn_if_port_unset("c_pop3s_port", 995);
+ config_warn_if_port_unset("c_imaps_port", 993);
+ config_warn_if_port_unset("c_pftcpdict_port", -1);
+ config_warn_if_port_unset("c_xmpp_c2s_port", 5222);
+ config_warn_if_port_unset("c_xmpp_s2s_port", 5269);
+ config_warn_if_port_unset("c_nntp_port", 119);
+ config_warn_if_port_unset("c_nntps_port", 563);
+
+ if (getpwuid(ctdluid) == NULL) {
+ syslog(LOG_ERR, "config: uid (%d) does not exist ... citserver will run as root", ctdluid);
+ }
+}
+
+
+// The "host key" is a 100-byte random string that is used to do some persistent hashing.
+// It must remain consistent throughout the lifetime of this Citadel installation
+void generate_host_key(void) {
+ syslog(LOG_INFO, "config: generating a host key");
+ int len = 0;
+ char host_key[128];
+
+ while (len < 100) {
+ do {
+ host_key[len] = random() % 0x7F;
+ } while (!isalnum(host_key[len]));
+ host_key[++len] = 0;
+ }
+ CtdlSetConfigStr("host_key", host_key);
+}
+
+
+// Put some sane default values into our configuration. Some will be overridden when we run setup.
+void brand_new_installation_set_defaults(void) {
+
+ struct utsname my_utsname;
+ struct hostent *he;
+ char detected_hostname[256];
+
+ // Determine our host name, in case we need to use it as a default
+ uname(&my_utsname);
+
+ // set some sample/default values in place of blanks...
+ extract_token(detected_hostname, my_utsname.nodename, 0, '.', sizeof detected_hostname);
+ CtdlSetConfigStr("c_nodename", detected_hostname);
+
+ if ((he = gethostbyname(my_utsname.nodename)) != NULL) {
+ CtdlSetConfigStr("c_fqdn", he->h_name);
+ }
+ else {
+ CtdlSetConfigStr("c_fqdn", my_utsname.nodename);
+ }
+
+ CtdlSetConfigStr("c_humannode", "Citadel Server");
+ CtdlSetConfigInt("c_initax", 4);
+ CtdlSetConfigStr("c_moreprompt", "<more>");
+ CtdlSetConfigStr("c_twitroom", "Trashcan");
+ CtdlSetConfigStr("c_baseroom", BASEROOM);
+ CtdlSetConfigStr("c_aideroom", "Aide");
+ CtdlSetConfigInt("c_sleeping", 900);
+
+ if (CtdlGetConfigInt("c_createax") == 0) {
+ CtdlSetConfigInt("c_createax", 3);
+ }
+
+ // Default port numbers for various services
+ CtdlSetConfigInt("c_port_number", 504);
+ CtdlSetConfigInt("c_smtp_port", 25);
+ CtdlSetConfigInt("c_pop3_port", 110);
+ CtdlSetConfigInt("c_imap_port", 143);
+ CtdlSetConfigInt("c_msa_port", 587);
+ CtdlSetConfigInt("c_smtps_port", 465);
+ CtdlSetConfigInt("c_pop3s_port", 995);
+ CtdlSetConfigInt("c_imaps_port", 993);
+ CtdlSetConfigInt("c_pftcpdict_port", -1);
+ CtdlSetConfigInt("c_xmpp_c2s_port", 5222);
+ CtdlSetConfigInt("c_xmpp_s2s_port", 5269);
+ CtdlSetConfigInt("c_nntp_port", 119);
+ CtdlSetConfigInt("c_nntps_port", 563);
+
+ // Prevent the "new installation, set defaults" behavior from occurring again
+ CtdlSetConfigLong("c_config_created_or_migrated", (long)time(NULL));
+}
+
+
+// Migrate a supplied legacy configuration to the new in-db format.
+// No individual site should ever have to do this more than once.
+void migrate_legacy_config(struct legacy_config *lconfig) {
+ CtdlSetConfigStr( "c_nodename" , lconfig->c_nodename );
+ CtdlSetConfigStr( "c_fqdn" , lconfig->c_fqdn );
+ CtdlSetConfigStr( "c_humannode" , lconfig->c_humannode );
+ CtdlSetConfigInt( "c_creataide" , lconfig->c_creataide );
+ CtdlSetConfigInt( "c_sleeping" , lconfig->c_sleeping );
+ CtdlSetConfigInt( "c_initax" , lconfig->c_initax );
+ CtdlSetConfigInt( "c_regiscall" , lconfig->c_regiscall );
+ CtdlSetConfigInt( "c_twitdetect" , lconfig->c_twitdetect );
+ CtdlSetConfigStr( "c_twitroom" , lconfig->c_twitroom );
+ CtdlSetConfigStr( "c_moreprompt" , lconfig->c_moreprompt );
+ CtdlSetConfigInt( "c_restrict" , lconfig->c_restrict );
+ CtdlSetConfigStr( "c_site_location" , lconfig->c_site_location );
+ CtdlSetConfigStr( "c_sysadm" , lconfig->c_sysadm );
+ CtdlSetConfigInt( "c_maxsessions" , lconfig->c_maxsessions );
+ CtdlSetConfigStr( "c_ip_addr" , lconfig->c_ip_addr );
+ CtdlSetConfigInt( "c_port_number" , lconfig->c_port_number );
+ CtdlSetConfigInt( "c_ep_mode" , lconfig->c_ep.expire_mode );
+ CtdlSetConfigInt( "c_ep_value" , lconfig->c_ep.expire_value );
+ CtdlSetConfigInt( "c_userpurge" , lconfig->c_userpurge );
+ CtdlSetConfigInt( "c_roompurge" , lconfig->c_roompurge );
+ CtdlSetConfigStr( "c_logpages" , lconfig->c_logpages );
+ CtdlSetConfigInt( "c_createax" , lconfig->c_createax );
+ CtdlSetConfigLong( "c_maxmsglen" , lconfig->c_maxmsglen );
+ CtdlSetConfigInt( "c_min_workers" , lconfig->c_min_workers );
+ CtdlSetConfigInt( "c_max_workers" , lconfig->c_max_workers );
+ CtdlSetConfigInt( "c_pop3_port" , lconfig->c_pop3_port );
+ CtdlSetConfigInt( "c_smtp_port" , lconfig->c_smtp_port );
+ CtdlSetConfigInt( "c_rfc822_strict_from" , lconfig->c_rfc822_strict_from );
+ CtdlSetConfigInt( "c_aide_zap" , lconfig->c_aide_zap );
+ CtdlSetConfigInt( "c_imap_port" , lconfig->c_imap_port );
+ CtdlSetConfigLong( "c_net_freq" , lconfig->c_net_freq );
+ CtdlSetConfigInt( "c_disable_newu" , lconfig->c_disable_newu );
+ CtdlSetConfigInt( "c_enable_fulltext" , lconfig->c_enable_fulltext );
+ CtdlSetConfigStr( "c_baseroom" , lconfig->c_baseroom );
+ CtdlSetConfigStr( "c_aideroom" , lconfig->c_aideroom );
+ CtdlSetConfigInt( "c_purge_hour" , lconfig->c_purge_hour );
+ CtdlSetConfigInt( "c_mbxep_mode" , lconfig->c_mbxep.expire_mode );
+ CtdlSetConfigInt( "c_mbxep_value" , lconfig->c_mbxep.expire_value );
+ CtdlSetConfigStr( "c_ldap_host" , lconfig->c_ldap_host );
+ CtdlSetConfigInt( "c_ldap_port" , lconfig->c_ldap_port );
+ CtdlSetConfigStr( "c_ldap_base_dn" , lconfig->c_ldap_base_dn );
+ CtdlSetConfigStr( "c_ldap_bind_dn" , lconfig->c_ldap_bind_dn );
+ CtdlSetConfigStr( "c_ldap_bind_pw" , lconfig->c_ldap_bind_pw );
+ CtdlSetConfigInt( "c_msa_port" , lconfig->c_msa_port );
+ CtdlSetConfigInt( "c_imaps_port" , lconfig->c_imaps_port );
+ CtdlSetConfigInt( "c_pop3s_port" , lconfig->c_pop3s_port );
+ CtdlSetConfigInt( "c_smtps_port" , lconfig->c_smtps_port );
+ CtdlSetConfigInt( "c_auto_cull" , lconfig->c_auto_cull );
+ CtdlSetConfigInt( "c_allow_spoofing" , lconfig->c_allow_spoofing );
+ CtdlSetConfigInt( "c_journal_email" , lconfig->c_journal_email );
+ CtdlSetConfigInt( "c_journal_pubmsgs" , lconfig->c_journal_pubmsgs );
+ CtdlSetConfigStr( "c_journal_dest" , lconfig->c_journal_dest );
+ CtdlSetConfigStr( "c_default_cal_zone" , lconfig->c_default_cal_zone );
+ CtdlSetConfigInt( "c_pftcpdict_port" , lconfig->c_pftcpdict_port );
+ CtdlSetConfigInt( "c_auth_mode" , lconfig->c_auth_mode );
+ CtdlSetConfigInt( "c_rbl_at_greeting" , lconfig->c_rbl_at_greeting );
+ CtdlSetConfigStr( "c_pager_program" , lconfig->c_pager_program );
+ CtdlSetConfigInt( "c_imap_keep_from" , lconfig->c_imap_keep_from );
+ CtdlSetConfigInt( "c_xmpp_c2s_port" , lconfig->c_xmpp_c2s_port );
+ CtdlSetConfigInt( "c_xmpp_s2s_port" , lconfig->c_xmpp_s2s_port );
+ CtdlSetConfigLong( "c_pop3_fetch" , lconfig->c_pop3_fetch );
+ CtdlSetConfigLong( "c_pop3_fastest" , lconfig->c_pop3_fastest );
+ CtdlSetConfigInt( "c_spam_flag_only" , lconfig->c_spam_flag_only );
+ CtdlSetConfigInt( "c_guest_logins" , lconfig->c_guest_logins );
+ CtdlSetConfigInt( "c_nntp_port" , lconfig->c_nntp_port );
+ CtdlSetConfigInt( "c_nntps_port" , lconfig->c_nntps_port );
+}
+
+
+// Called during the initialization of Citadel server.
+// It verifies the system's integrity and reads citadel.config into memory.
+void initialize_config_system(void) {
+ FILE *cfp;
+ int rv;
+ struct legacy_config lconfig; // legacy configuration
+ ctdlconfig = NewHash(1, NULL); // set up the real config system
+
+ // Ensure that we are linked to the correct version of libcitadel
+ if (libcitadel_version_number() < LIBCITADEL_VERSION_NUMBER) {
+ fprintf(stderr, "You are running libcitadel version %d\n", libcitadel_version_number());
+ fprintf(stderr, "citserver was compiled against version %d\n", LIBCITADEL_VERSION_NUMBER);
+ exit(CTDLEXIT_LIBCITADEL);
+ }
+
+ memset(&lconfig, 0, sizeof(struct legacy_config));
+ cfp = fopen(file_citadel_config, "rb");
+ if (cfp != NULL) {
+ if (CtdlGetConfigLong("c_config_created_or_migrated") > 0) {
+ fprintf(stderr, "Citadel Server found BOTH legacy and new configurations present.\n");
+ fprintf(stderr, "Exiting to prevent data corruption.\n");
+ exit(CTDLEXIT_CONFIG);
+ }
+ rv = fread((char *) &lconfig, sizeof(struct legacy_config), 1, cfp);
+ if (rv != 1) {
+ fprintf(stderr,
+ "Warning: Found a legacy config file %s has unexpected size. \n",
+ file_citadel_config
+ );
+ }
+
+ migrate_legacy_config(&lconfig);
+
+ fclose(cfp);
+ if (unlink(file_citadel_config) != 0) {
+ fprintf(stderr, "Unable to remove legacy config file %s after migrating it.\n", file_citadel_config);
+ fprintf(stderr, "Exiting to prevent data corruption.\n");
+ exit(CTDLEXIT_CONFIG);
+ }
+
+ // Prevent migration/initialization from happening again.
+ CtdlSetConfigLong("c_config_created_or_migrated", (long)time(NULL));
+
+ }
+
+ // New installation? Set up configuration
+ if (CtdlGetConfigLong("c_config_created_or_migrated") <= 0) {
+ brand_new_installation_set_defaults();
+ }
+
+ // Default maximum message length is 10 megabytes. This is site
+ // configurable. Also check to make sure the limit has not been
+ // set below 8192 bytes.
+ if (CtdlGetConfigLong("c_maxmsglen") <= 0) CtdlSetConfigLong("c_maxmsglen", 10485760);
+ if (CtdlGetConfigLong("c_maxmsglen") < 8192) CtdlSetConfigLong("c_maxmsglen", 8192);
+
+ // Default lower and upper limits on number of worker threads
+ if (CtdlGetConfigInt("c_min_workers") < 5) CtdlSetConfigInt("c_min_workers", 5); // min
+ if (CtdlGetConfigInt("c_max_workers") == 0) CtdlSetConfigInt("c_max_workers", 256); // default max
+ if (CtdlGetConfigInt("c_max_workers") < CtdlGetConfigInt("c_min_workers")) {
+ CtdlSetConfigInt("c_max_workers", CtdlGetConfigInt("c_min_workers")); // max >= min
+ }
+
+ // Networking more than once every five minutes just isn't sane
+ if (CtdlGetConfigLong("c_net_freq") == 0) CtdlSetConfigLong("c_net_freq", 3600); // once per hour default
+ if (CtdlGetConfigLong("c_net_freq") < 300) CtdlSetConfigLong("c_net_freq", 300); // minimum 5 minutes
+
+ // Same goes for POP3
+ if (CtdlGetConfigLong("c_pop3_fetch") == 0) CtdlSetConfigLong("c_pop3_fetch", 3600); // once per hour default
+ if (CtdlGetConfigLong("c_pop3_fetch") < 300) CtdlSetConfigLong("c_pop3_fetch", 300); // 5 minutes min
+ if (CtdlGetConfigLong("c_pop3_fastest") == 0) CtdlSetConfigLong("c_pop3_fastest", 3600); // once per hour default
+ if (CtdlGetConfigLong("c_pop3_fastest") < 300) CtdlSetConfigLong("c_pop3_fastest", 300); // 5 minutes min
+
+ // LDAP sync frequency
+ if (CtdlGetConfigLong("c_ldap_sync_freq") == 0) CtdlSetConfigLong("c_ldap_sync_freq", 300); // every 5 minutes default
+
+ // "create new user" only works with native authentication mode
+ if (CtdlGetConfigInt("c_auth_mode") != AUTHMODE_NATIVE) {
+ CtdlSetConfigInt("c_disable_newu", 1);
+ }
+
+ // If we do not have a host key, generate one now, and save it.
+ if (IsEmptyStr(CtdlGetConfigStr("host_key"))) {
+ generate_host_key();
+ }
+}
+
+
+// Called when Citadel server is shutting down.
+// Clears out the config hash table.
+void shutdown_config_system(void) {
+ DeleteHash(&ctdlconfig);
+}
+
+
+// Set a system config value. Simple key/value here.
+void CtdlSetConfigStr(char *key, char *value) {
+ int key_len = strlen(key);
+ int value_len = strlen(value);
+
+ // Save it in memory
+ Put(ctdlconfig, key, key_len, strdup(value), NULL);
+
+ // Also write it to the config database
+
+ int dbv_size = key_len + value_len + 2;
+ char *dbv = malloc(dbv_size);
+ strcpy(dbv, key);
+ strcpy(&dbv[key_len + 1], value);
+ cdb_store(CDB_CONFIG, key, key_len, dbv, dbv_size);
+ free(dbv);
+}
+
+
+// Set a numeric system config value (long integer)
+void CtdlSetConfigLong(char *key, long value) {
+ char longstr[256];
+ sprintf(longstr, "%ld", value);
+ CtdlSetConfigStr(key, longstr);
+}
+
+
+// Set a numeric system config value (integer)
+void CtdlSetConfigInt(char *key, int value) {
+ char intstr[256];
+ sprintf(intstr, "%d", value);
+ CtdlSetConfigStr(key, intstr);
+}
+
+
+// Delete a system config value.
+void CtdlDelConfig(char *key) {
+ int key_len = strlen(key);
+
+ if (IsEmptyStr(key)) return;
+
+ // Delete from the database.
+ cdb_delete(CDB_CONFIG, key, key_len);
+
+ // Delete from the in-memory cache
+ HashPos *Pos = GetNewHashPos(ctdlconfig, 1);
+ if (GetHashPosFromKey(ctdlconfig, key, key_len, Pos)) {
+ DeleteEntryFromHash(ctdlconfig, Pos);
+ }
+ DeleteHashPos(&Pos);
+
+ assert(Pos == NULL); // no memory leaks allowed
+}
+
+
+// Fetch a system config value. Caller does *not* own the returned value and may not alter it.
+char *CtdlGetConfigStr(char *key) {
+ char *value = NULL;
+ struct cdbdata *cdb;
+ int key_len = strlen(key);
+
+ if (IsEmptyStr(key)) return(NULL);
+
+ // First look in memory
+ if (GetHash(ctdlconfig, key, key_len, (void *)&value)) {
+ return value;
+ }
+
+ // Then look in the database.
+ cdb = cdb_fetch(CDB_CONFIG, key, key_len);
+ if (cdb == NULL) { // nope, not there either.
+ return(NULL);
+ }
+
+ // Got it. Save it in memory for the next fetch.
+ value = strdup(cdb->ptr + key_len + 1); // The key was stored there too; skip past it
+ cdb_free(cdb);
+ Put(ctdlconfig, key, key_len, value, NULL);
+ return value;
+}
+
+
+// Fetch a system config value - integer
+int CtdlGetConfigInt(char *key) {
+ char *s = CtdlGetConfigStr(key);
+ if (s) return atoi(s);
+ return 0;
+}
+
+
+// Fetch a system config value - long integer
+long CtdlGetConfigLong(char *key) {
+ char *s = CtdlGetConfigStr(key);
+ if (s) return atol(s);
+ return 0;
+}
+
+
+void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
+ config_msgnum = msgnum;
+}
+
+
+// This is for fetching longer configuration sets which are stored in the message base.
+char *CtdlGetSysConfig(char *sysconfname) {
+ char hold_rm[ROOMNAMELEN];
+ long msgnum = -1;
+ char *conf;
+ struct CtdlMessage *msg;
+ char buf[SIZ];
+
+ strcpy(hold_rm, CC->room.QRname);
+ if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
+ CtdlGetRoom(&CC->room, hold_rm);
+ return NULL;
+ }
+
+ // The new way: hunt for the message number in the config database
+ msgnum = CtdlGetConfigLong(sysconfname);
+
+ // Legacy format: hunt through the local system configuration room for a message with a matching MIME type
+ if (msgnum <= 0) {
+ begin_critical_section(S_CONFIG);
+ config_msgnum = -1;
+ CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL, CtdlGetSysConfigBackend, NULL);
+ msgnum = config_msgnum;
+ end_critical_section(S_CONFIG);
+ if (msgnum > 0) {
+ CtdlSetConfigLong(sysconfname, msgnum); // store it the new way so we don't have to do this again
+ }
+ }
+
+ if (msgnum <= 0) {
+ conf = NULL;
+ }
+ else {
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg != NULL) {
+ conf = strdup(msg->cm_fields[eMesageText]);
+ CM_Free(msg);
+ }
+ else {
+ conf = NULL;
+ }
+ }
+
+ CtdlGetRoom(&CC->room, hold_rm);
+
+ if (conf != NULL) { // Strip the MIME headers, leaving only the content
+ do {
+ extract_token(buf, conf, 0, '\n', sizeof buf);
+ strcpy(conf, &conf[strlen(buf)+1]);
+ } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
+ }
+
+ return(conf);
+}
+
+
+// This is for storing longer configuration sets which are stored in the message base.
+void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
+ long old_msgnum = -1;
+ long new_msgnum = -1;
+
+ // Search for the previous copy of this config item, so we can delete it
+ old_msgnum = CtdlGetConfigLong(sysconfname);
+
+ // Go ahead and save it, and write the new msgnum to the config database so we can find it again
+ new_msgnum = CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 0);
+ if (new_msgnum > 0) {
+ CtdlSetConfigLong(sysconfname, new_msgnum);
+
+ // Now delete the old copy
+ if (old_msgnum > 0) {
+ CtdlDeleteMessages(SYSCONFIGROOM, &old_msgnum, 1, "");
+ }
+ }
+}
--- /dev/null
+// Copyright (c) 1987-2016 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.
+
+#include "serv_extensions.h"
+#include "citadel_dirs.h"
+
+// This is the format of the legacy config file. Do not attempt to do anything with it other
+// than migrate it into the new format. Seriously -- DO NOT CHANGE IT. The only purpose of this
+// struct is to represent the OLD configuration format.
+struct legacy_config {
+ char c_nodename[16]; // short name of this node on a Citadel network
+ char c_fqdn[64]; // this site's fully qualified domain name
+ char c_humannode[21]; // human-readable site name
+ char c_niu_7[16];
+ uid_t c_niu_6;
+ char c_creataide; // 1 = creating a room auto-grants room aide privileges
+ int c_sleeping; // watchdog timer (seconds)
+ char c_initax; // initial access level for new users
+ char c_regiscall; // after c_regiscall logins user will be asked to register
+ char c_twitdetect; // automatically move messages from problem users to trashcan
+ char c_twitroom[ROOMNAMELEN]; // name of trashcan
+ char c_moreprompt[80]; // paginator prompt
+ char c_restrict; // require per-user permission to send Internet mail
+ long c_niu_1;
+ char c_site_location[32]; // geographic location of this Citadel site
+ char c_sysadm[26]; // name of system administrator
+ char c_niu_2[15];
+ int c_niu_3;
+ int c_maxsessions; // maximum number of concurrent sessions allowed
+ char c_ip_addr[20]; // bind address for listening sockets
+ int c_port_number; // port number for Citadel protocol (usually 504)
+ int c_niu_4;
+ struct ExpirePolicy c_ep; // default expire policy for the entire site
+ int c_userpurge; // user purge time (in days)
+ int c_roompurge; // room purge time (in days)
+ char c_logpages[ROOMNAMELEN];
+ char c_createax;
+ long c_maxmsglen;
+ int c_min_workers;
+ int c_max_workers;
+ int c_pop3_port;
+ int c_smtp_port;
+ int c_rfc822_strict_from;
+ int c_aide_zap;
+ int c_imap_port;
+ time_t c_net_freq;
+ char c_disable_newu;
+ char c_enable_fulltext;
+ char c_baseroom[ROOMNAMELEN];
+ char c_aideroom[ROOMNAMELEN];
+ int c_purge_hour;
+ struct ExpirePolicy c_mbxep;
+ char c_ldap_host[128];
+ int c_ldap_port;
+ char c_ldap_base_dn[256];
+ char c_ldap_bind_dn[256];
+ char c_ldap_bind_pw[256];
+ int c_msa_port;
+ int c_imaps_port;
+ int c_pop3s_port;
+ int c_smtps_port;
+ char c_auto_cull;
+ char c_niu_5;
+ char c_allow_spoofing;
+ char c_journal_email;
+ char c_journal_pubmsgs;
+ char c_journal_dest[128];
+ char c_default_cal_zone[128];
+ int c_pftcpdict_port;
+ int c_niu_9;
+ int c_auth_mode;
+ char c_niu_8[256];
+ int c_niu_10;
+ char c_niu_11[256];
+ char c_niu_12[256];
+ char c_rbl_at_greeting;
+ char c_master_user[32];
+ char c_master_pass[32];
+ char c_pager_program[256];
+ char c_imap_keep_from;
+ int c_xmpp_c2s_port;
+ int c_xmpp_s2s_port;
+ time_t c_pop3_fetch;
+ time_t c_pop3_fastest;
+ int c_spam_flag_only;
+ int c_guest_logins;
+ int c_nntp_port;
+ int c_nntps_port;
+};
+
+
+void initialize_config_system(void);
+void shutdown_config_system(void);
+void put_config(void);
+void CtdlSetConfigStr(char *, char *);
+char *CtdlGetConfigStr(char *);
+int CtdlGetConfigInt(char *);
+long CtdlGetConfigLong(char *);
+void CtdlSetConfigInt(char *key, int value);
+void CtdlSetConfigLong(char *key, long value);
+void CtdlDelConfig(char *key);
+
+char *CtdlGetSysConfig(char *sysconfname);
+void CtdlPutSysConfig(char *sysconfname, char *sysconfdata);
+void validate_config(void);
+void netcfg_keyname(char *, long);
--- /dev/null
+//
+// Citadel context management stuff.
+// Here's where we (hopefully) have all the code that manipulates contexts.
+//
+// Copyright (c) 1987-2020 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.
+
+#include "ctdl_module.h"
+#include "serv_extensions.h"
+#include "citserver.h"
+#include "user_ops.h"
+#include "locate_host.h"
+#include "context.h"
+#include "control.h"
+#include "config.h"
+
+pthread_key_t MyConKey; /* TSD key for MyContext() */
+CitContext masterCC;
+CitContext *ContextList = NULL;
+time_t last_purge = 0; /* Last dead session purge */
+int num_sessions = 0; /* Current number of sessions */
+int next_pid = 0;
+
+/* Flag for single user mode */
+static int want_single_user = 0;
+
+/* Try to go single user */
+
+int CtdlTrySingleUser(void) {
+ int can_do = 0;
+
+ begin_critical_section(S_SINGLE_USER);
+ if (want_single_user)
+ can_do = 0;
+ else
+ {
+ can_do = 1;
+ want_single_user = 1;
+ }
+ end_critical_section(S_SINGLE_USER);
+ return can_do;
+}
+
+
+void CtdlEndSingleUser(void) {
+ begin_critical_section(S_SINGLE_USER);
+ want_single_user = 0;
+ end_critical_section(S_SINGLE_USER);
+}
+
+
+int CtdlWantSingleUser(void) {
+ return want_single_user;
+}
+
+
+int CtdlIsSingleUser(void) {
+ if (want_single_user) {
+ /* check for only one context here */
+ if (num_sessions == 1)
+ return 1;
+ }
+ return 0;
+}
+
+
+/*
+ * Locate a context by its session number and terminate it if the user is able.
+ * User can NOT terminate their current session.
+ * User CAN terminate any other session that has them logged in.
+ * Aide CAN terminate any session except the current one.
+ */
+int CtdlTerminateOtherSession (int session_num) {
+ int ret = 0;
+ CitContext *ccptr;
+ int aide;
+
+ if (session_num == CC->cs_pid) return TERM_NOTALLOWED;
+
+ aide = ( (CC->user.axlevel >= AxAideU) || (CC->internal_pgm) ) ;
+
+ syslog(LOG_DEBUG, "context: locating session to kill");
+ begin_critical_section(S_SESSION_TABLE);
+ for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
+ if (session_num == ccptr->cs_pid) {
+ ret |= TERM_FOUND;
+ if ((ccptr->user.usernum == CC->user.usernum) || aide) {
+ ret |= TERM_ALLOWED;
+ }
+ break;
+ }
+ }
+
+ if (((ret & TERM_FOUND) != 0) && ((ret & TERM_ALLOWED) != 0))
+ {
+ if (ccptr->user.usernum == CC->user.usernum)
+ ccptr->kill_me = KILLME_ADMIN_TERMINATE;
+ else
+ ccptr->kill_me = KILLME_IDLE;
+ end_critical_section(S_SESSION_TABLE);
+ }
+ else
+ end_critical_section(S_SESSION_TABLE);
+
+ return ret;
+}
+
+
+/*
+ * Check to see if the user who we just sent mail to is logged in. If yes,
+ * bump the 'new mail' counter for their session. That enables them to
+ * receive a new mail notification without having to hit the database.
+ */
+void BumpNewMailCounter(long which_user) {
+ CtdlBumpNewMailCounter(which_user);
+}
+
+
+void CtdlBumpNewMailCounter(long which_user) {
+ CitContext *ptr;
+
+ begin_critical_section(S_SESSION_TABLE);
+
+ for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
+ if (ptr->user.usernum == which_user) {
+ ptr->newmail += 1;
+ }
+ }
+
+ end_critical_section(S_SESSION_TABLE);
+}
+
+
+/*
+ * Check to see if a user is currently logged in
+ * Take care with what you do as a result of this test.
+ * The user may not have been logged in when this function was called BUT
+ * because of threading the user might be logged in before you test the result.
+ */
+int CtdlIsUserLoggedIn(char *user_name) {
+ CitContext *cptr;
+ int ret = 0;
+
+ begin_critical_section (S_SESSION_TABLE);
+ for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
+ if (!strcasecmp(cptr->user.fullname, user_name)) {
+ ret = 1;
+ break;
+ }
+ }
+ end_critical_section(S_SESSION_TABLE);
+ return ret;
+}
+
+
+/*
+ * Check to see if a user is currently logged in.
+ * Basically same as CtdlIsUserLoggedIn() but uses the user number instead.
+ * Take care with what you do as a result of this test.
+ * The user may not have been logged in when this function was called BUT
+ * because of threading the user might be logged in before you test the result.
+ */
+int CtdlIsUserLoggedInByNum (long usernum) {
+ CitContext *cptr;
+ int ret = 0;
+
+ begin_critical_section(S_SESSION_TABLE);
+ for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
+ if (cptr->user.usernum == usernum) {
+ ret = 1;
+ }
+ }
+ end_critical_section(S_SESSION_TABLE);
+ return ret;
+}
+
+
+/*
+ * Return a pointer to the CitContext structure bound to the thread which
+ * called this function. If there's no such binding (for example, if it's
+ * called by the housekeeper thread) then a generic 'master' CC is returned.
+ *
+ * This function is used *VERY* frequently and must be kept small.
+ */
+CitContext *MyContext(void) {
+ register CitContext *c;
+ return ((c = (CitContext *) pthread_getspecific(MyConKey), c == NULL) ? &masterCC : c);
+}
+
+
+/*
+ * Terminate idle sessions. This function pounds through the session table
+ * comparing the current time to each session's time-of-last-command. If an
+ * idle session is found it is terminated, then the search restarts at the
+ * beginning because the pointer to our place in the list becomes invalid.
+ */
+void terminate_idle_sessions(void) {
+ CitContext *ccptr;
+ time_t now;
+ int killed = 0;
+ int longrunners = 0;
+
+ now = time(NULL);
+ begin_critical_section(S_SESSION_TABLE);
+ for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
+ if (
+ (ccptr != CC)
+ && (CtdlGetConfigLong("c_sleeping") > 0)
+ && (now - (ccptr->lastcmd) > CtdlGetConfigLong("c_sleeping"))
+ ) {
+ if (!ccptr->dont_term) {
+ ccptr->kill_me = KILLME_IDLE;
+ ++killed;
+ }
+ else {
+ ++longrunners;
+ }
+ }
+ }
+ end_critical_section(S_SESSION_TABLE);
+ if (killed > 0) {
+ syslog(LOG_INFO, "context: scheduled %d idle sessions for termination", killed);
+ }
+ if (longrunners > 0) {
+ syslog(LOG_INFO, "context: did not terminate %d protected idle sessions", longrunners);
+ }
+}
+
+
+/*
+ * During shutdown, close the sockets of any sessions still connected.
+ */
+void terminate_all_sessions(void) {
+ CitContext *ccptr;
+ int killed = 0;
+
+ begin_critical_section(S_SESSION_TABLE);
+ for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
+ if (ccptr->client_socket != -1)
+ {
+ syslog(LOG_INFO, "context: terminate_all_sessions() is murdering %s CC[%d]", ccptr->curr_user, ccptr->cs_pid);
+ close(ccptr->client_socket);
+ ccptr->client_socket = -1;
+ killed++;
+ }
+ }
+ end_critical_section(S_SESSION_TABLE);
+ if (killed > 0) {
+ syslog(LOG_INFO, "context: flushed %d stuck sessions", killed);
+ }
+}
+
+
+/*
+ * Terminate a session.
+ */
+void RemoveContext (CitContext *con) {
+ const char *c;
+ if (con == NULL) {
+ syslog(LOG_ERR, "context: RemoveContext() called with NULL, this should not happen");
+ return;
+ }
+ c = con->ServiceName;
+ if (c == NULL) {
+ c = "WTF?";
+ }
+ syslog(LOG_DEBUG, "context: RemoveContext(%s) session %d", c, con->cs_pid);
+
+ /* Run any cleanup routines registered by loadable modules.
+ * Note: We have to "become_session()" because the cleanup functions
+ * might make references to "CC" assuming it's the right one.
+ */
+ become_session(con);
+ CtdlUserLogout();
+ PerformSessionHooks(EVT_STOP);
+ client_close(); /* If the client is still connected, blow 'em away. */
+ become_session(NULL);
+
+ syslog(LOG_INFO, "context: [%3d]SRV[%s] Session ended.", con->cs_pid, c);
+
+ /*
+ * If the client is still connected, blow 'em away.
+ * if the socket is 0 or -1, its already gone or was never there.
+ */
+ if (con->client_socket > 0) {
+ syslog(LOG_INFO, "context: closing socket %d", con->client_socket);
+ close(con->client_socket);
+ }
+
+ /* If using AUTHMODE_LDAP, free the DN */
+ if (con->ldap_dn) {
+ free(con->ldap_dn);
+ con->ldap_dn = NULL;
+ }
+ FreeStrBuf(&con->StatusMessage);
+ FreeStrBuf(&con->MigrateBuf);
+ FreeStrBuf(&con->RecvBuf.Buf);
+ if (con->cached_msglist) {
+ free(con->cached_msglist);
+ }
+
+ syslog(LOG_DEBUG, "context: done with RemoveContext()");
+}
+
+
+/*
+ * Initialize a new context and place it in the list. The session number
+ * used to be the PID (which is why it's called cs_pid), but that was when we
+ * had one process per session. Now we just assign them sequentially, starting
+ * at 1 (don't change it to 0 because masterCC uses 0).
+ */
+CitContext *CreateNewContext(void) {
+ CitContext *me;
+
+ me = (CitContext *) malloc(sizeof(CitContext));
+ if (me == NULL) {
+ syslog(LOG_ERR, "citserver: malloc() failed: %m");
+ return NULL;
+ }
+ memset(me, 0, sizeof(CitContext));
+
+ /* Give the context a name. Hopefully makes it easier to track */
+ strcpy (me->user.fullname, "SYS_notauth");
+
+ /* The new context will be created already in the CON_EXECUTING state
+ * in order to prevent another thread from grabbing it while it's
+ * being set up.
+ */
+ me->state = CON_EXECUTING;
+ /*
+ * Generate a unique session number and insert this context into
+ * the list.
+ */
+ me->MigrateBuf = NewStrBuf();
+
+ me->RecvBuf.Buf = NewStrBuf();
+
+ me->lastcmd = time(NULL); /* set lastcmd to now to prevent idle timer infanticide TODO: if we have a valid IO, use that to set the timer. */
+
+
+ begin_critical_section(S_SESSION_TABLE);
+ me->cs_pid = ++next_pid;
+ me->prev = NULL;
+ me->next = ContextList;
+ ContextList = me;
+ if (me->next != NULL) {
+ me->next->prev = me;
+ }
+ ++num_sessions;
+ end_critical_section(S_SESSION_TABLE);
+ return (me);
+}
+
+
+/*
+ * Initialize a new context and place it in the list. The session number
+ * used to be the PID (which is why it's called cs_pid), but that was when we
+ * had one process per session. Now we just assign them sequentially, starting
+ * at 1 (don't change it to 0 because masterCC uses 0).
+ */
+CitContext *CloneContext(CitContext *CloneMe) {
+ CitContext *me;
+
+ me = (CitContext *) malloc(sizeof(CitContext));
+ if (me == NULL) {
+ syslog(LOG_ERR, "citserver: malloc() failed: %m");
+ return NULL;
+ }
+ memcpy(me, CloneMe, sizeof(CitContext));
+
+ memset(&me->RecvBuf, 0, sizeof(IOBuffer));
+ memset(&me->SendBuf, 0, sizeof(IOBuffer));
+ memset(&me->SBuf, 0, sizeof(IOBuffer));
+ me->MigrateBuf = NULL;
+ me->sMigrateBuf = NULL;
+ me->redirect_buffer = NULL;
+#ifdef HAVE_OPENSSL
+ me->ssl = NULL;
+#endif
+
+ me->download_fp = NULL;
+ me->upload_fp = NULL;
+ /// TODO: what about the room/user?
+ me->ma = NULL;
+ me->openid_data = NULL;
+ me->ldap_dn = NULL;
+ me->session_specific_data = NULL;
+
+ me->CIT_ICAL = NULL;
+
+ me->cached_msglist = NULL;
+ me->download_fp = NULL;
+ me->upload_fp = NULL;
+ me->client_socket = 0;
+
+ me->MigrateBuf = NewStrBuf();
+ me->RecvBuf.Buf = NewStrBuf();
+
+ begin_critical_section(S_SESSION_TABLE);
+ {
+ me->cs_pid = ++next_pid;
+ me->prev = NULL;
+ me->next = ContextList;
+ me->lastcmd = time(NULL); /* set lastcmd to now to prevent idle timer infanticide */
+ ContextList = me;
+ if (me->next != NULL) {
+ me->next->prev = me;
+ }
+ ++num_sessions;
+ }
+ end_critical_section(S_SESSION_TABLE);
+ return (me);
+}
+
+
+/*
+ * Return an array containing a copy of the context list.
+ * This allows worker threads to perform "for each context" operations without
+ * having to lock and traverse the live list.
+ */
+CitContext *CtdlGetContextArray(int *count) {
+ int nContexts, i;
+ CitContext *nptr, *cptr;
+
+ nContexts = num_sessions;
+ nptr = malloc(sizeof(CitContext) * nContexts);
+ if (!nptr) {
+ *count = 0;
+ return NULL;
+ }
+ begin_critical_section(S_SESSION_TABLE);
+ for (cptr = ContextList, i=0; cptr != NULL && i < nContexts; cptr = cptr->next, i++) {
+ memcpy(&nptr[i], cptr, sizeof (CitContext));
+ }
+ end_critical_section (S_SESSION_TABLE);
+
+ *count = i;
+ return nptr;
+}
+
+
+/*
+ * Back-end function for starting a session
+ */
+void begin_session(CitContext *con) {
+ /*
+ * Initialize some variables specific to our context.
+ */
+ con->logged_in = 0;
+ con->internal_pgm = 0;
+ con->download_fp = NULL;
+ con->upload_fp = NULL;
+ con->cached_msglist = NULL;
+ con->cached_num_msgs = 0;
+ con->FirstExpressMessage = NULL;
+ time(&con->lastcmd);
+ time(&con->lastidle);
+ strcpy(con->lastcmdname, " ");
+ strcpy(con->cs_clientname, "(unknown)");
+ strcpy(con->curr_user, NLI);
+ *con->cs_clientinfo = '\0';
+ safestrncpy(con->cs_host, CtdlGetConfigStr("c_fqdn"), sizeof con->cs_host);
+ safestrncpy(con->cs_addr, "", sizeof con->cs_addr);
+ con->cs_UDSclientUID = -1;
+ con->cs_host[sizeof con->cs_host - 1] = 0;
+ if (!CC->is_local_client) {
+ locate_host(con->cs_host, sizeof con->cs_host,
+ con->cs_addr, sizeof con->cs_addr,
+ con->client_socket
+ );
+ }
+ else {
+ con->cs_host[0] = 0;
+ con->cs_addr[0] = 0;
+#ifdef HAVE_STRUCT_UCRED
+ {
+ /* as http://www.wsinnovations.com/softeng/articles/uds.html told us... */
+ struct ucred credentials;
+ socklen_t ucred_length = sizeof(struct ucred);
+
+ /*fill in the user data structure */
+ if(getsockopt(con->client_socket, SOL_SOCKET, SO_PEERCRED, &credentials, &ucred_length)) {
+ syslog(LOG_ERR, "context: could obtain credentials from unix domain socket");
+
+ }
+ else {
+ /* the process ID of the process on the other side of the socket */
+ /* credentials.pid; */
+
+ /* the effective UID of the process on the other side of the socket */
+ con->cs_UDSclientUID = credentials.uid;
+
+ /* the effective primary GID of the process on the other side of the socket */
+ /* credentials.gid; */
+
+ /* To get supplemental groups, we will have to look them up in our account
+ database, after a reverse lookup on the UID to get the account name.
+ We can take this opportunity to check to see if this is a legit account.
+ */
+ snprintf(con->cs_clientinfo, sizeof(con->cs_clientinfo),
+ "PID: "F_PID_T"; UID: "F_UID_T"; GID: "F_XPID_T" ",
+ credentials.pid,
+ credentials.uid,
+ credentials.gid);
+ }
+ }
+#endif
+ }
+ con->cs_flags = 0;
+
+ con->nologin = 0;
+ if (((CtdlGetConfigInt("c_maxsessions") > 0)&&(num_sessions > CtdlGetConfigInt("c_maxsessions"))) || CtdlWantSingleUser()) {
+ con->nologin = 1;
+ }
+
+ syslog(LOG_INFO, "context: session (%s) started from %s (%s) uid=%d",
+ con->ServiceName, con->cs_host, con->cs_addr, con->cs_UDSclientUID
+ );
+
+ /* Run any session startup routines registered by loadable modules */
+ PerformSessionHooks(EVT_START);
+}
+
+
+/*
+ * This function fills in a context and its user field correctly
+ * Then creates/loads that user
+ */
+void CtdlFillSystemContext(CitContext *context, char *name) {
+ char sysname[SIZ];
+ long len;
+
+ memset(context, 0, sizeof(CitContext));
+ context->internal_pgm = 1;
+ context->cs_pid = 0;
+ strcpy (sysname, "SYS_");
+ strcat (sysname, name);
+ len = strlen(sysname);
+ memcpy(context->curr_user, sysname, len + 1);
+ context->client_socket = (-1);
+ context->state = CON_SYS;
+ context->ServiceName = name;
+
+ /* internal_create_user has the side effect of loading the user regardless of whether they
+ * already existed or needed to be created
+ */
+ internal_create_user(sysname, &(context->user), -1) ;
+
+ /* Check to see if the system user needs upgrading */
+ if (context->user.usernum == 0) { /* old system user with number 0, upgrade it */
+ context->user.usernum = get_new_user_number();
+ syslog(LOG_INFO, "context: upgrading system user \"%s\" from user number 0 to user number %ld", context->user.fullname, context->user.usernum);
+ /* add user to the database */
+ CtdlPutUser(&(context->user));
+ cdb_store(CDB_USERSBYNUMBER, &(context->user.usernum), sizeof(long), context->user.fullname, strlen(context->user.fullname)+1);
+ }
+}
+
+
+/*
+ * Cleanup any contexts that are left lying around
+ */
+void context_cleanup(void) {
+ CitContext *ptr = NULL;
+ CitContext *rem = NULL;
+
+ /*
+ * Clean up the contexts.
+ * There are no threads so no critical_section stuff is needed.
+ */
+ ptr = ContextList;
+
+ /* We need to update the ContextList because some modules may want to itterate it
+ * Question is should we NULL it before iterating here or should we just keep updating it
+ * as we remove items?
+ *
+ * Answer is to NULL it first to prevent modules from doing any actions on the list at all
+ */
+ ContextList=NULL;
+ while (ptr != NULL){
+ /* Remove the session from the active list */
+ rem = ptr->next;
+ --num_sessions;
+
+ syslog(LOG_DEBUG, "context: context_cleanup() purging session %d", ptr->cs_pid);
+ RemoveContext(ptr);
+ free (ptr);
+ ptr = rem;
+ }
+}
+
+
+/*
+ * Purge all sessions which have the 'kill_me' flag set.
+ * This function has code to prevent it from running more than once every
+ * few seconds, because running it after every single unbind would waste a lot
+ * of CPU time and keep the context list locked too much. To force it to run
+ * anyway, set "force" to nonzero.
+ */
+void dead_session_purge(int force) {
+ CitContext *ptr, *ptr2; /* general-purpose utility pointer */
+ CitContext *rem = NULL; /* list of sessions to be destroyed */
+
+ if (force == 0) {
+ if ( (time(NULL) - last_purge) < 5 ) {
+ return; /* Too soon, go away */
+ }
+ }
+ time(&last_purge);
+
+ if (try_critical_section(S_SESSION_TABLE))
+ return;
+
+ ptr = ContextList;
+ while (ptr) {
+ ptr2 = ptr;
+ ptr = ptr->next;
+
+ if ( (ptr2->state == CON_IDLE) && (ptr2->kill_me) ) {
+ /* Remove the session from the active list */
+ if (ptr2->prev) {
+ ptr2->prev->next = ptr2->next;
+ }
+ else {
+ ContextList = ptr2->next;
+ }
+ if (ptr2->next) {
+ ptr2->next->prev = ptr2->prev;
+ }
+
+ --num_sessions;
+ /* And put it on our to-be-destroyed list */
+ ptr2->next = rem;
+ rem = ptr2;
+ }
+ }
+ end_critical_section(S_SESSION_TABLE);
+
+ /* Now that we no longer have the session list locked, we can take
+ * our time and destroy any sessions on the to-be-killed list, which
+ * is allocated privately on this thread's stack.
+ */
+ while (rem != NULL) {
+ syslog(LOG_DEBUG, "context: dead_session_purge() purging session %d, reason=%d", rem->cs_pid, rem->kill_me);
+ RemoveContext(rem);
+ ptr = rem;
+ rem = rem->next;
+ free(ptr);
+ }
+}
+
+
+/*
+ * masterCC is the context we use when not attached to a session. This
+ * function initializes it.
+ */
+void InitializeMasterCC(void) {
+ memset(&masterCC, 0, sizeof(struct CitContext));
+ masterCC.internal_pgm = 1;
+ masterCC.cs_pid = 0;
+}
+
+
+/*
+ * Set the "async waiting" flag for a session, if applicable
+ */
+void set_async_waiting(struct CitContext *ccptr) {
+ syslog(LOG_DEBUG, "context: setting async_waiting flag for session %d", ccptr->cs_pid);
+ if (ccptr->is_async) {
+ ccptr->async_waiting++;
+ if (ccptr->state == CON_IDLE) {
+ ccptr->state = CON_READY;
+ }
+ }
+}
+
+
+CTDL_MODULE_INIT(session)
+{
+ if (!threading) {
+ }
+ return "session";
+}
--- /dev/null
+
+#ifndef CONTEXT_H
+#define CONTEXT_H
+
+#include <stdarg.h>
+#include "sysdep.h"
+#include "server.h"
+#include "sysdep_decls.h"
+#include "threads.h"
+
+
+/*
+ * Values for CitContext.state
+ *
+ * A session that is doing nothing is in CON_IDLE state. When activity
+ * is detected on the socket, it goes to CON_READY, indicating that it
+ * needs to have a worker thread bound to it. When a thread binds to
+ * the session, it goes to CON_EXECUTING and does its thing. When the
+ * transaction is finished, the thread sets it back to CON_IDLE and lets
+ * it go.
+ */
+typedef enum __CCState {
+ CON_IDLE, /* This context is doing nothing */
+ CON_GREETING, /* This context needs to output its greeting */
+ CON_STARTING, /* This context is outputting its greeting */
+ CON_READY, /* This context needs attention */
+ CON_EXECUTING, /* This context is bound to a thread */
+ CON_SYS /* This is a system context and mustn't be purged */
+} CCState;
+
+#ifndef __CIT_CONTEXT__
+#define __CIT_CONTEXT__
+typedef struct CitContext CitContext;
+#endif
+
+/*
+ * This structure keeps track of all information relating to a running
+ * session on the server. We keep one of these for each session.
+ */
+struct CitContext {
+ CitContext *prev; /* Link to previous session in list */
+ CitContext *next; /* Link to next session in the list */
+
+ int cs_pid; /* session ID */
+ int dont_term; /* for special activities like artv so we don't get killed */
+ double created; /* time of birth */
+ time_t lastcmd; /* time of last command executed */
+ time_t lastidle; /* For computing idle time */
+ CCState state; /* thread state (see CON_ values below) */
+ int kill_me; /* Set to nonzero to flag for termination */
+
+ IOBuffer SendBuf, /* Our write Buffer */
+ RecvBuf, /* Our block buffered read buffer */
+ SBuf; /* Our block buffered read buffer for clients */
+
+ StrBuf *MigrateBuf; /* Our block buffered read buffer */
+ StrBuf *sMigrateBuf; /* Our block buffered read buffer */
+
+ int client_socket;
+ int is_local_client; /* set to 1 if client is running on the same host */
+ /* Redirect this session's output to a memory buffer? */
+ StrBuf *redirect_buffer; /* the buffer */
+ StrBuf *StatusMessage;
+#ifdef HAVE_OPENSSL
+ SSL *ssl;
+ int redirect_ssl;
+#endif
+
+ char curr_user[USERNAME_SIZE]; /* name of current user */
+ int logged_in; /* logged in? */
+ int internal_pgm; /* authenticated as internal program? */
+ int nologin; /* not allowed to log in */
+ int curr_view; /* The view type for the current user/room */
+
+ time_t previous_login; /* Date/time of previous login */
+ char lastcmdname[5]; /* name of last command executed */
+ unsigned cs_flags; /* miscellaneous flags */
+ int is_async; /* Nonzero if client accepts async msgs */
+ int async_waiting; /* Nonzero if there are async msgs waiting */
+ int input_waiting; /* Nonzero if there is client input waiting */
+ int can_receive_im; /* Session is capable of receiving instant messages */
+
+ /* Client information */
+ int cs_clientdev; /* client developer ID */
+ int cs_clienttyp; /* client type code */
+ int cs_clientver; /* client version number */
+ char cs_clientinfo[256];/* if its a unix domain socket, some info for logging. */
+ uid_t cs_UDSclientUID; /* the uid of the client when talking via UDS */
+ char cs_clientname[32]; /* name of client software */
+ char cs_host[64]; /* host logged in from */
+ char cs_addr[64]; /* address logged in from */
+
+ /* The Internet type of thing */
+ char cs_principal_id[256]; /* User principal identity for XMPP, ActivityPub, etc. */
+ char cs_inet_email[128]; /* Return address of outbound Internet mail */
+ char cs_inet_other_emails[1024]; /* User's other valid Internet email addresses */
+ char cs_inet_fn[128]; /* Friendly-name of outbound Internet mail */
+
+ FILE *download_fp; /* Fields relating to file transfer */
+ size_t download_fp_total;
+ char download_desired_section[128];
+ FILE *upload_fp;
+ char upl_file[256];
+ char upl_path[PATH_MAX];
+ char upl_comment[256];
+ char upl_filedir[PATH_MAX];
+ char upl_mimetype[64];
+
+ struct ctdluser user; /* Database record buffers */
+ struct ctdlroom room;
+
+ /* A linked list of all instant messages sent to us. */
+ struct ExpressMessage *FirstExpressMessage;
+ int disable_exp; /* Set to 1 to disable incoming pages */
+ int newmail; /* Other sessions increment this */
+
+ /* Preferred MIME formats */
+ char preferred_formats[256];
+ int msg4_dont_decode;
+
+ /* Dynamically allocated session data */
+ void *session_specific_data; /* Used by individual protocol modules */
+ struct cit_ical *CIT_ICAL; /* calendaring data */
+ struct ma_info *ma; /* multipart/alternative data */
+ const char *ServiceName; /* readable purpose of this session */
+ long tcp_port;
+ void *openid_data; /* Data stored by the OpenID module */
+ char *ldap_dn; /* DN of user when using AUTHMODE_LDAP */
+
+ void (*h_command_function) (void) ; /* service command function */
+ void (*h_async_function) (void) ; /* do async msgs function */
+ void (*h_greeting_function) (void) ; /* greeting function for session startup */
+
+ long *cached_msglist; /* results of the previous CtdlForEachMessage() */
+ int cached_num_msgs;
+
+ char vcard_updated_by_ldap; /* !0 iff ldap changed the vcard, treat as aide update */
+};
+
+#define CC MyContext()
+
+extern pthread_key_t MyConKey; /* TSD key for MyContext() */
+extern int num_sessions;
+extern CitContext masterCC;
+extern CitContext *ContextList;
+
+CitContext *MyContext (void);
+void RemoveContext (struct CitContext *);
+CitContext *CreateNewContext (void);
+void context_cleanup(void);
+void kill_session (int session_to_kill);
+void InitializeMasterCC(void);
+void dead_session_purge(int force);
+void set_async_waiting(struct CitContext *ccptr);
+
+CitContext *CloneContext(CitContext *CloneMe);
+
+/* forcibly close and flush fd's on shutdown */
+void terminate_all_sessions(void);
+
+/* Deprecated, user CtdlBumpNewMailCounter() instead */
+void BumpNewMailCounter(long) __attribute__ ((deprecated));
+
+void terminate_idle_sessions(void);
+int CtdlTerminateOtherSession (int session_num);
+/* bits returned by CtdlTerminateOtherSession */
+#define TERM_FOUND 0x01
+#define TERM_ALLOWED 0x02
+#define TERM_KILLED 0x03
+#define TERM_NOTALLOWED -1
+
+/*
+ * Bind a thread to a context. (It's inline merely to speed things up.)
+ */
+static INLINE void become_session(CitContext *which_con) {
+ pthread_setspecific(MyConKey, (void *)which_con );
+}
+
+#endif /* CONTEXT_H */
--- /dev/null
+//
+// This module handles states which are global to the entire server.
+//
+// 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.
+
+#include <stdio.h>
+#include <sys/file.h>
+#include <libcitadel.h>
+
+#include "ctdl_module.h"
+#include "config.h"
+#include "citserver.h"
+#include "user_ops.h"
+
+long control_highest_user = 0;
+
+/*
+ * This is the legacy "control record" format for the message base. If found
+ * on disk, its contents will be migrated into the system configuration. Never
+ * change this.
+ */
+struct legacy_ctrl_format {
+ long MMhighest; /* highest message number in file */
+ unsigned MMflags; /* Global system flags */
+ long MMnextuser; /* highest user number on system */
+ long MMnextroom; /* highest room number on system */
+ int MM_hosted_upgrade_level; /* Server-hosted upgrade level */
+ int MM_fulltext_wordbreaker; /* ID of wordbreaker in use */
+ long MMfulltext; /* highest message number indexed */
+ int MMdbversion; /* Version of Berkeley DB used on previous server run */
+};
+
+
+/*
+ * data that gets passed back and forth between control_find_highest() and its caller
+ */
+struct cfh {
+ long highest_roomnum_found;
+ long highest_msgnum_found;
+};
+
+
+/*
+ * Callback to get highest room number when rebuilding message base metadata
+ *
+ * sanity_diag_mode (can be set by -s flag at startup) may be:
+ * 0 = attempt to fix inconsistencies
+ * 1 = show inconsistencies but don't repair them, exit after complete
+ * 2 = show inconsistencies but don't repair them, continue execution
+ */
+void control_find_highest(struct ctdlroom *qrbuf, void *data) {
+ struct cfh *cfh = (struct cfh *)data;
+ struct cdbdata *cdbfr;
+ long *msglist;
+ int num_msgs=0;
+ int c;
+
+ if (qrbuf->QRnumber > cfh->highest_roomnum_found) {
+ cfh->highest_roomnum_found = qrbuf->QRnumber;
+ }
+
+ /* Load the message list */
+ cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
+ if (cdbfr != NULL) {
+ msglist = (long *) cdbfr->ptr;
+ num_msgs = cdbfr->len / sizeof(long);
+ }
+ else {
+ return; /* No messages at all? No further action. */
+ }
+
+ if (num_msgs > 0) {
+ for (c=0; c<num_msgs; c++) {
+ if (msglist[c] > cfh->highest_msgnum_found) {
+ cfh->highest_msgnum_found = msglist[c];
+ }
+ }
+ }
+
+ cdb_free(cdbfr);
+}
+
+
+/*
+ * Callback to get highest user number.
+ */
+void control_find_user(char *username, void *out_data) {
+ struct ctdluser EachUser;
+
+ if (CtdlGetUser(&EachUser, username) != 0) {
+ return;
+ }
+
+ if (EachUser.usernum > CtdlGetConfigLong("MMnextuser")) {
+ syslog(LOG_DEBUG, "control: fixing MMnextuser %ld > %ld , found in %s",
+ EachUser.usernum, CtdlGetConfigLong("MMnextuser"), EachUser.fullname
+ );
+ if (!sanity_diag_mode) {
+ CtdlSetConfigLong("MMnextuser", EachUser.usernum);
+ }
+ }
+}
+
+
+/*
+ * If we have a legacy format control record on disk, import it.
+ */
+void migrate_legacy_control_record(void) {
+ FILE *fp = NULL;
+ struct legacy_ctrl_format c;
+ memset(&c, 0, sizeof(c));
+
+ fp = fopen("citadel.control", "rb+");
+ if (fp != NULL) {
+ syslog(LOG_INFO, "control: legacy format record found -- importing to db");
+ fread(&c, sizeof(struct legacy_ctrl_format), 1, fp);
+
+ CtdlSetConfigLong( "MMhighest", c.MMhighest);
+ CtdlSetConfigInt( "MMflags", c.MMflags);
+ CtdlSetConfigLong( "MMnextuser", c.MMnextuser);
+ CtdlSetConfigLong( "MMnextroom", c.MMnextroom);
+ CtdlSetConfigInt( "MM_hosted_upgrade_level", c.MM_hosted_upgrade_level);
+ CtdlSetConfigInt( "MM_fulltext_wordbreaker", c.MM_fulltext_wordbreaker);
+ CtdlSetConfigLong( "MMfulltext", c.MMfulltext);
+
+ fclose(fp);
+ if (unlink("citadel.control") != 0) {
+ fprintf(stderr, "Unable to remove legacy control record after migrating it.\n");
+ fprintf(stderr, "Exiting to prevent data corruption.\n");
+ exit(CTDLEXIT_CONFIG);
+ }
+ }
+}
+
+
+/*
+ * check_control - check the control record has sensible values for message, user and room numbers
+ */
+void check_control(void) {
+
+ syslog(LOG_INFO, "control: sanity checking the recorded highest message and room numbers");
+ struct cfh cfh;
+ memset(&cfh, 0, sizeof(struct cfh));
+ CtdlForEachRoom(control_find_highest, &cfh);
+
+ if (cfh.highest_roomnum_found > CtdlGetConfigLong("MMnextroom")) {
+ syslog(LOG_DEBUG, "control: fixing MMnextroom %ld > %ld", cfh.highest_roomnum_found, CtdlGetConfigLong("MMnextroom"));
+ if (!sanity_diag_mode) {
+ CtdlSetConfigLong("MMnextroom", cfh.highest_roomnum_found);
+ }
+ }
+
+ if (cfh.highest_msgnum_found > CtdlGetConfigLong("MMhighest")) {
+ syslog(LOG_DEBUG, "control: fixing MMhighest %ld > %ld", cfh.highest_msgnum_found, CtdlGetConfigLong("MMhighest"));
+ if (!sanity_diag_mode) {
+ CtdlSetConfigLong("MMhighest", cfh.highest_msgnum_found);
+ }
+ }
+
+ syslog(LOG_INFO, "control: sanity checking the recorded highest user number");
+ ForEachUser(control_find_user, NULL);
+
+ syslog(LOG_INFO, "control: sanity checks complete");
+
+ if (sanity_diag_mode == 1) {
+ syslog(LOG_INFO, "control: sanity check diagnostic mode is active - exiting now");
+ abort();
+ }
+}
+
+
+/*
+ * get_new_message_number() - Obtain a new, unique ID to be used for a message.
+ */
+long get_new_message_number(void)
+{
+ long retval = 0L;
+ begin_critical_section(S_CONTROL);
+ retval = CtdlGetConfigLong("MMhighest");
+ ++retval;
+ CtdlSetConfigLong("MMhighest", retval);
+ end_critical_section(S_CONTROL);
+ return(retval);
+}
+
+
+/*
+ * CtdlGetCurrentMessageNumber() - Obtain the current highest message number in the system
+ * This provides a quick way to initialize a variable that might be used to indicate
+ * messages that should not be processed. For example, an inbox rules script will use this
+ * to record determine that messages older than this should not be processed.
+ *
+ * (Why is this function here? Can't we just go straight to the config variable it fetches?)
+ */
+long CtdlGetCurrentMessageNumber(void)
+{
+ return CtdlGetConfigLong("MMhighest");
+}
+
+
+/*
+ * get_new_user_number() - Obtain a new, unique ID to be used for a user.
+ */
+long get_new_user_number(void)
+{
+ long retval = 0L;
+ begin_critical_section(S_CONTROL);
+ retval = CtdlGetConfigLong("MMnextuser");
+ ++retval;
+ CtdlSetConfigLong("MMnextuser", retval);
+ end_critical_section(S_CONTROL);
+ return(retval);
+}
+
+
+/*
+ * get_new_room_number() - Obtain a new, unique ID to be used for a room.
+ */
+long get_new_room_number(void)
+{
+ long retval = 0L;
+ begin_critical_section(S_CONTROL);
+ retval = CtdlGetConfigLong("MMnextroom");
+ ++retval;
+ CtdlSetConfigLong("MMnextroom", retval);
+ end_critical_section(S_CONTROL);
+ return(retval);
+}
+
+
+/*
+ * Helper function for cmd_conf() to handle boolean values
+ */
+int confbool(char *v)
+{
+ if (IsEmptyStr(v)) return(0);
+ if (atoi(v) != 0) return(1);
+ return(0);
+}
+
+
+/*
+ * Get or set global configuration options
+ */
+void cmd_conf(char *argbuf)
+{
+ char cmd[16];
+ char buf[1024];
+ int a, i;
+ long ii;
+ char *confptr;
+ char confname[128];
+
+ if (CtdlAccessCheck(ac_aide)) return;
+
+ extract_token(cmd, argbuf, 0, '|', sizeof cmd);
+
+ // CONF GET - retrieve system configuration in legacy format
+ // This is deprecated; please do not add fields or change their order.
+ if (!strcasecmp(cmd, "GET")) {
+ cprintf("%d Configuration...\n", LISTING_FOLLOWS);
+ cprintf("%s\n", CtdlGetConfigStr("c_nodename"));
+ cprintf("%s\n", CtdlGetConfigStr("c_fqdn"));
+ cprintf("%s\n", CtdlGetConfigStr("c_humannode"));
+ cprintf("xxx\n"); /* placeholder -- field no longer in use */
+ cprintf("%d\n", CtdlGetConfigInt("c_creataide"));
+ cprintf("%d\n", CtdlGetConfigInt("c_sleeping"));
+ cprintf("%d\n", CtdlGetConfigInt("c_initax"));
+ cprintf("%d\n", CtdlGetConfigInt("c_regiscall"));
+ cprintf("%d\n", CtdlGetConfigInt("c_twitdetect"));
+ cprintf("%s\n", CtdlGetConfigStr("c_twitroom"));
+ cprintf("%s\n", CtdlGetConfigStr("c_moreprompt"));
+ cprintf("%d\n", CtdlGetConfigInt("c_restrict"));
+ cprintf("%s\n", CtdlGetConfigStr("c_site_location"));
+ cprintf("%s\n", CtdlGetConfigStr("c_sysadm"));
+ cprintf("%d\n", CtdlGetConfigInt("c_maxsessions"));
+ cprintf("xxx\n"); /* placeholder -- field no longer in use */
+ cprintf("%d\n", CtdlGetConfigInt("c_userpurge"));
+ cprintf("%d\n", CtdlGetConfigInt("c_roompurge"));
+ cprintf("%s\n", CtdlGetConfigStr("c_logpages"));
+ cprintf("%d\n", CtdlGetConfigInt("c_createax"));
+ cprintf("%ld\n", CtdlGetConfigLong("c_maxmsglen"));
+ cprintf("%d\n", CtdlGetConfigInt("c_min_workers"));
+ cprintf("%d\n", CtdlGetConfigInt("c_max_workers"));
+ cprintf("%d\n", CtdlGetConfigInt("c_pop3_port"));
+ cprintf("%d\n", CtdlGetConfigInt("c_smtp_port"));
+ cprintf("%d\n", CtdlGetConfigInt("c_rfc822_strict_from"));
+ cprintf("%d\n", CtdlGetConfigInt("c_aide_zap"));
+ cprintf("%d\n", CtdlGetConfigInt("c_imap_port"));
+ cprintf("%ld\n", CtdlGetConfigLong("c_net_freq"));
+ cprintf("%d\n", CtdlGetConfigInt("c_disable_newu"));
+ cprintf("1\n"); /* niu */
+ cprintf("%d\n", CtdlGetConfigInt("c_purge_hour"));
+ cprintf("%s\n", CtdlGetConfigStr("c_ldap_host"));
+ cprintf("%d\n", CtdlGetConfigInt("c_ldap_port"));
+ cprintf("%s\n", CtdlGetConfigStr("c_ldap_base_dn"));
+ cprintf("%s\n", CtdlGetConfigStr("c_ldap_bind_dn"));
+ cprintf("%s\n", CtdlGetConfigStr("c_ldap_bind_pw"));
+ cprintf("%s\n", CtdlGetConfigStr("c_ip_addr"));
+ cprintf("%d\n", CtdlGetConfigInt("c_msa_port"));
+ cprintf("%d\n", CtdlGetConfigInt("c_imaps_port"));
+ cprintf("%d\n", CtdlGetConfigInt("c_pop3s_port"));
+ cprintf("%d\n", CtdlGetConfigInt("c_smtps_port"));
+ cprintf("%d\n", CtdlGetConfigInt("c_enable_fulltext"));
+ cprintf("%d\n", CtdlGetConfigInt("c_auto_cull"));
+ cprintf("1\n");
+ cprintf("%d\n", CtdlGetConfigInt("c_allow_spoofing"));
+ cprintf("%d\n", CtdlGetConfigInt("c_journal_email"));
+ cprintf("%d\n", CtdlGetConfigInt("c_journal_pubmsgs"));
+ cprintf("%s\n", CtdlGetConfigStr("c_journal_dest"));
+ cprintf("%s\n", CtdlGetConfigStr("c_default_cal_zone"));
+ cprintf("%d\n", CtdlGetConfigInt("c_pftcpdict_port"));
+ cprintf("0\n");
+ cprintf("%d\n", CtdlGetConfigInt("c_auth_mode"));
+ cprintf("\n");
+ cprintf("\n");
+ cprintf("\n");
+ cprintf("\n");
+ cprintf("%d\n", CtdlGetConfigInt("c_rbl_at_greeting"));
+ cprintf("\n");
+ cprintf("\n");
+ cprintf("%s\n", CtdlGetConfigStr("c_pager_program"));
+ cprintf("%d\n", CtdlGetConfigInt("c_imap_keep_from"));
+ cprintf("%d\n", CtdlGetConfigInt("c_xmpp_c2s_port"));
+ cprintf("%d\n", CtdlGetConfigInt("c_xmpp_s2s_port"));
+ cprintf("%ld\n", CtdlGetConfigLong("c_pop3_fetch"));
+ cprintf("%ld\n", CtdlGetConfigLong("c_pop3_fastest"));
+ cprintf("%d\n", CtdlGetConfigInt("c_spam_flag_only"));
+ cprintf("%d\n", CtdlGetConfigInt("c_guest_logins"));
+ cprintf("%d\n", CtdlGetConfigInt("c_port_number"));
+ cprintf("%d\n", ctdluid);
+ cprintf("%d\n", CtdlGetConfigInt("c_nntp_port"));
+ cprintf("%d\n", CtdlGetConfigInt("c_nntps_port"));
+ cprintf("000\n");
+ }
+
+ // CONF SET - set system configuration in legacy format
+ // This is deprecated; please do not add fields or change their order.
+ else if (!strcasecmp(cmd, "SET")) {
+ unbuffer_output();
+ cprintf("%d Send configuration...\n", SEND_LISTING);
+ a = 0;
+ while (client_getln(buf, sizeof buf) >= 0 && strcmp(buf, "000")) {
+ switch (a) {
+ case 0:
+ CtdlSetConfigStr("c_nodename", buf);
+ break;
+ case 1:
+ CtdlSetConfigStr("c_fqdn", buf);
+ break;
+ case 2:
+ CtdlSetConfigStr("c_humannode", buf);
+ break;
+ case 3:
+ /* placeholder -- field no longer in use */
+ break;
+ case 4:
+ CtdlSetConfigInt("c_creataide", confbool(buf));
+ break;
+ case 5:
+ CtdlSetConfigInt("c_sleeping", atoi(buf));
+ break;
+ case 6:
+ i = atoi(buf);
+ if (i < 1) i = 1;
+ if (i > 6) i = 6;
+ CtdlSetConfigInt("c_initax", i);
+ break;
+ case 7:
+ CtdlSetConfigInt("c_regiscall", confbool(buf));
+ break;
+ case 8:
+ CtdlSetConfigInt("c_twitdetect", confbool(buf));
+ break;
+ case 9:
+ CtdlSetConfigStr("c_twitroom", buf);
+ break;
+ case 10:
+ CtdlSetConfigStr("c_moreprompt", buf);
+ break;
+ case 11:
+ CtdlSetConfigInt("c_restrict", confbool(buf));
+ break;
+ case 12:
+ CtdlSetConfigStr("c_site_location", buf);
+ break;
+ case 13:
+ CtdlSetConfigStr("c_sysadm", buf);
+ break;
+ case 14:
+ i = atoi(buf);
+ if (i < 0) i = 0;
+ CtdlSetConfigInt("c_maxsessions", i);
+ break;
+ case 15:
+ /* placeholder -- field no longer in use */
+ break;
+ case 16:
+ CtdlSetConfigInt("c_userpurge", atoi(buf));
+ break;
+ case 17:
+ CtdlSetConfigInt("c_roompurge", atoi(buf));
+ break;
+ case 18:
+ CtdlSetConfigStr("c_logpages", buf);
+ break;
+ case 19:
+ i = atoi(buf);
+ if (i < 1) i = 1;
+ if (i > 6) i = 6;
+ CtdlSetConfigInt("c_createax", i);
+ break;
+ case 20:
+ ii = atol(buf);
+ if (ii >= 8192) {
+ CtdlSetConfigLong("c_maxmsglen", ii);
+ }
+ break;
+ case 21:
+ i = atoi(buf);
+ if (i >= 3) { // minimum value
+ CtdlSetConfigInt("c_min_workers", i);
+ }
+ break;
+ case 22:
+ i = atoi(buf);
+ if (i >= CtdlGetConfigInt("c_min_workers")) { // max must be >= min
+ CtdlSetConfigInt("c_max_workers", i);
+ }
+ break;
+ case 23:
+ CtdlSetConfigInt("c_pop3_port", atoi(buf));
+ break;
+ case 24:
+ CtdlSetConfigInt("c_smtp_port", atoi(buf));
+ break;
+ case 25:
+ CtdlSetConfigInt("c_rfc822_strict_from", atoi(buf));
+ break;
+ case 26:
+ CtdlSetConfigInt("c_aide_zap", confbool(buf));
+ break;
+ case 27:
+ CtdlSetConfigInt("c_imap_port", atoi(buf));
+ break;
+ case 28:
+ CtdlSetConfigLong("c_net_freq", atol(buf));
+ break;
+ case 29:
+ CtdlSetConfigInt("c_disable_newu", confbool(buf));
+ break;
+ case 30:
+ /* niu */
+ break;
+ case 31:
+ i = atoi(buf);
+ if ((i >= 0) && (i <= 23)) {
+ CtdlSetConfigInt("c_purge_hour", i);
+ }
+ break;
+ case 32:
+ CtdlSetConfigStr("c_ldap_host", buf);
+ break;
+ case 33:
+ CtdlSetConfigInt("c_ldap_port", atoi(buf));
+ break;
+ case 34:
+ CtdlSetConfigStr("c_ldap_base_dn", buf);
+ break;
+ case 35:
+ CtdlSetConfigStr("c_ldap_bind_dn", buf);
+ break;
+ case 36:
+ CtdlSetConfigStr("c_ldap_bind_pw", buf);
+ break;
+ case 37:
+ CtdlSetConfigStr("c_ip_addr", buf);
+ break;
+ case 38:
+ CtdlSetConfigInt("c_msa_port", atoi(buf));
+ break;
+ case 39:
+ CtdlSetConfigInt("c_imaps_port", atoi(buf));
+ break;
+ case 40:
+ CtdlSetConfigInt("c_pop3s_port", atoi(buf));
+ break;
+ case 41:
+ CtdlSetConfigInt("c_smtps_port", atoi(buf));
+ break;
+ case 42:
+ CtdlSetConfigInt("c_enable_fulltext", confbool(buf));
+ break;
+ case 43:
+ CtdlSetConfigInt("c_auto_cull", confbool(buf));
+ break;
+ case 44:
+ /* niu */
+ break;
+ case 45:
+ CtdlSetConfigInt("c_allow_spoofing", confbool(buf));
+ break;
+ case 46:
+ CtdlSetConfigInt("c_journal_email", confbool(buf));
+ break;
+ case 47:
+ CtdlSetConfigInt("c_journal_pubmsgs", confbool(buf));
+ break;
+ case 48:
+ CtdlSetConfigStr("c_journal_dest", buf);
+ break;
+ case 49:
+ CtdlSetConfigStr("c_default_cal_zone", buf);
+ break;
+ case 50:
+ CtdlSetConfigInt("c_pftcpdict_port", atoi(buf));
+ break;
+ case 51:
+ /* niu */
+ break;
+ case 52:
+ CtdlSetConfigInt("c_auth_mode", atoi(buf));
+ break;
+ case 53:
+ /* niu */
+ break;
+ case 54:
+ /* niu */
+ break;
+ case 55:
+ /* niu */
+ break;
+ case 56:
+ /* niu */
+ break;
+ case 57:
+ CtdlSetConfigInt("c_rbl_at_greeting", confbool(buf));
+ break;
+ case 58:
+ /* niu */
+ break;
+ case 59:
+ /* niu */
+ break;
+ case 60:
+ CtdlSetConfigStr("c_pager_program", buf);
+ break;
+ case 61:
+ CtdlSetConfigInt("c_imap_keep_from", confbool(buf));
+ break;
+ case 62:
+ CtdlSetConfigInt("c_xmpp_c2s_port", atoi(buf));
+ break;
+ case 63:
+ CtdlSetConfigInt("c_xmpp_s2s_port", atoi(buf));
+ break;
+ case 64:
+ CtdlSetConfigLong("c_pop3_fetch", atol(buf));
+ break;
+ case 65:
+ CtdlSetConfigLong("c_pop3_fastest", atol(buf));
+ break;
+ case 66:
+ CtdlSetConfigInt("c_spam_flag_only", confbool(buf));
+ break;
+ case 67:
+ CtdlSetConfigInt("c_guest_logins", confbool(buf));
+ break;
+ case 68:
+ CtdlSetConfigInt("c_port_number", atoi(buf));
+ break;
+ case 69:
+ /* niu */
+ break;
+ case 70:
+ CtdlSetConfigInt("c_nntp_port", atoi(buf));
+ break;
+ case 71:
+ CtdlSetConfigInt("c_nntps_port", atoi(buf));
+ break;
+ }
+ ++a;
+ }
+ snprintf(buf, sizeof buf,
+ "The global system configuration has been edited by %s.\n",
+ (CC->logged_in ? CC->curr_user : "an administrator")
+ );
+ CtdlAideMessage(buf, "Citadel Configuration Manager Message");
+
+ if (!IsEmptyStr(CtdlGetConfigStr("c_logpages")))
+ CtdlCreateRoom(CtdlGetConfigStr("c_logpages"), 3, "", 0, 1, 1, VIEW_BBS);
+
+ /* If full text indexing has been disabled, invalidate the
+ * index so it doesn't try to use it later.
+ */
+ if (CtdlGetConfigInt("c_enable_fulltext") == 0) {
+ CtdlSetConfigInt("MM_fulltext_wordbreaker", 0);
+ }
+ }
+
+ // CONF GETSYS - retrieve arbitrary system configuration stanzas stored in the message base
+ else if (!strcasecmp(cmd, "GETSYS")) {
+ extract_token(confname, argbuf, 1, '|', sizeof confname);
+ confptr = CtdlGetSysConfig(confname);
+ if (confptr != NULL) {
+ long len;
+
+ len = strlen(confptr);
+ cprintf("%d %s\n", LISTING_FOLLOWS, confname);
+ client_write(confptr, len);
+ if ((len > 0) && (confptr[len - 1] != 10))
+ client_write("\n", 1);
+ cprintf("000\n");
+ free(confptr);
+ } else {
+ cprintf("%d No such configuration.\n",
+ ERROR + ILLEGAL_VALUE);
+ }
+ }
+
+ // CONF PUTSYS - store arbitrary system configuration stanzas in the message base
+ else if (!strcasecmp(cmd, "PUTSYS")) {
+ extract_token(confname, argbuf, 1, '|', sizeof confname);
+ unbuffer_output();
+ cprintf("%d %s\n", SEND_LISTING, confname);
+ confptr = CtdlReadMessageBody(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
+ CtdlPutSysConfig(confname, confptr);
+ free(confptr);
+ }
+
+ // CONF GETVAL - retrieve configuration variables from the database
+ // CONF LOADVAL - same thing but can handle variables bigger than 1 KB (deprecated and probably safe to remove)
+ else if ( (!strcasecmp(cmd, "GETVAL")) || (!strcasecmp(cmd, "LOADVAL")) ) {
+ extract_token(confname, argbuf, 1, '|', sizeof confname);
+ char *v = CtdlGetConfigStr(confname);
+ if ( (v) && (!strcasecmp(cmd, "GETVAL")) ) {
+ cprintf("%d %s|\n", CIT_OK, v);
+ }
+ else if ( (v) && (!strcasecmp(cmd, "LOADVAL")) ) {
+ cprintf("%d %d\n", BINARY_FOLLOWS, (int)strlen(v));
+ client_write(v, strlen(v));
+ }
+ else {
+ cprintf("%d |\n", ERROR);
+ }
+ }
+
+ // CONF PUTVAL - store configuration variables in the database
+ else if (!strcasecmp(cmd, "PUTVAL")) {
+ if (num_tokens(argbuf, '|') < 3) {
+ cprintf("%d name and value required\n", ERROR);
+ }
+ else {
+ extract_token(confname, argbuf, 1, '|', sizeof confname);
+ extract_token(buf, argbuf, 2, '|', sizeof buf);
+ CtdlSetConfigStr(confname, buf);
+ cprintf("%d setting '%s' to '%s'\n", CIT_OK, confname, buf);
+ }
+ }
+
+ // CONF STOREVAL - store configuration variables in the database bigger than 1 KB (deprecated and probably safe to remove)
+ else if (!strcasecmp(cmd, "STOREVAL")) {
+ if (num_tokens(argbuf, '|') < 3) {
+ cprintf("%d name and length required\n", ERROR);
+ }
+ else {
+ extract_token(confname, argbuf, 1, '|', sizeof confname);
+ int bytes = extract_int(argbuf, 2);
+ char *valbuf = malloc(bytes + 1);
+ cprintf("%d %d\n", SEND_BINARY, bytes);
+ client_read(valbuf, bytes);
+ valbuf[bytes+1] = 0;
+ CtdlSetConfigStr(confname, valbuf);
+ free(valbuf);
+ }
+ }
+
+ // CONF LISTVAL - list configuration variables in the database and their values
+ else if (!strcasecmp(cmd, "LISTVAL")) {
+ struct cdbdata *cdbcfg;
+ int keylen = 0;
+ char *key = NULL;
+ char *value = NULL;
+
+ cprintf("%d all configuration variables\n", LISTING_FOLLOWS);
+ cdb_rewind(CDB_CONFIG);
+ while (cdbcfg = cdb_next_item(CDB_CONFIG), cdbcfg != NULL) {
+ if (cdbcfg->len < 1020) {
+ keylen = strlen(cdbcfg->ptr);
+ key = cdbcfg->ptr;
+ value = cdbcfg->ptr + keylen + 1;
+ cprintf("%s|%s\n", key, value);
+ }
+ cdb_free(cdbcfg);
+ }
+ cprintf("000\n");
+ }
+
+ else {
+ cprintf("%d Illegal option(s) specified.\n", ERROR + ILLEGAL_VALUE);
+ }
+}
+
+
+typedef struct __ConfType {
+ ConstStr Name;
+ long Type;
+}ConfType;
+
+ConfType CfgNames[] = {
+ { {HKEY("localhost") }, 0},
+ { {HKEY("directory") }, 0},
+ { {HKEY("smarthost") }, 2},
+ { {HKEY("fallbackhost") }, 2},
+ { {HKEY("rbl") }, 3},
+ { {HKEY("spamassassin") }, 3},
+ { {HKEY("masqdomain") }, 1},
+ { {HKEY("clamav") }, 3},
+ { {HKEY("notify") }, 3},
+ { {NULL, 0}, 0}
+};
+
+HashList *CfgNameHash = NULL;
+void cmd_gvdn(char *argbuf)
+{
+ const ConfType *pCfg;
+ char *confptr;
+ long min = atol(argbuf);
+ const char *Pos = NULL;
+ const char *PPos = NULL;
+ const char *HKey;
+ long HKLen;
+ StrBuf *Line;
+ StrBuf *Config;
+ StrBuf *Cfg;
+ StrBuf *CfgToken;
+ HashList *List;
+ HashPos *It;
+ void *vptr;
+
+ List = NewHash(1, NULL);
+ Cfg = NewStrBufPlain(CtdlGetConfigStr("c_fqdn"), -1);
+ Put(List, SKEY(Cfg), Cfg, HFreeStrBuf);
+ Cfg = NULL;
+
+ confptr = CtdlGetSysConfig(INTERNETCFG);
+ Config = NewStrBufPlain(confptr, -1);
+ free(confptr);
+
+ Line = NewStrBufPlain(NULL, StrLength(Config));
+ CfgToken = NewStrBufPlain(NULL, StrLength(Config));
+ while (StrBufSipLine(Line, Config, &Pos))
+ {
+ if (Cfg == NULL)
+ Cfg = NewStrBufPlain(NULL, StrLength(Line));
+ PPos = NULL;
+ StrBufExtract_NextToken(Cfg, Line, &PPos, '|');
+ StrBufExtract_NextToken(CfgToken, Line, &PPos, '|');
+ if (GetHash(CfgNameHash, SKEY(CfgToken), &vptr) &&
+ (vptr != NULL))
+ {
+ pCfg = (ConfType *) vptr;
+ if (pCfg->Type <= min)
+ {
+ Put(List, SKEY(Cfg), Cfg, HFreeStrBuf);
+ Cfg = NULL;
+ }
+ }
+ }
+
+ cprintf("%d Valid Domains\n", LISTING_FOLLOWS);
+ It = GetNewHashPos(List, 1);
+ while (GetNextHashPos(List, It, &HKLen, &HKey, &vptr))
+ {
+ cputbuf(vptr);
+ cprintf("\n");
+ }
+ cprintf("000\n");
+
+ DeleteHashPos(&It);
+ DeleteHash(&List);
+ FreeStrBuf(&Cfg);
+ FreeStrBuf(&Line);
+ FreeStrBuf(&CfgToken);
+ FreeStrBuf(&Config);
+}
+
+/*****************************************************************************/
+/* MODULE INITIALIZATION STUFF */
+/*****************************************************************************/
+
+char *ctdl_module_init_control(void) {
+ if (!threading) {
+ int i;
+
+ CfgNameHash = NewHash(1, NULL);
+ for (i = 0; CfgNames[i].Name.Key != NULL; i++)
+ Put(CfgNameHash, CKEY(CfgNames[i].Name), &CfgNames[i], reference_free_handler);
+
+ CtdlRegisterProtoHook(cmd_gvdn, "GVDN", "get valid domain names");
+ CtdlRegisterProtoHook(cmd_conf, "CONF", "get/set system configuration");
+ }
+ /* return our id for the log */
+ return "control";
+}
--- /dev/null
+/*
+ * 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.
+ */
+
+void get_control (void);
+void put_control (void);
+void check_control(void);
+long int get_new_message_number (void);
+long int get_new_user_number (void);
+long int get_new_room_number (void);
+void migrate_legacy_control_record(void);
--- /dev/null
+
+#ifndef CTDL_MODULE_H
+#define CTDL_MODULE_H
+
+#include "sysdep.h"
+
+#ifdef HAVE_GC
+#define GC_THREADS
+#define GC_REDIRECT_TO_LOCAL
+#include <gc/gc_local_alloc.h>
+#else
+#define GC_MALLOC malloc
+#define GC_MALLOC_ATOMIC malloc
+#define GC_FREE free
+#define GC_REALLOC realloc
+#endif
+
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <syslog.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#ifdef HAVE_STRINGS_H
+#include <strings.h>
+#endif
+#include <limits.h>
+
+#include <libcitadel.h>
+
+#include "server.h"
+#include "sysdep_decls.h"
+#include "msgbase.h"
+#include "threads.h"
+#include "citadel_dirs.h"
+#include "context.h"
+
+extern int threading;
+
+
+/*
+ * define macros for module init stuff
+ */
+
+#define CTDL_MODULE_INIT(module_name) char *ctdl_module_##module_name##_init (int threading)
+
+#define CTDL_INIT_CALL(module_name) ctdl_module_##module_name##_init (threading)
+
+#define CTDL_MODULE_UPGRADE(module_name) char *ctdl_module_##module_name##_upgrade (void)
+
+#define CTDL_UPGRADE_CALL(module_name) ctdl_module_##module_name##_upgrade ()
+
+#define CtdlAideMessage(TEXT, SUBJECT) \
+ quickie_message( \
+ "Citadel", \
+ NULL, \
+ NULL, \
+ AIDEROOM, \
+ TEXT, \
+ FMT_CITADEL, \
+ SUBJECT)
+
+/*
+ * Hook functions available to modules.
+ */
+/* Priorities for */
+#define PRIO_QUEUE 500
+#define PRIO_AGGR 1000
+#define PRIO_SEND 1500
+#define PRIO_CLEANUP 2000
+/* Priorities for EVT_HOUSE */
+#define PRIO_HOUSE 3000
+/* Priorities for EVT_LOGIN */
+#define PRIO_CREATE 10000
+/* Priorities for EVT_LOGOUT */
+#define PRIO_LOGOUT 15000
+/* Priorities for EVT_LOGIN */
+#define PRIO_LOGIN 20000
+/* Priorities for EVT_START */
+#define PRIO_START 25000
+/* Priorities for EVT_STOP */
+#define PRIO_STOP 30000
+/* Priorities for EVT_ASYNC */
+#define PRIO_ASYNC 35000
+/* Priorities for EVT_SHUTDOWN */
+#define PRIO_SHUTDOWN 40000
+/* Priorities for EVT_UNSTEALTH */
+#define PRIO_UNSTEALTH 45000
+/* Priorities for EVT_STEALTH */
+#define PRIO_STEALTH 50000
+
+
+void CtdlRegisterSessionHook(void (*fcn_ptr)(void), int EventType, int Priority);
+void CtdlUnregisterSessionHook(void (*fcn_ptr)(void), int EventType);
+void CtdlShutdownServiceHooks(void);
+
+void CtdlRegisterUserHook(void (*fcn_ptr)(struct ctdluser *), int EventType);
+void CtdlUnregisterUserHook(void (*fcn_ptr)(struct ctdluser *), int EventType);
+
+void CtdlRegisterXmsgHook(int (*fcn_ptr)(char *, char *, char *, char *), int order);
+void CtdlUnregisterXmsgHook(int (*fcn_ptr)(char *, char *, char *, char *), int order);
+
+void CtdlRegisterMessageHook(int (*handler)(struct CtdlMessage *, struct recptypes *), int EventType);
+void CtdlUnregisterMessageHook(int (*handler)(struct CtdlMessage *, struct recptypes *), int EventType);
+
+void CtdlRegisterRoomHook(int (*fcn_ptr)(struct ctdlroom *) );
+void CtdlUnregisterRoomHook(int (*fnc_ptr)(struct ctdlroom *) );
+
+void CtdlRegisterDeleteHook(void (*handler)(char *, long) );
+void CtdlUnregisterDeleteHook(void (*handler)(char *, long) );
+
+void CtdlRegisterCleanupHook(void (*fcn_ptr)(void));
+void CtdlUnregisterCleanupHook(void (*fcn_ptr)(void));
+
+void CtdlRegisterEVCleanupHook(void (*fcn_ptr)(void));
+void CtdlUnregisterEVCleanupHook(void (*fcn_ptr)(void));
+
+void CtdlRegisterProtoHook(void (*handler)(char *), char *cmd, char *desc);
+
+void CtdlRegisterServiceHook(int tcp_port,
+ char *sockpath,
+ void (*h_greeting_function) (void),
+ void (*h_command_function) (void),
+ void (*h_async_function) (void),
+ const char *ServiceName
+);
+void CtdlUnregisterServiceHook(int tcp_port,
+ char *sockpath,
+ void (*h_greeting_function) (void),
+ void (*h_command_function) (void),
+ void (*h_async_function) (void)
+);
+
+void CtdlRegisterFixedOutputHook(char *content_type, void (*output_function) (char *supplied_data, int len));
+void CtdlUnRegisterFixedOutputHook(char *content_type);
+void CtdlRegisterMaintenanceThread(char *name, void *(*thread_proc) (void *arg));
+void CtdlRegisterSearchFuncHook(void (*fcn_ptr)(int *, long **, const char *), char *name);
+
+/*
+ * if you say a) (which may take a while)
+ * don't forget to say b)
+ */
+void CtdlDisableHouseKeeping(void);
+void CtdlEnableHouseKeeping(void);
+
+/* TODODRW: This needs to be changed into a hook type interface
+ * for now we have this horrible hack
+ */
+void CtdlModuleStartCryptoMsgs(char *ok_response, char *nosup_response, char *error_response);
+
+/* return the current context list as an array and do it in a safe manner
+ * The returned data is a copy so only reading is useful
+ * The number of contexts is returned in count.
+ * Beware, this does not copy any of the data pointed to by the context.
+ * This means that you can not rely on things like the redirect buffer being valid.
+ * You must free the returned pointer when done.
+ */
+struct CitContext *CtdlGetContextArray (int *count);
+void CtdlFillSystemContext(struct CitContext *context, char *name);
+int CtdlTrySingleUser(void);
+void CtdlEndSingleUser(void);
+int CtdlWantSingleUser(void);
+int CtdlIsSingleUser(void);
+
+
+int CtdlIsUserLoggedIn (char *user_name);
+int CtdlIsUserLoggedInByNum (long usernum);
+void CtdlBumpNewMailCounter(long which_user);
+
+
+/*
+ * CtdlGetCurrentMessageNumber() - Obtain the current highest message number in the system
+ * This provides a quick way to initialize a variable that might be used to indicate
+ * messages that should not be processed. For example, a new inbox script will use this
+ * to record determine that messages older than this should not be processed.
+ * This function is defined in control.c
+ */
+long CtdlGetCurrentMessageNumber(void);
+
+
+
+/*
+ * Expose various room operation functions from room_ops.c to the modules API
+ */
+
+unsigned CtdlCreateRoom(char *new_room_name,
+ int new_room_type,
+ char *new_room_pass,
+ int new_room_floor,
+ int really_create,
+ int avoid_access,
+ int new_room_view);
+int CtdlGetRoom(struct ctdlroom *qrbuf, const char *room_name);
+int CtdlGetRoomLock(struct ctdlroom *qrbuf, const char *room_name);
+int CtdlDoIHavePermissionToDeleteThisRoom(struct ctdlroom *qr);
+void CtdlRoomAccess(struct ctdlroom *roombuf, struct ctdluser *userbuf, int *result, int *view);
+void CtdlPutRoomLock(struct ctdlroom *qrbuf);
+typedef void (*ForEachRoomCallBack)(struct ctdlroom *EachRoom, void *out_data);
+void CtdlForEachRoom(ForEachRoomCallBack CB, void *in_data);
+char *LoadRoomNetConfigFile(long roomnum);
+void SaveRoomNetConfigFile(long roomnum, const char *raw_netconfig);
+void CtdlDeleteRoom(struct ctdlroom *qrbuf);
+int CtdlRenameRoom(char *old_name, char *new_name, int new_floor);
+void CtdlUserGoto (char *where, int display_result, int transiently, int *msgs, int *new, long *oldest, long *newest);
+struct floor *CtdlGetCachedFloor(int floor_num);
+void CtdlScheduleRoomForDeletion(struct ctdlroom *qrbuf);
+void CtdlGetFloor (struct floor *flbuf, int floor_num);
+void CtdlPutFloor (struct floor *flbuf, int floor_num);
+void CtdlPutFloorLock(struct floor *flbuf, int floor_num);
+int CtdlGetFloorByName(const char *floor_name);
+int CtdlGetFloorByNameLock(const char *floor_name);
+int CtdlGetAvailableFloor(void);
+int CtdlIsNonEditable(struct ctdlroom *qrbuf);
+void CtdlPutRoom(struct ctdlroom *);
+
+/*
+ * Possible return values for CtdlRenameRoom()
+ */
+enum {
+ crr_ok, /* success */
+ crr_room_not_found, /* room not found */
+ crr_already_exists, /* new name already exists */
+ crr_noneditable, /* cannot edit this room */
+ crr_invalid_floor, /* target floor does not exist */
+ crr_access_denied /* not allowed to edit this room */
+};
+
+
+
+/*
+ * API declarations from citserver.h
+ */
+int CtdlAccessCheck(int);
+/* 'required access level' values which may be passed to CtdlAccessCheck()
+ */
+enum {
+ ac_none,
+ ac_logged_in_or_guest,
+ ac_logged_in,
+ ac_room_aide,
+ ac_aide,
+ ac_internal,
+};
+
+
+
+/*
+ * API declarations from serv_extensions.h
+ */
+void CtdlModuleDoSearch(int *num_msgs, long **search_msgs, const char *search_string, const char *func_name);
+
+#define NODENAME CtdlGetConfigStr("c_nodename")
+#define FQDN CtdlGetConfigStr("c_fqdn")
+#define CTDLUID ctdluid
+#define CREATAIDE CtdlGetConfigInt("c_creataide")
+#define REGISCALL CtdlGetConfigInt("c_regiscall")
+#define TWITDETECT CtdlGetConfigInt("c_twitdetect")
+#define TWITROOM CtdlGetConfigStr("c_twitroom")
+#define RESTRICT_INTERNET CtdlGetConfigInt("c_restrict")
+
+#define CtdlREGISTERRoomCfgType(a, p, uniq, nSegs, s, d) RegisterRoomCfgType(#a, sizeof(#a) - 1, a, p, uniq, nSegs, s, d);
+
+
+
+/*
+ * Expose API calls from user_ops.c
+ */
+int CtdlGetUser(struct ctdluser *usbuf, char *name);
+int CtdlGetUserLen(struct ctdluser *usbuf, const char *name, long len);
+int CtdlGetUserLock(struct ctdluser *usbuf, char *name);
+void CtdlPutUser(struct ctdluser *usbuf);
+void CtdlPutUserLock(struct ctdluser *usbuf);
+int CtdlLockGetCurrentUser(void);
+void CtdlPutCurrentUserLock(void);
+int CtdlGetUserByNumber(struct ctdluser *usbuf, long number);
+void CtdlGetRelationship(visit *vbuf, struct ctdluser *rel_user, struct ctdlroom *rel_room);
+void CtdlSetRelationship(visit *newvisit, struct ctdluser *rel_user, struct ctdlroom *rel_room);
+void CtdlMailboxName(char *buf, size_t n, const struct ctdluser *who, const char *prefix);
+int CtdlLoginExistingUser(const char *username);
+
+/*
+ * Values which may be returned by CtdlLoginExistingUser()
+ */
+enum {
+ pass_ok,
+ pass_already_logged_in,
+ pass_no_user,
+ pass_internal_error,
+ pass_wrong_password
+};
+
+int CtdlTryPassword(const char *password, long len);
+/*
+ * Values which may be returned by CtdlTryPassword()
+ */
+enum {
+ login_ok,
+ login_already_logged_in,
+ login_too_many_users,
+ login_not_found
+};
+
+void CtdlUserLogout(void);
+
+/*
+ * Expose API calls from msgbase.c
+ */
+
+
+/*
+ * Expose API calls from euidindex.c
+ */
+long CtdlLocateMessageByEuid(char *euid, struct ctdlroom *qrbuf);
+
+
+/*
+ * Expose API calls from modules/openid/serv_openid_rp.c in order to turn it into a generic external authentication driver
+ */
+int attach_extauth(struct ctdluser *who, StrBuf *claimed_id);
+
+#endif /* CTDL_MODULE_H */
--- /dev/null
+// This is a data store backend for the Citadel server which uses Berkeley DB.
+//
+// Copyright (c) 1987-2022 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.
+
+/*****************************************************************************
+ Tunable configuration parameters for the Berkeley DB back end
+ *****************************************************************************/
+
+/* Citadel will checkpoint the db at the end of every session, but only if
+ * the specified number of kilobytes has been written, or if the specified
+ * number of minutes has passed, since the last checkpoint.
+ */
+#define MAX_CHECKPOINT_KBYTES 256
+#define MAX_CHECKPOINT_MINUTES 15
+
+/*****************************************************************************/
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <zlib.h>
+#include <db.h>
+
+#if DB_VERSION_MAJOR < 5
+#error Citadel requires Berkeley DB v5.0 or newer. Please upgrade.
+#endif
+
+#include <libcitadel.h>
+#include "ctdl_module.h"
+#include "control.h"
+#include "citserver.h"
+#include "config.h"
+
+static DB *dbp[MAXCDB]; /* One DB handle for each Citadel database */
+static DB_ENV *dbenv; /* The DB environment (global) */
+
+
+void cdb_abort(void) {
+ syslog(LOG_DEBUG, "db: citserver is stopping in order to prevent data loss. uid=%d gid=%d euid=%d egid=%d",
+ getuid(), getgid(), geteuid(), getegid()
+ );
+ exit(CTDLEXIT_DB);
+}
+
+
+/* Verbose logging callback */
+void cdb_verbose_log(const DB_ENV *dbenv, const char *msg, const char *what_is_this) {
+ if (!IsEmptyStr(msg)) {
+ syslog(LOG_DEBUG, "db: %s", msg);
+ }
+}
+
+
+/* Verbose logging callback */
+void cdb_verbose_err(const DB_ENV *dbenv, const char *errpfx, const char *msg) {
+ syslog(LOG_ERR, "db: %s", msg);
+}
+
+
+/* wrapper for txn_abort() that logs/aborts on error */
+static void txabort(DB_TXN *tid) {
+ int ret;
+
+ ret = tid->abort(tid);
+
+ if (ret) {
+ syslog(LOG_ERR, "db: txn_abort: %s", db_strerror(ret));
+ cdb_abort();
+ }
+}
+
+
+/* wrapper for txn_commit() that logs/aborts on error */
+static void txcommit(DB_TXN *tid) {
+ int ret;
+
+ ret = tid->commit(tid, 0);
+
+ if (ret) {
+ syslog(LOG_ERR, "db: txn_commit: %s", db_strerror(ret));
+ cdb_abort();
+ }
+}
+
+
+/* wrapper for txn_begin() that logs/aborts on error */
+static void txbegin(DB_TXN **tid) {
+ int ret;
+
+ ret = dbenv->txn_begin(dbenv, NULL, tid, 0);
+
+ if (ret) {
+ syslog(LOG_ERR, "db: txn_begin: %s", db_strerror(ret));
+ cdb_abort();
+ }
+}
+
+
+/* panic callback */
+static void dbpanic(DB_ENV * env, int errval) {
+ syslog(LOG_ERR, "db: PANIC: %s", db_strerror(errval));
+}
+
+
+static void cclose(DBC * cursor) {
+ int ret;
+
+ if ((ret = cursor->c_close(cursor))) {
+ syslog(LOG_ERR, "db: c_close: %s", db_strerror(ret));
+ cdb_abort();
+ }
+}
+
+
+static void bailIfCursor(DBC ** cursors, const char *msg) {
+ int i;
+
+ for (i = 0; i < MAXCDB; i++)
+ if (cursors[i] != NULL) {
+ syslog(LOG_ERR, "db: cursor still in progress on cdb %02x: %s", i, msg);
+ cdb_abort();
+ }
+}
+
+
+void cdb_check_handles(void) {
+ bailIfCursor(TSD->cursors, "in check_handles");
+
+ if (TSD->tid != NULL) {
+ syslog(LOG_ERR, "db: transaction still in progress!");
+ cdb_abort();
+ }
+}
+
+
+/*
+ * Request a checkpoint of the database. Called once per minute by the thread manager.
+ */
+void cdb_checkpoint(void) {
+ int ret;
+
+ syslog(LOG_DEBUG, "db: -- checkpoint --");
+ ret = dbenv->txn_checkpoint(dbenv, MAX_CHECKPOINT_KBYTES, MAX_CHECKPOINT_MINUTES, 0);
+
+ if (ret != 0) {
+ syslog(LOG_ERR, "db: cdb_checkpoint() txn_checkpoint: %s", db_strerror(ret));
+ cdb_abort();
+ }
+
+ /* After a successful checkpoint, we can cull the unused logs */
+ if (CtdlGetConfigInt("c_auto_cull")) {
+ ret = dbenv->log_set_config(dbenv, DB_LOG_AUTO_REMOVE, 1);
+ }
+ else {
+ ret = dbenv->log_set_config(dbenv, DB_LOG_AUTO_REMOVE, 0);
+ }
+}
+
+
+/*
+ * Open the various databases we'll be using. Any database which
+ * does not exist should be created. Note that we don't need a
+ * critical section here, because there aren't any active threads
+ * manipulating the database yet.
+ */
+void open_databases(void) {
+ int ret;
+ int i;
+ char dbfilename[32];
+ u_int32_t flags = 0;
+ int dbversion_major, dbversion_minor, dbversion_patch;
+
+ syslog(LOG_DEBUG, "db: open_databases() starting");
+ syslog(LOG_DEBUG, "db: Compiled libdb: %s", DB_VERSION_STRING);
+ syslog(LOG_DEBUG, "db: Linked libdb: %s", db_version(&dbversion_major, &dbversion_minor, &dbversion_patch));
+ syslog(LOG_DEBUG, "db: Linked zlib: %s", zlibVersion());
+
+ /*
+ * Silently try to create the database subdirectory. If it's already there, no problem.
+ */
+ if ((mkdir(ctdl_db_dir, 0700) != 0) && (errno != EEXIST)) {
+ syslog(LOG_ERR, "db: unable to create database directory [%s]: %m", ctdl_db_dir);
+ }
+ if (chmod(ctdl_db_dir, 0700) != 0) {
+ syslog(LOG_ERR, "db: unable to set database directory permissions [%s]: %m", ctdl_db_dir);
+ }
+ if (chown(ctdl_db_dir, CTDLUID, (-1)) != 0) {
+ syslog(LOG_ERR, "db: unable to set the owner for [%s]: %m", ctdl_db_dir);
+ }
+ syslog(LOG_DEBUG, "db: Setting up DB environment");
+ // db_env_set_func_yield((int (*)(u_long, u_long))sched_yield);
+ ret = db_env_create(&dbenv, 0);
+ if (ret) {
+ syslog(LOG_ERR, "db: db_env_create: %s", db_strerror(ret));
+ syslog(LOG_ERR, "db: exit code %d", ret);
+ exit(CTDLEXIT_DB);
+ }
+ dbenv->set_errpfx(dbenv, "citserver");
+ dbenv->set_paniccall(dbenv, dbpanic);
+ dbenv->set_errcall(dbenv, cdb_verbose_err);
+ dbenv->set_errpfx(dbenv, "ctdl");
+ dbenv->set_msgcall(dbenv, cdb_verbose_log);
+ dbenv->set_verbose(dbenv, DB_VERB_DEADLOCK, 1);
+ dbenv->set_verbose(dbenv, DB_VERB_RECOVERY, 1);
+
+ /*
+ * We want to specify the shared memory buffer pool cachesize,
+ * but everything else is the default.
+ */
+ ret = dbenv->set_cachesize(dbenv, 0, 64 * 1024, 0);
+ if (ret) {
+ syslog(LOG_ERR, "db: set_cachesize: %s", db_strerror(ret));
+ dbenv->close(dbenv, 0);
+ syslog(LOG_ERR, "db: exit code %d", ret);
+ exit(CTDLEXIT_DB);
+ }
+
+ if ((ret = dbenv->set_lk_detect(dbenv, DB_LOCK_DEFAULT))) {
+ syslog(LOG_ERR, "db: set_lk_detect: %s", db_strerror(ret));
+ dbenv->close(dbenv, 0);
+ syslog(LOG_ERR, "db: exit code %d", ret);
+ exit(CTDLEXIT_DB);
+ }
+
+ flags = DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE | DB_INIT_TXN | DB_INIT_LOCK | DB_THREAD | DB_INIT_LOG;
+ syslog(LOG_DEBUG, "db: dbenv->open(dbenv, %s, %d, 0)", ctdl_db_dir, flags);
+ ret = dbenv->open(dbenv, ctdl_db_dir, flags, 0); // try opening the database cleanly
+ if (ret == DB_RUNRECOVERY) {
+ syslog(LOG_ERR, "db: dbenv->open: %s", db_strerror(ret));
+ syslog(LOG_ERR, "db: attempting recovery...");
+ flags |= DB_RECOVER;
+ ret = dbenv->open(dbenv, ctdl_db_dir, flags, 0); // try recovery
+ }
+ if (ret == DB_RUNRECOVERY) {
+ syslog(LOG_ERR, "db: dbenv->open: %s", db_strerror(ret));
+ syslog(LOG_ERR, "db: attempting catastrophic recovery...");
+ flags &= ~DB_RECOVER;
+ flags |= DB_RECOVER_FATAL;
+ ret = dbenv->open(dbenv, ctdl_db_dir, flags, 0); // try catastrophic recovery
+ }
+ if (ret) {
+ syslog(LOG_ERR, "db: dbenv->open: %s", db_strerror(ret));
+ dbenv->close(dbenv, 0);
+ syslog(LOG_ERR, "db: exit code %d", ret);
+ exit(CTDLEXIT_DB);
+ }
+
+ syslog(LOG_INFO, "db: mounting databases");
+ for (i = 0; i < MAXCDB; ++i) {
+ ret = db_create(&dbp[i], dbenv, 0); // Create a database handle
+ if (ret) {
+ syslog(LOG_ERR, "db: db_create: %s", db_strerror(ret));
+ syslog(LOG_ERR, "db: exit code %d", ret);
+ exit(CTDLEXIT_DB);
+ }
+
+ snprintf(dbfilename, sizeof dbfilename, "cdb.%02x", i); // table names by number
+ ret = dbp[i]->open(dbp[i], NULL, dbfilename, NULL, DB_BTREE, DB_CREATE | DB_AUTO_COMMIT | DB_THREAD, 0600);
+ if (ret) {
+ syslog(LOG_ERR, "db: db_open[%02x]: %s", i, db_strerror(ret));
+ if (ret == ENOMEM) {
+ syslog(LOG_ERR, "db: You may need to tune your database; please read http://www.citadel.org/doku.php?id=faq:troubleshooting:out_of_lock_entries for more information.");
+ }
+ syslog(LOG_ERR, "db: exit code %d", ret);
+ exit(CTDLEXIT_DB);
+ }
+ }
+}
+
+
+/*
+ * Make sure we own all the files, because in a few milliseconds we're going to drop root privs.
+ */
+void cdb_chmod_data(void) {
+ DIR *dp;
+ struct dirent *d;
+ char filename[PATH_MAX];
+
+ dp = opendir(ctdl_db_dir);
+ if (dp != NULL) {
+ while (d = readdir(dp), d != NULL) {
+ if (d->d_name[0] != '.') {
+ snprintf(filename, sizeof filename, "%s/%s", ctdl_db_dir, d->d_name);
+ syslog(LOG_DEBUG, "db: chmod(%s, 0600) returned %d", filename, chmod(filename, 0600));
+ syslog(LOG_DEBUG, "db: chown(%s, CTDLUID, -1) returned %d",
+ filename, chown(filename, CTDLUID, (-1))
+ );
+ }
+ }
+ closedir(dp);
+ }
+}
+
+
+/*
+ * Close all of the db database files we've opened. This can be done
+ * in a loop, since it's just a bunch of closes.
+ */
+void close_databases(void) {
+ int i;
+ int ret;
+
+ 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));
+ }
+
+ syslog(LOG_INFO, "db: flushing the database logs");
+ if ((ret = dbenv->log_flush(dbenv, NULL))) {
+ syslog(LOG_ERR, "db: log_flush: %s", db_strerror(ret));
+ }
+
+ /* close the tables */
+ syslog(LOG_INFO, "db: closing databases");
+ for (i = 0; i < MAXCDB; ++i) {
+ syslog(LOG_INFO, "db: closing database %02x", i);
+ ret = dbp[i]->close(dbp[i], 0);
+ 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
+ // /* print some statistics... */
+ // dbenv->lock_stat_print(dbenv, DB_STAT_ALL);
+ // #endif
+
+ /* Close the handle. */
+ ret = dbenv->close(dbenv, 0);
+ if (ret) {
+ syslog(LOG_ERR, "db: DBENV->close: %s", db_strerror(ret));
+ }
+}
+
+
+/*
+ * Decompress a database item if it was compressed on disk
+ */
+void cdb_decompress_if_necessary(struct cdbdata *cdb) {
+ static int magic = COMPRESS_MAGIC;
+
+ if ((cdb == NULL) || (cdb->ptr == NULL) || (cdb->len < sizeof(magic)) || (memcmp(cdb->ptr, &magic, sizeof(magic)))) {
+ return;
+ }
+
+ /* At this point we know we're looking at a compressed item. */
+
+ struct CtdlCompressHeader zheader;
+ char *uncompressed_data;
+ char *compressed_data;
+ uLongf destLen, sourceLen;
+ size_t cplen;
+
+ memset(&zheader, 0, sizeof(struct CtdlCompressHeader));
+ cplen = sizeof(struct CtdlCompressHeader);
+ if (sizeof(struct CtdlCompressHeader) > cdb->len) {
+ cplen = cdb->len;
+ }
+ memcpy(&zheader, cdb->ptr, cplen);
+
+ compressed_data = cdb->ptr;
+ compressed_data += sizeof(struct CtdlCompressHeader);
+
+ sourceLen = (uLongf) zheader.compressed_len;
+ destLen = (uLongf) zheader.uncompressed_len;
+ uncompressed_data = malloc(zheader.uncompressed_len);
+
+ if (uncompress((Bytef *) uncompressed_data,
+ (uLongf *) & destLen, (const Bytef *) compressed_data, (uLong) sourceLen) != Z_OK) {
+ syslog(LOG_ERR, "db: uncompress() error");
+ cdb_abort();
+ }
+
+ free(cdb->ptr);
+ cdb->len = (size_t) destLen;
+ cdb->ptr = uncompressed_data;
+}
+
+
+/*
+ * Store a piece of data. Returns 0 if the operation was successful. If a
+ * key already exists it should be overwritten.
+ */
+int cdb_store(int cdb, const void *ckey, int ckeylen, void *cdata, int cdatalen) {
+
+ DBT dkey, ddata;
+ DB_TXN *tid = NULL;
+ int ret = 0;
+ struct CtdlCompressHeader zheader;
+ char *compressed_data = NULL;
+ int compressing = 0;
+ size_t buffer_len = 0;
+ uLongf destLen = 0;
+
+ memset(&dkey, 0, sizeof(DBT));
+ memset(&ddata, 0, sizeof(DBT));
+ dkey.size = ckeylen;
+ dkey.data = (void *) ckey;
+ ddata.size = cdatalen;
+ ddata.data = cdata;
+
+ /* Only compress Visit and UseTable records. Everything else is uncompressed. */
+ if ((cdb == CDB_VISIT) || (cdb == CDB_USETABLE)) {
+ compressing = 1;
+ zheader.magic = COMPRESS_MAGIC;
+ zheader.uncompressed_len = cdatalen;
+ buffer_len = ((cdatalen * 101) / 100) + 100 + sizeof(struct CtdlCompressHeader);
+ destLen = (uLongf) buffer_len;
+ compressed_data = malloc(buffer_len);
+ if (compress2((Bytef *) (compressed_data + sizeof(struct CtdlCompressHeader)),
+ &destLen, (Bytef *) cdata, (uLongf) cdatalen, 1) != Z_OK) {
+ syslog(LOG_ERR, "db: compress2() error");
+ cdb_abort();
+ }
+ zheader.compressed_len = (size_t) destLen;
+ memcpy(compressed_data, &zheader, sizeof(struct CtdlCompressHeader));
+ ddata.size = (size_t) (sizeof(struct CtdlCompressHeader) + zheader.compressed_len);
+ ddata.data = compressed_data;
+ }
+
+ if (TSD->tid != NULL) {
+ ret = dbp[cdb]->put(dbp[cdb], // db
+ TSD->tid, // transaction ID
+ &dkey, // key
+ &ddata, // data
+ 0 // flags
+ );
+ if (ret) {
+ syslog(LOG_ERR, "db: cdb_store(%d): %s", cdb, db_strerror(ret));
+ cdb_abort();
+ }
+ if (compressing) {
+ free(compressed_data);
+ }
+ return ret;
+ } else {
+ bailIfCursor(TSD->cursors, "attempt to write during r/o cursor");
+
+ retry:
+ txbegin(&tid);
+
+ if ((ret = dbp[cdb]->put(dbp[cdb], // db
+ tid, // transaction ID
+ &dkey, // key
+ &ddata, // data
+ 0))) { // flags
+ if (ret == DB_LOCK_DEADLOCK) {
+ txabort(tid);
+ goto retry;
+ } else {
+ syslog(LOG_ERR, "db: cdb_store(%d): %s", cdb, db_strerror(ret));
+ cdb_abort();
+ }
+ } else {
+ txcommit(tid);
+ if (compressing) {
+ free(compressed_data);
+ }
+ return ret;
+ }
+ }
+ return ret;
+}
+
+
+/*
+ * Delete a piece of data. Returns 0 if the operation was successful.
+ */
+int cdb_delete(int cdb, void *key, int keylen) {
+ DBT dkey;
+ DB_TXN *tid;
+ int ret;
+
+ memset(&dkey, 0, sizeof dkey);
+ dkey.size = keylen;
+ dkey.data = key;
+
+ if (TSD->tid != NULL) {
+ ret = dbp[cdb]->del(dbp[cdb], TSD->tid, &dkey, 0);
+ if (ret) {
+ syslog(LOG_ERR, "db: cdb_delete(%d): %s", cdb, db_strerror(ret));
+ if (ret != DB_NOTFOUND) {
+ cdb_abort();
+ }
+ }
+ } else {
+ bailIfCursor(TSD->cursors, "attempt to delete during r/o cursor");
+
+ retry:
+ txbegin(&tid);
+
+ if ((ret = dbp[cdb]->del(dbp[cdb], tid, &dkey, 0)) && ret != DB_NOTFOUND) {
+ if (ret == DB_LOCK_DEADLOCK) {
+ txabort(tid);
+ goto retry;
+ } else {
+ syslog(LOG_ERR, "db: cdb_delete(%d): %s", cdb, db_strerror(ret));
+ cdb_abort();
+ }
+ } else {
+ txcommit(tid);
+ }
+ }
+ return ret;
+}
+
+
+static DBC *localcursor(int cdb) {
+ int ret;
+ DBC *curs;
+
+ if (TSD->cursors[cdb] == NULL) {
+ ret = dbp[cdb]->cursor(dbp[cdb], TSD->tid, &curs, 0);
+ }
+ else {
+ ret = TSD->cursors[cdb]->c_dup(TSD->cursors[cdb], &curs, DB_POSITION);
+ }
+
+ if (ret) {
+ syslog(LOG_ERR, "db: localcursor: %s", db_strerror(ret));
+ cdb_abort();
+ }
+
+ return curs;
+}
+
+
+/*
+ * Fetch a piece of data. If not found, returns NULL. Otherwise, it returns
+ * a struct cdbdata which it is the caller's responsibility to free later on
+ * using the cdb_free() routine.
+ */
+struct cdbdata *cdb_fetch(int cdb, const void *key, int keylen) {
+
+ if (keylen == 0) { // key length zero is impossible
+ return(NULL);
+ }
+
+ struct cdbdata *tempcdb;
+ DBT dkey, dret;
+ int ret;
+
+ memset(&dkey, 0, sizeof(DBT));
+ dkey.size = keylen;
+ dkey.data = (void *) key;
+
+ if (TSD->tid != NULL) {
+ memset(&dret, 0, sizeof(DBT));
+ dret.flags = DB_DBT_MALLOC;
+ ret = dbp[cdb]->get(dbp[cdb], TSD->tid, &dkey, &dret, 0);
+ }
+ else {
+ DBC *curs;
+
+ do {
+ memset(&dret, 0, sizeof(DBT));
+ dret.flags = DB_DBT_MALLOC;
+ curs = localcursor(cdb);
+ ret = curs->c_get(curs, &dkey, &dret, DB_SET);
+ cclose(curs);
+ } while (ret == DB_LOCK_DEADLOCK);
+ }
+
+ if ((ret != 0) && (ret != DB_NOTFOUND)) {
+ syslog(LOG_ERR, "db: cdb_fetch(%d): %s", cdb, db_strerror(ret));
+ cdb_abort();
+ }
+
+ if (ret != 0) {
+ return NULL;
+ }
+
+ tempcdb = (struct cdbdata *) malloc(sizeof(struct cdbdata));
+ if (tempcdb == NULL) {
+ syslog(LOG_ERR, "db: cdb_fetch() cannot allocate memory for tempcdb: %m");
+ cdb_abort();
+ return NULL; /* make it easier for static analysis... */
+ }
+ else {
+ tempcdb->len = dret.size;
+ tempcdb->ptr = dret.data;
+ cdb_decompress_if_necessary(tempcdb);
+ return (tempcdb);
+ }
+}
+
+
+/*
+ * Free a cdbdata item.
+ *
+ * Note that we only free the 'ptr' portion if it is not NULL. This allows
+ * other code to assume ownership of that memory simply by storing the
+ * pointer elsewhere and then setting 'ptr' to NULL. cdb_free() will then
+ * avoid freeing it.
+ */
+void cdb_free(struct cdbdata *cdb) {
+ if (cdb->ptr) {
+ free(cdb->ptr);
+ }
+ free(cdb);
+}
+
+
+void cdb_close_cursor(int cdb) {
+ if (TSD->cursors[cdb] != NULL) {
+ cclose(TSD->cursors[cdb]);
+ }
+
+ TSD->cursors[cdb] = NULL;
+}
+
+
+/*
+ * Prepare for a sequential search of an entire database.
+ * (There is guaranteed to be no more than one traversal in
+ * progress per thread at any given time.)
+ */
+void cdb_rewind(int cdb) {
+ int ret = 0;
+
+ if (TSD->cursors[cdb] != NULL) {
+ syslog(LOG_ERR, "db: cdb_rewind: must close cursor on database %d before reopening", cdb);
+ cdb_abort();
+ /* cclose(TSD->cursors[cdb]); */
+ }
+
+ /*
+ * Now initialize the cursor
+ */
+ ret = dbp[cdb]->cursor(dbp[cdb], TSD->tid, &TSD->cursors[cdb], 0);
+ if (ret) {
+ syslog(LOG_ERR, "db: cdb_rewind: db_cursor: %s", db_strerror(ret));
+ cdb_abort();
+ }
+}
+
+
+/*
+ * Fetch the next item in a sequential search. Returns a pointer to a
+ * cdbdata structure, or NULL if we've hit the end.
+ */
+struct cdbdata *cdb_next_item(int cdb) {
+ DBT key, data;
+ struct cdbdata *cdbret;
+ int ret = 0;
+
+ /* Initialize the key/data pair so the flags aren't set. */
+ memset(&key, 0, sizeof(key));
+ memset(&data, 0, sizeof(data));
+ data.flags = DB_DBT_MALLOC;
+
+ ret = TSD->cursors[cdb]->c_get(TSD->cursors[cdb], &key, &data, DB_NEXT);
+
+ if (ret) {
+ if (ret != DB_NOTFOUND) {
+ syslog(LOG_ERR, "db: cdb_next_item(%d): %s", cdb, db_strerror(ret));
+ cdb_abort();
+ }
+ cdb_close_cursor(cdb);
+ return NULL; /* presumably, end of file */
+ }
+
+ cdbret = (struct cdbdata *) malloc(sizeof(struct cdbdata));
+ cdbret->len = data.size;
+ cdbret->ptr = data.data;
+ cdb_decompress_if_necessary(cdbret);
+
+ return (cdbret);
+}
+
+
+/*
+ * Transaction-based stuff. I'm writing this as I bake cookies...
+ */
+void cdb_begin_transaction(void) {
+ bailIfCursor(TSD->cursors, "can't begin transaction during r/o cursor");
+
+ if (TSD->tid != NULL) {
+ syslog(LOG_ERR, "db: cdb_begin_transaction: ERROR: nested transaction");
+ cdb_abort();
+ }
+
+ txbegin(&TSD->tid);
+}
+
+
+void cdb_end_transaction(void) {
+ int i;
+
+ for (i = 0; i < MAXCDB; i++) {
+ if (TSD->cursors[i] != NULL) {
+ syslog(LOG_WARNING, "db: cdb_end_transaction: WARNING: cursor %d still open at transaction end", i);
+ cclose(TSD->cursors[i]);
+ TSD->cursors[i] = NULL;
+ }
+ }
+
+ if (TSD->tid == NULL) {
+ syslog(LOG_ERR, "db: cdb_end_transaction: ERROR: txcommit(NULL) !!");
+ cdb_abort();
+ }
+ else {
+ txcommit(TSD->tid);
+ }
+
+ TSD->tid = NULL;
+}
+
+
+/*
+ * Truncate (delete every record)
+ */
+void cdb_trunc(int cdb) {
+ /* DB_TXN *tid; */
+ int ret;
+ u_int32_t count;
+
+ if (TSD->tid != NULL) {
+ syslog(LOG_ERR, "db: cdb_trunc must not be called in a transaction.");
+ cdb_abort();
+ }
+ else {
+ bailIfCursor(TSD->cursors, "attempt to write during r/o cursor");
+
+ retry:
+ /* txbegin(&tid); */
+
+ if ((ret = dbp[cdb]->truncate(dbp[cdb], /* db */
+ NULL, /* transaction ID */
+ &count, /* #rows deleted */
+ 0))) { /* flags */
+ if (ret == DB_LOCK_DEADLOCK) {
+ /* txabort(tid); */
+ goto retry;
+ } else {
+ syslog(LOG_ERR, "db: cdb_truncate(%d): %s", cdb, db_strerror(ret));
+ if (ret == ENOMEM) {
+ syslog(LOG_ERR, "db: You may need to tune your database; please read http://www.citadel.org/doku.php?id=faq:troubleshooting:out_of_lock_entries for more information.");
+ }
+ exit(CTDLEXIT_DB);
+ }
+ }
+ else {
+ /* txcommit(tid); */
+ }
+ }
+}
+
+
+// compact (defragment) the database, possibly returning space back to the underlying filesystem
+void cdb_compact(void) {
+ int ret;
+ int i;
+
+ syslog(LOG_DEBUG, "db: cdb_compact() started");
+ for (i = 0; i < MAXCDB; i++) {
+ syslog(LOG_DEBUG, "db: compacting database %d", i);
+ ret = dbp[i]->compact(dbp[i], NULL, NULL, NULL, NULL, DB_FREE_SPACE, NULL);
+ if (ret) {
+ syslog(LOG_ERR, "db: compact: %s", db_strerror(ret));
+ }
+ }
+ syslog(LOG_DEBUG, "db: cdb_compact() finished");
+}
+
+
+// Has an item already been seen (is it in the CDB_USETABLE) ?
+// Returns 0 if it hasn't, 1 if it has
+// In either case, writes the item to the database for next time.
+int CheckIfAlreadySeen(StrBuf *guid) {
+ int found = 0;
+ struct UseTable ut;
+ struct cdbdata *cdbut;
+
+ syslog(LOG_DEBUG, "db: CheckIfAlreadySeen(%s)", ChrPtr(guid));
+ cdbut = cdb_fetch(CDB_USETABLE, SKEY(guid));
+ if (cdbut != NULL) {
+ found = 1;
+ cdb_free(cdbut);
+ }
+
+ /* (Re)write the record, to update the timestamp. Zeroing it out makes it compress better. */
+ memset(&ut, 0, sizeof(struct UseTable));
+ memcpy(ut.ut_msgid, SKEY(guid));
+ ut.ut_timestamp = time(NULL);
+ cdb_store(CDB_USETABLE, SKEY(guid), &ut, sizeof(struct UseTable));
+ return (found);
+}
+
+
+char *ctdl_module_init_database() {
+ if (!threading) {
+ // nothing to do here
+ }
+
+ /* return our module id for the log */
+ return "database";
+}
--- /dev/null
+/*
+ * Copyright (c) 1987-2017 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef DATABASE_H
+#define DATABASE_H
+
+
+void open_databases (void);
+void close_databases (void);
+int cdb_store (int cdb, const void *key, int keylen, void *data, int datalen);
+int cdb_delete (int cdb, void *key, int keylen);
+struct cdbdata *cdb_fetch (int cdb, const void *key, int keylen);
+void cdb_free (struct cdbdata *cdb);
+void cdb_rewind (int cdb);
+struct cdbdata *cdb_next_item (int cdb);
+void cdb_close_cursor(int cdb);
+void cdb_begin_transaction(void);
+void cdb_end_transaction(void);
+void cdb_allocate_tsd(void);
+void cdb_free_tsd(void);
+void cdb_check_handles(void);
+void cdb_trunc(int cdb);
+void *checkpoint_thread(void *arg);
+void cdb_chmod_data(void);
+void cdb_checkpoint(void);
+void check_handles(void *arg);
+void cdb_cull_logs(void);
+void cdb_compact(void);
+
+
+/*
+ * Database records beginning with this magic number are assumed to
+ * be compressed. In the event that a database record actually begins with
+ * this magic number, we *must* compress it whether we want to or not,
+ * because the fetch function will try to uncompress it anyway.
+ *
+ * (No need to #ifdef this stuff; it compiles ok even if zlib is not present
+ * and doesn't declare anything so it won't bloat the code)
+ */
+#define COMPRESS_MAGIC 0xc0ffeeee
+
+struct CtdlCompressHeader {
+ int magic;
+ size_t uncompressed_len;
+ size_t compressed_len;
+};
+
+int CheckIfAlreadySeen(StrBuf *guid);
+
+#endif /* DATABASE_H */
+
--- /dev/null
+/*
+ * Copyright (c) 1987-2018 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.
+ */
+
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include <libical/ical.h>
+#include <libcitadel.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "sysdep_decls.h"
+#include "support.h"
+#include "config.h"
+#include "default_timezone.h"
+#include "ctdl_module.h"
+
+
+/*
+ * Figure out which time zone needs to be used for timestamps that are
+ * not UTC and do not have a time zone specified.
+ */
+icaltimezone *get_default_icaltimezone(void) {
+
+ icaltimezone *zone = NULL;
+ char *default_zone_name = CtdlGetConfigStr("c_default_cal_zone");
+
+ if (!zone) {
+ zone = icaltimezone_get_builtin_timezone(default_zone_name);
+ }
+ if (!zone) {
+ syslog(LOG_ERR, "ical: Unable to load '%s' time zone. Defaulting to UTC.", default_zone_name);
+ zone = icaltimezone_get_utc_timezone();
+ }
+ if (!zone) {
+ syslog(LOG_ERR, "ical: unable to load UTC time zone!");
+ }
+
+ return zone;
+}
--- /dev/null
+icaltimezone *get_default_icaltimezone(void);
--- /dev/null
+/*
+ * DNS lookup for SMTP sender
+ *
+ * Copyright (c) 1987-2021 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.
+ */
+
+#include "sysdep.h"
+#include <stdio.h>
+#include <syslog.h>
+#ifdef HAVE_RESOLV_H
+#include <arpa/nameser.h>
+#ifdef HAVE_ARPA_NAMESER_COMPAT_H
+#include <arpa/nameser_compat.h>
+#endif
+#ifdef __FreeBSD__
+#include <netinet/in.h>
+#endif
+#include <resolv.h>
+#endif
+#include <libcitadel.h>
+#include "sysdep_decls.h"
+#include "citadel.h"
+#include "domain.h"
+#include "internet_addressing.h"
+
+
+/*
+ * get_hosts() checks the Internet configuration for various types of
+ * entries and returns them in the same format as getmx() does -- fill the
+ * buffer with a delimited list of hosts and return the number of hosts.
+ *
+ * This is used to fetch MX smarthosts, SpamAssassin hosts, etc.
+ */
+int get_hosts(char *mxbuf, char *rectype) {
+ int config_lines;
+ int i;
+ char buf[256];
+ char host[256], type[256];
+ int total_smarthosts = 0;
+
+ if (inetcfg == NULL) return(0);
+ strcpy(mxbuf, "");
+
+ config_lines = num_tokens(inetcfg, '\n');
+ for (i=0; i<config_lines; ++i) {
+ extract_token(buf, inetcfg, i, '\n', sizeof buf);
+ extract_token(host, buf, 0, '|', sizeof host);
+ extract_token(type, buf, 1, '|', sizeof type);
+
+ if (!strcasecmp(type, rectype)) {
+ strcat(mxbuf, host);
+ strcat(mxbuf, "|");
+ ++total_smarthosts;
+ }
+ }
+
+ return(total_smarthosts);
+}
+
+
+/*
+ * Compare the preference of two MX records. First check by the actual
+ * number listed in the MX record. If they're identical, randomize the
+ * result.
+ */
+int mx_compare_pref(const void *mx1, const void *mx2) {
+ int pref1;
+ int pref2;
+
+ pref1 = ((const struct mx *)mx1)->pref;
+ pref2 = ((const struct mx *)mx2)->pref;
+
+ if (pref1 > pref2) {
+ return(1);
+ }
+ else if (pref1 < pref2) {
+ return(0);
+ }
+ else {
+ return(rand() % 2);
+ }
+}
+
+
+/*
+ * getmx()
+ *
+ * Return one or more MX's for a mail destination.
+ *
+ * Upon success, it fills 'mxbuf' with one or more MX hosts, separated by
+ * vertical bar characters, and returns the number of hosts as its return
+ * value. If no MX's are found, it returns 0.
+ *
+ */
+int getmx(char *mxbuf, char *dest) {
+
+#ifdef HAVE_RESOLV_H
+ union {
+ u_char bytes[1024];
+ HEADER header;
+ } answer;
+#endif
+
+ int ret;
+ unsigned char *startptr, *endptr, *ptr;
+ char expanded_buf[1024];
+ unsigned short pref, type;
+ int n = 0;
+ int qdcount;
+ Array *mxrecords = NULL;
+ struct mx mx;
+
+ // If we're configured to send all mail to a smart-host, then our job here is really easy -- just return those.
+ n = get_hosts(mxbuf, "smarthost");
+ if (n > 0) {
+ return(n);
+ }
+
+ mxrecords = array_new(sizeof(struct mx));
+
+ // No smart-host? Look up the best MX for a site. Make a call to the resolver library.
+ ret = res_query(dest, C_IN, T_MX, (unsigned char *)answer.bytes, sizeof(answer));
+
+ if (ret < 0) {
+ mx.pref = 0;
+ strcpy(mx.host, dest);
+ array_append(mxrecords, &mx);
+ }
+ else {
+ if (ret > sizeof(answer)) { // If we had to truncate, shrink the number to avoid fireworks
+ ret = sizeof(answer);
+ }
+
+ startptr = &answer.bytes[0]; // start and end of buffer
+ endptr = &answer.bytes[ret];
+ ptr = startptr + HFIXEDSZ; // advance past header
+
+ for (qdcount = ntohs(answer.header.qdcount); qdcount--; ptr += ret + QFIXEDSZ) {
+ if ((ret = dn_skipname(ptr, endptr)) < 0) {
+ syslog(LOG_DEBUG, "domain: dn_skipname error");
+ return(0);
+ }
+ }
+
+ while(1) {
+ memset(expanded_buf, 0, sizeof(expanded_buf));
+ ret = dn_expand(startptr, endptr, ptr, expanded_buf, sizeof(expanded_buf));
+ if (ret < 0) break;
+ ptr += ret;
+
+ GETSHORT(type, ptr);
+ ptr += INT16SZ + INT32SZ;
+ GETSHORT(n, ptr);
+
+ if (type != T_MX) {
+ ptr += n;
+ }
+
+ else {
+ GETSHORT(pref, ptr);
+ ret = dn_expand(startptr, endptr, ptr, expanded_buf, sizeof(expanded_buf));
+ ptr += ret;
+
+ mx.pref = pref;
+ strcpy(mx.host, expanded_buf);
+ array_append(mxrecords, &mx);
+ }
+ }
+ }
+
+ // Sort the MX records by preference
+ if (array_len(mxrecords) > 1) {
+ array_sort(mxrecords, mx_compare_pref);
+ }
+
+ int num_mxrecs = array_len(mxrecords);
+ strcpy(mxbuf, "");
+ for (n=0; n<num_mxrecs; ++n) {
+ strcat(mxbuf, ((struct mx *)array_get_element_at(mxrecords, n))->host);
+ strcat(mxbuf, "|");
+ }
+ array_free(mxrecords);
+
+ // Append any fallback smart hosts we have configured.
+ num_mxrecs += get_hosts(&mxbuf[strlen(mxbuf)], "fallbackhost");
+ return(num_mxrecs);
+}
--- /dev/null
+/*
+ * Copyright (c) 1987-2012 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.
+ */
+
+struct mx {
+ int pref;
+ char host[1024];
+};
+
+int getmx(char *mxbuf, char *dest);
+int get_hosts(char *mxbuf, char *rectype);
+
+
+/* HP/UX has old include files...these are from arpa/nameser.h */
+
+#include "typesize.h"
+
+#ifndef HFIXEDSZ
+#define HFIXEDSZ 12 /* I hope! */
+#endif
+#ifndef INT16SZ
+#define INT16SZ sizeof(cit_int16_t)
+#endif
+#ifndef INT32SZ
+#define INT32SZ sizeof(cit_int32_t)
+#endif
--- /dev/null
+// Index messages by EUID per room.
+//
+// Copyright (c) 1987-2022 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.
+
+#include "sysdep.h"
+#include <stdio.h>
+#include <libcitadel.h>
+
+#include "citserver.h"
+#include "room_ops.h"
+
+// The structure of an euidindex record *key* is:
+//
+// |----room_number----|----------EUID-------------|
+// (sizeof long) (actual length of euid)
+//
+//
+// The structure of an euidindex record *value* is:
+//
+// |-----msg_number----|----room_number----|----------EUID-------------|
+// (sizeof long) (sizeof long) (actual length of euid)
+
+// Return nonzero if the supplied room is one which should have
+// an EUID index.
+int DoesThisRoomNeedEuidIndexing(struct ctdlroom *qrbuf) {
+
+ switch(qrbuf->QRdefaultview) {
+ case VIEW_BBS: return(0);
+ case VIEW_MAILBOX: return(0);
+ case VIEW_ADDRESSBOOK: return(1);
+ case VIEW_DRAFTS: return(0);
+ case VIEW_CALENDAR: return(1);
+ case VIEW_TASKS: return(1);
+ case VIEW_NOTES: return(1);
+ case VIEW_WIKI: return(1);
+ case VIEW_BLOG: return(1);
+ }
+
+ return(0);
+}
+
+
+// Locate a message in a given room with a given euid, and return
+// its message number.
+long locate_message_by_euid(char *euid, struct ctdlroom *qrbuf) {
+ return CtdlLocateMessageByEuid (euid, qrbuf);
+}
+
+
+long CtdlLocateMessageByEuid(char *euid, struct ctdlroom *qrbuf) {
+ char *key;
+ int key_len;
+ struct cdbdata *cdb_euid;
+ long msgnum = (-1L);
+
+ syslog(LOG_DEBUG, "euidindex: searching for EUID <%s> in <%s>", euid, qrbuf->QRname);
+
+ key_len = strlen(euid) + sizeof(long) + 1;
+ key = malloc(key_len);
+ memcpy(key, &qrbuf->QRnumber, sizeof(long));
+ strcpy(&key[sizeof(long)], euid);
+
+ cdb_euid = cdb_fetch(CDB_EUIDINDEX, key, key_len);
+ free(key);
+
+ if (cdb_euid == NULL) {
+ msgnum = (-1L);
+ }
+ else {
+ // The first (sizeof long) of the record is what we're looking for. Throw away the rest.
+ memcpy(&msgnum, cdb_euid->ptr, sizeof(long));
+ cdb_free(cdb_euid);
+ }
+ syslog(LOG_DEBUG, "euidindex: returning msgnum = %ld", msgnum);
+ return(msgnum);
+}
+
+
+// Store the euid index for a message, which has presumably just been
+// stored in this room by the caller.
+void index_message_by_euid(char *euid, struct ctdlroom *qrbuf, long msgnum) {
+ char *key;
+ int key_len;
+ char *data;
+ int data_len;
+
+ syslog(LOG_DEBUG, "euidindex: indexing message #%ld <%s> in <%s>", msgnum, euid, qrbuf->QRname);
+
+ key_len = strlen(euid) + sizeof(long) + 1;
+ key = malloc(key_len);
+ memcpy(key, &qrbuf->QRnumber, sizeof(long));
+ strcpy(&key[sizeof(long)], euid);
+
+ data_len = sizeof(long) + key_len;
+ data = malloc(data_len);
+
+ memcpy(data, &msgnum, sizeof(long));
+ memcpy(&data[sizeof(long)], key, key_len);
+
+ cdb_store(CDB_EUIDINDEX, key, key_len, data, data_len);
+ free(key);
+ free(data);
+}
+
+
+// Called by rebuild_euid_index_for_room() to index one message.
+void rebuild_euid_index_for_msg(long msgnum, void *userdata) {
+ struct CtdlMessage *msg = NULL;
+
+ msg = CtdlFetchMessage(msgnum, 0);
+ if (msg == NULL) return;
+ if (!CM_IsEmpty(msg, eExclusiveID)) {
+ index_message_by_euid(msg->cm_fields[eExclusiveID], &CC->room, msgnum);
+ }
+ CM_Free(msg);
+}
+
+
+void rebuild_euid_index_for_room(struct ctdlroom *qrbuf, void *data) {
+ static struct RoomProcList *rplist = NULL;
+ struct RoomProcList *ptr;
+ struct ctdlroom qr;
+
+ // Lazy programming here. Call this function as a CtdlForEachRoom backend
+ // in order to queue up the room names, or call it with a null room
+ // to make it do the processing.
+ if (qrbuf != NULL) {
+ ptr = (struct RoomProcList *)
+ malloc(sizeof (struct RoomProcList));
+ if (ptr == NULL) return;
+
+ safestrncpy(ptr->name, qrbuf->QRname, sizeof ptr->name);
+ ptr->next = rplist;
+ rplist = ptr;
+ return;
+ }
+
+ while (rplist != NULL) {
+ if (CtdlGetRoom(&qr, rplist->name) == 0) {
+ if (DoesThisRoomNeedEuidIndexing(&qr)) {
+ syslog(LOG_DEBUG,
+ "euidindex: rebuilding EUID index for <%s>",
+ rplist->name);
+ CtdlUserGoto(rplist->name, 0, 0, NULL, NULL, NULL, NULL);
+ CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, rebuild_euid_index_for_msg, NULL);
+ }
+ }
+ ptr = rplist;
+ rplist = rplist->next;
+ free(ptr);
+ }
+}
+
+
+// Globally rebuild the EUID indices in every room.
+void rebuild_euid_index(void) {
+ cdb_trunc(CDB_EUIDINDEX); // delete the old indices
+ CtdlForEachRoom(rebuild_euid_index_for_room, NULL); // enumerate room names
+ rebuild_euid_index_for_room(NULL, NULL); // and index them
+}
+
+
+// Server command to fetch a message number given an euid.
+void cmd_euid(char *cmdbuf) {
+ char euid[256];
+ long msgnum;
+ struct cdbdata *cdbfr;
+ long *msglist = NULL;
+ int num_msgs = 0;
+ int i;
+
+ if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+ extract_token(euid, cmdbuf, 0, '|', sizeof euid);
+ msgnum = CtdlLocateMessageByEuid(euid, &CC->room);
+ if (msgnum <= 0L) {
+ cprintf("%d not found\n", ERROR + MESSAGE_NOT_FOUND);
+ return;
+ }
+
+ cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
+ if (cdbfr != NULL) {
+ num_msgs = cdbfr->len / sizeof(long);
+ msglist = (long *) cdbfr->ptr;
+ for (i = 0; i < num_msgs; ++i) {
+ if (msglist[i] == msgnum) {
+ cdb_free(cdbfr);
+ cprintf("%d %ld\n", CIT_OK, msgnum);
+ return;
+ }
+ }
+ cdb_free(cdbfr);
+ }
+
+ cprintf("%d not found\n", ERROR + MESSAGE_NOT_FOUND);
+}
+
+
+char *ctdl_module_init_euidindex(void) {
+ if (!threading) {
+ CtdlRegisterProtoHook(cmd_euid, "EUID", "Perform operations on Extended IDs for messages");
+ }
+ /* return our id for the log */
+ return "euidindex";
+}
--- /dev/null
+/*
+ * Index messages by EUID per room.
+ */
+
+int DoesThisRoomNeedEuidIndexing(struct ctdlroom *qrbuf);
+/* locate_message_by_euid is deprecated. Use CtdlLocateMessageByEuid instead */
+long locate_message_by_euid(char *euid, struct ctdlroom *qrbuf) __attribute__ ((deprecated));
+void index_message_by_euid(char *euid, struct ctdlroom *qrbuf, long msgnum);
+void rebuild_euid_index(void);
--- /dev/null
+/*
+ * Function to generate RFC822-compliant textual time/date stamp
+ */
+
+#include "sysdep.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include "genstamp.h"
+
+static char *months[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+static char *weekdays[] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+/*
+ * Supplied with a unix timestamp, generate an RFC822-compliant textual
+ * time and date stamp.
+ */
+long datestring(char *buf, size_t n, time_t xtime, int which_format) {
+ struct tm t;
+
+ long offset;
+ char offsign;
+
+ localtime_r(&xtime, &t);
+
+ /* Convert "seconds west of GMT" to "hours/minutes offset" */
+#ifdef HAVE_STRUCT_TM_TM_GMTOFF
+ offset = t.tm_gmtoff;
+#else
+ offset = timezone;
+#endif
+ if (offset > 0) {
+ offsign = '+';
+ }
+ else {
+ offset = 0L - offset;
+ offsign = '-';
+ }
+ offset = ( (offset / 3600) * 100 ) + ( offset % 60 );
+
+ switch(which_format) {
+
+ case DATESTRING_RFC822:
+ return snprintf(
+ buf, n,
+ "%s, %02d %s %04d %02d:%02d:%02d %c%04ld",
+ weekdays[t.tm_wday],
+ t.tm_mday,
+ months[t.tm_mon],
+ t.tm_year + 1900,
+ t.tm_hour,
+ t.tm_min,
+ t.tm_sec,
+ offsign, offset
+ );
+ break;
+
+ case DATESTRING_IMAP:
+ return snprintf(
+ buf, n,
+ "%02d-%s-%04d %02d:%02d:%02d %c%04ld",
+ t.tm_mday,
+ months[t.tm_mon],
+ t.tm_year + 1900,
+ t.tm_hour,
+ t.tm_min,
+ t.tm_sec,
+ offsign, offset
+ );
+ break;
+
+ }
+ return 0;
+}
--- /dev/null
+
+long datestring(char *buf, size_t n, time_t xtime, int which_format);
+
+enum {
+ DATESTRING_RFC822,
+ DATESTRING_IMAP
+};
--- /dev/null
+/*
+ * This file contains miscellaneous housekeeping tasks.
+ *
+ * Copyright (c) 1987-2021 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.
+ */
+
+#include <stdio.h>
+#include <libcitadel.h>
+
+#include "ctdl_module.h"
+#include "serv_extensions.h"
+#include "room_ops.h"
+#include "internet_addressing.h"
+#include "config.h"
+#include "journaling.h"
+#include "citadel_ldap.h"
+
+void check_sched_shutdown(void) {
+ if ((ScheduledShutdown == 1) && (ContextList == NULL)) {
+ syslog(LOG_NOTICE, "housekeeping: scheduled shutdown initiating");
+ server_shutting_down = 1;
+ }
+}
+
+
+/*
+ * Check (and fix) floor reference counts. This doesn't need to be done
+ * very often, since the counts should remain correct during normal operation.
+ */
+void check_ref_counts_backend(struct ctdlroom *qrbuf, void *data) {
+
+ int *new_refcounts;
+
+ new_refcounts = (int *) data;
+
+ ++new_refcounts[(int)qrbuf->QRfloor];
+}
+
+
+void check_ref_counts(void) {
+ struct floor flbuf;
+ int a;
+
+ int new_refcounts[MAXFLOORS];
+
+ syslog(LOG_DEBUG, "housekeeping: checking floor reference counts");
+ for (a=0; a<MAXFLOORS; ++a) {
+ new_refcounts[a] = 0;
+ }
+
+ cdb_begin_transaction();
+ CtdlForEachRoom(check_ref_counts_backend, (void *)new_refcounts );
+ cdb_end_transaction();
+
+ for (a=0; a<MAXFLOORS; ++a) {
+ lgetfloor(&flbuf, a);
+ flbuf.f_ref_count = new_refcounts[a];
+ if (new_refcounts[a] > 0) {
+ flbuf.f_flags = flbuf.f_flags | QR_INUSE;
+ }
+ else {
+ flbuf.f_flags = flbuf.f_flags & ~QR_INUSE;
+ }
+ lputfloor(&flbuf, a);
+ syslog(LOG_DEBUG, "housekeeping: floor %d has %d rooms", a, new_refcounts[a]);
+ }
+}
+
+
+/*
+ * Provide hints as to whether we have any memory leaks
+ */
+void keep_an_eye_on_memory_usage(void) {
+ static void *original_brk = NULL;
+ if (!original_brk) original_brk = sbrk(0); // Remember the original program break so we can test for leaks
+ syslog(LOG_DEBUG, "original_brk=%lx, current_brk=%lx, addl=%ld", (long)original_brk, (long)sbrk(0), (long)(sbrk(0)-original_brk)); // FIXME not so noisy please
+}
+
+
+/*
+ * This is the housekeeping loop. Worker threads come through here after
+ * processing client requests but before jumping back into the pool. We
+ * only allow housekeeping to execute once per minute, and we only allow one
+ * instance to run at a time.
+ */
+static int housekeeping_in_progress = 0;
+static int housekeeping_disabled = 0;
+static time_t last_timer = 0L;
+
+void do_housekeeping(void) {
+ int do_housekeeping_now = 0;
+ int do_perminute_housekeeping_now = 0;
+ time_t now;
+
+ if (housekeeping_disabled) {
+ return;
+ }
+
+ /*
+ * We do it this way instead of wrapping the whole loop in an
+ * S_HOUSEKEEPING critical section because it eliminates the need to
+ * potentially have multiple concurrent mutexes in progress.
+ */
+ begin_critical_section(S_HOUSEKEEPING);
+ if (housekeeping_in_progress == 0) {
+ do_housekeeping_now = 1;
+ housekeeping_in_progress = 1;
+ }
+ end_critical_section(S_HOUSEKEEPING);
+
+ now = time(NULL);
+ if ( (do_housekeeping_now == 0) && (!CtdlIsSingleUser()) ) {
+ if ( (now - last_timer) > (time_t)300 ) {
+ syslog(LOG_WARNING,
+ "housekeeping: WARNING: housekeeping loop has not run for %ld minutes. Is something stuck?",
+ ((now - last_timer) / 60)
+ );
+ }
+ return;
+ }
+
+ /*
+ * Ok, at this point we've made the decision to run the housekeeping
+ * loop. Everything below this point is real work.
+ */
+
+ if ( (now - last_timer) > (time_t)60 ) {
+ do_perminute_housekeeping_now = 1;
+ last_timer = time(NULL);
+ }
+
+ /* First, do the "as often as needed" stuff... */
+ JournalRunQueue();
+ PerformSessionHooks(EVT_HOUSE);
+
+ /* Then, do the "once per minute" stuff... */
+ if (do_perminute_housekeeping_now) {
+ cdb_check_handles();
+ PerformSessionHooks(EVT_TIMER); // Run all registered TIMER hooks
+
+ // LDAP sync isn't in a module so we can put it here
+ static time_t last_ldap_sync = 0L;
+ if ( (now - last_ldap_sync) > (time_t)CtdlGetConfigLong("c_ldap_sync_freq") ) {
+ CtdlSynchronizeUsersFromLDAP();
+ last_ldap_sync = time(NULL);
+ }
+
+ keep_an_eye_on_memory_usage();
+ }
+
+ /*
+ * All done.
+ */
+ begin_critical_section(S_HOUSEKEEPING);
+ housekeeping_in_progress = 0;
+ end_critical_section(S_HOUSEKEEPING);
+}
+
+
+void CtdlDisableHouseKeeping(void) {
+ syslog(LOG_INFO, "housekeeping: trying to disable");
+ while ( (!housekeeping_disabled) && (!server_shutting_down) && (!housekeeping_in_progress) ) {
+
+ if (housekeeping_in_progress) {
+ sleep(1);
+ }
+ else {
+ begin_critical_section(S_HOUSEKEEPING);
+ if (!housekeeping_in_progress) {
+ housekeeping_disabled = 1;
+ }
+ end_critical_section(S_HOUSEKEEPING);
+ }
+ }
+ syslog(LOG_INFO, "housekeeping: disabled now");
+}
+
+
+void CtdlEnableHouseKeeping(void) {
+ begin_critical_section(S_HOUSEKEEPING);
+ housekeeping_in_progress = 0;
+ end_critical_section(S_HOUSEKEEPING);
+}
--- /dev/null
+void check_sched_shutdown(void);
+void check_ref_counts(void);
+void do_housekeeping(void);
--- /dev/null
+// This file contains functions which handle the mapping of Internet addresses
+// to users on the Citadel system.
+//
+// Copyright (c) 1987-2022 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.
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "citadel.h"
+#include "server.h"
+#include "sysdep_decls.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "msgbase.h"
+#include "internet_addressing.h"
+#include "user_ops.h"
+#include "room_ops.h"
+#include "parsedate.h"
+#include "database.h"
+#include "ctdl_module.h"
+#ifdef HAVE_ICONV
+#include <iconv.h>
+
+// This is the non-define version in case it is needed for debugging
+#if 0
+inline void FindNextEnd (char *bptr, char *end)
+{
+ /* Find the next ?Q? */
+ end = strchr(bptr + 2, '?');
+ if (end == NULL) return NULL;
+ if (((*(end + 1) == 'B') || (*(end + 1) == 'Q')) &&
+ (*(end + 2) == '?')) {
+ /* skip on to the end of the cluster, the next ?= */
+ end = strstr(end + 3, "?=");
+ }
+ else
+ /* sort of half valid encoding, try to find an end. */
+ end = strstr(bptr, "?=");
+}
+#endif
+
+#define FindNextEnd(bptr, end) { \
+ end = strchr(bptr + 2, '?'); \
+ if (end != NULL) { \
+ if (((*(end + 1) == 'B') || (*(end + 1) == 'Q')) && (*(end + 2) == '?')) { \
+ end = strstr(end + 3, "?="); \
+ } else end = strstr(bptr, "?="); \
+ } \
+}
+
+// Handle subjects with RFC2047 encoding such as:
+// =?koi8-r?B?78bP0s3Mxc7JxSDXz9rE1dvO2c3JINvB0sHNySDP?=
+void utf8ify_rfc822_string(char *buf) {
+ char *start, *end, *next, *nextend, *ptr;
+ char newbuf[1024];
+ char charset[128];
+ char encoding[16];
+ char istr[1024];
+ iconv_t ic = (iconv_t)(-1) ;
+ char *ibuf; // Buffer of characters to be converted
+ char *obuf; // Buffer for converted characters
+ size_t ibuflen; // Length of input buffer
+ size_t obuflen; // Length of output buffer
+ char *isav; // Saved pointer to input buffer
+ char *osav; // Saved pointer to output buffer
+ int passes = 0;
+ int i, len, delta;
+ int illegal_non_rfc2047_encoding = 0;
+
+ // Sometimes, badly formed messages contain strings which were simply
+ // written out directly in some foreign character set instead of
+ // using RFC2047 encoding. This is illegal but we will attempt to
+ // handle it anyway by converting from a user-specified default
+ // charset to UTF-8 if we see any nonprintable characters.
+ len = strlen(buf);
+ for (i=0; i<len; ++i) {
+ if ((buf[i] < 32) || (buf[i] > 126)) {
+ illegal_non_rfc2047_encoding = 1;
+ i = len; // take a shortcut, it won't be more than one.
+ }
+ }
+ if (illegal_non_rfc2047_encoding) {
+ const char *default_header_charset = "iso-8859-1";
+ if ( (strcasecmp(default_header_charset, "UTF-8")) && (strcasecmp(default_header_charset, "us-ascii")) ) {
+ ctdl_iconv_open("UTF-8", default_header_charset, &ic);
+ if (ic != (iconv_t)(-1) ) {
+ ibuf = malloc(1024);
+ isav = ibuf;
+ safestrncpy(ibuf, buf, 1024);
+ ibuflen = strlen(ibuf);
+ obuflen = 1024;
+ obuf = (char *) malloc(obuflen);
+ osav = obuf;
+ iconv(ic, &ibuf, &ibuflen, &obuf, &obuflen);
+ osav[1024-obuflen] = 0;
+ strcpy(buf, osav);
+ free(osav);
+ iconv_close(ic);
+ free(isav);
+ }
+ }
+ }
+
+ // pre evaluate the first pair
+ nextend = end = NULL;
+ len = strlen(buf);
+ start = strstr(buf, "=?");
+ if (start != NULL)
+ FindNextEnd (start, end);
+
+ while ((start != NULL) && (end != NULL)) {
+ next = strstr(end, "=?");
+ if (next != NULL)
+ FindNextEnd(next, nextend);
+ if (nextend == NULL)
+ next = NULL;
+
+ // did we find two partitions
+ if ((next != NULL) && ((next - end) > 2)) {
+ ptr = end + 2;
+ while ((ptr < next) &&
+ (isspace(*ptr) ||
+ (*ptr == '\r') ||
+ (*ptr == '\n') ||
+ (*ptr == '\t')))
+ ptr ++;
+ // did we find a gab just filled with blanks?
+ if (ptr == next) {
+ memmove(end + 2, next, len - (next - start));
+
+ // now terminate the gab at the end
+ delta = (next - end) - 2;
+ len -= delta;
+ buf[len] = '\0';
+
+ // move next to its new location.
+ next -= delta;
+ nextend -= delta;
+ }
+ }
+ // our next-pair is our new first pair now.
+ start = next;
+ end = nextend;
+ }
+
+ // Now we handle foreign character sets properly encoded in RFC2047 format.
+ start = strstr(buf, "=?");
+ FindNextEnd((start != NULL)? start : buf, end);
+ while (start != NULL && end != NULL && end > start) {
+ extract_token(charset, start, 1, '?', sizeof charset);
+ extract_token(encoding, start, 2, '?', sizeof encoding);
+ extract_token(istr, start, 3, '?', sizeof istr);
+
+ ibuf = malloc(1024);
+ isav = ibuf;
+ if (!strcasecmp(encoding, "B")) { // base64
+ ibuflen = CtdlDecodeBase64(ibuf, istr, strlen(istr));
+ }
+ else if (!strcasecmp(encoding, "Q")) { // quoted-printable
+ size_t len;
+ unsigned long pos;
+
+ len = strlen(istr);
+ pos = 0;
+ while (pos < len) {
+ if (istr[pos] == '_') istr[pos] = ' ';
+ pos++;
+ }
+ ibuflen = CtdlDecodeQuotedPrintable(ibuf, istr, len);
+ }
+ else {
+ strcpy(ibuf, istr); // unknown encoding
+ ibuflen = strlen(istr);
+ }
+
+ ctdl_iconv_open("UTF-8", charset, &ic);
+ if (ic != (iconv_t)(-1) ) {
+ obuflen = 1024;
+ obuf = (char *) malloc(obuflen);
+ osav = obuf;
+ iconv(ic, &ibuf, &ibuflen, &obuf, &obuflen);
+ osav[1024-obuflen] = 0;
+
+ end = start;
+ end++;
+ strcpy(start, "");
+ remove_token(end, 0, '?');
+ remove_token(end, 0, '?');
+ remove_token(end, 0, '?');
+ remove_token(end, 0, '?');
+ strcpy(end, &end[1]);
+
+ snprintf(newbuf, sizeof newbuf, "%s%s%s", buf, osav, end);
+ strcpy(buf, newbuf);
+ free(osav);
+ iconv_close(ic);
+ }
+ else {
+ end = start;
+ end++;
+ strcpy(start, "");
+ remove_token(end, 0, '?');
+ remove_token(end, 0, '?');
+ remove_token(end, 0, '?');
+ remove_token(end, 0, '?');
+ strcpy(end, &end[1]);
+
+ snprintf(newbuf, sizeof newbuf, "%s(unreadable)%s", buf, end);
+ strcpy(buf, newbuf);
+ }
+
+ free(isav);
+
+ // Since spammers will go to all sorts of absurd lengths to get their
+ // messages through, there are LOTS of corrupt headers out there.
+ // So, prevent a really badly formed RFC2047 header from throwing
+ // this function into an infinite loop.
+ ++passes;
+ if (passes > 20) return;
+
+ start = strstr(buf, "=?");
+ FindNextEnd((start != NULL)? start : buf, end);
+ }
+
+}
+#else
+inline void utf8ify_rfc822_string(char *a){};
+
+#endif
+
+
+char *inetcfg = NULL;
+
+// Return nonzero if the supplied name is an alias for this host.
+int CtdlHostAlias(char *fqdn) {
+ int config_lines;
+ int i;
+ char buf[256];
+ char host[256], type[256];
+ int found = 0;
+
+ if (fqdn == NULL) return(hostalias_nomatch);
+ if (IsEmptyStr(fqdn)) return(hostalias_nomatch);
+ if (!strcasecmp(fqdn, "localhost")) return(hostalias_localhost);
+ if (!strcasecmp(fqdn, CtdlGetConfigStr("c_fqdn"))) return(hostalias_localhost);
+ if (!strcasecmp(fqdn, CtdlGetConfigStr("c_nodename"))) return(hostalias_localhost);
+ if (inetcfg == NULL) return(hostalias_nomatch);
+
+ config_lines = num_tokens(inetcfg, '\n');
+ for (i=0; i<config_lines; ++i) {
+ extract_token(buf, inetcfg, i, '\n', sizeof buf);
+ extract_token(host, buf, 0, '|', sizeof host);
+ extract_token(type, buf, 1, '|', sizeof type);
+
+ found = 0;
+
+ // Process these in a specific order, in case there are multiple matches.
+ // We want localhost to override masq, for example.
+
+ if ( (!strcasecmp(type, "masqdomain")) && (!strcasecmp(fqdn, host))) {
+ found = hostalias_masq;
+ }
+
+ if ( (!strcasecmp(type, "localhost")) && (!strcasecmp(fqdn, host))) {
+ found = hostalias_localhost;
+ }
+
+ // "directory" used to be a distributed version of "localhost" but they're both the same now
+ if ( (!strcasecmp(type, "directory")) && (!strcasecmp(fqdn, host))) {
+ found = hostalias_localhost;
+ }
+
+ if (found) return(found);
+ }
+ return(hostalias_nomatch);
+}
+
+
+// Determine whether a given Internet address belongs to the current user
+int CtdlIsMe(char *addr, int addr_buf_len) {
+ struct recptypes *recp;
+ int i;
+
+ recp = validate_recipients(addr, NULL, 0);
+ if (recp == NULL) return(0);
+
+ if (recp->num_local == 0) {
+ free_recipients(recp);
+ return(0);
+ }
+
+ for (i=0; i<recp->num_local; ++i) {
+ extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
+ if (!strcasecmp(addr, CC->user.fullname)) {
+ free_recipients(recp);
+ return(1);
+ }
+ }
+
+ free_recipients(recp);
+ return(0);
+}
+
+
+// If the last item in a list of recipients was truncated to a partial address,
+// remove it completely in order to avoid choking library functions.
+void sanitize_truncated_recipient(char *str) {
+ if (!str) return;
+ if (num_tokens(str, ',') < 2) return;
+
+ int len = strlen(str);
+ if (len < 900) return;
+ if (len > 998) str[998] = 0;
+
+ char *cptr = strrchr(str, ',');
+ if (!cptr) return;
+
+ char *lptr = strchr(cptr, '<');
+ char *rptr = strchr(cptr, '>');
+
+ if ( (lptr) && (rptr) && (rptr > lptr) ) return;
+
+ *cptr = 0;
+}
+
+
+// This function is self explanatory.
+// (What can I say, I'm in a weird mood today...)
+void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name) {
+ char *ptr;
+ if (!name) return;
+
+ for (ptr=name; *ptr; ++ptr) {
+ while ( (isspace(*ptr)) && (*(ptr+1)=='@') ) {
+ strcpy(ptr, ptr+1);
+ if (ptr > name) --ptr;
+ }
+ while ( (*ptr=='@') && (*(ptr+1)!=0) && (isspace(*(ptr+1))) ) {
+ strcpy(ptr+1, ptr+2);
+ }
+ }
+}
+
+
+// values that can be returned by expand_aliases()
+enum {
+ EA_ERROR, // Can't send message due to bad address
+ EA_MULTIPLE, // Alias expanded into multiple recipients -- run me again!
+ EA_LOCAL, // Local message, do no network processing
+ EA_INTERNET, // Convert msg and send as Internet mail
+ EA_SKIP // This recipient has been invalidated -- skip it!
+};
+
+
+// Process alias and routing info for email addresses
+int expand_aliases(char *name, char *aliases) {
+ int a;
+ char aaa[SIZ];
+ int at = 0;
+
+ if (aliases) {
+ int num_aliases = num_tokens(aliases, '\n');
+ for (a=0; a<num_aliases; ++a) {
+ extract_token(aaa, aliases, a, '\n', sizeof aaa);
+ char *bar = strchr(aaa, '|');
+ if (bar) {
+ bar[0] = 0;
+ ++bar;
+ striplt(aaa);
+ striplt(bar);
+ if ( (!IsEmptyStr(aaa)) && (!strcasecmp(name, aaa)) ) {
+ syslog(LOG_DEBUG, "internet_addressing: global alias <%s> to <%s>", name, bar);
+ strcpy(name, bar);
+ }
+ }
+ }
+ if (strchr(name, ',')) {
+ return(EA_MULTIPLE);
+ }
+ }
+
+ char original_name[256]; // Now go for the regular aliases
+ safestrncpy(original_name, name, sizeof original_name);
+
+ // should these checks still be here, or maybe move them to split_recps() ?
+ striplt(name);
+ remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
+ stripallbut(name, '<', '>');
+
+ // Hit the email address directory
+ if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
+ strcpy(name, aaa);
+ }
+
+ if (strcasecmp(original_name, name)) {
+ syslog(LOG_INFO, "internet_addressing: directory alias <%s> to <%s>", original_name, name);
+ }
+
+ // Change "user @ xxx" to "user" if xxx is an alias for this host
+ for (a=0; name[a] != '\0'; ++a) {
+ if (name[a] == '@') {
+ if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
+ name[a] = 0;
+ syslog(LOG_DEBUG, "internet_addressing: host is local, recipient is <%s>", name);
+ break;
+ }
+ }
+ }
+
+ // Is this a local or remote recipient?
+ at = haschar(name, '@');
+ if (at == 0) {
+ return(EA_LOCAL); // no @'s = local address
+ }
+ else if (at == 1) {
+ return(EA_INTERNET); // one @ = internet address
+ }
+ else {
+ return(EA_ERROR); // more than one @ = badly formed address
+ }
+}
+
+
+// Return a supplied list of email addresses as an array, removing superfluous information and syntax.
+// If an existing Array is supplied as "append_to" it will do so; otherwise a new Array is allocated.
+Array *split_recps(char *addresses, Array *append_to) {
+
+ if (IsEmptyStr(addresses)) { // nothing supplied, nothing returned
+ return(NULL);
+ }
+
+ // Copy the supplied address list into our own memory space, because we are going to modify it.
+ char *a = strdup(addresses);
+ if (a == NULL) {
+ syslog(LOG_ERR, "internet_addressing: malloc() failed: %m");
+ return(NULL);
+ }
+
+ // Strip out anything in double quotes
+ char *l = NULL;
+ char *r = NULL;
+ do {
+ l = strchr(a, '\"');
+ r = strrchr(a, '\"');
+ if (r > l) {
+ strcpy(l, r+1);
+ }
+ } while (r > l);
+
+ // Transform all qualifying delimiters to commas
+ char *t;
+ for (t=a; t[0]; ++t) {
+ if ((t[0]==';') || (t[0]=='|')) {
+ t[0]=',';
+ }
+ }
+
+ // Tokenize the recipients into an array. No single recipient should be larger than 256 bytes.
+ Array *recipients_array = NULL;
+ if (append_to) {
+ recipients_array = append_to; // Append to an existing array of recipients
+ }
+ else {
+ recipients_array = array_new(256); // This is a new array of recipients
+ }
+
+ int num_addresses = num_tokens(a, ',');
+ int i;
+ for (i=0; i<num_addresses; ++i) {
+ char this_address[256];
+ extract_token(this_address, a, i, ',', sizeof this_address);
+ striplt(this_address); // strip leading and trailing whitespace
+ stripout(this_address, '(', ')'); // remove any portion in parentheses
+ stripallbut(this_address, '<', '>'); // if angle brackets are present, keep only what is inside them
+ if (!IsEmptyStr(this_address)) {
+ array_append(recipients_array, this_address);
+ }
+ }
+
+ free(a); // We don't need this buffer anymore.
+ return(recipients_array); // Return the completed array to the caller.
+}
+
+
+// Validate recipients, count delivery types and errors, and handle aliasing
+//
+// Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
+// were specified, or the number of addresses found invalid.
+//
+// Caller needs to free the result using free_recipients()
+//
+struct recptypes *validate_recipients(char *supplied_recipients, const char *RemoteIdentifier, int Flags) {
+ struct recptypes *ret;
+ char *recipients = NULL;
+ char append[SIZ];
+ long len;
+ int mailtype;
+ int invalid;
+ struct ctdluser tempUS;
+ struct ctdlroom original_room;
+ int err = 0;
+ char errmsg[SIZ];
+ char *org_recp;
+ char this_recp[256];
+
+ ret = (struct recptypes *) malloc(sizeof(struct recptypes)); // Initialize
+ if (ret == NULL) return(NULL);
+ memset(ret, 0, sizeof(struct recptypes)); // set all values to null/zero
+
+ if (supplied_recipients == NULL) {
+ recipients = strdup("");
+ }
+ else {
+ recipients = strdup(supplied_recipients);
+ }
+
+ len = strlen(recipients) + 1024; // allocate memory
+ ret->errormsg = malloc(len);
+ ret->recp_local = malloc(len);
+ ret->recp_internet = malloc(len);
+ ret->recp_room = malloc(len);
+ ret->display_recp = malloc(len);
+ ret->recp_orgroom = malloc(len);
+
+ ret->errormsg[0] = 0;
+ ret->recp_local[0] = 0;
+ ret->recp_internet[0] = 0;
+ ret->recp_room[0] = 0;
+ ret->recp_orgroom[0] = 0;
+ ret->display_recp[0] = 0;
+ ret->recptypes_magic = RECPTYPES_MAGIC;
+
+ Array *recp_array = split_recps(supplied_recipients, NULL);
+
+ char *aliases = CtdlGetSysConfig(GLOBAL_ALIASES); // First hit the Global Alias Table
+
+ int r;
+ for (r=0; (recp_array && r<array_len(recp_array)); ++r) {
+ org_recp = (char *)array_get_element_at(recp_array, r);
+ strncpy(this_recp, org_recp, sizeof this_recp);
+
+ int i;
+ for (i=0; i<3; ++i) { // pass three times through the aliaser
+ mailtype = expand_aliases(this_recp, aliases);
+
+ // If an alias expanded to multiple recipients, strip off those recipients and append them
+ // to the end of the array. This loop will hit those again when it gets there.
+ if (mailtype == EA_MULTIPLE) {
+ recp_array = split_recps(this_recp, recp_array);
+ }
+ }
+
+ // This loop searches for duplicate recipients in the final list and marks them to be skipped.
+ int j;
+ for (j=0; j<r; ++j) {
+ if (!strcasecmp(this_recp, (char *)array_get_element_at(recp_array, j) )) {
+ mailtype = EA_SKIP;
+ }
+ }
+
+ syslog(LOG_DEBUG, "Recipient #%d of type %d is <%s>", r, mailtype, this_recp);
+ invalid = 0;
+ errmsg[0] = 0;
+ switch(mailtype) {
+ case EA_LOCAL: // There are several types of "local" recipients.
+
+ // Old BBS conventions require mail to "sysop" to go somewhere. Send it to the admin room.
+ if (!strcasecmp(this_recp, "sysop")) {
+ ++ret->num_room;
+ strcpy(this_recp, CtdlGetConfigStr("c_aideroom"));
+ if (!IsEmptyStr(ret->recp_room)) {
+ strcat(ret->recp_room, "|");
+ }
+ strcat(ret->recp_room, this_recp);
+ }
+
+ // This handles rooms which can receive posts via email.
+ else if (!strncasecmp(this_recp, "room_", 5)) {
+ original_room = CC->room; // Remember where we parked
+
+ char mail_to_room[ROOMNAMELEN];
+ char *m;
+ strncpy(mail_to_room, &this_recp[5], sizeof mail_to_room);
+ for (m = mail_to_room; *m; ++m) {
+ if (m[0] == '_') m[0]=' ';
+ }
+ if (!CtdlGetRoom(&CC->room, mail_to_room)) { // Find the room they asked for
+
+ err = CtdlDoIHavePermissionToPostInThisRoom( // check for write permissions to room
+ errmsg,
+ sizeof errmsg,
+ Flags,
+ 0 // 0 means "this is not a reply"
+ );
+ if (err) {
+ ++ret->num_error;
+ invalid = 1;
+ }
+ else {
+ ++ret->num_room;
+ if (!IsEmptyStr(ret->recp_room)) {
+ strcat(ret->recp_room, "|");
+ }
+ strcat(ret->recp_room, CC->room.QRname);
+
+ if (!IsEmptyStr(ret->recp_orgroom)) {
+ strcat(ret->recp_orgroom, "|");
+ }
+ strcat(ret->recp_orgroom, this_recp);
+
+ }
+ }
+ else { // no such room exists
+ ++ret->num_error;
+ invalid = 1;
+ }
+
+ // Restore this session's original room location.
+ CC->room = original_room;
+
+ }
+
+ // This handles the most common case, which is mail to a user's inbox.
+ else if (CtdlGetUser(&tempUS, this_recp) == 0) {
+ ++ret->num_local;
+ strcpy(this_recp, tempUS.fullname);
+ if (!IsEmptyStr(ret->recp_local)) {
+ strcat(ret->recp_local, "|");
+ }
+ strcat(ret->recp_local, this_recp);
+ }
+
+ // No match for this recipient
+ else {
+ ++ret->num_error;
+ invalid = 1;
+ }
+ break;
+ case EA_INTERNET:
+ // Yes, you're reading this correctly: if the target domain points back to the local system,
+ // the address is invalid. That's because if the address were valid, we would have
+ // already translated it to a local address by now.
+ if (IsDirectory(this_recp, 0)) {
+ ++ret->num_error;
+ invalid = 1;
+ }
+ else {
+ ++ret->num_internet;
+ if (!IsEmptyStr(ret->recp_internet)) {
+ strcat(ret->recp_internet, "|");
+ }
+ strcat(ret->recp_internet, this_recp);
+ }
+ break;
+ case EA_MULTIPLE:
+ case EA_SKIP:
+ // no action required, anything in this slot has already been processed elsewhere
+ break;
+ case EA_ERROR:
+ ++ret->num_error;
+ invalid = 1;
+ break;
+ }
+ if (invalid) {
+ if (IsEmptyStr(errmsg)) {
+ snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
+ }
+ else {
+ snprintf(append, sizeof append, "%s", errmsg);
+ }
+ if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
+ if (!IsEmptyStr(ret->errormsg)) {
+ strcat(ret->errormsg, "; ");
+ }
+ strcat(ret->errormsg, append);
+ }
+ }
+ else {
+ if (IsEmptyStr(ret->display_recp)) {
+ strcpy(append, this_recp);
+ }
+ else {
+ snprintf(append, sizeof append, ", %s", this_recp);
+ }
+ if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
+ strcat(ret->display_recp, append);
+ }
+ }
+ }
+
+ if (aliases != NULL) { // ok, we're done with the global alias list now
+ free(aliases);
+ }
+
+ if ( (ret->num_local + ret->num_internet + ret->num_room + ret->num_error) == 0) {
+ ret->num_error = (-1);
+ strcpy(ret->errormsg, "No recipients specified.");
+ }
+
+ syslog(LOG_DEBUG, "internet_addressing: validate_recipients() = %d local, %d room, %d SMTP, %d error",
+ ret->num_local, ret->num_room, ret->num_internet, ret->num_error
+ );
+
+ free(recipients);
+ if (recp_array) {
+ array_free(recp_array);
+ }
+
+ return(ret);
+}
+
+
+// Destructor for recptypes
+void free_recipients(struct recptypes *valid) {
+
+ if (valid == NULL) {
+ return;
+ }
+
+ if (valid->recptypes_magic != RECPTYPES_MAGIC) {
+ syslog(LOG_ERR, "internet_addressing: attempt to call free_recipients() on some other data type!");
+ abort();
+ }
+
+ if (valid->errormsg != NULL) free(valid->errormsg);
+ if (valid->recp_local != NULL) free(valid->recp_local);
+ if (valid->recp_internet != NULL) free(valid->recp_internet);
+ if (valid->recp_room != NULL) free(valid->recp_room);
+ if (valid->recp_orgroom != NULL) free(valid->recp_orgroom);
+ if (valid->display_recp != NULL) free(valid->display_recp);
+ if (valid->bounce_to != NULL) free(valid->bounce_to);
+ if (valid->envelope_from != NULL) free(valid->envelope_from);
+ if (valid->sending_room != NULL) free(valid->sending_room);
+ free(valid);
+}
+
+
+char *qp_encode_email_addrs(char *source) {
+ char *user, *node, *name;
+ const char headerStr[] = "=?UTF-8?Q?";
+ char *Encoded;
+ char *EncodedName;
+ char *nPtr;
+ int need_to_encode = 0;
+ long SourceLen;
+ long EncodedMaxLen;
+ long nColons = 0;
+ long *AddrPtr;
+ long *AddrUtf8;
+ long nAddrPtrMax = 50;
+ long nmax;
+ int InQuotes = 0;
+ int i, n;
+
+ if (source == NULL) return source;
+ if (IsEmptyStr(source)) return source;
+ syslog(LOG_DEBUG, "internet_addressing: qp_encode_email_addrs <%s>", source);
+
+ AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
+ AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
+ memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
+ *AddrPtr = 0;
+ i = 0;
+ while (!IsEmptyStr (&source[i])) {
+ if (nColons >= nAddrPtrMax){
+ long *ptr;
+
+ ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
+ memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
+ free (AddrPtr), AddrPtr = ptr;
+
+ ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
+ memset(&ptr[nAddrPtrMax], 0, sizeof (long) * nAddrPtrMax);
+
+ memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
+ free (AddrUtf8), AddrUtf8 = ptr;
+ nAddrPtrMax *= 2;
+ }
+ if (((unsigned char) source[i] < 32) || ((unsigned char) source[i] > 126)) {
+ need_to_encode = 1;
+ AddrUtf8[nColons] = 1;
+ }
+ if (source[i] == '"') {
+ InQuotes = !InQuotes;
+ }
+ if (!InQuotes && source[i] == ',') {
+ AddrPtr[nColons] = i;
+ nColons++;
+ }
+ i++;
+ }
+ if (need_to_encode == 0) {
+ free(AddrPtr);
+ free(AddrUtf8);
+ return source;
+ }
+
+ SourceLen = i;
+ EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
+ Encoded = (char*) malloc (EncodedMaxLen);
+
+ for (i = 0; i < nColons; i++) {
+ source[AddrPtr[i]++] = '\0';
+ }
+ // TODO: if libidn, this might get larger
+ user = malloc(SourceLen + 1);
+ node = malloc(SourceLen + 1);
+ name = malloc(SourceLen + 1);
+
+ nPtr = Encoded;
+ *nPtr = '\0';
+ for (i = 0; i < nColons && nPtr != NULL; i++) {
+ nmax = EncodedMaxLen - (nPtr - Encoded);
+ if (AddrUtf8[i]) {
+ process_rfc822_addr(&source[AddrPtr[i]], user, node, name);
+ // TODO: libIDN here !
+ if (IsEmptyStr(name)) {
+ n = snprintf(nPtr, nmax, (i==0)?"%s@%s" : ",%s@%s", user, node);
+ }
+ else {
+ EncodedName = rfc2047encode(name, strlen(name));
+ n = snprintf(nPtr, nmax, (i==0)?"%s <%s@%s>" : ",%s <%s@%s>", EncodedName, user, node);
+ free(EncodedName);
+ }
+ }
+ else {
+ n = snprintf(nPtr, nmax, (i==0)?"%s" : ",%s", &source[AddrPtr[i]]);
+ }
+ if (n > 0 )
+ nPtr += n;
+ else {
+ char *ptr, *nnPtr;
+ ptr = (char*) malloc(EncodedMaxLen * 2);
+ memcpy(ptr, Encoded, EncodedMaxLen);
+ nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
+ free(Encoded), Encoded = ptr;
+ EncodedMaxLen *= 2;
+ i--; // do it once more with properly lengthened buffer
+ }
+ }
+ for (i = 0; i < nColons; i++)
+ source[--AddrPtr[i]] = ',';
+
+ free(user);
+ free(node);
+ free(name);
+ free(AddrUtf8);
+ free(AddrPtr);
+ return Encoded;
+}
+
+
+// Unfold a multi-line field into a single line, removing multi-whitespaces
+void unfold_rfc822_field(char **field, char **FieldEnd)
+{
+ int quote = 0;
+ char *pField = *field;
+ char *sField;
+ char *pFieldEnd = *FieldEnd;
+
+ while (isspace(*pField))
+ pField++;
+ // remove leading/trailing whitespace
+ ;
+
+ while (isspace(*pFieldEnd))
+ pFieldEnd --;
+
+ *FieldEnd = pFieldEnd;
+ // convert non-space whitespace to spaces, and remove double blanks
+ for (sField = *field = pField;
+ sField < pFieldEnd;
+ pField++, sField++)
+ {
+ if ((*sField=='\r') || (*sField=='\n'))
+ {
+ int offset = 1;
+ while ( ( (*(sField + offset) == '\r') || (*(sField + offset) == '\n' )) && (sField + offset < pFieldEnd) ) {
+ offset ++;
+ }
+ sField += offset;
+ *pField = *sField;
+ }
+ else {
+ if (*sField=='\"') quote = 1 - quote;
+ if (!quote) {
+ if (isspace(*sField)) {
+ *pField = ' ';
+ pField++;
+ sField++;
+
+ while ((sField < pFieldEnd) &&
+ isspace(*sField))
+ sField++;
+ *pField = *sField;
+ }
+ else *pField = *sField;
+ }
+ else *pField = *sField;
+ }
+ }
+ *pField = '\0';
+ *FieldEnd = pField - 1;
+}
+
+
+// Split an RFC822-style address into userid, host, and full name
+//
+// Note: This still handles obsolete address syntaxes such as user%node@node and ...node!user
+// We should probably remove that.
+void process_rfc822_addr(const char *rfc822, char *user, char *node, char *name) {
+ int a;
+
+ strcpy(user, "");
+ strcpy(node, CtdlGetConfigStr("c_fqdn"));
+ strcpy(name, "");
+
+ if (rfc822 == NULL) return;
+
+ // extract full name - first, it's From minus <userid>
+ strcpy(name, rfc822);
+ stripout(name, '<', '>');
+
+ // strip anything to the left of a bang
+ while ((!IsEmptyStr(name)) && (haschar(name, '!') > 0))
+ strcpy(name, &name[1]);
+
+ // and anything to the right of a @ or %
+ for (a = 0; name[a] != '\0'; ++a) {
+ if (name[a] == '@') {
+ name[a] = 0;
+ break;
+ }
+ if (name[a] == '%') {
+ name[a] = 0;
+ break;
+ }
+ }
+
+ // but if there are parentheses, that changes the rules...
+ if ((haschar(rfc822, '(') == 1) && (haschar(rfc822, ')') == 1)) {
+ strcpy(name, rfc822);
+ stripallbut(name, '(', ')');
+ }
+
+ // but if there are a set of quotes, that supersedes everything
+ if (haschar(rfc822, 34) == 2) {
+ strcpy(name, rfc822);
+ while ((!IsEmptyStr(name)) && (name[0] != 34)) {
+ strcpy(&name[0], &name[1]);
+ }
+ strcpy(&name[0], &name[1]);
+ for (a = 0; name[a] != '\0'; ++a)
+ if (name[a] == 34) {
+ name[a] = 0;
+ break;
+ }
+ }
+ // extract user id
+ strcpy(user, rfc822);
+
+ // first get rid of anything in parens
+ stripout(user, '(', ')');
+
+ // if there's a set of angle brackets, strip it down to that
+ if ((haschar(user, '<') == 1) && (haschar(user, '>') == 1)) {
+ stripallbut(user, '<', '>');
+ }
+
+ // strip anything to the left of a bang
+ while ((!IsEmptyStr(user)) && (haschar(user, '!') > 0))
+ strcpy(user, &user[1]);
+
+ // and anything to the right of a @ or %
+ for (a = 0; user[a] != '\0'; ++a) {
+ if (user[a] == '@') {
+ user[a] = 0;
+ break;
+ }
+ if (user[a] == '%') {
+ user[a] = 0;
+ break;
+ }
+ }
+
+
+ // extract node name
+ strcpy(node, rfc822);
+
+ // first get rid of anything in parens
+ stripout(node, '(', ')');
+
+ // if there's a set of angle brackets, strip it down to that
+ if ((haschar(node, '<') == 1) && (haschar(node, '>') == 1)) {
+ stripallbut(node, '<', '>');
+ }
+
+ // If no node specified, tack ours on instead
+ if (
+ (haschar(node, '@')==0)
+ && (haschar(node, '%')==0)
+ && (haschar(node, '!')==0)
+ ) {
+ strcpy(node, CtdlGetConfigStr("c_nodename"));
+ }
+ else {
+
+ // strip anything to the left of a @
+ while ((!IsEmptyStr(node)) && (haschar(node, '@') > 0))
+ strcpy(node, &node[1]);
+
+ // strip anything to the left of a %
+ while ((!IsEmptyStr(node)) && (haschar(node, '%') > 0))
+ strcpy(node, &node[1]);
+
+ // reduce multiple system bang paths to node!user
+ while ((!IsEmptyStr(node)) && (haschar(node, '!') > 1))
+ strcpy(node, &node[1]);
+
+ // now get rid of the user portion of a node!user string
+ for (a = 0; node[a] != '\0'; ++a)
+ if (node[a] == '!') {
+ node[a] = 0;
+ break;
+ }
+ }
+
+ // strip leading and trailing spaces in all strings
+ striplt(user);
+ striplt(node);
+ striplt(name);
+
+ // If we processed a string that had the address in angle brackets
+ // but no name outside the brackets, we now have an empty name. In
+ // this case, use the user portion of the address as the name.
+ if ((IsEmptyStr(name)) && (!IsEmptyStr(user))) {
+ strcpy(name, user);
+ }
+}
+
+
+// convert_field() is a helper function for convert_internet_message().
+// Given start/end positions for an rfc822 field, it converts it to a Citadel
+// field if it wants to, and unfolds it if necessary.
+//
+// Returns 1 if the field was converted and inserted into the Citadel message
+// structure, implying that the source field should be removed from the
+// message text.
+int convert_field(struct CtdlMessage *msg, const char *beg, const char *end) {
+ char *key, *value, *valueend;
+ long len;
+ const char *pos;
+ int i;
+ const char *colonpos = NULL;
+ int processed = 0;
+ char user[1024];
+ char node[1024];
+ char name[1024];
+ char addr[1024];
+ time_t parsed_date;
+ long valuelen;
+
+ for (pos = end; pos >= beg; pos--) {
+ if (*pos == ':') colonpos = pos;
+ }
+
+ if (colonpos == NULL) return(0); /* no colon? not a valid header line */
+
+ len = end - beg;
+ key = malloc(len + 2);
+ memcpy(key, beg, len + 1);
+ key[len] = '\0';
+ valueend = key + len;
+ * ( key + (colonpos - beg) ) = '\0';
+ value = &key[(colonpos - beg) + 1];
+ // printf("Header: [%s]\nValue: [%s]\n", key, value);
+ unfold_rfc822_field(&value, &valueend);
+ valuelen = valueend - value + 1;
+ // printf("UnfoldedValue: [%s]\n", value);
+
+ // Here's the big rfc822-to-citadel loop.
+
+ // Date/time is converted into a unix timestamp. If the conversion
+ // fails, we replace it with the time the message arrived locally.
+ if (!strcasecmp(key, "Date")) {
+ parsed_date = parsedate(value);
+ if (parsed_date < 0L) parsed_date = time(NULL);
+
+ if (CM_IsEmpty(msg, eTimestamp))
+ CM_SetFieldLONG(msg, eTimestamp, parsed_date);
+ processed = 1;
+ }
+
+ else if (!strcasecmp(key, "From")) {
+ process_rfc822_addr(value, user, node, name);
+ syslog(LOG_DEBUG, "internet_addressing: converted to <%s@%s> (%s)", user, node, name);
+ snprintf(addr, sizeof(addr), "%s@%s", user, node);
+ if (CM_IsEmpty(msg, eAuthor) && !IsEmptyStr(name)) {
+ CM_SetField(msg, eAuthor, name, -1);
+ }
+ if (CM_IsEmpty(msg, erFc822Addr) && !IsEmptyStr(addr)) {
+ CM_SetField(msg, erFc822Addr, addr, -1);
+ }
+ processed = 1;
+ }
+
+ else if (!strcasecmp(key, "Subject")) {
+ if (CM_IsEmpty(msg, eMsgSubject))
+ CM_SetField(msg, eMsgSubject, value, valuelen);
+ processed = 1;
+ }
+
+ else if (!strcasecmp(key, "List-ID")) {
+ if (CM_IsEmpty(msg, eListID))
+ CM_SetField(msg, eListID, value, valuelen);
+ processed = 1;
+ }
+
+ else if (!strcasecmp(key, "To")) {
+ if (CM_IsEmpty(msg, eRecipient))
+ CM_SetField(msg, eRecipient, value, valuelen);
+ processed = 1;
+ }
+
+ else if (!strcasecmp(key, "CC")) {
+ if (CM_IsEmpty(msg, eCarbonCopY))
+ CM_SetField(msg, eCarbonCopY, value, valuelen);
+ processed = 1;
+ }
+
+ else if (!strcasecmp(key, "Message-ID")) {
+ if (!CM_IsEmpty(msg, emessageId)) {
+ syslog(LOG_WARNING, "internet_addressing: duplicate message id");
+ }
+ else {
+ char *pValue;
+ long pValueLen;
+
+ pValue = value;
+ pValueLen = valuelen;
+ // Strip angle brackets
+ while (haschar(pValue, '<') > 0) {
+ pValue ++;
+ pValueLen --;
+ }
+
+ for (i = 0; i <= pValueLen; ++i)
+ if (pValue[i] == '>') {
+ pValueLen = i;
+ break;
+ }
+
+ CM_SetField(msg, emessageId, pValue, pValueLen);
+ }
+
+ processed = 1;
+ }
+
+ else if (!strcasecmp(key, "Return-Path")) {
+ if (CM_IsEmpty(msg, eMessagePath))
+ CM_SetField(msg, eMessagePath, value, valuelen);
+ processed = 1;
+ }
+
+ else if (!strcasecmp(key, "Envelope-To")) {
+ if (CM_IsEmpty(msg, eenVelopeTo))
+ CM_SetField(msg, eenVelopeTo, value, valuelen);
+ processed = 1;
+ }
+
+ else if (!strcasecmp(key, "References")) {
+ CM_SetField(msg, eWeferences, value, valuelen);
+ processed = 1;
+ }
+
+ else if (!strcasecmp(key, "Reply-To")) {
+ CM_SetField(msg, eReplyTo, value, valuelen);
+ processed = 1;
+ }
+
+ else if (!strcasecmp(key, "In-reply-to")) {
+ if (CM_IsEmpty(msg, eWeferences)) // References: supersedes In-reply-to:
+ CM_SetField(msg, eWeferences, value, valuelen);
+ processed = 1;
+ }
+
+
+
+ // Clean up and move on.
+ free(key); // Don't free 'value', it's actually the same buffer
+ return processed;
+}
+
+
+// Convert RFC822 references format (References) to Citadel references format (Weferences)
+void convert_references_to_wefewences(char *str) {
+ int bracket_nesting = 0;
+ char *ptr = str;
+ char *moveptr = NULL;
+ char ch;
+
+ while(*ptr) {
+ ch = *ptr;
+ if (ch == '>') {
+ --bracket_nesting;
+ if (bracket_nesting < 0) bracket_nesting = 0;
+ }
+ if ((ch == '>') && (bracket_nesting == 0) && (*(ptr+1)) && (ptr>str) ) {
+ *ptr = '|';
+ ++ptr;
+ }
+ else if (bracket_nesting > 0) {
+ ++ptr;
+ }
+ else {
+ moveptr = ptr;
+ while (*moveptr) {
+ *moveptr = *(moveptr+1);
+ ++moveptr;
+ }
+ }
+ if (ch == '<') ++bracket_nesting;
+ }
+
+}
+
+
+// Convert an RFC822 message (headers + body) to a CtdlMessage structure.
+// NOTE: the supplied buffer becomes part of the CtdlMessage structure, and
+// will be deallocated when CM_Free() is called. Therefore, the
+// supplied buffer should be DEREFERENCED. It should not be freed or used
+// again.
+struct CtdlMessage *convert_internet_message(char *rfc822) {
+ StrBuf *RFCBuf = NewStrBufPlain(rfc822, -1);
+ free (rfc822);
+ return convert_internet_message_buf(&RFCBuf);
+}
+
+
+struct CtdlMessage *convert_internet_message_buf(StrBuf **rfc822)
+{
+ struct CtdlMessage *msg;
+ const char *pos, *beg, *end, *totalend;
+ int done, alldone = 0;
+ int converted;
+ StrBuf *OtherHeaders;
+
+ msg = malloc(sizeof(struct CtdlMessage));
+ if (msg == NULL) return msg;
+
+ memset(msg, 0, sizeof(struct CtdlMessage));
+ msg->cm_magic = CTDLMESSAGE_MAGIC; // self check
+ msg->cm_anon_type = 0; // never anonymous
+ msg->cm_format_type = FMT_RFC822; // internet message
+
+ pos = ChrPtr(*rfc822);
+ totalend = pos + StrLength(*rfc822);
+ done = 0;
+ OtherHeaders = NewStrBufPlain(NULL, StrLength(*rfc822));
+
+ while (!alldone) {
+
+ /* Locate beginning and end of field, keeping in mind that
+ * some fields might be multiline
+ */
+ end = beg = pos;
+
+ while ((end < totalend) &&
+ (end == beg) &&
+ (done == 0) )
+ {
+
+ if ( (*pos=='\n') && ((*(pos+1))!=0x20) && ((*(pos+1))!=0x09) )
+ {
+ end = pos;
+ }
+
+ /* done with headers? */
+ if ((*pos=='\n') &&
+ ( (*(pos+1)=='\n') ||
+ (*(pos+1)=='\r')) )
+ {
+ alldone = 1;
+ }
+
+ if (pos >= (totalend - 1) )
+ {
+ end = pos;
+ done = 1;
+ }
+
+ ++pos;
+
+ }
+
+ /* At this point we have a field. Are we interested in it? */
+ converted = convert_field(msg, beg, end);
+
+ /* Strip the field out of the RFC822 header if we used it */
+ if (!converted) {
+ StrBufAppendBufPlain(OtherHeaders, beg, end - beg, 0);
+ StrBufAppendBufPlain(OtherHeaders, HKEY("\n"), 0);
+ }
+
+ /* If we've hit the end of the message, bail out */
+ if (pos >= totalend)
+ alldone = 1;
+ }
+ StrBufAppendBufPlain(OtherHeaders, HKEY("\n"), 0);
+ if (pos < totalend)
+ StrBufAppendBufPlain(OtherHeaders, pos, totalend - pos, 0);
+ FreeStrBuf(rfc822);
+ CM_SetAsFieldSB(msg, eMesageText, &OtherHeaders);
+
+ /* Follow-up sanity checks... */
+
+ /* If there's no timestamp on this message, set it to now. */
+ if (CM_IsEmpty(msg, eTimestamp)) {
+ CM_SetFieldLONG(msg, eTimestamp, time(NULL));
+ }
+
+ /* If a W (references, or rather, Wefewences) field is present, we
+ * have to convert it from RFC822 format to Citadel format.
+ */
+ if (!CM_IsEmpty(msg, eWeferences)) {
+ /// todo: API!
+ convert_references_to_wefewences(msg->cm_fields[eWeferences]);
+ }
+
+ return msg;
+}
+
+
+/*
+ * Look for a particular header field in an RFC822 message text. If the
+ * requested field is found, it is unfolded (if necessary) and returned to
+ * the caller. The field name is stripped out, leaving only its contents.
+ * The caller is responsible for freeing the returned buffer. If the requested
+ * field is not present, or anything else goes wrong, it returns NULL.
+ */
+char *rfc822_fetch_field(const char *rfc822, const char *fieldname) {
+ char *fieldbuf = NULL;
+ const char *end_of_headers;
+ const char *field_start;
+ const char *ptr;
+ char *cont;
+ char fieldhdr[SIZ];
+
+ /* Should never happen, but sometimes we get stupid */
+ if (rfc822 == NULL) return(NULL);
+ if (fieldname == NULL) return(NULL);
+
+ snprintf(fieldhdr, sizeof fieldhdr, "%s:", fieldname);
+
+ /* Locate the end of the headers, so we don't run past that point */
+ end_of_headers = cbmstrcasestr(rfc822, "\n\r\n");
+ if (end_of_headers == NULL) {
+ end_of_headers = cbmstrcasestr(rfc822, "\n\n");
+ }
+ if (end_of_headers == NULL) return (NULL);
+
+ field_start = cbmstrcasestr(rfc822, fieldhdr);
+ if (field_start == NULL) return(NULL);
+ if (field_start > end_of_headers) return(NULL);
+
+ fieldbuf = malloc(SIZ);
+ strcpy(fieldbuf, "");
+
+ ptr = field_start;
+ ptr = cmemreadline(ptr, fieldbuf, SIZ-strlen(fieldbuf) );
+ while ( (isspace(ptr[0])) && (ptr < end_of_headers) ) {
+ strcat(fieldbuf, " ");
+ cont = &fieldbuf[strlen(fieldbuf)];
+ ptr = cmemreadline(ptr, cont, SIZ-strlen(fieldbuf) );
+ striplt(cont);
+ }
+
+ strcpy(fieldbuf, &fieldbuf[strlen(fieldhdr)]);
+ striplt(fieldbuf);
+
+ return(fieldbuf);
+}
+
+
+/*****************************************************************************
+ * DIRECTORY MANAGEMENT FUNCTIONS *
+ *****************************************************************************/
+
+/*
+ * Generate the index key for an Internet e-mail address to be looked up
+ * in the database.
+ */
+void directory_key(char *key, char *addr) {
+ int i;
+ int keylen = 0;
+
+ for (i=0; !IsEmptyStr(&addr[i]); ++i) {
+ if (!isspace(addr[i])) {
+ key[keylen++] = tolower(addr[i]);
+ }
+ }
+ key[keylen++] = 0;
+
+ syslog(LOG_DEBUG, "internet_addressing: directory key is <%s>", key);
+}
+
+
+/*
+ * Return nonzero if the supplied address is in one of "our" domains
+ */
+int IsDirectory(char *addr, int allow_masq_domains) {
+ char domain[256];
+ int h;
+
+ extract_token(domain, addr, 1, '@', sizeof domain);
+ striplt(domain);
+
+ h = CtdlHostAlias(domain);
+
+ if ( (h == hostalias_masq) && allow_masq_domains)
+ return(1);
+
+ if (h == hostalias_localhost) {
+ return(1);
+ }
+ else {
+ return(0);
+ }
+}
+
+
+/*
+ * Add an Internet e-mail address to the directory for a user
+ */
+int CtdlDirectoryAddUser(char *internet_addr, char *citadel_addr) {
+ char key[SIZ];
+
+ if (IsDirectory(internet_addr, 0) == 0) {
+ return 0;
+ }
+ syslog(LOG_DEBUG, "internet_addressing: create directory entry: %s --> %s", internet_addr, citadel_addr);
+ directory_key(key, internet_addr);
+ cdb_store(CDB_DIRECTORY, key, strlen(key), citadel_addr, strlen(citadel_addr)+1 );
+ return 1;
+}
+
+
+/*
+ * Delete an Internet e-mail address from the directory.
+ *
+ * (NOTE: we don't actually use or need the citadel_addr variable; it's merely
+ * here because the callback API expects to be able to send it.)
+ */
+int CtdlDirectoryDelUser(char *internet_addr, char *citadel_addr) {
+ char key[SIZ];
+
+ syslog(LOG_DEBUG, "internet_addressing: delete directory entry: %s --> %s", internet_addr, citadel_addr);
+ directory_key(key, internet_addr);
+ return cdb_delete(CDB_DIRECTORY, key, strlen(key) ) == 0;
+}
+
+
+/*
+ * Look up an Internet e-mail address in the directory.
+ * On success: returns 0, and Citadel address stored in 'target'
+ * On failure: returns nonzero
+ */
+int CtdlDirectoryLookup(char *target, char *internet_addr, size_t targbuflen) {
+ struct cdbdata *cdbrec;
+ char key[SIZ];
+
+ /* Dump it in there unchanged, just for kicks */
+ if (target != NULL) {
+ safestrncpy(target, internet_addr, targbuflen);
+ }
+
+ /* Only do lookups for addresses with hostnames in them */
+ if (num_tokens(internet_addr, '@') != 2) return(-1);
+
+ /* Only do lookups for domains in the directory */
+ if (IsDirectory(internet_addr, 0) == 0) return(-1);
+
+ directory_key(key, internet_addr);
+ cdbrec = cdb_fetch(CDB_DIRECTORY, key, strlen(key) );
+ if (cdbrec != NULL) {
+ if (target != NULL) {
+ safestrncpy(target, cdbrec->ptr, targbuflen);
+ }
+ cdb_free(cdbrec);
+ return(0);
+ }
+
+ return(-1);
+}
+
+
+/*
+ * Harvest any email addresses that someone might want to have in their
+ * "collected addresses" book.
+ */
+char *harvest_collected_addresses(struct CtdlMessage *msg) {
+ char *coll = NULL;
+ char addr[256];
+ char user[256], node[256], name[256];
+ int is_harvestable;
+ int i, j, h;
+ eMsgField field = 0;
+
+ if (msg == NULL) return(NULL);
+
+ is_harvestable = 1;
+ strcpy(addr, "");
+ if (!CM_IsEmpty(msg, eAuthor)) {
+ strcat(addr, msg->cm_fields[eAuthor]);
+ }
+ if (!CM_IsEmpty(msg, erFc822Addr)) {
+ strcat(addr, " <");
+ strcat(addr, msg->cm_fields[erFc822Addr]);
+ strcat(addr, ">");
+ if (IsDirectory(msg->cm_fields[erFc822Addr], 0)) {
+ is_harvestable = 0;
+ }
+ }
+
+ if (is_harvestable) {
+ coll = strdup(addr);
+ }
+ else {
+ coll = strdup("");
+ }
+
+ if (coll == NULL) return(NULL);
+
+ /* Scan both the R (To) and Y (CC) fields */
+ for (i = 0; i < 2; ++i) {
+ if (i == 0) field = eRecipient;
+ if (i == 1) field = eCarbonCopY;
+
+ if (!CM_IsEmpty(msg, field)) {
+ for (j=0; j<num_tokens(msg->cm_fields[field], ','); ++j) {
+ extract_token(addr, msg->cm_fields[field], j, ',', sizeof addr);
+ if (strstr(addr, "=?") != NULL)
+ utf8ify_rfc822_string(addr);
+ process_rfc822_addr(addr, user, node, name);
+ h = CtdlHostAlias(node);
+ if (h != hostalias_localhost) {
+ coll = realloc(coll, strlen(coll) + strlen(addr) + 4);
+ if (coll == NULL) return(NULL);
+ if (!IsEmptyStr(coll)) {
+ strcat(coll, ",");
+ }
+ striplt(addr);
+ strcat(coll, addr);
+ }
+ }
+ }
+ }
+
+ if (IsEmptyStr(coll)) {
+ free(coll);
+ return(NULL);
+ }
+ return(coll);
+}
+
+
+/*
+ * Helper function for CtdlRebuildDirectoryIndex()
+ */
+void CtdlRebuildDirectoryIndex_backend(char *username, void *data) {
+
+ int j = 0;
+ struct ctdluser usbuf;
+
+ if (CtdlGetUser(&usbuf, username) != 0) {
+ return;
+ }
+
+ if ( (!IsEmptyStr(usbuf.fullname)) && (!IsEmptyStr(usbuf.emailaddrs)) ) {
+ for (j=0; j<num_tokens(usbuf.emailaddrs, '|'); ++j) {
+ char one_email[512];
+ extract_token(one_email, usbuf.emailaddrs, j, '|', sizeof one_email);
+ CtdlDirectoryAddUser(one_email, usbuf.fullname);
+ }
+ }
+}
+
+
+/*
+ * Initialize the directory database (erasing anything already there)
+ */
+void CtdlRebuildDirectoryIndex(void) {
+ syslog(LOG_INFO, "internet_addressing: rebuilding email address directory index");
+ cdb_trunc(CDB_DIRECTORY);
+ ForEachUser(CtdlRebuildDirectoryIndex_backend, NULL);
+}
+
+
+// Configure Internet email addresses for a user account, updating the Directory Index in the process
+void CtdlSetEmailAddressesForUser(char *requested_user, char *new_emailaddrs) {
+ struct ctdluser usbuf;
+ int i;
+ char buf[SIZ];
+
+ if (CtdlGetUserLock(&usbuf, requested_user) != 0) { // We can lock because the DirectoryIndex functions don't lock.
+ return; // Silently fail here if the specified user does not exist.
+ }
+
+ syslog(LOG_DEBUG, "internet_addressing: setting email addresses for <%s> to <%s>", usbuf.fullname, new_emailaddrs);
+
+ // Delete all of the existing directory index records for the user (easier this way)
+ for (i=0; i<num_tokens(usbuf.emailaddrs, '|'); ++i) {
+ extract_token(buf, usbuf.emailaddrs, i, '|', sizeof buf);
+ CtdlDirectoryDelUser(buf, requested_user);
+ }
+
+ strcpy(usbuf.emailaddrs, new_emailaddrs); // make it official.
+
+ // Index all of the new email addresses (they've already been sanitized)
+ for (i=0; i<num_tokens(usbuf.emailaddrs, '|'); ++i) {
+ extract_token(buf, usbuf.emailaddrs, i, '|', sizeof buf);
+ CtdlDirectoryAddUser(buf, requested_user);
+ }
+
+ CtdlPutUserLock(&usbuf);
+}
+
+
+/*
+ * Auto-generate an Internet email address for a user account
+ */
+void AutoGenerateEmailAddressForUser(struct ctdluser *user) {
+ char synthetic_email_addr[1024];
+ int i, j;
+ int u = 0;
+
+ for (i=0; u==0; ++i) {
+ if (i == 0) {
+ // first try just converting the user name to lowercase and replacing spaces with underscores
+ snprintf(synthetic_email_addr, sizeof synthetic_email_addr, "%s@%s", user->fullname, CtdlGetConfigStr("c_fqdn"));
+ for (j=0; ((synthetic_email_addr[j] != '\0')&&(synthetic_email_addr[j] != '@')); j++) {
+ synthetic_email_addr[j] = tolower(synthetic_email_addr[j]);
+ if (!isalnum(synthetic_email_addr[j])) {
+ synthetic_email_addr[j] = '_';
+ }
+ }
+ }
+ else if (i == 1) {
+ // then try 'ctdl' followed by the user number
+ snprintf(synthetic_email_addr, sizeof synthetic_email_addr, "ctdl%08lx@%s", user->usernum, CtdlGetConfigStr("c_fqdn"));
+ }
+ else if (i > 1) {
+ // oof. just keep trying other numbers until we find one
+ snprintf(synthetic_email_addr, sizeof synthetic_email_addr, "ctdl%08x@%s", i, CtdlGetConfigStr("c_fqdn"));
+ }
+ u = CtdlDirectoryLookup(NULL, synthetic_email_addr, 0);
+ syslog(LOG_DEBUG, "user_ops: address <%s> lookup returned <%d>", synthetic_email_addr, u);
+ }
+
+ CtdlSetEmailAddressesForUser(user->fullname, synthetic_email_addr);
+ strncpy(CC->user.emailaddrs, synthetic_email_addr, sizeof(user->emailaddrs));
+ syslog(LOG_DEBUG, "user_ops: auto-generated email address <%s> for <%s>", synthetic_email_addr, user->fullname);
+}
+
+
+// Determine whether the supplied email address is subscribed to the supplied room's mailing list service.
+int is_email_subscribed_to_list(char *email, char *room_name) {
+ struct ctdlroom room;
+ long roomnum;
+ char *roomnetconfig;
+ int found_it = 0;
+
+ if (CtdlGetRoom(&room, room_name)) {
+ return(0); // room not found, so definitely not subscribed
+ }
+
+ // If this room has the QR2_SMTP_PUBLIC flag set, anyone may email a post to this room, even non-subscribers.
+ if (room.QRflags2 & QR2_SMTP_PUBLIC) {
+ return(1);
+ }
+
+ roomnum = room.QRnumber;
+ roomnetconfig = LoadRoomNetConfigFile(roomnum);
+ if (roomnetconfig == NULL) {
+ return(0);
+ }
+
+ // We're going to do a very sloppy match here and simply search for the specified email address
+ // anywhere in the room's netconfig. If you don't like this, fix it yourself.
+ if (bmstrcasestr(roomnetconfig, email)) {
+ found_it = 1;
+ }
+ else {
+ found_it = 0;
+ }
+
+ free(roomnetconfig);
+ return(found_it);
+}
--- /dev/null
+
+#include "server.h"
+#include "ctdl_module.h"
+
+struct recptypes *validate_recipients(char *recipients, const char *RemoteIdentifier, int Flags);
+void free_recipients(struct recptypes *);
+void process_rfc822_addr(const char *rfc822, char *user, char *node, char *name);
+char *rfc822_fetch_field(const char *rfc822, const char *fieldname);
+void sanitize_truncated_recipient(char *str);
+char *qp_encode_email_addrs(char *source);
+int alias (char *name);
+int IsDirectory(char *addr, int allow_masq_domains);
+void CtdlRebuildDirectoryIndex(void);
+int CtdlDirectoryAddUser(char *internet_addr, char *citadel_addr);
+int CtdlDirectoryDelUser(char *internet_addr, char *citadel_addr);
+int CtdlDirectoryLookup(char *target, char *internet_addr, size_t targbuflen);
+void CtdlSetEmailAddressesForUser(char *requested_user, char *new_emailaddrs);
+void AutoGenerateEmailAddressForUser(struct ctdluser *user);
+struct CtdlMessage *convert_internet_message(char *rfc822);
+struct CtdlMessage *convert_internet_message_buf(StrBuf **rfc822);
+int CtdlIsMe(char *addr, int addr_buf_len);
+int CtdlHostAlias(char *fqdn);
+char *harvest_collected_addresses(struct CtdlMessage *msg);
+int is_email_subscribed_to_list(char *email, char *room_name);
+
+/*
+ * Values that can be returned by CtdlHostAlias()
+ */
+enum {
+ hostalias_nomatch,
+ hostalias_localhost,
+ hostalias_masq
+};
+
+extern char *inetcfg;
--- /dev/null
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define LISTING_FOLLOWS 100
+#define CIT_OK 200
+#define MORE_DATA 300
+#define SEND_LISTING 400
+#define ERROR 500
+#define BINARY_FOLLOWS 600
+#define SEND_BINARY 700
+#define START_CHAT_MODE 800
+
+#define INTERNAL_ERROR 10
+#define TOO_BIG 11
+#define ILLEGAL_VALUE 12
+#define NOT_LOGGED_IN 20
+#define CMD_NOT_SUPPORTED 30
+#define SERVER_SHUTTING_DOWN 31
+#define PASSWORD_REQUIRED 40
+#define ALREADY_LOGGED_IN 41
+#define USERNAME_REQUIRED 42
+#define HIGHER_ACCESS_REQUIRED 50
+#define MAX_SESSIONS_EXCEEDED 51
+#define RESOURCE_BUSY 52
+#define RESOURCE_NOT_OPEN 53
+#define NOT_HERE 60
+#define INVALID_FLOOR_OPERATION 61
+#define NO_SUCH_USER 70
+#define FILE_NOT_FOUND 71
+#define ROOM_NOT_FOUND 72
+#define NO_SUCH_SYSTEM 73
+#define ALREADY_EXISTS 74
+#define MESSAGE_NOT_FOUND 75
+
+#define ASYNC_MSG 900
+#define ASYNC_GEXP 02
+
+#define QR_PERMANENT 1 /* Room does not purge */
+#define QR_INUSE 2 /* Set if in use, clear if avail */
+#define QR_PRIVATE 4 /* Set for any type of private room */
+#define QR_PASSWORDED 8 /* Set if there's a password too */
+#define QR_GUESSNAME 16 /* Set if it's a guessname room */
+#define QR_DIRECTORY 32 /* Directory room */
+#define QR_UPLOAD 64 /* Allowed to upload */
+#define QR_DOWNLOAD 128 /* Allowed to download */
+#define QR_VISDIR 256 /* Visible directory */
+#define QR_ANONONLY 512 /* Anonymous-Only room */
+#define QR_ANONOPT 1024 /* Anonymous-Option room */
+#define QR_NETWORK 2048 /* Shared network room */
+#define QR_PREFONLY 4096 /* Preferred status needed to enter */
+#define QR_READONLY 8192 /* Aide status required to post */
+#define QR_MAILBOX 16384 /* Set if this is a private mailbox */
+
+#define QR2_SYSTEM 1 /* System room; hide by default */
+#define QR2_SELFLIST 2 /* Self-service mailing list mgmt */
+#define QR2_COLLABDEL 4 /* Anyone who can post can delete */
+#define QR2_SUBJECTREQ 8 /* Subject strongly recommended */
+#define QR2_SMTP_PUBLIC 16 /* Listservice Subscribers may post */
+
+#define US_NEEDVALID 1 /* User needs to be validated */
+#define US_EXTEDIT 2 /* Always use external editor */
+#define US_PERM 4 /* Permanent user */
+#define US_LASTOLD 16 /* Print last old message with new */
+#define US_EXPERT 32 /* Experienced user */
+#define US_UNLISTED 64 /* Unlisted userlog entry */
+#define US_NOPROMPT 128 /* Don't prompt after each message */
+#define US_PROMPTCTL 256 /* <N>ext & <S>top work at prompt */
+#define US_DISAPPEAR 512 /* Use "disappearing msg prompts" */
+#define US_REGIS 1024 /* Registered user */
+#define US_PAGINATOR 2048 /* Pause after each screen of text */
+#define US_INTERNET 4096 /* Internet mail privileges */
+#define US_FLOORS 8192 /* User wants to see floors */
+#define US_COLOR 16384 /* User wants ANSI color support */
+#define US_USER_SET (US_LASTOLD | US_EXPERT | US_UNLISTED | \
+ US_NOPROMPT | US_DISAPPEAR | US_PAGINATOR | \
+ US_FLOORS | US_COLOR | US_PROMPTCTL | US_EXTEDIT)
+
+#define UA_KNOWN 2 /* Room appears in a 'known rooms' list */
+#define UA_GOTOALLOWED 4 /* User may goto this room if specified by exact name */
+#define UA_HASNEWMSGS 8 /* Unread messages exist in this room */
+#define UA_ZAPPED 16 /* User has forgotten (zapped) this room */
+#define UA_POSTALLOWED 32 /* User may post top-level messages here */
+#define UA_ADMINALLOWED 64 /* Aide or Room Aide rights exist here */
+#define UA_DELETEALLOWED 128 /* User is allowed to delete messages from this room */
+#define UA_REPLYALLOWED 256 /* User is allowed to reply to existing messages here */
+
+#ifdef __cplusplus
+}
+#endif
--- /dev/null
+/*
+ * Message journaling functions.
+ *
+ * Copyright (c) 1987-2020 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.
+ */
+
+#include <stdio.h>
+#include <libcitadel.h>
+#include "ctdl_module.h"
+#include "citserver.h"
+#include "config.h"
+#include "user_ops.h"
+#include "serv_vcard.h" /* Needed for vcard_getuser and extract_inet_email_addrs */
+#include "internet_addressing.h"
+#include "journaling.h"
+
+struct jnlq *jnlq = NULL; /* journal queue */
+
+/*
+ * Hand off a copy of a message to be journalized.
+ */
+void JournalBackgroundSubmit(struct CtdlMessage *msg,
+ StrBuf *saved_rfc822_version,
+ struct recptypes *recps) {
+
+ struct jnlq *jptr = NULL;
+
+ /* Avoid double journaling! */
+ if (!CM_IsEmpty(msg, eJournal)) {
+ FreeStrBuf(&saved_rfc822_version);
+ return;
+ }
+
+ jptr = (struct jnlq *)malloc(sizeof(struct jnlq));
+ if (jptr == NULL) {
+ FreeStrBuf(&saved_rfc822_version);
+ return;
+ }
+ memset(jptr, 0, sizeof(struct jnlq));
+ if (recps != NULL) memcpy(&jptr->recps, recps, sizeof(struct recptypes));
+ if (!CM_IsEmpty(msg, eAuthor)) jptr->from = strdup(msg->cm_fields[eAuthor]);
+ if (!CM_IsEmpty(msg, erFc822Addr)) jptr->rfca = strdup(msg->cm_fields[erFc822Addr]);
+ if (!CM_IsEmpty(msg, eMsgSubject)) jptr->subj = strdup(msg->cm_fields[eMsgSubject]);
+ if (!CM_IsEmpty(msg, emessageId)) jptr->msgn = strdup(msg->cm_fields[emessageId]);
+ jptr->rfc822 = SmashStrBuf(&saved_rfc822_version);
+
+ /* Add to the queue */
+ begin_critical_section(S_JOURNAL_QUEUE);
+ jptr->next = jnlq;
+ jnlq = jptr;
+ end_critical_section(S_JOURNAL_QUEUE);
+}
+
+
+/*
+ * Convert a local user name to an internet email address for the journal
+ * FIXME - grab the user's Internet email address from the user record, not from vCard !!!!
+ */
+void local_to_inetemail(char *inetemail, char *localuser, size_t inetemail_len) {
+ struct ctdluser us;
+ struct vCard *v;
+
+ strcpy(inetemail, "");
+ if (CtdlGetUser(&us, localuser) != 0) {
+ return;
+ }
+
+ v = vcard_get_user(&us);
+ if (v == NULL) {
+ return;
+ }
+
+ extract_inet_email_addrs(inetemail, inetemail_len, NULL, 0, v, 1);
+ vcard_free(v);
+}
+
+
+/*
+ * Called by JournalRunQueue() to send an individual message.
+ */
+void JournalRunQueueMsg(struct jnlq *jmsg) {
+
+ struct CtdlMessage *journal_msg = NULL;
+ struct recptypes *journal_recps = NULL;
+ StrBuf *message_text = NULL;
+ char mime_boundary[256];
+ long mblen;
+ long rfc822len;
+ char recipient[256];
+ char inetemail[256];
+ static int seq = 0;
+ int i;
+
+ if (jmsg == NULL)
+ return;
+ journal_recps = validate_recipients(CtdlGetConfigStr("c_journal_dest"), NULL, 0);
+ if (journal_recps != NULL) {
+
+ if ( (journal_recps->num_local > 0)
+ || (journal_recps->num_internet > 0)
+ || (journal_recps->num_room > 0)
+ ) {
+
+ /*
+ * Construct journal message.
+ * Note that we are transferring ownership of some of the memory here.
+ */
+ journal_msg = malloc(sizeof(struct CtdlMessage));
+ memset(journal_msg, 0, sizeof(struct CtdlMessage));
+ journal_msg->cm_magic = CTDLMESSAGE_MAGIC;
+ journal_msg->cm_anon_type = MES_NORMAL;
+ journal_msg->cm_format_type = FMT_RFC822;
+ CM_SetField(journal_msg, eJournal, HKEY("is journal"));
+
+ if (!IsEmptyStr(jmsg->from)) {
+ CM_SetField(journal_msg, eAuthor, jmsg->from, -1);
+ }
+
+ if (!IsEmptyStr(jmsg->rfca)) {
+ CM_SetField(journal_msg, erFc822Addr, jmsg->rfca, -1);
+ }
+
+ if (!IsEmptyStr(jmsg->subj)) {
+ CM_SetField(journal_msg, eMsgSubject, jmsg->subj, -1);
+ }
+
+ mblen = snprintf(mime_boundary, sizeof(mime_boundary),
+ "--Citadel-Journal-%08lx-%04x--", time(NULL), ++seq);
+
+ if (!IsEmptyStr(jmsg->rfc822)) {
+ rfc822len = strlen(jmsg->rfc822);
+ }
+ else {
+ rfc822len = 0;
+ }
+
+ message_text = NewStrBufPlain(NULL, rfc822len + sizeof(struct recptypes) + 1024);
+
+ /*
+ * Here is where we begin to compose the journalized message.
+ * (The "ExJournalReport" header is consumed by some email retention services which assume the journaling agent is Exchange.)
+ */
+ StrBufAppendBufPlain(
+ message_text,
+ HKEY("Content-type: multipart/mixed; boundary=\""),
+ 0
+ );
+
+ StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0);
+
+ StrBufAppendBufPlain(
+ message_text,
+ HKEY("\"\r\n"
+ "Content-Identifier: ExJournalReport\r\n"
+ "MIME-Version: 1.0\r\n"
+ "\n"
+ "--"),
+ 0
+ );
+
+ StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0);
+
+ StrBufAppendBufPlain(
+ message_text,
+ HKEY("\r\n"
+ "Content-type: text/plain\r\n"
+ "\r\n"
+ "Sender: "), 0);
+
+ if (CM_IsEmpty(journal_msg, eAuthor))
+ StrBufAppendBufPlain(
+ message_text,
+ journal_msg->cm_fields[eAuthor], -1, 0);
+ else
+ StrBufAppendBufPlain(
+ message_text,
+ HKEY("(null)"), 0);
+
+ if (!CM_IsEmpty(journal_msg, erFc822Addr)) {
+ StrBufAppendPrintf(message_text, " <%s>",
+ journal_msg->cm_fields[erFc822Addr]);
+ }
+
+ StrBufAppendBufPlain(message_text, HKEY("\r\nMessage-ID: <"), 0);
+ StrBufAppendBufPlain(message_text, jmsg->msgn, -1, 0);
+ StrBufAppendBufPlain(message_text, HKEY(">\r\nRecipients:\r\n"), 0);
+
+ if (jmsg->recps.num_local > 0) {
+ for (i=0; i<jmsg->recps.num_local; ++i) {
+ extract_token(recipient, jmsg->recps.recp_local, i, '|', sizeof recipient);
+ local_to_inetemail(inetemail, recipient, sizeof inetemail);
+ StrBufAppendPrintf(message_text, " %s <%s>\r\n", recipient, inetemail);
+ }
+ }
+
+ if (jmsg->recps.num_internet > 0) {
+ for (i=0; i<jmsg->recps.num_internet; ++i) {
+ extract_token(recipient, jmsg->recps.recp_internet, i, '|', sizeof recipient);
+ StrBufAppendPrintf(message_text, " %s\r\n", recipient);
+ }
+ }
+
+ StrBufAppendBufPlain(message_text, HKEY("\r\n" "--"), 0);
+ StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0);
+ StrBufAppendBufPlain(message_text, HKEY("\r\nContent-type: message/rfc822\r\n\r\n"), 0);
+ StrBufAppendBufPlain(message_text, jmsg->rfc822, rfc822len, 0);
+ StrBufAppendBufPlain(message_text, HKEY("--"), 0);
+ StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0);
+ StrBufAppendBufPlain(message_text, HKEY("--\r\n"), 0);
+
+ CM_SetAsFieldSB(journal_msg, eMesageText, &message_text);
+ free(jmsg->rfc822);
+ free(jmsg->msgn);
+ jmsg->rfc822 = NULL;
+ jmsg->msgn = NULL;
+
+ /* Submit journal message */
+ CtdlSubmitMsg(journal_msg, journal_recps, "");
+ CM_Free(journal_msg);
+ }
+
+ free_recipients(journal_recps);
+ }
+
+ /* We are responsible for freeing this memory. */
+ free(jmsg);
+}
+
+
+/*
+ * Run the queue.
+ */
+void JournalRunQueue(void) {
+ struct jnlq *jptr = NULL;
+
+ while (jnlq != NULL) {
+ begin_critical_section(S_JOURNAL_QUEUE);
+ if (jnlq != NULL) {
+ jptr = jnlq;
+ jnlq = jnlq->next;
+ }
+ end_critical_section(S_JOURNAL_QUEUE);
+ JournalRunQueueMsg(jptr);
+ }
+}
+
+
--- /dev/null
+struct jnlq {
+ struct jnlq *next;
+ struct recptypes recps;
+ char *from;
+ char *node;
+ char *rfca;
+ char *subj;
+ char *msgn;
+ char *rfc822;
+};
+
+void JournalBackgroundSubmit(struct CtdlMessage *msg,
+ StrBuf *saved_rfc822_version,
+ struct recptypes *recps);
+void JournalRunQueueMsg(struct jnlq *jmsg);
+void JournalRunQueue(void);
--- /dev/null
+// These functions implement the portions of AUTHMODE_LDAP and AUTHMODE_LDAP_AD which
+// actually speak to the LDAP server.
+//
+// Copyright (c) 2011-2022 by the citadel.org development 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.
+
+// ldapsearch -D uid=admin,cn=users,cn=compat,dc=demo1,dc=freeipa,dc=org -w Secret123 -h ipa.demo1.freeipa.org
+
+int ctdl_require_ldap_version = 3;
+
+#define _GNU_SOURCE // Needed to suppress warning about vasprintf() when running on Linux/Linux
+#include <stdio.h>
+#include <libcitadel.h>
+#include "citserver.h"
+#include "citadel_ldap.h"
+#include "ctdl_module.h"
+#include "user_ops.h"
+#include "internet_addressing.h"
+#include "config.h"
+#include <ldap.h>
+
+
+// These functions are deprecated and until we change them to the new API we need these declarations to get around compiler warnings.
+int ldap_simple_bind_s(LDAP *, const char *, const char *);
+int ldap_unbind(LDAP *);
+
+
+// Utility function, supply a search result and get back the fullname (display name, common name, etc) from the first result
+//
+// POSIX schema: the display name will be found in "cn" (common name)
+// Active Directory: the display name will be found in "displayName"
+//
+void derive_fullname_from_ldap_result(char *fullname, int fullname_size, LDAP *ldserver, LDAPMessage *search_result) {
+ struct berval **values;
+
+ if (fullname == NULL) return;
+ if (search_result == NULL) return;
+ if (ldserver == NULL) return;
+
+ if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD) {
+ values = ldap_get_values_len(ldserver, search_result, "displayName");
+ if (values) {
+ if (ldap_count_values_len(values) > 0) {
+ safestrncpy(fullname, values[0]->bv_val, fullname_size);
+ syslog(LOG_DEBUG, "ldap: displayName = %s", fullname);
+ }
+ ldap_value_free_len(values);
+ }
+ }
+ else {
+ values = ldap_get_values_len(ldserver, search_result, "cn");
+ if (values) {
+ if (ldap_count_values_len(values) > 0) {
+ safestrncpy(fullname, values[0]->bv_val, fullname_size);
+ syslog(LOG_DEBUG, "ldap: cn = %s", fullname);
+ }
+ ldap_value_free_len(values);
+ }
+ }
+}
+
+
+// Utility function, supply a search result and get back the uid from the first result
+//
+// POSIX schema: numeric user id will be in the "uidNumber" attribute
+// Active Directory: we make a uid hashed from "objectGUID"
+//
+uid_t derive_uid_from_ldap(LDAP *ldserver, LDAPMessage *entry) {
+ struct berval **values;
+ uid_t uid = (-1);
+
+ if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD) {
+ values = ldap_get_values_len(ldserver, entry, "objectGUID");
+ if (values) {
+ if (ldap_count_values_len(values) > 0) {
+ uid = abs(HashLittle(values[0]->bv_val, values[0]->bv_len));
+ }
+ ldap_value_free_len(values);
+ }
+ }
+ else {
+ values = ldap_get_values_len(ldserver, entry, "uidNumber");
+ if (values) {
+ if (ldap_count_values_len(values) > 0) {
+ uid = atoi(values[0]->bv_val);
+ }
+ ldap_value_free_len(values);
+ }
+ }
+
+ syslog(LOG_DEBUG, "ldap: uid = %d", uid);
+ return(uid);
+}
+
+
+// Wrapper function for ldap_initialize() that consistently fills in the correct fields
+int ctdl_ldap_initialize(LDAP **ld) {
+
+ char server_url[256];
+ int ret;
+
+ snprintf(server_url, sizeof server_url, "ldap://%s:%d", CtdlGetConfigStr("c_ldap_host"), CtdlGetConfigInt("c_ldap_port"));
+ syslog(LOG_DEBUG, "ldap: initializing server %s", server_url);
+ ret = ldap_initialize(ld, server_url);
+ if (ret != LDAP_SUCCESS) {
+ syslog(LOG_ERR, "ldap: could not connect to %s : %m", server_url);
+ *ld = NULL;
+ return(errno);
+ }
+
+ return(ret);
+}
+
+
+// Bind to the LDAP server and return a working handle
+LDAP *ctdl_ldap_bind(void) {
+ LDAP *ldserver = NULL;
+ int i;
+
+ if (ctdl_ldap_initialize(&ldserver) != LDAP_SUCCESS) {
+ return(NULL);
+ }
+
+ ldap_set_option(ldserver, LDAP_OPT_PROTOCOL_VERSION, &ctdl_require_ldap_version);
+ ldap_set_option(ldserver, LDAP_OPT_REFERRALS, (void *)LDAP_OPT_OFF);
+
+ striplt(CtdlGetConfigStr("c_ldap_bind_dn"));
+ striplt(CtdlGetConfigStr("c_ldap_bind_pw"));
+ i = ldap_simple_bind_s(ldserver,
+ (!IsEmptyStr(CtdlGetConfigStr("c_ldap_bind_dn")) ? CtdlGetConfigStr("c_ldap_bind_dn") : NULL),
+ (!IsEmptyStr(CtdlGetConfigStr("c_ldap_bind_pw")) ? CtdlGetConfigStr("c_ldap_bind_pw") : NULL)
+ );
+ if (i != LDAP_SUCCESS) {
+ syslog(LOG_ERR, "ldap: Cannot bind: %s (%d)", ldap_err2string(i), i);
+ return(NULL);
+ }
+
+ return(ldserver);
+}
+
+
+// Look up a user in the directory to see if this is an account that can be authenticated
+//
+// POSIX schema: Search all "inetOrgPerson" objects with "uid" set to the supplied username
+// Active Directory: Look for an account with "sAMAccountName" set to the supplied username
+//
+int CtdlTryUserLDAP(char *username, char *found_dn, int found_dn_size, char *fullname, int fullname_size, uid_t *uid) {
+ LDAP *ldserver = NULL;
+ LDAPMessage *search_result = NULL;
+ LDAPMessage *entry = NULL;
+ char searchstring[1024];
+ struct timeval tv;
+ char *user_dn = NULL;
+
+ ldserver = ctdl_ldap_bind();
+ if (!ldserver) return(-1);
+
+ if (fullname) safestrncpy(fullname, username, fullname_size);
+ tv.tv_sec = 10;
+ tv.tv_usec = 0;
+
+ if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD) {
+ snprintf(searchstring, sizeof(searchstring), "(sAMAccountName=%s)", username);
+ }
+ else {
+ snprintf(searchstring, sizeof(searchstring), "(&(objectclass=inetOrgPerson)(uid=%s))", username);
+ }
+
+ syslog(LOG_DEBUG, "ldap: search: %s", searchstring);
+ syslog(LOG_DEBUG, "ldap: search results: %s", ldap_err2string(ldap_search_ext_s(
+ ldserver, // ld
+ CtdlGetConfigStr("c_ldap_base_dn"), // base
+ LDAP_SCOPE_SUBTREE, // scope
+ searchstring, // filter
+ NULL, // attrs (all attributes)
+ 0, // attrsonly (attrs + values)
+ NULL, // serverctrls (none)
+ NULL, // clientctrls (none)
+ &tv, // timeout
+ 1, // sizelimit (1 result max)
+ &search_result // put the result here
+ )));
+
+ // Ignore the return value of ldap_search_ext_s(). Sometimes it returns an error even when
+ // the search succeeds. Instead, we check to see whether search_result is still NULL.
+ if (search_result == NULL) {
+ syslog(LOG_DEBUG, "ldap: zero search results were returned");
+ ldap_unbind(ldserver);
+ return(2);
+ }
+
+ // At this point we've got at least one result from our query. If there are multiple
+ // results, we still only look at the first one.
+ entry = ldap_first_entry(ldserver, search_result);
+ if (entry) {
+
+ user_dn = ldap_get_dn(ldserver, entry);
+ if (user_dn) {
+ syslog(LOG_DEBUG, "ldap: dn = %s", user_dn);
+ }
+
+ derive_fullname_from_ldap_result(fullname, fullname_size, ldserver, search_result);
+ *uid = derive_uid_from_ldap(ldserver, search_result);
+ }
+
+ // free the results
+ ldap_msgfree(search_result);
+
+ // unbind so we can go back in as the authenticating user
+ ldap_unbind(ldserver);
+
+ if (!user_dn) {
+ syslog(LOG_DEBUG, "ldap: No such user was found.");
+ return(4);
+ }
+
+ if (found_dn) safestrncpy(found_dn, user_dn, found_dn_size);
+ ldap_memfree(user_dn);
+ return(0);
+}
+
+
+// This is an extension of CtdlTryPassword() which gets called when using LDAP authentication.
+int CtdlTryPasswordLDAP(char *user_dn, const char *password) {
+ LDAP *ldserver = NULL;
+ int i = (-1);
+
+ if (IsEmptyStr(password)) {
+ syslog(LOG_DEBUG, "ldap: empty passwords are not permitted");
+ return(1);
+ }
+
+ syslog(LOG_DEBUG, "ldap: trying to bind as %s", user_dn);
+ i = ctdl_ldap_initialize(&ldserver);
+ if (i == LDAP_SUCCESS) {
+ ldap_set_option(ldserver, LDAP_OPT_PROTOCOL_VERSION, &ctdl_require_ldap_version);
+ i = ldap_simple_bind_s(ldserver, user_dn, password);
+ if (i == LDAP_SUCCESS) {
+ syslog(LOG_DEBUG, "ldap: bind succeeded");
+ }
+ else {
+ syslog(LOG_DEBUG, "ldap: Cannot bind: %s (%d)", ldap_err2string(i), i);
+ }
+ ldap_set_option(ldserver, LDAP_OPT_REFERRALS, (void *)LDAP_OPT_OFF);
+ ldap_unbind(ldserver);
+ }
+
+ if (i == LDAP_SUCCESS) {
+ return(0);
+ }
+
+ return(1);
+}
+
+
+// set multiple properties
+// returns nonzero only if property changed.
+int vcard_set_props_iff_different(struct vCard *v, char *propname, int numvals, char **vals) {
+ int i;
+ char *oldval = "";
+ for (i=0; i<numvals; i++) {
+ oldval = vcard_get_prop(v, propname, 0, i, 0);
+ if (oldval == NULL) break;
+ if (strcmp(vals[i],oldval)) break;
+ }
+ if (i != numvals) {
+ syslog(LOG_DEBUG, "ldap: vcard property %s, element %d of %d changed from %s to %s", propname, i, numvals, oldval, vals[i]);
+ for (i=0; i<numvals; i++) {
+ vcard_set_prop(v,propname,vals[i],(i==0) ? 0 : 1);
+ }
+ return 1;
+ }
+ return 0;
+}
+
+
+// set one property
+// returns nonzero only if property changed.
+int vcard_set_one_prop_iff_different(struct vCard *v,char *propname, char *newfmt, ...) {
+ va_list args;
+ char *newvalue;
+ int did_change = 0;
+ va_start(args,newfmt);
+ if (vasprintf(&newvalue, newfmt, args) < 0) {
+ syslog(LOG_ERR, "ldap: out of memory");
+ return 0;
+ }
+ did_change = vcard_set_props_iff_different(v, propname, 1, &newvalue);
+ va_end(args);
+ free(newvalue);
+ return did_change;
+}
+
+
+// Learn LDAP attributes and stuff them into the vCard.
+// Returns nonzero if we changed anything.
+int Ctdl_LDAP_to_vCard(char *ldap_dn, struct vCard *v) {
+ int changed_something = 0;
+ LDAP *ldserver = NULL;
+ struct timeval tv;
+ LDAPMessage *search_result = NULL;
+ LDAPMessage *entry = NULL;
+ struct berval **givenName;
+ struct berval **sn;
+ struct berval **cn;
+ struct berval **initials;
+ struct berval **o;
+ struct berval **street;
+ struct berval **l;
+ struct berval **st;
+ struct berval **postalCode;
+ struct berval **telephoneNumber;
+ struct berval **mobile;
+ struct berval **homePhone;
+ struct berval **facsimileTelephoneNumber;
+ struct berval **mail;
+ struct berval **uid;
+ struct berval **homeDirectory;
+ struct berval **uidNumber;
+ struct berval **loginShell;
+ struct berval **gidNumber;
+ struct berval **c;
+ struct berval **title;
+ struct berval **uuid;
+ char *attrs[] = { "*","+",NULL};
+
+ if (!ldap_dn) return(0);
+ if (!v) return(0);
+
+ ldserver = ctdl_ldap_bind();
+ if (!ldserver) return(-1);
+
+ tv.tv_sec = 10;
+ tv.tv_usec = 0;
+
+ syslog(LOG_DEBUG, "ldap: search: %s", ldap_dn);
+ syslog(LOG_DEBUG, "ldap: search results: %s", ldap_err2string(ldap_search_ext_s(
+ ldserver, // ld
+ ldap_dn, // base
+ LDAP_SCOPE_SUBTREE, // scope
+ NULL, // filter
+ attrs, // attrs (all attributes)
+ 0, // attrsonly (attrs + values)
+ NULL, // serverctrls (none)
+ NULL, // clientctrls (none)
+ &tv, // timeout
+ 1, // sizelimit (1 result max)
+ &search_result // res
+ )));
+
+ // Ignore the return value of ldap_search_ext_s(). Sometimes it returns an error even when
+ // the search succeeds. Instead, we check to see whether search_result is still NULL.
+ if (search_result == NULL) {
+ syslog(LOG_DEBUG, "ldap: zero search results were returned");
+ ldap_unbind(ldserver);
+ return(0);
+ }
+
+ // At this point we've got at least one result from our query. If there are multiple
+ // results, we still only look at the first one.
+ entry = ldap_first_entry(ldserver, search_result);
+ if (entry) {
+ syslog(LOG_DEBUG, "ldap: search got user details for vcard.");
+ givenName = ldap_get_values_len(ldserver, search_result, "givenName");
+ sn = ldap_get_values_len(ldserver, search_result, "sn");
+ cn = ldap_get_values_len(ldserver, search_result, "cn");
+ initials = ldap_get_values_len(ldserver, search_result, "initials");
+ title = ldap_get_values_len(ldserver, search_result, "title");
+ o = ldap_get_values_len(ldserver, search_result, "o");
+ street = ldap_get_values_len(ldserver, search_result, "street");
+ l = ldap_get_values_len(ldserver, search_result, "l");
+ st = ldap_get_values_len(ldserver, search_result, "st");
+ postalCode = ldap_get_values_len(ldserver, search_result, "postalCode");
+ telephoneNumber = ldap_get_values_len(ldserver, search_result, "telephoneNumber");
+ mobile = ldap_get_values_len(ldserver, search_result, "mobile");
+ homePhone = ldap_get_values_len(ldserver, search_result, "homePhone");
+ facsimileTelephoneNumber = ldap_get_values_len(ldserver, search_result, "facsimileTelephoneNumber");
+ mail = ldap_get_values_len(ldserver, search_result, "mail");
+ uid = ldap_get_values_len(ldserver, search_result, "uid");
+ homeDirectory = ldap_get_values_len(ldserver, search_result, "homeDirectory");
+ uidNumber = ldap_get_values_len(ldserver, search_result, "uidNumber");
+ loginShell = ldap_get_values_len(ldserver, search_result, "loginShell");
+ gidNumber = ldap_get_values_len(ldserver, search_result, "gidNumber");
+ c = ldap_get_values_len(ldserver, search_result, "c");
+ uuid = ldap_get_values_len(ldserver, search_result, "entryUUID");
+
+ if (street && l && st && postalCode && c) changed_something |= vcard_set_one_prop_iff_different(v,"adr",";;%s;%s;%s;%s;%s",street[0]->bv_val,l[0]->bv_val,st[0]->bv_val,postalCode[0]->bv_val,c[0]->bv_val);
+ if (telephoneNumber) changed_something |= vcard_set_one_prop_iff_different(v,"tel;work","%s",telephoneNumber[0]->bv_val);
+ if (facsimileTelephoneNumber) changed_something |= vcard_set_one_prop_iff_different(v,"tel;fax","%s",facsimileTelephoneNumber[0]->bv_val);
+ if (mobile) changed_something |= vcard_set_one_prop_iff_different(v,"tel;cell","%s",mobile[0]->bv_val);
+ if (homePhone) changed_something |= vcard_set_one_prop_iff_different(v,"tel;home","%s",homePhone[0]->bv_val);
+ if (givenName && sn) {
+ if (initials) {
+ changed_something |= vcard_set_one_prop_iff_different(v,"n","%s;%s;%s",sn[0]->bv_val,givenName[0]->bv_val,initials[0]->bv_val);
+ }
+ else {
+ changed_something |= vcard_set_one_prop_iff_different(v,"n","%s;%s",sn[0]->bv_val,givenName[0]->bv_val);
+ }
+ }
+
+ // FIXME we need a new way to do this.
+ //if (mail) {
+ //changed_something |= vcard_set_props_iff_different(v,"email;internet",ldap_count_values_len(mail),mail);
+ //}
+
+ if (uuid) changed_something |= vcard_set_one_prop_iff_different(v,"X-uuid","%s",uuid[0]->bv_val);
+ if (o) changed_something |= vcard_set_one_prop_iff_different(v,"org","%s",o[0]->bv_val);
+ if (cn) changed_something |= vcard_set_one_prop_iff_different(v,"fn","%s",cn[0]->bv_val);
+ if (title) changed_something |= vcard_set_one_prop_iff_different(v,"title","%s",title[0]->bv_val);
+
+ if (givenName) ldap_value_free_len(givenName);
+ if (initials) ldap_value_free_len(initials);
+ if (sn) ldap_value_free_len(sn);
+ if (cn) ldap_value_free_len(cn);
+ if (o) ldap_value_free_len(o);
+ if (street) ldap_value_free_len(street);
+ if (l) ldap_value_free_len(l);
+ if (st) ldap_value_free_len(st);
+ if (postalCode) ldap_value_free_len(postalCode);
+ if (telephoneNumber) ldap_value_free_len(telephoneNumber);
+ if (mobile) ldap_value_free_len(mobile);
+ if (homePhone) ldap_value_free_len(homePhone);
+ if (facsimileTelephoneNumber) ldap_value_free_len(facsimileTelephoneNumber);
+ if (mail) ldap_value_free_len(mail);
+ if (uid) ldap_value_free_len(uid);
+ if (homeDirectory) ldap_value_free_len(homeDirectory);
+ if (uidNumber) ldap_value_free_len(uidNumber);
+ if (loginShell) ldap_value_free_len(loginShell);
+ if (gidNumber) ldap_value_free_len(gidNumber);
+ if (c) ldap_value_free_len(c);
+ if (title) ldap_value_free_len(title);
+ if (uuid) ldap_value_free_len(uuid);
+ }
+ // free the results
+ ldap_msgfree(search_result);
+
+ // unbind so we can go back in as the authenticating user
+ ldap_unbind(ldserver);
+ return(changed_something); // tell the caller whether we made any changes
+}
+
+
+// Extract a user's Internet email addresses from LDAP.
+// Returns zero if we got a valid set of addresses; nonzero for error.
+int extract_email_addresses_from_ldap(char *ldap_dn, char *emailaddrs) {
+ LDAP *ldserver = NULL;
+ struct timeval tv;
+ LDAPMessage *search_result = NULL;
+ LDAPMessage *entry = NULL;
+ struct berval **mail;
+ char *attrs[] = { "*","+",NULL };
+
+ if (!ldap_dn) return(1);
+ if (!emailaddrs) return(1);
+
+ ldserver = ctdl_ldap_bind();
+ if (!ldserver) return(-1);
+
+ tv.tv_sec = 10;
+ tv.tv_usec = 0;
+
+ syslog(LOG_DEBUG, "ldap: search: %s", ldap_dn);
+ syslog(LOG_DEBUG, "ldap: search results: %s", ldap_err2string(ldap_search_ext_s(
+ ldserver, // ld
+ ldap_dn, // base
+ LDAP_SCOPE_SUBTREE, // scope
+ NULL, // filter
+ attrs, // attrs (all attributes)
+ 0, // attrsonly (attrs + values)
+ NULL, // serverctrls (none)
+ NULL, // clientctrls (none)
+ &tv, // timeout
+ 1, // sizelimit (1 result max)
+ &search_result // res
+ )));
+
+ // Ignore the return value of ldap_search_ext_s(). Sometimes it returns an error even when
+ // the search succeeds. Instead, we check to see whether search_result is still NULL.
+ if (search_result == NULL) {
+ syslog(LOG_DEBUG, "ldap: zero search results were returned");
+ ldap_unbind(ldserver);
+ return(4);
+ }
+
+ // At this point we've got at least one result from our query.
+ // If there are multiple results, we still only look at the first one.
+ emailaddrs[0] = 0; // clear out any previous results
+ entry = ldap_first_entry(ldserver, search_result);
+ if (entry) {
+ syslog(LOG_DEBUG, "ldap: search got user details");
+ mail = ldap_get_values_len(ldserver, search_result, "mail");
+ if (mail) {
+ int q;
+ for (q=0; q<ldap_count_values_len(mail); ++q) {
+ if (IsDirectory(mail[q]->bv_val, 0)) {
+ if ((strlen(emailaddrs) + mail[q]->bv_len + 2) > 512) {
+ syslog(LOG_ERR, "ldap: can't fit all email addresses into user record");
+ }
+ else {
+ if (!IsEmptyStr(emailaddrs)) {
+ strcat(emailaddrs, "|");
+ }
+ strcat(emailaddrs, mail[q]->bv_val);
+ }
+ }
+ }
+ }
+ }
+
+ // free the results
+ ldap_msgfree(search_result);
+
+ // unbind so we can go back in as the authenticating user
+ ldap_unbind(ldserver);
+ return(0);
+}
+
+
+// Remember that a particular user exists in the Citadel database.
+// As we scan the LDAP tree we will remove users from this list when we find them.
+// At the end of the scan, any users remaining in this list are stale and should be deleted.
+void ldap_note_user_in_citadel(char *username, void *data) {
+ return;
+}
+
+
+// Scan LDAP for users and populate Citadel's user database with everyone
+//
+// POSIX schema: All objects of class "inetOrgPerson"
+// Active Directory: Objects that are class "user" and class "person" but NOT class "computer"
+//
+void CtdlSynchronizeUsersFromLDAP(void) {
+ LDAP *ldserver = NULL;
+ LDAPMessage *search_result = NULL;
+ LDAPMessage *entry = NULL;
+ char *user_dn = NULL;
+ char searchstring[1024];
+ struct timeval tv;
+
+ if ((CtdlGetConfigInt("c_auth_mode") != AUTHMODE_LDAP) && (CtdlGetConfigInt("c_auth_mode") != AUTHMODE_LDAP_AD)) {
+ return; // If this site is not running LDAP, stop here.
+ }
+
+ syslog(LOG_INFO, "ldap: synchronizing Citadel user database from LDAP");
+
+ // first, scan the existing Citadel user list
+ // ForEachUser(ldap_note_user_in_citadel, NULL); // FIXME finish this
+
+ ldserver = ctdl_ldap_bind();
+ if (!ldserver) return;
+
+ tv.tv_sec = 10;
+ tv.tv_usec = 0;
+
+ if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD) {
+ snprintf(searchstring, sizeof(searchstring), "(&(objectClass=user)(objectClass=person)(!(objectClass=computer)))");
+ }
+ else {
+ snprintf(searchstring, sizeof(searchstring), "(objectClass=inetOrgPerson)");
+ }
+
+ syslog(LOG_DEBUG, "ldap: search: %s", searchstring);
+ syslog(LOG_DEBUG, "ldap: search results: %s", ldap_err2string(ldap_search_ext_s(
+ ldserver, // ld
+ CtdlGetConfigStr("c_ldap_base_dn"), // base
+ LDAP_SCOPE_SUBTREE, // scope
+ searchstring, // filter
+ NULL, // attrs (all attributes)
+ 0, // attrsonly (attrs + values)
+ NULL, // serverctrls (none)
+ NULL, // clientctrls (none)
+ &tv, // timeout
+ INT_MAX, // sizelimit (max)
+ &search_result // put the result here
+ )));
+
+ // Ignore the return value of ldap_search_ext_s(). Sometimes it returns an error even when
+ // the search succeeds. Instead, we check to see whether search_result is still NULL.
+ if (search_result == NULL) {
+ syslog(LOG_DEBUG, "ldap: zero search results were returned");
+ ldap_unbind(ldserver);
+ return;
+ }
+
+ syslog(LOG_DEBUG, "ldap: %d entries returned", ldap_count_entries(ldserver, search_result));
+ for (entry=ldap_first_entry(ldserver, search_result); entry!=NULL; entry=ldap_next_entry(ldserver, entry)) {
+ user_dn = ldap_get_dn(ldserver, entry);
+ if (user_dn) {
+ syslog(LOG_DEBUG, "ldap: found %s", user_dn);
+
+ int fullname_size = 256;
+ char fullname[256] = { 0 } ;
+ uid_t uid = (-1);
+ char new_emailaddrs[512] = { 0 } ;
+
+ uid = derive_uid_from_ldap(ldserver, entry);
+ derive_fullname_from_ldap_result(fullname, fullname_size, ldserver, entry);
+ syslog(LOG_DEBUG, "ldap: display name: <%s> , uid = <%d>", fullname, uid);
+
+ // now create or update the user
+ int found_user;
+ struct ctdluser usbuf;
+
+ found_user = getuserbyuid(&usbuf, uid);
+ if (found_user != 0) {
+ create_user(fullname, CREATE_USER_DO_NOT_BECOME_USER, uid);
+ found_user = getuserbyuid(&usbuf, uid);
+ strcpy(fullname, usbuf.fullname);
+ }
+
+ if (found_user == 0) { // user record exists
+ // now update the account email addresses if necessary
+ if (CtdlGetConfigInt("c_ldap_sync_email_addrs") > 0) {
+ if (extract_email_addresses_from_ldap(user_dn, new_emailaddrs) == 0) {
+ if (strcmp(usbuf.emailaddrs, new_emailaddrs)) { // update only if changed
+ CtdlSetEmailAddressesForUser(usbuf.fullname, new_emailaddrs);
+ }
+ }
+ }
+ }
+ ldap_memfree(user_dn);
+ }
+ }
+
+ // free the results
+ ldap_msgfree(search_result);
+
+ // unbind so we can go back in as the authenticating user
+ ldap_unbind(ldserver);
+}
--- /dev/null
+/*
+ * Functions which handle hostname/address lookups and resolution
+ *
+ * Copyright (c) 1987-2019 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.
+ */
+
+#include "sysdep.h"
+#include <string.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <libcitadel.h>
+
+#include "context.h"
+#ifdef HAVE_RESOLV_H
+#include <arpa/nameser.h>
+#ifdef HAVE_ARPA_NAMESER_COMPAT_H
+#include <arpa/nameser_compat.h>
+#endif
+#include <resolv.h>
+#endif
+
+#include "domain.h"
+#include "locate_host.h"
+
+/* START: some missing macros on OpenBSD 3.9 */
+#ifndef NS_CMPRSFLGS
+#define NS_CMPRSFLGS 0xc0
+#endif
+#if !defined(NS_MAXCDNAME) && defined (MAXCDNAME)
+#define NS_MAXCDNAME MAXCDNAME
+#endif
+#if !defined(NS_INT16SZ) && defined(INT16SZ)
+#define NS_INT16SZ INT16SZ
+#define NS_INT32SZ INT32SZ
+#endif
+#ifndef NS_GET16
+# define NS_GET16 GETSHORT
+#endif
+/* END: some missing macros on OpenBSD 3.9 */
+
+
+/*
+ * Given an open client socket, return the host name and IP address at the other end.
+ * (IPv4 and IPv6 compatible)
+ */
+void locate_host(char *tbuf, size_t n, char *abuf, size_t na, int client_socket)
+{
+ struct sockaddr_in6 clientaddr;
+ unsigned int addrlen = sizeof(clientaddr);
+
+ tbuf[0] = 0;
+ abuf[0] = 0;
+
+ getpeername(client_socket, (struct sockaddr *)&clientaddr, &addrlen);
+ getnameinfo((struct sockaddr *)&clientaddr, addrlen, tbuf, n, NULL, 0, 0);
+ getnameinfo((struct sockaddr *)&clientaddr, addrlen, abuf, na, NULL, 0, NI_NUMERICHOST);
+
+ /* Convert IPv6-mapped IPv4 addresses back to traditional dotted quad.
+ *
+ * Other code here, such as the RBL check, will expect IPv4 addresses to be represented
+ * as dotted-quad, even if they come in over a hybrid IPv6/IPv4 socket.
+ */
+ if ( (strlen(abuf) > 7) && (!strncasecmp(abuf, "::ffff:", 7)) ) {
+ if (!strcmp(abuf, tbuf)) strcpy(tbuf, &tbuf[7]);
+ strcpy(abuf, &abuf[7]);
+ }
+}
+
+
+/*
+ * RBL check written by Edward S. Marshall [http://rblcheck.sourceforge.net]
+ */
+#define RESULT_SIZE 4096 /* What is the longest result text we support? */
+int rblcheck_backend(char *domain, char *txtbuf, int txtbufsize) {
+ int a, b, c;
+ char *result = NULL;
+ u_char fixedans[ PACKETSZ ];
+ u_char *answer;
+ int need_to_free_answer = 0;
+ const u_char *cp;
+ u_char *rp;
+ const u_char *cend;
+ const u_char *rend;
+ int len;
+ char *p = NULL;
+ static int res_initted = 0;
+
+ if (!res_initted) { /* only have to do this once */
+ res_init();
+ res_initted = 1;
+ }
+
+ /* Make our DNS query. */
+ answer = fixedans;
+ if (server_shutting_down) {
+ if (txtbuf != NULL) {
+ snprintf(txtbuf, txtbufsize, "System shutting down");
+ }
+ return (1);
+ }
+ len = res_query(domain, C_IN, T_A, answer, PACKETSZ);
+
+ /* Was there a problem? If so, the domain doesn't exist. */
+ if (len == -1) {
+ if (txtbuf != NULL) {
+ strcpy(txtbuf, "");
+ }
+ return(0);
+ }
+
+ if (len > PACKETSZ) {
+ answer = malloc(len);
+ need_to_free_answer = 1;
+ len = res_query(domain, C_IN, T_A, answer, len);
+ if( len == -1 ) {
+ if (txtbuf != NULL) {
+ snprintf(txtbuf, txtbufsize, "Message rejected due to known spammer source IP address");
+ }
+ if (need_to_free_answer) free(answer);
+ return(1);
+ }
+ }
+ if (server_shutting_down) {
+ if (txtbuf != NULL) {
+ snprintf(txtbuf, txtbufsize, "System shutting down");
+ }
+ if (need_to_free_answer) free(answer);
+ return (1);
+ }
+
+ result = (char *) malloc(RESULT_SIZE);
+ result[0] = '\0';
+
+ /* Make another DNS query for textual data; this shouldn't
+ * be a performance hit, since it'll now be cached at the
+ * nameserver we're using.
+ */
+ len = res_query(domain, C_IN, T_TXT, answer, PACKETSZ);
+ if (server_shutting_down) {
+ if (txtbuf != NULL) {
+ snprintf(txtbuf, txtbufsize, "System shutting down");
+ }
+ if (need_to_free_answer) free(answer);
+ free(result);
+ return (1);
+ }
+
+ /* Just in case there's no TXT record... */
+ if (len ==(-1)) {
+ if (txtbuf != NULL) {
+ snprintf(txtbuf, txtbufsize, "Message rejected due to known spammer source IP address");
+ }
+ if (need_to_free_answer) free(answer);
+ free(result);
+ return(1);
+ }
+
+ /* Skip the header and the address we queried. */
+ cp = answer + sizeof( HEADER );
+ while( *cp != '\0' ) {
+ a = *cp++;
+ while( a-- )
+ cp++;
+ }
+
+ /* This seems to be a bit of magic data that we need to
+ * skip. I wish there were good online documentation
+ * for programming for libresolv, so I'd know what I'm
+ * skipping here. Anyone reading this, feel free to
+ * enlighten me.
+ */
+ cp += 1 + NS_INT16SZ + NS_INT32SZ;
+
+ /* Skip the type, class and ttl. */
+ cp += (NS_INT16SZ * 2) + NS_INT32SZ;
+
+ /* Get the length and end of the buffer. */
+ NS_GET16(c, cp);
+ cend = cp + c;
+
+ /* Iterate over any multiple answers we might have. In
+ * this context, it's unlikely, but anyway.
+ */
+ rp = (u_char *) result;
+ rend = (u_char *) result + RESULT_SIZE - 1;
+ while (cp < cend && rp < rend) {
+ a = *cp++;
+ if (a != 0) {
+ for (b = a; b > 0 && cp < cend && rp < rend; b--) {
+ if (*cp == '\n' || *cp == '"' || *cp == '\\') {
+ *rp++ = '\\';
+ }
+ *rp++ = *cp++;
+ }
+ }
+ }
+ *rp = '\0';
+ if (txtbuf != NULL) {
+ long len;
+ len = snprintf(txtbuf, txtbufsize, "%s", result);
+
+ /* Remove nonprintable characters */
+ for (p = txtbuf; *p != '\0'; p++) {
+ if (!isprint(*p)) {
+ memmove (p,
+ p + 1,
+ len - (p - txtbuf) - 1);
+ }
+ }
+ }
+ if (need_to_free_answer) free(answer);
+ free(result);
+ return(1);
+}
+
+
+/*
+ * Check to see if the client host is on some sort of spam list (RBL)
+ * If spammer, returns nonzero and places reason in 'message_to_spammer'
+ */
+int rbl_check(char *cs_addr, char *message_to_spammer)
+{
+ char tbuf[256] = "";
+ int suffix_pos = 0;
+ int rbl;
+ int rc;
+ int num_rbl;
+ char rbl_domains[SIZ];
+ char txt_answer[1024];
+ struct timeval tx_start;
+ struct timeval tx_finish;
+
+ rc = 0;
+ strcpy(message_to_spammer, "ok");
+ gettimeofday(&tx_start, NULL); /* start a stopwatch for performance timing */
+
+ if ((strchr(cs_addr, '.')) && (!strchr(cs_addr, ':'))) {
+ int a1, a2, a3, a4;
+
+ sscanf(cs_addr, "%d.%d.%d.%d", &a1, &a2, &a3, &a4);
+ snprintf(tbuf, sizeof tbuf, "%d.%d.%d.%d.", a4, a3, a2, a1);
+ suffix_pos = strlen(tbuf);
+ }
+ else if ((!strchr(cs_addr, '.')) && (strchr(cs_addr, ':'))) {
+ int num_colons = 0;
+ int i = 0;
+ char workbuf[sizeof tbuf];
+ char *ptr;
+
+ /* tedious code to expand and reverse an IPv6 address */
+ safestrncpy(tbuf, cs_addr, sizeof tbuf);
+ num_colons = haschar(tbuf, ':');
+ if ((num_colons < 2) || (num_colons > 7))
+ goto finish_rbl; /* badly formed address */
+
+ /* expand the "::" shorthand */
+ while (num_colons < 7) {
+ ptr = strstr(tbuf, "::");
+ if (!ptr)
+ goto finish_rbl; /* badly formed address */
+
+ ++ptr;
+ strcpy(workbuf, ptr);
+ strcpy(ptr, ":");
+ strcat(ptr, workbuf);
+ ++num_colons;
+ }
+
+ /* expand to 32 hex characters with no colons */
+ strcpy(workbuf, tbuf);
+ strcpy(tbuf, "00000000000000000000000000000000");
+ for (i=0; i<8; ++i) {
+ char tokbuf[5];
+ extract_token(tokbuf, workbuf, i, ':', sizeof tokbuf);
+ memcpy(&tbuf[ (i*4) + (4-strlen(tokbuf)) ], tokbuf, strlen(tokbuf) );
+ }
+ if (strlen(tbuf) != 32) {
+ goto finish_rbl;
+ }
+
+ /* now reverse it and add dots */
+ strcpy(workbuf, tbuf);
+ for (i=0; i<32; ++i) {
+ tbuf[i*2] = workbuf[31-i];
+ tbuf[(i*2)+1] = '.';
+ }
+ tbuf[64] = 0;
+ suffix_pos = 64;
+ }
+ else {
+ goto finish_rbl; /* unknown address format */
+ }
+
+ /* See if we have any RBL domains configured */
+ num_rbl = get_hosts(rbl_domains, "rbl");
+ if (num_rbl < 1)
+ {
+ goto finish_rbl;
+ }
+
+ /* Try all configured RBL's */
+ for (rbl=0; rbl<num_rbl; ++rbl) {
+ extract_token(&tbuf[suffix_pos], rbl_domains, rbl, '|', (sizeof tbuf - suffix_pos));
+
+ if (rblcheck_backend(tbuf, txt_answer, sizeof txt_answer)) {
+ strcpy(message_to_spammer, txt_answer);
+ syslog(LOG_INFO, "RBL: %s %s", cs_addr, txt_answer);
+ rc = 1;
+ }
+ }
+finish_rbl:
+ /* How long did this transaction take? */
+ gettimeofday(&tx_finish, NULL);
+
+ syslog(LOG_WARNING, "rbl: %s [%ld.%06ld] %s",
+ cs_addr,
+ ((tx_finish.tv_sec*1000000 + tx_finish.tv_usec) - (tx_start.tv_sec*1000000 + tx_start.tv_usec)) / 1000000,
+ ((tx_finish.tv_sec*1000000 + tx_finish.tv_usec) - (tx_start.tv_sec*1000000 + tx_start.tv_usec)) % 1000000,
+ (rc)?"found":"none found"
+ );
+
+ return rc;
+}
+
+
+/*
+ * Convert a host name to a dotted quad address.
+ * Returns zero on success or nonzero on failure.
+ *
+ * FIXME this is obviously not IPv6 compatible.
+ */
+int hostname_to_dotted_quad(char *addr, char *host) {
+ struct hostent *ch;
+ const char *i;
+ int a1, a2, a3, a4;
+
+ ch = gethostbyname(host);
+ if (ch == NULL) {
+ strcpy(addr, "0.0.0.0");
+ return(1);
+ }
+
+ i = (const char *) ch->h_addr_list[0];
+ a1 = ((*i++) & 0xff);
+ a2 = ((*i++) & 0xff);
+ a3 = ((*i++) & 0xff);
+ a4 = ((*i++) & 0xff);
+ sprintf(addr, "%d.%d.%d.%d", a1, a2, a3, a4);
+ return(0);
+}
--- /dev/null
+void locate_host(char *tbuf, size_t n, char *abuf, size_t na, int client_socket);
+int rbl_check(char *cs_addr, char *message_to_spammer);
+int hostname_to_dotted_quad(char *addr, char *host);
+int rblcheck_backend(char *domain, char *txtbuf, int txtbufsize);
--- /dev/null
+/*
+ * Autocompletion of email recipients, etc.
+ *
+ * Copyright (c) 1987-2022 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.
+ */
+
+
+#include "../../ctdl_module.h"
+#include "serv_autocompletion.h"
+#include "../../config.h"
+
+
+/*
+ * Convert a structured name into a friendly name. Caller must free the
+ * returned pointer.
+ */
+char *n_to_fn(char *value) {
+ char *nnn = NULL;
+ int i;
+
+ nnn = malloc(strlen(value) + 10);
+ strcpy(nnn, "");
+ extract_token(&nnn[strlen(nnn)] , value, 3, ';', 999);
+ strcat(nnn, " ");
+ extract_token(&nnn[strlen(nnn)] , value, 1, ';', 999);
+ strcat(nnn, " ");
+ extract_token(&nnn[strlen(nnn)] , value, 2, ';', 999);
+ strcat(nnn, " ");
+ extract_token(&nnn[strlen(nnn)] , value, 0, ';', 999);
+ strcat(nnn, " ");
+ extract_token(&nnn[strlen(nnn)] , value, 4, ';', 999);
+ strcat(nnn, " ");
+ for (i=0; i<strlen(nnn); ++i) {
+ if (!strncmp(&nnn[i], " ", 2)) strcpy(&nnn[i], &nnn[i+1]);
+ }
+ striplt(nnn);
+ return(nnn);
+}
+
+
+
+
+/*
+ * Back end for cmd_auto()
+ */
+void hunt_for_autocomplete(long msgnum, char *search_string) {
+ struct CtdlMessage *msg;
+ struct vCard *v;
+ char *value = NULL;
+ char *value2 = NULL;
+ int i = 0;
+ char *nnn = NULL;
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) return;
+
+ v = vcard_load(msg->cm_fields[eMesageText]);
+ CM_Free(msg);
+
+ /*
+ * Try to match from a friendly name (the "fn" field). If there is
+ * a match, return the entry in the form of:
+ * Display Name <user@domain.org>
+ */
+ value = vcard_get_prop(v, "fn", 0, 0, 0);
+ if (value != NULL) if (bmstrcasestr(value, search_string)) {
+ value2 = vcard_get_prop(v, "email", 1, 0, 0);
+ if (value2 == NULL) value2 = "";
+ cprintf("%s <%s>\n", value, value2);
+ vcard_free(v);
+ return;
+ }
+
+ /*
+ * Try to match from a structured name (the "n" field). If there is
+ * a match, return the entry in the form of:
+ * Display Name <user@domain.org>
+ */
+ value = vcard_get_prop(v, "n", 0, 0, 0);
+ if (value != NULL) if (bmstrcasestr(value, search_string)) {
+
+ value2 = vcard_get_prop(v, "email", 1, 0, 0);
+ if (value2 == NULL) value2 = "";
+ nnn = n_to_fn(value);
+ cprintf("%s <%s>\n", nnn, value2);
+ free(nnn);
+ vcard_free(v);
+ return;
+ }
+
+ /*
+ * Try a partial match on all listed email addresses.
+ */
+ i = 0;
+ while (value = vcard_get_prop(v, "email", 1, i++, 0), value != NULL) {
+ if (bmstrcasestr(value, search_string)) {
+ if (vcard_get_prop(v, "fn", 0, 0, 0)) {
+ cprintf("%s <%s>\n", vcard_get_prop(v, "fn", 0, 0, 0), value);
+ }
+ else if (vcard_get_prop(v, "n", 0, 0, 0)) {
+ nnn = n_to_fn(vcard_get_prop(v, "n", 0, 0, 0));
+ cprintf("%s <%s>\n", nnn, value);
+ free(nnn);
+
+ }
+ else {
+ cprintf("%s\n", value);
+ }
+ vcard_free(v);
+ return;
+ }
+ }
+
+ vcard_free(v);
+}
+
+
+
+/*
+ * Attempt to autocomplete an address based on a partial...
+ */
+void cmd_auto(char *argbuf) {
+ char hold_rm[ROOMNAMELEN];
+ char search_string[256];
+ long *msglist = NULL;
+ int num_msgs = 0;
+ long *fts_msgs = NULL;
+ int fts_num_msgs = 0;
+ struct cdbdata *cdbfr;
+ int r = 0;
+ int i = 0;
+ int j = 0;
+ int search_match = 0;
+ char *rooms_to_try[] = { USERCONTACTSROOM, ADDRESS_BOOK_ROOM };
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+ extract_token(search_string, argbuf, 0, '|', sizeof search_string);
+ if (IsEmptyStr(search_string)) {
+ cprintf("%d You supplied an empty partial.\n",
+ ERROR + ILLEGAL_VALUE);
+ return;
+ }
+
+ strcpy(hold_rm, CC->room.QRname); /* save current room */
+ cprintf("%d try these:\n", LISTING_FOLLOWS);
+
+ /*
+ * Gather up message pointers in rooms containing vCards
+ */
+ for (r=0; r < (sizeof(rooms_to_try) / sizeof(char *)); ++r) {
+ if (CtdlGetRoom(&CC->room, rooms_to_try[r]) == 0) {
+ cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
+ if (cdbfr != NULL) {
+ msglist = realloc(msglist, (num_msgs * sizeof(long)) + cdbfr->len + 1);
+ memcpy(&msglist[num_msgs], cdbfr->ptr, cdbfr->len);
+ num_msgs += (cdbfr->len / sizeof(long));
+ cdb_free(cdbfr);
+ }
+ }
+ }
+
+ /*
+ * Search-reduce the results if we have the full text index available
+ */
+ if (CtdlGetConfigInt("c_enable_fulltext")) {
+ CtdlModuleDoSearch(&fts_num_msgs, &fts_msgs, search_string, "fulltext");
+ if (fts_msgs) {
+ for (i=0; i<num_msgs; ++i) {
+ search_match = 0;
+ for (j=0; j<fts_num_msgs; ++j) {
+ if (msglist[i] == fts_msgs[j]) {
+ search_match = 1;
+ j = fts_num_msgs + 1; /* end the search */
+ }
+ }
+ if (!search_match) {
+ msglist[i] = 0; /* invalidate this result */
+ }
+ }
+ free(fts_msgs);
+ }
+ else {
+ /* If no results, invalidate the whole list */
+ free(msglist);
+ msglist = NULL;
+ num_msgs = 0;
+ }
+ }
+
+ /*
+ * Now output the ones that look interesting
+ */
+ if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
+ if (msglist[i] != 0) {
+ hunt_for_autocomplete(msglist[i], search_string);
+ }
+ }
+
+ cprintf("000\n");
+ if (strcmp(CC->room.QRname, hold_rm)) {
+ CtdlGetRoom(&CC->room, hold_rm); /* return to saved room */
+ }
+
+ if (msglist) {
+ free(msglist);
+ }
+
+}
+
+
+char *ctdl_module_init_autocompletion(void) {
+ if (!threading) {
+ CtdlRegisterProtoHook(cmd_auto, "AUTO", "Do recipient autocompletion");
+ }
+ /* return our module name for the log */
+ return "autocompletion";
+}
--- /dev/null
+/*
+ * Copyright (c) 1987-2012 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.
+ */
+
+char *serv_autocompletion_init(void);
--- /dev/null
+/*
+ * This module implements server commands related to the display and
+ * manipulation of user "bio" files.
+ *
+ * Copyright (c) 1987-2022 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 as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ */
+
+#include "../../ctdl_module.h"
+#include "../../config.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+
+/*
+ * Command to enter user bio (profile) in plain text.
+ * This is deprecated , or at least it will be when its replacement is written :)
+ * I want commands to get/set bio in full MIME wonderfulness.
+ */
+void cmd_ebio(char *cmdbuf) {
+ char buf[SIZ];
+
+ unbuffer_output();
+
+ if (!(CC->logged_in)) {
+ cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
+ return;
+ }
+
+ StrBuf *NewProfile = NewStrBufPlain("Content-type: text/plain; charset=UTF-8\nContent-transfer-encoding: 8bit\n\n", -1);
+
+ cprintf("%d Transmit user profile in plain text now.\n", SEND_LISTING);
+ while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
+ StrBufAppendBufPlain(NewProfile, buf, -1, 0);
+ StrBufAppendBufPlain(NewProfile, HKEY("\n"), 0);
+ }
+
+ /* we have read the new profile from the user , now save it */
+ long old_msgnum = CC->user.msgnum_bio;
+ char userconfigroomname[ROOMNAMELEN];
+ CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &CC->user, USERCONFIGROOM);
+ long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, ChrPtr(NewProfile), FMT_RFC822, "Profile submitted with EBIO command");
+ FreeStrBuf(&NewProfile);
+ CtdlGetUserLock(&CC->user, CC->curr_user);
+ CC->user.msgnum_bio = new_msgnum;
+ CtdlPutUserLock(&CC->user);
+ if (old_msgnum > 0) {
+ syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
+ CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
+ }
+}
+
+
+/*
+ * Command to read user bio (profile) in plain text.
+ * This is deprecated , or at least it will be when its replacement is written :)
+ * I want commands to get/set bio in full MIME wonderfulness.
+ */
+void cmd_rbio(char *cmdbuf)
+{
+ struct ctdluser ruser;
+ char buf[SIZ];
+
+ extract_token(buf, cmdbuf, 0, '|', sizeof buf);
+ if (CtdlGetUser(&ruser, buf) != 0) {
+ cprintf("%d No such user.\n",ERROR + NO_SUCH_USER);
+ return;
+ }
+
+ cprintf("%d OK|%s|%ld|%d|%ld|%ld|%ld\n", LISTING_FOLLOWS,
+ ruser.fullname, ruser.usernum, ruser.axlevel,
+ (long)ruser.lastcall, ruser.timescalled, ruser.posted);
+
+ struct CtdlMessage *msg = CtdlFetchMessage(ruser.msgnum_bio, 1);
+ if (msg != NULL) {
+ CtdlOutputPreLoadedMsg(msg, MT_CITADEL, HEADERS_NONE, 0, 0, 0);
+ CM_Free(msg);
+ }
+ cprintf("000\n");
+}
+
+
+/*
+ * Import function called by import_old_bio_files() for a single user
+ */
+void import_one_bio_file(char *username, long usernum, char *path)
+{
+ syslog(LOG_DEBUG, "Import legacy bio for %s, usernum=%ld, filename=%s", username, usernum, path);
+
+ FILE *fp = fopen(path, "r");
+ if (!fp) return;
+
+ fseek(fp, 0, SEEK_END);
+ long data_length = ftell(fp);
+
+ if (data_length >= 1) {
+ rewind(fp);
+ char *unencoded_data = malloc(data_length);
+ if (unencoded_data) {
+ fread(unencoded_data, data_length, 1, fp);
+ char *encoded_data = malloc((data_length * 2) + 100);
+ if (encoded_data) {
+ sprintf(encoded_data, "Content-type: text/plain; charset=UTF-8\nContent-transfer-encoding: base64\n\n");
+ CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
+
+ char userconfigroomname[ROOMNAMELEN];
+ struct ctdluser usbuf;
+
+ if (CtdlGetUser(&usbuf, username) == 0) { // no need to lock it , we are still initializing
+ long old_msgnum = usbuf.msgnum_bio;
+ CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &usbuf, USERCONFIGROOM);
+ long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, encoded_data, FMT_RFC822, "Profile imported from bio");
+ syslog(LOG_DEBUG, "Message %ld is now the profile for %s", new_msgnum, username);
+ usbuf.msgnum_bio = new_msgnum;
+ CtdlPutUser(&usbuf);
+ unlink(path); // delete the old file , it's in the database now
+ if (old_msgnum > 0) {
+ syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
+ CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
+ }
+ }
+ free(encoded_data);
+ }
+ free(unencoded_data);
+ }
+ }
+ fclose(fp);
+}
+
+
+/*
+ * Look for old-format "bio" files and import them into the message base
+ */
+void import_old_bio_files(void)
+{
+ DIR *filedir = NULL;
+ struct dirent *filedir_entry;
+ size_t d_namelen;
+ struct ctdluser usbuf;
+ long usernum = 0;
+ int d_type = 0;
+ struct stat s;
+ char path[PATH_MAX];
+
+
+ syslog(LOG_DEBUG, "Importing old style bio files into the message base");
+ filedir = opendir("bio");
+ if (filedir == NULL) {
+ return;
+ }
+ while ( (filedir_entry = readdir(filedir)) , (filedir_entry != NULL))
+ {
+#ifdef _DIRENT_HAVE_D_NAMLEN
+ d_namelen = filedir_entry->d_namlen;
+
+#else
+ d_namelen = strlen(filedir_entry->d_name);
+#endif
+
+#ifdef _DIRENT_HAVE_D_TYPE
+ d_type = filedir_entry->d_type;
+#else
+
+#ifndef DT_UNKNOWN
+#define DT_UNKNOWN 0
+#define DT_DIR 4
+#define DT_REG 8
+#define DT_LNK 10
+
+#define IFTODT(mode) (((mode) & 0170000) >> 12)
+#define DTTOIF(dirtype) ((dirtype) << 12)
+#endif
+ d_type = DT_UNKNOWN;
+#endif
+ if ((d_namelen == 1) &&
+ (filedir_entry->d_name[0] == '.'))
+ continue;
+
+ if ((d_namelen == 2) &&
+ (filedir_entry->d_name[0] == '.') &&
+ (filedir_entry->d_name[1] == '.'))
+ continue;
+
+ snprintf(path, PATH_MAX, "bio/%s", filedir_entry->d_name);
+ if (d_type == DT_UNKNOWN) {
+ if (lstat(path, &s) == 0) {
+ d_type = IFTODT(s.st_mode);
+ }
+ }
+ switch (d_type)
+ {
+ case DT_DIR:
+ break;
+ case DT_LNK:
+ case DT_REG:
+ usernum = atol(filedir_entry->d_name);
+ if (CtdlGetUserByNumber(&usbuf, usernum) == 0) {
+ import_one_bio_file(usbuf.fullname, usernum, path);
+ }
+ }
+ }
+ closedir(filedir);
+ rmdir("bio");
+}
+
+
+
+char *ctdl_module_init_bio(void) {
+ if (!threading) {
+ import_old_bio_files();
+ CtdlRegisterProtoHook(cmd_ebio, "EBIO", "Enter your bio");
+ CtdlRegisterProtoHook(cmd_rbio, "RBIO", "Read a user's bio");
+ }
+ /* return our module name for the log */
+ return "bio";
+}
--- /dev/null
+// Support for blog rooms
+//
+// Copyright (c) 1999-2022 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 as published
+// by the Free Software Foundation; either version 3 of the License, or
+// (at your option) any later version.
+//
+// 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.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../control.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../serv_vcard.h"
+#include "../../citadel_ldap.h"
+#include "../../ctdl_module.h"
+
+// Pre-save hook for saving a message in a blog room.
+// (Do we want to only do this for top-level messages?)
+int blog_upload_beforesave(struct CtdlMessage *msg, struct recptypes *recp) {
+
+ // Only run this hook for blog rooms
+ if (CC->room.QRdefaultview != VIEW_BLOG) {
+ return(0);
+ }
+
+ // If the message doesn't have an EUID, give it one.
+ if (CM_IsEmpty(msg, eExclusiveID)) {
+ char uuid[SIZ];
+ generate_uuid(uuid);
+ CM_SetField(msg, eExclusiveID, uuid, strlen(uuid));
+ }
+
+ // We also want to define a maximum length, whether we generated it or not.
+ CM_CutFieldAt(msg, eExclusiveID, BLOG_EUIDBUF_SIZE - 1);
+
+ // Now allow the save to complete.
+ return(0);
+}
+
+
+// Module initialization
+char *ctdl_module_init_blog(void) {
+ if (!threading) {
+ CtdlRegisterMessageHook(blog_upload_beforesave, EVT_BEFORESAVE);
+ }
+
+ // return our module id for the log
+ return "blog";
+}
--- /dev/null
+/*
+ * This module implements iCalendar object processing and the Calendar>
+ * room on a Citadel server. It handles iCalendar objects using the
+ * iTIP protocol. See RFCs 2445 and 2446.
+ *
+ * Copyright (c) 1987-2022 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.
+ */
+
+#define PRODID "-//Citadel//NONSGML Citadel Calendar//EN"
+
+#include "../../ctdl_module.h"
+#include <libical/ical.h>
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "serv_calendar.h"
+#include "../../room_ops.h"
+#include "../../euidindex.h"
+#include "../../default_timezone.h"
+#include "../../config.h"
+
+struct ical_respond_data {
+ char desired_partnum[SIZ];
+ icalcomponent *cal;
+};
+
+
+/*
+ * Utility function to create a new VCALENDAR component with some of the
+ * required fields already set the way we like them.
+ */
+icalcomponent *icalcomponent_new_citadel_vcalendar(void) {
+ icalcomponent *encaps;
+
+ encaps = icalcomponent_new_vcalendar();
+ if (encaps == NULL) {
+ syslog(LOG_ERR, "calendar: could not allocate component");
+ return NULL;
+ }
+
+ /* Set the Product ID */
+ icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
+
+ /* Set the Version Number */
+ icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
+
+ return(encaps);
+}
+
+
+/*
+ * Utility function to encapsulate a subcomponent into a full VCALENDAR
+ */
+icalcomponent *ical_encapsulate_subcomponent(icalcomponent *subcomp) {
+ icalcomponent *encaps;
+
+ /* If we're already looking at a full VCALENDAR component,
+ * don't bother ... just return itself.
+ */
+ if (icalcomponent_isa(subcomp) == ICAL_VCALENDAR_COMPONENT) {
+ return subcomp;
+ }
+
+ /* Encapsulate the VEVENT component into a complete VCALENDAR */
+ encaps = icalcomponent_new_citadel_vcalendar();
+ if (encaps == NULL) return NULL;
+
+ /* Encapsulate the subcomponent inside */
+ icalcomponent_add_component(encaps, subcomp);
+
+ /* Return the object we just created. */
+ return(encaps);
+}
+
+
+/*
+ * Write a calendar object into the specified user's calendar room.
+ * If the supplied user is NULL, this function writes the calendar object
+ * to the currently selected room.
+ */
+void ical_write_to_cal(struct ctdluser *u, icalcomponent *cal) {
+ char *ser = NULL;
+ long serlen;
+ icalcomponent *encaps = NULL;
+ struct CtdlMessage *msg = NULL;
+ icalcomponent *tmp=NULL;
+
+ if (cal == NULL) return;
+
+ /* If the supplied object is a subcomponent, encapsulate it in
+ * a full VCALENDAR component, and save that instead.
+ */
+ if (icalcomponent_isa(cal) != ICAL_VCALENDAR_COMPONENT) {
+ tmp = icalcomponent_new_clone(cal);
+ encaps = ical_encapsulate_subcomponent(tmp);
+ ical_write_to_cal(u, encaps);
+ icalcomponent_free(tmp);
+ icalcomponent_free(encaps);
+ return;
+ }
+
+ ser = icalcomponent_as_ical_string_r(cal);
+ if (ser == NULL) return;
+
+ serlen = strlen(ser);
+
+ /* If the caller supplied a user, write to that user's default calendar room */
+ if (u) {
+ /* This handy API function does all the work for us. */
+ CtdlWriteObject(USERCALENDARROOM, /* which room */
+ "text/calendar", /* MIME type */
+ ser, /* data */
+ serlen + 1, /* length */
+ u, /* which user */
+ 0, /* not binary */
+ 0 /* no flags */
+ );
+ }
+
+ /* If the caller did not supply a user, write to the currently selected room */
+ if (!u) {
+ struct CitContext *CCC = CC;
+ StrBuf *MsgBody;
+
+ msg = malloc(sizeof(struct CtdlMessage));
+ memset(msg, 0, sizeof(struct CtdlMessage));
+ msg->cm_magic = CTDLMESSAGE_MAGIC;
+ msg->cm_anon_type = MES_NORMAL;
+ msg->cm_format_type = 4;
+ CM_SetField(msg, eAuthor, CCC->user.fullname, strlen(CCC->user.fullname));
+ CM_SetField(msg, eOriginalRoom, CCC->room.QRname, strlen(CCC->room.QRname));
+
+ MsgBody = NewStrBufPlain(NULL, serlen + 100);
+ StrBufAppendBufPlain(MsgBody, HKEY("Content-type: text/calendar\r\n\r\n"), 0);
+ StrBufAppendBufPlain(MsgBody, ser, serlen, 0);
+
+ CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
+
+ /* Now write the data */
+ CtdlSubmitMsg(msg, NULL, "");
+ CM_Free(msg);
+ }
+
+ /* In either case, now we can free the serialized calendar object */
+ free(ser);
+}
+
+
+/*
+ * Send a reply to a meeting invitation.
+ *
+ * 'request' is the invitation to reply to.
+ * 'action' is the string "accept" or "decline" or "tentative".
+ *
+ */
+void ical_send_a_reply(icalcomponent *request, char *action) {
+ icalcomponent *the_reply = NULL;
+ icalcomponent *vevent = NULL;
+ icalproperty *attendee = NULL;
+ char attendee_string[SIZ];
+ icalproperty *organizer = NULL;
+ char organizer_string[SIZ];
+ icalproperty *summary = NULL;
+ char summary_string[SIZ];
+ icalproperty *me_attend = NULL;
+ struct recptypes *recp = NULL;
+ icalparameter *partstat = NULL;
+ char *serialized_reply = NULL;
+ char *reply_message_text = NULL;
+ const char *ch;
+ struct CtdlMessage *msg = NULL;
+ struct recptypes *valid = NULL;
+
+ *organizer_string = '\0';
+ strcpy(summary_string, "Calendar item");
+
+ if (request == NULL) {
+ syslog(LOG_ERR, "calendar: trying to reply to NULL event");
+ return;
+ }
+
+ the_reply = icalcomponent_new_clone(request);
+ if (the_reply == NULL) {
+ syslog(LOG_ERR, "calendar: cannot clone request");
+ return;
+ }
+
+ /* Change the method from REQUEST to REPLY */
+ icalcomponent_set_method(the_reply, ICAL_METHOD_REPLY);
+
+ vevent = icalcomponent_get_first_component(the_reply, ICAL_VEVENT_COMPONENT);
+ if (vevent != NULL) {
+ /* Hunt for attendees, removing ones that aren't us.
+ * (Actually, remove them all, cloning our own one so we can
+ * re-insert it later)
+ */
+ while (attendee = icalcomponent_get_first_property(vevent,
+ ICAL_ATTENDEE_PROPERTY), (attendee != NULL)
+ ) {
+ ch = icalproperty_get_attendee(attendee);
+ if ((ch != NULL) && !strncasecmp(ch, "MAILTO:", 7)) {
+ safestrncpy(attendee_string, ch + 7, sizeof (attendee_string));
+ striplt(attendee_string);
+ recp = validate_recipients(attendee_string, NULL, 0);
+ if (recp != NULL) {
+ if (!strcasecmp(recp->recp_local, CC->user.fullname)) {
+ if (me_attend) icalproperty_free(me_attend);
+ me_attend = icalproperty_new_clone(attendee);
+ }
+ free_recipients(recp);
+ }
+ }
+
+ /* Remove it... */
+ icalcomponent_remove_property(vevent, attendee);
+ icalproperty_free(attendee);
+ }
+
+ /* We found our own address in the attendee list. */
+ if (me_attend) {
+ /* Change the partstat from NEEDS-ACTION to ACCEPT or DECLINE */
+ icalproperty_remove_parameter_by_kind(me_attend, ICAL_PARTSTAT_PARAMETER);
+
+ if (!strcasecmp(action, "accept")) {
+ partstat = icalparameter_new_partstat(ICAL_PARTSTAT_ACCEPTED);
+ }
+ else if (!strcasecmp(action, "decline")) {
+ partstat = icalparameter_new_partstat(ICAL_PARTSTAT_DECLINED);
+ }
+ else if (!strcasecmp(action, "tentative")) {
+ partstat = icalparameter_new_partstat(ICAL_PARTSTAT_TENTATIVE);
+ }
+
+ if (partstat) icalproperty_add_parameter(me_attend, partstat);
+
+ /* Now insert it back into the vevent. */
+ icalcomponent_add_property(vevent, me_attend);
+ }
+
+ /* Figure out who to send this thing to */
+ organizer = icalcomponent_get_first_property(vevent, ICAL_ORGANIZER_PROPERTY);
+ if (organizer != NULL) {
+ if (icalproperty_get_organizer(organizer)) {
+ strcpy(organizer_string,
+ icalproperty_get_organizer(organizer) );
+ }
+ }
+ if (!strncasecmp(organizer_string, "MAILTO:", 7)) {
+ strcpy(organizer_string, &organizer_string[7]);
+ striplt(organizer_string);
+ } else {
+ strcpy(organizer_string, "");
+ }
+
+ /* Extract the summary string -- we'll use it as the
+ * message subject for the reply
+ */
+ summary = icalcomponent_get_first_property(vevent, ICAL_SUMMARY_PROPERTY);
+ if (summary != NULL) {
+ if (icalproperty_get_summary(summary)) {
+ strcpy(summary_string,
+ icalproperty_get_summary(summary) );
+ }
+ }
+ }
+
+ /* Now generate the reply message and send it out. */
+ serialized_reply = icalcomponent_as_ical_string_r(the_reply);
+ icalcomponent_free(the_reply); /* don't need this anymore */
+ if (serialized_reply == NULL) return;
+
+ reply_message_text = malloc(strlen(serialized_reply) + SIZ);
+ if (reply_message_text != NULL) {
+ sprintf(reply_message_text,
+ "Content-type: text/calendar; charset=\"utf-8\"\r\n\r\n%s\r\n",
+ serialized_reply
+ );
+
+ msg = CtdlMakeMessage(&CC->user,
+ organizer_string, /* to */
+ "", /* cc */
+ CC->room.QRname, 0, FMT_RFC822,
+ "",
+ "",
+ summary_string, /* Use summary for subject */
+ NULL,
+ reply_message_text,
+ NULL);
+
+ if (msg != NULL) {
+ valid = validate_recipients(organizer_string, NULL, 0);
+ CtdlSubmitMsg(msg, valid, "");
+ CM_Free(msg);
+ free_recipients(valid);
+ }
+ }
+ free(serialized_reply);
+}
+
+
+/*
+ * Callback function for mime parser that hunts for calendar content types
+ * and turns them into calendar objects. If something is found, it is placed
+ * in ird->cal, and the caller now owns that memory and is responsible for freeing it.
+ */
+void ical_locate_part(char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+ char *cbid, void *cbuserdata) {
+
+ struct ical_respond_data *ird = NULL;
+
+ ird = (struct ical_respond_data *) cbuserdata;
+
+ /* desired_partnum can be set to "_HUNT_" to have it just look for
+ * the first part with a content type of text/calendar. Otherwise
+ * we have to only process the right one.
+ */
+ if (strcasecmp(ird->desired_partnum, "_HUNT_")) {
+ if (strcasecmp(partnum, ird->desired_partnum)) {
+ return;
+ }
+ }
+
+ if ( (strcasecmp(cbtype, "text/calendar"))
+ && (strcasecmp(cbtype, "application/ics")) ) {
+ return;
+ }
+
+ if (ird->cal != NULL) {
+ icalcomponent_free(ird->cal);
+ ird->cal = NULL;
+ }
+
+ ird->cal = icalcomponent_new_from_string(content);
+}
+
+
+/*
+ * Respond to a meeting request.
+ */
+void ical_respond(long msgnum, char *partnum, char *action) {
+ struct CtdlMessage *msg = NULL;
+ struct ical_respond_data ird;
+
+ if (
+ (strcasecmp(action, "accept"))
+ && (strcasecmp(action, "decline"))
+ ) {
+ cprintf("%d Action must be 'accept' or 'decline'\n",
+ ERROR + ILLEGAL_VALUE
+ );
+ return;
+ }
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) {
+ cprintf("%d Message %ld not found.\n",
+ ERROR + ILLEGAL_VALUE,
+ (long)msgnum
+ );
+ return;
+ }
+
+ memset(&ird, 0, sizeof ird);
+ strcpy(ird.desired_partnum, partnum);
+ mime_parser(CM_RANGE(msg, eMesageText),
+ *ical_locate_part, /* callback function */
+ NULL, NULL,
+ (void *) &ird, /* user data */
+ 0
+ );
+
+ /* We're done with the incoming message, because we now have a
+ * calendar object in memory.
+ */
+ CM_Free(msg);
+
+ /*
+ * Here is the real meat of this function. Handle the event.
+ */
+ if (ird.cal != NULL) {
+ /* Save this in the user's calendar if necessary */
+ if (!strcasecmp(action, "accept")) {
+ ical_write_to_cal(&CC->user, ird.cal);
+ }
+
+ /* Send a reply if necessary */
+ if (icalcomponent_get_method(ird.cal) == ICAL_METHOD_REQUEST) {
+ ical_send_a_reply(ird.cal, action);
+ }
+
+ /* We used to delete the invitation after handling it.
+ * We don't do that anymore, but here is the code that handled it:
+ * CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
+ */
+
+ /* Free the memory we allocated and return a response. */
+ icalcomponent_free(ird.cal);
+ ird.cal = NULL;
+ cprintf("%d ok\n", CIT_OK);
+ return;
+ }
+ else {
+ cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
+ return;
+ }
+
+ /* should never get here */
+}
+
+
+/*
+ * Figure out the UID of the calendar event being referred to in a
+ * REPLY object. This function is recursive.
+ */
+void ical_learn_uid_of_reply(char *uidbuf, icalcomponent *cal) {
+ icalcomponent *subcomponent;
+ icalproperty *p;
+
+ /* If this object is a REPLY, then extract the UID. */
+ if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
+ p = icalcomponent_get_first_property(cal, ICAL_UID_PROPERTY);
+ if (p != NULL) {
+ strcpy(uidbuf, icalproperty_get_comment(p));
+ }
+ }
+
+ /* Otherwise, recurse through any VEVENT subcomponents. We do NOT want the
+ * UID of the reply; we want the UID of the invitation being replied to.
+ */
+ for (subcomponent = icalcomponent_get_first_component(cal, ICAL_VEVENT_COMPONENT);
+ subcomponent != NULL;
+ subcomponent = icalcomponent_get_next_component(cal, ICAL_VEVENT_COMPONENT) ) {
+ ical_learn_uid_of_reply(uidbuf, subcomponent);
+ }
+}
+
+
+/*
+ * ical_update_my_calendar_with_reply() refers to this callback function; when we
+ * locate the message containing the calendar event we're replying to, this function
+ * gets called. It basically just sticks the message number in a supplied buffer.
+ */
+void ical_hunt_for_event_to_update(long msgnum, void *data) {
+ long *msgnumptr;
+
+ msgnumptr = (long *) data;
+ *msgnumptr = msgnum;
+}
+
+
+struct original_event_container {
+ icalcomponent *c;
+};
+
+/*
+ * Callback function for mime parser that hunts for calendar content types
+ * and turns them into calendar objects (called by ical_update_my_calendar_with_reply()
+ * to fetch the object being updated)
+ */
+void ical_locate_original_event(char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+ char *cbid, void *cbuserdata) {
+
+ struct original_event_container *oec = NULL;
+
+ if ( (strcasecmp(cbtype, "text/calendar"))
+ && (strcasecmp(cbtype, "application/ics")) ) {
+ return;
+ }
+ oec = (struct original_event_container *) cbuserdata;
+ if (oec->c != NULL) {
+ icalcomponent_free(oec->c);
+ }
+ oec->c = icalcomponent_new_from_string(content);
+}
+
+
+/*
+ * Merge updated attendee information from a REPLY into an existing event.
+ */
+void ical_merge_attendee_reply(icalcomponent *event, icalcomponent *reply) {
+ icalcomponent *c;
+ icalproperty *e_attendee, *r_attendee;
+
+ /* First things first. If we're not looking at a VEVENT component,
+ * recurse through subcomponents until we find one.
+ */
+ if (icalcomponent_isa(event) != ICAL_VEVENT_COMPONENT) {
+ for (c = icalcomponent_get_first_component(event, ICAL_VEVENT_COMPONENT);
+ c != NULL;
+ c = icalcomponent_get_next_component(event, ICAL_VEVENT_COMPONENT) ) {
+ ical_merge_attendee_reply(c, reply);
+ }
+ return;
+ }
+
+ /* Now do the same thing with the reply.
+ */
+ if (icalcomponent_isa(reply) != ICAL_VEVENT_COMPONENT) {
+ for (c = icalcomponent_get_first_component(reply, ICAL_VEVENT_COMPONENT);
+ c != NULL;
+ c = icalcomponent_get_next_component(reply, ICAL_VEVENT_COMPONENT) ) {
+ ical_merge_attendee_reply(event, c);
+ }
+ return;
+ }
+
+ /* Clone the reply, because we're going to rip its guts out. */
+ reply = icalcomponent_new_clone(reply);
+
+ /* At this point we're looking at the correct subcomponents.
+ * Iterate through the attendees looking for a match.
+ */
+STARTOVER:
+ for (e_attendee = icalcomponent_get_first_property(event, ICAL_ATTENDEE_PROPERTY);
+ e_attendee != NULL;
+ e_attendee = icalcomponent_get_next_property(event, ICAL_ATTENDEE_PROPERTY)) {
+
+ for (r_attendee = icalcomponent_get_first_property(reply, ICAL_ATTENDEE_PROPERTY);
+ r_attendee != NULL;
+ r_attendee = icalcomponent_get_next_property(reply, ICAL_ATTENDEE_PROPERTY)) {
+
+ /* Check to see if these two attendees match...
+ */
+ const char *e, *r;
+ e = icalproperty_get_attendee(e_attendee);
+ r = icalproperty_get_attendee(r_attendee);
+
+ if ((e != NULL) &&
+ (r != NULL) &&
+ !strcasecmp(e, r)) {
+ /* ...and if they do, remove the attendee from the event
+ * and replace it with the attendee from the reply. (The
+ * reply's copy will have the same address, but an updated
+ * status.)
+ */
+ icalcomponent_remove_property(event, e_attendee);
+ icalproperty_free(e_attendee);
+ icalcomponent_remove_property(reply, r_attendee);
+ icalcomponent_add_property(event, r_attendee);
+
+ /* Since we diddled both sets of attendees, we have to start
+ * the iteration over again. This will not create an infinite
+ * loop because we removed the attendee from the reply. (That's
+ * why we cloned the reply, and that's what we mean by "ripping
+ * its guts out.")
+ */
+ goto STARTOVER;
+ }
+
+ }
+ }
+
+ /* Free the *clone* of the reply. */
+ icalcomponent_free(reply);
+}
+
+
+/*
+ * Handle an incoming RSVP (object with method==ICAL_METHOD_REPLY) for a
+ * calendar event. The object has already been deserialized for us; all
+ * we have to do here is hunt for the event in our calendar, merge in the
+ * updated attendee status, and save it again.
+ *
+ * This function returns 0 on success, 1 if the event was not found in the
+ * user's calendar, or 2 if an internal error occurred.
+ */
+int ical_update_my_calendar_with_reply(icalcomponent *cal) {
+ char uid[SIZ];
+ char hold_rm[ROOMNAMELEN];
+ long msgnum_being_replaced = 0;
+ struct CtdlMessage *msg = NULL;
+ struct original_event_container oec;
+ icalcomponent *original_event;
+ char *serialized_event = NULL;
+ char roomname[ROOMNAMELEN];
+ char *message_text = NULL;
+
+ /* Figure out just what event it is we're dealing with */
+ strcpy(uid, "--==<< InVaLiD uId >>==--");
+ ical_learn_uid_of_reply(uid, cal);
+ syslog(LOG_DEBUG, "calendar: UID of event being replied to is <%s>", uid);
+
+ strcpy(hold_rm, CC->room.QRname); /* save current room */
+
+ if (CtdlGetRoom(&CC->room, USERCALENDARROOM) != 0) {
+ CtdlGetRoom(&CC->room, hold_rm);
+ syslog(LOG_ERR, "calendar: cannot get user calendar room");
+ return(2);
+ }
+
+ /*
+ * Look in the EUID index for a message with
+ * the Citadel EUID set to the value we're looking for. Since
+ * Citadel always sets the message EUID to the iCalendar UID of
+ * the event, this will work.
+ */
+ msgnum_being_replaced = CtdlLocateMessageByEuid(uid, &CC->room);
+
+ CtdlGetRoom(&CC->room, hold_rm); /* return to saved room */
+
+ syslog(LOG_DEBUG, "calendar: msgnum_being_replaced == %ld", msgnum_being_replaced);
+ if (msgnum_being_replaced == 0) {
+ return(1); /* no calendar event found */
+ }
+
+ /* Now we know the ID of the message containing the event being updated.
+ * We don't actually have to delete it; that'll get taken care of by the
+ * server when we save another event with the same UID. This just gives
+ * us the ability to load the event into memory so we can diddle the
+ * attendees.
+ */
+ msg = CtdlFetchMessage(msgnum_being_replaced, 1);
+ if (msg == NULL) {
+ return(2); /* internal error */
+ }
+ oec.c = NULL;
+ mime_parser(CM_RANGE(msg, eMesageText),
+ *ical_locate_original_event, /* callback function */
+ NULL, NULL,
+ &oec, /* user data */
+ 0
+ );
+ CM_Free(msg);
+
+ original_event = oec.c;
+ if (original_event == NULL) {
+ syslog(LOG_ERR, "calendar: original_component is NULL");
+ return(2);
+ }
+
+ /* Merge the attendee's updated status into the event */
+ ical_merge_attendee_reply(original_event, cal);
+
+ /* Serialize it */
+ serialized_event = icalcomponent_as_ical_string_r(original_event);
+ icalcomponent_free(original_event); /* Don't need this anymore. */
+ if (serialized_event == NULL) return(2);
+
+ CtdlMailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM);
+
+ message_text = malloc(strlen(serialized_event) + SIZ);
+ if (message_text != NULL) {
+ sprintf(message_text,
+ "Content-type: text/calendar; charset=\"utf-8\"\r\n\r\n%s\r\n",
+ serialized_event
+ );
+
+ msg = CtdlMakeMessage(&CC->user,
+ "", /* No recipient */
+ "", /* No recipient */
+ roomname,
+ 0, FMT_RFC822,
+ "",
+ "",
+ "", /* no subject */
+ NULL,
+ message_text,
+ NULL);
+
+ if (msg != NULL) {
+ CIT_ICAL->avoid_sending_invitations = 1;
+ CtdlSubmitMsg(msg, NULL, roomname);
+ CM_Free(msg);
+ CIT_ICAL->avoid_sending_invitations = 0;
+ }
+ }
+ free(serialized_event);
+ return(0);
+}
+
+
+/*
+ * Handle an incoming RSVP for an event. (This is the server subcommand part; it
+ * simply extracts the calendar object from the message, deserializes it, and
+ * passes it up to ical_update_my_calendar_with_reply() for processing.
+ */
+void ical_handle_rsvp(long msgnum, char *partnum, char *action) {
+ struct CtdlMessage *msg = NULL;
+ struct ical_respond_data ird;
+ int ret;
+
+ if (
+ (strcasecmp(action, "update"))
+ && (strcasecmp(action, "ignore"))
+ ) {
+ cprintf("%d Action must be 'update' or 'ignore'\n",
+ ERROR + ILLEGAL_VALUE
+ );
+ return;
+ }
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) {
+ cprintf("%d Message %ld not found.\n",
+ ERROR + ILLEGAL_VALUE,
+ (long)msgnum
+ );
+ return;
+ }
+
+ memset(&ird, 0, sizeof ird);
+ strcpy(ird.desired_partnum, partnum);
+ mime_parser(CM_RANGE(msg, eMesageText),
+ *ical_locate_part, /* callback function */
+ NULL, NULL,
+ (void *) &ird, /* user data */
+ 0
+ );
+
+ /* We're done with the incoming message, because we now have a
+ * calendar object in memory.
+ */
+ CM_Free(msg);
+
+ /*
+ * Here is the real meat of this function. Handle the event.
+ */
+ if (ird.cal != NULL) {
+ /* Update the user's calendar if necessary */
+ if (!strcasecmp(action, "update")) {
+ ret = ical_update_my_calendar_with_reply(ird.cal);
+ if (ret == 0) {
+ cprintf("%d Your calendar has been updated with this reply.\n",
+ CIT_OK);
+ }
+ else if (ret == 1) {
+ cprintf("%d This event does not exist in your calendar.\n",
+ ERROR + FILE_NOT_FOUND);
+ }
+ else {
+ cprintf("%d An internal error occurred.\n",
+ ERROR + INTERNAL_ERROR);
+ }
+ }
+ else {
+ cprintf("%d This reply has been ignored.\n", CIT_OK);
+ }
+
+ /* Now that we've processed this message, we don't need it
+ * anymore. So delete it. (Don't do this anymore.)
+ CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
+ */
+
+ /* Free the memory we allocated and return a response. */
+ icalcomponent_free(ird.cal);
+ ird.cal = NULL;
+ return;
+ }
+ else {
+ cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
+ return;
+ }
+
+ /* should never get here */
+}
+
+
+/*
+ * Search for a property in both the top level and in a VEVENT subcomponent
+ */
+icalproperty *ical_ctdl_get_subprop(
+ icalcomponent *cal,
+ icalproperty_kind which_prop
+) {
+ icalproperty *p;
+ icalcomponent *c;
+
+ p = icalcomponent_get_first_property(cal, which_prop);
+ if (p == NULL) {
+ c = icalcomponent_get_first_component(cal,
+ ICAL_VEVENT_COMPONENT);
+ if (c != NULL) {
+ p = icalcomponent_get_first_property(c, which_prop);
+ }
+ }
+ return p;
+}
+
+
+/*
+ * Check to see if two events overlap. Returns nonzero if they do.
+ * (This function is used in both Citadel and WebCit. If you change it in
+ * one place, change it in the other. Better yet, put it in a library.)
+ */
+int ical_ctdl_is_overlap(
+ struct icaltimetype t1start,
+ struct icaltimetype t1end,
+ struct icaltimetype t2start,
+ struct icaltimetype t2end
+) {
+ if (icaltime_is_null_time(t1start)) return(0);
+ if (icaltime_is_null_time(t2start)) return(0);
+
+ /* if either event lacks end time, assume end = start */
+ if (icaltime_is_null_time(t1end))
+ memcpy(&t1end, &t1start, sizeof(struct icaltimetype));
+ else {
+ if (t1end.is_date && icaltime_compare(t1start, t1end)) {
+ /*
+ * the end date is non-inclusive so adjust it by one
+ * day because our test is inclusive, note that a day is
+ * not too much because we are talking about all day
+ * events
+ * if start = end we assume that nevertheless the whole
+ * day is meant
+ */
+ icaltime_adjust(&t1end, -1, 0, 0, 0);
+ }
+ }
+
+ if (icaltime_is_null_time(t2end))
+ memcpy(&t2end, &t2start, sizeof(struct icaltimetype));
+ else {
+ if (t2end.is_date && icaltime_compare(t2start, t2end)) {
+ icaltime_adjust(&t2end, -1, 0, 0, 0);
+ }
+ }
+
+ /* First, check for all-day events */
+ if (t1start.is_date || t2start.is_date) {
+ /* If event 1 ends before event 2 starts, we're in the clear. */
+ if (icaltime_compare_date_only(t1end, t2start) < 0) return(0);
+
+ /* If event 2 ends before event 1 starts, we're also ok. */
+ if (icaltime_compare_date_only(t2end, t1start) < 0) return(0);
+
+ return(1);
+ }
+
+ /* syslog(LOG_DEBUG, "Comparing t1start %d:%d t1end %d:%d t2start %d:%d t2end %d:%d",
+ t1start.hour, t1start.minute, t1end.hour, t1end.minute,
+ t2start.hour, t2start.minute, t2end.hour, t2end.minute);
+ */
+
+ /* Now check for overlaps using date *and* time. */
+
+ /* If event 1 ends before event 2 starts, we're in the clear. */
+ if (icaltime_compare(t1end, t2start) <= 0) return(0);
+ /* syslog(LOG_DEBUG, "calendar: first passed"); */
+
+ /* If event 2 ends before event 1 starts, we're also ok. */
+ if (icaltime_compare(t2end, t1start) <= 0) return(0);
+ /* syslog(LOG_DEBUG, "calendar: second passed"); */
+
+ /* Otherwise, they overlap. */
+ return(1);
+}
+
+
+/*
+ * Phase 6 of "hunt for conflicts"
+ * called by ical_conflicts_phase5()
+ *
+ * Now both the proposed and existing events have been boiled down to start and end times.
+ * Check for overlap and output any conflicts.
+ *
+ * Returns nonzero if a conflict was reported. This allows the caller to stop iterating.
+ */
+int ical_conflicts_phase6(struct icaltimetype t1start,
+ struct icaltimetype t1end,
+ struct icaltimetype t2start,
+ struct icaltimetype t2end,
+ long existing_msgnum,
+ char *conflict_event_uid,
+ char *conflict_event_summary,
+ char *compare_uid)
+{
+ int conflict_reported = 0;
+
+ /* debugging cruft *
+ time_t tt;
+ tt = icaltime_as_timet_with_zone(t1start, t1start.zone);
+ syslog(LOG_DEBUG, "PROPOSED START: %s", ctime(&tt));
+ tt = icaltime_as_timet_with_zone(t1end, t1end.zone);
+ syslog(LOG_DEBUG, " PROPOSED END: %s", ctime(&tt));
+ tt = icaltime_as_timet_with_zone(t2start, t2start.zone);
+ syslog(LOG_DEBUG, "EXISTING START: %s", ctime(&tt));
+ tt = icaltime_as_timet_with_zone(t2end, t2end.zone);
+ syslog(LOG_DEBUG, " EXISTING END: %s", ctime(&tt));
+ * debugging cruft */
+
+ /* compare and output */
+
+ if (ical_ctdl_is_overlap(t1start, t1end, t2start, t2end)) {
+ cprintf("%ld||%s|%s|%d|\n",
+ existing_msgnum,
+ conflict_event_uid,
+ conflict_event_summary,
+ ( (!IsEmptyStr(compare_uid)
+ &&(!strcasecmp(compare_uid,
+ conflict_event_uid))) ? 1 : 0
+ )
+ );
+ conflict_reported = 1;
+ }
+
+ return(conflict_reported);
+}
+
+
+/*
+ * Phase 5 of "hunt for conflicts"
+ * Called by ical_conflicts_phase4()
+ *
+ * We have the proposed event boiled down to start and end times.
+ * Now check it against an existing event.
+ */
+void ical_conflicts_phase5(struct icaltimetype t1start,
+ struct icaltimetype t1end,
+ icalcomponent *existing_event,
+ long existing_msgnum,
+ char *compare_uid)
+{
+ char conflict_event_uid[SIZ];
+ char conflict_event_summary[SIZ];
+ struct icaltimetype t2start, t2end;
+ icalproperty *p;
+
+ /* recur variables */
+ icalproperty *rrule = NULL;
+ struct icalrecurrencetype recur;
+ icalrecur_iterator *ritr = NULL;
+ struct icaldurationtype dur;
+ int num_recur = 0;
+
+ /* initialization */
+ strcpy(conflict_event_uid, "");
+ strcpy(conflict_event_summary, "");
+ t2start = icaltime_null_time();
+ t2end = icaltime_null_time();
+
+ /* existing event stuff */
+ p = ical_ctdl_get_subprop(existing_event, ICAL_DTSTART_PROPERTY);
+ if (p == NULL) return;
+ if (p != NULL) t2start = icalproperty_get_dtstart(p);
+ if (icaltime_is_utc(t2start)) {
+ t2start.zone = icaltimezone_get_utc_timezone();
+ }
+ else {
+ t2start.zone = icalcomponent_get_timezone(existing_event,
+ icalparameter_get_tzid(
+ icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
+ )
+ );
+ if (!t2start.zone) {
+ t2start.zone = get_default_icaltimezone();
+ }
+ }
+
+ p = ical_ctdl_get_subprop(existing_event, ICAL_DTEND_PROPERTY);
+ if (p != NULL) {
+ t2end = icalproperty_get_dtend(p);
+
+ if (icaltime_is_utc(t2end)) {
+ t2end.zone = icaltimezone_get_utc_timezone();
+ }
+ else {
+ t2end.zone = icalcomponent_get_timezone(existing_event,
+ icalparameter_get_tzid(
+ icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
+ )
+ );
+ if (!t2end.zone) {
+ t2end.zone = get_default_icaltimezone();
+ }
+ }
+ dur = icaltime_subtract(t2end, t2start);
+ }
+ else {
+ memset (&dur, 0, sizeof(struct icaldurationtype));
+ }
+
+ rrule = ical_ctdl_get_subprop(existing_event, ICAL_RRULE_PROPERTY);
+ if (rrule) {
+ recur = icalproperty_get_rrule(rrule);
+ ritr = icalrecur_iterator_new(recur, t2start);
+ }
+
+ do {
+ p = ical_ctdl_get_subprop(existing_event, ICAL_UID_PROPERTY);
+ if (p != NULL) {
+ strcpy(conflict_event_uid, icalproperty_get_comment(p));
+ }
+
+ p = ical_ctdl_get_subprop(existing_event, ICAL_SUMMARY_PROPERTY);
+ if (p != NULL) {
+ strcpy(conflict_event_summary, icalproperty_get_comment(p));
+ }
+
+ if (ical_conflicts_phase6(t1start, t1end, t2start, t2end,
+ existing_msgnum, conflict_event_uid, conflict_event_summary, compare_uid))
+ {
+ num_recur = MAX_RECUR + 1; /* force it out of scope, no need to continue */
+ }
+
+ if (rrule) {
+ t2start = icalrecur_iterator_next(ritr);
+ if (!icaltime_is_null_time(t2end)) {
+ const icaltimezone *hold_zone = t2end.zone;
+ t2end = icaltime_add(t2start, dur);
+ t2end.zone = hold_zone;
+ }
+ ++num_recur;
+ }
+
+ if (icaltime_compare(t2start, t1end) < 0) {
+ num_recur = MAX_RECUR + 1; /* force it out of scope */
+ }
+
+ } while ( (rrule) && (!icaltime_is_null_time(t2start)) && (num_recur < MAX_RECUR) );
+ icalrecur_iterator_free(ritr);
+}
+
+
+/*
+ * Phase 4 of "hunt for conflicts"
+ * Called by ical_hunt_for_conflicts_backend()
+ *
+ * At this point we've got it boiled down to two icalcomponent events in memory.
+ * If they conflict, output something to the client.
+ */
+void ical_conflicts_phase4(icalcomponent *proposed_event,
+ icalcomponent *existing_event,
+ long existing_msgnum)
+{
+ struct icaltimetype t1start, t1end;
+ icalproperty *p;
+ char compare_uid[SIZ];
+
+ /* recur variables */
+ icalproperty *rrule = NULL;
+ struct icalrecurrencetype recur;
+ icalrecur_iterator *ritr = NULL;
+ struct icaldurationtype dur;
+ int num_recur = 0;
+
+ /* initialization */
+ t1end = icaltime_null_time();
+ *compare_uid = '\0';
+
+ /* proposed event stuff */
+
+ p = ical_ctdl_get_subprop(proposed_event, ICAL_DTSTART_PROPERTY);
+ if (p == NULL)
+ return;
+ else
+ t1start = icalproperty_get_dtstart(p);
+
+ if (icaltime_is_utc(t1start)) {
+ t1start.zone = icaltimezone_get_utc_timezone();
+ }
+ else {
+ t1start.zone = icalcomponent_get_timezone(proposed_event,
+ icalparameter_get_tzid(
+ icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
+ )
+ );
+ if (!t1start.zone) {
+ t1start.zone = get_default_icaltimezone();
+ }
+ }
+
+ p = ical_ctdl_get_subprop(proposed_event, ICAL_DTEND_PROPERTY);
+ if (p != NULL) {
+ t1end = icalproperty_get_dtend(p);
+
+ if (icaltime_is_utc(t1end)) {
+ t1end.zone = icaltimezone_get_utc_timezone();
+ }
+ else {
+ t1end.zone = icalcomponent_get_timezone(proposed_event,
+ icalparameter_get_tzid(
+ icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
+ )
+ );
+ if (!t1end.zone) {
+ t1end.zone = get_default_icaltimezone();
+ }
+ }
+
+ dur = icaltime_subtract(t1end, t1start);
+ }
+ else {
+ memset (&dur, 0, sizeof(struct icaldurationtype));
+ }
+
+ rrule = ical_ctdl_get_subprop(proposed_event, ICAL_RRULE_PROPERTY);
+ if (rrule) {
+ recur = icalproperty_get_rrule(rrule);
+ ritr = icalrecur_iterator_new(recur, t1start);
+ }
+
+ p = ical_ctdl_get_subprop(proposed_event, ICAL_UID_PROPERTY);
+ if (p != NULL) {
+ strcpy(compare_uid, icalproperty_get_comment(p));
+ }
+
+ do {
+ ical_conflicts_phase5(t1start, t1end, existing_event, existing_msgnum, compare_uid);
+
+ if (rrule) {
+ t1start = icalrecur_iterator_next(ritr);
+ if (!icaltime_is_null_time(t1end)) {
+ const icaltimezone *hold_zone = t1end.zone;
+ t1end = icaltime_add(t1start, dur);
+ t1end.zone = hold_zone;
+ }
+ ++num_recur;
+ }
+
+ } while ( (rrule) && (!icaltime_is_null_time(t1start)) && (num_recur < MAX_RECUR) );
+ icalrecur_iterator_free(ritr);
+}
+
+
+/*
+ * Phase 3 of "hunt for conflicts"
+ * Called by ical_hunt_for_conflicts()
+ */
+void ical_hunt_for_conflicts_backend(long msgnum, void *data) {
+ icalcomponent *proposed_event;
+ struct CtdlMessage *msg = NULL;
+ struct ical_respond_data ird;
+
+ proposed_event = (icalcomponent *)data;
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) return;
+ memset(&ird, 0, sizeof ird);
+ strcpy(ird.desired_partnum, "_HUNT_");
+ mime_parser(CM_RANGE(msg, eMesageText),
+ *ical_locate_part, /* callback function */
+ NULL, NULL,
+ (void *) &ird, /* user data */
+ 0
+ );
+ CM_Free(msg);
+
+ if (ird.cal == NULL) return;
+
+ ical_conflicts_phase4(proposed_event, ird.cal, msgnum);
+ icalcomponent_free(ird.cal);
+}
+
+
+/*
+ * Phase 2 of "hunt for conflicts" operation.
+ * At this point we have a calendar object which represents the VEVENT that
+ * is proposed for addition to the calendar. Now hunt through the user's
+ * calendar room, and output zero or more existing VEVENTs which conflict
+ * with this one.
+ */
+void ical_hunt_for_conflicts(icalcomponent *cal) {
+ char hold_rm[ROOMNAMELEN];
+
+ strcpy(hold_rm, CC->room.QRname); /* save current room */
+
+ if (CtdlGetRoom(&CC->room, USERCALENDARROOM) != 0) {
+ CtdlGetRoom(&CC->room, hold_rm);
+ cprintf("%d You do not have a calendar.\n", ERROR + ROOM_NOT_FOUND);
+ return;
+ }
+
+ cprintf("%d Conflicting events:\n", LISTING_FOLLOWS);
+
+ CtdlForEachMessage(MSGS_ALL, 0, NULL,
+ NULL,
+ NULL,
+ ical_hunt_for_conflicts_backend,
+ (void *) cal
+ );
+
+ cprintf("000\n");
+ CtdlGetRoom(&CC->room, hold_rm); /* return to saved room */
+
+}
+
+
+/*
+ * Hunt for conflicts (Phase 1 -- retrieve the object and call Phase 2)
+ */
+void ical_conflicts(long msgnum, char *partnum) {
+ struct CtdlMessage *msg = NULL;
+ struct ical_respond_data ird;
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) {
+ cprintf("%d Message %ld not found\n",
+ ERROR + ILLEGAL_VALUE,
+ (long)msgnum
+ );
+ return;
+ }
+
+ memset(&ird, 0, sizeof ird);
+ strcpy(ird.desired_partnum, partnum);
+ mime_parser(CM_RANGE(msg, eMesageText),
+ *ical_locate_part, /* callback function */
+ NULL, NULL,
+ (void *) &ird, /* user data */
+ 0
+ );
+
+ CM_Free(msg);
+
+ if (ird.cal != NULL) {
+ ical_hunt_for_conflicts(ird.cal);
+ icalcomponent_free(ird.cal);
+ return;
+ }
+
+ cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
+}
+
+
+/*
+ * Look for busy time in a VEVENT and add it to the supplied VFREEBUSY.
+ *
+ * fb The VFREEBUSY component to which we are appending
+ * top_level_cal The top-level VCALENDAR component which contains a VEVENT to be added
+ */
+void ical_add_to_freebusy(icalcomponent *fb, icalcomponent *top_level_cal) {
+ icalcomponent *cal;
+ icalproperty *p;
+ icalvalue *v;
+ struct icalperiodtype this_event_period = icalperiodtype_null_period();
+ icaltimetype dtstart;
+ icaltimetype dtend;
+
+ /* recur variables */
+ icalproperty *rrule = NULL;
+ struct icalrecurrencetype recur;
+ icalrecur_iterator *ritr = NULL;
+ struct icaldurationtype dur;
+ int num_recur = 0;
+
+ if (!top_level_cal) return;
+
+ /* Find the VEVENT component containing an event */
+ cal = icalcomponent_get_first_component(top_level_cal, ICAL_VEVENT_COMPONENT);
+ if (!cal) return;
+
+ /* If this event is not opaque, the user isn't publishing it as
+ * busy time, so don't bother doing anything else.
+ */
+ p = icalcomponent_get_first_property(cal, ICAL_TRANSP_PROPERTY);
+ if (p != NULL) {
+ v = icalproperty_get_value(p);
+ if (v != NULL) {
+ if (icalvalue_get_transp(v) != ICAL_TRANSP_OPAQUE) {
+ return;
+ }
+ }
+ }
+
+ /*
+ * Now begin calculating the event start and end times.
+ */
+ p = icalcomponent_get_first_property(cal, ICAL_DTSTART_PROPERTY);
+ if (!p) return;
+ dtstart = icalproperty_get_dtstart(p);
+
+ if (icaltime_is_utc(dtstart)) {
+ dtstart.zone = icaltimezone_get_utc_timezone();
+ }
+ else {
+ dtstart.zone = icalcomponent_get_timezone(top_level_cal,
+ icalparameter_get_tzid(
+ icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
+ )
+ );
+ if (!dtstart.zone) {
+ dtstart.zone = get_default_icaltimezone();
+ }
+ }
+
+ dtend = icalcomponent_get_dtend(cal);
+ if (!icaltime_is_null_time(dtend)) {
+ dur = icaltime_subtract(dtend, dtstart);
+ }
+ else {
+ memset (&dur, 0, sizeof(struct icaldurationtype));
+ }
+
+ /* Is a recurrence specified? If so, get ready to process it... */
+ rrule = ical_ctdl_get_subprop(cal, ICAL_RRULE_PROPERTY);
+ if (rrule) {
+ recur = icalproperty_get_rrule(rrule);
+ ritr = icalrecur_iterator_new(recur, dtstart);
+ }
+
+ do {
+ /* Convert the DTSTART and DTEND properties to an icalperiod. */
+ this_event_period.start = dtstart;
+
+ if (!icaltime_is_null_time(dtend)) {
+ this_event_period.end = dtend;
+ }
+
+ /* Convert the timestamps to UTC. It's ok to do this because we've already expanded
+ * recurrences and this data is never going to get used again.
+ */
+ this_event_period.start = icaltime_convert_to_zone(
+ this_event_period.start,
+ icaltimezone_get_utc_timezone()
+ );
+ this_event_period.end = icaltime_convert_to_zone(
+ this_event_period.end,
+ icaltimezone_get_utc_timezone()
+ );
+
+ /* Now add it. */
+ icalcomponent_add_property(fb, icalproperty_new_freebusy(this_event_period));
+
+ /* Make sure the DTSTART property of the freebusy *list* is set to
+ * the DTSTART property of the *earliest event*.
+ */
+ p = icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY);
+ if (p == NULL) {
+ icalcomponent_set_dtstart(fb, this_event_period.start);
+ }
+ else {
+ if (icaltime_compare(this_event_period.start, icalcomponent_get_dtstart(fb)) < 0) {
+ icalcomponent_set_dtstart(fb, this_event_period.start);
+ }
+ }
+
+ /* Make sure the DTEND property of the freebusy *list* is set to
+ * the DTEND property of the *latest event*.
+ */
+ p = icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY);
+ if (p == NULL) {
+ icalcomponent_set_dtend(fb, this_event_period.end);
+ }
+ else {
+ if (icaltime_compare(this_event_period.end, icalcomponent_get_dtend(fb)) > 0) {
+ icalcomponent_set_dtend(fb, this_event_period.end);
+ }
+ }
+
+ if (rrule) {
+ dtstart = icalrecur_iterator_next(ritr);
+ if (!icaltime_is_null_time(dtend)) {
+ dtend = icaltime_add(dtstart, dur);
+ dtend.zone = dtstart.zone;
+ }
+ ++num_recur;
+ }
+
+ } while ( (rrule) && (!icaltime_is_null_time(dtstart)) && (num_recur < MAX_RECUR) ) ;
+ icalrecur_iterator_free(ritr);
+}
+
+
+/*
+ * Backend for ical_freebusy()
+ *
+ * This function simply loads the messages in the user's calendar room,
+ * which contain VEVENTs, then strips them of all non-freebusy data, and
+ * adds them to the supplied VCALENDAR.
+ *
+ */
+void ical_freebusy_backend(long msgnum, void *data) {
+ icalcomponent *fb;
+ struct CtdlMessage *msg = NULL;
+ struct ical_respond_data ird;
+
+ fb = (icalcomponent *)data; /* User-supplied data will be the VFREEBUSY component */
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) return;
+ memset(&ird, 0, sizeof ird);
+ strcpy(ird.desired_partnum, "_HUNT_");
+ mime_parser(CM_RANGE(msg, eMesageText),
+ *ical_locate_part, /* callback function */
+ NULL, NULL,
+ (void *) &ird, /* user data */
+ 0
+ );
+ CM_Free(msg);
+
+ if (ird.cal) {
+ ical_add_to_freebusy(fb, ird.cal); /* Add VEVENT times to VFREEBUSY */
+ icalcomponent_free(ird.cal);
+ }
+}
+
+
+/*
+ * Grab another user's free/busy times
+ */
+void ical_freebusy(char *who) {
+ struct ctdluser usbuf;
+ char calendar_room_name[ROOMNAMELEN];
+ char hold_rm[ROOMNAMELEN];
+ char *serialized_request = NULL;
+ icalcomponent *encaps = NULL;
+ icalcomponent *fb = NULL;
+ int found_user = (-1);
+ struct recptypes *recp = NULL;
+ char buf[256];
+ char host[256];
+ char type[256];
+ int i = 0;
+ int config_lines = 0;
+
+ /* First try an exact match. */
+ found_user = CtdlGetUser(&usbuf, who);
+
+ /* If not found, try it as an unqualified email address. */
+ if (found_user != 0) {
+ strcpy(buf, who);
+ recp = validate_recipients(buf, NULL, 0);
+ syslog(LOG_DEBUG, "calendar: trying <%s>", buf);
+ if (recp != NULL) {
+ if (recp->num_local == 1) {
+ found_user = CtdlGetUser(&usbuf, recp->recp_local);
+ }
+ free_recipients(recp);
+ }
+ }
+
+ /* If still not found, try it as an address qualified with the
+ * primary FQDN of this Citadel node.
+ */
+ if (found_user != 0) {
+ snprintf(buf, sizeof buf, "%s@%s", who, CtdlGetConfigStr("c_fqdn"));
+ syslog(LOG_DEBUG, "calendar: trying <%s>", buf);
+ recp = validate_recipients(buf, NULL, 0);
+ if (recp != NULL) {
+ if (recp->num_local == 1) {
+ found_user = CtdlGetUser(&usbuf, recp->recp_local);
+ }
+ free_recipients(recp);
+ }
+ }
+
+ /* Still not found? Try qualifying it with every domain we
+ * might have addresses in.
+ */
+ if (found_user != 0) {
+ config_lines = num_tokens(inetcfg, '\n');
+ for (i=0; ((i < config_lines) && (found_user != 0)); ++i) {
+ extract_token(buf, inetcfg, i, '\n', sizeof buf);
+ extract_token(host, buf, 0, '|', sizeof host);
+ extract_token(type, buf, 1, '|', sizeof type);
+
+ if ( (!strcasecmp(type, "localhost"))
+ || (!strcasecmp(type, "directory")) ) {
+ snprintf(buf, sizeof buf, "%s@%s", who, host);
+ syslog(LOG_DEBUG, "calendar: trying <%s>", buf);
+ recp = validate_recipients(buf, NULL, 0);
+ if (recp != NULL) {
+ if (recp->num_local == 1) {
+ found_user = CtdlGetUser(&usbuf, recp->recp_local);
+ }
+ free_recipients(recp);
+ }
+ }
+ }
+ }
+
+ if (found_user != 0) {
+ cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
+ return;
+ }
+
+ CtdlMailboxName(calendar_room_name, sizeof calendar_room_name,
+ &usbuf, USERCALENDARROOM);
+
+ strcpy(hold_rm, CC->room.QRname); /* save current room */
+
+ if (CtdlGetRoom(&CC->room, calendar_room_name) != 0) {
+ cprintf("%d Cannot open calendar\n", ERROR + ROOM_NOT_FOUND);
+ CtdlGetRoom(&CC->room, hold_rm);
+ return;
+ }
+
+ /* Create a VFREEBUSY subcomponent */
+ syslog(LOG_DEBUG, "calendar: creating VFREEBUSY component");
+ fb = icalcomponent_new_vfreebusy();
+ if (fb == NULL) {
+ cprintf("%d Internal error: cannot allocate memory.\n", ERROR + INTERNAL_ERROR);
+ CtdlGetRoom(&CC->room, hold_rm);
+ return;
+ }
+
+ /* Set the method to PUBLISH */
+ icalcomponent_set_method(fb, ICAL_METHOD_PUBLISH);
+
+ /* Set the DTSTAMP to right now. */
+ icalcomponent_set_dtstamp(fb, icaltime_from_timet_with_zone(time(NULL), 0, icaltimezone_get_utc_timezone()));
+
+ /* Add the user's email address as ORGANIZER */
+ sprintf(buf, "MAILTO:%s", who);
+ if (strchr(buf, '@') == NULL) {
+ strcat(buf, "@");
+ strcat(buf, CtdlGetConfigStr("c_fqdn"));
+ }
+ for (i=0; buf[i]; ++i) {
+ if (buf[i]==' ') buf[i] = '_';
+ }
+ icalcomponent_add_property(fb, icalproperty_new_organizer(buf));
+
+ /* Add busy time from events */
+ syslog(LOG_DEBUG, "calendar: adding busy time from events");
+ CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL, ical_freebusy_backend, (void *)fb );
+
+ /* If values for DTSTART and DTEND are still not present, set them
+ * to yesterday and tomorrow as default values.
+ */
+ if (icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY) == NULL) {
+ icalcomponent_set_dtstart(fb, icaltime_from_timet_with_zone(time(NULL)-86400L, 0, icaltimezone_get_utc_timezone()));
+ }
+ if (icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY) == NULL) {
+ icalcomponent_set_dtend(fb, icaltime_from_timet_with_zone(time(NULL)+86400L, 0, icaltimezone_get_utc_timezone()));
+ }
+
+ /* Put the freebusy component into the calendar component */
+ syslog(LOG_DEBUG, "calendar: encapsulating");
+ encaps = ical_encapsulate_subcomponent(fb);
+ if (encaps == NULL) {
+ icalcomponent_free(fb);
+ cprintf("%d Internal error: cannot allocate memory.\n",
+ ERROR + INTERNAL_ERROR);
+ CtdlGetRoom(&CC->room, hold_rm);
+ return;
+ }
+
+ /* Set the method to PUBLISH */
+ syslog(LOG_DEBUG, "calendar: setting method");
+ icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
+
+ /* Serialize it */
+ syslog(LOG_DEBUG, "calendar: serializing");
+ serialized_request = icalcomponent_as_ical_string_r(encaps);
+ icalcomponent_free(encaps); /* Don't need this anymore. */
+
+ cprintf("%d Free/busy for %s\n", LISTING_FOLLOWS, usbuf.fullname);
+ if (serialized_request != NULL) {
+ client_write(serialized_request, strlen(serialized_request));
+ free(serialized_request);
+ }
+ cprintf("\n000\n");
+
+ /* Go back to the room from which we came... */
+ CtdlGetRoom(&CC->room, hold_rm);
+}
+
+
+/*
+ * Backend for ical_getics()
+ *
+ * This is a ForEachMessage() callback function that searches the current room
+ * for calendar events and adds them each into one big calendar component.
+ */
+void ical_getics_backend(long msgnum, void *data) {
+ icalcomponent *encaps, *c;
+ struct CtdlMessage *msg = NULL;
+ struct ical_respond_data ird;
+
+ encaps = (icalcomponent *)data;
+ if (encaps == NULL) return;
+
+ /* Look for the calendar event... */
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) return;
+ memset(&ird, 0, sizeof ird);
+ strcpy(ird.desired_partnum, "_HUNT_");
+ mime_parser(CM_RANGE(msg, eMesageText),
+ *ical_locate_part, /* callback function */
+ NULL, NULL,
+ (void *) &ird, /* user data */
+ 0
+ );
+ CM_Free(msg);
+
+ if (ird.cal == NULL) return;
+
+ /* Here we go: put the VEVENT into the VCALENDAR. We now no longer
+ * are responsible for "the_request"'s memory -- it will be freed
+ * when we free "encaps".
+ */
+
+ /* If the top-level component is *not* a VCALENDAR, we can drop it right
+ * in. This will almost never happen.
+ */
+ if (icalcomponent_isa(ird.cal) != ICAL_VCALENDAR_COMPONENT) {
+ icalcomponent_add_component(encaps, ird.cal);
+ }
+ /*
+ * In the more likely event that we're looking at a VCALENDAR with the VEVENT
+ * and other components encapsulated inside, we have to extract them.
+ */
+ else {
+ for (c = icalcomponent_get_first_component(ird.cal, ICAL_ANY_COMPONENT);
+ (c != NULL);
+ c = icalcomponent_get_next_component(ird.cal, ICAL_ANY_COMPONENT)) {
+
+ /* For VTIMEZONE components, suppress duplicates of the same tzid */
+
+ if (icalcomponent_isa(c) == ICAL_VTIMEZONE_COMPONENT) {
+ icalproperty *p = icalcomponent_get_first_property(c, ICAL_TZID_PROPERTY);
+ if (p) {
+ const char *tzid = icalproperty_get_tzid(p);
+ if (!icalcomponent_get_timezone(encaps, tzid)) {
+ icalcomponent_add_component(encaps,
+ icalcomponent_new_clone(c));
+ }
+ }
+ }
+
+ /* All other types of components can go in verbatim */
+ else {
+ icalcomponent_add_component(encaps, icalcomponent_new_clone(c));
+ }
+ }
+ icalcomponent_free(ird.cal);
+ }
+}
+
+
+/*
+ * Retrieve all of the calendar items in the current room, and output them
+ * as a single icalendar object.
+ */
+void ical_getics(void)
+{
+ icalcomponent *encaps = NULL;
+ char *ser = NULL;
+
+ if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
+ &&(CC->room.QRdefaultview != VIEW_TASKS) ) {
+ cprintf("%d Not a calendar room\n", ERROR+NOT_HERE);
+ return; /* Not an iCalendar-centric room */
+ }
+
+ encaps = icalcomponent_new_vcalendar();
+ if (encaps == NULL) {
+ syslog(LOG_ERR, "calendar: could not allocate component!");
+ cprintf("%d Could not allocate memory\n", ERROR+INTERNAL_ERROR);
+ return;
+ }
+
+ cprintf("%d one big calendar\n", LISTING_FOLLOWS);
+
+ /* Set the Product ID */
+ icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
+
+ /* Set the Version Number */
+ icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
+
+ /* Set the method to PUBLISH */
+ icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
+
+ /* Now go through the room encapsulating all calendar items. */
+ CtdlForEachMessage(MSGS_ALL, 0, NULL,
+ NULL,
+ NULL,
+ ical_getics_backend,
+ (void *) encaps
+ );
+
+ ser = icalcomponent_as_ical_string_r(encaps);
+ icalcomponent_free(encaps); /* Don't need this anymore. */
+ client_write(ser, strlen(ser));
+ free(ser);
+ cprintf("\n000\n");
+}
+
+
+/*
+ * Helper callback function for ical_putics() to discover which TZID's we need.
+ * Simply put the tzid name string into a hash table. After the callbacks are
+ * done we'll go through them and attach the ones that we have.
+ */
+void ical_putics_grabtzids(icalparameter *param, void *data)
+{
+ const char *tzid = icalparameter_get_tzid(param);
+ HashList *keys = (HashList *) data;
+
+ if ( (keys) && (tzid) && (!IsEmptyStr(tzid)) ) {
+ Put(keys, tzid, strlen(tzid), strdup(tzid), NULL);
+ }
+}
+
+
+/*
+ * Delete all of the calendar items in the current room, and replace them
+ * with calendar items from a client-supplied data stream.
+ */
+void ical_putics(void)
+{
+ char *calstream = NULL;
+ icalcomponent *cal;
+ icalcomponent *c;
+ icalcomponent *encaps = NULL;
+ HashList *tzidlist = NULL;
+ HashPos *HashPos;
+ void *Value;
+ const char *Key;
+ long len;
+
+ /* Only allow this operation if we're in a room containing a calendar or tasks view */
+ if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
+ &&(CC->room.QRdefaultview != VIEW_TASKS) ) {
+ cprintf("%d Not a calendar room\n", ERROR+NOT_HERE);
+ return;
+ }
+
+ /* Only allow this operation if we have permission to overwrite the existing calendar */
+ if (!CtdlDoIHavePermissionToDeleteMessagesFromThisRoom()) {
+ cprintf("%d Permission denied.\n", ERROR+HIGHER_ACCESS_REQUIRED);
+ return;
+ }
+
+ cprintf("%d Transmit data now\n", SEND_LISTING);
+ calstream = CtdlReadMessageBody(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
+ if (calstream == NULL) {
+ return;
+ }
+
+ cal = icalcomponent_new_from_string(calstream);
+ free(calstream);
+
+ /* We got our data stream -- now do something with it. */
+
+ /* Delete the existing messages in the room, because we are overwriting
+ * the entire calendar with an entire new (or updated) calendar.
+ * (Careful: this opens an S_ROOMS critical section!)
+ */
+ CtdlDeleteMessages(CC->room.QRname, NULL, 0, "");
+
+ /* If the top-level component is *not* a VCALENDAR, we can drop it right
+ * in. This will almost never happen.
+ */
+ if (icalcomponent_isa(cal) != ICAL_VCALENDAR_COMPONENT) {
+ ical_write_to_cal(NULL, cal);
+ }
+ /*
+ * In the more likely event that we're looking at a VCALENDAR with the VEVENT
+ * and other components encapsulated inside, we have to extract them.
+ */
+ else {
+ for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
+ (c != NULL);
+ c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
+
+ /* Non-VTIMEZONE components each get written as individual messages.
+ * But we also need to attach the relevant VTIMEZONE components to them.
+ */
+ if ( (icalcomponent_isa(c) != ICAL_VTIMEZONE_COMPONENT)
+ && (encaps = icalcomponent_new_vcalendar()) ) {
+ icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
+ icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
+ icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
+
+ /* Attach any needed timezones here */
+ tzidlist = NewHash(1, NULL);
+ if (tzidlist) {
+ icalcomponent_foreach_tzid(c, ical_putics_grabtzids, tzidlist);
+ }
+ HashPos = GetNewHashPos(tzidlist, 0);
+
+ while (GetNextHashPos(tzidlist, HashPos, &len, &Key, &Value)) {
+ syslog(LOG_DEBUG, "calendar: attaching timezone '%s'", (char*) Value);
+ icaltimezone *t = NULL;
+
+ /* First look for a timezone attached to the original calendar */
+ t = icalcomponent_get_timezone(cal, Value);
+
+ /* Try built-in tzdata if the right one wasn't attached */
+ if (!t) {
+ t = icaltimezone_get_builtin_timezone(Value);
+ }
+
+ /* I've got a valid timezone to attach. */
+ if (t) {
+ icalcomponent_add_component(encaps,
+ icalcomponent_new_clone(
+ icaltimezone_get_component(t)
+ )
+ );
+ }
+
+ }
+ DeleteHashPos(&HashPos);
+ DeleteHash(&tzidlist);
+
+ /* Now attach the component itself (usually a VEVENT or VTODO) */
+ icalcomponent_add_component(encaps, icalcomponent_new_clone(c));
+
+ /* Write it to the message store */
+ ical_write_to_cal(NULL, encaps);
+ icalcomponent_free(encaps);
+ }
+ }
+ }
+
+ icalcomponent_free(cal);
+}
+
+
+/*
+ * All Citadel calendar commands from the client come through here.
+ */
+void cmd_ical(char *argbuf)
+{
+ char subcmd[64];
+ long msgnum;
+ char partnum[256];
+ char action[256];
+ char who[256];
+
+ extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
+
+ /* Allow "test" and "freebusy" subcommands without logging in. */
+
+ if (!strcasecmp(subcmd, "test")) {
+ cprintf("%d This server supports calendaring\n", CIT_OK);
+ return;
+ }
+
+ if (!strcasecmp(subcmd, "freebusy")) {
+ extract_token(who, argbuf, 1, '|', sizeof who);
+ ical_freebusy(who);
+ return;
+ }
+
+ if (!strcasecmp(subcmd, "sgi")) {
+ CIT_ICAL->server_generated_invitations = (extract_int(argbuf, 1) ? 1 : 0) ;
+ cprintf("%d %d\n", CIT_OK, CIT_ICAL->server_generated_invitations);
+ return;
+ }
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ if (!strcasecmp(subcmd, "respond")) {
+ msgnum = extract_long(argbuf, 1);
+ extract_token(partnum, argbuf, 2, '|', sizeof partnum);
+ extract_token(action, argbuf, 3, '|', sizeof action);
+ ical_respond(msgnum, partnum, action);
+ return;
+ }
+
+ if (!strcasecmp(subcmd, "handle_rsvp")) {
+ msgnum = extract_long(argbuf, 1);
+ extract_token(partnum, argbuf, 2, '|', sizeof partnum);
+ extract_token(action, argbuf, 3, '|', sizeof action);
+ ical_handle_rsvp(msgnum, partnum, action);
+ return;
+ }
+
+ if (!strcasecmp(subcmd, "conflicts")) {
+ msgnum = extract_long(argbuf, 1);
+ extract_token(partnum, argbuf, 2, '|', sizeof partnum);
+ ical_conflicts(msgnum, partnum);
+ return;
+ }
+
+ if (!strcasecmp(subcmd, "getics")) {
+ ical_getics();
+ return;
+ }
+
+ if (!strcasecmp(subcmd, "putics")) {
+ ical_putics();
+ return;
+ }
+
+ cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
+}
+
+
+/*
+ * We don't know if the calendar room exists so we just create it at login
+ */
+void ical_CtdlCreateRoom(void)
+{
+ struct ctdlroom qr;
+ visit vbuf;
+
+ /* Create the calendar room if it doesn't already exist */
+ CtdlCreateRoom(USERCALENDARROOM, 4, "", 0, 1, 0, VIEW_CALENDAR);
+
+ /* Set expiration policy to manual; otherwise objects will be lost! */
+ if (CtdlGetRoomLock(&qr, USERCALENDARROOM)) {
+ syslog(LOG_ERR, "calendar: couldn't get the user calendar room");
+ return;
+ }
+ qr.QRep.expire_mode = EXPIRE_MANUAL;
+ qr.QRdefaultview = VIEW_CALENDAR; /* 3 = calendar view */
+ CtdlPutRoomLock(&qr);
+
+ /* Set the view to a calendar view */
+ CtdlGetRelationship(&vbuf, &CC->user, &qr);
+ vbuf.v_view = VIEW_CALENDAR;
+ CtdlSetRelationship(&vbuf, &CC->user, &qr);
+
+ /* Create the tasks list room if it doesn't already exist */
+ CtdlCreateRoom(USERTASKSROOM, 4, "", 0, 1, 0, VIEW_TASKS);
+
+ /* Set expiration policy to manual; otherwise objects will be lost! */
+ if (CtdlGetRoomLock(&qr, USERTASKSROOM)) {
+ syslog(LOG_ERR, "calendar: couldn't get the user calendar room!");
+ return;
+ }
+ qr.QRep.expire_mode = EXPIRE_MANUAL;
+ qr.QRdefaultview = VIEW_TASKS;
+ CtdlPutRoomLock(&qr);
+
+ /* Set the view to a task list view */
+ CtdlGetRelationship(&vbuf, &CC->user, &qr);
+ vbuf.v_view = VIEW_TASKS;
+ CtdlSetRelationship(&vbuf, &CC->user, &qr);
+
+ /* Create the notes room if it doesn't already exist */
+ CtdlCreateRoom(USERNOTESROOM, 4, "", 0, 1, 0, VIEW_NOTES);
+
+ /* Set expiration policy to manual; otherwise objects will be lost! */
+ if (CtdlGetRoomLock(&qr, USERNOTESROOM)) {
+ syslog(LOG_ERR, "calendar: couldn't get the user calendar room!");
+ return;
+ }
+ qr.QRep.expire_mode = EXPIRE_MANUAL;
+ qr.QRdefaultview = VIEW_NOTES;
+ CtdlPutRoomLock(&qr);
+
+ /* Set the view to a notes view */
+ CtdlGetRelationship(&vbuf, &CC->user, &qr);
+ vbuf.v_view = VIEW_NOTES;
+ CtdlSetRelationship(&vbuf, &CC->user, &qr);
+
+ return;
+}
+
+
+/*
+ * ical_send_out_invitations() is called by ical_saving_vevent() when it finds a VEVENT.
+ *
+ * top_level_cal is the highest available level calendar object.
+ * cal is the subcomponent containing the VEVENT.
+ *
+ * Note: if you change the encapsulation code here, change it in WebCit's ical_encapsulate_subcomponent()
+ */
+void ical_send_out_invitations(icalcomponent *top_level_cal, icalcomponent *cal) {
+ icalcomponent *the_request = NULL;
+ char *serialized_request = NULL;
+ icalcomponent *encaps = NULL;
+ char *request_message_text = NULL;
+ struct CtdlMessage *msg = NULL;
+ struct recptypes *valid = NULL;
+ char attendees_string[SIZ];
+ int num_attendees = 0;
+ char this_attendee[256];
+ icalproperty *attendee = NULL;
+ char summary_string[SIZ];
+ icalproperty *summary = NULL;
+ size_t reqsize;
+ icalproperty *p;
+ struct icaltimetype t;
+ const icaltimezone *attached_zones[5] = { NULL, NULL, NULL, NULL, NULL };
+ int i;
+ const icaltimezone *z;
+ int num_zones_attached = 0;
+ int zone_already_attached;
+ icalparameter *tzidp = NULL;
+ const char *tzidc = NULL;
+
+ if (cal == NULL) {
+ syslog(LOG_ERR, "calendar: trying to reply to NULL event?");
+ return;
+ }
+
+ /* If this is a VCALENDAR component, look for a VEVENT subcomponent. */
+ if (icalcomponent_isa(cal) == ICAL_VCALENDAR_COMPONENT) {
+ ical_send_out_invitations(top_level_cal,
+ icalcomponent_get_first_component(
+ cal, ICAL_VEVENT_COMPONENT
+ )
+ );
+ return;
+ }
+
+ /* Clone the event */
+ the_request = icalcomponent_new_clone(cal);
+ if (the_request == NULL) {
+ syslog(LOG_ERR, "calendar: cannot clone calendar object");
+ return;
+ }
+
+ /* Extract the summary string -- we'll use it as the
+ * message subject for the request
+ */
+ strcpy(summary_string, "Meeting request");
+ summary = icalcomponent_get_first_property(the_request, ICAL_SUMMARY_PROPERTY);
+ if (summary != NULL) {
+ if (icalproperty_get_summary(summary)) {
+ strcpy(summary_string,
+ icalproperty_get_summary(summary) );
+ }
+ }
+
+ /* Determine who the recipients of this message are (the attendees) */
+ strcpy(attendees_string, "");
+ for (attendee = icalcomponent_get_first_property(the_request, ICAL_ATTENDEE_PROPERTY); attendee != NULL; attendee = icalcomponent_get_next_property(the_request, ICAL_ATTENDEE_PROPERTY)) {
+ const char *ch = icalproperty_get_attendee(attendee);
+ if ((ch != NULL) && !strncasecmp(ch, "MAILTO:", 7)) {
+ safestrncpy(this_attendee, ch + 7, sizeof(this_attendee));
+
+ if (!CtdlIsMe(this_attendee, sizeof this_attendee)) { /* don't send an invitation to myself! */
+ snprintf(&attendees_string[strlen(attendees_string)],
+ sizeof(attendees_string) - strlen(attendees_string),
+ "%s, ",
+ this_attendee
+ );
+ ++num_attendees;
+ }
+ }
+ }
+
+ syslog(LOG_DEBUG, "calendar: <%d> attendees: <%s>", num_attendees, attendees_string);
+
+ /* If there are no attendees, there are no invitations to send, so...
+ * don't bother putting one together! Punch out, Maverick!
+ */
+ if (num_attendees == 0) {
+ icalcomponent_free(the_request);
+ return;
+ }
+
+ /* Encapsulate the VEVENT component into a complete VCALENDAR */
+ encaps = icalcomponent_new_vcalendar();
+ if (encaps == NULL) {
+ syslog(LOG_ERR, "calendar: could not allocate component!");
+ icalcomponent_free(the_request);
+ return;
+ }
+
+ /* Set the Product ID */
+ icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
+
+ /* Set the Version Number */
+ icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
+
+ /* Set the method to REQUEST */
+ icalcomponent_set_method(encaps, ICAL_METHOD_REQUEST);
+
+ /* Look for properties containing timezone parameters, to see if we need to attach VTIMEZONEs */
+ for (p = icalcomponent_get_first_property(the_request, ICAL_ANY_PROPERTY);
+ p != NULL;
+ p = icalcomponent_get_next_property(the_request, ICAL_ANY_PROPERTY))
+ {
+ if ( (icalproperty_isa(p) == ICAL_COMPLETED_PROPERTY)
+ || (icalproperty_isa(p) == ICAL_CREATED_PROPERTY)
+ || (icalproperty_isa(p) == ICAL_DATEMAX_PROPERTY)
+ || (icalproperty_isa(p) == ICAL_DATEMIN_PROPERTY)
+ || (icalproperty_isa(p) == ICAL_DTEND_PROPERTY)
+ || (icalproperty_isa(p) == ICAL_DTSTAMP_PROPERTY)
+ || (icalproperty_isa(p) == ICAL_DTSTART_PROPERTY)
+ || (icalproperty_isa(p) == ICAL_DUE_PROPERTY)
+ || (icalproperty_isa(p) == ICAL_EXDATE_PROPERTY)
+ || (icalproperty_isa(p) == ICAL_LASTMODIFIED_PROPERTY)
+ || (icalproperty_isa(p) == ICAL_MAXDATE_PROPERTY)
+ || (icalproperty_isa(p) == ICAL_MINDATE_PROPERTY)
+ || (icalproperty_isa(p) == ICAL_RECURRENCEID_PROPERTY)
+ ) {
+ t = icalproperty_get_dtstart(p); // it's safe to use dtstart for all of them
+
+ /* Determine the tzid in order for some of the conditions below to work */
+ tzidp = icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER);
+ if (tzidp) {
+ tzidc = icalparameter_get_tzid(tzidp);
+ }
+ else {
+ tzidc = NULL;
+ }
+
+ /* First see if there's a timezone attached to the data structure itself */
+ if (icaltime_is_utc(t)) {
+ z = icaltimezone_get_utc_timezone();
+ }
+ else {
+ z = icaltime_get_timezone(t);
+ }
+
+ /* If not, try to determine the tzid from the parameter using attached zones */
+ if ((!z) && (tzidc)) {
+ z = icalcomponent_get_timezone(top_level_cal, tzidc);
+ }
+
+ /* Still no good? Try our internal database */
+ if ((!z) && (tzidc)) {
+ z = icaltimezone_get_builtin_timezone_from_tzid(tzidc);
+ }
+
+ if (z) {
+ /* We have a valid timezone. Good. Now we need to attach it. */
+
+ zone_already_attached = 0;
+ for (i=0; i<5; ++i) {
+ if (z == attached_zones[i]) {
+ /* We've already got this one, no need to attach another. */
+ ++zone_already_attached;
+ }
+ }
+ if ((!zone_already_attached) && (num_zones_attached < 5)) {
+ /* This is a new one, so attach it. */
+ attached_zones[num_zones_attached++] = z;
+ }
+
+ icalproperty_set_parameter(p, icalparameter_new_tzid(icaltimezone_get_tzid(z))
+ );
+ }
+ }
+ }
+
+ /* Encapsulate any timezones we need */
+ if (num_zones_attached > 0) for (i=0; i<num_zones_attached; ++i) {
+ icalcomponent *zc;
+ zc = icalcomponent_new_clone(icaltimezone_get_component(attached_zones[i]));
+ icalcomponent_add_component(encaps, zc);
+ }
+
+ /* Here we go: encapsulate the VEVENT into the VCALENDAR. We now no longer
+ * are responsible for "the_request"'s memory -- it will be freed
+ * when we free "encaps".
+ */
+ icalcomponent_add_component(encaps, the_request);
+
+ /* Serialize it */
+ serialized_request = icalcomponent_as_ical_string_r(encaps);
+ icalcomponent_free(encaps); /* Don't need this anymore. */
+ if (serialized_request == NULL) return;
+
+ reqsize = strlen(serialized_request) + SIZ;
+ request_message_text = malloc(reqsize);
+ if (request_message_text != NULL) {
+ snprintf(request_message_text, reqsize,
+ "Content-type: text/calendar\r\n\r\n%s\r\n",
+ serialized_request
+ );
+
+ msg = CtdlMakeMessage(
+ &CC->user,
+ NULL, /* No single recipient here */
+ NULL, /* No single recipient here */
+ CC->room.QRname,
+ 0,
+ FMT_RFC822,
+ NULL,
+ NULL,
+ summary_string, /* Use summary for subject */
+ NULL,
+ request_message_text,
+ NULL
+ );
+
+ if (msg != NULL) {
+ valid = validate_recipients(attendees_string, NULL, 0);
+ CtdlSubmitMsg(msg, valid, "");
+ CM_Free(msg);
+ free_recipients(valid);
+ }
+ }
+ free(serialized_request);
+}
+
+
+/*
+ * When a calendar object is being saved, determine whether it's a VEVENT
+ * and the user saving it is the organizer. If so, send out invitations
+ * to any listed attendees.
+ *
+ * This function is recursive. The caller can simply supply the same object
+ * as both arguments. When it recurses it will alter the second argument
+ * while holding on to the top level object. This allows us to go back and
+ * grab things like time zones which might be attached.
+ *
+ */
+void ical_saving_vevent(icalcomponent *top_level_cal, icalcomponent *cal) {
+ icalcomponent *c;
+ icalproperty *organizer = NULL;
+ char organizer_string[SIZ];
+
+ syslog(LOG_DEBUG, "calendar: ical_saving_vevent() has been called");
+
+ /* Don't send out invitations unless the client wants us to. */
+ if (CIT_ICAL->server_generated_invitations == 0) {
+ return;
+ }
+
+ /* Don't send out invitations if we've been asked not to. */
+ if (CIT_ICAL->avoid_sending_invitations > 0) {
+ return;
+ }
+
+ strcpy(organizer_string, "");
+ /*
+ * The VEVENT subcomponent is the one we're interested in.
+ * Send out invitations if, and only if, this user is the Organizer.
+ */
+ if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
+ organizer = icalcomponent_get_first_property(cal, ICAL_ORGANIZER_PROPERTY);
+ if (organizer != NULL) {
+ if (icalproperty_get_organizer(organizer)) {
+ strcpy(organizer_string,
+ icalproperty_get_organizer(organizer));
+ }
+ }
+ if (!strncasecmp(organizer_string, "MAILTO:", 7)) {
+ strcpy(organizer_string, &organizer_string[7]);
+ striplt(organizer_string);
+ /*
+ * If the user saving the event is listed as the
+ * organizer, then send out invitations.
+ */
+ if (CtdlIsMe(organizer_string, sizeof organizer_string)) {
+ ical_send_out_invitations(top_level_cal, cal);
+ }
+ }
+ }
+
+ /* If the component has subcomponents, recurse through them. */
+ for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
+ (c != NULL);
+ c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
+ /* Recursively process subcomponent */
+ ical_saving_vevent(top_level_cal, c);
+ }
+
+}
+
+
+/*
+ * Back end for ical_obj_beforesave()
+ * This hunts for the UID of the calendar event (becomes Citadel msg EUID),
+ * the summary of the event (becomes message subject),
+ * and the start time (becomes message date/time).
+ */
+void ical_obj_beforesave_backend(char *name, char *filename, char *partnum,
+ char *disp, void *content, char *cbtype, char *cbcharset, size_t length,
+ char *encoding, char *cbid, void *cbuserdata)
+{
+ const char* pch;
+ icalcomponent *cal, *nested_event, *nested_todo, *whole_cal;
+ icalproperty *p;
+ char new_uid[256] = "";
+ struct CtdlMessage *msg = (struct CtdlMessage *) cbuserdata;
+
+ if (!msg) return;
+
+ /* We're only interested in calendar data. */
+ if ( (strcasecmp(cbtype, "text/calendar"))
+ && (strcasecmp(cbtype, "application/ics")) ) {
+ return;
+ }
+
+ /* Hunt for the UID and drop it in
+ * the "user data" pointer for the MIME parser. When
+ * ical_obj_beforesave() sees it there, it'll set the Exclusive msgid
+ * to that string.
+ */
+ whole_cal = icalcomponent_new_from_string(content);
+ cal = whole_cal;
+ if (cal != NULL) {
+ if (icalcomponent_isa(cal) == ICAL_VCALENDAR_COMPONENT) {
+ nested_event = icalcomponent_get_first_component(
+ cal, ICAL_VEVENT_COMPONENT);
+ if (nested_event != NULL) {
+ cal = nested_event;
+ }
+ else {
+ nested_todo = icalcomponent_get_first_component(
+ cal, ICAL_VTODO_COMPONENT);
+ if (nested_todo != NULL) {
+ cal = nested_todo;
+ }
+ }
+ }
+
+ if (cal != NULL) {
+
+ /* Set the message EUID to the iCalendar UID */
+
+ p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
+ if (p == NULL) {
+ /* If there's no uid we must generate one */
+ generate_uuid(new_uid);
+ icalcomponent_add_property(cal, icalproperty_new_uid(new_uid));
+ p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
+ }
+ if (p != NULL) {
+ pch = icalproperty_get_comment(p);
+ if (!IsEmptyStr(pch)) {
+ CM_SetField(msg, eExclusiveID, pch, strlen(pch));
+ syslog(LOG_DEBUG, "calendar: saving calendar UID <%s>", pch);
+ }
+ }
+
+ /* Set the message subject to the iCalendar summary */
+
+ p = ical_ctdl_get_subprop(cal, ICAL_SUMMARY_PROPERTY);
+ if (p != NULL) {
+ pch = icalproperty_get_comment(p);
+ if (!IsEmptyStr(pch)) {
+ char *subj;
+
+ subj = rfc2047encode(pch, strlen(pch));
+ CM_SetAsField(msg, eMsgSubject, &subj, strlen(subj));
+ }
+ }
+
+ /* Set the message date/time to the iCalendar start time */
+
+ p = ical_ctdl_get_subprop(cal, ICAL_DTSTART_PROPERTY);
+ if (p != NULL) {
+ time_t idtstart;
+ idtstart = icaltime_as_timet(icalproperty_get_dtstart(p));
+ if (idtstart > 0) {
+ CM_SetFieldLONG(msg, eTimestamp, idtstart);
+ }
+ }
+
+ }
+ icalcomponent_free(cal);
+ if (whole_cal != cal) {
+ icalcomponent_free(whole_cal);
+ }
+ }
+}
+
+
+/*
+ * See if we need to prevent the object from being saved (we don't allow
+ * MIME types other than text/calendar in "calendar" or "tasks" rooms).
+ *
+ * If the message is being saved, we also set various message header fields
+ * using data found in the iCalendar object.
+ */
+int ical_obj_beforesave(struct CtdlMessage *msg, struct recptypes *recp)
+{
+ /* First determine if this is a calendar or tasks room */
+ if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
+ && (CC->room.QRdefaultview != VIEW_TASKS)
+ ) {
+ return(0); /* Not an iCalendar-centric room */
+ }
+
+ /* It must be an RFC822 message! */
+ if (msg->cm_format_type != 4) {
+ syslog(LOG_DEBUG, "calendar: rejecting non-RFC822 message");
+ return(1); /* You tried to save a non-RFC822 message! */
+ }
+
+ if (CM_IsEmpty(msg, eMesageText)) {
+ return(1); /* You tried to save a null message! */
+ }
+
+ /* Do all of our lovely back-end parsing */
+ mime_parser(CM_RANGE(msg, eMesageText),
+ *ical_obj_beforesave_backend,
+ NULL, NULL,
+ (void *)msg,
+ 0
+ );
+
+ return(0);
+}
+
+
+/*
+ * Things we need to do after saving a calendar event.
+ */
+void ical_obj_aftersave_backend(char *name, char *filename, char *partnum,
+ char *disp, void *content, char *cbtype, char *cbcharset, size_t length,
+ char *encoding, char *cbid, void *cbuserdata)
+{
+ icalcomponent *cal;
+
+ /* We're only interested in calendar items here. */
+ if ( (strcasecmp(cbtype, "text/calendar"))
+ && (strcasecmp(cbtype, "application/ics")) ) {
+ return;
+ }
+
+ /* Hunt for the UID and drop it in
+ * the "user data" pointer for the MIME parser. When
+ * ical_obj_beforesave() sees it there, it'll set the Exclusive msgid
+ * to that string.
+ */
+ if ( (!strcasecmp(cbtype, "text/calendar"))
+ || (!strcasecmp(cbtype, "application/ics")) ) {
+ cal = icalcomponent_new_from_string(content);
+ if (cal != NULL) {
+ ical_saving_vevent(cal, cal);
+ icalcomponent_free(cal);
+ }
+ }
+}
+
+
+/*
+ * Things we need to do after saving a calendar event.
+ * (This will start back end tasks such as automatic generation of invitations,
+ * if such actions are appropriate.)
+ */
+int ical_obj_aftersave(struct CtdlMessage *msg, struct recptypes *recp)
+{
+ char roomname[ROOMNAMELEN];
+
+ /*
+ * If this isn't the Calendar> room, no further action is necessary.
+ */
+
+ /* First determine if this is our room */
+ CtdlMailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM);
+ if (strcasecmp(roomname, CC->room.QRname)) {
+ return(0); /* Not the Calendar room -- don't do anything. */
+ }
+
+ /* It must be an RFC822 message! */
+ if (msg->cm_format_type != 4) return(1);
+
+ /* Reject null messages */
+ if (CM_IsEmpty(msg, eMesageText)) return(1);
+
+ /* Now recurse through it looking for our icalendar data */
+ mime_parser(CM_RANGE(msg, eMesageText),
+ *ical_obj_aftersave_backend,
+ NULL, NULL,
+ NULL,
+ 0
+ );
+
+ return(0);
+}
+
+
+void ical_session_startup(void) {
+ CIT_ICAL = malloc(sizeof(struct cit_ical));
+ memset(CIT_ICAL, 0, sizeof(struct cit_ical));
+}
+
+
+void ical_session_shutdown(void) {
+ free(CIT_ICAL);
+}
+
+
+/*
+ * Back end for ical_fixed_output()
+ */
+void ical_fixed_output_backend(icalcomponent *cal, int recursion_level) {
+ icalcomponent *c;
+ icalproperty *p;
+ char buf[256];
+ const char *ch;
+
+ p = icalcomponent_get_first_property(cal, ICAL_SUMMARY_PROPERTY);
+ if (p != NULL) {
+ cprintf("%s\n", (const char *)icalproperty_get_comment(p));
+ }
+
+ p = icalcomponent_get_first_property(cal, ICAL_LOCATION_PROPERTY);
+ if (p != NULL) {
+ cprintf("%s\n", (const char *)icalproperty_get_comment(p));
+ }
+
+ p = icalcomponent_get_first_property(cal, ICAL_DESCRIPTION_PROPERTY);
+ if (p != NULL) {
+ cprintf("%s\n", (const char *)icalproperty_get_comment(p));
+ }
+
+ /* If the component has attendees, iterate through them. */
+ for (p = icalcomponent_get_first_property(cal, ICAL_ATTENDEE_PROPERTY); (p != NULL); p = icalcomponent_get_next_property(cal, ICAL_ATTENDEE_PROPERTY)) {
+ ch = icalproperty_get_attendee(p);
+ if ((ch != NULL) &&
+ !strncasecmp(ch, "MAILTO:", 7)) {
+
+ /* screen name or email address */
+ safestrncpy(buf, ch + 7, sizeof(buf));
+ striplt(buf);
+ cprintf("%s ", buf);
+ }
+ cprintf("\n");
+ }
+
+ /* If the component has subcomponents, recurse through them. */
+ for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
+ (c != 0);
+ c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
+ /* Recursively process subcomponent */
+ ical_fixed_output_backend(c, recursion_level+1);
+ }
+}
+
+
+/*
+ * Function to output iCalendar data as plain text. Nobody uses MSG0
+ * anymore, so really this is just so we expose the vCard data to the full
+ * text indexer.
+ */
+void ical_fixed_output(char *ptr, int len) {
+ icalcomponent *cal;
+ char *stringy_cal;
+
+ stringy_cal = malloc(len + 1);
+ safestrncpy(stringy_cal, ptr, len + 1);
+ cal = icalcomponent_new_from_string(stringy_cal);
+ free(stringy_cal);
+
+ if (cal == NULL) {
+ return;
+ }
+
+ ical_fixed_output_backend(cal, 0);
+
+ /* Free the memory we obtained from libical's constructor */
+ icalcomponent_free(cal);
+}
+
+
+/*
+ * Register this module with the Citadel server.
+ */
+char *ctdl_module_init_calendar(void) {
+ if (!threading) {
+
+ /* Tell libical to return errors instead of aborting if it gets bad data */
+
+#ifdef LIBICAL_ICAL_EXPORT // cheap and sleazy way to detect libical >=2.0
+ icalerror_set_errors_are_fatal(0);
+#else
+ icalerror_errors_are_fatal = 0;
+#endif
+
+ /* Use our own application prefix in tzid's generated from system tzdata */
+ icaltimezone_set_tzid_prefix("/citadel.org/");
+
+ /* Initialize our hook functions */
+ CtdlRegisterMessageHook(ical_obj_beforesave, EVT_BEFORESAVE);
+ CtdlRegisterMessageHook(ical_obj_aftersave, EVT_AFTERSAVE);
+ CtdlRegisterSessionHook(ical_CtdlCreateRoom, EVT_LOGIN, PRIO_LOGIN + 1);
+ CtdlRegisterProtoHook(cmd_ical, "ICAL", "Citadel iCalendar commands");
+ CtdlRegisterSessionHook(ical_session_startup, EVT_START, PRIO_START + 1);
+ CtdlRegisterSessionHook(ical_session_shutdown, EVT_STOP, PRIO_STOP + 80);
+ CtdlRegisterFixedOutputHook("text/calendar", ical_fixed_output);
+ CtdlRegisterFixedOutputHook("application/ics", ical_fixed_output);
+ }
+
+ /* return our module name for the log */
+ return "calendar";
+}
--- /dev/null
+/*
+ * iCalendar implementation for Citadel
+ *
+ *
+ * Copyright (c) 1987-2012 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.
+ *
+ *
+ *
+ *
+ */
+
+/*
+ * "server_generated_invitations" tells the Citadel server that the
+ * client wants invitations to be generated and sent out by the
+ * server. Set to 1 to enable this functionality.
+ *
+ * "avoid_sending_invitations" is a server-internal variable. It is
+ * set internally during certain transactions and cleared
+ * automatically.
+ */
+struct cit_ical {
+ int server_generated_invitations;
+ int avoid_sending_invitations;
+};
+
+#define CIT_ICAL CC->CIT_ICAL
+#define MAX_RECUR 1000
--- /dev/null
+/*
+ * checkpointing module for the database
+ *
+ * Copyright (c) 1987-2022 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.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+
+#include <libcitadel.h>
+
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../msgbase.h"
+#include "../../sysdep_decls.h"
+#include "../../config.h"
+#include "../../threads.h"
+#include "../../ctdl_module.h"
+#include "../../context.h"
+
+char *ctdl_module_init_checkpoint(void) {
+ if (threading) {
+ CtdlRegisterSessionHook(cdb_checkpoint, EVT_TIMER, PRIO_CLEANUP + 10);
+ }
+ /* return our module name for the log */
+ return "checkpoint";
+}
--- /dev/null
+/*
+ * This module allows Citadel to use clamd to filter incoming messages
+ * arriving via SMTP. For more information on clamd, visit
+ * http://clamav.net (the ClamAV project is not in any way
+ * affiliated with the Citadel project).
+ *
+ * Copyright (c) 1987-2022 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.
+ */
+
+#define CLAMD_PORT "3310"
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/socket.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../control.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../domain.h"
+#include "../../clientsocket.h"
+#include "../../ctdl_module.h"
+
+
+/*
+ * Connect to the clamd server and scan a message.
+ */
+int clamd(struct CtdlMessage *msg, struct recptypes *recp) {
+ int sock = (-1);
+ int streamsock = (-1);
+ char clamhosts[SIZ];
+ int num_clamhosts;
+ char buf[SIZ];
+ char hostbuf[SIZ];
+ char portbuf[SIZ];
+ int is_virus = 0;
+ int clamhost;
+ StrBuf *msgtext;
+ CitContext *CCC;
+
+ /* See if we have any clamd hosts configured */
+ num_clamhosts = get_hosts(clamhosts, "clamav");
+ if (num_clamhosts < 1) {
+ return(0);
+ }
+
+ /* Try them one by one until we get a working one */
+ for (clamhost=0; clamhost<num_clamhosts; ++clamhost) {
+ extract_token(buf, clamhosts, clamhost, '|', sizeof buf);
+ syslog(LOG_INFO, "Connecting to clamd at <%s>\n", buf);
+
+ /* Assuming a host:port entry */
+ extract_token(hostbuf, buf, 0, ':', sizeof hostbuf);
+ if (extract_token(portbuf, buf, 1, ':', sizeof portbuf)==-1)
+ /* Didn't specify a port so we'll try the psuedo-standard 3310 */
+ sock = sock_connect(hostbuf, CLAMD_PORT);
+ else
+ /* Port specified lets try connecting to it! */
+ sock = sock_connect(hostbuf, portbuf);
+
+ if (sock >= 0) syslog(LOG_DEBUG, "Connected!\n");
+ }
+
+ if (sock < 0) {
+ /* If the service isn't running, just pass the mail
+ * through. Potentially throwing away mails isn't good.
+ */
+ return(0);
+ }
+ CCC=CC;
+ CCC->SBuf.Buf = NewStrBuf();
+ CCC->sMigrateBuf = NewStrBuf();
+ CCC->SBuf.ReadWritePointer = NULL;
+
+ /* Command */
+ syslog(LOG_DEBUG, "Transmitting STREAM command\n");
+ sprintf(buf, "STREAM\r\n");
+ sock_write(&sock, buf, strlen(buf));
+
+ syslog(LOG_DEBUG, "Waiting for PORT number\n");
+ if (sock_getln(&sock, buf, sizeof buf) < 0) {
+ goto bail;
+ }
+
+ syslog(LOG_DEBUG, "<%s\n", buf);
+ if (strncasecmp(buf, "PORT", 4)!=0) {
+ goto bail;
+ }
+
+ /* Should have received a port number to connect to */
+ extract_token(portbuf, buf, 1, ' ', sizeof portbuf);
+
+ /* Attempt to establish connection to STREAM socket */
+ streamsock = sock_connect(hostbuf, portbuf);
+
+ if (streamsock < 0) {
+ /* If the service isn't running, just pass the mail
+ * through. Potentially throwing away mails isn't good.
+ */
+ FreeStrBuf(&CCC->SBuf.Buf);
+ FreeStrBuf(&CCC->sMigrateBuf);
+ return(0);
+ }
+ else {
+ syslog(LOG_DEBUG, "STREAM socket connected!\n");
+ }
+
+
+ /* Message */
+ CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+ CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, 0);
+ msgtext = CC->redirect_buffer;
+ CC->redirect_buffer = NULL;
+
+ sock_write(&streamsock, SKEY(msgtext));
+ FreeStrBuf(&msgtext);
+
+ /* Close the streamsocket connection; this tells clamd
+ * that we're done.
+ */
+ if (streamsock != -1) {
+ close(streamsock);
+ }
+
+ /* Response */
+ syslog(LOG_DEBUG, "Awaiting response\n");
+ if (sock_getln(&sock, buf, sizeof buf) < 0) {
+ goto bail;
+ }
+ syslog(LOG_DEBUG, "<%s\n", buf);
+ if (strncasecmp(buf, "stream: OK", 10)!=0) {
+ is_virus = 1;
+ }
+
+ if (is_virus) {
+ CM_SetField(msg, eErrorMsg, HKEY("message rejected by virus filter"));
+ }
+
+bail: close(sock);
+ FreeStrBuf(&CCC->SBuf.Buf);
+ FreeStrBuf(&CCC->sMigrateBuf);
+ return(is_virus);
+}
+
+
+char *ctdl_module_init_virus(void) {
+ if (!threading) {
+ CtdlRegisterMessageHook(clamd, EVT_SMTPSCAN);
+ }
+
+ /* return our module name for the log */
+ return "virus";
+}
--- /dev/null
+// Copyright (c) 1987-2022 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.
+
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include "../../sysdep.h"
+
+#ifdef HAVE_OPENSSL
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#endif
+
+#include <time.h>
+
+#ifdef HAVE_PTHREAD_H
+#include <pthread.h>
+#endif
+
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+#include <stdio.h>
+#include <libcitadel.h>
+#include "../../server.h"
+#include "serv_crypto.h"
+#include "../../sysdep_decls.h"
+#include "../../citadel.h"
+#include "../../config.h"
+#include "../../ctdl_module.h"
+
+#ifdef HAVE_OPENSSL
+
+SSL_CTX *ssl_ctx = NULL; // This SSL context is used for all sessions.
+char *ssl_cipher_list = CIT_CIPHERS;
+
+// If a private key does not exist, generate one now.
+void generate_key(char *keyfilename) {
+ int ret = 0;
+ RSA *rsa = NULL;
+ BIGNUM *bne = NULL;
+ int bits = 2048;
+ unsigned long e = RSA_F4;
+ FILE *fp;
+
+ if (access(keyfilename, R_OK) == 0) { // Already have one.
+ syslog(LOG_INFO, "crypto: %s exists and is readable", keyfilename);
+ return;
+ }
+
+ syslog(LOG_INFO, "crypto: generating RSA key pair");
+
+ // generate rsa key
+ bne = BN_new();
+ ret = BN_set_word(bne,e);
+ if (ret != 1) {
+ goto free_all;
+ }
+
+ rsa = RSA_new();
+ ret = RSA_generate_key_ex(rsa, bits, bne, NULL);
+ if (ret != 1) {
+ goto free_all;
+ }
+
+ // write the key file
+ fp = fopen(keyfilename, "w");
+ if (fp != NULL) {
+ chmod(keyfilename, 0600);
+ if (PEM_write_RSAPrivateKey(fp, // the file
+ rsa, // the key
+ NULL, // no enc
+ NULL, // no passphrase
+ 0, // no passphrase
+ NULL, // no callback
+ NULL // no callback
+ ) != 1) {
+ syslog(LOG_ERR, "crypto: cannot write key: %s", ERR_reason_error_string(ERR_get_error()));
+ unlink(keyfilename);
+ }
+ fclose(fp);
+ }
+
+ // free the memory we used
+free_all:
+ RSA_free(rsa);
+ BN_free(bne);
+}
+
+
+// If a certificate does not exist, generate a self-signed one now.
+void generate_certificate(char *keyfilename, char *certfilename) {
+ RSA *private_key = NULL;
+ EVP_PKEY *public_key = NULL;
+ X509_REQ *certificate_signing_request = NULL;
+ X509_NAME *name = NULL;
+ X509 *certificate = NULL;
+ FILE *fp;
+
+ if (access(certfilename, R_OK) == 0) { // already have one.
+ syslog(LOG_INFO, "crypto: %s exists and is readable", certfilename);
+ return;
+ }
+
+ syslog(LOG_INFO, "crypto: generating a self-signed certificate");
+
+ // Read in our private key.
+ fp = fopen(keyfilename, "r");
+ if (fp) {
+ private_key = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL);
+ fclose(fp);
+ }
+
+ if (!private_key) {
+ syslog(LOG_ERR, "crypto: cannot read the private key");
+ return;
+ }
+
+ // Create a public key from the private key
+ public_key = EVP_PKEY_new();
+ if (!public_key) {
+ syslog(LOG_ERR, "crypto: cannot allocate public key");
+ RSA_free(private_key);
+ return;
+ }
+ EVP_PKEY_assign_RSA(public_key, private_key);
+
+ // Create a certificate signing request
+ certificate_signing_request = X509_REQ_new();
+ if (!certificate_signing_request) {
+ syslog(LOG_ERR, "crypto: cannot allocate certificate signing request");
+ RSA_free(private_key);
+ EVP_PKEY_free(public_key);
+ return;
+ }
+
+ // Assign the public key to the certificate signing request
+ X509_REQ_set_pubkey(certificate_signing_request, public_key);
+ X509_REQ_set_version(certificate_signing_request, 0L);
+
+ // Tell it who we are
+ name = X509_REQ_get_subject_name(certificate_signing_request);
+ X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned const char *)"ZZ", -1, -1, 0);
+ X509_NAME_add_entry_by_txt(name, "ST", MBSTRING_ASC, (unsigned const char *)"The World", -1, -1, 0);
+ X509_NAME_add_entry_by_txt(name, "L", MBSTRING_ASC, (unsigned const char *)"My Location", -1, -1, 0);
+ X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned const char *)"Generic certificate", -1, -1, 0);
+ X509_NAME_add_entry_by_txt(name, "OU", MBSTRING_ASC, (unsigned const char *)"Citadel server", -1, -1, 0);
+ X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned const char *)"*", -1, -1, 0);
+ X509_REQ_set_subject_name(certificate_signing_request, name);
+
+ // Sign the CSR
+ if (!X509_REQ_sign(certificate_signing_request, public_key, EVP_md5())) {
+ syslog(LOG_ERR, "crypto: X509_REQ_sign(): error");
+ X509_REQ_free(certificate_signing_request);
+ RSA_free(private_key);
+ EVP_PKEY_free(public_key);
+ return;
+ }
+
+ // Generate the self-signed certificate
+ certificate = X509_new();
+ if (!certificate) {
+ syslog(LOG_ERR, "crypto: cannot allocate X.509 certificate");
+ X509_REQ_free(certificate_signing_request);
+ RSA_free(private_key);
+ EVP_PKEY_free(public_key);
+ return;
+ }
+
+ ASN1_INTEGER_set(X509_get_serialNumber(certificate), 0);
+ X509_set_issuer_name(certificate, X509_REQ_get_subject_name(certificate_signing_request));
+ X509_set_subject_name(certificate, X509_REQ_get_subject_name(certificate_signing_request));
+ X509_gmtime_adj(X509_get_notBefore(certificate), 0);
+ X509_gmtime_adj(X509_get_notAfter(certificate), (long)60*60*24*SIGN_DAYS);
+ X509_set_pubkey(certificate, public_key);
+ X509_REQ_free(certificate_signing_request); // We're done with the CSR; free it
+
+ // Finally, sign the certificate with our private key.
+ if (!X509_sign(certificate, public_key, EVP_md5())) {
+ syslog(LOG_ERR, "crypto: X509_sign() error");
+ X509_free(certificate);
+ RSA_free(private_key);
+ EVP_PKEY_free(public_key);
+ return;
+ }
+
+ // Write the certificate to disk
+ fp = fopen(certfilename, "w");
+ if (fp != NULL) {
+ chmod(certfilename, 0600);
+ PEM_write_X509(fp, certificate);
+ fclose(fp);
+ }
+ else {
+ syslog(LOG_ERR, "crypto: %s: %m", certfilename);
+ }
+
+ X509_free(certificate);
+ EVP_PKEY_free(public_key);
+ // do not RSA_free(private_key); because it was freed by EVP_PKEY_free() above
+}
+
+
+// Set the private key and certificate chain for the global SSL Context.
+// This is called during initialization, and can be called again later if the certificate changes.
+void bind_to_key_and_certificate(void) {
+
+ SSL_CTX *old_ctx = NULL;
+ SSL_CTX *new_ctx = NULL;
+
+ const SSL_METHOD *method = SSLv23_server_method();
+ if (!method) {
+ syslog(LOG_ERR, "crypto: SSLv23_server_method() failed: %s", ERR_reason_error_string(ERR_get_error()));
+ return;
+ }
+
+ new_ctx = SSL_CTX_new(method);
+ if (!new_ctx) {
+ syslog(LOG_ERR, "crypto: SSL_CTX_new failed: %s", ERR_reason_error_string(ERR_get_error()));
+ return;
+ }
+
+ if (!(SSL_CTX_set_cipher_list(new_ctx, ssl_cipher_list))) {
+ syslog(LOG_ERR, "crypto: SSL_CTX_set_cipher_list failed: %s", ERR_reason_error_string(ERR_get_error()));
+ return;
+ }
+
+ syslog(LOG_DEBUG, "crypto: using certificate chain %s", file_crpt_file_cer);
+ if (!SSL_CTX_use_certificate_chain_file(new_ctx, file_crpt_file_cer)) {
+ syslog(LOG_ERR, "crypto: SSL_CTX_use_certificate_chain_file failed: %s", ERR_reason_error_string(ERR_get_error()));
+ return;
+ }
+
+ syslog(LOG_DEBUG, "crypto: using private key %s", file_crpt_file_key);
+ if (!SSL_CTX_use_PrivateKey_file(new_ctx, file_crpt_file_key, SSL_FILETYPE_PEM)) {
+ syslog(LOG_ERR, "crypto: SSL_CTX_use_PrivateKey_file failed: %s", ERR_reason_error_string(ERR_get_error()));
+ return;
+ }
+
+ old_ctx = ssl_ctx;
+ ssl_ctx = new_ctx; // All future binds will use the new certificate
+
+ if (old_ctx != NULL) {
+ sleep(1); // Hopefully wait until all in-progress binds to the old certificate have completed
+ SSL_CTX_free(old_ctx);
+ }
+}
+
+
+// Check the modification time of the key and certificate files. Reload if either one changed.
+void update_key_and_cert_if_needed(void) {
+ static time_t previous_mtime = 0;
+ struct stat keystat;
+ struct stat certstat;
+
+ if (stat(file_crpt_file_key, &keystat) != 0) {
+ syslog(LOG_ERR, "%s: %s", file_crpt_file_key, strerror(errno));
+ return;
+ }
+ if (stat(file_crpt_file_cer, &certstat) != 0) {
+ syslog(LOG_ERR, "%s: %s", file_crpt_file_cer, strerror(errno));
+ return;
+ }
+
+ if ((keystat.st_mtime + certstat.st_mtime) != previous_mtime) {
+ begin_critical_section(S_OPENSSL);
+ bind_to_key_and_certificate();
+ end_critical_section(S_OPENSSL);
+ previous_mtime = keystat.st_mtime + certstat.st_mtime;
+ }
+}
+
+
+// Initialize the TLS subsystem.
+void init_ssl(void) {
+
+ // Initialize the OpenSSL library
+ SSL_library_init();
+ SSL_load_error_strings();
+
+ // Load (or generate) a key and certificate
+ mkdir(ctdl_key_dir, 0700); // If the keys directory does not exist, create it
+ generate_key(file_crpt_file_key); // If a private key does not exist, create it
+ generate_certificate(file_crpt_file_key, file_crpt_file_cer); // If a certificate does not exist, create it
+ bind_to_key_and_certificate(); // Load key and cert from disk, and bind to them.
+
+ // Register some Citadel protocol commands for dealing with encrypted sessions
+ CtdlRegisterProtoHook(cmd_stls, "STLS", "Start TLS session");
+ CtdlRegisterProtoHook(cmd_gtls, "GTLS", "Get TLS session status");
+ CtdlRegisterSessionHook(endtls, EVT_STOP, PRIO_STOP + 10);
+}
+
+
+// client_write_ssl() Send binary data to the client encrypted.
+void client_write_ssl(const char *buf, int nbytes) {
+ int retval;
+ int nremain;
+ char junk[1];
+
+ nremain = nbytes;
+
+ while (nremain > 0) {
+ if (SSL_want_write(CC->ssl)) {
+ if ((SSL_read(CC->ssl, junk, 0)) < 1) {
+ syslog(LOG_DEBUG, "crypto: SSL_read in client_write: %s", ERR_reason_error_string(ERR_get_error()));
+ }
+ }
+ retval =
+ SSL_write(CC->ssl, &buf[nbytes - nremain], nremain);
+ if (retval < 1) {
+ long errval;
+
+ errval = SSL_get_error(CC->ssl, retval);
+ if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
+ sleep(1);
+ continue;
+ }
+ syslog(LOG_DEBUG, "crypto: SSL_write got error %ld, ret %d", errval, retval);
+ if (retval == -1) {
+ syslog(LOG_DEBUG, "crypto: errno is %d", errno);
+ }
+ endtls();
+ client_write(&buf[nbytes - nremain], nremain);
+ return;
+ }
+ nremain -= retval;
+ }
+}
+
+
+// read data from the encrypted layer.
+int client_read_sslbuffer(StrBuf *buf, int timeout) {
+ char sbuf[16384]; // OpenSSL communicates in 16k blocks, so let's speak its native tongue.
+ int rlen;
+ char junk[1];
+ SSL *pssl = CC->ssl;
+
+ if (pssl == NULL) return(-1);
+
+ while (1) {
+ if (SSL_want_read(pssl)) {
+ if ((SSL_write(pssl, junk, 0)) < 1) {
+ syslog(LOG_DEBUG, "crypto: SSL_write in client_read");
+ }
+ }
+ rlen = SSL_read(pssl, sbuf, sizeof(sbuf));
+ if (rlen < 1) {
+ long errval;
+
+ errval = SSL_get_error(pssl, rlen);
+ if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
+ sleep(1);
+ continue;
+ }
+ syslog(LOG_DEBUG, "crypto: SSL_read got error %ld", errval);
+ endtls();
+ return (-1);
+ }
+ StrBufAppendBufPlain(buf, sbuf, rlen, 0);
+ return rlen;
+ }
+ return (0);
+}
+
+
+int client_readline_sslbuffer(StrBuf *Line, StrBuf *IOBuf, const char **Pos, int timeout) {
+ const char *pos = NULL;
+ const char *pLF;
+ int len, rlen;
+ int nSuccessLess = 0;
+ const char *pch = NULL;
+
+ if ((Line == NULL) || (Pos == NULL) || (IOBuf == NULL)) {
+ if (Pos != NULL) {
+ *Pos = NULL;
+ }
+ return -1;
+ }
+
+ pos = *Pos;
+ if ((StrLength(IOBuf) > 0) && (pos != NULL) && (pos < ChrPtr(IOBuf) + StrLength(IOBuf))) {
+ pch = pos;
+ pch = strchr(pch, '\n');
+
+ if (pch == NULL) {
+ StrBufAppendBufPlain(Line, pos, StrLength(IOBuf) - (pos - ChrPtr(IOBuf)), 0);
+ FlushStrBuf(IOBuf);
+ *Pos = NULL;
+ }
+ else {
+ int n = 0;
+ if ((pch > ChrPtr(IOBuf)) && (*(pch - 1) == '\r')) {
+ n = 1;
+ }
+ StrBufAppendBufPlain(Line, pos, (pch - pos - n), 0);
+
+ if (StrLength(IOBuf) <= (pch - ChrPtr(IOBuf) + 1)) {
+ FlushStrBuf(IOBuf);
+ *Pos = NULL;
+ }
+ else {
+ *Pos = pch + 1;
+ }
+ return StrLength(Line);
+ }
+ }
+
+ pLF = NULL;
+ while ((nSuccessLess < timeout) && (pLF == NULL) && (CC->ssl != NULL)) {
+
+ rlen = client_read_sslbuffer(IOBuf, timeout);
+ if (rlen < 1) {
+ return -1;
+ }
+ else if (rlen > 0) {
+ pLF = strchr(ChrPtr(IOBuf), '\n');
+ }
+ }
+ *Pos = NULL;
+ if (pLF != NULL) {
+ pos = ChrPtr(IOBuf);
+ len = pLF - pos;
+ if (len > 0 && (*(pLF - 1) == '\r') )
+ len --;
+ StrBufAppendBufPlain(Line, pos, len, 0);
+ if (pLF + 1 >= ChrPtr(IOBuf) + StrLength(IOBuf)) {
+ FlushStrBuf(IOBuf);
+ }
+ else {
+ *Pos = pLF + 1;
+ }
+ return StrLength(Line);
+ }
+ return -1;
+}
+
+
+int client_read_sslblob(StrBuf *Target, long bytes, int timeout) {
+ long baselen;
+ long RemainRead;
+ int retval = 0;
+
+ baselen = StrLength(Target);
+
+ if (StrLength(CC->RecvBuf.Buf) > 0) {
+ long RemainLen;
+ long TotalLen;
+ const char *pchs;
+
+ if (CC->RecvBuf.ReadWritePointer == NULL) {
+ CC->RecvBuf.ReadWritePointer = ChrPtr(CC->RecvBuf.Buf);
+ }
+ pchs = ChrPtr(CC->RecvBuf.Buf);
+ TotalLen = StrLength(CC->RecvBuf.Buf);
+ RemainLen = TotalLen - (pchs - CC->RecvBuf.ReadWritePointer);
+ if (RemainLen > bytes) {
+ RemainLen = bytes;
+ }
+ if (RemainLen > 0) {
+ StrBufAppendBufPlain(Target, CC->RecvBuf.ReadWritePointer, RemainLen, 0);
+ CC->RecvBuf.ReadWritePointer += RemainLen;
+ }
+ if ((ChrPtr(CC->RecvBuf.Buf) + StrLength(CC->RecvBuf.Buf)) <= CC->RecvBuf.ReadWritePointer) {
+ CC->RecvBuf.ReadWritePointer = NULL;
+ FlushStrBuf(CC->RecvBuf.Buf);
+ }
+ }
+
+ if (StrLength(Target) >= bytes + baselen) {
+ return 1;
+ }
+
+ CC->RecvBuf.ReadWritePointer = NULL;
+
+ while ((StrLength(Target) < bytes + baselen) && (retval >= 0)) {
+ retval = client_read_sslbuffer(CC->RecvBuf.Buf, timeout);
+ if (retval >= 0) {
+ RemainRead = bytes - (StrLength (Target) - baselen);
+ if (RemainRead < StrLength(CC->RecvBuf.Buf)) {
+ StrBufAppendBufPlain(
+ Target,
+ ChrPtr(CC->RecvBuf.Buf),
+ RemainRead, 0);
+ CC->RecvBuf.ReadWritePointer = ChrPtr(CC->RecvBuf.Buf) + RemainRead;
+ break;
+ }
+ StrBufAppendBuf(Target, CC->RecvBuf.Buf, 0);
+ FlushStrBuf(CC->RecvBuf.Buf);
+ }
+ else {
+ FlushStrBuf(CC->RecvBuf.Buf);
+ return -1;
+
+ }
+ }
+ return 1;
+}
+
+
+// CtdlStartTLS() starts TLS encryption for the current session. It
+// must be supplied with pre-generated strings for responses of "ok," "no
+// support for TLS," and "error" so that we can use this in any protocol.
+void CtdlStartTLS(char *ok_response, char *nosup_response, char *error_response) {
+ int retval, bits, alg_bits;
+
+ if (CC->redirect_ssl) {
+ syslog(LOG_ERR, "crypto: attempt to begin SSL on an already encrypted connection");
+ if (error_response != NULL) {
+ cprintf("%s", error_response);
+ }
+ return;
+ }
+
+ if (!ssl_ctx) {
+ syslog(LOG_ERR, "crypto: SSL failed: ../../context.has not been initialized");
+ if (nosup_response != NULL) {
+ cprintf("%s", nosup_response);
+ }
+ return;
+ }
+
+ update_key_and_cert_if_needed(); // did someone update the key or cert? if so, re-bind them
+
+ if (!(CC->ssl = SSL_new(ssl_ctx))) {
+ syslog(LOG_ERR, "crypto: SSL_new failed: %s", ERR_reason_error_string(ERR_get_error()));
+ if (error_response != NULL) {
+ cprintf("%s", error_response);
+ }
+ return;
+ }
+ if (!(SSL_set_fd(CC->ssl, CC->client_socket))) {
+ syslog(LOG_ERR, "crypto: SSL_set_fd failed: %s", ERR_reason_error_string(ERR_get_error()));
+ SSL_free(CC->ssl);
+ CC->ssl = NULL;
+ if (error_response != NULL) {
+ cprintf("%s", error_response);
+ }
+ return;
+ }
+ if (ok_response != NULL) {
+ cprintf("%s", ok_response);
+ }
+ retval = SSL_accept(CC->ssl);
+ if (retval < 1) {
+ // Can't notify the client of an error here; they will
+ // discover the problem at the SSL layer and should
+ // revert to unencrypted communications.
+ syslog(LOG_ERR, "crypto: SSL_accept failed: %s", ERR_reason_error_string(ERR_get_error()));
+ SSL_free(CC->ssl);
+ CC->ssl = NULL;
+ return;
+ }
+ bits = SSL_CIPHER_get_bits(SSL_get_current_cipher(CC->ssl), &alg_bits);
+ syslog(LOG_INFO, "crypto: TLS using %s on %s (%d of %d bits)",
+ SSL_CIPHER_get_name(SSL_get_current_cipher(CC->ssl)),
+ SSL_CIPHER_get_version(SSL_get_current_cipher(CC->ssl)),
+ bits, alg_bits
+ );
+ CC->redirect_ssl = 1;
+}
+
+
+// cmd_stls() starts TLS encryption for the current session
+void cmd_stls(char *params) {
+ char ok_response[SIZ];
+ char nosup_response[SIZ];
+ char error_response[SIZ];
+
+ unbuffer_output();
+
+ sprintf(ok_response, "%d Begin TLS negotiation now\n", CIT_OK);
+ sprintf(nosup_response, "%d TLS not supported here\n", ERROR + CMD_NOT_SUPPORTED);
+ sprintf(error_response, "%d TLS negotiation error\n", ERROR + INTERNAL_ERROR);
+
+ CtdlStartTLS(ok_response, nosup_response, error_response);
+}
+
+
+// cmd_gtls() returns status info about the TLS connection
+void cmd_gtls(char *params) {
+ int bits, alg_bits;
+
+ if (!CC->ssl || !CC->redirect_ssl) {
+ cprintf("%d Session is not encrypted.\n", ERROR);
+ return;
+ }
+ bits = SSL_CIPHER_get_bits(SSL_get_current_cipher(CC->ssl), &alg_bits);
+ cprintf("%d %s|%s|%d|%d\n", CIT_OK,
+ SSL_CIPHER_get_version(SSL_get_current_cipher(CC->ssl)),
+ SSL_CIPHER_get_name(SSL_get_current_cipher(CC->ssl)),
+ alg_bits, bits);
+}
+
+
+// endtls() shuts down the TLS connection
+void endtls(void) {
+ if (!CC->ssl) {
+ CC->redirect_ssl = 0;
+ return;
+ }
+
+ syslog(LOG_INFO, "crypto: ending TLS on this session");
+ SSL_shutdown(CC->ssl);
+ SSL_free(CC->ssl);
+ CC->ssl = NULL;
+ CC->redirect_ssl = 0;
+}
+
+#endif // HAVE_OPENSSL
--- /dev/null
+
+/*
+ * Number of days for which self-signed certs are valid.
+ */
+#define SIGN_DAYS 1100 // Just over three years
+
+// Which ciphers will be offered; see https://www.openssl.org/docs/manmaster/man1/ciphers.html
+#define CIT_CIPHERS "ALL:RC4+RSA:+SSLv2:+TLSv1:!MD5:@STRENGTH"
+
+#ifdef HAVE_OPENSSL
+#define OPENSSL_NO_KRB5 /* work around redhat b0rken ssl headers */
+void init_ssl(void);
+void client_write_ssl (const char *buf, int nbytes);
+int client_read_sslbuffer(StrBuf *buf, int timeout);
+int client_readline_sslbuffer(StrBuf *Target, StrBuf *Buffer, const char **Pos, int timeout);
+int client_read_sslblob(StrBuf *Target, long want_len, int timeout);
+void cmd_stls(char *params);
+void cmd_gtls(char *params);
+void endtls(void);
+void CtdlStartTLS(char *ok_response, char *nosup_response, char *error_response);
+extern SSL_CTX *ssl_ctx;
+
+#endif
--- /dev/null
+#ifndef FILE_OPS_H
+#define FILE_OPS_H
+
+#include "context.h"
+
+void OpenCmdResult (char *, const char *);
+void abort_upl (CitContext *who);
+
+
+#endif /* FILE_OPS_H */
--- /dev/null
+/*
+ * Citadel protocol main dispatcher
+ *
+ * Copyright (c) 1987-2017 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <stdio.h>
+#include <libcitadel.h>
+
+#include "../../citserver.h"
+#include "../../ctdl_module.h"
+#include "../../config.h"
+/*
+ * This loop recognizes all server commands.
+ */
+void do_command_loop(void) {
+ struct CitContext *CCC = CC;
+ char cmdbuf[SIZ];
+
+ time(&CCC->lastcmd);
+ memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
+ if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
+ syslog(LOG_INFO, "Citadel client disconnected: ending session.");
+ CCC->kill_me = KILLME_CLIENT_DISCONNECTED;
+ return;
+ }
+
+ /* Log the server command, but don't show passwords... */
+ if ( (strncasecmp(cmdbuf, "PASS", 4)) && (strncasecmp(cmdbuf, "SETP", 4)) ) {
+ syslog(LOG_DEBUG, "[%s(%ld)] %s",
+ CCC->curr_user, CCC->user.usernum, cmdbuf
+ );
+ }
+ else {
+ syslog(LOG_DEBUG, "[%s(%ld)] <password command hidden from log>",
+ CCC->curr_user, CCC->user.usernum
+ );
+ }
+
+ buffer_output();
+
+ /*
+ * Let other clients see the last command we executed, and
+ * update the idle time, but not NOOP, QNOP, PEXP, GEXP, RWHO, or TIME.
+ */
+ if ( (strncasecmp(cmdbuf, "NOOP", 4))
+ && (strncasecmp(cmdbuf, "QNOP", 4))
+ && (strncasecmp(cmdbuf, "PEXP", 4))
+ && (strncasecmp(cmdbuf, "GEXP", 4))
+ && (strncasecmp(cmdbuf, "RWHO", 4))
+ && (strncasecmp(cmdbuf, "TIME", 4)) ) {
+ strcpy(CCC->lastcmdname, " ");
+ safestrncpy(CCC->lastcmdname, cmdbuf, sizeof(CCC->lastcmdname));
+ time(&CCC->lastidle);
+ }
+
+ if ((strncasecmp(cmdbuf, "ENT0", 4))
+ && (strncasecmp(cmdbuf, "MESG", 4))
+ && (strncasecmp(cmdbuf, "MSGS", 4)))
+ {
+ CCC->cs_flags &= ~CS_POSTING;
+ }
+
+ if (!DLoader_Exec_Cmd(cmdbuf)) {
+ cprintf("%d Unrecognized or unsupported command.\n", ERROR + CMD_NOT_SUPPORTED);
+ }
+
+ unbuffer_output();
+
+ /* Run any after-each-command routines registered by modules */
+ PerformSessionHooks(EVT_CMD);
+}
+
--- /dev/null
+/*
+ * Server functions which handle file transfers and room directories.
+ *
+ * Copyright (c) 1987-2022 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.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <libcitadel.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "../../ctdl_module.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+
+
+/*
+ * Server command to delete a file from a room's directory
+ */
+void cmd_delf(char *filename)
+{
+ char pathname[64];
+ int a;
+
+ if (CtdlAccessCheck(ac_room_aide))
+ return;
+
+ if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
+ cprintf("%d No directory in this room.\n", ERROR + NOT_HERE);
+ return;
+ }
+
+ if (IsEmptyStr(filename)) {
+ cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND);
+ return;
+ }
+ for (a = 0; !IsEmptyStr(&filename[a]); ++a) {
+ if ( (filename[a] == '/') || (filename[a] == '\\') ) {
+ filename[a] = '_';
+ }
+ }
+ snprintf(pathname, sizeof pathname,
+ "%s/%s/%s",
+ ctdl_file_dir,
+ CC->room.QRdirname, filename
+ );
+ a = unlink(pathname);
+ if (a == 0) {
+ cprintf("%d File '%s' deleted.\n", CIT_OK, pathname);
+ }
+ else {
+ cprintf("%d File '%s' not found.\n", ERROR + FILE_NOT_FOUND, pathname);
+ }
+}
+
+
+/*
+ * move a file from one room directory to another
+ */
+void cmd_movf(char *cmdbuf)
+{
+ char filename[PATH_MAX];
+ char pathname[PATH_MAX];
+ char newpath[PATH_MAX];
+ char newroom[ROOMNAMELEN];
+ char buf[PATH_MAX];
+ int a;
+ struct ctdlroom qrbuf;
+
+ extract_token(filename, cmdbuf, 0, '|', sizeof filename);
+ extract_token(newroom, cmdbuf, 1, '|', sizeof newroom);
+
+ if (CtdlAccessCheck(ac_room_aide)) return;
+
+ if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
+ cprintf("%d No directory in this room.\n", ERROR + NOT_HERE);
+ return;
+ }
+
+ if (IsEmptyStr(filename)) {
+ cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND);
+ return;
+ }
+
+ for (a = 0; !IsEmptyStr(&filename[a]); ++a) {
+ if ( (filename[a] == '/') || (filename[a] == '\\') ) {
+ filename[a] = '_';
+ }
+ }
+ snprintf(pathname, sizeof pathname, "./files/%s/%s", CC->room.QRdirname, filename);
+ if (access(pathname, 0) != 0) {
+ cprintf("%d File '%s' not found.\n", ERROR + FILE_NOT_FOUND, pathname);
+ return;
+ }
+
+ if (CtdlGetRoom(&qrbuf, newroom) != 0) {
+ cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, newroom);
+ return;
+ }
+ if ((qrbuf.QRflags & QR_DIRECTORY) == 0) {
+ cprintf("%d '%s' is not a directory room.\n", ERROR + NOT_HERE, qrbuf.QRname);
+ return;
+ }
+ snprintf(newpath, sizeof newpath, "./files/%s/%s", qrbuf.QRdirname, filename);
+ if (link(pathname, newpath) != 0) {
+ cprintf("%d Couldn't move file: %s\n", ERROR + INTERNAL_ERROR, strerror(errno));
+ return;
+ }
+ unlink(pathname);
+
+ /* this is a crude method of copying the file description */
+ snprintf(buf, sizeof buf, "cat ./files/%s/filedir |grep \"%s\" >>./files/%s/filedir", CC->room.QRdirname, filename, qrbuf.QRdirname);
+ system(buf);
+ cprintf("%d File '%s' has been moved.\n", CIT_OK, filename);
+}
+
+
+/*
+ * This code is common to all commands which open a file for downloading,
+ * regardless of whether it's a file from the directory, an image, a network
+ * spool file, a MIME attachment, etc.
+ * It examines the file and displays the OK result code and some information
+ * about the file. NOTE: this stuff is Unix dependent.
+ */
+void OpenCmdResult(char *filename, const char *mime_type)
+{
+ struct stat statbuf;
+ time_t modtime;
+ long filesize;
+
+ fstat(fileno(CC->download_fp), &statbuf);
+ CC->download_fp_total = statbuf.st_size;
+ filesize = (long) statbuf.st_size;
+ modtime = (time_t) statbuf.st_mtime;
+
+ cprintf("%d %ld|%ld|%s|%s\n", CIT_OK, filesize, (long)modtime, filename, mime_type);
+}
+
+
+/*
+ * open a file for downloading
+ */
+void cmd_open(char *cmdbuf)
+{
+ char filename[256];
+ char pathname[PATH_MAX];
+ int a;
+
+ extract_token(filename, cmdbuf, 0, '|', sizeof filename);
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
+ cprintf("%d No directory in this room.\n", ERROR + NOT_HERE);
+ return;
+ }
+
+ if (IsEmptyStr(filename)) {
+ cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND);
+ return;
+ }
+ if (strstr(filename, "../") != NULL)
+ {
+ cprintf("%d syntax error.\n", ERROR + ILLEGAL_VALUE);
+ return;
+ }
+
+ if (CC->download_fp != NULL) {
+ cprintf("%d You already have a download file open.\n", ERROR + RESOURCE_BUSY);
+ return;
+ }
+
+ for (a = 0; !IsEmptyStr(&filename[a]); ++a) {
+ if ( (filename[a] == '/') || (filename[a] == '\\') ) {
+ filename[a] = '_';
+ }
+ }
+
+ snprintf(pathname, sizeof pathname, "%s/%s/%s", ctdl_file_dir, CC->room.QRdirname, filename);
+ CC->download_fp = fopen(pathname, "r");
+
+ if (CC->download_fp == NULL) {
+ cprintf("%d cannot open %s: %s\n", ERROR + INTERNAL_ERROR, pathname, strerror(errno));
+ return;
+ }
+
+ OpenCmdResult(filename, "application/octet-stream");
+}
+
+
+/*
+ * open an image file
+ */
+void cmd_oimg(char *cmdbuf)
+{
+ char filename[PATH_MAX];
+ char pathname[PATH_MAX];
+ char MimeTestBuf[32];
+ int rv;
+
+ extract_token(filename, cmdbuf, 0, '|', sizeof filename);
+
+ if (IsEmptyStr(filename)) {
+ cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND);
+ return;
+ }
+
+ if (CC->download_fp != NULL) {
+ cprintf("%d You already have a download file open.\n", ERROR + RESOURCE_BUSY);
+ return;
+ }
+
+ CC->download_fp = fopen(pathname, "rb");
+ if (CC->download_fp == NULL) {
+ strcat(pathname, ".gif");
+ CC->download_fp = fopen(pathname, "rb");
+ }
+ if (CC->download_fp == NULL) {
+ cprintf("%d Cannot open %s: %s\n", ERROR + FILE_NOT_FOUND, pathname, strerror(errno));
+ return;
+ }
+ rv = fread(&MimeTestBuf[0], 1, 32, CC->download_fp);
+ if (rv == -1) {
+ cprintf("%d Cannot access %s: %s\n", ERROR + FILE_NOT_FOUND, pathname, strerror(errno));
+ return;
+ }
+
+ rewind (CC->download_fp);
+ OpenCmdResult(pathname, GuessMimeType(&MimeTestBuf[0], 32));
+}
+
+
+/*
+ * open a file for uploading
+ */
+void cmd_uopn(char *cmdbuf)
+{
+ int a;
+
+ extract_token(CC->upl_file, cmdbuf, 0, '|', sizeof CC->upl_file);
+ extract_token(CC->upl_mimetype, cmdbuf, 1, '|', sizeof CC->upl_mimetype);
+ extract_token(CC->upl_comment, cmdbuf, 2, '|', sizeof CC->upl_comment);
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
+ cprintf("%d No directory in this room.\n", ERROR + NOT_HERE);
+ return;
+ }
+
+ if (IsEmptyStr(CC->upl_file)) {
+ cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND);
+ return;
+ }
+
+ if (CC->upload_fp != NULL) {
+ cprintf("%d You already have a upload file open.\n", ERROR + RESOURCE_BUSY);
+ return;
+ }
+
+ for (a = 0; !IsEmptyStr(&CC->upl_file[a]); ++a) {
+ if ( (CC->upl_file[a] == '/') || (CC->upl_file[a] == '\\') ) {
+ CC->upl_file[a] = '_';
+ }
+ }
+ snprintf(CC->upl_path, sizeof CC->upl_path, "%s/%s/%s", ctdl_file_dir, CC->room.QRdirname, CC->upl_file);
+ snprintf(CC->upl_filedir, sizeof CC->upl_filedir, "%s/%s/filedir", ctdl_file_dir, CC->room.QRdirname);
+
+ CC->upload_fp = fopen(CC->upl_path, "r");
+ if (CC->upload_fp != NULL) {
+ fclose(CC->upload_fp);
+ CC->upload_fp = NULL;
+ cprintf("%d '%s' already exists\n", ERROR + ALREADY_EXISTS, CC->upl_path);
+ return;
+ }
+
+ CC->upload_fp = fopen(CC->upl_path, "wb");
+ if (CC->upload_fp == NULL) {
+ cprintf("%d Cannot open %s: %s\n", ERROR + INTERNAL_ERROR, CC->upl_path, strerror(errno));
+ return;
+ }
+ cprintf("%d Ok\n", CIT_OK);
+}
+
+
+/*
+ * open an image file for uploading
+ */
+void cmd_uimg(char *cmdbuf)
+{
+ int is_this_for_real;
+ char basenm[256];
+ int a;
+
+ if (num_parms(cmdbuf) < 2) {
+ cprintf("%d Usage error.\n", ERROR + ILLEGAL_VALUE);
+ return;
+ }
+
+ is_this_for_real = extract_int(cmdbuf, 0);
+ extract_token(CC->upl_mimetype, cmdbuf, 1, '|', sizeof CC->upl_mimetype);
+ extract_token(basenm, cmdbuf, 2, '|', sizeof basenm);
+ if (CC->upload_fp != NULL) {
+ cprintf("%d You already have an upload file open.\n", ERROR + RESOURCE_BUSY);
+ return;
+ }
+
+ strcpy(CC->upl_path, "");
+
+ for (a = 0; !IsEmptyStr(&basenm[a]); ++a) {
+ basenm[a] = tolower(basenm[a]);
+ if ( (basenm[a] == '/') || (basenm[a] == '\\') ) {
+ basenm[a] = '_';
+ }
+ }
+
+ if (CC->user.axlevel >= AxAideU) {
+ snprintf(CC->upl_path, sizeof CC->upl_path, "%s/%s", ctdl_image_dir, basenm);
+ }
+
+ if (IsEmptyStr(CC->upl_path)) {
+ cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
+ return;
+ }
+
+ if (is_this_for_real == 0) {
+ cprintf("%d Ok to send image\n", CIT_OK);
+ return;
+ }
+
+ CC->upload_fp = fopen(CC->upl_path, "wb");
+ if (CC->upload_fp == NULL) {
+ cprintf("%d Cannot open %s: %s\n", ERROR + INTERNAL_ERROR, CC->upl_path, strerror(errno));
+ return;
+ }
+ cprintf("%d Ok\n", CIT_OK);
+}
+
+
+/*
+ * close the download file
+ */
+void cmd_clos(char *cmdbuf)
+{
+ if (CC->download_fp == NULL) {
+ cprintf("%d You don't have a download file open.\n", ERROR + RESOURCE_NOT_OPEN);
+ return;
+ }
+
+ fclose(CC->download_fp);
+ CC->download_fp = NULL;
+ cprintf("%d Ok\n", CIT_OK);
+}
+
+
+/*
+ * abort an upload
+ */
+void abort_upl(CitContext *who)
+{
+ if (who->upload_fp != NULL) {
+ fclose(who->upload_fp);
+ who->upload_fp = NULL;
+ unlink(CC->upl_path);
+ }
+}
+
+
+/*
+ * close the upload file
+ */
+void cmd_ucls(char *cmd)
+{
+ FILE *fp;
+ char upload_notice[SIZ];
+
+ if (CC->upload_fp == NULL) {
+ cprintf("%d You don't have an upload file open.\n", ERROR + RESOURCE_NOT_OPEN);
+ return;
+ }
+
+ fclose(CC->upload_fp);
+ CC->upload_fp = NULL;
+
+ if (!strcasecmp(cmd, "1")) {
+ cprintf("%d File '%s' saved.\n", CIT_OK, CC->upl_path);
+ fp = fopen(CC->upl_filedir, "a");
+ if (fp == NULL) {
+ fp = fopen(CC->upl_filedir, "w");
+ }
+ if (fp != NULL) {
+ fprintf(fp, "%s %s %s\n", CC->upl_file, CC->upl_mimetype, CC->upl_comment);
+ fclose(fp);
+ }
+
+ if ((CC->room.QRflags2 & QR2_NOUPLMSG) == 0) {
+ /* put together an upload notice */
+ snprintf(upload_notice, sizeof upload_notice,
+ "NEW UPLOAD: '%s'\n %s\n%s\n",
+ CC->upl_file,
+ CC->upl_comment,
+ CC->upl_mimetype
+ );
+ quickie_message(CC->curr_user, NULL, NULL, CC->room.QRname, upload_notice, 0, NULL);
+ }
+ } else {
+ abort_upl(CC);
+ cprintf("%d File '%s' aborted.\n", CIT_OK, CC->upl_path);
+ }
+}
+
+
+/*
+ * read from the download file
+ */
+void cmd_read(char *cmdbuf)
+{
+ long start_pos;
+ size_t bytes;
+ char buf[SIZ];
+ int rc;
+
+ /* The client will transmit its requested offset and byte count */
+ start_pos = extract_long(cmdbuf, 0);
+ bytes = extract_int(cmdbuf, 1);
+ if ((start_pos < 0) || (bytes <= 0)) {
+ cprintf("%d you have to specify a value > 0.\n", ERROR + ILLEGAL_VALUE);
+ return;
+ }
+
+ if (CC->download_fp == NULL) {
+ cprintf("%d You don't have a download file open.\n", ERROR + RESOURCE_NOT_OPEN);
+ return;
+ }
+
+ /* If necessary, reduce the byte count to the size of our buffer */
+ if (bytes > sizeof(buf)) {
+ bytes = sizeof(buf);
+ }
+
+ rc = fseek(CC->download_fp, start_pos, 0);
+ if (rc < 0) {
+ cprintf("%d your file is smaller than %ld.\n", ERROR + ILLEGAL_VALUE, start_pos);
+ syslog(LOG_ERR, "serv_file: your file %s is smaller than %ld [%s]",
+ CC->upl_path,
+ start_pos,
+ strerror(errno)
+ );
+
+ return;
+ }
+ bytes = fread(buf, 1, bytes, CC->download_fp);
+ if (bytes > 0) {
+ /* Tell the client the actual byte count and transmit it */
+ cprintf("%d %d\n", BINARY_FOLLOWS, (int)bytes);
+ client_write(buf, bytes);
+ }
+ else {
+ cprintf("%d %s\n", ERROR, strerror(errno));
+ }
+}
+
+
+/*
+ * write to the upload file
+ */
+void cmd_writ(char *cmdbuf)
+{
+ struct CitContext *CCC = CC;
+ int bytes;
+ char *buf;
+ int rv;
+
+ unbuffer_output();
+
+ bytes = extract_int(cmdbuf, 0);
+
+ if (CCC->upload_fp == NULL) {
+ cprintf("%d You don't have an upload file open.\n", ERROR + RESOURCE_NOT_OPEN);
+ return;
+ }
+ if (bytes <= 0) {
+ cprintf("%d you have to specify a value > 0.\n", ERROR + ILLEGAL_VALUE);
+ return;
+ }
+
+ if (bytes > 100000) {
+ bytes = 100000;
+ }
+
+ cprintf("%d %d\n", SEND_BINARY, bytes);
+ buf = malloc(bytes + 1);
+ client_read(buf, bytes);
+ rv = fwrite(buf, bytes, 1, CCC->upload_fp);
+ if (rv == -1) {
+ syslog(LOG_ERR, "serv_file: %s", strerror(errno));
+ }
+ free(buf);
+}
+
+
+void files_logout_hook(void)
+{
+ CitContext *CCC = MyContext();
+
+ /*
+ * If there is a download in progress, abort it.
+ */
+ if (CCC->download_fp != NULL) {
+ fclose(CCC->download_fp);
+ CCC->download_fp = NULL;
+ }
+
+ /*
+ * If there is an upload in progress, abort it.
+ */
+ if (CCC->upload_fp != NULL) {
+ abort_upl(CCC);
+ }
+
+}
+
+
+/*
+ * help_subst() - support routine for help file viewer
+ */
+void help_subst(char *strbuf, char *source, char *dest)
+{
+ char workbuf[SIZ];
+ int p;
+
+ while (p = pattern2(strbuf, source), (p >= 0)) {
+ strcpy(workbuf, &strbuf[p + strlen(source)]);
+ strcpy(&strbuf[p], dest);
+ strcat(strbuf, workbuf);
+ }
+}
+
+
+void do_help_subst(char *buffer)
+{
+ char buf2[16];
+
+ help_subst(buffer, "^nodename", CtdlGetConfigStr("c_nodename"));
+ help_subst(buffer, "^humannode", CtdlGetConfigStr("c_humannode"));
+ help_subst(buffer, "^fqdn", CtdlGetConfigStr("c_fqdn"));
+ help_subst(buffer, "^username", CC->user.fullname);
+ snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
+ help_subst(buffer, "^usernum", buf2);
+ help_subst(buffer, "^sysadm", CtdlGetConfigStr("c_sysadm"));
+ help_subst(buffer, "^variantname", CITADEL);
+ help_subst(buffer, "^maxsessions", CtdlGetConfigStr("c_maxsessions")); // yes it's numeric but str is ok here
+ help_subst(buffer, "^bbsdir", ctdl_message_dir);
+}
+
+
+typedef const char *ccharp;
+/*
+ * display system messages or help
+ */
+void cmd_mesg(char *mname)
+{
+ FILE *mfp;
+ char targ[256];
+ char buf[256];
+ char buf2[256];
+ DIR *dp;
+ struct dirent *d;
+
+ extract_token(buf, mname, 0, '|', sizeof buf);
+
+ snprintf(buf2, sizeof buf2, "%s.%d.%d", buf, CC->cs_clientdev, CC->cs_clienttyp);
+
+ /* If the client requested "?" then produce a listing */
+ if (!strcmp(buf, "?")) {
+ cprintf("%d %s\n", LISTING_FOLLOWS, buf);
+ dp = opendir(ctdl_message_dir);
+ if (dp != NULL) {
+ while (d = readdir(dp), d != NULL) {
+ if (d->d_name[0] != '.') {
+ cprintf(" %s\n", d->d_name);
+ }
+ }
+ closedir(dp);
+ }
+ cprintf("000\n");
+ return;
+ }
+
+ /* Otherwise, look for the requested file by name. */
+ snprintf(targ, sizeof targ, "%s/%s", ctdl_message_dir, buf);
+ mfp = fopen(targ, "r");
+ if (mfp==NULL) {
+ cprintf("%d Cannot open '%s': %s\n",
+ ERROR + FILE_NOT_FOUND, targ, strerror(errno));
+ return;
+ }
+ cprintf("%d %s\n", LISTING_FOLLOWS, buf);
+
+ while (fgets(buf, (sizeof buf - 1), mfp) != NULL) {
+ buf[strlen(buf)-1] = 0;
+ do_help_subst(buf);
+ cprintf("%s\n",buf);
+ }
+
+ fclose(mfp);
+ cprintf("000\n");
+}
+
+
+/*
+ * enter system messages or help
+ */
+void cmd_emsg(char *mname)
+{
+ FILE *mfp;
+ char targ[256];
+ char buf[256];
+ int a;
+
+ unbuffer_output();
+
+ if (CtdlAccessCheck(ac_aide)) return;
+
+ extract_token(buf, mname, 0, '|', sizeof buf);
+ for (a=0; !IsEmptyStr(&buf[a]); ++a) { /* security measure */
+ if (buf[a] == '/') buf[a] = '.';
+ }
+
+ if (IsEmptyStr(targ)) {
+ snprintf(targ, sizeof targ, "%s/%s", ctdl_message_dir, buf);
+ }
+
+ mfp = fopen(targ, "w");
+ if (mfp==NULL) {
+ cprintf("%d Cannot open '%s': %s\n",
+ ERROR + INTERNAL_ERROR, targ, strerror(errno));
+ return;
+ }
+ cprintf("%d %s\n", SEND_LISTING, targ);
+
+ while (client_getln(buf, sizeof buf) >=0 && strcmp(buf, "000")) {
+ fprintf(mfp, "%s\n", buf);
+ }
+
+ fclose(mfp);
+}
+
+/*****************************************************************************/
+/* MODULE INITIALIZATION STUFF */
+/*****************************************************************************/
+
+char *ctdl_module_init_file_ops(void) {
+ if (!threading) {
+ CtdlRegisterSessionHook(files_logout_hook, EVT_LOGOUT, PRIO_LOGOUT + 8);
+ CtdlRegisterProtoHook(cmd_delf, "DELF", "Delete a file");
+ CtdlRegisterProtoHook(cmd_movf, "MOVF", "Move a file");
+ CtdlRegisterProtoHook(cmd_open, "OPEN", "Open a download file transfer");
+ CtdlRegisterProtoHook(cmd_clos, "CLOS", "Close a download file transfer");
+ CtdlRegisterProtoHook(cmd_uopn, "UOPN", "Open an upload file transfer");
+ CtdlRegisterProtoHook(cmd_ucls, "UCLS", "Close an upload file transfer");
+ CtdlRegisterProtoHook(cmd_read, "READ", "File transfer read operation");
+ CtdlRegisterProtoHook(cmd_writ, "WRIT", "File transfer write operation");
+ CtdlRegisterProtoHook(cmd_oimg, "OIMG", "Open an image file for download");
+ CtdlRegisterProtoHook(cmd_uimg, "UIMG", "Upload an image file");
+ CtdlRegisterProtoHook(cmd_mesg, "MESG", "fetch system banners");
+ CtdlRegisterProtoHook(cmd_emsg, "EMSG", "submit system banners");
+ }
+ /* return our Subversion id for the Log */
+ return "file_ops";
+}
--- /dev/null
+// Message-related protocol commands for Citadel clients
+//
+// Copyright (c) 1987-2022 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.
+
+#include <stdio.h>
+#include <libcitadel.h>
+#include "../../citserver.h"
+#include "../../ctdl_module.h"
+#include "../../internet_addressing.h"
+#include "../../user_ops.h"
+#include "../../room_ops.h"
+#include "../../config.h"
+
+extern char *msgkeys[];
+
+
+// Back end for the MSGS command: output message number only.
+void simple_listing(long msgnum, void *userdata) {
+ cprintf("%ld\n", msgnum);
+}
+
+
+// Back end for the MSGS command: output header summary.
+void headers_listing(long msgnum, void *userdata) {
+ struct CtdlMessage *msg;
+ int output_mode = *(int *)userdata;
+
+ msg = CtdlFetchMessage(msgnum, 0);
+ if (msg == NULL) {
+ cprintf("%ld|0|||||||\n", msgnum);
+ return;
+ }
+
+ // change all vertical bars in the subject to hyphens so it doesn't screw up the protocol
+ if (!CM_IsEmpty(msg, eMsgSubject)) {
+ char *p;
+ for (p=msg->cm_fields[eMsgSubject]; *p; p++) {
+ if (*p == '|') {
+ *p = '-';
+ }
+ }
+ }
+
+ // output all fields except the references hash
+ cprintf("%ld|%s|%s|%s|%s|%s",
+ msgnum,
+ (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"),
+ (!CM_IsEmpty(msg, eAuthor) ? msg->cm_fields[eAuthor] : ""),
+ CtdlGetConfigStr("c_nodename"), // no more nodenames anymore
+ (!CM_IsEmpty(msg, erFc822Addr) ? msg->cm_fields[erFc822Addr] : ""),
+ (!CM_IsEmpty(msg, eMsgSubject) ? msg->cm_fields[eMsgSubject] : "")
+ );
+
+ if (output_mode == MSG_HDRS_THREADS) { // field view with thread hashes
+
+ // output the references hash
+ cprintf ("|%d|",
+ (!CM_IsEmpty(msg, emessageId) ? HashLittle(msg->cm_fields[emessageId],strlen(msg->cm_fields[emessageId])) : 0)
+ );
+
+ // output the references hash (yes it's ok that we're trashing the source buffer by doing this)
+ if (!CM_IsEmpty(msg, eWeferences)) {
+ char *token;
+ char *rest = msg->cm_fields[eWeferences];
+ char *prev = rest;
+ while((token = strtok_r(rest, "|", &rest))) {
+ cprintf("%d%s", HashLittle(token,rest-prev-(*rest==0?0:1)), (*rest==0?"":","));
+ prev = rest;
+ }
+ }
+
+ cprintf("|\n");
+ }
+
+ else { // field view with no threads, subject extends out forever
+ cprintf("\n");
+ }
+
+ CM_Free(msg);
+}
+
+typedef struct _msg_filter {
+ HashList *Filter;
+ HashPos *p;
+ StrBuf *buffer;
+} msg_filter;
+
+
+void headers_brief_filter(long msgnum, void *userdata) {
+ long i, l;
+ struct CtdlMessage *msg;
+ msg_filter *flt = (msg_filter*) userdata;
+
+ l = GetCount(flt->Filter);
+ msg = CtdlFetchMessage(msgnum, 0);
+ StrBufPrintf(flt->buffer, "%ld", msgnum);
+ if (msg == NULL) {
+ for (i = 0; i < l; i++) {
+ StrBufAppendBufPlain(flt->buffer, HKEY("|"), 0);
+ }
+ }
+ else {
+ const char *k;
+ long len;
+ void *v;
+ RewindHashPos(flt->Filter, flt->p, 0);
+ while (GetNextHashPos(flt->Filter, flt->p, &len, &k, &v)) {
+ eMsgField f = (eMsgField) v;
+
+ StrBufAppendBufPlain(flt->buffer, HKEY("|"), 0);
+ if (!CM_IsEmpty(msg, f)) {
+ StrBufAppendBufPlain(flt->buffer, CM_KEY(msg, f), 0);
+ }
+ }
+ }
+ StrBufAppendBufPlain(flt->buffer, HKEY("\n"), 0);
+ cputbuf(flt->buffer);
+}
+
+// Back end for the MSGS command: output EUID header.
+void headers_euid(long msgnum, void *userdata) {
+ struct CtdlMessage *msg;
+
+ msg = CtdlFetchMessage(msgnum, 0);
+ if (msg == NULL) {
+ cprintf("%ld||\n", msgnum);
+ return;
+ }
+
+ cprintf("%ld|%s|%s\n",
+ msgnum,
+ (!CM_IsEmpty(msg, eExclusiveID) ? msg->cm_fields[eExclusiveID] : ""),
+ (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"));
+ CM_Free(msg);
+}
+
+
+// cmd_msgs() - get list of message #'s in this room
+// implements the MSGS server command using CtdlForEachMessage()
+void cmd_msgs(char *cmdbuf) {
+ int mode = 0;
+ char which[16];
+ char buf[256];
+ char tfield[256];
+ char tvalue[256];
+ int cm_ref = 0;
+ int with_template = 0;
+ struct CtdlMessage *template = NULL;
+ msg_filter filt;
+ char search_string[1024];
+ ForEachMsgCallback CallBack;
+
+ if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+ extract_token(which, cmdbuf, 0, '|', sizeof which);
+ cm_ref = extract_int(cmdbuf, 1);
+ extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
+ with_template = extract_int(cmdbuf, 2);
+ int output_mode = extract_int(cmdbuf, 3);
+ switch (output_mode) {
+ default:
+ case MSG_HDRS_BRIEF:
+ CallBack = simple_listing;
+ break;
+ case MSG_HDRS_ALL:
+ case MSG_HDRS_THREADS:
+ CallBack = headers_listing;
+ break;
+ case MSG_HDRS_EUID:
+ CallBack = headers_euid;
+ break;
+ case MSG_HDRS_BRIEFFILTER:
+ with_template = 2;
+ CallBack = headers_brief_filter;
+ break;
+ }
+
+ strcat(which, " ");
+ if (!strncasecmp(which, "OLD", 3))
+ mode = MSGS_OLD;
+ else if (!strncasecmp(which, "NEW", 3))
+ mode = MSGS_NEW;
+ else if (!strncasecmp(which, "FIRST", 5))
+ mode = MSGS_FIRST;
+ else if (!strncasecmp(which, "LAST", 4))
+ mode = MSGS_LAST;
+ else if (!strncasecmp(which, "GT", 2))
+ mode = MSGS_GT;
+ else if (!strncasecmp(which, "LT", 2))
+ mode = MSGS_LT;
+ else if (!strncasecmp(which, "SEARCH", 6))
+ mode = MSGS_SEARCH;
+ else
+ mode = MSGS_ALL;
+
+ if ( (mode == MSGS_SEARCH) && (!CtdlGetConfigInt("c_enable_fulltext")) ) {
+ cprintf("%d Full text index is not enabled on this server.\n",
+ ERROR + CMD_NOT_SUPPORTED);
+ return;
+ }
+
+ if (with_template == 1) {
+ memset(buf, 0, 5);
+ unbuffer_output();
+ cprintf("%d Send template then receive message list\n",
+ START_CHAT_MODE);
+ template = (struct CtdlMessage *)
+ malloc(sizeof(struct CtdlMessage));
+ memset(template, 0, sizeof(struct CtdlMessage));
+ template->cm_magic = CTDLMESSAGE_MAGIC;
+ template->cm_anon_type = MES_NORMAL;
+
+ while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
+ eMsgField f;
+ long tValueLen;
+
+ tValueLen = extract_token(tfield, buf, 0, '|', sizeof tfield);
+ if ((tValueLen == 4) && GetFieldFromMnemonic(&f, tfield))
+ {
+ tValueLen = extract_token(tvalue, buf, 1, '|', sizeof tvalue);
+ if (tValueLen >= 0) {
+ CM_SetField(template, f, tvalue, tValueLen);
+ }
+ }
+ }
+ buffer_output();
+ }
+ else if (with_template == 2) {
+ long i = 0;
+ memset(buf, 0, 5);
+ cprintf("%d Send list of headers\n",
+ START_CHAT_MODE);
+ filt.Filter = NewHash(1, lFlathash);
+ filt.buffer = NewStrBufPlain(NULL, 1024);
+ while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
+ eMsgField f;
+
+ if (GetFieldFromMnemonic(&f, buf))
+ {
+ Put(filt.Filter, LKEY(i), (void*)f, reference_free_handler);
+ i++;
+ }
+ }
+ filt.p = GetNewHashPos(filt.Filter, 0);
+ buffer_output();
+ }
+ else {
+ cprintf("%d \n", LISTING_FOLLOWS);
+ }
+
+ if (with_template < 2) {
+ CtdlForEachMessage(mode,
+ ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
+ ( (mode == MSGS_SEARCH) ? search_string : NULL ),
+ NULL,
+ template,
+ CallBack,
+ &output_mode);
+ if (template != NULL) CM_Free(template);
+ }
+ else {
+ CtdlForEachMessage(mode,
+ ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
+ ( (mode == MSGS_SEARCH) ? search_string : NULL ),
+ NULL,
+ NULL,
+ CallBack,
+ &filt);
+ DeleteHashPos(&filt.p);
+ DeleteHash(&filt.Filter);
+ FreeStrBuf(&filt.buffer);
+
+ }
+ cprintf("000\n");
+}
+
+
+/*
+ * display a message (mode 0 - Citadel proprietary)
+ */
+void cmd_msg0(char *cmdbuf)
+{
+ long msgid;
+ int headers_only = HEADERS_ALL;
+
+ msgid = extract_long(cmdbuf, 0);
+ headers_only = extract_int(cmdbuf, 1);
+
+ CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0, NULL, NULL, NULL);
+ return;
+}
+
+
+// display a message (mode 2 - RFC822)
+void cmd_msg2(char *cmdbuf) {
+ long msgid;
+ int headers_only = HEADERS_ALL;
+
+ msgid = extract_long(cmdbuf, 0);
+ headers_only = extract_int(cmdbuf, 1);
+
+ CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0, NULL, NULL, NULL);
+}
+
+
+// Display a message using MIME content types
+void cmd_msg4(char *cmdbuf) {
+ long msgid;
+ char section[64];
+
+ msgid = extract_long(cmdbuf, 0);
+ extract_token(section, cmdbuf, 1, '|', sizeof section);
+ CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0, NULL, NULL, NULL);
+}
+
+
+// Client tells us its preferred message format(s)
+void cmd_msgp(char *cmdbuf) {
+ if (!strcasecmp(cmdbuf, "dont_decode")) {
+ CC->msg4_dont_decode = 1;
+ cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
+ }
+ else {
+ safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
+ cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
+ }
+}
+
+
+// Open a component of a MIME message as a download file
+void cmd_opna(char *cmdbuf) {
+ long msgid;
+ char desired_section[128];
+
+ msgid = extract_long(cmdbuf, 0);
+ extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
+ safestrncpy(CC->download_desired_section, desired_section,
+ sizeof CC->download_desired_section);
+ CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0, NULL, NULL, NULL);
+}
+
+
+// Open a component of a MIME message and transmit it all at once
+void cmd_dlat(char *cmdbuf) {
+ long msgid;
+ char desired_section[128];
+
+ msgid = extract_long(cmdbuf, 0);
+ extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
+ safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
+ CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0, NULL, NULL, NULL);
+}
+
+
+// message entry - mode 0 (normal)
+void cmd_ent0(char *entargs) {
+ int post = 0;
+ char recp[SIZ];
+ char cc[SIZ];
+ char bcc[SIZ];
+ char supplied_euid[128];
+ int anon_flag = 0;
+ int format_type = 0;
+ char newusername[256];
+ char newuseremail[256];
+ struct CtdlMessage *msg;
+ int anonymous = 0;
+ char errmsg[SIZ];
+ int err = 0;
+ struct recptypes *valid = NULL;
+ struct recptypes *valid_to = NULL;
+ struct recptypes *valid_cc = NULL;
+ struct recptypes *valid_bcc = NULL;
+ char subject[SIZ];
+ int subject_required = 0;
+ int do_confirm = 0;
+ long msgnum;
+ int i, j;
+ char buf[256];
+ int newuseremail_ok = 0;
+ char references[SIZ];
+ char *ptr;
+
+ unbuffer_output();
+
+ post = extract_int(entargs, 0);
+ extract_token(recp, entargs, 1, '|', sizeof recp);
+ anon_flag = extract_int(entargs, 2);
+ format_type = extract_int(entargs, 3);
+ extract_token(subject, entargs, 4, '|', sizeof subject);
+ extract_token(newusername, entargs, 5, '|', sizeof newusername);
+ do_confirm = extract_int(entargs, 6);
+ extract_token(cc, entargs, 7, '|', sizeof cc);
+ extract_token(bcc, entargs, 8, '|', sizeof bcc);
+ switch(CC->room.QRdefaultview) {
+ case VIEW_NOTES:
+ case VIEW_WIKI:
+ extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
+ break;
+ default:
+ supplied_euid[0] = 0;
+ break;
+ }
+ extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
+ extract_token(references, entargs, 11, '|', sizeof references);
+ for (ptr=references; *ptr != 0; ++ptr) {
+ if (*ptr == '!') *ptr = '|';
+ }
+
+ /* first check to make sure the request is valid. */
+
+ err = CtdlDoIHavePermissionToPostInThisRoom(
+ errmsg,
+ sizeof errmsg,
+ POST_LOGGED_IN,
+ (!IsEmptyStr(references)) // is this a reply? or a top-level post?
+ );
+ if (err) {
+ cprintf("%d %s\n", err, errmsg);
+ return;
+ }
+
+ /* Check some other permission type things. */
+
+ if (IsEmptyStr(newusername)) {
+ strcpy(newusername, CC->user.fullname);
+ }
+ if ( (CC->user.axlevel < AxAideU)
+ && (strcasecmp(newusername, CC->user.fullname))
+ && (strcasecmp(newusername, CC->cs_inet_fn))
+ ) {
+ cprintf("%d You don't have permission to author messages as '%s'.\n",
+ ERROR + HIGHER_ACCESS_REQUIRED,
+ newusername
+ );
+ return;
+ }
+
+ if (IsEmptyStr(newuseremail)) {
+ newuseremail_ok = 1;
+ }
+
+ if (!IsEmptyStr(newuseremail)) {
+ if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
+ newuseremail_ok = 1;
+ }
+ else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
+ j = num_tokens(CC->cs_inet_other_emails, '|');
+ for (i=0; i<j; ++i) {
+ extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
+ if (!strcasecmp(newuseremail, buf)) {
+ newuseremail_ok = 1;
+ }
+ }
+ }
+ }
+
+ if (!newuseremail_ok) {
+ cprintf("%d You don't have permission to author messages as '%s'.\n",
+ ERROR + HIGHER_ACCESS_REQUIRED,
+ newuseremail
+ );
+ return;
+ }
+
+ CC->cs_flags |= CS_POSTING;
+
+ // In mailbox rooms we have to behave a little differently --
+ // make sure the user has specified at least one recipient. Then
+ // validate the recipient(s). We do this for the Mail> room, as
+ // well as any room which has the "Mailbox" view set - unless it
+ // is the DRAFTS room which does not require recipients.
+
+ if ( ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
+ || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
+ ) && (strcasecmp(&CC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
+ if (CC->user.axlevel < AxProbU) {
+ strcpy(recp, "sysop");
+ strcpy(cc, "");
+ strcpy(bcc, "");
+ }
+
+ valid_to = validate_recipients(recp, NULL, 0);
+ if (valid_to->num_error > 0) {
+ cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
+ free_recipients(valid_to);
+ return;
+ }
+
+ valid_cc = validate_recipients(cc, NULL, 0);
+ if (valid_cc->num_error > 0) {
+ cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
+ free_recipients(valid_to);
+ free_recipients(valid_cc);
+ return;
+ }
+
+ valid_bcc = validate_recipients(bcc, NULL, 0);
+ if (valid_bcc->num_error > 0) {
+ cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
+ free_recipients(valid_to);
+ free_recipients(valid_cc);
+ free_recipients(valid_bcc);
+ return;
+ }
+
+ // Recipient required, but none were specified
+ if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
+ free_recipients(valid_to);
+ free_recipients(valid_cc);
+ free_recipients(valid_bcc);
+ cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
+ return;
+ }
+
+ if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
+ if (CtdlCheckInternetMailPermission(&CC->user)==0) {
+ cprintf("%d You do not have permission "
+ "to send Internet mail.\n",
+ ERROR + HIGHER_ACCESS_REQUIRED);
+ free_recipients(valid_to);
+ free_recipients(valid_cc);
+ free_recipients(valid_bcc);
+ return;
+ }
+ }
+
+ if ( ( (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet) > 0) && (CC->user.axlevel < AxNetU) ) {
+ cprintf("%d Higher access required for network mail.\n", ERROR + HIGHER_ACCESS_REQUIRED);
+ free_recipients(valid_to);
+ free_recipients(valid_cc);
+ free_recipients(valid_bcc);
+ return;
+ }
+
+ if ((RESTRICT_INTERNET == 1)
+ && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
+ && ((CC->user.flags & US_INTERNET) == 0)
+ && (!CC->internal_pgm)) {
+ cprintf("%d You don't have access to Internet mail.\n",
+ ERROR + HIGHER_ACCESS_REQUIRED);
+ free_recipients(valid_to);
+ free_recipients(valid_cc);
+ free_recipients(valid_bcc);
+ return;
+ }
+
+ }
+
+ // Is this a room which has anonymous-only or anonymous-option?
+ anonymous = MES_NORMAL;
+ if (CC->room.QRflags & QR_ANONONLY) {
+ anonymous = MES_ANONONLY;
+ }
+ if (CC->room.QRflags & QR_ANONOPT) {
+ if (anon_flag == 1) { // only if the user requested it
+ anonymous = MES_ANONOPT;
+ }
+ }
+
+ if ((CC->room.QRflags & QR_MAILBOX) == 0) {
+ recp[0] = 0;
+ }
+
+ // Recommend to the client that the use of a message subject is
+ // strongly recommended in this room, if either the SUBJECTREQ flag
+ // is set, or if there is one or more Internet email recipients.
+
+ if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
+ if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
+ if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
+ if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
+
+ // If we're only checking the validity of the request, return success without creating the message.
+ if (post == 0) {
+ cprintf("%d %s|%d\n", CIT_OK,
+ ((valid_to != NULL) ? valid_to->display_recp : ""),
+ subject_required);
+ free_recipients(valid_to);
+ free_recipients(valid_cc);
+ free_recipients(valid_bcc);
+ return;
+ }
+
+ // We don't need these anymore because we'll do it differently below
+ free_recipients(valid_to);
+ free_recipients(valid_cc);
+ free_recipients(valid_bcc);
+
+ // Read in the message from the client.
+ if (do_confirm) {
+ cprintf("%d send message\n", START_CHAT_MODE);
+ }
+ else {
+ cprintf("%d send message\n", SEND_LISTING);
+ }
+
+ msg = CtdlMakeMessage(&CC->user, recp, cc,
+ CC->room.QRname, anonymous, format_type,
+ newusername, newuseremail, subject,
+ ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
+ NULL, references);
+
+ // Put together one big recipients struct containing to/cc/bcc all in one. This is for the envelope.
+ char *all_recps = malloc(SIZ * 3);
+ strcpy(all_recps, recp);
+ if (!IsEmptyStr(cc)) {
+ if (!IsEmptyStr(all_recps)) {
+ strcat(all_recps, ",");
+ }
+ strcat(all_recps, cc);
+ }
+ if (!IsEmptyStr(bcc)) {
+ if (!IsEmptyStr(all_recps)) {
+ strcat(all_recps, ",");
+ }
+ strcat(all_recps, bcc);
+ }
+ if (!IsEmptyStr(all_recps)) {
+ valid = validate_recipients(all_recps, NULL, 0);
+ }
+ else {
+ valid = NULL;
+ }
+ free(all_recps);
+
+ // posting into a mailing list room? set the envelope from
+ // to the actual mail address so others get a valid reply-to-header.
+ if ((valid != NULL) && (valid->num_room == 1) && !IsEmptyStr(valid->recp_orgroom)) {
+ CM_SetField(msg, eenVelopeTo, valid->recp_orgroom, strlen(valid->recp_orgroom));
+ }
+
+ if (msg != NULL) {
+ msgnum = CtdlSubmitMsg(msg, valid, "");
+ if (do_confirm) {
+ cprintf("%ld\n", msgnum);
+
+ if (StrLength(CC->StatusMessage) > 0) {
+ cprintf("%s\n", ChrPtr(CC->StatusMessage));
+ }
+ else if (msgnum >= 0L) {
+ client_write(HKEY("Message accepted.\n"));
+ }
+ else {
+ client_write(HKEY("Internal error.\n"));
+ }
+
+ if (!CM_IsEmpty(msg, eExclusiveID)) {
+ cprintf("%s\n", msg->cm_fields[eExclusiveID]);
+ } else {
+ cprintf("\n");
+ }
+ cprintf("000\n");
+ }
+
+ CM_Free(msg);
+ }
+ if (valid != NULL) {
+ free_recipients(valid);
+ }
+ return;
+}
+
+
+// Delete message from current room
+void cmd_dele(char *args) {
+ int num_deleted;
+ int i;
+ char msgset[SIZ];
+ char msgtok[32];
+ long *msgs;
+ int num_msgs = 0;
+
+ extract_token(msgset, args, 0, '|', sizeof msgset);
+ num_msgs = num_tokens(msgset, ',');
+ if (num_msgs < 1) {
+ cprintf("%d Nothing to do.\n", CIT_OK);
+ return;
+ }
+
+ if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
+ cprintf("%d Higher access required.\n",
+ ERROR + HIGHER_ACCESS_REQUIRED);
+ return;
+ }
+
+ // Build our message set to be moved/copied
+ msgs = malloc(num_msgs * sizeof(long));
+ for (i=0; i<num_msgs; ++i) {
+ extract_token(msgtok, msgset, i, ',', sizeof msgtok);
+ msgs[i] = atol(msgtok);
+ }
+
+ num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
+ free(msgs);
+
+ if (num_deleted) {
+ cprintf("%d %d message%s deleted.\n", CIT_OK,
+ num_deleted, ((num_deleted != 1) ? "s" : ""));
+ } else {
+ cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
+ }
+}
+
+
+// move or copy a message to another room
+void cmd_move(char *args) {
+ char msgset[SIZ];
+ char msgtok[32];
+ long *msgs;
+ int num_msgs = 0;
+
+ char targ[ROOMNAMELEN];
+ struct ctdlroom qtemp;
+ int err;
+ int is_copy = 0;
+ int ra;
+ int permit = 0;
+ int i;
+
+ extract_token(msgset, args, 0, '|', sizeof msgset);
+ num_msgs = num_tokens(msgset, ',');
+ if (num_msgs < 1) {
+ cprintf("%d Nothing to do.\n", CIT_OK);
+ return;
+ }
+
+ extract_token(targ, args, 1, '|', sizeof targ);
+ convert_room_name_macros(targ, sizeof targ);
+ targ[ROOMNAMELEN - 1] = 0;
+ is_copy = extract_int(args, 2);
+
+ if (CtdlGetRoom(&qtemp, targ) != 0) {
+ cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
+ return;
+ }
+
+ if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
+ cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
+ return;
+ }
+
+ CtdlGetUser(&CC->user, CC->curr_user);
+ CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
+
+ // Check for permission to perform this operation.
+ // Remember: "CC->room" is source, "qtemp" is target.
+ permit = 0;
+
+ // Admins can move/copy
+ if (CC->user.axlevel >= AxAideU) permit = 1;
+
+ // Room aides can move/copy
+ if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
+
+ // Permit move/copy from personal rooms
+ if ((CC->room.QRflags & QR_MAILBOX)
+ && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
+
+ // Permit only copy from public to personal room
+ if ( (is_copy)
+ && (!(CC->room.QRflags & QR_MAILBOX))
+ && (qtemp.QRflags & QR_MAILBOX)
+ ) {
+ permit = 1;
+ }
+
+ // Permit message removal from collaborative delete rooms
+ if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
+
+ // Users allowed to post into the target room may move into it too.
+ if ((CC->room.QRflags & QR_MAILBOX) &&
+ (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
+
+ // User must have access to target room
+ if (!(ra & UA_KNOWN)) permit = 0;
+
+ if (!permit) {
+ cprintf("%d Higher access required.\n",
+ ERROR + HIGHER_ACCESS_REQUIRED);
+ return;
+ }
+
+ // Build our message set to be moved/copied
+ msgs = malloc(num_msgs * sizeof(long));
+ for (i=0; i<num_msgs; ++i) {
+ extract_token(msgtok, msgset, i, ',', sizeof msgtok);
+ msgs[i] = atol(msgtok);
+ }
+
+ // Do the copy
+ err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
+ if (err != 0) {
+ cprintf("%d Cannot store message(s) in %s: error %d\n",
+ err, targ, err);
+ free(msgs);
+ return;
+ }
+
+ // Now delete the message from the source room, if this is a 'move' rather than a 'copy' operation.
+ if (is_copy == 0) {
+ CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
+ }
+ free(msgs);
+
+ cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
+}
+
+
+/*****************************************************************************/
+/* MODULE INITIALIZATION STUFF */
+/*****************************************************************************/
+char *ctdl_module_init_ctdl_message(void) {
+ if (!threading) {
+ CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
+ CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
+ CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
+ CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
+ CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
+ CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
+ CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
+ CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
+ CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
+ CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
+ }
+
+ /* return our Subversion id for the Log */
+ return "ctdl_message";
+}
--- /dev/null
+// Server functions which perform operations on room objects.
+//
+// Copyright (c) 1987-2022 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.
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h> /* for cmd_rdir to read contents of the directory */
+#include <libcitadel.h>
+
+#include "../../citserver.h"
+#include "../../ctdl_module.h"
+#include "../../room_ops.h"
+#include "../../config.h"
+
+// Back-back-end for all room listing commands
+void list_roomname(struct ctdlroom *qrbuf, int ra, int current_view, int default_view) {
+ char truncated_roomname[ROOMNAMELEN];
+
+ // For my own mailbox rooms, chop off the owner prefix
+ if ( (qrbuf->QRflags & QR_MAILBOX)
+ && (atol(qrbuf->QRname) == CC->user.usernum) ) {
+ safestrncpy(truncated_roomname, qrbuf->QRname, sizeof truncated_roomname);
+ safestrncpy(truncated_roomname, &truncated_roomname[11], sizeof truncated_roomname);
+ cprintf("%s", truncated_roomname);
+ }
+ // For all other rooms, just display the name in its entirety
+ else {
+ cprintf("%s", qrbuf->QRname);
+ }
+
+ /* ...and now the other parameters */
+ cprintf("|%u|%d|%d|%d|%d|%d|%d|%ld|\n",
+ qrbuf->QRflags,
+ (int) qrbuf->QRfloor,
+ (int) qrbuf->QRorder,
+ (int) qrbuf->QRflags2,
+ ra,
+ current_view,
+ default_view,
+ qrbuf->QRmtime
+ );
+}
+
+
+// cmd_lrms() - List all accessible rooms, known or forgotten
+void cmd_lrms_backend(struct ctdlroom *qrbuf, void *data) {
+ int FloorBeingSearched = (-1);
+ int ra;
+ int view;
+
+ FloorBeingSearched = *(int *)data;
+ CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
+
+ if ((( ra & (UA_KNOWN | UA_ZAPPED)))
+ && ((qrbuf->QRfloor == (FloorBeingSearched))
+ || ((FloorBeingSearched) < 0)))
+ list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
+}
+
+
+void cmd_lrms(char *argbuf) {
+ int FloorBeingSearched = (-1);
+ if (!IsEmptyStr(argbuf))
+ FloorBeingSearched = extract_int(argbuf, 0);
+
+ if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+ CtdlGetUser(&CC->user, CC->curr_user);
+ cprintf("%d Accessible rooms:\n", LISTING_FOLLOWS);
+
+ CtdlForEachRoom(cmd_lrms_backend, &FloorBeingSearched);
+ cprintf("000\n");
+}
+
+
+// cmd_lkra() - List all known rooms
+void cmd_lkra_backend(struct ctdlroom *qrbuf, void *data) {
+ int FloorBeingSearched = (-1);
+ int ra;
+ int view;
+
+ FloorBeingSearched = *(int *)data;
+ CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
+
+ if ((( ra & (UA_KNOWN))) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) {
+ list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
+ }
+}
+
+
+void cmd_lkra(char *argbuf) {
+ int FloorBeingSearched = (-1);
+ if (!IsEmptyStr(argbuf)) {
+ FloorBeingSearched = extract_int(argbuf, 0);
+ }
+
+ if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+ CtdlGetUser(&CC->user, CC->curr_user);
+ cprintf("%d Known rooms:\n", LISTING_FOLLOWS);
+
+ CtdlForEachRoom(cmd_lkra_backend, &FloorBeingSearched);
+ cprintf("000\n");
+}
+
+
+void cmd_lprm_backend(struct ctdlroom *qrbuf, void *data) {
+ int FloorBeingSearched = (-1);
+ int ra;
+ int view;
+
+ FloorBeingSearched = *(int *)data;
+ CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
+
+ if (((qrbuf->QRflags & QR_PRIVATE) == 0) && ((qrbuf->QRflags & QR_MAILBOX) == 0) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) {
+ list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
+ }
+}
+
+
+void cmd_lprm(char *argbuf) {
+ int FloorBeingSearched = (-1);
+ if (!IsEmptyStr(argbuf)) {
+ FloorBeingSearched = extract_int(argbuf, 0);
+ }
+
+ cprintf("%d Public rooms:\n", LISTING_FOLLOWS);
+
+ CtdlForEachRoom(cmd_lprm_backend, &FloorBeingSearched);
+ cprintf("000\n");
+}
+
+
+// cmd_lkrn() - List all known rooms with new messages
+void cmd_lkrn_backend(struct ctdlroom *qrbuf, void *data) {
+ int FloorBeingSearched = (-1);
+ int ra;
+ int view;
+
+ FloorBeingSearched = *(int *)data;
+ CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
+
+ if ((ra & UA_KNOWN) && (ra & UA_HASNEWMSGS) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) {
+ list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
+ }
+}
+
+
+void cmd_lkrn(char *argbuf) {
+ int FloorBeingSearched = (-1);
+ if (!IsEmptyStr(argbuf)) {
+ FloorBeingSearched = extract_int(argbuf, 0);
+ }
+
+ if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+ CtdlGetUser(&CC->user, CC->curr_user);
+ cprintf("%d Rooms w/ new msgs:\n", LISTING_FOLLOWS);
+
+ CtdlForEachRoom(cmd_lkrn_backend, &FloorBeingSearched);
+ cprintf("000\n");
+}
+
+
+// cmd_lkro() - List all known rooms
+void cmd_lkro_backend(struct ctdlroom *qrbuf, void *data) {
+ int FloorBeingSearched = (-1);
+ int ra;
+ int view;
+
+ FloorBeingSearched = *(int *)data;
+ CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
+
+ if ((ra & UA_KNOWN) && ((ra & UA_HASNEWMSGS) == 0) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) {
+ list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
+ }
+}
+
+
+void cmd_lkro(char *argbuf) {
+ int FloorBeingSearched = (-1);
+ if (!IsEmptyStr(argbuf)) {
+ FloorBeingSearched = extract_int(argbuf, 0);
+ }
+
+ if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+ CtdlGetUser(&CC->user, CC->curr_user);
+ cprintf("%d Rooms w/o new msgs:\n", LISTING_FOLLOWS);
+
+ CtdlForEachRoom(cmd_lkro_backend, &FloorBeingSearched);
+ cprintf("000\n");
+}
+
+
+// cmd_lzrm() - List all forgotten rooms
+void cmd_lzrm_backend(struct ctdlroom *qrbuf, void *data) {
+ int FloorBeingSearched = (-1);
+ int ra;
+ int view;
+
+ FloorBeingSearched = *(int *)data;
+ CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
+
+ if ((ra & UA_GOTOALLOWED) && (ra & UA_ZAPPED) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) {
+ list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
+ }
+}
+
+
+void cmd_lzrm(char *argbuf) {
+ int FloorBeingSearched = (-1);
+ if (!IsEmptyStr(argbuf))
+ FloorBeingSearched = extract_int(argbuf, 0);
+
+ if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+ CtdlGetUser(&CC->user, CC->curr_user);
+ cprintf("%d Zapped rooms:\n", LISTING_FOLLOWS);
+
+ CtdlForEachRoom(cmd_lzrm_backend, &FloorBeingSearched);
+ cprintf("000\n");
+}
+
+
+// cmd_goto() - goto a new room
+void cmd_goto(char *gargs) {
+ struct CitContext *CCC = CC;
+ struct ctdlroom QRscratch;
+ int c;
+ int ok = 0;
+ int ra;
+ char augmented_roomname[ROOMNAMELEN];
+ char towhere[ROOMNAMELEN];
+ char password[32];
+ int transiently = 0;
+
+ if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+ extract_token(towhere, gargs, 0, '|', sizeof towhere);
+ extract_token(password, gargs, 1, '|', sizeof password);
+ transiently = extract_int(gargs, 2);
+
+ CtdlGetUser(&CCC->user, CCC->curr_user);
+
+ // Handle some of the macro named rooms
+ convert_room_name_macros(towhere, sizeof towhere);
+
+ // First try a regular match
+ c = CtdlGetRoom(&QRscratch, towhere);
+
+ // Then try a mailbox name match
+ if (c != 0) {
+ CtdlMailboxName(augmented_roomname, sizeof augmented_roomname, &CCC->user, towhere);
+ c = CtdlGetRoom(&QRscratch, augmented_roomname);
+ if (c == 0) {
+ safestrncpy(towhere, augmented_roomname, sizeof towhere);
+ }
+ }
+
+ // And if the room was found...
+ if (c == 0) {
+ // Let internal programs go directly to any room.
+ if (CCC->internal_pgm) {
+ memcpy(&CCC->room, &QRscratch, sizeof(struct ctdlroom));
+ CtdlUserGoto(NULL, 1, transiently, NULL, NULL, NULL, NULL);
+ return;
+ }
+
+ // See if there is an existing user/room relationship
+ CtdlRoomAccess(&QRscratch, &CCC->user, &ra, NULL);
+
+ // normal clients have to pass through security
+ if (ra & UA_GOTOALLOWED) {
+ ok = 1;
+ }
+
+ if (ok == 1) {
+ if ((QRscratch.QRflags & QR_MAILBOX) &&
+ ((ra & UA_GOTOALLOWED))) {
+ memcpy(&CCC->room, &QRscratch, sizeof(struct ctdlroom));
+ CtdlUserGoto(NULL, 1, transiently, NULL, NULL, NULL, NULL);
+ return;
+ }
+ else if ((QRscratch.QRflags & QR_PASSWORDED) &&
+ ((ra & UA_KNOWN) == 0) &&
+ (strcasecmp(QRscratch.QRpasswd, password)) &&
+ (CCC->user.axlevel < AxAideU)
+ ) {
+ cprintf("%d wrong or missing passwd\n", ERROR + PASSWORD_REQUIRED);
+ return;
+ }
+ else if ((QRscratch.QRflags & QR_PRIVATE) &&
+ ((QRscratch.QRflags & QR_PASSWORDED) == 0) &&
+ ((QRscratch.QRflags & QR_GUESSNAME) == 0) &&
+ ((ra & UA_KNOWN) == 0) &&
+ (CCC->user.axlevel < AxAideU)
+ ) {
+ syslog(LOG_DEBUG, "rooms: failed to acquire private room");
+ }
+ else {
+ memcpy(&CCC->room, &QRscratch, sizeof(struct ctdlroom));
+ CtdlUserGoto(NULL, 1, transiently, NULL, NULL, NULL, NULL);
+ return;
+ }
+ }
+ }
+
+ cprintf("%d room '%s' not found\n", ERROR + ROOM_NOT_FOUND, towhere);
+}
+
+
+void cmd_whok(char *cmdbuf) {
+ struct ctdluser temp;
+ struct cdbdata *cdbus;
+ int ra;
+
+ cprintf("%d Who knows room:\n", LISTING_FOLLOWS);
+ cdb_rewind(CDB_USERS);
+ while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL) {
+ memset(&temp, 0, sizeof temp);
+ memcpy(&temp, cdbus->ptr, sizeof temp);
+ cdb_free(cdbus);
+
+ CtdlRoomAccess(&CC->room, &temp, &ra, NULL);
+ if ((!IsEmptyStr(temp.fullname)) &&
+ (CC->room.QRflags & QR_INUSE) &&
+ (ra & UA_KNOWN)
+ )
+ cprintf("%s\n", temp.fullname);
+ }
+ cprintf("000\n");
+}
+
+
+// RDIR command for room directory
+void cmd_rdir(char *cmdbuf) {
+ char buf[256];
+ char comment[256];
+ FILE *fd;
+ struct stat statbuf;
+ DIR *filedir = NULL;
+ struct dirent *filedir_entry;
+ int d_namelen;
+ char buf2[SIZ];
+ char mimebuf[64];
+ long len;
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ CtdlGetRoom(&CC->room, CC->room.QRname);
+ CtdlGetUser(&CC->user, CC->curr_user);
+
+ if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
+ cprintf("%d not here.\n", ERROR + NOT_HERE);
+ return;
+ }
+ if (((CC->room.QRflags & QR_VISDIR) == 0)
+ && (CC->user.axlevel < AxAideU)
+ && (CC->user.usernum != CC->room.QRroomaide))
+ {
+ cprintf("%d not here.\n", ERROR + HIGHER_ACCESS_REQUIRED);
+ return;
+ }
+
+ snprintf(buf, sizeof buf, "%s/%s", ctdl_file_dir, CC->room.QRdirname);
+ filedir = opendir (buf);
+
+ if (filedir == NULL) {
+ cprintf("%d not here.\n", ERROR + HIGHER_ACCESS_REQUIRED);
+ return;
+ }
+ cprintf("%d %s|%s/%s\n", LISTING_FOLLOWS, CtdlGetConfigStr("c_fqdn"), ctdl_file_dir, CC->room.QRdirname);
+
+ snprintf(buf, sizeof buf, "%s/%s/filedir", ctdl_file_dir, CC->room.QRdirname);
+ fd = fopen(buf, "r");
+ if (fd == NULL) {
+ fd = fopen("/dev/null", "r");
+ }
+ while ((filedir_entry = readdir(filedir))) {
+ if (strcasecmp(filedir_entry->d_name, "filedir") && filedir_entry->d_name[0] != '.') {
+#ifdef _DIRENT_HAVE_D_NAMELEN
+ d_namelen = filedir_entry->d_namlen;
+#else
+ d_namelen = strlen(filedir_entry->d_name);
+#endif
+ snprintf(buf, sizeof buf, "%s/%s/%s", ctdl_file_dir, CC->room.QRdirname, filedir_entry->d_name);
+ stat(buf, &statbuf); /* stat the file */
+ if (!(statbuf.st_mode & S_IFREG)) {
+ snprintf(buf2, sizeof buf2,
+ "\"%s\" appears in the file directory for room \"%s\" but is not a regular file. Directories, named pipes, sockets, etc. are not usable in Citadel room directories.\n",
+ buf, CC->room.QRname
+ );
+ CtdlAideMessage(buf2, "Unusable data found in room directory");
+ continue; /* not a useable file type so don't show it */
+ }
+ safestrncpy(comment, "", sizeof comment);
+ fseek(fd, 0L, 0); /* rewind descriptions file */
+ /* Get the description from the descriptions file */
+ while ((fgets(buf, sizeof buf, fd) != NULL) && (IsEmptyStr(comment))) {
+ buf[strlen(buf) - 1] = 0;
+ if ((!strncasecmp(buf, filedir_entry->d_name, d_namelen)) && (buf[d_namelen] == ' '))
+ safestrncpy(comment, &buf[d_namelen + 1], sizeof comment);
+ }
+ len = extract_token (mimebuf, comment, 0,' ', 64);
+ if ((len <0) || strchr(mimebuf, '/') == NULL) {
+ snprintf (mimebuf, 64, "application/octetstream");
+ len = 0;
+ }
+ cprintf("%s|%ld|%s|%s\n",
+ filedir_entry->d_name,
+ (long)statbuf.st_size,
+ mimebuf,
+ &comment[len]);
+ }
+ }
+ fclose(fd);
+ closedir(filedir);
+
+ cprintf("000\n");
+}
+
+
+// get room parameters (admin or room admin command)
+void cmd_getr(char *cmdbuf) {
+ if (CtdlAccessCheck(ac_room_aide)) return;
+
+ CtdlGetRoom(&CC->room, CC->room.QRname);
+ cprintf("%d%c%s|%s|%s|%d|%d|%d|%d|%d|\n",
+ CIT_OK,
+ CtdlCheckExpress(),
+ ((CC->room.QRflags & QR_MAILBOX) ? &CC->room.QRname[11] : CC->room.QRname),
+ ((CC->room.QRflags & QR_PASSWORDED) ? CC->room.QRpasswd : ""),
+ ((CC->room.QRflags & QR_DIRECTORY) ? CC->room.QRdirname : ""),
+ CC->room.QRflags,
+ (int) CC->room.QRfloor,
+ (int) CC->room.QRorder,
+ CC->room.QRdefaultview,
+ CC->room.QRflags2
+ );
+}
+
+
+// set room parameters (admin or room admin command)
+void cmd_setr(char *args) {
+ char buf[256];
+ int new_order = 0;
+ int r;
+ int new_floor;
+ char new_name[ROOMNAMELEN];
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ if (num_parms(args) >= 6) {
+ new_floor = extract_int(args, 5);
+ }
+ else {
+ new_floor = (-1); /* don't change the floor */
+ }
+
+ /* When is a new name more than just a new name? When the old name
+ * has a namespace prefix.
+ */
+ if (CC->room.QRflags & QR_MAILBOX) {
+ sprintf(new_name, "%010ld.", atol(CC->room.QRname) );
+ }
+ else {
+ safestrncpy(new_name, "", sizeof new_name);
+ }
+ extract_token(&new_name[strlen(new_name)], args, 0, '|', (sizeof new_name - strlen(new_name)));
+
+ r = CtdlRenameRoom(CC->room.QRname, new_name, new_floor);
+
+ if (r == crr_room_not_found) {
+ cprintf("%d Internal error - room not found?\n", ERROR + INTERNAL_ERROR);
+ }
+ else if (r == crr_already_exists) {
+ cprintf("%d '%s' already exists.\n",
+ ERROR + ALREADY_EXISTS, new_name);
+ }
+ else if (r == crr_noneditable) {
+ cprintf("%d Cannot edit this room.\n", ERROR + NOT_HERE);
+ }
+ else if (r == crr_invalid_floor) {
+ cprintf("%d Target floor does not exist.\n",
+ ERROR + INVALID_FLOOR_OPERATION);
+ }
+ else if (r == crr_access_denied) {
+ cprintf("%d You do not have permission to edit '%s'\n",
+ ERROR + HIGHER_ACCESS_REQUIRED,
+ CC->room.QRname);
+ }
+ else if (r != crr_ok) {
+ cprintf("%d Error: CtdlRenameRoom() returned %d\n",
+ ERROR + INTERNAL_ERROR, r);
+ }
+
+ if (r != crr_ok) {
+ return;
+ }
+
+ CtdlGetRoom(&CC->room, new_name);
+
+ /* Now we have to do a bunch of other stuff */
+
+ if (num_parms(args) >= 7) {
+ new_order = extract_int(args, 6);
+ if (new_order < 1)
+ new_order = 1;
+ if (new_order > 127)
+ new_order = 127;
+ }
+
+ CtdlGetRoomLock(&CC->room, CC->room.QRname);
+
+ /* Directory room */
+ extract_token(buf, args, 2, '|', sizeof buf);
+ buf[15] = 0;
+ safestrncpy(CC->room.QRdirname, buf,
+ sizeof CC->room.QRdirname);
+
+ /* Default view */
+ if (num_parms(args) >= 8) {
+ CC->room.QRdefaultview = extract_int(args, 7);
+ }
+
+ /* Second set of flags */
+ if (num_parms(args) >= 9) {
+ CC->room.QRflags2 = extract_int(args, 8);
+ }
+
+ /* Misc. flags */
+ CC->room.QRflags = (extract_int(args, 3) | QR_INUSE);
+ /* Clean up a client boo-boo: if the client set the room to
+ * guess-name or passworded, ensure that the private flag is
+ * also set.
+ */
+ if ((CC->room.QRflags & QR_GUESSNAME)
+ || (CC->room.QRflags & QR_PASSWORDED))
+ CC->room.QRflags |= QR_PRIVATE;
+
+ /* Some changes can't apply to BASEROOM */
+ if (!strncasecmp(CC->room.QRname, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)) {
+ CC->room.QRorder = 0;
+ CC->room.QRpasswd[0] = '\0';
+ CC->room.QRflags &= ~(QR_PRIVATE & QR_PASSWORDED &
+ QR_GUESSNAME & QR_PREFONLY & QR_MAILBOX);
+ CC->room.QRflags |= QR_PERMANENT;
+ }
+ else {
+ /* March order (doesn't apply to AIDEROOM) */
+ if (num_parms(args) >= 7)
+ CC->room.QRorder = (char) new_order;
+ /* Room password */
+ extract_token(buf, args, 1, '|', sizeof buf);
+ buf[10] = 0;
+ safestrncpy(CC->room.QRpasswd, buf, sizeof CC->room.QRpasswd);
+ /* Kick everyone out if the client requested it
+ * (by changing the room's generation number)
+ */
+ if (extract_int(args, 4)) {
+ time(&CC->room.QRgen);
+ }
+ }
+ /* Some changes can't apply to AIDEROOM */
+ if (!strncasecmp(CC->room.QRname, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)) {
+ CC->room.QRorder = 0;
+ CC->room.QRflags &= ~QR_MAILBOX;
+ CC->room.QRflags |= QR_PERMANENT;
+ }
+
+ /* Write the room record back to disk */
+ CtdlPutRoomLock(&CC->room);
+
+ /* Create a room directory if necessary */
+ if (CC->room.QRflags & QR_DIRECTORY) {
+ snprintf(buf, sizeof buf,"%s/%s", ctdl_file_dir, CC->room.QRdirname);
+ mkdir(buf, 0755);
+ }
+ snprintf(buf, sizeof buf, "The room \"%s\" has been edited by %s.\n",
+ CC->room.QRname,
+ (CC->logged_in ? CC->curr_user : "an administrator")
+ );
+ CtdlAideMessage(buf, "Room modification Message");
+ cprintf("%d Ok\n", CIT_OK);
+}
+
+
+// get the name of the room admin for this room
+void cmd_geta(char *cmdbuf) {
+ struct ctdluser usbuf;
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ if (CtdlGetUserByNumber(&usbuf, CC->room.QRroomaide) == 0) {
+ cprintf("%d %s\n", CIT_OK, usbuf.fullname);
+ }
+ else {
+ cprintf("%d \n", CIT_OK);
+ }
+}
+
+
+// set the room admin for this room
+void cmd_seta(char *new_ra) {
+ struct ctdluser usbuf;
+ long newu;
+ char buf[SIZ];
+ int post_notice;
+
+ if (CtdlAccessCheck(ac_room_aide)) return;
+
+ if (CtdlGetUser(&usbuf, new_ra) != 0) {
+ newu = (-1L);
+ }
+ else {
+ newu = usbuf.usernum;
+ }
+
+ CtdlGetRoomLock(&CC->room, CC->room.QRname);
+ post_notice = 0;
+ if (CC->room.QRroomaide != newu) {
+ post_notice = 1;
+ }
+ CC->room.QRroomaide = newu;
+ CtdlPutRoomLock(&CC->room);
+
+ // We have to post the change notice _after_ writing changes to
+ // the room table, otherwise it would deadlock!
+ if (post_notice == 1) {
+ if (!IsEmptyStr(usbuf.fullname))
+ snprintf(buf, sizeof buf,
+ "%s is now the room admin for \"%s\".\n",
+ usbuf.fullname, CC->room.QRname);
+ else
+ snprintf(buf, sizeof buf,
+ "There is now no room admin for \"%s\".\n",
+ CC->room.QRname);
+ CtdlAideMessage(buf, "Admin Room Modification");
+ }
+ cprintf("%d Ok\n", CIT_OK);
+}
+
+
+// Retrieve info file for this room (this ought to be upgraded to handle non-plain-text)
+void cmd_rinf(char *argbuf) {
+ struct CtdlMessage *msg = CtdlFetchMessage(CC->room.msgnum_info, 1);
+ if (msg != NULL) {
+ cprintf("%d Info:\n", LISTING_FOLLOWS);
+ CtdlOutputPreLoadedMsg(msg, MT_CITADEL, HEADERS_NONE, 0, 0, 0);
+ CM_Free(msg);
+ cprintf("000\n");
+ }
+ else {
+ cprintf("%d No info file.\n", ERROR + FILE_NOT_FOUND);
+ }
+}
+
+
+// admin command: kill the current room
+void cmd_kill(char *argbuf) {
+ char deleted_room_name[ROOMNAMELEN];
+ char msg[SIZ];
+ int kill_ok;
+
+ kill_ok = extract_int(argbuf, 0);
+
+ if (CtdlDoIHavePermissionToDeleteThisRoom(&CC->room) == 0) {
+ cprintf("%d Can't delete this room.\n", ERROR + NOT_HERE);
+ return;
+ }
+ if (kill_ok) {
+ if (CC->room.QRflags & QR_MAILBOX) {
+ safestrncpy(deleted_room_name, &CC->room.QRname[11], sizeof deleted_room_name);
+ }
+ else {
+ safestrncpy(deleted_room_name, CC->room.QRname, sizeof deleted_room_name);
+ }
+
+ /* Do the dirty work */
+ CtdlScheduleRoomForDeletion(&CC->room);
+
+ /* Return to the Lobby */
+ CtdlUserGoto(CtdlGetConfigStr("c_baseroom"), 0, 0, NULL, NULL, NULL, NULL);
+
+ /* tell the world what we did */
+ snprintf(msg, sizeof msg, "The room \"%s\" has been deleted by %s.\n",
+ deleted_room_name,
+ (CC->logged_in ? CC->curr_user : "an administrator")
+ );
+ CtdlAideMessage(msg, "Room Purger Message");
+ cprintf("%d '%s' deleted.\n", CIT_OK, deleted_room_name);
+ }
+ else {
+ cprintf("%d ok to delete.\n", CIT_OK);
+ }
+}
+
+
+// create a new room
+void cmd_cre8(char *args) {
+ int cre8_ok;
+ char new_room_name[ROOMNAMELEN];
+ int new_room_type;
+ char new_room_pass[32];
+ int new_room_floor;
+ int new_room_view;
+ char *notification_message = NULL;
+ unsigned newflags;
+ struct floor *fl;
+ int avoid_access = 0;
+
+ cre8_ok = extract_int(args, 0);
+ extract_token(new_room_name, args, 1, '|', sizeof new_room_name);
+ new_room_name[ROOMNAMELEN - 1] = 0;
+ new_room_type = extract_int(args, 2);
+ extract_token(new_room_pass, args, 3, '|', sizeof new_room_pass);
+ avoid_access = extract_int(args, 5);
+ new_room_view = extract_int(args, 6);
+ new_room_pass[9] = 0;
+ new_room_floor = 0;
+
+ if ((IsEmptyStr(new_room_name)) && (cre8_ok == 1)) {
+ cprintf("%d Invalid room name.\n", ERROR + ILLEGAL_VALUE);
+ return;
+ }
+
+ if (!strcasecmp(new_room_name, MAILROOM)) {
+ cprintf("%d '%s' already exists.\n",
+ ERROR + ALREADY_EXISTS, new_room_name);
+ return;
+ }
+
+ if (num_parms(args) >= 5) {
+ fl = CtdlGetCachedFloor(extract_int(args, 4));
+ if (fl == NULL) {
+ cprintf("%d Invalid floor number.\n",
+ ERROR + INVALID_FLOOR_OPERATION);
+ return;
+ }
+ else if ((fl->f_flags & F_INUSE) == 0) {
+ cprintf("%d Invalid floor number.\n",
+ ERROR + INVALID_FLOOR_OPERATION);
+ return;
+ } else {
+ new_room_floor = extract_int(args, 4);
+ }
+ }
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ if (CC->user.axlevel < CtdlGetConfigInt("c_createax") && !CC->internal_pgm) {
+ cprintf("%d You need higher access to create rooms.\n",
+ ERROR + HIGHER_ACCESS_REQUIRED);
+ return;
+ }
+
+ if ((IsEmptyStr(new_room_name)) && (cre8_ok == 0)) {
+ cprintf("%d Ok to create rooms.\n", CIT_OK);
+ return;
+ }
+
+ if ((new_room_type < 0) || (new_room_type > 5)) {
+ cprintf("%d Invalid room type.\n", ERROR + ILLEGAL_VALUE);
+ return;
+ }
+
+ if (new_room_type == 5) {
+ if (CC->user.axlevel < AxAideU) {
+ cprintf("%d Higher access required\n",
+ ERROR + HIGHER_ACCESS_REQUIRED);
+ return;
+ }
+ }
+
+ /* Check to make sure the requested room name doesn't already exist */
+ newflags = CtdlCreateRoom(new_room_name,
+ new_room_type, new_room_pass, new_room_floor,
+ 0, avoid_access, new_room_view);
+ if (newflags == 0) {
+ cprintf("%d '%s' already exists.\n",
+ ERROR + ALREADY_EXISTS, new_room_name);
+ return;
+ }
+
+ if (cre8_ok == 0) {
+ cprintf("%d OK to create '%s'\n", CIT_OK, new_room_name);
+ return;
+ }
+
+ /* If we reach this point, the room needs to be created. */
+
+ newflags = CtdlCreateRoom(new_room_name,
+ new_room_type, new_room_pass, new_room_floor, 1, 0,
+ new_room_view);
+
+ /* post a message in Aide> describing the new room */
+ notification_message = malloc(1024);
+ snprintf(notification_message, 1024,
+ "A new room called \"%s\" has been created by %s%s%s%s%s%s\n",
+ new_room_name,
+ (CC->logged_in ? CC->curr_user : "an administrator"),
+ ((newflags & QR_MAILBOX) ? " [personal]" : ""),
+ ((newflags & QR_PRIVATE) ? " [private]" : ""),
+ ((newflags & QR_GUESSNAME) ? " [hidden]" : ""),
+ ((newflags & QR_PASSWORDED) ? " Password: " : ""),
+ ((newflags & QR_PASSWORDED) ? new_room_pass : "")
+ );
+ CtdlAideMessage(notification_message, "Room Creation Message");
+ free(notification_message);
+
+ cprintf("%d '%s' has been created.\n", CIT_OK, new_room_name);
+}
+
+
+// Upload the room banner text for this room.
+// This should be amended to handle content types other than plain text.
+void cmd_einf(char *ok) { /* enter info file for current room */
+ char buf[SIZ];
+ unbuffer_output();
+
+ if (CtdlAccessCheck(ac_room_aide)) return;
+
+ if (atoi(ok) == 0) {
+ cprintf("%d Ok.\n", CIT_OK);
+ return;
+ }
+
+ StrBuf *NewBanner = NewStrBufPlain("Content-type: text/plain; charset=UTF-8\nContent-transfer-encoding: 8bit\n\n", -1);
+
+ cprintf("%d Transmit new banner in plain text now.\n", SEND_LISTING);
+ while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
+ StrBufAppendBufPlain(NewBanner, buf, -1, 0);
+ StrBufAppendBufPlain(NewBanner, HKEY("\n"), 0);
+ }
+
+ // We have read the new banner from the user , now save it
+ long new_msgnum = quickie_message("Citadel", NULL, NULL, SYSCONFIGROOM, ChrPtr(NewBanner), FMT_RFC822, "Banner submitted with EINF command");
+ FreeStrBuf(&NewBanner);
+
+ // Update the room record with a pointer to our new banner
+ CtdlGetRoomLock(&CC->room, CC->room.QRname);
+ long old_msgnum = CC->room.msgnum_info;
+ CC->room.msgnum_info = new_msgnum;
+ CtdlPutRoomLock(&CC->room);
+
+ // Delete the old one
+ CtdlDeleteMessages(SYSCONFIGROOM, &old_msgnum, 1, "");
+}
+
+
+// cmd_lflr() - List all known floors
+void cmd_lflr(char *gargs) {
+ int a;
+ struct floor flbuf;
+
+ if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+ cprintf("%d Known floors:\n", LISTING_FOLLOWS);
+
+ for (a = 0; a < MAXFLOORS; ++a) {
+ CtdlGetFloor(&flbuf, a);
+ if (flbuf.f_flags & F_INUSE) {
+ cprintf("%d|%s|%d\n", a, flbuf.f_name, flbuf.f_ref_count);
+ }
+ }
+ cprintf("000\n");
+}
+
+
+// create a new floor
+void cmd_cflr(char *argbuf) {
+ char new_floor_name[256];
+ struct floor flbuf;
+ int cflr_ok;
+ int free_slot = (-1);
+ int a;
+
+ extract_token(new_floor_name, argbuf, 0, '|', sizeof new_floor_name);
+ cflr_ok = extract_int(argbuf, 1);
+
+ if (CtdlAccessCheck(ac_aide)) return;
+
+ if (IsEmptyStr(new_floor_name)) {
+ cprintf("%d Blank floor name not allowed.\n",
+ ERROR + ILLEGAL_VALUE);
+ return;
+ }
+
+ for (a = 0; a < MAXFLOORS; ++a) {
+ CtdlGetFloor(&flbuf, a);
+
+ /* note any free slots while we're scanning... */
+ if (((flbuf.f_flags & F_INUSE) == 0)
+ && (free_slot < 0))
+ free_slot = a;
+
+ /* check to see if it already exists */
+ if ((!strcasecmp(flbuf.f_name, new_floor_name))
+ && (flbuf.f_flags & F_INUSE)) {
+ cprintf("%d Floor '%s' already exists.\n",
+ ERROR + ALREADY_EXISTS,
+ flbuf.f_name);
+ return;
+ }
+ }
+
+ if (free_slot < 0) {
+ cprintf("%d There is no space available for a new floor.\n",
+ ERROR + INVALID_FLOOR_OPERATION);
+ return;
+ }
+ if (cflr_ok == 0) {
+ cprintf("%d ok to create...\n", CIT_OK);
+ return;
+ }
+ lgetfloor(&flbuf, free_slot);
+ flbuf.f_flags = F_INUSE;
+ flbuf.f_ref_count = 0;
+ safestrncpy(flbuf.f_name, new_floor_name, sizeof flbuf.f_name);
+ lputfloor(&flbuf, free_slot);
+ cprintf("%d %d\n", CIT_OK, free_slot);
+}
+
+
+// delete a floor
+void cmd_kflr(char *argbuf) {
+ struct floor flbuf;
+ int floor_to_delete;
+ int kflr_ok;
+ int delete_ok;
+
+ floor_to_delete = extract_int(argbuf, 0);
+ kflr_ok = extract_int(argbuf, 1);
+
+ if (CtdlAccessCheck(ac_aide)) return;
+
+ lgetfloor(&flbuf, floor_to_delete);
+
+ delete_ok = 1;
+ if ((flbuf.f_flags & F_INUSE) == 0) {
+ cprintf("%d Floor %d not in use.\n", ERROR + INVALID_FLOOR_OPERATION, floor_to_delete);
+ delete_ok = 0;
+ } else {
+ if (flbuf.f_ref_count != 0) {
+ cprintf("%d Cannot delete; floor contains %d rooms.\n",
+ ERROR + INVALID_FLOOR_OPERATION,
+ flbuf.f_ref_count);
+ delete_ok = 0;
+ }
+ else {
+ if (kflr_ok == 1) {
+ cprintf("%d Ok\n", CIT_OK);
+ }
+ else {
+ cprintf("%d Ok to delete...\n", CIT_OK);
+ }
+
+ }
+
+ }
+
+ if ((delete_ok == 1) && (kflr_ok == 1)) {
+ flbuf.f_flags = 0;
+ }
+ lputfloor(&flbuf, floor_to_delete);
+}
+
+
+// edit a floor
+void cmd_eflr(char *argbuf) {
+ struct floor flbuf;
+ int floor_num;
+ int np;
+
+ np = num_parms(argbuf);
+ if (np < 1) {
+ cprintf("%d Usage error.\n", ERROR + ILLEGAL_VALUE);
+ return;
+ }
+
+ if (CtdlAccessCheck(ac_aide)) return;
+
+ floor_num = extract_int(argbuf, 0);
+ lgetfloor(&flbuf, floor_num);
+ if ((flbuf.f_flags & F_INUSE) == 0) {
+ lputfloor(&flbuf, floor_num);
+ cprintf("%d Floor %d is not in use.\n", ERROR + INVALID_FLOOR_OPERATION, floor_num);
+ return;
+ }
+ if (np >= 2) {
+ extract_token(flbuf.f_name, argbuf, 1, '|', sizeof flbuf.f_name);
+ }
+ lputfloor(&flbuf, floor_num);
+
+ cprintf("%d Ok\n", CIT_OK);
+}
+
+
+// cmd_stat() - return the modification time of the current room (maybe other things in the future)
+void cmd_stat(char *gargs) {
+ if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+ CtdlGetRoom(&CC->room, CC->room.QRname);
+ cprintf("%d %s|%ld|\n", CIT_OK, CC->room.QRname, CC->room.QRmtime);
+}
+
+
+/*****************************************************************************/
+/* MODULE INITIALIZATION STUFF */
+/*****************************************************************************/
+
+char *ctdl_module_init_rooms(void) {
+ if (!threading) {
+ CtdlRegisterProtoHook(cmd_lrms, "LRMS", "List rooms");
+ CtdlRegisterProtoHook(cmd_lkra, "LKRA", "List all known rooms");
+ CtdlRegisterProtoHook(cmd_lkrn, "LKRN", "List known rooms with new messages");
+ CtdlRegisterProtoHook(cmd_lkro, "LKRO", "List known rooms without new messages");
+ CtdlRegisterProtoHook(cmd_lzrm, "LZRM", "List zapped rooms");
+ CtdlRegisterProtoHook(cmd_lprm, "LPRM", "List public rooms");
+ CtdlRegisterProtoHook(cmd_goto, "GOTO", "Goto a named room");
+ CtdlRegisterProtoHook(cmd_stat, "STAT", "Get mtime of the current room");
+ CtdlRegisterProtoHook(cmd_whok, "WHOK", "List users who know this room");
+ CtdlRegisterProtoHook(cmd_rdir, "RDIR", "List files in room directory");
+ CtdlRegisterProtoHook(cmd_getr, "GETR", "Get room parameters");
+ CtdlRegisterProtoHook(cmd_setr, "SETR", "Set room parameters");
+ CtdlRegisterProtoHook(cmd_geta, "GETA", "Get the room admin name");
+ CtdlRegisterProtoHook(cmd_seta, "SETA", "Set the room admin for this room");
+ CtdlRegisterProtoHook(cmd_rinf, "RINF", "Fetch room info file");
+ CtdlRegisterProtoHook(cmd_kill, "KILL", "Kill (delete) the current room");
+ CtdlRegisterProtoHook(cmd_cre8, "CRE8", "Create a new room");
+ CtdlRegisterProtoHook(cmd_einf, "EINF", "Enter info file for the current room");
+ CtdlRegisterProtoHook(cmd_lflr, "LFLR", "List all known floors");
+ CtdlRegisterProtoHook(cmd_cflr, "CFLR", "Create a new floor");
+ CtdlRegisterProtoHook(cmd_kflr, "KFLR", "Kill a floor");
+ CtdlRegisterProtoHook(cmd_eflr, "EFLR", "Edit a floor");
+ }
+ /* return our id for the log */
+ return "rooms";
+}
--- /dev/null
+/*
+ * Server functions which perform operations on user objects.
+ *
+ * Copyright (c) 1987-2022 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.
+ */
+
+#include <stdio.h>
+#include <libcitadel.h>
+#include "../../citserver.h"
+#include "../../ctdl_module.h"
+#include "../../config.h"
+
+
+void cmd_noop(char *argbuf)
+{
+ cprintf("%d%cok\n", CIT_OK, CtdlCheckExpress() );
+}
+
+
+void cmd_qnop(char *argbuf)
+{
+ /* do nothing, this command returns no response */
+}
+
+
+/*
+ * Set or unset asynchronous protocol mode
+ */
+void cmd_asyn(char *argbuf)
+{
+ int new_state;
+
+ new_state = extract_int(argbuf, 0);
+ if ((new_state == 0) || (new_state == 1)) {
+ CC->is_async = new_state;
+ }
+ cprintf("%d %d\n", CIT_OK, CC->is_async);
+}
+
+
+/*
+ * cmd_info() - Identify this server and its capabilities to the client
+ */
+void cmd_info(char *cmdbuf) {
+ cprintf("%d Server info:\n", LISTING_FOLLOWS);
+ cprintf("%d\n", CC->cs_pid);
+ cprintf("%s\n", CtdlGetConfigStr("c_nodename"));
+ cprintf("%s\n", CtdlGetConfigStr("c_humannode"));
+ cprintf("%s\n", CtdlGetConfigStr("c_fqdn"));
+ cprintf("%s\n", CITADEL);
+ cprintf("%d\n", REV_LEVEL);
+ cprintf("%s\n", CtdlGetConfigStr("c_site_location"));
+ cprintf("%s\n", CtdlGetConfigStr("c_sysadm"));
+ cprintf("%d\n", SERVER_TYPE);
+ cprintf("%s\n", CtdlGetConfigStr("c_moreprompt"));
+ cprintf("1\n"); /* 1 = yes, this system supports floors */
+ cprintf("1\n"); /* 1 = we support the extended paging options */
+ cprintf("\n"); /* no longer used */
+ cprintf("1\n"); /* 1 = yes, this system supports the QNOP command */
+ cprintf("1\n"); /* 1 = yes, this server is LDAP-enabled */
+
+ if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_NATIVE) && (CtdlGetConfigInt("c_disable_newu") == 0))
+ {
+ cprintf("%d\n", CtdlGetConfigInt("c_disable_newu"));
+ }
+ else {
+ cprintf("1\n"); /* "create new user" does not work with non-native auth modes */
+ }
+
+ cprintf("%s\n", CtdlGetConfigStr("c_default_cal_zone"));
+
+ cprintf("0\n"); /* no longer used */
+ cprintf("0\n"); /* no longer used */
+ cprintf("0\n"); /* no longer used */
+ cprintf("0\n"); /* no longer used */
+
+ cprintf("%d\n", CtdlGetConfigInt("c_enable_fulltext"));
+ cprintf("%s\n", BUILD_ID);
+
+ if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_NATIVE) {
+ cprintf("%d\n", openid_level_supported); /* OpenID is enabled when using native auth */
+ }
+ else {
+ cprintf("0\n"); /* OpenID is disabled when using non-native auth */
+ }
+
+ cprintf("%d\n", CtdlGetConfigInt("c_guest_logins"));
+ cprintf("000\n");
+}
+
+
+/*
+ * echo
+ */
+void cmd_echo(char *etext)
+{
+ cprintf("%d %s\n", CIT_OK, etext);
+}
+
+
+/*
+ * get the paginator prompt
+ */
+void cmd_more(char *argbuf) {
+ cprintf("%d %s\n", CIT_OK, CtdlGetConfigStr("c_moreprompt"));
+}
+
+
+/*
+ * the client is identifying itself to the server
+ */
+void cmd_iden(char *argbuf)
+{
+ int dev_code;
+ int cli_code;
+ int rev_level;
+ char desc[128];
+ char from_host[128];
+
+ if (num_parms(argbuf)<4) {
+ cprintf("%d usage error\n", ERROR + ILLEGAL_VALUE);
+ return;
+ }
+
+ dev_code = extract_int(argbuf,0);
+ cli_code = extract_int(argbuf,1);
+ rev_level = extract_int(argbuf,2);
+ extract_token(desc, argbuf, 3, '|', sizeof desc);
+
+ safestrncpy(from_host, CtdlGetConfigStr("c_fqdn"), sizeof from_host);
+ from_host[sizeof from_host - 1] = 0;
+ if (num_parms(argbuf)>=5) extract_token(from_host, argbuf, 4, '|', sizeof from_host);
+
+ CC->cs_clientdev = dev_code;
+ CC->cs_clienttyp = cli_code;
+ CC->cs_clientver = rev_level;
+ safestrncpy(CC->cs_clientname, desc, sizeof CC->cs_clientname);
+ CC->cs_clientname[31] = 0;
+
+ /* For local sockets, allow the client to supply the user's origin address */
+ if ((CC->is_local_client) || (!IsEmptyStr(CC->cs_addr) && (!strcmp(CC->cs_addr, "127.0.0.1")) || (!strcmp(CC->cs_addr, "::1")))) {
+ safestrncpy(CC->cs_host, from_host, sizeof CC->cs_host);
+ CC->cs_host[sizeof CC->cs_host - 1] = 0;
+ CC->cs_addr[0] = 0;
+ }
+
+ syslog(LOG_NOTICE, "session: client %d/%d/%01d.%02d (%s) from %s",
+ dev_code,
+ cli_code,
+ (rev_level / 100),
+ (rev_level % 100),
+ desc,
+ CC->cs_host
+ );
+ cprintf("%d Ok\n",CIT_OK);
+}
+
+
+/*
+ * Terminate another running session
+ */
+void cmd_term(char *cmdbuf)
+{
+ int session_num = extract_int(cmdbuf, 0);
+ int terminated = CtdlTerminateOtherSession(session_num);
+
+ if (terminated < 0) {
+ cprintf("%d You can't kill your own session.\n", ERROR + ILLEGAL_VALUE);
+ return;
+ }
+
+ if (terminated & TERM_FOUND) {
+ if (terminated == TERM_KILLED) {
+ cprintf("%d Session terminated.\n", CIT_OK);
+ }
+ else {
+ cprintf("%d You are not allowed to do that.\n", ERROR + HIGHER_ACCESS_REQUIRED);
+ }
+ }
+ else {
+ cprintf("%d No such session.\n", ERROR + ILLEGAL_VALUE);
+ }
+}
+
+
+void cmd_time(char *argbuf)
+{
+ time_t tv;
+ struct tm tmp;
+
+ tv = time(NULL);
+ localtime_r(&tv, &tmp);
+
+ /* timezone and daylight global variables are not portable. */
+#ifdef HAVE_STRUCT_TM_TM_GMTOFF
+ cprintf("%d %ld|%ld|%d|%ld\n", CIT_OK, (long)tv, tmp.tm_gmtoff, tmp.tm_isdst, server_startup_time);
+#else
+ cprintf("%d %ld|%ld|%d|%ld\n", CIT_OK, (long)tv, timezone, tmp.tm_isdst, server_startup_time);
+#endif
+}
+
+
+/*****************************************************************************/
+/* MODULE INITIALIZATION STUFF */
+/*****************************************************************************/
+
+char *ctdl_module_init_serv_session(void) {
+ if (!threading) {
+ CtdlRegisterProtoHook(cmd_noop, "NOOP", "no operation");
+ CtdlRegisterProtoHook(cmd_qnop, "QNOP", "no operation with no response");
+ CtdlRegisterProtoHook(cmd_asyn, "ASYN", "enable asynchronous server responses");
+ CtdlRegisterProtoHook(cmd_info, "INFO", "fetch server capabilities and configuration");
+ CtdlRegisterProtoHook(cmd_echo, "ECHO", "echo text back to the client");
+ CtdlRegisterProtoHook(cmd_more, "MORE", "fetch the paginator prompt");
+ CtdlRegisterProtoHook(cmd_iden, "IDEN", "identify the client software and location");
+ CtdlRegisterProtoHook(cmd_term, "TERM", "terminate another running session");
+ CtdlRegisterProtoHook(cmd_time, "TIME", "fetch the date and time from the server");
+ }
+ /* return our id for the Log */
+ return "serv_session";
+}
--- /dev/null
+// System management commands for Citadel server
+//
+// Copyright (c) 1987-2022 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.
+
+#include <stdio.h>
+#include <libcitadel.h>
+
+#include "../../serv_extensions.h"
+#include "../../ctdl_module.h"
+
+
+// Shut down or restart the server
+void cmd_down(char *argbuf) {
+ char *Reply ="%d Shutting down server. Goodbye.\n";
+
+ if (CtdlAccessCheck(ac_aide)) return;
+
+ if (!IsEmptyStr(argbuf)) {
+ int state = CIT_OK;
+ restart_server = extract_int(argbuf, 0);
+
+ if (restart_server > 0) {
+ Reply = "%d citserver will now shut down and automatically restart.\n";
+ }
+ cprintf(Reply, state);
+ }
+ else {
+ cprintf(Reply, CIT_OK + SERVER_SHUTTING_DOWN);
+ }
+ CC->kill_me = KILLME_SERVER_SHUTTING_DOWN;
+ server_shutting_down = 1;
+}
+
+
+// Halt the server without exiting the server process.
+void cmd_halt(char *argbuf) {
+ if (CtdlAccessCheck(ac_aide)) return;
+
+ cprintf("%d Halting server. Goodbye.\n", CIT_OK);
+ server_shutting_down = 1;
+ shutdown_and_halt = 1;
+}
+
+
+// Schedule or cancel a server shutdown
+void cmd_scdn(char *argbuf) {
+ int new_state;
+ int state = CIT_OK;
+ char *Reply = "%d %d\n";
+
+ if (CtdlAccessCheck(ac_aide)) return;
+
+ new_state = extract_int(argbuf, 0);
+ if ((new_state == 2) || (new_state == 3)) {
+ restart_server = 1;
+ restart_server = extract_int(argbuf, 0);
+ new_state -= 2;
+ }
+ if ((new_state == 0) || (new_state == 1)) {
+ ScheduledShutdown = new_state;
+ }
+ cprintf(Reply, state, ScheduledShutdown);
+}
+
+
+// ****************************************************************************
+// * MODULE INITIALIZATION STUFF *
+// ****************************************************************************
+
+char *ctdl_module_init_syscmd(void) {
+ if (!threading) {
+ CtdlRegisterProtoHook(cmd_down, "DOWN", "perform a server shutdown");
+ CtdlRegisterProtoHook(cmd_halt, "HALT", "halt the server without exiting the server process");
+ CtdlRegisterProtoHook(cmd_scdn, "SCDN", "schedule or cancel a server shutdown");
+ }
+ // return our id for the log
+ return "syscmd";
+}
--- /dev/null
+// Server functions which perform operations on user objects.
+//
+// Copyright (c) 1987-2022 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.
+
+
+#include "../../support.h"
+#include "../../control.h"
+#include "../../ctdl_module.h"
+#include "../../citserver.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../internet_addressing.h"
+
+
+// USER command -- attempt to log in as an existing user
+void cmd_user(char *cmdbuf) {
+ char username[256];
+ int a;
+
+ extract_token(username, cmdbuf, 0, '|', sizeof username);
+ striplt(username);
+ syslog(LOG_DEBUG, "user_ops: cmd_user(%s)", username);
+
+ a = CtdlLoginExistingUser(username);
+ switch (a) {
+ case login_already_logged_in:
+ cprintf("%d Already logged in.\n", ERROR + ALREADY_LOGGED_IN);
+ return;
+ case login_too_many_users:
+ cprintf("%d %s: Too many users are already online (maximum is %d)\n",
+ ERROR + MAX_SESSIONS_EXCEEDED,
+ CtdlGetConfigStr("c_nodename"), CtdlGetConfigInt("c_maxsessions")
+ );
+ return;
+ case login_ok:
+ cprintf("%d Password required for %s\n", MORE_DATA, CC->curr_user);
+ return;
+ case login_not_found:
+ cprintf("%d %s not found.\n", ERROR + NO_SUCH_USER, username);
+ return;
+ default:
+ cprintf("%d Internal error\n", ERROR + INTERNAL_ERROR);
+ }
+}
+
+
+// PASS command -- complete logging in as an existing user (used after USER returns MORE_DATA)
+void cmd_pass(char *buf) {
+ char password[SIZ];
+ int a;
+ long len;
+
+ memset(password, 0, sizeof(password));
+ len = extract_token(password, buf, 0, '|', sizeof password);
+ a = CtdlTryPassword(password, len);
+
+ switch (a) {
+ case pass_already_logged_in:
+ cprintf("%d Already logged in.\n", ERROR + ALREADY_LOGGED_IN);
+ return;
+ case pass_no_user:
+ cprintf("%d You must send a name with USER first.\n", ERROR + USERNAME_REQUIRED);
+ return;
+ case pass_wrong_password:
+ cprintf("%d Wrong password.\n", ERROR + PASSWORD_REQUIRED);
+ return;
+ case pass_ok:
+ logged_in_response();
+ return;
+ }
+}
+
+
+// cmd_newu() - create a new user account and log in as that user
+void cmd_newu(char *cmdbuf) {
+ int a;
+ char username[SIZ];
+
+ if (CtdlGetConfigInt("c_auth_mode") != AUTHMODE_NATIVE) {
+ cprintf("%d This system does not use native mode authentication.\n",
+ ERROR + NOT_HERE);
+ return;
+ }
+
+ if (CtdlGetConfigInt("c_disable_newu")) {
+ cprintf("%d Self-service user account creation is disabled on this system.\n", ERROR + NOT_HERE);
+ return;
+ }
+
+ if (CC->logged_in) {
+ cprintf("%d Already logged in.\n", ERROR + ALREADY_LOGGED_IN);
+ return;
+ }
+ if (CC->nologin) {
+ cprintf("%d %s: Too many users are already online (maximum is %d)\n",
+ ERROR + MAX_SESSIONS_EXCEEDED,
+ CtdlGetConfigStr("c_nodename"), CtdlGetConfigInt("c_maxsessions"));
+ return;
+ }
+ extract_token(username, cmdbuf, 0, '|', sizeof username);
+ strproc(username);
+
+ if (IsEmptyStr(username)) {
+ cprintf("%d You must supply a user name.\n", ERROR + USERNAME_REQUIRED);
+ return;
+ }
+
+ if ((!strcasecmp(username, "bbs")) ||
+ (!strcasecmp(username, "new")) ||
+ (!strcasecmp(username, "."))
+ ) {
+ cprintf("%d '%s' is an invalid login name.\n", ERROR + ILLEGAL_VALUE, username);
+ return;
+ }
+
+ a = create_user(username, CREATE_USER_BECOME_USER, NATIVE_AUTH_UID);
+
+ if (a == 0) {
+ logged_in_response();
+ }
+ else if (a == ERROR + ALREADY_EXISTS) {
+ cprintf("%d '%s' already exists.\n",
+ ERROR + ALREADY_EXISTS, username);
+ return;
+ }
+ else if (a == ERROR + INTERNAL_ERROR) {
+ cprintf("%d Internal error - user record disappeared?\n",
+ ERROR + INTERNAL_ERROR);
+ return;
+ }
+ else {
+ cprintf("%d unknown error\n", ERROR + INTERNAL_ERROR);
+ }
+}
+
+
+// set password - citadel protocol implementation
+void cmd_setp(char *new_pw) {
+ if (CtdlAccessCheck(ac_logged_in)) {
+ return;
+ }
+ if ( (CC->user.uid != CTDLUID) && (CC->user.uid != (-1)) ) {
+ cprintf("%d Not allowed. Use the 'passwd' command.\n", ERROR + NOT_HERE);
+ return;
+ }
+
+ if (!strcasecmp(new_pw, "GENERATE_RANDOM_PASSWORD")) {
+ char random_password[17];
+ snprintf(random_password, sizeof random_password, "%08lx%08lx", random(), random());
+ CtdlSetPassword(random_password);
+ cprintf("%d %s\n", CIT_OK, random_password);
+ }
+ else {
+ strproc(new_pw);
+ if (IsEmptyStr(new_pw)) {
+ cprintf("%d Password unchanged.\n", CIT_OK);
+ return;
+ }
+ CtdlSetPassword(new_pw);
+ cprintf("%d Password changed.\n", CIT_OK);
+ }
+}
+
+
+// cmd_creu() - administratively create a new user account (do not log in to it)
+void cmd_creu(char *cmdbuf) {
+ int a;
+ char username[SIZ];
+ char password[SIZ];
+ struct ctdluser tmp;
+
+ if (CtdlAccessCheck(ac_aide)) {
+ return;
+ }
+
+ extract_token(username, cmdbuf, 0, '|', sizeof username);
+ strproc(username);
+ strproc(password);
+ if (IsEmptyStr(username)) {
+ cprintf("%d You must supply a user name.\n", ERROR + USERNAME_REQUIRED);
+ return;
+ }
+
+ extract_token(password, cmdbuf, 1, '|', sizeof password);
+
+ a = create_user(username, CREATE_USER_DO_NOT_BECOME_USER, NATIVE_AUTH_UID);
+
+ if (a == 0) {
+ if (!IsEmptyStr(password)) {
+ CtdlGetUserLock(&tmp, username);
+ safestrncpy(tmp.password, password, sizeof(tmp.password));
+ CtdlPutUserLock(&tmp);
+ }
+ cprintf("%d User '%s' created %s.\n", CIT_OK, username, (!IsEmptyStr(password)) ? "and password set" : "with no password");
+ return;
+ }
+ else if (a == ERROR + ALREADY_EXISTS) {
+ cprintf("%d '%s' already exists.\n", ERROR + ALREADY_EXISTS, username);
+ return;
+ }
+ else if ( (CtdlGetConfigInt("c_auth_mode") != AUTHMODE_NATIVE) && (a == ERROR + NO_SUCH_USER) ) {
+ cprintf("%d User accounts are not created within Citadel in host authentication mode.\n", ERROR + NO_SUCH_USER);
+ return;
+ }
+ else {
+ cprintf("%d An error occurred creating the user account.\n", ERROR + INTERNAL_ERROR);
+ }
+}
+
+
+// get user parameters
+void cmd_getu(char *cmdbuf) {
+ if (CtdlAccessCheck(ac_logged_in)) {
+ return;
+ }
+
+ CtdlGetUser(&CC->user, CC->curr_user);
+ cprintf("%d 80|24|%d|\n", CIT_OK, (CC->user.flags & US_USER_SET));
+}
+
+
+// set user parameters
+void cmd_setu(char *new_parms) {
+ if (CtdlAccessCheck(ac_logged_in)) {
+ return;
+ }
+
+ if (num_parms(new_parms) < 3) {
+ cprintf("%d Usage error.\n", ERROR + ILLEGAL_VALUE);
+ return;
+ }
+ CtdlLockGetCurrentUser();
+ CC->user.flags = CC->user.flags & (~US_USER_SET);
+ CC->user.flags = CC->user.flags | (extract_int(new_parms, 2) & US_USER_SET);
+ CtdlPutCurrentUserLock();
+ cprintf("%d Ok\n", CIT_OK);
+}
+
+
+// set last read pointer (marks all messages in the current room as read, up to the specified point)
+void cmd_slrp(char *new_ptr) {
+ long newlr;
+ visit vbuf;
+ visit original_vbuf;
+
+ if (CtdlAccessCheck(ac_logged_in)) {
+ return;
+ }
+
+ if (!strncasecmp(new_ptr, "highest", 7)) {
+ newlr = CC->room.QRhighest;
+ }
+ else {
+ newlr = atol(new_ptr);
+ }
+
+ CtdlLockGetCurrentUser();
+
+ CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+ memcpy(&original_vbuf, &vbuf, sizeof(visit));
+ vbuf.v_lastseen = newlr;
+ snprintf(vbuf.v_seen, sizeof vbuf.v_seen, "*:%ld", newlr);
+
+ // Only rewrite the record if it changed
+ if ( (vbuf.v_lastseen != original_vbuf.v_lastseen)
+ || (strcmp(vbuf.v_seen, original_vbuf.v_seen))
+ ) {
+ CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
+ }
+
+ CtdlPutCurrentUserLock();
+ cprintf("%d %ld\n", CIT_OK, newlr);
+}
+
+
+void cmd_seen(char *argbuf) {
+ long target_msgnum = 0L;
+ int target_setting = 0;
+
+ if (CtdlAccessCheck(ac_logged_in)) {
+ return;
+ }
+
+ if (num_parms(argbuf) != 2) {
+ cprintf("%d Invalid parameters\n", ERROR + ILLEGAL_VALUE);
+ return;
+ }
+
+ target_msgnum = extract_long(argbuf, 0);
+ target_setting = extract_int(argbuf, 1);
+
+ CtdlSetSeen(&target_msgnum, 1, target_setting,
+ ctdlsetseen_seen, NULL, NULL);
+ cprintf("%d OK\n", CIT_OK);
+}
+
+
+void cmd_gtsn(char *argbuf) {
+ visit vbuf;
+
+ if (CtdlAccessCheck(ac_logged_in)) {
+ return;
+ }
+
+ // Learn about the user and room in question
+ CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+
+ cprintf("%d ", CIT_OK);
+ client_write(vbuf.v_seen, strlen(vbuf.v_seen));
+ client_write(HKEY("\n"));
+}
+
+
+// INVT and KICK commands (grant/revoke access to an invitation-only room)
+void cmd_invt_kick(char *iuser, int op) {
+
+ // These commands are only allowed by admins, room admins,
+ // and room namespace owners
+ if (is_room_aide()) {
+ // access granted
+ }
+ else if ( ((atol(CC->room.QRname) == CC->user.usernum) ) && (CC->user.usernum != 0) ) {
+ // access granted
+ }
+ else {
+ // access denied
+ cprintf("%d Higher access or room ownership required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
+ return;
+ }
+
+ if (!strncasecmp(CC->room.QRname, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)) {
+ cprintf("%d Can't add/remove users from this room.\n", ERROR + NOT_HERE);
+ return;
+ }
+
+ if (CtdlInvtKick(iuser, op) != 0) {
+ cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
+ return;
+ }
+
+ cprintf("%d %s %s %s.\n",
+ CIT_OK, iuser,
+ ((op == 1) ? "invited to" : "kicked out of"),
+ CC->room.QRname);
+ return;
+}
+
+
+void cmd_invt(char *iuser) {
+ cmd_invt_kick(iuser, 1);
+}
+
+
+void cmd_kick(char *iuser) {
+ cmd_invt_kick(iuser, 0);
+}
+
+
+// forget (Zap) the current room
+void cmd_forg(char *argbuf) {
+
+ if (CtdlAccessCheck(ac_logged_in)) {
+ return;
+ }
+
+ if (CtdlForgetThisRoom() == 0) {
+ cprintf("%d Ok\n", CIT_OK);
+ }
+ else {
+ cprintf("%d You may not forget this room.\n", ERROR + NOT_HERE);
+ }
+}
+
+
+// Get Next Unregistered User
+void cmd_gnur(char *argbuf) {
+ struct cdbdata *cdbus;
+ struct ctdluser usbuf;
+
+ if (CtdlAccessCheck(ac_aide)) {
+ return;
+ }
+
+ if ((CtdlGetConfigInt("MMflags") & MM_VALID) == 0) {
+ cprintf("%d There are no unvalidated users.\n", CIT_OK);
+ return;
+ }
+
+ // There are unvalidated users. Traverse the user database, and return the first user we find that needs validation.
+ cdb_rewind(CDB_USERS);
+ while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL) {
+ memset(&usbuf, 0, sizeof(struct ctdluser));
+ memcpy(&usbuf, cdbus->ptr, ((cdbus->len > sizeof(struct ctdluser)) ? sizeof(struct ctdluser) : cdbus->len));
+ cdb_free(cdbus);
+ if ((usbuf.flags & US_NEEDVALID) && (usbuf.axlevel > AxDeleted)) {
+ cprintf("%d %s\n", MORE_DATA, usbuf.fullname);
+ cdb_close_cursor(CDB_USERS);
+ return;
+ }
+ }
+
+ // If we get to this point, there are no more unvalidated users. Therefore we clear the "users need validation" flag.
+ begin_critical_section(S_CONTROL);
+ int flags;
+ flags = CtdlGetConfigInt("MMflags");
+ flags = flags & (~MM_VALID);
+ CtdlSetConfigInt("MMflags", flags);
+ end_critical_section(S_CONTROL);
+ cprintf("%d *** End of registration.\n", CIT_OK);
+}
+
+
+// validate a user
+void cmd_vali(char *v_args) {
+ char user[128];
+ int newax;
+ struct ctdluser userbuf;
+
+ extract_token(user, v_args, 0, '|', sizeof user);
+ newax = extract_int(v_args, 1);
+
+ if (CtdlAccessCheck(ac_aide) || (newax > AxAideU) || (newax < AxDeleted)) {
+ return;
+ }
+
+ if (CtdlGetUserLock(&userbuf, user) != 0) {
+ cprintf("%d '%s' not found.\n", ERROR + NO_SUCH_USER, user);
+ return;
+ }
+
+ userbuf.axlevel = newax;
+ userbuf.flags = (userbuf.flags & ~US_NEEDVALID);
+
+ CtdlPutUserLock(&userbuf);
+
+ // If the access level was set to zero, delete the user
+ if (newax == 0) {
+ if (purge_user(user) == 0) {
+ cprintf("%d %s Deleted.\n", CIT_OK, userbuf.fullname);
+ return;
+ }
+ }
+ cprintf("%d User '%s' validated.\n", CIT_OK, userbuf.fullname);
+}
+
+
+// List one user (this works with cmd_list)
+void ListThisUser(char *username, void *data) {
+ char *searchstring;
+ struct ctdluser usbuf;
+
+ if (CtdlGetUser(&usbuf, username) != 0) {
+ return;
+ }
+
+ searchstring = (char *)data;
+ if (bmstrcasestr(usbuf.fullname, searchstring) == NULL) {
+ return;
+ }
+
+ if (usbuf.axlevel > AxDeleted) {
+ if ((CC->user.axlevel >= AxAideU)
+ || ((usbuf.flags & US_UNLISTED) == 0)
+ || ((CC->internal_pgm))) {
+ cprintf("%s|%d|%ld|%ld|%ld|%ld||\n",
+ usbuf.fullname,
+ usbuf.axlevel,
+ usbuf.usernum,
+ (long)usbuf.lastcall,
+ usbuf.timescalled,
+ usbuf.posted);
+ }
+ }
+}
+
+
+// List users (searchstring may be empty to list all users)
+void cmd_list(char *cmdbuf) {
+ char searchstring[256];
+ extract_token(searchstring, cmdbuf, 0, '|', sizeof searchstring);
+ striplt(searchstring);
+ cprintf("%d \n", LISTING_FOLLOWS);
+ ForEachUser(ListThisUser, (void *)searchstring );
+ cprintf("000\n");
+}
+
+
+// assorted info we need to check at login
+void cmd_chek(char *argbuf) {
+ int mail = 0;
+ int regis = 0;
+ int vali = 0;
+
+ if (CtdlAccessCheck(ac_logged_in)) {
+ return;
+ }
+
+ CtdlGetUser(&CC->user, CC->curr_user); // no lock is needed here
+ if ((REGISCALL != 0) && ((CC->user.flags & US_REGIS) == 0)) {
+ regis = 1;
+ }
+
+ if (CC->user.axlevel >= AxAideU) {
+ if (CtdlGetConfigInt("MMflags") & MM_VALID) {
+ vali = 1;
+ }
+ }
+
+ mail = InitialMailCheck(); // check for mail
+ cprintf("%d %d|%d|%d|%s|\n", CIT_OK, mail, regis, vali, CC->cs_inet_email);
+}
+
+
+// check to see if a user exists
+void cmd_qusr(char *who) {
+ struct ctdluser usbuf;
+
+ if (CtdlGetUser(&usbuf, who) == 0) {
+ cprintf("%d %s\n", CIT_OK, usbuf.fullname);
+ }
+ else {
+ cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
+ }
+}
+
+
+// Administrative Get User Parameters
+void cmd_agup(char *cmdbuf) {
+ struct ctdluser usbuf;
+ char requested_user[128];
+
+ if (CtdlAccessCheck(ac_aide)) {
+ return;
+ }
+
+ extract_token(requested_user, cmdbuf, 0, '|', sizeof requested_user);
+ if (CtdlGetUser(&usbuf, requested_user) != 0) {
+ cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
+ return;
+ }
+ cprintf("%d %s|%s|%u|%ld|%ld|%d|%ld|%ld|%d\n",
+ CIT_OK,
+ usbuf.fullname,
+ usbuf.password,
+ usbuf.flags,
+ usbuf.timescalled,
+ usbuf.posted,
+ (int) usbuf.axlevel,
+ usbuf.usernum,
+ (long)usbuf.lastcall,
+ usbuf.USuserpurge);
+}
+
+
+// Administrative Set User Parameters
+void cmd_asup(char *cmdbuf) {
+ struct ctdluser usbuf;
+ char requested_user[128];
+ char notify[SIZ];
+ int np;
+ int newax;
+ int deleted = 0;
+
+ if (CtdlAccessCheck(ac_aide))
+ return;
+
+ extract_token(requested_user, cmdbuf, 0, '|', sizeof requested_user);
+ if (CtdlGetUserLock(&usbuf, requested_user) != 0) {
+ cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
+ return;
+ }
+ np = num_parms(cmdbuf);
+ if (np > 1)
+ extract_token(usbuf.password, cmdbuf, 1, '|', sizeof usbuf.password);
+ if (np > 2)
+ usbuf.flags = extract_int(cmdbuf, 2);
+ if (np > 3)
+ usbuf.timescalled = extract_int(cmdbuf, 3);
+ if (np > 4)
+ usbuf.posted = extract_int(cmdbuf, 4);
+ if (np > 5) {
+ newax = extract_int(cmdbuf, 5);
+ if ((newax >= AxDeleted) && (newax <= AxAideU)) {
+ usbuf.axlevel = newax;
+ }
+ }
+ if (np > 7) {
+ usbuf.lastcall = extract_long(cmdbuf, 7);
+ }
+ if (np > 8) {
+ usbuf.USuserpurge = extract_int(cmdbuf, 8);
+ }
+ CtdlPutUserLock(&usbuf);
+ if (usbuf.axlevel == AxDeleted) {
+ if (purge_user(requested_user) == 0) {
+ deleted = 1;
+ }
+ }
+
+ if (deleted) {
+ snprintf(notify, SIZ,
+ "User \"%s\" has been deleted by %s.\n",
+ usbuf.fullname, (CC->logged_in ? CC->user.fullname : "an administrator")
+ );
+ CtdlAideMessage(notify, "User Deletion Message");
+ }
+
+ cprintf("%d Ok", CIT_OK);
+ if (deleted) {
+ cprintf(" (%s deleted)", requested_user);
+ }
+ cprintf("\n");
+}
+
+
+// Citadel protocol command to do the same
+void cmd_isme(char *argbuf) {
+ char addr[256];
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+ extract_token(addr, argbuf, 0, '|', sizeof addr);
+
+ if (CtdlIsMe(addr, sizeof addr)) {
+ cprintf("%d %s\n", CIT_OK, addr);
+ }
+ else {
+ cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
+ }
+
+}
+
+
+// Retrieve all Internet email addresses/aliases for the specified user
+void cmd_agea(char *cmdbuf) {
+ struct ctdluser usbuf;
+ char requested_user[128];
+ int i, num_e;
+ char e[512];
+
+ if (CtdlAccessCheck(ac_aide)) {
+ return;
+ }
+
+ extract_token(requested_user, cmdbuf, 0, '|', sizeof requested_user);
+ if (CtdlGetUser(&usbuf, requested_user) != 0) {
+ cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
+ return;
+ }
+ cprintf("%d internet email addresses for %s\n", LISTING_FOLLOWS, usbuf.fullname);
+ num_e = num_tokens(usbuf.emailaddrs, '|');
+ for (i=0; i<num_e; ++i) {
+ extract_token(e, usbuf.emailaddrs, i, '|', sizeof e);
+ cprintf("%s\n", e);
+ }
+ cprintf("000\n");
+}
+
+
+// Set the Internet email addresses/aliases for the specified user
+void cmd_asea(char *cmdbuf) {
+ struct ctdluser usbuf;
+ char requested_user[128];
+ char buf[SIZ];
+ char whodat[64];
+ char new_emailaddrs[512] = { 0 } ;
+
+ if (CtdlAccessCheck(ac_aide)) return;
+
+ extract_token(requested_user, cmdbuf, 0, '|', sizeof requested_user);
+ if (CtdlGetUser(&usbuf, requested_user) != 0) {
+ cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
+ return;
+ }
+
+ cprintf("%d Ok\n", SEND_LISTING);
+ while (client_getln(buf, sizeof buf) >= 0 && strcmp(buf, "000")) {
+ if (IsEmptyStr(buf)) {
+ syslog(LOG_ERR, "user_ops: address <%s> is empty - not using", buf);
+ }
+ else if ((strlen(new_emailaddrs) + strlen(buf) + 2) > sizeof(new_emailaddrs)) {
+ syslog(LOG_ERR, "user_ops: address <%s> does not fit in buffer - not using", buf);
+ }
+ else if (!IsDirectory(buf, 0)) {
+ syslog(LOG_ERR, "user_ops: address <%s> is not in one of our domains - not using", buf);
+ }
+ else if ( (CtdlDirectoryLookup(whodat, buf, sizeof whodat) == 0) && (CtdlUserCmp(whodat, requested_user)) ) {
+ syslog(LOG_ERR, "user_ops: address <%s> already belongs to <%s> - not using", buf, whodat);
+ }
+ else {
+ syslog(LOG_DEBUG, "user_ops: address <%s> validated", buf);
+ if (!IsEmptyStr(new_emailaddrs)) {
+ strcat(new_emailaddrs, "|");
+ }
+ strcat(new_emailaddrs, buf);
+ }
+ }
+
+ CtdlSetEmailAddressesForUser(requested_user, new_emailaddrs);
+}
+
+
+// Set the preferred view for the current user/room combination
+void cmd_view(char *cmdbuf) {
+ int requested_view;
+ visit vbuf;
+
+ if (CtdlAccessCheck(ac_logged_in)) {
+ return;
+ }
+
+ requested_view = extract_int(cmdbuf, 0);
+
+ CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+ vbuf.v_view = requested_view;
+ CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
+
+ cprintf("%d ok\n", CIT_OK);
+}
+
+
+// Rename a user
+void cmd_renu(char *cmdbuf) {
+ int retcode;
+ char oldname[USERNAME_SIZE];
+ char newname[USERNAME_SIZE];
+
+ if (CtdlAccessCheck(ac_aide)) {
+ return;
+ }
+
+ extract_token(oldname, cmdbuf, 0, '|', sizeof oldname);
+ extract_token(newname, cmdbuf, 1, '|', sizeof newname);
+
+ retcode = rename_user(oldname, newname);
+ switch(retcode) {
+ case RENAMEUSER_OK:
+ cprintf("%d '%s' has been renamed to '%s'.\n", CIT_OK, oldname, newname);
+ return;
+ case RENAMEUSER_LOGGED_IN:
+ cprintf("%d '%s' is currently logged in and cannot be renamed.\n",
+ ERROR + ALREADY_LOGGED_IN , oldname
+ );
+ return;
+ case RENAMEUSER_NOT_FOUND:
+ cprintf("%d '%s' does not exist.\n", ERROR + NO_SUCH_USER, oldname);
+ return;
+ case RENAMEUSER_ALREADY_EXISTS:
+ cprintf("%d A user named '%s' already exists.\n", ERROR + ALREADY_EXISTS, newname);
+ return;
+ }
+
+ cprintf("%d An unknown error occurred.\n", ERROR);
+}
+
+
+void cmd_quit(char *argbuf) {
+ cprintf("%d Goodbye.\n", CIT_OK);
+ CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
+}
+
+
+void cmd_lout(char *argbuf) {
+ if (CC->logged_in)
+ CtdlUserLogout();
+ cprintf("%d logged out.\n", CIT_OK);
+}
+
+
+/*****************************************************************************/
+/* MODULE INITIALIZATION STUFF */
+/*****************************************************************************/
+
+
+char *ctdl_module_init_serv_user(void) {
+ if (!threading) {
+ CtdlRegisterProtoHook(cmd_user, "USER", "Submit username for login");
+ CtdlRegisterProtoHook(cmd_pass, "PASS", "Complete login by submitting a password");
+ CtdlRegisterProtoHook(cmd_quit, "QUIT", "log out and disconnect from server");
+ CtdlRegisterProtoHook(cmd_lout, "LOUT", "log out but do not disconnect from server");
+ CtdlRegisterProtoHook(cmd_creu, "CREU", "Create User");
+ CtdlRegisterProtoHook(cmd_setp, "SETP", "Set the password for an account");
+ CtdlRegisterProtoHook(cmd_getu, "GETU", "Get User parameters");
+ CtdlRegisterProtoHook(cmd_setu, "SETU", "Set User parameters");
+ CtdlRegisterProtoHook(cmd_slrp, "SLRP", "Set Last Read Pointer");
+ CtdlRegisterProtoHook(cmd_invt, "INVT", "Invite a user to a room");
+ CtdlRegisterProtoHook(cmd_kick, "KICK", "Kick a user out of a room");
+ CtdlRegisterProtoHook(cmd_forg, "FORG", "Forget a room");
+ CtdlRegisterProtoHook(cmd_gnur, "GNUR", "Get Next Unregistered User");
+ CtdlRegisterProtoHook(cmd_vali, "VALI", "Validate new users");
+ CtdlRegisterProtoHook(cmd_list, "LIST", "List users");
+ CtdlRegisterProtoHook(cmd_chek, "CHEK", "assorted info we need to check at login");
+ CtdlRegisterProtoHook(cmd_qusr, "QUSR", "check to see if a user exists");
+ CtdlRegisterProtoHook(cmd_agup, "AGUP", "Administratively Get User Parameters");
+ CtdlRegisterProtoHook(cmd_asup, "ASUP", "Administratively Set User Parameters");
+ CtdlRegisterProtoHook(cmd_agea, "AGEA", "Administratively Get Email Addresses");
+ CtdlRegisterProtoHook(cmd_asea, "ASEA", "Administratively Set Email Addresses");
+ CtdlRegisterProtoHook(cmd_seen, "SEEN", "Manipulate seen/unread message flags");
+ CtdlRegisterProtoHook(cmd_gtsn, "GTSN", "Fetch seen/unread message flags");
+ CtdlRegisterProtoHook(cmd_view, "VIEW", "Set preferred view for user/room combination");
+ CtdlRegisterProtoHook(cmd_renu, "RENU", "Rename a user");
+ CtdlRegisterProtoHook(cmd_newu, "NEWU", "Log in as a new user");
+ CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
+ }
+ /* return our Subversion id for the Log */
+ return "user";
+}
--- /dev/null
+/*
+ * 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.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <string.h>
+
+#include <time.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../database.h"
+#include "../../config.h"
+#include "../../room_ops.h"
+#include "../../sysdep_decls.h"
+#include "../../support.h"
+#include "../../msgbase.h"
+#include "../../citserver.h"
+#include "../../ctdl_module.h"
+#include "../../user_ops.h"
+
+/*
+ * 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 (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
+ */
+ if ( (qrbuf->QRflags & QR_MAILBOX) == 0) {
+ fl = CtdlGetCachedFloor(qrbuf->QRfloor);
+ if (fl->f_ep.expire_mode != 0) {
+ memcpy(epbuf, &fl->f_ep, sizeof(struct ExpirePolicy));
+ return;
+ }
+ }
+
+ /* (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");
+ epbuf->expire_value = CtdlGetConfigInt("c_mbxep_value");
+ return;
+ }
+ }
+
+ /* Otherwise, fall back on the system default */
+ epbuf->expire_mode = CtdlGetConfigInt("c_ep_mode");
+ epbuf->expire_value = CtdlGetConfigInt("c_ep_value");
+}
+
+
+/*
+ * Get Policy EXpire
+ */
+void cmd_gpex(char *argbuf) {
+ struct ExpirePolicy exp;
+ struct floor *fl;
+ char which[128];
+
+ memset(&exp, 0, sizeof(struct ExpirePolicy));
+ extract_token(which, argbuf, 0, '|', sizeof which);
+ if (!strcasecmp(which, strof(roompolicy)) || !strcasecmp(which, "room")) {
+ memcpy(&exp, &CC->room.QRep, sizeof(struct ExpirePolicy));
+ }
+ else if (!strcasecmp(which, strof(floorpolicy)) || !strcasecmp(which, "floor")) {
+ fl = CtdlGetCachedFloor(CC->room.QRfloor);
+ memcpy(&exp, &fl->f_ep, sizeof(struct ExpirePolicy));
+ }
+ else if (!strcasecmp(which, strof(mailboxespolicy)) || !strcasecmp(which, "mailboxes")) {
+ exp.expire_mode = CtdlGetConfigInt("c_mbxep_mode");
+ exp.expire_value = CtdlGetConfigInt("c_mbxep_value");
+ }
+ else if (!strcasecmp(which, strof(sitepolicy)) || !strcasecmp(which, "site")) {
+ exp.expire_mode = CtdlGetConfigInt("c_ep_mode");
+ exp.expire_value = CtdlGetConfigInt("c_ep_value");
+ }
+ else {
+ cprintf("%d Invalid keyword \"%s\"\n", ERROR + ILLEGAL_VALUE, which);
+ return;
+ }
+
+ cprintf("%d %d|%d\n", CIT_OK, exp.expire_mode, exp.expire_value);
+}
+
+
+/*
+ * Set Policy EXpire
+ */
+void cmd_spex(char *argbuf) {
+ struct ExpirePolicy exp;
+ struct floor flbuf;
+ char which[128];
+
+ memset(&exp, 0, sizeof(struct ExpirePolicy));
+ extract_token(which, argbuf, 0, '|', sizeof which);
+ exp.expire_mode = extract_int(argbuf, 1);
+ exp.expire_value = extract_int(argbuf, 2);
+
+ if ((exp.expire_mode < 0) || (exp.expire_mode > 3)) {
+ cprintf("%d Invalid policy.\n", ERROR + ILLEGAL_VALUE);
+ return;
+ }
+
+ if ((!strcasecmp(which, strof(roompolicy))) || (!strcasecmp(which, "room")))
+ {
+ if (!is_room_aide()) {
+ cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
+ return;
+ }
+ CtdlGetRoomLock(&CC->room, CC->room.QRname);
+ memcpy(&CC->room.QRep, &exp, sizeof(struct ExpirePolicy));
+ CtdlPutRoomLock(&CC->room);
+ cprintf("%d Room expire policy for '%s' has been updated.\n", CIT_OK, CC->room.QRname);
+ syslog(LOG_DEBUG, "Room: %s , Policy: %d , Value: %d",
+ CC->room.QRname,
+ exp.expire_mode,
+ exp.expire_value
+ );
+ return;
+ }
+
+ if (CC->user.axlevel < AxAideU) {
+ cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
+ return;
+ }
+
+ 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);
+ cprintf("%d Floor expire policy has been updated.\n", CIT_OK);
+ return;
+ }
+
+ 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")))
+ {
+ if (exp.expire_mode == EXPIRE_NEXTLEVEL) {
+ cprintf("%d Invalid policy (no higher level)\n", ERROR + ILLEGAL_VALUE);
+ return;
+ }
+ CtdlSetConfigInt("c_ep_mode", exp.expire_mode);
+ CtdlSetConfigInt("c_ep_value", exp.expire_value);
+ cprintf("%d Site expire policy has been updated.\n", CIT_OK);
+ return;
+ }
+
+ cprintf("%d Invalid keyword '%s'\n", ERROR + ILLEGAL_VALUE, which);
+}
--- /dev/null
+void GetExpirePolicy(struct ExpirePolicy *epbuf, struct ctdlroom *qrbuf);
+void cmd_gpex(char *argbuf);
+void cmd_spex(char *argbuf);
--- /dev/null
+// This module handles the expiry of old messages and the purging of old users.
+//
+// You might also see this module affectionately referred to as TDAP (The Dreaded Auto-Purger).
+//
+// Copyright (c) 1988-2022 by citadel.org (Art Cancro, Wilifried Goesgens, and others)
+//
+// This program is open source software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as published
+// by the Free Software Foundation; either version 3 of the License, or
+// (at your option) any later version.
+//
+// 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.
+
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "policy.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../user_ops.h"
+#include "../../control.h"
+#include "../../threads.h"
+#include "../../context.h"
+
+#include "../../ctdl_module.h"
+
+
+struct PurgeList {
+ struct PurgeList *next;
+ char name[ROOMNAMELEN]; // use the larger of username or roomname
+};
+
+struct VPurgeList {
+ struct VPurgeList *next;
+ long vp_roomnum;
+ long vp_roomgen;
+ long vp_usernum;
+};
+
+struct ValidRoom {
+ struct ValidRoom *next;
+ long vr_roomnum;
+ long vr_roomgen;
+};
+
+struct ValidUser {
+ struct ValidUser *next;
+ long vu_usernum;
+};
+
+struct ctdlroomref {
+ struct ctdlroomref *next;
+ long msgnum;
+};
+
+struct UPurgeList {
+ struct UPurgeList *next;
+ char up_key[256];
+};
+
+struct EPurgeList {
+ struct EPurgeList *next;
+ int ep_keylen;
+ char *ep_key;
+};
+
+
+struct PurgeList *UserPurgeList = NULL;
+struct PurgeList *RoomPurgeList = NULL;
+struct ValidRoom *ValidRoomList = NULL;
+struct ValidUser *ValidUserList = NULL;
+int messages_purged;
+int users_not_purged;
+char *users_corrupt_msg = NULL;
+char *users_zero_msg = NULL;
+struct ctdlroomref *rr = NULL;
+int force_purge_now = 0; // set to nonzero to force a run right now
+
+
+// First phase of message purge -- gather the locations of messages which
+// qualify for purging and write them to a temp file.
+void GatherPurgeMessages(struct ctdlroom *qrbuf, void *data) {
+ struct ExpirePolicy epbuf;
+ long delnum;
+ time_t xtime, now;
+ struct CtdlMessage *msg = NULL;
+ int a;
+ struct cdbdata *cdbfr;
+ long *msglist = NULL;
+ int num_msgs = 0;
+ FILE *purgelist;
+
+ purgelist = (FILE *)data;
+ fprintf(purgelist, "r=%s\n", qrbuf->QRname);
+
+ time(&now);
+ GetExpirePolicy(&epbuf, qrbuf);
+
+ // If the room is set to never expire messages ... do nothing
+ if (epbuf.expire_mode == EXPIRE_NEXTLEVEL) return;
+ if (epbuf.expire_mode == EXPIRE_MANUAL) return;
+
+ // Don't purge messages containing system configuration, dumbass.
+ if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
+
+ // Ok, we got this far ... now let's see what's in the room.
+ cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
+
+ if (cdbfr != NULL) {
+ msglist = malloc(cdbfr->len);
+ memcpy(msglist, cdbfr->ptr, cdbfr->len);
+ num_msgs = cdbfr->len / sizeof(long);
+ cdb_free(cdbfr);
+ }
+
+ // Nothing to do if there aren't any messages
+ if (num_msgs == 0) {
+ if (msglist != NULL) free(msglist);
+ return;
+ }
+
+ // If the room is set to expire by count, do that.
+ if (epbuf.expire_mode == EXPIRE_NUMMSGS) {
+ if (num_msgs > epbuf.expire_value) {
+ for (a=0; a<(num_msgs - epbuf.expire_value); ++a) {
+ fprintf(purgelist, "m=%ld\n", msglist[a]);
+ ++messages_purged;
+ }
+ }
+ }
+
+ // If the room is set to expire by age...
+ if (epbuf.expire_mode == EXPIRE_AGE) {
+ for (a=0; a<num_msgs; ++a) {
+ delnum = msglist[a];
+
+ msg = CtdlFetchMessage(delnum, 0); // don't need the body
+ if (msg != NULL) {
+ xtime = atol(msg->cm_fields[eTimestamp]);
+ CM_Free(msg);
+ }
+ else {
+ xtime = 0L;
+ }
+
+ if ((xtime > 0L) && (now - xtime > (time_t)(epbuf.expire_value * 86400L))) {
+ fprintf(purgelist, "m=%ld\n", delnum);
+ ++messages_purged;
+ }
+ }
+ }
+
+ if (msglist != NULL) free(msglist);
+}
+
+
+// Second phase of message purge -- read list of msgs from temp file and delete them.
+void DoPurgeMessages(FILE *purgelist) {
+ char roomname[ROOMNAMELEN];
+ long msgnum;
+ char buf[SIZ];
+
+ rewind(purgelist);
+ strcpy(roomname, "nonexistent room ___ ___");
+ while (fgets(buf, sizeof buf, purgelist) != NULL) {
+ buf[strlen(buf)-1]=0;
+ if (!strncasecmp(buf, "r=", 2)) {
+ strcpy(roomname, &buf[2]);
+ }
+ if (!strncasecmp(buf, "m=", 2)) {
+ msgnum = atol(&buf[2]);
+ if (msgnum > 0L) {
+ CtdlDeleteMessages(roomname, &msgnum, 1, "");
+ }
+ }
+ }
+}
+
+
+void PurgeMessages(void) {
+ FILE *purgelist;
+
+ syslog(LOG_DEBUG, "PurgeMessages() called");
+ messages_purged = 0;
+
+ purgelist = tmpfile();
+ if (purgelist == NULL) {
+ syslog(LOG_CRIT, "Can't create purgelist temp file: %s", strerror(errno));
+ return;
+ }
+
+ CtdlForEachRoom(GatherPurgeMessages, (void *)purgelist );
+ DoPurgeMessages(purgelist);
+ fclose(purgelist);
+}
+
+
+void AddValidUser(char *username, void *data) {
+ struct ValidUser *vuptr;
+ struct ctdluser usbuf;
+
+ if (CtdlGetUser(&usbuf, username) != 0) {
+ return;
+ }
+
+ vuptr = (struct ValidUser *)malloc(sizeof(struct ValidUser));
+ vuptr->next = ValidUserList;
+ vuptr->vu_usernum = usbuf.usernum;
+ ValidUserList = vuptr;
+}
+
+
+void AddValidRoom(struct ctdlroom *qrbuf, void *data) {
+ struct ValidRoom *vrptr;
+
+ vrptr = (struct ValidRoom *)malloc(sizeof(struct ValidRoom));
+ vrptr->next = ValidRoomList;
+ vrptr->vr_roomnum = qrbuf->QRnumber;
+ vrptr->vr_roomgen = qrbuf->QRgen;
+ ValidRoomList = vrptr;
+}
+
+
+void DoPurgeRooms(struct ctdlroom *qrbuf, void *data) {
+ time_t age, purge_secs;
+ struct PurgeList *pptr;
+ struct ValidUser *vuptr;
+ int do_purge = 0;
+
+ // For mailbox rooms, there's only one purging rule: if the user who
+ // owns the room still exists, we keep the room; otherwise, we purge
+ // it. Bypass any other rules.
+ if (qrbuf->QRflags & QR_MAILBOX) {
+ // if user not found, do_purge will be 1
+ do_purge = 1;
+ for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
+ if (vuptr->vu_usernum == atol(qrbuf->QRname)) {
+ do_purge = 0;
+ }
+ }
+ }
+ else {
+ // Any of these attributes render a room non-purgable
+ if (qrbuf->QRflags & QR_PERMANENT) return;
+ if (qrbuf->QRflags & QR_DIRECTORY) return;
+ if (qrbuf->QRflags & QR_NETWORK) return;
+ if (qrbuf->QRflags2 & QR2_SYSTEM) return;
+ if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
+ if (CtdlIsNonEditable(qrbuf)) return;
+
+ // If we don't know the modification date, be safe and don't purge
+ if (qrbuf->QRmtime <= (time_t)0) return;
+
+ // If no room purge time is set, be safe and don't purge
+ if (CtdlGetConfigLong("c_roompurge") < 0) return;
+
+ // Otherwise, check the date of last modification
+ age = time(NULL) - (qrbuf->QRmtime);
+ purge_secs = CtdlGetConfigLong("c_roompurge") * 86400;
+ if (purge_secs <= (time_t)0) return;
+ syslog(LOG_DEBUG, "<%s> is <%ld> seconds old", qrbuf->QRname, (long)age);
+ if (age > purge_secs) do_purge = 1;
+ } // !QR_MAILBOX
+
+ if (do_purge) {
+ pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
+ pptr->next = RoomPurgeList;
+ strcpy(pptr->name, qrbuf->QRname);
+ RoomPurgeList = pptr;
+ }
+
+}
+
+
+int PurgeRooms(void) {
+ struct PurgeList *pptr;
+ int num_rooms_purged = 0;
+ struct ctdlroom qrbuf;
+ struct ValidUser *vuptr;
+ char *transcript = NULL;
+
+ syslog(LOG_DEBUG, "PurgeRooms() called");
+
+ // Load up a table full of valid user numbers so we can delete
+ // user-owned rooms for users who no longer exist
+ ForEachUser(AddValidUser, NULL);
+
+ // Then cycle through the room file
+ CtdlForEachRoom(DoPurgeRooms, NULL);
+
+ // Free the valid user list
+ while (ValidUserList != NULL) {
+ vuptr = ValidUserList->next;
+ free(ValidUserList);
+ ValidUserList = vuptr;
+ }
+
+ transcript = malloc(SIZ);
+ strcpy(transcript, "The following rooms have been auto-purged:\n");
+
+ while (RoomPurgeList != NULL) {
+ if (CtdlGetRoom(&qrbuf, RoomPurgeList->name) == 0) {
+ transcript=realloc(transcript, strlen(transcript)+SIZ);
+ snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
+ qrbuf.QRname);
+ CtdlDeleteRoom(&qrbuf);
+ }
+ pptr = RoomPurgeList->next;
+ free(RoomPurgeList);
+ RoomPurgeList = pptr;
+ ++num_rooms_purged;
+ }
+
+ if (num_rooms_purged > 0) CtdlAideMessage(transcript, "Room Autopurger Message");
+ free(transcript);
+
+ syslog(LOG_DEBUG, "Purged %d rooms.", num_rooms_purged);
+ return(num_rooms_purged);
+}
+
+
+// Back end function to check user accounts for expiration.
+void do_user_purge(char *username, void *data) {
+ int purge;
+ time_t now;
+ time_t purge_time;
+ struct PurgeList *pptr;
+ struct ctdluser us;
+
+ if (CtdlGetUser(&us, username) != 0) {
+ return;
+ }
+
+ // Set purge time; if the user overrides the system default, use it
+ if (us.USuserpurge > 0) {
+ purge_time = ((time_t)us.USuserpurge) * 86400;
+ }
+ else {
+ purge_time = CtdlGetConfigLong("c_userpurge") * 86400;
+ }
+
+ // The default rule is to not purge.
+ purge = 0;
+
+ // If the user has not logged in for the configured amount of time, expire the account.
+ if (CtdlGetConfigLong("c_userpurge") > 0) {
+ now = time(NULL);
+ if ((now - us.lastcall) > purge_time) purge = 1;
+ }
+
+ // If the account is marked as permanent, don't purge it.
+ if (us.flags & US_PERM) purge = 0;
+
+ // If the account is an administrator, don't purge it.
+ if (us.axlevel == 6) purge = 0;
+
+ // If the access level is 0, the record should already have been
+ // deleted, but maybe the user was logged in at the time or something.
+ // Delete the record now.
+ if (us.axlevel == 0) purge = 1;
+
+ // If the user set his/her password to 'deleteme', he/she
+ // wishes to be deleted, so purge the record.
+ // Moved this lower down so that aides and permanent users get purged if they ask to be.
+ if (!strcasecmp(us.password, "deleteme")) purge = 1;
+
+ // Fewer than zero calls is impossible, indicating a corrupted record.
+ if (us.timescalled < 0) purge = 1;
+
+ // any negative user number, is also impossible.
+ if (us.usernum < 0L) purge = 1;
+
+ // Don't purge user 0. That user is there for the system
+ if (us.usernum == 0L) {
+ // FIXME: Temporary log message. Until we do unauth access with user 0 we should
+ // try to get rid of all user 0 occurences. Many will be remnants from old code so
+ // we will need to try and purge them from users data bases.Some will not have names but
+ // those with names should be purged.
+ syslog(LOG_DEBUG, "Auto purger found a user 0 with name <%s>", us.fullname);
+ // purge = 0;
+ }
+
+ // If the user has no full name entry then we can't purge them since the actual purge can't find them.
+ // This shouldn't happen but does somehow.
+ if (IsEmptyStr(us.fullname)) {
+ purge = 0;
+
+ if (us.usernum > 0L) {
+ purge=0;
+ if (users_corrupt_msg == NULL) {
+ users_corrupt_msg = malloc(SIZ);
+ strcpy(users_corrupt_msg,
+ "The auto-purger found the following user numbers with no name.\n"
+ "The system has no way to purge a user with no name,"
+ " and should not be able to create them either.\n"
+ "This indicates corruption of the user DB or possibly a bug.\n"
+ "It may be a good idea to restore your DB from a backup.\n"
+ );
+ }
+
+ users_corrupt_msg=realloc(users_corrupt_msg, strlen(users_corrupt_msg)+30);
+ snprintf(&users_corrupt_msg[strlen(users_corrupt_msg)], 29, " %ld\n", us.usernum);
+ }
+ }
+
+ if (purge == 1) {
+ pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
+ pptr->next = UserPurgeList;
+ strcpy(pptr->name, us.fullname);
+ UserPurgeList = pptr;
+ }
+ else {
+ ++users_not_purged;
+ }
+
+}
+
+
+int PurgeUsers(void) {
+ struct PurgeList *pptr;
+ int num_users_purged = 0;
+ char *transcript = NULL;
+
+ syslog(LOG_DEBUG, "PurgeUsers() called");
+ users_not_purged = 0;
+
+ switch(CtdlGetConfigInt("c_auth_mode")) {
+ case AUTHMODE_NATIVE:
+ ForEachUser(do_user_purge, NULL);
+ break;
+ default:
+ syslog(LOG_DEBUG, "User purge for auth mode %d is not implemented.", CtdlGetConfigInt("c_auth_mode"));
+ break;
+ }
+
+ transcript = malloc(SIZ);
+
+ if (users_not_purged == 0) {
+ strcpy(transcript, "The auto-purger was told to purge every user. It is\n"
+ "refusing to do this because it usually indicates a problem\n"
+ "such as an inability to communicate with a name service.\n"
+ );
+ while (UserPurgeList != NULL) {
+ pptr = UserPurgeList->next;
+ free(UserPurgeList);
+ UserPurgeList = pptr;
+ ++num_users_purged;
+ }
+ }
+
+ else {
+ strcpy(transcript, "The following users have been auto-purged:\n");
+ while (UserPurgeList != NULL) {
+ transcript=realloc(transcript, strlen(transcript)+SIZ);
+ snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
+ UserPurgeList->name);
+ purge_user(UserPurgeList->name);
+ pptr = UserPurgeList->next;
+ free(UserPurgeList);
+ UserPurgeList = pptr;
+ ++num_users_purged;
+ }
+ }
+
+ if (num_users_purged > 0) CtdlAideMessage(transcript, "User Purge Message");
+ free(transcript);
+
+ if (users_corrupt_msg) {
+ CtdlAideMessage(users_corrupt_msg, "User Corruption Message");
+ free (users_corrupt_msg);
+ users_corrupt_msg = NULL;
+ }
+
+ if(users_zero_msg) {
+ CtdlAideMessage(users_zero_msg, "User Zero Message");
+ free (users_zero_msg);
+ users_zero_msg = NULL;
+ }
+
+ syslog(LOG_DEBUG, "Purged %d users.", num_users_purged);
+ return(num_users_purged);
+}
+
+
+// Purge visits
+//
+// This is a really cumbersome "garbage collection" function. We have to
+// delete visits which refer to rooms and/or users which no longer exist. In
+// order to prevent endless traversals of the room and user files, we first
+// build linked lists of rooms and users which _do_ exist on the system, then
+// traverse the visit file, checking each record against those two lists and
+// purging the ones that do not have a match on _both_ lists. (Remember, if
+// either the room or user being referred to is no longer on the system, the
+// record is completely useless.)
+//
+int PurgeVisits(void) {
+ struct cdbdata *cdbvisit;
+ visit vbuf;
+ struct VPurgeList *VisitPurgeList = NULL;
+ struct VPurgeList *vptr;
+ int purged = 0;
+ char IndexBuf[32];
+ int IndexLen;
+ struct ValidRoom *vrptr;
+ struct ValidUser *vuptr;
+ int RoomIsValid, UserIsValid;
+
+ // First, load up a table full of valid room/gen combinations
+ CtdlForEachRoom(AddValidRoom, NULL);
+
+ // Then load up a table full of valid user numbers
+ ForEachUser(AddValidUser, NULL);
+
+ // Now traverse through the visits, purging irrelevant records...
+ cdb_rewind(CDB_VISIT);
+ while(cdbvisit = cdb_next_item(CDB_VISIT), cdbvisit != NULL) {
+ memset(&vbuf, 0, sizeof(visit));
+ memcpy(&vbuf, cdbvisit->ptr,
+ ( (cdbvisit->len > sizeof(visit)) ?
+ sizeof(visit) : cdbvisit->len) );
+ cdb_free(cdbvisit);
+
+ RoomIsValid = 0;
+ UserIsValid = 0;
+
+ // Check to see if the room exists
+ for (vrptr=ValidRoomList; vrptr!=NULL; vrptr=vrptr->next) {
+ if ( (vrptr->vr_roomnum==vbuf.v_roomnum)
+ && (vrptr->vr_roomgen==vbuf.v_roomgen))
+ RoomIsValid = 1;
+ }
+
+ // Check to see if the user exists
+ for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
+ if (vuptr->vu_usernum == vbuf.v_usernum)
+ UserIsValid = 1;
+ }
+
+ // Put the record on the purge list if it's dead
+ if ((RoomIsValid==0) || (UserIsValid==0)) {
+ vptr = (struct VPurgeList *)
+ malloc(sizeof(struct VPurgeList));
+ vptr->next = VisitPurgeList;
+ vptr->vp_roomnum = vbuf.v_roomnum;
+ vptr->vp_roomgen = vbuf.v_roomgen;
+ vptr->vp_usernum = vbuf.v_usernum;
+ VisitPurgeList = vptr;
+ }
+
+ }
+
+ // Free the valid room/gen combination list
+ while (ValidRoomList != NULL) {
+ vrptr = ValidRoomList->next;
+ free(ValidRoomList);
+ ValidRoomList = vrptr;
+ }
+
+ // Free the valid user list
+ while (ValidUserList != NULL) {
+ vuptr = ValidUserList->next;
+ free(ValidUserList);
+ ValidUserList = vuptr;
+ }
+
+ // Now delete every visit on the purged list
+ while (VisitPurgeList != NULL) {
+ IndexLen = GenerateRelationshipIndex(IndexBuf,
+ VisitPurgeList->vp_roomnum,
+ VisitPurgeList->vp_roomgen,
+ VisitPurgeList->vp_usernum);
+ cdb_delete(CDB_VISIT, IndexBuf, IndexLen);
+ vptr = VisitPurgeList->next;
+ free(VisitPurgeList);
+ VisitPurgeList = vptr;
+ ++purged;
+ }
+
+ return(purged);
+}
+
+
+// Purge the use table of old entries.
+int PurgeUseTable(StrBuf *ErrMsg) {
+ int purged = 0;
+ struct cdbdata *cdbut;
+ struct UseTable ut;
+ struct UPurgeList *ul = NULL;
+ struct UPurgeList *uptr;
+
+ // Phase 1: traverse through the table, discovering old records...
+
+ syslog(LOG_DEBUG, "Purge use table: phase 1");
+ cdb_rewind(CDB_USETABLE);
+ while(cdbut = cdb_next_item(CDB_USETABLE), cdbut != NULL) {
+ if (cdbut->len > sizeof(struct UseTable))
+ memcpy(&ut, cdbut->ptr, sizeof(struct UseTable));
+ else {
+ memset(&ut, 0, sizeof(struct UseTable));
+ memcpy(&ut, cdbut->ptr, cdbut->len);
+ }
+ cdb_free(cdbut);
+
+ if ( (time(NULL) - ut.ut_timestamp) > USETABLE_RETAIN ) {
+ uptr = (struct UPurgeList *) malloc(sizeof(struct UPurgeList));
+ if (uptr != NULL) {
+ uptr->next = ul;
+ safestrncpy(uptr->up_key, ut.ut_msgid, sizeof uptr->up_key);
+ ul = uptr;
+ }
+ ++purged;
+ }
+
+ }
+
+ // Phase 2: delete the records
+ syslog(LOG_DEBUG, "Purge use table: phase 2");
+ while (ul != NULL) {
+ cdb_delete(CDB_USETABLE, ul->up_key, strlen(ul->up_key));
+ uptr = ul->next;
+ free(ul);
+ ul = uptr;
+ }
+
+ syslog(LOG_DEBUG, "Purge use table: finished (purged %d records)", purged);
+ return(purged);
+}
+
+
+// Purge the EUID Index of old records.
+int PurgeEuidIndexTable(void) {
+ int purged = 0;
+ struct cdbdata *cdbei;
+ struct EPurgeList *el = NULL;
+ struct EPurgeList *eptr;
+ long msgnum;
+ struct CtdlMessage *msg = NULL;
+
+ // Phase 1: traverse through the table, discovering old records...
+ syslog(LOG_DEBUG, "Purge EUID index: phase 1");
+ cdb_rewind(CDB_EUIDINDEX);
+ while(cdbei = cdb_next_item(CDB_EUIDINDEX), cdbei != NULL) {
+
+ memcpy(&msgnum, cdbei->ptr, sizeof(long));
+
+ msg = CtdlFetchMessage(msgnum, 0);
+ if (msg != NULL) {
+ CM_Free(msg); // it still exists, so do nothing
+ }
+ else {
+ eptr = (struct EPurgeList *) malloc(sizeof(struct EPurgeList));
+ if (eptr != NULL) {
+ eptr->next = el;
+ eptr->ep_keylen = cdbei->len - sizeof(long);
+ eptr->ep_key = malloc(cdbei->len);
+ memcpy(eptr->ep_key, &cdbei->ptr[sizeof(long)], eptr->ep_keylen);
+ el = eptr;
+ }
+ ++purged;
+ }
+
+ cdb_free(cdbei);
+
+ }
+
+ // Phase 2: delete the records
+ syslog(LOG_DEBUG, "Purge euid index: phase 2");
+ while (el != NULL) {
+ cdb_delete(CDB_EUIDINDEX, el->ep_key, el->ep_keylen);
+ free(el->ep_key);
+ eptr = el->next;
+ free(el);
+ el = eptr;
+ }
+
+ syslog(LOG_DEBUG, "Purge euid index: finished (purged %d records)", purged);
+ return(purged);
+}
+
+
+// Purge external auth assocations for missing users (theoretically this will never delete anything)
+int PurgeStaleExtAuthAssociations(void) {
+ struct cdbdata *cdboi;
+ struct ctdluser usbuf;
+ HashList *keys = NULL;
+ HashPos *HashPos;
+ char *deleteme = NULL;
+ long len;
+ void *Value;
+ const char *Key;
+ int num_deleted = 0;
+ long usernum = 0L;
+
+ keys = NewHash(1, NULL);
+ if (!keys) return(0);
+
+ cdb_rewind(CDB_EXTAUTH);
+ while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
+ if (cdboi->len > sizeof(long)) {
+ memcpy(&usernum, cdboi->ptr, sizeof(long));
+ if (CtdlGetUserByNumber(&usbuf, usernum) != 0) {
+ deleteme = strdup(cdboi->ptr + sizeof(long)),
+ Put(keys, deleteme, strlen(deleteme), deleteme, NULL);
+ }
+ }
+ cdb_free(cdboi);
+ }
+
+ // Go through the hash list, deleting keys we stored in it
+
+ HashPos = GetNewHashPos(keys, 0);
+ while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0) {
+ syslog(LOG_DEBUG, "Deleting associated external authenticator <%s>", (char*)Value);
+ cdb_delete(CDB_EXTAUTH, Value, strlen(Value));
+ // note: don't free(Value) -- deleting the hash list will handle this for us
+ ++num_deleted;
+ }
+ DeleteHashPos(&HashPos);
+ DeleteHash(&keys);
+ return num_deleted;
+}
+
+
+void purge_databases(void) {
+ int retval;
+ static time_t last_purge = 0;
+ time_t now;
+ struct tm tm;
+
+ // Do the auto-purge if the current hour equals the purge hour,
+ // but not if the operation has already been performed in the
+ // last twelve hours. This is usually enough granularity.
+ now = time(NULL);
+ localtime_r(&now, &tm);
+ if (((tm.tm_hour != CtdlGetConfigInt("c_purge_hour")) || ((now - last_purge) < 43200)) && (force_purge_now == 0)) {
+ return;
+ }
+
+ syslog(LOG_INFO, "Auto-purger: starting.");
+
+ if (!server_shutting_down) {
+ retval = PurgeUsers();
+ syslog(LOG_NOTICE, "Purged %d users.", retval);
+ }
+
+ if (!server_shutting_down) {
+ PurgeMessages();
+ syslog(LOG_NOTICE, "Expired %d messages.", messages_purged);
+ }
+
+ if (!server_shutting_down) {
+ retval = PurgeRooms();
+ syslog(LOG_NOTICE, "Expired %d rooms.", retval);
+ }
+
+ if (!server_shutting_down) {
+ retval = PurgeVisits();
+ syslog(LOG_NOTICE, "Purged %d visits.", retval);
+ }
+
+ if (!server_shutting_down) {
+ StrBuf *ErrMsg;
+ ErrMsg = NewStrBuf();
+ retval = PurgeUseTable(ErrMsg);
+ syslog(LOG_NOTICE, "Purged %d entries from the use table.", retval);
+ FreeStrBuf(&ErrMsg);
+ }
+
+ if (!server_shutting_down) {
+ retval = PurgeEuidIndexTable();
+ syslog(LOG_NOTICE, "Purged %d entries from the EUID index.", retval);
+ }
+
+ if (!server_shutting_down) {
+ retval = PurgeStaleExtAuthAssociations();
+ syslog(LOG_NOTICE, "Purged %d stale external auth associations.", retval);
+ }
+
+ //if (!server_shutting_down) {
+ // FIXME this is where we could do a non-interactive delete of zero-refcount messages
+ //}
+
+ if ( (!server_shutting_down) && (CtdlGetConfigInt("c_shrink_db_files") != 0) ) {
+ cdb_compact(); // Shrink the DB files on disk
+ }
+
+ if (!server_shutting_down) {
+ syslog(LOG_INFO, "Auto-purger: finished.");
+ last_purge = now; // So we don't do it again soon
+ force_purge_now = 0;
+ }
+ else {
+ syslog(LOG_INFO, "Auto-purger: STOPPED.");
+ }
+}
+
+
+// Manually initiate a run of The Dreaded Auto-Purger (tm)
+void cmd_tdap(char *argbuf) {
+ if (CtdlAccessCheck(ac_aide)) return;
+ force_purge_now = 1;
+ cprintf("%d Manually initiating a purger run now.\n", CIT_OK);
+}
+
+
+char *ctdl_module_init_expire(void) {
+ if (!threading) {
+ CtdlRegisterProtoHook(cmd_tdap, "TDAP", "Manually initiate auto-purger");
+ CtdlRegisterProtoHook(cmd_gpex, "GPEX", "Get expire policy");
+ CtdlRegisterProtoHook(cmd_spex, "SPEX", "Set expire policy");
+ CtdlRegisterSessionHook(purge_databases, EVT_TIMER, PRIO_CLEANUP + 20);
+ }
+
+ // return our module name for the log
+ return "expire";
+}
--- /dev/null
+/****************************************************************************
+
+ Filename: crc16.c
+ Description: Cyclic Redundancy Check 16 functions
+ Created: 24-Feb-1999
+
+ Copyright (c) 1999-2003, Indigo Systems Corporation
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ Neither the name of the Indigo Systems Corporation nor the names of its
+ contributors may be used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ THE POSSIBILITY OF SUCH DAMAGE.
+
+****************************************************************************/
+
+#include "crc16.h"
+
+#ifdef _OPT_SIZE
+
+
+/*
+ * ===== ByteCRC16 =====
+ * Calculate (update) the CRC16 for a single 8-bit byte
+ */
+int ByteCRC16(int value, int crcin)
+{
+ int k = (((crcin >> 8) ^ value) & 255) << 8;
+ int crc = 0;
+ int bits = 8;
+ do
+ {
+ if (( crc ^ k ) & 0x8000)
+ crc = (crc << 1) ^ 0x1021;
+ else
+ crc <<= 1;
+ k <<= 1;
+ }
+ while (--bits);
+ return ((crcin << 8) ^ crc);
+}
+
+#else
+
+const CRC16 ccitt_16Table[] = {
+ 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
+ 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
+ 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
+ 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
+ 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
+ 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
+ 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
+ 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
+ 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
+ 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
+ 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
+ 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
+ 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
+ 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
+ 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
+ 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
+ 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
+ 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
+ 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
+ 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
+ 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
+ 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
+ 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
+ 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
+ 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
+ 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
+ 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
+ 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
+ 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
+ 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
+ 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
+ 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
+};
+
+#define ByteCRC16(v, crc) \
+ (unsigned short)((crc << 8) ^ ccitt_16Table[((crc >> 8) ^ (v)) & 255])
+
+/*
+ * ===== CalcCRC16Words =====
+ * Calculate the CRC for a buffer of 16-bit words. Supports both
+ * Little and Big Endian formats using conditional compilation.
+ * Note: minimum count is 1 (0 case not handled)
+ */
+CRC16 CalcCRC16Words(unsigned int count, short *buffer) {
+
+ int crc = 0;
+
+ do {
+
+ int value = *buffer++;
+#ifdef _BIG_ENDIAN
+ crc = ByteCRC16(value >> 8, crc);
+ crc = ByteCRC16(value, crc);
+#else
+ crc = ByteCRC16(value, crc);
+ crc = ByteCRC16(value >> 8, crc);
+#endif
+ }
+ while (--count);
+ return (CRC16) crc;
+}
+
+#endif /* _OPT_SIZE */
+
+#ifdef _CRC16_BYTES
+
+/*
+ * ===== CalcCRC16Bytes =====
+ * Calculate the CRC for a buffer of 8-bit words.
+ * Note: minimum count is 1 (0 case not handled)
+ */
+CRC16 CalcCRC16Bytes(unsigned int count, char *buffer) {
+
+ int crc = 0;
+
+ do {
+
+ int value = *buffer++;
+ crc = ByteCRC16(value, crc);
+ }
+ while (--count);
+ return crc;
+}
+
+#endif /* _CRC16_BYTES */
+
+
--- /dev/null
+/****************************************************************************
+
+ Filename: crc16.h
+ Description: Cyclic Redundancy Check 16 functions
+ Created: 24-Feb-1999
+
+ Copyright (c) 2002-2003, Indigo Systems Corporation
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ Neither the name of the Indigo Systems Corporation nor the names of its
+ contributors may be used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ THE POSSIBILITY OF SUCH DAMAGE.
+
+****************************************************************************/
+
+#define _CRC16_BYTES 1 /* ig */
+
+#ifndef __CRC16_H__
+#define __CRC16_H__
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+typedef unsigned short CRC16;
+
+#ifdef _OPT_SIZE
+ int ByteCRC16(int value, int crcin);
+#else
+ CRC16 CalcCRC16Words(unsigned int count, short *buffer);
+#endif
+
+#ifdef _CRC16_BYTES
+ CRC16 CalcCRC16Bytes(unsigned int count, char *buffer);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __CRC16_H__ */
+
+
+
--- /dev/null
+/*
+ * Default wordbreaker module for full text indexing.
+ *
+ * Copyright (c) 2005-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.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../sysdep_decls.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../control.h"
+#include "ft_wordbreaker.h"
+#include "crc16.h"
+#include "../../ctdl_module.h"
+
+/*
+ * Noise words are not included in search indices.
+ * NOTE: if the noise word list is altered in any way, the FT_WORDBREAKER_ID
+ * must also be changed, so that the index is rebuilt.
+ */
+static char *noise_words[] = {
+ "about",
+ "after",
+ "also",
+ "another",
+ "because",
+ "been",
+ "before",
+ "being",
+ "between",
+ "both",
+ "came",
+ "come",
+ "could",
+ "each",
+ "from",
+ "have",
+ "here",
+ "himself",
+ "into",
+ "like",
+ "make",
+ "many",
+ "might",
+ "more",
+ "most",
+ "much",
+ "must",
+ "never",
+ "only",
+ "other",
+ "over",
+ "said",
+ "same",
+ "should",
+ "since",
+ "some",
+ "still",
+ "such",
+ "take",
+ "than",
+ "that",
+ "their",
+ "them",
+ "then",
+ "there",
+ "these",
+ "they",
+ "this",
+ "those",
+ "through",
+ "under",
+ "very",
+ "well",
+ "were",
+ "what",
+ "where",
+ "which",
+ "while",
+ "with",
+ "would",
+ "your"
+};
+#define NUM_NOISE (sizeof(noise_words) / sizeof(char *))
+
+
+/*
+ * Compare function
+ */
+int intcmp(const void *rec1, const void *rec2) {
+ int i1, i2;
+
+ i1 = *(const int *)rec1;
+ i2 = *(const int *)rec2;
+
+ if (i1 > i2) return(1);
+ if (i1 < i2) return(-1);
+ return(0);
+}
+
+
+void wordbreaker(const char *text, int *num_tokens, int **tokens) {
+
+ int wb_num_tokens = 0;
+ int wb_num_alloc = 0;
+ int *wb_tokens = NULL;
+
+ const char *ptr;
+ const char *word_start;
+ const char *word_end;
+ char ch;
+ int word_len;
+ char word[256];
+ int i;
+ int word_crc;
+
+ if (text == NULL) { /* no NULL text please */
+ *num_tokens = 0;
+ *tokens = NULL;
+ return;
+ }
+
+ if (text[0] == 0) { /* no empty text either */
+ *num_tokens = 0;
+ *tokens = NULL;
+ return;
+ }
+
+ ptr = text;
+ word_start = NULL;
+ while (*ptr) {
+ ch = *ptr;
+ if (isalnum(ch)) {
+ if (!word_start) {
+ word_start = ptr;
+ }
+ }
+ ++ptr;
+ ch = *ptr;
+ if ( (!isalnum(ch)) && (word_start) ) {
+ word_end = ptr;
+
+ /* extract the word */
+ word_len = word_end - word_start;
+ if (word_len >= sizeof word) {
+ syslog(LOG_DEBUG, "wordbreaker: invalid word length: %d", word_len);
+ safestrncpy(word, word_start, sizeof word);
+ word[(sizeof word) - 1] = 0;
+ }
+ else {
+ safestrncpy(word, word_start, word_len+1);
+ word[word_len] = 0;
+ }
+ word_start = NULL;
+
+ /* are we ok with the length? */
+ if ( (word_len >= WB_MIN) && (word_len <= WB_MAX) ) {
+ for (i=0; i<word_len; ++i) {
+ word[i] = tolower(word[i]);
+ }
+ /* disqualify noise words */
+ for (i=0; i<NUM_NOISE; ++i) {
+ if (!strcmp(word, noise_words[i])) {
+ word_len = 0;
+ break;
+ }
+ }
+
+ if (word_len == 0)
+ continue;
+
+ word_crc = (int) CalcCRC16Bytes(word_len, word);
+
+ ++wb_num_tokens;
+ if (wb_num_tokens > wb_num_alloc) {
+ wb_num_alloc += 512;
+ wb_tokens = realloc(wb_tokens, (sizeof(int) * wb_num_alloc));
+ }
+ wb_tokens[wb_num_tokens - 1] = word_crc;
+ }
+ }
+ }
+
+ /* sort and purge dups */
+ if (wb_num_tokens > 1) {
+ qsort(wb_tokens, wb_num_tokens, sizeof(int), intcmp);
+ for (i=0; i<(wb_num_tokens-1); ++i) {
+ if (wb_tokens[i] == wb_tokens[i+1]) {
+ memmove(&wb_tokens[i], &wb_tokens[i+1],
+ ((wb_num_tokens - i - 1)*sizeof(int)));
+ --wb_num_tokens;
+ --i;
+ }
+ }
+ }
+
+ *num_tokens = wb_num_tokens;
+ *tokens = wb_tokens;
+}
+
--- /dev/null
+/*
+ * Copyright (c) 2005-2021 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.
+ */
+
+/*
+ * This is an ID for the wordbreaker module. If we do pluggable wordbreakers
+ * later on, or even if we update this one, we can use a different ID so the
+ * system knows it needs to throw away the existing index and rebuild it.
+ */
+#define FT_WORDBREAKER_ID 0x0021
+
+/*
+ * Minimum and maximum length of words to index
+ */
+#define WB_MIN 4 // nothing with 3 or less chars
+#define WB_MAX 40
+
+void wordbreaker(const char *text, int *num_tokens, int **tokens);
--- /dev/null
+/*
+ * This module handles fulltext indexing of the message base.
+ * Copyright (c) 2005-2022 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 as published
+ * by the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../control.h"
+#include "serv_fulltext.h"
+#include "ft_wordbreaker.h"
+#include "../../threads.h"
+#include "../../context.h"
+#include "../../ctdl_module.h"
+
+long ft_newhighest = 0L;
+long *ft_newmsgs = NULL;
+int ft_num_msgs = 0;
+int ft_num_alloc = 0;
+
+int ftc_num_msgs[65536];
+long *ftc_msgs[65536];
+
+
+/*
+ * Compare function
+ */
+int longcmp(const void *rec1, const void *rec2) {
+ long i1, i2;
+
+ i1 = *(const long *)rec1;
+ i2 = *(const long *)rec2;
+
+ if (i1 > i2) return(1);
+ if (i1 < i2) return(-1);
+ return(0);
+}
+
+
+/*
+ * Flush our index cache out to disk.
+ */
+void ft_flush_cache(void) {
+ int i;
+ time_t last_update = 0;
+
+ for (i=0; i<65536; ++i) {
+ if ((time(NULL) - last_update) >= 10) {
+ syslog(LOG_INFO,
+ "fulltext: flushing index cache to disk (%d%% complete)",
+ (i * 100 / 65536)
+ );
+ last_update = time(NULL);
+ }
+ if (ftc_msgs[i] != NULL) {
+ cdb_store(CDB_FULLTEXT, &i, sizeof(int), ftc_msgs[i],
+ (ftc_num_msgs[i] * sizeof(long)));
+ ftc_num_msgs[i] = 0;
+ free(ftc_msgs[i]);
+ ftc_msgs[i] = NULL;
+ }
+ }
+ syslog(LOG_INFO, "fulltext: flushed index cache to disk (100%% complete)");
+}
+
+
+/*
+ * Index or de-index a message. (op == 1 to index, 0 to de-index)
+ */
+void ft_index_message(long msgnum, int op) {
+ int num_tokens = 0;
+ int *tokens = NULL;
+ int i, j;
+ struct cdbdata *cdb_bucket;
+ StrBuf *msgtext;
+ char *txt;
+ int tok;
+ struct CtdlMessage *msg = NULL;
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) {
+ syslog(LOG_ERR, "fulltext: ft_index_message() could not load msg %ld", msgnum);
+ return;
+ }
+
+ if (!CM_IsEmpty(msg, eSuppressIdx)) {
+ syslog(LOG_DEBUG, "fulltext: ft_index_message() excluded msg %ld", msgnum);
+ CM_Free(msg);
+ return;
+ }
+
+ syslog(LOG_DEBUG, "fulltext: ft_index_message() %s msg %ld", (op ? "adding" : "removing") , msgnum);
+
+ /* Output the message as text before indexing it, so we don't end up
+ * indexing a bunch of encoded base64, etc.
+ */
+ CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+ CtdlOutputPreLoadedMsg(msg, MT_CITADEL, HEADERS_ALL, 0, 1, 0);
+ CM_Free(msg);
+ msgtext = CC->redirect_buffer;
+ CC->redirect_buffer = NULL;
+ if (msgtext != NULL) {
+ syslog(LOG_DEBUG, "fulltext: wordbreaking message %ld (%d bytes)", msgnum, StrLength(msgtext));
+ }
+ txt = SmashStrBuf(&msgtext);
+ wordbreaker(txt, &num_tokens, &tokens);
+ free(txt);
+
+ syslog(LOG_DEBUG, "fulltext: indexing message %ld [%d tokens]", msgnum, num_tokens);
+ if (num_tokens > 0) {
+ for (i=0; i<num_tokens; ++i) {
+
+ /* Add the message to the relevant token bucket */
+
+ /* search for tokens[i] */
+ tok = tokens[i];
+
+ if ( (tok >= 0) && (tok <= 65535) ) {
+ /* fetch the bucket, Liza */
+ if (ftc_msgs[tok] == NULL) {
+ cdb_bucket = cdb_fetch(CDB_FULLTEXT, &tok, sizeof(int));
+ if (cdb_bucket != NULL) {
+ ftc_num_msgs[tok] = cdb_bucket->len / sizeof(long);
+ ftc_msgs[tok] = (long *)cdb_bucket->ptr;
+ cdb_bucket->ptr = NULL;
+ cdb_free(cdb_bucket);
+ }
+ else {
+ ftc_num_msgs[tok] = 0;
+ ftc_msgs[tok] = malloc(sizeof(long));
+ }
+ }
+
+
+ if (op == 1) { /* add to index */
+ ++ftc_num_msgs[tok];
+ ftc_msgs[tok] = realloc(ftc_msgs[tok],
+ ftc_num_msgs[tok]*sizeof(long));
+ ftc_msgs[tok][ftc_num_msgs[tok] - 1] = msgnum;
+ }
+
+ if (op == 0) { /* remove from index */
+ if (ftc_num_msgs[tok] >= 1) {
+ for (j=0; j<ftc_num_msgs[tok]; ++j) {
+ if (ftc_msgs[tok][j] == msgnum) {
+ memmove(&ftc_msgs[tok][j], &ftc_msgs[tok][j+1], ((ftc_num_msgs[tok] - j - 1)*sizeof(long)));
+ --ftc_num_msgs[tok];
+ --j;
+ }
+ }
+ }
+ }
+ }
+ else {
+ syslog(LOG_ALERT, "fulltext: invalid token %d !!", tok);
+ }
+ }
+
+ free(tokens);
+ }
+}
+
+
+/*
+ * Add a message to the list of those to be indexed.
+ */
+void ft_index_msg(long msgnum, void *userdata) {
+
+ if ((msgnum > CtdlGetConfigLong("MMfulltext")) && (msgnum <= ft_newhighest)) {
+ ++ft_num_msgs;
+ if (ft_num_msgs > ft_num_alloc) {
+ ft_num_alloc += 1024;
+ ft_newmsgs = realloc(ft_newmsgs, (ft_num_alloc * sizeof(long)));
+ }
+ ft_newmsgs[ft_num_msgs - 1] = msgnum;
+ }
+
+}
+
+
+/*
+ * Scan a room for messages to index.
+ */
+void ft_index_room(struct ctdlroom *qrbuf, void *data)
+{
+ if (server_shutting_down)
+ return;
+
+ CtdlGetRoom(&CC->room, qrbuf->QRname);
+ CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, ft_index_msg, NULL);
+}
+
+
+/*
+ * Begin the fulltext indexing process.
+ */
+void do_fulltext_indexing(void) {
+ int i;
+ static time_t last_progress = 0L;
+ static int is_running = 0;
+ if (is_running) return; /* Concurrency check - only one can run */
+ is_running = 1;
+
+ /*
+ * Don't do this if the site doesn't have it enabled.
+ */
+ if (!CtdlGetConfigInt("c_enable_fulltext")) {
+ return;
+ }
+
+ /*
+ * If we've switched wordbreaker modules, burn the index and start over.
+ */
+ begin_critical_section(S_CONTROL);
+ if (CtdlGetConfigInt("MM_fulltext_wordbreaker") != FT_WORDBREAKER_ID) {
+ syslog(LOG_DEBUG, "fulltext: wb ver on disk = %d, code ver = %d",
+ CtdlGetConfigInt("MM_fulltext_wordbreaker"), FT_WORDBREAKER_ID
+ );
+ syslog(LOG_INFO, "fulltext: (re)initializing index");
+ cdb_trunc(CDB_FULLTEXT);
+ CtdlSetConfigLong("MMfulltext", 0);
+ }
+ end_critical_section(S_CONTROL);
+
+ /*
+ * Silently return if our fulltext index is up to date with new messages.
+ */
+ if ((CtdlGetConfigLong("MMfulltext") >= CtdlGetConfigLong("MMhighest"))) {
+ return; /* nothing to do! */
+ }
+
+ /*
+ * Now go through each room and find messages to index.
+ */
+ ft_newhighest = CtdlGetConfigLong("MMhighest");
+ CtdlForEachRoom(ft_index_room, NULL); /* load all msg pointers */
+
+ if (ft_num_msgs > 0) {
+ qsort(ft_newmsgs, ft_num_msgs, sizeof(long), longcmp);
+ for (i=0; i<(ft_num_msgs-1); ++i) { /* purge dups */
+ if (ft_newmsgs[i] == ft_newmsgs[i+1]) {
+ memmove(&ft_newmsgs[i], &ft_newmsgs[i+1],
+ ((ft_num_msgs - i - 1)*sizeof(long)));
+ --ft_num_msgs;
+ --i;
+ }
+ }
+
+ /* Here it is ... do each message! */
+ for (i=0; i<ft_num_msgs; ++i) {
+ if (time(NULL) != last_progress) {
+ syslog(LOG_DEBUG,
+ "fulltext: indexed %d of %d messages (%d%%)",
+ i, ft_num_msgs,
+ ((i*100) / ft_num_msgs)
+ );
+ last_progress = time(NULL);
+ }
+ ft_index_message(ft_newmsgs[i], 1);
+
+ /* Check to see if we need to quit early */
+ if (server_shutting_down) {
+ syslog(LOG_DEBUG, "fulltext: indexer quitting early");
+ ft_newhighest = ft_newmsgs[i];
+ break;
+ }
+
+ /* Check to see if we have to maybe flush to disk */
+ if (i >= FT_MAX_CACHE) {
+ syslog(LOG_DEBUG, "fulltext: time to flush.");
+ ft_newhighest = ft_newmsgs[i];
+ break;
+ }
+
+ }
+
+ free(ft_newmsgs);
+ ft_num_msgs = 0;
+ ft_num_alloc = 0;
+ ft_newmsgs = NULL;
+ }
+
+ if (server_shutting_down) {
+ is_running = 0;
+ return;
+ }
+
+ /* Save our place so we don't have to do this again */
+ ft_flush_cache();
+ begin_critical_section(S_CONTROL);
+ CtdlSetConfigLong("MMfulltext", ft_newhighest);
+ CtdlSetConfigInt("MM_fulltext_wordbreaker", FT_WORDBREAKER_ID);
+ end_critical_section(S_CONTROL);
+
+ syslog(LOG_DEBUG, "fulltext: indexing finished");
+ is_running = 0;
+ return;
+}
+
+
+/*
+ * API call to perform searches.
+ * (This one does the "all of these words" search.)
+ * Caller is responsible for freeing the message list.
+ */
+void ft_search(int *fts_num_msgs, long **fts_msgs, const char *search_string) {
+ int num_tokens = 0;
+ int *tokens = NULL;
+ int i, j;
+ struct cdbdata *cdb_bucket;
+ int num_all_msgs = 0;
+ long *all_msgs = NULL;
+ int num_ret_msgs = 0;
+ int num_ret_alloc = 0;
+ long *ret_msgs = NULL;
+ int tok;
+
+ wordbreaker(search_string, &num_tokens, &tokens);
+ if (num_tokens > 0) {
+ for (i=0; i<num_tokens; ++i) {
+
+ /* search for tokens[i] */
+ tok = tokens[i];
+
+ /* fetch the bucket, Liza */
+ if (ftc_msgs[tok] == NULL) {
+ cdb_bucket = cdb_fetch(CDB_FULLTEXT, &tok, sizeof(int));
+ if (cdb_bucket != NULL) {
+ ftc_num_msgs[tok] = cdb_bucket->len / sizeof(long);
+ ftc_msgs[tok] = (long *)cdb_bucket->ptr;
+ cdb_bucket->ptr = NULL;
+ cdb_free(cdb_bucket);
+ }
+ else {
+ ftc_num_msgs[tok] = 0;
+ ftc_msgs[tok] = malloc(sizeof(long));
+ }
+ }
+
+ num_all_msgs += ftc_num_msgs[tok];
+ if (num_all_msgs > 0) {
+ all_msgs = realloc(all_msgs, num_all_msgs*sizeof(long) );
+ memcpy(&all_msgs[num_all_msgs-ftc_num_msgs[tok]],
+ ftc_msgs[tok], ftc_num_msgs[tok]*sizeof(long) );
+ }
+
+ }
+ free(tokens);
+ if (all_msgs != NULL) {
+ qsort(all_msgs, num_all_msgs, sizeof(long), longcmp);
+
+ /*
+ * At this point, if a message appears num_tokens times in the
+ * list, then it contains all of the search tokens.
+ */
+ if (num_all_msgs >= num_tokens)
+ for (j=0; j<(num_all_msgs-num_tokens+1); ++j) {
+ if (all_msgs[j] == all_msgs[j+num_tokens-1]) {
+
+ ++num_ret_msgs;
+ if (num_ret_msgs > num_ret_alloc) {
+ num_ret_alloc += 64;
+ ret_msgs = realloc(ret_msgs,
+ (num_ret_alloc*sizeof(long)) );
+ }
+ ret_msgs[num_ret_msgs - 1] = all_msgs[j];
+
+ }
+ }
+ free(all_msgs);
+ }
+ }
+
+ *fts_num_msgs = num_ret_msgs;
+ *fts_msgs = ret_msgs;
+}
+
+
+/*
+ * This search command is for diagnostic purposes and may be removed or replaced.
+ */
+void cmd_srch(char *argbuf) {
+ int num_msgs = 0;
+ long *msgs = NULL;
+ int i;
+ char search_string[256];
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ if (!CtdlGetConfigInt("c_enable_fulltext")) {
+ cprintf("%d Full text index is not enabled on this server.\n",
+ ERROR + CMD_NOT_SUPPORTED);
+ return;
+ }
+
+ extract_token(search_string, argbuf, 0, '|', sizeof search_string);
+ ft_search(&num_msgs, &msgs, search_string);
+
+ cprintf("%d %d msgs match all search words:\n",
+ LISTING_FOLLOWS, num_msgs);
+ if (num_msgs > 0) {
+ for (i=0; i<num_msgs; ++i) {
+ cprintf("%ld\n", msgs[i]);
+ }
+ }
+ if (msgs != NULL) free(msgs);
+ cprintf("000\n");
+}
+
+
+/*
+ * Zero out our index cache.
+ */
+void initialize_ft_cache(void) {
+ memset(ftc_num_msgs, 0, (65536 * sizeof(int)));
+ memset(ftc_msgs, 0, (65536 * sizeof(long *)));
+}
+
+
+void ft_delete_remove(char *room, long msgnum)
+{
+ if (room) return;
+
+ /* Remove from fulltext index */
+ if (CtdlGetConfigInt("c_enable_fulltext")) {
+ ft_index_message(msgnum, 0);
+ }
+}
+
+
+/*****************************************************************************/
+
+char *ctdl_module_init_fulltext(void) {
+ if (!threading) {
+ initialize_ft_cache();
+ CtdlRegisterProtoHook(cmd_srch, "SRCH", "Full text search");
+ CtdlRegisterDeleteHook(ft_delete_remove);
+ CtdlRegisterSearchFuncHook(ft_search, "fulltext");
+ CtdlRegisterSessionHook(do_fulltext_indexing, EVT_TIMER, PRIO_CLEANUP + 300);
+ }
+ /* return our module name for the log */
+ return "fulltext";
+}
--- /dev/null
+/*
+ * Copyright (c) 2005-2012 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.
+ *
+ *
+ *
+ *
+ */
+
+void ft_index_message(long msgnum, int op);
+void ft_search(int *fts_num_msgs, long **fts_msgs, const char *search_string);
+void *indexer_thread(void *arg);
--- /dev/null
+/*
+ * Copyright (c) 1987-2022 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 as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ */
+
+#include "../../ctdl_module.h"
+#include "../../config.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+
+/*
+ * DownLoad Room Image (see its icon or whatever)
+ * If this command succeeds, it follows the same protocol as the DLAT command.
+ */
+void cmd_dlri(char *cmdbuf) {
+ if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+ if (CC->room.msgnum_pic < 1) {
+ cprintf("%d No image found.\n", ERROR + FILE_NOT_FOUND);
+ return;
+ }
+
+ struct CtdlMessage *msg = CtdlFetchMessage(CC->room.msgnum_pic, 1);
+ if (msg != NULL) {
+ // The call to CtdlOutputPreLoadedMsg() with MT_SPEW_SECTION will cause the DLRI command
+ // to have the same output format as the DLAT command, because it calls the same code.
+ // For example: 600 402132|-1||image/gif|
+ safestrncpy(CC->download_desired_section, "1", sizeof CC->download_desired_section);
+ CtdlOutputPreLoadedMsg(msg, MT_SPEW_SECTION, HEADERS_NONE, 1, 0, 0);
+ CM_Free(msg);
+ }
+ else {
+ cprintf("%d No image found.\n", ERROR + MESSAGE_NOT_FOUND);
+ return;
+ }
+}
+
+
+/*
+ * UpLoad Room Image (avatar or photo or whatever)
+ */
+void cmd_ulri(char *cmdbuf) {
+ long data_length;
+ char mimetype[SIZ];
+
+ if (CtdlAccessCheck(ac_room_aide)) return;
+
+ data_length = extract_long(cmdbuf, 0);
+ extract_token(mimetype, cmdbuf, 1, '|', sizeof mimetype);
+
+ if (data_length < 20) {
+ cprintf("%d That's an awfully small file. Try again.\n", ERROR + ILLEGAL_VALUE);
+ return;
+ }
+
+ if (strncasecmp(mimetype, "image/", 6)) {
+ cprintf("%d Only image files are permitted.\n", ERROR + ILLEGAL_VALUE);
+ return;
+ }
+
+ char *unencoded_data = malloc(data_length + 1);
+ if (!unencoded_data) {
+ cprintf("%d Could not allocate %ld bytes of memory\n", ERROR + INTERNAL_ERROR , data_length);
+ return;
+ }
+
+ cprintf("%d %ld\n", SEND_BINARY, data_length);
+ client_read(unencoded_data, data_length);
+
+ // We've got the data read from the client, now save it.
+ char *encoded_data = malloc((data_length * 2) + 100);
+ if (encoded_data) {
+ sprintf(encoded_data, "Content-type: %s\nContent-transfer-encoding: base64\n\n", mimetype);
+ CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
+ long new_msgnum = quickie_message("Citadel", NULL, NULL, SYSCONFIGROOM, encoded_data, FMT_RFC822, "Image uploaded by admin user");
+
+ if (CtdlGetRoomLock(&CC->room, CC->room.QRname) == 0) {
+ long old_msgnum = CC->room.msgnum_pic;
+ syslog(LOG_DEBUG, "Message %ld is now the photo for %s", new_msgnum, CC->room.QRname);
+ CC->room.msgnum_pic = new_msgnum;
+ CtdlPutRoomLock(&CC->room);
+ if (old_msgnum > 0) {
+ syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, SYSCONFIGROOM);
+ CtdlDeleteMessages(SYSCONFIGROOM, &old_msgnum, 1, "");
+ }
+ }
+ free(encoded_data);
+ }
+
+ free(unencoded_data);
+}
+
+
+/*
+ * DownLoad User Image (see their avatar or photo or whatever)
+ * If this command succeeds, it follows the same protocol as the DLAT command.
+ */
+void cmd_dlui(char *cmdbuf) {
+ struct ctdluser ruser;
+ char buf[SIZ];
+
+ if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+ extract_token(buf, cmdbuf, 0, '|', sizeof buf);
+ if (CtdlGetUser(&ruser, buf) != 0) {
+ cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
+ return;
+ }
+ if (ruser.msgnum_pic < 1) {
+ cprintf("%d No image found.\n", ERROR + FILE_NOT_FOUND);
+ return;
+ }
+
+ struct CtdlMessage *msg = CtdlFetchMessage(ruser.msgnum_pic, 1);
+ if (msg != NULL) {
+ // The call to CtdlOutputPreLoadedMsg() with MT_SPEW_SECTION will cause the DLUI command
+ // to have the same output format as the DLAT command, because it calls the same code.
+ // For example: 600 402132|-1||image/gif|
+ safestrncpy(CC->download_desired_section, "1", sizeof CC->download_desired_section);
+ CtdlOutputPreLoadedMsg(msg, MT_SPEW_SECTION, HEADERS_NONE, 1, 0, 0);
+ CM_Free(msg);
+ }
+ else {
+ cprintf("%d No image found.\n", ERROR + MESSAGE_NOT_FOUND);
+ return;
+ }
+}
+
+
+/*
+ * UpLoad User Image (avatar or photo or whatever)
+ */
+void cmd_ului(char *cmdbuf) {
+ long data_length;
+ char mimetype[SIZ];
+ char username[USERNAME_SIZE];
+ char userconfigroomname[ROOMNAMELEN];
+
+ if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+ if (num_parms(cmdbuf) < 2) {
+ cprintf("%d Usage error\n", ERROR + ILLEGAL_VALUE);
+ return;
+ }
+
+ data_length = extract_long(cmdbuf, 0);
+ extract_token(mimetype, cmdbuf, 1, '|', sizeof mimetype);
+ extract_token(username, cmdbuf, 2, '|', sizeof username);
+
+ if (data_length < 20) {
+ cprintf("%d That's an awfully small file. Try again.\n", ERROR + ILLEGAL_VALUE);
+ return;
+ }
+
+ if (strncasecmp(mimetype, "image/", 6)) {
+ cprintf("%d Only image files are permitted.\n", ERROR + ILLEGAL_VALUE);
+ return;
+ }
+
+ if (IsEmptyStr(username)) {
+ safestrncpy(username, CC->curr_user, sizeof username);
+ }
+
+ // Normal users can only change their own photo
+ if ( (strcasecmp(username, CC->curr_user)) && (CC->user.axlevel < AxAideU) && (!CC->internal_pgm) ) {
+ cprintf("%d Higher access required to change another user's photo.\n", ERROR + HIGHER_ACCESS_REQUIRED);
+ }
+
+ // Check to make sure the user exists
+ struct ctdluser usbuf;
+ if (CtdlGetUser(&usbuf, username) != 0) { // check for existing user, don't lock it yet
+ cprintf("%d %s not found.\n", ERROR + NO_SUCH_USER , username);
+ return;
+ }
+ CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &usbuf, USERCONFIGROOM);
+
+ char *unencoded_data = malloc(data_length + 1);
+ if (!unencoded_data) {
+ cprintf("%d Could not allocate %ld bytes of memory\n", ERROR + INTERNAL_ERROR , data_length);
+ return;
+ }
+
+ cprintf("%d %ld\n", SEND_BINARY, data_length);
+ client_read(unencoded_data, data_length);
+
+ // We've got the data read from the client, now save it.
+ char *encoded_data = malloc((data_length * 2) + 100);
+ if (encoded_data) {
+ sprintf(encoded_data, "Content-type: %s\nContent-transfer-encoding: base64\n\n", mimetype);
+ CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
+ long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, encoded_data, FMT_RFC822, "Photo uploaded by user");
+
+ if (CtdlGetUserLock(&usbuf, username) == 0) { // lock it this time
+ long old_msgnum = usbuf.msgnum_pic;
+ syslog(LOG_DEBUG, "Message %ld is now the photo for %s", new_msgnum, username);
+ usbuf.msgnum_pic = new_msgnum;
+ CtdlPutUserLock(&usbuf);
+ if (old_msgnum > 0) {
+ syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
+ CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
+ }
+ }
+
+ free(encoded_data);
+ }
+
+ free(unencoded_data);
+}
+
+
+/*
+ * Import function called by import_old_userpic_files() for a single user
+ */
+void import_one_userpic_file(char *username, long usernum, char *path) {
+ syslog(LOG_DEBUG, "Import legacy userpic for %s, usernum=%ld, filename=%s", username, usernum, path);
+
+ FILE *fp = fopen(path, "r");
+ if (!fp) return;
+
+ fseek(fp, 0, SEEK_END);
+ long data_length = ftell(fp);
+
+ if (data_length >= 1) {
+ rewind(fp);
+ char *unencoded_data = malloc(data_length);
+ if (unencoded_data) {
+ fread(unencoded_data, data_length, 1, fp);
+ char *encoded_data = malloc((data_length * 2) + 100);
+ if (encoded_data) {
+ sprintf(encoded_data, "Content-type: %s\nContent-transfer-encoding: base64\n\n", GuessMimeByFilename(path, strlen(path)));
+ CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
+
+ char userconfigroomname[ROOMNAMELEN];
+ struct ctdluser usbuf;
+
+ if (CtdlGetUser(&usbuf, username) == 0) { // no need to lock it , we are still initializing
+ long old_msgnum = usbuf.msgnum_pic;
+ CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &usbuf, USERCONFIGROOM);
+ long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, encoded_data, FMT_RFC822, "Photo imported from file");
+ syslog(LOG_DEBUG, "Message %ld is now the photo for %s", new_msgnum, username);
+ usbuf.msgnum_pic = new_msgnum;
+ CtdlPutUser(&usbuf);
+ unlink(path); // delete the old file , it's in the database now
+ if (old_msgnum > 0) {
+ syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
+ CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
+ }
+ }
+ free(encoded_data);
+ }
+ free(unencoded_data);
+ }
+ }
+ fclose(fp);
+}
+
+
+/*
+ * Look for old-format "userpic" files and import them into the message base
+ */
+void import_old_userpic_files(void) {
+ DIR *filedir = NULL;
+ struct dirent *filedir_entry;
+ size_t d_namelen;
+ struct ctdluser usbuf;
+ long usernum = 0;
+ int d_type = 0;
+ struct stat s;
+ char path[PATH_MAX];
+
+
+ syslog(LOG_DEBUG, "Importing old style userpic files into the message base");
+ filedir = opendir (ctdl_usrpic_dir);
+ if (filedir == NULL) {
+ return;
+ }
+ while ( (filedir_entry = readdir(filedir)) , (filedir_entry != NULL))
+ {
+#ifdef _DIRENT_HAVE_D_NAMLEN
+ d_namelen = filedir_entry->d_namlen;
+
+#else
+ d_namelen = strlen(filedir_entry->d_name);
+#endif
+
+#ifdef _DIRENT_HAVE_D_TYPE
+ d_type = filedir_entry->d_type;
+#else
+
+#ifndef DT_UNKNOWN
+#define DT_UNKNOWN 0
+#define DT_DIR 4
+#define DT_REG 8
+#define DT_LNK 10
+
+#define IFTODT(mode) (((mode) & 0170000) >> 12)
+#define DTTOIF(dirtype) ((dirtype) << 12)
+#endif
+ d_type = DT_UNKNOWN;
+#endif
+ if ((d_namelen == 1) &&
+ (filedir_entry->d_name[0] == '.'))
+ continue;
+
+ if ((d_namelen == 2) &&
+ (filedir_entry->d_name[0] == '.') &&
+ (filedir_entry->d_name[1] == '.'))
+ continue;
+
+ snprintf(path, PATH_MAX, "%s/%s", ctdl_usrpic_dir, filedir_entry->d_name);
+ if (d_type == DT_UNKNOWN) {
+ if (lstat(path, &s) == 0) {
+ d_type = IFTODT(s.st_mode);
+ }
+ }
+ switch (d_type)
+ {
+ case DT_DIR:
+ break;
+ case DT_LNK:
+ case DT_REG:
+ usernum = atol(filedir_entry->d_name);
+ if (CtdlGetUserByNumber(&usbuf, usernum) == 0) {
+ import_one_userpic_file(usbuf.fullname, usernum, path);
+ }
+ }
+ }
+ closedir(filedir);
+ rmdir(ctdl_usrpic_dir);
+}
+
+
+
+char *ctdl_module_init_image(void) {
+ if (!threading) {
+ import_old_userpic_files();
+ CtdlRegisterProtoHook(cmd_dlri, "DLRI", "DownLoad Room Image");
+ CtdlRegisterProtoHook(cmd_ulri, "ULRI", "UpLoad Room Image");
+ CtdlRegisterProtoHook(cmd_dlui, "DLUI", "DownLoad User Image");
+ CtdlRegisterProtoHook(cmd_ului, "ULUI", "UpLoad User Image");
+ }
+ /* return our module name for the log */
+ return "image";
+}
--- /dev/null
+/*
+ * Functions which implement RFC2086 (and maybe RFC4314) (IMAP ACL extension)
+ *
+ * Copyright (c) 2007-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 as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../sysdep_decls.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "serv_imap.h"
+#include "imap_tools.h"
+#include "imap_fetch.h"
+#include "imap_misc.h"
+#include "../../genstamp.h"
+#include "../../ctdl_module.h"
+
+
+/*
+ * Implements the SETACL command.
+ */
+void imap_setacl(int num_parms, ConstStr *Params) {
+
+ IReply("BAD not yet implemented FIXME");
+ return;
+}
+
+
+/*
+ * Implements the DELETEACL command.
+ */
+void imap_deleteacl(int num_parms, ConstStr *Params) {
+
+ IReply("BAD not yet implemented FIXME");
+ return;
+}
+
+
+/*
+ * Given the bits returned by CtdlRoomAccess(), populate a string buffer
+ * with IMAP ACL format flags. This code is common to GETACL and MYRIGHTS.
+ */
+void imap_acl_flags(StrBuf *rights, int ra)
+{
+ FlushStrBuf(rights);
+
+ /* l - lookup (mailbox is visible to LIST/LSUB commands)
+ * r - read (SELECT the mailbox, perform STATUS et al)
+ * s - keep seen/unseen information across sessions (STORE SEEN flag)
+ */
+ if ( (ra & UA_KNOWN) /* known rooms */
+ || ((ra & UA_GOTOALLOWED) && (ra & UA_ZAPPED)) /* zapped rooms */
+ ) {
+ StrBufAppendBufPlain(rights, HKEY("l"), 0);
+ StrBufAppendBufPlain(rights, HKEY("r"), 0);
+ StrBufAppendBufPlain(rights, HKEY("s"), 0);
+
+ /* Only output the remaining flags if the room is known */
+
+ /* w - write (set or clear flags other than SEEN or DELETED, not supported in Citadel */
+
+ /* i - insert (perform APPEND, COPY into mailbox) */
+ /* p - post (send mail to submission address for mailbox - not enforced) */
+ /* c - create (CREATE new sub-mailboxes) */
+ if (ra & UA_POSTALLOWED) {
+ StrBufAppendBufPlain(rights, HKEY("i"), 0);
+ StrBufAppendBufPlain(rights, HKEY("p"), 0);
+ StrBufAppendBufPlain(rights, HKEY("c"), 0);
+ }
+
+ /* d - delete messages (STORE DELETED flag, perform EXPUNGE) */
+ if (ra & UA_DELETEALLOWED) {
+ StrBufAppendBufPlain(rights, HKEY("d"), 0);
+ }
+
+ /* a - administer (perform SETACL/DELETEACL/GETACL/LISTRIGHTS) */
+ if (ra & UA_ADMINALLOWED) {
+ /*
+ * This is the correct place to put the "a" flag. We are leaving
+ * it commented out for now, because it implies that we could
+ * perform any of SETACL/DELETEACL/GETACL/LISTRIGHTS. Since these
+ * commands are not yet implemented, omitting the flag should
+ * theoretically prevent compliant clients from attempting to
+ * perform them.
+ *
+ * StrBufAppendBufPlain(rights, HKEY("a"), 0);
+ */
+ }
+ }
+}
+
+
+/*
+ * Implements the GETACL command.
+ */
+void imap_getacl(int num_parms, ConstStr *Params) {
+ char roomname[ROOMNAMELEN];
+ char savedroom[ROOMNAMELEN];
+ int msgs, new;
+ int ret;
+ struct ctdluser temp;
+ struct cdbdata *cdbus;
+ int ra;
+ StrBuf *rights;
+
+ if (num_parms != 3) {
+ IReply("BAD usage error");
+ return;
+ }
+
+ /*
+ * Search for the specified room or folder
+ */
+ ret = imap_grabroom(roomname, Params[2].Key, 1);
+ if (ret != 0) {
+ IReply("NO Invalid mailbox name or access denied");
+ return;
+ }
+
+ /*
+ * CtdlUserGoto() formally takes us to the desired room. (If another
+ * folder is selected, save its name so we can return there!!!!!)
+ */
+ if (IMAP->selected) {
+ strcpy(savedroom, CC->room.QRname);
+ }
+ CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
+
+ IAPuts("* ACL ");
+ IPutCParamStr(2);
+
+ /*
+ * Traverse the userlist
+ */
+ rights = NewStrBuf();
+ cdb_rewind(CDB_USERS);
+ while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL)
+ {
+ memset(&temp, 0, sizeof temp);
+ memcpy(&temp, cdbus->ptr, sizeof temp);
+ cdb_free(cdbus);
+
+ CtdlRoomAccess(&CC->room, &temp, &ra, NULL);
+ if (!IsEmptyStr(temp.fullname)) {
+ imap_acl_flags(rights, ra);
+ if (StrLength(rights) > 0) {
+ IAPuts(" ");
+ IPutStr(temp.fullname, strlen(temp.fullname));
+ IAPuts(" ");
+ iaputs(SKEY( rights));
+ }
+ }
+ }
+ FreeStrBuf(&rights);
+ IAPuts("\r\n");
+
+ /*
+ * If another folder is selected, go back to that room so we can resume
+ * our happy day without violent explosions.
+ */
+ if (IMAP->selected) {
+ CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
+ }
+
+ IReply("OK GETACL completed");
+}
+
+
+/*
+ * Implements the LISTRIGHTS command.
+ */
+void imap_listrights(int num_parms, ConstStr *Params) {
+ char roomname[ROOMNAMELEN];
+ char savedroom[ROOMNAMELEN];
+ int msgs, new;
+ int ret;
+ struct recptypes *valid;
+ struct ctdluser temp;
+
+ if (num_parms != 4) {
+ IReply("BAD usage error");
+ return;
+ }
+
+ /*
+ * Search for the specified room/folder
+ */
+ ret = imap_grabroom(roomname, Params[2].Key, 1);
+ if (ret != 0) {
+ IReply("NO Invalid mailbox name or access denied");
+ return;
+ }
+
+ /*
+ * Search for the specified user
+ */
+ ret = (-1);
+ valid = validate_recipients(Params[3].Key, NULL, 0);
+ if (valid != NULL) {
+ if (valid->num_local == 1) {
+ ret = CtdlGetUser(&temp, valid->recp_local);
+ }
+ free_recipients(valid);
+ }
+ if (ret != 0) {
+ IReply("NO Invalid user name or access denied");
+ return;
+ }
+
+ /*
+ * CtdlUserGoto() formally takes us to the desired room. (If another
+ * folder is selected, save its name so we can return there!!!!!)
+ */
+ if (IMAP->selected) {
+ strcpy(savedroom, CC->room.QRname);
+ }
+ CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
+
+ /*
+ * Now output the list of rights
+ */
+ IAPuts("* LISTRIGHTS ");
+ IPutCParamStr(2);
+ IAPuts(" ");
+ IPutCParamStr(3);
+ IAPuts(" ");
+ IPutStr(HKEY("")); /* FIXME ... do something here */
+ IAPuts("\r\n");
+
+ /*
+ * If another folder is selected, go back to that room so we can resume
+ * our happy day without violent explosions.
+ */
+ if (IMAP->selected) {
+ CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
+ }
+
+ IReply("OK LISTRIGHTS completed");
+ return;
+}
+
+
+/*
+ * Implements the MYRIGHTS command.
+ */
+void imap_myrights(int num_parms, ConstStr *Params) {
+ char roomname[ROOMNAMELEN];
+ char savedroom[ROOMNAMELEN];
+ int msgs, new;
+ int ret;
+ int ra;
+ StrBuf *rights;
+
+ if (num_parms != 3) {
+ IReply("BAD usage error");
+ return;
+ }
+
+ ret = imap_grabroom(roomname, Params[2].Key, 1);
+ if (ret != 0) {
+ IReply("NO Invalid mailbox name or access denied");
+ return;
+ }
+
+ /*
+ * CtdlUserGoto() formally takes us to the desired room. (If another
+ * folder is selected, save its name so we can return there!!!!!)
+ */
+ if (IMAP->selected) {
+ strcpy(savedroom, CC->room.QRname);
+ }
+ CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
+
+ CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
+ rights = NewStrBuf();
+ imap_acl_flags(rights, ra);
+
+ IAPuts("* MYRIGHTS ");
+ IPutCParamStr(2);
+ IAPuts(" ");
+ IPutStr(SKEY(rights));
+ IAPuts("\r\n");
+
+ FreeStrBuf(&rights);
+
+ /*
+ * If a different folder was previously selected, return there now.
+ */
+ if ( (IMAP->selected) && (strcasecmp(roomname, savedroom)) ) {
+ CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
+ }
+
+ IReply("OK MYRIGHTS completed");
+ return;
+}
--- /dev/null
+/*
+ * Copyright (c) 2007-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.
+ */
+
+void imap_setacl(int num_parms, ConstStr *Params);
+void imap_deleteacl(int num_parms, ConstStr *Params);
+void imap_getacl(int num_parms, ConstStr *Params);
+void imap_listrights(int num_parms, ConstStr *Params);
+void imap_myrights(int num_parms, ConstStr *Params);
+
--- /dev/null
+/*
+ * Implements the FETCH command in IMAP.
+ * This is a good example of the protocol's gratuitous complexity.
+ *
+ * Copyright (c) 2001-2020 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 as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ */
+
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../sysdep_decls.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "serv_imap.h"
+#include "imap_tools.h"
+#include "imap_fetch.h"
+#include "../../genstamp.h"
+#include "../../ctdl_module.h"
+
+
+
+/*
+ * Individual field functions for imap_do_fetch_msg() ...
+ */
+
+void imap_fetch_uid(int seq) {
+ IAPrintf("UID %ld", IMAP->msgids[seq-1]);
+}
+
+void imap_fetch_flags(int seq)
+{
+ citimap *Imap = IMAP;
+ int num_flags_printed = 0;
+ IAPuts("FLAGS (");
+ if (Imap->flags[seq] & IMAP_DELETED) {
+ if (num_flags_printed > 0)
+ IAPuts(" ");
+ IAPuts("\\Deleted");
+ ++num_flags_printed;
+ }
+ if (Imap->flags[seq] & IMAP_SEEN) {
+ if (num_flags_printed > 0)
+ IAPuts(" ");
+ IAPuts("\\Seen");
+ ++num_flags_printed;
+ }
+ if (Imap->flags[seq] & IMAP_ANSWERED) {
+ if (num_flags_printed > 0)
+ IAPuts(" ");
+ IAPuts("\\Answered");
+ ++num_flags_printed;
+ }
+ if (Imap->flags[seq] & IMAP_RECENT) {
+ if (num_flags_printed > 0)
+ IAPuts(" ");
+ IAPuts("\\Recent");
+ ++num_flags_printed;
+ }
+ IAPuts(")");
+}
+
+
+void imap_fetch_internaldate(struct CtdlMessage *msg) {
+ char datebuf[64];
+ time_t msgdate;
+
+ if (!msg) return;
+ if (!CM_IsEmpty(msg, eTimestamp)) {
+ msgdate = atol(msg->cm_fields[eTimestamp]);
+ }
+ else {
+ msgdate = time(NULL);
+ }
+
+ datestring(datebuf, sizeof datebuf, msgdate, DATESTRING_IMAP);
+ IAPrintf( "INTERNALDATE \"%s\"", datebuf);
+}
+
+
+/*
+ * Fetch RFC822-formatted messages.
+ *
+ * 'whichfmt' should be set to one of:
+ * "RFC822" entire message
+ * "RFC822.HEADER" headers only (with trailing blank line)
+ * "RFC822.SIZE" size of translated message
+ * "RFC822.TEXT" body only (without leading blank line)
+ */
+void imap_fetch_rfc822(long msgnum, const char *whichfmt) {
+ CitContext *CCC = CC;
+ citimap *Imap = CCCIMAP;
+ const char *ptr = NULL;
+ size_t headers_size, text_size, total_size;
+ size_t bytes_to_send = 0;
+ struct MetaData smi;
+ int need_to_rewrite_metadata = 0;
+ int need_body = 0;
+
+ /* Determine whether this particular fetch operation requires
+ * us to fetch the message body from disk. If not, we can save
+ * on some disk operations...
+ */
+ if ( (!strcasecmp(whichfmt, "RFC822")) || (!strcasecmp(whichfmt, "RFC822.TEXT")) ) {
+ need_body = 1;
+ }
+
+ /* If this is an RFC822.SIZE fetch, first look in the message's
+ * metadata record to see if we've saved that information.
+ */
+ if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
+ GetMetaData(&smi, msgnum);
+ if (smi.meta_rfc822_length > 0L) {
+ IAPrintf("RFC822.SIZE %ld", smi.meta_rfc822_length);
+ return;
+ }
+ need_to_rewrite_metadata = 1;
+ need_body = 1;
+ }
+
+ /* Cache the most recent RFC822 FETCH because some clients like to
+ * fetch in pieces, and we don't want to have to go back to the
+ * message store for each piece. We also burn the cache if the
+ * client requests something that involves reading the message
+ * body, but we haven't fetched the body yet.
+ */
+ if ((Imap->cached_rfc822 != NULL)
+ && (Imap->cached_rfc822_msgnum == msgnum)
+ && (Imap->cached_rfc822_withbody || (!need_body)) ) {
+ /* Good to go! */
+ }
+ else if (Imap->cached_rfc822 != NULL) {
+ /* Some other message is cached -- free it */
+ FreeStrBuf(&Imap->cached_rfc822);
+ Imap->cached_rfc822_msgnum = (-1);
+ }
+
+ /* At this point, we now can fetch and convert the message iff it's not
+ * the one we had cached.
+ */
+ if (Imap->cached_rfc822 == NULL) {
+ /*
+ * Load the message into memory for translation & measurement
+ */
+ CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+ CtdlOutputMsg(msgnum, MT_RFC822,
+ (need_body ? HEADERS_ALL : HEADERS_FAST),
+ 0, 1, NULL, SUPPRESS_ENV_TO, NULL, NULL, NULL
+ );
+ if (!need_body) {
+ client_write(HKEY("\r\n")); // extra trailing newline -- to the redirect_buffer, *not* to the client
+ }
+ Imap->cached_rfc822 = CCC->redirect_buffer;
+ CCC->redirect_buffer = NULL;
+ Imap->cached_rfc822_msgnum = msgnum;
+ Imap->cached_rfc822_withbody = need_body;
+ if ( (need_to_rewrite_metadata) &&
+ (StrLength(Imap->cached_rfc822) > 0) ) {
+ smi.meta_rfc822_length = StrLength(Imap->cached_rfc822);
+ PutMetaData(&smi);
+ }
+ }
+
+ /*
+ * Now figure out where the headers/text break is. IMAP considers the
+ * intervening blank line to be part of the headers, not the text.
+ */
+ headers_size = 0;
+
+ if (need_body) {
+ StrBuf *Line = NewStrBuf();
+ ptr = NULL;
+ do {
+ StrBufSipLine(Line, Imap->cached_rfc822, &ptr);
+
+ if ((StrLength(Line) != 0) && (ptr != StrBufNOTNULL))
+ {
+ StrBufTrim(Line);
+ if ((StrLength(Line) != 0) &&
+ (ptr != StrBufNOTNULL) )
+ {
+ headers_size = ptr - ChrPtr(Imap->cached_rfc822);
+ }
+ }
+ } while ( (headers_size == 0) &&
+ (ptr != StrBufNOTNULL) );
+
+ total_size = StrLength(Imap->cached_rfc822);
+ text_size = total_size - headers_size;
+ FreeStrBuf(&Line);
+ }
+ else {
+ headers_size = total_size = StrLength(Imap->cached_rfc822);
+ text_size = 0;
+ }
+
+ syslog(LOG_DEBUG, "imap: RFC822 headers=" SIZE_T_FMT ", text=" SIZE_T_FMT ", total=" SIZE_T_FMT, headers_size, text_size, total_size);
+
+ if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
+ IAPrintf("RFC822.SIZE " SIZE_T_FMT, total_size);
+ return;
+ }
+
+ else if (!strcasecmp(whichfmt, "RFC822")) {
+ ptr = ChrPtr(Imap->cached_rfc822);
+ bytes_to_send = total_size;
+ }
+
+ else if (!strcasecmp(whichfmt, "RFC822.HEADER")) {
+ ptr = ChrPtr(Imap->cached_rfc822);
+ bytes_to_send = headers_size;
+ }
+
+ else if (!strcasecmp(whichfmt, "RFC822.TEXT")) {
+ ptr = &ChrPtr(Imap->cached_rfc822)[headers_size];
+ bytes_to_send = text_size;
+ }
+
+ IAPrintf("%s {" SIZE_T_FMT "}\r\n", whichfmt, bytes_to_send);
+ iaputs(ptr, bytes_to_send);
+}
+
+
+/*
+ * Load a specific part of a message into the temp file to be output to a
+ * client. FIXME we can handle parts like "2" and "2.1" and even "2.MIME"
+ * but we still can't handle "2.HEADER" (which might not be a problem).
+ *
+ * Note: mime_parser() was called with dont_decode set to 1, so we have the
+ * luxury of simply spewing without having to re-encode.
+ */
+void imap_load_part(char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+ char *cbid, void *cbuserdata)
+{
+ char mimebuf2[SIZ];
+ StrBuf *desired_section;
+
+ desired_section = (StrBuf *)cbuserdata;
+ syslog(LOG_DEBUG, "imap: imap_load_part() looking for %s, found %s", ChrPtr(desired_section), partnum);
+
+ if (!strcasecmp(partnum, ChrPtr(desired_section))) {
+ client_write(content, length);
+ }
+
+ snprintf(mimebuf2, sizeof mimebuf2, "%s.MIME", partnum);
+
+ if (!strcasecmp(ChrPtr(desired_section), mimebuf2)) {
+ client_write(HKEY("Content-type: "));
+ client_write(cbtype, strlen(cbtype));
+ if (!IsEmptyStr(cbcharset)) {
+ client_write(HKEY("; charset=\""));
+ client_write(cbcharset, strlen(cbcharset));
+ client_write(HKEY("\""));
+ }
+ if (!IsEmptyStr(name)) {
+ client_write(HKEY("; name=\""));
+ client_write(name, strlen(name));
+ client_write(HKEY("\""));
+ }
+ client_write(HKEY("\r\n"));
+ if (!IsEmptyStr(encoding)) {
+ client_write(HKEY("Content-Transfer-Encoding: "));
+ client_write(encoding, strlen(encoding));
+ client_write(HKEY("\r\n"));
+ }
+ if (!IsEmptyStr(encoding)) {
+ client_write(HKEY("Content-Disposition: "));
+ client_write(disp, strlen(disp));
+
+ if (!IsEmptyStr(filename)) {
+ client_write(HKEY("; filename=\""));
+ client_write(filename, strlen(filename));
+ client_write(HKEY("\""));
+ }
+ client_write(HKEY("\r\n"));
+ }
+ cprintf("Content-Length: %ld\r\n\r\n", (long)length);
+ }
+}
+
+
+/*
+ * Called by imap_fetch_envelope() to output the "From" field.
+ * This is in its own function because its logic is kind of complex. We
+ * really need to make this suck less.
+ */
+void imap_output_envelope_from(struct CtdlMessage *msg) {
+ char user[SIZ], node[SIZ], name[SIZ];
+
+ if (!msg) return;
+
+ /* For anonymous messages, it's so easy! */
+ if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) {
+ IAPuts("((\"----\" NIL \"x\" \"x.org\")) ");
+ return;
+ }
+ if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) {
+ IAPuts("((\"anonymous\" NIL \"x\" \"x.org\")) ");
+ return;
+ }
+
+ /* For everything else, we do stuff. */
+ IAPuts("(("); // open double-parens
+ IPutMsgField(eAuthor); // display name
+ IAPuts(" NIL "); // source route (not used)
+
+ if (!CM_IsEmpty(msg, erFc822Addr)) {
+ process_rfc822_addr(msg->cm_fields[erFc822Addr], user, node, name);
+ IPutStr(user, strlen(user)); /* mailbox name (user id) */
+ IAPuts(" ");
+ IPutStr(node, strlen(node)); /* host name */
+ }
+ else {
+ IPutMsgField(eAuthor); /* Make up a synthetic address */
+ IAPuts(" ");
+ IPutStr(CtdlGetConfigStr("c_fqdn"), strlen(CtdlGetConfigStr("c_fqdn")));
+ }
+
+ IAPuts(")) "); /* close double-parens */
+}
+
+
+/*
+ * Output an envelope address (or set of addresses) in the official,
+ * convoluted, braindead format. (Note that we can't use this for
+ * the "From" address because its data may come from a number of different
+ * fields. But we can use it for "To" and possibly others.
+ */
+void imap_output_envelope_addr(char *addr) {
+ char individual_addr[256];
+ int num_addrs;
+ int i;
+ char user[256];
+ char node[256];
+ char name[256];
+
+ if (addr == NULL) {
+ IAPuts("NIL ");
+ return;
+ }
+
+ if (IsEmptyStr(addr)) {
+ IAPuts("NIL ");
+ return;
+ }
+
+ IAPuts("(");
+
+ /* How many addresses are listed here? */
+ num_addrs = num_tokens(addr, ',');
+
+ /* Output them one by one. */
+ for (i=0; i<num_addrs; ++i) {
+ extract_token(individual_addr, addr, i, ',', sizeof individual_addr);
+ striplt(individual_addr);
+ process_rfc822_addr(individual_addr, user, node, name);
+ IAPuts("(");
+ IPutStr(name, strlen(name));
+ IAPuts(" NIL ");
+ IPutStr(user, strlen(user));
+ IAPuts(" ");
+ IPutStr(node, strlen(node));
+ IAPuts(")");
+ if (i < (num_addrs-1))
+ IAPuts(" ");
+ }
+
+ IAPuts(") ");
+}
+
+
+/*
+ * Implements the ENVELOPE fetch item
+ *
+ * Note that the imap_strout() function can cleverly output NULL fields as NIL,
+ * so we don't have to check for that condition like we do elsewhere.
+ */
+void imap_fetch_envelope(struct CtdlMessage *msg) {
+ char datestringbuf[SIZ];
+ time_t msgdate;
+ char *fieldptr = NULL;
+ long len;
+
+ if (!msg) return;
+
+ /* Parse the message date into an IMAP-format date string */
+ if (!CM_IsEmpty(msg, eTimestamp)) {
+ msgdate = atol(msg->cm_fields[eTimestamp]);
+ }
+ else {
+ msgdate = time(NULL);
+ }
+ len = datestring(datestringbuf, sizeof datestringbuf, msgdate, DATESTRING_IMAP);
+
+ /* Now start spewing data fields. The order is important, as it is
+ * defined by the protocol specification. Nonexistent fields must
+ * be output as NIL, existent fields must be quoted or literalled.
+ * The imap_strout() function conveniently does all this for us.
+ */
+ IAPuts("ENVELOPE (");
+
+ /* Date */
+ IPutStr(datestringbuf, len);
+ IAPuts(" ");
+
+ /* Subject */
+ IPutMsgField(eMsgSubject);
+ IAPuts(" ");
+
+ /* From */
+ imap_output_envelope_from(msg);
+
+ /* Sender (default to same as 'From' if not present) */
+ fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Sender");
+ if (fieldptr != NULL) {
+ imap_output_envelope_addr(fieldptr);
+ free(fieldptr);
+ }
+ else {
+ imap_output_envelope_from(msg);
+ }
+
+ /* Reply-to */
+ fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Reply-to");
+ if (fieldptr != NULL) {
+ imap_output_envelope_addr(fieldptr);
+ free(fieldptr);
+ }
+ else {
+ imap_output_envelope_from(msg);
+ }
+
+ /* To */
+ imap_output_envelope_addr(msg->cm_fields[eRecipient]);
+
+ /* Cc (we do it this way because there might be a legacy non-Citadel Cc: field present) */
+ fieldptr = msg->cm_fields[eCarbonCopY];
+ if (fieldptr != NULL) {
+ imap_output_envelope_addr(fieldptr);
+ }
+ else {
+ fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Cc");
+ imap_output_envelope_addr(fieldptr);
+ if (fieldptr != NULL) free(fieldptr);
+ }
+
+ /* Bcc */
+ fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Bcc");
+ imap_output_envelope_addr(fieldptr);
+ if (fieldptr != NULL) free(fieldptr);
+
+ /* In-reply-to */
+ fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "In-reply-to");
+ IPutStr(fieldptr, (fieldptr)?strlen(fieldptr):0);
+ IAPuts(" ");
+ if (fieldptr != NULL) free(fieldptr);
+
+ /* message ID */
+ len = msg->cm_lengths[emessageId];
+
+ if ((len == 0) || (
+ (msg->cm_fields[emessageId][0] == '<') &&
+ (msg->cm_fields[emessageId][len - 1] == '>'))
+ ) {
+ IPutMsgField(emessageId);
+ }
+ else
+ {
+ char *Buf = malloc(len + 3);
+ long pos = 0;
+
+ if (msg->cm_fields[emessageId][0] != '<')
+ {
+ Buf[pos] = '<';
+ pos ++;
+ }
+ memcpy(&Buf[pos], msg->cm_fields[emessageId], len);
+ pos += len;
+ if (msg->cm_fields[emessageId][len] != '>')
+ {
+ Buf[pos] = '>';
+ pos++;
+ }
+ Buf[pos] = '\0';
+ IPutStr(Buf, pos);
+ free(Buf);
+ }
+ IAPuts(")");
+}
+
+
+/*
+ * This function is called only when CC->redirect_buffer contains a set of
+ * RFC822 headers with no body attached. Its job is to strip that set of
+ * headers down to *only* the ones we're interested in.
+ */
+void imap_strip_headers(StrBuf *section) {
+ citimap_command Cmd;
+ StrBuf *which_fields = NULL;
+ int doing_headers = 0;
+ int headers_not = 0;
+ int num_parms = 0;
+ int i;
+ StrBuf *boiled_headers = NULL;
+ StrBuf *Line;
+ int ok = 0;
+ int done_headers = 0;
+ const char *Ptr = NULL;
+ CitContext *CCC = CC;
+
+ if (CCC->redirect_buffer == NULL) return;
+
+ which_fields = NewStrBufDup(section);
+
+ if (!strncasecmp(ChrPtr(which_fields), "HEADER.FIELDS", 13))
+ doing_headers = 1;
+ if (doing_headers &&
+ !strncasecmp(ChrPtr(which_fields), "HEADER.FIELDS.NOT", 17))
+ headers_not = 1;
+
+ for (i=0; i < StrLength(which_fields); ++i) {
+ if (ChrPtr(which_fields)[i]=='(')
+ StrBufReplaceToken(which_fields, i, 1, HKEY(""));
+ }
+ for (i=0; i < StrLength(which_fields); ++i) {
+ if (ChrPtr(which_fields)[i]==')') {
+ StrBufCutAt(which_fields, i, NULL);
+ break;
+ }
+ }
+ memset(&Cmd, 0, sizeof(citimap_command));
+ Cmd.CmdBuf = which_fields;
+ num_parms = imap_parameterize(&Cmd);
+
+ boiled_headers = NewStrBufPlain(NULL, StrLength(CCC->redirect_buffer));
+ Line = NewStrBufPlain(NULL, SIZ);
+ Ptr = NULL;
+ ok = 0;
+ do {
+ StrBufSipLine(Line, CCC->redirect_buffer, &Ptr);
+
+ if (!isspace(ChrPtr(Line)[0])) {
+
+ if (doing_headers == 0) ok = 1;
+ else {
+ /* we're supposed to print all headers that are not matching the filter list */
+ if (headers_not) for (i=0, ok = 1; (i < num_parms) && (ok == 1); ++i) {
+ if ( (!strncasecmp(ChrPtr(Line),
+ Cmd.Params[i].Key,
+ Cmd.Params[i].len)) &&
+ (ChrPtr(Line)[Cmd.Params[i].len]==':') ) {
+ ok = 0;
+ }
+ }
+ /* we're supposed to print all headers matching the filterlist */
+ else for (i=0, ok = 0; ((i < num_parms) && (ok == 0)); ++i) {
+ if ( (!strncasecmp(ChrPtr(Line),
+ Cmd.Params[i].Key,
+ Cmd.Params[i].len)) &&
+ (ChrPtr(Line)[Cmd.Params[i].len]==':') ) {
+ ok = 1;
+ }
+ }
+ }
+ }
+
+ if (ok) {
+ StrBufAppendBuf(boiled_headers, Line, 0);
+ StrBufAppendBufPlain(boiled_headers, HKEY("\r\n"), 0);
+ }
+
+ if ((Ptr == StrBufNOTNULL) ||
+ (StrLength(Line) == 0) ||
+ (ChrPtr(Line)[0]=='\r') ||
+ (ChrPtr(Line)[0]=='\n') ) done_headers = 1;
+ } while (!done_headers);
+
+ StrBufAppendBufPlain(boiled_headers, HKEY("\r\n"), 0);
+
+ /* Now save it back (it'll always be smaller) */
+ FreeStrBuf(&CCC->redirect_buffer);
+ CCC->redirect_buffer = boiled_headers;
+
+ free(Cmd.Params);
+ FreeStrBuf(&which_fields);
+ FreeStrBuf(&Line);
+}
+
+
+/*
+ * Implements the BODY and BODY.PEEK fetch items
+ */
+void imap_fetch_body(long msgnum, ConstStr item, int is_peek) {
+ struct CtdlMessage *msg = NULL;
+ StrBuf *section;
+ StrBuf *partial;
+ int is_partial = 0;
+ size_t pstart, pbytes;
+ int loading_body_now = 0;
+ int need_body = 1;
+ int burn_the_cache = 0;
+ CitContext *CCC = CC;
+ citimap *Imap = CCCIMAP;
+
+ /* extract section */
+ section = NewStrBufPlain(CKEY(item));
+
+ if (strchr(ChrPtr(section), '[') != NULL) {
+ StrBufStripAllBut(section, '[', ']');
+ }
+ syslog(LOG_DEBUG, "imap: selected section is [%s]", (StrLength(section) == 0) ? "(empty)" : ChrPtr(section));
+
+ /* Burn the cache if we don't have the same section of the
+ * same message again.
+ */
+ if (Imap->cached_body != NULL) {
+ if (Imap->cached_bodymsgnum != msgnum) {
+ burn_the_cache = 1;
+ }
+ else if ( (!Imap->cached_body_withbody) && (need_body) ) {
+ burn_the_cache = 1;
+ }
+ else if (strcasecmp(Imap->cached_bodypart, ChrPtr(section))) {
+ burn_the_cache = 1;
+ }
+ if (burn_the_cache) {
+ /* Yup, go ahead and burn the cache. */
+ free(Imap->cached_body);
+ Imap->cached_body_len = 0;
+ Imap->cached_body = NULL;
+ Imap->cached_bodymsgnum = (-1);
+ strcpy(Imap->cached_bodypart, "");
+ }
+ }
+
+ /* extract partial */
+ partial = NewStrBufPlain(CKEY(item));
+ if (strchr(ChrPtr(partial), '<') != NULL) {
+ StrBufStripAllBut(partial, '<', '>');
+ is_partial = 1;
+ }
+ if ( (is_partial == 1) && (StrLength(partial) > 0) ) {
+ syslog(LOG_DEBUG, "imap: selected partial is <%s>", ChrPtr(partial));
+ }
+
+ if (Imap->cached_body == NULL) {
+ CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+ loading_body_now = 1;
+ msg = CtdlFetchMessage(msgnum, (need_body ? 1 : 0));
+ }
+
+ /* Now figure out what the client wants, and get it */
+
+ if (!loading_body_now) {
+ /* What we want is already in memory */
+ }
+
+ else if ( (!strcmp(ChrPtr(section), "1")) && (msg->cm_format_type != 4) ) {
+ CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1, SUPPRESS_ENV_TO);
+ }
+
+ else if (StrLength(section) == 0) {
+ CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, SUPPRESS_ENV_TO);
+ }
+
+ /*
+ * If the client asked for just headers, or just particular header
+ * fields, strip it down.
+ */
+ else if (!strncasecmp(ChrPtr(section), "HEADER", 6)) {
+ /* This used to work with HEADERS_FAST, but then Apple got stupid with their
+ * IMAP library and this broke Mail.App and iPhone Mail, so we had to change it
+ * to HEADERS_ONLY so the trendy hipsters with their iPhones can read mail.
+ */
+ CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ONLY, 0, 1, SUPPRESS_ENV_TO);
+ imap_strip_headers(section);
+ }
+
+ /*
+ * Strip it down if the client asked for everything _except_ headers.
+ */
+ else if (!strncasecmp(ChrPtr(section), "TEXT", 4)) {
+ CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1, SUPPRESS_ENV_TO);
+ }
+
+ /*
+ * Anything else must be a part specifier.
+ * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
+ */
+ else {
+ mime_parser(CM_RANGE(msg, eMesageText),
+ *imap_load_part, NULL, NULL,
+ section,
+ 1
+ );
+ }
+
+ if (loading_body_now) {
+ Imap->cached_body_len = StrLength(CCC->redirect_buffer);
+ Imap->cached_body = SmashStrBuf(&CCC->redirect_buffer);
+ Imap->cached_bodymsgnum = msgnum;
+ Imap->cached_body_withbody = need_body;
+ strcpy(Imap->cached_bodypart, ChrPtr(section));
+ }
+
+ if (is_partial == 0) {
+ IAPuts("BODY[");
+ iaputs(SKEY(section));
+ IAPrintf("] {" SIZE_T_FMT "}\r\n", Imap->cached_body_len);
+ pstart = 0;
+ pbytes = Imap->cached_body_len;
+ }
+ else {
+ sscanf(ChrPtr(partial), SIZE_T_FMT "." SIZE_T_FMT, &pstart, &pbytes);
+ if (pbytes > (Imap->cached_body_len - pstart)) {
+ pbytes = Imap->cached_body_len - pstart;
+ }
+ IAPuts("BODY[");
+ iaputs(SKEY(section));
+ IAPrintf("]<" SIZE_T_FMT "> {" SIZE_T_FMT "}\r\n", pstart, pbytes);
+ }
+
+ FreeStrBuf(&partial);
+
+ /* Here we go -- output it */
+ iaputs(&Imap->cached_body[pstart], pbytes);
+
+ if (msg != NULL) {
+ CM_Free(msg);
+ }
+
+ /* Mark this message as "seen" *unless* this is a "peek" operation */
+ if (is_peek == 0) {
+ CtdlSetSeen(&msgnum, 1, 1, ctdlsetseen_seen, NULL, NULL);
+ }
+ FreeStrBuf(§ion);
+}
+
+/*
+ * Called immediately before outputting a multipart bodystructure
+ */
+void imap_fetch_bodystructure_pre(
+ char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+ char *cbid, void *cbuserdata
+ ) {
+
+ IAPuts("(");
+}
+
+
+
+/*
+ * Called immediately after outputting a multipart bodystructure
+ */
+void imap_fetch_bodystructure_post(
+ char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+ char *cbid, void *cbuserdata
+ ) {
+ long len;
+ char subtype[128];
+
+ IAPuts(" ");
+
+ /* disposition */
+ len = extract_token(subtype, cbtype, 1, '/', sizeof subtype);
+ IPutStr(subtype, len);
+
+ /* body language */
+ /* IAPuts(" NIL"); We thought we needed this at one point, but maybe we don't... */
+
+ IAPuts(")");
+}
+
+
+
+/*
+ * Output the info for a MIME part in the format required by BODYSTRUCTURE.
+ *
+ */
+void imap_fetch_bodystructure_part(
+ char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+ char *cbid, void *cbuserdata
+ ) {
+
+ int have_cbtype = 0;
+ int have_encoding = 0;
+ int lines = 0;
+ size_t i;
+ char cbmaintype[128];
+ char cbsubtype[128];
+ long cbmaintype_len;
+ long cbsubtype_len;
+
+ if (cbtype != NULL) if (!IsEmptyStr(cbtype)) have_cbtype = 1;
+ if (have_cbtype) {
+ cbmaintype_len = extract_token(cbmaintype, cbtype, 0, '/', sizeof cbmaintype);
+ cbsubtype_len = extract_token(cbsubtype, cbtype, 1, '/', sizeof cbsubtype);
+ }
+ else {
+ strcpy(cbmaintype, "TEXT");
+ cbmaintype_len = 4;
+ strcpy(cbsubtype, "PLAIN");
+ cbsubtype_len = 5;
+ }
+
+ IAPuts("(");
+ IPutStr(cbmaintype, cbmaintype_len); /* body type */
+ IAPuts(" ");
+ IPutStr(cbsubtype, cbsubtype_len); /* body subtype */
+ IAPuts(" ");
+
+ IAPuts("("); /* begin body parameter list */
+
+ /* "NAME" must appear as the first parameter. This is not required by IMAP,
+ * but the Asterisk voicemail application blindly assumes that NAME will be in
+ * the first position. If it isn't, it rejects the message.
+ */
+ if ((name != NULL) && (!IsEmptyStr(name))) {
+ IAPuts("\"NAME\" ");
+ IPutStr(name, strlen(name));
+ IAPuts(" ");
+ }
+
+ IAPuts("\"CHARSET\" ");
+ if ((cbcharset == NULL) || (cbcharset[0] == 0)){
+ IPutStr(HKEY("US-ASCII"));
+ }
+ else {
+ IPutStr(cbcharset, strlen(cbcharset));
+ }
+ IAPuts(") "); /* end body parameter list */
+
+ IAPuts("NIL "); /* Body ID */
+ IAPuts("NIL "); /* Body description */
+
+ if ((encoding != NULL) && (encoding[0] != 0)) have_encoding = 1;
+ if (have_encoding) {
+ IPutStr(encoding, strlen(encoding));
+ }
+ else {
+ IPutStr(HKEY("7BIT"));
+ }
+ IAPuts(" ");
+
+ /* The next field is the size of the part in bytes. */
+ IAPrintf("%ld ", (long)length); /* bytes */
+
+ /* The next field is the number of lines in the part, if and only
+ * if the part is TEXT. More gratuitous complexity.
+ */
+ if (!strcasecmp(cbmaintype, "TEXT")) {
+ if (length) for (i=0; i<length; ++i) {
+ if (((char *)content)[i] == '\n') ++lines;
+ }
+ IAPrintf("%d ", lines);
+ }
+
+ /* More gratuitous complexity */
+ if ((!strcasecmp(cbmaintype, "MESSAGE"))
+ && (!strcasecmp(cbsubtype, "RFC822"))) {
+ /* FIXME: message/rfc822 also needs to output the envelope structure,
+ * body structure, and line count of the encapsulated message. Fortunately
+ * there are not yet any clients depending on this, so we can get away
+ * with not implementing it for now.
+ */
+ }
+
+ /* MD5 value of body part; we can get away with NIL'ing this */
+ IAPuts("NIL ");
+
+ /* Disposition */
+ if ((disp == NULL) || IsEmptyStr(disp)) {
+ IAPuts("NIL");
+ }
+ else {
+ IAPuts("(");
+ IPutStr(disp, strlen(disp));
+ if ((filename != NULL) && (!IsEmptyStr(filename))) {
+ IAPuts(" (\"FILENAME\" ");
+ IPutStr(filename, strlen(filename));
+ IAPuts(")");
+ }
+ IAPuts(")");
+ }
+
+ /* Body language (not defined yet) */
+ IAPuts(" NIL)");
+}
+
+
+
+/*
+ * Spew the BODYSTRUCTURE data for a message.
+ *
+ */
+void imap_fetch_bodystructure (long msgnum, const char *item,
+ struct CtdlMessage *msg) {
+ const char *rfc822 = NULL;
+ const char *rfc822_body = NULL;
+ size_t rfc822_len;
+ size_t rfc822_headers_len;
+ size_t rfc822_body_len;
+ const char *ptr = NULL;
+ char *pch;
+ char buf[SIZ];
+ int lines = 0;
+
+ /* Handle NULL message gracefully */
+ if (msg == NULL) {
+ IAPuts("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
+ "(\"CHARSET\" \"US-ASCII\") NIL NIL "
+ "\"7BIT\" 0 0)");
+ return;
+ }
+
+ /* For non-RFC822 (ordinary Citadel) messages, this is short and
+ * sweet...
+ */
+ if (msg->cm_format_type != FMT_RFC822) {
+
+ /* *sigh* We have to RFC822-format the message just to be able
+ * to measure it. FIXME use smi cached fields if possible
+ */
+
+ CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+ CtdlOutputPreLoadedMsg(msg, MT_RFC822, 0, 0, 1, SUPPRESS_ENV_TO);
+ rfc822_len = StrLength(CC->redirect_buffer);
+ rfc822 = pch = SmashStrBuf(&CC->redirect_buffer);
+
+ ptr = rfc822;
+ do {
+ ptr = cmemreadline(ptr, buf, sizeof buf);
+ ++lines;
+ if ((IsEmptyStr(buf)) && (rfc822_body == NULL)) {
+ rfc822_body = ptr;
+ }
+ } while (*ptr != 0);
+
+ rfc822_headers_len = rfc822_body - rfc822;
+ rfc822_body_len = rfc822_len - rfc822_headers_len;
+ free(pch);
+
+ IAPuts("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
+ "(\"CHARSET\" \"US-ASCII\") NIL NIL "
+ "\"7BIT\" ");
+ IAPrintf(SIZE_T_FMT " %d)", rfc822_body_len, lines);
+
+ return;
+ }
+
+ /* For messages already stored in RFC822 format, we have to parse. */
+ IAPuts("BODYSTRUCTURE ");
+ mime_parser(CM_RANGE(msg, eMesageText),
+ *imap_fetch_bodystructure_part, /* part */
+ *imap_fetch_bodystructure_pre, /* pre-multi */
+ *imap_fetch_bodystructure_post, /* post-multi */
+ NULL,
+ 1); /* don't decode -- we want it as-is */
+}
+
+
+/*
+ * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
+ * individual message, once it has been selected for output.
+ */
+void imap_do_fetch_msg(int seq, citimap_command *Cmd) {
+ int i;
+ citimap *Imap = IMAP;
+ struct CtdlMessage *msg = NULL;
+ int body_loaded = 0;
+
+ /* Don't attempt to fetch bogus messages or UID's */
+ if (seq < 1) return;
+ if (Imap->msgids[seq-1] < 1L) return;
+
+ buffer_output();
+ IAPrintf("* %d FETCH (", seq);
+
+ for (i=0; i<Cmd->num_parms; ++i) {
+
+ /* Fetchable without going to the message store at all */
+ if (!strcasecmp(Cmd->Params[i].Key, "UID")) {
+ imap_fetch_uid(seq);
+ }
+ else if (!strcasecmp(Cmd->Params[i].Key, "FLAGS")) {
+ imap_fetch_flags(seq-1);
+ }
+
+ /* Potentially fetchable from cache, if the client requests
+ * stuff from the same message several times in a row.
+ */
+ else if (!strcasecmp(Cmd->Params[i].Key, "RFC822")) {
+ imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
+ }
+ else if (!strcasecmp(Cmd->Params[i].Key, "RFC822.HEADER")) {
+ imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
+ }
+ else if (!strcasecmp(Cmd->Params[i].Key, "RFC822.SIZE")) {
+ imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
+ }
+ else if (!strcasecmp(Cmd->Params[i].Key, "RFC822.TEXT")) {
+ imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
+ }
+
+ /* BODY fetches do their own fetching and caching too. */
+ else if (!strncasecmp(Cmd->Params[i].Key, "BODY[", 5)) {
+ imap_fetch_body(Imap->msgids[seq-1], Cmd->Params[i], 0);
+ }
+ else if (!strncasecmp(Cmd->Params[i].Key, "BODY.PEEK[", 10)) {
+ imap_fetch_body(Imap->msgids[seq-1], Cmd->Params[i], 1);
+ }
+
+ /* Otherwise, load the message into memory.
+ */
+ else if (!strcasecmp(Cmd->Params[i].Key, "BODYSTRUCTURE")) {
+ if ((msg != NULL) && (!body_loaded)) {
+ CM_Free(msg); /* need the whole thing */
+ msg = NULL;
+ }
+ if (msg == NULL) {
+ msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+ body_loaded = 1;
+ }
+ imap_fetch_bodystructure(Imap->msgids[seq-1],
+ Cmd->Params[i].Key, msg);
+ }
+ else if (!strcasecmp(Cmd->Params[i].Key, "ENVELOPE")) {
+ if (msg == NULL) {
+ msg = CtdlFetchMessage(Imap->msgids[seq-1], 0);
+ body_loaded = 0;
+ }
+ imap_fetch_envelope(msg);
+ }
+ else if (!strcasecmp(Cmd->Params[i].Key, "INTERNALDATE")) {
+ if (msg == NULL) {
+ msg = CtdlFetchMessage(Imap->msgids[seq-1], 0);
+ body_loaded = 0;
+ }
+ imap_fetch_internaldate(msg);
+ }
+
+ if (i != Cmd->num_parms-1) IAPuts(" ");
+ }
+
+ IAPuts(")\r\n");
+ unbuffer_output();
+ if (msg != NULL) {
+ CM_Free(msg);
+ }
+}
+
+
+
+/*
+ * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
+ * validated and boiled down the request a bit.
+ */
+void imap_do_fetch(citimap_command *Cmd) {
+ citimap *Imap = IMAP;
+ int i;
+#if 0
+/* debug output the parsed vector */
+ {
+ int i;
+ syslog(LOG_DEBUG, "imap: ----- %ld params", Cmd->num_parms);
+
+ for (i=0; i < Cmd->num_parms; i++) {
+ if (Cmd->Params[i].len != strlen(Cmd->Params[i].Key))
+ syslog(LOG_DEBUG, "imap: *********** %ld != %ld : %s",
+ Cmd->Params[i].len,
+ strlen(Cmd->Params[i].Key),
+ Cmd->Params[i].Key);
+ else
+ syslog(LOG_DEBUG, "imap: %ld : %s",
+ Cmd->Params[i].len,
+ Cmd->Params[i].Key);
+ }}
+
+#endif
+
+ if (Imap->num_msgs > 0) {
+ for (i = 0; i < Imap->num_msgs; ++i) {
+
+ /* Abort the fetch loop if the session breaks.
+ * This is important for users who keep mailboxes
+ * that are too big *and* are too impatient to
+ * let them finish loading. :)
+ */
+ if (CC->kill_me) return;
+
+ /* Get any message marked for fetch. */
+ if (Imap->flags[i] & IMAP_SELECTED) {
+ imap_do_fetch_msg(i+1, Cmd);
+ }
+ }
+ }
+}
+
+
+
+/*
+ * Back end for imap_handle_macros()
+ * Note that this function *only* looks at the beginning of the string. It
+ * is not a generic search-and-replace function.
+ */
+void imap_macro_replace(StrBuf *Buf, long where,
+ StrBuf *TmpBuf,
+ char *find, long findlen,
+ char *replace, long replacelen)
+{
+
+ if (StrLength(Buf) - where > findlen)
+ return;
+
+ if (!strncasecmp(ChrPtr(Buf) + where, find, findlen)) {
+ if (ChrPtr(Buf)[where + findlen] == ' ') {
+ StrBufPlain(TmpBuf, replace, replacelen);
+ StrBufAppendBufPlain(TmpBuf, HKEY(" "), 0);
+ StrBufReplaceToken(Buf, where, findlen,
+ SKEY(TmpBuf));
+ }
+ if (where + findlen == StrLength(Buf)) {
+ StrBufReplaceToken(Buf, where, findlen,
+ replace, replacelen);
+ }
+ }
+}
+
+
+
+/*
+ * Handle macros embedded in FETCH data items.
+ * (What the heck are macros doing in a wire protocol? Are we trying to save
+ * the computer at the other end the trouble of typing a lot of characters?)
+ */
+void imap_handle_macros(citimap_command *Cmd) {
+ long i;
+ int nest = 0;
+ StrBuf *Tmp = NewStrBuf();
+
+ for (i=0; i < StrLength(Cmd->CmdBuf); ++i) {
+ char ch = ChrPtr(Cmd->CmdBuf)[i];
+ if ((ch=='(') ||
+ (ch=='[') ||
+ (ch=='<') ||
+ (ch=='{')) ++nest;
+ else if ((ch==')') ||
+ (ch==']') ||
+ (ch=='>') ||
+ (ch=='}')) --nest;
+
+ if (nest <= 0) {
+ imap_macro_replace(Cmd->CmdBuf, i,
+ Tmp,
+ HKEY("ALL"),
+ HKEY("FLAGS INTERNALDATE RFC822.SIZE ENVELOPE")
+ );
+ imap_macro_replace(Cmd->CmdBuf, i,
+ Tmp,
+ HKEY("BODY"),
+ HKEY("BODYSTRUCTURE")
+ );
+ imap_macro_replace(Cmd->CmdBuf, i,
+ Tmp,
+ HKEY("FAST"),
+ HKEY("FLAGS INTERNALDATE RFC822.SIZE")
+ );
+ imap_macro_replace(Cmd->CmdBuf, i,
+ Tmp,
+ HKEY("FULL"),
+ HKEY("FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY")
+ );
+ }
+ }
+ FreeStrBuf(&Tmp);
+}
+
+
+/*
+ * Break out the data items requested, possibly a parenthesized list.
+ * Returns the number of data items, or -1 if the list is invalid.
+ * NOTE: this function alters the string it is fed, and uses it as a buffer
+ * to hold the data for the pointers it returns.
+ */
+int imap_extract_data_items(citimap_command *Cmd)
+{
+ int nArgs;
+ int nest = 0;
+ const char *pch, *end;
+
+ /* Convert all whitespace to ordinary space characters. */
+ pch = ChrPtr(Cmd->CmdBuf);
+ end = pch + StrLength(Cmd->CmdBuf);
+
+ while (pch < end)
+ {
+ if (isspace(*pch))
+ StrBufPeek(Cmd->CmdBuf, pch, 0, ' ');
+ pch++;
+ }
+
+ /* Strip leading and trailing whitespace, then strip leading and
+ * trailing parentheses if it's a list
+ */
+ StrBufTrim(Cmd->CmdBuf);
+ pch = ChrPtr(Cmd->CmdBuf);
+ if ( (pch[0]=='(') &&
+ (pch[StrLength(Cmd->CmdBuf)-1]==')') )
+ {
+ StrBufCutRight(Cmd->CmdBuf, 1);
+ StrBufCutLeft(Cmd->CmdBuf, 1);
+ StrBufTrim(Cmd->CmdBuf);
+ }
+
+ /* Parse any macro data items */
+ imap_handle_macros(Cmd);
+
+ /*
+ * Now break out the data items. We throw in one trailing space in
+ * order to avoid having to break out the last one manually.
+ */
+ nArgs = StrLength(Cmd->CmdBuf) / 10 + 10;
+ nArgs = CmdAdjust(Cmd, nArgs, 0);
+ Cmd->num_parms = 0;
+ Cmd->Params[Cmd->num_parms].Key = pch = ChrPtr(Cmd->CmdBuf);
+ end = Cmd->Params[Cmd->num_parms].Key + StrLength(Cmd->CmdBuf);
+
+ while (pch < end)
+ {
+ if ((*pch=='(') ||
+ (*pch=='[') ||
+ (*pch=='<') ||
+ (*pch=='{'))
+ ++nest;
+
+ else if ((*pch==')') ||
+ (*pch==']') ||
+ (*pch=='>') ||
+ (*pch=='}'))
+ --nest;
+
+ if ((nest <= 0) && (*pch==' ')) {
+ StrBufPeek(Cmd->CmdBuf, pch, 0, '\0');
+ Cmd->Params[Cmd->num_parms].len =
+ pch - Cmd->Params[Cmd->num_parms].Key;
+
+ if (Cmd->num_parms + 1 >= Cmd->avail_parms) {
+ nArgs = CmdAdjust(Cmd, nArgs * 2, 1);
+ }
+ Cmd->num_parms++;
+ Cmd->Params[Cmd->num_parms].Key = ++pch;
+ }
+ else if (pch + 1 == end) {
+ Cmd->Params[Cmd->num_parms].len =
+ pch - Cmd->Params[Cmd->num_parms].Key + 1;
+
+ Cmd->num_parms++;
+ }
+ pch ++;
+ }
+ return Cmd->num_parms;
+
+}
+
+
+/*
+ * One particularly hideous aspect of IMAP is that we have to allow the client
+ * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
+ * handles this by setting the IMAP_SELECTED flag for each message specified in
+ * the ranges/sets, then looping through the message array, outputting messages
+ * with the flag set. We don't bother returning an error if an out-of-range
+ * number is specified (we just return quietly) because any client braindead
+ * enough to request a bogus message number isn't going to notice the
+ * difference anyway.
+ *
+ * This function clears out the IMAP_SELECTED bits, then sets that bit for each
+ * message included in the specified range.
+ *
+ * Set is_uid to 1 to fetch by UID instead of sequence number.
+ */
+void imap_pick_range(const char *supplied_range, int is_uid) {
+ citimap *Imap = IMAP;
+ int i;
+ int num_sets;
+ int s;
+ char setstr[SIZ], lostr[SIZ], histr[SIZ];
+ long lo, hi;
+ char actual_range[SIZ];
+
+ /*
+ * Handle the "ALL" macro
+ */
+ if (!strcasecmp(supplied_range, "ALL")) {
+ safestrncpy(actual_range, "1:*", sizeof actual_range);
+ }
+ else {
+ safestrncpy(actual_range, supplied_range, sizeof actual_range);
+ }
+
+ /*
+ * Clear out the IMAP_SELECTED flags for all messages.
+ */
+ for (i = 0; i < Imap->num_msgs; ++i) {
+ Imap->flags[i] = Imap->flags[i] & ~IMAP_SELECTED;
+ }
+
+ /*
+ * Now set it for all specified messages.
+ */
+ num_sets = num_tokens(actual_range, ',');
+ for (s=0; s<num_sets; ++s) {
+ extract_token(setstr, actual_range, s, ',', sizeof setstr);
+
+ extract_token(lostr, setstr, 0, ':', sizeof lostr);
+ if (num_tokens(setstr, ':') >= 2) {
+ extract_token(histr, setstr, 1, ':', sizeof histr);
+ if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
+ }
+ else {
+ safestrncpy(histr, lostr, sizeof histr);
+ }
+ lo = atol(lostr);
+ hi = atol(histr);
+
+ /* Loop through the array, flipping bits where appropriate */
+ for (i = 1; i <= Imap->num_msgs; ++i) {
+ if (is_uid) { /* fetch by sequence number */
+ if ( (Imap->msgids[i-1]>=lo)
+ && (Imap->msgids[i-1]<=hi)) {
+ Imap->flags[i-1] |= IMAP_SELECTED;
+ }
+ }
+ else { /* fetch by uid */
+ if ( (i>=lo) && (i<=hi)) {
+ Imap->flags[i-1] |= IMAP_SELECTED;
+ }
+ }
+ }
+ }
+}
+
+
+
+/*
+ * This function is called by the main command loop.
+ */
+void imap_fetch(int num_parms, ConstStr *Params) {
+ citimap_command Cmd;
+ int num_items;
+
+ if (num_parms < 4) {
+ IReply("BAD invalid parameters");
+ return;
+ }
+
+ imap_pick_range(Params[2].Key, 0);
+
+ memset(&Cmd, 0, sizeof(citimap_command));
+ Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
+ MakeStringOf(Cmd.CmdBuf, 3);
+
+ num_items = imap_extract_data_items(&Cmd);
+ if (num_items < 1) {
+ IReply("BAD invalid data item list");
+ FreeStrBuf(&Cmd.CmdBuf);
+ free(Cmd.Params);
+ return;
+ }
+
+ imap_do_fetch(&Cmd);
+ IReply("OK FETCH completed");
+ FreeStrBuf(&Cmd.CmdBuf);
+ free(Cmd.Params);
+}
+
+/*
+ * This function is called by the main command loop.
+ */
+void imap_uidfetch(int num_parms, ConstStr *Params) {
+ citimap_command Cmd;
+ int num_items;
+ int i;
+ int have_uid_item = 0;
+
+ if (num_parms < 5) {
+ IReply("BAD invalid parameters");
+ return;
+ }
+
+ imap_pick_range(Params[3].Key, 1);
+
+ memset(&Cmd, 0, sizeof(citimap_command));
+ Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
+
+ MakeStringOf(Cmd.CmdBuf, 4);
+#if 0
+ syslog(LOG_DEBUG, "imap: -------%s--------", ChrPtr(Cmd.CmdBuf));
+#endif
+ num_items = imap_extract_data_items(&Cmd);
+ if (num_items < 1) {
+ IReply("BAD invalid data item list");
+ FreeStrBuf(&Cmd.CmdBuf);
+ free(Cmd.Params);
+ return;
+ }
+
+ /* If the "UID" item was not included, we include it implicitly
+ * (at the beginning) because this is a UID FETCH command
+ */
+ for (i=0; i<num_items; ++i) {
+ if (!strcasecmp(Cmd.Params[i].Key, "UID")) ++have_uid_item;
+ }
+ if (have_uid_item == 0) {
+ if (Cmd.num_parms + 1 >= Cmd.avail_parms)
+ CmdAdjust(&Cmd, Cmd.avail_parms + 1, 1);
+ memmove(&Cmd.Params[1],
+ &Cmd.Params[0],
+ sizeof(ConstStr) * Cmd.num_parms);
+
+ Cmd.num_parms++;
+ Cmd.Params[0] = (ConstStr){HKEY("UID")};
+ }
+
+ imap_do_fetch(&Cmd);
+ IReply("OK UID FETCH completed");
+ FreeStrBuf(&Cmd.CmdBuf);
+ free(Cmd.Params);
+}
+
+
--- /dev/null
+/*
+ * Copyright (c) 2001-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.
+ */
+
+void imap_pick_range(const char *range, int is_uid);
+void imap_fetch(int num_parms, ConstStr *Params);
+void imap_uidfetch(int num_parms, ConstStr *Params);
+void imap_fetch_flags(int seq);
+int imap_extract_data_items(citimap_command *Cmd);
--- /dev/null
+/*
+ * Implements the LIST and LSUB commands.
+ *
+ * Copyright (c) 2000-2017 by Art Cancro and others.
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../sysdep_decls.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "serv_imap.h"
+#include "imap_tools.h"
+#include "imap_fetch.h"
+#include "imap_search.h"
+#include "imap_store.h"
+#include "imap_acl.h"
+#include "imap_misc.h"
+#include "imap_list.h"
+#include "../../ctdl_module.h"
+
+
+typedef struct __ImapRoomListFilter {
+ char verb[16];
+ int subscribed_rooms_only;
+ int return_subscribed;
+ int return_children;
+
+ int num_patterns;
+ int num_patterns_avail;
+ StrBuf **patterns;
+}ImapRoomListFilter;
+
+/*
+ * Used by LIST and LSUB to show the floors in the listing
+ */
+void imap_list_floors(char *verb, int num_patterns, StrBuf **patterns)
+{
+ int i;
+ struct floor *fl;
+ int j = 0;
+ int match = 0;
+
+ for (i = 0; i < MAXFLOORS; ++i) {
+ fl = CtdlGetCachedFloor(i);
+ if (fl->f_flags & F_INUSE) {
+ match = 0;
+ for (j=0; j<num_patterns; ++j) {
+ if (imap_mailbox_matches_pattern (ChrPtr(patterns[j]), fl->f_name)) {
+ match = 1;
+ }
+ }
+ if (match) {
+ IAPrintf("* %s (\\NoSelect \\HasChildren) \"/\" ", verb);
+ IPutStr(fl->f_name, (fl->f_name)?strlen(fl->f_name):0);
+ IAPuts("\r\n");
+ }
+ }
+ }
+}
+
+
+/*
+ * Back end for imap_list()
+ *
+ * Implementation note: IMAP "subscribed folder" is equivalent to Citadel "known room"
+ *
+ * The "user data" field is actually an array of pointers; see below for the breakdown
+ *
+ */
+void imap_listroom(struct ctdlroom *qrbuf, void *data)
+{
+#define SUBSCRIBED_STR "\\Subscribed"
+#define HASCHILD_STR "\\HasChildren"
+ char MailboxName[SIZ];
+ char return_options[256];
+ int ra;
+ int yes_output_this_room;
+ ImapRoomListFilter *ImapFilter;
+ int i = 0;
+ int match = 0;
+ int ROLen;
+
+ /* Here's how we break down the array of pointers passed to us */
+ ImapFilter = (ImapRoomListFilter*)data;
+
+ /* Only list rooms to which the user has access!! */
+ yes_output_this_room = 0;
+ *return_options = '\0';
+ ROLen = 0;
+ CtdlRoomAccess(qrbuf, &CC->user, &ra, NULL);
+
+ if (ImapFilter->return_subscribed) {
+ if (ra & UA_KNOWN) {
+ memcpy(return_options, HKEY(SUBSCRIBED_STR) + 1);
+ ROLen += sizeof(SUBSCRIBED_STR) - 1;
+ }
+ }
+
+ /* Warning: ugly hack.
+ * We don't have any way to determine the presence of child mailboxes
+ * without refactoring this entire module. So we're just going to return
+ * the \HasChildren attribute for every room.
+ * We'll fix this later when we have time.
+ */
+ if (ImapFilter->return_children) {
+ if (!IsEmptyStr(return_options)) {
+ memcpy(return_options + ROLen, HKEY(" "));
+ ROLen ++;
+ }
+ memcpy(return_options + ROLen, HKEY(SUBSCRIBED_STR) + 1);
+ }
+
+ if (ImapFilter->subscribed_rooms_only) {
+ if (ra & UA_KNOWN) {
+ yes_output_this_room = 1;
+ }
+ }
+ else {
+ if ((ra & UA_KNOWN) || ((ra & UA_GOTOALLOWED) && (ra & UA_ZAPPED))) {
+ yes_output_this_room = 1;
+ }
+ }
+
+ if (yes_output_this_room) {
+ long len;
+ len = imap_mailboxname(MailboxName, sizeof MailboxName, qrbuf);
+ match = 0;
+ for (i=0; i<ImapFilter->num_patterns; ++i) {
+ if (imap_mailbox_matches_pattern(ChrPtr(ImapFilter->patterns[i]), MailboxName)) {
+ match = 1;
+ }
+ }
+ if (match) {
+ IAPrintf("* %s (%s) \"/\" ", ImapFilter->verb, return_options);
+ IPutStr(MailboxName, len);
+ IAPuts("\r\n");
+ }
+ }
+}
+
+
+/*
+ * Implements the LIST and LSUB commands
+ */
+void imap_list(int num_parms, ConstStr *Params)
+{
+ struct CitContext *CCC = CC;
+ citimap *Imap = CCCIMAP;
+ int i, j, paren_nest;
+ ImapRoomListFilter ImapFilter;
+ int selection_left = (-1);
+ int selection_right = (-1);
+ int return_left = (-1);
+ int root_pos = 2;
+ int patterns_left = 3;
+ int patterns_right = 3;
+ int extended_list_in_use = 0;
+
+ if (num_parms < 4) {
+ IReply("BAD arguments invalid");
+ return;
+ }
+
+ ImapFilter.num_patterns = 1;
+ ImapFilter.return_subscribed = 0;
+ ImapFilter.return_children = 0;
+ ImapFilter.subscribed_rooms_only = 0;
+
+
+ /* parms[1] is the IMAP verb being used (e.g. LIST or LSUB)
+ * This tells us how to behave, and what verb to return back to the caller
+ */
+ safestrncpy(ImapFilter.verb, Params[1].Key, sizeof ImapFilter.verb);
+ j = Params[1].len;
+ for (i=0; i<j; ++i) {
+ ImapFilter.verb[i] = toupper(ImapFilter.verb[i]);
+ }
+
+ if (!strcasecmp(ImapFilter.verb, "LSUB")) {
+ ImapFilter.subscribed_rooms_only = 1;
+ }
+
+ /*
+ * Partial implementation of LIST-EXTENDED (which will not get used because
+ * we don't advertise it in our capabilities string). Several requirements:
+ *
+ * Extraction of selection options:
+ * SUBSCRIBED option: done
+ * RECURSIVEMATCH option: not done yet
+ * REMOTE: safe to silently ignore
+ *
+ * Extraction of return options:
+ * SUBSCRIBED option: done
+ * CHILDREN option: done, but needs a non-ugly rewrite
+ *
+ * Multiple match patterns: done
+ */
+
+ /*
+ * If parameter 2 begins with a '(' character, the client is specifying
+ * selection options. Extract their exact position, and then modify our
+ * expectation of where the root folder will be specified.
+ */
+ if (Params[2].Key[0] == '(') {
+ extended_list_in_use = 1;
+ selection_left = 2;
+ paren_nest = 0;
+ for (i=2; i<num_parms; ++i) {
+ for (j=0; Params[i].Key[j]; ++j) {
+ if (Params[i].Key[j] == '(') ++paren_nest;
+ if (Params[i].Key[j] == ')') --paren_nest;
+ }
+ if (paren_nest == 0) {
+ selection_right = i; /* found end of selection options */
+ root_pos = i+1; /* folder root appears after selection options */
+ i = num_parms + 1; /* break out of the loop */
+ }
+ }
+ }
+
+ /* If selection options were found, do something with them.
+ */
+ if ((selection_left > 0) && (selection_right >= selection_left)) {
+
+ /* Strip off the outer parentheses */
+ if (Params[selection_left].Key[0] == '(') {
+ TokenCutLeft(&Imap->Cmd,
+ &Params[selection_left],
+ 1);
+ }
+ if (Params[selection_right].Key[Params[selection_right].len-1] == ')') {
+ TokenCutRight(&Imap->Cmd,
+ &Params[selection_right],
+ 1);
+ }
+
+ for (i=selection_left; i<=selection_right; ++i) {
+
+ if (!strcasecmp(Params[i].Key, "SUBSCRIBED")) {
+ ImapFilter.subscribed_rooms_only = 1;
+ }
+
+ else if (!strcasecmp(Params[i].Key, "RECURSIVEMATCH")) {
+ /* FIXME - do this! */
+ }
+
+ }
+
+ }
+
+ /* The folder root appears immediately after the selection options,
+ * or in position 2 if no selection options were specified.
+ */
+ ImapFilter.num_patterns_avail = num_parms + 1;
+ ImapFilter.patterns = malloc(ImapFilter.num_patterns_avail * sizeof(StrBuf*));
+ memset(ImapFilter.patterns, 0, ImapFilter.num_patterns_avail * sizeof(StrBuf*));
+
+ patterns_left = root_pos + 1;
+ patterns_right = root_pos + 1;
+
+ if (Params[patterns_left].Key[0] == '(') {
+ extended_list_in_use = 1;
+ paren_nest = 0;
+ for (i=patterns_left; i<num_parms; ++i) {
+ for (j=0; &Params[i].Key[j]; ++j) {
+ if (Params[i].Key[j] == '(') ++paren_nest;
+ if (Params[i].Key[j] == ')') --paren_nest;
+ }
+ if (paren_nest == 0) {
+ patterns_right = i; /* found end of patterns */
+ i = num_parms + 1; /* break out of the loop */
+ }
+ }
+ ImapFilter.num_patterns = patterns_right - patterns_left + 1;
+ for (i=0; i<ImapFilter.num_patterns; ++i) {
+ if (i < MAX_PATTERNS) {
+ ImapFilter.patterns[i] = NewStrBufPlain(NULL,
+ Params[root_pos].len +
+ Params[patterns_left+i].len);
+ if (i == 0) {
+ if (Params[root_pos].len > 1)
+ StrBufAppendBufPlain(ImapFilter.patterns[i],
+ 1 + CKEY(Params[root_pos]) - 1, 0);
+ }
+ else
+ StrBufAppendBufPlain(ImapFilter.patterns[i],
+ CKEY(Params[root_pos]), 0);
+
+ if (i == ImapFilter.num_patterns-1) {
+ if (Params[patterns_left+i].len > 1)
+ StrBufAppendBufPlain(ImapFilter.patterns[i],
+ CKEY(Params[patterns_left+i]) - 1, 0);
+ }
+ else StrBufAppendBufPlain(ImapFilter.patterns[i],
+ CKEY(Params[patterns_left+i]), 0);
+
+ }
+
+ }
+ }
+ else {
+ ImapFilter.num_patterns = 1;
+ ImapFilter.patterns[0] = NewStrBufPlain(NULL,
+ Params[root_pos].len +
+ Params[patterns_left].len);
+ StrBufAppendBufPlain(ImapFilter.patterns[0],
+ CKEY(Params[root_pos]), 0);
+ StrBufAppendBufPlain(ImapFilter.patterns[0],
+ CKEY(Params[patterns_left]), 0);
+ }
+
+ /* If the word "RETURN" appears after the folder pattern list, then the client
+ * is specifying return options.
+ */
+ if (num_parms - patterns_right > 2) if (!strcasecmp(Params[patterns_right+1].Key, "RETURN")) {
+ return_left = patterns_right + 2;
+ extended_list_in_use = 1;
+ paren_nest = 0;
+ for (i=return_left; i<num_parms; ++i) {
+ for (j=0; Params[i].Key[j]; ++j) {
+ if (Params[i].Key[j] == '(') ++paren_nest;
+ if (Params[i].Key[j] == ')') --paren_nest;
+ }
+
+ /* Might as well look for these while we're in here... */
+ if (Params[i].Key[0] == '(')
+ TokenCutLeft(&Imap->Cmd,
+ &Params[i],
+ 1);
+ if (Params[i].Key[Params[i].len-1] == ')')
+ TokenCutRight(&Imap->Cmd,
+ &Params[i],
+ 1);
+
+ syslog(LOG_DEBUG, "evaluating <%s>", Params[i].Key);
+
+ if (!strcasecmp(Params[i].Key, "SUBSCRIBED")) {
+ ImapFilter.return_subscribed = 1;
+ }
+
+ else if (!strcasecmp(Params[i].Key, "CHILDREN")) {
+ ImapFilter.return_children = 1;
+ }
+
+ if (paren_nest == 0) {
+ i = num_parms + 1; /* break out of the loop */
+ }
+ }
+ }
+
+ /* Now start setting up the data we're going to send to the CtdlForEachRoom() callback.
+ */
+
+ /* The non-extended LIST command is required to treat an empty
+ * ("" string) mailbox name argument as a special request to return the
+ * hierarchy delimiter and the root name of the name given in the
+ * reference parameter.
+ */
+ if ( (StrLength(ImapFilter.patterns[0]) == 0) && (extended_list_in_use == 0) ) {
+ IAPrintf("* %s (\\Noselect) \"/\" \"\"\r\n", ImapFilter.verb);
+ }
+
+ /* Non-empty mailbox names, and any form of the extended LIST command,
+ * is handled by this loop.
+ */
+ else {
+ imap_list_floors(ImapFilter.verb,
+ ImapFilter.num_patterns,
+ ImapFilter.patterns);
+ CtdlForEachRoom(imap_listroom, (char**)&ImapFilter);
+ }
+
+ /*
+ * Free the pattern buffers we allocated above.
+ */
+ for (i=0; i<ImapFilter.num_patterns; ++i) {
+ FreeStrBuf(&ImapFilter.patterns[i]);
+ }
+ free(ImapFilter.patterns);
+
+ IReplyPrintf("OK %s completed", ImapFilter.verb);
+}
--- /dev/null
+
+/*
+ * In the extended form of LIST the client is allowed to specify
+ * multiple match patterns. How many will we allow?
+ */
+#define MAX_PATTERNS 20
+
+void imap_list(int num_parms, ConstStr *Params);
--- /dev/null
+/*
+ * IMAP METADATA extension
+ *
+ * This is an implementation of the Bynari variant of the METADATA extension.
+ *
+ * Copyright (c) 2007-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 as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../sysdep_decls.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "serv_imap.h"
+#include "imap_tools.h"
+#include "imap_fetch.h"
+#include "imap_misc.h"
+#include "../../genstamp.h"
+#include "../../ctdl_module.h"
+
+/*
+ * Implements the SETMETADATA command.
+ *
+ * Again, the only thing we're interested in setting here is the folder type.
+ *
+ * Attempting to set anything else calls a stub which fools the client into
+ * thinking that there is no remaining space available to store annotations.
+ */
+void imap_setmetadata(int num_parms, ConstStr *Params) {
+ char roomname[ROOMNAMELEN];
+ char savedroom[ROOMNAMELEN];
+ int msgs, new;
+ int ret;
+ int setting_user_value = 0;
+ char set_value[32];
+ int set_view = VIEW_BBS;
+ visit vbuf;
+
+ if (num_parms != 6) {
+ IReply("BAD usage error");
+ return;
+ }
+
+ /*
+ * Don't allow other types of metadata to be set
+ */
+ if (strcasecmp(Params[3].Key, "/vendor/kolab/folder-type")) {
+ IReply("NO [METADATA TOOMANY] SETMETADATA failed");
+ return;
+ }
+
+ if (!strcasecmp(Params[4].Key, "(value.shared")) {
+ setting_user_value = 0; /* global view */
+ }
+ else if (!strcasecmp(Params[4].Key, "(value.priv")) {
+ setting_user_value = 1; /* per-user view */
+ }
+ else {
+ IReply("NO [METADATA TOOMANY] SETMETADATA failed");
+ return;
+ }
+
+ /*
+ * Extract the folder type without any parentheses. Then learn
+ * the Citadel view type based on the supplied folder type.
+ */
+ extract_token(set_value, Params[5].Key, 0, ')', sizeof set_value);
+ if (!strncasecmp(set_value, "mail", 4)) {
+ set_view = VIEW_MAILBOX;
+ }
+ else if (!strncasecmp(set_value, "event", 5)) {
+ set_view = VIEW_CALENDAR;
+ }
+ else if (!strncasecmp(set_value, "contact", 7)) {
+ set_view = VIEW_ADDRESSBOOK;
+ }
+ else if (!strncasecmp(set_value, "journal", 7)) {
+ set_view = VIEW_JOURNAL;
+ }
+ else if (!strncasecmp(set_value, "note", 4)) {
+ set_view = VIEW_NOTES;
+ }
+ else if (!strncasecmp(set_value, "task", 4)) {
+ set_view = VIEW_TASKS;
+ }
+ else {
+ set_view = VIEW_MAILBOX;
+ }
+
+ ret = imap_grabroom(roomname, Params[2].Key, 1);
+ if (ret != 0) {
+ IReply("NO Invalid mailbox name or access denied");
+ return;
+ }
+
+ /*
+ * CtdlUserGoto() formally takes us to the desired room. (If another
+ * folder is selected, save its name so we can return there!!!!!)
+ */
+ if (IMAP->selected) {
+ strcpy(savedroom, CC->room.QRname);
+ }
+ CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
+
+ /*
+ * Always set the per-user view to the requested one.
+ */
+ CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+ vbuf.v_view = set_view;
+ CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
+
+ /* If this is a "value.priv" set operation, we're done. */
+
+ if (setting_user_value)
+ {
+ IReply("OK SETANNOTATION complete");
+ }
+
+ /* If this is a "value.shared" set operation, we are allowed to perform it
+ * under certain conditions.
+ */
+ else if ( (is_room_aide()) /* aide or room aide */
+ || ( (CC->room.QRflags & QR_MAILBOX)
+ && (CC->user.usernum == atol(CC->room.QRname)) /* mailbox owner */
+ )
+ || (msgs == 0) /* hack: if room is empty, assume we just created it */
+ ) {
+ CtdlGetRoomLock(&CC->room, CC->room.QRname);
+ CC->room.QRdefaultview = set_view;
+ CtdlPutRoomLock(&CC->room);
+ IReply("OK SETANNOTATION complete");
+ }
+
+ /* If we got to this point, we don't have permission to set the default view. */
+ else {
+ IReply("NO [METADATA TOOMANY] SETMETADATA failed");
+ }
+
+ /*
+ * If a different folder was previously selected, return there now.
+ */
+ if ( (IMAP->selected) && (strcasecmp(roomname, savedroom)) ) {
+ CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
+ }
+ return;
+}
+
+
+/*
+ * Implements the GETMETADATA command.
+ *
+ * Regardless of what the client asked for, we are going to supply them with
+ * the folder type. It's the only metadata we have anyway.
+ */
+void imap_getmetadata(int num_parms, ConstStr *Params) {
+ char roomname[ROOMNAMELEN];
+ char savedroom[ROOMNAMELEN];
+ int msgs, new;
+ int ret;
+ int found = 0;
+
+/* this doesn't work if you have rooms/floors with spaces.
+ we need this for the bynari connector.
+ if (num_parms > 5) {
+ IReply("BAD usage error");
+ return;
+ }
+*/
+ ret = imap_grabroom(roomname, Params[2].Key, 1);
+ if (ret != 0) {
+ IReply("NO Invalid mailbox name or access denied");
+ return;
+ }
+
+ /*
+ * CtdlUserGoto() formally takes us to the desired room. (If another
+ * folder is selected, save its name so we can return there!!!!!)
+ */
+ if (IMAP->selected) {
+ strcpy(savedroom, CC->room.QRname);
+ }
+ CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
+
+ IAPuts("* METADATA ");
+ IPutCParamStr(2);
+ IAPuts(" \"/vendor/kolab/folder-type\" (\"value.shared\" \"");
+
+ /* If it's one of our hard-coded default rooms, we know what to do... */
+
+ if (CC->room.QRname[10] == '.')
+ {
+ if (!strcasecmp(&CC->room.QRname[11], MAILROOM)) {
+ found = 1;
+ IAPuts("mail.inbox");
+ }
+ else if (!strcasecmp(&CC->room.QRname[11], SENTITEMS)) {
+ found = 1;
+ IAPuts("mail.sentitems");
+ }
+ else if (!strcasecmp(&CC->room.QRname[11], USERDRAFTROOM)) {
+ found = 1;
+ IAPuts("mail.drafts");
+ }
+ else if (!strcasecmp(&CC->room.QRname[11], USERCALENDARROOM)) {
+ found = 1;
+ IAPuts("event.default");
+ }
+ else if (!strcasecmp(&CC->room.QRname[11], USERCONTACTSROOM)) {
+ found = 1;
+ IAPuts("contact.default");
+ }
+ else if (!strcasecmp(&CC->room.QRname[11], USERNOTESROOM)) {
+ found = 1;
+ IAPuts("note.default");
+ }
+ else if (!strcasecmp(&CC->room.QRname[11], USERTASKSROOM)) {
+ found = 1;
+ IAPuts("task.default");
+ }
+ }
+
+ /* Otherwise, use the view for this room to determine the type of data.
+ * We are going with the default view rather than the user's view, because
+ * the default view almost always defines the actual contents, while the
+ * user's view might only make changes to presentation. It also saves us
+ * an extra database access because we don't need to load the visit record.
+ */
+ if (!found)
+ {
+ if (CC->room.QRdefaultview == VIEW_CALENDAR) {
+ IAPuts("event");
+ }
+ else if (CC->room.QRdefaultview == VIEW_ADDRESSBOOK) {
+ IAPuts("contact");
+ }
+ else if (CC->room.QRdefaultview == VIEW_TASKS) {
+ IAPuts("task");
+ }
+ else if (CC->room.QRdefaultview == VIEW_NOTES) {
+ IAPuts("note");
+ }
+ else if (CC->room.QRdefaultview == VIEW_JOURNAL) {
+ IAPuts("journal");
+ }
+ }
+ /* If none of the above conditions were met, consider it an ordinary mailbox. */
+
+ if (!found) {
+ IAPuts("mail");
+ }
+
+ /* "mail.outbox" and "junkemail" are not implemented. */
+
+ IAPuts("\")\r\n");
+
+ /*
+ * If a different folder was previously selected, return there now.
+ */
+ if ( (IMAP->selected) && (strcasecmp(roomname, savedroom)) ) {
+ CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
+ }
+
+ IReply("OK GETMETADATA complete");
+ return;
+}
+
--- /dev/null
+/*
+ * Copyright (c) 2007-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.
+ */
+
+void imap_getmetadata(int num_parms, ConstStr *Params);
+void imap_setmetadata(int num_parms, ConstStr *Params);
--- /dev/null
+/*
+ * Copyright (c) 1987-2020 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 as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ */
+
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../sysdep_decls.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../room_ops.h"
+#include "../../internet_addressing.h"
+#include "serv_imap.h"
+#include "imap_tools.h"
+#include "imap_fetch.h"
+#include "imap_misc.h"
+#include "../../genstamp.h"
+#include "../../ctdl_module.h"
+
+
+
+
+
+/*
+ * imap_copy() calls imap_do_copy() to do its actual work, once it's
+ * validated and boiled down the request a bit. (returns 0 on success)
+ */
+int imap_do_copy(const char *destination_folder) {
+ citimap *Imap = IMAP;
+ int i;
+ char roomname[ROOMNAMELEN];
+ struct ctdlroom qrbuf;
+ long *selected_msgs = NULL;
+ int num_selected = 0;
+
+ if (Imap->num_msgs < 1) {
+ return(0);
+ }
+
+ i = imap_grabroom(roomname, destination_folder, 1);
+ if (i != 0) return(i);
+
+ /*
+ * Copy all the message pointers in one shot.
+ */
+ selected_msgs = malloc(sizeof(long) * Imap->num_msgs);
+ if (selected_msgs == NULL) return(-1);
+
+ for (i = 0; i < Imap->num_msgs; ++i) {
+ if (Imap->flags[i] & IMAP_SELECTED) {
+ selected_msgs[num_selected++] = Imap->msgids[i];
+ }
+ }
+
+ if (num_selected > 0) {
+ CtdlSaveMsgPointersInRoom(roomname, selected_msgs, num_selected, 1, NULL, 0);
+ }
+ free(selected_msgs);
+
+ /* Don't bother wasting any more time if there were no messages. */
+ if (num_selected == 0) {
+ return(0);
+ }
+
+ /* Enumerate lists of messages for which flags are toggled */
+ long *seen_yes = NULL;
+ int num_seen_yes = 0;
+ long *seen_no = NULL;
+ int num_seen_no = 0;
+ long *answ_yes = NULL;
+ int num_answ_yes = 0;
+ long *answ_no = NULL;
+ int num_answ_no = 0;
+
+ seen_yes = malloc(num_selected * sizeof(long));
+ seen_no = malloc(num_selected * sizeof(long));
+ answ_yes = malloc(num_selected * sizeof(long));
+ answ_no = malloc(num_selected * sizeof(long));
+
+ for (i = 0; i < Imap->num_msgs; ++i) {
+ if (Imap->flags[i] & IMAP_SELECTED) {
+ if (Imap->flags[i] & IMAP_SEEN) {
+ seen_yes[num_seen_yes++] = Imap->msgids[i];
+ }
+ if ((Imap->flags[i] & IMAP_SEEN) == 0) {
+ seen_no[num_seen_no++] = Imap->msgids[i];
+ }
+ if (Imap->flags[i] & IMAP_ANSWERED) {
+ answ_yes[num_answ_yes++] = Imap->msgids[i];
+ }
+ if ((Imap->flags[i] & IMAP_ANSWERED) == 0) {
+ answ_no[num_answ_no++] = Imap->msgids[i];
+ }
+ }
+ }
+
+ /* Set the flags... */
+ i = CtdlGetRoom(&qrbuf, roomname);
+ if (i == 0) {
+ CtdlSetSeen(seen_yes, num_seen_yes, 1, ctdlsetseen_seen, NULL, &qrbuf);
+ CtdlSetSeen(seen_no, num_seen_no, 0, ctdlsetseen_seen, NULL, &qrbuf);
+ CtdlSetSeen(answ_yes, num_answ_yes, 1, ctdlsetseen_answered, NULL, &qrbuf);
+ CtdlSetSeen(answ_no, num_answ_no, 0, ctdlsetseen_answered, NULL, &qrbuf);
+ }
+
+ free(seen_yes);
+ free(seen_no);
+ free(answ_yes);
+ free(answ_no);
+
+ return(0);
+}
+
+
+/*
+ * Output the [COPYUID xxx yyy] response code required by RFC2359
+ * to tell the client the UID's of the messages that were copied (if any).
+ * We are assuming that the IMAP_SELECTED flag is still set on any relevant
+ * messages in our source room. Since the Citadel system uses UID's that
+ * are both globally unique and persistent across a room-to-room copy, we
+ * can get this done quite easily.
+ */
+void imap_output_copyuid_response(citimap *Imap) {
+ int i;
+ StrBuf *MsgsCopied = NewStrBuf();
+
+ for (i = 0; i < Imap->num_msgs; ++i) {
+ if (Imap->flags[i] & IMAP_SELECTED) {
+ if (StrLength(MsgsCopied) > 0) {
+ StrBufAppendBufPlain(MsgsCopied, HKEY(","), 0);
+ }
+ StrBufAppendPrintf(MsgsCopied, "%ld", Imap->msgids[i]);
+ }
+ }
+
+ if (StrLength(MsgsCopied) > 0) {
+ IAPrintf("[COPYUID %ld %s %s] ", GLOBAL_UIDVALIDITY_VALUE, ChrPtr(MsgsCopied), ChrPtr(MsgsCopied));
+ }
+
+ FreeStrBuf(&MsgsCopied);
+}
+
+
+/*
+ * This function is called by the main command loop.
+ */
+void imap_copy(int num_parms, ConstStr *Params) {
+ int ret;
+
+ if (num_parms != 4) {
+ IReply("BAD invalid parameters");
+ return;
+ }
+
+ if (imap_is_message_set(Params[2].Key)) {
+ imap_pick_range(Params[2].Key, 0);
+ }
+ else {
+ IReply("BAD invalid parameters");
+ return;
+ }
+
+ ret = imap_do_copy(Params[3].Key);
+ if (!ret) {
+ IAPrintf("%s OK ", Params[0].Key);
+ imap_output_copyuid_response(IMAP);
+ IAPuts("COPY completed\r\n");
+ }
+ else {
+ IReplyPrintf("NO COPY failed (error %d)", ret);
+ }
+}
+
+/*
+ * This function is called by the main command loop.
+ */
+void imap_uidcopy(int num_parms, ConstStr *Params) {
+
+ if (num_parms != 5) {
+ IReply("BAD invalid parameters");
+ return;
+ }
+
+ if (imap_is_message_set(Params[3].Key)) {
+ imap_pick_range(Params[3].Key, 1);
+ }
+ else {
+ IReply("BAD invalid parameters");
+ return;
+ }
+
+ if (imap_do_copy(Params[4].Key) == 0) {
+ IAPrintf("%s OK ", Params[0].Key);
+ imap_output_copyuid_response(IMAP);
+ IAPuts("UID COPY completed\r\n");
+ }
+ else {
+ IReply("NO UID COPY failed");
+ }
+}
+
+
+/*
+ * imap_do_append_flags() is called by imap_append() to set any flags that
+ * the client specified at append time.
+ *
+ * FIXME find a way to do these in bulk so we don't max out our db journal
+ */
+void imap_do_append_flags(long new_msgnum, char *new_message_flags) {
+ char flags[32];
+ char this_flag[sizeof flags];
+ int i;
+
+ if (new_message_flags == NULL) return;
+ if (IsEmptyStr(new_message_flags)) return;
+
+ safestrncpy(flags, new_message_flags, sizeof flags);
+
+ for (i=0; i<num_tokens(flags, ' '); ++i) {
+ extract_token(this_flag, flags, i, ' ', sizeof this_flag);
+ if (this_flag[0] == '\\') strcpy(this_flag, &this_flag[1]);
+ if (!strcasecmp(this_flag, "Seen")) {
+ CtdlSetSeen(&new_msgnum, 1, 1, ctdlsetseen_seen,
+ NULL, NULL);
+ }
+ if (!strcasecmp(this_flag, "Answered")) {
+ CtdlSetSeen(&new_msgnum, 1, 1, ctdlsetseen_answered,
+ NULL, NULL);
+ }
+ }
+}
+
+
+/*
+ * This function is called by the main command loop.
+ */
+void imap_append(int num_parms, ConstStr *Params) {
+ long literal_length;
+ struct CtdlMessage *msg = NULL;
+ long new_msgnum = (-1L);
+ int ret = 0;
+ char roomname[ROOMNAMELEN];
+ char errbuf[SIZ];
+ char dummy[SIZ];
+ char savedroom[ROOMNAMELEN];
+ int msgs, new;
+ int i;
+ char new_message_flags[SIZ];
+ citimap *Imap;
+
+ if (num_parms < 4) {
+ IReply("BAD usage error");
+ return;
+ }
+
+ if ( (Params[num_parms-1].Key[0] != '{')
+ || (Params[num_parms-1].Key[Params[num_parms-1].len-1] != '}') ) {
+ IReply("BAD no message literal supplied");
+ return;
+ }
+
+ *new_message_flags = '\0';
+ if (num_parms >= 5) {
+ for (i=3; i<num_parms; ++i) {
+ strcat(new_message_flags, Params[i].Key);
+ strcat(new_message_flags, " ");
+ }
+ stripallbut(new_message_flags, '(', ')');
+ }
+
+ /* This is how we'd do this if it were relevant in our data store.
+ * if (num_parms >= 6) {
+ * new_message_internaldate = parms[4];
+ * }
+ */
+
+ literal_length = atol(&Params[num_parms-1].Key[1]);
+ if (literal_length < 1) {
+ IReply("BAD Message length must be at least 1.");
+ return;
+ }
+
+ Imap = IMAP;
+ imap_free_transmitted_message(); /* just in case. */
+
+ Imap->TransmittedMessage = NewStrBufPlain(NULL, literal_length);
+
+ if (Imap->TransmittedMessage == NULL) {
+ IReply("NO Cannot allocate memory.");
+ return;
+ }
+
+ IAPrintf("+ Transmit message now.\r\n");
+
+ IUnbuffer ();
+
+ client_read_blob(Imap->TransmittedMessage, literal_length, CtdlGetConfigInt("c_sleeping"));
+
+ if ((ret < 0) || (StrLength(Imap->TransmittedMessage) < literal_length)) {
+ IReply("NO Read failed.");
+ return;
+ }
+
+ /* Client will transmit a trailing CRLF after the literal (the message
+ * text) is received. This call to client_getln() absorbs it.
+ */
+ flush_output();
+ client_getln(dummy, sizeof dummy);
+
+ /* Convert RFC822 newlines (CRLF) to Unix newlines (LF) */
+ syslog(LOG_DEBUG, "Converting CRLF to LF");
+ StrBufToUnixLF(Imap->TransmittedMessage);
+
+ syslog(LOG_DEBUG, "Converting message format");
+ msg = convert_internet_message_buf(&Imap->TransmittedMessage);
+
+ ret = imap_grabroom(roomname, Params[2].Key, 1);
+ if (ret != 0) {
+ IReply("NO Invalid mailbox name or access denied");
+ return;
+ }
+
+ /*
+ * CtdlUserGoto() formally takes us to the desired room. (If another
+ * folder is selected, save its name so we can return there!!!!!)
+ */
+ if (Imap->selected) {
+ strcpy(savedroom, CC->room.QRname);
+ }
+ CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
+
+ /* If the user is locally authenticated, FORCE the From: header to
+ * show up as the real sender. (Configurable setting)
+ */
+ if (CC->logged_in) {
+ if ( ((CC->room.QRflags & QR_MAILBOX) == 0) && (CtdlGetConfigInt("c_imap_keep_from") == 0))
+ {
+ CM_SetField(msg, eAuthor, CC->user.fullname, strlen(CC->user.fullname));
+ }
+ }
+
+ /*
+ * Can we post here?
+ */
+ ret = CtdlDoIHavePermissionToPostInThisRoom(errbuf, sizeof errbuf, POST_LOGGED_IN, 0);
+
+ if (ret) {
+ /* Nope ... print an error message */
+ IReplyPrintf("NO %s", errbuf);
+ }
+
+ else {
+ /* Yes ... go ahead and post! */
+ if (msg != NULL) {
+ new_msgnum = CtdlSubmitMsg(msg, NULL, "");
+ }
+ if (new_msgnum >= 0L) {
+ IReplyPrintf("OK [APPENDUID %ld %ld] APPEND completed",
+ GLOBAL_UIDVALIDITY_VALUE, new_msgnum);
+ }
+ else {
+ IReplyPrintf("BAD Error %ld saving message to disk.",
+ new_msgnum);
+ }
+ }
+
+ /*
+ * IMAP protocol response to client has already been sent by now.
+ *
+ * If another folder is selected, go back to that room so we can resume
+ * our happy day without violent explosions.
+ */
+ if (Imap->selected) {
+ CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
+ }
+
+ /* We don't need this buffer anymore */
+ CM_Free(msg);
+
+ if (IsEmptyStr(new_message_flags)) {
+ imap_do_append_flags(new_msgnum, new_message_flags);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2001-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.
+ */
+
+void imap_copy(int num_parms, ConstStr *Params);
+void imap_uidcopy(int num_parms, ConstStr *Params);
+void imap_append(int num_parms, ConstStr *Params);
--- /dev/null
+/*
+ * Implements IMAP's gratuitously complex SEARCH command.
+ *
+ * Copyright (c) 2001-2020 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.
+ */
+
+#include "../../ctdl_module.h"
+
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../sysdep_decls.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "serv_imap.h"
+#include "imap_tools.h"
+#include "imap_fetch.h"
+#include "imap_search.h"
+#include "../../genstamp.h"
+
+
+/*
+ * imap_do_search() calls imap_do_search_msg() to search an individual
+ * message after it has been fetched from the disk. This function returns
+ * nonzero if there is a match.
+ *
+ * supplied_msg MAY be used to pass a pointer to the message in memory,
+ * if for some reason it's already been loaded. If not, the message will
+ * be loaded only if one or more search criteria require it.
+ */
+int imap_do_search_msg(int seq, struct CtdlMessage *supplied_msg,
+ int num_items, ConstStr *itemlist, int is_uid) {
+
+ citimap *Imap = IMAP;
+ int match = 0;
+ int is_not = 0;
+ int is_or = 0;
+ int pos = 0;
+ int i;
+ char *fieldptr;
+ struct CtdlMessage *msg = NULL;
+ int need_to_free_msg = 0;
+
+ if (num_items == 0) {
+ return(0);
+ }
+ msg = supplied_msg;
+
+ /* Initially we start at the beginning. */
+ pos = 0;
+
+ /* Check for the dreaded NOT criterion. */
+ if (!strcasecmp(itemlist[0].Key, "NOT")) {
+ is_not = 1;
+ pos = 1;
+ }
+
+ /* Check for the dreaded OR criterion. */
+ if (!strcasecmp(itemlist[0].Key, "OR")) {
+ is_or = 1;
+ pos = 1;
+ }
+
+ /* Now look for criteria. */
+ if (!strcasecmp(itemlist[pos].Key, "ALL")) {
+ match = 1;
+ ++pos;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "ANSWERED")) {
+ if (Imap->flags[seq-1] & IMAP_ANSWERED) {
+ match = 1;
+ }
+ ++pos;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "BCC")) {
+ if (msg == NULL) {
+ msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+ need_to_free_msg = 1;
+ }
+ if (msg != NULL) {
+ fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Bcc");
+ if (fieldptr != NULL) {
+ if (bmstrcasestr(fieldptr, itemlist[pos+1].Key)) {
+ match = 1;
+ }
+ free(fieldptr);
+ }
+ }
+ pos += 2;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "BEFORE")) {
+ if (msg == NULL) {
+ msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+ need_to_free_msg = 1;
+ }
+ if (msg != NULL) {
+ if (!CM_IsEmpty(msg, eTimestamp)) {
+ if (imap_datecmp(itemlist[pos+1].Key,
+ atol(msg->cm_fields[eTimestamp])) < 0) {
+ match = 1;
+ }
+ }
+ }
+ pos += 2;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "BODY")) {
+
+ /* If fulltext indexing is active, on this server,
+ * all messages have already been qualified.
+ */
+ if (CtdlGetConfigInt("c_enable_fulltext")) {
+ match = 1;
+ }
+
+ /* Otherwise, we have to do a slow search. */
+ else {
+ if (msg == NULL) {
+ msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+ need_to_free_msg = 1;
+ }
+ if (msg != NULL) {
+ if (bmstrcasestr(msg->cm_fields[eMesageText], itemlist[pos+1].Key)) {
+ match = 1;
+ }
+ }
+ }
+
+ pos += 2;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "CC")) {
+ if (msg == NULL) {
+ msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+ need_to_free_msg = 1;
+ }
+ if (msg != NULL) {
+ fieldptr = msg->cm_fields[eCarbonCopY];
+ if (fieldptr != NULL) {
+ if (bmstrcasestr(fieldptr, itemlist[pos+1].Key)) {
+ match = 1;
+ }
+ }
+ else {
+ fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Cc");
+ if (fieldptr != NULL) {
+ if (bmstrcasestr(fieldptr, itemlist[pos+1].Key)) {
+ match = 1;
+ }
+ free(fieldptr);
+ }
+ }
+ }
+ pos += 2;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "DELETED")) {
+ if (Imap->flags[seq-1] & IMAP_DELETED) {
+ match = 1;
+ }
+ ++pos;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "DRAFT")) {
+ if (Imap->flags[seq-1] & IMAP_DRAFT) {
+ match = 1;
+ }
+ ++pos;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "FLAGGED")) {
+ if (Imap->flags[seq-1] & IMAP_FLAGGED) {
+ match = 1;
+ }
+ ++pos;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "FROM")) {
+ if (msg == NULL) {
+ msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+ need_to_free_msg = 1;
+ }
+ if (msg != NULL) {
+ if (bmstrcasestr(msg->cm_fields[eAuthor], itemlist[pos+1].Key)) {
+ match = 1;
+ }
+ if (bmstrcasestr(msg->cm_fields[erFc822Addr], itemlist[pos+1].Key)) {
+ match = 1;
+ }
+ }
+ pos += 2;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "HEADER")) {
+
+ /* We've got to do a slow search for this because the client
+ * might be asking for an RFC822 header field that has not been
+ * converted into a Citadel header field. That requires
+ * examining the message body.
+ */
+ if (msg == NULL) {
+ msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+ need_to_free_msg = 1;
+ }
+
+ if (msg != NULL) {
+
+ CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+ CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_FAST, 0, 1, 0);
+
+ fieldptr = rfc822_fetch_field(ChrPtr(CC->redirect_buffer), itemlist[pos+1].Key);
+ if (fieldptr != NULL) {
+ if (bmstrcasestr(fieldptr, itemlist[pos+2].Key)) {
+ match = 1;
+ }
+ free(fieldptr);
+ }
+
+ FreeStrBuf(&CC->redirect_buffer);
+ }
+
+ pos += 3; /* Yes, three */
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "KEYWORD")) {
+ /* not implemented */
+ pos += 2;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "LARGER")) {
+ if (msg == NULL) {
+ msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+ need_to_free_msg = 1;
+ }
+ if (msg != NULL) {
+ if (msg->cm_lengths[eMesageText] > atoi(itemlist[pos+1].Key)) {
+ match = 1;
+ }
+ }
+ pos += 2;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "NEW")) {
+ if ( (Imap->flags[seq-1] & IMAP_RECENT) && (!(Imap->flags[seq-1] & IMAP_SEEN))) {
+ match = 1;
+ }
+ ++pos;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "OLD")) {
+ if (!(Imap->flags[seq-1] & IMAP_RECENT)) {
+ match = 1;
+ }
+ ++pos;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "ON")) {
+ if (msg == NULL) {
+ msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+ need_to_free_msg = 1;
+ }
+ if (msg != NULL) {
+ if (!CM_IsEmpty(msg, eTimestamp)) {
+ if (imap_datecmp(itemlist[pos+1].Key,
+ atol(msg->cm_fields[eTimestamp])) == 0) {
+ match = 1;
+ }
+ }
+ }
+ pos += 2;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "RECENT")) {
+ if (Imap->flags[seq-1] & IMAP_RECENT) {
+ match = 1;
+ }
+ ++pos;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "SEEN")) {
+ if (Imap->flags[seq-1] & IMAP_SEEN) {
+ match = 1;
+ }
+ ++pos;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "SENTBEFORE")) {
+ if (msg == NULL) {
+ msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+ need_to_free_msg = 1;
+ }
+ if (msg != NULL) {
+ if (!CM_IsEmpty(msg, eTimestamp)) {
+ if (imap_datecmp(itemlist[pos+1].Key,
+ atol(msg->cm_fields[eTimestamp])) < 0) {
+ match = 1;
+ }
+ }
+ }
+ pos += 2;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "SENTON")) {
+ if (msg == NULL) {
+ msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+ need_to_free_msg = 1;
+ }
+ if (msg != NULL) {
+ if (!CM_IsEmpty(msg, eTimestamp)) {
+ if (imap_datecmp(itemlist[pos+1].Key,
+ atol(msg->cm_fields[eTimestamp])) == 0) {
+ match = 1;
+ }
+ }
+ }
+ pos += 2;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "SENTSINCE")) {
+ if (msg == NULL) {
+ msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+ need_to_free_msg = 1;
+ }
+ if (msg != NULL) {
+ if (!CM_IsEmpty(msg, eTimestamp)) {
+ if (imap_datecmp(itemlist[pos+1].Key,
+ atol(msg->cm_fields[eTimestamp])) >= 0) {
+ match = 1;
+ }
+ }
+ }
+ pos += 2;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "SINCE")) {
+ if (msg == NULL) {
+ msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+ need_to_free_msg = 1;
+ }
+ if (msg != NULL) {
+ if (!CM_IsEmpty(msg, eTimestamp)) {
+ if (imap_datecmp(itemlist[pos+1].Key,
+ atol(msg->cm_fields[eTimestamp])) >= 0) {
+ match = 1;
+ }
+ }
+ }
+ pos += 2;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "SMALLER")) {
+ if (msg == NULL) {
+ msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+ need_to_free_msg = 1;
+ }
+ if (msg != NULL) {
+ if (msg->cm_lengths[eMesageText] < atoi(itemlist[pos+1].Key)) {
+ match = 1;
+ }
+ }
+ pos += 2;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "SUBJECT")) {
+ if (msg == NULL) {
+ msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+ need_to_free_msg = 1;
+ }
+ if (msg != NULL) {
+ if (bmstrcasestr(msg->cm_fields[eMsgSubject], itemlist[pos+1].Key)) {
+ match = 1;
+ }
+ }
+ pos += 2;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "TEXT")) {
+ if (msg == NULL) {
+ msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+ need_to_free_msg = 1;
+ }
+ if (msg != NULL) {
+ for (i='A'; i<='Z'; ++i) {
+ if (bmstrcasestr(msg->cm_fields[i], itemlist[pos+1].Key)) {
+ match = 1;
+ }
+ }
+ }
+ pos += 2;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "TO")) {
+ if (msg == NULL) {
+ msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
+ need_to_free_msg = 1;
+ }
+ if (msg != NULL) {
+ if (bmstrcasestr(msg->cm_fields[eRecipient], itemlist[pos+1].Key)) {
+ match = 1;
+ }
+ }
+ pos += 2;
+ }
+
+ /* FIXME this is b0rken. fix it. */
+ else if (imap_is_message_set(itemlist[pos].Key)) {
+ if (is_msg_in_sequence_set(itemlist[pos].Key, seq)) {
+ match = 1;
+ }
+ pos += 1;
+ }
+
+ /* FIXME this is b0rken. fix it. */
+ else if (!strcasecmp(itemlist[pos].Key, "UID")) {
+ if (is_msg_in_sequence_set(itemlist[pos+1].Key, Imap->msgids[seq-1])) {
+ match = 1;
+ }
+ pos += 2;
+ }
+
+ /* Now here come the 'UN' criteria. Why oh why do we have to
+ * implement *both* the 'UN' criteria *and* the 'NOT' keyword? Why
+ * can't there be *one* way to do things? More gratuitous complexity.
+ */
+
+ else if (!strcasecmp(itemlist[pos].Key, "UNANSWERED")) {
+ if ((Imap->flags[seq-1] & IMAP_ANSWERED) == 0) {
+ match = 1;
+ }
+ ++pos;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "UNDELETED")) {
+ if ((Imap->flags[seq-1] & IMAP_DELETED) == 0) {
+ match = 1;
+ }
+ ++pos;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "UNDRAFT")) {
+ if ((Imap->flags[seq-1] & IMAP_DRAFT) == 0) {
+ match = 1;
+ }
+ ++pos;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "UNFLAGGED")) {
+ if ((Imap->flags[seq-1] & IMAP_FLAGGED) == 0) {
+ match = 1;
+ }
+ ++pos;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "UNKEYWORD")) {
+ /* FIXME */
+ pos += 2;
+ }
+
+ else if (!strcasecmp(itemlist[pos].Key, "UNSEEN")) {
+ if ((Imap->flags[seq-1] & IMAP_SEEN) == 0) {
+ match = 1;
+ }
+ ++pos;
+ }
+
+ /* Remember to negate if we were told to */
+ if (is_not) {
+ match = !match;
+ }
+
+ /* Keep going if there are more criteria! */
+ if (pos < num_items) {
+
+ if (is_or) {
+ match = (match || imap_do_search_msg(seq, msg,
+ num_items - pos, &itemlist[pos], is_uid));
+ }
+ else {
+ match = (match && imap_do_search_msg(seq, msg,
+ num_items - pos, &itemlist[pos], is_uid));
+ }
+
+ }
+
+ if (need_to_free_msg) {
+ CM_Free(msg);
+ }
+ return(match);
+}
+
+
+/*
+ * imap_search() calls imap_do_search() to do its actual work, once it's
+ * validated and boiled down the request a bit.
+ */
+void imap_do_search(int num_items, ConstStr *itemlist, int is_uid) {
+ citimap *Imap = IMAP;
+ int i, j, k;
+ int fts_num_msgs = 0;
+ long *fts_msgs = NULL;
+ int is_in_list = 0;
+ int num_results = 0;
+
+ /* Strip parentheses. We realize that this method will not work
+ * in all cases, but it seems to work with all currently available
+ * client software. Revisit later...
+ */
+ for (i=0; i<num_items; ++i) {
+ if (itemlist[i].Key[0] == '(') {
+
+ TokenCutLeft(&Imap->Cmd,
+ &itemlist[i],
+ 1);
+ }
+ if (itemlist[i].Key[itemlist[i].len-1] == ')') {
+ TokenCutRight(&Imap->Cmd,
+ &itemlist[i],
+ 1);
+ }
+ }
+
+ /* If there is a BODY search criterion in the query, use our full
+ * text index to disqualify messages that don't have any chance of
+ * matching. (Only do this if the index is enabled!!)
+ */
+ if (CtdlGetConfigInt("c_enable_fulltext")) for (i=0; i<(num_items-1); ++i) {
+ if (!strcasecmp(itemlist[i].Key, "BODY")) {
+ CtdlModuleDoSearch(&fts_num_msgs, &fts_msgs, itemlist[i+1].Key, "fulltext");
+ if (fts_num_msgs > 0) {
+ for (j=0; j < Imap->num_msgs; ++j) {
+ if (Imap->flags[j] & IMAP_SELECTED) {
+ is_in_list = 0;
+ for (k=0; k<fts_num_msgs; ++k) {
+ if (Imap->msgids[j] == fts_msgs[k]) {
+ ++is_in_list;
+ }
+ }
+ }
+ if (!is_in_list) {
+ Imap->flags[j] = Imap->flags[j] & ~IMAP_SELECTED;
+ }
+ }
+ }
+ else { /* no hits on the index; disqualify every message */
+ for (j=0; j < Imap->num_msgs; ++j) {
+ Imap->flags[j] = Imap->flags[j] & ~IMAP_SELECTED;
+ }
+ }
+ if (fts_msgs) {
+ free(fts_msgs);
+ }
+ }
+ }
+
+ /* Now go through the messages and apply all search criteria. */
+ buffer_output();
+ IAPuts("* SEARCH ");
+ if (Imap->num_msgs > 0)
+ for (i = 0; i < Imap->num_msgs; ++i)
+ if (Imap->flags[i] & IMAP_SELECTED) {
+ if (imap_do_search_msg(i+1, NULL, num_items, itemlist, is_uid)) {
+ if (num_results != 0) {
+ IAPuts(" ");
+ }
+ if (is_uid) {
+ IAPrintf("%ld", Imap->msgids[i]);
+ }
+ else {
+ IAPrintf("%d", i+1);
+ }
+ ++num_results;
+ }
+ }
+ IAPuts("\r\n");
+ unbuffer_output();
+}
+
+
+/*
+ * This function is called by the main command loop.
+ */
+void imap_search(int num_parms, ConstStr *Params) {
+ int i;
+
+ if (num_parms < 3) {
+ IReply("BAD invalid parameters");
+ return;
+ }
+
+ for (i = 0; i < IMAP->num_msgs; ++i) {
+ IMAP->flags[i] |= IMAP_SELECTED;
+ }
+
+ imap_do_search(num_parms-2, &Params[2], 0);
+ IReply("OK SEARCH completed");
+}
+
+/*
+ * This function is called by the main command loop.
+ */
+void imap_uidsearch(int num_parms, ConstStr *Params) {
+ int i;
+
+ if (num_parms < 4) {
+ IReply("BAD invalid parameters");
+ return;
+ }
+
+ for (i = 0; i < IMAP->num_msgs; ++i) {
+ IMAP->flags[i] |= IMAP_SELECTED;
+ }
+
+ imap_do_search(num_parms-3, &Params[3], 1);
+ IReply("OK UID SEARCH completed");
+}
+
+
--- /dev/null
+/*
+ * Copyright (c) 2001-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.
+ */
+
+void imap_search(int num_parms, ConstStr *Params);
+void imap_uidsearch(int num_parms, ConstStr *Params);
--- /dev/null
+/*
+ * Implements the STORE command in IMAP.
+ *
+ * Copyright (c) 2001-2009 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 as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "../../ctdl_module.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../sysdep_decls.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../room_ops.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "serv_imap.h"
+#include "imap_tools.h"
+#include "imap_fetch.h"
+#include "imap_store.h"
+#include "../../genstamp.h"
+
+
+/*
+ * imap_do_store() calls imap_do_store_msg() to tweak the settings of
+ * an individual message.
+ *
+ * We also implement the ".SILENT" protocol option here. :(
+ */
+void imap_do_store_msg(int seq, const char *oper, unsigned int bits_to_twiddle) {
+ citimap *Imap = IMAP;
+
+ if (!strncasecmp(oper, "FLAGS", 5)) {
+ Imap->flags[seq] &= IMAP_MASK_SYSTEM;
+ Imap->flags[seq] |= bits_to_twiddle;
+ }
+ else if (!strncasecmp(oper, "+FLAGS", 6)) {
+ Imap->flags[seq] |= bits_to_twiddle;
+ }
+ else if (!strncasecmp(oper, "-FLAGS", 6)) {
+ Imap->flags[seq] &= (~bits_to_twiddle);
+ }
+}
+
+
+/*
+ * imap_store() calls imap_do_store() to perform the actual bit twiddling
+ * on the flags.
+ */
+void imap_do_store(citimap_command *Cmd) {
+ int i, j;
+ unsigned int bits_to_twiddle = 0;
+ const char *oper;
+ char flag[32];
+ char whichflags[256];
+ char num_flags;
+ int silent = 0;
+ long *ss_msglist;
+ int num_ss = 0;
+ int last_item_twiddled = (-1);
+ citimap *Imap = IMAP;
+
+ if (Cmd->num_parms < 2) return;
+ oper = Cmd->Params[0].Key;
+ if (cbmstrcasestr(oper, ".SILENT")) {
+ silent = 1;
+ }
+
+ /*
+ * ss_msglist is an array of message numbers to manipulate. We
+ * are going to supply this array to CtdlSetSeen() later.
+ */
+ ss_msglist = malloc(Imap->num_msgs * sizeof(long));
+ if (ss_msglist == NULL) return;
+
+ /*
+ * Ok, go ahead and parse the flags.
+ */
+ for (i=1; i<Cmd->num_parms; ++i) {///TODO: why strcpy?
+ strcpy(whichflags, Cmd->Params[i].Key);
+ if (whichflags[0]=='(') {
+ safestrncpy(whichflags, &whichflags[1],
+ sizeof whichflags);
+ }
+ if (whichflags[strlen(whichflags)-1]==')') {
+ whichflags[strlen(whichflags)-1]=0;
+ }
+ striplt(whichflags);
+
+ /* A client might twiddle more than one bit at a time.
+ * Note that we check for the flag names without the leading
+ * backslash because imap_parameterize() strips them out.
+ */
+ num_flags = num_tokens(whichflags, ' ');
+ for (j=0; j<num_flags; ++j) {
+ extract_token(flag, whichflags, j, ' ', sizeof flag);
+
+ if ((!strcasecmp(flag, "\\Deleted"))
+ || (!strcasecmp(flag, "Deleted"))) {
+ if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom()) {
+ bits_to_twiddle |= IMAP_DELETED;
+ }
+ }
+ if ((!strcasecmp(flag, "\\Seen"))
+ || (!strcasecmp(flag, "Seen"))) {
+ bits_to_twiddle |= IMAP_SEEN;
+ }
+ if ((!strcasecmp(flag, "\\Answered"))
+ || (!strcasecmp(flag, "Answered"))) {
+ bits_to_twiddle |= IMAP_ANSWERED;
+ }
+ }
+ }
+
+ if (Imap->num_msgs > 0) {
+ for (i = 0; i < Imap->num_msgs; ++i) {
+ if (Imap->flags[i] & IMAP_SELECTED) {
+ last_item_twiddled = i;
+
+ ss_msglist[num_ss++] = Imap->msgids[i];
+ imap_do_store_msg(i, oper, bits_to_twiddle);
+
+ if (!silent) {
+ IAPrintf("* %d FETCH (", i+1);
+ imap_fetch_flags(i);
+ IAPuts(")\r\n");
+ }
+
+ }
+ }
+ }
+
+ /*
+ * Now manipulate the database -- all in one shot.
+ */
+ if ( (last_item_twiddled >= 0) && (num_ss > 0) ) {
+
+ if (bits_to_twiddle & IMAP_SEEN) {
+ CtdlSetSeen(ss_msglist, num_ss,
+ ((Imap->flags[last_item_twiddled] & IMAP_SEEN) ? 1 : 0),
+ ctdlsetseen_seen,
+ NULL, NULL
+ );
+ }
+
+ if (bits_to_twiddle & IMAP_ANSWERED) {
+ CtdlSetSeen(ss_msglist, num_ss,
+ ((Imap->flags[last_item_twiddled] & IMAP_ANSWERED) ? 1 : 0),
+ ctdlsetseen_answered,
+ NULL, NULL
+ );
+ }
+
+ }
+
+ free(ss_msglist);
+ imap_do_expunge(); // Citadel always expunges immediately.
+ imap_rescan_msgids();
+}
+
+
+/*
+ * This function is called by the main command loop.
+ */
+void imap_store(int num_parms, ConstStr *Params) {
+ citimap_command Cmd;
+ int num_items;
+
+ if (num_parms < 3) {
+ IReply("BAD invalid parameters");
+ return;
+ }
+
+ if (imap_is_message_set(Params[2].Key)) {
+ imap_pick_range(Params[2].Key, 0);
+ }
+ else {
+ IReply("BAD invalid parameters");
+ return;
+ }
+
+ memset(&Cmd, 0, sizeof(citimap_command));
+ Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
+ MakeStringOf(Cmd.CmdBuf, 3);
+
+ num_items = imap_extract_data_items(&Cmd);
+ if (num_items < 1) {
+ IReply("BAD invalid data item list");
+ FreeStrBuf(&Cmd.CmdBuf);
+ free(Cmd.Params);
+ return;
+ }
+
+ imap_do_store(&Cmd);
+ IReply("OK STORE completed");
+ FreeStrBuf(&Cmd.CmdBuf);
+ free(Cmd.Params);
+}
+
+/*
+ * This function is called by the main command loop.
+ */
+void imap_uidstore(int num_parms, ConstStr *Params) {
+ citimap_command Cmd;
+ int num_items;
+
+ if (num_parms < 4) {
+ IReply("BAD invalid parameters");
+ return;
+ }
+
+ if (imap_is_message_set(Params[3].Key)) {
+ imap_pick_range(Params[3].Key, 1);
+ }
+ else {
+ IReply("BAD invalid parameters");
+ return;
+ }
+
+ memset(&Cmd, 0, sizeof(citimap_command));
+ Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
+ MakeStringOf(Cmd.CmdBuf, 4);
+
+ num_items = imap_extract_data_items(&Cmd);
+ if (num_items < 1) {
+ IReply("BAD invalid data item list");
+ FreeStrBuf(&Cmd.CmdBuf);
+ free(Cmd.Params);
+ return;
+ }
+
+ imap_do_store(&Cmd);
+ IReply("OK UID STORE completed");
+ FreeStrBuf(&Cmd.CmdBuf);
+ free(Cmd.Params);
+}
+
+
--- /dev/null
+/*
+ * Copyright (c) 2001-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.
+ */
+
+void imap_store(int num_parms, ConstStr *Params);
+void imap_uidstore(int num_parms, ConstStr *Params);
--- /dev/null
+/*
+ * Utility functions for the IMAP module.
+ *
+ * Copyright (c) 2001-2017 by the citadel.org team and others, except for
+ * most of the UTF7 and UTF8 handling code which was lifted from Evolution.
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define SHOW_ME_VAPPEND_PRINTF
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdarg.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../sysdep_decls.h"
+#include "../../internet_addressing.h"
+#include "serv_imap.h"
+#include "imap_tools.h"
+#include "../../ctdl_module.h"
+
+/* String handling helpers */
+
+/* This code uses some pretty nasty string manipulation. To make everything
+ * manageable, we use this semi-high-level string manipulation API. Strings are
+ * always \0-terminated, despite the fact that we keep track of the size.
+ */
+struct string {
+ char* buffer;
+ int maxsize;
+ int size;
+};
+
+static void string_init(struct string* s, char* buf, int bufsize)
+{
+ s->buffer = buf;
+ s->maxsize = bufsize-1;
+ s->size = strlen(buf);
+}
+
+static char* string_end(struct string* s)
+{
+ return s->buffer + s->size;
+}
+
+/* Append a UTF8 string of a particular length (in bytes). -1 to autocalculate. */
+
+static void string_append_sn(struct string* s, char* p, int len)
+{
+ if (len == -1)
+ len = strlen(p);
+ if ((s->size+len) > s->maxsize)
+ len = s->maxsize - s->size;
+ memcpy(s->buffer + s->size, p, len);
+ s->size += len;
+ s->buffer[s->size] = '\0';
+}
+
+/* As above, always autocalculate. */
+
+#define string_append_s(s, p) string_append_sn((s), (p), -1)
+
+/* Appends a UTF8 character --- which may make the size change by more than 1!
+ * If the string overflows, the last character may become invalid. */
+
+static void string_append_c(struct string* s, int c)
+{
+ char UmlChar[5];
+ int len = 0;
+
+ /* Don't do anything if there's no room. */
+
+ if (s->size == s->maxsize)
+ return;
+
+ if (c <= 0x7F)
+ {
+ /* This is the most common case, so we optimise it. */
+
+ s->buffer[s->size++] = c;
+ s->buffer[s->size] = 0;
+ return;
+ }
+ else if (c <= 0x7FF)
+ {
+ UmlChar[0] = 0xC0 | (c >> 6);
+ UmlChar[1] = 0x80 | (c & 0x3F);
+ len = 2;
+ }
+ else if (c <= 0xFFFF)
+ {
+ UmlChar[0] = 0xE0 | (c >> 12);
+ UmlChar[1] = 0x80 | ((c >> 6) & 0x3f);
+ UmlChar[2] = 0x80 | (c & 0x3f);
+ len = 3;
+ }
+ else
+ {
+ UmlChar[0] = 0xf0 | c >> 18;
+ UmlChar[1] = 0x80 | ((c >> 12) & 0x3f);
+ UmlChar[2] = 0x80 | ((c >> 6) & 0x3f);
+ UmlChar[3] = 0x80 | (c & 0x3f);
+ len = 4;
+ }
+
+ string_append_sn(s, UmlChar, len);
+}
+
+/* Reads a UTF8 character from a char*, advancing the pointer. */
+
+int utf8_getc(char** ptr)
+{
+ unsigned char* p = (unsigned char*) *ptr;
+ unsigned char c, r;
+ int v, m;
+
+ for (;;)
+ {
+ r = *p++;
+ loop:
+ if (r < 0x80)
+ {
+ *ptr = (char*) p;
+ v = r;
+ break;
+ }
+ else if (r < 0xf8)
+ {
+ /* valid start char? (max 4 octets) */
+ v = r;
+ m = 0x7f80; /* used to mask out the length bits */
+ do {
+ c = *p++;
+ if ((c & 0xc0) != 0x80)
+ {
+ r = c;
+ goto loop;
+ }
+ v = (v<<6) | (c & 0x3f);
+ r<<=1;
+ m<<=5;
+ } while (r & 0x40);
+
+ *ptr = (char*)p;
+
+ v &= ~m;
+ break;
+ }
+ }
+
+ return v;
+}
+
+/* IMAP name safety */
+
+/* IMAP has certain special requirements in its character set, which means we
+ * have to do a fair bit of work to convert Citadel's UTF8 strings to IMAP
+ * strings. The next two routines (and their data tables) do that.
+ */
+
+static char *utf7_alphabet =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
+
+static unsigned char utf7_rank[256] = {
+ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3E,0x3F,0xFF,0xFF,0xFF,
+ 0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+ 0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,
+ 0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF,
+ 0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,
+ 0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0xFF,0xFF,0xFF,0xFF,0xFF,
+ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+};
+
+/* Base64 helpers. */
+
+static void utf7_closeb64(struct string* out, int v, int i)
+{
+ int x;
+
+ if (i > 0)
+ {
+ x = (v << (6-i)) & 0x3F;
+ string_append_c(out, utf7_alphabet[x]);
+ }
+ string_append_c(out, '-');
+}
+
+/* Convert from a Citadel name to an IMAP-safe name. Returns the end
+ * of the destination.
+ */
+static char* toimap(char* destp, char* destend, char* src)
+{
+ struct string dest;
+ int state = 0;
+ int v = 0;
+ int i = 0;
+
+ *destp = 0;
+ string_init(&dest, destp, destend-destp);
+ /* syslog(LOG_DEBUG, "toimap %s", src); */
+
+ for (;;)
+ {
+ int c = utf8_getc(&src);
+ if (c == '\0')
+ break;
+
+ if (c >= 0x20 && c <= 0x7e)
+ {
+ if (state == 1)
+ {
+ utf7_closeb64(&dest, v, i);
+ state = 0;
+ i = 0;
+ }
+
+ switch (c)
+ {
+ case '&':
+ string_append_sn(&dest, "&-", 2);
+ break;
+
+ case '/':
+ /* Citadel extension: / becomes |, because /
+ * isn't valid as part of an IMAP name. */
+
+ c = '|';
+ goto defaultcase;
+
+ case '\\':
+ /* Citadel extension: backslashes mark folder
+ * seperators in the IMAP subfolder emulation
+ * hack. We turn them into / characters,
+ * *except* if it's the last character in the
+ * string. */
+
+ if (*src != '\0')
+ c = '/';
+ /* fall through */
+
+ default:
+ defaultcase:
+ string_append_c(&dest, c);
+ }
+ }
+ else
+ {
+ if (state == 0)
+ {
+ string_append_c(&dest, '&');
+ state = 1;
+ }
+ v = (v << 16) | c;
+ i += 16;
+ while (i >= 6)
+ {
+ int x = (v >> (i-6)) & 0x3f;
+ string_append_c(&dest, utf7_alphabet[x]);
+ i -= 6;
+ }
+ }
+ }
+
+ if (state == 1)
+ utf7_closeb64(&dest, v, i);
+ /* syslog(LOG_DEBUG, " -> %s", destp); */
+ return string_end(&dest);
+}
+
+/* Convert from an IMAP-safe name back into a Citadel name. Returns the end of the destination. */
+
+static int cfrommap(int c);
+static char* fromimap(char* destp, char* destend, const char* src)
+{
+ struct string dest;
+ unsigned const char *p = (unsigned const char*) src;
+ int v = 0;
+ int i = 0;
+ int state = 0;
+ int c;
+
+ *destp = 0;
+ string_init(&dest, destp, destend-destp);
+ /* syslog(LOG_DEBUG, "fromimap %s", src); */
+
+ do {
+ c = *p++;
+ switch (state)
+ {
+ case 0:
+ /* US-ASCII characters. */
+
+ if (c == '&')
+ state = 1;
+ else
+ string_append_c(&dest, cfrommap(c));
+ break;
+
+ case 1:
+ if (c == '-')
+ {
+ string_append_c(&dest, '&');
+ state = 0;
+ }
+ else if (utf7_rank[c] != 0xff)
+ {
+ v = utf7_rank[c];
+ i = 6;
+ state = 2;
+ }
+ else
+ {
+ /* invalid char */
+ string_append_sn(&dest, "&-", 2);
+ state = 0;
+ }
+ break;
+
+ case 2:
+ if (c == '-')
+ state = 0;
+ else if (utf7_rank[c] != 0xFF)
+ {
+ v = (v<<6) | utf7_rank[c];
+ i += 6;
+ if (i >= 16)
+ {
+ int x = (v >> (i-16)) & 0xFFFF;
+ string_append_c(&dest, cfrommap(x));
+ i -= 16;
+ }
+ }
+ else
+ {
+ string_append_c(&dest, cfrommap(c));
+ state = 0;
+ }
+ break;
+ }
+ } while (c != '\0');
+
+ /* syslog(LOG_DEBUG, " -> %s", destp); */
+ return string_end(&dest);
+}
+
+/* Undoes the special character conversion. */
+static int cfrommap(int c)
+{
+ switch (c)
+ {
+ case '|': return '/';
+ case '/': return '\\';
+ }
+ return c;
+}
+
+
+/* Break a command down into tokens, unquoting any escaped characters. */
+void MakeStringOf(StrBuf *Buf, int skip)
+{
+ int i;
+ citimap_command *Cmd = &IMAP->Cmd;
+
+ for (i=skip; i<Cmd->num_parms; ++i) {
+ StrBufAppendBufPlain(Buf, Cmd->Params[i].Key, Cmd->Params[i].len, 0);
+ if (i < (Cmd->num_parms-1)) StrBufAppendBufPlain(Buf, HKEY(" "), 0);
+ }
+}
+
+
+void TokenCutRight(citimap_command *Cmd,
+ ConstStr *CutMe,
+ int n)
+{
+ const char *CutAt;
+
+ if (CutMe->len < n) {
+ CutAt = CutMe->Key;
+ CutMe->len = 0;
+ }
+ else {
+ CutAt = CutMe->Key + CutMe->len - n;
+ CutMe->len -= n;
+ }
+ StrBufPeek(Cmd->CmdBuf, CutAt, -1, '\0');
+}
+
+void TokenCutLeft(citimap_command *Cmd,
+ ConstStr *CutMe,
+ int n)
+{
+ if (CutMe->len < n) {
+ CutMe->Key += CutMe->len;
+ CutMe->len = 0;
+ }
+ else {
+ CutMe->Key += n;
+ CutMe->len -= n;
+ }
+}
+
+
+
+int CmdAdjust(citimap_command *Cmd,
+ int nArgs,
+ int Realloc)
+{
+ ConstStr *Params;
+ if (nArgs > Cmd->avail_parms) {
+ Params = (ConstStr*) malloc(sizeof(ConstStr) * nArgs);
+ if (Realloc) {
+ memcpy(Params,
+ Cmd->Params,
+ sizeof(ConstStr) * Cmd->avail_parms);
+
+ memset(Cmd->Params +
+ sizeof(ConstStr) * Cmd->avail_parms,
+ 0,
+ sizeof(ConstStr) * nArgs -
+ sizeof(ConstStr) * Cmd->avail_parms
+ );
+ }
+ else {
+ Cmd->num_parms = 0;
+ memset(Params, 0,
+ sizeof(ConstStr) * nArgs);
+ }
+ Cmd->avail_parms = nArgs;
+ if (Cmd->Params != NULL)
+ free (Cmd->Params);
+ Cmd->Params = Params;
+ }
+ else {
+ if (!Realloc) {
+ memset(Cmd->Params,
+ 0,
+ sizeof(ConstStr) * Cmd->avail_parms);
+ Cmd->num_parms = 0;
+ }
+ }
+ return Cmd->avail_parms;
+}
+
+int imap_parameterize(citimap_command *Cmd)
+{
+ int nArgs;
+ const char *In, *End;
+
+ In = ChrPtr(Cmd->CmdBuf);
+ End = In + StrLength(Cmd->CmdBuf);
+
+ /* we start with 10 chars per arg, maybe we need to realloc later. */
+ nArgs = StrLength(Cmd->CmdBuf) / 10 + 10;
+ nArgs = CmdAdjust(Cmd, nArgs, 0);
+ while (In < End)
+ {
+ /* Skip whitespace. */
+ while (isspace(*In))
+ In++;
+ if (*In == '\0')
+ break;
+
+ /* Found the start of a token. */
+
+ Cmd->Params[Cmd->num_parms].Key = In;
+
+ /* Read in the token. */
+
+ for (;;)
+ {
+ if (isspace(*In))
+ break;
+
+ if (*In == '\"')
+ {
+ /* Found a quoted section. */
+
+ Cmd->Params[Cmd->num_parms].Key++;
+ //In++;
+ for (;;)
+ {
+ In++;
+ if (*In == '\"') {
+ StrBufPeek(Cmd->CmdBuf, In, -1, '\0');
+ break;
+ }
+ else if (*In == '\\')
+ In++;
+
+ if (*In == '\0') {
+ Cmd->Params[Cmd->num_parms].len =
+ In - Cmd->Params[Cmd->num_parms].Key;
+ Cmd->num_parms++;
+ return Cmd->num_parms;
+ }
+ }
+ break;
+ }
+ else if (*In == '\\')
+ {
+ In++;
+ }
+
+ if (*In == '\0') {
+ Cmd->Params[Cmd->num_parms].len =
+ In - Cmd->Params[Cmd->num_parms].Key;
+ Cmd->num_parms++;
+ return Cmd->num_parms;
+ }
+ In++;
+ }
+ StrBufPeek(Cmd->CmdBuf, In, -1, '\0');
+ Cmd->Params[Cmd->num_parms].len =
+ In - Cmd->Params[Cmd->num_parms].Key;
+ if (Cmd->num_parms + 1 >= Cmd->avail_parms) {
+ nArgs = CmdAdjust(Cmd, nArgs * 2, 1);
+ }
+ Cmd->num_parms ++;
+ In++;
+ }
+ return Cmd->num_parms;
+}
+
+
+/* Convert a struct ctdlroom to an IMAP-compatible mailbox name. */
+long imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf)
+{
+ char* bufend = buf+bufsize;
+ struct floor *fl;
+ char* p = buf;
+ const char *pend;
+
+ /* For mailboxes, just do it straight.
+ * Do the Cyrus-compatible thing: all private folders are
+ * subfolders of INBOX. */
+
+ if (qrbuf->QRflags & QR_MAILBOX)
+ {
+ if (strcasecmp(qrbuf->QRname+11, MAILROOM) == 0)
+ {
+ pend = toimap(p, bufend, "INBOX");
+ return pend - buf;
+ }
+ else
+ {
+ p = toimap(p, bufend, "INBOX");
+ if (p < bufend)
+ *p++ = '/';
+ pend = toimap(p, bufend, qrbuf->QRname+11);
+ return pend - buf;
+ }
+ }
+ else
+ {
+ /* Otherwise, prefix the floor name as a "public folders" moniker. */
+
+ fl = CtdlGetCachedFloor(qrbuf->QRfloor);
+ p = toimap(p, bufend, fl->f_name);
+ if (p < bufend)
+ *p++ = '/';
+ pend = toimap(p, bufend, qrbuf->QRname);
+ return pend - buf;
+ }
+}
+
+/*
+ * Convert an inputted folder name to our best guess as to what an equivalent
+ * room name should be.
+ *
+ * If an error occurs, it returns -1. Otherwise...
+ *
+ * The lower eight bits of the return value are the floor number on which the
+ * room most likely resides. The upper eight bits may contain flags,
+ * including IR_MAILBOX if we're dealing with a personal room.
+ *
+ */
+int imap_roomname(char *rbuf, int bufsize, const char *foldername)
+{
+ int levels;
+ char floorname[ROOMNAMELEN*2];
+ char roomname[ROOMNAMELEN];
+ int i;
+ struct floor *fl;
+ int ret = (-1);
+
+ if (foldername == NULL)
+ return(-1);
+
+ /* Unmunge the entire string into the output buffer. */
+
+ fromimap(rbuf, rbuf+bufsize, foldername);
+
+ /* Is this an IMAP inbox? */
+
+ if (strncasecmp(rbuf, "INBOX", 5) == 0)
+ {
+ if (rbuf[5] == 0)
+ {
+ /* It's the system inbox. */
+
+ safestrncpy(rbuf, MAILROOM, bufsize);
+ ret = (0 | IR_MAILBOX);
+ goto exit;
+ }
+ else if (rbuf[5] == FDELIM)
+ {
+ /* It's another personal mail folder. */
+
+ safestrncpy(rbuf, rbuf+6, bufsize);
+ ret = (0 | IR_MAILBOX);
+ goto exit;
+ }
+
+ /* If we get here, the folder just happens to start with INBOX
+ * --- fall through. */
+ }
+
+ /* Is this a multi-level room name? */
+
+ levels = num_tokens(rbuf, FDELIM);
+ if (levels > 1)
+ {
+ long len;
+ /* Extract the main room name. */
+
+ len = extract_token(floorname, rbuf, 0, FDELIM, sizeof floorname);
+ if (len < 0) len = 0;
+ safestrncpy(roomname, &rbuf[len + 1], sizeof(roomname));
+
+ /* Try and find it on any floor. */
+
+ for (i = 0; i < MAXFLOORS; ++i)
+ {
+ fl = CtdlGetCachedFloor(i);
+ if (fl->f_flags & F_INUSE)
+ {
+ if (strcasecmp(floorname, fl->f_name) == 0)
+ {
+ /* Got it! */
+
+ safestrncpy(rbuf, roomname, bufsize);
+ ret = i;
+ goto exit;
+ }
+ }
+ }
+ }
+
+ /* Meh. It's either not a multi-level room name, or else we
+ * couldn't find it.
+ */
+ ret = (0 | IR_MAILBOX);
+
+exit:
+ syslog(LOG_DEBUG, "(That translates to \"%s\")", rbuf);
+ return(ret);
+}
+
+
+/*
+ * Determine whether the supplied string is a valid message set.
+ * If the string contains only numbers, colons, commas, and asterisks,
+ * return 1 for a valid message set. If any other character is found,
+ * return 0.
+ */
+int imap_is_message_set(const char *buf)
+{
+ int i;
+
+ if (buf == NULL)
+ return (0); /* stupidity checks */
+ if (IsEmptyStr(buf))
+ return (0);
+
+ if (!strcasecmp(buf, "ALL"))
+ return (1); /* macro? why? */
+
+ for (i = 0; buf[i]; ++i) { /* now start the scan */
+ if (
+ (!isdigit(buf[i]))
+ && (buf[i] != ':')
+ && (buf[i] != ',')
+ && (buf[i] != '*')
+ )
+ return (0);
+ }
+
+ return (1); /* looks like we're good */
+}
+
+
+/*
+ * imap_match.c, based on wildmat.c from INN
+ * hacked for Citadel/IMAP by Daniel Malament
+ */
+
+/* note: not all return statements use these; don't change them */
+#define WILDMAT_TRUE 1
+#define WILDMAT_FALSE 0
+#define WILDMAT_ABORT -1
+#define WILDMAT_DELIM '/'
+
+/*
+ * Match text and p, return TRUE, FALSE, or ABORT.
+ */
+static int do_imap_match(const char *supplied_text, const char *supplied_p)
+{
+ int matched, i;
+ char lcase_text[SIZ], lcase_p[SIZ];
+ char *text;
+ char *p;
+
+ /* Copy both strings and lowercase them, in order to
+ * make this entire operation case-insensitive.
+ */
+ for (i=0;
+ ((supplied_text[i] != '\0') &&
+ (i < sizeof(lcase_text)));
+ ++i)
+ lcase_text[i] = tolower(supplied_text[i]);
+ lcase_text[i] = '\0';
+
+ for (i=0;
+ ((supplied_p[i] != '\0') &&
+ (i < sizeof(lcase_p)));
+ ++i)
+ lcase_p[i] = tolower(supplied_p[i]);
+ lcase_p[i] = '\0';
+
+ /* Start matching */
+ for (p = lcase_p, text = lcase_text;
+ !IsEmptyStr(p) && !IsEmptyStr(text);
+ text++, p++) {
+ if ((*text == '\0') && (*p != '*') && (*p != '%')) {
+ return WILDMAT_ABORT;
+ }
+ switch (*p) {
+ default:
+ if (*text != *p) {
+ return WILDMAT_FALSE;
+ }
+ continue;
+ case '*':
+star:
+ while (++p, ((*p == '*') || (*p == '%'))) {
+ /* Consecutive stars or %'s act
+ * just like one star.
+ */
+ continue;
+ }
+ if (*p == '\0') {
+ /* Trailing star matches everything. */
+ return WILDMAT_TRUE;
+ }
+ while (*text) {
+ if ((matched = do_imap_match(text++, p))
+ != WILDMAT_FALSE) {
+ return matched;
+ }
+ }
+ return WILDMAT_ABORT;
+ case '%':
+ while (++p, (!IsEmptyStr(p) && ((*p == '*') || (*p == '%'))))
+ {
+ /* Consecutive %'s act just like one, but even
+ * a single star makes the sequence act like
+ * one star, instead.
+ */
+ if (*p == '*') {
+ goto star;
+ }
+ continue;
+ }
+ if (*p == '\0') {
+ /*
+ * Trailing % matches everything
+ * without a delimiter.
+ */
+ while (!IsEmptyStr(text)) {
+ if (*text == WILDMAT_DELIM) {
+ return WILDMAT_FALSE;
+ }
+ text++;
+ }
+ return WILDMAT_TRUE;
+ }
+ while (!IsEmptyStr(text) &&
+ /* make sure text - 1 isn't before lcase_p */
+ ((text == lcase_text) || (*(text - 1) != WILDMAT_DELIM)))
+ {
+ if ((matched = do_imap_match(text++, p))
+ != WILDMAT_FALSE) {
+ return matched;
+ }
+ }
+ return WILDMAT_ABORT;
+ }
+ }
+
+ if ((*text == '\0') && (*p == '\0')) return WILDMAT_TRUE;
+ else return WILDMAT_FALSE;
+}
+
+
+/*
+ * Support function for mailbox pattern name matching in LIST and LSUB
+ * Returns nonzero if the supplied mailbox name matches the supplied pattern.
+ */
+int imap_mailbox_matches_pattern(const char *pattern, char *mailboxname)
+{
+ /* handle just-star case quickly */
+ if ((pattern[0] == '*') && (pattern[1] == '\0')) {
+ return WILDMAT_TRUE;
+ }
+ return (do_imap_match(mailboxname, pattern) == WILDMAT_TRUE);
+}
+
+
+
+/*
+ * Compare an IMAP date string (date only, no time) to the date found in
+ * a Unix timestamp.
+ */
+int imap_datecmp(const char *datestr, time_t msgtime) {
+ char daystr[256];
+ char monthstr[256];
+ char yearstr[256];
+ int i;
+ int day, month, year;
+ int msgday, msgmonth, msgyear;
+ struct tm msgtm;
+
+ char *imap_datecmp_ascmonths[12] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ };
+
+ if (datestr == NULL) return(0);
+
+ /* Expecting a date in the form dd-Mmm-yyyy */
+ extract_token(daystr, datestr, 0, '-', sizeof daystr);
+ extract_token(monthstr, datestr, 1, '-', sizeof monthstr);
+ extract_token(yearstr, datestr, 2, '-', sizeof yearstr);
+
+ day = atoi(daystr);
+ year = atoi(yearstr);
+ month = 0;
+ for (i=0; i<12; ++i) {
+ if (!strcasecmp(monthstr, imap_datecmp_ascmonths[i])) {
+ month = i;
+ }
+ }
+
+ /* Extract day/month/year from message timestamp */
+ localtime_r(&msgtime, &msgtm);
+ msgday = msgtm.tm_mday;
+ msgmonth = msgtm.tm_mon;
+ msgyear = msgtm.tm_year + 1900;
+
+ /* Now start comparing */
+
+ if (year < msgyear) return(+1);
+ if (year > msgyear) return(-1);
+
+ if (month < msgmonth) return(+1);
+ if (month > msgmonth) return(-1);
+
+ if (day < msgday) return(+1);
+ if (day > msgday) return(-1);
+
+ return(0);
+}
+
+
+void IAPrintf(const char *Format, ...)
+{
+ va_list arg_ptr;
+
+ va_start(arg_ptr, Format);
+ StrBufVAppendPrintf(IMAP->Reply, Format, arg_ptr);
+ va_end(arg_ptr);
+}
+
+
+void iaputs(const char *Str, long Len)
+{
+ StrBufAppendBufPlain(IMAP->Reply, Str, Len, 0);
+}
+
+
+void ireply(const char *Msg, long len)
+{
+ citimap *Imap = IMAP;
+
+ StrBufAppendBufPlain(Imap->Reply,
+ CKEY(Imap->Cmd.Params[0]), 0);
+ StrBufAppendBufPlain(Imap->Reply,
+ HKEY(" "), 0);
+ StrBufAppendBufPlain(Imap->Reply,
+ Msg, len, 0);
+
+ StrBufAppendBufPlain(Imap->Reply,
+ HKEY("\r\n"), 0);
+
+}
+
+
+void IReplyPrintf(const char *Format, ...)
+{
+ citimap *Imap = IMAP;
+ va_list arg_ptr;
+
+
+ StrBufAppendBufPlain(Imap->Reply,
+ CKEY(Imap->Cmd.Params[0]), 0);
+
+ StrBufAppendBufPlain(Imap->Reply,
+ HKEY(" "), 0);
+
+ va_start(arg_ptr, Format);
+ StrBufVAppendPrintf(IMAP->Reply, Format, arg_ptr);
+ va_end(arg_ptr);
+
+ StrBufAppendBufPlain(Imap->Reply,
+ HKEY("\r\n"), 0);
+
+}
+
+
+/* Output a string to the IMAP client, either as a literal or quoted.
+ * (We do a literal if it has any double-quotes or backslashes.) */
+void IPutStr(const char *Msg, long Len)
+{
+ int i;
+ int is_literal = 0;
+ citimap *Imap = IMAP;
+
+
+ if ((Msg == NULL) || (Len == 0))
+ { /* yeah, we handle this */
+ StrBufAppendBufPlain(Imap->Reply, HKEY("NIL"), 0);
+ return;
+ }
+
+ for (i = 0; i < Len; ++i) {
+ if ((Msg[i] == '\"') || (Msg[i] == '\\'))
+ is_literal = 1;
+ }
+
+ if (is_literal) {
+ StrBufAppendPrintf(Imap->Reply, "{%ld}\r\n", Len);
+ StrBufAppendBufPlain(Imap->Reply, Msg, Len, 0);
+ } else {
+ StrBufAppendBufPlain(Imap->Reply,
+ HKEY("\""), 0);
+ StrBufAppendBufPlain(Imap->Reply,
+ Msg, Len, 0);
+ StrBufAppendBufPlain(Imap->Reply,
+ HKEY("\""), 0);
+ }
+}
+
+void IUnbuffer (void)
+{
+ citimap *Imap = IMAP;
+
+ cputbuf(Imap->Reply);
+ FlushStrBuf(Imap->Reply);
+}
--- /dev/null
+
+/*
+ * since we work with shifted pointers to ConstStrs in some places,
+ * we can't just say we want to cut the n'th of Cmd, we have to pass it in
+ * and rely on that CutMe references Cmd->CmdBuf; else peek won't work out
+ * and len will differ.
+ */
+void TokenCutRight(citimap_command *Cmd,
+ ConstStr *CutMe,
+ int n);
+/*
+ * since we just move Key around here, Cmd is just here so the syntax is identical.
+ */
+void TokenCutLeft(citimap_command *Cmd,
+ ConstStr *CutMe,
+ int n);
+void MakeStringOf(StrBuf *Buf, int skip);
+
+int CmdAdjust(citimap_command *Cmd,
+ int nArgs,
+ int Realloc);
+
+
+void imap_strout(ConstStr *args);
+void imap_strbuffer(StrBuf *Reply, ConstStr *args);
+void plain_imap_strbuffer(StrBuf *Reply, char *buf);
+int imap_parameterize(citimap_command *Cmd);
+long imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf);
+int imap_roomname(char *buf, int bufsize, const char *foldername);
+int imap_is_message_set(const char *);
+int imap_mailbox_matches_pattern(const char *pattern, char *mailboxname);
+int imap_datecmp(const char *datestr, time_t msgtime);
+
+
+/* Imap Append Printf, send to the outbuffer */
+void IAPrintf(const char *Format, ...) __attribute__((__format__(__printf__,1,2)));
+
+void iaputs(const char *Str, long Len);
+#define IAPuts(Msg) iaputs(HKEY(Msg))
+/* give it a naughty name since its ugly. */
+#define _iaputs(Msg) iaputs(Msg, strlen(Msg))
+
+/* outputs a static message prepended by the sequence no */
+void ireply(const char *Msg, long len);
+#define IReply(msg) ireply(HKEY(msg))
+/* outputs a dynamic message prepended by the sequence no */
+void IReplyPrintf(const char *Format, ...);
+
+
+/* output a string like that {%ld}%s */
+void IPutStr(const char *Msg, long Len);
+#define IPutCStr(_ConstStr) IPutStr(CKEY(_ConstStr))
+#define IPutCParamStr(n) IPutStr(CKEY(Params[n]))
+#define IPutMsgField(Which) IPutStr(CM_KEY(msg, Which))
+void IUnbuffer (void);
--- /dev/null
+/*
+ * IMAP server for the Citadel system
+ *
+ * Copyright (C) 2000-2022 by Art Cancro and others.
+ * This code is released under the terms of the GNU General Public License.
+ *
+ * WARNING: the IMAP protocol is badly designed. No implementation of it
+ * is perfect. Indeed, with so much gratuitous complexity, *all* IMAP
+ * implementations have bugs.
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "serv_imap.h"
+#include "imap_tools.h"
+#include "imap_list.h"
+#include "imap_fetch.h"
+#include "imap_search.h"
+#include "imap_store.h"
+#include "imap_acl.h"
+#include "imap_metadata.h"
+#include "imap_misc.h"
+
+#include "../../ctdl_module.h"
+HashList *ImapCmds = NULL;
+void registerImapCMD(const char *First, long FLen,
+ const char *Second, long SLen,
+ imap_handler H,
+ int Flags)
+{
+ imap_handler_hook *h;
+
+ h = (imap_handler_hook*) malloc(sizeof(imap_handler_hook));
+ memset(h, 0, sizeof(imap_handler_hook));
+
+ h->Flags = Flags;
+ h->h = H;
+ if (SLen == 0) {
+ Put(ImapCmds, First, FLen, h, NULL);
+ }
+ else {
+ char CMD[SIZ];
+ memcpy(CMD, First, FLen);
+ memcpy(CMD+FLen, Second, SLen);
+ CMD[FLen+SLen] = '\0';
+ Put(ImapCmds, CMD, FLen + SLen, h, NULL);
+ }
+}
+
+
+const imap_handler_hook *imap_lookup(int num_parms, ConstStr *Params)
+{
+ struct CitContext *CCC = CC;
+ void *v;
+ citimap *Imap = CCCIMAP;
+
+ if (num_parms < 1)
+ return NULL;
+
+ /* we abuse the Reply-buffer for uppercasing... */
+ StrBufPlain(Imap->Reply, CKEY(Params[1]));
+ StrBufUpCase(Imap->Reply);
+
+ syslog(LOG_DEBUG, "---- Looking up [%s] -----", ChrPtr(Imap->Reply));
+ if (GetHash(ImapCmds, SKEY(Imap->Reply), &v))
+ {
+ syslog(LOG_DEBUG, "Found.");
+ FlushStrBuf(Imap->Reply);
+ return (imap_handler_hook *) v;
+ }
+
+ if (num_parms == 1)
+ {
+ syslog(LOG_DEBUG, "NOT Found.");
+ FlushStrBuf(Imap->Reply);
+ return NULL;
+ }
+
+ syslog(LOG_DEBUG, "---- Looking up [%s] -----", ChrPtr(Imap->Reply));
+ StrBufAppendBufPlain(Imap->Reply, CKEY(Params[2]), 0);
+ StrBufUpCase(Imap->Reply);
+ if (GetHash(ImapCmds, SKEY(Imap->Reply), &v))
+ {
+ syslog(LOG_DEBUG, "Found.");
+ FlushStrBuf(Imap->Reply);
+ return (imap_handler_hook *) v;
+ }
+ syslog(LOG_DEBUG, "NOT Found.");
+ FlushStrBuf(Imap->Reply);
+ return NULL;
+}
+
+/* imap_rename() uses this struct containing list of rooms to rename */
+struct irl {
+ struct irl *next;
+ char irl_oldroom[ROOMNAMELEN];
+ char irl_newroom[ROOMNAMELEN];
+ int irl_newfloor;
+};
+
+/* Data which is passed between imap_rename() and imap_rename_backend() */
+typedef struct __irlparms {
+ const char *oldname;
+ long oldnamelen;
+ const char *newname;
+ long newnamelen;
+ struct irl **irl;
+}irlparms;
+
+
+/*
+ * If there is a message ID map in memory, free it
+ */
+void imap_free_msgids(void)
+{
+ citimap *Imap = IMAP;
+ if (Imap->msgids != NULL) {
+ free(Imap->msgids);
+ Imap->msgids = NULL;
+ Imap->num_msgs = 0;
+ Imap->num_alloc = 0;
+ }
+ if (Imap->flags != NULL) {
+ free(Imap->flags);
+ Imap->flags = NULL;
+ }
+ Imap->last_mtime = (-1);
+}
+
+
+/*
+ * If there is a transmitted message in memory, free it
+ */
+void imap_free_transmitted_message(void)
+{
+ FreeStrBuf(&IMAP->TransmittedMessage);
+}
+
+
+/*
+ * Set the \Seen, \Recent. and \Answered flags, based on the sequence
+ * sets stored in the visit record for this user/room. Note that we have
+ * to parse each sequence set manually here, because calling the utility
+ * function is_msg_in_sequence_set() over and over again is too expensive.
+ *
+ * first_msg should be set to 0 to rescan the flags for every message in the
+ * room, or some other value if we're only interested in an incremental
+ * update.
+ */
+void imap_set_seen_flags(int first_msg)
+{
+ citimap *Imap = IMAP;
+ visit vbuf;
+ int i;
+ int num_sets;
+ int s;
+ char setstr[64], lostr[64], histr[64];
+ long lo, hi;
+
+ if (Imap->num_msgs < 1) return;
+ CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+
+ for (i = first_msg; i < Imap->num_msgs; ++i) {
+ Imap->flags[i] = Imap->flags[i] & ~IMAP_SEEN;
+ Imap->flags[i] |= IMAP_RECENT;
+ Imap->flags[i] = Imap->flags[i] & ~IMAP_ANSWERED;
+ }
+
+ /*
+ * Do the "\Seen" flag.
+ * (Any message not "\Seen" is considered "\Recent".)
+ */
+ num_sets = num_tokens(vbuf.v_seen, ',');
+ for (s=0; s<num_sets; ++s) {
+ extract_token(setstr, vbuf.v_seen, s, ',', sizeof setstr);
+
+ extract_token(lostr, setstr, 0, ':', sizeof lostr);
+ if (num_tokens(setstr, ':') >= 2) {
+ extract_token(histr, setstr, 1, ':', sizeof histr);
+ if (!strcmp(histr, "*")) {
+ snprintf(histr, sizeof histr, "%ld", LONG_MAX);
+ }
+ }
+ else {
+ strcpy(histr, lostr);
+ }
+ lo = atol(lostr);
+ hi = atol(histr);
+
+ for (i = first_msg; i < Imap->num_msgs; ++i) {
+ if ((Imap->msgids[i] >= lo) && (Imap->msgids[i] <= hi)){
+ Imap->flags[i] |= IMAP_SEEN;
+ Imap->flags[i] = Imap->flags[i] & ~IMAP_RECENT;
+ }
+ }
+ }
+
+ /* Do the ANSWERED flag */
+ num_sets = num_tokens(vbuf.v_answered, ',');
+ for (s=0; s<num_sets; ++s) {
+ extract_token(setstr, vbuf.v_answered, s, ',', sizeof setstr);
+
+ extract_token(lostr, setstr, 0, ':', sizeof lostr);
+ if (num_tokens(setstr, ':') >= 2) {
+ extract_token(histr, setstr, 1, ':', sizeof histr);
+ if (!strcmp(histr, "*")) {
+ snprintf(histr, sizeof histr, "%ld", LONG_MAX);
+ }
+ }
+ else {
+ strcpy(histr, lostr);
+ }
+ lo = atol(lostr);
+ hi = atol(histr);
+
+ for (i = first_msg; i < Imap->num_msgs; ++i) {
+ if ((Imap->msgids[i] >= lo) && (Imap->msgids[i] <= hi)){
+ Imap->flags[i] |= IMAP_ANSWERED;
+ }
+ }
+ }
+
+}
+
+
+
+/*
+ * Back end for imap_load_msgids()
+ *
+ * Optimization: instead of calling realloc() to add each message, we
+ * allocate space in the list for REALLOC_INCREMENT messages at a time. This
+ * allows the mapping to proceed much faster.
+ */
+void imap_add_single_msgid(long msgnum, void *userdata)
+{
+ citimap *Imap = IMAP;
+
+ ++Imap->num_msgs;
+ if (Imap->num_msgs > Imap->num_alloc) {
+ Imap->num_alloc += REALLOC_INCREMENT;
+ Imap->msgids = realloc(Imap->msgids, (Imap->num_alloc * sizeof(long)) );
+ Imap->flags = realloc(Imap->flags, (Imap->num_alloc * sizeof(unsigned int)) );
+ }
+ Imap->msgids[Imap->num_msgs - 1] = msgnum;
+ Imap->flags[Imap->num_msgs - 1] = 0;
+}
+
+
+
+/*
+ * Set up a message ID map for the current room (folder)
+ */
+void imap_load_msgids(void)
+{
+ struct CitContext *CCC = CC;
+ struct cdbdata *cdbfr;
+ citimap *Imap = CCCIMAP;
+
+ if (Imap->selected == 0) {
+ syslog(LOG_ERR, "imap_load_msgids() can't run; no room selected");
+ return;
+ }
+
+ imap_free_msgids(); /* If there was already a map, free it */
+
+ /* Load the message list */
+ cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
+ if (cdbfr != NULL) {
+ Imap->msgids = (long*)cdbfr->ptr;
+ Imap->num_msgs = cdbfr->len / sizeof(long);
+ Imap->num_alloc = cdbfr->len / sizeof(long);
+ cdbfr->ptr = NULL;
+ cdbfr->len = 0;
+ cdb_free(cdbfr);
+ }
+
+ if (Imap->num_msgs) {
+ Imap->flags = malloc(Imap->num_alloc * sizeof(unsigned int));
+ memset(Imap->flags, 0, (Imap->num_alloc * sizeof(unsigned int)) );
+ }
+
+ imap_set_seen_flags(0);
+}
+
+
+/*
+ * Re-scan the selected room (folder) and see if it's been changed at all
+ */
+void imap_rescan_msgids(void)
+{
+ struct CitContext *CCC = CC;
+ citimap *Imap = CCCIMAP;
+ int original_num_msgs = 0;
+ long original_highest = 0L;
+ int i, j, jstart;
+ int message_still_exists;
+ struct cdbdata *cdbfr;
+ long *msglist = NULL;
+ int num_msgs = 0;
+ int num_recent = 0;
+
+ if (Imap->selected == 0) {
+ syslog(LOG_ERR, "imap_load_msgids() can't run; no room selected");
+ return;
+ }
+
+ /*
+ * Check to see if the room's contents have changed.
+ * If not, we can avoid this rescan.
+ */
+ CtdlGetRoom(&CC->room, CC->room.QRname);
+ if (Imap->last_mtime == CC->room.QRmtime) { /* No changes! */
+ return;
+ }
+
+ /* Load the *current* message list from disk, so we can compare it
+ * to what we have in memory.
+ */
+ cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
+ if (cdbfr != NULL) {
+ msglist = (long*)cdbfr->ptr;
+ cdbfr->ptr = NULL;
+ num_msgs = cdbfr->len / sizeof(long);
+ cdbfr->len = 0;
+ cdb_free(cdbfr);
+ } else {
+ num_msgs = 0;
+ }
+
+ /*
+ * Check to see if any of the messages we know about have been expunged
+ */
+ if (Imap->num_msgs > 0) {
+ jstart = 0;
+ for (i = 0; i < Imap->num_msgs; ++i) {
+
+ message_still_exists = 0;
+ if (num_msgs > 0) {
+ for (j = jstart; j < num_msgs; ++j) {
+ if (msglist[j] == Imap->msgids[i]) {
+ message_still_exists = 1;
+ jstart = j;
+ break;
+ }
+ }
+ }
+
+ if (message_still_exists == 0) {
+ IAPrintf("* %d EXPUNGE\r\n", i + 1);
+
+ /* Here's some nice stupid nonsense. When a
+ * message is expunged, we have to slide all
+ * the existing messages up in the message
+ * array.
+ */
+ --Imap->num_msgs;
+ memmove(&Imap->msgids[i],
+ &Imap->msgids[i + 1],
+ (sizeof(long) *
+ (Imap->num_msgs - i)));
+ memmove(&Imap->flags[i],
+ &Imap->flags[i + 1],
+ (sizeof(unsigned int) *
+ (Imap->num_msgs - i)));
+ --i;
+ }
+
+ }
+ }
+
+ /*
+ * Remember how many messages were here before we re-scanned.
+ */
+ original_num_msgs = Imap->num_msgs;
+ if (Imap->num_msgs > 0) {
+ original_highest = Imap->msgids[Imap->num_msgs - 1];
+ } else {
+ original_highest = 0L;
+ }
+
+ /*
+ * Now peruse the room for *new* messages only.
+ * This logic is probably the cause of Bug # 368
+ * [ http://bugzilla.citadel.org/show_bug.cgi?id=368 ]
+ */
+ if (num_msgs > 0) {
+ for (j = 0; j < num_msgs; ++j) {
+ if (msglist[j] > original_highest) {
+ imap_add_single_msgid(msglist[j], NULL);
+ }
+ }
+ }
+ imap_set_seen_flags(original_num_msgs);
+
+ /*
+ * If new messages have arrived, tell the client about them.
+ */
+ if (Imap->num_msgs > original_num_msgs) {
+
+ for (j = 0; j < num_msgs; ++j) {
+ if (Imap->flags[j] & IMAP_RECENT) {
+ ++num_recent;
+ }
+ }
+
+ IAPrintf("* %d EXISTS\r\n", Imap->num_msgs);
+ IAPrintf("* %d RECENT\r\n", num_recent);
+ }
+
+ if (msglist != NULL) {
+ free(msglist);
+ }
+ Imap->last_mtime = CC->room.QRmtime;
+}
+
+
+/*
+ * This cleanup function blows away the temporary memory and files used by
+ * the IMAP server.
+ */
+void imap_cleanup_function(void)
+{
+ struct CitContext *CCC = CC;
+ citimap *Imap = CCCIMAP;
+
+ /* Don't do this stuff if this is not a Imap session! */
+ if (CC->h_command_function != imap_command_loop)
+ return;
+
+ /* If there is a mailbox selected, auto-expunge it. */
+ if (Imap->selected) {
+ imap_do_expunge();
+ }
+
+ syslog(LOG_DEBUG, "Performing IMAP cleanup hook");
+ imap_free_msgids();
+ imap_free_transmitted_message();
+
+ if (Imap->cached_rfc822 != NULL) {
+ FreeStrBuf(&Imap->cached_rfc822);
+ Imap->cached_rfc822_msgnum = (-1);
+ Imap->cached_rfc822_withbody = 0;
+ }
+
+ if (Imap->cached_body != NULL) {
+ free(Imap->cached_body);
+ Imap->cached_body = NULL;
+ Imap->cached_body_len = 0;
+ Imap->cached_bodymsgnum = (-1);
+ }
+ FreeStrBuf(&Imap->Cmd.CmdBuf);
+ FreeStrBuf(&Imap->Reply);
+ if (Imap->Cmd.Params != NULL) free(Imap->Cmd.Params);
+ free(Imap);
+ syslog(LOG_DEBUG, "Finished IMAP cleanup hook");
+}
+
+
+/*
+ * Does the actual work of the CAPABILITY command (because we need to
+ * output this stuff in other places as well)
+ */
+void imap_output_capability_string(void) {
+ IAPuts("CAPABILITY IMAP4REV1 NAMESPACE ID AUTH=PLAIN AUTH=LOGIN UIDPLUS");
+
+#ifdef HAVE_OPENSSL
+ if (!CC->redirect_ssl) IAPuts(" STARTTLS");
+#endif
+
+#ifndef DISABLE_IMAP_ACL
+ IAPuts(" ACL");
+#endif
+
+ /* We are building a partial implementation of METADATA for the sole purpose
+ * of interoperating with the ical/vcard version of the Bynari Insight Connector.
+ * It is not a full RFC5464 implementation, but it should refuse non-Bynari
+ * metadata in a compatible and graceful way.
+ */
+ IAPuts(" METADATA");
+
+ /*
+ * LIST-EXTENDED was originally going to be required by the METADATA extension.
+ * It was mercifully removed prior to the finalization of RFC5464. We started
+ * implementing this but stopped when we learned that it would not be needed.
+ * If you uncomment this declaration you are responsible for writing a lot of new
+ * code.
+ *
+ * IAPuts(" LIST-EXTENDED")
+ */
+}
+
+
+/*
+ * implements the CAPABILITY command
+ */
+void imap_capability(int num_parms, ConstStr *Params)
+{
+ IAPuts("* ");
+ imap_output_capability_string();
+ IAPuts("\r\n");
+ IReply("OK CAPABILITY completed");
+}
+
+
+/*
+ * Implements the ID command (specified by RFC2971)
+ *
+ * We ignore the client-supplied information, and output a NIL response.
+ * Although this is technically a valid implementation of the extension, it
+ * is quite useless. It exists only so that we may see which clients are
+ * making use of this extension.
+ *
+ */
+void imap_id(int num_parms, ConstStr *Params)
+{
+ IAPuts("* ID NIL\r\n");
+ IReply("OK ID completed");
+}
+
+
+/*
+ * Here's where our IMAP session begins its happy day.
+ */
+void imap_greeting(void)
+{
+ citimap *Imap;
+ CitContext *CCC = CC;
+
+ strcpy(CCC->cs_clientname, "IMAP session");
+ CCC->session_specific_data = malloc(sizeof(citimap));
+ Imap = (citimap *)CCC->session_specific_data;
+ memset(Imap, 0, sizeof(citimap));
+ Imap->authstate = imap_as_normal;
+ Imap->cached_rfc822_msgnum = (-1);
+ Imap->cached_rfc822_withbody = 0;
+ Imap->Reply = NewStrBufPlain(NULL, SIZ * 10); /* 40k */
+
+ if (CCC->nologin)
+ {
+ IAPuts("* BYE; Server busy, try later\r\n");
+ CCC->kill_me = KILLME_NOLOGIN;
+ IUnbuffer();
+ return;
+ }
+
+ IAPuts("* OK [");
+ imap_output_capability_string();
+ IAPrintf("] %s IMAP4rev1 %s ready\r\n", CtdlGetConfigStr("c_fqdn"), CITADEL);
+ IUnbuffer();
+}
+
+
+/*
+ * IMAPS is just like IMAP, except it goes crypto right away.
+ */
+void imaps_greeting(void) {
+ CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
+#ifdef HAVE_OPENSSL
+ if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO; /* kill session if no crypto */
+#endif
+ imap_greeting();
+}
+
+
+/*
+ * implements the LOGIN command (ordinary username/password login)
+ */
+void imap_login(int num_parms, ConstStr *Params)
+{
+
+ switch (num_parms) {
+ case 3:
+ if (Params[2].Key[0] == '{') {
+ IAPuts("+ go ahead\r\n");
+ IMAP->authstate = imap_as_expecting_multilineusername;
+ strcpy(IMAP->authseq, Params[0].Key);
+ return;
+ }
+ else {
+ IReply("BAD incorrect number of parameters");
+ return;
+ }
+ case 4:
+ if (CtdlLoginExistingUser(Params[2].Key) == login_ok) {
+ if (CtdlTryPassword(Params[3].Key, Params[3].len) == pass_ok) {
+ /* hm, thats not doable by IReply :-( */
+ IAPrintf("%s OK [", Params[0].Key);
+ imap_output_capability_string();
+ IAPrintf("] Hello, %s\r\n", CC->user.fullname);
+ return;
+ }
+ else
+ {
+ IReplyPrintf("NO AUTHENTICATE %s failed", Params[3].Key);
+ return;
+ }
+ }
+
+ IReply("BAD Login incorrect");
+ return;
+ default:
+ IReply("BAD incorrect number of parameters");
+ return;
+ }
+
+}
+
+
+/*
+ * Implements the AUTHENTICATE command
+ */
+void imap_authenticate(int num_parms, ConstStr *Params)
+{
+ char UsrBuf[SIZ];
+
+ if (num_parms != 3) {
+ IReply("BAD incorrect number of parameters");
+ return;
+ }
+
+ if (CC->logged_in) {
+ IReply("BAD Already logged in.");
+ return;
+ }
+
+ if (!strcasecmp(Params[2].Key, "LOGIN")) {
+ size_t len = CtdlEncodeBase64(UsrBuf, "Username:", 9, 0);
+ if (UsrBuf[len - 1] == '\n') {
+ UsrBuf[len - 1] = '\0';
+ }
+
+ IAPrintf("+ %s\r\n", UsrBuf);
+ IMAP->authstate = imap_as_expecting_username;
+ strcpy(IMAP->authseq, Params[0].Key);
+ return;
+ }
+
+ if (!strcasecmp(Params[2].Key, "PLAIN")) {
+ // size_t len = CtdlEncodeBase64(UsrBuf, "Username:", 9, 0);
+ // if (UsrBuf[len - 1] == '\n') {
+ // UsrBuf[len - 1] = '\0';
+ // }
+ // IAPuts("+ %s\r\n", UsrBuf);
+ IAPuts("+ \r\n");
+ IMAP->authstate = imap_as_expecting_plainauth;
+ strcpy(IMAP->authseq, Params[0].Key);
+ return;
+ }
+
+ else {
+ IReplyPrintf("NO AUTHENTICATE %s failed",
+ Params[1].Key);
+ }
+}
+
+
+void imap_auth_plain(void)
+{
+ citimap *Imap = IMAP;
+ const char *decoded_authstring;
+ char ident[256] = "";
+ char user[256] = "";
+ char pass[256] = "";
+ int result;
+ long decoded_len;
+ long len = 0;
+ long plen = 0;
+
+ memset(pass, 0, sizeof(pass));
+ decoded_len = StrBufDecodeBase64(Imap->Cmd.CmdBuf);
+
+ if (decoded_len > 0)
+ {
+ decoded_authstring = ChrPtr(Imap->Cmd.CmdBuf);
+
+ len = safestrncpy(ident, decoded_authstring, sizeof ident);
+
+ decoded_len -= len - 1;
+ decoded_authstring += len + 1;
+
+ if (decoded_len > 0)
+ {
+ len = safestrncpy(user, decoded_authstring, sizeof user);
+
+ decoded_authstring += len + 1;
+ decoded_len -= len - 1;
+ }
+
+ if (decoded_len > 0)
+ {
+ plen = safestrncpy(pass, decoded_authstring, sizeof pass);
+
+ if (plen < 0)
+ plen = sizeof(pass) - 1;
+ }
+ }
+ Imap->authstate = imap_as_normal;
+
+ if (!IsEmptyStr(ident)) {
+ result = CtdlLoginExistingUser(ident);
+ }
+ else {
+ result = CtdlLoginExistingUser(user);
+ }
+
+ if (result == login_ok) {
+ if (CtdlTryPassword(pass, plen) == pass_ok) {
+ IAPrintf("%s OK authentication succeeded\r\n", Imap->authseq);
+ return;
+ }
+ }
+ IAPrintf("%s NO authentication failed\r\n", Imap->authseq);
+}
+
+
+void imap_auth_login_user(long state)
+{
+ char PWBuf[SIZ];
+ citimap *Imap = IMAP;
+
+ switch (state){
+ case imap_as_expecting_username:
+ StrBufDecodeBase64(Imap->Cmd.CmdBuf);
+ CtdlLoginExistingUser(ChrPtr(Imap->Cmd.CmdBuf));
+ size_t len = CtdlEncodeBase64(PWBuf, "Password:", 9, 0);
+ if (PWBuf[len - 1] == '\n') {
+ PWBuf[len - 1] = '\0';
+ }
+
+ IAPrintf("+ %s\r\n", PWBuf);
+
+ Imap->authstate = imap_as_expecting_password;
+ return;
+ case imap_as_expecting_multilineusername:
+ extract_token(PWBuf, ChrPtr(Imap->Cmd.CmdBuf), 1, ' ', sizeof(PWBuf));
+ CtdlLoginExistingUser(ChrPtr(Imap->Cmd.CmdBuf));
+ IAPuts("+ go ahead\r\n");
+ Imap->authstate = imap_as_expecting_multilinepassword;
+ return;
+ }
+}
+
+
+void imap_auth_login_pass(long state)
+{
+ citimap *Imap = IMAP;
+ const char *pass = NULL;
+ long len = 0;
+
+ switch (state) {
+ default:
+ case imap_as_expecting_password:
+ StrBufDecodeBase64(Imap->Cmd.CmdBuf);
+ pass = ChrPtr(Imap->Cmd.CmdBuf);
+ len = StrLength(Imap->Cmd.CmdBuf);
+ break;
+ case imap_as_expecting_multilinepassword:
+ pass = ChrPtr(Imap->Cmd.CmdBuf);
+ len = StrLength(Imap->Cmd.CmdBuf);
+ break;
+ }
+ if (len > USERNAME_SIZE)
+ StrBufCutAt(Imap->Cmd.CmdBuf, USERNAME_SIZE, NULL);
+
+ if (CtdlTryPassword(pass, len) == pass_ok) {
+ IAPrintf("%s OK authentication succeeded\r\n", Imap->authseq);
+ } else {
+ IAPrintf("%s NO authentication failed\r\n", Imap->authseq);
+ }
+ Imap->authstate = imap_as_normal;
+ return;
+}
+
+
+/*
+ * implements the STARTTLS command (Citadel API version)
+ */
+void imap_starttls(int num_parms, ConstStr *Params)
+{
+ char ok_response[SIZ];
+ char nosup_response[SIZ];
+ char error_response[SIZ];
+
+ snprintf(ok_response, SIZ, "%s OK begin TLS negotiation now\r\n", Params[0].Key);
+ snprintf(nosup_response, SIZ, "%s NO TLS not supported here\r\n", Params[0].Key);
+ snprintf(error_response, SIZ, "%s BAD Internal error\r\n", Params[0].Key);
+ CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
+}
+
+
+/*
+ * implements the SELECT command
+ */
+void imap_select(int num_parms, ConstStr *Params)
+{
+ citimap *Imap = IMAP;
+ char towhere[ROOMNAMELEN];
+ char augmented_roomname[ROOMNAMELEN];
+ int c = 0;
+ int ok = 0;
+ int ra = 0;
+ struct ctdlroom QRscratch;
+ int msgs, new;
+ int i;
+
+ /* Convert the supplied folder name to a roomname */
+ i = imap_roomname(towhere, sizeof towhere, Params[2].Key);
+ if (i < 0) {
+ IReply("NO Invalid mailbox name.");
+ Imap->selected = 0;
+ return;
+ }
+
+ /* First try a regular match */
+ c = CtdlGetRoom(&QRscratch, towhere);
+
+ /* Then try a mailbox name match */
+ if (c != 0) {
+ CtdlMailboxName(augmented_roomname, sizeof augmented_roomname, &CC->user, towhere);
+ c = CtdlGetRoom(&QRscratch, augmented_roomname);
+ if (c == 0) {
+ safestrncpy(towhere, augmented_roomname, sizeof(towhere));
+ }
+ }
+
+ /* If the room exists, check security/access */
+ if (c == 0) {
+ /* See if there is an existing user/room relationship */
+ CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL);
+
+ /* normal clients have to pass through security */
+ if (ra & UA_KNOWN) {
+ ok = 1;
+ }
+ }
+
+ /* Fail here if no such room */
+ if (!ok) {
+ IReply("NO ... no such room, or access denied");
+ return;
+ }
+
+ /* If we already had some other folder selected, auto-expunge it */
+ imap_do_expunge();
+
+ /*
+ * CtdlUserGoto() formally takes us to the desired room, happily returning
+ * the number of messages and number of new messages.
+ */
+ memcpy(&CC->room, &QRscratch, sizeof(struct ctdlroom));
+ CtdlUserGoto(NULL, 0, 0, &msgs, &new, NULL, NULL);
+ Imap->selected = 1;
+
+ if (!strcasecmp(Params[1].Key, "EXAMINE")) {
+ Imap->readonly = 1;
+ } else {
+ Imap->readonly = 0;
+ }
+
+ imap_load_msgids();
+ Imap->last_mtime = CC->room.QRmtime;
+
+ IAPrintf("* %d EXISTS\r\n", msgs);
+ IAPrintf("* %d RECENT\r\n", new);
+
+ IAPrintf("* OK [UIDVALIDITY %ld] UID validity status\r\n", GLOBAL_UIDVALIDITY_VALUE);
+ IAPrintf("* OK [UIDNEXT %ld] Predicted next UID\r\n", CtdlGetConfigLong("MMhighest") + 1);
+
+ /* Technically, \Deleted is a valid flag, but not a permanent flag,
+ * because we don't maintain its state across sessions. Citadel
+ * automatically expunges mailboxes when they are de-selected.
+ *
+ * Unfortunately, omitting \Deleted as a PERMANENTFLAGS flag causes
+ * some clients (particularly Thunderbird) to misbehave -- they simply
+ * elect not to transmit the flag at all. So we have to advertise
+ * \Deleted as a PERMANENTFLAGS flag, even though it technically isn't.
+ */
+ IAPuts("* FLAGS (\\Deleted \\Seen \\Answered)\r\n");
+ IAPuts("* OK [PERMANENTFLAGS (\\Deleted \\Seen \\Answered)] permanent flags\r\n");
+
+ IReplyPrintf("OK [%s] %s completed",
+ (Imap->readonly ? "READ-ONLY" : "READ-WRITE"), Params[1].Key
+ );
+}
+
+
+/*
+ * Does the real work for expunge.
+ */
+int imap_do_expunge(void)
+{
+ struct CitContext *CCC = CC;
+ citimap *Imap = CCCIMAP;
+ int i;
+ int num_expunged = 0;
+ long *delmsgs = NULL;
+ int num_delmsgs = 0;
+
+ syslog(LOG_DEBUG, "imap_do_expunge() called");
+ if (Imap->selected == 0) {
+ return (0);
+ }
+
+ if (Imap->num_msgs > 0) {
+ delmsgs = malloc(Imap->num_msgs * sizeof(long));
+ for (i = 0; i < Imap->num_msgs; ++i) {
+ if (Imap->flags[i] & IMAP_DELETED) {
+ delmsgs[num_delmsgs++] = Imap->msgids[i];
+ }
+ }
+ if (num_delmsgs > 0) {
+ CtdlDeleteMessages(CC->room.QRname, delmsgs, num_delmsgs, "");
+ }
+ num_expunged += num_delmsgs;
+ free(delmsgs);
+ }
+
+ if (num_expunged > 0) {
+ imap_rescan_msgids();
+ }
+
+ syslog(LOG_DEBUG, "Expunged %d messages from <%s>", num_expunged, CC->room.QRname);
+ return (num_expunged);
+}
+
+
+/*
+ * implements the EXPUNGE command syntax
+ */
+void imap_expunge(int num_parms, ConstStr *Params)
+{
+ int num_expunged = 0;
+
+ num_expunged = imap_do_expunge();
+ IReplyPrintf("OK expunged %d messages.", num_expunged);
+}
+
+
+/*
+ * implements the CLOSE command
+ */
+void imap_close(int num_parms, ConstStr *Params)
+{
+
+ /* Yes, we always expunge on close. */
+ if (IMAP->selected) {
+ imap_do_expunge();
+ }
+
+ IMAP->selected = 0;
+ IMAP->readonly = 0;
+ imap_free_msgids();
+ IReply("OK CLOSE completed");
+}
+
+
+/*
+ * Implements the NAMESPACE command.
+ */
+void imap_namespace(int num_parms, ConstStr *Params)
+{
+ long len;
+ int i;
+ struct floor *fl;
+ int floors = 0;
+ char Namespace[SIZ];
+
+ IAPuts("* NAMESPACE ");
+
+ /* All personal folders are subordinate to INBOX. */
+ IAPuts("((\"INBOX/\" \"/\")) ");
+
+ /* Other users' folders ... coming soon! FIXME */
+ IAPuts("NIL ");
+
+ /* Show all floors as shared namespaces. Neato! */
+ IAPuts("(");
+ for (i = 0; i < MAXFLOORS; ++i) {
+ fl = CtdlGetCachedFloor(i);
+ if (fl->f_flags & F_INUSE) {
+ /* if (floors > 0) IAPuts(" "); samjam says this confuses javamail */
+ IAPuts("(");
+ len = snprintf(Namespace, sizeof(Namespace), "%s/", fl->f_name);
+ IPutStr(Namespace, len);
+ IAPuts(" \"/\")");
+ ++floors;
+ }
+ }
+ IAPuts(")");
+
+ /* Wind it up with a newline and a completion message. */
+ IAPuts("\r\n");
+ IReply("OK NAMESPACE completed");
+}
+
+
+/*
+ * Implements the CREATE command
+ *
+ */
+void imap_create(int num_parms, ConstStr *Params)
+{
+ int ret;
+ char roomname[ROOMNAMELEN];
+ int floornum;
+ int flags;
+ int newroomtype = 0;
+ int newroomview = 0;
+ char *notification_message = NULL;
+
+ if (num_parms < 3) {
+ IReply("NO A foder name must be specified");
+ return;
+ }
+
+ if (strchr(Params[2].Key, '\\') != NULL) {
+ IReply("NO Invalid character in folder name");
+ syslog(LOG_ERR, "invalid character in folder name");
+ return;
+ }
+
+ ret = imap_roomname(roomname, sizeof roomname, Params[2].Key);
+ if (ret < 0) {
+ IReply("NO Invalid mailbox name or location");
+ syslog(LOG_ERR, "invalid mailbox name or location");
+ return;
+ }
+ floornum = (ret & 0x00ff); /* lower 8 bits = floor number */
+ flags = (ret & 0xff00); /* upper 8 bits = flags */
+
+ if (flags & IR_MAILBOX) {
+ if (strncasecmp(Params[2].Key, "INBOX/", 6)) {
+ IReply("NO Personal folders must be created under INBOX");
+ syslog(LOG_ERR, "not subordinate to inbox");
+ return;
+ }
+ }
+
+ if (flags & IR_MAILBOX) {
+ newroomtype = 4; /* private mailbox */
+ newroomview = VIEW_MAILBOX;
+ } else {
+ newroomtype = 0; /* public folder */
+ newroomview = VIEW_BBS;
+ }
+
+ syslog(LOG_INFO, "Create new room <%s> on floor <%d> with type <%d>",
+ roomname, floornum, newroomtype);
+
+ ret = CtdlCreateRoom(roomname, newroomtype, "", floornum, 1, 0, newroomview);
+ if (ret == 0) {
+ /*** DO NOT CHANGE THIS ERROR MESSAGE IN ANY WAY! BYNARI CONNECTOR DEPENDS ON IT! ***/
+ IReply("NO Mailbox already exists, or create failed");
+ } else {
+ IReply("OK CREATE completed");
+ /* post a message in Aide> describing the new room */
+ notification_message = malloc(1024);
+ snprintf(notification_message, 1024,
+ "A new room called \"%s\" has been created by %s%s%s%s\n",
+ roomname,
+ CC->user.fullname,
+ ((ret & QR_MAILBOX) ? " [personal]" : ""),
+ ((ret & QR_PRIVATE) ? " [private]" : ""),
+ ((ret & QR_GUESSNAME) ? " [hidden]" : "")
+ );
+ CtdlAideMessage(notification_message, "Room Creation Message");
+ free(notification_message);
+ }
+ syslog(LOG_DEBUG, "imap_create() completed");
+}
+
+
+/*
+ * Locate a room by its IMAP folder name, and check access to it.
+ * If zapped_ok is nonzero, we can also look for the room in the zapped list.
+ */
+int imap_grabroom(char *returned_roomname, const char *foldername, int zapped_ok)
+{
+ int ret;
+ char augmented_roomname[ROOMNAMELEN];
+ char roomname[ROOMNAMELEN];
+ int c;
+ struct ctdlroom QRscratch;
+ int ra;
+ int ok = 0;
+
+ ret = imap_roomname(roomname, sizeof roomname, foldername);
+ if (ret < 0) {
+ return (1);
+ }
+
+ /* First try a regular match */
+ c = CtdlGetRoom(&QRscratch, roomname);
+
+ /* Then try a mailbox name match */
+ if (c != 0) {
+ CtdlMailboxName(augmented_roomname, sizeof augmented_roomname,
+ &CC->user, roomname);
+ c = CtdlGetRoom(&QRscratch, augmented_roomname);
+ if (c == 0)
+ safestrncpy(roomname, augmented_roomname, sizeof(roomname));
+ }
+
+ /* If the room exists, check security/access */
+ if (c == 0) {
+ /* See if there is an existing user/room relationship */
+ CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL);
+
+ /* normal clients have to pass through security */
+ if (ra & UA_KNOWN) {
+ ok = 1;
+ }
+ if ((zapped_ok) && (ra & UA_ZAPPED)) {
+ ok = 1;
+ }
+ }
+
+ /* Fail here if no such room */
+ if (!ok) {
+ strcpy(returned_roomname, "");
+ return (2);
+ } else {
+ safestrncpy(returned_roomname, QRscratch.QRname, ROOMNAMELEN);
+ return (0);
+ }
+}
+
+
+/*
+ * Implements the STATUS command (sort of)
+ *
+ */
+void imap_status(int num_parms, ConstStr *Params)
+{
+ long len;
+ int ret;
+ char roomname[ROOMNAMELEN];
+ char imaproomname[SIZ];
+ char savedroom[ROOMNAMELEN];
+ int msgs, new;
+
+ ret = imap_grabroom(roomname, Params[2].Key, 1);
+ if (ret != 0) {
+ IReply("NO Invalid mailbox name or location, or access denied");
+ return;
+ }
+
+ /*
+ * CtdlUserGoto() formally takes us to the desired room, happily returning
+ * the number of messages and number of new messages. (If another
+ * folder is selected, save its name so we can return there!!!!!)
+ */
+ if (IMAP->selected) {
+ strcpy(savedroom, CC->room.QRname);
+ }
+ CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
+
+ /*
+ * Tell the client what it wants to know. In fact, tell it *more* than
+ * it wants to know. We happily IGnore the supplied status data item
+ * names and simply spew all possible data items. It's far easier to
+ * code and probably saves us some processing time too.
+ */
+ len = imap_mailboxname(imaproomname, sizeof imaproomname, &CC->room);
+ IAPuts("* STATUS ");
+ IPutStr(imaproomname, len);
+ IAPrintf(" (MESSAGES %d ", msgs);
+ IAPrintf("RECENT %d ", new); /* Initially, new==recent */
+ IAPrintf("UIDNEXT %ld ", CtdlGetConfigLong("MMhighest") + 1);
+ IAPrintf("UNSEEN %d)\r\n", new);
+
+ /*
+ * If another folder is selected, go back to that room so we can resume
+ * our happy day without violent explosions.
+ */
+ if (IMAP->selected) {
+ CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
+ }
+
+ /*
+ * Oooh, look, we're done!
+ */
+ IReply("OK STATUS completed");
+}
+
+
+/*
+ * Implements the SUBSCRIBE command
+ *
+ */
+void imap_subscribe(int num_parms, ConstStr *Params)
+{
+ int ret;
+ char roomname[ROOMNAMELEN];
+ char savedroom[ROOMNAMELEN];
+ int msgs, new;
+
+ ret = imap_grabroom(roomname, Params[2].Key, 1);
+ if (ret != 0) {
+ IReplyPrintf(
+ "NO Error %d: invalid mailbox name or location, or access denied",
+ ret
+ );
+ return;
+ }
+
+ /*
+ * CtdlUserGoto() formally takes us to the desired room, which has the side
+ * effect of marking the room as not-zapped ... exactly the effect
+ * we're looking for.
+ */
+ if (IMAP->selected) {
+ strcpy(savedroom, CC->room.QRname);
+ }
+ CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
+
+ /*
+ * If another folder is selected, go back to that room so we can resume
+ * our happy day without violent explosions.
+ */
+ if (IMAP->selected) {
+ CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
+ }
+
+ IReply("OK SUBSCRIBE completed");
+}
+
+
+/*
+ * Implements the UNSUBSCRIBE command
+ *
+ */
+void imap_unsubscribe(int num_parms, ConstStr *Params)
+{
+ int ret;
+ char roomname[ROOMNAMELEN];
+ char savedroom[ROOMNAMELEN];
+ int msgs, new;
+
+ ret = imap_grabroom(roomname, Params[2].Key, 1);
+ if (ret != 0) {
+ IReply("NO Invalid mailbox name or location, or access denied");
+ return;
+ }
+
+ /*
+ * CtdlUserGoto() formally takes us to the desired room.
+ */
+ if (IMAP->selected) {
+ strcpy(savedroom, CC->room.QRname);
+ }
+ CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
+
+ /*
+ * Now make the API call to zap the room
+ */
+ if (CtdlForgetThisRoom() == 0) {
+ IReply("OK UNSUBSCRIBE completed");
+ } else {
+ IReply("NO You may not unsubscribe from this folder.");
+ }
+
+ /*
+ * If another folder is selected, go back to that room so we can resume
+ * our happy day without violent explosions.
+ */
+ if (IMAP->selected) {
+ CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
+ }
+}
+
+
+/*
+ * Implements the DELETE command
+ *
+ */
+void imap_delete(int num_parms, ConstStr *Params)
+{
+ int ret;
+ char roomname[ROOMNAMELEN];
+ char savedroom[ROOMNAMELEN];
+ int msgs, new;
+
+ ret = imap_grabroom(roomname, Params[2].Key, 1);
+ if (ret != 0) {
+ IReply("NO Invalid mailbox name, or access denied");
+ return;
+ }
+
+ /*
+ * CtdlUserGoto() formally takes us to the desired room, happily returning
+ * the number of messages and number of new messages. (If another
+ * folder is selected, save its name so we can return there!!!!!)
+ */
+ if (IMAP->selected) {
+ strcpy(savedroom, CC->room.QRname);
+ }
+ CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
+
+ /*
+ * Now delete the room.
+ */
+ if (CtdlDoIHavePermissionToDeleteThisRoom(&CC->room)) {
+ CtdlScheduleRoomForDeletion(&CC->room);
+ IReply("OK DELETE completed");
+ } else {
+ IReply("NO Can't delete this folder.");
+ }
+
+ /*
+ * If another folder is selected, go back to that room so we can resume
+ * our happy day without violent explosions.
+ */
+ if (IMAP->selected) {
+ CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
+ }
+}
+
+
+/*
+ * Back end function for imap_rename()
+ */
+void imap_rename_backend(struct ctdlroom *qrbuf, void *data)
+{
+ char foldername[SIZ];
+ char newfoldername[SIZ];
+ char newroomname[ROOMNAMELEN];
+ int newfloor = 0;
+ struct irl *irlp = NULL; /* scratch pointer */
+ irlparms *myirlparms;
+
+ myirlparms = (irlparms *) data;
+ imap_mailboxname(foldername, sizeof foldername, qrbuf);
+
+ /* Rename subfolders */
+ if ((!strncasecmp(foldername, myirlparms->oldname, myirlparms->oldnamelen) && (foldername[myirlparms->oldnamelen] == '/'))) {
+ snprintf(newfoldername, sizeof newfoldername, "%s/%s", myirlparms->newname, &foldername[myirlparms->oldnamelen + 1]);
+ newfloor = imap_roomname(newroomname, sizeof newroomname, newfoldername) & 0xFF;
+ irlp = (struct irl *) malloc(sizeof(struct irl));
+ strcpy(irlp->irl_newroom, newroomname);
+ strcpy(irlp->irl_oldroom, qrbuf->QRname);
+ irlp->irl_newfloor = newfloor;
+ irlp->next = *(myirlparms->irl);
+ *(myirlparms->irl) = irlp;
+ }
+}
+
+
+/*
+ * Implements the RENAME command
+ *
+ */
+void imap_rename(int num_parms, ConstStr *Params)
+{
+ char old_room[ROOMNAMELEN];
+ char new_room[ROOMNAMELEN];
+ int newr;
+ int new_floor;
+ int r;
+ struct irl *irl = NULL; /* the list */
+ struct irl *irlp = NULL; /* scratch pointer */
+ irlparms irlparms;
+ char aidemsg[1024];
+
+ if (strchr(Params[3].Key, '\\') != NULL) {
+ IReply("NO Invalid character in folder name");
+ return;
+ }
+
+ imap_roomname(old_room, sizeof old_room, Params[2].Key);
+ newr = imap_roomname(new_room, sizeof new_room, Params[3].Key);
+ new_floor = (newr & 0xFF);
+
+ r = CtdlRenameRoom(old_room, new_room, new_floor);
+
+ if (r == crr_room_not_found) {
+ IReply("NO Could not locate this folder");
+ return;
+ }
+ if (r == crr_already_exists) {
+ IReplyPrintf("NO '%s' already exists.");
+ return;
+ }
+ if (r == crr_noneditable) {
+ IReply("NO This folder is not editable.");
+ return;
+ }
+ if (r == crr_invalid_floor) {
+ IReply("NO Folder root does not exist.");
+ return;
+ }
+ if (r == crr_access_denied) {
+ IReply("NO You do not have permission to edit this folder.");
+ return;
+ }
+ if (r != crr_ok) {
+ IReplyPrintf("NO Rename failed - undefined error %d", r);
+ return;
+ }
+
+ /* If this is the INBOX, then RFC2060 says we have to just move the
+ * contents. In a Citadel environment it's easier to rename the room
+ * (already did that) and create a new inbox.
+ */
+ if (!strcasecmp(Params[2].Key, "INBOX")) {
+ CtdlCreateRoom(MAILROOM, 4, "", 0, 1, 0, VIEW_MAILBOX);
+ }
+
+ /* Otherwise, do the subfolders. Build a list of rooms to rename... */
+ else {
+ irlparms.oldname = Params[2].Key;
+ irlparms.oldnamelen = Params[2].len;
+ irlparms.newname = Params[3].Key;
+ irlparms.newnamelen = Params[3].len;
+ irlparms.irl = &irl;
+ CtdlForEachRoom(imap_rename_backend, (void *) &irlparms);
+
+ /* ... and now rename them. */
+ while (irl != NULL) {
+ r = CtdlRenameRoom(irl->irl_oldroom,
+ irl->irl_newroom,
+ irl->irl_newfloor);
+ if (r != crr_ok) {
+ /* FIXME handle error returns better */
+ syslog(LOG_ERR, "CtdlRenameRoom() error %d", r);
+ }
+ irlp = irl;
+ irl = irl->next;
+ free(irlp);
+ }
+ }
+
+ snprintf(aidemsg, sizeof aidemsg, "IMAP folder \"%s\" renamed to \"%s\" by %s\n",
+ Params[2].Key,
+ Params[3].Key,
+ CC->curr_user
+ );
+ CtdlAideMessage(aidemsg, "IMAP folder rename");
+
+ IReply("OK RENAME completed");
+}
+
+
+/*
+ * Main command loop for IMAP sessions.
+ */
+void imap_command_loop(void)
+{
+ struct CitContext *CCC = CC;
+ struct timeval tv1, tv2;
+ suseconds_t total_time = 0;
+ citimap *Imap;
+ const char *pchs, *pche;
+ const imap_handler_hook *h;
+
+ gettimeofday(&tv1, NULL);
+ CCC->lastcmd = time(NULL);
+ Imap = CCCIMAP;
+
+ flush_output();
+ if (Imap->Cmd.CmdBuf == NULL)
+ Imap->Cmd.CmdBuf = NewStrBufPlain(NULL, SIZ);
+ else
+ FlushStrBuf(Imap->Cmd.CmdBuf);
+
+ if (CtdlClientGetLine(Imap->Cmd.CmdBuf) < 1) {
+ syslog(LOG_ERR, "client disconnected: ending session.");
+ CC->kill_me = KILLME_CLIENT_DISCONNECTED;
+ return;
+ }
+
+ if (Imap->authstate == imap_as_expecting_password) {
+ syslog(LOG_INFO, "<password>");
+ }
+ else if (Imap->authstate == imap_as_expecting_plainauth) {
+ syslog(LOG_INFO, "<plain_auth>");
+ }
+ else if ((Imap->authstate == imap_as_expecting_multilineusername) ||
+ cbmstrcasestr(ChrPtr(Imap->Cmd.CmdBuf), " LOGIN ")) {
+ syslog(LOG_INFO, "LOGIN...");
+ }
+ else {
+ syslog(LOG_DEBUG, "%s", ChrPtr(Imap->Cmd.CmdBuf));
+ }
+
+ pchs = ChrPtr(Imap->Cmd.CmdBuf);
+ pche = pchs + StrLength(Imap->Cmd.CmdBuf);
+
+ while ((pche > pchs) &&
+ ((*pche == '\n') ||
+ (*pche == '\r')))
+ {
+ pche --;
+ StrBufCutRight(Imap->Cmd.CmdBuf, 1);
+ }
+ StrBufTrim(Imap->Cmd.CmdBuf);
+
+ /* If we're in the middle of a multi-line command, handle that */
+ switch (Imap->authstate){
+ case imap_as_expecting_username:
+ imap_auth_login_user(imap_as_expecting_username);
+ IUnbuffer();
+ return;
+ case imap_as_expecting_multilineusername:
+ imap_auth_login_user(imap_as_expecting_multilineusername);
+ IUnbuffer();
+ return;
+ case imap_as_expecting_plainauth:
+ imap_auth_plain();
+ IUnbuffer();
+ return;
+ case imap_as_expecting_password:
+ imap_auth_login_pass(imap_as_expecting_password);
+ IUnbuffer();
+ return;
+ case imap_as_expecting_multilinepassword:
+ imap_auth_login_pass(imap_as_expecting_multilinepassword);
+ IUnbuffer();
+ return;
+ default:
+ break;
+ }
+
+ /* Ok, at this point we're in normal command mode.
+ * If the command just submitted does not contain a literal, we
+ * might think about delivering some untagged stuff...
+ */
+
+ /* Grab the tag, command, and parameters. */
+ imap_parameterize(&Imap->Cmd);
+#if 0
+/* debug output the parsed vector */
+ {
+ int i;
+ syslog(LOG_DEBUG, "----- %ld params", Imap->Cmd.num_parms);
+
+ for (i=0; i < Imap->Cmd.num_parms; i++) {
+ if (Imap->Cmd.Params[i].len != strlen(Imap->Cmd.Params[i].Key))
+ syslog(LOG_DEBUG, "*********** %ld != %ld : %s",
+ Imap->Cmd.Params[i].len,
+ strlen(Imap->Cmd.Params[i].Key),
+ Imap->Cmd.Params[i].Key);
+ else
+ syslog(LOG_DEBUG, "%ld : %s",
+ Imap->Cmd.Params[i].len,
+ Imap->Cmd.Params[i].Key);
+ }}
+#endif
+
+ /* Now for the command set. */
+ h = imap_lookup(Imap->Cmd.num_parms, Imap->Cmd.Params);
+
+ if (h == NULL)
+ {
+ IReply("BAD command unrecognized");
+ goto BAIL;
+ }
+
+ /* RFC3501 says that we cannot output untagged data during these commands */
+ if ((h->Flags & I_FLAG_UNTAGGED) == 0) {
+
+ /* we can put any additional untagged stuff right here in the future */
+
+ /*
+ * Before processing the command that was just entered... if we happen
+ * to have a folder selected, we'd like to rescan that folder for new
+ * messages, and for deletions/changes of existing messages. This
+ * could probably be optimized better with some deep thought...
+ */
+ if (Imap->selected) {
+ imap_rescan_msgids();
+ }
+ }
+
+ /* does our command require a logged-in state */
+ if ((!CC->logged_in) && ((h->Flags & I_FLAG_LOGGED_IN) != 0)) {
+ IReply("BAD Not logged in.");
+ goto BAIL;
+ }
+
+ /* does our command require the SELECT state on a mailbox */
+ if ((Imap->selected == 0) && ((h->Flags & I_FLAG_SELECT) != 0)){
+ IReply("BAD no folder selected");
+ goto BAIL;
+ }
+ h->h(Imap->Cmd.num_parms, Imap->Cmd.Params);
+
+ /* If the client transmitted a message we can free it now */
+
+BAIL:
+ IUnbuffer();
+
+ imap_free_transmitted_message();
+
+ gettimeofday(&tv2, NULL);
+ total_time = (tv2.tv_usec + (tv2.tv_sec * 1000000)) - (tv1.tv_usec + (tv1.tv_sec * 1000000));
+ syslog(LOG_DEBUG, "IMAP command completed in %ld.%ld seconds",
+ (total_time / 1000000),
+ (total_time % 1000000)
+ );
+}
+
+void imap_noop (int num_parms, ConstStr *Params)
+{
+ IReply("OK No operation");
+}
+
+void imap_logout(int num_parms, ConstStr *Params)
+{
+ if (IMAP->selected) {
+ imap_do_expunge(); /* yes, we auto-expunge at logout */
+ }
+ IAPrintf("* BYE %s logging out\r\n", CtdlGetConfigStr("c_fqdn"));
+ IReply("OK Citadel IMAP session ended.");
+ CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
+ return;
+}
+
+const char *CitadelServiceIMAP="IMAP";
+const char *CitadelServiceIMAPS="IMAPS";
+
+/*
+ * This function is called to register the IMAP extension with Citadel.
+ */
+char *ctdl_module_init_imap(void) {
+ if (ImapCmds == NULL) {
+ ImapCmds = NewHash(1, NULL);
+ }
+
+ RegisterImapCMD("NOOP", "", imap_noop, I_FLAG_NONE);
+ RegisterImapCMD("CHECK", "", imap_noop, I_FLAG_NONE);
+ RegisterImapCMD("ID", "", imap_id, I_FLAG_NONE);
+ RegisterImapCMD("LOGOUT", "", imap_logout, I_FLAG_NONE);
+ RegisterImapCMD("LOGIN", "", imap_login, I_FLAG_NONE);
+ RegisterImapCMD("AUTHENTICATE", "", imap_authenticate, I_FLAG_NONE);
+ RegisterImapCMD("CAPABILITY", "", imap_capability, I_FLAG_NONE);
+#ifdef HAVE_OPENSSL
+ RegisterImapCMD("STARTTLS", "", imap_starttls, I_FLAG_NONE);
+#endif
+
+ /* The commans below require a logged-in state */
+ RegisterImapCMD("SELECT", "", imap_select, I_FLAG_LOGGED_IN);
+ RegisterImapCMD("EXAMINE", "", imap_select, I_FLAG_LOGGED_IN);
+ RegisterImapCMD("LSUB", "", imap_list, I_FLAG_LOGGED_IN);
+ RegisterImapCMD("LIST", "", imap_list, I_FLAG_LOGGED_IN);
+ RegisterImapCMD("CREATE", "", imap_create, I_FLAG_LOGGED_IN);
+ RegisterImapCMD("DELETE", "", imap_delete, I_FLAG_LOGGED_IN);
+ RegisterImapCMD("RENAME", "", imap_rename, I_FLAG_LOGGED_IN);
+ RegisterImapCMD("STATUS", "", imap_status, I_FLAG_LOGGED_IN);
+ RegisterImapCMD("SUBSCRIBE", "", imap_subscribe, I_FLAG_LOGGED_IN);
+ RegisterImapCMD("UNSUBSCRIBE", "", imap_unsubscribe, I_FLAG_LOGGED_IN);
+ RegisterImapCMD("APPEND", "", imap_append, I_FLAG_LOGGED_IN);
+ RegisterImapCMD("NAMESPACE", "", imap_namespace, I_FLAG_LOGGED_IN);
+ RegisterImapCMD("SETACL", "", imap_setacl, I_FLAG_LOGGED_IN);
+ RegisterImapCMD("DELETEACL", "", imap_deleteacl, I_FLAG_LOGGED_IN);
+ RegisterImapCMD("GETACL", "", imap_getacl, I_FLAG_LOGGED_IN);
+ RegisterImapCMD("LISTRIGHTS", "", imap_listrights, I_FLAG_LOGGED_IN);
+ RegisterImapCMD("MYRIGHTS", "", imap_myrights, I_FLAG_LOGGED_IN);
+ RegisterImapCMD("GETMETADATA", "", imap_getmetadata, I_FLAG_LOGGED_IN);
+ RegisterImapCMD("SETMETADATA", "", imap_setmetadata, I_FLAG_LOGGED_IN);
+
+ /* The commands below require the SELECT state on a mailbox */
+ RegisterImapCMD("FETCH", "", imap_fetch, I_FLAG_LOGGED_IN | I_FLAG_SELECT | I_FLAG_UNTAGGED);
+ RegisterImapCMD("UID", "FETCH", imap_uidfetch, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
+ RegisterImapCMD("SEARCH", "", imap_search, I_FLAG_LOGGED_IN | I_FLAG_SELECT | I_FLAG_UNTAGGED);
+ RegisterImapCMD("UID", "SEARCH", imap_uidsearch, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
+ RegisterImapCMD("STORE", "", imap_store, I_FLAG_LOGGED_IN | I_FLAG_SELECT | I_FLAG_UNTAGGED);
+ RegisterImapCMD("UID", "STORE", imap_uidstore, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
+ RegisterImapCMD("COPY", "", imap_copy, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
+ RegisterImapCMD("UID", "COPY", imap_uidcopy, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
+ RegisterImapCMD("EXPUNGE", "", imap_expunge, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
+ RegisterImapCMD("UID", "EXPUNGE", imap_expunge, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
+ RegisterImapCMD("CLOSE", "", imap_close, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
+
+ if (!threading) {
+ CtdlRegisterServiceHook(CtdlGetConfigInt("c_imap_port"),
+ NULL, imap_greeting, imap_command_loop, NULL, CitadelServiceIMAP);
+#ifdef HAVE_OPENSSL
+ CtdlRegisterServiceHook(CtdlGetConfigInt("c_imaps_port"),
+ NULL, imaps_greeting, imap_command_loop, NULL, CitadelServiceIMAPS);
+#endif
+ CtdlRegisterSessionHook(imap_cleanup_function, EVT_STOP, PRIO_STOP + 30);
+ }
+
+ /* return our module name for the log */
+ return "imap";
+}
--- /dev/null
+#define GLOBAL_UIDVALIDITY_VALUE 1L
+
+
+void imap_cleanup_function(void);
+void imap_greeting(void);
+void imap_command_loop(void);
+int imap_grabroom(char *returned_roomname, const char *foldername, int zapped_ok);
+void imap_free_transmitted_message(void);
+int imap_do_expunge(void);
+void imap_rescan_msgids(void);
+
+/*
+ * FDELIM defines which character we want to use as a folder delimiter
+ * in room names. Originally we used a forward slash, but that caused
+ * rooms with names like "Sent/Received Pages" to get delimited, so we
+ * changed it to a backslash. This is completely irrelevant to how Citadel
+ * speaks to IMAP clients -- the delimiter used in the IMAP protocol is
+ * a vertical bar, which is illegal in Citadel room names anyway.
+ */
+
+typedef void (*imap_handler)(int num_parms, ConstStr *Params);
+
+typedef struct _imap_handler_hook {
+ imap_handler h;
+ int Flags;
+} imap_handler_hook;
+
+typedef struct __citimap_command {
+ StrBuf *CmdBuf; /* our current commandline; gets chopped into: */
+ ConstStr *Params; /* Commandline tokens */
+ int num_parms; /* Number of Commandline tokens available */
+ int avail_parms; /* Number of ConstStr args is big */
+ const imap_handler_hook *hh;
+} citimap_command;
+
+
+typedef struct __citimap {
+ StrBuf *Reply;
+ int authstate;
+ char authseq[SIZ];
+ int selected; /* set to 1 if in the SELECTED state */
+ int readonly; /* mailbox is open read only */
+ int num_msgs; /* Number of messages being mapped */
+ int num_alloc; /* Number of messages for which we've allocated space */
+ time_t last_mtime; /* For checking whether the room was modified... */
+ long *msgids;
+ unsigned int *flags;
+
+ StrBuf *TransmittedMessage; /* for APPEND command... */
+
+ citimap_command Cmd; /* our current commandline */
+
+ /* Cache most recent RFC822 FETCH because client might load in pieces */
+ StrBuf *cached_rfc822;
+ long cached_rfc822_msgnum;
+ char cached_rfc822_withbody; /* 1 = body cached; 0 = only headers cached */
+
+ /* Cache most recent BODY FETCH because client might load in pieces */
+ char *cached_body;
+ size_t cached_body_len;
+ char cached_bodypart[SIZ];
+ long cached_bodymsgnum;
+ char cached_body_withbody; /* 1 = body cached; 0 = only headers cached */
+} citimap;
+
+/*
+ * values of 'authstate'
+ */
+enum {
+ imap_as_normal,
+ imap_as_expecting_username,
+ imap_as_expecting_password,
+ imap_as_expecting_plainauth,
+ imap_as_expecting_multilineusername,
+ imap_as_expecting_multilinepassword
+};
+
+/* Flags for the above struct. Note that some of these are for internal use,
+ * and are not to be reported to IMAP clients.
+ */
+#define IMAP_ANSWERED 1 /* reportable and setable */
+#define IMAP_FLAGGED 2 /* reportable and setable */
+#define IMAP_DELETED 4 /* reportable and setable */
+#define IMAP_DRAFT 8 /* reportable and setable */
+#define IMAP_SEEN 16 /* reportable and setable */
+
+#define IMAP_MASK_SETABLE 0x1f
+#define IMAP_MASK_SYSTEM 0xe0
+
+#define IMAP_SELECTED 32 /* neither reportable nor setable */
+#define IMAP_RECENT 64 /* reportable but not setable */
+
+
+/*
+ * Flags that may be returned by imap_roomname()
+ * (the lower eight bits will be the floor number)
+ */
+#define IR_MAILBOX 0x0100 /* Mailbox */
+#define IR_EXISTS 0x0200 /* Room exists (not implemented) */
+#define IR_BABOON 0x0000 /* Just had to put this here :) */
+
+#define FDELIM '\\'
+
+#define IMAP ((citimap *)CC->session_specific_data)
+#define CCCIMAP ((citimap *)CCC->session_specific_data)
+
+#define IMAPDBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (IMAPDebugEnabled != 0))
+
+#define I_FLAG_NONE (0)
+#define I_FLAG_LOGGED_IN (1<<0)
+#define I_FLAG_SELECT (1<<1)
+/* RFC3501 says that we cannot output untagged data during these commands */
+#define I_FLAG_UNTAGGED (1<<2)
+
+/*
+ * When loading arrays of message ID's into memory, increase the buffer to
+ * hold this many additional messages instead of calling realloc() each time.
+ */
+#define REALLOC_INCREMENT 100
+
+
+void registerImapCMD(const char *First, long FLen,
+ const char *Second, long SLen,
+ imap_handler H,
+ int Flags);
+
+#define RegisterImapCMD(First, Second, H, Flags) \
+ registerImapCMD(HKEY(First), HKEY(Second), H, Flags)
--- /dev/null
+// Inbox handling rules
+//
+// Copyright (c) 1987-2022 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.
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../ctdl_module.h"
+
+
+// The next sections are enums and keys that drive the serialize/deserialize functions for the inbox rules/state configuration.
+
+// Fields to be compared
+enum {
+ field_from,
+ field_tocc,
+ field_subject,
+ field_replyto,
+ field_sender,
+ field_resentfrom,
+ field_resentto,
+ field_envfrom,
+ field_envto,
+ field_xmailer,
+ field_xspamflag,
+ field_xspamstatus,
+ field_listid,
+ field_size,
+ field_all
+};
+char *field_keys[] = {
+ "from",
+ "tocc",
+ "subject",
+ "replyto",
+ "sender",
+ "resentfrom",
+ "resentto",
+ "envfrom",
+ "envto",
+ "xmailer",
+ "xspamflag",
+ "xspamstatus",
+ "listid",
+ "size",
+ "all"
+};
+
+// Field comparison operators
+enum {
+ fcomp_contains,
+ fcomp_notcontains,
+ fcomp_is,
+ fcomp_isnot,
+ fcomp_matches,
+ fcomp_notmatches
+};
+char *fcomp_keys[] = {
+ "contains",
+ "notcontains",
+ "is",
+ "isnot",
+ "matches",
+ "notmatches"
+};
+
+// Actions
+enum {
+ action_keep,
+ action_discard,
+ action_reject,
+ action_fileinto,
+ action_redirect,
+ action_vacation
+};
+char *action_keys[] = {
+ "keep",
+ "discard",
+ "reject",
+ "fileinto",
+ "redirect",
+ "vacation"
+};
+
+// Size comparison operators
+enum {
+ scomp_larger,
+ scomp_smaller
+};
+char *scomp_keys[] = {
+ "larger",
+ "smaller"
+};
+
+// Final actions
+enum {
+ final_continue,
+ final_stop
+};
+char *final_keys[] = {
+ "continue",
+ "stop"
+};
+
+// This data structure represents ONE inbox rule within the configuration.
+struct irule {
+ int compared_field;
+ int field_compare_op;
+ char compared_value[128];
+ int size_compare_op;
+ long compared_size;
+ int action;
+ char file_into[ROOMNAMELEN];
+ char redirect_to[1024];
+ char autoreply_message[SIZ];
+ int final_action;
+};
+
+// This data structure represents the entire inbox rules configuration AND current state for a single user.
+struct inboxrules {
+ long lastproc;
+ int num_rules;
+ struct irule *rules;
+};
+
+
+// Destructor for 'struct inboxrules'
+void free_inbox_rules(struct inboxrules *ibr) {
+ free(ibr->rules);
+ free(ibr);
+}
+
+
+// Constructor for 'struct inboxrules' that deserializes the configuration from text input.
+struct inboxrules *deserialize_inbox_rules(char *serialized_rules) {
+ int i;
+
+ if (!serialized_rules) {
+ return NULL;
+ }
+
+ // Make a copy of the supplied buffer because we're going to shit all over it with strtok_r()
+ char *sr = strdup(serialized_rules);
+ if (!sr) {
+ return NULL;
+ }
+
+ struct inboxrules *ibr = malloc(sizeof(struct inboxrules));
+ if (ibr == NULL) {
+ return NULL;
+ }
+ memset(ibr, 0, sizeof(struct inboxrules));
+
+ char *token;
+ char *rest = sr;
+ while ((token = strtok_r(rest, "\n", &rest))) {
+
+ // For backwards compatibility, "# WEBCIT_RULE" is an alias for "rule".
+ // Prior to version 930, WebCit converted its rules to Sieve scripts, but saved the rules as comments for
+ // later re-editing. Now, the rules hidden in the comments become the real rules.
+ if (!strncasecmp(token, "# WEBCIT_RULE|", 14)) {
+ strcpy(token, "rule|");
+ strcpy(&token[5], &token[14]);
+ }
+
+ // Lines containing actual rules are double-serialized with Base64. It seemed like a good idea at the time :(
+ if (!strncasecmp(token, "rule|", 5)) {
+ remove_token(&token[5], 0, '|');
+ char *decoded_rule = malloc(strlen(token));
+ CtdlDecodeBase64(decoded_rule, &token[5], strlen(&token[5]));
+ ibr->num_rules++;
+ ibr->rules = realloc(ibr->rules, (sizeof(struct irule) * ibr->num_rules));
+ struct irule *new_rule = &ibr->rules[ibr->num_rules - 1];
+ memset(new_rule, 0, sizeof(struct irule));
+
+ // We have a rule , now parse it
+ char rtoken[SIZ];
+ int nt = num_tokens(decoded_rule, '|');
+ int t = 0;
+ for (t=0; t<nt; ++t) {
+ extract_token(rtoken, decoded_rule, t, '|', sizeof(rtoken));
+ striplt(rtoken);
+ switch(t) {
+ case 1: // field to compare
+ for (i=0; i<=field_all; ++i) {
+ if (!strcasecmp(rtoken, field_keys[i])) {
+ new_rule->compared_field = i;
+ }
+ }
+ break;
+ case 2: // field comparison operation
+ for (i=0; i<=fcomp_notmatches; ++i) {
+ if (!strcasecmp(rtoken, fcomp_keys[i])) {
+ new_rule->field_compare_op = i;
+ }
+ }
+ break;
+ case 3: // field comparison value
+ safestrncpy(new_rule->compared_value, rtoken, sizeof(new_rule->compared_value));
+ break;
+ case 4: // size comparison operation
+ for (i=0; i<=scomp_smaller; ++i) {
+ if (!strcasecmp(rtoken, scomp_keys[i])) {
+ new_rule->size_compare_op = i;
+ }
+ }
+ break;
+ case 5: // size comparison value
+ new_rule->compared_size = atol(rtoken);
+ break;
+ case 6: // action
+ for (i=0; i<=action_vacation; ++i) {
+ if (!strcasecmp(rtoken, action_keys[i])) {
+ new_rule->action = i;
+ }
+ }
+ break;
+ case 7: // file into (target room)
+ safestrncpy(new_rule->file_into, rtoken, sizeof(new_rule->file_into));
+ break;
+ case 8: // redirect to (target address)
+ safestrncpy(new_rule->redirect_to, rtoken, sizeof(new_rule->redirect_to));
+ break;
+ case 9: // autoreply message
+ safestrncpy(new_rule->autoreply_message, rtoken, sizeof(new_rule->autoreply_message));
+ break;
+ case 10: // final_action;
+ for (i=0; i<=final_stop; ++i) {
+ if (!strcasecmp(rtoken, final_keys[i])) {
+ new_rule->final_action = i;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ free(decoded_rule);
+ }
+
+ // "lastproc" indicates the newest message number in the inbox that was previously processed by our inbox rules.
+ // This is a legacy location for this value and will only be used if it's the only one present.
+ else if (!strncasecmp(token, "lastproc|", 5)) {
+ ibr->lastproc = atol(&token[9]);
+ }
+
+ // Lines which do not contain a recognizable token must be IGNORED. These lines may be left over
+ // from a previous version and will disappear when we rewrite the config.
+
+ }
+
+ free(sr); // free our copy of the source buffer that has now been trashed with null bytes...
+ return(ibr); // and return our complex data type to the caller.
+}
+
+
+// Perform the "fileinto" action (save the message in another room)
+// Returns: 1 or 0 to tell the caller to keep (1) or delete (0) the inbox copy of the message.
+//
+int inbox_do_fileinto(struct irule *rule, long msgnum) {
+ char *dest_folder = rule->file_into;
+ char original_room_name[ROOMNAMELEN];
+ char foldername[ROOMNAMELEN];
+ int c;
+
+ // Situations where we want to just keep the message in the inbox:
+ if (
+ (IsEmptyStr(dest_folder)) // no destination room was specified
+ || (!strcasecmp(dest_folder, "INBOX")) // fileinto inbox is the same as keep
+ || (!strcasecmp(dest_folder, MAILROOM)) // fileinto "Mail" is the same as keep
+ ) {
+ return(1); // don't delete the inbox copy if this failed
+ }
+
+ // Remember what room we came from
+ safestrncpy(original_room_name, CC->room.QRname, sizeof original_room_name);
+
+ // First try a mailbox name match (check personal mail folders first)
+ strcpy(foldername, original_room_name); // This keeps the user namespace of the inbox
+ snprintf(&foldername[10], sizeof(foldername)-10, ".%s", dest_folder); // And this tacks on the target room name
+ c = CtdlGetRoom(&CC->room, foldername);
+
+ // Then a regular room name match (public and private rooms)
+ if (c != 0) {
+ safestrncpy(foldername, dest_folder, sizeof foldername);
+ c = CtdlGetRoom(&CC->room, foldername);
+ }
+
+ if (c != 0) {
+ syslog(LOG_WARNING, "inboxrules: target <%s> does not exist", dest_folder);
+ return(1); // don't delete the inbox copy if this failed
+ }
+
+ // Yes, we actually have to go there
+ CtdlUserGoto(NULL, 0, 0, NULL, NULL, NULL, NULL);
+
+ c = CtdlSaveMsgPointersInRoom(NULL, &msgnum, 1, 0, NULL, 0);
+
+ // Go back to the room we came from
+ if (strcasecmp(original_room_name, CC->room.QRname)) {
+ CtdlUserGoto(original_room_name, 0, 0, NULL, NULL, NULL, NULL);
+ }
+
+ return(0); // delete the inbox copy
+}
+
+
+// Perform the "redirect" action (divert the message to another email address)
+// Returns: 1 or 0 to tell the caller to keep (1) or delete (0) the inbox copy of the message.
+//
+int inbox_do_redirect(struct irule *rule, long msgnum) {
+ if (IsEmptyStr(rule->redirect_to)) {
+ syslog(LOG_WARNING, "inboxrules: inbox_do_redirect() invalid recipient <%s>", rule->redirect_to);
+ return(1); // don't delete the inbox copy if this failed
+ }
+
+ struct recptypes *valid = validate_recipients(rule->redirect_to, NULL, 0);
+ if (valid == NULL) {
+ syslog(LOG_WARNING, "inboxrules: inbox_do_redirect() invalid recipient <%s>", rule->redirect_to);
+ return(1); // don't delete the inbox copy if this failed
+ }
+ if (valid->num_error > 0) {
+ free_recipients(valid);
+ syslog(LOG_WARNING, "inboxrules: inbox_do_redirect() invalid recipient <%s>", rule->redirect_to);
+ return(1); // don't delete the inbox copy if this failed
+ }
+
+ struct CtdlMessage *msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) {
+ free_recipients(valid);
+ syslog(LOG_WARNING, "inboxrules: cannot reload message %ld for forwarding", msgnum);
+ return(1); // don't delete the inbox copy if this failed
+ }
+
+ CtdlSubmitMsg(msg, valid, NULL); // send the message to the new recipient
+ free_recipients(valid);
+ CM_Free(msg);
+ return(0); // delete the inbox copy
+}
+
+
+// Perform the "reject" action (delete the message, and tell the sender we deleted it)
+void inbox_do_reject(struct irule *rule, struct CtdlMessage *msg) {
+ syslog(LOG_DEBUG, "inbox_do_reject: sender: <%s>, reject", msg->cm_fields[erFc822Addr]);
+
+ // If we can't determine who sent the message, reject silently.
+ char *sender;
+ if (!IsEmptyStr(msg->cm_fields[eMessagePath])) {
+ sender = msg->cm_fields[eMessagePath];
+ }
+ else if (!IsEmptyStr(msg->cm_fields[erFc822Addr])) {
+ sender = msg->cm_fields[erFc822Addr];
+ }
+ else {
+ return;
+ }
+
+ // Assemble the reject message.
+ char *reject_text = malloc(strlen(rule->autoreply_message) + 1024);
+ if (reject_text == NULL) {
+ return;
+ }
+ sprintf(reject_text,
+ "Content-type: text/plain\n"
+ "\n"
+ "The message was refused by the recipient's mail filtering program.\n"
+ "The reason given was as follows:\n"
+ "\n"
+ "%s\n"
+ "\n"
+ ,
+ rule->autoreply_message
+ );
+
+ // Deliver the message
+ quickie_message(
+ " ",
+ msg->cm_fields[eenVelopeTo],
+ sender,
+ MAILROOM,
+ reject_text,
+ FMT_RFC822,
+ "Delivery status notification"
+ );
+ free(reject_text);
+}
+
+
+// Perform the "vacation" action (send an automatic response)
+void inbox_do_vacation(struct irule *rule, struct CtdlMessage *msg) {
+ syslog(LOG_DEBUG, "inbox_do_vacation: sender: <%s>, vacation", msg->cm_fields[erFc822Addr]);
+
+ // If we can't determine who sent the message, no auto-reply can be sent.
+ char *sender;
+ if (!IsEmptyStr(msg->cm_fields[eMessagePath])) {
+ sender = msg->cm_fields[eMessagePath];
+ }
+ else if (!IsEmptyStr(msg->cm_fields[erFc822Addr])) {
+ sender = msg->cm_fields[erFc822Addr];
+ }
+ else {
+ return;
+ }
+
+ // Avoid repeatedly sending auto-replies to the same correspondent over and over again by creating
+ // a hash of the user, correspondent, and reply text, and hitting the S_USETABLE database.
+ StrBuf *u = NewStrBuf();
+ StrBufPrintf(u, "vacation/%x/%x/%x",
+ HashLittle(sender, strlen(sender)),
+ HashLittle(msg->cm_fields[eenVelopeTo], msg->cm_lengths[eenVelopeTo]),
+ HashLittle(rule->autoreply_message, strlen(rule->autoreply_message))
+ );
+ int already_seen = CheckIfAlreadySeen(u);
+ FreeStrBuf(&u);
+
+ if (!already_seen) {
+ // Assemble the auto-reply message.
+ StrBuf *reject_text = NewStrBuf();
+ if (reject_text == NULL) {
+ return;
+ }
+
+ StrBufPrintf(reject_text,
+ "Content-type: text/plain\n"
+ "\n"
+ "%s\n"
+ "\n"
+ ,
+ rule->autoreply_message
+ );
+
+ // Deliver the auto-reply.
+ quickie_message(
+ "",
+ msg->cm_fields[eenVelopeTo],
+ sender,
+ MAILROOM,
+ ChrPtr(reject_text),
+ FMT_RFC822,
+ "Delivery status notification"
+ );
+ FreeStrBuf(&reject_text);
+ }
+}
+
+
+/*
+ * Process a single message. We know the room, the user, the rules, the message number, etc.
+ */
+void inbox_do_msg(long msgnum, void *userdata) {
+ struct inboxrules *ii = (struct inboxrules *) userdata;
+ struct CtdlMessage *msg = NULL; // If we are loading a message to process, put it here.
+ int headers_loaded = 0; // Did we load the headers yet? Do it only once.
+ int body_loaded = 0; // Did we load the message body yet? Do it only once.
+ int metadata_loaded = 0; // Did we load the metadata yet? Do it only once.
+ struct MetaData smi; // If we are loading the metadata to compare, put it here.
+ int rule_activated = 0; // On each rule, this is set if the compare succeeds and the rule activates.
+ char compare_me[SIZ]; // On each rule, we will store the field to be compared here.
+ int compare_compound = 0; // Set to 1 when we are comparing both display name and email address
+ int keep_message = 1; // Nonzero to keep the message in the inbox after processing, 0 to delete it.
+ int i;
+
+ syslog(LOG_DEBUG, "inboxrules: processing message #%ld which is higher than %ld, we are in %s", msgnum, ii->lastproc, CC->room.QRname);
+
+ if (ii->num_rules <= 0) {
+ syslog(LOG_DEBUG, "inboxrules: rule set is empty");
+ return;
+ }
+
+ for (i=0; i<ii->num_rules; ++i) {
+ syslog(LOG_DEBUG, "inboxrules: processing rule %d is %s", i, field_keys[ ii->rules[i].compared_field ]);
+ rule_activated = 0;
+
+ // Before doing a field compare, check to see if we have the correct parts of the message in memory.
+
+ switch(ii->rules[i].compared_field) {
+ // These fields require loading only the top-level headers
+ case field_from: // From:
+ case field_tocc: // To: or Cc:
+ case field_subject: // Subject:
+ case field_replyto: // Reply-to:
+ case field_listid: // List-ID:
+ case field_envto: // Envelope-to:
+ case field_envfrom: // Return-path:
+ if (!headers_loaded) {
+ syslog(LOG_DEBUG, "inboxrules: loading headers for message %ld", msgnum);
+ msg = CtdlFetchMessage(msgnum, 0);
+ if (!msg) {
+ return;
+ }
+ headers_loaded = 1;
+ }
+ break;
+ // These fields are not stored as Citadel headers, and therefore require a full message load.
+ case field_sender:
+ case field_resentfrom:
+ case field_resentto:
+ case field_xmailer:
+ case field_xspamflag:
+ case field_xspamstatus:
+ if (!body_loaded) {
+ syslog(LOG_DEBUG, "inboxrules: loading all of message %ld", msgnum);
+ if (msg != NULL) {
+ CM_Free(msg);
+ }
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (!msg) {
+ return;
+ }
+ headers_loaded = 1;
+ body_loaded = 1;
+ }
+ break;
+ case field_size:
+ if (!metadata_loaded) {
+ syslog(LOG_DEBUG, "inboxrules: loading metadata for message %ld", msgnum);
+ GetMetaData(&smi, msgnum);
+ metadata_loaded = 1;
+ }
+ break;
+ case field_all:
+ syslog(LOG_DEBUG, "inboxrules: this is an always-on rule");
+ break;
+ default:
+ syslog(LOG_DEBUG, "inboxrules: unknown rule key");
+ }
+
+ // If the rule involves a field comparison, load the field to be compared.
+ compare_me[0] = 0;
+ compare_compound = 0;
+ switch(ii->rules[i].compared_field) {
+ case field_from: // From:
+ if ( (!IsEmptyStr(msg->cm_fields[erFc822Addr])) && (!IsEmptyStr(msg->cm_fields[erFc822Addr])) ) {
+ snprintf(compare_me, sizeof compare_me, "%s|%s",
+ msg->cm_fields[eAuthor],
+ msg->cm_fields[erFc822Addr]
+ );
+ compare_compound = 1; // there will be two fields to compare "name|address"
+ }
+ else if (!IsEmptyStr(msg->cm_fields[erFc822Addr])) {
+ safestrncpy(compare_me, msg->cm_fields[erFc822Addr], sizeof compare_me);
+ }
+ else if (!IsEmptyStr(msg->cm_fields[eAuthor])) {
+ safestrncpy(compare_me, msg->cm_fields[eAuthor], sizeof compare_me);
+ }
+ break;
+ case field_tocc: // To: or Cc:
+ if (!IsEmptyStr(msg->cm_fields[eRecipient])) {
+ safestrncpy(compare_me, msg->cm_fields[eRecipient], sizeof compare_me);
+ }
+ if (!IsEmptyStr(msg->cm_fields[eCarbonCopY])) {
+ if (!IsEmptyStr(compare_me)) {
+ strcat(compare_me, ",");
+ }
+ safestrncpy(&compare_me[strlen(compare_me)], msg->cm_fields[eCarbonCopY], (sizeof compare_me - strlen(compare_me)));
+ }
+ break;
+ case field_subject: // Subject:
+ if (!IsEmptyStr(msg->cm_fields[eMsgSubject])) {
+ safestrncpy(compare_me, msg->cm_fields[eMsgSubject], sizeof compare_me);
+ }
+ break;
+ case field_replyto: // Reply-to:
+ if (!IsEmptyStr(msg->cm_fields[eReplyTo])) {
+ safestrncpy(compare_me, msg->cm_fields[eReplyTo], sizeof compare_me);
+ }
+ break;
+ case field_listid: // List-ID:
+ if (!IsEmptyStr(msg->cm_fields[eListID])) {
+ safestrncpy(compare_me, msg->cm_fields[eListID], sizeof compare_me);
+ }
+ break;
+ case field_envto: // Envelope-to:
+ if (!IsEmptyStr(msg->cm_fields[eenVelopeTo])) {
+ safestrncpy(compare_me, msg->cm_fields[eenVelopeTo], sizeof compare_me);
+ }
+ break;
+ case field_envfrom: // Return-path:
+ if (!IsEmptyStr(msg->cm_fields[eMessagePath])) {
+ safestrncpy(compare_me, msg->cm_fields[eMessagePath], sizeof compare_me);
+ }
+ break;
+
+ case field_sender:
+ case field_resentfrom:
+ case field_resentto:
+ case field_xmailer:
+ case field_xspamflag:
+ case field_xspamstatus:
+
+ default:
+ break;
+ }
+
+ // Message data to compare is loaded, now do something.
+ switch(ii->rules[i].compared_field) {
+ case field_from: // From:
+ case field_tocc: // To: or Cc:
+ case field_subject: // Subject:
+ case field_replyto: // Reply-to:
+ case field_listid: // List-ID:
+ case field_envto: // Envelope-to:
+ case field_envfrom: // Return-path:
+ case field_sender:
+ case field_resentfrom:
+ case field_resentto:
+ case field_xmailer:
+ case field_xspamflag:
+ case field_xspamstatus:
+
+ // For all of the above fields, we can compare the field we've loaded into the buffer.
+ syslog(LOG_DEBUG, "Value of field to compare is: <%s>", compare_me);
+ int substring_match = (bmstrcasestr(compare_me, ii->rules[i].compared_value) ? 1 : 0);
+ int exact_match = 0;
+ if (compare_compound) {
+ char *sep = strchr(compare_me, '|');
+ if (sep) {
+ *sep = 0;
+ exact_match =
+ (strcasecmp(compare_me, ii->rules[i].compared_value) ? 0 : 1)
+ + (strcasecmp(++sep, ii->rules[i].compared_value) ? 0 : 1)
+ ;
+ }
+ }
+ else {
+ exact_match = (strcasecmp(compare_me, ii->rules[i].compared_value) ? 0 : 1);
+ }
+ syslog(LOG_DEBUG, "substring match: %d", substring_match);
+ syslog(LOG_DEBUG, "exact match: %d", exact_match);
+ switch(ii->rules[i].field_compare_op) {
+ case fcomp_contains:
+ case fcomp_matches:
+ rule_activated = substring_match;
+ break;
+ case fcomp_notcontains:
+ case fcomp_notmatches:
+ rule_activated = !substring_match;
+ break;
+ case fcomp_is:
+ rule_activated = exact_match;
+ break;
+ case fcomp_isnot:
+ rule_activated = !exact_match;
+ break;
+ }
+ break;
+
+ case field_size:
+ rule_activated = 0;
+ switch(ii->rules[i].field_compare_op) {
+ case scomp_larger:
+ rule_activated = ((smi.meta_rfc822_length > ii->rules[i].compared_size) ? 1 : 0);
+ break;
+ case scomp_smaller:
+ rule_activated = ((smi.meta_rfc822_length < ii->rules[i].compared_size) ? 1 : 0);
+ break;
+ }
+ break;
+ case field_all: // The "all messages" rule ALWAYS triggers
+ rule_activated = 1;
+ break;
+ default: // no matches, fall through and do nothing
+ syslog(LOG_WARNING, "inboxrules: an unknown field comparison was encountered");
+ rule_activated = 0;
+ break;
+ }
+
+ // If the rule matched, perform the requested action.
+ if (rule_activated) {
+ syslog(LOG_DEBUG, "inboxrules: rule activated");
+
+ // Perform the requested action
+ switch(ii->rules[i].action) {
+ case action_keep:
+ keep_message = 1;
+ break;
+ case action_discard:
+ keep_message = 0;
+ break;
+ case action_reject:
+ inbox_do_reject(&ii->rules[i], msg);
+ keep_message = 0;
+ break;
+ case action_fileinto:
+ keep_message = inbox_do_fileinto(&ii->rules[i], msgnum);
+ break;
+ case action_redirect:
+ keep_message = inbox_do_redirect(&ii->rules[i], msgnum);
+ break;
+ case action_vacation:
+ inbox_do_vacation(&ii->rules[i], msg);
+ keep_message = 1;
+ break;
+ }
+
+ // Now perform the "final" action (anything other than "stop" means continue)
+ if (ii->rules[i].final_action == final_stop) {
+ syslog(LOG_DEBUG, "inboxrules: stop processing");
+ i = ii->num_rules + 1; // throw us out of scope to stop
+ }
+
+
+ }
+ else {
+ syslog(LOG_DEBUG, "inboxrules: rule not activated");
+ }
+ }
+
+ if (msg != NULL) { // Delete the copy of the message that is currently in memory. We don't need it anymore.
+ CM_Free(msg);
+ }
+
+ if (!keep_message) { // Delete the copy of the message that is currently in the inbox, if rules dictated that.
+ syslog(LOG_DEBUG, "inboxrules: delete %ld from inbox", msgnum);
+ CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, ""); // we're in the inbox already
+ }
+
+ ii->lastproc = msgnum; // make note of the last message we processed, so we don't scan the whole inbox again
+}
+
+
+/*
+ * A user account is identified as requring inbox processing.
+ * Do it.
+ */
+void do_inbox_processing_for_user(long usernum) {
+ struct CtdlMessage *msg;
+ struct inboxrules *ii;
+ char roomname[ROOMNAMELEN];
+ char username[64];
+
+ if (CtdlGetUserByNumber(&CC->user, usernum) != 0) { // grab the user record
+ return; // and bail out if we were given an invalid user
+ }
+
+ strcpy(username, CC->user.fullname); // save the user name so we can fetch it later and lock it
+
+ if (CC->user.msgnum_inboxrules <= 0) {
+ return; // this user has no inbox rules
+ }
+
+ msg = CtdlFetchMessage(CC->user.msgnum_inboxrules, 1);
+ if (msg == NULL) {
+ return; // config msgnum is set but that message does not exist
+ }
+
+ ii = deserialize_inbox_rules(msg->cm_fields[eMesageText]);
+ CM_Free(msg);
+
+ if (ii == NULL) {
+ return; // config message exists but body is null
+ }
+
+ if (ii->lastproc > CC->user.lastproc_inboxrules) { // There might be a "last message processed" number left over
+ CC->user.lastproc_inboxrules = ii->lastproc; // in the ruleset from a previous version. Use this if it is
+ } // a higher number.
+ else {
+ ii->lastproc = CC->user.lastproc_inboxrules;
+ }
+
+ long original_lastproc = ii->lastproc;
+ syslog(LOG_DEBUG, "inboxrules: for %s, messages newer than %ld", CC->user.fullname, original_lastproc);
+
+ // Go to the user's inbox room and process all new messages
+ snprintf(roomname, sizeof roomname, "%010ld.%s", usernum, MAILROOM);
+ if (CtdlGetRoom(&CC->room, roomname) == 0) {
+ CtdlForEachMessage(MSGS_GT, ii->lastproc, NULL, NULL, NULL, inbox_do_msg, (void *) ii);
+ }
+
+ // Record the number of the last message we processed
+ if (ii->lastproc > original_lastproc) {
+ CtdlGetUserLock(&CC->user, username);
+ CC->user.lastproc_inboxrules = ii->lastproc; // Avoid processing the entire inbox next time
+ CtdlPutUserLock(&CC->user);
+ }
+
+ // And free the memory.
+ free_inbox_rules(ii);
+}
+
+
+/*
+ * Here is an array of users (by number) who have received messages in their inbox and may require processing.
+ */
+long *users_requiring_inbox_processing = NULL;
+int num_urip = 0;
+int num_urip_alloc = 0;
+
+
+/*
+ * Perform inbox processing for all rooms which require it
+ */
+void perform_inbox_processing(void) {
+ int i = 0;
+
+ if (num_urip == 0) {
+ return; // no action required
+ }
+
+ for (i=0; i<num_urip; ++i) {
+ do_inbox_processing_for_user(users_requiring_inbox_processing[i]);
+ }
+
+ free(users_requiring_inbox_processing);
+ users_requiring_inbox_processing = NULL;
+ num_urip = 0;
+ num_urip_alloc = 0;
+}
+
+
+/*
+ * This function is called after a message is saved to a room.
+ * If it's someone's inbox, we have to check for inbox rules
+ */
+int serv_inboxrules_roomhook(struct ctdlroom *room) {
+ int i = 0;
+
+ // Is this someone's inbox?
+ if (!strcasecmp(&room->QRname[11], MAILROOM)) {
+ long usernum = atol(room->QRname);
+ if (usernum > 0) {
+
+ // first check to see if this user is already on the list
+ if (num_urip > 0) {
+ for (i=0; i<=num_urip; ++i) {
+ if (users_requiring_inbox_processing[i] == usernum) { // already on the list!
+ return(0);
+ }
+ }
+ }
+
+ // make room if we need to
+ if (num_urip_alloc == 0) {
+ num_urip_alloc = 100;
+ users_requiring_inbox_processing = malloc(sizeof(long) * num_urip_alloc);
+ }
+ else if (num_urip >= num_urip_alloc) {
+ num_urip_alloc += 100;
+ users_requiring_inbox_processing = realloc(users_requiring_inbox_processing, (sizeof(long) * num_urip_alloc));
+ }
+
+ // now add the user to the list
+ users_requiring_inbox_processing[num_urip++] = usernum;
+ }
+ }
+
+ // No errors are possible from this function.
+ return(0);
+}
+
+
+
+/*
+ * Get InBox Rules
+ *
+ * This is a client-facing function which fetches the user's inbox rules -- it omits all lines containing anything other than a rule.
+ *
+ * hmmmmm ... should we try to rebuild this in terms of deserialize_inbox_rules() instread?
+ */
+void cmd_gibr(char *argbuf) {
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ cprintf("%d inbox rules for %s\n", LISTING_FOLLOWS, CC->user.fullname);
+
+ struct CtdlMessage *msg = CtdlFetchMessage(CC->user.msgnum_inboxrules, 1);
+ if (msg != NULL) {
+ if (!CM_IsEmpty(msg, eMesageText)) {
+ char *token;
+ char *rest = msg->cm_fields[eMesageText];
+ while ((token = strtok_r(rest, "\n", &rest))) {
+
+ // for backwards compatibility, "# WEBCIT_RULE" is an alias for "rule"
+ if (!strncasecmp(token, "# WEBCIT_RULE|", 14)) {
+ strcpy(token, "rule|");
+ strcpy(&token[5], &token[14]);
+ }
+
+ // Output only lines containing rules.
+ if (!strncasecmp(token, "rule|", 5)) {
+ cprintf("%s\n", token);
+ }
+ else {
+ cprintf("# invalid rule found : %s\n", token);
+ }
+ }
+ }
+ CM_Free(msg);
+ }
+ cprintf("000\n");
+}
+
+
+/*
+ * Rewrite the rule set to disk after it has been modified
+ * Called by cmd_pibr()
+ * Returns the msgnum of the new rules message
+ */
+void rewrite_rules_to_disk(const char *new_config) {
+ long old_msgnum = CC->user.msgnum_inboxrules;
+ char userconfigroomname[ROOMNAMELEN];
+ CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &CC->user, USERCONFIGROOM);
+ long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, new_config, FMT_RFC822, "inbox rules configuration");
+ CtdlGetUserLock(&CC->user, CC->curr_user);
+ CC->user.msgnum_inboxrules = new_msgnum; // Now we know where to get the rules next time
+ CC->user.lastproc_inboxrules = new_msgnum; // Avoid processing the entire inbox next time
+ CtdlPutUserLock(&CC->user);
+ if (old_msgnum > 0) {
+ syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
+ CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
+ }
+}
+
+
+/*
+ * Put InBox Rules
+ *
+ * User transmits the new inbox rules for the account. They are inserted into the account, replacing the ones already there.
+ */
+void cmd_pibr(char *argbuf) {
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ unbuffer_output();
+ cprintf("%d send new rules\n", SEND_LISTING);
+ char *newrules = CtdlReadMessageBody(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
+ StrBuf *NewConfig = NewStrBufPlain("Content-type: application/x-citadel-sieve-config; charset=UTF-8\nContent-transfer-encoding: 8bit\n\n", -1);
+
+ char *token;
+ char *rest = newrules;
+ while ((token = strtok_r(rest, "\n", &rest))) {
+ // Accept only lines containing rules
+ if (!strncasecmp(token, "rule|", 5)) {
+ StrBufAppendBufPlain(NewConfig, token, -1, 0);
+ StrBufAppendBufPlain(NewConfig, HKEY("\n"), 0);
+ }
+ }
+ free(newrules);
+ rewrite_rules_to_disk(ChrPtr(NewConfig));
+ FreeStrBuf(&NewConfig);
+}
+
+
+char *ctdl_module_init_sieve(void) {
+ if (!threading) {
+ CtdlRegisterProtoHook(cmd_gibr, "GIBR", "Get InBox Rules");
+ CtdlRegisterProtoHook(cmd_pibr, "PIBR", "Put InBox Rules");
+ CtdlRegisterRoomHook(serv_inboxrules_roomhook);
+ CtdlRegisterSessionHook(perform_inbox_processing, EVT_HOUSE, PRIO_HOUSE + 10);
+ }
+
+ /* return our module name for the log */
+ return "inboxrules";
+}
--- /dev/null
+/*
+ * This module handles the loading/saving and maintenance of the
+ * system's Internet configuration. It's not an optional component; I
+ * wrote it as a module merely to keep things as clean and loosely coupled
+ * as possible.
+ *
+ * Copyright (c) 1987-2022 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.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../genstamp.h"
+#include "../../domain.h"
+#include "../../ctdl_module.h"
+
+
+void inetcfg_setTo(struct CtdlMessage *msg) {
+ char *conf;
+ char buf[SIZ];
+
+ if (CM_IsEmpty(msg, eMesageText)) return;
+ conf = strdup(msg->cm_fields[eMesageText]);
+
+ if (conf != NULL) {
+ do {
+ extract_token(buf, conf, 0, '\n', sizeof buf);
+ strcpy(conf, &conf[strlen(buf)+1]);
+ } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
+
+ if (inetcfg != NULL) free(inetcfg);
+ inetcfg = conf;
+ }
+}
+
+
+/*
+ * This handler detects changes being made to the system's Internet
+ * configuration.
+ */
+int inetcfg_aftersave(struct CtdlMessage *msg, struct recptypes *recp) {
+ char *ptr;
+ int linelen;
+
+ /* If this isn't the configuration room, or if this isn't a MIME
+ * message, don't bother.
+ */
+ if ((msg->cm_fields[eOriginalRoom]) && (strcasecmp(msg->cm_fields[eOriginalRoom], SYSCONFIGROOM))) {
+ return(0);
+ }
+ if (msg->cm_format_type != 4) {
+ return(0);
+ }
+
+ ptr = msg->cm_fields[eMesageText];
+ while (ptr != NULL) {
+
+ linelen = strcspn(ptr, "\n");
+ if (linelen == 0) {
+ return(0); /* end of headers */
+ }
+
+ if (!strncasecmp(ptr, "Content-type: ", 14)) {
+ if (!strncasecmp(&ptr[14], INTERNETCFG, strlen(INTERNETCFG))) {
+ inetcfg_setTo(msg); /* changing configs */
+ }
+ }
+
+ ptr = strchr((char *)ptr, '\n');
+ if (ptr != NULL) ++ptr;
+ }
+
+ return(0);
+}
+
+
+void inetcfg_init_backend(long msgnum, void *userdata) {
+ struct CtdlMessage *msg;
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg != NULL) {
+ inetcfg_setTo(msg);
+ CM_Free(msg);
+ }
+}
+
+
+void inetcfg_init(void) {
+ syslog(LOG_DEBUG, "EVQ: called inetcfg_init()");
+ if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
+ return;
+ }
+ CtdlForEachMessage(MSGS_LAST, 1, NULL, INTERNETCFG, NULL, inetcfg_init_backend, NULL);
+}
+
+
+/*****************************************************************************/
+/* MODULE INITIALIZATION STUFF */
+/*****************************************************************************/
+
+
+char *ctdl_module_init_inetcfg(void) {
+ if (!threading) {
+ CtdlRegisterMessageHook(inetcfg_aftersave, EVT_AFTERSAVE);
+ inetcfg_init();
+ }
+
+ /* return our module name for the log */
+ return "inetcfg";
+}
--- /dev/null
+/*
+ * This module handles instant messaging between users.
+ *
+ * Copyright (c) 1987-2022 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.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../sysdep.h"
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../context.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../msgbase.h"
+#include "../../user_ops.h"
+#include "../../ctdl_module.h"
+#include "serv_instmsg.h"
+
+struct imlog {
+ struct imlog *next;
+ long usernums[2];
+ char usernames[2][128];
+ time_t lastmsg;
+ int last_serial;
+ StrBuf *conversation;
+};
+
+struct imlog *imlist = NULL;
+
+/*
+ * This function handles the logging of instant messages to disk.
+ */
+void log_instant_message(struct CitContext *me, struct CitContext *them, char *msgtext, int serial_number)
+{
+ long usernums[2];
+ long t;
+ struct imlog *iptr = NULL;
+ struct imlog *this_im = NULL;
+
+ memset(usernums, 0, sizeof usernums);
+ usernums[0] = me->user.usernum;
+ usernums[1] = them->user.usernum;
+
+ /* Always put the lower user number first, so we can use the array as a hash value which
+ * represents a pair of users. For a broadcast message one of the users will be 0.
+ */
+ if (usernums[0] > usernums[1]) {
+ t = usernums[0];
+ usernums[0] = usernums[1];
+ usernums[1] = t;
+ }
+
+ begin_critical_section(S_IM_LOGS);
+
+ /* Look for an existing conversation in the hash table.
+ * If not found, create a new one.
+ */
+
+ this_im = NULL;
+ for (iptr = imlist; iptr != NULL; iptr = iptr->next) {
+ if ((iptr->usernums[0] == usernums[0]) && (iptr->usernums[1] == usernums[1])) {
+ /* Existing conversation */
+ this_im = iptr;
+ }
+ }
+ if (this_im == NULL) {
+ /* New conversation */
+ this_im = malloc(sizeof(struct imlog));
+ memset(this_im, 0, sizeof (struct imlog));
+ this_im->usernums[0] = usernums[0];
+ this_im->usernums[1] = usernums[1];
+ /* usernames[] and usernums[] might not be in the same order. This is not an error. */
+ if (me) {
+ safestrncpy(this_im->usernames[0], me->user.fullname, sizeof this_im->usernames[0]);
+ }
+ if (them) {
+ safestrncpy(this_im->usernames[1], them->user.fullname, sizeof this_im->usernames[1]);
+ }
+ this_im->conversation = NewStrBuf();
+ this_im->next = imlist;
+ imlist = this_im;
+ StrBufAppendBufPlain(this_im->conversation, HKEY("<html><body>\r\n"), 0);
+ }
+
+ /* Since it's possible for this function to get called more than once if a user is logged
+ * in on multiple sessions, we use the message's serial number to keep track of whether
+ * we've already logged it.
+ */
+ if (this_im->last_serial != serial_number)
+ {
+ this_im->lastmsg = time(NULL); /* Touch the timestamp so we know when to flush */
+ this_im->last_serial = serial_number;
+ StrBufAppendBufPlain(this_im->conversation, HKEY("<p><b>"), 0);
+ StrBufAppendBufPlain(this_im->conversation, me->user.fullname, -1, 0);
+ StrBufAppendBufPlain(this_im->conversation, HKEY(":</b> "), 0);
+ StrEscAppend(this_im->conversation, NULL, msgtext, 0, 0);
+ StrBufAppendBufPlain(this_im->conversation, HKEY("</p>\r\n"), 0);
+ }
+ end_critical_section(S_IM_LOGS);
+}
+
+
+/*
+ * Delete any remaining instant messages
+ */
+void delete_instant_messages(void) {
+ struct ExpressMessage *ptr;
+
+ begin_critical_section(S_SESSION_TABLE);
+ while (CC->FirstExpressMessage != NULL) {
+ ptr = CC->FirstExpressMessage->next;
+ if (CC->FirstExpressMessage->text != NULL)
+ free(CC->FirstExpressMessage->text);
+ free(CC->FirstExpressMessage);
+ CC->FirstExpressMessage = ptr;
+ }
+ end_critical_section(S_SESSION_TABLE);
+}
+
+
+/*
+ * Retrieve instant messages
+ */
+void cmd_gexp(char *argbuf) {
+ struct ExpressMessage *ptr;
+
+ if (CC->FirstExpressMessage == NULL) {
+ cprintf("%d No instant messages waiting.\n", ERROR + MESSAGE_NOT_FOUND);
+ return;
+ }
+
+ begin_critical_section(S_SESSION_TABLE);
+ ptr = CC->FirstExpressMessage;
+ CC->FirstExpressMessage = CC->FirstExpressMessage->next;
+ end_critical_section(S_SESSION_TABLE);
+
+ cprintf("%d %d|%ld|%d|%s|%s|%s\n",
+ LISTING_FOLLOWS,
+ ((ptr->next != NULL) ? 1 : 0), /* more msgs? */
+ (long)ptr->timestamp, /* time sent */
+ ptr->flags, /* flags */
+ ptr->sender, /* sender of msg */
+ CtdlGetConfigStr("c_nodename"), /* static for now (and possibly deprecated) */
+ ptr->sender_email /* email or jid of sender */
+ );
+
+ if (ptr->text != NULL) {
+ memfmout(ptr->text, "\n");
+ free(ptr->text);
+ }
+
+ cprintf("000\n");
+ free(ptr);
+}
+
+
+/*
+ * Asynchronously deliver instant messages
+ */
+void cmd_gexp_async(void) {
+
+ /* Only do this if the session can handle asynchronous protocol */
+ if (CC->is_async == 0) return;
+
+ /* And don't do it if there's nothing to send. */
+ if (CC->FirstExpressMessage == NULL) return;
+
+ cprintf("%d instant msg\n", ASYNC_MSG + ASYNC_GEXP);
+}
+
+
+/*
+ * Back end support function for send_instant_message() and company
+ */
+void add_xmsg_to_context(struct CitContext *ccptr, struct ExpressMessage *newmsg)
+{
+ struct ExpressMessage *findend;
+
+ if (ccptr->FirstExpressMessage == NULL) {
+ ccptr->FirstExpressMessage = newmsg;
+ }
+ else {
+ findend = ccptr->FirstExpressMessage;
+ while (findend->next != NULL) {
+ findend = findend->next;
+ }
+ findend->next = newmsg;
+ }
+
+ /* If the target context is a session which can handle asynchronous
+ * messages, go ahead and set the flag for that.
+ */
+ set_async_waiting(ccptr);
+}
+
+
+/*
+ * This is the back end to the instant message sending function.
+ * Returns the number of users to which the message was sent.
+ * Sending a zero-length message tests for recipients without sending messages.
+ */
+int send_instant_message(char *lun, char *lem, char *x_user, char *x_msg)
+{
+ int message_sent = 0; /* number of successful sends */
+ struct CitContext *ccptr;
+ struct ExpressMessage *newmsg = NULL;
+ int do_send = 0; /* 1 = send message; 0 = only check for valid recipient */
+ static int serial_number = 0; /* this keeps messages from getting logged twice */
+
+ if (!IsEmptyStr(x_msg)) {
+ do_send = 1;
+ }
+
+ /* find the target user's context and append the message */
+ begin_critical_section(S_SESSION_TABLE);
+ ++serial_number;
+ for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
+
+ if ( ((!strcasecmp(ccptr->user.fullname, x_user))
+ || (!strcasecmp(x_user, "broadcast")))
+ && (ccptr->can_receive_im)
+ && ((ccptr->disable_exp == 0)
+ || (CC->user.axlevel >= AxAideU)) ) {
+ if (do_send) {
+ newmsg = (struct ExpressMessage *) malloc(sizeof (struct ExpressMessage));
+ memset(newmsg, 0, sizeof (struct ExpressMessage));
+ time(&(newmsg->timestamp));
+ safestrncpy(newmsg->sender, lun, sizeof newmsg->sender);
+ safestrncpy(newmsg->sender_email, lem, sizeof newmsg->sender_email);
+ if (!strcasecmp(x_user, "broadcast")) {
+ newmsg->flags |= EM_BROADCAST;
+ }
+ newmsg->text = strdup(x_msg);
+
+ add_xmsg_to_context(ccptr, newmsg);
+
+ /* and log it ... */
+ if (ccptr != CC) {
+ log_instant_message(CC, ccptr, newmsg->text, serial_number);
+ }
+ }
+ ++message_sent;
+ }
+ }
+ end_critical_section(S_SESSION_TABLE);
+ return (message_sent);
+}
+
+
+/*
+ * send instant messages
+ */
+void cmd_sexp(char *argbuf)
+{
+ int message_sent = 0;
+ char x_user[USERNAME_SIZE];
+ char x_msg[1024];
+ char *lem;
+ char *x_big_msgbuf = NULL;
+
+ if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
+ cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
+ return;
+ }
+
+ lem = CC->cs_principal_id;
+
+ extract_token(x_user, argbuf, 0, '|', sizeof x_user);
+ extract_token(x_msg, argbuf, 1, '|', sizeof x_msg);
+
+ if (!x_user[0]) {
+ cprintf("%d You were not previously paged.\n", ERROR + NO_SUCH_USER);
+ return;
+ }
+ if ((!strcasecmp(x_user, "broadcast")) && (CC->user.axlevel < AxAideU)) {
+ cprintf("%d Higher access required to send a broadcast.\n",
+ ERROR + HIGHER_ACCESS_REQUIRED);
+ return;
+ }
+ /* This loop handles text-transfer pages */
+ if (!strcmp(x_msg, "-")) {
+ message_sent = PerformXmsgHooks(CC->user.fullname, lem, x_user, "");
+ if (message_sent == 0) {
+ if (CtdlGetUser(NULL, x_user))
+ cprintf("%d '%s' does not exist.\n",
+ ERROR + NO_SUCH_USER, x_user);
+ else
+ cprintf("%d '%s' is not logged in "
+ "or is not accepting pages.\n",
+ ERROR + RESOURCE_NOT_OPEN, x_user);
+ return;
+ }
+ unbuffer_output();
+ cprintf("%d Transmit message (will deliver to %d users)\n",
+ SEND_LISTING, message_sent);
+ x_big_msgbuf = malloc(SIZ);
+ memset(x_big_msgbuf, 0, SIZ);
+ while (client_getln(x_msg, sizeof x_msg) >= 0 && strcmp(x_msg, "000")) {
+ x_big_msgbuf = realloc(x_big_msgbuf,
+ strlen(x_big_msgbuf) + strlen(x_msg) + 4);
+ if (!IsEmptyStr(x_big_msgbuf))
+ if (x_big_msgbuf[strlen(x_big_msgbuf)] != '\n')
+ strcat(x_big_msgbuf, "\n");
+ strcat(x_big_msgbuf, x_msg);
+ }
+ PerformXmsgHooks(CC->user.fullname, lem, x_user, x_big_msgbuf);
+ free(x_big_msgbuf);
+
+ /* This loop handles inline pages */
+ } else {
+ message_sent = PerformXmsgHooks(CC->user.fullname, lem, x_user, x_msg);
+
+ if (message_sent > 0) {
+ if (!IsEmptyStr(x_msg)) {
+ cprintf("%d Message sent", CIT_OK);
+ }
+ else {
+ cprintf("%d Ok to send message", CIT_OK);
+ }
+ if (message_sent > 1) {
+ cprintf(" to %d users", message_sent);
+ }
+ cprintf(".\n");
+ } else {
+ if (CtdlGetUser(NULL, x_user)) {
+ cprintf("%d '%s' does not exist.\n", ERROR + NO_SUCH_USER, x_user);
+ }
+ else {
+ cprintf("%d '%s' is not logged in or is not accepting instant messages.\n",
+ ERROR + RESOURCE_NOT_OPEN, x_user);
+ }
+ }
+
+
+ }
+}
+
+
+/*
+ * Enter or exit paging-disabled mode
+ */
+void cmd_dexp(char *argbuf)
+{
+ int new_state;
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ new_state = extract_int(argbuf, 0);
+ if ((new_state == 0) || (new_state == 1)) {
+ CC->disable_exp = new_state;
+ }
+
+ cprintf("%d %d\n", CIT_OK, CC->disable_exp);
+}
+
+
+/*
+ * Request client termination
+ */
+void cmd_reqt(char *argbuf) {
+ struct CitContext *ccptr;
+ int sessions = 0;
+ int which_session;
+ struct ExpressMessage *newmsg;
+
+ if (CtdlAccessCheck(ac_aide)) return;
+ which_session = extract_int(argbuf, 0);
+
+ begin_critical_section(S_SESSION_TABLE);
+ for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
+ if ((ccptr->cs_pid == which_session) || (which_session == 0)) {
+
+ newmsg = (struct ExpressMessage *)
+ malloc(sizeof (struct ExpressMessage));
+ memset(newmsg, 0,
+ sizeof (struct ExpressMessage));
+ time(&(newmsg->timestamp));
+ safestrncpy(newmsg->sender, CC->user.fullname,
+ sizeof newmsg->sender);
+ newmsg->flags |= EM_GO_AWAY;
+ newmsg->text = strdup("Automatic logoff requested.");
+
+ add_xmsg_to_context(ccptr, newmsg);
+ ++sessions;
+
+ }
+ }
+ end_critical_section(S_SESSION_TABLE);
+ cprintf("%d Sent termination request to %d sessions.\n", CIT_OK, sessions);
+}
+
+
+/*
+ * This is the back end for flush_conversations_to_disk()
+ * At this point we've isolated a single conversation (struct imlog)
+ * and are ready to write it to disk.
+ */
+void flush_individual_conversation(struct imlog *im) {
+ struct CtdlMessage *msg;
+ long msgnum = 0;
+ char roomname[ROOMNAMELEN];
+ StrBuf *MsgBuf, *FullMsgBuf;
+
+ StrBufAppendBufPlain(im->conversation, HKEY(
+ "</body>\r\n"
+ "</html>\r\n"
+ ), 0
+ );
+
+ MsgBuf = StrBufRFC2047encodeMessage(im->conversation);
+ FlushStrBuf(im->conversation);
+ FullMsgBuf = NewStrBufPlain(NULL, StrLength(im->conversation) + 100);
+
+ StrBufAppendBufPlain(FullMsgBuf, HKEY(
+ "Content-type: text/html; charset=UTF-8\r\n"
+ "Content-Transfer-Encoding: quoted-printable\r\n"
+ "\r\n"
+ ), 0
+ );
+ StrBufAppendBuf (FullMsgBuf, MsgBuf, 0);
+ FreeStrBuf(&MsgBuf);
+
+ msg = malloc(sizeof(struct CtdlMessage));
+ memset(msg, 0, sizeof(struct CtdlMessage));
+ msg->cm_magic = CTDLMESSAGE_MAGIC;
+ msg->cm_anon_type = MES_NORMAL;
+ msg->cm_format_type = FMT_RFC822;
+ if (!IsEmptyStr(im->usernames[0])) {
+ CM_SetField(msg, eAuthor, im->usernames[0], strlen(im->usernames[0]));
+ } else {
+ CM_SetField(msg, eAuthor, HKEY("Citadel"));
+ }
+ if (!IsEmptyStr(im->usernames[1])) {
+ CM_SetField(msg, eRecipient, im->usernames[1], strlen(im->usernames[1]));
+ }
+
+ CM_SetField(msg, eOriginalRoom, HKEY(PAGELOGROOM));
+ CM_SetAsFieldSB(msg, eMesageText, &FullMsgBuf); /* we own this memory now */
+
+ /* Start with usernums[1] because it's guaranteed to be higher than usernums[0],
+ * so if there's only one party, usernums[0] will be zero but usernums[1] won't.
+ * Create the room if necessary. Note that we create as a type 5 room rather
+ * than 4, which indicates that it's a personal room but we've already supplied
+ * the namespace prefix.
+ *
+ * In the unlikely event that usernums[1] is zero, a room with an invalid namespace
+ * prefix will be created. That's ok because the auto-purger will clean it up later.
+ */
+ snprintf(roomname, sizeof roomname, "%010ld.%s", im->usernums[1], PAGELOGROOM);
+ CtdlCreateRoom(roomname, 5, "", 0, 1, 1, VIEW_BBS);
+ msgnum = CtdlSubmitMsg(msg, NULL, roomname);
+ CM_Free(msg);
+
+ /* If there is a valid user number in usernums[0], save a copy for them too. */
+ if (im->usernums[0] > 0) {
+ snprintf(roomname, sizeof roomname, "%010ld.%s", im->usernums[0], PAGELOGROOM);
+ CtdlCreateRoom(roomname, 5, "", 0, 1, 1, VIEW_BBS);
+ CtdlSaveMsgPointerInRoom(roomname, msgnum, 0, NULL);
+ }
+
+ /* Finally, if we're logging instant messages globally, do that now. */
+ if (!IsEmptyStr(CtdlGetConfigStr("c_logpages"))) {
+ CtdlCreateRoom(CtdlGetConfigStr("c_logpages"), 3, "", 0, 1, 1, VIEW_BBS);
+ CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_logpages"), msgnum, 0, NULL);
+ }
+
+}
+
+/*
+ * Locate instant message conversations which have gone idle
+ * (or, if the server is shutting down, locate *all* conversations)
+ * and flush them to disk (in the participants' log rooms, etc.)
+ */
+void flush_conversations_to_disk(time_t if_older_than) {
+
+ struct imlog *flush_these = NULL;
+ struct imlog *dont_flush_these = NULL;
+ struct imlog *imptr = NULL;
+ struct CitContext *nptr;
+ int nContexts, i;
+
+ nptr = CtdlGetContextArray(&nContexts) ; /* Make a copy of the current wholist */
+
+ begin_critical_section(S_IM_LOGS);
+ while (imlist)
+ {
+ imptr = imlist;
+ imlist = imlist->next;
+
+ /* For a two party conversation, if one party has logged out, force flush. */
+ if (nptr) {
+ int user0_is_still_online = 0;
+ int user1_is_still_online = 0;
+ for (i=0; i<nContexts; i++) {
+ if (nptr[i].user.usernum == imptr->usernums[0]) ++user0_is_still_online;
+ if (nptr[i].user.usernum == imptr->usernums[1]) ++user1_is_still_online;
+ }
+ if (imptr->usernums[0] != imptr->usernums[1]) { /* two party conversation */
+ if ((!user0_is_still_online) || (!user1_is_still_online)) {
+ imptr->lastmsg = 0L; /* force flush */
+ }
+ }
+ else { /* one party conversation (yes, people do IM themselves) */
+ if (!user0_is_still_online) {
+ imptr->lastmsg = 0L; /* force flush */
+ }
+ }
+ }
+
+ /* Now test this conversation to see if it qualifies for flushing. */
+ if ((time(NULL) - imptr->lastmsg) > if_older_than)
+ {
+ /* This conversation qualifies. Move it to the list of ones to flush. */
+ imptr->next = flush_these;
+ flush_these = imptr;
+ }
+ else {
+ /* Move it to the list of ones not to flush. */
+ imptr->next = dont_flush_these;
+ dont_flush_these = imptr;
+ }
+ }
+ imlist = dont_flush_these;
+ end_critical_section(S_IM_LOGS);
+ free(nptr);
+
+ /* We are now outside of the critical section, and we are the only thread holding a
+ * pointer to a linked list of conversations to be flushed to disk.
+ */
+ while (flush_these) {
+
+ flush_individual_conversation(flush_these); /* This will free the string buffer */
+ imptr = flush_these;
+ flush_these = flush_these->next;
+ free(imptr);
+ }
+}
+
+
+
+void instmsg_timer(void) {
+ flush_conversations_to_disk(300); /* Anything that hasn't peeped in more than 5 minutes */
+}
+
+void instmsg_shutdown(void) {
+ flush_conversations_to_disk(0); /* Get it ALL onto disk NOW. */
+}
+
+char *ctdl_module_init_instmsg(void) {
+ if (!threading) {
+ CtdlRegisterProtoHook(cmd_gexp, "GEXP", "Get instant messages");
+ CtdlRegisterProtoHook(cmd_sexp, "SEXP", "Send an instant message");
+ CtdlRegisterProtoHook(cmd_dexp, "DEXP", "Disable instant messages");
+ CtdlRegisterProtoHook(cmd_reqt, "REQT", "Request client termination");
+ CtdlRegisterSessionHook(cmd_gexp_async, EVT_ASYNC, PRIO_ASYNC + 1);
+ CtdlRegisterSessionHook(delete_instant_messages, EVT_STOP, PRIO_STOP + 1);
+ CtdlRegisterXmsgHook(send_instant_message, XMSG_PRI_LOCAL);
+ CtdlRegisterSessionHook(instmsg_timer, EVT_TIMER, PRIO_CLEANUP + 400);
+ CtdlRegisterSessionHook(instmsg_shutdown, EVT_SHUTDOWN, PRIO_SHUTDOWN + 10);
+ }
+
+ /* return our module name for the log */
+ return "instmsg";
+}
--- /dev/null
+
+void ChatUnloadingTest(void);
+void allwrite (char *cmdbuf, int flag, char *username);
+CitContext *find_context (char **unstr);
+void cmd_pexp (char *argbuf); /* arg unused */
+void cmd_sexp (char *argbuf);
+void delete_instant_messages(void);
+void cmd_gexp(char *);
+int send_instant_message(char *, char *, char *, char *);
--- /dev/null
+// This module delivers messages to mailing lists.
+//
+// Copyright (c) 2002-2022 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.
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../clientsocket.h"
+#include "../../ctdl_module.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
+ char *netconf; // netconfig for this room (contains the recipients)
+};
+
+
+
+void listdeliver_do_msg(long msgnum, void *userdata) {
+ struct lddata *ld = (struct lddata *) userdata;
+ if (!ld) return;
+ char buf[SIZ];
+ char *ch;
+ char bounce_to[256];
+ int i = 0;
+
+ ld->msgnum = msgnum;
+ if (msgnum <= 0) return;
+
+ struct CtdlMessage *TheMessage = CtdlFetchMessage(msgnum, 1);
+ if (!TheMessage) return;
+
+ // 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, eMsgSubject, buf, strlen(buf));
+ }
+
+ // From: should be set to the list address because doing otherwise makes DKIM parsers angry.
+ // Reply-to: should be set to the list address so that replies come to the list.
+ snprintf(buf, sizeof buf, "room_%s@%s", CC->room.QRname, CtdlGetConfigStr("c_fqdn"));
+ for (ch=buf; *ch; ++ch) {
+ if (isspace(*ch)) *ch = '_';
+ }
+ CM_SetField(TheMessage, erFc822Addr, buf, strlen(buf));
+ CM_SetField(TheMessage, eReplyTo, buf, strlen(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) {
+ recipients[0] = 0;
+
+ int config_lines = num_tokens(ld->netconf, '\n');
+ for (i=0; i<config_lines; ++i) {
+ extract_token(buf, ld->netconf, i, '\n', sizeof buf);
+ if (!strncasecmp(buf, "listrecp|", 9)) {
+ if (recipients[0] != 0) {
+ strcat(recipients, ",");
+ }
+ strcat(recipients, &buf[9]);
+ }
+ if (!strncasecmp(buf, "digestrecp|", 11)) {
+ if (recipients[0] != 0) {
+ strcat(recipients, ",");
+ }
+ strcat(recipients, &buf[11]);
+ }
+ }
+
+ // Where do we want bounces and other noise to be sent? Certainly not to the list members!
+ snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", CtdlGetConfigStr("c_fqdn"));
+
+ // Now submit the message
+ struct recptypes *valid = validate_recipients(recipients, NULL, 0);
+ if (valid) {
+ valid->bounce_to = strdup(bounce_to);
+ valid->envelope_from = strdup(bounce_to);
+ valid->sending_room = strdup(CC->room.QRname);
+ CtdlSubmitMsg(TheMessage, valid, "");
+ free_recipients(valid);
+ }
+ }
+ CM_Free(TheMessage);
+}
+
+
+// Sweep through one room looking for mailing list deliveries to do
+void listdeliver_sweep_room(char *roomname) {
+ char *netconfig = NULL;
+ char *newnetconfig = NULL;
+ long lastsent = 0;
+ char buf[SIZ];
+ int config_lines;
+ int i;
+ int number_of_messages_processed = 0;
+ int number_of_recipients = 0;
+ struct lddata ld;
+
+ if (CtdlGetRoom(&CC->room, roomname)) {
+ syslog(LOG_DEBUG, "listdeliver: no room <%s>", roomname);
+ return;
+ }
+
+ netconfig = LoadRoomNetConfigFile(CC->room.QRnumber);
+ if (!netconfig) {
+ return; // no netconfig, no processing, no problem
+ }
+
+ config_lines = num_tokens(netconfig, '\n');
+ for (i=0; i<config_lines; ++i) {
+ extract_token(buf, netconfig, i, '\n', sizeof buf);
+
+ if (!strncasecmp(buf, "lastsent|", 9)) {
+ lastsent = atol(&buf[9]);
+ }
+ else if ( (!strncasecmp(buf, "listrecp|", 9)) || (!strncasecmp(buf, "digestrecp|", 11)) ) {
+ ++number_of_recipients;
+ }
+ }
+
+ if (number_of_recipients > 0) {
+ syslog(LOG_DEBUG, "listdeliver: processing new messages in <%s> for <%d> recipients", CC->room.QRname, number_of_recipients);
+ ld.netconf = netconfig;
+ number_of_messages_processed = CtdlForEachMessage(MSGS_GT, lastsent, NULL, NULL, NULL, listdeliver_do_msg, &ld);
+ syslog(LOG_INFO, "listdeliver: processed <%d> messages in <%s> for <%d> recipients", number_of_messages_processed, CC->room.QRname, number_of_recipients);
+
+ if (number_of_messages_processed > 0) {
+ syslog(LOG_DEBUG, "listdeliver: new lastsent is %ld", ld.msgnum);
+
+ // Update this room's netconfig with the updated lastsent
+ begin_critical_section(S_NETCONFIGS);
+ netconfig = LoadRoomNetConfigFile(CC->room.QRnumber);
+ if (!netconfig) {
+ netconfig = strdup("");
+ }
+
+ // The new netconfig begins with the new lastsent directive
+ newnetconfig = malloc(strlen(netconfig) + 1024);
+ sprintf(newnetconfig, "lastsent|%ld\n", ld.msgnum);
+
+ // And then we append all of the old netconfig, minus the old lastsent. Also omit blank lines.
+ config_lines = num_tokens(netconfig, '\n');
+ for (i=0; i<config_lines; ++i) {
+ extract_token(buf, netconfig, i, '\n', sizeof buf);
+ if ( (!IsEmptyStr(buf)) && (strncasecmp(buf, "lastsent|", 9)) ) {
+ sprintf(&newnetconfig[strlen(newnetconfig)], "%s\n", buf);
+ }
+ }
+
+ // Write the new netconfig back to disk
+ SaveRoomNetConfigFile(CC->room.QRnumber, newnetconfig);
+ end_critical_section(S_NETCONFIGS);
+ free(newnetconfig); // this was the new netconfig, free it because we're done with it
+ }
+ }
+ free(netconfig); // this was the old netconfig, free it even if we didn't do anything
+}
+
+
+// Callback for listdeliver_sweep()
+// Adds one room to the queue
+void listdeliver_queue_room(struct ctdlroom *qrbuf, void *data) {
+ Array *roomlistarr = (Array *)data;
+ array_append(roomlistarr, qrbuf->QRname);
+}
+
+
+// Queue up the list of rooms so we can sweep them for mailing list delivery instructions
+void listdeliver_sweep(void) {
+ static time_t last_run = 0L;
+ int i = 0;
+ time_t now = time(NULL);
+
+ // Run mailing list delivery no more frequently than once every 15 minutes (we should make this configurable)
+ if ( (now - last_run) < 900 ) {
+ syslog(LOG_DEBUG,
+ "listdeliver: delivery interval not yet reached; last run was %ldm%lds ago",
+ ((now - last_run) / 60),
+ ((now - last_run) % 60)
+ );
+ return;
+ }
+
+ // This is a simple concurrency check to make sure only one listdeliver
+ // run is done at a time. We could do this with a mutex, but since we
+ // don't really require extremely fine granularity here, we'll do it
+ // with a static variable instead.
+ if (doing_listdeliver) return;
+ doing_listdeliver = 1;
+
+ // Go through each room looking for mailing lists to process
+ syslog(LOG_DEBUG, "listdeliver: sweep started");
+
+ Array *roomlistarr = array_new(ROOMNAMELEN); // we have to queue them
+ CtdlForEachRoom(listdeliver_queue_room, roomlistarr); // otherwise we get multiple cursors in progress
+
+ for (i=0; i<array_len(roomlistarr); ++i) {
+ listdeliver_sweep_room((char *)array_get_element_at(roomlistarr, i));
+ }
+
+ array_free(roomlistarr);
+ syslog(LOG_DEBUG, "listdeliver: ended");
+ last_run = time(NULL);
+ doing_listdeliver = 0;
+}
+
+
+// Module entry point
+char *ctdl_module_init_listdeliver(void) {
+ if (!threading) {
+ CtdlRegisterSessionHook(listdeliver_sweep, EVT_TIMER, PRIO_AGGR + 50);
+ }
+
+ // return our module name for the log
+ return "listsub";
+}
--- /dev/null
+// This module handles self-service subscription/unsubscription to mail lists.
+//
+// Copyright (c) 2002-2022 by the citadel.org team
+//
+// This program is open source software. It runs great on the
+// Linux operating system (and probably elsewhere). You can use,
+// copy, and run 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.
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <crypt.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../clientsocket.h"
+#include "../../ctdl_module.h"
+
+
+enum { // one of these gets passed to do_subscribe_or_unsubscribe() so it knows what we asked for
+ UNSUBSCRIBE,
+ SUBSCRIBE
+};
+
+
+// The confirmation token will be generated by combining the room name and email address with the host key,
+// and then generating an encrypted hash of that string. The encrypted hash is included as part of the
+// confirmation link.
+void generate_confirmation_token(char *token_buf, size_t token_buf_len, char *roomname, char *emailaddr) {
+ char string_to_hash[1024];
+ struct crypt_data cd;
+ char *ptr;
+
+ snprintf(string_to_hash, sizeof string_to_hash, "%s|%s|%s", roomname, emailaddr, CtdlGetConfigStr("host_key"));
+ memset(&cd, 0, sizeof cd);
+
+ strncpy(token_buf, crypt_r(string_to_hash, "$1$ctdl", &cd), token_buf_len);
+
+ for (ptr=token_buf; *ptr; ++ptr) {
+ if (!isalnum((char)*ptr)) *ptr='X';
+ }
+}
+
+
+// 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 from_address[1024];
+ snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
+
+ char emailtext[SIZ];
+ snprintf(emailtext, sizeof emailtext,
+ "MIME-Version: 1.0\n"
+ "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
+ "\n"
+ "This is a multipart message in MIME format.\n"
+ "\n"
+ "--__ctdlmultipart__\n"
+ "Content-type: text/plain\n"
+ "\n"
+ "Someone (probably you) has submitted a request to subscribe\n"
+ "<%s> to the <%s> mailing list.\n"
+ "\n"
+ "Please go here to confirm this request:\n"
+ "%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s\n"
+ "\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"
+ "and you will not receive any further mailings.\n"
+ "\n"
+ "--__ctdlmultipart__\n"
+ "Content-type: text/html\n"
+ "\n"
+ "<html><body><p>Someone (probably you) has submitted a request to subscribe "
+ "<strong>%s</strong> to the <strong>%s</strong> mailing list.</p>"
+ "<p>Please go here to confirm this request:</p>"
+ "<p><a href=\"%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s\">"
+ "%s?cmd=confirm_subscribe&email=%s&room=%s&token=%s</a></p>"
+ "<p>If this request has been submitted in error and you do not "
+ "wish to receive the <strong>%s<strong> mailing list, simply do nothing, "
+ "and you will not receive any further mailings.</p>"
+ "</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
+ );
+
+ quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list subscription");
+ cprintf("%d confirmation email sent\n", CIT_OK);
+}
+
+
+// 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 from_address[1024];
+ snprintf(from_address, sizeof from_address, "noreply@%s", CtdlGetConfigStr("c_fqdn"));
+
+ char emailtext[SIZ];
+ snprintf(emailtext, sizeof emailtext,
+ "MIME-Version: 1.0\n"
+ "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n"
+ "\n"
+ "This is a multipart message in MIME format.\n"
+ "\n"
+ "--__ctdlmultipart__\n"
+ "Content-type: text/plain\n"
+ "\n"
+ "Someone (probably you) has submitted a request to unsubscribe\n"
+ "<%s> from the <%s> mailing list.\n"
+ "\n"
+ "Please go here to confirm this request:\n"
+ "%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s\n"
+ "\n"
+ "If this request has been submitted in error and you still\n"
+ "wish to receive the <%s> mailing list, simply do nothing,\n"
+ "and you will remain subscribed.\n"
+ "\n"
+ "--__ctdlmultipart__\n"
+ "Content-type: text/html\n"
+ "\n"
+ "<html><body><p>Someone (probably you) has submitted a request to unsubscribe "
+ "<strong>%s</strong> from the <strong>%s</strong> mailing list.</p>"
+ "<p>Please go here to confirm this request:</p>"
+ "<p><a href=\"%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s\">"
+ "%s?cmd=confirm_unsubscribe&email=%s&room=%s&token=%s</a></p>"
+ "<p>If this request has been submitted in error and you still "
+ "wish to receive the <strong>%s<strong> mailing list, simply do nothing, "
+ "and you will remain subscribed.</p>"
+ "</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
+ );
+
+ quickie_message("Citadel", from_address, emailaddr, NULL, emailtext, FMT_RFC822, "Please confirm your list unsubscription");
+ cprintf("%d confirmation email sent\n", CIT_OK);
+}
+
+
+// Confirm a list subscription or unsubscription
+void do_confirm(int cmd, char *roomname, char *emailaddr, char *url, char *generated_token, char *supplied_token) {
+ int i;
+ char buf[1024];
+ int config_lines = 0;
+ char *oldnetconfig, *newnetconfig;
+
+ // During opt #1, the server generated a persistent confirmation token for the user+room combination.
+ // Let's see if the user has supplied the same token during opt #2.
+ if (strcmp(generated_token, supplied_token)) {
+ cprintf("%d This request could not be authenticated.\n", ERROR + PASSWORD_REQUIRED);
+ return;
+ }
+
+ // If the generated token matches the supplied token, the request is authentic. Do what it says.
+
+ // Load the room's network configuration...
+ oldnetconfig = LoadRoomNetConfigFile(CC->room.QRnumber);
+ if (!oldnetconfig) {
+ oldnetconfig = strdup("");
+ }
+
+ // The new netconfig begins with an empty buffer...
+ begin_critical_section(S_NETCONFIGS);
+ newnetconfig = malloc(strlen(oldnetconfig) + 1024);
+ newnetconfig[0] = 0;
+
+ // Load the config lines in one by one, skipping any that reference this subscriber. Also remove blank lines.
+ config_lines = num_tokens(oldnetconfig, '\n');
+ for (i=0; i<config_lines; ++i) {
+ char buf_email[256];
+ extract_token(buf, oldnetconfig, i, '\n', sizeof buf);
+ extract_token(buf_email, buf, 1, '|', sizeof buf_email);
+ if ( !IsEmptyStr(buf) && (strcasecmp(buf_email, emailaddr)) ) {
+ sprintf(&newnetconfig[strlen(newnetconfig)], "%s\n", buf);
+ }
+ }
+
+ // We have now removed all lines containing the subscriber's email address. This deletes any pending requests.
+ // If this was an unsubscribe operation, they're now gone from the list.
+ // But if this was a subscribe operation, we now need to add them.
+ if (cmd == SUBSCRIBE) {
+ sprintf(&newnetconfig[strlen(newnetconfig)], "listrecp|%s\n", emailaddr);
+ }
+
+ // write it back to disk
+ SaveRoomNetConfigFile(CC->room.QRnumber, newnetconfig);
+ end_critical_section(S_NETCONFIGS);
+ free(oldnetconfig);
+ free(newnetconfig);
+ cprintf("%d The pending request was confirmed.\n", CIT_OK);
+}
+
+
+// process subscribe/unsubscribe requests and confirmations
+void cmd_lsub(char *cmdbuf) {
+ char cmd[20];
+ char roomname[ROOMNAMELEN];
+ char emailaddr[1024];
+ char url[1024];
+ char generated_token[128];
+ char supplied_token[128];
+
+ extract_token(cmd, cmdbuf, 0, '|', sizeof cmd); // token 0 is the sub-command being sent
+ extract_token(roomname, cmdbuf, 1, '|', sizeof roomname); // token 1 is always a room name
+ extract_token(emailaddr, cmdbuf, 2, '|', sizeof emailaddr); // token 2 is the subscriber's email address
+ extract_token(url, cmdbuf, 3, '|', sizeof url); // token 3 is the URL at which we subscribed
+ extract_token(supplied_token, cmdbuf, 4, '|', sizeof supplied_token); // token 4 is the token supplied by the caller
+
+ // First confirm that the caller is referencing a room that actually exists.
+ if (CtdlGetRoom(&CC->room, roomname) != 0) {
+ cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, roomname);
+ return;
+ }
+
+ if ((CC->room.QRflags2 & QR2_SELFLIST) == 0) {
+ cprintf("%d '%s' does not accept subscribe/unsubscribe requests.\n", ERROR + ROOM_NOT_FOUND, roomname);
+ return;
+ }
+
+ // Generate a confirmation token -- either to supply to the user for opt #1 or to compare for opt #2
+ generate_confirmation_token(generated_token, sizeof generated_token, roomname, emailaddr);
+
+ // Now parse the command.
+ if (!strcasecmp(cmd, "subscribe")) {
+ send_subscribe_confirmation_email(roomname, emailaddr, url, generated_token);
+ }
+
+ else if (!strcasecmp(cmd, "unsubscribe")) {
+ send_unsubscribe_confirmation_email(roomname, emailaddr, url, generated_token);
+ }
+
+ else if (!strcasecmp(cmd, "confirm_subscribe")) {
+ do_confirm(SUBSCRIBE, roomname, emailaddr, url, generated_token, supplied_token);
+ }
+
+ else if (!strcasecmp(cmd, "confirm_unsubscribe")) {
+ do_confirm(UNSUBSCRIBE, roomname, emailaddr, url, generated_token, supplied_token);
+ }
+
+ else { // sorry man, I can't deal with that
+ cprintf("%d Invalid command '%s'\n", ERROR + ILLEGAL_VALUE, cmd);
+ }
+}
+
+
+/*
+ * Module entry point
+ */
+char *ctdl_module_init_listsub(void) {
+ if (!threading) {
+ CtdlRegisterProtoHook(cmd_lsub, "LSUB", "List subscribe/unsubscribe");
+ }
+
+ /* return our module name for the log */
+ return "listsub";
+}
--- /dev/null
+// This module dumps and/or loads the Citadel database in XML format.
+//
+// Copyright (c) 1987-2022 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.
+//
+// Explanation of <progress> tags:
+//
+// 0% started
+// 2% finished exporting configuration
+// 7% finished exporting users
+// 12% finished exporting openids
+// 17% finished exporting rooms
+// 18% finished exporting floors
+// 25% finished exporting visits
+// 26-99% exporting messages
+// 100% finished exporting messages
+//
+// These tags are inserted into the XML stream to give the reader an approximation of its progress.
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <ctype.h>
+#include <limits.h>
+#include <expat.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../user_ops.h"
+#include "../../euidindex.h"
+#include "../../ctdl_module.h"
+
+char migr_tempfilename1[PATH_MAX];
+char migr_tempfilename2[PATH_MAX];
+FILE *migr_global_message_list;
+int total_msgs = 0;
+char *ikey = NULL; // If we're importing a config key we store it here.
+
+//*****************************************************************************
+//* Code which implements the export appears in this section *
+//*****************************************************************************
+
+// Output a string to the client with these characters escaped: & < > " '
+void xml_strout(char *str) {
+
+ char *c = str;
+
+ if (str == NULL) {
+ return;
+ }
+
+ while (*c != 0) {
+ if (*c == '\"') {
+ client_write(HKEY("""));
+ }
+ else if (*c == '\'') {
+ client_write(HKEY("'"));
+ }
+ else if (*c == '<') {
+ client_write(HKEY("<"));
+ }
+ else if (*c == '>') {
+ client_write(HKEY(">"));
+ }
+ else if (*c == '&') {
+ client_write(HKEY("&"));
+ }
+ else {
+ client_write(c, 1);
+ }
+ ++c;
+ }
+}
+
+
+// Export a user record as XML
+void migr_export_users_backend(char *username, void *data) {
+
+ struct ctdluser u;
+ if (CtdlGetUser(&u, username) != 0) {
+ return;
+ }
+
+ client_write(HKEY("<user>\n"));
+ cprintf("<u_version>%d</u_version>\n", u.version);
+ cprintf("<u_uid>%ld</u_uid>\n", (long)u.uid);
+ client_write(HKEY("<u_password>")); xml_strout(u.password); client_write(HKEY("</u_password>\n"));
+ cprintf("<u_flags>%u</u_flags>\n", u.flags);
+ cprintf("<u_timescalled>%ld</u_timescalled>\n", u.timescalled);
+ cprintf("<u_posted>%ld</u_posted>\n", u.posted);
+ cprintf("<u_axlevel>%d</u_axlevel>\n", u.axlevel);
+ cprintf("<u_usernum>%ld</u_usernum>\n", u.usernum);
+ cprintf("<u_lastcall>%ld</u_lastcall>\n", (long)u.lastcall);
+ cprintf("<u_USuserpurge>%d</u_USuserpurge>\n", u.USuserpurge);
+ client_write(HKEY("<u_fullname>")); xml_strout(u.fullname); client_write(HKEY("</u_fullname>\n"));
+ cprintf("<u_msgnum_bio>%ld</u_msgnum_bio>\n", u.msgnum_bio);
+ cprintf("<u_msgnum_pic>%ld</u_msgnum_pic>\n", u.msgnum_pic);
+ cprintf("<u_emailaddrs>%s</u_emailaddrs>\n", u.emailaddrs);
+ cprintf("<u_msgnum_inboxrules>%ld</u_msgnum_inboxrules>\n", u.msgnum_inboxrules);
+ cprintf("<u_lastproc_inboxrules>%ld</u_lastproc_inboxrules>\n", u.lastproc_inboxrules);
+ client_write(HKEY("</user>\n"));
+}
+
+
+void migr_export_users(void) {
+ ForEachUser(migr_export_users_backend, NULL);
+}
+
+
+void migr_export_room_msg(long msgnum, void *userdata) {
+ static int count = 0;
+
+ cprintf("%ld,", msgnum);
+ if (++count%10==0) {
+ cprintf("\n");
+ }
+ fprintf(migr_global_message_list, "%ld\n", msgnum);
+}
+
+
+void migr_export_rooms_backend(struct ctdlroom *buf, void *data) {
+ client_write(HKEY("<room>\n"));
+ client_write(HKEY("<QRname>"));
+ xml_strout(buf->QRname);
+ client_write(HKEY("</QRname>\n"));
+ client_write(HKEY("<QRpasswd>"));
+ xml_strout(buf->QRpasswd);
+ client_write(HKEY("</QRpasswd>\n"));
+ cprintf("<QRroomaide>%ld</QRroomaide>\n", buf->QRroomaide);
+ cprintf("<QRhighest>%ld</QRhighest>\n", buf->QRhighest);
+ cprintf("<QRgen>%ld</QRgen>\n", (long)buf->QRgen);
+ cprintf("<QRflags>%u</QRflags>\n", buf->QRflags);
+ if (buf->QRflags & QR_DIRECTORY) {
+ client_write(HKEY("<QRdirname>"));
+ xml_strout(buf->QRdirname);
+ client_write(HKEY("</QRdirname>\n"));
+ }
+ cprintf("<QRfloor>%d</QRfloor>\n", buf->QRfloor);
+ cprintf("<QRmtime>%ld</QRmtime>\n", (long)buf->QRmtime);
+ cprintf("<QRexpire_mode>%d</QRexpire_mode>\n", buf->QRep.expire_mode);
+ cprintf("<QRexpire_value>%d</QRexpire_value>\n", buf->QRep.expire_value);
+ cprintf("<QRnumber>%ld</QRnumber>\n", buf->QRnumber);
+ cprintf("<QRorder>%d</QRorder>\n", buf->QRorder);
+ cprintf("<QRflags2>%u</QRflags2>\n", buf->QRflags2);
+ cprintf("<QRdefaultview>%d</QRdefaultview>\n", buf->QRdefaultview);
+ cprintf("<msgnum_info>%ld</msgnum_info>\n", buf->msgnum_info);
+ cprintf("<msgnum_pic>%ld</msgnum_pic>\n", buf->msgnum_pic);
+ client_write(HKEY("</room>\n"));
+
+ // message list goes inside this tag
+ CtdlGetRoom(&CC->room, buf->QRname);
+ client_write(HKEY("<room_messages>"));
+ client_write(HKEY("<FRname>"));
+ xml_strout(buf->QRname); // buf->QRname rather than CC->room.QRname to guarantee consistency
+ client_write(HKEY("</FRname>\n"));
+ client_write(HKEY("<FRmsglist>\n"));
+ CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, migr_export_room_msg, NULL);
+ client_write(HKEY("</FRmsglist>\n"));
+ client_write(HKEY("</room_messages>\n"));
+}
+
+
+void migr_export_rooms(void) {
+ char cmd[SIZ];
+ migr_global_message_list = fopen(migr_tempfilename1, "w");
+ if (migr_global_message_list != NULL) {
+ CtdlForEachRoom(migr_export_rooms_backend, NULL);
+ fclose(migr_global_message_list);
+ }
+
+ // Process the 'global' message list. (Sort it and remove dups.
+ // Dups are ok because a message may be in more than one room, but
+ // this will be handled by exporting the reference count, not by
+ // exporting the message multiple times.)
+ snprintf(cmd, sizeof cmd, "sort -n <%s >%s", migr_tempfilename1, migr_tempfilename2);
+ if (system(cmd) != 0) {
+ syslog(LOG_ERR, "migrate: error %d", errno);
+ }
+ snprintf(cmd, sizeof cmd, "uniq <%s >%s", migr_tempfilename2, migr_tempfilename1);
+ if (system(cmd) != 0) {
+ syslog(LOG_ERR, "migrate: error %d", errno);
+ }
+
+ snprintf(cmd, sizeof cmd, "wc -l %s", migr_tempfilename1);
+ FILE *fp = popen(cmd, "r");
+ if (fp) {
+ fgets(cmd, sizeof cmd, fp);
+ pclose(fp);
+ total_msgs = atoi(cmd);
+ }
+ else {
+ total_msgs = 1; // any nonzero just to keep it from barfing
+ }
+ syslog(LOG_DEBUG, "migrate: total messages to be exported: %d", total_msgs);
+}
+
+
+void migr_export_floors(void) {
+ struct floor qfbuf, *buf;
+ int i;
+
+ for (i=0; i < MAXFLOORS; ++i) {
+ client_write(HKEY("<floor>\n"));
+ cprintf("<f_num>%d</f_num>\n", i);
+ CtdlGetFloor(&qfbuf, i);
+ buf = &qfbuf;
+ cprintf("<f_flags>%u</f_flags>\n", buf->f_flags);
+ client_write(HKEY("<f_name>")); xml_strout(buf->f_name); client_write(HKEY("</f_name>\n"));
+ cprintf("<f_ref_count>%d</f_ref_count>\n", buf->f_ref_count);
+ cprintf("<f_ep_expire_mode>%d</f_ep_expire_mode>\n", buf->f_ep.expire_mode);
+ cprintf("<f_ep_expire_value>%d</f_ep_expire_value>\n", buf->f_ep.expire_value);
+ client_write(HKEY("</floor>\n"));
+ }
+}
+
+
+// Return nonzero if the supplied string contains only characters which are valid in a sequence set.
+int is_sequence_set(char *s) {
+ if (!s) return(0);
+
+ char *c = s;
+ char ch;
+ while (ch = *c++, ch) {
+ if (!strchr("0123456789*,:", ch)) {
+ return(0);
+ }
+ }
+ return(1);
+}
+
+
+// Traverse the visits file...
+void migr_export_visits(void) {
+ visit vbuf;
+ struct cdbdata *cdbv;
+
+ cdb_rewind(CDB_VISIT);
+
+ while (cdbv = cdb_next_item(CDB_VISIT), cdbv != NULL) {
+ memset(&vbuf, 0, sizeof(visit));
+ memcpy(&vbuf, cdbv->ptr,
+ ((cdbv->len > sizeof(visit)) ?
+ sizeof(visit) : cdbv->len));
+ cdb_free(cdbv);
+
+ client_write(HKEY("<visit>\n"));
+ cprintf("<v_roomnum>%ld</v_roomnum>\n", vbuf.v_roomnum);
+ cprintf("<v_roomgen>%ld</v_roomgen>\n", vbuf.v_roomgen);
+ cprintf("<v_usernum>%ld</v_usernum>\n", vbuf.v_usernum);
+
+ client_write(HKEY("<v_seen>"));
+ if ( (!IsEmptyStr(vbuf.v_seen)) && (is_sequence_set(vbuf.v_seen)) ) {
+ xml_strout(vbuf.v_seen);
+ }
+ else {
+ cprintf("%ld", vbuf.v_lastseen);
+ }
+ client_write(HKEY("</v_seen>"));
+
+ if ( (!IsEmptyStr(vbuf.v_answered)) && (is_sequence_set(vbuf.v_answered)) ) {
+ client_write(HKEY("<v_answered>"));
+ xml_strout(vbuf.v_answered);
+ client_write(HKEY("</v_answered>\n"));
+ }
+
+ cprintf("<v_flags>%u</v_flags>\n", vbuf.v_flags);
+ cprintf("<v_view>%d</v_view>\n", vbuf.v_view);
+ client_write(HKEY("</visit>\n"));
+ }
+}
+
+
+void migr_export_message(long msgnum) {
+ struct MetaData smi;
+ struct CtdlMessage *msg;
+ struct ser_ret smr;
+ long bytes_written = 0;
+ long this_block = 0;
+
+ // We can use a static buffer here because there will never be more than
+ // one of this operation happening at any given time, and it's really best
+ // to just keep it allocated once instead of torturing malloc/free.
+ // Call this function with msgnum "-1" to free the buffer when finished.
+
+ static int encoded_alloc = 0;
+ static char *encoded_msg = NULL;
+
+ if (msgnum < 0) {
+ if ((encoded_alloc == 0) && (encoded_msg != NULL)) {
+ free(encoded_msg);
+ encoded_alloc = 0;
+ encoded_msg = NULL;
+ }
+ return;
+ }
+
+ // Ok, here we go ...
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) return; // fail silently
+
+ client_write(HKEY("<message>\n"));
+ GetMetaData(&smi, msgnum);
+ cprintf("<msg_msgnum>%ld</msg_msgnum>\n", msgnum);
+ cprintf("<msg_meta_refcount>%d</msg_meta_refcount>\n", smi.meta_refcount);
+ cprintf("<msg_meta_rfc822_length>%ld</msg_meta_rfc822_length>\n", smi.meta_rfc822_length);
+ client_write(HKEY("<msg_meta_content_type>")); xml_strout(smi.meta_content_type); client_write(HKEY("</msg_meta_content_type>\n"));
+
+ client_write(HKEY("<msg_text>"));
+ CtdlSerializeMessage(&smr, msg);
+ CM_Free(msg);
+
+ // Predict the buffer size we need. Expand the buffer if necessary.
+ int encoded_len = smr.len * 15 / 10 ;
+ if (encoded_len > encoded_alloc) {
+ encoded_alloc = encoded_len;
+ encoded_msg = realloc(encoded_msg, encoded_alloc);
+ }
+
+ if (encoded_msg == NULL) {
+ // Questionable hack that hopes it'll work next time and we only lose one message
+ encoded_alloc = 0;
+ }
+ else {
+ // Once we do the encoding we know the exact size
+ encoded_len = CtdlEncodeBase64(encoded_msg, (char *)smr.ser, smr.len, 1);
+
+ // Careful now. If the message is gargantuan, trying to write multiple gigamegs in one
+ // big write operation can make our transport unhappy. So we'll chunk it up 10 KB at a time.
+ bytes_written = 0;
+ while ( (bytes_written < encoded_len) && (!server_shutting_down) ) {
+ this_block = encoded_len - bytes_written;
+ if (this_block > 10240) {
+ this_block = 10240;
+ }
+ client_write(&encoded_msg[bytes_written], this_block);
+ bytes_written += this_block;
+ }
+ }
+
+ free(smr.ser);
+
+ client_write(HKEY("</msg_text>\n"));
+ client_write(HKEY("</message>\n"));
+}
+
+
+void migr_export_openids(void) {
+ struct cdbdata *cdboi;
+ long usernum;
+ char url[512];
+
+ cdb_rewind(CDB_EXTAUTH);
+ while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
+ if (cdboi->len > sizeof(long)) {
+ client_write(HKEY("<openid>\n"));
+ memcpy(&usernum, cdboi->ptr, sizeof(long));
+ snprintf(url, sizeof url, "%s", (cdboi->ptr)+sizeof(long) );
+ client_write(HKEY("<oid_url>"));
+ xml_strout(url);
+ client_write(HKEY("</oid_url>\n"));
+ cprintf("<oid_usernum>%ld</oid_usernum>\n", usernum);
+ client_write(HKEY("</openid>\n"));
+ }
+ cdb_free(cdboi);
+ }
+}
+
+
+void migr_export_configs(void) {
+ struct cdbdata *cdbcfg;
+ int keylen = 0;
+ char *key = NULL;
+ char *value = NULL;
+
+ cdb_rewind(CDB_CONFIG);
+ while (cdbcfg = cdb_next_item(CDB_CONFIG), cdbcfg != NULL) {
+
+ keylen = strlen(cdbcfg->ptr);
+ key = cdbcfg->ptr;
+ value = cdbcfg->ptr + keylen + 1;
+
+ client_write("<config key=\"", 13);
+ xml_strout(key);
+ client_write("\">", 2);
+ xml_strout(value);
+ client_write("</config>\n", 10);
+ cdb_free(cdbcfg);
+ }
+}
+
+
+void migr_export_messages(void) {
+ char buf[SIZ];
+ long msgnum;
+ int count = 0;
+ int progress = 0;
+ int prev_progress = 0;
+ CitContext *Ctx;
+
+ Ctx = CC;
+ migr_global_message_list = fopen(migr_tempfilename1, "r");
+ if (migr_global_message_list != NULL) {
+ syslog(LOG_INFO, "migrate: opened %s", migr_tempfilename1);
+ while ((Ctx->kill_me == 0) &&
+ (fgets(buf, sizeof(buf), migr_global_message_list) != NULL)) {
+ msgnum = atol(buf);
+ if (msgnum > 0L) {
+ migr_export_message(msgnum);
+ ++count;
+ }
+ progress = (count * 74 / total_msgs) + 25 ;
+ if ((progress > prev_progress) && (progress < 100)) {
+ cprintf("<progress>%d</progress>\n", progress);
+ }
+ prev_progress = progress;
+ }
+ fclose(migr_global_message_list);
+ }
+ if (Ctx->kill_me == 0) {
+ syslog(LOG_INFO, "migrate: exported %d messages.", count);
+ }
+ else {
+ syslog(LOG_ERR, "migrate: export aborted due to client disconnect!");
+ }
+
+ migr_export_message(-1L); // This frees the encoding buffer
+}
+
+
+void migr_do_export(void) {
+ CitContext *Ctx;
+
+ Ctx = CC;
+ cprintf("%d Exporting all Citadel databases.\n", LISTING_FOLLOWS);
+ Ctx->dont_term = 1;
+
+ client_write(HKEY("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"));
+ client_write(HKEY("<citadel_migrate_data>\n"));
+ cprintf("<version>%d</version>\n", REV_LEVEL);
+ cprintf("<progress>%d</progress>\n", 0);
+
+ // export the configuration database
+ migr_export_configs();
+ cprintf("<progress>%d</progress>\n", 2);
+
+ if (Ctx->kill_me == 0) migr_export_users();
+ cprintf("<progress>%d</progress>\n", 7);
+ if (Ctx->kill_me == 0) migr_export_openids();
+ cprintf("<progress>%d</progress>\n", 12);
+ if (Ctx->kill_me == 0) migr_export_rooms();
+ cprintf("<progress>%d</progress>\n", 17);
+ if (Ctx->kill_me == 0) migr_export_floors();
+ cprintf("<progress>%d</progress>\n", 18);
+ if (Ctx->kill_me == 0) migr_export_visits();
+ cprintf("<progress>%d</progress>\n", 25);
+ if (Ctx->kill_me == 0) migr_export_messages();
+ cprintf("<progress>%d</progress>\n", 100);
+ client_write(HKEY("</citadel_migrate_data>\n"));
+ client_write(HKEY("000\n"));
+ Ctx->dont_term = 0;
+}
+
+
+// Import code
+// Here's the code that implements the import side. It's going to end up
+// being one big loop with lots of global variables. I don't care.
+// You wouldn't run multiple concurrent imports anyway. If this offends your
+// delicate sensibilities then go rewrite it in Ruby on Rails or something.
+
+
+int citadel_migrate_data = 0; // Are we inside a <citadel_migrate_data> tag pair?
+StrBuf *migr_chardata = NULL;
+StrBuf *migr_MsgData = NULL;
+struct ctdluser usbuf;
+struct ctdlroom qrbuf;
+char openid_url[512];
+long openid_usernum = 0;
+char FRname[ROOMNAMELEN];
+struct floor flbuf;
+int floornum = 0;
+visit vbuf;
+struct MetaData smi;
+long import_msgnum = 0;
+
+// This callback stores up the data which appears in between tags.
+void migr_xml_chardata(void *data, const XML_Char *s, int len) {
+ StrBufAppendBufPlain(migr_chardata, s, len, 0);
+}
+
+
+void migr_xml_start(void *data, const char *el, const char **attr) {
+ int i;
+
+ // *** GENERAL STUFF ***
+
+ // Reset chardata_len to zero and init buffer
+ FlushStrBuf(migr_chardata);
+ FlushStrBuf(migr_MsgData);
+
+ if (!strcasecmp(el, "citadel_migrate_data")) {
+ ++citadel_migrate_data;
+
+ // As soon as it looks like the input data is a genuine Citadel XML export,
+ // whack the existing database on disk to make room for the new one.
+ if (citadel_migrate_data == 1) {
+ syslog(LOG_INFO, "migrate: erasing existing databases so we can receive the incoming import");
+ for (i = 0; i < MAXCDB; ++i) {
+ cdb_trunc(i);
+ }
+ }
+ return;
+ }
+
+ if (citadel_migrate_data != 1) {
+ syslog(LOG_ERR, "migrate: out-of-sequence tag <%s> detected. Warning: ODD-DATA!", el);
+ return;
+ }
+
+ // When we begin receiving XML for one of these record types, clear out the associated
+ // buffer so we don't accidentally carry over any data from a previous record.
+ if (!strcasecmp(el, "user")) memset(&usbuf, 0, sizeof (struct ctdluser));
+ else if (!strcasecmp(el, "openid")) memset(openid_url, 0, sizeof openid_url);
+ else if (!strcasecmp(el, "room")) memset(&qrbuf, 0, sizeof (struct ctdlroom));
+ else if (!strcasecmp(el, "room_messages")) memset(FRname, 0, sizeof FRname);
+ else if (!strcasecmp(el, "floor")) memset(&flbuf, 0, sizeof (struct floor));
+ else if (!strcasecmp(el, "visit")) memset(&vbuf, 0, sizeof (visit));
+
+ else if (!strcasecmp(el, "message")) {
+ memset(&smi, 0, sizeof (struct MetaData));
+ import_msgnum = 0;
+ }
+ else if (!strcasecmp(el, "config")) {
+ if (ikey != NULL) {
+ free(ikey);
+ ikey = NULL;
+ }
+ while (*attr) {
+ if (!strcasecmp(attr[0], "key")) {
+ ikey = strdup(attr[1]);
+ }
+ attr += 2;
+ }
+ }
+
+}
+
+
+int migr_userrecord(void *data, const char *el) {
+ if (!strcasecmp(el, "u_version")) usbuf.version = atoi(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "u_uid")) usbuf.uid = atol(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "u_password")) safestrncpy(usbuf.password, ChrPtr(migr_chardata), sizeof usbuf.password);
+ else if (!strcasecmp(el, "u_flags")) usbuf.flags = atoi(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "u_timescalled")) usbuf.timescalled = atol(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "u_posted")) usbuf.posted = atol(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "u_axlevel")) usbuf.axlevel = atoi(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "u_usernum")) usbuf.usernum = atol(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "u_lastcall")) usbuf.lastcall = atol(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "u_USuserpurge")) usbuf.USuserpurge = atoi(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "u_fullname")) safestrncpy(usbuf.fullname, ChrPtr(migr_chardata), sizeof usbuf.fullname);
+ else if (!strcasecmp(el, "u_msgnum_bio")) usbuf.msgnum_bio = atol(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "u_msgnum_pic")) usbuf.msgnum_pic = atol(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "u_emailaddrs")) safestrncpy(usbuf.emailaddrs, ChrPtr(migr_chardata), sizeof usbuf.emailaddrs);
+ else if (!strcasecmp(el, "u_msgnum_inboxrules")) usbuf.msgnum_inboxrules = atol(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "u_lastproc_inboxrules")) usbuf.lastproc_inboxrules = atol(ChrPtr(migr_chardata));
+ else return 0;
+ return 1;
+}
+
+
+int migr_roomrecord(void *data, const char *el) {
+ if (!strcasecmp(el, "QRname")) safestrncpy(qrbuf.QRname, ChrPtr(migr_chardata), sizeof qrbuf.QRname);
+ else if (!strcasecmp(el, "QRpasswd")) safestrncpy(qrbuf.QRpasswd, ChrPtr(migr_chardata), sizeof qrbuf.QRpasswd);
+ else if (!strcasecmp(el, "QRroomaide")) qrbuf.QRroomaide = atol(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "QRhighest")) qrbuf.QRhighest = atol(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "QRgen")) qrbuf.QRgen = atol(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "QRflags")) qrbuf.QRflags = atoi(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "QRdirname")) safestrncpy(qrbuf.QRdirname, ChrPtr(migr_chardata), sizeof qrbuf.QRdirname);
+ else if (!strcasecmp(el, "QRfloor")) qrbuf.QRfloor = atoi(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "QRmtime")) qrbuf.QRmtime = atol(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "QRexpire_mode")) qrbuf.QRep.expire_mode = atoi(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "QRexpire_value")) qrbuf.QRep.expire_value = atoi(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "QRnumber")) qrbuf.QRnumber = atol(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "QRorder")) qrbuf.QRorder = atoi(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "QRflags2")) qrbuf.QRflags2 = atoi(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "QRdefaultview")) qrbuf.QRdefaultview = atoi(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "msgnum_info")) qrbuf.msgnum_info = atol(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "msgnum_pic")) qrbuf.msgnum_pic = atol(ChrPtr(migr_chardata));
+ else return 0;
+ return 1;
+}
+
+
+int migr_floorrecord(void *data, const char *el) {
+ if (!strcasecmp(el, "f_num")) floornum = atoi(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "f_flags")) flbuf.f_flags = atoi(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "f_name")) safestrncpy(flbuf.f_name, ChrPtr(migr_chardata), sizeof flbuf.f_name);
+ else if (!strcasecmp(el, "f_ref_count")) flbuf.f_ref_count = atoi(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "f_ep_expire_mode")) flbuf.f_ep.expire_mode = atoi(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "f_ep_expire_value")) flbuf.f_ep.expire_value = atoi(ChrPtr(migr_chardata));
+ else return 0;
+ return 1;
+}
+
+
+int migr_visitrecord(void *data, const char *el) {
+ if (!strcasecmp(el, "v_roomnum")) vbuf.v_roomnum = atol(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "v_roomgen")) vbuf.v_roomgen = atol(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "v_usernum")) vbuf.v_usernum = atol(ChrPtr(migr_chardata));
+
+ else if (!strcasecmp(el, "v_seen")) {
+ int is_textual_seen = 0;
+ int i;
+ int max = StrLength(migr_chardata);
+
+ vbuf.v_lastseen = atol(ChrPtr(migr_chardata));
+ is_textual_seen = 0;
+ for (i=0; i < max; ++i)
+ if (!isdigit(ChrPtr(migr_chardata)[i]))
+ is_textual_seen = 1;
+ if (is_textual_seen)
+ safestrncpy(vbuf.v_seen, ChrPtr(migr_chardata), sizeof vbuf.v_seen);
+ }
+
+ else if (!strcasecmp(el, "v_answered")) safestrncpy(vbuf.v_answered, ChrPtr(migr_chardata), sizeof vbuf.v_answered);
+ else if (!strcasecmp(el, "v_flags")) vbuf.v_flags = atoi(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "v_view")) vbuf.v_view = atoi(ChrPtr(migr_chardata));
+ else return 0;
+ return 1;
+}
+
+
+void migr_xml_end(void *data, const char *el) {
+ const char *ptr;
+ int msgcount = 0;
+ long msgnum = 0L;
+ long *msglist = NULL;
+ int msglist_alloc = 0;
+ // *** GENERAL STUFF ***
+
+ if (!strcasecmp(el, "citadel_migrate_data")) {
+ --citadel_migrate_data;
+ return;
+ }
+
+ if (citadel_migrate_data != 1) {
+ syslog(LOG_ERR, "migrate: out-of-sequence tag <%s> detected. Warning: ODD-DATA!", el);
+ return;
+ }
+
+ // syslog(LOG_DEBUG, "END TAG: <%s> DATA: <%s>\n", el, (migr_chardata_len ? migr_chardata : ""));
+
+ // *** CONFIG ***
+
+ if (!strcasecmp(el, "config")) {
+ syslog(LOG_DEBUG, "migrate: imported config key=%s", ikey);
+
+ if (ikey != NULL) {
+ CtdlSetConfigStr(ikey, (char *)ChrPtr(migr_chardata));
+ free(ikey);
+ ikey = NULL;
+ }
+ else {
+ syslog(LOG_INFO, "migrate: closed a <config> tag but no key name was supplied.");
+ }
+ }
+
+ // *** USER ***
+ else if ((!strncasecmp(el, HKEY("u_"))) &&
+ migr_userrecord(data, el))
+ ; /* Nothing to do anymore */
+ else if (!strcasecmp(el, "user")) {
+ CtdlPutUser(&usbuf);
+ syslog(LOG_INFO, "migrate: imported user: %s", usbuf.fullname);
+ }
+
+ // *** OPENID ***
+
+ else if (!strcasecmp(el, "oid_url")) safestrncpy(openid_url, ChrPtr(migr_chardata), sizeof openid_url);
+ else if (!strcasecmp(el, "oid_usernum")) openid_usernum = atol(ChrPtr(migr_chardata));
+
+ else if (!strcasecmp(el, "openid")) { // see serv_openid_rp.c for a description of the record format
+ char *oid_data;
+ int oid_data_len;
+ oid_data_len = sizeof(long) + strlen(openid_url) + 1;
+ oid_data = malloc(oid_data_len);
+ memcpy(oid_data, &openid_usernum, sizeof(long));
+ memcpy(&oid_data[sizeof(long)], openid_url, strlen(openid_url) + 1);
+ cdb_store(CDB_EXTAUTH, openid_url, strlen(openid_url), oid_data, oid_data_len);
+ free(oid_data);
+ syslog(LOG_INFO, "migrate: imported OpenID: %s (%ld)", openid_url, openid_usernum);
+ }
+
+ // *** ROOM ***
+ else if ((!strncasecmp(el, HKEY("QR"))) &&
+ migr_roomrecord(data, el))
+ ; // Nothing to do anymore
+ else if (!strcasecmp(el, "room")) {
+ CtdlPutRoom(&qrbuf);
+ syslog(LOG_INFO, "migrate: imported room: %s", qrbuf.QRname);
+ }
+
+ // *** ROOM MESSAGE POINTERS ***
+
+ else if (!strcasecmp(el, "FRname")) {
+ safestrncpy(FRname, ChrPtr(migr_chardata), sizeof FRname);
+ }
+
+ else if (!strcasecmp(el, "FRmsglist")) {
+ if (!IsEmptyStr(FRname)) {
+ msgcount = 0;
+ msglist_alloc = 1000;
+ msglist = malloc(sizeof(long) * msglist_alloc);
+
+ syslog(LOG_DEBUG, "migrate: message list for: %s", FRname);
+
+ ptr = ChrPtr(migr_chardata);
+ while (*ptr != 0) {
+ while ((*ptr != 0) && (!isdigit(*ptr))) {
+ ++ptr;
+ }
+ if ((*ptr != 0) && (isdigit(*ptr))) {
+ msgnum = atol(ptr);
+ if (msgnum > 0L) {
+ if (msgcount >= msglist_alloc) {
+ msglist_alloc *= 2;
+ msglist = realloc(msglist, sizeof(long) * msglist_alloc);
+ }
+ msglist[msgcount++] = msgnum;
+ }
+ }
+ while ((*ptr != 0) && (isdigit(*ptr))) {
+ ++ptr;
+ }
+ }
+ }
+ if (msgcount > 0) {
+ CtdlSaveMsgPointersInRoom(FRname, msglist, msgcount, 0, NULL, 1);
+ }
+ free(msglist);
+ msglist = NULL;
+ msglist_alloc = 0;
+ syslog(LOG_DEBUG, "migrate: imported %d messages.", msgcount);
+ if (server_shutting_down) {
+ return;
+ }
+ }
+
+ // *** FLOORS ***
+ else if ((!strncasecmp(el, HKEY("f_"))) &&
+ migr_floorrecord(data, el))
+ ; // Nothing to do anymore
+
+ else if (!strcasecmp(el, "floor")) {
+ CtdlPutFloor(&flbuf, floornum);
+ syslog(LOG_INFO, "migrate: imported floor #%d (%s)", floornum, flbuf.f_name);
+ }
+
+ // *** VISITS ***
+ else if ((!strncasecmp(el, HKEY("v_"))) && migr_visitrecord(data, el)) {
+ ; // Nothing to do anymore
+ }
+ else if (!strcasecmp(el, "visit")) {
+ put_visit(&vbuf);
+ syslog(LOG_INFO, "migrate: imported visit: %ld/%ld/%ld", vbuf.v_roomnum, vbuf.v_roomgen, vbuf.v_usernum);
+ }
+
+ // *** MESSAGES ***
+
+ else if (!strcasecmp(el, "msg_msgnum")) smi.meta_msgnum = import_msgnum = atol(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "msg_meta_refcount")) smi.meta_refcount = atoi(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "msg_meta_rfc822_length")) smi.meta_rfc822_length = atoi(ChrPtr(migr_chardata));
+ else if (!strcasecmp(el, "msg_meta_content_type")) safestrncpy(smi.meta_content_type, ChrPtr(migr_chardata), sizeof smi.meta_content_type);
+
+ else if (!strcasecmp(el, "msg_text")) {
+ long rc;
+ struct CtdlMessage *msg;
+
+ FlushStrBuf(migr_MsgData);
+ StrBufDecodeBase64To(migr_chardata, migr_MsgData);
+
+ msg = CtdlDeserializeMessage(import_msgnum, -1,
+ ChrPtr(migr_MsgData),
+ StrLength(migr_MsgData));
+ if (msg != NULL) {
+ rc = CtdlSaveThisMessage(msg, import_msgnum, 0);
+ if (rc == 0) {
+ PutMetaData(&smi);
+ }
+ CM_Free(msg);
+ }
+ else {
+ rc = -1;
+ }
+
+ syslog(LOG_INFO,
+ "migrate: %s message #%ld, size=%d, ref=%d, body=%ld, content-type: %s",
+ (rc!= 0)?"failed to import":"imported",
+ import_msgnum,
+ StrLength(migr_MsgData),
+ smi.meta_refcount,
+ smi.meta_rfc822_length,
+ smi.meta_content_type
+ );
+ memset(&smi, 0, sizeof(smi));
+ }
+
+ // *** MORE GENERAL STUFF ***
+
+ FlushStrBuf(migr_chardata);
+}
+
+
+// Import begins here
+void migr_do_import(void) {
+ XML_Parser xp;
+ char buf[SIZ];
+
+ unbuffer_output();
+ migr_chardata = NewStrBufPlain(NULL, SIZ * 20);
+ migr_MsgData = NewStrBufPlain(NULL, SIZ * 20);
+ xp = XML_ParserCreate(NULL);
+ if (!xp) {
+ cprintf("%d Failed to create XML parser instance\n", ERROR+INTERNAL_ERROR);
+ return;
+ }
+ XML_SetElementHandler(xp, migr_xml_start, migr_xml_end);
+ XML_SetCharacterDataHandler(xp, migr_xml_chardata);
+
+ CC->dont_term = 1;
+
+ cprintf("%d sock it to me\n", SEND_LISTING);
+ unbuffer_output();
+
+ client_set_inbound_buf(SIZ * 10);
+
+ while (client_getln(buf, sizeof buf) >= 0 && strcmp(buf, "000")) {
+ XML_Parse(xp, buf, strlen(buf), 0);
+ if (server_shutting_down)
+ break; // Should we break or return?
+ }
+
+ XML_Parse(xp, "", 0, 1);
+ XML_ParserFree(xp);
+ FreeStrBuf(&migr_chardata);
+ FreeStrBuf(&migr_MsgData);
+ rebuild_euid_index();
+ rebuild_usersbynumber();
+ CtdlSetConfigInt("MM_fulltext_wordbreaker", -1); // Set an invalid wordbreaker to force re-indexing
+ CC->dont_term = 0;
+}
+
+
+// ******************************************************************************
+// * Dispatcher, Common code *
+// ******************************************************************************
+
+// Dump out the pathnames of directories which can be copied "as is"
+void migr_do_listdirs(void) {
+ cprintf("%d Don't forget these:\n", LISTING_FOLLOWS);
+ cprintf("files|%s\n", ctdl_file_dir);
+ cprintf("messages|%s\n", ctdl_message_dir);
+ cprintf("keys|%s\n", ctdl_key_dir);
+ cprintf("000\n");
+}
+
+
+// ******************************************************************************
+// * Repair database integrity *
+// ******************************************************************************
+
+StrBuf *PlainMessageBuf = NULL;
+HashList *UsedMessageIDS = NULL;
+
+int migr_restore_message_metadata(long msgnum, int refcount) {
+ struct MetaData smi;
+ struct CtdlMessage *msg;
+ char *mptr = NULL;
+
+ // We can use a static buffer here because there will never be more than
+ // one of this operation happening at any given time, and it's really best
+ // to just keep it allocated once instead of torturing malloc/free.
+ // Call this function with msgnum "-1" to free the buffer when finished.
+ static int encoded_alloc = 0;
+ static char *encoded_msg = NULL;
+
+ if (msgnum < 0) {
+ if ((encoded_alloc == 0) && (encoded_msg != NULL)) {
+ free(encoded_msg);
+ encoded_alloc = 0;
+ encoded_msg = NULL;
+ // todo FreeStrBuf(&PlainMessageBuf); PlainMessageBuf = NULL;
+ }
+ return 0;
+ }
+
+ if (PlainMessageBuf == NULL) {
+ PlainMessageBuf = NewStrBufPlain(NULL, 10*SIZ);
+ }
+
+ // Ok, here we go ...
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) {
+ return 1;
+ }
+
+ GetMetaData(&smi, msgnum);
+ smi.meta_msgnum = msgnum;
+ smi.meta_refcount = refcount;
+
+ // restore the content type from the message body:
+ mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
+ if (mptr != NULL) {
+ char *aptr;
+ safestrncpy(smi.meta_content_type, &mptr[13], sizeof smi.meta_content_type);
+ striplt(smi.meta_content_type);
+ aptr = smi.meta_content_type;
+ while (!IsEmptyStr(aptr)) {
+ if ((*aptr == ';')
+ || (*aptr == ' ')
+ || (*aptr == 13)
+ || (*aptr == 10)) {
+ memset(aptr, 0, sizeof(smi.meta_content_type) - (aptr - smi.meta_content_type));
+ }
+ else aptr++;
+ }
+ }
+
+ CC->redirect_buffer = PlainMessageBuf;
+ CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
+ smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
+ CC->redirect_buffer = NULL;
+
+ syslog(LOG_INFO,
+ "migrate: setting message #%ld meta data to: refcount=%d, bodylength=%ld, content-type: %s",
+ smi.meta_msgnum,
+ smi.meta_refcount,
+ smi.meta_rfc822_length,
+ smi.meta_content_type
+ );
+
+ PutMetaData(&smi);
+ CM_Free(msg);
+ return 0;
+}
+
+
+void migr_check_room_msg(long msgnum, void *userdata) {
+ fprintf(migr_global_message_list, "%ld %s\n", msgnum, CC->room.QRname);
+}
+
+
+void migr_check_rooms_backend(struct ctdlroom *buf, void *data) {
+ // message list goes inside this tag
+ CtdlGetRoom(&CC->room, buf->QRname);
+ CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, migr_check_room_msg, NULL);
+}
+
+
+void RemoveMessagesFromRooms(StrBuf *RoomNameVec, long msgnum) {
+ struct MetaData smi;
+ const char *Pos = NULL;
+ StrBuf *oneRoom = NewStrBuf();
+
+ syslog(LOG_INFO, "migrate: removing message pointer %ld from these rooms: %s", msgnum, ChrPtr(RoomNameVec));
+
+ while (Pos != StrBufNOTNULL){
+ StrBufExtract_NextToken(oneRoom, RoomNameVec, &Pos, '|');
+ CtdlDeleteMessages(ChrPtr(oneRoom), &msgnum, 1, "");
+ };
+ GetMetaData(&smi, msgnum);
+ AdjRefCount(msgnum, -smi.meta_refcount);
+}
+
+
+void migr_do_restore_meta(void) {
+ char buf[SIZ];
+ int failGetMessage;
+ long msgnum = 0;
+ int lastnum = 0;
+ int refcount = 0;
+ CitContext *Ctx;
+ char *prn;
+ StrBuf *RoomNames;
+ char cmd[SIZ];
+
+ migr_global_message_list = fopen(migr_tempfilename1, "w");
+ if (migr_global_message_list != NULL) {
+ CtdlForEachRoom(migr_check_rooms_backend, NULL);
+ fclose(migr_global_message_list);
+ }
+
+ // Process the 'global' message list. (Sort it and remove dups.
+ // Dups are ok because a message may be in more than one room, but
+ // this will be handled by exporting the reference count, not by
+ // exporting the message multiple times.)
+ snprintf(cmd, sizeof cmd, "sort -n <%s >%s", migr_tempfilename1, migr_tempfilename2);
+ if (system(cmd) != 0) {
+ syslog(LOG_ERR, "migrate: error %d", errno);
+ }
+
+ RoomNames = NewStrBuf();
+ Ctx = CC;
+ migr_global_message_list = fopen(migr_tempfilename2, "r");
+ if (migr_global_message_list != NULL) {
+ syslog(LOG_INFO, "migrate: opened %s", migr_tempfilename1);
+ while ((Ctx->kill_me == 0) &&
+ (fgets(buf, sizeof(buf), migr_global_message_list) != NULL)) {
+ msgnum = atol(buf);
+ if (msgnum == 0L)
+ continue;
+ if (lastnum == 0) {
+ lastnum = msgnum;
+ }
+ prn = strchr(buf, ' ');
+ if (lastnum != msgnum) {
+ failGetMessage = migr_restore_message_metadata(lastnum, refcount);
+ if (failGetMessage) {
+ RemoveMessagesFromRooms(RoomNames, lastnum);
+ }
+ refcount = 1;
+ lastnum = msgnum;
+ if (prn != NULL) {
+ StrBufPlain(RoomNames, prn + 1, -1);
+ }
+ StrBufTrim(RoomNames);
+ }
+ else {
+ if (prn != NULL) {
+ if (StrLength(RoomNames) > 0) {
+ StrBufAppendBufPlain(RoomNames, HKEY("|"), 0);
+ }
+ StrBufAppendBufPlain(RoomNames, prn, -1, 1);
+ StrBufTrim(RoomNames);
+ }
+ refcount ++;
+ }
+ lastnum = msgnum;
+ }
+ failGetMessage = migr_restore_message_metadata(msgnum, refcount);
+ if (failGetMessage) {
+ RemoveMessagesFromRooms(RoomNames, lastnum);
+ }
+ fclose(migr_global_message_list);
+ }
+
+ migr_restore_message_metadata(-1L, -1); // This frees the encoding buffer
+ cprintf("%d system analysis completed", CIT_OK);
+ Ctx->kill_me = 1;
+}
+
+
+// ******************************************************************************
+// * Dispatcher, Common code *
+// ******************************************************************************
+void cmd_migr(char *cmdbuf) {
+ char cmd[32];
+
+ if (CtdlAccessCheck(ac_aide)) return;
+
+ if (CtdlTrySingleUser()) {
+ CtdlDisableHouseKeeping();
+ CtdlMakeTempFileName(migr_tempfilename1, sizeof migr_tempfilename1);
+ CtdlMakeTempFileName(migr_tempfilename2, sizeof migr_tempfilename2);
+
+ extract_token(cmd, cmdbuf, 0, '|', sizeof cmd);
+ if (!strcasecmp(cmd, "export")) {
+ migr_do_export();
+ }
+ else if (!strcasecmp(cmd, "import")) {
+ migr_do_import();
+ }
+ else if (!strcasecmp(cmd, "listdirs")) {
+ migr_do_listdirs();
+ }
+ else if (!strcasecmp(cmd, "restoremeta")) {
+ migr_do_restore_meta();
+ }
+ else {
+ cprintf("%d illegal command\n", ERROR + ILLEGAL_VALUE);
+ }
+
+ unlink(migr_tempfilename1);
+ unlink(migr_tempfilename2);
+
+ CtdlEnableHouseKeeping();
+ CtdlEndSingleUser();
+ }
+ else {
+ cprintf("%d The migrator is already running.\n", ERROR + RESOURCE_BUSY);
+ }
+}
+
+
+// ******************************************************************************
+// * Module Hook *
+// ******************************************************************************
+
+char *ctdl_module_init_migrate() {
+ if (!threading) {
+ CtdlRegisterProtoHook(cmd_migr, "MIGR", "Across-the-wire migration");
+ }
+
+ // return our module name for the log
+ return "migrate";
+}
--- /dev/null
+/*
+ * Automatically copies the contents of a "New User Greetings" room to the
+ * inbox of any new user upon account creation.
+ *
+ * Copyright (c) 1987-2022 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.
+ */
+
+/*
+ * Name of the New User Greetings room.
+ */
+#define NEWUSERGREETINGS "New User Greetings"
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include "../../ctdl_module.h"
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+
+/*
+ * Copy the contents of the New User Greetings> room to the user's Mail> room.
+ */
+void CopyNewUserGreetings(void) {
+ struct cdbdata *cdbfr;
+ long *msglist = NULL;
+ int num_msgs = 0;
+ char mailboxname[ROOMNAMELEN];
+
+
+ /* Only do this for new users. */
+ if (CC->user.timescalled != 1) return;
+
+ /* This user's mailbox. */
+ CtdlMailboxName(mailboxname, sizeof mailboxname, &CC->user, MAILROOM);
+
+ /* Go to the source room ... bail out silently if it's not there,
+ * or if it's not private.
+ */
+ if (CtdlGetRoom(&CC->room, NEWUSERGREETINGS) != 0) return;
+ if ((CC->room.QRflags & QR_PRIVATE) == 0) return;
+
+ cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
+
+ if (cdbfr != NULL) {
+ msglist = malloc(cdbfr->len);
+ memcpy(msglist, cdbfr->ptr, cdbfr->len);
+ num_msgs = cdbfr->len / sizeof(long);
+ cdb_free(cdbfr);
+ }
+
+ if (num_msgs > 0) {
+ CtdlSaveMsgPointersInRoom(mailboxname, msglist, num_msgs, 1, NULL, 0);
+ }
+
+ /* Now free the memory we used, and go away. */
+ if (msglist != NULL) free(msglist);
+}
+
+
+char *ctdl_module_init_newuser(void) {
+ if (!threading) {
+ CtdlRegisterSessionHook(CopyNewUserGreetings, EVT_LOGIN, PRIO_LOGIN + 1);
+ }
+
+ /* return our module name for the log */
+ return "newuser";
+}
--- /dev/null
+//
+// NNTP server module (RFC 3977)
+//
+// Copyright (c) 2014-2022 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.
+//
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <termios.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <syslog.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../control.h"
+#include "../../user_ops.h"
+#include "../../room_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../genstamp.h"
+#include "../../domain.h"
+#include "../../clientsocket.h"
+#include "../../locate_host.h"
+#include "../../citadel_dirs.h"
+#include "../../ctdl_module.h"
+#include "serv_nntp.h"
+
+#ifndef __FreeBSD__
+extern long timezone;
+#endif
+
+//
+// Tests whether the supplied string is a valid newsgroup name
+// Returns true (nonzero) or false (0)
+//
+int is_valid_newsgroup_name(char *name) {
+ char *ptr = name;
+ int has_a_letter = 0;
+ int num_dots = 0;
+
+ if (!ptr) return(0);
+ if (!strncasecmp(name, "ctdl.", 5)) return(0);
+
+ while (*ptr != 0) {
+
+ if (isalpha(ptr[0])) {
+ has_a_letter = 1;
+ }
+
+ if (ptr[0] == '.') {
+ ++num_dots;
+ }
+
+ if ( (isalnum(ptr[0]))
+ || (ptr[0] == '.')
+ || (ptr[0] == '+')
+ || (ptr[0] == '-')
+ ) {
+ ++ptr;
+ }
+ else {
+ return(0);
+ }
+ }
+ return( (has_a_letter) && (num_dots >= 1) ) ;
+}
+
+
+//
+// Convert a Citadel room name to a valid newsgroup name
+//
+void room_to_newsgroup(char *target, char *source, size_t target_size) {
+
+ if (!target) return;
+ if (!source) return;
+
+ if (is_valid_newsgroup_name(source)) {
+ strncpy(target, source, target_size);
+ return;
+ }
+
+ strcpy(target, "ctdl.");
+ int len = 5;
+ char *ptr = source;
+ char ch;
+
+ while (ch=*ptr++, ch!=0) {
+ if (len >= target_size) return;
+ if ( (isalnum(ch))
+ || (ch == '.')
+ || (ch == '-')
+ ) {
+ target[len++] = tolower(ch);
+ target[len] = 0;
+ }
+ else {
+ target[len++] = '+' ;
+ sprintf(&target[len], "%02x", ch);
+ len += 2;
+ target[len] = 0;
+ }
+ }
+}
+
+
+//
+// Convert a newsgroup name to a Citadel room name.
+// This function recognizes names converted with room_to_newsgroup() and restores them with full fidelity.
+//
+void newsgroup_to_room(char *target, char *source, size_t target_size) {
+
+ if (!target) return;
+ if (!source) return;
+
+ if (strncasecmp(source, "ctdl.", 5)) { // not a converted room name; pass through as-is
+ strncpy(target, source, target_size);
+ return;
+ }
+
+ target[0] = 0;
+ int len = 0;
+ char *ptr = &source[5];
+ char ch;
+
+ while (ch=*ptr++, ch!=0) {
+ if (len >= target_size) return;
+ if (ch == '+') {
+ char hex[3];
+ long digit;
+ hex[0] = *ptr++;
+ hex[1] = *ptr++;
+ hex[2] = 0;
+ digit = strtol(hex, NULL, 16);
+ ch = (char)digit;
+ }
+ target[len++] = ch;
+ target[len] = 0;
+ }
+}
+
+
+//
+// Here's where our NNTP session begins its happy day.
+//
+void nntp_greeting(void) {
+ strcpy(CC->cs_clientname, "NNTP session");
+ CC->cs_flags |= CS_STEALTH;
+
+ CC->session_specific_data = malloc(sizeof(citnntp));
+ citnntp *nntpstate = (citnntp *) CC->session_specific_data;
+ memset(nntpstate, 0, sizeof(citnntp));
+
+ if (CC->nologin==1) {
+ cprintf("451 Too many connections are already open; please try again later.\r\n");
+ CC->kill_me = KILLME_MAX_SESSIONS_EXCEEDED;
+ return;
+ }
+
+ // Display the standard greeting
+ cprintf("200 %s NNTP Citadel server is not finished yet\r\n", CtdlGetConfigStr("c_fqdn"));
+}
+
+
+//
+// NNTPS is just like NNTP, except it goes crypto right away.
+//
+void nntps_greeting(void) {
+ CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
+#ifdef HAVE_OPENSSL
+ if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO; /* kill session if no crypto */
+#endif
+ nntp_greeting();
+}
+
+
+//
+// implements the STARTTLS command
+//
+void nntp_starttls(void) {
+ char ok_response[SIZ];
+ char nosup_response[SIZ];
+ char error_response[SIZ];
+
+ sprintf(ok_response, "382 Begin TLS negotiation now\r\n");
+ sprintf(nosup_response, "502 Can not initiate TLS negotiation\r\n");
+ sprintf(error_response, "580 Internal error\r\n");
+ CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
+}
+
+
+//
+// Implements the CAPABILITY command
+//
+void nntp_capabilities(void) {
+ cprintf("101 Capability list:\r\n");
+ cprintf("IMPLEMENTATION Citadel %d\r\n", REV_LEVEL);
+ cprintf("VERSION 2\r\n");
+ cprintf("READER\r\n");
+ cprintf("MODE-READER\r\n");
+ cprintf("LIST ACTIVE NEWSGROUPS\r\n");
+ cprintf("OVER\r\n");
+#ifdef HAVE_OPENSSL
+ cprintf("STARTTLS\r\n");
+#endif
+ if (!CC->logged_in) {
+ cprintf("AUTHINFO USER\r\n");
+ }
+ cprintf(".\r\n");
+}
+
+
+//
+// Implements the QUIT command
+//
+void nntp_quit(void) {
+ cprintf("221 Goodbye...\r\n");
+ CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
+}
+
+
+//
+// Implements the AUTHINFO USER command (RFC 4643)
+//
+void nntp_authinfo_user(const char *username) {
+ int a = CtdlLoginExistingUser(username);
+ switch (a) {
+ case login_already_logged_in:
+ cprintf("482 Already logged in\r\n");
+ return;
+ case login_too_many_users:
+ cprintf("481 Too many users are already online (maximum is %d)\r\n", CtdlGetConfigInt("c_maxsessions"));
+ return;
+ case login_ok:
+ cprintf("381 Password required for %s\r\n", CC->curr_user);
+ return;
+ case login_not_found:
+ cprintf("481 %s not found\r\n", username);
+ return;
+ default:
+ cprintf("502 Internal error\r\n");
+ }
+}
+
+
+//
+// Implements the AUTHINFO PASS command (RFC 4643)
+//
+void nntp_authinfo_pass(const char *buf) {
+ int a;
+
+ a = CtdlTryPassword(buf, strlen(buf));
+
+ switch (a) {
+ case pass_already_logged_in:
+ cprintf("482 Already logged in\r\n");
+ return;
+ case pass_no_user:
+ cprintf("482 Authentication commands issued out of sequence\r\n");
+ return;
+ case pass_wrong_password:
+ cprintf("481 Authentication failed\r\n");
+ return;
+ case pass_ok:
+ cprintf("281 Authentication accepted\r\n");
+ return;
+ }
+}
+
+
+//
+// Implements the AUTHINFO extension (RFC 4643) in USER/PASS mode
+//
+void nntp_authinfo(const char *cmd) {
+
+ if (!strncasecmp(cmd, "authinfo user ", 14)) {
+ nntp_authinfo_user(&cmd[14]);
+ }
+
+ else if (!strncasecmp(cmd, "authinfo pass ", 14)) {
+ nntp_authinfo_pass(&cmd[14]);
+ }
+
+ else {
+ cprintf("502 command unavailable\r\n");
+ }
+}
+
+
+//
+// Utility function to fetch the current list of message numbers in a room
+//
+struct nntp_msglist nntp_fetch_msglist(struct ctdlroom *qrbuf) {
+ struct nntp_msglist nm;
+ struct cdbdata *cdbfr;
+
+ cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
+ if (cdbfr != NULL) {
+ nm.msgnums = (long*)cdbfr->ptr;
+ cdbfr->ptr = NULL;
+ nm.num_msgs = cdbfr->len / sizeof(long);
+ cdbfr->len = 0;
+ cdb_free(cdbfr);
+ } else {
+ nm.num_msgs = 0;
+ nm.msgnums = NULL;
+ }
+ return(nm);
+}
+
+
+//
+// Output a room name (newsgroup name) in formats required for LIST and NEWGROUPS command
+//
+void output_roomname_in_list_format(struct ctdlroom *qrbuf, int which_format, char *wildmat_pattern) {
+ char n_name[1024];
+ struct nntp_msglist nm;
+ long low_water_mark = 0;
+ long high_water_mark = 0;
+
+ room_to_newsgroup(n_name, qrbuf->QRname, sizeof n_name);
+
+ if ((wildmat_pattern != NULL) && (!IsEmptyStr(wildmat_pattern))) {
+ if (!wildmat(n_name, wildmat_pattern)) {
+ return;
+ }
+ }
+
+ nm = nntp_fetch_msglist(qrbuf);
+ if ((nm.num_msgs > 0) && (nm.msgnums != NULL)) {
+ low_water_mark = nm.msgnums[0];
+ high_water_mark = nm.msgnums[nm.num_msgs - 1];
+ }
+
+ // Only the mandatory formats are supported
+ switch(which_format) {
+ case NNTP_LIST_ACTIVE:
+ // FIXME we have hardcoded "n" for "no posting allowed" -- fix when we add posting
+ cprintf("%s %ld %ld n\r\n", n_name, high_water_mark, low_water_mark);
+ break;
+ case NNTP_LIST_NEWSGROUPS:
+ cprintf("%s %s\r\n", n_name, qrbuf->QRname);
+ break;
+ }
+
+ if (nm.msgnums != NULL) {
+ free(nm.msgnums);
+ }
+}
+
+
+//
+// Called once per room by nntp_newgroups() to qualify and possibly output a single room
+//
+void nntp_newgroups_backend(struct ctdlroom *qrbuf, void *data) {
+ int ra;
+ int view;
+ time_t thetime = *(time_t *)data;
+
+ CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
+
+ /*
+ * The "created after <date/time>" heuristics depend on the happy coincidence
+ * that for a very long time we have used a unix timestamp as the room record's
+ * generation number (QRgen). When this module is merged into the master
+ * source tree we should rename QRgen to QR_create_time or something like that.
+ */
+
+ if (ra & UA_KNOWN) {
+ if (qrbuf->QRgen >= thetime) {
+ output_roomname_in_list_format(qrbuf, NNTP_LIST_ACTIVE, NULL);
+ }
+ }
+}
+
+
+//
+// Implements the NEWGROUPS command
+//
+void nntp_newgroups(const char *cmd) {
+ if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+ char stringy_date[16];
+ char stringy_time[16];
+ char stringy_gmt[16];
+ struct tm tm;
+ time_t thetime;
+
+ extract_token(stringy_date, cmd, 1, ' ', sizeof stringy_date);
+ extract_token(stringy_time, cmd, 2, ' ', sizeof stringy_time);
+ extract_token(stringy_gmt, cmd, 3, ' ', sizeof stringy_gmt);
+
+ memset(&tm, 0, sizeof tm);
+ if (strlen(stringy_date) == 6) {
+ sscanf(stringy_date, "%2d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday);
+ tm.tm_year += 100;
+ }
+ else {
+ sscanf(stringy_date, "%4d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday);
+ tm.tm_year -= 1900;
+ }
+ tm.tm_mon -= 1; // tm_mon is zero based (0=January)
+ tm.tm_isdst = (-1); // let the C library figure out whether DST is in effect
+ sscanf(stringy_time, "%2d%2d%2d", &tm.tm_hour, &tm.tm_min ,&tm.tm_sec);
+ thetime = mktime(&tm);
+ if (!strcasecmp(stringy_gmt, "GMT")) {
+ tzset();
+#ifdef __FreeBSD__
+ thetime += &tm.tm_gmtoff;
+#else
+ thetime += timezone;
+#endif
+ }
+
+
+ cprintf("231 list of new newsgroups follows\r\n");
+ CtdlGetUser(&CC->user, CC->curr_user);
+ CtdlForEachRoom(nntp_newgroups_backend, &thetime);
+ cprintf(".\r\n");
+}
+
+
+//
+// Called once per room by nntp_list() to qualify and possibly output a single room
+//
+void nntp_list_backend(struct ctdlroom *qrbuf, void *data) {
+ int ra;
+ int view;
+ struct nntp_list_data *nld = (struct nntp_list_data *)data;
+
+ CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
+ if (ra & UA_KNOWN) {
+ output_roomname_in_list_format(qrbuf, nld->list_format, nld->wildmat_pattern);
+ }
+}
+
+
+//
+// Implements the LIST commands
+//
+void nntp_list(const char *cmd) {
+ if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+ char list_format[64];
+ char wildmat_pattern[1024];
+ struct nntp_list_data nld;
+
+ extract_token(list_format, cmd, 1, ' ', sizeof list_format);
+ extract_token(wildmat_pattern, cmd, 2, ' ', sizeof wildmat_pattern);
+
+ if (strlen(wildmat_pattern) > 0) {
+ nld.wildmat_pattern = wildmat_pattern;
+ }
+ else {
+ nld.wildmat_pattern = NULL;
+ }
+
+ if ( (strlen(cmd) < 6) || (!strcasecmp(list_format, "ACTIVE")) ) {
+ nld.list_format = NNTP_LIST_ACTIVE;
+ }
+ else if (!strcasecmp(list_format, "NEWSGROUPS")) {
+ nld.list_format = NNTP_LIST_NEWSGROUPS;
+ }
+ else if (!strcasecmp(list_format, "OVERVIEW.FMT")) {
+ nld.list_format = NNTP_LIST_OVERVIEW_FMT;
+ }
+ else {
+ cprintf("501 syntax error , unsupported list format\r\n");
+ return;
+ }
+
+ // OVERVIEW.FMT delivers a completely different type of data than all of the
+ // other LIST commands. It's a stupid place to put it. But that's how it's
+ // written into RFC3977, so we have to handle it here.
+ if (nld.list_format == NNTP_LIST_OVERVIEW_FMT) {
+ cprintf("215 Order of fields in overview database.\r\n");
+ cprintf("Subject:\r\n");
+ cprintf("From:\r\n");
+ cprintf("Date:\r\n");
+ cprintf("Message-ID:\r\n");
+ cprintf("References:\r\n");
+ cprintf("Bytes:\r\n");
+ cprintf("Lines:\r\n");
+ cprintf(".\r\n");
+ return;
+ }
+
+ cprintf("215 list of newsgroups follows\r\n");
+ CtdlGetUser(&CC->user, CC->curr_user);
+ CtdlForEachRoom(nntp_list_backend, &nld);
+ cprintf(".\r\n");
+}
+
+
+//
+// Implement HELP command.
+//
+void nntp_help(void) {
+ cprintf("100 This is the Citadel NNTP service.\r\n");
+ cprintf("RTFM http://www.ietf.org/rfc/rfc3977.txt\r\n");
+ cprintf(".\r\n");
+}
+
+
+//
+// Implement DATE command.
+//
+void nntp_date(void) {
+ time_t now;
+ struct tm nowLocal;
+ struct tm nowUtc;
+ char tsFromUtc[32];
+
+ now = time(NULL);
+ localtime_r(&now, &nowLocal);
+ gmtime_r(&now, &nowUtc);
+
+ strftime(tsFromUtc, sizeof(tsFromUtc), "%Y%m%d%H%M%S", &nowUtc);
+
+ cprintf("111 %s\r\n", tsFromUtc);
+}
+
+
+//
+// back end for the LISTGROUP command , called for each message number
+//
+void nntp_listgroup_backend(long msgnum, void *userdata) {
+
+ struct listgroup_range *lr = (struct listgroup_range *)userdata;
+
+ // check range if supplied
+ if (msgnum < lr->lo) return;
+ if ((lr->hi != 0) && (msgnum > lr->hi)) return;
+
+ cprintf("%ld\r\n", msgnum);
+}
+
+
+//
+// Implements the GROUP and LISTGROUP commands
+//
+void nntp_group(const char *cmd) {
+ if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+ citnntp *nntpstate = (citnntp *) CC->session_specific_data;
+ char verb[16];
+ char requested_group[1024];
+ char message_range[256];
+ char range_lo[256];
+ char range_hi[256];
+ char requested_room[ROOMNAMELEN];
+ char augmented_roomname[ROOMNAMELEN];
+ int c = 0;
+ int ok = 0;
+ int ra = 0;
+ struct ctdlroom QRscratch;
+ int msgs, new;
+ long oldest,newest;
+ struct listgroup_range lr;
+
+ extract_token(verb, cmd, 0, ' ', sizeof verb);
+ extract_token(requested_group, cmd, 1, ' ', sizeof requested_group);
+ extract_token(message_range, cmd, 2, ' ', sizeof message_range);
+ extract_token(range_lo, message_range, 0, '-', sizeof range_lo);
+ extract_token(range_hi, message_range, 1, '-', sizeof range_hi);
+ lr.lo = atoi(range_lo);
+ lr.hi = atoi(range_hi);
+
+ /* In LISTGROUP mode we can specify an empty name for 'currently selected' */
+ if ((!strcasecmp(verb, "LISTGROUP")) && (IsEmptyStr(requested_group))) {
+ room_to_newsgroup(requested_group, CC->room.QRname, sizeof requested_group);
+ }
+
+ /* First try a regular match */
+ newsgroup_to_room(requested_room, requested_group, sizeof requested_room);
+ c = CtdlGetRoom(&QRscratch, requested_room);
+
+ /* Then try a mailbox name match */
+ if (c != 0) {
+ CtdlMailboxName(augmented_roomname, sizeof augmented_roomname, &CC->user, requested_room);
+ c = CtdlGetRoom(&QRscratch, augmented_roomname);
+ if (c == 0) {
+ safestrncpy(requested_room, augmented_roomname, sizeof(requested_room));
+ }
+ }
+
+ /* If the room exists, check security/access */
+ if (c == 0) {
+ /* See if there is an existing user/room relationship */
+ CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL);
+
+ /* normal clients have to pass through security */
+ if (ra & UA_KNOWN) {
+ ok = 1;
+ }
+ }
+
+ /* Fail here if no such room */
+ if (!ok) {
+ cprintf("411 no such newsgroup\r\n");
+ return;
+ }
+
+
+ /*
+ * CtdlUserGoto() formally takes us to the desired room, happily returning
+ * the number of messages and number of new messages.
+ */
+ memcpy(&CC->room, &QRscratch, sizeof(struct ctdlroom));
+ CtdlUserGoto(NULL, 0, 0, &msgs, &new, &oldest, &newest);
+ cprintf("211 %d %ld %ld %s\r\n", msgs, oldest, newest, requested_group);
+
+ // If this is a GROUP command, set the "current article number" to zero, and then stop here.
+ if (!strcasecmp(verb, "GROUP")) {
+ nntpstate->current_article_number = oldest;
+ return;
+ }
+
+ // If we get to this point we are running a LISTGROUP command. Fetch those message numbers.
+ CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, nntp_listgroup_backend, &lr);
+ cprintf(".\r\n");
+}
+
+
+//
+// Implements the MODE command
+//
+void nntp_mode(const char *cmd) {
+
+ char which_mode[16];
+
+ extract_token(which_mode, cmd, 1, ' ', sizeof which_mode);
+
+ if (!strcasecmp(which_mode, "reader")) {
+ // FIXME implement posting and change to 200
+ cprintf("201 Reader mode activated\r\n");
+ }
+ else {
+ cprintf("501 unknown mode\r\n");
+ }
+}
+
+
+//
+// Implements the ARTICLE, HEAD, BODY, and STAT commands.
+// (These commands all accept the same parameters; they differ only in how they output the retrieved message.)
+//
+void nntp_article(const char *cmd) {
+ if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+ citnntp *nntpstate = (citnntp *) CC->session_specific_data;
+ char which_command[16];
+ int acmd = 0;
+ char requested_article[256];
+ long requested_msgnum = 0;
+ char *lb, *rb = NULL;
+ int must_change_currently_selected_article = 0;
+
+ // We're going to store one of these values in the variable 'acmd' so that
+ // we can quickly check later which version of the output we want.
+ enum {
+ ARTICLE,
+ HEAD,
+ BODY,
+ STAT
+ };
+
+ extract_token(which_command, cmd, 0, ' ', sizeof which_command);
+
+ if (!strcasecmp(which_command, "article")) {
+ acmd = ARTICLE;
+ }
+ else if (!strcasecmp(which_command, "head")) {
+ acmd = HEAD;
+ }
+ else if (!strcasecmp(which_command, "body")) {
+ acmd = BODY;
+ }
+ else if (!strcasecmp(which_command, "stat")) {
+ acmd = STAT;
+ }
+ else {
+ cprintf("500 I'm afraid I can't do that.\r\n");
+ return;
+ }
+
+ // Which NNTP command was issued, determines whether we will fetch headers, body, or both.
+ int headers_only = HEADERS_ALL;
+ if (acmd == HEAD) headers_only = HEADERS_FAST;
+ else if (acmd == BODY) headers_only = HEADERS_NONE;
+ else if (acmd == STAT) headers_only = HEADERS_FAST;
+
+ // now figure out what the client is asking for.
+ extract_token(requested_article, cmd, 1, ' ', sizeof requested_article);
+ lb = strchr(requested_article, '<');
+ rb = strchr(requested_article, '>');
+ requested_msgnum = atol(requested_article);
+
+ // If no article number or message-id is specified, the client wants the "currently selected article"
+ if (IsEmptyStr(requested_article)) {
+ if (nntpstate->current_article_number < 1) {
+ cprintf("420 No current article selected\r\n");
+ return;
+ }
+ requested_msgnum = nntpstate->current_article_number;
+ must_change_currently_selected_article = 1;
+ // got it -- now fall through and keep going
+ }
+
+ // If the requested article is numeric, it maps directly to a message number. Good.
+ else if (requested_msgnum > 0) {
+ must_change_currently_selected_article = 1;
+ // good -- fall through and keep going
+ }
+
+ // If the requested article has angle brackets, the client wants a specific message-id.
+ // We don't know how to do that yet.
+ else if ( (lb != NULL) && (rb != NULL) && (lb < rb) ) {
+ must_change_currently_selected_article = 0;
+ cprintf("500 I don't know how to fetch by message-id yet.\r\n"); // FIXME
+ return;
+ }
+
+ // Anything else is noncompliant gobbledygook and should die in a car fire.
+ else {
+ must_change_currently_selected_article = 0;
+ cprintf("500 syntax error\r\n");
+ return;
+ }
+
+ // At this point we know the message number of the "article" being requested.
+ // We have an awesome API call that does all the heavy lifting for us.
+ char *fetched_message_id = NULL;
+ CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+ int fetch = CtdlOutputMsg(requested_msgnum,
+ MT_RFC822, // output in RFC822 format ... sort of
+ headers_only, // headers, body, or both?
+ 0, // don't do Citadel protocol responses
+ 1, // CRLF newlines
+ NULL, // teh whole thing, not just a section
+ 0, // no flags yet ... maybe new ones for Path: etc ?
+ NULL,
+ NULL,
+ &fetched_message_id // extract the message ID from the message as we go...
+ );
+ StrBuf *msgtext = CC->redirect_buffer;
+ CC->redirect_buffer = NULL;
+
+ if (fetch != om_ok) {
+ cprintf("423 no article with that number\r\n");
+ FreeStrBuf(&msgtext);
+ return;
+ }
+
+ // RFC3977 6.2.1.2 specifes conditions under which the "currently selected article"
+ // MUST or MUST NOT be set to the message we just referenced.
+ if (must_change_currently_selected_article) {
+ nntpstate->current_article_number = requested_msgnum;
+ }
+
+ // Now give the client what it asked for.
+ if (acmd == ARTICLE) {
+ cprintf("220 %ld <%s>\r\n", requested_msgnum, fetched_message_id);
+ }
+ if (acmd == HEAD) {
+ cprintf("221 %ld <%s>\r\n", requested_msgnum, fetched_message_id);
+ }
+ if (acmd == BODY) {
+ cprintf("222 %ld <%s>\r\n", requested_msgnum, fetched_message_id);
+ }
+ if (acmd == STAT) {
+ cprintf("223 %ld <%s>\r\n", requested_msgnum, fetched_message_id);
+ FreeStrBuf(&msgtext);
+ return;
+ }
+
+ client_write(SKEY(msgtext));
+ cprintf(".\r\n"); // this protocol uses a dot terminator
+ FreeStrBuf(&msgtext);
+ if (fetched_message_id) free(fetched_message_id);
+}
+
+
+//
+// Utility function for nntp_last_next() that turns a msgnum into a message ID.
+// The memory for the returned string is pwnz0red by the caller.
+//
+char *message_id_from_msgnum(long msgnum) {
+ char *fetched_message_id = NULL;
+ CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+ CtdlOutputMsg(msgnum,
+ MT_RFC822, // output in RFC822 format ... sort of
+ HEADERS_FAST, // headers, body, or both?
+ 0, // don't do Citadel protocol responses
+ 1, // CRLF newlines
+ NULL, // teh whole thing, not just a section
+ 0, // no flags yet ... maybe new ones for Path: etc ?
+ NULL,
+ NULL,
+ &fetched_message_id // extract the message ID from the message as we go...
+ );
+ StrBuf *msgtext = CC->redirect_buffer;
+ CC->redirect_buffer = NULL;
+
+ FreeStrBuf(&msgtext);
+ return(fetched_message_id);
+}
+
+
+//
+// The LAST and NEXT commands are so similar that they are handled by a single function.
+//
+void nntp_last_next(const char *cmd) {
+ if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+ citnntp *nntpstate = (citnntp *) CC->session_specific_data;
+ char which_command[16];
+ int acmd = 0;
+
+ // We're going to store one of these values in the variable 'acmd' so that
+ // we can quickly check later which version of the output we want.
+ enum {
+ NNTP_LAST,
+ NNTP_NEXT
+ };
+
+ extract_token(which_command, cmd, 0, ' ', sizeof which_command);
+
+ if (!strcasecmp(which_command, "last")) {
+ acmd = NNTP_LAST;
+ }
+ else if (!strcasecmp(which_command, "next")) {
+ acmd = NNTP_NEXT;
+ }
+ else {
+ cprintf("500 I'm afraid I can't do that.\r\n");
+ return;
+ }
+
+ // ok, here we go ... fetch the msglist so we can figure out our place in the universe
+ struct nntp_msglist nm;
+ int i = 0;
+ long selected_msgnum = 0;
+ char *message_id = NULL;
+
+ nm = nntp_fetch_msglist(&CC->room);
+ if ((nm.num_msgs < 0) || (nm.msgnums == NULL)) {
+ cprintf("500 something bad happened\r\n");
+ return;
+ }
+
+ if ( (acmd == NNTP_LAST) && (nm.num_msgs == 0) ) {
+ cprintf("422 no previous article in this group\r\n"); // nothing here
+ }
+
+ else if ( (acmd == NNTP_LAST) && (nntpstate->current_article_number <= nm.msgnums[0]) ) {
+ cprintf("422 no previous article in this group\r\n"); // already at the beginning
+ }
+
+ else if (acmd == NNTP_LAST) {
+ for (i=0; ((i<nm.num_msgs)&&(selected_msgnum<=0)); ++i) {
+ if ( (nm.msgnums[i] >= nntpstate->current_article_number) && (i > 0) ) {
+ selected_msgnum = nm.msgnums[i-1];
+ }
+ }
+ if (selected_msgnum > 0) {
+ nntpstate->current_article_number = selected_msgnum;
+ message_id = message_id_from_msgnum(nntpstate->current_article_number);
+ cprintf("223 %ld <%s>\r\n", nntpstate->current_article_number, message_id);
+ if (message_id) free(message_id);
+ }
+ else {
+ cprintf("422 no previous article in this group\r\n");
+ }
+ }
+
+ else if ( (acmd == NNTP_NEXT) && (nm.num_msgs == 0) ) {
+ cprintf("421 no next article in this group\r\n"); // nothing here
+ }
+
+ else if ( (acmd == NNTP_NEXT) && (nntpstate->current_article_number >= nm.msgnums[nm.num_msgs-1]) ) {
+ cprintf("421 no next article in this group\r\n"); // already at the end
+ }
+
+ else if (acmd == NNTP_NEXT) {
+ for (i=0; ((i<nm.num_msgs)&&(selected_msgnum<=0)); ++i) {
+ if (nm.msgnums[i] > nntpstate->current_article_number) {
+ selected_msgnum = nm.msgnums[i];
+ }
+ }
+ if (selected_msgnum > 0) {
+ nntpstate->current_article_number = selected_msgnum;
+ message_id = message_id_from_msgnum(nntpstate->current_article_number);
+ cprintf("223 %ld <%s>\r\n", nntpstate->current_article_number, message_id);
+ if (message_id) free(message_id);
+ }
+ else {
+ cprintf("421 no next article in this group\r\n");
+ }
+ }
+
+ // should never get here
+ else {
+ cprintf("500 internal error\r\n");
+ }
+
+ if (nm.msgnums != NULL) {
+ free(nm.msgnums);
+ }
+
+}
+
+
+//
+// back end for the XOVER command , called for each message number
+//
+void nntp_xover_backend(long msgnum, void *userdata) {
+
+ struct listgroup_range *lr = (struct listgroup_range *)userdata;
+
+ // check range if supplied
+ if (msgnum < lr->lo) return;
+ if ((lr->hi != 0) && (msgnum > lr->hi)) return;
+
+ struct CtdlMessage *msg = CtdlFetchMessage(msgnum, 0);
+ if (msg == NULL) {
+ return;
+ }
+
+ // Teh RFC says we need:
+ // -------------------------
+ // Subject header content
+ // From header content
+ // Date header content
+ // Message-ID header content
+ // References header content
+ // :bytes metadata item
+ // :lines metadata item
+
+ time_t msgtime = atol(msg->cm_fields[eTimestamp]);
+ char strtimebuf[26];
+ ctime_r(&msgtime, strtimebuf);
+
+ // here we go -- print the line o'data
+ cprintf("%ld\t%s\t%s <%s>\t%s\t%s\t%s\t100\t10\r\n",
+ msgnum,
+ msg->cm_fields[eMsgSubject],
+ msg->cm_fields[eAuthor],
+ msg->cm_fields[erFc822Addr],
+ strtimebuf,
+ msg->cm_fields[emessageId],
+ msg->cm_fields[eWeferences]
+ );
+
+ CM_Free(msg);
+}
+
+
+//
+//
+// XOVER is used by some clients, even if we don't offer it
+//
+void nntp_xover(const char *cmd) {
+ if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
+
+ citnntp *nntpstate = (citnntp *) CC->session_specific_data;
+ char range[256];
+ struct listgroup_range lr;
+
+ extract_token(range, cmd, 1, ' ', sizeof range);
+ lr.lo = atol(range);
+ if (lr.lo <= 0) {
+ lr.lo = nntpstate->current_article_number;
+ lr.hi = nntpstate->current_article_number;
+ }
+ else {
+ char *dash = strchr(range, '-');
+ if (dash != NULL) {
+ ++dash;
+ lr.hi = atol(dash);
+ if (lr.hi == 0) {
+ lr.hi = LONG_MAX;
+ }
+ if (lr.hi < lr.lo) {
+ lr.hi = lr.lo;
+ }
+ }
+ else {
+ lr.hi = lr.lo;
+ }
+ }
+
+ cprintf("224 Overview information follows\r\n");
+ CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, nntp_xover_backend, &lr);
+ cprintf(".\r\n");
+}
+
+
+//
+// Main command loop for NNTP server sessions.
+//
+void nntp_command_loop(void) {
+ StrBuf *Cmd = NewStrBuf();
+ char cmdname[16];
+
+ time(&CC->lastcmd);
+ if (CtdlClientGetLine(Cmd) < 1) {
+ syslog(LOG_CRIT, "NNTP: client disconnected: ending session.\n");
+ CC->kill_me = KILLME_CLIENT_DISCONNECTED;
+ FreeStrBuf(&Cmd);
+ return;
+ }
+ syslog(LOG_DEBUG, "NNTP: %s\n", ((!strncasecmp(ChrPtr(Cmd), "AUTHINFO", 8)) ? "AUTHINFO ..." : ChrPtr(Cmd)));
+ extract_token(cmdname, ChrPtr(Cmd), 0, ' ', sizeof cmdname);
+
+ // Rumpelstiltskin lookups are *awesome*
+
+ if (!strcasecmp(cmdname, "quit")) {
+ nntp_quit();
+ }
+
+ else if (!strcasecmp(cmdname, "help")) {
+ nntp_help();
+ }
+
+ else if (!strcasecmp(cmdname, "date")) {
+ nntp_date();
+ }
+
+ else if (!strcasecmp(cmdname, "capabilities")) {
+ nntp_capabilities();
+ }
+
+ else if (!strcasecmp(cmdname, "starttls")) {
+ nntp_starttls();
+ }
+
+ else if (!strcasecmp(cmdname, "authinfo")) {
+ nntp_authinfo(ChrPtr(Cmd));
+ }
+
+ else if (!strcasecmp(cmdname, "newgroups")) {
+ nntp_newgroups(ChrPtr(Cmd));
+ }
+
+ else if (!strcasecmp(cmdname, "list")) {
+ nntp_list(ChrPtr(Cmd));
+ }
+
+ else if (!strcasecmp(cmdname, "group")) {
+ nntp_group(ChrPtr(Cmd));
+ }
+
+ else if (!strcasecmp(cmdname, "listgroup")) {
+ nntp_group(ChrPtr(Cmd));
+ }
+
+ else if (!strcasecmp(cmdname, "mode")) {
+ nntp_mode(ChrPtr(Cmd));
+ }
+
+ else if (
+ (!strcasecmp(cmdname, "article"))
+ || (!strcasecmp(cmdname, "head"))
+ || (!strcasecmp(cmdname, "body"))
+ || (!strcasecmp(cmdname, "stat"))
+ )
+ {
+ nntp_article(ChrPtr(Cmd));
+ }
+
+ else if (
+ (!strcasecmp(cmdname, "last"))
+ || (!strcasecmp(cmdname, "next"))
+ )
+ {
+ nntp_last_next(ChrPtr(Cmd));
+ }
+
+ else if (
+ (!strcasecmp(cmdname, "xover"))
+ || (!strcasecmp(cmdname, "over"))
+ )
+ {
+ nntp_xover(ChrPtr(Cmd));
+ }
+
+ else {
+ cprintf("500 I'm afraid I can't do that.\r\n");
+ }
+
+ FreeStrBuf(&Cmd);
+}
+
+
+// ****************************************************************************
+// MODULE INITIALIZATION STUFF
+// ****************************************************************************
+
+
+//
+// This cleanup function blows away the temporary memory used by
+// the NNTP server.
+//
+void nntp_cleanup_function(void) {
+ /* Don't do this stuff if this is not an NNTP session! */
+ if (CC->h_command_function != nntp_command_loop) return;
+
+ syslog(LOG_DEBUG, "Performing NNTP cleanup hook\n");
+ citnntp *nntpstate = (citnntp *) CC->session_specific_data;
+ if (nntpstate != NULL) {
+ free(nntpstate);
+ nntpstate = NULL;
+ }
+}
+
+const char *CitadelServiceNNTP="NNTP";
+const char *CitadelServiceNNTPS="NNTPS";
+
+char *ctdl_module_init_nntp(void) {
+ if (!threading) {
+ CtdlRegisterServiceHook(CtdlGetConfigInt("c_nntp_port"),
+ NULL,
+ nntp_greeting,
+ nntp_command_loop,
+ NULL,
+ CitadelServiceNNTP);
+
+#ifdef HAVE_OPENSSL
+ CtdlRegisterServiceHook(CtdlGetConfigInt("c_nntps_port"),
+ NULL,
+ nntps_greeting,
+ nntp_command_loop,
+ NULL,
+ CitadelServiceNNTPS);
+#endif
+
+ CtdlRegisterSessionHook(nntp_cleanup_function, EVT_STOP, PRIO_STOP + 250);
+ }
+
+ /* return our module name for the log */
+ return "nntp";
+}
--- /dev/null
+//
+// Header file for NNTP server module
+//
+// Copyright (c) 2014 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.
+//
+
+
+// data returned by a message list fetch
+struct nntp_msglist {
+ int num_msgs;
+ long *msgnums;
+};
+
+
+// data passed by the LIST commands to its helper function
+struct nntp_list_data {
+ int list_format;
+ char *wildmat_pattern;
+};
+
+
+//
+// data passed between nntp_listgroup() and nntp_listgroup_backend()
+//
+struct listgroup_range {
+ long lo;
+ long hi;
+};
+
+
+typedef struct _citnntp { // Information about the current session
+ long current_article_number;
+} citnntp;
+
+
+//
+// Various output formats for the LIST commands
+//
+enum {
+ NNTP_LIST_ACTIVE,
+ NNTP_LIST_ACTIVE_TIMES,
+ NNTP_LIST_DISTRIB_PATS,
+ NNTP_LIST_HEADERS,
+ NNTP_LIST_NEWSGROUPS,
+ NNTP_LIST_OVERVIEW_FMT
+};
+
+
+int wildmat(const char *text, const char *p);
+
--- /dev/null
+/* wildmat.h - NNTP wildmat processing functions
+ *
+ * Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The name "Carnegie Mellon University" must not be used to
+ * endorse or promote products derived from this software without
+ * prior written permission. For permission or any legal
+ * details, please contact
+ * Carnegie Mellon University
+ * Center for Technology Transfer and Enterprise Creation
+ * 4615 Forbes Avenue
+ * Suite 302
+ * Pittsburgh, PA 15213
+ * (412) 268-7393, fax: (412) 268-7395
+ * innovation@andrew.cmu.edu
+ *
+ * 4. Redistributions of any form whatsoever must retain the following
+ * acknowledgment:
+ * "This product includes software developed by Computing Services
+ * at Carnegie Mellon University (http://www.cmu.edu/computing/)."
+ *
+ * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
+ * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
+ * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $Id: wildmat.c,v 1.4 2010/01/06 17:01:47 murch Exp $
+ */
+
+/*
+**
+** Do shell-style pattern matching for ?, \, [], and * characters.
+** Might not be robust in face of malformed patterns; e.g., "foo[a-"
+** could cause a segmentation violation. It is 8bit clean.
+**
+** Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986.
+** Rich $alz is now <rsalz@osf.org>.
+** April, 1991: Replaced mutually-recursive calls with in-line code
+** for the star character.
+**
+** Special thanks to Lars Mathiesen <thorinn@diku.dk> for the ABORT code.
+** This can greatly speed up failing wildcard patterns. For example:
+** pattern: -*-*-*-*-*-*-12-*-*-*-m-*-*-*
+** text 1: -adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1
+** text 2: -adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1
+** Text 1 matches with 51 calls, while text 2 fails with 54 calls. Without
+** the ABORT code, it takes 22310 calls to fail. Ugh. The following
+** explanation is from Lars:
+** The precondition that must be fulfilled is that DoMatch will consume
+** at least one character in text. This is true if *p is neither '*' nor
+** '\0'.) The last return has ABORT instead of FALSE to avoid quadratic
+** behaviour in cases like pattern "*a*b*c*d" with text "abcxxxxx". With
+** FALSE, each star-loop has to run to the end of the text; with ABORT
+** only the last one does.
+**
+** Once the control of one instance of DoMatch enters the star-loop, that
+** instance will return either TRUE or ABORT, and any calling instance
+** will therefore return immediately after (without calling recursively
+** again). In effect, only one star-loop is ever active. It would be
+** possible to modify the code to maintain this context explicitly,
+** eliminating all recursive calls at the cost of some complication and
+** loss of clarity (and the ABORT stuff seems to be unclear enough by
+** itself). I think it would be unwise to try to get this into a
+** released version unless you have a good test data base to try it out
+** on.
+*/
+#include <stdio.h>
+#include <sys/types.h>
+
+
+
+#define TRUE 1
+#define FALSE 0
+#define ABORT -1
+
+
+ /* What character marks an inverted character class? */
+#define NEGATE_CLASS '^'
+ /* Is "*" a common pattern? */
+#define OPTIMIZE_JUST_STAR
+ /* Do tar(1) matching rules, which ignore a trailing slash? */
+#undef MATCH_TAR_PATTERN
+
+
+/*
+** Match text and p, return TRUE, FALSE, or ABORT.
+*/
+static int DoMatch(const char *text, const char *p)
+{
+ int last;
+ int matched;
+ int reverse;
+
+ for ( ; *p; text++, p++) {
+ if (*text == '\0' && *p != '*')
+ return ABORT;
+ switch (*p) {
+ case '\\':
+ /* Literal match with following character. */
+ p++;
+ /* FALLTHROUGH */
+ default:
+ if (*text != *p)
+ return FALSE;
+ continue;
+ case '?':
+ /* Match anything. */
+ continue;
+ case '*':
+ while (*++p == '*')
+ /* Consecutive stars act just like one. */
+ continue;
+ if (*p == '\0')
+ /* Trailing star matches everything. */
+ return TRUE;
+ while (*text)
+ if ((matched = DoMatch(text++, p)) != FALSE)
+ return matched;
+ return ABORT;
+ case '[':
+ reverse = p[1] == NEGATE_CLASS ? TRUE : FALSE;
+ if (reverse)
+ /* Inverted character class. */
+ p++;
+ matched = FALSE;
+ if (p[1] == ']' || p[1] == '-')
+ if (*++p == *text)
+ matched = TRUE;
+ for (last = *p; *++p && *p != ']'; last = *p)
+ /* This next line requires a good C compiler. */
+ if (*p == '-' && p[1] != ']'
+ ? *text <= *++p && *text >= last : *text == *p)
+ matched = TRUE;
+ if (matched == reverse)
+ return FALSE;
+ continue;
+ }
+ }
+
+#ifdef MATCH_TAR_PATTERN
+ if (*text == '/')
+ return TRUE;
+#endif /* MATCH_TAR_ATTERN */
+ return *text == '\0';
+}
+
+
+/*
+** User-level routine. Returns TRUE or FALSE.
+*/
+int wildmat(const char *text, const char *p)
+{
+#ifdef OPTIMIZE_JUST_STAR
+ if (p[0] == '*' && p[1] == '\0')
+ return TRUE;
+#endif /* OPTIMIZE_JUST_STAR */
+ return DoMatch(text, p) == TRUE;
+}
--- /dev/null
+/*
+ * Handles functions related to yellow sticky notes.
+ *
+ * Copyright (c) 2007-2022 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.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../ctdl_module.h"
+
+
+/*
+ * Callback function for serv_notes_beforesave() hunts for a vNote in the MIME structure
+ */
+void notes_extract_vnote(char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length,
+ char *encoding, char *cbid, void *cbuserdata)
+{
+ struct vnote **v = (struct vnote **) cbuserdata;
+
+ if (!strcasecmp(cbtype, "text/vnote")) {
+
+ syslog(LOG_DEBUG, "Part %s contains a vNote! Loading...\n", partnum);
+ if (*v != NULL) {
+ vnote_free(*v);
+ }
+ *v = vnote_new_from_str(content);
+ }
+}
+
+
+/*
+ * Before-save hook searches for two different types of notes (legacy Kolab/Aethera notes
+ * and modern vNote format notes) and does its best to learn the subject (summary)
+ * and EUID (uid) of the note for Citadel's own nefarious purposes.
+ */
+int serv_notes_beforesave(struct CtdlMessage *msg, struct recptypes *recp)
+{
+ char *p;
+ int a, i;
+ char uuid[512];
+ struct vnote *v = NULL;
+
+ /* First determine if this room has the "notes" view set */
+
+ if (CC->room.QRdefaultview != VIEW_NOTES) {
+ return(0); /* not notes; do nothing */
+ }
+
+ /* It must be an RFC822 message! */
+ if (msg->cm_format_type != 4) {
+ return(0); /* You tried to save a non-RFC822 message! */
+ }
+
+ /*
+ * If we are in a "notes" view room, and the client has sent an RFC822
+ * message containing an X-KOrg-Note-Id: field (Aethera does this, as
+ * do some Kolab clients) then set both the Subject and the Exclusive ID
+ * of the message to that. It's going to be a UUID so we want to replace
+ * any existing message containing that UUID.
+ */
+ strcpy(uuid, "");
+ p = msg->cm_fields[eMesageText];
+ a = msg->cm_lengths[eMesageText];
+ while (--a > 0) {
+ if (!strncasecmp(p, "X-KOrg-Note-Id: ", 16)) { /* Found it */
+ safestrncpy(uuid, p + 16, sizeof(uuid));
+ for (i = 0; uuid[i]; ++i) {
+ if ( (uuid[i] == '\r') || (uuid[i] == '\n') ) {
+ uuid[i] = 0;
+ break;
+ }
+ }
+
+ syslog(LOG_DEBUG, "UUID of note is: %s\n", uuid);
+ if (!IsEmptyStr(uuid)) {
+ CM_SetField(msg, eExclusiveID, uuid, strlen(uuid));
+
+ CM_CopyField(msg, eMsgSubject, eExclusiveID);
+ }
+ }
+ p++;
+ }
+
+ /* Modern clients are using vNote format. Check for one... */
+
+ mime_parser(CM_RANGE(msg, eMesageText),
+ *notes_extract_vnote,
+ NULL, NULL,
+ &v, /* user data ptr - put the vnote here */
+ 0
+ );
+
+ if (v == NULL) return(0); /* no vNotes were found in this message */
+
+ /* Set the message EUID to the vNote UID */
+
+ if ((v->uid) && (!IsEmptyStr(v->uid))) {
+ syslog(LOG_DEBUG, "UID of vNote is: %s\n", v->uid);
+ CM_SetField(msg, eExclusiveID, v->uid, strlen(v->uid));
+ }
+
+ /* Set the message Subject to the vNote Summary */
+
+ if ((v->summary) && (!IsEmptyStr(v->summary))) {
+ CM_SetField(msg, eMsgSubject, v->summary, strlen(v->summary));
+
+ if (msg->cm_lengths[eMsgSubject] > 72) {
+ strcpy(&msg->cm_fields[eMsgSubject][68], "...");
+ CM_CutFieldAt(msg, eMsgSubject, 72);
+ }
+ }
+
+ vnote_free(v);
+
+ return(0);
+}
+
+
+char *ctdl_module_init_notes(void) {
+ if (!threading) {
+ CtdlRegisterMessageHook(serv_notes_beforesave, EVT_BEFORESAVE);
+ }
+
+ /* return our module name for the log */
+ return "notes";
+}
--- /dev/null
+/*
+ * This is an implementation of OpenID 2.0 relying party support in stateless mode.
+ *
+ * Copyright (c) 2007-2022 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 as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <curl/curl.h>
+#include <expat.h>
+#include "../../ctdl_module.h"
+#include "../../config.h"
+#include "../../citserver.h"
+#include "../../user_ops.h"
+
+typedef struct _ctdl_openid {
+ StrBuf *op_url; /* OpenID Provider Endpoint URL */
+ StrBuf *claimed_id; /* Claimed Identifier */
+ int verified;
+ HashList *sreg_keys;
+} ctdl_openid;
+
+enum {
+ openid_disco_none,
+ openid_disco_xrds,
+ openid_disco_html
+};
+
+
+void Free_ctdl_openid(ctdl_openid **FreeMe)
+{
+ if (*FreeMe == NULL) {
+ return;
+ }
+ FreeStrBuf(&(*FreeMe)->op_url);
+ FreeStrBuf(&(*FreeMe)->claimed_id);
+ DeleteHash(&(*FreeMe)->sreg_keys);
+ free(*FreeMe);
+ *FreeMe = NULL;
+}
+
+
+/*
+ * This cleanup function blows away the temporary memory used by this module.
+ */
+void openid_cleanup_function(void) {
+ struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
+
+ if (CCC->openid_data != NULL) {
+ syslog(LOG_DEBUG, "openid: Clearing OpenID session state");
+ Free_ctdl_openid((ctdl_openid **) &CCC->openid_data);
+ }
+}
+
+
+/**************************************************************************/
+/* */
+/* Functions in this section handle Citadel internal OpenID mapping stuff */
+/* */
+/**************************************************************************/
+
+
+/*
+ * The structure of an openid record *key* is:
+ *
+ * |--------------claimed_id-------------|
+ * (actual length of claimed id)
+ *
+ *
+ * The structure of an openid record *value* is:
+ *
+ * |-----user_number----|------------claimed_id---------------|
+ * (sizeof long) (actual length of claimed id)
+ *
+ */
+
+
+
+/*
+ * Attach an external authenticator (such as an OpenID) to a Citadel account
+ */
+int attach_extauth(struct ctdluser *who, StrBuf *claimed_id)
+{
+ struct cdbdata *cdboi;
+ long fetched_usernum;
+ char *data;
+ int data_len;
+ char buf[2048];
+
+ if (!who) return(1);
+ if (StrLength(claimed_id)==0) return(1);
+
+ /* Check to see if this authenticator is already in the database */
+
+ cdboi = cdb_fetch(CDB_EXTAUTH, ChrPtr(claimed_id), StrLength(claimed_id));
+ if (cdboi != NULL) {
+ memcpy(&fetched_usernum, cdboi->ptr, sizeof(long));
+ cdb_free(cdboi);
+
+ if (fetched_usernum == who->usernum) {
+ syslog(LOG_INFO, "openid: %s already associated; no action is taken", ChrPtr(claimed_id));
+ return(0);
+ }
+ else {
+ syslog(LOG_INFO, "openid: %s already belongs to another user", ChrPtr(claimed_id));
+ return(3);
+ }
+ }
+
+ /* Not already in the database, so attach it now */
+
+ data_len = sizeof(long) + StrLength(claimed_id) + 1;
+ data = malloc(data_len);
+
+ memcpy(data, &who->usernum, sizeof(long));
+ memcpy(&data[sizeof(long)], ChrPtr(claimed_id), StrLength(claimed_id) + 1);
+
+ cdb_store(CDB_EXTAUTH, ChrPtr(claimed_id), StrLength(claimed_id), data, data_len);
+ free(data);
+
+ snprintf(buf, sizeof buf, "User <%s> (#%ld) is now associated with %s\n", who->fullname, who->usernum, ChrPtr(claimed_id));
+ CtdlAideMessage(buf, "External authenticator claim");
+ syslog(LOG_INFO, "openid: %s", buf);
+ return(0);
+}
+
+
+/*
+ * When a user is being deleted, we have to delete any OpenID associations
+ */
+void extauth_purge(struct ctdluser *usbuf) {
+ struct cdbdata *cdboi;
+ HashList *keys = NULL;
+ HashPos *HashPos;
+ char *deleteme = NULL;
+ long len;
+ void *Value;
+ const char *Key;
+ long usernum = 0L;
+
+ keys = NewHash(1, NULL);
+ if (!keys) return;
+
+ cdb_rewind(CDB_EXTAUTH);
+ while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
+ if (cdboi->len > sizeof(long)) {
+ memcpy(&usernum, cdboi->ptr, sizeof(long));
+ if (usernum == usbuf->usernum) {
+ deleteme = strdup(cdboi->ptr + sizeof(long)),
+ Put(keys, deleteme, strlen(deleteme), deleteme, NULL);
+ }
+ }
+ cdb_free(cdboi);
+ }
+
+ /* Go through the hash list, deleting keys we stored in it */
+
+ HashPos = GetNewHashPos(keys, 0);
+ while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0)
+ {
+ syslog(LOG_DEBUG, "openid: deleting associated external authenticator <%s>", (char*)Value);
+ cdb_delete(CDB_EXTAUTH, Value, strlen(Value));
+ /* note: don't free(Value) -- deleting the hash list will handle this for us */
+ }
+ DeleteHashPos(&HashPos);
+ DeleteHash(&keys);
+}
+
+
+/*
+ * List the OpenIDs associated with the currently logged in account
+ */
+void cmd_oidl(char *argbuf) {
+ struct cdbdata *cdboi;
+ long usernum = 0L;
+
+ if (CtdlGetConfigInt("c_disable_newu"))
+ {
+ cprintf("%d this system does not support openid.\n", ERROR + CMD_NOT_SUPPORTED);
+ return;
+ }
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ cdb_rewind(CDB_EXTAUTH);
+ cprintf("%d Associated external authenticators:\n", LISTING_FOLLOWS);
+
+ while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
+ if (cdboi->len > sizeof(long)) {
+ memcpy(&usernum, cdboi->ptr, sizeof(long));
+ if (usernum == CC->user.usernum) {
+ cprintf("%s\n", cdboi->ptr + sizeof(long));
+ }
+ }
+ cdb_free(cdboi);
+ }
+ cprintf("000\n");
+}
+
+
+/*
+ * List ALL OpenIDs in the database
+ */
+void cmd_oida(char *argbuf) {
+ struct cdbdata *cdboi;
+ long usernum;
+ struct ctdluser usbuf;
+
+ if (CtdlGetConfigInt("c_disable_newu"))
+ {
+ cprintf("%d this system does not support openid.\n",
+ ERROR + CMD_NOT_SUPPORTED);
+ return;
+ }
+ if (CtdlAccessCheck(ac_aide)) return;
+ cdb_rewind(CDB_EXTAUTH);
+ cprintf("%d List of all OpenIDs in the database:\n", LISTING_FOLLOWS);
+
+ while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
+ if (cdboi->len > sizeof(long)) {
+ memcpy(&usernum, cdboi->ptr, sizeof(long));
+ if (CtdlGetUserByNumber(&usbuf, usernum) != 0) {
+ usbuf.fullname[0] = 0;
+ }
+ cprintf("%s|%ld|%s\n",
+ cdboi->ptr + sizeof(long),
+ usernum,
+ usbuf.fullname
+ );
+ }
+ cdb_free(cdboi);
+ }
+ cprintf("000\n");
+}
+
+
+/*
+ * Create a new user account, manually specifying the name, after successfully
+ * verifying an OpenID (which will of course be attached to the account)
+ */
+void cmd_oidc(char *argbuf) {
+ ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
+
+ if (CtdlGetConfigInt("c_disable_newu"))
+ {
+ cprintf("%d this system does not support openid.\n",
+ ERROR + CMD_NOT_SUPPORTED);
+ return;
+ }
+ if ( (!oiddata) || (!oiddata->verified) ) {
+ cprintf("%d You have not verified an OpenID yet.\n", ERROR);
+ return;
+ }
+
+ /* We can make the semantics of OIDC exactly the same as NEWU, simply
+ * by _calling_ cmd_newu() and letting it run. Very clever!
+ */
+ cmd_newu(argbuf);
+
+ /* Now, if this logged us in, we have to attach the OpenID */
+ if (CC->logged_in) {
+ attach_extauth(&CC->user, oiddata->claimed_id);
+ }
+
+}
+
+
+/*
+ * Detach an OpenID from the currently logged in account
+ */
+void cmd_oidd(char *argbuf) {
+ struct cdbdata *cdboi;
+ char id_to_detach[1024];
+ int this_is_mine = 0;
+ long usernum = 0L;
+
+ if (CtdlGetConfigInt("c_disable_newu"))
+ {
+ cprintf("%d this system does not support openid.\n",
+ ERROR + CMD_NOT_SUPPORTED);
+ return;
+ }
+ if (CtdlAccessCheck(ac_logged_in)) return;
+ extract_token(id_to_detach, argbuf, 0, '|', sizeof id_to_detach);
+ if (IsEmptyStr(id_to_detach)) {
+ cprintf("%d An empty OpenID URL is not allowed.\n", ERROR + ILLEGAL_VALUE);
+ }
+
+ cdb_rewind(CDB_EXTAUTH);
+ while (cdboi = cdb_next_item(CDB_EXTAUTH), cdboi != NULL) {
+ if (cdboi->len > sizeof(long)) {
+ memcpy(&usernum, cdboi->ptr, sizeof(long));
+ if (usernum == CC->user.usernum) {
+ this_is_mine = 1;
+ }
+ }
+ cdb_free(cdboi);
+ }
+
+ if (!this_is_mine) {
+ cprintf("%d That OpenID was not found or not associated with your account.\n",
+ ERROR + ILLEGAL_VALUE);
+ return;
+ }
+
+ cdb_delete(CDB_EXTAUTH, id_to_detach, strlen(id_to_detach));
+ cprintf("%d %s detached from your account.\n", CIT_OK, id_to_detach);
+}
+
+
+/*
+ * Attempt to auto-create a new Citadel account using the nickname from Attribute Exchange
+ */
+int openid_create_user_via_ax(StrBuf *claimed_id, HashList *sreg_keys)
+{
+ char *nickname = NULL;
+ char *firstname = NULL;
+ char *lastname = NULL;
+ char new_password[32];
+ long len;
+ const char *Key;
+ void *Value;
+
+ if (CtdlGetConfigInt("c_auth_mode") != AUTHMODE_NATIVE) return(1);
+ if (CtdlGetConfigInt("c_disable_newu")) return(2);
+ if (CC->logged_in) return(3);
+
+ HashPos *HashPos = GetNewHashPos(sreg_keys, 0);
+ while (GetNextHashPos(sreg_keys, HashPos, &len, &Key, &Value) != 0) {
+ syslog(LOG_DEBUG, "openid: %s = %s", Key, (char *)Value);
+
+ if (cbmstrcasestr(Key, "value.nickname") != NULL) {
+ nickname = (char *)Value;
+ }
+ else if ( (nickname == NULL) && (cbmstrcasestr(Key, "value.nickname") != NULL)) {
+ nickname = (char *)Value;
+ }
+ else if (cbmstrcasestr(Key, "value.firstname") != NULL) {
+ firstname = (char *)Value;
+ }
+ else if (cbmstrcasestr(Key, "value.lastname") != NULL) {
+ lastname = (char *)Value;
+ }
+
+ }
+ DeleteHashPos(&HashPos);
+
+ if (nickname == NULL) {
+ if ((firstname != NULL) || (lastname != NULL)) {
+ char fullname[1024] = "";
+ if (firstname) strcpy(fullname, firstname);
+ if (firstname && lastname) strcat(fullname, " ");
+ if (lastname) strcat(fullname, lastname);
+ nickname = fullname;
+ }
+ }
+
+ if (nickname == NULL) {
+ return(4);
+ }
+ syslog(LOG_DEBUG, "openid: the desired account name is <%s>", nickname);
+
+ if (!CtdlGetUser(&CC->user, nickname)) {
+ syslog(LOG_DEBUG, "openid: <%s> is already taken by another user.", nickname);
+ memset(&CC->user, 0, sizeof(struct ctdluser));
+ return(5);
+ }
+
+ /* The desired account name is available. Create the account and log it in! */
+ if (create_user(nickname, CREATE_USER_BECOME_USER, NATIVE_AUTH_UID)) return(6);
+
+ /* Generate a random password.
+ * The user doesn't care what the password is since he is using OpenID.
+ */
+ snprintf(new_password, sizeof new_password, "%08lx%08lx", random(), random());
+ CtdlSetPassword(new_password);
+
+ /* Now attach the verified OpenID to this account. */
+ attach_extauth(&CC->user, claimed_id);
+
+ return(0);
+}
+
+
+/*
+ * If a user account exists which is associated with the Claimed ID, log it in and return zero.
+ * Otherwise it returns nonzero.
+ */
+int login_via_extauth(StrBuf *claimed_id)
+{
+ struct cdbdata *cdboi;
+ long usernum = 0;
+
+ cdboi = cdb_fetch(CDB_EXTAUTH, ChrPtr(claimed_id), StrLength(claimed_id));
+ if (cdboi == NULL) {
+ return(-1);
+ }
+
+ memcpy(&usernum, cdboi->ptr, sizeof(long));
+ cdb_free(cdboi);
+
+ if (!CtdlGetUserByNumber(&CC->user, usernum)) {
+ /* Now become the user we just created */
+ safestrncpy(CC->curr_user, CC->user.fullname, sizeof CC->curr_user);
+ do_login();
+ return(0);
+ }
+ else {
+ memset(&CC->user, 0, sizeof(struct ctdluser));
+ return(-1);
+ }
+}
+
+
+
+
+/**************************************************************************/
+/* */
+/* Functions in this section handle OpenID protocol */
+/* */
+/**************************************************************************/
+
+
+/*
+ * Locate a <link> tag and, given its 'rel=' parameter, return its 'href' parameter
+ */
+void extract_link(StrBuf *target_buf, const char *rel, long repllen, StrBuf *source_buf)
+{
+ int i;
+ const char *ptr;
+ const char *href_start = NULL;
+ const char *href_end = NULL;
+ const char *link_tag_start = NULL;
+ const char *link_tag_end = NULL;
+ const char *rel_start = NULL;
+ const char *rel_end = NULL;
+
+ if (!target_buf) return;
+ if (!rel) return;
+ if (!source_buf) return;
+
+ ptr = ChrPtr(source_buf);
+
+ FlushStrBuf(target_buf);
+ while (ptr = cbmstrcasestr(ptr, "<link"), ptr != NULL) {
+
+ link_tag_start = ptr;
+ link_tag_end = strchr(ptr, '>');
+ if (link_tag_end == NULL)
+ break;
+ for (i=0; i < 1; i++ ){
+ rel_start = cbmstrcasestr(link_tag_start, "rel=");
+ if ((rel_start == NULL) ||
+ (rel_start > link_tag_end))
+ continue;
+
+ rel_start = strchr(rel_start, '\"');
+ if ((rel_start == NULL) ||
+ (rel_start > link_tag_end))
+ continue;
+ ++rel_start;
+ rel_end = strchr(rel_start, '\"');
+ if ((rel_end == NULL) ||
+ (rel_end == rel_start) ||
+ (rel_end >= link_tag_end) )
+ continue;
+ if (strncasecmp(rel, rel_start, repllen)!= 0)
+ continue; /* didn't match? never mind... */
+
+ href_start = cbmstrcasestr(link_tag_start, "href=");
+ if ((href_start == NULL) ||
+ (href_start >= link_tag_end))
+ continue;
+ href_start = strchr(href_start, '\"');
+ if ((href_start == NULL) |
+ (href_start >= link_tag_end))
+ continue;
+ ++href_start;
+ href_end = strchr(href_start, '\"');
+ if ((href_end == NULL) ||
+ (href_end == href_start) ||
+ (href_start >= link_tag_end))
+ continue;
+ StrBufPlain(target_buf, href_start, href_end - href_start);
+ }
+ ptr = link_tag_end;
+ }
+}
+
+
+/*
+ * Wrapper for curl_easy_init() that includes the options common to all calls
+ * used in this module.
+ */
+CURL *ctdl_openid_curl_easy_init(char *errmsg) {
+ CURL *curl;
+
+ curl = curl_easy_init();
+ if (!curl) {
+ return(curl);
+ }
+
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
+
+ if (errmsg) {
+ curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errmsg);
+ }
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
+#ifdef CURLOPT_HTTP_CONTENT_DECODING
+ curl_easy_setopt(curl, CURLOPT_HTTP_CONTENT_DECODING, 1);
+ curl_easy_setopt(curl, CURLOPT_ENCODING, "");
+#endif
+ curl_easy_setopt(curl, CURLOPT_USERAGENT, CITADEL);
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30); /* die after 30 seconds */
+
+ if (
+ (!IsEmptyStr(CtdlGetConfigStr("c_ip_addr")))
+ && (strcmp(CtdlGetConfigStr("c_ip_addr"), "*"))
+ && (strcmp(CtdlGetConfigStr("c_ip_addr"), "::"))
+ && (strcmp(CtdlGetConfigStr("c_ip_addr"), "0.0.0.0"))
+ ) {
+ curl_easy_setopt(curl, CURLOPT_INTERFACE, CtdlGetConfigStr("c_ip_addr"));
+ }
+
+ return(curl);
+}
+
+
+struct xrds {
+ StrBuf *CharData;
+ int nesting_level;
+ int in_xrd;
+ int current_service_priority;
+ int selected_service_priority;
+ StrBuf *current_service_uri;
+ StrBuf *selected_service_uri;
+ int current_service_is_oid2auth;
+};
+
+
+void xrds_xml_start(void *data, const char *supplied_el, const char **attr) {
+ struct xrds *xrds = (struct xrds *) data;
+ int i;
+
+ ++xrds->nesting_level;
+
+ if (!strcasecmp(supplied_el, "XRD")) {
+ ++xrds->in_xrd;
+ }
+
+ else if (!strcasecmp(supplied_el, "service")) {
+ xrds->current_service_priority = 0;
+ xrds->current_service_is_oid2auth = 0;
+ for (i=0; attr[i] != NULL; i+=2) {
+ if (!strcasecmp(attr[i], "priority")) {
+ xrds->current_service_priority = atoi(attr[i+1]);
+ }
+ }
+ }
+
+ FlushStrBuf(xrds->CharData);
+}
+
+
+void xrds_xml_end(void *data, const char *supplied_el) {
+ struct xrds *xrds = (struct xrds *) data;
+
+ --xrds->nesting_level;
+
+ if (!strcasecmp(supplied_el, "XRD")) {
+ --xrds->in_xrd;
+ }
+
+ else if (!strcasecmp(supplied_el, "type")) {
+ if ( (xrds->in_xrd)
+ && (!strcasecmp(ChrPtr(xrds->CharData), "http://specs.openid.net/auth/2.0/server"))
+ ) {
+ xrds->current_service_is_oid2auth = 1;
+ }
+ if ( (xrds->in_xrd)
+ && (!strcasecmp(ChrPtr(xrds->CharData), "http://specs.openid.net/auth/2.0/signon"))
+ ) {
+ xrds->current_service_is_oid2auth = 1;
+ /* FIXME in this case, the Claimed ID should be considered immutable */
+ }
+ }
+
+ else if (!strcasecmp(supplied_el, "uri")) {
+ if (xrds->in_xrd) {
+ FlushStrBuf(xrds->current_service_uri);
+ StrBufAppendBuf(xrds->current_service_uri, xrds->CharData, 0);
+ }
+ }
+
+ else if (!strcasecmp(supplied_el, "service")) {
+ if ( (xrds->in_xrd)
+ && (xrds->current_service_priority < xrds->selected_service_priority)
+ && (xrds->current_service_is_oid2auth)
+ ) {
+ xrds->selected_service_priority = xrds->current_service_priority;
+ FlushStrBuf(xrds->selected_service_uri);
+ StrBufAppendBuf(xrds->selected_service_uri, xrds->current_service_uri, 0);
+ }
+
+ }
+
+ FlushStrBuf(xrds->CharData);
+}
+
+
+void xrds_xml_chardata(void *data, const XML_Char *s, int len) {
+ struct xrds *xrds = (struct xrds *) data;
+
+ StrBufAppendBufPlain (xrds->CharData, s, len, 0);
+}
+
+
+/*
+ * Parse an XRDS document.
+ * If an OpenID Provider URL is discovered, op_url to that value and return nonzero.
+ * If nothing useful happened, return 0.
+ */
+int parse_xrds_document(StrBuf *ReplyBuf) {
+ ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
+ struct xrds xrds;
+ int return_value = 0;
+
+ memset(&xrds, 0, sizeof (struct xrds));
+ xrds.selected_service_priority = INT_MAX;
+ xrds.CharData = NewStrBuf();
+ xrds.current_service_uri = NewStrBuf();
+ xrds.selected_service_uri = NewStrBuf();
+ XML_Parser xp = XML_ParserCreate(NULL);
+ if (xp) {
+ XML_SetUserData(xp, &xrds);
+ XML_SetElementHandler(xp, xrds_xml_start, xrds_xml_end);
+ XML_SetCharacterDataHandler(xp, xrds_xml_chardata);
+ XML_Parse(xp, ChrPtr(ReplyBuf), StrLength(ReplyBuf), 0);
+ XML_Parse(xp, "", 0, 1);
+ XML_ParserFree(xp);
+ }
+ else {
+ syslog(LOG_ERR, "openid: cannot create XML parser");
+ }
+
+ if (xrds.selected_service_priority < INT_MAX) {
+ if (oiddata->op_url == NULL) {
+ oiddata->op_url = NewStrBuf();
+ }
+ FlushStrBuf(oiddata->op_url);
+ StrBufAppendBuf(oiddata->op_url, xrds.selected_service_uri, 0);
+ return_value = openid_disco_xrds;
+ }
+
+ FreeStrBuf(&xrds.CharData);
+ FreeStrBuf(&xrds.current_service_uri);
+ FreeStrBuf(&xrds.selected_service_uri);
+
+ return(return_value);
+}
+
+
+/*
+ * Callback function for perform_openid2_discovery()
+ * We're interested in the X-XRDS-Location: header.
+ */
+size_t yadis_headerfunction(void *ptr, size_t size, size_t nmemb, void *userdata) {
+ char hdr[1024];
+ StrBuf **x_xrds_location = (StrBuf **) userdata;
+
+ memcpy(hdr, ptr, (size*nmemb));
+ hdr[size*nmemb] = 0;
+
+ if (!strncasecmp(hdr, "X-XRDS-Location:", 16)) {
+ *x_xrds_location = NewStrBufPlain(&hdr[16], ((size*nmemb)-16));
+ StrBufTrim(*x_xrds_location);
+ }
+
+ return(size * nmemb);
+}
+
+
+/* Attempt to perform Yadis discovery as specified in Yadis 1.0 section 6.2.5.
+ *
+ * If Yadis fails, we then attempt HTML discovery using the same document.
+ *
+ * If successful, returns nonzero and calls parse_xrds_document() to act upon the received data.
+ * If fails, returns 0 and does nothing else.
+ */
+int perform_openid2_discovery(StrBuf *SuppliedURL) {
+ ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
+ int docbytes = (-1);
+ StrBuf *ReplyBuf = NULL;
+ int return_value = 0;
+ CURL *curl;
+ CURLcode result;
+ char errmsg[1024] = "";
+ struct curl_slist *my_headers = NULL;
+ StrBuf *x_xrds_location = NULL;
+
+ if (!SuppliedURL) return(0);
+ syslog(LOG_DEBUG, "openid: perform_openid2_discovery(%s)", ChrPtr(SuppliedURL));
+ if (StrLength(SuppliedURL) == 0) return(0);
+
+ ReplyBuf = NewStrBuf();
+ if (!ReplyBuf) return(0);
+
+ curl = ctdl_openid_curl_easy_init(errmsg);
+ if (!curl) return(0);
+
+ curl_easy_setopt(curl, CURLOPT_URL, ChrPtr(SuppliedURL));
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, ReplyBuf);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlFillStrBuf_callback);
+
+ my_headers = curl_slist_append(my_headers, "Accept:"); /* disable the default Accept: header */
+ my_headers = curl_slist_append(my_headers, "Accept: application/xrds+xml");
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, my_headers);
+
+ curl_easy_setopt(curl, CURLOPT_WRITEHEADER, &x_xrds_location);
+ curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, yadis_headerfunction);
+
+ result = curl_easy_perform(curl);
+ if (result) {
+ syslog(LOG_DEBUG, "openid: libcurl error %d: %s", result, errmsg);
+ }
+ curl_slist_free_all(my_headers);
+ curl_easy_cleanup(curl);
+ docbytes = StrLength(ReplyBuf);
+
+ /*
+ * The response from the server will be one of:
+ *
+ * Option 1: An HTML document with a <head> element that includes a <meta> element with http-equiv
+ * attribute, X-XRDS-Location,
+ *
+ * Does any provider actually do this? If so then we will implement it in the future.
+ */
+
+ /*
+ * Option 2: HTTP response-headers that include an X-XRDS-Location response-header,
+ * together with a document.
+ * Option 3: HTTP response-headers only, which MAY include an X-XRDS-Location response-header,
+ * a contenttype response-header specifying MIME media type,
+ * application/xrds+xml, or both.
+ *
+ * If the X-XRDS-Location header was delivered, we know about it at this point...
+ */
+ if ( (x_xrds_location)
+ && (strcmp(ChrPtr(x_xrds_location), ChrPtr(SuppliedURL)))
+ ) {
+ syslog(LOG_DEBUG, "openid: X-XRDS-Location: %s ... recursing!", ChrPtr(x_xrds_location));
+ return_value = perform_openid2_discovery(x_xrds_location);
+ FreeStrBuf(&x_xrds_location);
+ }
+
+ /*
+ * Option 4: the returned web page may *be* an XRDS document. Try to parse it.
+ */
+ if ( (return_value == 0) && (docbytes >= 0)) {
+ return_value = parse_xrds_document(ReplyBuf);
+ }
+
+ /*
+ * Option 5: if all else fails, attempt HTML based discovery.
+ */
+ if ( (return_value == 0) && (docbytes >= 0)) {
+ if (oiddata->op_url == NULL) {
+ oiddata->op_url = NewStrBuf();
+ }
+ extract_link(oiddata->op_url, HKEY("openid2.provider"), ReplyBuf);
+ if (StrLength(oiddata->op_url) > 0) {
+ return_value = openid_disco_html;
+ }
+ }
+
+ if (ReplyBuf != NULL) {
+ FreeStrBuf(&ReplyBuf);
+ }
+ return(return_value);
+}
+
+
+/*
+ * Setup an OpenID authentication
+ */
+void cmd_oids(char *argbuf) {
+ struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
+ const char *Pos = NULL;
+ StrBuf *ArgBuf = NULL;
+ StrBuf *ReplyBuf = NULL;
+ StrBuf *return_to = NULL;
+ StrBuf *RedirectUrl = NULL;
+ ctdl_openid *oiddata;
+ int discovery_succeeded = 0;
+
+ if (CtdlGetConfigInt("c_disable_newu"))
+ {
+ cprintf("%d this system does not support openid.\n",
+ ERROR + CMD_NOT_SUPPORTED);
+ return;
+ }
+ Free_ctdl_openid ((ctdl_openid**)&CCC->openid_data);
+
+ CCC->openid_data = oiddata = malloc(sizeof(ctdl_openid));
+ if (oiddata == NULL) {
+ syslog(LOG_ERR, "openid: malloc() failed: %m");
+ cprintf("%d malloc failed\n", ERROR + INTERNAL_ERROR);
+ return;
+ }
+ memset(oiddata, 0, sizeof(ctdl_openid));
+
+ ArgBuf = NewStrBufPlain(argbuf, -1);
+
+ oiddata->verified = 0;
+ oiddata->claimed_id = NewStrBufPlain(NULL, StrLength(ArgBuf));
+ return_to = NewStrBufPlain(NULL, StrLength(ArgBuf));
+
+ StrBufExtract_NextToken(oiddata->claimed_id, ArgBuf, &Pos, '|');
+ StrBufExtract_NextToken(return_to, ArgBuf, &Pos, '|');
+
+ syslog(LOG_DEBUG, "openid: user-Supplied Identifier is: %s", ChrPtr(oiddata->claimed_id));
+
+ /********** OpenID 2.0 section 7.3 - Discovery **********/
+
+ /* Section 7.3.1 says we have to attempt XRI based discovery.
+ * No one is using this, no one is asking for it, no one wants it.
+ * So we're not even going to bother attempting this mode.
+ */
+
+ /* Attempt section 7.3.2 (Yadis discovery) and section 7.3.3 (HTML discovery);
+ */
+ discovery_succeeded = perform_openid2_discovery(oiddata->claimed_id);
+
+ if (discovery_succeeded == 0) {
+ cprintf("%d There is no OpenID identity provider at this location.\n", ERROR);
+ }
+
+ else {
+ /*
+ * If we get to this point we are in possession of a valid OpenID Provider URL.
+ */
+ syslog(LOG_DEBUG, "openid: OP URI '%s' discovered using method %d",
+ ChrPtr(oiddata->op_url),
+ discovery_succeeded
+ );
+
+ /* We have to "normalize" our Claimed ID otherwise it will cause some OP's to barf */
+ if (cbmstrcasestr(ChrPtr(oiddata->claimed_id), "://") == NULL) {
+ StrBuf *cid = oiddata->claimed_id;
+ oiddata->claimed_id = NewStrBufPlain(HKEY("http://"));
+ StrBufAppendBuf(oiddata->claimed_id, cid, 0);
+ FreeStrBuf(&cid);
+ }
+
+ /*
+ * OpenID 2.0 section 9: request authentication
+ * Assemble a URL to which the user-agent will be redirected.
+ */
+
+ RedirectUrl = NewStrBufDup(oiddata->op_url);
+
+ StrBufAppendBufPlain(RedirectUrl, HKEY("?openid.ns="), 0);
+ StrBufUrlescAppend(RedirectUrl, NULL, "http://specs.openid.net/auth/2.0");
+
+ StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.mode=checkid_setup"), 0);
+
+ StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.claimed_id="), 0);
+ StrBufUrlescAppend(RedirectUrl, oiddata->claimed_id, NULL);
+
+ StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.identity="), 0);
+ StrBufUrlescAppend(RedirectUrl, oiddata->claimed_id, NULL);
+
+ /* return_to tells the provider how to complete the round trip back to our site */
+ StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.return_to="), 0);
+ StrBufUrlescAppend(RedirectUrl, return_to, NULL);
+
+ /* Attribute Exchange
+ * See:
+ * http://openid.net/specs/openid-attribute-exchange-1_0.html
+ * http://code.google.com/apis/accounts/docs/OpenID.html#endpoint
+ * http://test-id.net/OP/AXFetch.aspx
+ */
+
+ StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ns.ax="), 0);
+ StrBufUrlescAppend(RedirectUrl, NULL, "http://openid.net/srv/ax/1.0");
+
+ StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.mode=fetch_request"), 0);
+
+ StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.required=firstname,lastname,friendly,nickname"), 0);
+
+ StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.firstname="), 0);
+ StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/first");
+
+ StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.lastname="), 0);
+ StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/last");
+
+ StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.friendly="), 0);
+ StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/friendly");
+
+ StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.nickname="), 0);
+ StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/nickname");
+
+ syslog(LOG_DEBUG, "openid: redirecting client to %s", ChrPtr(RedirectUrl));
+ cprintf("%d %s\n", CIT_OK, ChrPtr(RedirectUrl));
+ }
+
+ FreeStrBuf(&ArgBuf);
+ FreeStrBuf(&ReplyBuf);
+ FreeStrBuf(&return_to);
+ FreeStrBuf(&RedirectUrl);
+}
+
+
+/*
+ * Finalize an OpenID authentication
+ */
+void cmd_oidf(char *argbuf) {
+ long len;
+ char buf[2048];
+ char thiskey[1024];
+ char thisdata[1024];
+ HashList *keys = NULL;
+ const char *Key;
+ void *Value;
+ ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data;
+
+ if (CtdlGetConfigInt("c_disable_newu"))
+ {
+ cprintf("%d this system does not support openid.\n",
+ ERROR + CMD_NOT_SUPPORTED);
+ return;
+ }
+ if (oiddata == NULL) {
+ cprintf("%d run OIDS first.\n", ERROR + INTERNAL_ERROR);
+ return;
+ }
+ if (StrLength(oiddata->op_url) == 0){
+ cprintf("%d No OpenID Endpoint URL has been obtained.\n", ERROR + ILLEGAL_VALUE);
+ return;
+ }
+ keys = NewHash(1, NULL);
+ if (!keys) {
+ cprintf("%d NewHash() failed\n", ERROR + INTERNAL_ERROR);
+ return;
+ }
+ cprintf("%d Transmit OpenID data now\n", START_CHAT_MODE);
+
+ while (client_getln(buf, sizeof buf), strcmp(buf, "000")) {
+ len = extract_token(thiskey, buf, 0, '|', sizeof thiskey);
+ if (len < 0) {
+ len = sizeof(thiskey) - 1;
+ }
+ extract_token(thisdata, buf, 1, '|', sizeof thisdata);
+ Put(keys, thiskey, len, strdup(thisdata), NULL);
+ }
+
+ /* Check to see if this is a correct response.
+ * Start with verified=1 but then set it to 0 if anything looks wrong.
+ */
+ oiddata->verified = 1;
+
+ char *openid_ns = NULL;
+ if ( (!GetHash(keys, "ns", 2, (void *) &openid_ns))
+ || (strcasecmp(openid_ns, "http://specs.openid.net/auth/2.0"))
+ ) {
+ syslog(LOG_DEBUG, "openid: this is not an an OpenID assertion");
+ oiddata->verified = 0;
+ }
+
+ char *openid_mode = NULL;
+ if ( (!GetHash(keys, "mode", 4, (void *) &openid_mode))
+ || (strcasecmp(openid_mode, "id_res"))
+ ) {
+ oiddata->verified = 0;
+ }
+
+ char *openid_claimed_id = NULL;
+ if (GetHash(keys, "claimed_id", 10, (void *) &openid_claimed_id)) {
+ FreeStrBuf(&oiddata->claimed_id);
+ oiddata->claimed_id = NewStrBufPlain(openid_claimed_id, -1);
+ syslog(LOG_DEBUG, "openid: provider is asserting the Claimed ID '%s'", ChrPtr(oiddata->claimed_id));
+ }
+
+ /* Validate the assertion against the server */
+ syslog(LOG_DEBUG, "openid: validating...");
+
+ CURL *curl;
+ CURLcode res;
+ struct curl_httppost *formpost = NULL;
+ struct curl_httppost *lastptr = NULL;
+ char errmsg[1024] = "";
+ StrBuf *ReplyBuf = NewStrBuf();
+
+ curl_formadd(&formpost, &lastptr,
+ CURLFORM_COPYNAME, "openid.mode",
+ CURLFORM_COPYCONTENTS, "check_authentication",
+ CURLFORM_END
+ );
+
+ HashPos *HashPos = GetNewHashPos(keys, 0);
+ while (GetNextHashPos(keys, HashPos, &len, &Key, &Value) != 0) {
+ if (strcasecmp(Key, "mode")) {
+ char k_o_keyname[1024];
+ snprintf(k_o_keyname, sizeof k_o_keyname, "openid.%s", (const char *)Key);
+ curl_formadd(&formpost, &lastptr,
+ CURLFORM_COPYNAME, k_o_keyname,
+ CURLFORM_COPYCONTENTS, (char *)Value,
+ CURLFORM_END
+ );
+ }
+ }
+ DeleteHashPos(&HashPos);
+
+ curl = ctdl_openid_curl_easy_init(errmsg);
+ curl_easy_setopt(curl, CURLOPT_URL, ChrPtr(oiddata->op_url));
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, ReplyBuf);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlFillStrBuf_callback);
+ curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
+
+ res = curl_easy_perform(curl);
+ if (res) {
+ syslog(LOG_DEBUG, "openid: cmd_oidf() libcurl error %d: %s", res, errmsg);
+ oiddata->verified = 0;
+ }
+ curl_easy_cleanup(curl);
+ curl_formfree(formpost);
+
+ if (cbmstrcasestr(ChrPtr(ReplyBuf), "is_valid:true") == NULL) {
+ oiddata->verified = 0;
+ }
+ FreeStrBuf(&ReplyBuf);
+
+ syslog(LOG_DEBUG, "openid: authentication %s", (oiddata->verified ? "succeeded" : "failed") );
+
+ /* Respond to the client */
+
+ if (oiddata->verified) {
+
+ /* If we were already logged in, attach the OpenID to the user's account */
+ if (CC->logged_in) {
+ if (attach_extauth(&CC->user, oiddata->claimed_id) == 0) {
+ cprintf("attach\n");
+ syslog(LOG_DEBUG, "openid: attach succeeded");
+ }
+ else {
+ cprintf("fail\n");
+ syslog(LOG_DEBUG, "openid: attach failed");
+ }
+ }
+
+ /* Otherwise, a user is attempting to log in using the verified OpenID */
+ else {
+ /*
+ * Existing user who has claimed this OpenID?
+ *
+ * Note: if you think that sending the password back over the wire is insecure,
+ * check your assumptions. If someone has successfully asserted an OpenID that
+ * is associated with the account, they already have password equivalency and can
+ * login, so they could just as easily change the password, etc.
+ */
+ if (login_via_extauth(oiddata->claimed_id) == 0) {
+ cprintf("authenticate\n%s\n%s\n", CC->user.fullname, CC->user.password);
+ logged_in_response();
+ syslog(LOG_DEBUG, "openid: logged in using previously claimed OpenID");
+ }
+
+ /*
+ * If this system does not allow self-service new user registration, the
+ * remaining modes do not apply, so fail here and now.
+ */
+ else if (CtdlGetConfigInt("c_disable_newu")) {
+ cprintf("fail\n");
+ syslog(LOG_DEBUG, "openid: creating user failed due to local policy");
+ }
+
+ /*
+ * New user whose OpenID is verified and Attribute Exchange gave us a name?
+ */
+ else if (openid_create_user_via_ax(oiddata->claimed_id, keys) == 0) {
+ cprintf("authenticate\n%s\n%s\n", CC->user.fullname, CC->user.password);
+ logged_in_response();
+ syslog(LOG_DEBUG, "openid: successfully auto-created new user");
+ }
+
+ /*
+ * OpenID is verified, but the desired username either was not specified or
+ * conflicts with an existing user. Manual account creation is required.
+ */
+ else {
+ char *desired_name = NULL;
+ cprintf("verify_only\n");
+ cprintf("%s\n", ChrPtr(oiddata->claimed_id));
+ if (GetHash(keys, "sreg.nickname", 13, (void *) &desired_name)) {
+ cprintf("%s\n", desired_name);
+ }
+ else {
+ cprintf("\n");
+ }
+ syslog(LOG_DEBUG, "openid: the desired display name is already taken.");
+ }
+ }
+ }
+ else {
+ cprintf("fail\n");
+ }
+ cprintf("000\n");
+
+ if (oiddata->sreg_keys != NULL) {
+ DeleteHash(&oiddata->sreg_keys);
+ oiddata->sreg_keys = NULL;
+ }
+ oiddata->sreg_keys = keys;
+}
+
+
+
+/**************************************************************************/
+/* */
+/* Functions in this section handle module initialization and shutdown */
+/* */
+/**************************************************************************/
+
+
+char *ctdl_module_init_openid_rp(void) {
+ if (!threading) {
+ /* Only enable the OpenID command set when native mode authentication is in use. */
+ if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_NATIVE) {
+ CtdlRegisterProtoHook(cmd_oids, "OIDS", "Setup OpenID authentication");
+ CtdlRegisterProtoHook(cmd_oidf, "OIDF", "Finalize OpenID authentication");
+ CtdlRegisterProtoHook(cmd_oidl, "OIDL", "List OpenIDs associated with an account");
+ CtdlRegisterProtoHook(cmd_oidd, "OIDD", "Detach an OpenID from an account");
+ CtdlRegisterProtoHook(cmd_oidc, "OIDC", "Create new user after validating OpenID");
+ CtdlRegisterProtoHook(cmd_oida, "OIDA", "List all OpenIDs in the database");
+ }
+ CtdlRegisterSessionHook(openid_cleanup_function, EVT_LOGOUT, PRIO_LOGOUT + 10);
+ CtdlRegisterUserHook(extauth_purge, EVT_PURGEUSER);
+ openid_level_supported = 1; /* This module supports OpenID 1.0 only */
+ }
+
+ /* return our module name for the log */
+ return "openid_rp";
+}
--- /dev/null
+// POP3 service for the Citadel system
+//
+// Copyright (c) 1998-2022 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.
+//
+// Current status of standards conformance:
+//
+// -> All required POP3 commands described in RFC1939 are implemented.
+// -> All optional POP3 commands described in RFC1939 are also implemented.
+// -> The deprecated "LAST" command is included in this implementation, because
+// there exist mail clients which insist on using it (such as Bynari
+// TradeMail, and certain versions of Eudora).
+// -> Capability detection via the method described in RFC2449 is implemented.
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "serv_pop3.h"
+#include "../../ctdl_module.h"
+
+
+// This cleanup function blows away the temporary memory and files used by
+// the POP3 server.
+void pop3_cleanup_function(void) {
+ /* Don't do this stuff if this is not a POP3 session! */
+ if (CC->h_command_function != pop3_command_loop) return;
+
+ struct citpop3 *pop3 = ((struct citpop3 *)CC->session_specific_data);
+ syslog(LOG_DEBUG, "pop3: performing cleanup hook");
+ if (pop3->msgs != NULL) {
+ free(pop3->msgs);
+ }
+
+ free(pop3);
+}
+
+
+// Here's where our POP3 session begins its happy day.
+void pop3_greeting(void) {
+ strcpy(CC->cs_clientname, "POP3 session");
+ CC->internal_pgm = 1;
+ CC->session_specific_data = malloc(sizeof(struct citpop3));
+ memset(POP3, 0, sizeof(struct citpop3));
+
+ cprintf("+OK Citadel POP3 server ready.\r\n");
+}
+
+
+// POP3S is just like POP3, except it goes crypto right away.
+void pop3s_greeting(void) {
+ CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
+
+/* kill session if no crypto */
+#ifdef HAVE_OPENSSL
+ if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;
+#else
+ CC->kill_me = KILLME_NO_CRYPTO;
+#endif
+
+ pop3_greeting();
+}
+
+
+// Specify user name (implements POP3 "USER" command)
+void pop3_user(char *argbuf) {
+ char username[SIZ];
+
+ if (CC->logged_in) {
+ cprintf("-ERR You are already logged in.\r\n");
+ return;
+ }
+
+ strcpy(username, argbuf);
+ striplt(username);
+
+ if (CtdlLoginExistingUser(username) == login_ok) {
+ cprintf("+OK Password required for %s\r\n", username);
+ }
+ else {
+ cprintf("-ERR No such user.\r\n");
+ }
+}
+
+
+// Back end for pop3_grab_mailbox()
+void pop3_add_message(long msgnum, void *userdata) {
+ struct MetaData smi;
+
+ ++POP3->num_msgs;
+ if (POP3->num_msgs < 2) {
+ POP3->msgs = malloc(sizeof(struct pop3msg));
+ }
+ else {
+ POP3->msgs = realloc(POP3->msgs, (POP3->num_msgs * sizeof(struct pop3msg)) ) ;
+ }
+ POP3->msgs[POP3->num_msgs-1].msgnum = msgnum;
+ POP3->msgs[POP3->num_msgs-1].deleted = 0;
+
+ // We need to know the length of this message when it is printed in
+ // RFC822 format. Perhaps we have cached this length in the message's
+ // metadata record. If so, great; if not, measure it and then cache
+ // it for next time.
+ GetMetaData(&smi, msgnum);
+ if (smi.meta_rfc822_length <= 0L) {
+ CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+ CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, SUPPRESS_ENV_TO, NULL, NULL, NULL);
+ smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
+ FreeStrBuf(&CC->redirect_buffer);
+ PutMetaData(&smi);
+ }
+ POP3->msgs[POP3->num_msgs-1].rfc822_length = smi.meta_rfc822_length;
+}
+
+
+// Open the inbox and read its contents.
+// (This should be called only once, by pop3_pass(), and returns the number
+// of messages in the inbox, or -1 for error)
+int pop3_grab_mailbox(void) {
+ visit vbuf;
+ int i;
+
+ if (CtdlGetRoom(&CC->room, MAILROOM) != 0) return(-1);
+
+ /* Load up the messages */
+ CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, pop3_add_message, NULL);
+
+ /* Figure out which are old and which are new */
+ CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+ POP3->lastseen = (-1);
+ if (POP3->num_msgs) for (i=0; i<POP3->num_msgs; ++i) {
+ if (is_msg_in_sequence_set(vbuf.v_seen, (POP3->msgs[POP3->num_msgs-1].msgnum) )) {
+ POP3->lastseen = i;
+ }
+ }
+
+ return(POP3->num_msgs);
+}
+
+
+void pop3_login(void) {
+ int msgs;
+
+ msgs = pop3_grab_mailbox();
+ if (msgs >= 0) {
+ cprintf("+OK %s is logged in (%d messages)\r\n",
+ CC->user.fullname, msgs);
+ syslog(LOG_DEBUG, "pop3: authenticated %s", CC->user.fullname);
+ }
+ else {
+ cprintf("-ERR Can't open your mailbox\r\n");
+ }
+
+}
+
+
+// Authorize with password (implements POP3 "PASS" command)
+void pop3_pass(char *argbuf) {
+ char password[SIZ];
+
+ safestrncpy(password, argbuf, sizeof password);
+ striplt(password);
+
+ if (CtdlTryPassword(password, strlen(password)) == pass_ok) {
+ pop3_login();
+ }
+ else {
+ cprintf("-ERR That is NOT the password.\r\n");
+ }
+}
+
+
+// list available msgs
+void pop3_list(char *argbuf) {
+ int i;
+ int which_one;
+
+ which_one = atoi(argbuf);
+
+ // "list one" mode
+ if (which_one > 0) {
+ if (which_one > POP3->num_msgs) {
+ cprintf("-ERR no such message, only %d are here\r\n", POP3->num_msgs);
+ return;
+ }
+ else if (POP3->msgs[which_one-1].deleted) {
+ cprintf("-ERR Sorry, you deleted that message.\r\n");
+ return;
+ }
+ else {
+ cprintf("+OK %d %ld\r\n", which_one, (long)POP3->msgs[which_one-1].rfc822_length);
+ return;
+ }
+ }
+
+ // "list all" (scan listing) mode
+ else {
+ cprintf("+OK Here's your mail:\r\n");
+ if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
+ if (! POP3->msgs[i].deleted) {
+ cprintf("%d %ld\r\n", i+1, (long)POP3->msgs[i].rfc822_length);
+ }
+ }
+ cprintf(".\r\n");
+ }
+}
+
+
+// STAT (tally up the total message count and byte count) command
+void pop3_stat(char *argbuf) {
+ int total_msgs = 0;
+ size_t total_octets = 0;
+ int i;
+
+ if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
+ if (! POP3->msgs[i].deleted) {
+ ++total_msgs;
+ total_octets += POP3->msgs[i].rfc822_length;
+ }
+ }
+
+ cprintf("+OK %d %ld\r\n", total_msgs, (long)total_octets);
+}
+
+
+// RETR command (fetch a message)
+void pop3_retr(char *argbuf) {
+ int which_one;
+
+ which_one = atoi(argbuf);
+ if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
+ cprintf("-ERR No such message.\r\n");
+ return;
+ }
+
+ if (POP3->msgs[which_one - 1].deleted) {
+ cprintf("-ERR Sorry, you deleted that message.\r\n");
+ return;
+ }
+
+ cprintf("+OK Message %d:\r\n", which_one);
+ CtdlOutputMsg(POP3->msgs[which_one - 1].msgnum, MT_RFC822,
+ HEADERS_ALL, 0, 1, NULL,
+ (ESC_DOT|SUPPRESS_ENV_TO), NULL, NULL, NULL
+ );
+ cprintf(".\r\n");
+}
+
+
+// TOP command (dumb way of fetching a partial message or headers-only)
+void pop3_top(char *argbuf) {
+ int which_one;
+ int lines_requested = 0;
+ int lines_dumped = 0;
+ char buf[1024];
+ StrBuf *msgtext;
+ const char *ptr;
+ int in_body = 0;
+ int done = 0;
+
+ sscanf(argbuf, "%d %d", &which_one, &lines_requested);
+ if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
+ cprintf("-ERR No such message.\r\n");
+ return;
+ }
+
+ if (POP3->msgs[which_one - 1].deleted) {
+ cprintf("-ERR Sorry, you deleted that message.\r\n");
+ return;
+ }
+
+ CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+
+ CtdlOutputMsg(POP3->msgs[which_one - 1].msgnum,
+ MT_RFC822,
+ HEADERS_ALL,
+ 0, 1, NULL,
+ SUPPRESS_ENV_TO,
+ NULL, NULL, NULL);
+
+ msgtext = CC->redirect_buffer;
+ CC->redirect_buffer = NULL;
+
+ cprintf("+OK Message %d:\r\n", which_one);
+
+ ptr = ChrPtr(msgtext);
+ while (ptr = cmemreadline(ptr, buf, (sizeof buf - 2)),
+ ( (*ptr != 0) && (done == 0))) {
+ strcat(buf, "\r\n");
+ if (in_body == 1) {
+ if (lines_dumped >= lines_requested) {
+ done = 1;
+ }
+ }
+ if ((in_body == 0) || (done == 0)) {
+ client_write(buf, strlen(buf));
+ }
+ if (in_body) {
+ ++lines_dumped;
+ }
+ if ((buf[0]==13)||(buf[0]==10)) in_body = 1;
+ }
+
+ if (buf[strlen(buf)-1] != 10) cprintf("\n");
+ FreeStrBuf(&msgtext);
+
+ cprintf(".\r\n");
+}
+
+
+// DELE (delete message from mailbox)
+void pop3_dele(char *argbuf) {
+ int which_one;
+
+ which_one = atoi(argbuf);
+ if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
+ cprintf("-ERR No such message.\r\n");
+ return;
+ }
+
+ if (POP3->msgs[which_one - 1].deleted) {
+ cprintf("-ERR You already deleted that message.\r\n");
+ return;
+ }
+
+ // Flag the message as deleted. Will expunge during QUIT command.
+ POP3->msgs[which_one - 1].deleted = 1;
+ cprintf("+OK Message %d deleted.\r\n",
+ which_one);
+}
+
+
+// Perform "UPDATE state" stuff
+void pop3_update(void) {
+ int i;
+ visit vbuf;
+
+ long *deletemsgs = NULL;
+ int num_deletemsgs = 0;
+
+ // Remove messages marked for deletion
+ if (POP3->num_msgs > 0) {
+ deletemsgs = malloc(POP3->num_msgs * sizeof(long));
+ for (i=0; i<POP3->num_msgs; ++i) {
+ if (POP3->msgs[i].deleted) {
+ deletemsgs[num_deletemsgs++] = POP3->msgs[i].msgnum;
+ }
+ }
+ if (num_deletemsgs > 0) {
+ CtdlDeleteMessages(MAILROOM, deletemsgs, num_deletemsgs, "");
+ }
+ free(deletemsgs);
+ }
+
+ // Set last read pointer
+ if (POP3->num_msgs > 0) {
+ CtdlLockGetCurrentUser();
+ CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+ snprintf(vbuf.v_seen, sizeof vbuf.v_seen, "*:%ld", POP3->msgs[POP3->num_msgs-1].msgnum);
+ CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
+ CtdlPutCurrentUserLock();
+ }
+
+}
+
+
+// RSET (reset, i.e. undelete any deleted messages) command
+void pop3_rset(char *argbuf) {
+ int i;
+
+ if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
+ if (POP3->msgs[i].deleted) {
+ POP3->msgs[i].deleted = 0;
+ }
+ }
+ cprintf("+OK Reset completed.\r\n");
+}
+
+
+// LAST (Determine which message is the last unread message)
+void pop3_last(char *argbuf) {
+ cprintf("+OK %d\r\n", POP3->lastseen + 1);
+}
+
+
+// CAPA is a command which tells the client which POP3 extensions are supported.
+void pop3_capa(void) {
+ cprintf("+OK Capability list follows\r\n"
+ "TOP\r\n"
+ "USER\r\n"
+ "UIDL\r\n"
+ "IMPLEMENTATION %s\r\n"
+ ".\r\n"
+ ,
+ CITADEL
+ );
+}
+
+
+// UIDL (Universal IDentifier Listing) is easy. Our 'unique' message
+// identifiers are simply the Citadel message numbers in the database.
+void pop3_uidl(char *argbuf) {
+ int i;
+ int which_one;
+
+ which_one = atoi(argbuf);
+
+ // "list one" mode
+ if (which_one > 0) {
+ if (which_one > POP3->num_msgs) {
+ cprintf("-ERR no such message, only %d are here\r\n", POP3->num_msgs);
+ return;
+ }
+ else if (POP3->msgs[which_one-1].deleted) {
+ cprintf("-ERR Sorry, you deleted that message.\r\n");
+ return;
+ }
+ else {
+ cprintf("+OK %d %ld\r\n", which_one, POP3->msgs[which_one-1].msgnum);
+ return;
+ }
+ }
+
+ // "list all" (scan listing) mode
+ else {
+ cprintf("+OK Here's your mail:\r\n");
+ if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
+ if (! POP3->msgs[i].deleted) {
+ cprintf("%d %ld\r\n", i+1, POP3->msgs[i].msgnum);
+ }
+ }
+ cprintf(".\r\n");
+ }
+}
+
+
+// implements the STLS command (Citadel API version)
+void pop3_stls(void) {
+ char ok_response[SIZ];
+ char nosup_response[SIZ];
+ char error_response[SIZ];
+
+ sprintf(ok_response, "+OK Begin TLS negotiation now\r\n");
+ sprintf(nosup_response, "-ERR TLS not supported here\r\n");
+ sprintf(error_response, "-ERR Internal error\r\n");
+ CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
+}
+
+
+// Main command loop for POP3 sessions.
+void pop3_command_loop(void) {
+ char cmdbuf[SIZ];
+
+ time(&CC->lastcmd);
+ memset(cmdbuf, 0, sizeof cmdbuf); // Clear it, just in case
+ if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
+ syslog(LOG_INFO, "pop3: client disconnected; ending session.");
+ CC->kill_me = KILLME_CLIENT_DISCONNECTED;
+ return;
+ }
+ if (!strncasecmp(cmdbuf, "PASS", 4)) {
+ syslog(LOG_DEBUG, "pop3: PASS...");
+ }
+ else {
+ syslog(LOG_DEBUG, "pop3: %s", cmdbuf);
+ }
+ while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
+
+ if (!strncasecmp(cmdbuf, "NOOP", 4)) {
+ cprintf("+OK No operation.\r\n");
+ }
+
+ else if (!strncasecmp(cmdbuf, "CAPA", 4)) {
+ pop3_capa();
+ }
+
+ else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
+ cprintf("+OK Goodbye...\r\n");
+ pop3_update();
+ CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
+ return;
+ }
+
+ else if (!strncasecmp(cmdbuf, "USER", 4)) {
+ pop3_user(&cmdbuf[5]);
+ }
+
+ else if (!strncasecmp(cmdbuf, "PASS", 4)) {
+ pop3_pass(&cmdbuf[5]);
+ }
+
+#ifdef HAVE_OPENSSL
+ else if (!strncasecmp(cmdbuf, "STLS", 4)) {
+ pop3_stls();
+ }
+#endif
+
+ else if (!CC->logged_in) {
+ cprintf("-ERR Not logged in.\r\n");
+ }
+
+ else if (CC->nologin) {
+ cprintf("-ERR System busy, try later.\r\n");
+ CC->kill_me = KILLME_NOLOGIN;
+ }
+
+ else if (!strncasecmp(cmdbuf, "LIST", 4)) {
+ pop3_list(&cmdbuf[5]);
+ }
+
+ else if (!strncasecmp(cmdbuf, "STAT", 4)) {
+ pop3_stat(&cmdbuf[5]);
+ }
+
+ else if (!strncasecmp(cmdbuf, "RETR", 4)) {
+ pop3_retr(&cmdbuf[5]);
+ }
+
+ else if (!strncasecmp(cmdbuf, "DELE", 4)) {
+ pop3_dele(&cmdbuf[5]);
+ }
+
+ else if (!strncasecmp(cmdbuf, "RSET", 4)) {
+ pop3_rset(&cmdbuf[5]);
+ }
+
+ else if (!strncasecmp(cmdbuf, "UIDL", 4)) {
+ pop3_uidl(&cmdbuf[5]);
+ }
+
+ else if (!strncasecmp(cmdbuf, "TOP", 3)) {
+ pop3_top(&cmdbuf[4]);
+ }
+
+ else if (!strncasecmp(cmdbuf, "LAST", 4)) {
+ pop3_last(&cmdbuf[4]);
+ }
+
+ else {
+ cprintf("-ERR I'm afraid I can't do that.\r\n");
+ }
+
+}
+
+const char *CitadelServicePop3="POP3";
+const char *CitadelServicePop3S="POP3S";
+
+
+char *ctdl_module_init_pop3(void) {
+ if (!threading) {
+ CtdlRegisterServiceHook(CtdlGetConfigInt("c_pop3_port"),
+ NULL,
+ pop3_greeting,
+ pop3_command_loop,
+ NULL,
+ CitadelServicePop3);
+#ifdef HAVE_OPENSSL
+ CtdlRegisterServiceHook(CtdlGetConfigInt("c_pop3s_port"),
+ NULL,
+ pop3s_greeting,
+ pop3_command_loop,
+ NULL,
+ CitadelServicePop3S);
+#endif
+ CtdlRegisterSessionHook(pop3_cleanup_function, EVT_STOP, PRIO_STOP + 30);
+ }
+
+ /* return our module name for the log */
+ return "pop3";
+}
--- /dev/null
+/*
+ * 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.
+ */
+
+struct pop3msg {
+ long msgnum;
+ size_t rfc822_length;
+ int deleted;
+};
+
+struct citpop3 { /* Information about the current session */
+ struct pop3msg *msgs; /* Array of message pointers */
+ int num_msgs; /* Number of messages in array */
+ int lastseen; /* Offset of last-read message in array */
+};
+ /* Note: the "lastseen" is represented as the
+ * offset in this array (zero-based), so when
+ * displaying it to a POP3 client, it must be
+ * incremented by one.
+ */
+
+#define POP3 ((struct citpop3 *)CC->session_specific_data)
+
+void pop3_cleanup_function(void);
+void pop3_greeting(void);
+void pop3_user(char *argbuf);
+void pop3_pass(char *argbuf);
+void pop3_list(char *argbuf);
+void pop3_command_loop(void);
+void pop3_login(void);
+
--- /dev/null
+/*
+ * Consolidate mail from remote POP3 accounts.
+ *
+ * Copyright (c) 2007-2022 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 as published
+ * by the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <time.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <curl/curl.h>
+#include <libcitadel.h>
+#include "../../sysconfig.h"
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../ctdl_module.h"
+#include "../../clientsocket.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../database.h"
+#include "../../citadel_dirs.h"
+
+struct p3cq { // module-local queue of pop3 client work that needs processing
+ struct p3cq *next;
+ char *room;
+ char *host;
+ char *user;
+ char *pass;
+ int keep;
+ long interval;
+};
+
+static int doing_pop3client = 0;
+struct p3cq *p3cq = NULL;
+
+/*
+ * Process one mailbox.
+ */
+void pop3client_one_mailbox(char *room, const char *host, const char *user, const char *pass, int keep, long interval) {
+ syslog(LOG_DEBUG, "pop3client: room=<%s> host=<%s> user=<%s> keep=<%d> interval=<%ld>", room, host, user, keep, interval);
+
+ char url[SIZ];
+ CURL *curl;
+ CURLcode res = CURLE_OK;
+ StrBuf *Uidls = NULL;
+ int i;
+ char cmd[1024];
+
+ curl = curl_easy_init();
+ if (!curl) {
+ return;
+ }
+
+ Uidls = NewStrBuf();
+
+ curl_easy_setopt(curl, CURLOPT_USERNAME, user);
+ curl_easy_setopt(curl, CURLOPT_PASSWORD, pass);
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT, 15);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlFillStrBuf_callback); // What to do with downloaded data
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, Uidls); // Give it our StrBuf to work with
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "UIDL");
+
+ /* Try POP3S (SSL encrypted) first */
+ snprintf(url, sizeof url, "pop3s://%s", host);
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ res = curl_easy_perform(curl);
+ if (res == CURLE_OK) {
+ }
+ else {
+ syslog(LOG_DEBUG, "pop3client: POP3S connection failed: %s , trying POP3 next", curl_easy_strerror(res));
+ snprintf(url, sizeof url, "pop3://%s", host); // try unencrypted next
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ FlushStrBuf(Uidls);
+ res = curl_easy_perform(curl);
+ }
+
+ if (res != CURLE_OK) {
+ syslog(LOG_DEBUG, "pop3client: POP3 connection failed: %s", curl_easy_strerror(res));
+ curl_easy_cleanup(curl);
+ FreeStrBuf(&Uidls);
+ return;
+ }
+
+ // If we got this far, a connection was established, we know whether it's pop3s or pop3, and UIDL is supported.
+ // Now go through the UIDL list and look for messages.
+
+ int num_msgs = num_tokens(ChrPtr(Uidls), '\n');
+ syslog(LOG_DEBUG, "pop3client: there are %d messages", num_msgs);
+ for (i=0; i<num_msgs; ++i) {
+ char oneuidl[1024];
+ extract_token(oneuidl, ChrPtr(Uidls), i, '\n', sizeof oneuidl);
+ if (strlen(oneuidl) > 2) {
+ if (oneuidl[strlen(oneuidl)-1] == '\r') {
+ oneuidl[strlen(oneuidl)-1] = 0;
+ }
+ int this_msg = atoi(oneuidl);
+ char *c = strchr(oneuidl, ' ');
+ if (c) strcpy(oneuidl, ++c);
+
+ // Make up the Use Table record so we can check if we've already seen this message.
+ StrBuf *UT = NewStrBuf();
+ StrBufPrintf(UT, "pop3/%s/%s:%s@%s", room, oneuidl, user, host);
+ int already_seen = CheckIfAlreadySeen(UT);
+ FreeStrBuf(&UT);
+
+ // Only fetch the message if we haven't seen it before.
+ if (already_seen == 0) {
+ StrBuf *TheMsg = NewStrBuf();
+ snprintf(cmd, sizeof cmd, "RETR %d", this_msg);
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, cmd);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, TheMsg);
+ res = curl_easy_perform(curl);
+ if (res == CURLE_OK) {
+ struct CtdlMessage *msg = convert_internet_message_buf(&TheMsg);
+ CtdlSubmitMsg(msg, NULL, room);
+ CM_Free(msg);
+ }
+ else {
+ FreeStrBuf(&TheMsg);
+ }
+
+ // Unless the configuration says to keep the message on the server, delete it.
+ if (keep == 0) {
+ snprintf(cmd, sizeof cmd, "DELE %d", this_msg);
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, cmd);
+ res = curl_easy_perform(curl);
+ }
+ }
+ else {
+ syslog(LOG_DEBUG, "pop3client: %s has already been retrieved", oneuidl);
+ }
+ }
+ }
+
+ curl_easy_cleanup(curl);
+ FreeStrBuf(&Uidls);
+ return;
+}
+
+
+// Scan a room's netconfig looking for RSS feed parsing requests
+//
+void pop3client_scan_room(struct ctdlroom *qrbuf, void *data)
+{
+ char *serialized_config = NULL;
+ int num_configs = 0;
+ char cfgline[SIZ];
+ char cfgelement[SIZ];
+ int i = 0;
+
+ serialized_config = LoadRoomNetConfigFile(qrbuf->QRnumber);
+ if (!serialized_config) {
+ return;
+ }
+
+ num_configs = num_tokens(serialized_config, '\n');
+ for (i=0; i<num_configs; ++i) {
+ extract_token(cfgline, serialized_config, i, '\n', sizeof cfgline);
+ if (!strncasecmp(cfgline, HKEY("pop3client|"))) {
+ struct p3cq *pptr = malloc(sizeof(struct p3cq));
+ pptr->next = p3cq;
+ p3cq = pptr;
+ p3cq->room = strdup(qrbuf->QRname);
+ extract_token(cfgelement, cfgline, 1, '|', sizeof cfgelement);
+ p3cq->host = strdup(cfgelement);
+ extract_token(cfgelement, cfgline, 2, '|', sizeof cfgelement);
+ p3cq->user = strdup(cfgelement);
+ extract_token(cfgelement, cfgline, 3, '|', sizeof cfgelement);
+ p3cq->pass = strdup(cfgelement);
+ p3cq->keep = extract_int(cfgline, 4);
+ p3cq->interval = extract_long(cfgline, 5);
+ }
+ }
+
+ free(serialized_config);
+}
+
+
+void pop3client_scan(void) {
+ static time_t last_run = 0L;
+ time_t fastest_scan;
+ struct p3cq *pptr = NULL;
+
+ if (CtdlGetConfigLong("c_pop3_fastest") < CtdlGetConfigLong("c_pop3_fetch")) {
+ fastest_scan = CtdlGetConfigLong("c_pop3_fastest");
+ }
+ else {
+ fastest_scan = CtdlGetConfigLong("c_pop3_fetch");
+ }
+
+ /*
+ * Run POP3 aggregation no more frequently than once every n seconds
+ */
+ if ( (time(NULL) - last_run) < fastest_scan ) {
+ return;
+ }
+
+ /*
+ * This is a simple concurrency check to make sure only one pop3client
+ * run is done at a time. We could do this with a mutex, but since we
+ * don't really require extremely fine granularity here, we'll do it
+ * with a static variable instead.
+ */
+ if (doing_pop3client) return;
+ doing_pop3client = 1;
+
+ syslog(LOG_DEBUG, "pop3client: scan started");
+ CtdlForEachRoom(pop3client_scan_room, NULL);
+
+ /*
+ * We have to queue and process in separate phases, otherwise we leave a cursor open
+ */
+ syslog(LOG_DEBUG, "pop3client: processing started");
+ while (p3cq != NULL) {
+ pptr = p3cq;
+ p3cq = p3cq->next;
+
+ pop3client_one_mailbox(pptr->room, pptr->host, pptr->user, pptr->pass, pptr->keep, pptr->interval);
+
+ free(pptr->room);
+ free(pptr->host);
+ free(pptr->user);
+ free(pptr->pass);
+ free(pptr);
+ }
+
+ syslog(LOG_DEBUG, "pop3client: ended");
+ last_run = time(NULL);
+ doing_pop3client = 0;
+}
+
+
+char *ctdl_module_init_pop3client(void) {
+ if (!threading) {
+ CtdlRegisterSessionHook(pop3client_scan, EVT_TIMER, PRIO_AGGR + 50);
+ }
+
+ /* return our module id for the log */
+ return "pop3client";
+}
--- /dev/null
+/*
+ * This module handles instant messaging between users.
+ *
+ * Copyright (c) 2012-2022 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.
+ */
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <assert.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../msgbase.h"
+#include "../../user_ops.h"
+#include "../../ctdl_module.h"
+
+struct chatmsg {
+ struct chatmsg *next;
+ time_t timestamp;
+ int seq;
+ long roomnum;
+ char *sender;
+ char *msgtext;
+};
+
+struct chatmsg *first_chat_msg = NULL;
+struct chatmsg *last_chat_msg = NULL;
+
+
+/*
+ * Periodically called for housekeeping. Expire old chat messages so they don't take up memory forever.
+ */
+void roomchat_timer(void) {
+ struct chatmsg *ptr;
+
+ begin_critical_section(S_CHATQUEUE);
+
+ while ((first_chat_msg != NULL) && ((time(NULL) - first_chat_msg->timestamp) > 300)) {
+ ptr = first_chat_msg->next;
+ free(first_chat_msg->sender);
+ free(first_chat_msg->msgtext);
+ free(first_chat_msg);
+ first_chat_msg = ptr;
+ if (first_chat_msg == NULL) {
+ last_chat_msg = NULL;
+ }
+ }
+
+ end_critical_section(S_CHATQUEUE);
+}
+
+
+/*
+ * Perform shutdown-related activities...
+ */
+void roomchat_shutdown(void) {
+ /* if we ever start logging chats, we have to flush them to disk here .*/
+}
+
+
+/*
+ * Add a message into the chat queue
+ */
+void add_to_chat_queue(char *msg) {
+ static int seq = 0;
+
+ struct chatmsg *m = malloc(sizeof(struct chatmsg));
+ if (!m) return;
+
+ m->next = NULL;
+ m->timestamp = time(NULL);
+ m->roomnum = CC->room.QRnumber;
+ m->sender = strdup(CC->user.fullname);
+ m->msgtext = strdup(msg);
+
+ if ((m->sender == NULL) || (m->msgtext == NULL)) {
+ free(m->sender);
+ free(m->msgtext);
+ free(m);
+ return;
+ }
+
+ begin_critical_section(S_CHATQUEUE);
+ m->seq = ++seq;
+
+ if (first_chat_msg == NULL) {
+ assert(last_chat_msg == NULL);
+ first_chat_msg = m;
+ last_chat_msg = m;
+ }
+ else {
+ assert(last_chat_msg != NULL);
+ assert(last_chat_msg->next == NULL);
+ last_chat_msg->next = m;
+ last_chat_msg = m;
+ }
+
+ end_critical_section(S_CHATQUEUE);
+}
+
+
+/*
+ * Transmit a message into a room chat
+ */
+void roomchat_send(char *argbuf) {
+ char buf[1024];
+
+ if ((CC->cs_flags & CS_CHAT) == 0) {
+ cprintf("%d Session is not in chat mode.\n", ERROR);
+ return;
+ }
+
+ cprintf("%d send now\n", SEND_LISTING);
+ while (client_getln(buf, sizeof buf) >= 0 && strcmp(buf, "000")) {
+ add_to_chat_queue(buf);
+ }
+}
+
+
+/*
+ * Poll room for incoming chat messages
+ */
+void roomchat_poll(char *argbuf) {
+ int newer_than = 0;
+ struct chatmsg *found = NULL;
+ struct chatmsg *ptr = NULL;
+
+ newer_than = extract_int(argbuf, 1);
+
+ if ((CC->cs_flags & CS_CHAT) == 0) {
+ cprintf("%d Session is not in chat mode.\n", ERROR);
+ return;
+ }
+
+ begin_critical_section(S_CHATQUEUE);
+ for (ptr = first_chat_msg; ((ptr != NULL) && (found == NULL)); ptr = ptr->next) {
+ if ((ptr->seq > newer_than) && (ptr->roomnum == CC->room.QRnumber)) {
+ found = ptr;
+ }
+ }
+ end_critical_section(S_CHATQUEUE);
+
+ if (found == NULL) {
+ cprintf("%d no messages\n", ERROR + MESSAGE_NOT_FOUND);
+ return;
+ }
+
+ cprintf("%d %d|%ld|%s\n", LISTING_FOLLOWS, found->seq, found->timestamp, found->sender);
+ cprintf("%s\n", found->msgtext);
+ cprintf("000\n");
+}
+
+
+
+/*
+ * list users in chat in this room
+ */
+void roomchat_rwho(char *argbuf) {
+ struct CitContext *nptr;
+ int nContexts, i;
+
+ if ((CC->cs_flags & CS_CHAT) == 0) {
+ cprintf("%d Session is not in chat mode.\n", ERROR);
+ return;
+ }
+
+ cprintf("%d%c \n", LISTING_FOLLOWS, CtdlCheckExpress() );
+
+ nptr = CtdlGetContextArray(&nContexts) ; // grab a copy of the wholist
+ if (nptr) {
+ for (i=0; i<nContexts; i++) { // list the users
+ if ( (nptr[i].room.QRnumber == CC->room.QRnumber)
+ && (nptr[i].cs_flags & CS_CHAT)
+ ) {
+ cprintf("%s\n", nptr[i].user.fullname);
+ }
+ }
+ free(nptr); // free our copy
+ }
+
+ cprintf("000\n");
+}
+
+
+
+/*
+ * Participate in real time chat in a room
+ */
+void cmd_rcht(char *argbuf)
+{
+ char subcmd[16];
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
+
+ if (!strcasecmp(subcmd, "enter")) {
+ CC->cs_flags |= CS_CHAT;
+ cprintf("%d Entering chat mode.\n", CIT_OK);
+ }
+ else if (!strcasecmp(subcmd, "exit")) {
+ CC->cs_flags &= ~CS_CHAT;
+ cprintf("%d Exiting chat mode.\n", CIT_OK);
+ }
+ else if (!strcasecmp(subcmd, "send")) {
+ roomchat_send(argbuf);
+ }
+ else if (!strcasecmp(subcmd, "poll")) {
+ roomchat_poll(argbuf);
+ }
+ else if (!strcasecmp(subcmd, "rwho")) {
+ roomchat_rwho(argbuf);
+ }
+ else {
+ cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
+ }
+}
+
+
+char *ctdl_module_init_roomchat(void) {
+ if (!threading) {
+ CtdlRegisterProtoHook(cmd_rcht, "RCHT", "Participate in real time chat in a room");
+ CtdlRegisterSessionHook(roomchat_timer, EVT_TIMER, PRIO_CLEANUP + 400);
+ CtdlRegisterSessionHook(roomchat_shutdown, EVT_SHUTDOWN, PRIO_SHUTDOWN + 55);
+ }
+
+ /* return our module name for the log */
+ return "roomchat";
+}
--- /dev/null
+/*
+ * Bring external RSS and/or Atom feeds into rooms. This module implements a
+ * very loose parser that scrapes both kinds of feeds and is not picky about
+ * the standards compliance of the source data.
+ *
+ * Copyright (c) 2007-2022 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.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <time.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <expat.h>
+#include <curl/curl.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../threads.h"
+#include "../../ctdl_module.h"
+#include "../../msgbase.h"
+#include "../../parsedate.h"
+#include "../../database.h"
+#include "../../citadel_dirs.h"
+#include "../../context.h"
+#include "../../internet_addressing.h"
+
+struct rssroom {
+ struct rssroom *next;
+ char *room;
+};
+
+struct rssurl {
+ struct rssurl *next;
+ char *url;
+ struct rssroom *rooms;
+};
+
+struct rssparser {
+ StrBuf *CData;
+ struct CtdlMessage *msg;
+ char *link;
+ char *description;
+ char *item_id;
+ struct rssroom *rooms;
+};
+
+time_t last_run = 0L;
+struct rssurl *rsstodo = NULL;
+
+
+// This handler is called whenever an XML tag opens.
+//
+void rss_start_element(void *data, const char *el, const char **attribute) {
+ struct rssparser *r = (struct rssparser *)data;
+ int i;
+
+ if (server_shutting_down) return; // shunt the whole operation if we're exiting
+
+ if (
+ (!strcasecmp(el, "entry"))
+ || (!strcasecmp(el, "item"))
+ ) {
+ // this is the start of a new item(rss) or entry(atom)
+ if (r->msg != NULL) {
+ CM_Free(r->msg);
+ r->msg = NULL;
+ }
+ r->msg = malloc(sizeof(struct CtdlMessage));
+ memset(r->msg, 0, sizeof(struct CtdlMessage));
+ r->msg->cm_magic = CTDLMESSAGE_MAGIC;
+ r->msg->cm_anon_type = MES_NORMAL;
+ r->msg->cm_format_type = FMT_RFC822;
+ }
+
+ else if (!strcasecmp(el, "link")) { // atom feeds have the link as an attribute
+ for(i = 0; attribute[i]; i += 2) {
+ if (!strcasecmp(attribute[i], "href")) {
+ if (r->link != NULL) {
+ free(r->link);
+ r->link = NULL;
+ }
+ r->link = strdup(attribute[i+1]);
+ striplt(r->link);
+ }
+ }
+ }
+}
+
+
+// This handler is called whenever an XML tag closes.
+//
+void rss_end_element(void *data, const char *el) {
+ struct rssparser *r = (struct rssparser *)data;
+ StrBuf *encoded_field;
+
+ if (server_shutting_down) return; // shunt the whole operation if we're exiting
+
+ if (StrLength(r->CData) > 0) { // strip leading/trailing whitespace from field
+ StrBufTrim(r->CData);
+ }
+
+ if ( // end of a new item(rss) or entry(atom)
+ (!strcasecmp(el, "entry"))
+ || (!strcasecmp(el, "item"))
+ ) {
+ if (r->msg != NULL) { // Save the message to the rooms
+
+ // use the link as an item id if nothing else is available
+ if ((r->item_id == NULL) && (r->link != NULL)) {
+ r->item_id = strdup(r->link);
+ }
+
+ // check the use table
+ StrBuf *u = NewStrBuf();
+ StrBufAppendPrintf(u, "rss/%s", r->item_id);
+ int already_seen = CheckIfAlreadySeen(u);
+ FreeStrBuf(&u);
+
+ if (already_seen == 0) {
+
+ // Compose the message text
+ StrBuf *TheMessage = NewStrBuf();
+ StrBufAppendPrintf(TheMessage,
+ "Content-type: text/html\n\n"
+ "\n\n"
+ "<html><head></head><body>"
+ );
+
+ if (r->description != NULL) {
+ StrBufAppendPrintf(TheMessage, "%s<br><br>\r\n", r->description);
+ free(r->description);
+ r->description = NULL;
+ }
+
+ if (r->link != NULL) {
+ StrBufAppendPrintf(TheMessage, "<a href=\"%s\">%s</a>\r\n", r->link, r->link);
+ free(r->link);
+ r->link = NULL;
+ }
+
+ StrBufAppendPrintf(TheMessage, "</body></html>\r\n");
+ CM_SetField(r->msg, eMesageText, ChrPtr(TheMessage), StrLength(TheMessage));
+ FreeStrBuf(&TheMessage);
+
+ if (CM_IsEmpty(r->msg, eAuthor)) {
+ CM_SetField(r->msg, eAuthor, HKEY("rss"));
+ }
+
+ if (CM_IsEmpty(r->msg, eTimestamp)) {
+ CM_SetFieldLONG(r->msg, eTimestamp, time(NULL));
+ }
+
+ // Save it to the room(s)
+ struct rssroom *rr = NULL;
+ long msgnum = (-1);
+ for (rr=r->rooms; rr!=NULL; rr=rr->next) {
+ if (rr == r->rooms) {
+ msgnum = CtdlSubmitMsg(r->msg, NULL, rr->room); // in first room, save msg
+ }
+ else {
+ CtdlSaveMsgPointerInRoom(rr->room, msgnum, 0, NULL); // elsewhere, save a pointer
+ }
+ syslog(LOG_DEBUG, "rssclient: saved message %ld to %s", msgnum, rr->room);
+ }
+ }
+ else {
+ syslog(LOG_DEBUG, "rssclient: already seen %s", r->item_id);
+ }
+
+ CM_Free(r->msg);
+ r->msg = NULL;
+ }
+
+ if (r->item_id != NULL) {
+ free(r->item_id);
+ r->item_id = NULL;
+ }
+ }
+
+ else if (!strcasecmp(el, "title")) { // item subject (rss and atom)
+ if ((r->msg != NULL) && (CM_IsEmpty(r->msg, eMsgSubject))) {
+ encoded_field = NewStrBuf();
+ StrBufRFC2047encode(&encoded_field, r->CData);
+ CM_SetAsFieldSB(r->msg, eMsgSubject, &encoded_field);
+ }
+ }
+
+ else if (!strcasecmp(el, "creator")) { // <creator> can be used if <author> is not present
+ if ((r->msg != NULL) && (CM_IsEmpty(r->msg, eAuthor))) {
+ encoded_field = NewStrBuf();
+ StrBufRFC2047encode(&encoded_field, r->CData);
+ CM_SetAsFieldSB(r->msg, eAuthor, &encoded_field);
+ }
+ }
+
+ else if (!strcasecmp(el, "author")) { // <author> supercedes <creator> if both are present
+ if (r->msg != NULL) {
+ encoded_field = NewStrBuf();
+ StrBufRFC2047encode(&encoded_field, r->CData);
+ CM_SetAsFieldSB(r->msg, eAuthor, &encoded_field);
+ }
+ }
+
+ else if (!strcasecmp(el, "pubdate")) { // date/time stamp (rss) Sat, 25 Feb 2017 14:28:01 EST
+ if ((r->msg)&&(r->msg->cm_fields[eTimestamp]==NULL)) {
+ CM_SetFieldLONG(r->msg, eTimestamp, parsedate(ChrPtr(r->CData)));
+ }
+ }
+
+ else if (!strcasecmp(el, "updated")) { // date/time stamp (atom) 2003-12-13T18:30:02Z
+ if ((r->msg)&&(r->msg->cm_fields[eTimestamp]==NULL)) {
+ struct tm t;
+ char zulu;
+ memset(&t, 0, sizeof t);
+ sscanf(ChrPtr(r->CData), "%d-%d-%dT%d:%d:%d%c", &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec, &zulu);
+ t.tm_year -= 1900;
+ t.tm_mon -= 1;
+ CM_SetFieldLONG(r->msg, eTimestamp, mktime(&t));
+ }
+ }
+
+ else if (!strcasecmp(el, "link")) { // link to story (rss)
+ if (r->link != NULL) {
+ free(r->link);
+ r->link = NULL;
+ }
+ r->link = strdup(ChrPtr(r->CData));
+ }
+
+ else if (
+ (!strcasecmp(el, "guid")) // unique item id (rss)
+ || (!strcasecmp(el, "id")) // unique item id (atom)
+ ) {
+ if (r->item_id != NULL) {
+ free(r->item_id);
+ r->item_id = NULL;
+ }
+ r->item_id = strdup(ChrPtr(r->CData));
+ }
+
+ else if (
+ (!strcasecmp(el, "description")) // message text (rss)
+ || (!strcasecmp(el, "summary")) // message text (atom)
+ || (!strcasecmp(el, "content")) // message text (atom)
+ ) {
+ if (r->description != NULL) {
+ free(r->description);
+ r->description = NULL;
+ }
+ r->description = strdup(ChrPtr(r->CData));
+ }
+
+ if (r->CData != NULL) {
+ FreeStrBuf(&r->CData);
+ r->CData = NULL;
+ }
+}
+
+
+// This handler is called whenever data appears between opening and closing tags.
+//
+void rss_handle_data(void *data, const char *content, int length)
+{
+ struct rssparser *r = (struct rssparser *)data;
+
+ if (r->CData == NULL) {
+ r->CData = NewStrBuf();
+ }
+
+ StrBufAppendBufPlain(r->CData, content, length, 0);
+}
+
+
+// Feed has been downloaded, now parse it.
+//
+void rss_parse_feed(StrBuf *Feed, struct rssroom *rooms)
+{
+ struct rssparser r;
+
+ memset(&r, 0, sizeof r);
+ r.rooms = rooms;
+ XML_Parser p = XML_ParserCreate("UTF-8");
+ XML_SetElementHandler(p, rss_start_element, rss_end_element);
+ XML_SetCharacterDataHandler(p, rss_handle_data);
+ XML_SetUserData(p, (void *)&r);
+ XML_Parse(p, ChrPtr(Feed), StrLength(Feed), XML_TRUE);
+ XML_ParserFree(p);
+}
+
+
+// Add a feed/room pair into the todo list
+//
+void rssclient_push_todo(char *rssurl, char *roomname)
+{
+ struct rssurl *r = NULL;
+ struct rssurl *thisone = NULL;
+ struct rssroom *newroom = NULL;
+
+ syslog(LOG_DEBUG, "rssclient: will fetch %s to %s", rssurl, roomname);
+
+ for (r=rsstodo; r!=NULL; r=r->next) {
+ if (!strcasecmp(r->url, rssurl)) {
+ thisone = r;
+ }
+ }
+
+ if (thisone == NULL) {
+ thisone = malloc(sizeof(struct rssurl));
+ thisone->url = strdup(rssurl);
+ thisone->rooms = NULL;
+ thisone->next = rsstodo;
+ rsstodo = thisone;
+ }
+
+ newroom = malloc(sizeof(struct rssroom));
+ newroom->room = strdup(roomname);
+ newroom->next = thisone->rooms;
+ thisone->rooms = newroom;
+}
+
+
+// pull one feed (possibly multiple rooms)
+//
+void rss_pull_one_feed(struct rssurl *url)
+{
+ CURL *curl;
+ CURLcode res;
+ StrBuf *Downloaded = NULL;
+
+ syslog(LOG_DEBUG, "rssclient: fetching %s", url->url);
+
+ curl = curl_easy_init();
+ if (!curl) {
+ return;
+ }
+
+ Downloaded = NewStrBuf();
+
+ curl_easy_setopt(curl, CURLOPT_URL, url->url);
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // Follow redirects
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlFillStrBuf_callback); // What to do with downloaded data
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, Downloaded); // Give it our StrBuf to work with
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT, 20L); // Time out after 20 seconds
+ res = curl_easy_perform(curl); // Perform the request
+ if (res != CURLE_OK) {
+ syslog(LOG_WARNING, "rssclient: failed to load feed: %s", curl_easy_strerror(res));
+ }
+ curl_easy_cleanup(curl);
+
+ rss_parse_feed(Downloaded, url->rooms); // parse the feed
+ FreeStrBuf(&Downloaded); // free the downloaded feed data
+}
+
+
+// We have a list, now download the feeds
+//
+void rss_pull_feeds(void)
+{
+ struct rssurl *r;
+ struct rssroom *rr;
+
+ while ((rsstodo != NULL) && (!server_shutting_down)) {
+ rss_pull_one_feed(rsstodo);
+ r = rsstodo;
+ rsstodo = rsstodo->next;
+ while (r->rooms != NULL) {
+ rr = r->rooms;
+ r->rooms = r->rooms->next;
+ free(rr->room);
+ free(rr);
+ }
+ free(r->url);
+ free(r);
+ }
+}
+
+
+// Scan a room's netconfig looking for RSS feed parsing requests
+//
+void rssclient_scan_room(struct ctdlroom *qrbuf, void *data)
+{
+ char *serialized_config = NULL;
+ int num_configs = 0;
+ char cfgline[SIZ];
+ int i = 0;
+
+ if (server_shutting_down) return;
+
+ serialized_config = LoadRoomNetConfigFile(qrbuf->QRnumber);
+ if (!serialized_config) {
+ return;
+ }
+
+ num_configs = num_tokens(serialized_config, '\n');
+ for (i=0; i<num_configs; ++i) {
+ extract_token(cfgline, serialized_config, i, '\n', sizeof cfgline);
+ if (!strncasecmp(cfgline, HKEY("rssclient|"))) {
+ strcpy(cfgline, &cfgline[10]);
+ char *vbar = strchr(cfgline, '|');
+ if (vbar != NULL) {
+ *vbar = 0;
+ }
+ rssclient_push_todo(cfgline, qrbuf->QRname);
+ }
+ }
+
+ free(serialized_config);
+}
+
+
+/*
+ * Scan for rooms that have RSS client requests configured
+ */
+void rssclient_scan(void) {
+ time_t now = time(NULL);
+
+ /* Run no more than once every 15 minutes. */
+ if ((now - last_run) < 900) {
+ syslog(LOG_DEBUG,
+ "rssclient: polling interval not yet reached; last run was %ldm%lds ago",
+ ((now - last_run) / 60),
+ ((now - last_run) % 60)
+ );
+ return;
+ }
+
+ syslog(LOG_DEBUG, "rssclient: started");
+ CtdlForEachRoom(rssclient_scan_room, NULL);
+ rss_pull_feeds();
+ syslog(LOG_DEBUG, "rssclient: ended");
+ last_run = time(NULL);
+ return;
+}
+
+
+char *ctdl_module_init_rssclient(void) {
+ if (!threading) {
+ syslog(LOG_INFO, "rssclient: using %s", curl_version());
+ CtdlRegisterSessionHook(rssclient_scan, EVT_TIMER, PRIO_AGGR + 300);
+ }
+ return "rssclient";
+}
--- /dev/null
+/*
+ * This module implements server commands related to the display and
+ * manipulation of the "Who's online" list.
+ *
+ * Copyright (c) 1987-2022 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.
+ *
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../control.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../ctdl_module.h"
+
+/* Don't show the names of private rooms unless the viewing
+ * user also knows the rooms.
+ */
+void GenerateRoomDisplay(char *real_room,
+ CitContext *viewed,
+ CitContext *viewer) {
+
+ int ra;
+
+ strcpy(real_room, viewed->room.QRname);
+ if (viewed->room.QRflags & QR_MAILBOX) {
+ strcpy(real_room, &real_room[11]);
+ }
+ if (viewed->room.QRflags & QR_PRIVATE) {
+ CtdlRoomAccess(&viewed->room, &viewer->user, &ra, NULL);
+ if ( (ra & UA_KNOWN) == 0) {
+ strcpy(real_room, " ");
+ }
+ }
+
+ if (viewed->cs_flags & CS_CHAT) {
+ while (strlen(real_room) < 14) {
+ strcat(real_room, " ");
+ }
+ strcpy(&real_room[14], "<chat>");
+ }
+
+}
+
+
+
+/*
+ * display who's online
+ */
+void cmd_rwho(char *argbuf) {
+ struct CitContext *nptr;
+ int nContexts, i;
+ int spoofed = 0;
+ int aide;
+ char room[ROOMNAMELEN];
+ char flags[5];
+
+ /* So that we don't keep the context list locked for a long time
+ * we create a copy of it first
+ */
+ nptr = CtdlGetContextArray(&nContexts) ;
+ if (!nptr)
+ {
+ /* Couldn't malloc so we have to bail but stick to the protocol */
+ cprintf("%d%c \n", LISTING_FOLLOWS, CtdlCheckExpress() );
+ cprintf("000\n");
+ return;
+ }
+
+ aide = ( (CC->user.axlevel >= AxAideU) || (CC->internal_pgm) ) ;
+ cprintf("%d%c \n", LISTING_FOLLOWS, CtdlCheckExpress() );
+
+ for (i=0; i<nContexts; i++) {
+ flags[0] = '\0';
+ spoofed = 0;
+
+ if (!aide && nptr[i].state == CON_SYS)
+ continue;
+
+ if (!aide && nptr[i].kill_me != 0)
+ continue;
+
+ if (nptr[i].cs_flags & CS_POSTING) {
+ strcat(flags, "*");
+ }
+ else {
+ strcat(flags, ".");
+ }
+
+ GenerateRoomDisplay(room, &nptr[i], CC);
+
+ if ((aide) && (spoofed)) {
+ strcat(flags, "+");
+ }
+
+ if ((nptr[i].cs_flags & CS_STEALTH) && (aide)) {
+ strcat(flags, "-");
+ }
+
+ if (((nptr[i].cs_flags&CS_STEALTH)==0) || (aide)) {
+
+ cprintf("%d|%s|%s|%s|%s|%ld|%s|%s|",
+ nptr[i].cs_pid, nptr[i].curr_user, room,
+ nptr[i].cs_host, nptr[i].cs_clientname,
+ (long)(nptr[i].lastidle),
+ nptr[i].lastcmdname, flags
+ );
+
+ cprintf("|"); // no spoofed user names anymore
+ cprintf("|"); // no spoofed room names anymore
+ cprintf("|"); // no spoofed host names anymore
+
+ cprintf("%d\n", nptr[i].logged_in);
+ }
+ }
+
+ /* release our copy of the context list */
+ free(nptr);
+
+ /* Now it's magic time. Before we finish, call any EVT_RWHO hooks
+ * so that external paging modules such as serv_icq can add more
+ * content to the Wholist.
+ */
+ PerformSessionHooks(EVT_RWHO);
+ cprintf("000\n");
+}
+
+
+/*
+ * enter or exit "stealth mode"
+ */
+void cmd_stel(char *cmdbuf)
+{
+ int requested_mode;
+
+ requested_mode = extract_int(cmdbuf,0);
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ if (requested_mode == 1) {
+ CC->cs_flags = CC->cs_flags | CS_STEALTH;
+ PerformSessionHooks(EVT_STEALTH);
+ }
+ if (requested_mode == 0) {
+ CC->cs_flags = CC->cs_flags & ~CS_STEALTH;
+ PerformSessionHooks(EVT_UNSTEALTH);
+ }
+
+ cprintf("%d %d\n", CIT_OK,
+ ((CC->cs_flags & CS_STEALTH) ? 1 : 0) );
+}
+
+
+char *ctdl_module_init_rwho(void) {
+ if (!threading) {
+ CtdlRegisterProtoHook(cmd_rwho, "RWHO", "Display who is online");
+ CtdlRegisterProtoHook(cmd_stel, "STEL", "Enter/exit stealth mode");
+ //CtdlRegisterSessionHook(dead_io_check, EVT_TIMER, PRIO_QUEUE + 50);
+
+ }
+
+ /* return our module name for the log */
+ return "rwho";
+}
--- /dev/null
+// This module is an SMTP and ESMTP server for the Citadel system.
+// It is compliant with all of the following:
+//
+// RFC 821 - Simple Mail Transfer Protocol
+// RFC 876 - Survey of SMTP Implementations
+// RFC 1047 - Duplicate messages and SMTP
+// RFC 1652 - 8 bit MIME
+// RFC 1869 - Extended Simple Mail Transfer Protocol
+// RFC 1870 - SMTP Service Extension for Message Size Declaration
+// RFC 2033 - Local Mail Transfer Protocol
+// RFC 2197 - SMTP Service Extension for Command Pipelining
+// RFC 2476 - Message Submission
+// RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
+// RFC 2554 - SMTP Service Extension for Authentication
+// RFC 2821 - Simple Mail Transfer Protocol
+// RFC 2822 - Internet Message Format
+// RFC 2920 - SMTP Service Extension for Command Pipelining
+//
+// The VRFY and EXPN commands have been removed from this implementation
+// because nobody uses these commands anymore, except for spammers.
+//
+// Copyright (c) 1998-2022 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.
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <termios.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <syslog.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../control.h"
+#include "../../user_ops.h"
+#include "../../room_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../genstamp.h"
+#include "../../domain.h"
+#include "../../clientsocket.h"
+#include "../../locate_host.h"
+#include "../../citadel_dirs.h"
+#include "../../ctdl_module.h"
+
+#include "smtp_util.h"
+
+enum { // Command states for login authentication
+ smtp_command,
+ smtp_user,
+ smtp_password,
+ smtp_plain
+};
+
+enum SMTP_FLAGS {
+ HELO,
+ EHLO,
+ LHLO
+};
+
+
+// Here's where our SMTP session begins its happy day.
+void smtp_greeting(int is_msa) {
+ char message_to_spammer[1024];
+
+ strcpy(CC->cs_clientname, "SMTP session");
+ CC->internal_pgm = 1;
+ CC->cs_flags |= CS_STEALTH;
+ CC->session_specific_data = malloc(sizeof(struct citsmtp));
+ memset(SMTP, 0, sizeof(struct citsmtp));
+ SMTP->is_msa = is_msa;
+ SMTP->Cmd = NewStrBufPlain(NULL, SIZ);
+ SMTP->helo_node = NewStrBuf();
+ SMTP->from = NewStrBufPlain(NULL, SIZ);
+ SMTP->recipients = NewStrBufPlain(NULL, SIZ);
+ SMTP->OneRcpt = NewStrBufPlain(NULL, SIZ);
+ SMTP->preferred_sender_email = NULL;
+ SMTP->preferred_sender_name = NULL;
+
+ // If this config option is set, reject connections from problem
+ // addresses immediately instead of after they execute a RCPT
+ if ( (CtdlGetConfigInt("c_rbl_at_greeting")) && (SMTP->is_msa == 0) ) {
+ if (rbl_check(CC->cs_addr, message_to_spammer)) {
+ if (server_shutting_down)
+ cprintf("421 %s\r\n", message_to_spammer);
+ else
+ cprintf("550 %s\r\n", message_to_spammer);
+ CC->kill_me = KILLME_SPAMMER;
+ /* no need to free_recipients(valid), it's not allocated yet */
+ return;
+ }
+ }
+
+ // Otherwise we're either clean or we check later.
+
+ if (CC->nologin==1) {
+ cprintf("451 Too many connections are already open; please try again later.\r\n");
+ CC->kill_me = KILLME_MAX_SESSIONS_EXCEEDED;
+ // no need to free_recipients(valid), it's not allocated yet
+ return;
+ }
+
+ // Note: the FQDN *must* appear as the first thing after the 220 code.
+ // Some clients (including citmail.c) depend on it being there.
+ cprintf("220 %s ESMTP Citadel server ready.\r\n", CtdlGetConfigStr("c_fqdn"));
+}
+
+
+// SMTPS is just like SMTP, except it goes crypto right away.
+void smtps_greeting(void) {
+ CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
+#ifdef HAVE_OPENSSL
+ if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO; // kill session if no crypto
+#endif
+ smtp_greeting(0);
+}
+
+
+// SMTP MSA port requires authentication.
+void smtp_msa_greeting(void) {
+ smtp_greeting(1);
+}
+
+
+// LMTP is like SMTP but with some extra bonus footage added.
+void lmtp_greeting(void) {
+
+ smtp_greeting(0);
+ SMTP->is_lmtp = 1;
+}
+
+
+// Generic SMTP MTA greeting
+void smtp_mta_greeting(void) {
+ smtp_greeting(0);
+}
+
+
+// We also have an unfiltered LMTP socket that bypasses spam filters.
+void lmtp_unfiltered_greeting(void) {
+ smtp_greeting(0);
+ SMTP->is_lmtp = 1;
+ SMTP->is_unfiltered = 1;
+}
+
+
+// Login greeting common to all auth methods
+void smtp_auth_greeting(void) {
+ cprintf("235 Hello, %s\r\n", CC->user.fullname);
+ syslog(LOG_INFO, "serv_smtp: SMTP authenticated %s", CC->user.fullname);
+ CC->internal_pgm = 0;
+ CC->cs_flags &= ~CS_STEALTH;
+}
+
+
+// Implement HELO and EHLO commands.
+// which_command: 0=HELO, 1=EHLO, 2=LHLO
+void smtp_hello(int which_command) {
+
+ if (StrLength(SMTP->Cmd) >= 6) {
+ FlushStrBuf(SMTP->helo_node);
+ StrBufAppendBuf(SMTP->helo_node, SMTP->Cmd, 5);
+ }
+
+ if ( (which_command != LHLO) && (SMTP->is_lmtp) ) {
+ cprintf("500 Only LHLO is allowed when running LMTP\r\n");
+ return;
+ }
+
+ if ( (which_command == LHLO) && (SMTP->is_lmtp == 0) ) {
+ cprintf("500 LHLO is only allowed when running LMTP\r\n");
+ return;
+ }
+
+ if (which_command == HELO) {
+ cprintf("250 Hello %s (%s [%s])\r\n",
+ ChrPtr(SMTP->helo_node),
+ CC->cs_host,
+ CC->cs_addr
+ );
+ }
+ else {
+ if (which_command == EHLO) {
+ cprintf("250-Hello %s (%s [%s])\r\n",
+ ChrPtr(SMTP->helo_node),
+ CC->cs_host,
+ CC->cs_addr
+ );
+ }
+ else {
+ cprintf("250-Greetings and joyous salutations.\r\n");
+ }
+ cprintf("250-HELP\r\n");
+ cprintf("250-SIZE %ld\r\n", CtdlGetConfigLong("c_maxmsglen"));
+
+#ifdef HAVE_OPENSSL
+ // Offer TLS, but only if TLS is not already active.
+ // Furthermore, only offer TLS when running on
+ // the SMTP-MSA port, not on the SMTP-MTA port, due to
+ // questionable reliability of TLS in certain sending MTA's.
+ if ( (!CC->redirect_ssl) && (SMTP->is_msa) ) {
+ cprintf("250-STARTTLS\r\n");
+ }
+#endif
+
+ cprintf("250-AUTH LOGIN PLAIN\r\n"
+ "250-AUTH=LOGIN PLAIN\r\n"
+ "250 8BITMIME\r\n"
+ );
+ }
+}
+
+
+// Backend function for smtp_webcit_preferences_hack().
+// Look at a message and determine if it's the preferences file.
+void smtp_webcit_preferences_hack_backend(long msgnum, void *userdata) {
+ struct CtdlMessage *msg;
+ char **webcit_conf = (char **) userdata;
+
+ if (*webcit_conf) {
+ return; // already got it
+ }
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) {
+ return;
+ }
+
+ if ( !CM_IsEmpty(msg, eMsgSubject) && (!strcasecmp(msg->cm_fields[eMsgSubject], "__ WebCit Preferences __"))) {
+ // This is it! Change ownership of the message text so it doesn't get freed.
+ *webcit_conf = (char *)msg->cm_fields[eMesageText];
+ msg->cm_fields[eMesageText] = NULL;
+ }
+ CM_Free(msg);
+}
+
+
+// The configuration item for the user's preferred display name for outgoing email is, unfortunately,
+// stored in the account's WebCit configuration. We have to fetch it now.
+void smtp_webcit_preferences_hack(void) {
+ char config_roomname[ROOMNAMELEN];
+ char *webcit_conf = NULL;
+
+ snprintf(config_roomname, sizeof config_roomname, "%010ld.%s", CC->user.usernum, USERCONFIGROOM);
+ if (CtdlGetRoom(&CC->room, config_roomname) != 0) {
+ return;
+ }
+
+ // Find the WebCit configuration message
+ CtdlForEachMessage(MSGS_ALL, 1, NULL, NULL, NULL, smtp_webcit_preferences_hack_backend, (void *)&webcit_conf);
+
+ if (!webcit_conf) {
+ return;
+ }
+
+ // Parse the webcit configuration and attempt to do something useful with it
+ char *str = webcit_conf;
+ char *saveptr = str;
+ char *this_line = NULL;
+ while (this_line = strtok_r(str, "\n", &saveptr), this_line != NULL) {
+ str = NULL;
+ if (!strncasecmp(this_line, "defaultfrom|", 12)) {
+ SMTP->preferred_sender_email = NewStrBufPlain(&this_line[12], -1);
+ }
+ if (!strncasecmp(this_line, "defaultname|", 12)) {
+ SMTP->preferred_sender_name = NewStrBufPlain(&this_line[12], -1);
+ }
+ if ((!strncasecmp(this_line, "defaultname|", 12)) && (SMTP->preferred_sender_name == NULL)) {
+ SMTP->preferred_sender_name = NewStrBufPlain(&this_line[12], -1);
+ }
+
+ }
+ free(webcit_conf);
+}
+
+
+// Implement HELP command.
+void smtp_help(void) {
+ cprintf("214 RTFM http://www.ietf.org/rfc/rfc2821.txt\r\n");
+}
+
+
+void smtp_get_user(int offset) {
+ char buf[SIZ];
+
+ StrBuf *UserName = NewStrBufDup(SMTP->Cmd);
+ StrBufCutLeft(UserName, offset);
+ StrBufDecodeBase64(UserName);
+
+ if (CtdlLoginExistingUser(ChrPtr(UserName)) == login_ok) {
+ size_t len = CtdlEncodeBase64(buf, "Password:", 9, 0);
+
+ if (buf[len - 1] == '\n') {
+ buf[len - 1] = '\0';
+ }
+ cprintf("334 %s\r\n", buf);
+ SMTP->command_state = smtp_password;
+ }
+ else {
+ cprintf("500 No such user.\r\n");
+ SMTP->command_state = smtp_command;
+ }
+ FreeStrBuf(&UserName);
+}
+
+
+void smtp_get_pass(void) {
+ char password[SIZ];
+
+ memset(password, 0, sizeof(password));
+ StrBufDecodeBase64(SMTP->Cmd);
+ syslog(LOG_DEBUG, "serv_smtp: trying <%s>", password);
+ if (CtdlTryPassword(SKEY(SMTP->Cmd)) == pass_ok) {
+ smtp_auth_greeting();
+ }
+ else {
+ cprintf("535 Authentication failed.\r\n");
+ }
+ SMTP->command_state = smtp_command;
+}
+
+
+// Back end for PLAIN auth method (either inline or multistate)
+void smtp_try_plain(void) {
+ const char*decoded_authstring;
+ char ident[256] = "";
+ char user[256] = "";
+ char pass[256] = "";
+ int result;
+
+ long decoded_len;
+ long len = 0;
+ long plen = 0;
+
+ memset(pass, 0, sizeof(pass));
+ decoded_len = StrBufDecodeBase64(SMTP->Cmd);
+
+ if (decoded_len > 0) {
+ decoded_authstring = ChrPtr(SMTP->Cmd);
+
+ len = safestrncpy(ident, decoded_authstring, sizeof ident);
+
+ decoded_len -= len - 1;
+ decoded_authstring += len + 1;
+
+ if (decoded_len > 0) {
+ len = safestrncpy(user, decoded_authstring, sizeof user);
+
+ decoded_authstring += len + 1;
+ decoded_len -= len - 1;
+ }
+
+ if (decoded_len > 0) {
+ plen = safestrncpy(pass, decoded_authstring, sizeof pass);
+
+ if (plen < 0)
+ plen = sizeof(pass) - 1;
+ }
+ }
+
+ SMTP->command_state = smtp_command;
+
+ if (!IsEmptyStr(ident)) {
+ result = CtdlLoginExistingUser(ident);
+ }
+ else {
+ result = CtdlLoginExistingUser(user);
+ }
+
+ if (result == login_ok) {
+ if (CtdlTryPassword(pass, plen) == pass_ok) {
+ smtp_webcit_preferences_hack();
+ smtp_auth_greeting();
+ return;
+ }
+ }
+ cprintf("504 Authentication failed.\r\n");
+}
+
+
+// Attempt to perform authenticated SMTP
+void smtp_auth(void) {
+ char username_prompt[64];
+ char method[64];
+ char encoded_authstring[1024];
+
+ if (CC->logged_in) {
+ cprintf("504 Already logged in.\r\n");
+ return;
+ }
+
+ if (StrLength(SMTP->Cmd) < 6) {
+ cprintf("501 Syntax error\r\n");
+ return;
+ }
+
+ extract_token(method, ChrPtr(SMTP->Cmd) + 5, 0, ' ', sizeof method);
+
+ if (!strncasecmp(method, "login", 5) ) {
+ if (StrLength(SMTP->Cmd) >= 12) {
+ syslog(LOG_DEBUG, "serv_smtp: username <%s> supplied inline", ChrPtr(SMTP->Cmd)+11);
+ smtp_get_user(11);
+ }
+ else {
+ size_t len = CtdlEncodeBase64(username_prompt, "Username:", 9, 0);
+ if (username_prompt[len - 1] == '\n') {
+ username_prompt[len - 1] = '\0';
+ }
+ cprintf("334 %s\r\n", username_prompt);
+ SMTP->command_state = smtp_user;
+ }
+ return;
+ }
+
+ if (!strncasecmp(method, "plain", 5) ) {
+ long len;
+ if (num_tokens(ChrPtr(SMTP->Cmd) + 5, ' ') < 2) {
+ cprintf("334 \r\n");
+ SMTP->command_state = smtp_plain;
+ return;
+ }
+
+ len = extract_token(encoded_authstring,
+ ChrPtr(SMTP->Cmd) + 5,
+ 1, ' ',
+ sizeof encoded_authstring);
+ StrBufPlain(SMTP->Cmd, encoded_authstring, len);
+ smtp_try_plain();
+ return;
+ }
+
+ cprintf("504 Unknown authentication method.\r\n");
+ return;
+}
+
+
+// Implements the RSET (reset state) command.
+// Currently this just zeroes out the state buffer. If pointers to data
+// allocated with malloc() are ever placed in the state buffer, we have to
+// be sure to free() them first!
+//
+// Set do_response to nonzero to output the SMTP RSET response code.
+void smtp_rset(int do_response) {
+ FlushStrBuf(SMTP->Cmd);
+ FlushStrBuf(SMTP->helo_node);
+ FlushStrBuf(SMTP->from);
+ FlushStrBuf(SMTP->recipients);
+ FlushStrBuf(SMTP->OneRcpt);
+
+ SMTP->command_state = 0;
+ SMTP->number_of_recipients = 0;
+ SMTP->delivery_mode = 0;
+ SMTP->message_originated_locally = 0;
+ SMTP->is_msa = 0;
+ // is_lmtp and is_unfiltered should not be cleared.
+
+ if (do_response) {
+ cprintf("250 Zap!\r\n");
+ }
+}
+
+
+// Clear out the portions of the state buffer that need to be cleared out
+// after the DATA command finishes.
+void smtp_data_clear(void) {
+ FlushStrBuf(SMTP->from);
+ FlushStrBuf(SMTP->recipients);
+ FlushStrBuf(SMTP->OneRcpt);
+ SMTP->number_of_recipients = 0;
+ SMTP->delivery_mode = 0;
+ SMTP->message_originated_locally = 0;
+}
+
+
+// Implements the "MAIL FROM:" command
+void smtp_mail(void) {
+ char user[SIZ];
+ char node[SIZ];
+ char name[SIZ];
+
+ if (StrLength(SMTP->from) > 0) {
+ cprintf("503 Only one sender permitted\r\n");
+ return;
+ }
+
+ if (StrLength(SMTP->Cmd) < 6) {
+ cprintf("501 Syntax error\r\n");
+ return;
+ }
+
+ if (strncasecmp(ChrPtr(SMTP->Cmd) + 5, "From:", 5)) {
+ cprintf("501 Syntax error\r\n");
+ return;
+ }
+
+ StrBufAppendBuf(SMTP->from, SMTP->Cmd, 5);
+ StrBufTrim(SMTP->from);
+ if (strchr(ChrPtr(SMTP->from), '<') != NULL) {
+ StrBufStripAllBut(SMTP->from, '<', '>');
+ }
+
+ // We used to reject empty sender names, until it was brought to our
+ // attention that RFC1123 5.2.9 requires that this be allowed. So now
+ // we allow it, but replace the empty string with a fake
+ // address so we don't have to contend with the empty string causing
+ // other code to fail when it's expecting something there.
+ if (StrLength(SMTP->from) == 0) {
+ StrBufPlain(SMTP->from, HKEY("someone@example.com"));
+ }
+
+ // If this SMTP connection is from a logged-in user, force the 'from'
+ // to be the user's Internet e-mail address as Citadel knows it.
+ if (CC->logged_in) {
+ StrBufPlain(SMTP->from, CC->cs_inet_email, -1);
+ cprintf("250 Sender ok <%s>\r\n", ChrPtr(SMTP->from));
+ SMTP->message_originated_locally = 1;
+ return;
+ }
+
+ else if (SMTP->is_lmtp) {
+ // Bypass forgery checking for LMTP
+ }
+
+ // Otherwise, make sure outsiders aren't trying to forge mail from
+ // this system (unless, of course, c_allow_spoofing is enabled)
+ else if (CtdlGetConfigInt("c_allow_spoofing") == 0) {
+ process_rfc822_addr(ChrPtr(SMTP->from), user, node, name);
+ syslog(LOG_DEBUG, "serv_smtp: claimed envelope sender is '%s' == '%s' @ '%s' ('%s')",
+ ChrPtr(SMTP->from), user, node, name
+ );
+ if (CtdlHostAlias(node) != hostalias_nomatch) {
+ cprintf("550 You must log in to send mail from %s\r\n", node);
+ FlushStrBuf(SMTP->from);
+ syslog(LOG_DEBUG, "serv_smtp: rejecting unauthenticated mail from %s", node);
+ return;
+ }
+ }
+
+ cprintf("250 Sender ok\r\n");
+}
+
+
+// Implements the "RCPT To:" command
+void smtp_rcpt(void) {
+ char message_to_spammer[SIZ];
+ struct recptypes *valid = NULL;
+
+ if (StrLength(SMTP->from) == 0) {
+ cprintf("503 Need MAIL before RCPT\r\n");
+ return;
+ }
+
+ if (StrLength(SMTP->Cmd) < 6) {
+ cprintf("501 Syntax error\r\n");
+ return;
+ }
+
+ if (strncasecmp(ChrPtr(SMTP->Cmd) + 5, "To:", 3)) {
+ cprintf("501 Syntax error\r\n");
+ return;
+ }
+
+ if ( (SMTP->is_msa) && (!CC->logged_in) ) {
+ cprintf("550 You must log in to send mail on this port.\r\n");
+ FlushStrBuf(SMTP->from);
+ return;
+ }
+
+ FlushStrBuf(SMTP->OneRcpt);
+ StrBufAppendBuf(SMTP->OneRcpt, SMTP->Cmd, 8);
+ StrBufTrim(SMTP->OneRcpt);
+ StrBufStripAllBut(SMTP->OneRcpt, '<', '>');
+
+ if ( (StrLength(SMTP->OneRcpt) + StrLength(SMTP->recipients)) >= SIZ) {
+ cprintf("452 Too many recipients\r\n");
+ return;
+ }
+
+ // RBL check
+ if (
+ (!CC->logged_in) // Don't RBL authenticated users
+ && (!SMTP->is_lmtp) // Don't RBL LMTP clients
+ && (CtdlGetConfigInt("c_rbl_at_greeting") == 0) // Don't RBL if we did it at connection time
+ && (rbl_check(CC->cs_addr, message_to_spammer))
+ ) {
+ cprintf("550 %s\r\n", message_to_spammer);
+ return; // no need to free_recipients(valid)
+ } // because it hasn't been allocated yet
+
+ // This is a *preliminary* call to validate_recipients() to evaluate one recipient.
+ valid = validate_recipients(
+ (char *)ChrPtr(SMTP->OneRcpt),
+ smtp_get_Recipients(),
+ (SMTP->is_lmtp)? POST_LMTP: (CC->logged_in)? POST_LOGGED_IN: POST_EXTERNAL
+ );
+
+ // Any type of error thrown by validate_recipients() will make the SMTP transaction fail at this point.
+ if (valid->num_error != 0) {
+ cprintf("550 %s\r\n", valid->errormsg);
+ free_recipients(valid);
+ return;
+ }
+
+ if (
+ (valid->num_internet > 0) // If it's outbound Internet mail...
+ && (CC->logged_in) // ...and we're a logged-in user...
+ && (CtdlCheckInternetMailPermission(&CC->user)==0) // ...who does not have Internet mail rights...
+ ) {
+ cprintf("551 <%s> - you do not have permission to send Internet mail\r\n", ChrPtr(SMTP->OneRcpt));
+ free_recipients(valid);
+ return;
+ }
+
+ if (
+ (valid->num_internet > 0) // If it's outbound Internet mail...
+ && (SMTP->message_originated_locally == 0) // ...and also inbound Internet mail...
+ && (SMTP->is_lmtp == 0) /// ...and didn't arrive via LMTP...
+ ) {
+ cprintf("551 <%s> - relaying denied\r\n", ChrPtr(SMTP->OneRcpt));
+ free_recipients(valid);
+ return;
+ }
+
+ if (
+ (valid->num_room > 0) // If it's mail to a room (mailing list)...
+ && (SMTP->message_originated_locally == 0) // ...and also inbound Internet mail...
+ && (is_email_subscribed_to_list((char *)ChrPtr(SMTP->from), valid->recp_room) == 0) // ...and not a subscriber
+ ) {
+ cprintf("551 <%s> - The message is not from a list member\r\n", ChrPtr(SMTP->OneRcpt));
+ free_recipients(valid);
+ return;
+ }
+
+ cprintf("250 RCPT ok <%s>\r\n", ChrPtr(SMTP->OneRcpt));
+ if (StrLength(SMTP->recipients) > 0) {
+ StrBufAppendBufPlain(SMTP->recipients, HKEY(","), 0);
+ }
+ StrBufAppendBuf(SMTP->recipients, SMTP->OneRcpt, 0);
+ SMTP->number_of_recipients ++;
+ if (valid != NULL) {
+ free_recipients(valid);
+ }
+}
+
+
+// Implements the DATA command
+void smtp_data(void) {
+ StrBuf *body;
+ StrBuf *defbody;
+ struct CtdlMessage *msg = NULL;
+ long msgnum = (-1L);
+ char nowstamp[SIZ];
+ struct recptypes *valid;
+ int scan_errors;
+ int i;
+
+ if (StrLength(SMTP->from) == 0) {
+ cprintf("503 Need MAIL command first.\r\n");
+ return;
+ }
+
+ if (SMTP->number_of_recipients < 1) {
+ cprintf("503 Need RCPT command first.\r\n");
+ return;
+ }
+
+ cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
+
+ datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
+ defbody = NewStrBufPlain(NULL, SIZ);
+
+ if (defbody != NULL) {
+ if (SMTP->is_lmtp && (CC->cs_UDSclientUID != -1)) {
+ StrBufPrintf(
+ defbody,
+ "Received: from %s (Citadel from userid %ld)\n"
+ " by %s; %s\n",
+ ChrPtr(SMTP->helo_node),
+ (long int) CC->cs_UDSclientUID,
+ CtdlGetConfigStr("c_fqdn"),
+ nowstamp);
+ }
+ else {
+ StrBufPrintf(
+ defbody,
+ "Received: from %s (%s [%s])\n"
+ " by %s; %s\n",
+ ChrPtr(SMTP->helo_node),
+ CC->cs_host,
+ CC->cs_addr,
+ CtdlGetConfigStr("c_fqdn"),
+ nowstamp);
+ }
+ }
+ body = CtdlReadMessageBodyBuf(HKEY("."), CtdlGetConfigLong("c_maxmsglen"), defbody, 1);
+ FreeStrBuf(&defbody);
+ if (body == NULL) {
+ cprintf("550 Unable to save message: internal error.\r\n");
+ return;
+ }
+
+ syslog(LOG_DEBUG, "serv_smtp: converting message...");
+ msg = convert_internet_message_buf(&body);
+
+ // If the user is locally authenticated, FORCE the From: header to
+ // show up as the real sender. Yes, this violates the RFC standard,
+ // but IT MAKES SENSE. If you prefer strict RFC adherence over
+ // common sense, you can disable this in the configuration.
+ //
+ // We also set the "message room name" ('O' field) to MAILROOM
+ // (which is Mail> on most systems) to prevent it from getting set
+ // to something ugly like "0000058008.Sent Items>" when the message
+ // is read with a Citadel client.
+
+ if ( (CC->logged_in) && (CtdlGetConfigInt("c_rfc822_strict_from") != CFG_SMTP_FROM_NOFILTER) ) {
+ int validemail = 0;
+
+ if (!CM_IsEmpty(msg, erFc822Addr) &&
+ ((CtdlGetConfigInt("c_rfc822_strict_from") == CFG_SMTP_FROM_CORRECT) ||
+ (CtdlGetConfigInt("c_rfc822_strict_from") == CFG_SMTP_FROM_REJECT) ) )
+ {
+ if (!IsEmptyStr(CC->cs_inet_email))
+ validemail = strcmp(CC->cs_inet_email, msg->cm_fields[erFc822Addr]) == 0;
+ if ((!validemail) &&
+ (!IsEmptyStr(CC->cs_inet_other_emails)))
+ {
+ int num_secondary_emails = 0;
+ int i;
+ num_secondary_emails = num_tokens(CC->cs_inet_other_emails, '|');
+ for (i=0; i < num_secondary_emails && !validemail; ++i) {
+ char buf[256];
+ extract_token(buf, CC->cs_inet_other_emails,i,'|',sizeof CC->cs_inet_other_emails);
+ validemail = strcmp(buf, msg->cm_fields[erFc822Addr]) == 0;
+ }
+ }
+ }
+
+ if (!validemail && (CtdlGetConfigInt("c_rfc822_strict_from") == CFG_SMTP_FROM_REJECT)) {
+ syslog(LOG_ERR, "serv_smtp: invalid sender '%s' - rejecting this message", msg->cm_fields[erFc822Addr]);
+ cprintf("550 Invalid sender '%s' - rejecting this message.\r\n", msg->cm_fields[erFc822Addr]);
+ return;
+ }
+
+ CM_SetField(msg, eOriginalRoom, HKEY(MAILROOM));
+ if (SMTP->preferred_sender_name != NULL)
+ CM_SetField(msg, eAuthor, SKEY(SMTP->preferred_sender_name));
+ else
+ CM_SetField(msg, eAuthor, CC->user.fullname, strlen(CC->user.fullname));
+
+ if (!validemail) {
+ if (SMTP->preferred_sender_email != NULL) {
+ CM_SetField(msg, erFc822Addr, SKEY(SMTP->preferred_sender_email));
+ }
+ else {
+ CM_SetField(msg, erFc822Addr, CC->cs_inet_email, strlen(CC->cs_inet_email));
+ }
+ }
+ }
+
+ // Set the "envelope from" address
+ CM_SetField(msg, eMessagePath, SKEY(SMTP->from));
+
+ // Set the "envelope to" address
+ CM_SetField(msg, eenVelopeTo, SKEY(SMTP->recipients));
+
+ // Submit the message into the Citadel system.
+ valid = validate_recipients(
+ (char *)ChrPtr(SMTP->recipients),
+ smtp_get_Recipients(),
+ (SMTP->is_lmtp)? POST_LMTP: (CC->logged_in)? POST_LOGGED_IN: POST_EXTERNAL
+ );
+
+ // If there are modules that want to scan this message before final
+ // submission (such as virus checkers or spam filters), call them now
+ // and give them an opportunity to reject the message.
+ if (SMTP->is_unfiltered) {
+ scan_errors = 0;
+ }
+ else {
+ scan_errors = PerformMessageHooks(msg, valid, EVT_SMTPSCAN);
+ }
+
+ if (scan_errors > 0) { // We don't want this message!
+
+ if (CM_IsEmpty(msg, eErrorMsg)) {
+ CM_SetField(msg, eErrorMsg, HKEY("Message rejected by filter"));
+ }
+
+ StrBufPrintf(SMTP->OneRcpt, "550 %s\r\n", msg->cm_fields[eErrorMsg]);
+ }
+
+ else { // Ok, we'll accept this message.
+ msgnum = CtdlSubmitMsg(msg, valid, "");
+ if (msgnum > 0L) {
+ StrBufPrintf(SMTP->OneRcpt, "250 Message accepted.\r\n");
+ }
+ else {
+ StrBufPrintf(SMTP->OneRcpt, "550 Internal delivery error\r\n");
+ }
+ }
+
+ // For SMTP and ESMTP, just print the result message. For LMTP, we
+ // have to print one result message for each recipient. Since there
+ // is nothing in Citadel which would cause different recipients to
+ // have different results, we can get away with just spitting out the
+ // same message once for each recipient.
+ if (SMTP->is_lmtp) {
+ for (i=0; i<SMTP->number_of_recipients; ++i) {
+ cputbuf(SMTP->OneRcpt);
+ }
+ }
+ else {
+ cputbuf(SMTP->OneRcpt);
+ }
+
+ // Write something to the syslog(which may or may not be where the
+ // rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
+ syslog((LOG_MAIL | LOG_INFO),
+ "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
+ msgnum,
+ ChrPtr(SMTP->from),
+ SMTP->number_of_recipients,
+ CC->cs_host,
+ CC->cs_addr,
+ ChrPtr(SMTP->OneRcpt)
+ );
+
+ // Clean up
+ CM_Free(msg);
+ free_recipients(valid);
+ smtp_data_clear(); // clear out the buffers now
+}
+
+
+// Implements the STARTTLS command
+void smtp_starttls(void) {
+ char ok_response[SIZ];
+ char nosup_response[SIZ];
+ char error_response[SIZ];
+
+ sprintf(ok_response, "220 Begin TLS negotiation now\r\n");
+ sprintf(nosup_response, "554 TLS not supported here\r\n");
+ sprintf(error_response, "554 Internal error\r\n");
+ CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
+ smtp_rset(0);
+}
+
+
+// Implements the NOOP (NO OPeration) command
+void smtp_noop(void) {
+ cprintf("250 NOOP\r\n");
+}
+
+
+// Implements the QUIT command
+void smtp_quit(void) {
+ cprintf("221 Goodbye...\r\n");
+ CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
+}
+
+
+// Main command loop for SMTP server sessions.
+void smtp_command_loop(void) {
+ static const ConstStr AuthPlainStr = {HKEY("AUTH PLAIN")};
+
+ if (SMTP == NULL) {
+ syslog(LOG_ERR, "serv_smtp: Session SMTP data is null. WTF? We will crash now.");
+ abort();
+ }
+
+ time(&CC->lastcmd);
+ if (CtdlClientGetLine(SMTP->Cmd) < 1) {
+ syslog(LOG_INFO, "SMTP: client disconnected: ending session.");
+ CC->kill_me = KILLME_CLIENT_DISCONNECTED;
+ return;
+ }
+
+ if (SMTP->command_state == smtp_user) {
+ if (!strncmp(ChrPtr(SMTP->Cmd), AuthPlainStr.Key, AuthPlainStr.len)) {
+ smtp_try_plain();
+ }
+ else {
+ smtp_get_user(0);
+ }
+ return;
+ }
+
+ else if (SMTP->command_state == smtp_password) {
+ smtp_get_pass();
+ return;
+ }
+
+ else if (SMTP->command_state == smtp_plain) {
+ smtp_try_plain();
+ return;
+ }
+
+ syslog(LOG_DEBUG, "serv_smtp: client sent command <%s>", ChrPtr(SMTP->Cmd));
+
+ if (!strncasecmp(ChrPtr(SMTP->Cmd), "NOOP", 4)) {
+ smtp_noop();
+ return;
+ }
+
+ if (!strncasecmp(ChrPtr(SMTP->Cmd), "QUIT", 4)) {
+ smtp_quit();
+ return;
+ }
+
+ if (!strncasecmp(ChrPtr(SMTP->Cmd), "HELO", 4)) {
+ smtp_hello(HELO);
+ return;
+ }
+
+ if (!strncasecmp(ChrPtr(SMTP->Cmd), "EHLO", 4)) {
+ smtp_hello(EHLO);
+ return;
+ }
+
+ if (!strncasecmp(ChrPtr(SMTP->Cmd), "LHLO", 4)) {
+ smtp_hello(LHLO);
+ return;
+ }
+
+ if (!strncasecmp(ChrPtr(SMTP->Cmd), "RSET", 4)) {
+ smtp_rset(1);
+ return;
+ }
+
+ if (!strncasecmp(ChrPtr(SMTP->Cmd), "AUTH", 4)) {
+ smtp_auth();
+ return;
+ }
+
+ if (!strncasecmp(ChrPtr(SMTP->Cmd), "DATA", 4)) {
+ smtp_data();
+ return;
+ }
+
+ if (!strncasecmp(ChrPtr(SMTP->Cmd), "HELP", 4)) {
+ smtp_help();
+ return;
+ }
+
+ if (!strncasecmp(ChrPtr(SMTP->Cmd), "MAIL", 4)) {
+ smtp_mail();
+ return;
+ }
+
+ if (!strncasecmp(ChrPtr(SMTP->Cmd), "RCPT", 4)) {
+ smtp_rcpt();
+ return;
+ }
+#ifdef HAVE_OPENSSL
+ if (!strncasecmp(ChrPtr(SMTP->Cmd), "STARTTLS", 8)) {
+ smtp_starttls();
+ return;
+ }
+#endif
+
+ cprintf("502 I'm afraid I can't do that.\r\n");
+}
+
+
+/*****************************************************************************/
+/* MODULE INITIALIZATION STUFF */
+/*****************************************************************************/
+/*
+ * This cleanup function blows away the temporary memory used by
+ * the SMTP server.
+ */
+void smtp_cleanup_function(void)
+{
+ /* Don't do this stuff if this is not an SMTP session! */
+ if (CC->h_command_function != smtp_command_loop) return;
+
+ syslog(LOG_DEBUG, "Performing SMTP cleanup hook");
+
+ FreeStrBuf(&SMTP->Cmd);
+ FreeStrBuf(&SMTP->helo_node);
+ FreeStrBuf(&SMTP->from);
+ FreeStrBuf(&SMTP->recipients);
+ FreeStrBuf(&SMTP->OneRcpt);
+ FreeStrBuf(&SMTP->preferred_sender_email);
+ FreeStrBuf(&SMTP->preferred_sender_name);
+
+ free(SMTP);
+}
+
+const char *CitadelServiceSMTP_MTA="SMTP-MTA";
+const char *CitadelServiceSMTPS_MTA="SMTPs-MTA";
+const char *CitadelServiceSMTP_MSA="SMTP-MSA";
+const char *CitadelServiceSMTP_LMTP="LMTP";
+const char *CitadelServiceSMTP_LMTP_UNF="LMTP-UnF";
+
+
+char *ctdl_module_init_smtp(void) {
+ if (!threading) {
+ CtdlRegisterServiceHook(CtdlGetConfigInt("c_smtp_port"), /* SMTP MTA */
+ NULL,
+ smtp_mta_greeting,
+ smtp_command_loop,
+ NULL,
+ CitadelServiceSMTP_MTA);
+
+#ifdef HAVE_OPENSSL
+ CtdlRegisterServiceHook(CtdlGetConfigInt("c_smtps_port"), /* SMTPS MTA */
+ NULL,
+ smtps_greeting,
+ smtp_command_loop,
+ NULL,
+ CitadelServiceSMTPS_MTA);
+#endif
+
+ CtdlRegisterServiceHook(CtdlGetConfigInt("c_msa_port"), /* SMTP MSA */
+ NULL,
+ smtp_msa_greeting,
+ smtp_command_loop,
+ NULL,
+ CitadelServiceSMTP_MSA);
+
+ CtdlRegisterServiceHook(0, /* local LMTP */
+ file_lmtp_socket,
+ lmtp_greeting,
+ smtp_command_loop,
+ NULL,
+ CitadelServiceSMTP_LMTP);
+
+ CtdlRegisterServiceHook(0, /* local LMTP */
+ file_lmtp_unfiltered_socket,
+ lmtp_unfiltered_greeting,
+ smtp_command_loop,
+ NULL,
+ CitadelServiceSMTP_LMTP_UNF);
+
+ CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP, PRIO_STOP + 250);
+ }
+
+ /* return our module name for the log */
+ return "smtp";
+}
--- /dev/null
+// Transmit outbound SMTP mail to the big wide world of the Internet
+//
+// This is the new, exciting, clever version that makes libcurl do all the work :)
+//
+// Copyright (c) 1997-2022 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 as published
+// by the Free Software Foundation; either version 3 of the License, or
+// (at your option) any later version.
+//
+// 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.
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <time.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <libcitadel.h>
+#include <curl/curl.h>
+#include "../../sysconfig.h"
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../ctdl_module.h"
+#include "../../clientsocket.h"
+#include "../../msgbase.h"
+#include "../../domain.h"
+#include "../../internet_addressing.h"
+#include "../../citadel_dirs.h"
+#include "../smtp/smtp_util.h"
+
+struct smtpmsgsrc { // Data passed in and out of libcurl for message upload
+ StrBuf *TheMessage;
+ int bytes_total;
+ int bytes_sent;
+};
+
+static int doing_smtpclient = 0;
+long *smtpq = NULL; // array of msgnums containing queue instructions
+int smtpq_count = 0; // number of queue messages in smtpq
+int smtpq_alloc = 0; // current allocation size for smtpq
+
+
+// Initialize the SMTP outbound queue
+void smtp_init_spoolout(void) {
+ struct ctdlroom qrbuf;
+
+ // Create the room. This will silently fail if the room already
+ // exists, and that's perfectly ok, because we want it to exist.
+ CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_QUEUE);
+
+ // Make sure it's set to be a "system room" so it doesn't show up
+ // in the <K>nown rooms list for administrators.
+ if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
+ qrbuf.QRflags2 |= QR2_SYSTEM;
+ CtdlPutRoomLock(&qrbuf);
+ }
+}
+
+
+// For internet mail, generate delivery instructions.
+// Yes, this is recursive. Deal with it. Infinite recursion does
+// not happen because the delivery instructions message does not
+// contain a recipient.
+int smtp_aftersave(struct CtdlMessage *msg, struct recptypes *recps) {
+ if ((recps != NULL) && (recps->num_internet > 0)) {
+ struct CtdlMessage *imsg = NULL;
+ char recipient[SIZ];
+ StrBuf *SpoolMsg = NewStrBuf();
+ long nTokens;
+ int i;
+
+ syslog(LOG_DEBUG, "smtpclient: generating delivery instructions");
+
+ StrBufPrintf(SpoolMsg,
+ "Content-type: " SPOOLMIME "\n"
+ "\n"
+ "msgid|%s\n"
+ "submitted|%ld\n" "bounceto|%s\n", msg->cm_fields[eVltMsgNum], (long) time(NULL), recps->bounce_to);
+
+ if (recps->envelope_from != NULL) {
+ StrBufAppendBufPlain(SpoolMsg, HKEY("envelope_from|"), 0);
+ StrBufAppendBufPlain(SpoolMsg, recps->envelope_from, -1, 0);
+ StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
+ }
+ if (recps->sending_room != NULL) {
+ StrBufAppendBufPlain(SpoolMsg, HKEY("source_room|"), 0);
+ StrBufAppendBufPlain(SpoolMsg, recps->sending_room, -1, 0);
+ StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
+ }
+
+ nTokens = num_tokens(recps->recp_internet, '|');
+ for (i = 0; i < nTokens; i++) {
+ long len;
+ len = extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
+ if (len > 0) {
+ StrBufAppendBufPlain(SpoolMsg, HKEY("remote|"), 0);
+ StrBufAppendBufPlain(SpoolMsg, recipient, len, 0);
+ StrBufAppendBufPlain(SpoolMsg, HKEY("|0||\n"), 0);
+ }
+ }
+
+ imsg = malloc(sizeof(struct CtdlMessage));
+ memset(imsg, 0, sizeof(struct CtdlMessage));
+ imsg->cm_magic = CTDLMESSAGE_MAGIC;
+ imsg->cm_anon_type = MES_NORMAL;
+ imsg->cm_format_type = FMT_RFC822;
+ CM_SetField(imsg, eMsgSubject, HKEY("QMSG"));
+ CM_SetField(imsg, eAuthor, HKEY("Citadel"));
+ CM_SetField(imsg, eJournal, HKEY("do not journal"));
+ CM_SetAsFieldSB(imsg, eMesageText, &SpoolMsg);
+ CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
+ CM_Free(imsg);
+ }
+ return 0;
+}
+
+
+// Callback for smtp_attempt_delivery() to supply libcurl with upload data.
+static size_t upload_source(void *ptr, size_t size, size_t nmemb, void *userp) {
+ struct smtpmsgsrc *s = (struct smtpmsgsrc *) userp;
+ int sendbytes = 0;
+ const char *send_this = NULL;
+
+ sendbytes = (size * nmemb);
+
+ if (s->bytes_sent >= s->bytes_total) {
+ return (0); // no data remaining; we are done
+ }
+
+ if (sendbytes > (s->bytes_total - s->bytes_sent)) {
+ sendbytes = s->bytes_total - s->bytes_sent; // can't send more than we have
+ }
+
+ send_this = ChrPtr(s->TheMessage);
+ send_this += s->bytes_sent; // start where we last left off
+
+ memcpy(ptr, send_this, sendbytes);
+ s->bytes_sent += sendbytes;
+ return(sendbytes); // return the number of bytes _actually_ copied
+}
+
+
+// The libcurl API doesn't provide a way to capture the actual SMTP result message returned
+// by the remote server. This is an ugly way to extract it, by capturing debug data from
+// the library and filtering on the lines we want.
+int ctdl_libcurl_smtp_debug_callback(CURL *handle, curl_infotype type, char *data, size_t size, void *userptr) {
+ if (type != CURLINFO_HEADER_IN)
+ return 0;
+ if (!userptr)
+ return 0;
+ char *debugbuf = (char *) userptr;
+
+ int len = strlen(debugbuf);
+ if (len + size > SIZ)
+ return 0;
+
+ memcpy(&debugbuf[len], data, size);
+ debugbuf[len + size] = 0;
+ return 0;
+}
+
+
+// Go through the debug output of an SMTP transaction, and boil it down to just the final success or error response message.
+void trim_response(long response_code, char *response) {
+ if ((response_code < 100) || (response_code > 999) || (IsEmptyStr(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
+ *p = ' ';
+ }
+ }
+
+ char response_code_str[4];
+ snprintf(response_code_str, sizeof response_code_str, "%ld", response_code);
+ char *respstart = strstr(response, response_code_str);
+ if (respstart == NULL) { // If we have a response code but no response text,
+ strcpy(response, smtpstatus(response_code)); // use one of our canned messages.
+ return;
+ }
+ strcpy(response, respstart);
+
+ p = strstr(response, "\n");
+ if (p != NULL) {
+ *p = 0;
+ }
+}
+
+
+// Attempt a delivery to one recipient.
+// Returns a three-digit SMTP status code.
+int smtp_attempt_delivery(long msgid, char *recp, char *envelope_from, char *source_room, char *response) {
+ struct smtpmsgsrc s;
+ char *fromaddr = NULL;
+ CURL *curl;
+ CURLcode res = CURLE_OK;
+ struct curl_slist *recipients = NULL;
+ long response_code = 421;
+ int num_mx = 0;
+ char mxes[SIZ];
+ char user[1024];
+ char node[1024];
+ char name[1024];
+ char try_this_mx[256];
+ char smtp_url[512];
+ int i;
+
+ syslog(LOG_DEBUG, "smtpclient: smtp_attempt_delivery(%ld, %s)", msgid, recp);
+
+ 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);
+ }
+
+ CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+ 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
+ );
+ }
+ 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;
+ response_code = 421;
+ // 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
+
+ curl = curl_easy_init();
+ if (curl) {
+ response[0] = 0;
+
+ if (!IsEmptyStr(envelope_from)) {
+ curl_easy_setopt(curl, CURLOPT_MAIL_FROM, envelope_from);
+ }
+ else {
+ curl_easy_setopt(curl, CURLOPT_MAIL_FROM, fromaddr);
+ }
+
+ recipients = curl_slist_append(recipients, recp);
+ curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);
+ curl_easy_setopt(curl, CURLOPT_READFUNCTION, upload_source);
+ curl_easy_setopt(curl, CURLOPT_READDATA, &s);
+ curl_easy_setopt(curl, CURLOPT_UPLOAD, 1); // tell libcurl we are uploading
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT, 20L); // Time out after 20 seconds
+ if (CtdlGetConfigInt("c_smtpclient_disable_starttls") == 0) {
+ curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_TRY); // Attempt STARTTLS if offered
+ }
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
+ curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, ctdl_libcurl_smtp_debug_callback);
+ curl_easy_setopt(curl, CURLOPT_DEBUGDATA, (void *) response);
+ curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+
+ // Construct an SMTP URL in the form of:
+ // smtp[s]://target_host/source_host
+ // This looks weird but libcurl uses that last part to set our name for EHLO or HELO.
+ // We check for "smtp://" and "smtps://" because the admin may have put those prefixes in a smart-host entry.
+ // If there is no prefix we add "smtp://"
+ extract_token(try_this_mx, mxes, i, '|', (sizeof try_this_mx - 7));
+ snprintf(smtp_url, sizeof smtp_url,
+ "%s%s/%s",
+ (((!strncasecmp(try_this_mx, HKEY("smtp://")))
+ || (!strncasecmp(try_this_mx, HKEY("smtps://")))) ? "" : "smtp://"),
+ try_this_mx, CtdlGetConfigStr("c_fqdn")
+ );
+ curl_easy_setopt(curl, CURLOPT_URL, smtp_url);
+ syslog(LOG_DEBUG, "smtpclient: trying MX %d of %d <%s>", i+1, num_mx, smtp_url); // send the message
+ res = curl_easy_perform(curl);
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
+ syslog(LOG_DEBUG,
+ "smtpclient: libcurl returned %d (%s) , SMTP response %ld",
+ res, curl_easy_strerror(res), response_code
+ );
+
+ if ((res != CURLE_OK) && (response_code == 0)) { // check for errors
+ response_code = 421;
+ }
+
+ curl_slist_free_all(recipients);
+ recipients = NULL; // this gets reused; avoid double-free
+ curl_easy_cleanup(curl);
+ curl = NULL; // this gets reused; avoid double-free
+
+ // Trim the error message buffer down to just the actual message
+ trim_response(response_code, response);
+ }
+ }
+
+ FreeStrBuf(&s.TheMessage);
+ if (fromaddr) {
+ free(fromaddr);
+ }
+ return ((int) response_code);
+}
+
+
+// Process one outbound message.
+void smtp_process_one_msg(long qmsgnum) {
+ struct CtdlMessage *msg = NULL;
+ char *instr = NULL;
+ int i;
+ int num_success = 0;
+ int num_fail = 0;
+ int num_delayed = 0;
+ long deletes[2];
+ int delete_this_queue = 0;
+ char server_response[SIZ];
+
+ msg = CtdlFetchMessage(qmsgnum, 1);
+ if (msg == NULL) {
+ syslog(LOG_WARNING, "smtpclient: %ld does not exist", qmsgnum);
+ return;
+ }
+
+ instr = msg->cm_fields[eMesageText];
+ msg->cm_fields[eMesageText] = NULL;
+ CM_Free(msg);
+
+ // if the queue message has any CRLF's convert them to LF's
+ char *crlf = NULL;
+ while (crlf = strstr(instr, "\r\n"), crlf != NULL) {
+ strcpy(crlf, crlf + 1);
+ }
+
+ // Strip out the headers and we are now left with just the instructions.
+ char *soi = strstr(instr, "\n\n");
+ if (soi) {
+ strcpy(instr, soi + 2);
+ }
+
+ long msgid = 0;
+ time_t submitted = time(NULL);
+ time_t attempted = 0;
+ char *bounceto = NULL;
+ char *envelope_from = NULL;
+ char *source_room = NULL;
+
+ char cfgline[SIZ];
+ for (i = 0; i < num_tokens(instr, '\n'); ++i) {
+ extract_token(cfgline, instr, i, '\n', sizeof cfgline);
+ if (!strncasecmp(cfgline, HKEY("msgid|")))
+ msgid = atol(&cfgline[6]);
+ if (!strncasecmp(cfgline, HKEY("submitted|")))
+ submitted = atol(&cfgline[10]);
+ if (!strncasecmp(cfgline, HKEY("attempted|")))
+ attempted = atol(&cfgline[10]);
+ if (!strncasecmp(cfgline, HKEY("bounceto|")))
+ bounceto = strdup(&cfgline[9]);
+ if (!strncasecmp(cfgline, HKEY("envelope_from|")))
+ envelope_from = strdup(&cfgline[14]);
+ if (!strncasecmp(cfgline, HKEY("source_room|")))
+ source_room = strdup(&cfgline[12]);
+ }
+
+ int should_try_now = 0;
+ if (attempted < submitted) { // If no attempts have been made yet, try now
+ should_try_now = 1;
+ }
+ else if ((attempted - submitted) <= 14400) {
+ if ((time(NULL) - attempted) > 1800) { // First four hours, retry every 30 minutes
+ should_try_now = 1;
+ }
+ }
+ else {
+ if ((time(NULL) - attempted) > 14400) { // After that, retry once every 4 hours
+ should_try_now = 1;
+ }
+ }
+
+ if (should_try_now) {
+ syslog(LOG_DEBUG, "smtpclient: attempting delivery of message <%ld> now", qmsgnum);
+ if (source_room) {
+ syslog(LOG_DEBUG, "smtpclient: this message originated in <%s>", source_room);
+ }
+ StrBuf *NewInstr = NewStrBuf();
+ StrBufAppendPrintf(NewInstr, "Content-type: " SPOOLMIME "\n\n");
+ StrBufAppendPrintf(NewInstr, "msgid|%ld\n", msgid);
+ StrBufAppendPrintf(NewInstr, "submitted|%ld\n", submitted);
+ if (bounceto) {
+ StrBufAppendPrintf(NewInstr, "bounceto|%s\n", bounceto);
+ }
+ if (envelope_from) {
+ StrBufAppendPrintf(NewInstr, "envelope_from|%s\n", envelope_from);
+ }
+ for (i = 0; i < num_tokens(instr, '\n'); ++i) {
+ extract_token(cfgline, instr, i, '\n', sizeof cfgline);
+ if (!strncasecmp(cfgline, HKEY("remote|"))) {
+ char recp[SIZ];
+ int previous_result = extract_int(cfgline, 2);
+ if ((previous_result == 0)
+ || (previous_result == 4)) {
+ int new_result = 421;
+ extract_token(recp, cfgline, 1, '|', sizeof recp);
+ new_result = smtp_attempt_delivery(msgid, recp, envelope_from, source_room, server_response);
+ syslog(LOG_DEBUG, "smtpclient: recp: <%s> , result: %d (%s)", recp, new_result, server_response);
+ if ((new_result / 100) == 2) {
+ ++num_success;
+ }
+ else {
+ if ((new_result / 100) == 5) {
+ ++num_fail;
+ }
+ else {
+ ++num_delayed;
+ }
+ StrBufAppendPrintf
+ (NewInstr,
+ "remote|%s|%ld|%ld (%s)\n",
+ recp, (new_result / 100), new_result, server_response);
+ }
+ }
+ }
+ }
+
+ StrBufAppendPrintf(NewInstr, "attempted|%ld\n", time(NULL));
+
+ // All deliveries have now been attempted. Now determine the disposition of this queue entry.
+
+ time_t age = time(NULL) - submitted;
+ syslog(LOG_DEBUG,
+ "smtpclient: submission age: %ldd%ldh%ldm%lds",
+ (age / 86400), ((age % 86400) / 3600), ((age % 3600) / 60), (age % 60));
+ syslog(LOG_DEBUG, "smtpclient: num_success=%d , num_fail=%d , num_delayed=%d", num_success, num_fail, num_delayed);
+
+ // If there are permanent fails on this attempt, deliver a bounce to the user.
+ // The 5XX fails will be recorded in the rewritten queue, but they will be removed before the next attempt.
+ if (num_fail > 0) {
+ smtp_do_bounce(ChrPtr(NewInstr), SDB_BOUNCE_FATALS);
+ }
+ // If all deliveries have either succeeded or failed, we are finished with this queue entry.
+ if (num_delayed == 0) {
+ delete_this_queue = 1;
+ }
+ // If it's been more than five days, give up and tell the sender that delivery failed
+ else if ((time(NULL) - submitted) > SMTP_DELIVER_FAIL) {
+ smtp_do_bounce(ChrPtr(NewInstr), SDB_BOUNCE_ALL);
+ delete_this_queue = 1;
+ }
+ // If it's been more than four hours but less than five days, warn the sender that delivery is delayed
+ else if (((attempted - submitted) < SMTP_DELIVER_WARN)
+ && ((time(NULL) - submitted) >= SMTP_DELIVER_WARN)) {
+ smtp_do_bounce(ChrPtr(NewInstr), SDB_WARN);
+ }
+
+ if (delete_this_queue) {
+ syslog(LOG_DEBUG, "smtpclient: %ld deleting", qmsgnum);
+ deletes[0] = qmsgnum;
+ deletes[1] = msgid;
+ CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, deletes, 2, "");
+ FreeStrBuf(&NewInstr); // We have to free NewInstr here, no longer needed
+ }
+ else {
+ // replace the old queue entry with the new one
+ syslog(LOG_DEBUG, "smtpclient: %ld rewriting", qmsgnum);
+ msg = convert_internet_message_buf(&NewInstr); // This function will free NewInstr for us
+ CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
+ CM_Free(msg);
+ CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &qmsgnum, 1, "");
+ }
+ }
+ else {
+ syslog(LOG_DEBUG, "smtpclient: %ld retry time not reached", qmsgnum);
+ }
+
+ if (bounceto != NULL) {
+ free(bounceto);
+ }
+ if (envelope_from != NULL) {
+ free(envelope_from);
+ }
+ if (source_room != NULL) {
+ free(source_room);
+ }
+ free(instr);
+}
+
+
+// Callback for smtp_do_queue()
+void smtp_add_msg(long msgnum, void *userdata) {
+
+ if (smtpq == NULL) {
+ smtpq_count = 0;
+ smtpq_alloc = 100;
+ smtpq = malloc(smtpq_alloc * sizeof(long));
+ }
+
+ if (smtpq_alloc >= smtpq_count) {
+ smtpq_alloc += 100;
+ smtpq = realloc(smtpq, (smtpq_alloc * sizeof(long)));
+ }
+
+ smtpq[smtpq_count++] = msgnum;
+}
+
+
+// Run through the queue sending out messages.
+void smtp_do_queue(void) {
+ int i = 0;
+
+ // This is a simple concurrency check to make sure only one smtpclient
+ // run is done at a time. We could do this with a mutex, but since we
+ // don't really require extremely fine granularity here, we'll do it
+ // with a static variable instead.
+ if (doing_smtpclient) {
+ return;
+ }
+ doing_smtpclient = 1;
+
+ syslog(LOG_DEBUG, "smtpclient: start queue run");
+
+ if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
+ syslog(LOG_WARNING, "Cannot find room <%s>", SMTP_SPOOLOUT_ROOM);
+ doing_smtpclient = 0;
+ return;
+ }
+ // Put the queue in memory so we can close the db cursor
+ CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_add_msg, NULL);
+
+ // We are ready to run through the queue now.
+ for (i = 0; i < smtpq_count; ++i) {
+ smtp_process_one_msg(smtpq[i]);
+ }
+
+ smtpq_count = 0; // don't free it, we will use this memory on the next run
+ doing_smtpclient = 0;
+ syslog(LOG_DEBUG, "smtpclient: end queue run");
+}
+
+
+// Module entry point
+char *ctdl_module_init_smtpclient(void) {
+ if (!threading) {
+ CtdlRegisterMessageHook(smtp_aftersave, EVT_AFTERSAVE);
+ CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER, PRIO_AGGR + 51);
+ smtp_init_spoolout();
+ }
+
+ // return our module id for the log
+ return "smtpclient";
+}
--- /dev/null
+/*
+ * Utility functions for the Citadel SMTP implementation
+ *
+ * Copyright (c) 1998-2021 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.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <termios.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <syslog.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../control.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../genstamp.h"
+#include "../../domain.h"
+#include "../../clientsocket.h"
+#include "../../locate_host.h"
+#include "../../citadel_dirs.h"
+#include "../../ctdl_module.h"
+#include "smtp_util.h"
+
+const char *smtp_get_Recipients(void) {
+ struct citsmtp *sSMTP = SMTP;
+
+ if (sSMTP == NULL)
+ return NULL;
+ else return ChrPtr(sSMTP->from);
+}
+
+
+/*
+ * smtp_do_bounce() is caled by smtp_process_one_msg() to scan a set of delivery
+ * instructions for errors and produce/deliver a "bounce" message (delivery
+ * status notification).
+ *
+ * is_final should be set to:
+ * SDB_BOUNCE_FATALS Advise the sender of all 5XX (permanent failures)
+ * SDB_BOUNCE_ALL Advise the sender that all deliveries have failed and will not be retried
+ * SDB_WARN Warn the sender about all 4XX transient delays
+ */
+void smtp_do_bounce(const char *instr, int is_final) {
+ int i;
+ int lines;
+ int status;
+ char buf[1024];
+ char key[1024];
+ char addr[1024];
+ char dsn[1024];
+ char bounceto[1024];
+ StrBuf *boundary;
+ int num_bounces = 0;
+ int bounce_this = 0;
+ struct CtdlMessage *bmsg = NULL;
+ struct recptypes *valid;
+ int successful_bounce = 0;
+ static int seq = 0;
+ StrBuf *BounceMB;
+ long omsgid = (-1);
+
+ syslog(LOG_DEBUG, "smtp_do_bounce() called");
+ strcpy(bounceto, "");
+ boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
+
+ StrBufAppendPrintf(boundary, "%s_%04x%04x", CtdlGetConfigStr("c_fqdn"), getpid(), ++seq);
+
+ /* Start building our bounce message */
+
+ bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
+ if (bmsg == NULL) return;
+ memset(bmsg, 0, sizeof(struct CtdlMessage));
+ BounceMB = NewStrBufPlain(NULL, 1024);
+
+ bmsg->cm_magic = CTDLMESSAGE_MAGIC;
+ bmsg->cm_anon_type = MES_NORMAL;
+ bmsg->cm_format_type = FMT_RFC822;
+ CM_SetField(bmsg, eAuthor, HKEY("Citadel"));
+ CM_SetField(bmsg, eOriginalRoom, HKEY(MAILROOM));
+ CM_SetField(bmsg, eMsgSubject, HKEY("Delivery Status Notification (Failure)"));
+ StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
+ StrBufAppendBuf(BounceMB, boundary, 0);
+ StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
+ StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
+ StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
+ StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
+ StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
+ StrBufAppendBuf(BounceMB, boundary, 0);
+ StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
+ StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
+
+ if (is_final == SDB_BOUNCE_ALL) {
+ StrBufAppendBufPlain(
+ BounceMB,
+ HKEY( "A message you sent could not be delivered "
+ "to some or all of its recipients\ndue to "
+ "prolonged unavailability of its destination(s).\n"
+ "Giving up on the following addresses:\n\n"),
+ 0);
+ }
+ else if (is_final == SDB_BOUNCE_FATALS) {
+ StrBufAppendBufPlain(
+ BounceMB,
+ HKEY( "A message you sent could not be delivered "
+ "to some or all of its recipients.\n"
+ "The following addresses were undeliverable:\n\n"),
+ 0);
+ }
+ else if (is_final == SDB_WARN) {
+ StrBufAppendBufPlain(
+ BounceMB,
+ HKEY("A message you sent has not been delivered "
+ "to some or all of its recipients.\n"
+ "Citadel will continue attempting delivery for five days.\n"
+ "The following addresses were undeliverable:\n\n"),
+ 0);
+ }
+ else { // should never get here
+ StrBufAppendBufPlain(BounceMB, HKEY("This message should never occur.\n\n"), 0);
+ }
+
+ /*
+ * Now go through the instructions checking for stuff.
+ */
+ lines = num_tokens(instr, '\n');
+ for (i=0; i<lines; ++i) {
+ long addrlen;
+ long dsnlen;
+ extract_token(buf, instr, i, '\n', sizeof buf);
+ extract_token(key, buf, 0, '|', sizeof key);
+ addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
+ status = extract_int(buf, 2);
+ dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
+ bounce_this = 0;
+
+ syslog(LOG_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>", key, addr, status, dsn);
+
+ if (!strcasecmp(key, "bounceto")) {
+ strcpy(bounceto, addr);
+ }
+
+ if (!strcasecmp(key, "msgid")) {
+ omsgid = atol(addr);
+ }
+
+ if (!strcasecmp(key, "remote")) {
+ if ((is_final == SDB_BOUNCE_FATALS) && (status == 5)) bounce_this = 1;
+ if ((is_final == SDB_BOUNCE_ALL) && (status != 2)) bounce_this = 1;
+ if ((is_final == SDB_WARN) && (status == 4)) bounce_this = 1;
+ }
+
+ if (bounce_this) {
+ ++num_bounces;
+ StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
+ StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
+ StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
+ StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
+ }
+ }
+
+ /* Attach the original message */
+ if (omsgid >= 0) {
+ StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
+ StrBufAppendBuf(BounceMB, boundary, 0);
+ StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
+ StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
+ StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
+ StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
+ StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
+
+ CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+ CtdlOutputMsg(omsgid,
+ MT_RFC822,
+ HEADERS_ALL,
+ 0, 1, NULL, 0,
+ NULL, NULL, NULL
+ );
+ StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
+ FreeStrBuf(&CC->redirect_buffer);
+ }
+
+ /* Close the multipart MIME scope */
+ StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
+ StrBufAppendBuf(BounceMB, boundary, 0);
+ StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
+ CM_SetAsFieldSB(bmsg, eMesageText, &BounceMB);
+
+ /* Deliver the bounce if there's anything worth mentioning */
+ syslog(LOG_DEBUG, "num_bounces = %d", num_bounces);
+ if (num_bounces > 0) {
+
+ /* First try the user who sent the message */
+ if (IsEmptyStr(bounceto)) {
+ syslog(LOG_ERR, "No bounce address specified");
+ }
+ else {
+ syslog(LOG_DEBUG, "bounce to user <%s>", bounceto);
+ }
+ /* Can we deliver the bounce to the original sender? */
+ valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
+ if (valid != NULL) {
+ if (valid->num_error == 0) {
+ CtdlSubmitMsg(bmsg, valid, "");
+ successful_bounce = 1;
+ }
+ }
+
+ /* If not, post it in the Aide> room */
+ if (successful_bounce == 0) {
+ CtdlSubmitMsg(bmsg, NULL, CtdlGetConfigStr("c_aideroom"));
+ }
+
+ /* Free up the memory we used */
+ if (valid != NULL) {
+ free_recipients(valid);
+ }
+ }
+ FreeStrBuf(&boundary);
+ CM_Free(bmsg);
+ syslog(LOG_DEBUG, "Done processing bounces");
+}
+
+
+char *smtpcodes[][2] = {
+ { "211 - System status / system help reply" },
+ { "214", "Help message" },
+ { "220", "Domain service ready" },
+ { "221", "Domain service closing transmission channel" },
+ { "250", "Requested mail action completed and OK" },
+ { "251", "Not Local User, forward email to forward path" },
+ { "252", "Cannot Verify user, will attempt delivery later" },
+ { "253", "Pending messages for node started" },
+ { "354", "Start mail input; end with ." },
+ { "355", "Octet-offset is the transaction offset" },
+ { "421", "Domain service not available, closing transmission channel" },
+ { "432", "Domain service not available, closing transmission channel" },
+ { "450", "Requested mail action not taken: mailbox unavailable. request refused" },
+ { "451", "Requested action aborted: local error in processing Request is unable to be processed, try again" },
+ { "452", "Requested action not taken: insufficient system storage" },
+ { "453", "No mail" },
+ { "454", "TLS not available due to temporary reason. Encryption required for requested authentication mechanism" },
+ { "458", "Unable to queue messages for node" },
+ { "459", "Node not allowed: reason" },
+ { "500", "Syntax error, command unrecognized" },
+ { "501", "Syntax error in parameters or arguments" },
+ { "502", "Command not implemented" },
+ { "503", "Bad sequence of commands" },
+ { "504", "Command parameter not implemented" },
+ { "510", "Check the recipient address" },
+ { "512", "Domain can not be found. Unknown host." },
+ { "515", "Destination mailbox address invalid" },
+ { "517", "Problem with senders mail attribute, check properties" },
+ { "521", "Domain does not accept mail" },
+ { "522", "Recipient has exceeded mailbox limit" },
+ { "523", "Server limit exceeded. Message too large" },
+ { "530", "Access Denied. Authentication required" },
+ { "531", "Mail system Full" },
+ { "533", "Remote ../../server.has insufficient disk space to hold email" },
+ { "534", "Authentication mechanism is too weak. Message too big" },
+ { "535", "Multiple servers using same IP. Required Authentication" },
+ { "538", "Encryption required for requested authentication mechanism" },
+ { "540", "Email address has no DNS Server" },
+ { "541", "No response from host" },
+ { "542", "Bad Connection" },
+ { "543", "Routing server failure. No available route" },
+ { "546", "Email looping" },
+ { "547", "Delivery time-out" },
+ { "550", "Requested action not taken: mailbox unavailable or relaying denied" },
+ { "551", "User not local; please try forward path" },
+ { "552", "Requested mail action aborted: exceeded storage allocation" },
+ { "553", "Requested action not taken: mailbox name not allowed" },
+ { "554", "Transaction failed" }
+};
+
+
+char *smtpstatus(int code) {
+ int i;
+
+ for (i=0; i<(sizeof(smtpcodes)/sizeof(char *)/2); ++i) {
+ if (atoi(smtpcodes[i][0]) == code) {
+ return(smtpcodes[i][1]);
+ }
+ }
+
+ return("Unknown or other SMTP status");
+}
--- /dev/null
+/*
+ * 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.
+ *
+ */
+
+const char *smtp_get_Recipients(void);
+
+struct citsmtp { /* Information about the current session */
+ int command_state;
+ StrBuf *Cmd;
+ StrBuf *helo_node;
+ StrBuf *from;
+ StrBuf *recipients;
+ StrBuf *OneRcpt;
+ int number_of_recipients;
+ int delivery_mode;
+ int message_originated_locally;
+ int is_lmtp;
+ int is_unfiltered;
+ int is_msa;
+ StrBuf *preferred_sender_email;
+ StrBuf *preferred_sender_name;
+};
+
+#define SMTP ((struct citsmtp *)CC->session_specific_data)
+
+// These are all the values that can be passed to the is_final parameter of smtp_do_bounce()
+enum {
+ SDB_BOUNCE_FATALS,
+ SDB_BOUNCE_ALL,
+ SDB_WARN
+};
+
+void smtp_do_bounce(const char *instr, int is_final);
+char *smtpstatus(int code);
--- /dev/null
+/*
+ * This module allows Citadel to use SpamAssassin to filter incoming messages
+ * arriving via SMTP. For more information on SpamAssassin, visit
+ * http://www.spamassassin.org (the SpamAssassin project is not in any way
+ * affiliated with the Citadel project).
+ *
+ * Copyright (c) 1998-2022 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.
+ */
+
+#define SPAMASSASSIN_PORT "783"
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/socket.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../control.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../domain.h"
+#include "../../clientsocket.h"
+
+
+#include "../../ctdl_module.h"
+
+
+
+/*
+ * Connect to the SpamAssassin server and scan a message.
+ */
+int spam_assassin(struct CtdlMessage *msg, struct recptypes *recp) {
+ int sock = (-1);
+ char sahosts[SIZ];
+ int num_sahosts;
+ char buf[SIZ];
+ int is_spam = 0;
+ int sa;
+ StrBuf *msgtext;
+ CitContext *CCC=CC;
+
+ /* For users who have authenticated to this server we never want to
+ * apply spam filtering, because presumably they're trustworthy.
+ */
+ if (CC->logged_in) return(0);
+
+ /* See if we have any SpamAssassin hosts configured */
+ num_sahosts = get_hosts(sahosts, "spamassassin");
+ if (num_sahosts < 1) return(0);
+
+ /* Try them one by one until we get a working one */
+ for (sa=0; sa<num_sahosts; ++sa) {
+ extract_token(buf, sahosts, sa, '|', sizeof buf);
+ syslog(LOG_INFO, "Connecting to SpamAssassin at <%s>\n", buf);
+ sock = sock_connect(buf, SPAMASSASSIN_PORT);
+ if (sock >= 0) syslog(LOG_DEBUG, "Connected!\n");
+ }
+
+ if (sock < 0) {
+ /* If the service isn't running, just pass the mail
+ * through. Potentially throwing away mails isn't good.
+ */
+ return(0);
+ }
+
+ CCC->SBuf.Buf = NewStrBuf();
+ CCC->sMigrateBuf = NewStrBuf();
+ CCC->SBuf.ReadWritePointer = NULL;
+
+ /* Command */
+ syslog(LOG_DEBUG, "Transmitting command\n");
+ sprintf(buf, "CHECK SPAMC/1.2\r\n\r\n");
+ sock_write(&sock, buf, strlen(buf));
+
+ /* Message */
+ CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+ CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, 0);
+ msgtext = CC->redirect_buffer;
+ CC->redirect_buffer = NULL;
+
+ sock_write(&sock, SKEY(msgtext));
+ FreeStrBuf(&msgtext);
+
+ /* Close one end of the socket connection; this tells SpamAssassin
+ * that we're done.
+ */
+ if (sock != -1)
+ sock_shutdown(sock, SHUT_WR);
+
+ /* Response */
+ syslog(LOG_DEBUG, "Awaiting response\n");
+ if (sock_getln(&sock, buf, sizeof buf) < 0) {
+ goto bail;
+ }
+ syslog(LOG_DEBUG, "<%s\n", buf);
+ if (strncasecmp(buf, "SPAMD", 5)) {
+ goto bail;
+ }
+ if (sock_getln(&sock, buf, sizeof buf) < 0) {
+ goto bail;
+ }
+ syslog(LOG_DEBUG, "<%s\n", buf);
+ syslog(LOG_DEBUG, "c_spam_flag_only setting %d\n", CtdlGetConfigInt("c_spam_flag_only"));
+ if (CtdlGetConfigInt("c_spam_flag_only")) {
+ int headerlen;
+ char *cur;
+ char sastatus[10];
+ char sascore[10];
+ char saoutof[10];
+ int numscore;
+
+ syslog(LOG_DEBUG, "flag spam code used");
+
+ extract_token(sastatus, buf, 1, ' ', sizeof sastatus);
+ extract_token(sascore, buf, 3, ' ', sizeof sascore);
+ extract_token(saoutof, buf, 5, ' ', sizeof saoutof);
+
+ memcpy(buf, HKEY("X-Spam-Level: "));
+ cur = buf + 14;
+ for (numscore = atoi(sascore); numscore>0; numscore--)
+ *(cur++) = '*';
+ *cur = '\0';
+
+ headerlen = cur - buf;
+ headerlen += snprintf(cur, (sizeof(buf) - headerlen),
+ "\r\nX-Spam-Status: %s, score=%s required=%s\r\n",
+ sastatus, sascore, saoutof);
+
+ CM_PrependToField(msg, eMesageText, buf, headerlen);
+
+ } else {
+ syslog(LOG_DEBUG, "reject spam code used");
+ if (!strncasecmp(buf, "Spam: True", 10)) {
+ is_spam = 1;
+ }
+
+ if (is_spam) {
+ CM_SetField(msg, eErrorMsg, HKEY("message rejected by spam filter"));
+ }
+ }
+
+bail: close(sock);
+ FreeStrBuf(&CCC->SBuf.Buf);
+ FreeStrBuf(&CCC->sMigrateBuf);
+ return(is_spam);
+}
+
+
+
+char *ctdl_module_init_spam(void) {
+ if (!threading) {
+ CtdlRegisterMessageHook(spam_assassin, EVT_SMTPSCAN);
+ }
+
+ /* return our module name for the log */
+ return "spam";
+}
--- /dev/null
+// This is an empty skeleton of a Citadel server module, included to demonstrate
+// how to add a new module to the system and how to activate it in the server.
+//
+// Copyright (c) 1998-2022 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.
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include "../../ctdl_module.h"
+
+
+void CleanupTest(void) {
+ syslog(LOG_DEBUG, "--- test of adding an unload hook --- \n");
+}
+
+void NewRoomTest(void) {
+ syslog(LOG_DEBUG, "--- test module was told we're now in a new room ---\n");
+}
+
+void SessionStartTest(void) {
+ syslog(LOG_DEBUG, "--- starting up session %d ---\n", CC->cs_pid);
+}
+
+void SessionStopTest(void) {
+ syslog(LOG_DEBUG, "--- ending session %d ---\n", CC->cs_pid);
+}
+
+void LoginTest(void) {
+ syslog(LOG_DEBUG, "--- Hello, %s ---\n", CC->curr_user);
+}
+
+// To insert this module into the server activate the next block by changing the #if 0 to #if 1
+char *ctdl_module_init_test() {
+#if 0
+ if (!threading) {
+ CtdlRegisterCleanupHook(CleanupTest);
+ CtdlRegisterSessionHook(NewRoomTest, EVT_NEWROOM, 1);
+ CtdlRegisterSessionHook(SessionStartTest, EVT_START, 1);
+ CtdlRegisterSessionHook(SessionStopTest, EVT_STOP, 1);
+ CtdlRegisterSessionHook(LoginTest, EVT_LOGIN, 1);
+ }
+#else
+ syslog(LOG_DEBUG, "test: module is disabled");
+#endif
+
+ // return our module name for the log
+ return "test";
+}
--- /dev/null
+// Transparently handle the upgrading of server data formats. If we see
+// an existing version number of our database, we can make some intelligent
+// guesses about what kind of data format changes need to be applied, and
+// we apply them transparently.
+//
+// Copyright (c) 1987-2022 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.
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../control.h"
+#include "../../database.h"
+#include "../../user_ops.h"
+#include "../../msgbase.h"
+#include "serv_upgrade.h"
+#include "../../euidindex.h"
+#include "../../ctdl_module.h"
+#include "../../serv_vcard.h"
+#include "../../internet_addressing.h"
+
+// oldver is the version number of Citadel Server which was active on the previous run of the program, learned from the system configuration.
+// If we are running a new Citadel Server for the first time, oldver will be 0.
+// We keep this value around for the entire duration of the program run because we'll need it during several stages of startup.
+int oldver = 0;
+
+// Try to remove any extra users with number 0
+void fix_sys_user_name(void) {
+ struct ctdluser usbuf;
+ char usernamekey[USERNAME_SIZE];
+
+ while (CtdlGetUserByNumber(&usbuf, 0) == 0) {
+ // delete user with number 0 and no name
+ if (IsEmptyStr(usbuf.fullname)) {
+ cdb_delete(CDB_USERS, "", 0);
+ }
+ else {
+ // temporarily set this user to -1
+ usbuf.usernum = -1;
+ CtdlPutUser(&usbuf);
+ }
+ }
+
+ // Delete any "user 0" accounts
+ while (CtdlGetUserByNumber(&usbuf, -1) == 0) {
+ makeuserkey(usernamekey, usbuf.fullname);
+ cdb_delete(CDB_USERS, usernamekey, strlen(usernamekey));
+ }
+}
+
+
+// Back end processing function for reindex_uids()
+void reindex_uids_backend(char *username, void *data) {
+
+ struct ctdluser us;
+
+ if (CtdlGetUserLock(&us, username) == 0) {
+ syslog(LOG_DEBUG, "Processing <%s> (%d)", us.fullname, us.uid);
+ if (us.uid == CTDLUID) {
+ us.uid = NATIVE_AUTH_UID;
+ }
+ CtdlPutUserLock(&us);
+ if ((us.uid > 0) && (us.uid != NATIVE_AUTH_UID)) { // if non-native auth , index by uid
+ StrBuf *claimed_id = NewStrBuf();
+ StrBufPrintf(claimed_id, "uid:%d", us.uid);
+ attach_extauth(&us, claimed_id);
+ FreeStrBuf(&claimed_id);
+ }
+ }
+}
+
+
+// Build extauth index of all users with uid-based join (system auth, LDAP auth)
+// Also changes all users with a uid of CTDLUID to NATIVE_AUTH_UID (-1)
+void reindex_uids(void) {
+ syslog(LOG_WARNING, "upgrade: reindexing and applying uid changes");
+ ForEachUser(reindex_uids_backend, NULL);
+ return;
+}
+
+
+// These accounts may have been created by code that ran between mid 2008 and early 2011.
+// If present they are no longer in use and may be deleted.
+void remove_thread_users(void) {
+ char *deleteusers[] = {
+ "SYS_checkpoint",
+ "SYS_extnotify",
+ "SYS_IGnet Queue",
+ "SYS_indexer",
+ "SYS_network",
+ "SYS_popclient",
+ "SYS_purger",
+ "SYS_rssclient",
+ "SYS_select_on_master",
+ "SYS_SMTP Send"
+ };
+
+ int i;
+ struct ctdluser usbuf;
+ for (i=0; i<(sizeof(deleteusers)/sizeof(char *)); ++i) {
+ if (CtdlGetUser(&usbuf, deleteusers[i]) == 0) {
+ usbuf.axlevel = 0;
+ strcpy(usbuf.password, "deleteme");
+ CtdlPutUser(&usbuf);
+ syslog(LOG_INFO,
+ "System user account <%s> is no longer in use and will be deleted.",
+ deleteusers[i]
+ );
+ }
+ }
+}
+
+
+// Attempt to guess the name of the time zone currently in use
+// on the underlying host system.
+void guess_time_zone(void) {
+ FILE *fp;
+ char buf[PATH_MAX];
+
+ fp = popen(file_guesstimezone, "r");
+ if (fp) {
+ if (fgets(buf, sizeof buf, fp) && (strlen(buf) > 2)) {
+ buf[strlen(buf)-1] = 0;
+ CtdlSetConfigStr("c_default_cal_zone", buf);
+ syslog(LOG_INFO, "Configuring timezone: %s", buf);
+ }
+ fclose(fp);
+ }
+}
+
+
+// Per-room callback function for ingest_old_roominfo_and_roompic_files()
+// This is the second pass, where we process the list of rooms with info or pic files.
+void iorarf_oneroom(char *roomname, char *infofile, char *picfile) {
+ FILE *fp;
+ long data_length;
+ char *unencoded_data;
+ char *encoded_data;
+ long info_msgnum = 0;
+ long pic_msgnum = 0;
+ char subject[SIZ];
+
+ // Test for the presence of a legacy "room info file"
+ if (!IsEmptyStr(infofile)) {
+ fp = fopen(infofile, "r");
+ }
+ else {
+ fp = NULL;
+ }
+ if (fp) {
+ fseek(fp, 0, SEEK_END);
+ data_length = ftell(fp);
+
+ if (data_length >= 1) {
+ rewind(fp);
+ unencoded_data = malloc(data_length);
+ if (unencoded_data) {
+ fread(unencoded_data, data_length, 1, fp);
+ encoded_data = malloc((data_length * 2) + 100);
+ if (encoded_data) {
+ sprintf(encoded_data, "Content-type: text/plain\nContent-transfer-encoding: base64\n\n");
+ CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
+ snprintf(subject, sizeof subject, "Imported room banner for %s", roomname);
+ info_msgnum = quickie_message("Citadel", NULL, NULL, SYSCONFIGROOM, encoded_data, FMT_RFC822, subject);
+ free(encoded_data);
+ }
+ free(unencoded_data);
+ }
+ }
+ fclose(fp);
+ if (info_msgnum > 0) unlink(infofile);
+ }
+
+ // Test for the presence of a legacy "room picture file" and import it.
+ if (!IsEmptyStr(picfile)) {
+ fp = fopen(picfile, "r");
+ }
+ else {
+ fp = NULL;
+ }
+ if (fp) {
+ fseek(fp, 0, SEEK_END);
+ data_length = ftell(fp);
+
+ if (data_length >= 1) {
+ rewind(fp);
+ unencoded_data = malloc(data_length);
+ if (unencoded_data) {
+ fread(unencoded_data, data_length, 1, fp);
+ encoded_data = malloc((data_length * 2) + 100);
+ if (encoded_data) {
+ sprintf(encoded_data, "Content-type: image/gif\nContent-transfer-encoding: base64\n\n");
+ CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
+ snprintf(subject, sizeof subject, "Imported room icon for %s", roomname);
+ pic_msgnum = quickie_message("Citadel", NULL, NULL, SYSCONFIGROOM, encoded_data, FMT_RFC822, subject);
+ free(encoded_data);
+ }
+ free(unencoded_data);
+ }
+ }
+ fclose(fp);
+ if (pic_msgnum > 0) unlink(picfile);
+ }
+
+ // Now we have the message numbers of our new banner and icon. Record them in the room record.
+ // NOTE: we are not deleting the old msgnum_info because that position in the record was previously
+ // a pointer to the highest message number which existed in the room when the info file was saved,
+ // and we don't want to delete messages that are not *actually* old banners.
+ struct ctdlroom qrbuf;
+ if (CtdlGetRoomLock(&qrbuf, roomname) == 0) {
+ qrbuf.msgnum_info = info_msgnum;
+ qrbuf.msgnum_pic = pic_msgnum;
+ CtdlPutRoomLock(&qrbuf);
+ }
+
+}
+
+
+struct iorarf_list {
+ struct iorarf_list *next;
+ char name[ROOMNAMELEN];
+ char info[PATH_MAX];
+ char pic[PATH_MAX];
+};
+
+
+// Per-room callback function for ingest_old_roominfo_and_roompic_files()
+// This is the first pass, where the list of qualifying rooms is gathered.
+void iorarf_backend(struct ctdlroom *qrbuf, void *data) {
+ FILE *fp;
+ struct iorarf_list **iorarf_list = (struct iorarf_list **)data;
+
+ struct iorarf_list *i = malloc(sizeof(struct iorarf_list));
+ i->next = *iorarf_list;
+ strcpy(i->name, qrbuf->QRname);
+ strcpy(i->info, "");
+ strcpy(i->pic, "");
+
+ // Test for the presence of a legacy "room info file"
+ assoc_file_name(i->info, sizeof i->info, qrbuf, ctdl_info_dir);
+ fp = fopen(i->info, "r");
+ if (fp) {
+ fclose(fp);
+ }
+ else {
+ i->info[0] = 0;
+ }
+
+ // Test for the presence of a legacy "room picture file"
+ assoc_file_name(i->pic, sizeof i->pic, qrbuf, ctdl_image_dir);
+ fp = fopen(i->pic, "r");
+ if (fp) {
+ fclose(fp);
+ }
+ else {
+ i->pic[0] = 0;
+ }
+
+ if ( (!IsEmptyStr(i->info)) || (!IsEmptyStr(i->pic)) ) {
+ *iorarf_list = i;
+ }
+ else {
+ free(i);
+ }
+}
+
+
+// Prior to Citadel Server version 902, room info and pictures (which comprise the
+// displayed banner for each room) were stored in the filesystem. If we are upgrading
+// from version >000 to version >=902, ingest those files into the database.
+void ingest_old_roominfo_and_roompic_files(void) {
+ struct iorarf_list *il = NULL;
+
+ CtdlForEachRoom(iorarf_backend, &il);
+
+ struct iorarf_list *p;
+ while (il) {
+ iorarf_oneroom(il->name, il->info, il->pic);
+ p = il->next;
+ free(il);
+ il = p;
+ }
+
+ unlink(ctdl_info_dir);
+}
+
+
+// For upgrades in which a new config setting appears for the first time, set default values.
+// For new installations (oldver == 0) also set default values.
+void update_config(void) {
+
+ if (oldver < 606) {
+ CtdlSetConfigInt("c_rfc822_strict_from", 0);
+ }
+
+ if (oldver < 609) {
+ CtdlSetConfigInt("c_purge_hour", 3);
+ }
+
+ if (oldver < 615) {
+ CtdlSetConfigInt("c_ldap_port", 389);
+ }
+
+ if (oldver < 623) {
+ CtdlSetConfigStr("c_ip_addr", "*");
+ }
+
+ if (oldver < 650) {
+ CtdlSetConfigInt("c_enable_fulltext", 1);
+ }
+
+ if (oldver < 652) {
+ CtdlSetConfigInt("c_auto_cull", 1);
+ }
+
+ if (oldver < 725) {
+ CtdlSetConfigInt("c_xmpp_c2s_port", 5222);
+ CtdlSetConfigInt("c_xmpp_s2s_port", 5269);
+ }
+
+ if (oldver < 830) {
+ CtdlSetConfigInt("c_nntp_port", 119);
+ CtdlSetConfigInt("c_nntps_port", 563);
+ }
+
+ if (IsEmptyStr(CtdlGetConfigStr("c_default_cal_zone"))) {
+ guess_time_zone();
+ }
+}
+
+
+// Helper function for move_inet_addrs_from_vcards_to_user_records()
+//
+// Call this function as a ForEachUser backend in order to queue up
+// user names, or call it with a null user to make it do the processing.
+// This allows us to maintain the list as a static instead of passing
+// pointers around.
+void miafvtur_backend(char *username, void *data) {
+ struct ctdluser usbuf;
+ char primary_inet_email[512] = { 0 };
+ char other_inet_emails[512] = { 0 };
+ char combined_inet_emails[512] = { 0 };
+
+ if (CtdlGetUser(&usbuf, username) != 0) {
+ return;
+ }
+
+ struct vCard *v = vcard_get_user(&usbuf);
+ if (!v) return;
+ extract_inet_email_addrs(primary_inet_email, sizeof primary_inet_email, other_inet_emails, sizeof other_inet_emails, v, 1);
+ vcard_free(v);
+
+ if ( (IsEmptyStr(primary_inet_email)) && (IsEmptyStr(other_inet_emails)) ) {
+ return;
+ }
+
+ snprintf(combined_inet_emails, 512, "%s%s%s",
+ (!IsEmptyStr(primary_inet_email) ? primary_inet_email : ""),
+ ((!IsEmptyStr(primary_inet_email)&&(!IsEmptyStr(other_inet_emails))) ? "|" : ""),
+ (!IsEmptyStr(other_inet_emails) ? other_inet_emails : "")
+ );
+
+ CtdlSetEmailAddressesForUser(usbuf.fullname, combined_inet_emails);
+}
+
+
+// If our system still has a "refcount_adjustments.dat" sitting around from an old version, ingest it now.
+int ProcessOldStyleAdjRefCountQueue(void) {
+ int r;
+ FILE *fp;
+ struct arcq arcq_rec;
+ int num_records_processed = 0;
+
+ fp = fopen(file_arcq, "rb");
+ if (fp == NULL) {
+ return(num_records_processed);
+ }
+
+ syslog(LOG_INFO, "msgbase: ingesting %s", file_arcq);
+
+ while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
+ AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
+ ++num_records_processed;
+ }
+
+ fclose(fp);
+ r = unlink(file_arcq);
+ if (r != 0) {
+ syslog(LOG_ERR, "%s: %m", file_arcq);
+ }
+
+ return(num_records_processed);
+}
+
+
+// Prior to version 912 we kept a user's various Internet email addresses in their vCards.
+// This function moves them over to the user record, which is where we keep them now.
+void move_inet_addrs_from_vcards_to_user_records(void) {
+ ForEachUser(miafvtur_backend, NULL);
+ CtdlRebuildDirectoryIndex();
+}
+
+
+// We found the legacy sieve config in the user's config room. Store the message number in the user record.
+void mifm_found_config(long msgnum, void *userdata) {
+ struct ctdluser *us = (struct ctdluser *)userdata;
+
+ us->msgnum_inboxrules = msgnum;
+ syslog(LOG_DEBUG, "user: <%s> inbox filter msgnum: <%ld>", us->fullname, us->msgnum_inboxrules);
+}
+
+
+// Helper function for migrate_inbox_filter_msgnums()
+void mifm_backend(char *username, void *data) {
+ struct ctdluser us;
+ char roomname[ROOMNAMELEN];
+
+ if (CtdlGetUserLock(&us, username) == 0) {
+ // Take a spin through the user's personal config room
+ syslog(LOG_DEBUG, "Processing <%s> (%ld)", us.fullname, us.usernum);
+ snprintf(roomname, sizeof roomname, "%010ld.%s", us.usernum, USERCONFIGROOM);
+ if (CtdlGetRoom(&CC->room, roomname) == 0) {
+ CtdlForEachMessage(MSGS_LAST, 1, NULL, SIEVECONFIG, NULL, mifm_found_config, (void *)&us );
+ }
+ CtdlPutUserLock(&us);
+ }
+}
+
+
+// Prior to version 930 we used a MIME type search to locate the user's inbox filter rules.
+// This function locates those ruleset messages and simply stores the message number in the user record.
+void migrate_inbox_filter_msgnums(void) {
+ ForEachUser(mifm_backend, NULL);
+}
+
+
+// Create a default administrator account so we can log in to a new installation
+void create_default_admin_account(void) {
+ struct ctdluser usbuf;
+
+ create_user(DEFAULT_ADMIN_USERNAME, CREATE_USER_DO_NOT_BECOME_USER, (-1));
+ CtdlGetUser(&usbuf, DEFAULT_ADMIN_USERNAME);
+ safestrncpy(usbuf.password, DEFAULT_ADMIN_PASSWORD, sizeof(usbuf.password));
+ usbuf.axlevel = AxAideU;
+ CtdlPutUser(&usbuf);
+ CtdlSetConfigStr("c_sysadm", DEFAULT_ADMIN_USERNAME);
+}
+
+
+// Based on the server version number reported by the existing database,
+// run in-place data format upgrades until everything is up to date.
+void pre_startup_upgrades(void) {
+
+ oldver = CtdlGetConfigInt("MM_hosted_upgrade_level");
+ syslog(LOG_INFO, "Existing database version on disk is %d", oldver);
+ update_config();
+
+ if (oldver < REV_LEVEL) {
+ syslog(LOG_WARNING, "Running pre-startup database upgrades.");
+ }
+ else {
+ return;
+ }
+
+ if ((oldver > 000) && (oldver < 591)) {
+ syslog(LOG_EMERG, "This database is too old to be upgraded. Citadel server will exit.");
+ exit(EXIT_FAILURE);
+ }
+ if ((oldver > 000) && (oldver < 913)) {
+ reindex_uids();
+ }
+ if ((oldver > 000) && (oldver < 659)) {
+ rebuild_euid_index();
+ }
+ if (oldver < 735) {
+ fix_sys_user_name();
+ }
+ if (oldver < 736) {
+ rebuild_usersbynumber();
+ }
+ if (oldver < 790) {
+ remove_thread_users();
+ }
+ if (oldver < 810) {
+ struct ctdlroom QRoom;
+ if (!CtdlGetRoom(&QRoom, SMTP_SPOOLOUT_ROOM)) {
+ QRoom.QRdefaultview = VIEW_QUEUE;
+ CtdlPutRoom(&QRoom);
+ }
+ }
+
+ if ((oldver > 000) && (oldver < 902)) {
+ ingest_old_roominfo_and_roompic_files();
+ }
+
+ CtdlSetConfigInt("MM_hosted_upgrade_level", REV_LEVEL);
+
+ // Negative values for maxsessions are not allowed.
+ if (CtdlGetConfigInt("c_maxsessions") < 0) {
+ CtdlSetConfigInt("c_maxsessions", 0);
+ }
+
+ // We need a system default message expiry policy, because this is
+ // the top level and there's no 'higher' policy to fall back on.
+ // By default, do not expire messages at all.
+ if (CtdlGetConfigInt("c_ep_mode") == 0) {
+ CtdlSetConfigInt("c_ep_mode", EXPIRE_MANUAL);
+ CtdlSetConfigInt("c_ep_value", 0);
+ }
+
+ // If this is the first run on an empty database, create a default administrator
+ if (oldver == 0) {
+ create_default_admin_account();
+ }
+}
+
+
+// Based on the server version number reported by the existing database,
+// run in-place data format upgrades until everything is up to date.
+void post_startup_upgrades(void) {
+
+ syslog(LOG_INFO, "Existing database version on disk is %d", oldver);
+
+ if (oldver < REV_LEVEL) {
+ syslog(LOG_WARNING, "Running post-startup database upgrades.");
+ }
+ else {
+ return;
+ }
+
+ if ((oldver > 000) && (oldver < 912)) {
+ move_inet_addrs_from_vcards_to_user_records();
+ }
+
+ if ((oldver > 000) && (oldver < 922)) {
+ ProcessOldStyleAdjRefCountQueue();
+ }
+
+ if ((oldver > 000) && (oldver < 930)) {
+ migrate_inbox_filter_msgnums();
+ }
+
+}
+
+
+char *ctdl_module_init_upgrade(void) {
+ if (!threading) {
+ post_startup_upgrades();
+ }
+
+ /* return our module name for the log */
+ return "upgrade";
+}
--- /dev/null
+/*
+ * Copyright (c) 1987-2012 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.
+ *
+ *
+ *
+ *
+ */
+
--- /dev/null
+/*
+ * A server-side module for Citadel which supports address book information
+ * using the standard vCard format.
+ *
+ * Copyright (c) 1999-2022 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.
+ */
+
+/*
+ * Format of the "Exclusive ID" field of the message containing a user's
+ * vCard. Doesn't matter what it really looks like as long as it's both
+ * unique and consistent (because we use it for replication checking to
+ * delete the old vCard network-wide when the user enters a new one).
+ */
+#define VCARD_EXT_FORMAT "Citadel vCard: personal card for %s at %s"
+
+/*
+ * Citadel will accept either text/vcard or text/x-vcard as the MIME type
+ * for a vCard. The following definition determines which one it *generates*
+ * when serializing.
+ */
+#define VCARD_MIME_TYPE "text/x-vcard"
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../control.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../room_ops.h"
+#include "../../internet_addressing.h"
+#include "../../serv_vcard.h"
+#include "../../citadel_ldap.h"
+#include "../../ctdl_module.h"
+
+/*
+ * set global flag calling for an aide to validate new users
+ */
+void set_mm_valid(void) {
+ int flags = 0;
+
+ begin_critical_section(S_CONTROL);
+ flags = CtdlGetConfigInt("MMflags");
+ flags = flags | MM_VALID ;
+ CtdlSetConfigInt("MMflags", flags);
+ end_critical_section(S_CONTROL);
+}
+
+
+///TODO: gettext!
+#define _(a) a
+
+/*
+ * See if there is a valid Internet address in a vCard to use for outbound
+ * Internet messages. If there is, stick it in the buffer.
+ */
+void extract_inet_email_addrs(char *emailaddrbuf, size_t emailaddrbuf_len,
+ char *secemailaddrbuf, size_t secemailaddrbuf_len,
+ struct vCard *v,
+ int local_addrs_only
+) {
+ char *s, *k, *addr;
+ int instance = 0;
+ int IsDirectoryAddress;
+ int saved_instance = 0;
+
+ /* Go through the vCard searching for *all* Internet email addresses
+ */
+ while (s = vcard_get_prop(v, "email", 1, instance, 0), s != NULL) {
+ k = vcard_get_prop(v, "email", 1, instance, 1);
+ if ( (s != NULL) && (k != NULL) && (bmstrcasestr(k, "internet")) ) {
+ addr = strdup(s);
+ striplt(addr);
+ if (!IsEmptyStr(addr)) {
+ IsDirectoryAddress = IsDirectory(addr, 1);
+
+ syslog(LOG_DEBUG, "EVQ: addr=<%s> IsDirectoryAddress=<%d> local_addrs_only=<%d>", addr, IsDirectoryAddress, local_addrs_only);
+
+ if ( IsDirectoryAddress || !local_addrs_only)
+ {
+ ++saved_instance;
+ if ((saved_instance == 1) && (emailaddrbuf != NULL)) {
+ safestrncpy(emailaddrbuf, addr, emailaddrbuf_len);
+ }
+ else if ((saved_instance == 2) && (secemailaddrbuf != NULL)) {
+ safestrncpy(secemailaddrbuf, addr, secemailaddrbuf_len);
+ }
+ else if ((saved_instance > 2) && (secemailaddrbuf != NULL)) {
+ if ( (strlen(addr) + strlen(secemailaddrbuf) + 2)
+ < secemailaddrbuf_len ) {
+ strcat(secemailaddrbuf, "|");
+ strcat(secemailaddrbuf, addr);
+ }
+ }
+ }
+ if (!IsDirectoryAddress && local_addrs_only)
+ {
+ StrBufAppendPrintf(CC->StatusMessage, "\n%d|", ERROR+ ILLEGAL_VALUE);
+ StrBufAppendBufPlain(CC->StatusMessage, addr, -1, 0);
+ StrBufAppendBufPlain(CC->StatusMessage, HKEY("|"), 0);
+ StrBufAppendBufPlain(CC->StatusMessage, _("unable to add this emailaddress; its not matching our domain."), -1, 0);
+ }
+ }
+ free(addr);
+ }
+ ++instance;
+ }
+}
+
+
+/*
+ * See if there is a name / screen name / friendly name in a vCard to use for outbound
+ * Internet messages. If there is, stick it in the buffer.
+ */
+void extract_friendly_name(char *namebuf, size_t namebuf_len, struct vCard *v)
+{
+ char *s;
+
+ s = vcard_get_prop(v, "fn", 1, 0, 0);
+ if (s == NULL) {
+ s = vcard_get_prop(v, "n", 1, 0, 0);
+ }
+
+ if (s != NULL) {
+ safestrncpy(namebuf, s, namebuf_len);
+ }
+}
+
+
+/*
+ * Callback function for vcard_upload_beforesave() hunts for the real vcard in the MIME structure
+ */
+void vcard_extract_vcard(char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length,
+ char *encoding, char *cbid, void *cbuserdata)
+{
+ struct vCard **v = (struct vCard **) cbuserdata;
+
+ if ( (!strcasecmp(cbtype, "text/x-vcard"))
+ || (!strcasecmp(cbtype, "text/vcard")) ) {
+
+ syslog(LOG_DEBUG, "vcard: part %s contains a vCard! Loading...", partnum);
+ if (*v != NULL) {
+ vcard_free(*v);
+ }
+ *v = vcard_load(content);
+ }
+}
+
+
+/*
+ * This handler detects whether the user is attempting to save a new
+ * vCard as part of his/her personal configuration, and handles the replace
+ * function accordingly (delete the user's existing vCard in the config room
+ * and in the global address book).
+ */
+int vcard_upload_beforesave(struct CtdlMessage *msg, struct recptypes *recp) {
+ char *s;
+ char buf[SIZ];
+ struct ctdluser usbuf;
+ long what_user;
+ struct vCard *v = NULL;
+ char *ser = NULL;
+ int i = 0;
+ int yes_my_citadel_config = 0;
+ int yes_any_vcard_room = 0;
+
+ if ((!CC->logged_in) && (CC->vcard_updated_by_ldap==0)) return(0); /* Only do this if logged in, or if ldap changed the vcard. */
+
+ /* Is this some user's "My Citadel Config" room? */
+ if (((CC->room.QRflags & QR_MAILBOX) != 0) &&
+ (!strcasecmp(&CC->room.QRname[11], USERCONFIGROOM)) ) {
+ /* Yes, we want to do this */
+ yes_my_citadel_config = 1;
+#ifdef VCARD_SAVES_BY_AIDES_ONLY
+ /* Prevent non-aides from performing registration changes, but ldap is ok. */
+ if ((CC->user.axlevel < AxAideU) && (CC->vcard_updated_by_ldap==0)) {
+ return(1);
+ }
+#endif
+
+ }
+
+ /* Is this a room with an address book in it? */
+ if (CC->room.QRdefaultview == VIEW_ADDRESSBOOK) {
+ yes_any_vcard_room = 1;
+ }
+
+ /* If neither condition exists, don't run this hook. */
+ if ( (!yes_my_citadel_config) && (!yes_any_vcard_room) ) {
+ return(0);
+ }
+
+ /* If this isn't a MIME message, don't bother. */
+ if (msg->cm_format_type != 4) return(0);
+
+ /* Ok, if we got this far, look into the situation further... */
+
+ if (CM_IsEmpty(msg, eMesageText)) return(0);
+
+ mime_parser(CM_RANGE(msg, eMesageText),
+ *vcard_extract_vcard,
+ NULL, NULL,
+ &v, /* user data ptr - put the vcard here */
+ 0
+ );
+
+ if (v == NULL) return(0); /* no vCards were found in this message */
+
+ /* If users cannot create their own accounts, they cannot re-register either. */
+ if ( (yes_my_citadel_config) &&
+ (CtdlGetConfigInt("c_disable_newu")) &&
+ (CC->user.axlevel < AxAideU) &&
+ (CC->vcard_updated_by_ldap==0) )
+ {
+ return(1);
+ }
+
+ vcard_get_prop(v, "fn", 1, 0, 0);
+
+
+ if (yes_my_citadel_config) {
+ /* Bingo! The user is uploading a new vCard, so
+ * delete the old one. First, figure out which user
+ * is being re-registered...
+ */
+ what_user = atol(CC->room.QRname);
+
+ if (what_user == CC->user.usernum) {
+ /* It's the logged in user. That was easy. */
+ memcpy(&usbuf, &CC->user, sizeof(struct ctdluser));
+ }
+
+ else if (CtdlGetUserByNumber(&usbuf, what_user) == 0) {
+ /* We fetched a valid user record */
+ }
+
+ else {
+ /* somebody set up us the bomb! */
+ yes_my_citadel_config = 0;
+ }
+ }
+
+ if (yes_my_citadel_config) {
+ /* Delete the user's old vCard. This would probably
+ * get taken care of by the replication check, but we
+ * want to make sure there is absolutely only one
+ * vCard in the user's config room at all times.
+ *
+ */
+ CtdlDeleteMessages(CC->room.QRname, NULL, 0, "[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$");
+
+ /* Make the author of the message the name of the user. */
+ if (!IsEmptyStr(usbuf.fullname)) {
+ CM_SetField(msg, eAuthor, usbuf.fullname, strlen(usbuf.fullname));
+ }
+ }
+
+ /* Insert or replace RFC2739-compliant free/busy URL */
+ if (yes_my_citadel_config) {
+ sprintf(buf, "http://%s/%s.vfb",
+ CtdlGetConfigStr("c_fqdn"),
+ usbuf.fullname);
+ for (i=0; buf[i]; ++i) {
+ if (buf[i] == ' ') buf[i] = '_';
+ }
+ vcard_set_prop(v, "FBURL;PREF", buf, 0);
+ }
+
+
+ s = vcard_get_prop(v, "UID", 1, 0, 0);
+ if (s == NULL) { /* Note LDAP auth sets UID from the LDAP UUID, use that if it exists. */
+ /* Enforce local UID policy if applicable */
+ if (yes_my_citadel_config) {
+ snprintf(buf, sizeof buf, VCARD_EXT_FORMAT, msg->cm_fields[eAuthor], NODENAME);
+ } else {
+ /* If the vCard has no UID, then give it one. */
+ generate_uuid(buf);
+ }
+ vcard_set_prop(v, "UID", buf, 0);
+ }
+
+
+ /*
+ * Set the EUID of the message to the UID of the vCard.
+ */
+ CM_FlushField(msg, eExclusiveID);
+
+ s = vcard_get_prop(v, "UID", 1, 0, 0);
+ if (!IsEmptyStr(s)) {
+ CM_SetField(msg, eExclusiveID, s, strlen(s));
+ if (CM_IsEmpty(msg, eMsgSubject)) {
+ CM_CopyField(msg, eMsgSubject, eExclusiveID);
+ }
+ }
+
+ /*
+ * Set the Subject to the name in the vCard.
+ */
+ s = vcard_get_prop(v, "FN", 1, 0, 0);
+ if (s == NULL) {
+ s = vcard_get_prop(v, "N", 1, 0, 0);
+ }
+ if (!IsEmptyStr(s)) {
+ CM_SetField(msg, eMsgSubject, s, strlen(s));
+ }
+
+ /* Re-serialize it back into the msg body */
+ ser = vcard_serialize(v);
+ if (!IsEmptyStr(ser)) {
+ StrBuf *buf;
+ long serlen;
+
+ serlen = strlen(ser);
+ buf = NewStrBufPlain(NULL, serlen + 1024);
+
+ StrBufAppendBufPlain(buf, HKEY("Content-type: " VCARD_MIME_TYPE "\r\n\r\n"), 0);
+ StrBufAppendBufPlain(buf, ser, serlen, 0);
+ StrBufAppendBufPlain(buf, HKEY("\r\n"), 0);
+ CM_SetAsFieldSB(msg, eMesageText, &buf);
+ free(ser);
+ }
+
+ /* Now allow the save to complete. */
+ vcard_free(v);
+ return(0);
+}
+
+
+/*
+ * This handler detects whether the user is attempting to save a new
+ * vCard as part of his/her personal configuration, and handles the replace
+ * function accordingly (copy the vCard from the config room to the global
+ * address book).
+ */
+int vcard_upload_aftersave(struct CtdlMessage *msg, struct recptypes *recp) {
+ char *ptr;
+ int linelen;
+ long I;
+ struct vCard *v;
+ int is_UserConf=0;
+ int is_MY_UserConf=0;
+ int is_GAB=0;
+ char roomname[ROOMNAMELEN];
+
+ if (msg->cm_format_type != 4) return(0);
+ if ((!CC->logged_in) && (CC->vcard_updated_by_ldap==0)) return(0); /* Only do this if logged in, or if ldap changed the vcard. */
+
+ /* We're interested in user config rooms only. */
+
+ if ( !IsEmptyStr(CC->room.QRname) &&
+ (strlen(CC->room.QRname) >= 12) &&
+ (!strcasecmp(&CC->room.QRname[11], USERCONFIGROOM)) ) {
+ is_UserConf = 1; /* It's someone's config room */
+ }
+ CtdlMailboxName(roomname, sizeof roomname, &CC->user, USERCONFIGROOM);
+ if (!strcasecmp(CC->room.QRname, roomname)) {
+ is_UserConf = 1;
+ is_MY_UserConf = 1; /* It's MY config room */
+ }
+ if (!strcasecmp(CC->room.QRname, ADDRESS_BOOK_ROOM)) {
+ is_GAB = 1; /* It's the Global Address Book */
+ }
+
+ if (!is_UserConf && !is_GAB) return(0);
+
+ if (CM_IsEmpty(msg, eMesageText))
+ return 0;
+
+ ptr = msg->cm_fields[eMesageText];
+
+ CC->vcard_updated_by_ldap=0; /* As this will write LDAP's previous changes, disallow LDAP change auth until next LDAP change. */
+
+ NewStrBufDupAppendFlush(&CC->StatusMessage, NULL, NULL, 0);
+
+ StrBufPrintf(CC->StatusMessage, "%d\n", LISTING_FOLLOWS);
+
+ while (ptr != NULL) {
+
+ linelen = strcspn(ptr, "\n");
+ if (linelen == 0) return(0); /* end of headers */
+
+ if ( (!strncasecmp(ptr, "Content-type: text/x-vcard", 26))
+ || (!strncasecmp(ptr, "Content-type: text/vcard", 24)) ) {
+ /*
+ * Bingo! The user is uploading a new vCard, so
+ * copy it to the Global Address Book room.
+ */
+
+ I = atol(msg->cm_fields[eVltMsgNum]);
+ if (I <= 0L) return(0);
+
+ /* Store our friendly/display name in memory */
+ if (is_MY_UserConf) {
+ v = vcard_load(msg->cm_fields[eMesageText]);
+ extract_friendly_name(CC->cs_inet_fn, sizeof CC->cs_inet_fn, v);
+ vcard_free(v);
+ }
+
+ if (!is_GAB)
+ { // This is not the GAB
+ /* Put it in the Global Address Book room... */
+ CtdlSaveMsgPointerInRoom(ADDRESS_BOOK_ROOM, I, 1, msg);
+ }
+
+ /* Some sites want an Aide to be notified when a
+ * user registers or re-registers
+ * But if the user was an Aide or was edited by an Aide then we can
+ * Assume they don't need validating.
+ */
+ if (CC->user.axlevel >= AxAideU) {
+ CtdlLockGetCurrentUser();
+ CC->user.flags |= US_REGIS;
+ CtdlPutCurrentUserLock();
+ return (0);
+ }
+
+ set_mm_valid();
+
+ /* ...which also means we need to flag the user */
+ CtdlLockGetCurrentUser();
+ CC->user.flags |= (US_REGIS|US_NEEDVALID);
+ CtdlPutCurrentUserLock();
+
+ return(0);
+ }
+
+ ptr = strchr((char *)ptr, '\n');
+ if (ptr != NULL) ++ptr;
+ }
+
+ return(0);
+}
+
+
+
+/*
+ * back end function used for callbacks
+ */
+void vcard_gu_backend(long supplied_msgnum, void *userdata) {
+ long *msgnum;
+
+ msgnum = (long *) userdata;
+ *msgnum = supplied_msgnum;
+}
+
+
+/*
+ * If this user has a vcard on disk, read it into memory, otherwise allocate
+ * and return an empty vCard.
+ */
+struct vCard *vcard_get_user(struct ctdluser *u) {
+ char hold_rm[ROOMNAMELEN];
+ char config_rm[ROOMNAMELEN];
+ struct CtdlMessage *msg = NULL;
+ struct vCard *v;
+ long VCmsgnum;
+
+ strcpy(hold_rm, CC->room.QRname); /* save current room */
+ CtdlMailboxName(config_rm, sizeof config_rm, u, USERCONFIGROOM);
+
+ if (CtdlGetRoom(&CC->room, config_rm) != 0) {
+ CtdlGetRoom(&CC->room, hold_rm);
+ return vcard_new();
+ }
+
+ /* We want the last (and probably only) vcard in this room */
+ VCmsgnum = (-1);
+ CtdlForEachMessage(MSGS_LAST, 1, NULL, "[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$",
+ NULL, vcard_gu_backend, (void *)&VCmsgnum );
+ CtdlGetRoom(&CC->room, hold_rm); /* return to saved room */
+
+ if (VCmsgnum < 0L) return vcard_new();
+
+ msg = CtdlFetchMessage(VCmsgnum, 1);
+ if (msg == NULL) return vcard_new();
+
+ v = vcard_load(msg->cm_fields[eMesageText]);
+ CM_Free(msg);
+ return v;
+}
+
+
+/*
+ * Store this user's vCard in the appropriate place
+ */
+/*
+ * Write our config to disk
+ */
+void vcard_write_user(struct ctdluser *u, struct vCard *v) {
+ char *ser;
+
+ ser = vcard_serialize(v);
+ if (ser == NULL) {
+ ser = strdup("begin:vcard\r\nend:vcard\r\n");
+ }
+ if (ser == NULL) return;
+
+ /* This handy API function does all the work for us.
+ * NOTE: normally we would want to set that last argument to 1, to
+ * force the system to delete the user's old vCard. But it doesn't
+ * have to, because the vcard_upload_beforesave() hook above
+ * is going to notice what we're trying to do, and delete the old vCard.
+ */
+ CtdlWriteObject(USERCONFIGROOM, /* which room */
+ VCARD_MIME_TYPE, /* MIME type */
+ ser, /* data */
+ strlen(ser)+1, /* length */
+ u, /* which user */
+ 0, /* not binary */
+ 0); /* no flags */
+
+ free(ser);
+}
+
+
+
+/*
+ * Old style "enter registration info" command. This function simply honors
+ * the REGI protocol command, translates the entered parameters into a vCard,
+ * and enters the vCard into the user's configuration.
+ */
+void cmd_regi(char *argbuf) {
+ int a,b,c;
+ char buf[SIZ];
+ struct vCard *my_vcard;
+
+ char tmpaddr[SIZ];
+ char tmpcity[SIZ];
+ char tmpstate[SIZ];
+ char tmpzip[SIZ];
+ char tmpaddress[SIZ];
+ char tmpcountry[SIZ];
+
+ unbuffer_output();
+
+ if (!(CC->logged_in)) {
+ cprintf("%d Not logged in.\n",ERROR + NOT_LOGGED_IN);
+ return;
+ }
+
+ /* If users cannot create their own accounts, they cannot re-register either. */
+ if ( (CtdlGetConfigInt("c_disable_newu")) && (CC->user.axlevel < AxAideU) ) {
+ cprintf("%d Self-service registration is not allowed here.\n",
+ ERROR + HIGHER_ACCESS_REQUIRED);
+ }
+
+ my_vcard = vcard_get_user(&CC->user);
+ strcpy(tmpaddr, "");
+ strcpy(tmpcity, "");
+ strcpy(tmpstate, "");
+ strcpy(tmpzip, "");
+ strcpy(tmpcountry, "USA");
+
+ cprintf("%d Send registration...\n", SEND_LISTING);
+ a=0;
+ while (client_getln(buf, sizeof buf), strcmp(buf,"000")) {
+ if (a==0) vcard_set_prop(my_vcard, "n", buf, 0);
+ if (a==1) strcpy(tmpaddr, buf);
+ if (a==2) strcpy(tmpcity, buf);
+ if (a==3) strcpy(tmpstate, buf);
+ if (a==4) {
+ for (c=0; buf[c]; ++c) {
+ if ((buf[c]>='0') && (buf[c]<='9')) {
+ b = strlen(tmpzip);
+ tmpzip[b] = buf[c];
+ tmpzip[b+1] = 0;
+ }
+ }
+ }
+ if (a==5) vcard_set_prop(my_vcard, "tel", buf, 0);
+ if (a==6) vcard_set_prop(my_vcard, "email;internet", buf, 0);
+ if (a==7) strcpy(tmpcountry, buf);
+ ++a;
+ }
+
+ snprintf(tmpaddress, sizeof tmpaddress, ";;%s;%s;%s;%s;%s",
+ tmpaddr, tmpcity, tmpstate, tmpzip, tmpcountry);
+ vcard_set_prop(my_vcard, "adr", tmpaddress, 0);
+ vcard_write_user(&CC->user, my_vcard);
+ vcard_free(my_vcard);
+}
+
+
+/*
+ * Protocol command to fetch registration info for a user
+ */
+void cmd_greg(char *argbuf)
+{
+ struct ctdluser usbuf;
+ struct vCard *v;
+ char *s;
+ char who[USERNAME_SIZE];
+ char adr[256];
+ char buf[256];
+
+ extract_token(who, argbuf, 0, '|', sizeof who);
+
+ if (!(CC->logged_in)) {
+ cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
+ return;
+ }
+
+ if (!strcasecmp(who,"_SELF_")) strcpy(who,CC->curr_user);
+
+ if ((CC->user.axlevel < AxAideU) && (strcasecmp(who,CC->curr_user))) {
+ cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
+ return;
+ }
+
+ if (CtdlGetUser(&usbuf, who) != 0) {
+ cprintf("%d '%s' not found.\n", ERROR + NO_SUCH_USER, who);
+ return;
+ }
+
+ v = vcard_get_user(&usbuf);
+
+ cprintf("%d %s\n", LISTING_FOLLOWS, usbuf.fullname);
+ cprintf("%ld\n", usbuf.usernum);
+ cprintf("%s\n", usbuf.password);
+ s = vcard_get_prop(v, "n", 1, 0, 0);
+ cprintf("%s\n", s ? s : " "); /* name */
+ s = vcard_get_prop(v, "adr", 1, 0, 0);
+ snprintf(adr, sizeof adr, "%s", s ? s : " "); /* address */
+ extract_token(buf, adr, 2, ';', sizeof buf);
+ cprintf("%s\n", buf); /* street */
+ extract_token(buf, adr, 3, ';', sizeof buf);
+ cprintf("%s\n", buf); /* city */
+ extract_token(buf, adr, 4, ';', sizeof buf);
+ cprintf("%s\n", buf); /* state */
+ extract_token(buf, adr, 5, ';', sizeof buf);
+ cprintf("%s\n", buf); /* zip */
+
+ s = vcard_get_prop(v, "tel", 1, 0, 0);
+ if (s == NULL) s = vcard_get_prop(v, "tel", 1, 0, 0);
+ if (s != NULL) {
+ cprintf("%s\n", s);
+ }
+ else {
+ cprintf(" \n");
+ }
+
+ cprintf("%d\n", usbuf.axlevel);
+
+ s = vcard_get_prop(v, "email;internet", 0, 0, 0);
+ cprintf("%s\n", s ? s : " ");
+ s = vcard_get_prop(v, "adr", 0, 0, 0);
+ snprintf(adr, sizeof adr, "%s", s ? s : " ");/* address... */
+
+ extract_token(buf, adr, 6, ';', sizeof buf);
+ cprintf("%s\n", buf); /* country */
+ cprintf("000\n");
+ vcard_free(v);
+}
+
+
+
+/*
+ * When a user is being created, create his/her vCard.
+ */
+void vcard_newuser(struct ctdluser *usbuf) {
+ char vname[256];
+ char buf[256];
+ int i;
+ struct vCard *v;
+ int need_default_vcard;
+
+ need_default_vcard =1;
+ vcard_fn_to_n(vname, usbuf->fullname, sizeof vname);
+ syslog(LOG_DEBUG, "vcard: converted <%s> to <%s>", usbuf->fullname, vname);
+
+ /* Create and save the vCard */
+ v = vcard_new();
+ if (v == NULL) return;
+ vcard_add_prop(v, "fn", usbuf->fullname);
+ vcard_add_prop(v, "n", vname);
+ vcard_add_prop(v, "adr", "adr:;;_;_;_;00000;__");
+
+#ifdef HAVE_GETPWUID_R
+ /* If using host auth mode, we add an email address based on the login */
+ if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_HOST) {
+ struct passwd pwd;
+ char pwd_buffer[SIZ];
+
+#ifdef SOLARIS_GETPWUID
+ if (getpwuid_r(usbuf->uid, &pwd, pwd_buffer, sizeof pwd_buffer) != NULL) {
+#else // SOLARIS_GETPWUID
+ struct passwd *result = NULL;
+ syslog(LOG_DEBUG, "vcard: searching for uid %d", usbuf->uid);
+ if (getpwuid_r(usbuf->uid, &pwd, pwd_buffer, sizeof pwd_buffer, &result) == 0) {
+#endif // HAVE_GETPWUID_R
+ snprintf(buf, sizeof buf, "%s@%s", pwd.pw_name, CtdlGetConfigStr("c_fqdn"));
+ vcard_add_prop(v, "email;internet", buf);
+ need_default_vcard = 0;
+ }
+ }
+#endif
+
+ /*
+ * Is this an LDAP session? If so, copy various LDAP attributes from the directory entry
+ * into the user's vCard.
+ */
+ if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) {
+ //uid_t ldap_uid;
+ int found_user;
+ char ldap_cn[512];
+ char ldap_dn[512];
+
+ syslog(LOG_DEBUG, "\033[31m FIXME BORK BORK BORK try lookup by uid , or maybe dn?\033[0m");
+
+ found_user = CtdlTryUserLDAP(usbuf->fullname, ldap_dn, sizeof ldap_dn, ldap_cn, sizeof ldap_cn, &usbuf->uid);
+ if (found_user == 0) {
+ if (Ctdl_LDAP_to_vCard(ldap_dn, v)) {
+ /* Allow global address book and internet directory update without login long enough to write this. */
+ CC->vcard_updated_by_ldap++; /* Otherwise we'll only update the user config. */
+ need_default_vcard = 0;
+ syslog(LOG_DEBUG, "vcard: LDAP Created Initial vCard for %s\n",usbuf->fullname);
+ }
+ }
+ }
+
+ if (need_default_vcard!=0) {
+ /* Everyone gets an email address based on their display name */
+ snprintf(buf, sizeof buf, "%s@%s", usbuf->fullname, CtdlGetConfigStr("c_fqdn"));
+ for (i=0; buf[i]; ++i) {
+ if (buf[i] == ' ') buf[i] = '_';
+ }
+ vcard_add_prop(v, "email;internet", buf);
+ }
+ vcard_write_user(usbuf, v);
+ vcard_free(v);
+}
+
+
+/*
+ * Get Valid Screen Names
+ */
+void cmd_gvsn(char *argbuf)
+{
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ cprintf("%d valid screen names:\n", LISTING_FOLLOWS);
+ cprintf("%s\n", CC->user.fullname);
+ if ( (!IsEmptyStr(CC->cs_inet_fn)) && (strcasecmp(CC->user.fullname, CC->cs_inet_fn)) ) {
+ cprintf("%s\n", CC->cs_inet_fn);
+ }
+ cprintf("000\n");
+}
+
+
+/*
+ * Get Valid Email Addresses
+ * FIXME this doesn't belong in serv_vcard.c anymore , maybe move it to internet_addressing.c
+ */
+void cmd_gvea(char *argbuf)
+{
+ int num_secondary_emails = 0;
+ int i;
+ char buf[256];
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ cprintf("%d valid email addresses:\n", LISTING_FOLLOWS);
+ if (!IsEmptyStr(CC->cs_inet_email)) {
+ cprintf("%s\n", CC->cs_inet_email);
+ }
+ if (!IsEmptyStr(CC->cs_inet_other_emails)) {
+ num_secondary_emails = num_tokens(CC->cs_inet_other_emails, '|');
+ for (i=0; i<num_secondary_emails; ++i) {
+ extract_token(buf, CC->cs_inet_other_emails,i,'|',sizeof CC->cs_inet_other_emails);
+ cprintf("%s\n", buf);
+ }
+ }
+ cprintf("000\n");
+}
+
+
+/*
+ * Callback function for cmd_dvca() that hunts for vCard content types
+ * and outputs any email addresses found within.
+ */
+void dvca_mime_callback(char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+ char *cbid, void *cbuserdata) {
+
+ struct vCard *v;
+ char displayname[256] = "";
+ int displayname_len;
+ char emailaddr[256] = "";
+ int i;
+ int has_commas = 0;
+
+ if ( (strcasecmp(cbtype, "text/vcard")) && (strcasecmp(cbtype, "text/x-vcard")) ) {
+ return;
+ }
+
+ v = vcard_load(content);
+ if (v == NULL) return;
+
+ extract_friendly_name(displayname, sizeof displayname, v);
+ extract_inet_email_addrs(emailaddr, sizeof emailaddr, NULL, 0, v, 0);
+
+ displayname_len = strlen(displayname);
+ for (i=0; i<displayname_len; ++i) {
+ if (displayname[i] == '\"') displayname[i] = ' ';
+ if (displayname[i] == ';') displayname[i] = ',';
+ if (displayname[i] == ',') has_commas = 1;
+ }
+ striplt(displayname);
+
+ cprintf("%s%s%s <%s>\n",
+ (has_commas ? "\"" : ""),
+ displayname,
+ (has_commas ? "\"" : ""),
+ emailaddr
+ );
+
+ vcard_free(v);
+}
+
+
+/*
+ * Back end callback function for cmd_dvca()
+ *
+ * It's basically just passed a list of message numbers, which we're going
+ * to fetch off the disk and then pass along to the MIME parser via another
+ * layer of callback...
+ */
+void dvca_callback(long msgnum, void *userdata) {
+ struct CtdlMessage *msg = NULL;
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) return;
+ mime_parser(CM_RANGE(msg, eMesageText),
+ *dvca_mime_callback, /* callback function */
+ NULL, NULL,
+ NULL, /* user data */
+ 0
+ );
+ CM_Free(msg);
+}
+
+
+/*
+ * Dump VCard Addresses
+ */
+void cmd_dvca(char *argbuf)
+{
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ cprintf("%d addresses:\n", LISTING_FOLLOWS);
+ CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL, dvca_callback, NULL);
+ cprintf("000\n");
+}
+
+
+/*
+ * Query Directory
+ */
+void cmd_qdir(char *argbuf) {
+ char citadel_addr[256];
+ char internet_addr[256];
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ extract_token(internet_addr, argbuf, 0, '|', sizeof internet_addr);
+
+ if (CtdlDirectoryLookup(citadel_addr, internet_addr, sizeof citadel_addr) != 0) {
+ cprintf("%d %s was not found.\n",
+ ERROR + NO_SUCH_USER, internet_addr);
+ return;
+ }
+
+ cprintf("%d %s\n", CIT_OK, citadel_addr);
+}
+
+
+/*
+ * Query Directory, in fact an alias to match postfix tcp auth.
+ */
+void check_get(void) {
+ char internet_addr[256];
+
+ char cmdbuf[SIZ];
+
+ time(&CC->lastcmd);
+ memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
+ if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
+ syslog(LOG_ERR, "vcard: client disconnected: ending session.");
+ CC->kill_me = KILLME_CLIENT_DISCONNECTED;
+ return;
+ }
+ syslog(LOG_INFO, ": %s", cmdbuf);
+ while (strlen(cmdbuf) < 3) strcat(cmdbuf, " ");
+ syslog(LOG_INFO, "[ %s]", cmdbuf);
+
+ if (strncasecmp(cmdbuf, "GET ", 4)==0)
+ {
+ struct recptypes *rcpt;
+ char *argbuf = &cmdbuf[4];
+
+ extract_token(internet_addr, argbuf, 0, '|', sizeof internet_addr);
+ rcpt = validate_recipients(internet_addr, NULL, CHECK_EXIST);
+ if ( (rcpt != NULL) &&
+ (
+ (*rcpt->recp_local != '\0') ||
+ (*rcpt->recp_room != '\0')
+ )
+ ) {
+ cprintf("200 OK %s\n", internet_addr);
+ syslog(LOG_INFO, "vcard: sending 200 OK for the room %s", rcpt->display_recp);
+ }
+ else
+ {
+ cprintf("500 REJECT no one here by that name.\n");
+
+ syslog(LOG_INFO, "vcard: sending 500 REJECT no one here by that name: %s", internet_addr);
+ }
+ if (rcpt != NULL)
+ free_recipients(rcpt);
+ }
+ else {
+ cprintf("500 REJECT invalid Query.\n");
+ syslog(LOG_INFO, "vcard: sending 500 REJECT invalid query: %s", internet_addr);
+ }
+}
+
+
+void check_get_greeting(void) {
+/* dummy function, we have no greeting in this very simple protocol. */
+}
+
+
+/*
+ * We don't know if the Contacts room exists so we just create it at login
+ */
+void vcard_CtdlCreateRoom(void)
+{
+ struct ctdlroom qr;
+ visit vbuf;
+
+ /* Create the calendar room if it doesn't already exist */
+ CtdlCreateRoom(USERCONTACTSROOM, 4, "", 0, 1, 0, VIEW_ADDRESSBOOK);
+
+ /* Set expiration policy to manual; otherwise objects will be lost! */
+ if (CtdlGetRoomLock(&qr, USERCONTACTSROOM)) {
+ syslog(LOG_ERR, "vcard: couldn't get the user CONTACTS room!");
+ return;
+ }
+ qr.QRep.expire_mode = EXPIRE_MANUAL;
+ qr.QRdefaultview = VIEW_ADDRESSBOOK; /* 2 = address book view */
+ CtdlPutRoomLock(&qr);
+
+ /* Set the view to a calendar view */
+ CtdlGetRelationship(&vbuf, &CC->user, &qr);
+ vbuf.v_view = 2; /* 2 = address book view */
+ CtdlSetRelationship(&vbuf, &CC->user, &qr);
+
+ return;
+}
+
+
+
+
+/*
+ * When a user logs in...
+ */
+void vcard_session_login_hook(void) {
+ struct vCard *v = NULL;
+
+ /*
+ * Is this an LDAP session? If so, copy various LDAP attributes from the directory entry
+ * into the user's vCard.
+ */
+ if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) {
+ v = vcard_get_user(&CC->user);
+ if (v) {
+ if (Ctdl_LDAP_to_vCard(CC->ldap_dn, v)) {
+ CC->vcard_updated_by_ldap++; /* Make sure changes make it to the global address book and internet directory, not just the user config. */
+ syslog(LOG_DEBUG, "vcard: LDAP Detected vcard change");
+ vcard_write_user(&CC->user, v);
+ }
+ }
+ }
+
+ /*
+ * Extract the user's friendly/screen name
+ * These are inserted into the session data for various message entry commands to use.
+ */
+ v = vcard_get_user(&CC->user);
+ if (v) {
+ extract_friendly_name(CC->cs_inet_fn, sizeof CC->cs_inet_fn, v);
+ vcard_free(v);
+ }
+
+ /*
+ * Create the user's 'Contacts' room (personal address book) if it doesn't already exist.
+ */
+ vcard_CtdlCreateRoom();
+}
+
+
+/*
+ * Turn an arbitrary RFC822 address into a struct vCard for possible
+ * inclusion into an address book.
+ */
+struct vCard *vcard_new_from_rfc822_addr(char *addr) {
+ struct vCard *v;
+ char user[256], node[256], name[256], email[256], n[256], uid[256];
+ int i;
+
+ v = vcard_new();
+ if (v == NULL) return(NULL);
+
+ process_rfc822_addr(addr, user, node, name);
+ vcard_set_prop(v, "fn", name, 0);
+
+ vcard_fn_to_n(n, name, sizeof n);
+ vcard_set_prop(v, "n", n, 0);
+
+ snprintf(email, sizeof email, "%s@%s", user, node);
+ vcard_set_prop(v, "email;internet", email, 0);
+
+ snprintf(uid, sizeof uid, "collected: %s %s@%s", name, user, node);
+ for (i=0; uid[i]; ++i) {
+ if (isspace(uid[i])) uid[i] = '_';
+ uid[i] = tolower(uid[i]);
+ }
+ vcard_set_prop(v, "UID", uid, 0);
+
+ return(v);
+}
+
+
+/*
+ * This is called by store_harvested_addresses() to remove from the
+ * list any addresses we already have in our address book.
+ */
+void strip_addresses_already_have(long msgnum, void *userdata) {
+ char *collected_addresses;
+ struct CtdlMessage *msg = NULL;
+ struct vCard *v;
+ char *value = NULL;
+ int i, j;
+ char addr[256], user[256], node[256], name[256];
+
+ collected_addresses = (char *)userdata;
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) return;
+ v = vcard_load(msg->cm_fields[eMesageText]);
+ CM_Free(msg);
+
+ i = 0;
+ while (value = vcard_get_prop(v, "email", 1, i++, 0), value != NULL) {
+
+ for (j=0; j<num_tokens(collected_addresses, ','); ++j) {
+ extract_token(addr, collected_addresses, j, ',', sizeof addr);
+
+ /* Remove the address if we already have it! */
+ process_rfc822_addr(addr, user, node, name);
+ snprintf(addr, sizeof addr, "%s@%s", user, node);
+ if (!strcasecmp(value, addr)) {
+ remove_token(collected_addresses, j, ',');
+ }
+ }
+
+ }
+
+ vcard_free(v);
+}
+
+
+
+/*
+ * Back end function for store_harvested_addresses()
+ */
+void store_this_ha(struct addresses_to_be_filed *aptr) {
+ struct CtdlMessage *vmsg = NULL;
+ char *ser = NULL;
+ struct vCard *v = NULL;
+ char recipient[256];
+ int i;
+
+ /* First remove any addresses we already have in the address book */
+ CtdlUserGoto(aptr->roomname, 0, 0, NULL, NULL, NULL, NULL);
+ CtdlForEachMessage(MSGS_ALL, 0, NULL, "[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$", NULL,
+ strip_addresses_already_have, aptr->collected_addresses);
+
+ if (!IsEmptyStr(aptr->collected_addresses))
+ for (i=0; i<num_tokens(aptr->collected_addresses, ','); ++i) {
+
+ /* Make a vCard out of each address */
+ extract_token(recipient, aptr->collected_addresses, i, ',', sizeof recipient);
+ striplt(recipient);
+ v = vcard_new_from_rfc822_addr(recipient);
+ if (v != NULL) {
+ const char *s;
+ vmsg = malloc(sizeof(struct CtdlMessage));
+ memset(vmsg, 0, sizeof(struct CtdlMessage));
+ vmsg->cm_magic = CTDLMESSAGE_MAGIC;
+ vmsg->cm_anon_type = MES_NORMAL;
+ vmsg->cm_format_type = FMT_RFC822;
+ CM_SetField(vmsg, eAuthor, HKEY("Citadel"));
+ s = vcard_get_prop(v, "UID", 1, 0, 0);
+ if (!IsEmptyStr(s)) {
+ CM_SetField(vmsg, eExclusiveID, s, strlen(s));
+ }
+ ser = vcard_serialize(v);
+ if (ser != NULL) {
+ StrBuf *buf;
+ long serlen;
+
+ serlen = strlen(ser);
+ buf = NewStrBufPlain(NULL, serlen + 1024);
+
+ StrBufAppendBufPlain(buf, HKEY("Content-type: " VCARD_MIME_TYPE "\r\n\r\n"), 0);
+ StrBufAppendBufPlain(buf, ser, serlen, 0);
+ StrBufAppendBufPlain(buf, HKEY("\r\n"), 0);
+ CM_SetAsFieldSB(vmsg, eMesageText, &buf);
+ free(ser);
+ }
+ vcard_free(v);
+
+ syslog(LOG_DEBUG, "vcard: adding contact: %s", recipient);
+ CtdlSubmitMsg(vmsg, NULL, aptr->roomname);
+ CM_Free(vmsg);
+ }
+ }
+
+ free(aptr->roomname);
+ free(aptr->collected_addresses);
+ free(aptr);
+}
+
+
+/*
+ * When a user sends a message, we may harvest one or more email addresses
+ * from the recipient list to be added to the user's address book. But we
+ * want to do this asynchronously so it doesn't keep the user waiting.
+ */
+void store_harvested_addresses(void) {
+
+ struct addresses_to_be_filed *aptr = NULL;
+
+ if (atbf == NULL) return;
+
+ begin_critical_section(S_ATBF);
+ while (atbf != NULL) {
+ aptr = atbf;
+ atbf = atbf->next;
+ end_critical_section(S_ATBF);
+ store_this_ha(aptr);
+ begin_critical_section(S_ATBF);
+ }
+ end_critical_section(S_ATBF);
+}
+
+
+/*
+ * Function to output vCard data as plain text. Nobody uses MSG0 anymore, so
+ * really this is just so we expose the vCard data to the full text indexer.
+ */
+void vcard_fixed_output(char *ptr, int len) {
+ char *serialized_vcard;
+ struct vCard *v;
+ char *key, *value;
+ int i = 0;
+
+ serialized_vcard = malloc(len + 1);
+ safestrncpy(serialized_vcard, ptr, len+1);
+ v = vcard_load(serialized_vcard);
+ free(serialized_vcard);
+
+ i = 0;
+ while (key = vcard_get_prop(v, "", 0, i, 1), key != NULL) {
+ value = vcard_get_prop(v, "", 0, i++, 0);
+ cprintf("%s\n", value);
+ }
+
+ vcard_free(v);
+}
+
+
+const char *CitadelServiceDICT_TCP="DICT_TCP";
+
+char *ctdl_module_init_vcard(void) {
+ struct ctdlroom qr;
+
+ if (!threading) {
+ CtdlRegisterSessionHook(vcard_session_login_hook, EVT_LOGIN, PRIO_LOGIN + 70);
+ CtdlRegisterMessageHook(vcard_upload_beforesave, EVT_BEFORESAVE);
+ CtdlRegisterMessageHook(vcard_upload_aftersave, EVT_AFTERSAVE);
+ CtdlRegisterProtoHook(cmd_regi, "REGI", "Enter registration info");
+ CtdlRegisterProtoHook(cmd_greg, "GREG", "Get registration info");
+ CtdlRegisterProtoHook(cmd_qdir, "QDIR", "Query Directory");
+ CtdlRegisterProtoHook(cmd_gvsn, "GVSN", "Get Valid Screen Names");
+ CtdlRegisterProtoHook(cmd_gvea, "GVEA", "Get Valid Email Addresses");
+ CtdlRegisterProtoHook(cmd_dvca, "DVCA", "Dump VCard Addresses");
+ CtdlRegisterUserHook(vcard_newuser, EVT_NEWUSER);
+ CtdlRegisterSessionHook(store_harvested_addresses, EVT_TIMER, PRIO_CLEANUP + 470);
+ CtdlRegisterFixedOutputHook("text/x-vcard", vcard_fixed_output);
+ CtdlRegisterFixedOutputHook("text/vcard", vcard_fixed_output);
+
+ /* Create the Global Address Book room if necessary */
+ CtdlCreateRoom(ADDRESS_BOOK_ROOM, 3, "", 0, 1, 0, VIEW_ADDRESSBOOK);
+
+ /* Set expiration policy to manual; otherwise objects will be lost! */
+ if (!CtdlGetRoomLock(&qr, ADDRESS_BOOK_ROOM)) {
+ qr.QRep.expire_mode = EXPIRE_MANUAL;
+ qr.QRdefaultview = VIEW_ADDRESSBOOK; // 2 = address book view
+ CtdlPutRoomLock(&qr);
+ }
+
+ /* for postfix tcpdict */
+ CtdlRegisterServiceHook(CtdlGetConfigInt("c_pftcpdict_port"), // Postfix
+ NULL,
+ check_get_greeting,
+ check_get,
+ NULL,
+ CitadelServiceDICT_TCP
+ );
+ }
+
+ /* return our module name for the log */
+ return "vcard";
+}
--- /dev/null
+/*
+ * Server-side module for Wiki rooms. This handles things like version control.
+ *
+ * Copyright (c) 2009-2022 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.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../control.h"
+#include "../../user_ops.h"
+#include "../../room_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../euidindex.h"
+#include "../../ctdl_module.h"
+
+/*
+ * Data passed back and forth between wiki_rev() and its MIME parser callback
+ */
+struct HistoryEraserCallBackData {
+ char *tempfilename; /* name of temp file being patched */
+ char *stop_when; /* stop when we hit this uuid */
+ int done; /* set to nonzero when we're done patching */
+};
+
+/*
+ * Name of the temporary room we create to store old revisions when someone requests them.
+ * We put it in an invalid namespace so the DAP cleans up after us later.
+ */
+char *wwm = "9999999999.WikiWaybackMachine";
+
+/*
+ * Before allowing a wiki page save to execute, we have to perform version control.
+ * This involves fetching the old version of the page if it exists.
+ */
+int wiki_upload_beforesave(struct CtdlMessage *msg, struct recptypes *recp) {
+ long old_msgnum = (-1L);
+ struct CtdlMessage *old_msg = NULL;
+ long history_msgnum = (-1L);
+ struct CtdlMessage *history_msg = NULL;
+ char diff_old_filename[PATH_MAX];
+ char diff_new_filename[PATH_MAX];
+ char diff_out_filename[PATH_MAX];
+ char diff_cmd[PATH_MAX];
+ FILE *fp;
+ int rv;
+ char history_page[1024];
+ long history_page_len;
+ char boundary[256];
+ char prefixed_boundary[258];
+ char buf[1024];
+ char *diffbuf = NULL;
+ size_t diffbuf_len = 0;
+ char *ptr = NULL;
+ long newmsgid;
+ StrBuf *msgidbuf;
+
+ if (!CC->logged_in) return(0); /* Only do this if logged in. */
+
+ /* Is this a room with a Wiki in it, don't run this hook. */
+ if (CC->room.QRdefaultview != VIEW_WIKI) {
+ return(0);
+ }
+
+ /* If this isn't a MIME message, don't bother. */
+ if (msg->cm_format_type != 4) return(0);
+
+ /* If there's no EUID we can't do this. Reject the post. */
+ if (CM_IsEmpty(msg, eExclusiveID)) return(1);
+
+ newmsgid = get_new_message_number();
+ msgidbuf = NewStrBuf();
+ StrBufPrintf(msgidbuf, "%08lX-%08lX@%s/%s",
+ (long unsigned int) time(NULL),
+ (long unsigned int) newmsgid,
+ CtdlGetConfigStr("c_fqdn"),
+ msg->cm_fields[eExclusiveID]
+ );
+
+ CM_SetAsFieldSB(msg, emessageId, &msgidbuf);
+
+ history_page_len = snprintf(history_page, sizeof history_page,
+ "%s_HISTORY_", msg->cm_fields[eExclusiveID]);
+
+ /* Make sure we're saving a real wiki page rather than a wiki history page.
+ * This is important in order to avoid recursing infinitely into this hook.
+ */
+ if ( (msg->cm_lengths[eExclusiveID] >= 9)
+ && (!strcasecmp(&msg->cm_fields[eExclusiveID][msg->cm_lengths[eExclusiveID]-9], "_HISTORY_"))
+ ) {
+ syslog(LOG_DEBUG, "History page not being historied\n");
+ return(0);
+ }
+
+ /* If there's no message text, obviously this is all b0rken and shouldn't happen at all */
+ if (CM_IsEmpty(msg, eMesageText)) return(0);
+
+ /* Set the message subject identical to the page name */
+ CM_CopyField(msg, eMsgSubject, eExclusiveID);
+
+ /* See if we can retrieve the previous version. */
+ old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CC->room);
+ if (old_msgnum > 0L) {
+ old_msg = CtdlFetchMessage(old_msgnum, 1);
+ }
+ else {
+ old_msg = NULL;
+ }
+
+ if ((old_msg != NULL) && (CM_IsEmpty(old_msg, eMesageText))) { /* old version is corrupt? */
+ CM_Free(old_msg);
+ old_msg = NULL;
+ }
+
+ /* If no changes were made, don't bother saving it again */
+ if ((old_msg != NULL) && (!strcmp(msg->cm_fields[eMesageText], old_msg->cm_fields[eMesageText]))) {
+ CM_Free(old_msg);
+ return(1);
+ }
+
+ /*
+ * Generate diffs
+ */
+ CtdlMakeTempFileName(diff_old_filename, sizeof diff_old_filename);
+ CtdlMakeTempFileName(diff_new_filename, sizeof diff_new_filename);
+ CtdlMakeTempFileName(diff_out_filename, sizeof diff_out_filename);
+
+ if (old_msg != NULL) {
+ fp = fopen(diff_old_filename, "w");
+ rv = fwrite(old_msg->cm_fields[eMesageText], old_msg->cm_lengths[eMesageText], 1, fp);
+ fclose(fp);
+ CM_Free(old_msg);
+ }
+
+ fp = fopen(diff_new_filename, "w");
+ rv = fwrite(msg->cm_fields[eMesageText], msg->cm_lengths[eMesageText], 1, fp);
+ fclose(fp);
+
+ snprintf(diff_cmd, sizeof diff_cmd,
+ "diff -u %s %s >%s",
+ diff_new_filename,
+ ((old_msg != NULL) ? diff_old_filename : "/dev/null"),
+ diff_out_filename
+ );
+ syslog(LOG_DEBUG, "diff cmd: %s", diff_cmd);
+ rv = system(diff_cmd);
+ syslog(LOG_DEBUG, "diff cmd returned %d", rv);
+
+ diffbuf_len = 0;
+ diffbuf = NULL;
+ fp = fopen(diff_out_filename, "r");
+ if (fp == NULL) {
+ fp = fopen("/dev/null", "r");
+ }
+ if (fp != NULL) {
+ fseek(fp, 0L, SEEK_END);
+ diffbuf_len = ftell(fp);
+ fseek(fp, 0L, SEEK_SET);
+ diffbuf = malloc(diffbuf_len + 1);
+ fread(diffbuf, diffbuf_len, 1, fp);
+ diffbuf[diffbuf_len] = '\0';
+ fclose(fp);
+ }
+
+ syslog(LOG_DEBUG, "diff length is "SIZE_T_FMT" bytes", diffbuf_len);
+
+ unlink(diff_old_filename);
+ unlink(diff_new_filename);
+ unlink(diff_out_filename);
+
+ /* Determine whether this was a bogus (empty) edit */
+ if ((diffbuf_len = 0) && (diffbuf != NULL)) {
+ free(diffbuf);
+ diffbuf = NULL;
+ }
+ if (diffbuf == NULL) {
+ return(1); /* No changes at all? Abandon the post entirely! */
+ }
+
+ /* Now look for the existing edit history */
+
+ history_msgnum = CtdlLocateMessageByEuid(history_page, &CC->room);
+ history_msg = NULL;
+ if (history_msgnum > 0L) {
+ history_msg = CtdlFetchMessage(history_msgnum, 1);
+ }
+
+ /* Create a new history message if necessary */
+ if (history_msg == NULL) {
+ char *buf;
+ long len;
+
+ history_msg = malloc(sizeof(struct CtdlMessage));
+ memset(history_msg, 0, sizeof(struct CtdlMessage));
+ history_msg->cm_magic = CTDLMESSAGE_MAGIC;
+ history_msg->cm_anon_type = MES_NORMAL;
+ history_msg->cm_format_type = FMT_RFC822;
+ CM_SetField(history_msg, eAuthor, HKEY("Citadel"));
+ if (!IsEmptyStr(CC->room.QRname)){
+ CM_SetField(history_msg, eRecipient, CC->room.QRname, strlen(CC->room.QRname));
+ }
+ CM_SetField(history_msg, eExclusiveID, history_page, history_page_len);
+ CM_SetField(history_msg, eMsgSubject, history_page, history_page_len);
+ CM_SetField(history_msg, eSuppressIdx, HKEY("1")); /* suppress full text indexing */
+ snprintf(boundary, sizeof boundary, "Citadel--Multipart--%04x--%08lx", getpid(), time(NULL));
+ buf = (char*) malloc(1024);
+ len = snprintf(buf, 1024,
+ "Content-type: multipart/mixed; boundary=\"%s\"\n\n"
+ "This is a Citadel wiki history encoded as multipart MIME.\n"
+ "Each part is comprised of a diff script representing one change set.\n"
+ "\n"
+ "--%s--\n",
+ boundary, boundary
+ );
+ CM_SetAsField(history_msg, eMesageText, &buf, len);
+ }
+
+ /* Update the history message (regardless of whether it's new or existing) */
+
+ /* Remove the Message-ID from the old version of the history message. This will cause a brand
+ * new one to be generated, avoiding an uninitentional hit of the loop zapper when we replicate.
+ */
+ CM_FlushField(history_msg, emessageId);
+
+ /* Figure out the boundary string. We do this even when we generated the
+ * boundary string in the above code, just to be safe and consistent.
+ */
+ *boundary = '\0';
+
+ ptr = history_msg->cm_fields[eMesageText];
+ do {
+ ptr = memreadline(ptr, buf, sizeof buf);
+ if (*ptr != 0) {
+ striplt(buf);
+ if (!IsEmptyStr(buf) && (!strncasecmp(buf, "Content-type:", 13))) {
+ if (
+ (bmstrcasestr(buf, "multipart") != NULL)
+ && (bmstrcasestr(buf, "boundary=") != NULL)
+ ) {
+ safestrncpy(boundary, bmstrcasestr(buf, "\""), sizeof boundary);
+ char *qu;
+ qu = strchr(boundary, '\"');
+ if (qu) {
+ strcpy(boundary, ++qu);
+ }
+ qu = strchr(boundary, '\"');
+ if (qu) {
+ *qu = 0;
+ }
+ }
+ }
+ }
+ } while ( (IsEmptyStr(boundary)) && (*ptr != 0) );
+
+ /*
+ * Now look for the first boundary. That is where we need to insert our fun.
+ */
+ if (!IsEmptyStr(boundary)) {
+ char *MsgText;
+ long MsgTextLen;
+ time_t Now = time(NULL);
+
+ snprintf(prefixed_boundary, sizeof(prefixed_boundary), "--%s", boundary);
+
+ CM_GetAsField(history_msg, eMesageText, &MsgText, &MsgTextLen);
+
+ ptr = bmstrcasestr(MsgText, prefixed_boundary);
+ if (ptr != NULL) {
+ StrBuf *NewMsgText;
+ char uuid[64];
+ char memo[512];
+ long memolen;
+ char encoded_memo[1024];
+
+ NewMsgText = NewStrBufPlain(NULL, MsgTextLen + diffbuf_len + 1024);
+
+ generate_uuid(uuid);
+ memolen = snprintf(memo, sizeof(memo), "%s|%ld|%s|%s",
+ uuid,
+ Now,
+ CC->user.fullname,
+ CtdlGetConfigStr("c_nodename"));
+
+ memolen = CtdlEncodeBase64(encoded_memo, memo, memolen, 0);
+
+ StrBufAppendBufPlain(NewMsgText, HKEY("--"), 0);
+ StrBufAppendBufPlain(NewMsgText, boundary, -1, 0);
+ StrBufAppendBufPlain(
+ NewMsgText,
+ HKEY("\n"
+ "Content-type: text/plain\n"
+ "Content-Disposition: inline; filename=\""), 0);
+
+ StrBufAppendBufPlain(NewMsgText, encoded_memo, memolen, 0);
+
+ StrBufAppendBufPlain(
+ NewMsgText,
+ HKEY("\"\n"
+ "Content-Transfer-Encoding: 8bit\n"
+ "\n"), 0);
+
+ StrBufAppendBufPlain(NewMsgText, diffbuf, diffbuf_len, 0);
+ StrBufAppendBufPlain(NewMsgText, HKEY("\n"), 0);
+
+ StrBufAppendBufPlain(NewMsgText, ptr, MsgTextLen - (ptr - MsgText), 0);
+ free(MsgText);
+ CM_SetAsFieldSB(history_msg, eMesageText, &NewMsgText);
+ }
+ else
+ {
+ CM_SetAsField(history_msg, eMesageText, &MsgText, MsgTextLen);
+ }
+
+ CM_SetFieldLONG(history_msg, eTimestamp, Now);
+
+ CtdlSubmitMsg(history_msg, NULL, "");
+ }
+ else {
+ syslog(LOG_ALERT, "Empty boundary string in history message. No history!\n");
+ }
+
+ free(diffbuf);
+ CM_Free(history_msg);
+ return(0);
+}
+
+
+/*
+ * MIME Parser callback for wiki_history()
+ *
+ * The "filename" field will contain a memo field. All we have to do is decode
+ * the base64 and output it. The data is already in a delimited format suitable
+ * for our client protocol.
+ */
+void wiki_history_callback(char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length,
+ char *encoding, char *cbid, void *cbuserdata)
+{
+ char memo[1024];
+
+ CtdlDecodeBase64(memo, filename, strlen(filename));
+ cprintf("%s\n", memo);
+}
+
+
+/*
+ * Fetch a list of revisions for a particular wiki page
+ */
+void wiki_history(char *pagename) {
+ int r;
+ char history_page_name[270];
+ long msgnum;
+ struct CtdlMessage *msg;
+
+ r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
+ if (r != om_ok) {
+ if (r == om_not_logged_in) {
+ cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
+ }
+ else {
+ cprintf("%d An unknown error has occurred.\n", ERROR);
+ }
+ return;
+ }
+
+ snprintf(history_page_name, sizeof history_page_name, "%s_HISTORY_", pagename);
+ msgnum = CtdlLocateMessageByEuid(history_page_name, &CC->room);
+ if (msgnum > 0L) {
+ msg = CtdlFetchMessage(msgnum, 1);
+ }
+ else {
+ msg = NULL;
+ }
+
+ if ((msg != NULL) && CM_IsEmpty(msg, eMesageText)) {
+ CM_Free(msg);
+ msg = NULL;
+ }
+
+ if (msg == NULL) {
+ cprintf("%d Revision history for '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename);
+ return;
+ }
+
+
+ cprintf("%d Revision history for '%s'\n", LISTING_FOLLOWS, pagename);
+ mime_parser(CM_RANGE(msg, eMesageText), *wiki_history_callback, NULL, NULL, NULL, 0);
+ cprintf("000\n");
+
+ CM_Free(msg);
+ return;
+}
+
+/*
+ * MIME Parser callback for wiki_rev()
+ *
+ * The "filename" field will contain a memo field, which includes (among other things)
+ * the uuid of this revision. After we hit the desired revision, we stop processing.
+ *
+ * The "content" filed will contain "diff" output suitable for applying via "patch"
+ * to our temporary file.
+ */
+void wiki_rev_callback(char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length,
+ char *encoding, char *cbid, void *cbuserdata)
+{
+ struct HistoryEraserCallBackData *hecbd = (struct HistoryEraserCallBackData *)cbuserdata;
+ char memo[1024];
+ char this_rev[256];
+ FILE *fp;
+ char *ptr = NULL;
+ char buf[1024];
+
+ /* Did a previous callback already indicate that we've reached our target uuid?
+ * If so, don't process anything else.
+ */
+ if (hecbd->done) {
+ return;
+ }
+
+ CtdlDecodeBase64(memo, filename, strlen(filename));
+ extract_token(this_rev, memo, 0, '|', sizeof this_rev);
+ striplt(this_rev);
+
+ /* Perform the patch */
+ fp = popen("patch -f -s -p0 -r /dev/null >/dev/null 2>/dev/null", "w");
+ if (fp) {
+ /* Replace the filenames in the patch with the tempfilename we're actually tweaking */
+ fprintf(fp, "--- %s\n", hecbd->tempfilename);
+ fprintf(fp, "+++ %s\n", hecbd->tempfilename);
+
+ ptr = (char *)content;
+ int linenum = 0;
+ do {
+ ++linenum;
+ ptr = memreadline(ptr, buf, sizeof buf);
+ if (*ptr != 0) {
+ if (linenum <= 2) {
+ /* skip the first two lines; they contain bogus filenames */
+ }
+ else {
+ fprintf(fp, "%s\n", buf);
+ }
+ }
+ } while ((*ptr != 0) && (ptr < ((char*)content + length)));
+ if (pclose(fp) != 0) {
+ syslog(LOG_ERR, "pclose() returned an error - patch failed\n");
+ }
+ }
+
+ if (!strcasecmp(this_rev, hecbd->stop_when)) {
+ /* Found our target rev. Tell any subsequent callbacks to suppress processing. */
+ syslog(LOG_DEBUG, "Target revision has been reached -- stop patching.\n");
+ hecbd->done = 1;
+ }
+}
+
+
+/*
+ * Fetch a specific revision of a wiki page. The "operation" string may be set to "fetch" in order
+ * to simply fetch the desired revision and store it in a temporary location for viewing, or "revert"
+ * to revert the currently active page to that revision.
+ */
+void wiki_rev(char *pagename, char *rev, char *operation)
+{
+ int r;
+ char history_page_name[270];
+ long msgnum;
+ char temp[PATH_MAX];
+ struct CtdlMessage *msg;
+ FILE *fp;
+ struct HistoryEraserCallBackData hecbd;
+ long len = 0L;
+ int rv;
+
+ r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
+ if (r != om_ok) {
+ if (r == om_not_logged_in) {
+ cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
+ }
+ else {
+ cprintf("%d An unknown error has occurred.\n", ERROR);
+ }
+ return;
+ }
+
+ if (!strcasecmp(operation, "revert")) {
+ r = CtdlDoIHavePermissionToPostInThisRoom(temp, sizeof temp, POST_LOGGED_IN, 0);
+ if (r != 0) {
+ cprintf("%d %s\n", r, temp);
+ return;
+ }
+ }
+
+ /* Begin by fetching the current version of the page. We're going to patch
+ * backwards through the diffs until we get the one we want.
+ */
+ msgnum = CtdlLocateMessageByEuid(pagename, &CC->room);
+ if (msgnum > 0L) {
+ msg = CtdlFetchMessage(msgnum, 1);
+ }
+ else {
+ msg = NULL;
+ }
+
+ if ((msg != NULL) && CM_IsEmpty(msg, eMesageText)) {
+ CM_Free(msg);
+ msg = NULL;
+ }
+
+ if (msg == NULL) {
+ cprintf("%d Page '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename);
+ return;
+ }
+
+ /* Output it to a temporary file */
+
+ CtdlMakeTempFileName(temp, sizeof temp);
+ fp = fopen(temp, "w");
+ if (fp != NULL) {
+ r = fwrite(msg->cm_fields[eMesageText], msg->cm_lengths[eMesageText], 1, fp);
+ fclose(fp);
+ }
+ else {
+ syslog(LOG_ERR, "%s: %m", temp);
+ }
+ CM_Free(msg);
+
+ /* Get the revision history */
+
+ snprintf(history_page_name, sizeof history_page_name, "%s_HISTORY_", pagename);
+ msgnum = CtdlLocateMessageByEuid(history_page_name, &CC->room);
+ if (msgnum > 0L) {
+ msg = CtdlFetchMessage(msgnum, 1);
+ }
+ else {
+ msg = NULL;
+ }
+
+ if ((msg != NULL) && CM_IsEmpty(msg, eMesageText)) {
+ CM_Free(msg);
+ msg = NULL;
+ }
+
+ if (msg == NULL) {
+ cprintf("%d Revision history for '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename);
+ return;
+ }
+
+ /* Start patching backwards (newest to oldest) through the revision history until we
+ * hit the revision uuid requested by the user. (The callback will perform each one.)
+ */
+
+ memset(&hecbd, 0, sizeof(struct HistoryEraserCallBackData));
+ hecbd.tempfilename = temp;
+ hecbd.stop_when = rev;
+ striplt(hecbd.stop_when);
+
+ mime_parser(CM_RANGE(msg, eMesageText), *wiki_rev_callback, NULL, NULL, (void *)&hecbd, 0);
+ CM_Free(msg);
+
+ /* Were we successful? */
+ if (hecbd.done == 0) {
+ cprintf("%d Revision '%s' of page '%s' was not found.\n",
+ ERROR + MESSAGE_NOT_FOUND, rev, pagename
+ );
+ }
+
+ /* We have the desired revision on disk. Now do something with it. */
+
+ else if ( (!strcasecmp(operation, "fetch")) || (!strcasecmp(operation, "revert")) ) {
+ msg = malloc(sizeof(struct CtdlMessage));
+ memset(msg, 0, sizeof(struct CtdlMessage));
+ msg->cm_magic = CTDLMESSAGE_MAGIC;
+ msg->cm_anon_type = MES_NORMAL;
+ msg->cm_format_type = FMT_RFC822;
+ fp = fopen(temp, "r");
+ if (fp) {
+ char *msgbuf;
+ fseek(fp, 0L, SEEK_END);
+ len = ftell(fp);
+ fseek(fp, 0L, SEEK_SET);
+ msgbuf = malloc(len + 1);
+ rv = fread(msgbuf, len, 1, fp);
+ syslog(LOG_DEBUG, "did %d blocks of %ld bytes\n", rv, len);
+ msgbuf[len] = '\0';
+ CM_SetAsField(msg, eMesageText, &msgbuf, len);
+ fclose(fp);
+ }
+ if (len <= 0) {
+ msgnum = (-1L);
+ }
+ else if (!strcasecmp(operation, "fetch")) {
+ CM_SetField(msg, eAuthor, HKEY("Citadel"));
+ CtdlCreateRoom(wwm, 5, "", 0, 1, 1, VIEW_BBS); /* Not an error if already exists */
+ msgnum = CtdlSubmitMsg(msg, NULL, wwm); /* Store the revision here */
+
+ /*
+ * WARNING: VILE SLEAZY HACK
+ * This will avoid the 'message xxx is not in this room' security error,
+ * but only if the client fetches the message we just generated immediately
+ * without first trying to perform other fetch operations.
+ */
+ if (CC->cached_msglist != NULL) {
+ free(CC->cached_msglist);
+ CC->cached_msglist = NULL;
+ CC->cached_num_msgs = 0;
+ }
+ CC->cached_msglist = malloc(sizeof(long));
+ if (CC->cached_msglist != NULL) {
+ CC->cached_num_msgs = 1;
+ CC->cached_msglist[0] = msgnum;
+ }
+
+ }
+ else if (!strcasecmp(operation, "revert")) {
+ CM_SetFieldLONG(msg, eTimestamp, time(NULL));
+ if (!IsEmptyStr(CC->user.fullname)) {
+ CM_SetField(msg, eAuthor, CC->user.fullname, strlen(CC->user.fullname));
+ }
+
+ if (!IsEmptyStr(CC->cs_inet_email)) {
+ CM_SetField(msg, erFc822Addr, CC->cs_inet_email, strlen(CC->cs_inet_email));
+ }
+
+ if (!IsEmptyStr(CC->room.QRname)) {
+ CM_SetField(msg, eOriginalRoom, CC->room.QRname, strlen(CC->room.QRname));
+ }
+
+ if (!IsEmptyStr(pagename)) {
+ CM_SetField(msg, eExclusiveID, pagename, strlen(pagename));
+ }
+ msgnum = CtdlSubmitMsg(msg, NULL, ""); /* Replace the current revision */
+ }
+ else {
+ /* Theoretically it is impossible to get here, but throw an error anyway */
+ msgnum = (-1L);
+ }
+ CM_Free(msg);
+ if (msgnum >= 0L) {
+ cprintf("%d %ld\n", CIT_OK, msgnum); /* Give the client a msgnum */
+ }
+ else {
+ cprintf("%d Error %ld has occurred.\n", ERROR+INTERNAL_ERROR, msgnum);
+ }
+ }
+
+ /* We did all this work for nothing. Express anguish to the caller. */
+ else {
+ cprintf("%d An unknown operation was requested.\n", ERROR+CMD_NOT_SUPPORTED);
+ }
+
+ unlink(temp);
+ return;
+}
+
+
+
+/*
+ * commands related to wiki management
+ */
+void cmd_wiki(char *argbuf) {
+ char subcmd[32];
+ char pagename[256];
+ char rev[128];
+ char operation[16];
+
+ extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
+
+ if (!strcasecmp(subcmd, "history")) {
+ extract_token(pagename, argbuf, 1, '|', sizeof pagename);
+ wiki_history(pagename);
+ return;
+ }
+
+ if (!strcasecmp(subcmd, "rev")) {
+ extract_token(pagename, argbuf, 1, '|', sizeof pagename);
+ extract_token(rev, argbuf, 2, '|', sizeof rev);
+ extract_token(operation, argbuf, 3, '|', sizeof operation);
+ wiki_rev(pagename, rev, operation);
+ return;
+ }
+
+ cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
+}
+
+
+
+/*
+ * Module initialization
+ */
+char *ctdl_module_init_wiki() {
+ if (!threading) {
+ CtdlRegisterMessageHook(wiki_upload_beforesave, EVT_BEFORESAVE);
+ CtdlRegisterProtoHook(cmd_wiki, "WIKI", "Commands related to Wiki management");
+ }
+
+ /* return our module name for the log */
+ return "wiki";
+}
--- /dev/null
+/*
+ * XMPP (Jabber) service for the Citadel system
+ * Copyright (c) 2007-2022 by Art Cancro and citadel.org
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+#include <libcitadel.h>
+#include <expat.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../database.h"
+#include "../../msgbase.h"
+#include "../../internet_addressing.h"
+#include "../../ctdl_module.h"
+#include "serv_xmpp.h"
+
+/* uncomment for more verbosity - it will log all received XML tags */
+// #define XMPP_XML_DEBUG
+
+/* XML_StopParser is present in expat 2.x */
+#if XML_MAJOR_VERSION > 1
+#define HAVE_XML_STOPPARSER
+#endif
+
+struct xmpp_event *xmpp_queue = NULL;
+
+#ifdef HAVE_XML_STOPPARSER
+/* Stop the parser if an entity declaration is hit. */
+static void xmpp_entity_declaration(void *userData, const XML_Char *entityName,
+ int is_parameter_entity, const XML_Char *value,
+ int value_length, const XML_Char *base,
+ const XML_Char *systemId, const XML_Char *publicId,
+ const XML_Char *notationName
+) {
+ syslog(LOG_WARNING, "xmpp: illegal entity declaration encountered; stopping parser.");
+ XML_StopParser(XMPP->xp, XML_FALSE);
+}
+#endif
+
+
+/*
+ * support function for xmlesc() which helps with UTF-8 strings
+ */
+static inline int Ctdl_GetUtf8SequenceLength(const char *CharS, const char *CharE)
+{
+ int n = 0;
+ unsigned char test = (1<<7);
+
+ if ((*CharS & 0xC0) != 0xC0) {
+ return 1;
+ }
+
+ while ((n < 8) && ((test & ((unsigned char)*CharS)) != 0)) {
+ test = test >> 1;
+ n ++;
+ }
+ if ((n > 6) || ((CharE - CharS) < n)) {
+ n = 0;
+ }
+ return n;
+}
+
+
+/*
+ * Given a source string and a target buffer, returns the string
+ * properly escaped for insertion into an XML stream. Returns a
+ * pointer to the target buffer for convenience.
+ */
+char *xmlesc(char *buf, char *str, int bufsiz)
+{
+ int IsUtf8Sequence;
+ char *ptr, *pche;
+ unsigned char ch;
+ int inlen;
+ int len = 0;
+
+ if (!buf) return(NULL);
+ buf[0] = 0;
+ len = 0;
+ if (!str) {
+ return(buf);
+ }
+ inlen = strlen(str);
+ pche = str + inlen;
+
+ for (ptr=str; *ptr; ptr++) {
+ ch = *ptr;
+ if (ch == '<') {
+ strcpy(&buf[len], "<");
+ len += 4;
+ }
+ else if (ch == '>') {
+ strcpy(&buf[len], ">");
+ len += 4;
+ }
+ else if (ch == '&') {
+ strcpy(&buf[len], "&");
+ len += 5;
+ }
+ else if ((ch >= 0x20) && (ch <= 0x7F)) {
+ buf[len++] = ch;
+ buf[len] = 0;
+ }
+ else if (ch < 0x20) {
+ buf[len++] = '_'; // we probably shouldn't be doing this
+ buf[len] = 0;
+ }
+ else {
+ IsUtf8Sequence = Ctdl_GetUtf8SequenceLength(ptr, pche);
+ if (IsUtf8Sequence)
+ {
+ while ((IsUtf8Sequence > 0) &&
+ (ptr < pche))
+ {
+ buf[len] = *ptr;
+ ptr ++;
+ --IsUtf8Sequence;
+ }
+ }
+ else
+ {
+ char oct[10];
+ sprintf(oct, "&#%o;", ch);
+ strcpy(&buf[len], oct);
+ len += strlen(oct);
+ }
+ }
+ if ((len + 6) > bufsiz) {
+ return(buf);
+ }
+ }
+ return(buf);
+}
+
+
+/*
+ * We have just received a <stream> tag from the client, so send them ours
+ */
+void xmpp_stream_start(void *data, const char *supplied_el, const char **attr)
+{
+ char xmlbuf[256];
+
+ while (*attr) {
+ if (!strcasecmp(attr[0], "to")) {
+ safestrncpy(XMPP->server_name, attr[1], sizeof XMPP->server_name);
+ }
+ attr += 2;
+ }
+
+ cprintf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+ cprintf("<stream:stream ");
+ cprintf("from=\"%s\" ", xmlesc(xmlbuf, XMPP->server_name, sizeof xmlbuf));
+ cprintf("id=\"%08x\" ", CC->cs_pid);
+ cprintf("version=\"1.0\" ");
+ cprintf("xmlns:stream=\"http://etherx.jabber.org/streams\" ");
+ cprintf("xmlns=\"jabber:client\">");
+
+ /* The features of this stream are... */
+ cprintf("<stream:features>");
+
+ /*
+ * TLS encryption (but only if it isn't already active)
+ */
+#ifdef HAVE_OPENSSL
+ if (!CC->redirect_ssl) {
+ cprintf("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'></starttls>");
+ }
+#endif
+
+ if (!CC->logged_in) {
+ /* If we're not logged in yet, offer SASL as our feature set */
+ xmpp_output_auth_mechs();
+
+ /* Also offer non-SASL authentication */
+ cprintf("<auth xmlns=\"http://jabber.org/features/iq-auth\"/>");
+ }
+
+ /* Offer binding and sessions as part of our feature set */
+ cprintf("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>");
+ cprintf("<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>");
+ cprintf("</stream:features>");
+
+ CC->is_async = 1; /* XMPP sessions are inherently async-capable */
+}
+
+
+void xmpp_xml_start(void *data, const char *supplied_el, const char **attr) {
+ char el[256];
+ char *sep = NULL;
+ int i;
+
+ /* Create a version of the element with the namespace removed.
+ * Now we can access "el" or "supplied_el" depending on whether we want to see the whole namespace.
+ */
+ safestrncpy(el, supplied_el, sizeof el);
+ while (sep = strchr(el, ':'), sep) {
+ strcpy(el, ++sep);
+ }
+
+#ifdef XMPP_XML_DEBUG
+ syslog(LOG_DEBUG, "xmpp: ELEMENT START: <%s>", el);
+ for (i=0; attr[i] != NULL; i+=2) {
+ syslog(LOG_DEBUG, "xmpp: Attribute '%s' = '%s'", attr[i], attr[i+1]);
+ }
+#endif
+
+ if (!strcasecmp(el, "stream")) {
+ xmpp_stream_start(data, supplied_el, attr);
+ }
+
+ else if (!strcasecmp(el, "query")) {
+ XMPP->iq_query_xmlns[0] = 0;
+ safestrncpy(XMPP->iq_query_xmlns, supplied_el, sizeof XMPP->iq_query_xmlns);
+ }
+
+ else if (!strcasecmp(el, "bind")) {
+ XMPP->bind_requested = 1;
+ }
+
+ else if (!strcasecmp(el, "iq")) {
+ for (i=0; attr[i] != NULL; i+=2) {
+ if (!strcasecmp(attr[i], "type")) {
+ safestrncpy(XMPP->iq_type, attr[i+1], sizeof XMPP->iq_type);
+ }
+ else if (!strcasecmp(attr[i], "id")) {
+ safestrncpy(XMPP->iq_id, attr[i+1], sizeof XMPP->iq_id);
+ }
+ else if (!strcasecmp(attr[i], "from")) {
+ safestrncpy(XMPP->iq_from, attr[i+1], sizeof XMPP->iq_from);
+ }
+ else if (!strcasecmp(attr[i], "to")) {
+ safestrncpy(XMPP->iq_to, attr[i+1], sizeof XMPP->iq_to);
+ }
+ }
+ }
+
+ else if (!strcasecmp(el, "auth")) {
+ XMPP->sasl_auth_mech[0] = 0;
+ for (i=0; attr[i] != NULL; i+=2) {
+ if (!strcasecmp(attr[i], "mechanism")) {
+ safestrncpy(XMPP->sasl_auth_mech, attr[i+1], sizeof XMPP->sasl_auth_mech);
+ }
+ }
+ }
+
+ else if (!strcasecmp(el, "message")) {
+ for (i=0; attr[i] != NULL; i+=2) {
+ if (!strcasecmp(attr[i], "to")) {
+ safestrncpy(XMPP->message_to, attr[i+1], sizeof XMPP->message_to);
+ }
+ }
+ }
+
+ else if (!strcasecmp(el, "html")) {
+ ++XMPP->html_tag_level;
+ }
+}
+
+
+void xmpp_xml_end(void *data, const char *supplied_el) {
+ char el[256];
+ char *sep = NULL;
+ char xmlbuf[256];
+
+ /* Create a version of the element with the namespace removed.
+ * Now we can access "el" or "supplied_el" depending on whether we want to see the whole namespace.
+ */
+ safestrncpy(el, supplied_el, sizeof el);
+ while (sep = strchr(el, ':'), sep) {
+ strcpy(el, ++sep);
+ }
+
+#ifdef XMPP_XML_DEBUG
+ syslog(LOG_DEBUG, "xmpp: ELEMENT END : <%s>", el);
+ if (XMPP->chardata_len > 0) {
+ syslog(LOG_DEBUG, "xmpp: chardata: %s", XMPP->chardata);
+ }
+#endif
+
+ if (!strcasecmp(el, "resource")) {
+ if (XMPP->chardata_len > 0) {
+ safestrncpy(XMPP->iq_client_resource, XMPP->chardata, sizeof XMPP->iq_client_resource);
+ striplt(XMPP->iq_client_resource);
+ }
+ }
+
+ else if (!strcasecmp(el, "username")) { /* NON SASL ONLY */
+ if (XMPP->chardata_len > 0) {
+ safestrncpy(XMPP->iq_client_username, XMPP->chardata, sizeof XMPP->iq_client_username);
+ striplt(XMPP->iq_client_username);
+ }
+ }
+
+ else if (!strcasecmp(el, "password")) { /* NON SASL ONLY */
+ if (XMPP->chardata_len > 0) {
+ safestrncpy(XMPP->iq_client_password, XMPP->chardata, sizeof XMPP->iq_client_password);
+ striplt(XMPP->iq_client_password);
+ }
+ }
+
+ else if (!strcasecmp(el, "iq")) {
+
+ /*
+ * iq type="get" (handle queries)
+ */
+ if (!strcasecmp(XMPP->iq_type, "get")) {
+
+ /*
+ * Query on a namespace
+ */
+ if (!IsEmptyStr(XMPP->iq_query_xmlns)) {
+ xmpp_query_namespace(XMPP->iq_id, XMPP->iq_from, XMPP->iq_to, XMPP->iq_query_xmlns);
+ }
+
+ /*
+ * ping ( http://xmpp.org/extensions/xep-0199.html )
+ */
+ else if (XMPP->ping_requested) {
+ cprintf("<iq type=\"result\" ");
+ if (!IsEmptyStr(XMPP->iq_from)) {
+ cprintf("to=\"%s\" ", xmlesc(xmlbuf, XMPP->iq_from, sizeof xmlbuf));
+ }
+ if (!IsEmptyStr(XMPP->iq_to)) {
+ cprintf("from=\"%s\" ", xmlesc(xmlbuf, XMPP->iq_to, sizeof xmlbuf));
+ }
+ cprintf("id=\"%s\"/>", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
+ }
+
+ /*
+ * Client is requesting its own vCard
+ * (If we make this more elaborate, move it to a separate function)
+ */
+ else if (XMPP->iq_vcard) {
+ cprintf("<iq type=\"result\" id=\"%s\" ", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
+ cprintf("to=\"%s\">", xmlesc(xmlbuf, XMPP->iq_from, sizeof xmlbuf));
+ cprintf("<vCard xmlns=\"vcard-temp\">");
+ cprintf("<fn>%s</fn>", xmlesc(xmlbuf, CC->user.fullname, sizeof xmlbuf));
+ cprintf("<nickname>%s</nickname>", xmlesc(xmlbuf, CC->user.fullname, sizeof xmlbuf));
+ cprintf("</vCard>");
+ cprintf("</iq>");
+ }
+
+ /*
+ * Unknown query ... return the XML equivalent of a blank stare
+ */
+ else {
+ syslog(LOG_DEBUG, "xmpp: Unknown query <%s> - returning <service-unavailable/>", el);
+ cprintf("<iq type=\"error\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
+ cprintf("<error code=\"503\" type=\"cancel\">"
+ "<service-unavailable xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
+ "</error>"
+ );
+ cprintf("</iq>");
+ }
+ }
+
+ /*
+ * Non SASL authentication
+ */
+ else if (
+ (!strcasecmp(XMPP->iq_type, "set"))
+ && (!strcasecmp(XMPP->iq_query_xmlns, "jabber:iq:auth:query"))
+ ) {
+ xmpp_non_sasl_authenticate(XMPP->iq_id, XMPP->iq_client_username, XMPP->iq_client_password);
+ }
+
+ /*
+ * If this <iq> stanza was a "bind" attempt, process it ...
+ */
+ else if (
+ (XMPP->bind_requested)
+ && (!IsEmptyStr(XMPP->iq_id))
+ && (CC->logged_in)
+ ) {
+ /* If the client has not specified a client resource, generate one */
+ if (IsEmptyStr(XMPP->iq_client_resource)) {
+ snprintf(XMPP->iq_client_resource, sizeof XMPP->iq_client_resource, "%d", CC->cs_pid);
+ }
+
+ /* Generate the "full JID" of the client (user@host/resource) */
+ snprintf(XMPP->client_jid, sizeof XMPP->client_jid, "%s/%s", CC->cs_principal_id, XMPP->iq_client_resource);
+
+ /* Tell the client what its JID is */
+ cprintf("<iq type=\"result\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
+ cprintf("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">");
+ cprintf("<jid>%s</jid>", xmlesc(xmlbuf, XMPP->client_jid, sizeof xmlbuf));
+ cprintf("</bind>");
+ cprintf("</iq>");
+ }
+
+ else if (XMPP->iq_session) {
+ cprintf("<iq type=\"result\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
+ cprintf("</iq>");
+ }
+
+ else {
+ cprintf("<iq type=\"error\" id=\"%s\">", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf));
+ cprintf("<error>Don't know how to do '%s'!</error>", xmlesc(xmlbuf, XMPP->iq_type, sizeof xmlbuf));
+ cprintf("</iq>");
+ syslog(LOG_DEBUG, "XMPP: don't know how to do iq_type='%s' with iq_query_xmlns='%s'", XMPP->iq_type, XMPP->iq_query_xmlns);
+ }
+
+ /* Now clear these fields out so they don't get used by a future stanza */
+ XMPP->iq_id[0] = 0;
+ XMPP->iq_from[0] = 0;
+ XMPP->iq_to[0] = 0;
+ XMPP->iq_type[0] = 0;
+ XMPP->iq_client_resource[0] = 0;
+ XMPP->iq_session = 0;
+ XMPP->iq_vcard = 0;
+ XMPP->iq_query_xmlns[0] = 0;
+ XMPP->bind_requested = 0;
+ XMPP->ping_requested = 0;
+ }
+
+ else if (!strcasecmp(el, "auth")) {
+ /* Try to authenticate (this function is responsible for the output stanza) */
+ xmpp_sasl_auth(XMPP->sasl_auth_mech, (XMPP->chardata != NULL ? XMPP->chardata : "") );
+
+ /* Now clear these fields out so they don't get used by a future stanza */
+ XMPP->sasl_auth_mech[0] = 0;
+ }
+
+ else if (!strcasecmp(el, "session")) {
+ XMPP->iq_session = 1;
+ }
+
+ else if (!strcasecmp(supplied_el, "vcard-temp:vCard")) {
+ XMPP->iq_vcard = 1;
+ }
+
+ else if (!strcasecmp(el, "presence")) {
+ /* Respond to a <presence> update by firing back with presence information
+ * on the entire wholist. Check this assumption, it's probably wrong.
+ */
+ xmpp_wholist_presence_dump();
+ }
+
+ else if ( (!strcasecmp(el, "body")) && (XMPP->html_tag_level == 0) ) {
+ if (XMPP->message_body != NULL) {
+ free(XMPP->message_body);
+ XMPP->message_body = NULL;
+ }
+ if (XMPP->chardata_len > 0) {
+ XMPP->message_body = strdup(XMPP->chardata);
+ }
+ }
+
+ else if (!strcasecmp(el, "message")) {
+ xmpp_send_message(XMPP->message_to, XMPP->message_body);
+ XMPP->html_tag_level = 0;
+ }
+
+ else if (!strcasecmp(el, "html")) {
+ --XMPP->html_tag_level;
+ }
+
+ else if (!strcasecmp(el, "starttls")) {
+#ifdef HAVE_OPENSSL
+ cprintf("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
+ CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
+ if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;
+#else
+ cprintf("<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
+ CC->kill_me = KILLME_NO_CRYPTO;
+#endif
+ }
+
+ else if (!strcasecmp(el, "ping")) {
+ XMPP->ping_requested = 1;
+ }
+
+ else if (!strcasecmp(el, "stream")) {
+ syslog(LOG_DEBUG, "xmpp: client shut down their stream");
+ xmpp_massacre_roster();
+ cprintf("</stream>\n");
+ CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
+ }
+
+ else if (!strcasecmp(el, "query")) {
+ /* already processed , no further action needed here */
+ }
+
+ else if (!strcasecmp(el, "bind")) {
+ /* already processed , no further action needed here */
+ }
+
+ else {
+ syslog(LOG_DEBUG, "xmpp: ignoring unknown tag <%s>", el);
+ }
+
+ XMPP->chardata_len = 0;
+ if (XMPP->chardata_alloc > 0) {
+ XMPP->chardata[0] = 0;
+ }
+}
+
+
+void xmpp_xml_chardata(void *data, const XML_Char *s, int len)
+{
+ citxmpp *X = XMPP;
+
+ if (X->chardata_alloc == 0) {
+ X->chardata_alloc = SIZ;
+ X->chardata = malloc(X->chardata_alloc);
+ }
+ if ((X->chardata_len + len + 1) > X->chardata_alloc) {
+ X->chardata_alloc = X->chardata_len + len + 1024;
+ X->chardata = realloc(X->chardata, X->chardata_alloc);
+ }
+ memcpy(&X->chardata[X->chardata_len], s, len);
+ X->chardata_len += len;
+ X->chardata[X->chardata_len] = 0;
+}
+
+
+/*
+ * This cleanup function blows away the temporary memory and files used by the XMPP service.
+ */
+void xmpp_cleanup_function(void) {
+
+ /* Don't do this stuff if this is not a XMPP session! */
+ if (CC->h_command_function != xmpp_command_loop) return;
+
+ if (XMPP->chardata != NULL) {
+ free(XMPP->chardata);
+ XMPP->chardata = NULL;
+ XMPP->chardata_len = 0;
+ XMPP->chardata_alloc = 0;
+ if (XMPP->message_body != NULL) {
+ free(XMPP->message_body);
+ }
+ }
+ XML_ParserFree(XMPP->xp);
+ free(XMPP);
+}
+
+
+/*
+ * Here's where our XMPP session begins its happy day.
+ */
+void xmpp_greeting(void) {
+ client_set_inbound_buf(4);
+ strcpy(CC->cs_clientname, "XMPP session");
+ CC->session_specific_data = malloc(sizeof(citxmpp));
+ memset(XMPP, 0, sizeof(citxmpp));
+ XMPP->last_event_processed = queue_event_seq;
+
+ /* XMPP does not use a greeting, but we still have to initialize some things. */
+
+ XMPP->xp = XML_ParserCreateNS("UTF-8", ':');
+ if (XMPP->xp == NULL) {
+ syslog(LOG_ERR, "xmpp: cannot create XML parser");
+ CC->kill_me = KILLME_XML_PARSER;
+ return;
+ }
+
+ XML_SetElementHandler(XMPP->xp, xmpp_xml_start, xmpp_xml_end);
+ XML_SetCharacterDataHandler(XMPP->xp, xmpp_xml_chardata);
+ // XML_SetUserData(XMPP->xp, something...);
+
+ /* Prevent the "billion laughs" attack against expat by disabling
+ * internal entity expansion. With 2.x, forcibly stop the parser
+ * if an entity is declared - this is safer and a more obvious
+ * failure mode. With older versions, simply prevent expansion
+ * of such entities. */
+#ifdef HAVE_XML_STOPPARSER
+ XML_SetEntityDeclHandler(XMPP->xp, xmpp_entity_declaration);
+#else
+ XML_SetDefaultHandler(XMPP->xp, NULL);
+#endif
+
+ CC->can_receive_im = 1; /* This protocol is capable of receiving instant messages */
+}
+
+
+/*
+ * Main command loop for XMPP sessions.
+ */
+void xmpp_command_loop(void) {
+ int rc;
+ StrBuf *stream_input = NewStrBuf();
+
+ time(&CC->lastcmd);
+ rc = client_read_random_blob(stream_input, 30);
+ if (rc > 0) {
+ XML_Parse(XMPP->xp, ChrPtr(stream_input), rc, 0);
+ }
+ else {
+ syslog(LOG_ERR, "xmpp: client disconnected: ending session.");
+ CC->kill_me = KILLME_CLIENT_DISCONNECTED;
+ }
+ FreeStrBuf(&stream_input);
+}
+
+
+/*
+ * Async loop for XMPP sessions (handles the transmission of unsolicited stanzas)
+ */
+void xmpp_async_loop(void) {
+ xmpp_process_events();
+ xmpp_output_incoming_messages();
+}
+
+
+/*
+ * Login hook for XMPP sessions
+ */
+void xmpp_login_hook(void) {
+ xmpp_queue_event(XMPP_EVT_LOGIN, CC->cs_principal_id);
+}
+
+
+/*
+ * Logout hook for XMPP sessions
+ */
+void xmpp_logout_hook(void) {
+ xmpp_queue_event(XMPP_EVT_LOGOUT, CC->cs_principal_id);
+}
+
+
+const char *CitadelServiceXMPP="XMPP";
+char *ctdl_module_init_xmpp() {
+ if (!threading) {
+ CtdlRegisterServiceHook(CtdlGetConfigInt("c_xmpp_c2s_port"),
+ NULL,
+ xmpp_greeting,
+ xmpp_command_loop,
+ xmpp_async_loop,
+ CitadelServiceXMPP
+ );
+ CtdlRegisterSessionHook(xmpp_cleanup_function, EVT_STOP, PRIO_STOP + 70);
+ CtdlRegisterSessionHook(xmpp_login_hook, EVT_LOGIN, PRIO_LOGIN + 90);
+ CtdlRegisterSessionHook(xmpp_logout_hook, EVT_LOGOUT, PRIO_LOGOUT + 90);
+ CtdlRegisterSessionHook(xmpp_login_hook, EVT_UNSTEALTH, PRIO_UNSTEALTH + 1);
+ CtdlRegisterSessionHook(xmpp_logout_hook, EVT_STEALTH, PRIO_STEALTH + 1);
+
+ }
+
+ /* return our module name for the log */
+ return "xmpp";
+}
--- /dev/null
+/*
+ * Copyright (c) 2007-2019 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.
+ */
+
+typedef struct _citxmpp { /* Information about the current session */
+ XML_Parser xp; /* XML parser instance for incoming client stream */
+ char server_name[256]; /* who they think we are */
+ char *chardata;
+ int chardata_len;
+ int chardata_alloc;
+ char client_jid[256]; /* "full JID" of the client */
+ int last_event_processed;
+
+ char iq_type[256]; /* for <iq> stanzas */
+ char iq_id[256];
+ char iq_from[256];
+ char iq_to[256];
+ char iq_client_username[256]; /* username requested by the client (NON SASL ONLY) */
+ char iq_client_password[256]; /* password requested by the client (NON SASL ONLY) */
+ char iq_client_resource[256]; /* resource name requested by the client */
+ int iq_session; /* nonzero == client is requesting a session */
+ int iq_vcard; /* nonzero == client is requesting its vCard */
+ char iq_query_xmlns[256]; /* Namespace of <query> */
+
+ char sasl_auth_mech[32]; /* SASL auth mechanism requested by the client */
+
+ char message_to[256];
+ char *message_body; /* Message body in transit */
+ int html_tag_level; /* <html> tag nesting level */
+
+ int bind_requested; /* In this stanza, client is asking server to bind a resource. */
+ int ping_requested; /* In this stanza, client is pinging the server. */
+} citxmpp;
+
+#define XMPP ((citxmpp *)CC->session_specific_data)
+
+struct xmpp_event {
+ struct xmpp_event *next;
+ int event_seq;
+ time_t event_time;
+ int event_type;
+ char event_jid[256];
+ int session_which_generated_this_event;
+};
+
+extern struct xmpp_event *xmpp_queue;
+extern int queue_event_seq;
+
+enum {
+ XMPP_EVT_LOGIN,
+ XMPP_EVT_LOGOUT
+};
+
+void xmpp_cleanup_function(void);
+void xmpp_greeting(void);
+void xmpp_command_loop(void);
+void xmpp_async_loop(void);
+void xmpp_sasl_auth(char *, char *);
+void xmpp_output_auth_mechs(void);
+void xmpp_query_namespace(char *, char *, char *, char *);
+void xmpp_wholist_presence_dump(void);
+void xmpp_output_incoming_messages(void);
+void xmpp_queue_event(int, char *);
+void xmpp_process_events(void);
+void xmpp_presence_notify(char *, int);
+void xmpp_roster_item(struct CitContext *);
+void xmpp_send_message(char *, char *);
+void xmpp_non_sasl_authenticate(char *, char *, char *);
+void xmpp_massacre_roster(void);
+void xmpp_delete_old_buddies_who_no_longer_exist_from_the_client_roster(void);
+int xmpp_is_visible(struct CitContext *from, struct CitContext *to_whom);
+char *xmlesc(char *buf, char *str, int bufsiz);
--- /dev/null
+/*
+ * Handle messages sent and received using XMPP (Jabber) protocol
+ *
+ * Copyright (c) 2007-2010 by Art Cancro
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+#include <expat.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../internet_addressing.h"
+#include "../../ctdl_module.h"
+#include "serv_xmpp.h"
+
+
+/*
+ * This function is called by the XMPP service's async loop.
+ * If the client session has instant messages waiting, it outputs
+ * unsolicited XML stanzas containing them.
+ */
+void xmpp_output_incoming_messages(void) {
+
+ struct ExpressMessage *ptr;
+ char xmlbuf1[4096];
+ char xmlbuf2[4096];
+
+ while (CC->FirstExpressMessage != NULL) {
+
+ begin_critical_section(S_SESSION_TABLE);
+ ptr = CC->FirstExpressMessage;
+ CC->FirstExpressMessage = CC->FirstExpressMessage->next;
+ end_critical_section(S_SESSION_TABLE);
+
+ cprintf("<message to=\"%s\" from=\"%s\" type=\"chat\">",
+ xmlesc(xmlbuf1, XMPP->client_jid, sizeof xmlbuf1),
+ xmlesc(xmlbuf2, ptr->sender_email, sizeof xmlbuf2)
+ );
+ if (ptr->text != NULL) {
+ striplt(ptr->text);
+ cprintf("<body>%s</body>", xmlesc(xmlbuf1, ptr->text, sizeof xmlbuf1));
+ free(ptr->text);
+ }
+ cprintf("</message>");
+ free(ptr);
+ }
+}
+
+
+/*
+ * Client is sending a message.
+ */
+void xmpp_send_message(char *message_to, char *message_body) {
+ char *recp = NULL;
+ struct CitContext *cptr;
+
+ if (message_body == NULL) return;
+ if (message_to == NULL) return;
+ if (IsEmptyStr(message_to)) return;
+ if (!CC->logged_in) return;
+
+ for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
+ if ( (cptr->logged_in)
+ && (cptr->can_receive_im)
+ && (!strcasecmp(cptr->cs_principal_id, message_to))
+ ) {
+ recp = cptr->user.fullname;
+ }
+ }
+
+ if (recp) {
+ PerformXmsgHooks(CC->user.fullname, CC->cs_principal_id, recp, message_body);
+ }
+
+ free(XMPP->message_body);
+ XMPP->message_body = NULL;
+ XMPP->message_to[0] = 0;
+ time(&CC->lastidle);
+}
+
--- /dev/null
+/*
+ * Handle XMPP presence exchanges
+ *
+ * Copyright (c) 2007-2021 by Art Cancro and citadel.org
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <assert.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+#include <expat.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../internet_addressing.h"
+#include "../../ctdl_module.h"
+#include "serv_xmpp.h"
+
+
+/*
+ * Indicate the presence of another user to the client
+ * (used in several places)
+ */
+void xmpp_indicate_presence(char *presence_jid) {
+ char xmlbuf[256];
+
+ syslog(LOG_DEBUG, "xmpp: indicating presence of <%s> to <%s>", presence_jid, XMPP->client_jid);
+ cprintf("<presence from=\"%s\" ", xmlesc(xmlbuf, presence_jid, sizeof xmlbuf));
+ cprintf("to=\"%s\"></presence>", xmlesc(xmlbuf, XMPP->client_jid, sizeof xmlbuf));
+}
+
+
+/*
+ * Convenience function to determine whether any given session is 'visible' to any other given session,
+ * and is capable of receiving instant messages from that session.
+ */
+int xmpp_is_visible(struct CitContext *cptr, struct CitContext *to_whom) {
+ int aide = (to_whom->user.axlevel >= AxAideU);
+
+ if ( (cptr->logged_in)
+ && (((cptr->cs_flags&CS_STEALTH)==0) || (aide)) /* aides see everyone */
+ && (cptr->user.usernum != to_whom->user.usernum) /* don't show myself */
+ && (cptr->can_receive_im) /* IM-capable session */
+ ) {
+ return(1);
+ }
+ else {
+ return(0);
+ }
+}
+
+
+/*
+ * Initial dump of the entire wholist
+ */
+void xmpp_wholist_presence_dump(void) {
+ struct CitContext *cptr = NULL;
+ int nContexts, i;
+
+ cptr = CtdlGetContextArray(&nContexts);
+ if (!cptr) {
+ return;
+ }
+
+ for (i=0; i<nContexts; i++) {
+ if (xmpp_is_visible(&cptr[i], CC)) {
+ xmpp_indicate_presence(cptr[i].cs_principal_id);
+ }
+ }
+ free(cptr);
+}
+
+
+/*
+ * Function to remove a buddy subscription and delete from the roster
+ * (used in several places)
+ */
+void xmpp_destroy_buddy(char *presence_jid, int aggressively) {
+ static int unsolicited_id = 1;
+ char xmlbuf1[256];
+ char xmlbuf2[256];
+
+ if (!presence_jid) return;
+ if (!XMPP) return;
+ if (!XMPP->client_jid) return;
+
+ /* Transmit non-presence information */
+ cprintf("<presence type=\"unavailable\" from=\"%s\" to=\"%s\"></presence>",
+ xmlesc(xmlbuf1, presence_jid, sizeof xmlbuf1),
+ xmlesc(xmlbuf2, XMPP->client_jid, sizeof xmlbuf2)
+ );
+
+ /*
+ * Setting the "aggressively" flag also sends an "unsubscribed" presence update.
+ * We only ask for this when flushing the client side roster, because if we do it
+ * in the middle of a session when another user logs off, some clients (Jitsi) interpret
+ * it as a rejection of a subscription request.
+ */
+ if (aggressively) {
+ cprintf("<presence type=\"unsubscribed\" from=\"%s\" to=\"%s\"></presence>",
+ xmlesc(xmlbuf1, presence_jid, sizeof xmlbuf1),
+ xmlesc(xmlbuf2, XMPP->client_jid, sizeof xmlbuf2)
+ );
+ }
+
+ // note: we should implement xmpp_indicate_nonpresence so we can use it elsewhere
+
+ /* Do an unsolicited roster update that deletes the contact. */
+ cprintf("<iq from=\"%s\" to=\"%s\" id=\"unbuddy_%x\" type=\"result\">",
+ xmlesc(xmlbuf1, CC->cs_principal_id, sizeof xmlbuf1),
+ xmlesc(xmlbuf2, XMPP->client_jid, sizeof xmlbuf2),
+ ++unsolicited_id
+ );
+ cprintf("<query xmlns=\"jabber:iq:roster\">");
+ cprintf("<item jid=\"%s\" subscription=\"remove\">", xmlesc(xmlbuf1, presence_jid, sizeof xmlbuf1));
+ cprintf("<group>%s</group>", xmlesc(xmlbuf1, CtdlGetConfigStr("c_humannode"), sizeof xmlbuf1));
+ cprintf("</item>");
+ cprintf("</query>"
+ "</iq>"
+ );
+}
+
+
+/*
+ * When a user logs in or out of the local Citadel system, notify all XMPP sessions about it.
+ * THIS FUNCTION HAS A BUG IN IT THAT ENUMERATES THE SESSIONS WRONG.
+ */
+void xmpp_presence_notify(char *presence_jid, int event_type) {
+ struct CitContext *cptr;
+ static int unsolicited_id = 12345;
+ int visible_sessions = 0;
+ int nContexts, i;
+ int which_cptr_is_relevant = (-1);
+
+ if (IsEmptyStr(presence_jid)) return;
+ if (CC->kill_me) return;
+
+ cptr = CtdlGetContextArray(&nContexts);
+ if (!cptr) {
+ return;
+ }
+
+ /* Count the visible sessions for this user */
+ for (i=0; i<nContexts; i++) {
+ if ( (!strcasecmp(cptr[i].cs_principal_id, presence_jid))
+ && (xmpp_is_visible(&cptr[i], CC))
+ ) {
+ ++visible_sessions;
+ which_cptr_is_relevant = i;
+ }
+ }
+
+ syslog(LOG_DEBUG, "xmpp: %d sessions for <%s> are now visible to session %d", visible_sessions, presence_jid, CC->cs_pid);
+
+ if ( (event_type == XMPP_EVT_LOGIN) && (visible_sessions == 1) ) {
+
+ syslog(LOG_DEBUG, "xmpp: telling session %d that <%s> logged in", CC->cs_pid, presence_jid);
+
+ /* Do an unsolicited roster update that adds a new contact. */
+ assert(which_cptr_is_relevant >= 0);
+ cprintf("<iq id=\"unsolicited_%x\" type=\"result\">", ++unsolicited_id);
+ cprintf("<query xmlns=\"jabber:iq:roster\">");
+ xmpp_roster_item(&cptr[which_cptr_is_relevant]);
+ cprintf("</query></iq>");
+
+ /* Transmit presence information */
+ xmpp_indicate_presence(presence_jid);
+ }
+
+ if (visible_sessions == 0) {
+ syslog(LOG_DEBUG, "xmpp: telling session %d that <%s> logged out", CC->cs_pid, presence_jid);
+ xmpp_destroy_buddy(presence_jid, 0); /* non aggressive presence update */
+ }
+
+ free(cptr);
+}
+
+
+void xmpp_fetch_mortuary_backend(long msgnum, void *userdata) {
+ HashList *mortuary = (HashList *) userdata;
+ struct CtdlMessage *msg;
+ char *ptr = NULL;
+ char *lasts = NULL;
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) {
+ return;
+ }
+
+ /* now add anyone we find into the hashlist */
+
+ /* skip past the headers */
+ ptr = strstr(msg->cm_fields[eMesageText], "\n\n");
+ if (ptr != NULL) {
+ ptr += 2;
+ }
+ else {
+ ptr = strstr(msg->cm_fields[eMesageText], "\n\r\n");
+ if (ptr != NULL) {
+ ptr += 3;
+ }
+ }
+
+ /* the remaining lines are addresses */
+ if (ptr != NULL) {
+ ptr = strtok_r(ptr, "\n", &lasts);
+ while (ptr != NULL) {
+ char *pch = strdup(ptr);
+ Put(mortuary, pch, strlen(pch), pch, NULL);
+ ptr = strtok_r(NULL, "\n", &lasts);
+ }
+ }
+
+ CM_Free(msg);
+}
+
+
+/*
+ * Fetch the "mortuary" - a list of dead buddies which we keep around forever
+ * so we can remove them from any client's roster that still has them listed
+ */
+HashList *xmpp_fetch_mortuary(void) {
+ HashList *mortuary = NewHash(1, NULL);
+ if (!mortuary) {
+ syslog(LOG_ALERT, "xmpp: NewHash() failed!");
+ return(NULL);
+ }
+
+ if (CtdlGetRoom(&CC->room, USERCONFIGROOM) != 0) {
+ /* no config room exists - no further processing is required. */
+ return(mortuary);
+ }
+ CtdlForEachMessage(MSGS_LAST, 1, NULL, XMPPMORTUARY, NULL,
+ xmpp_fetch_mortuary_backend, (void *)mortuary );
+
+ return(mortuary);
+}
+
+
+/*
+ * Fetch the "mortuary" - a list of dead buddies which we keep around forever
+ * so we can remove them from any client's roster that still has them listed
+ */
+void xmpp_store_mortuary(HashList *mortuary) {
+ HashPos *HashPos;
+ long len;
+ void *Value;
+ const char *Key;
+ StrBuf *themsg;
+
+ themsg = NewStrBuf();
+ StrBufPrintf(themsg, "Content-type: " XMPPMORTUARY "\n"
+ "Content-transfer-encoding: 7bit\n"
+ "\n"
+ );
+
+ HashPos = GetNewHashPos(mortuary, 0);
+ while (GetNextHashPos(mortuary, HashPos, &len, &Key, &Value) != 0) {
+ StrBufAppendPrintf(themsg, "%s\n", (char *)Value);
+ }
+ DeleteHashPos(&HashPos);
+
+ /* Delete the old mortuary */
+ CtdlDeleteMessages(USERCONFIGROOM, NULL, 0, XMPPMORTUARY);
+
+ /* And save the new one to disk */
+ quickie_message(CC->user.fullname, NULL, NULL, USERCONFIGROOM, ChrPtr(themsg), 4, "XMPP Mortuary");
+ FreeStrBuf(&themsg);
+}
+
+
+/*
+ * Upon logout we make an attempt to delete the whole roster, in order to
+ * try to keep "ghost" buddies from remaining in the client-side roster.
+ *
+ * Since the client is probably not still alive, also remember the current
+ * roster for next time so we can delete dead buddies then.
+ */
+void xmpp_massacre_roster(void) {
+ struct CitContext *cptr;
+ int nContexts, i;
+ HashList *mortuary = xmpp_fetch_mortuary();
+
+ cptr = CtdlGetContextArray(&nContexts);
+ if (cptr) {
+ for (i=0; i<nContexts; i++) {
+ if (xmpp_is_visible(&cptr[i], CC)) {
+ if (mortuary) {
+ char *buddy = strdup(cptr[i].cs_principal_id);
+ Put(mortuary, buddy, strlen(buddy), buddy, NULL);
+ }
+ }
+ }
+ free (cptr);
+ }
+
+ if (mortuary) {
+ xmpp_store_mortuary(mortuary);
+ DeleteHash(&mortuary);
+ }
+}
+
+
+/*
+ * Stupidly, XMPP does not specify a way to tell the client to flush its client-side roster
+ * and prepare to receive a new one. So instead we remember every buddy we've ever told the
+ * client about, and push delete operations out at the beginning of a session.
+ *
+ * We omit any users who happen to be online right now, but we still keep them in the mortuary,
+ * which needs to be maintained as a list of every buddy the user has ever seen. We don't know
+ * when they're connecting from the same client and when they're connecting from a different client,
+ * so we have no guarantee of what is in the client side roster at connect time.
+ */
+void xmpp_delete_old_buddies_who_no_longer_exist_from_the_client_roster(void) {
+ long len;
+ void *Value;
+ const char *Key;
+ struct CitContext *cptr;
+ int nContexts, i;
+ int online_now = 0;
+ HashList *mortuary = xmpp_fetch_mortuary();
+ HashPos *HashPos = GetNewHashPos(mortuary, 0);
+
+ /* we need to omit anyone who is currently online */
+ cptr = CtdlGetContextArray(&nContexts);
+
+ /* go through the list of users in the mortuary... */
+ while (GetNextHashPos(mortuary, HashPos, &len, &Key, &Value) != 0)
+ {
+
+ online_now = 0;
+ if (cptr) for (i=0; i<nContexts; i++) {
+ if (xmpp_is_visible(&cptr[i], CC)) {
+ if (!strcasecmp(cptr[i].cs_principal_id, (char *)Value)) {
+ online_now = 1;
+ }
+ }
+ }
+
+ if (!online_now) {
+ xmpp_destroy_buddy((char *)Value, 1); /* aggressive presence update */
+ }
+
+ }
+ DeleteHashPos(&HashPos);
+ DeleteHash(&mortuary);
+ free(cptr);
+}
--- /dev/null
+/*
+ * Handle <iq> <get> <query> type situations (namespace queries)
+ *
+ * Copyright (c) 2007-2015 by Art Cancro and citadel.org
+ *
+ * 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.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+#include <expat.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../internet_addressing.h"
+#include "../../ctdl_module.h"
+#include "serv_xmpp.h"
+
+
+/*
+ * Output a single roster item, for roster queries or pushes
+ */
+void xmpp_roster_item(struct CitContext *cptr) {
+ char xmlbuf1[256];
+ char xmlbuf2[256];
+
+ cprintf("<item jid=\"%s\" name=\"%s\" subscription=\"both\">",
+ xmlesc(xmlbuf1, cptr->cs_principal_id, sizeof xmlbuf1),
+ xmlesc(xmlbuf2, cptr->user.fullname, sizeof xmlbuf2)
+ );
+ cprintf("<group>%s</group>", xmlesc(xmlbuf1, CtdlGetConfigStr("c_humannode"), sizeof xmlbuf1));
+ cprintf("</item>");
+}
+
+
+/*
+ * Return the results for a "jabber:iq:roster:query"
+ *
+ * Since we are not yet managing a roster, we simply return the entire wholist
+ * (minus any entries for this user -- don't tell me about myself)
+ *
+ */
+void xmpp_iq_roster_query(void)
+{
+ struct CitContext *cptr;
+ int nContexts, i;
+
+ syslog(LOG_DEBUG, "xmpp: roster push!");
+ cprintf("<query xmlns=\"jabber:iq:roster\">");
+ cptr = CtdlGetContextArray(&nContexts);
+ if (cptr) {
+ for (i=0; i<nContexts; i++) {
+ if (xmpp_is_visible(&cptr[i], CC)) {
+ syslog(LOG_DEBUG, "xmpp: rosterizing %s", cptr[i].user.fullname);
+ xmpp_roster_item(&cptr[i]);
+ }
+ }
+ free (cptr);
+ }
+ cprintf("</query>");
+}
+
+
+/*
+ * Client is doing a namespace query. These are all handled differently.
+ */
+void xmpp_query_namespace(char *iq_id, char *iq_from, char *iq_to, char *query_xmlns)
+{
+ int supported_namespace = 0;
+ int roster_query = 0;
+ char xmlbuf[256];
+ int reply_must_be_from_my_jid = 0;
+
+ /* We need to know before we begin the response whether this is a supported namespace, so
+ * unfortunately all supported namespaces need to be defined here *and* down below where
+ * they are handled.
+ */
+ if (
+ (!strcasecmp(query_xmlns, "jabber:iq:roster:query"))
+ || (!strcasecmp(query_xmlns, "jabber:iq:auth:query"))
+ || (!strcasecmp(query_xmlns, "http://jabber.org/protocol/disco#items:query"))
+ || (!strcasecmp(query_xmlns, "http://jabber.org/protocol/disco#info:query"))
+ ) {
+ supported_namespace = 1;
+ }
+
+ syslog(LOG_DEBUG, "xmpp: xmpp_query_namespace(id=%s, from=%s, to=%s, xmlns=%s)", iq_id, iq_from, iq_to, query_xmlns);
+
+ /*
+ * Beginning of query result.
+ */
+
+ if (!strcasecmp(query_xmlns, "jabber:iq:roster:query")) {
+ reply_must_be_from_my_jid = 1;
+ }
+
+ char dom[1024]; // client is expecting to see the reply
+ if (reply_must_be_from_my_jid) { // coming "from" the user's jid
+ safestrncpy(dom, XMPP->client_jid, sizeof(dom));
+ char *slash = strchr(dom, '/');
+ if (slash) {
+ *slash = 0;
+ }
+ }
+ else {
+ safestrncpy(dom, XMPP->client_jid, sizeof(dom)); // client is expecting to see the reply
+ if (IsEmptyStr(dom)) { // coming "from" the domain of the user's jid
+ safestrncpy(dom, XMPP->server_name, sizeof(dom));
+ }
+ char *at = strrchr(dom, '@');
+ if (at) {
+ strcpy(dom, ++at);
+ }
+ char *slash = strchr(dom, '/');
+ if (slash) {
+ *slash = 0;
+ }
+ }
+
+ if (supported_namespace) {
+ cprintf("<iq type=\"result\" from=\"%s\" ", xmlesc(xmlbuf, dom, sizeof xmlbuf) );
+ }
+ else {
+ cprintf("<iq type=\"error\" from=\"%s\" ", xmlesc(xmlbuf, dom, sizeof xmlbuf) );
+ }
+ if (!IsEmptyStr(iq_from)) {
+ cprintf("to=\"%s\" ", xmlesc(xmlbuf, iq_from, sizeof xmlbuf));
+ }
+ cprintf("id=\"%s\">", xmlesc(xmlbuf, iq_id, sizeof xmlbuf));
+
+ /*
+ * Is this a query we know how to handle?
+ */
+
+ if (!strcasecmp(query_xmlns, "jabber:iq:roster:query")) {
+ roster_query = 1;
+ xmpp_iq_roster_query();
+ }
+
+ else if (!strcasecmp(query_xmlns, "jabber:iq:auth:query")) {
+ cprintf("<query xmlns=\"jabber:iq:auth\">"
+ "<username/><password/><resource/>"
+ "</query>"
+ );
+ }
+
+ // Extension "xep-0030" (http://xmpp.org/extensions/xep-0030.html) (return an empty set of results)
+ else if (!strcasecmp(query_xmlns, "http://jabber.org/protocol/disco#items:query")) {
+ cprintf("<query xmlns=\"%s\"/>", xmlesc(xmlbuf, query_xmlns, sizeof xmlbuf));
+ }
+
+ // Extension "xep-0030" (http://xmpp.org/extensions/xep-0030.html) (return an empty set of results)
+ else if (!strcasecmp(query_xmlns, "http://jabber.org/protocol/disco#info:query")) {
+ cprintf("<query xmlns=\"%s\"/>", xmlesc(xmlbuf, query_xmlns, sizeof xmlbuf));
+ }
+
+ /*
+ * If we didn't hit any known query namespaces then we should deliver a
+ * "service unavailable" error (see RFC3921 section 2.4 and 11.1.5.4)
+ */
+
+ else {
+ syslog(LOG_DEBUG, "xmpp: unknown query namespace '%s' - returning <service-unavailable/>", query_xmlns);
+ cprintf("<error code=\"503\" type=\"cancel\">"
+ "<service-unavailable xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
+ "</error>"
+ );
+ }
+
+ cprintf("</iq>");
+
+ /* If we told the client who is on the roster, we also need to tell the client
+ * who is *not* on the roster. (It's down here because we can't do it in the same
+ * stanza; this will be an unsolicited push.)
+ */
+ if (roster_query) {
+ xmpp_delete_old_buddies_who_no_longer_exist_from_the_client_roster();
+ }
+}
--- /dev/null
+/*
+ * XMPP event queue
+ *
+ * Copyright (c) 2007-2021 by Art Cancro
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+#include <expat.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../internet_addressing.h"
+#include "../../ctdl_module.h"
+#include "serv_xmpp.h"
+
+int queue_event_seq = 0;
+
+void xmpp_queue_event(int event_type, char *email_addr) {
+
+ struct xmpp_event *xptr = NULL;
+ struct xmpp_event *new_event = NULL;
+ struct xmpp_event *last = NULL;
+ int purged_something = 0;
+ struct CitContext *cptr;
+
+ syslog(LOG_DEBUG, "xmpp: xmpp_queue_event(%d, %s)", event_type, email_addr);
+
+ /* Purge events more than a minute old */
+ begin_critical_section(S_XMPP_QUEUE);
+ do {
+ purged_something = 0;
+ if (xmpp_queue != NULL) {
+ if ((time(NULL) - xmpp_queue->event_time) > 60) {
+ xptr = xmpp_queue->next;
+ free(xmpp_queue);
+ xmpp_queue = xptr;
+ purged_something = 1;
+ }
+ }
+ } while(purged_something);
+ end_critical_section(S_XMPP_QUEUE);
+
+ /* Create a new event */
+ new_event = (struct xmpp_event *) malloc(sizeof(struct xmpp_event));
+ new_event->next = NULL;
+ new_event->event_time = time(NULL);
+ new_event->event_seq = ++queue_event_seq;
+ new_event->event_type = event_type;
+ new_event->session_which_generated_this_event = CC->cs_pid;
+ safestrncpy(new_event->event_jid, email_addr, sizeof new_event->event_jid);
+
+ /* Add it to the list */
+ begin_critical_section(S_XMPP_QUEUE);
+ if (xmpp_queue == NULL) {
+ xmpp_queue = new_event;
+ }
+ else {
+ for (xptr = xmpp_queue; xptr != NULL; xptr = xptr->next) {
+ if (xptr->next == NULL) {
+ last = xptr;
+ }
+ }
+ last->next = new_event;
+ }
+ end_critical_section(S_XMPP_QUEUE);
+
+ /* Tell the sessions that something is happening */
+ begin_critical_section(S_SESSION_TABLE);
+ for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
+ if ((cptr->logged_in) && (cptr->h_async_function == xmpp_async_loop)) {
+ set_async_waiting(cptr);
+ }
+ }
+ end_critical_section(S_SESSION_TABLE);
+}
+
+
+/*
+ * Are we interested in anything from the queue? (Called in async loop)
+ */
+void xmpp_process_events(void) {
+ struct xmpp_event *xptr = NULL;
+ int highest_event = 0;
+
+ for (xptr=xmpp_queue; xptr!=NULL; xptr=xptr->next) {
+ if (xptr->event_seq > XMPP->last_event_processed) {
+
+ switch(xptr->event_type) {
+
+ case XMPP_EVT_LOGIN:
+ case XMPP_EVT_LOGOUT:
+ if (xptr->session_which_generated_this_event != CC->cs_pid) {
+ xmpp_presence_notify(xptr->event_jid, xptr->event_type);
+ }
+ break;
+ }
+
+ if (xptr->event_seq > highest_event) {
+ highest_event = xptr->event_seq;
+ }
+ }
+ }
+
+ XMPP->last_event_processed = highest_event;
+}
--- /dev/null
+/*
+ * Barebones SASL authentication service for XMPP (Jabber) clients.
+ *
+ * Note: RFC3920 says we "must" support DIGEST-MD5 but we only support PLAIN.
+ *
+ * Copyright (c) 2007-2019 by Art Cancro
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include "../../sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+#include <expat.h>
+#include <libcitadel.h>
+#include "../../citadel.h"
+#include "../../server.h"
+#include "../../citserver.h"
+#include "../../support.h"
+#include "../../config.h"
+#include "../../user_ops.h"
+#include "../../internet_addressing.h"
+#include "../../ctdl_module.h"
+#include "serv_xmpp.h"
+
+
+/*
+ * PLAIN authentication. Returns zero on success, nonzero on failure.
+ */
+int xmpp_auth_plain(char *authstring)
+{
+ char decoded_authstring[1024];
+ char ident[256];
+ char user[256];
+ char pass[256];
+ int result;
+ long len;
+
+ /* Take apart the authentication string */
+ memset(pass, 0, sizeof(pass));
+
+ CtdlDecodeBase64(decoded_authstring, authstring, strlen(authstring));
+ safestrncpy(ident, decoded_authstring, sizeof ident);
+ safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
+ len = safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
+ if (len < 0) {
+ len = -len;
+ }
+
+ if (!IsEmptyStr(ident)) {
+ result = CtdlLoginExistingUser(ident);
+ }
+ else {
+ result = CtdlLoginExistingUser(user);
+ }
+
+ if (result == login_ok) {
+ if (CtdlTryPassword(pass, len) == pass_ok) {
+ return(0); /* success */
+ }
+ }
+
+ return(1); /* failure */
+}
+
+
+/*
+ * Output the list of SASL mechanisms offered by this stream.
+ */
+void xmpp_output_auth_mechs(void) {
+ cprintf("<mechanisms xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
+ cprintf("<mechanism>PLAIN</mechanism>");
+ cprintf("</mechanisms>");
+}
+
+
+/*
+ * Here we go ... client is trying to authenticate.
+ */
+void xmpp_sasl_auth(char *sasl_auth_mech, char *authstring) {
+
+ if (strcasecmp(sasl_auth_mech, "PLAIN")) {
+ cprintf("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
+ cprintf("<invalid-mechanism/>");
+ cprintf("</failure>");
+ return;
+ }
+
+ if (CC->logged_in) {
+ CtdlUserLogout(); /* Client may try to log in twice. Handle this. */
+ }
+
+ if (CC->nologin) {
+ cprintf("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
+ cprintf("<system-shutdown/>");
+ cprintf("</failure>");
+ }
+
+ else if (xmpp_auth_plain(authstring) == 0) {
+ cprintf("<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>");
+ }
+
+ else {
+ cprintf("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
+ cprintf("<not-authorized/>");
+ cprintf("</failure>");
+ }
+}
+
+
+/*
+ * Non-SASL authentication
+ */
+void xmpp_non_sasl_authenticate(char *iq_id, char *username, char *password) {
+ int result;
+ char xmlbuf[256];
+
+ if (CC->logged_in) {
+ CtdlUserLogout(); /* Client may try to log in twice. Handle this. */
+ }
+
+ result = CtdlLoginExistingUser(username);
+ if (result == login_ok) {
+ result = CtdlTryPassword(password, strlen(password));
+ if (result == pass_ok) {
+ cprintf("<iq type=\"result\" id=\"%s\"></iq>", xmlesc(xmlbuf, iq_id, sizeof xmlbuf)); /* success */
+ return;
+ }
+ }
+
+ /* failure */
+ cprintf("<iq type=\"error\" id=\"%s\">", xmlesc(xmlbuf, iq_id, sizeof xmlbuf));
+ cprintf("<error code=\"401\" type=\"auth\">"
+ "<not-authorized xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
+ "</error>"
+ "</iq>"
+ );
+}
--- /dev/null
+// Module initialization hub
+//
+// 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.
+//
+// To add new modules to the server:
+// 1. Write the module and place it in a server/modules/[module_name]/ directory
+// 2. Add its initialization function to server/modules_init.h
+// 3. Call its initialization function from server/modules_init.c
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <stdio.h>
+#include "modules_init.h"
+
+// Module initialization functions will be called TWICE during startup, once before
+// the server has gone into multithreading mode, and once afterwards. Most modules
+// can be initialized while in multithreading mode, but your module code should be
+// prepared for this kind of initialization. Look at the existing modules to see what
+// goes on there.
+int threading = 0;
+
+void initialize_modules(int is_threading) {
+ threading = is_threading;
+ syslog(LOG_DEBUG, "modules: begin initializing modules (threading=%d)", threading);
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_control());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_euidindex());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_msgbase());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_database());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_autocompletion());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_bio());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_blog());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_calendar());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_checkpoint());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_virus());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_file_ops());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_ctdl_message());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_rooms());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_serv_session());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_syscmd());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_serv_user());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_expire());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_fulltext());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_image());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_imap());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_sieve());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_inetcfg());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_instmsg());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_listdeliver());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_listsub());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_migrate());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_newuser());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_nntp());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_notes());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_openid_rp());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_pop3());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_pop3client());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_roomchat());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_rssclient());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_rwho());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_smtp());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_smtpclient());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_spam());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_test());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_upgrade());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_vcard());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_wiki());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_xmpp());
+ syslog(LOG_DEBUG, "modules: init %s", ctdl_module_init_netconfig());
+ syslog(LOG_DEBUG, "modules: finished initializing modules (threading=%d)", threading);
+}
--- /dev/null
+// Headers for module initialization hub
+//
+// 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.
+//
+// To add new modules to the server:
+// 1. Write the module and place it in a server/modules/[module_name]/ directory
+// 2. Add its initialization function to server/modules_init.h
+// 3. Call its initialization function from server/modules_init.c
+
+#ifndef MODULES_INIT_H
+#define MODULES_INIT_H
+#include "ctdl_module.h"
+extern size_t nSizErrmsg;
+void initialize_modules (int threading);
+void pre_startup_upgrades(void);
+char *ctdl_module_init_control(void);
+char *ctdl_module_init_euidindex(void);
+char *ctdl_module_init_msgbase(void);
+char *ctdl_module_init_database(void);
+char *ctdl_module_init_autocompletion(void);
+char *ctdl_module_init_bio(void);
+char *ctdl_module_init_blog(void);
+char *ctdl_module_init_calendar(void);
+char *ctdl_module_init_checkpoint(void);
+char *ctdl_module_init_virus(void);
+char *ctdl_module_init_file_ops(void);
+char *ctdl_module_init_ctdl_message(void);
+char *ctdl_module_init_rooms(void);
+char *ctdl_module_init_serv_session(void);
+char *ctdl_module_init_syscmd(void);
+char *ctdl_module_init_serv_user(void);
+char *ctdl_module_init_expire(void);
+char *ctdl_module_init_fulltext(void);
+char *ctdl_module_init_image(void);
+char *ctdl_module_init_imap(void);
+char *ctdl_module_init_sieve(void);
+char *ctdl_module_init_inetcfg(void);
+char *ctdl_module_init_instmsg(void);
+char *ctdl_module_init_listdeliver(void);
+char *ctdl_module_init_listsub(void);
+char *ctdl_module_init_migrate(void);
+char *ctdl_module_init_newuser(void);
+char *ctdl_module_init_nntp(void);
+char *ctdl_module_init_notes(void);
+char *ctdl_module_init_openid_rp(void);
+char *ctdl_module_init_pop3(void);
+char *ctdl_module_init_pop3client(void);
+char *ctdl_module_init_roomchat(void);
+char *ctdl_module_init_rssclient(void);
+char *ctdl_module_init_rwho(void);
+char *ctdl_module_init_smtp(void);
+char *ctdl_module_init_smtpclient(void);
+char *ctdl_module_init_spam(void);
+char *ctdl_module_init_test(void);
+char *ctdl_module_init_upgrade(void);
+char *ctdl_module_init_vcard(void);
+char *ctdl_module_init_wiki(void);
+char *ctdl_module_init_xmpp(void);
+char *ctdl_module_init_netconfig(void);
+#endif /* MODULES_INIT_H */
--- /dev/null
+// Implements the message store.
+//
+// Copyright (c) 1987-2022 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.
+
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <regex.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <libcitadel.h>
+#include "ctdl_module.h"
+#include "citserver.h"
+#include "control.h"
+#include "config.h"
+#include "clientsocket.h"
+#include "genstamp.h"
+#include "room_ops.h"
+#include "user_ops.h"
+#include "internet_addressing.h"
+#include "euidindex.h"
+#include "msgbase.h"
+#include "journaling.h"
+
+struct addresses_to_be_filed *atbf = NULL;
+
+// These are the four-character field headers we use when outputting
+// messages in Citadel format (as opposed to RFC822 format).
+char *msgkeys[] = {
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL,
+ "from", // A -> eAuthor
+ NULL, // B -> eBig_message
+ NULL, // C (formerly used as eRemoteRoom)
+ NULL, // D (formerly used as eDestination)
+ "exti", // E -> eXclusivID
+ "rfca", // F -> erFc822Addr
+ NULL, // G
+ "hnod", // H (formerly used as eHumanNode)
+ "msgn", // I -> emessageId
+ "jrnl", // J -> eJournal
+ "rep2", // K -> eReplyTo
+ "list", // L -> eListID
+ "text", // M -> eMesageText
+ NULL, // N (formerly used as eNodename)
+ "room", // O -> eOriginalRoom
+ "path", // P -> eMessagePath
+ NULL, // Q
+ "rcpt", // R -> eRecipient
+ NULL, // S (formerly used as eSpecialField)
+ "time", // T -> eTimestamp
+ "subj", // U -> eMsgSubject
+ "nvto", // V -> eenVelopeTo
+ "wefw", // W -> eWeferences
+ NULL, // X
+ "cccc", // Y -> eCarbonCopY
+ NULL // Z
+};
+
+
+HashList *msgKeyLookup = NULL;
+
+int GetFieldFromMnemonic(eMsgField *f, const char* c) {
+ void *v = NULL;
+ if (GetHash(msgKeyLookup, c, 4, &v)) {
+ *f = (eMsgField) v;
+ return 1;
+ }
+ return 0;
+}
+
+void FillMsgKeyLookupTable(void) {
+ long i;
+
+ msgKeyLookup = NewHash (1, FourHash);
+
+ for (i=0; i < 91; i++) {
+ if (msgkeys[i] != NULL) {
+ Put(msgKeyLookup, msgkeys[i], 4, (void*)i, reference_free_handler);
+ }
+ }
+}
+
+
+eMsgField FieldOrder[] = {
+/* Important fields */
+ emessageId ,
+ eMessagePath ,
+ eTimestamp ,
+ eAuthor ,
+ erFc822Addr ,
+ eOriginalRoom,
+ eRecipient ,
+/* Semi-important fields */
+ eBig_message ,
+ eExclusiveID ,
+ eWeferences ,
+ eJournal ,
+/* G is not used yet */
+ eReplyTo ,
+ eListID ,
+/* Q is not used yet */
+ eenVelopeTo ,
+/* X is not used yet */
+/* Z is not used yet */
+ eCarbonCopY ,
+ eMsgSubject ,
+/* internal only */
+ eErrorMsg ,
+ eSuppressIdx ,
+ eExtnotify ,
+/* Message text (MUST be last) */
+ eMesageText
+/* Not saved to disk:
+ eVltMsgNum
+*/
+};
+
+static const long NDiskFields = sizeof(FieldOrder) / sizeof(eMsgField);
+
+
+int CM_IsEmpty(struct CtdlMessage *Msg, eMsgField which) {
+ return !((Msg->cm_fields[which] != NULL) && (Msg->cm_fields[which][0] != '\0'));
+}
+
+
+void CM_SetField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length) {
+ if (Msg->cm_fields[which] != NULL) {
+ free (Msg->cm_fields[which]);
+ }
+ if (length < 0) { // You can set the length to -1 to have CM_SetField measure it for you
+ length = strlen(buf);
+ }
+ Msg->cm_fields[which] = malloc(length + 1);
+ memcpy(Msg->cm_fields[which], buf, length);
+ Msg->cm_fields[which][length] = '\0';
+ Msg->cm_lengths[which] = length;
+}
+
+
+void CM_SetFieldLONG(struct CtdlMessage *Msg, eMsgField which, long lvalue) {
+ char buf[128];
+ long len;
+ len = snprintf(buf, sizeof(buf), "%ld", lvalue);
+ CM_SetField(Msg, which, buf, len);
+}
+
+
+void CM_CutFieldAt(struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen) {
+ if (Msg->cm_fields[WhichToCut] == NULL)
+ return;
+
+ if (Msg->cm_lengths[WhichToCut] > maxlen)
+ {
+ Msg->cm_fields[WhichToCut][maxlen] = '\0';
+ Msg->cm_lengths[WhichToCut] = maxlen;
+ }
+}
+
+
+void CM_FlushField(struct CtdlMessage *Msg, eMsgField which) {
+ if (Msg->cm_fields[which] != NULL)
+ free (Msg->cm_fields[which]);
+ Msg->cm_fields[which] = NULL;
+ Msg->cm_lengths[which] = 0;
+}
+
+
+void CM_Flush(struct CtdlMessage *Msg) {
+ int i;
+
+ if (CM_IsValidMsg(Msg) == 0) {
+ return;
+ }
+
+ for (i = 0; i < 256; ++i) {
+ CM_FlushField(Msg, i);
+ }
+}
+
+
+void CM_CopyField(struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy) {
+ long len;
+ if (Msg->cm_fields[WhichToPutTo] != NULL) {
+ free (Msg->cm_fields[WhichToPutTo]);
+ }
+
+ if (Msg->cm_fields[WhichtToCopy] != NULL) {
+ len = Msg->cm_lengths[WhichtToCopy];
+ Msg->cm_fields[WhichToPutTo] = malloc(len + 1);
+ memcpy(Msg->cm_fields[WhichToPutTo], Msg->cm_fields[WhichtToCopy], len);
+ Msg->cm_fields[WhichToPutTo][len] = '\0';
+ Msg->cm_lengths[WhichToPutTo] = len;
+ }
+ else {
+ Msg->cm_fields[WhichToPutTo] = NULL;
+ Msg->cm_lengths[WhichToPutTo] = 0;
+ }
+}
+
+
+void CM_PrependToField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length) {
+ if (Msg->cm_fields[which] != NULL) {
+ long oldmsgsize;
+ long newmsgsize;
+ char *new;
+
+ oldmsgsize = Msg->cm_lengths[which] + 1;
+ newmsgsize = length + oldmsgsize;
+
+ new = malloc(newmsgsize);
+ memcpy(new, buf, length);
+ memcpy(new + length, Msg->cm_fields[which], oldmsgsize);
+ free(Msg->cm_fields[which]);
+ Msg->cm_fields[which] = new;
+ Msg->cm_lengths[which] = newmsgsize - 1;
+ }
+ else {
+ Msg->cm_fields[which] = malloc(length + 1);
+ memcpy(Msg->cm_fields[which], buf, length);
+ Msg->cm_fields[which][length] = '\0';
+ Msg->cm_lengths[which] = length;
+ }
+}
+
+
+void CM_SetAsField(struct CtdlMessage *Msg, eMsgField which, char **buf, long length) {
+ if (Msg->cm_fields[which] != NULL) {
+ free (Msg->cm_fields[which]);
+ }
+
+ Msg->cm_fields[which] = *buf;
+ *buf = NULL;
+ if (length < 0) { // You can set the length to -1 to have CM_SetField measure it for you
+ Msg->cm_lengths[which] = strlen(Msg->cm_fields[which]);
+ }
+ else {
+ Msg->cm_lengths[which] = length;
+ }
+}
+
+
+void CM_SetAsFieldSB(struct CtdlMessage *Msg, eMsgField which, StrBuf **buf) {
+ if (Msg->cm_fields[which] != NULL) {
+ free (Msg->cm_fields[which]);
+ }
+
+ Msg->cm_lengths[which] = StrLength(*buf);
+ Msg->cm_fields[which] = SmashStrBuf(buf);
+}
+
+
+void CM_GetAsField(struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen) {
+ if (Msg->cm_fields[which] != NULL) {
+ *retlen = Msg->cm_lengths[which];
+ *ret = Msg->cm_fields[which];
+ Msg->cm_fields[which] = NULL;
+ Msg->cm_lengths[which] = 0;
+ }
+ else {
+ *ret = NULL;
+ *retlen = 0;
+ }
+}
+
+
+// Returns 1 if the supplied pointer points to a valid Citadel message.
+// If the pointer is NULL or the magic number check fails, returns 0.
+int CM_IsValidMsg(struct CtdlMessage *msg) {
+ if (msg == NULL) {
+ return 0;
+ }
+ if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
+ syslog(LOG_WARNING, "msgbase: CM_IsValidMsg() self-check failed");
+ return 0;
+ }
+ return 1;
+}
+
+
+void CM_FreeContents(struct CtdlMessage *msg) {
+ int i;
+
+ for (i = 0; i < 256; ++i)
+ if (msg->cm_fields[i] != NULL) {
+ free(msg->cm_fields[i]);
+ msg->cm_lengths[i] = 0;
+ }
+
+ msg->cm_magic = 0; // just in case
+}
+
+
+// 'Destructor' for struct CtdlMessage
+void CM_Free(struct CtdlMessage *msg) {
+ if (CM_IsValidMsg(msg) == 0) {
+ if (msg != NULL) free (msg);
+ return;
+ }
+ CM_FreeContents(msg);
+ free(msg);
+}
+
+
+int CM_DupField(eMsgField i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg) {
+ long len;
+ len = OrgMsg->cm_lengths[i];
+ NewMsg->cm_fields[i] = malloc(len + 1);
+ if (NewMsg->cm_fields[i] == NULL) {
+ return 0;
+ }
+ memcpy(NewMsg->cm_fields[i], OrgMsg->cm_fields[i], len);
+ NewMsg->cm_fields[i][len] = '\0';
+ NewMsg->cm_lengths[i] = len;
+ return 1;
+}
+
+
+struct CtdlMessage *CM_Duplicate(struct CtdlMessage *OrgMsg) {
+ int i;
+ struct CtdlMessage *NewMsg;
+
+ if (CM_IsValidMsg(OrgMsg) == 0) {
+ return NULL;
+ }
+ NewMsg = (struct CtdlMessage *)malloc(sizeof(struct CtdlMessage));
+ if (NewMsg == NULL) {
+ return NULL;
+ }
+
+ memcpy(NewMsg, OrgMsg, sizeof(struct CtdlMessage));
+
+ memset(&NewMsg->cm_fields, 0, sizeof(char*) * 256);
+
+ for (i = 0; i < 256; ++i) {
+ if (OrgMsg->cm_fields[i] != NULL) {
+ if (!CM_DupField(i, OrgMsg, NewMsg)) {
+ CM_Free(NewMsg);
+ return NULL;
+ }
+ }
+ }
+
+ return NewMsg;
+}
+
+
+// Determine if a given message matches the fields in a message template.
+// Return 0 for a successful match.
+int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
+ int i;
+
+ // If there aren't any fields in the template, all messages will match.
+ if (template == NULL) return(0);
+
+ // Null messages are bogus.
+ if (msg == NULL) return(1);
+
+ for (i='A'; i<='Z'; ++i) {
+ if (template->cm_fields[i] != NULL) {
+ if (msg->cm_fields[i] == NULL) {
+ // Considered equal if temmplate is empty string
+ if (IsEmptyStr(template->cm_fields[i])) continue;
+ return 1;
+ }
+ if ((template->cm_lengths[i] != msg->cm_lengths[i]) ||
+ (strcasecmp(msg->cm_fields[i], template->cm_fields[i])))
+ return 1;
+ }
+ }
+
+ // All compares succeeded: we have a match!
+ return 0;
+}
+
+
+// Retrieve the "seen" message list for the current room.
+void CtdlGetSeen(char *buf, int which_set) {
+ visit vbuf;
+
+ // Learn about the user and room in question
+ CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+
+ if (which_set == ctdlsetseen_seen) {
+ safestrncpy(buf, vbuf.v_seen, SIZ);
+ }
+ if (which_set == ctdlsetseen_answered) {
+ safestrncpy(buf, vbuf.v_answered, SIZ);
+ }
+}
+
+
+// Manipulate the "seen msgs" string (or other message set strings)
+void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
+ int target_setting, int which_set,
+ struct ctdluser *which_user, struct ctdlroom *which_room) {
+ struct cdbdata *cdbfr;
+ int i, k;
+ int is_seen = 0;
+ int was_seen = 0;
+ long lo = (-1L);
+ long hi = (-1L);
+ visit vbuf;
+ long *msglist;
+ int num_msgs = 0;
+ StrBuf *vset;
+ StrBuf *setstr;
+ StrBuf *lostr;
+ StrBuf *histr;
+ const char *pvset;
+ char *is_set; // actually an array of booleans
+
+ // Don't bother doing *anything* if we were passed a list of zero messages
+ if (num_target_msgnums < 1) {
+ return;
+ }
+
+ // If no room was specified, we go with the current room.
+ if (!which_room) {
+ which_room = &CC->room;
+ }
+
+ // If no user was specified, we go with the current user.
+ if (!which_user) {
+ which_user = &CC->user;
+ }
+
+ syslog(LOG_DEBUG, "msgbase: CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>",
+ num_target_msgnums, target_msgnums[0],
+ (target_setting ? "SET" : "CLEAR"),
+ which_set,
+ which_room->QRname);
+
+ // Learn about the user and room in question
+ CtdlGetRelationship(&vbuf, which_user, which_room);
+
+ // Load the message list
+ cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
+ if (cdbfr != NULL) {
+ msglist = (long *) cdbfr->ptr;
+ cdbfr->ptr = NULL; // CtdlSetSeen() now owns this memory
+ num_msgs = cdbfr->len / sizeof(long);
+ cdb_free(cdbfr);
+ }
+ else {
+ return; // No messages at all? No further action.
+ }
+
+ is_set = malloc(num_msgs * sizeof(char));
+ memset(is_set, 0, (num_msgs * sizeof(char)) );
+
+ // Decide which message set we're manipulating
+ switch(which_set) {
+ case ctdlsetseen_seen:
+ vset = NewStrBufPlain(vbuf.v_seen, -1);
+ break;
+ case ctdlsetseen_answered:
+ vset = NewStrBufPlain(vbuf.v_answered, -1);
+ break;
+ default:
+ vset = NewStrBuf();
+ }
+
+
+#if 0 // This is a special diagnostic section. Do not allow it to run during normal operation.
+ syslog(LOG_DEBUG, "There are %d messages in the room.\n", num_msgs);
+ for (i=0; i<num_msgs; ++i) {
+ if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
+ }
+ syslog(LOG_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
+ for (k=0; k<num_target_msgnums; ++k) {
+ if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
+ }
+#endif
+
+ // Translate the existing sequence set into an array of booleans
+ setstr = NewStrBuf();
+ lostr = NewStrBuf();
+ histr = NewStrBuf();
+ pvset = NULL;
+ while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
+
+ StrBufExtract_token(lostr, setstr, 0, ':');
+ if (StrBufNum_tokens(setstr, ':') >= 2) {
+ StrBufExtract_token(histr, setstr, 1, ':');
+ }
+ else {
+ FlushStrBuf(histr);
+ StrBufAppendBuf(histr, lostr, 0);
+ }
+ lo = StrTol(lostr);
+ if (!strcmp(ChrPtr(histr), "*")) {
+ hi = LONG_MAX;
+ }
+ else {
+ hi = StrTol(histr);
+ }
+
+ for (i = 0; i < num_msgs; ++i) {
+ if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
+ is_set[i] = 1;
+ }
+ }
+ }
+ FreeStrBuf(&setstr);
+ FreeStrBuf(&lostr);
+ FreeStrBuf(&histr);
+
+ // Now translate the array of booleans back into a sequence set
+ FlushStrBuf(vset);
+ was_seen = 0;
+ lo = (-1);
+ hi = (-1);
+
+ for (i=0; i<num_msgs; ++i) {
+ is_seen = is_set[i];
+
+ // Apply changes
+ for (k=0; k<num_target_msgnums; ++k) {
+ if (msglist[i] == target_msgnums[k]) {
+ is_seen = target_setting;
+ }
+ }
+
+ if ((was_seen == 0) && (is_seen == 1)) {
+ lo = msglist[i];
+ }
+ else if ((was_seen == 1) && (is_seen == 0)) {
+ hi = msglist[i-1];
+
+ if (StrLength(vset) > 0) {
+ StrBufAppendBufPlain(vset, HKEY(","), 0);
+ }
+ if (lo == hi) {
+ StrBufAppendPrintf(vset, "%ld", hi);
+ }
+ else {
+ StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
+ }
+ }
+
+ if ((is_seen) && (i == num_msgs - 1)) {
+ if (StrLength(vset) > 0) {
+ StrBufAppendBufPlain(vset, HKEY(","), 0);
+ }
+ if ((i==0) || (was_seen == 0)) {
+ StrBufAppendPrintf(vset, "%ld", msglist[i]);
+ }
+ else {
+ StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
+ }
+ }
+
+ was_seen = is_seen;
+ }
+
+ // We will have to stuff this string back into a 4096 byte buffer, so if it's
+ // larger than that now, truncate it by removing tokens from the beginning.
+ // The limit of 100 iterations is there to prevent an infinite loop in case
+ // something unexpected happens.
+ int number_of_truncations = 0;
+ while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
+ StrBufRemove_token(vset, 0, ',');
+ ++number_of_truncations;
+ }
+
+ // If we're truncating the sequence set of messages marked with the 'seen' flag,
+ // we want the earliest messages (the truncated ones) to be marked, not unmarked.
+ // Otherwise messages at the beginning will suddenly appear to be 'unseen'.
+ if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
+ StrBuf *first_tok;
+ first_tok = NewStrBuf();
+ StrBufExtract_token(first_tok, vset, 0, ',');
+ StrBufRemove_token(vset, 0, ',');
+
+ if (StrBufNum_tokens(first_tok, ':') > 1) {
+ StrBufRemove_token(first_tok, 0, ':');
+ }
+
+ StrBuf *new_set;
+ new_set = NewStrBuf();
+ StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
+ StrBufAppendBuf(new_set, first_tok, 0);
+ StrBufAppendBufPlain(new_set, HKEY(":"), 0);
+ StrBufAppendBuf(new_set, vset, 0);
+
+ FreeStrBuf(&vset);
+ FreeStrBuf(&first_tok);
+ vset = new_set;
+ }
+
+ // Decide which message set we're manipulating
+ switch (which_set) {
+ case ctdlsetseen_seen:
+ safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
+ break;
+ case ctdlsetseen_answered:
+ safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
+ break;
+ }
+
+ free(is_set);
+ free(msglist);
+ CtdlSetRelationship(&vbuf, which_user, which_room);
+ FreeStrBuf(&vset);
+}
+
+
+// API function to perform an operation for each qualifying message in the
+// current room. (Returns the number of messages processed.)
+int CtdlForEachMessage(int mode, long ref, char *search_string,
+ char *content_type,
+ struct CtdlMessage *compare,
+ ForEachMsgCallback CallBack,
+ void *userdata)
+{
+ int a, i, j;
+ visit vbuf;
+ struct cdbdata *cdbfr;
+ long *msglist = NULL;
+ int num_msgs = 0;
+ int num_processed = 0;
+ long thismsg;
+ struct MetaData smi;
+ struct CtdlMessage *msg = NULL;
+ int is_seen = 0;
+ long lastold = 0L;
+ int printed_lastold = 0;
+ int num_search_msgs = 0;
+ long *search_msgs = NULL;
+ regex_t re;
+ int need_to_free_re = 0;
+ regmatch_t pm;
+
+ if ((content_type) && (!IsEmptyStr(content_type))) {
+ regcomp(&re, content_type, 0);
+ need_to_free_re = 1;
+ }
+
+ // Learn about the user and room in question
+ if (server_shutting_down) {
+ if (need_to_free_re) regfree(&re);
+ return -1;
+ }
+ CtdlGetUser(&CC->user, CC->curr_user);
+
+ if (server_shutting_down) {
+ if (need_to_free_re) regfree(&re);
+ return -1;
+ }
+ CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+
+ if (server_shutting_down) {
+ if (need_to_free_re) regfree(&re);
+ return -1;
+ }
+
+ // Load the message list
+ cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
+ if (cdbfr == NULL) {
+ if (need_to_free_re) regfree(&re);
+ return 0; // No messages at all? No further action.
+ }
+
+ msglist = (long *) cdbfr->ptr;
+ num_msgs = cdbfr->len / sizeof(long);
+
+ cdbfr->ptr = NULL; // clear this so that cdb_free() doesn't free it
+ cdb_free(cdbfr); // we own this memory now
+
+ /*
+ * Now begin the traversal.
+ */
+ if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
+
+ /* If the caller is looking for a specific MIME type, filter
+ * out all messages which are not of the type requested.
+ */
+ if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
+
+ /* This call to GetMetaData() sits inside this loop
+ * so that we only do the extra database read per msg
+ * if we need to. Doing the extra read all the time
+ * really kills the server. If we ever need to use
+ * metadata for another search criterion, we need to
+ * move the read somewhere else -- but still be smart
+ * enough to only do the read if the caller has
+ * specified something that will need it.
+ */
+ if (server_shutting_down) {
+ if (need_to_free_re) regfree(&re);
+ free(msglist);
+ return -1;
+ }
+ GetMetaData(&smi, msglist[a]);
+
+ /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
+ if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
+ msglist[a] = 0L;
+ }
+ }
+ }
+
+ num_msgs = sort_msglist(msglist, num_msgs);
+
+ /* If a template was supplied, filter out the messages which
+ * don't match. (This could induce some delays!)
+ */
+ if (num_msgs > 0) {
+ if (compare != NULL) {
+ for (a = 0; a < num_msgs; ++a) {
+ if (server_shutting_down) {
+ if (need_to_free_re) regfree(&re);
+ free(msglist);
+ return -1;
+ }
+ msg = CtdlFetchMessage(msglist[a], 1);
+ if (msg != NULL) {
+ if (CtdlMsgCmp(msg, compare)) {
+ msglist[a] = 0L;
+ }
+ CM_Free(msg);
+ }
+ }
+ }
+ }
+
+ /* If a search string was specified, get a message list from
+ * the full text index and remove messages which aren't on both
+ * lists.
+ *
+ * How this works:
+ * Since the lists are sorted and strictly ascending, and the
+ * output list is guaranteed to be shorter than or equal to the
+ * input list, we overwrite the bottom of the input list. This
+ * eliminates the need to memmove big chunks of the list over and
+ * over again.
+ */
+ if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
+
+ /* Call search module via hook mechanism.
+ * NULL means use any search function available.
+ * otherwise replace with a char * to name of search routine
+ */
+ CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
+
+ if (num_search_msgs > 0) {
+
+ int orig_num_msgs;
+
+ orig_num_msgs = num_msgs;
+ num_msgs = 0;
+ for (i=0; i<orig_num_msgs; ++i) {
+ for (j=0; j<num_search_msgs; ++j) {
+ if (msglist[i] == search_msgs[j]) {
+ msglist[num_msgs++] = msglist[i];
+ }
+ }
+ }
+ }
+ else {
+ num_msgs = 0; /* No messages qualify */
+ }
+ if (search_msgs != NULL) free(search_msgs);
+
+ /* Now that we've purged messages which don't contain the search
+ * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
+ * point on.
+ */
+ mode = MSGS_ALL;
+ }
+
+ /*
+ * Now iterate through the message list, according to the
+ * criteria supplied by the caller.
+ */
+ if (num_msgs > 0)
+ for (a = 0; a < num_msgs; ++a) {
+ if (server_shutting_down) {
+ if (need_to_free_re) regfree(&re);
+ free(msglist);
+ return num_processed;
+ }
+ thismsg = msglist[a];
+ if (mode == MSGS_ALL) {
+ is_seen = 0;
+ }
+ else {
+ is_seen = is_msg_in_sequence_set(vbuf.v_seen, thismsg);
+ if (is_seen) lastold = thismsg;
+ }
+ if (
+ (thismsg > 0L)
+ && (
+ (mode == MSGS_ALL)
+ || ((mode == MSGS_OLD) && (is_seen))
+ || ((mode == MSGS_NEW) && (!is_seen))
+ || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
+ || ((mode == MSGS_FIRST) && (a < ref))
+ || ((mode == MSGS_GT) && (thismsg > ref))
+ || ((mode == MSGS_LT) && (thismsg < ref))
+ || ((mode == MSGS_EQ) && (thismsg == ref))
+ )
+ ) {
+ if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
+ if (CallBack) {
+ CallBack(lastold, userdata);
+ }
+ printed_lastold = 1;
+ ++num_processed;
+ }
+ if (CallBack) {
+ CallBack(thismsg, userdata);
+ }
+ ++num_processed;
+ }
+ }
+ if (need_to_free_re) regfree(&re);
+
+ /*
+ * We cache the most recent msglist in order to do security checks later
+ */
+ if (CC->client_socket > 0) {
+ if (CC->cached_msglist != NULL) {
+ free(CC->cached_msglist);
+ }
+ CC->cached_msglist = msglist;
+ CC->cached_num_msgs = num_msgs;
+ }
+ else {
+ free(msglist);
+ }
+
+ return num_processed;
+}
+
+
+/*
+ * memfmout() - Citadel text formatter and paginator.
+ * Although the original purpose of this routine was to format
+ * text to the reader's screen width, all we're really using it
+ * for here is to format text out to 80 columns before sending it
+ * to the client. The client software may reformat it again.
+ */
+void memfmout(
+ char *mptr, /* where are we going to get our text from? */
+ const char *nl /* string to terminate lines with */
+) {
+ int column = 0;
+ unsigned char ch = 0;
+ char outbuf[1024];
+ int len = 0;
+ int nllen = 0;
+
+ if (!mptr) return;
+ nllen = strlen(nl);
+ while (ch=*(mptr++), ch != 0) {
+
+ if (ch == '\n') {
+ if (client_write(outbuf, len) == -1) {
+ syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
+ return;
+ }
+ len = 0;
+ if (client_write(nl, nllen) == -1) {
+ syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
+ return;
+ }
+ column = 0;
+ }
+ else if (ch == '\r') {
+ /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
+ }
+ else if (isspace(ch)) {
+ if (column > 72) { /* Beyond 72 columns, break on the next space */
+ if (client_write(outbuf, len) == -1) {
+ syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
+ return;
+ }
+ len = 0;
+ if (client_write(nl, nllen) == -1) {
+ syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
+ return;
+ }
+ column = 0;
+ }
+ else {
+ outbuf[len++] = ch;
+ ++column;
+ }
+ }
+ else {
+ outbuf[len++] = ch;
+ ++column;
+ if (column > 1000) { /* Beyond 1000 columns, break anywhere */
+ if (client_write(outbuf, len) == -1) {
+ syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
+ return;
+ }
+ len = 0;
+ if (client_write(nl, nllen) == -1) {
+ syslog(LOG_ERR, "msgbase: memfmout(): aborting due to write failure");
+ return;
+ }
+ column = 0;
+ }
+ }
+ }
+ if (len) {
+ if (client_write(outbuf, len) == -1) {
+ syslog(LOG_ERR, "msgbase: memfmout() aborting due to write failure");
+ return;
+ }
+ client_write(nl, nllen);
+ column = 0;
+ }
+}
+
+
+/*
+ * Callback function for mime parser that simply lists the part
+ */
+void list_this_part(char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+ char *cbid, void *cbuserdata)
+{
+ struct ma_info *ma;
+
+ ma = (struct ma_info *)cbuserdata;
+ if (ma->is_ma == 0) {
+ cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
+ name,
+ filename,
+ partnum,
+ disp,
+ cbtype,
+ (long)length,
+ cbid,
+ cbcharset);
+ }
+}
+
+
+/*
+ * Callback function for multipart prefix
+ */
+void list_this_pref(char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+ char *cbid, void *cbuserdata)
+{
+ struct ma_info *ma;
+
+ ma = (struct ma_info *)cbuserdata;
+ if (!strcasecmp(cbtype, "multipart/alternative")) {
+ ++ma->is_ma;
+ }
+
+ if (ma->is_ma == 0) {
+ cprintf("pref=%s|%s\n", partnum, cbtype);
+ }
+}
+
+
+/*
+ * Callback function for multipart sufffix
+ */
+void list_this_suff(char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+ char *cbid, void *cbuserdata)
+{
+ struct ma_info *ma;
+
+ ma = (struct ma_info *)cbuserdata;
+ if (ma->is_ma == 0) {
+ cprintf("suff=%s|%s\n", partnum, cbtype);
+ }
+ if (!strcasecmp(cbtype, "multipart/alternative")) {
+ --ma->is_ma;
+ }
+}
+
+
+/*
+ * Callback function for mime parser that opens a section for downloading
+ * we use serv_files function here:
+ */
+extern void OpenCmdResult(char *filename, const char *mime_type);
+void mime_download(char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length,
+ char *encoding, char *cbid, void *cbuserdata)
+{
+ int rv = 0;
+
+ /* Silently go away if there's already a download open. */
+ if (CC->download_fp != NULL)
+ return;
+
+ if (
+ (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
+ || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
+ ) {
+ CC->download_fp = tmpfile();
+ if (CC->download_fp == NULL) {
+ syslog(LOG_ERR, "msgbase: mime_download() couldn't write: %m");
+ cprintf("%d cannot open temporary file: %s\n", ERROR + INTERNAL_ERROR, strerror(errno));
+ return;
+ }
+
+ rv = fwrite(content, length, 1, CC->download_fp);
+ if (rv <= 0) {
+ syslog(LOG_ERR, "msgbase: mime_download() Couldn't write: %m");
+ cprintf("%d unable to write tempfile.\n", ERROR + TOO_BIG);
+ fclose(CC->download_fp);
+ CC->download_fp = NULL;
+ return;
+ }
+ fflush(CC->download_fp);
+ rewind(CC->download_fp);
+
+ OpenCmdResult(filename, cbtype);
+ }
+}
+
+
+/*
+ * Callback function for mime parser that outputs a section all at once.
+ * We can specify the desired section by part number *or* content-id.
+ */
+void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length,
+ char *encoding, char *cbid, void *cbuserdata)
+{
+ int *found_it = (int *)cbuserdata;
+
+ if (
+ (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
+ || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
+ ) {
+ *found_it = 1;
+ cprintf("%d %d|-1|%s|%s|%s\n",
+ BINARY_FOLLOWS,
+ (int)length,
+ filename,
+ cbtype,
+ cbcharset
+ );
+ client_write(content, length);
+ }
+}
+
+
+struct CtdlMessage *CtdlDeserializeMessage(long msgnum, int with_body, const char *Buffer, long Length) {
+ struct CtdlMessage *ret = NULL;
+ const char *mptr;
+ const char *upper_bound;
+ cit_uint8_t ch;
+ cit_uint8_t field_header;
+ eMsgField which;
+
+ mptr = Buffer;
+ upper_bound = Buffer + Length;
+ if (msgnum <= 0) {
+ return NULL;
+ }
+
+ // Parse the three bytes that begin EVERY message on disk.
+ // The first is always 0xFF, the on-disk magic number.
+ // The second is the anonymous/public type byte.
+ // The third is the format type byte (vari, fixed, or MIME).
+ //
+ ch = *mptr++;
+ if (ch != 255) {
+ syslog(LOG_ERR, "msgbase: message %ld appears to be corrupted", msgnum);
+ return NULL;
+ }
+ ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
+ memset(ret, 0, sizeof(struct CtdlMessage));
+
+ ret->cm_magic = CTDLMESSAGE_MAGIC;
+ ret->cm_anon_type = *mptr++; // Anon type byte
+ ret->cm_format_type = *mptr++; // Format type byte
+
+ // The rest is zero or more arbitrary fields. Load them in.
+ // We're done when we encounter either a zero-length field or
+ // have just processed the 'M' (message text) field.
+ //
+ do {
+ field_header = '\0';
+ long len;
+
+ while (field_header == '\0') { // work around possibly buggy messages
+ if (mptr >= upper_bound) {
+ break;
+ }
+ field_header = *mptr++;
+ }
+ if (mptr >= upper_bound) {
+ break;
+ }
+ which = field_header;
+ len = strlen(mptr);
+
+ CM_SetField(ret, which, mptr, len);
+
+ mptr += len + 1; // advance to next field
+
+ } while ((mptr < upper_bound) && (field_header != 'M'));
+ return (ret);
+}
+
+
+// Load a message from disk into memory.
+// This is used by CtdlOutputMsg() and other fetch functions.
+//
+// NOTE: Caller is responsible for freeing the returned CtdlMessage struct
+// using the CM_Free(); function.
+//
+struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body) {
+ struct cdbdata *dmsgtext;
+ struct CtdlMessage *ret = NULL;
+
+ syslog(LOG_DEBUG, "msgbase: CtdlFetchMessage(%ld, %d)", msgnum, with_body);
+ dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
+ if (dmsgtext == NULL) {
+ syslog(LOG_ERR, "msgbase: message #%ld was not found", msgnum);
+ return NULL;
+ }
+
+ if (dmsgtext->ptr[dmsgtext->len - 1] != '\0') {
+ syslog(LOG_ERR, "msgbase: CtdlFetchMessage(%ld, %d) Forcefully terminating message!!", msgnum, with_body);
+ dmsgtext->ptr[dmsgtext->len - 1] = '\0';
+ }
+
+ ret = CtdlDeserializeMessage(msgnum, with_body, dmsgtext->ptr, dmsgtext->len);
+
+ cdb_free(dmsgtext);
+
+ if (ret == NULL) {
+ return NULL;
+ }
+
+ // Always make sure there's something in the msg text field. If
+ // it's NULL, the message text is most likely stored separately,
+ // so go ahead and fetch that. Failing that, just set a dummy
+ // body so other code doesn't barf.
+ //
+ if ( (CM_IsEmpty(ret, eMesageText)) && (with_body) ) {
+ dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
+ if (dmsgtext != NULL) {
+ CM_SetAsField(ret, eMesageText, &dmsgtext->ptr, dmsgtext->len - 1);
+ cdb_free(dmsgtext);
+ }
+ }
+ if (CM_IsEmpty(ret, eMesageText)) {
+ CM_SetField(ret, eMesageText, HKEY("\r\n\r\n (no text)\r\n"));
+ }
+
+ return (ret);
+}
+
+
+// Pre callback function for multipart/alternative
+//
+// NOTE: this differs from the standard behavior for a reason. Normally when
+// displaying multipart/alternative you want to show the _last_ usable
+// format in the message. Here we show the _first_ one, because it's
+// usually text/plain. Since this set of functions is designed for text
+// output to non-MIME-aware clients, this is the desired behavior.
+//
+void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
+ char *cbid, void *cbuserdata)
+{
+ struct ma_info *ma;
+
+ ma = (struct ma_info *)cbuserdata;
+ syslog(LOG_DEBUG, "msgbase: fixed_output_pre() type=<%s>", cbtype);
+ if (!strcasecmp(cbtype, "multipart/alternative")) {
+ ++ma->is_ma;
+ ma->did_print = 0;
+ }
+ if (!strcasecmp(cbtype, "message/rfc822")) {
+ ++ma->freeze;
+ }
+}
+
+
+//
+// Post callback function for multipart/alternative
+//
+void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length,
+ char *encoding, char *cbid, void *cbuserdata)
+{
+ struct ma_info *ma;
+
+ ma = (struct ma_info *)cbuserdata;
+ syslog(LOG_DEBUG, "msgbase: fixed_output_post() type=<%s>", cbtype);
+ if (!strcasecmp(cbtype, "multipart/alternative")) {
+ --ma->is_ma;
+ ma->did_print = 0;
+ }
+ if (!strcasecmp(cbtype, "message/rfc822")) {
+ --ma->freeze;
+ }
+}
+
+
+// Inline callback function for mime parser that wants to display text
+void fixed_output(char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length,
+ char *encoding, char *cbid, void *cbuserdata)
+{
+ char *ptr;
+ char *wptr;
+ size_t wlen;
+ struct ma_info *ma;
+
+ ma = (struct ma_info *)cbuserdata;
+
+ syslog(LOG_DEBUG,
+ "msgbase: fixed_output() part %s: %s (%s) (%ld bytes)",
+ partnum, filename, cbtype, (long)length
+ );
+
+ // If we're in the middle of a multipart/alternative scope and
+ // we've already printed another section, skip this one.
+ if ( (ma->is_ma) && (ma->did_print) ) {
+ syslog(LOG_DEBUG, "msgbase: skipping part %s (%s)", partnum, cbtype);
+ return;
+ }
+ ma->did_print = 1;
+
+ if ( (!strcasecmp(cbtype, "text/plain"))
+ || (IsEmptyStr(cbtype)) ) {
+ wptr = content;
+ if (length > 0) {
+ client_write(wptr, length);
+ if (wptr[length-1] != '\n') {
+ cprintf("\n");
+ }
+ }
+ return;
+ }
+
+ if (!strcasecmp(cbtype, "text/html")) {
+ ptr = html_to_ascii(content, length, 80);
+ wlen = strlen(ptr);
+ client_write(ptr, wlen);
+ if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
+ cprintf("\n");
+ }
+ free(ptr);
+ return;
+ }
+
+ if (ma->use_fo_hooks) {
+ if (PerformFixedOutputHooks(cbtype, content, length)) { // returns nonzero if it handled the part
+ return;
+ }
+ }
+
+ if (strncasecmp(cbtype, "multipart/", 10)) {
+ cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
+ partnum, filename, cbtype, (long)length);
+ return;
+ }
+}
+
+
+// The client is elegant and sophisticated and wants to be choosy about
+// MIME content types, so figure out which multipart/alternative part
+// we're going to send.
+//
+// We use a system of weights. When we find a part that matches one of the
+// MIME types we've declared as preferential, we can store it in ma->chosen_part
+// and then set ma->chosen_pref to that MIME type's position in our preference
+// list. If we then hit another match, we only replace the first match if
+// the preference value is lower.
+void choose_preferred(char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length,
+ char *encoding, char *cbid, void *cbuserdata)
+{
+ char buf[1024];
+ int i;
+ struct ma_info *ma;
+
+ ma = (struct ma_info *)cbuserdata;
+
+ for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
+ extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
+ if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
+ if (i < ma->chosen_pref) {
+ syslog(LOG_DEBUG, "msgbase: setting chosen part to <%s>", partnum);
+ safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
+ ma->chosen_pref = i;
+ }
+ }
+ }
+}
+
+
+// Now that we've chosen our preferred part, output it.
+void output_preferred(char *name,
+ char *filename,
+ char *partnum,
+ char *disp,
+ void *content,
+ char *cbtype,
+ char *cbcharset,
+ size_t length,
+ char *encoding,
+ char *cbid,
+ void *cbuserdata)
+{
+ int i;
+ char buf[128];
+ int add_newline = 0;
+ char *text_content;
+ struct ma_info *ma;
+ char *decoded = NULL;
+ size_t bytes_decoded;
+ int rc = 0;
+
+ ma = (struct ma_info *)cbuserdata;
+
+ // This is not the MIME part you're looking for...
+ if (strcasecmp(partnum, ma->chosen_part)) return;
+
+ // If the content-type of this part is in our preferred formats
+ // list, we can simply output it verbatim.
+ for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
+ extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
+ if (!strcasecmp(buf, cbtype)) {
+ /* Yeah! Go! W00t!! */
+ if (ma->dont_decode == 0)
+ rc = mime_decode_now (content,
+ length,
+ encoding,
+ &decoded,
+ &bytes_decoded);
+ if (rc < 0)
+ break; // Give us the chance, maybe theres another one.
+
+ if (rc == 0) text_content = (char *)content;
+ else {
+ text_content = decoded;
+ length = bytes_decoded;
+ }
+
+ if (text_content[length-1] != '\n') {
+ ++add_newline;
+ }
+ cprintf("Content-type: %s", cbtype);
+ if (!IsEmptyStr(cbcharset)) {
+ cprintf("; charset=%s", cbcharset);
+ }
+ cprintf("\nContent-length: %d\n",
+ (int)(length + add_newline) );
+ if (!IsEmptyStr(encoding)) {
+ cprintf("Content-transfer-encoding: %s\n", encoding);
+ }
+ else {
+ cprintf("Content-transfer-encoding: 7bit\n");
+ }
+ cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
+ cprintf("\n");
+ if (client_write(text_content, length) == -1)
+ {
+ syslog(LOG_ERR, "msgbase: output_preferred() aborting due to write failure");
+ return;
+ }
+ if (add_newline) cprintf("\n");
+ if (decoded != NULL) free(decoded);
+ return;
+ }
+ }
+
+ // No translations required or possible: output as text/plain
+ cprintf("Content-type: text/plain\n\n");
+ rc = 0;
+ if (ma->dont_decode == 0)
+ rc = mime_decode_now (content,
+ length,
+ encoding,
+ &decoded,
+ &bytes_decoded);
+ if (rc < 0)
+ return; // Give us the chance, maybe theres another one.
+
+ if (rc == 0) text_content = (char *)content;
+ else {
+ text_content = decoded;
+ length = bytes_decoded;
+ }
+
+ fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset, length, encoding, cbid, cbuserdata);
+ if (decoded != NULL) free(decoded);
+}
+
+
+struct encapmsg {
+ char desired_section[64];
+ char *msg;
+ size_t msglen;
+};
+
+
+// Callback function
+void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
+ void *content, char *cbtype, char *cbcharset, size_t length,
+ char *encoding, char *cbid, void *cbuserdata)
+{
+ struct encapmsg *encap;
+
+ encap = (struct encapmsg *)cbuserdata;
+
+ // Only proceed if this is the desired section...
+ if (!strcasecmp(encap->desired_section, partnum)) {
+ encap->msglen = length;
+ encap->msg = malloc(length + 2);
+ memcpy(encap->msg, content, length);
+ return;
+ }
+}
+
+
+// Determine whether the specified message exists in the cached_msglist
+// (This is a security check)
+int check_cached_msglist(long msgnum) {
+
+ // cases in which we skip the check
+ if (!CC) return om_ok; // not a session
+ if (CC->client_socket <= 0) return om_ok; // not a client session
+ if (CC->cached_msglist == NULL) return om_access_denied; // no msglist fetched
+ if (CC->cached_num_msgs == 0) return om_access_denied; // nothing to check
+
+ // Do a binary search within the cached_msglist for the requested msgnum
+ int min = 0;
+ int max = (CC->cached_num_msgs - 1);
+
+ while (max >= min) {
+ int middle = min + (max-min) / 2 ;
+ if (msgnum == CC->cached_msglist[middle]) {
+ return om_ok;
+ }
+ if (msgnum > CC->cached_msglist[middle]) {
+ min = middle + 1;
+ }
+ else {
+ max = middle - 1;
+ }
+ }
+
+ return om_access_denied;
+}
+
+
+// Get a message off disk. (returns om_* values found in msgbase.h)
+int CtdlOutputMsg(long msg_num, // message number (local) to fetch
+ int mode, // how would you like that message?
+ int headers_only, // eschew the message body?
+ int do_proto, // do Citadel protocol responses?
+ int crlf, // Use CRLF newlines instead of LF?
+ char *section, // NULL or a message/rfc822 section
+ int flags, // various flags; see msgbase.h
+ char **Author,
+ char **Address,
+ char **MessageID
+) {
+ struct CtdlMessage *TheMessage = NULL;
+ int retcode = CIT_OK;
+ struct encapmsg encap;
+ int r;
+
+ syslog(LOG_DEBUG, "msgbase: CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)",
+ msg_num, mode,
+ (section ? section : "<>")
+ );
+
+ r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
+ if (r != om_ok) {
+ if (do_proto) {
+ if (r == om_not_logged_in) {
+ cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
+ }
+ else {
+ cprintf("%d An unknown error has occurred.\n", ERROR);
+ }
+ }
+ return(r);
+ }
+
+ /*
+ * Check to make sure the message is actually IN this room
+ */
+ r = check_cached_msglist(msg_num);
+ if (r == om_access_denied) {
+ /* Not in the cache? We get ONE shot to check it again. */
+ CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
+ r = check_cached_msglist(msg_num);
+ }
+ if (r != om_ok) {
+ syslog(LOG_DEBUG, "msgbase: security check fail; message %ld is not in %s",
+ msg_num, CC->room.QRname
+ );
+ if (do_proto) {
+ if (r == om_access_denied) {
+ cprintf("%d message %ld was not found in this room\n",
+ ERROR + HIGHER_ACCESS_REQUIRED,
+ msg_num
+ );
+ }
+ }
+ return(r);
+ }
+
+ /*
+ * Fetch the message from disk. If we're in HEADERS_FAST mode,
+ * request that we don't even bother loading the body into memory.
+ */
+ if (headers_only == HEADERS_FAST) {
+ TheMessage = CtdlFetchMessage(msg_num, 0);
+ }
+ else {
+ TheMessage = CtdlFetchMessage(msg_num, 1);
+ }
+
+ if (TheMessage == NULL) {
+ if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
+ ERROR + MESSAGE_NOT_FOUND, msg_num);
+ return(om_no_such_msg);
+ }
+
+ /* Here is the weird form of this command, to process only an
+ * encapsulated message/rfc822 section.
+ */
+ if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
+ memset(&encap, 0, sizeof encap);
+ safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
+ mime_parser(CM_RANGE(TheMessage, eMesageText),
+ *extract_encapsulated_message,
+ NULL, NULL, (void *)&encap, 0
+ );
+
+ if ((Author != NULL) && (*Author == NULL))
+ {
+ long len;
+ CM_GetAsField(TheMessage, eAuthor, Author, &len);
+ }
+ if ((Address != NULL) && (*Address == NULL))
+ {
+ long len;
+ CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
+ }
+ if ((MessageID != NULL) && (*MessageID == NULL))
+ {
+ long len;
+ CM_GetAsField(TheMessage, emessageId, MessageID, &len);
+ }
+ CM_Free(TheMessage);
+ TheMessage = NULL;
+
+ if (encap.msg) {
+ encap.msg[encap.msglen] = 0;
+ TheMessage = convert_internet_message(encap.msg);
+ encap.msg = NULL; /* no free() here, TheMessage owns it now */
+
+ /* Now we let it fall through to the bottom of this
+ * function, because TheMessage now contains the
+ * encapsulated message instead of the top-level
+ * message. Isn't that neat?
+ */
+ }
+ else {
+ if (do_proto) {
+ cprintf("%d msg %ld has no part %s\n",
+ ERROR + MESSAGE_NOT_FOUND,
+ msg_num,
+ section);
+ }
+ retcode = om_no_such_msg;
+ }
+
+ }
+
+ /* Ok, output the message now */
+ if (retcode == CIT_OK)
+ retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
+ if ((Author != NULL) && (*Author == NULL))
+ {
+ long len;
+ CM_GetAsField(TheMessage, eAuthor, Author, &len);
+ }
+ if ((Address != NULL) && (*Address == NULL))
+ {
+ long len;
+ CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
+ }
+ if ((MessageID != NULL) && (*MessageID == NULL))
+ {
+ long len;
+ CM_GetAsField(TheMessage, emessageId, MessageID, &len);
+ }
+
+ CM_Free(TheMessage);
+
+ return(retcode);
+}
+
+
+void OutputCtdlMsgHeaders(struct CtdlMessage *TheMessage, int do_proto) {
+ int i;
+ char buf[SIZ];
+ char display_name[256];
+
+ /* begin header processing loop for Citadel message format */
+ safestrncpy(display_name, "<unknown>", sizeof display_name);
+ if (!CM_IsEmpty(TheMessage, eAuthor)) {
+ strcpy(buf, TheMessage->cm_fields[eAuthor]);
+ if (TheMessage->cm_anon_type == MES_ANONONLY) {
+ safestrncpy(display_name, "****", sizeof display_name);
+ }
+ else if (TheMessage->cm_anon_type == MES_ANONOPT) {
+ safestrncpy(display_name, "anonymous", sizeof display_name);
+ }
+ else {
+ safestrncpy(display_name, buf, sizeof display_name);
+ }
+ if ((is_room_aide())
+ && ((TheMessage->cm_anon_type == MES_ANONONLY)
+ || (TheMessage->cm_anon_type == MES_ANONOPT))) {
+ size_t tmp = strlen(display_name);
+ snprintf(&display_name[tmp],
+ sizeof display_name - tmp,
+ " [%s]", buf);
+ }
+ }
+
+ /* Now spew the header fields in the order we like them. */
+ for (i=0; i< NDiskFields; ++i) {
+ eMsgField Field;
+ Field = FieldOrder[i];
+ if (Field != eMesageText) {
+ if ( (!CM_IsEmpty(TheMessage, Field)) && (msgkeys[Field] != NULL) ) {
+ if ((Field == eenVelopeTo) || (Field == eRecipient) || (Field == eCarbonCopY)) {
+ sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
+ }
+ if (Field == eAuthor) {
+ if (do_proto) {
+ cprintf("%s=%s\n", msgkeys[Field], display_name);
+ }
+ }
+ /* Masquerade display name if needed */
+ else {
+ if (do_proto) {
+ cprintf("%s=%s\n", msgkeys[Field], TheMessage->cm_fields[Field]);
+ }
+ }
+ /* Give the client a hint about whether the message originated locally */
+ if (Field == erFc822Addr) {
+ if (IsDirectory(TheMessage->cm_fields[Field] ,0)) {
+ cprintf("locl=yes\n"); // message originated locally.
+ }
+
+
+
+ }
+ }
+ }
+ }
+}
+
+
+void OutputRFC822MsgHeaders(
+ struct CtdlMessage *TheMessage,
+ int flags, /* should the message be exported clean */
+ const char *nl, int nlen,
+ char *mid, long sizeof_mid,
+ char *suser, long sizeof_suser,
+ char *luser, long sizeof_luser,
+ char *fuser, long sizeof_fuser,
+ char *snode, long sizeof_snode)
+{
+ char datestamp[100];
+ int subject_found = 0;
+ char buf[SIZ];
+ int i, j, k;
+ char *mptr = NULL;
+ char *mpptr = NULL;
+ char *hptr;
+
+ for (i = 0; i < NDiskFields; ++i) {
+ if (TheMessage->cm_fields[FieldOrder[i]]) {
+ mptr = mpptr = TheMessage->cm_fields[FieldOrder[i]];
+ switch (FieldOrder[i]) {
+ case eAuthor:
+ safestrncpy(luser, mptr, sizeof_luser);
+ safestrncpy(suser, mptr, sizeof_suser);
+ break;
+ case eCarbonCopY:
+ if ((flags & QP_EADDR) != 0) {
+ mptr = qp_encode_email_addrs(mptr);
+ }
+ sanitize_truncated_recipient(mptr);
+ cprintf("CC: %s%s", mptr, nl);
+ break;
+ case eMessagePath:
+ cprintf("Return-Path: %s%s", mptr, nl);
+ break;
+ case eListID:
+ cprintf("List-ID: %s%s", mptr, nl);
+ break;
+ case eenVelopeTo:
+ if ((flags & QP_EADDR) != 0)
+ mptr = qp_encode_email_addrs(mptr);
+ hptr = mptr;
+ while ((*hptr != '\0') && isspace(*hptr))
+ hptr ++;
+ if (!IsEmptyStr(hptr))
+ cprintf("Envelope-To: %s%s", hptr, nl);
+ break;
+ case eMsgSubject:
+ cprintf("Subject: %s%s", mptr, nl);
+ subject_found = 1;
+ break;
+ case emessageId:
+ safestrncpy(mid, mptr, sizeof_mid);
+ break;
+ case erFc822Addr:
+ safestrncpy(fuser, mptr, sizeof_fuser);
+ break;
+ case eRecipient:
+ if (haschar(mptr, '@') == 0) {
+ sanitize_truncated_recipient(mptr);
+ cprintf("To: %s@%s", mptr, CtdlGetConfigStr("c_fqdn"));
+ cprintf("%s", nl);
+ }
+ else {
+ if ((flags & QP_EADDR) != 0) {
+ mptr = qp_encode_email_addrs(mptr);
+ }
+ sanitize_truncated_recipient(mptr);
+ cprintf("To: %s", mptr);
+ cprintf("%s", nl);
+ }
+ break;
+ case eTimestamp:
+ datestring(datestamp, sizeof datestamp, atol(mptr), DATESTRING_RFC822);
+ cprintf("Date: %s%s", datestamp, nl);
+ break;
+ case eWeferences:
+ cprintf("References: ");
+ k = num_tokens(mptr, '|');
+ for (j=0; j<k; ++j) {
+ extract_token(buf, mptr, j, '|', sizeof buf);
+ cprintf("<%s>", buf);
+ if (j == (k-1)) {
+ cprintf("%s", nl);
+ }
+ else {
+ cprintf(" ");
+ }
+ }
+ break;
+ case eReplyTo:
+ hptr = mptr;
+ while ((*hptr != '\0') && isspace(*hptr))
+ hptr ++;
+ if (!IsEmptyStr(hptr))
+ cprintf("Reply-To: %s%s", mptr, nl);
+ break;
+
+ case eExclusiveID:
+ case eJournal:
+ case eMesageText:
+ case eBig_message:
+ case eOriginalRoom:
+ case eErrorMsg:
+ case eSuppressIdx:
+ case eExtnotify:
+ case eVltMsgNum:
+ /* these don't map to mime message headers. */
+ break;
+ }
+ if (mptr != mpptr) {
+ free (mptr);
+ }
+ }
+ }
+ if (subject_found == 0) {
+ cprintf("Subject: (no subject)%s", nl);
+ }
+}
+
+
+void Dump_RFC822HeadersBody(
+ struct CtdlMessage *TheMessage,
+ int headers_only, /* eschew the message body? */
+ int flags, /* should the bessage be exported clean? */
+ const char *nl, int nlen)
+{
+ cit_uint8_t prev_ch;
+ int eoh = 0;
+ const char *StartOfText = StrBufNOTNULL;
+ char outbuf[1024];
+ int outlen = 0;
+ int nllen = strlen(nl);
+ char *mptr;
+ int lfSent = 0;
+
+ mptr = TheMessage->cm_fields[eMesageText];
+
+ prev_ch = '\0';
+ while (*mptr != '\0') {
+ if (*mptr == '\r') {
+ /* do nothing */
+ }
+ else {
+ if ((!eoh) &&
+ (*mptr == '\n'))
+ {
+ eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
+ if (!eoh)
+ eoh = *(mptr+1) == '\n';
+ if (eoh)
+ {
+ StartOfText = mptr;
+ StartOfText = strchr(StartOfText, '\n');
+ StartOfText = strchr(StartOfText, '\n');
+ }
+ }
+ if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
+ ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
+ ((headers_only != HEADERS_NONE) &&
+ (headers_only != HEADERS_ONLY))
+ ) {
+ if (*mptr == '\n') {
+ memcpy(&outbuf[outlen], nl, nllen);
+ outlen += nllen;
+ outbuf[outlen] = '\0';
+ }
+ else {
+ outbuf[outlen++] = *mptr;
+ }
+ }
+ }
+ if (flags & ESC_DOT) {
+ if ((prev_ch == '\n') && (*mptr == '.') && ((*(mptr+1) == '\r') || (*(mptr+1) == '\n'))) {
+ outbuf[outlen++] = '.';
+ }
+ prev_ch = *mptr;
+ }
+ ++mptr;
+ if (outlen > 1000) {
+ if (client_write(outbuf, outlen) == -1) {
+ syslog(LOG_ERR, "msgbase: Dump_RFC822HeadersBody() aborting due to write failure");
+ return;
+ }
+ lfSent = (outbuf[outlen - 1] == '\n');
+ outlen = 0;
+ }
+ }
+ if (outlen > 0) {
+ client_write(outbuf, outlen);
+ lfSent = (outbuf[outlen - 1] == '\n');
+ }
+ if (!lfSent)
+ client_write(nl, nlen);
+}
+
+
+/* If the format type on disk is 1 (fixed-format), then we want
+ * everything to be output completely literally ... regardless of
+ * what message transfer format is in use.
+ */
+void DumpFormatFixed(
+ struct CtdlMessage *TheMessage,
+ int mode, /* how would you like that message? */
+ const char *nl, int nllen)
+{
+ cit_uint8_t ch;
+ char buf[SIZ];
+ int buflen;
+ int xlline = 0;
+ char *mptr;
+
+ mptr = TheMessage->cm_fields[eMesageText];
+
+ if (mode == MT_MIME) {
+ cprintf("Content-type: text/plain\n\n");
+ }
+ *buf = '\0';
+ buflen = 0;
+ while (ch = *mptr++, ch > 0) {
+ if (ch == '\n')
+ ch = '\r';
+
+ if ((buflen > 250) && (!xlline)){
+ int tbuflen;
+ tbuflen = buflen;
+
+ while ((buflen > 0) &&
+ (!isspace(buf[buflen])))
+ buflen --;
+ if (buflen == 0) {
+ xlline = 1;
+ }
+ else {
+ mptr -= tbuflen - buflen;
+ buf[buflen] = '\0';
+ ch = '\r';
+ }
+ }
+
+ /* if we reach the outer bounds of our buffer, abort without respect for what we purge. */
+ if (xlline && ((isspace(ch)) || (buflen > SIZ - nllen - 2))) {
+ ch = '\r';
+ }
+
+ if (ch == '\r') {
+ memcpy (&buf[buflen], nl, nllen);
+ buflen += nllen;
+ buf[buflen] = '\0';
+
+ if (client_write(buf, buflen) == -1) {
+ syslog(LOG_ERR, "msgbase: DumpFormatFixed() aborting due to write failure");
+ return;
+ }
+ *buf = '\0';
+ buflen = 0;
+ xlline = 0;
+ } else {
+ buf[buflen] = ch;
+ buflen++;
+ }
+ }
+ buf[buflen] = '\0';
+ if (!IsEmptyStr(buf)) {
+ cprintf("%s%s", buf, nl);
+ }
+}
+
+
+/*
+ * Get a message off disk. (returns om_* values found in msgbase.h)
+ */
+int CtdlOutputPreLoadedMsg(
+ struct CtdlMessage *TheMessage,
+ int mode, /* how would you like that message? */
+ int headers_only, /* eschew the message body? */
+ int do_proto, /* do Citadel protocol responses? */
+ int crlf, /* Use CRLF newlines instead of LF? */
+ int flags /* should the bessage be exported clean? */
+) {
+ int i;
+ const char *nl; /* newline string */
+ int nlen;
+ struct ma_info ma;
+
+ /* Buffers needed for RFC822 translation. These are all filled
+ * using functions that are bounds-checked, and therefore we can
+ * make them substantially smaller than SIZ.
+ */
+ char suser[1024];
+ char luser[1024];
+ char fuser[1024];
+ char snode[1024];
+ char mid[1024];
+
+ syslog(LOG_DEBUG, "msgbase: CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d",
+ ((TheMessage == NULL) ? "NULL" : "not null"),
+ mode, headers_only, do_proto, crlf
+ );
+
+ strcpy(mid, "unknown");
+ nl = (crlf ? "\r\n" : "\n");
+ nlen = crlf ? 2 : 1;
+
+ if (!CM_IsValidMsg(TheMessage)) {
+ syslog(LOG_ERR, "msgbase: error; invalid preloaded message for output");
+ return(om_no_such_msg);
+ }
+
+ /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
+ * Pad it with spaces in order to avoid changing the RFC822 length of the message.
+ */
+ if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
+ memset(TheMessage->cm_fields[eenVelopeTo], ' ', TheMessage->cm_lengths[eenVelopeTo]);
+ }
+
+ /* Are we downloading a MIME component? */
+ if (mode == MT_DOWNLOAD) {
+ if (TheMessage->cm_format_type != FMT_RFC822) {
+ if (do_proto)
+ cprintf("%d This is not a MIME message.\n",
+ ERROR + ILLEGAL_VALUE);
+ } else if (CC->download_fp != NULL) {
+ if (do_proto) cprintf(
+ "%d You already have a download open.\n",
+ ERROR + RESOURCE_BUSY);
+ } else {
+ /* Parse the message text component */
+ mime_parser(CM_RANGE(TheMessage, eMesageText),
+ *mime_download, NULL, NULL, NULL, 0);
+ /* If there's no file open by this time, the requested
+ * section wasn't found, so print an error
+ */
+ if (CC->download_fp == NULL) {
+ if (do_proto) cprintf(
+ "%d Section %s not found.\n",
+ ERROR + FILE_NOT_FOUND,
+ CC->download_desired_section);
+ }
+ }
+ return((CC->download_fp != NULL) ? om_ok : om_mime_error);
+ }
+
+ // MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
+ // in a single server operation instead of opening a download file.
+ if (mode == MT_SPEW_SECTION) {
+ if (TheMessage->cm_format_type != FMT_RFC822) {
+ if (do_proto)
+ cprintf("%d This is not a MIME message.\n",
+ ERROR + ILLEGAL_VALUE);
+ }
+ else {
+ // Locate and parse the component specified by the caller
+ int found_it = 0;
+ mime_parser(CM_RANGE(TheMessage, eMesageText), *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
+
+ // If section wasn't found, print an error
+ if (!found_it) {
+ if (do_proto) {
+ cprintf( "%d Section %s not found.\n", ERROR + FILE_NOT_FOUND, CC->download_desired_section);
+ }
+ }
+ }
+ return((CC->download_fp != NULL) ? om_ok : om_mime_error);
+ }
+
+ // now for the user-mode message reading loops
+ if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
+
+ // Does the caller want to skip the headers?
+ if (headers_only == HEADERS_NONE) goto START_TEXT;
+
+ // Tell the client which format type we're using.
+ if ( (mode == MT_CITADEL) && (do_proto) ) {
+ cprintf("type=%d\n", TheMessage->cm_format_type); // Tell the client which format type we're using.
+ }
+
+ // nhdr=yes means that we're only displaying headers, no body
+ if ( (TheMessage->cm_anon_type == MES_ANONONLY)
+ && ((mode == MT_CITADEL) || (mode == MT_MIME))
+ && (do_proto)
+ ) {
+ cprintf("nhdr=yes\n");
+ }
+
+ if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
+ OutputCtdlMsgHeaders(TheMessage, do_proto);
+ }
+
+ // begin header processing loop for RFC822 transfer format
+ strcpy(suser, "");
+ strcpy(luser, "");
+ strcpy(fuser, "");
+ strcpy(snode, "");
+ if (mode == MT_RFC822)
+ OutputRFC822MsgHeaders(
+ TheMessage,
+ flags,
+ nl, nlen,
+ mid, sizeof(mid),
+ suser, sizeof(suser),
+ luser, sizeof(luser),
+ fuser, sizeof(fuser),
+ snode, sizeof(snode)
+ );
+
+
+ for (i=0; !IsEmptyStr(&suser[i]); ++i) {
+ suser[i] = tolower(suser[i]);
+ if (!isalnum(suser[i])) suser[i]='_';
+ }
+
+ if (mode == MT_RFC822) {
+ /* Construct a fun message id */
+ cprintf("Message-ID: <%s", mid);
+ if (strchr(mid, '@')==NULL) {
+ cprintf("@%s", snode);
+ }
+ cprintf(">%s", nl);
+
+ if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
+ cprintf("From: \"----\" <x@x.org>%s", nl);
+ }
+ else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
+ cprintf("From: \"anonymous\" <x@x.org>%s", nl);
+ }
+ else if (!IsEmptyStr(fuser)) {
+ cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
+ }
+ else {
+ cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
+ }
+
+ /* Blank line signifying RFC822 end-of-headers */
+ if (TheMessage->cm_format_type != FMT_RFC822) {
+ cprintf("%s", nl);
+ }
+ }
+
+ // end header processing loop ... at this point, we're in the text
+START_TEXT:
+ if (headers_only == HEADERS_FAST) goto DONE;
+
+ // Tell the client about the MIME parts in this message
+ if (TheMessage->cm_format_type == FMT_RFC822) {
+ if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
+ memset(&ma, 0, sizeof(struct ma_info));
+ mime_parser(CM_RANGE(TheMessage, eMesageText),
+ (do_proto ? *list_this_part : NULL),
+ (do_proto ? *list_this_pref : NULL),
+ (do_proto ? *list_this_suff : NULL),
+ (void *)&ma, 1);
+ }
+ else if (mode == MT_RFC822) { // unparsed RFC822 dump
+ Dump_RFC822HeadersBody(
+ TheMessage,
+ headers_only,
+ flags,
+ nl, nlen);
+ goto DONE;
+ }
+ }
+
+ if (headers_only == HEADERS_ONLY) {
+ goto DONE;
+ }
+
+ // signify start of msg text
+ if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
+ if (do_proto) cprintf("text\n");
+ }
+
+ if (TheMessage->cm_format_type == FMT_FIXED)
+ DumpFormatFixed(
+ TheMessage,
+ mode, // how would you like that message?
+ nl, nlen);
+
+ // If the message on disk is format 0 (Citadel vari-format), we
+ // output using the formatter at 80 columns. This is the final output
+ // form if the transfer format is RFC822, but if the transfer format
+ // is Citadel proprietary, it'll still work, because the indentation
+ // for new paragraphs is correct and the client will reformat the
+ // message to the reader's screen width.
+ //
+ if (TheMessage->cm_format_type == FMT_CITADEL) {
+ if (mode == MT_MIME) {
+ cprintf("Content-type: text/x-citadel-variformat\n\n");
+ }
+ memfmout(TheMessage->cm_fields[eMesageText], nl);
+ }
+
+ // If the message on disk is format 4 (MIME), we've gotta hand it
+ // off to the MIME parser. The client has already been told that
+ // this message is format 1 (fixed format), so the callback function
+ // we use will display those parts as-is.
+ //
+ if (TheMessage->cm_format_type == FMT_RFC822) {
+ memset(&ma, 0, sizeof(struct ma_info));
+
+ if (mode == MT_MIME) {
+ ma.use_fo_hooks = 0;
+ strcpy(ma.chosen_part, "1");
+ ma.chosen_pref = 9999;
+ ma.dont_decode = CC->msg4_dont_decode;
+ mime_parser(CM_RANGE(TheMessage, eMesageText),
+ *choose_preferred, *fixed_output_pre,
+ *fixed_output_post, (void *)&ma, 1);
+ mime_parser(CM_RANGE(TheMessage, eMesageText),
+ *output_preferred, NULL, NULL, (void *)&ma, 1);
+ }
+ else {
+ ma.use_fo_hooks = 1;
+ mime_parser(CM_RANGE(TheMessage, eMesageText),
+ *fixed_output, *fixed_output_pre,
+ *fixed_output_post, (void *)&ma, 0);
+ }
+
+ }
+
+DONE: /* now we're done */
+ if (do_proto) cprintf("000\n");
+ return(om_ok);
+}
+
+// Save one or more message pointers into a specified room
+// (Returns 0 for success, nonzero for failure)
+// roomname may be NULL to use the current room
+//
+// Note that the 'supplied_msg' field may be set to NULL, in which case
+// the message will be fetched from disk, by number, if we need to perform
+// replication checks. This adds an additional database read, so if the
+// caller already has the message in memory then it should be supplied. (Obviously
+// this mode of operation only works if we're saving a single message.)
+//
+int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
+ int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
+) {
+ int i, j, unique;
+ char hold_rm[ROOMNAMELEN];
+ struct cdbdata *cdbfr;
+ int num_msgs;
+ long *msglist;
+ long highest_msg = 0L;
+
+ long msgid = 0;
+ struct CtdlMessage *msg = NULL;
+
+ long *msgs_to_be_merged = NULL;
+ int num_msgs_to_be_merged = 0;
+
+ syslog(LOG_DEBUG,
+ "msgbase: CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)",
+ roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
+ );
+
+ strcpy(hold_rm, CC->room.QRname);
+
+ /* Sanity checks */
+ if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
+ if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
+ if (num_newmsgs > 1) supplied_msg = NULL;
+
+ /* Now the regular stuff */
+ if (CtdlGetRoomLock(&CC->room,
+ ((roomname != NULL) ? roomname : CC->room.QRname) )
+ != 0) {
+ syslog(LOG_ERR, "msgbase: no such room <%s>", roomname);
+ return(ERROR + ROOM_NOT_FOUND);
+ }
+
+
+ msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
+ num_msgs_to_be_merged = 0;
+
+
+ cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
+ if (cdbfr == NULL) {
+ msglist = NULL;
+ num_msgs = 0;
+ } else {
+ msglist = (long *) cdbfr->ptr;
+ cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
+ num_msgs = cdbfr->len / sizeof(long);
+ cdb_free(cdbfr);
+ }
+
+
+ /* Create a list of msgid's which were supplied by the caller, but do
+ * not already exist in the target room. It is absolutely taboo to
+ * have more than one reference to the same message in a room.
+ */
+ for (i=0; i<num_newmsgs; ++i) {
+ unique = 1;
+ if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
+ if (msglist[j] == newmsgidlist[i]) {
+ unique = 0;
+ }
+ }
+ if (unique) {
+ msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
+ }
+ }
+
+ syslog(LOG_DEBUG, "msgbase: %d unique messages to be merged", num_msgs_to_be_merged);
+
+ /*
+ * Now merge the new messages
+ */
+ msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
+ if (msglist == NULL) {
+ syslog(LOG_ALERT, "msgbase: ERROR; can't realloc message list!");
+ free(msgs_to_be_merged);
+ return (ERROR + INTERNAL_ERROR);
+ }
+ memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
+ num_msgs += num_msgs_to_be_merged;
+
+ /* Sort the message list, so all the msgid's are in order */
+ num_msgs = sort_msglist(msglist, num_msgs);
+
+ /* Determine the highest message number */
+ highest_msg = msglist[num_msgs - 1];
+
+ /* Write it back to disk. */
+ cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
+ msglist, (int)(num_msgs * sizeof(long)));
+
+ /* Free up the memory we used. */
+ free(msglist);
+
+ /* Update the highest-message pointer and unlock the room. */
+ CC->room.QRhighest = highest_msg;
+ CtdlPutRoomLock(&CC->room);
+
+ /* Perform replication checks if necessary */
+ if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
+ syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() doing repl checks");
+
+ for (i=0; i<num_msgs_to_be_merged; ++i) {
+ msgid = msgs_to_be_merged[i];
+
+ if (supplied_msg != NULL) {
+ msg = supplied_msg;
+ }
+ else {
+ msg = CtdlFetchMessage(msgid, 0);
+ }
+
+ if (msg != NULL) {
+ ReplicationChecks(msg);
+
+ /* If the message has an Exclusive ID, index that... */
+ if (!CM_IsEmpty(msg, eExclusiveID)) {
+ index_message_by_euid(msg->cm_fields[eExclusiveID], &CC->room, msgid);
+ }
+
+ /* Free up the memory we may have allocated */
+ if (msg != supplied_msg) {
+ CM_Free(msg);
+ }
+ }
+
+ }
+ }
+
+ else {
+ syslog(LOG_DEBUG, "msgbase: CtdlSaveMsgPointerInRoom() skips repl checks");
+ }
+
+ /* Submit this room for processing by hooks */
+ int total_roomhook_errors = PerformRoomHooks(&CC->room);
+ if (total_roomhook_errors) {
+ syslog(LOG_WARNING, "msgbase: room hooks returned %d errors", total_roomhook_errors);
+ }
+
+ /* Go back to the room we were in before we wandered here... */
+ CtdlGetRoom(&CC->room, hold_rm);
+
+ /* Bump the reference count for all messages which were merged */
+ if (!suppress_refcount_adj) {
+ AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
+ }
+
+ /* Free up memory... */
+ if (msgs_to_be_merged != NULL) {
+ free(msgs_to_be_merged);
+ }
+
+ /* Return success. */
+ return (0);
+}
+
+
+/*
+ * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
+ * a single message.
+ */
+int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
+ int do_repl_check, struct CtdlMessage *supplied_msg)
+{
+ return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
+}
+
+
+/*
+ * Message base operation to save a new message to the message store
+ * (returns new message number)
+ *
+ * This is the back end for CtdlSubmitMsg() and should not be directly
+ * called by server-side modules.
+ *
+ */
+long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply) {
+ long retval;
+ struct ser_ret smr;
+ int is_bigmsg = 0;
+ char *holdM = NULL;
+ long holdMLen = 0;
+
+ /*
+ * If the message is big, set its body aside for storage elsewhere
+ * and we hide the message body from the serializer
+ */
+ if (!CM_IsEmpty(msg, eMesageText) && msg->cm_lengths[eMesageText] > BIGMSG) {
+ is_bigmsg = 1;
+ holdM = msg->cm_fields[eMesageText];
+ msg->cm_fields[eMesageText] = NULL;
+ holdMLen = msg->cm_lengths[eMesageText];
+ msg->cm_lengths[eMesageText] = 0;
+ }
+
+ /* Serialize our data structure for storage in the database */
+ CtdlSerializeMessage(&smr, msg);
+
+ if (is_bigmsg) {
+ /* put the message body back into the message */
+ msg->cm_fields[eMesageText] = holdM;
+ msg->cm_lengths[eMesageText] = holdMLen;
+ }
+
+ if (smr.len == 0) {
+ if (Reply) {
+ cprintf("%d Unable to serialize message\n",
+ ERROR + INTERNAL_ERROR);
+ }
+ else {
+ syslog(LOG_ERR, "msgbase: CtdlSaveMessage() unable to serialize message");
+
+ }
+ return (-1L);
+ }
+
+ /* Write our little bundle of joy into the message base */
+ retval = cdb_store(CDB_MSGMAIN, &msgid, (int)sizeof(long), smr.ser, smr.len);
+ if (retval < 0) {
+ syslog(LOG_ERR, "msgbase: can't store message %ld: %ld", msgid, retval);
+ }
+ else {
+ if (is_bigmsg) {
+ retval = cdb_store(CDB_BIGMSGS,
+ &msgid,
+ (int)sizeof(long),
+ holdM,
+ (holdMLen + 1)
+ );
+ if (retval < 0) {
+ syslog(LOG_ERR, "msgbase: failed to store message body for msgid %ld: %ld", msgid, retval);
+ }
+ }
+ }
+
+ /* Free the memory we used for the serialized message */
+ free(smr.ser);
+
+ return(retval);
+}
+
+
+long send_message(struct CtdlMessage *msg) {
+ long newmsgid;
+ long retval;
+ char msgidbuf[256];
+ long msgidbuflen;
+
+ /* Get a new message number */
+ newmsgid = get_new_message_number();
+
+ /* Generate an ID if we don't have one already */
+ if (CM_IsEmpty(msg, emessageId)) {
+ msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
+ (long unsigned int) time(NULL),
+ (long unsigned int) newmsgid,
+ CtdlGetConfigStr("c_fqdn")
+ );
+
+ CM_SetField(msg, emessageId, msgidbuf, msgidbuflen);
+ }
+
+ retval = CtdlSaveThisMessage(msg, newmsgid, 1);
+
+ if (retval == 0) {
+ retval = newmsgid;
+ }
+
+ /* Return the *local* message ID to the caller
+ * (even if we're storing an incoming network message)
+ */
+ return(retval);
+}
+
+
+/*
+ * Serialize a struct CtdlMessage into the format used on disk.
+ *
+ * This function loads up a "struct ser_ret" (defined in server.h) which
+ * contains the length of the serialized message and a pointer to the
+ * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
+ */
+void CtdlSerializeMessage(struct ser_ret *ret, /* return values */
+ struct CtdlMessage *msg) /* unserialized msg */
+{
+ size_t wlen;
+ int i;
+
+ /*
+ * Check for valid message format
+ */
+ if (CM_IsValidMsg(msg) == 0) {
+ syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() aborting due to invalid message");
+ ret->len = 0;
+ ret->ser = NULL;
+ return;
+ }
+
+ ret->len = 3;
+ for (i=0; i < NDiskFields; ++i)
+ if (msg->cm_fields[FieldOrder[i]] != NULL)
+ ret->len += msg->cm_lengths[FieldOrder[i]] + 2;
+
+ ret->ser = malloc(ret->len);
+ if (ret->ser == NULL) {
+ syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() malloc(%ld) failed: %m", (long)ret->len);
+ ret->len = 0;
+ ret->ser = NULL;
+ return;
+ }
+
+ ret->ser[0] = 0xFF;
+ ret->ser[1] = msg->cm_anon_type;
+ ret->ser[2] = msg->cm_format_type;
+ wlen = 3;
+
+ for (i=0; i < NDiskFields; ++i) {
+ if (msg->cm_fields[FieldOrder[i]] != NULL) {
+ ret->ser[wlen++] = (char)FieldOrder[i];
+
+ memcpy(&ret->ser[wlen],
+ msg->cm_fields[FieldOrder[i]],
+ msg->cm_lengths[FieldOrder[i]] + 1);
+
+ wlen = wlen + msg->cm_lengths[FieldOrder[i]] + 1;
+ }
+ }
+
+ if (ret->len != wlen) {
+ syslog(LOG_ERR, "msgbase: ERROR; len=%ld wlen=%ld", (long)ret->len, (long)wlen);
+ }
+
+ return;
+}
+
+
+/*
+ * Check to see if any messages already exist in the current room which
+ * carry the same Exclusive ID as this one. If any are found, delete them.
+ */
+void ReplicationChecks(struct CtdlMessage *msg) {
+ long old_msgnum = (-1L);
+
+ if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
+
+ syslog(LOG_DEBUG, "msgbase: performing replication checks in <%s>", CC->room.QRname);
+
+ /* No exclusive id? Don't do anything. */
+ if (msg == NULL) return;
+ if (CM_IsEmpty(msg, eExclusiveID)) return;
+
+ /*syslog(LOG_DEBUG, "msgbase: exclusive ID: <%s> for room <%s>",
+ msg->cm_fields[eExclusiveID], CC->room.QRname);*/
+
+ old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CC->room);
+ if (old_msgnum > 0L) {
+ syslog(LOG_DEBUG, "msgbase: ReplicationChecks() replacing message %ld", old_msgnum);
+ CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
+ }
+}
+
+
+/*
+ * Save a message to disk and submit it into the delivery system.
+ */
+long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
+ struct recptypes *recps, /* recipients (if mail) */
+ const char *force /* force a particular room? */
+) {
+ char hold_rm[ROOMNAMELEN];
+ char actual_rm[ROOMNAMELEN];
+ char force_room[ROOMNAMELEN];
+ char content_type[SIZ]; /* We have to learn this */
+ char recipient[SIZ];
+ char bounce_to[1024];
+ const char *room;
+ long newmsgid;
+ const char *mptr = NULL;
+ struct ctdluser userbuf;
+ int a, i;
+ struct MetaData smi;
+ char *collected_addresses = NULL;
+ struct addresses_to_be_filed *aptr = NULL;
+ StrBuf *saved_rfc822_version = NULL;
+ int qualified_for_journaling = 0;
+
+ syslog(LOG_DEBUG, "msgbase: CtdlSubmitMsg() called");
+ if (CM_IsValidMsg(msg) == 0) return(-1); /* self check */
+
+ /* If this message has no timestamp, we take the liberty of
+ * giving it one, right now.
+ */
+ if (CM_IsEmpty(msg, eTimestamp)) {
+ CM_SetFieldLONG(msg, eTimestamp, time(NULL));
+ }
+
+ /* If this message has no path, we generate one.
+ */
+ if (CM_IsEmpty(msg, eMessagePath)) {
+ if (!CM_IsEmpty(msg, eAuthor)) {
+ CM_CopyField(msg, eMessagePath, eAuthor);
+ for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
+ if (isspace(msg->cm_fields[eMessagePath][a])) {
+ msg->cm_fields[eMessagePath][a] = ' ';
+ }
+ }
+ }
+ else {
+ CM_SetField(msg, eMessagePath, HKEY("unknown"));
+ }
+ }
+
+ if (force == NULL) {
+ force_room[0] = '\0';
+ }
+ else {
+ strcpy(force_room, force);
+ }
+
+ /* Learn about what's inside, because it's what's inside that counts */
+ if (CM_IsEmpty(msg, eMesageText)) {
+ syslog(LOG_ERR, "msgbase: ERROR; attempt to save message with NULL body");
+ return(-2);
+ }
+
+ switch (msg->cm_format_type) {
+ case 0:
+ strcpy(content_type, "text/x-citadel-variformat");
+ break;
+ case 1:
+ strcpy(content_type, "text/plain");
+ break;
+ case 4:
+ strcpy(content_type, "text/plain");
+ mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
+ if (mptr != NULL) {
+ char *aptr;
+ safestrncpy(content_type, &mptr[13], sizeof content_type);
+ striplt(content_type);
+ aptr = content_type;
+ while (!IsEmptyStr(aptr)) {
+ if ((*aptr == ';')
+ || (*aptr == ' ')
+ || (*aptr == 13)
+ || (*aptr == 10)) {
+ *aptr = 0;
+ }
+ else aptr++;
+ }
+ }
+ }
+
+ /* Goto the correct room */
+ room = (recps) ? CC->room.QRname : SENTITEMS;
+ syslog(LOG_DEBUG, "msgbase: selected room %s", room);
+ strcpy(hold_rm, CC->room.QRname);
+ strcpy(actual_rm, CC->room.QRname);
+ if (recps != NULL) {
+ strcpy(actual_rm, SENTITEMS);
+ }
+
+ /* If the user is a twit, move to the twit room for posting */
+ if (TWITDETECT) {
+ if (CC->user.axlevel == AxProbU) {
+ strcpy(hold_rm, actual_rm);
+ strcpy(actual_rm, CtdlGetConfigStr("c_twitroom"));
+ syslog(LOG_DEBUG, "msgbase: diverting to twit room");
+ }
+ }
+
+ /* ...or if this message is destined for Aide> then go there. */
+ if (!IsEmptyStr(force_room)) {
+ strcpy(actual_rm, force_room);
+ }
+
+ syslog(LOG_DEBUG, "msgbase: final selection: %s (%s)", actual_rm, room);
+ if (strcasecmp(actual_rm, CC->room.QRname)) {
+ CtdlUserGoto(actual_rm, 0, 1, NULL, NULL, NULL, NULL);
+ }
+
+ /*
+ * If this message has no O (room) field, generate one.
+ */
+ if (CM_IsEmpty(msg, eOriginalRoom) && !IsEmptyStr(CC->room.QRname)) {
+ CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
+ }
+
+ /* Perform "before save" hooks (aborting if any return nonzero) */
+ syslog(LOG_DEBUG, "msgbase: performing before-save hooks");
+ if (PerformMessageHooks(msg, recps, EVT_BEFORESAVE) > 0) return(-3);
+
+ /*
+ * If this message has an Exclusive ID, and the room is replication
+ * checking enabled, then do replication checks.
+ */
+ if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
+ ReplicationChecks(msg);
+ }
+
+ /* Save it to disk */
+ syslog(LOG_DEBUG, "msgbase: saving to disk");
+ newmsgid = send_message(msg);
+ if (newmsgid <= 0L) return(-5);
+
+ /* Write a supplemental message info record. This doesn't have to
+ * be a critical section because nobody else knows about this message
+ * yet.
+ */
+ syslog(LOG_DEBUG, "msgbase: creating metadata record");
+ memset(&smi, 0, sizeof(struct MetaData));
+ smi.meta_msgnum = newmsgid;
+ smi.meta_refcount = 0;
+ safestrncpy(smi.meta_content_type, content_type, sizeof smi.meta_content_type);
+
+ /*
+ * Measure how big this message will be when rendered as RFC822.
+ * We do this for two reasons:
+ * 1. We need the RFC822 length for the new metadata record, so the
+ * POP and IMAP services don't have to calculate message lengths
+ * while the user is waiting (multiplied by potentially hundreds
+ * or thousands of messages).
+ * 2. If journaling is enabled, we will need an RFC822 version of the
+ * message to attach to the journalized copy.
+ */
+ if (CC->redirect_buffer != NULL) {
+ syslog(LOG_ALERT, "msgbase: CC->redirect_buffer is not NULL during message submission!");
+ abort();
+ }
+ CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+ CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
+ smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
+ saved_rfc822_version = CC->redirect_buffer;
+ CC->redirect_buffer = NULL;
+
+ PutMetaData(&smi);
+
+ /* Now figure out where to store the pointers */
+ syslog(LOG_DEBUG, "msgbase: storing pointers");
+
+ /* If this is being done by the networker delivering a private
+ * message, we want to BYPASS saving the sender's copy (because there
+ * is no local sender; it would otherwise go to the Trashcan).
+ */
+ if ((!CC->internal_pgm) || (recps == NULL)) {
+ if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
+ syslog(LOG_ERR, "msgbase: ERROR saving message pointer %ld in %s", newmsgid, actual_rm);
+ CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
+ }
+ }
+
+ /* For internet mail, drop a copy in the outbound queue room */
+ if ((recps != NULL) && (recps->num_internet > 0)) {
+ CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
+ }
+
+ /* If other rooms are specified, drop them there too. */
+ if ((recps != NULL) && (recps->num_room > 0)) {
+ for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
+ extract_token(recipient, recps->recp_room, i, '|', sizeof recipient);
+ syslog(LOG_DEBUG, "msgbase: delivering to room <%s>", recipient);
+ CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
+ }
+ }
+
+ /* Bump this user's messages posted counter. */
+ syslog(LOG_DEBUG, "msgbase: updating user");
+ CtdlLockGetCurrentUser();
+ CC->user.posted = CC->user.posted + 1;
+ CtdlPutCurrentUserLock();
+
+ /* Decide where bounces need to be delivered */
+ if ((recps != NULL) && (recps->bounce_to == NULL)) {
+ if (CC->logged_in) {
+ strcpy(bounce_to, CC->user.fullname);
+ }
+ else if (!IsEmptyStr(msg->cm_fields[eAuthor])){
+ strcpy(bounce_to, msg->cm_fields[eAuthor]);
+ }
+ recps->bounce_to = bounce_to;
+ }
+
+ CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
+
+ /* If this is private, local mail, make a copy in the
+ * recipient's mailbox and bump the reference count.
+ */
+ if ((recps != NULL) && (recps->num_local > 0)) {
+ char *pch;
+ int ntokens;
+
+ pch = recps->recp_local;
+ recps->recp_local = recipient;
+ ntokens = num_tokens(pch, '|');
+ for (i=0; i<ntokens; ++i) {
+ extract_token(recipient, pch, i, '|', sizeof recipient);
+ syslog(LOG_DEBUG, "msgbase: delivering private local mail to <%s>", recipient);
+ if (CtdlGetUser(&userbuf, recipient) == 0) {
+ CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
+ CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
+ CtdlBumpNewMailCounter(userbuf.usernum);
+ PerformMessageHooks(msg, recps, EVT_AFTERUSRMBOXSAVE);
+ }
+ else {
+ syslog(LOG_DEBUG, "msgbase: no user <%s>", recipient);
+ CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
+ }
+ }
+ recps->recp_local = pch;
+ }
+
+ /* Perform "after save" hooks */
+ syslog(LOG_DEBUG, "msgbase: performing after-save hooks");
+
+ PerformMessageHooks(msg, recps, EVT_AFTERSAVE);
+ CM_FlushField(msg, eVltMsgNum);
+
+ /* Go back to the room we started from */
+ syslog(LOG_DEBUG, "msgbase: returning to original room %s", hold_rm);
+ if (strcasecmp(hold_rm, CC->room.QRname)) {
+ CtdlUserGoto(hold_rm, 0, 1, NULL, NULL, NULL, NULL);
+ }
+
+ /*
+ * Any addresses to harvest for someone's address book?
+ */
+ if ( (CC->logged_in) && (recps != NULL) ) {
+ collected_addresses = harvest_collected_addresses(msg);
+ }
+
+ if (collected_addresses != NULL) {
+ aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed));
+ CtdlMailboxName(actual_rm, sizeof actual_rm, &CC->user, USERCONTACTSROOM);
+ aptr->roomname = strdup(actual_rm);
+ aptr->collected_addresses = collected_addresses;
+ begin_critical_section(S_ATBF);
+ aptr->next = atbf;
+ atbf = aptr;
+ end_critical_section(S_ATBF);
+ }
+
+ /*
+ * Determine whether this message qualifies for journaling.
+ */
+ if (!CM_IsEmpty(msg, eJournal)) {
+ qualified_for_journaling = 0;
+ }
+ else {
+ if (recps == NULL) {
+ qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
+ }
+ else if (recps->num_local + recps->num_internet > 0) {
+ qualified_for_journaling = CtdlGetConfigInt("c_journal_email");
+ }
+ else {
+ qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
+ }
+ }
+
+ /*
+ * Do we have to perform journaling? If so, hand off the saved
+ * RFC822 version will be handed off to the journaler for background
+ * submit. Otherwise, we have to free the memory ourselves.
+ */
+ if (saved_rfc822_version != NULL) {
+ if (qualified_for_journaling) {
+ JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
+ }
+ else {
+ FreeStrBuf(&saved_rfc822_version);
+ }
+ }
+
+ if ((recps != NULL) && (recps->bounce_to == bounce_to))
+ recps->bounce_to = NULL;
+
+ /* Done. */
+ return(newmsgid);
+}
+
+
+/*
+ * Convenience function for generating small administrative messages.
+ */
+long quickie_message(char *from,
+ char *fromaddr,
+ char *to,
+ char *room,
+ char *text,
+ int format_type,
+ char *subject)
+{
+ struct CtdlMessage *msg;
+ struct recptypes *recp = NULL;
+
+ msg = malloc(sizeof(struct CtdlMessage));
+ memset(msg, 0, sizeof(struct CtdlMessage));
+ msg->cm_magic = CTDLMESSAGE_MAGIC;
+ msg->cm_anon_type = MES_NORMAL;
+ msg->cm_format_type = format_type;
+
+ if (!IsEmptyStr(from)) {
+ CM_SetField(msg, eAuthor, from, -1);
+ }
+ else if (!IsEmptyStr(fromaddr)) {
+ char *pAt;
+ CM_SetField(msg, eAuthor, fromaddr, -1);
+ pAt = strchr(msg->cm_fields[eAuthor], '@');
+ if (pAt != NULL) {
+ CM_CutFieldAt(msg, eAuthor, pAt - msg->cm_fields[eAuthor]);
+ }
+ }
+ else {
+ msg->cm_fields[eAuthor] = strdup("Citadel");
+ }
+
+ if (!IsEmptyStr(fromaddr)) CM_SetField(msg, erFc822Addr, fromaddr, -1);
+ if (!IsEmptyStr(room)) CM_SetField(msg, eOriginalRoom, room, -1);
+ if (!IsEmptyStr(to)) {
+ CM_SetField(msg, eRecipient, to, -1);
+ recp = validate_recipients(to, NULL, 0);
+ }
+ if (!IsEmptyStr(subject)) {
+ CM_SetField(msg, eMsgSubject, subject, -1);
+ }
+ if (!IsEmptyStr(text)) {
+ CM_SetField(msg, eMesageText, text, -1);
+ }
+
+ long msgnum = CtdlSubmitMsg(msg, recp, room);
+ CM_Free(msg);
+ if (recp != NULL) free_recipients(recp);
+ return msgnum;
+}
+
+
+/*
+ * Back end function used by CtdlMakeMessage() and similar functions
+ */
+StrBuf *CtdlReadMessageBodyBuf(char *terminator, // token signalling EOT
+ long tlen,
+ size_t maxlen, // maximum message length
+ StrBuf *exist, // if non-null, append to it; exist is ALWAYS freed
+ int crlf // CRLF newlines instead of LF
+) {
+ StrBuf *Message;
+ StrBuf *LineBuf;
+ int flushing = 0;
+ int finished = 0;
+ int dotdot = 0;
+
+ LineBuf = NewStrBufPlain(NULL, SIZ);
+ if (exist == NULL) {
+ Message = NewStrBufPlain(NULL, 4 * SIZ);
+ }
+ else {
+ Message = NewStrBufDup(exist);
+ }
+
+ /* Do we need to change leading ".." to "." for SMTP escaping? */
+ if ((tlen == 1) && (*terminator == '.')) {
+ dotdot = 1;
+ }
+
+ /* read in the lines of message text one by one */
+ do {
+ if (CtdlClientGetLine(LineBuf) < 0) {
+ finished = 1;
+ }
+ if ((StrLength(LineBuf) == tlen) && (!strcmp(ChrPtr(LineBuf), terminator))) {
+ finished = 1;
+ }
+ if ( (!flushing) && (!finished) ) {
+ if (crlf) {
+ StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
+ }
+ else {
+ StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
+ }
+
+ /* Unescape SMTP-style input of two dots at the beginning of the line */
+ if ((dotdot) && (StrLength(LineBuf) > 1) && (ChrPtr(LineBuf)[0] == '.')) {
+ StrBufCutLeft(LineBuf, 1);
+ }
+ StrBufAppendBuf(Message, LineBuf, 0);
+ }
+
+ /* if we've hit the max msg length, flush the rest */
+ if (StrLength(Message) >= maxlen) {
+ flushing = 1;
+ }
+
+ } while (!finished);
+ FreeStrBuf(&LineBuf);
+
+ if (flushing) {
+ syslog(LOG_ERR, "msgbase: exceeded maximum message length of %ld - message was truncated", maxlen);
+ }
+
+ return Message;
+}
+
+
+// Back end function used by CtdlMakeMessage() and similar functions
+char *CtdlReadMessageBody(char *terminator, // token signalling EOT
+ long tlen,
+ size_t maxlen, // maximum message length
+ StrBuf *exist, // if non-null, append to it; exist is ALWAYS freed
+ int crlf // CRLF newlines instead of LF
+) {
+ StrBuf *Message;
+
+ Message = CtdlReadMessageBodyBuf(terminator,
+ tlen,
+ maxlen,
+ exist,
+ crlf
+ );
+ if (Message == NULL) {
+ return NULL;
+ }
+ else {
+ return SmashStrBuf(&Message);
+ }
+}
+
+
+struct CtdlMessage *CtdlMakeMessage(
+ struct ctdluser *author, /* author's user structure */
+ char *recipient, /* NULL if it's not mail */
+ char *recp_cc, /* NULL if it's not mail */
+ char *room, /* room where it's going */
+ int type, /* see MES_ types in header file */
+ int format_type, /* variformat, plain text, MIME... */
+ char *fake_name, /* who we're masquerading as */
+ char *my_email, /* which of my email addresses to use (empty is ok) */
+ char *subject, /* Subject (optional) */
+ char *supplied_euid, /* ...or NULL if this is irrelevant */
+ char *preformatted_text, /* ...or NULL to read text from client */
+ char *references /* Thread references */
+) {
+ return CtdlMakeMessageLen(
+ author, /* author's user structure */
+ recipient, /* NULL if it's not mail */
+ (recipient)?strlen(recipient) : 0,
+ recp_cc, /* NULL if it's not mail */
+ (recp_cc)?strlen(recp_cc): 0,
+ room, /* room where it's going */
+ (room)?strlen(room): 0,
+ type, /* see MES_ types in header file */
+ format_type, /* variformat, plain text, MIME... */
+ fake_name, /* who we're masquerading as */
+ (fake_name)?strlen(fake_name): 0,
+ my_email, /* which of my email addresses to use (empty is ok) */
+ (my_email)?strlen(my_email): 0,
+ subject, /* Subject (optional) */
+ (subject)?strlen(subject): 0,
+ supplied_euid, /* ...or NULL if this is irrelevant */
+ (supplied_euid)?strlen(supplied_euid):0,
+ preformatted_text, /* ...or NULL to read text from client */
+ (preformatted_text)?strlen(preformatted_text) : 0,
+ references, /* Thread references */
+ (references)?strlen(references):0);
+
+}
+
+
+/*
+ * Build a binary message to be saved on disk.
+ * (NOTE: if you supply 'preformatted_text', the buffer you give it
+ * will become part of the message. This means you are no longer
+ * responsible for managing that memory -- it will be freed along with
+ * the rest of the fields when CM_Free() is called.)
+ */
+struct CtdlMessage *CtdlMakeMessageLen(
+ struct ctdluser *author, /* author's user structure */
+ char *recipient, /* NULL if it's not mail */
+ long rcplen,
+ char *recp_cc, /* NULL if it's not mail */
+ long cclen,
+ char *room, /* room where it's going */
+ long roomlen,
+ int type, /* see MES_ types in header file */
+ int format_type, /* variformat, plain text, MIME... */
+ char *fake_name, /* who we're masquerading as */
+ long fnlen,
+ char *my_email, /* which of my email addresses to use (empty is ok) */
+ long myelen,
+ char *subject, /* Subject (optional) */
+ long subjlen,
+ char *supplied_euid, /* ...or NULL if this is irrelevant */
+ long euidlen,
+ char *preformatted_text, /* ...or NULL to read text from client */
+ long textlen,
+ char *references, /* Thread references */
+ long reflen
+) {
+ long blen;
+ char buf[1024];
+ struct CtdlMessage *msg;
+ StrBuf *FakeAuthor;
+ StrBuf *FakeEncAuthor = NULL;
+
+ msg = malloc(sizeof(struct CtdlMessage));
+ memset(msg, 0, sizeof(struct CtdlMessage));
+ msg->cm_magic = CTDLMESSAGE_MAGIC;
+ msg->cm_anon_type = type;
+ msg->cm_format_type = format_type;
+
+ if (recipient != NULL) rcplen = striplt(recipient);
+ if (recp_cc != NULL) cclen = striplt(recp_cc);
+
+ /* Path or Return-Path */
+ if (myelen > 0) {
+ CM_SetField(msg, eMessagePath, my_email, myelen);
+ }
+ else if (!IsEmptyStr(author->fullname)) {
+ CM_SetField(msg, eMessagePath, author->fullname, -1);
+ }
+ convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
+
+ blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
+ CM_SetField(msg, eTimestamp, buf, blen);
+
+ if (fnlen > 0) {
+ FakeAuthor = NewStrBufPlain (fake_name, fnlen);
+ }
+ else {
+ FakeAuthor = NewStrBufPlain (author->fullname, -1);
+ }
+ StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
+ CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor);
+ FreeStrBuf(&FakeAuthor);
+
+ if (!!IsEmptyStr(CC->room.QRname)) {
+ if (CC->room.QRflags & QR_MAILBOX) { /* room */
+ CM_SetField(msg, eOriginalRoom, &CC->room.QRname[11], -1);
+ }
+ else {
+ CM_SetField(msg, eOriginalRoom, CC->room.QRname, -1);
+ }
+ }
+
+ if (rcplen > 0) {
+ CM_SetField(msg, eRecipient, recipient, rcplen);
+ }
+ if (cclen > 0) {
+ CM_SetField(msg, eCarbonCopY, recp_cc, cclen);
+ }
+
+ if (myelen > 0) {
+ CM_SetField(msg, erFc822Addr, my_email, myelen);
+ }
+ else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
+ CM_SetField(msg, erFc822Addr, CC->cs_inet_email, -1);
+ }
+
+ if (subject != NULL) {
+ long length;
+ length = striplt(subject);
+ if (length > 0) {
+ long i;
+ long IsAscii;
+ IsAscii = -1;
+ i = 0;
+ while ((subject[i] != '\0') &&
+ (IsAscii = isascii(subject[i]) != 0 ))
+ i++;
+ if (IsAscii != 0)
+ CM_SetField(msg, eMsgSubject, subject, subjlen);
+ else /* ok, we've got utf8 in the string. */
+ {
+ char *rfc2047Subj;
+ rfc2047Subj = rfc2047encode(subject, length);
+ CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj));
+ }
+
+ }
+ }
+
+ if (euidlen > 0) {
+ CM_SetField(msg, eExclusiveID, supplied_euid, euidlen);
+ }
+
+ if (reflen > 0) {
+ CM_SetField(msg, eWeferences, references, reflen);
+ }
+
+ if (preformatted_text != NULL) {
+ CM_SetField(msg, eMesageText, preformatted_text, textlen);
+ }
+ else {
+ StrBuf *MsgBody;
+ MsgBody = CtdlReadMessageBodyBuf(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
+ if (MsgBody != NULL) {
+ CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
+ }
+ }
+
+ return(msg);
+}
+
+
+/*
+ * API function to delete messages which match a set of criteria
+ * (returns the actual number of messages deleted)
+ */
+int CtdlDeleteMessages(const char *room_name, // which room
+ long *dmsgnums, // array of msg numbers to be deleted
+ int num_dmsgnums, // number of msgs to be deleted, or 0 for "any"
+ char *content_type // or "" for any. regular expressions expected.
+) {
+ struct ctdlroom qrbuf;
+ struct cdbdata *cdbfr;
+ long *msglist = NULL;
+ long *dellist = NULL;
+ int num_msgs = 0;
+ int i, j;
+ int num_deleted = 0;
+ int delete_this;
+ struct MetaData smi;
+ regex_t re;
+ regmatch_t pm;
+ int need_to_free_re = 0;
+
+ if (content_type) if (!IsEmptyStr(content_type)) {
+ regcomp(&re, content_type, 0);
+ need_to_free_re = 1;
+ }
+ syslog(LOG_DEBUG, "msgbase: CtdlDeleteMessages(%s, %d msgs, %s)", room_name, num_dmsgnums, content_type);
+
+ /* get room record, obtaining a lock... */
+ if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
+ syslog(LOG_ERR, "msgbase: CtdlDeleteMessages(): Room <%s> not found", room_name);
+ if (need_to_free_re) regfree(&re);
+ return(0); /* room not found */
+ }
+ cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
+
+ if (cdbfr != NULL) {
+ dellist = malloc(cdbfr->len);
+ msglist = (long *) cdbfr->ptr;
+ cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
+ num_msgs = cdbfr->len / sizeof(long);
+ cdb_free(cdbfr);
+ }
+ if (num_msgs > 0) {
+ int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
+ int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
+ int have_more_del = 1;
+
+ num_msgs = sort_msglist(msglist, num_msgs);
+ if (num_dmsgnums > 1)
+ num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
+/*
+ {
+ StrBuf *dbg = NewStrBuf();
+ for (i = 0; i < num_dmsgnums; i++)
+ StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
+ syslog(LOG_DEBUG, "msgbase: Deleting before: %s", ChrPtr(dbg));
+ FreeStrBuf(&dbg);
+ }
+*/
+ i = 0; j = 0;
+ while ((i < num_msgs) && (have_more_del)) {
+ delete_this = 0x00;
+
+ /* Set/clear a bit for each criterion */
+
+ /* 0 messages in the list or a null list means that we are
+ * interested in deleting any messages which meet the other criteria.
+ */
+ if (have_delmsgs) {
+ delete_this |= 0x01;
+ }
+ else {
+ while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
+
+ if (i >= num_msgs)
+ continue;
+
+ if (msglist[i] == dmsgnums[j]) {
+ delete_this |= 0x01;
+ }
+ j++;
+ have_more_del = (j < num_dmsgnums);
+ }
+
+ if (have_contenttype) {
+ GetMetaData(&smi, msglist[i]);
+ if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
+ delete_this |= 0x02;
+ }
+ } else {
+ delete_this |= 0x02;
+ }
+
+ /* Delete message only if all bits are set */
+ if (delete_this == 0x03) {
+ dellist[num_deleted++] = msglist[i];
+ msglist[i] = 0L;
+ }
+ i++;
+ }
+/*
+ {
+ StrBuf *dbg = NewStrBuf();
+ for (i = 0; i < num_deleted; i++)
+ StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
+ syslog(LOG_DEBUG, "msgbase: Deleting: %s", ChrPtr(dbg));
+ FreeStrBuf(&dbg);
+ }
+*/
+ num_msgs = sort_msglist(msglist, num_msgs);
+ cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
+ msglist, (int)(num_msgs * sizeof(long)));
+
+ if (num_msgs > 0)
+ qrbuf.QRhighest = msglist[num_msgs - 1];
+ else
+ qrbuf.QRhighest = 0;
+ }
+ CtdlPutRoomLock(&qrbuf);
+
+ /* Go through the messages we pulled out of the index, and decrement
+ * their reference counts by 1. If this is the only room the message
+ * was in, the reference count will reach zero and the message will
+ * automatically be deleted from the database. We do this in a
+ * separate pass because there might be plug-in hooks getting called,
+ * and we don't want that happening during an S_ROOMS critical
+ * section.
+ */
+ if (num_deleted) {
+ for (i=0; i<num_deleted; ++i) {
+ PerformDeleteHooks(qrbuf.QRname, dellist[i]);
+ }
+ AdjRefCountList(dellist, num_deleted, -1);
+ }
+ /* Now free the memory we used, and go away. */
+ if (msglist != NULL) free(msglist);
+ if (dellist != NULL) free(dellist);
+ syslog(LOG_DEBUG, "msgbase: %d message(s) deleted", num_deleted);
+ if (need_to_free_re) regfree(&re);
+ return (num_deleted);
+}
+
+
+/*
+ * GetMetaData() - Get the supplementary record for a message
+ */
+void GetMetaData(struct MetaData *smibuf, long msgnum)
+{
+ struct cdbdata *cdbsmi;
+ long TheIndex;
+
+ memset(smibuf, 0, sizeof(struct MetaData));
+ smibuf->meta_msgnum = msgnum;
+ smibuf->meta_refcount = 1; /* Default reference count is 1 */
+
+ /* Use the negative of the message number for its supp record index */
+ TheIndex = (0L - msgnum);
+
+ cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
+ if (cdbsmi == NULL) {
+ return; /* record not found; leave it alone */
+ }
+ memcpy(smibuf, cdbsmi->ptr,
+ ((cdbsmi->len > sizeof(struct MetaData)) ?
+ sizeof(struct MetaData) : cdbsmi->len)
+ );
+ cdb_free(cdbsmi);
+ return;
+}
+
+
+/*
+ * PutMetaData() - (re)write supplementary record for a message
+ */
+void PutMetaData(struct MetaData *smibuf)
+{
+ long TheIndex;
+
+ /* Use the negative of the message number for the metadata db index */
+ TheIndex = (0L - smibuf->meta_msgnum);
+
+ cdb_store(CDB_MSGMAIN,
+ &TheIndex, (int)sizeof(long),
+ smibuf, (int)sizeof(struct MetaData)
+ );
+}
+
+
+/*
+ * Convenience function to process a big block of AdjRefCount() operations
+ */
+void AdjRefCountList(long *msgnum, long nmsg, int incr)
+{
+ long i;
+
+ for (i = 0; i < nmsg; i++) {
+ AdjRefCount(msgnum[i], incr);
+ }
+}
+
+
+/*
+ * AdjRefCount - adjust the reference count for a message. We need to delete from disk any message whose reference count reaches zero.
+ */
+void AdjRefCount(long msgnum, int incr)
+{
+ struct MetaData smi;
+ long delnum;
+
+ /* This is a *tight* critical section; please keep it that way, as
+ * it may get called while nested in other critical sections.
+ * Complicating this any further will surely cause deadlock!
+ */
+ begin_critical_section(S_SUPPMSGMAIN);
+ GetMetaData(&smi, msgnum);
+ smi.meta_refcount += incr;
+ PutMetaData(&smi);
+ end_critical_section(S_SUPPMSGMAIN);
+ syslog(LOG_DEBUG, "msgbase: AdjRefCount() msg %ld ref count delta %+d, is now %d", msgnum, incr, smi.meta_refcount);
+
+ /* If the reference count is now zero, delete both the message and its metadata record.
+ */
+ if (smi.meta_refcount == 0) {
+ syslog(LOG_DEBUG, "msgbase: deleting message <%ld>", msgnum);
+
+ /* Call delete hooks with NULL room to show it has gone altogether */
+ PerformDeleteHooks(NULL, msgnum);
+
+ /* Remove from message base */
+ delnum = msgnum;
+ cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
+ cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
+
+ /* Remove metadata record */
+ delnum = (0L - msgnum);
+ cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
+ }
+}
+
+
+/*
+ * Write a generic object to this room
+ *
+ * Returns the message number of the written object, in case you need it.
+ */
+long CtdlWriteObject(char *req_room, /* Room to stuff it in */
+ char *content_type, /* MIME type of this object */
+ char *raw_message, /* Data to be written */
+ off_t raw_length, /* Size of raw_message */
+ struct ctdluser *is_mailbox, /* Mailbox room? */
+ int is_binary, /* Is encoding necessary? */
+ unsigned int flags /* Internal save flags */
+) {
+ struct ctdlroom qrbuf;
+ char roomname[ROOMNAMELEN];
+ struct CtdlMessage *msg;
+ StrBuf *encoded_message = NULL;
+
+ if (is_mailbox != NULL) {
+ CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
+ }
+ else {
+ safestrncpy(roomname, req_room, sizeof(roomname));
+ }
+
+ syslog(LOG_DEBUG, "msfbase: raw length is %ld", (long)raw_length);
+
+ if (is_binary) {
+ encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) );
+ }
+ else {
+ encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096));
+ }
+
+ StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0);
+ StrBufAppendBufPlain(encoded_message, content_type, -1, 0);
+ StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0);
+
+ if (is_binary) {
+ StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0);
+ }
+ else {
+ StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0);
+ }
+
+ if (is_binary) {
+ StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0);
+ }
+ else {
+ StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0);
+ }
+
+ syslog(LOG_DEBUG, "msgbase: allocating");
+ msg = malloc(sizeof(struct CtdlMessage));
+ memset(msg, 0, sizeof(struct CtdlMessage));
+ msg->cm_magic = CTDLMESSAGE_MAGIC;
+ msg->cm_anon_type = MES_NORMAL;
+ msg->cm_format_type = 4;
+ CM_SetField(msg, eAuthor, CC->user.fullname, -1);
+ CM_SetField(msg, eOriginalRoom, req_room, -1);
+ msg->cm_flags = flags;
+
+ CM_SetAsFieldSB(msg, eMesageText, &encoded_message);
+
+ /* Create the requested room if we have to. */
+ if (CtdlGetRoom(&qrbuf, roomname) != 0) {
+ CtdlCreateRoom(roomname, ( (is_mailbox != NULL) ? 5 : 3 ), "", 0, 1, 0, VIEW_BBS);
+ }
+
+ /* Now write the data */
+ long new_msgnum = CtdlSubmitMsg(msg, NULL, roomname);
+ CM_Free(msg);
+ return new_msgnum;
+}
+
+
+/************************************************************************/
+/* MODULE INITIALIZATION */
+/************************************************************************/
+
+char *ctdl_module_init_msgbase(void) {
+ if (!threading) {
+ FillMsgKeyLookupTable();
+ }
+
+ /* return our module id for the log */
+ return "msgbase";
+}
--- /dev/null
+
+#ifndef MSGBASE_H
+#define MSGBASE_H
+
+enum {
+ MSGS_ALL,
+ MSGS_OLD,
+ MSGS_NEW,
+ MSGS_FIRST,
+ MSGS_LAST,
+ MSGS_GT,
+ MSGS_EQ,
+ MSGS_SEARCH,
+ MSGS_LT
+};
+
+enum {
+ MSG_HDRS_BRIEF = 0,
+ MSG_HDRS_ALL = 1,
+ MSG_HDRS_EUID = 4,
+ MSG_HDRS_BRIEFFILTER = 8,
+ MSG_HDRS_THREADS = 9
+};
+
+/*
+ * Possible return codes from CtdlOutputMsg()
+ */
+enum {
+ om_ok,
+ om_not_logged_in,
+ om_no_such_msg,
+ om_mime_error,
+ om_access_denied
+};
+
+/*
+ * Values of "headers_only" when calling message output routines
+ */
+#define HEADERS_ALL 0 /* Headers and body */
+#define HEADERS_ONLY 1 /* Headers only */
+#define HEADERS_NONE 2 /* Body only */
+#define HEADERS_FAST 3 /* Headers only with no MIME info */
+
+
+struct ma_info {
+ int is_ma; /* Set to 1 if we are using this stuff */
+ int freeze; /* Freeze the replacement chain because we're
+ * digging through a subsection */
+ int did_print; /* One alternative has been displayed */
+ char chosen_part[128]; /* Which part of a m/a did we choose? */
+ int chosen_pref; /* Chosen part preference level (lower is better) */
+ int use_fo_hooks; /* Use fixed output hooks */
+ int dont_decode; /* should we call the decoder or not? */
+};
+
+
+struct repl { /* Info for replication checking */
+ char exclusive_id[SIZ];
+ time_t highest;
+};
+
+
+/*
+ * This is a list of "harvested" email addresses that we might want to
+ * stick into someone's address book. But we defer this operaiton so
+ * it can be done asynchronously.
+ */
+struct addresses_to_be_filed {
+ struct addresses_to_be_filed *next;
+ char *roomname;
+ char *collected_addresses;
+};
+
+extern struct addresses_to_be_filed *atbf;
+
+int GetFieldFromMnemonic(eMsgField *f, const char* c);
+void memfmout (char *mptr, const char *nl);
+void output_mime_parts(char *);
+long send_message (struct CtdlMessage *);
+void loadtroom (void);
+long CtdlSubmitMsg(struct CtdlMessage *, struct recptypes *, const char *);
+long quickie_message(char *from, char *fromaddr, char *to, char *room, char *text, int format_type, char *subject);
+void GetMetaData(struct MetaData *, long);
+void PutMetaData(struct MetaData *);
+void AdjRefCount(long, int);
+void TDAP_AdjRefCount(long, int);
+int TDAP_ProcessAdjRefCountQueue(void);
+void simple_listing(long, void *);
+int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template);
+typedef void (*ForEachMsgCallback)(long MsgNumber, void *UserData);
+int CtdlForEachMessage(int mode,
+ long ref,
+ char *searchstring,
+ char *content_type,
+ struct CtdlMessage *compare,
+ ForEachMsgCallback CallBack,
+ void *userdata);
+int CtdlDeleteMessages(const char *, long *, int, char *);
+long CtdlWriteObject(char *req_room, /* Room to stuff it in */
+ char *content_type, /* MIME type of this object */
+ char *raw_message, /* Data to be written */
+ off_t raw_length, /* Size of raw_message */
+ struct ctdluser *is_mailbox, /* Mailbox room? */
+ int is_binary, /* Is encoding necessary? */
+ unsigned int flags /* Internal save flags */
+);
+struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body);
+struct CtdlMessage * CM_Duplicate
+ (struct CtdlMessage *OrgMsg);
+int CM_IsEmpty (struct CtdlMessage *Msg, eMsgField which);
+void CM_SetField (struct CtdlMessage *Msg, eMsgField which, const char *buf, long length);
+void CM_SetFieldLONG (struct CtdlMessage *Msg, eMsgField which, long lvalue);
+void CM_CopyField (struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy);
+void CM_CutFieldAt (struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen);
+void CM_FlushField (struct CtdlMessage *Msg, eMsgField which);
+void CM_Flush (struct CtdlMessage *Msg);
+void CM_SetAsField (struct CtdlMessage *Msg, eMsgField which, char **buf, long length);
+void CM_SetAsFieldSB (struct CtdlMessage *Msg, eMsgField which, StrBuf **buf);
+void CM_GetAsField (struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen);
+void CM_PrependToField (struct CtdlMessage *Msg, eMsgField which, const char *buf, long length);
+
+void CM_Free (struct CtdlMessage *msg);
+void CM_FreeContents (struct CtdlMessage *msg);
+int CM_IsValidMsg (struct CtdlMessage *msg);
+
+#define CM_KEY(Message, Which) Message->cm_fields[Which], Message->cm_lengths[Which]
+#define CM_RANGE(Message, Which) Message->cm_fields[Which], \
+ Message->cm_fields[Which] + Message->cm_lengths[Which]
+
+void CtdlSerializeMessage(struct ser_ret *, struct CtdlMessage *);
+struct CtdlMessage *CtdlDeserializeMessage(long msgnum, int with_body, const char *Buffer, long Length);
+void ReplicationChecks(struct CtdlMessage *);
+int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
+ int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
+);
+int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check, struct CtdlMessage *msg);
+long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply);
+char *CtdlReadMessageBody(char *terminator, long tlen, size_t maxlen, StrBuf *exist, int crlf);
+StrBuf *CtdlReadMessageBodyBuf(
+ char *terminator, /* token signalling EOT */
+ long tlen,
+ size_t maxlen, /* maximum message length */
+ StrBuf *exist, /* if non-null, append to it; exist is ALWAYS freed */
+ int crlf /* CRLF newlines instead of LF */
+);
+
+int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
+ int mode, /* how would you like that message? */
+ int headers_only, /* eschew the message body? */
+ int do_proto, /* do Citadel protocol responses? */
+ int crlf, /* 0=LF, 1=CRLF */
+ char *section, /* output a message/rfc822 section */
+ int flags, /* should the bessage be exported clean? */
+ char **Author, /* if you want to know the author of the message... */
+ char **Address, /* if you want to know the sender address of the message... */
+ char **MessageID /* if you want to know the Message-ID of the message... */
+);
+
+/* Flags which may be passed to CtdlOutputMsg() and CtdlOutputPreLoadedMsg() */
+#define QP_EADDR (1<<0) /* quoted-printable encode email addresses */
+#define CRLF (1<<1)
+#define ESC_DOT (1<<2) /* output a line containing only "." as ".." instead */
+#define SUPPRESS_ENV_TO (1<<3) /* suppress Envelope-to: header (warning: destructive!) */
+
+int CtdlOutputPreLoadedMsg(struct CtdlMessage *,
+ int mode, /* how would you like that message? */
+ int headers_only, /* eschew the message body? */
+ int do_proto, /* do Citadel protocol responses? */
+ int crlf, /* 0=LF, 1=CRLF */
+ int flags /* should the bessage be exported clean? */
+);
+
+
+/* values for which_set */
+enum {
+ ctdlsetseen_seen,
+ ctdlsetseen_answered
+};
+
+void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
+ int target_setting, int which_set,
+ struct ctdluser *which_user, struct ctdlroom *which_room
+);
+
+void CtdlGetSeen(char *buf, int which_set);
+
+
+struct CtdlMessage *CtdlMakeMessage(
+ struct ctdluser *author, /* author's user structure */
+ char *recipient, /* NULL if it's not mail */
+ char *recp_cc, /* NULL if it's not mail */
+ char *room, /* room where it's going */
+ int type, /* see MES_ types in header file */
+ int format_type, /* variformat, plain text, MIME... */
+ char *fake_name, /* who we're masquerading as */
+ char *my_email, /* which of my email addresses to use (empty is ok) */
+ char *subject, /* Subject (optional) */
+ char *supplied_euid, /* ...or NULL if this is irrelevant */
+ char *preformatted_text, /* ...or NULL to read text from client */
+ char *references /* Thread references */
+);
+
+struct CtdlMessage *CtdlMakeMessageLen(
+ struct ctdluser *author, /* author's user structure */
+ char *recipient, /* NULL if it's not mail */
+ long rcplen,
+ char *recp_cc, /* NULL if it's not mail */
+ long cclen,
+ char *room, /* room where it's going */
+ long roomlen,
+ int type, /* see MES_ types in header file */
+ int format_type, /* variformat, plain text, MIME... */
+ char *fake_name, /* who we're masquerading as */
+ long fnlen,
+ char *my_email, /* which of my email addresses to use (empty is ok) */
+ long myelen,
+ char *subject, /* Subject (optional) */
+ long subjlen,
+ char *supplied_euid, /* ...or NULL if this is irrelevant */
+ long euidlen,
+ char *preformatted_text, /* ...or NULL to read text from client */
+ long textlen,
+ char *references, /* Thread references */
+ long reflen
+);
+
+void AdjRefCountList(long *msgnum, long nmsg, int incr);
+
+#endif /* MSGBASE_H */
--- /dev/null
+// This module handles loading, saving, and parsing of room network configurations.
+//
+// Copyright (c) 2000-2022 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.
+
+#include "sysdep.h"
+#include <stdio.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#ifdef HAVE_SYSCALL_H
+# include <syscall.h>
+#else
+# if HAVE_SYS_SYSCALL_H
+# include <sys/syscall.h>
+# endif
+#endif
+#include <dirent.h>
+#include <assert.h>
+#include <libcitadel.h>
+#include "ctdl_module.h"
+#include "serv_extensions.h"
+#include "config.h"
+
+
+// Create a config key for a room's netconfig entry
+void netcfg_keyname(char *keybuf, long roomnum) {
+ if (!keybuf) return;
+ sprintf(keybuf, "c_netconfig_%010ld", roomnum);
+}
+
+
+// Given a room number and a textual netconfig, convert to base64 and write to the configdb
+void SaveRoomNetConfigFile(long roomnum, const char *raw_netconfig) {
+ char keyname[25];
+ char *enc;
+ int enc_len;
+ int len;
+
+ len = strlen(raw_netconfig);
+ netcfg_keyname(keyname, roomnum);
+ enc = malloc(len * 2);
+
+ if (enc) {
+ enc_len = CtdlEncodeBase64(enc, raw_netconfig, len, 0);
+ if ((enc_len > 1) && (enc[enc_len-2] == 13)) enc[enc_len-2] = 0;
+ if ((enc_len > 0) && (enc[enc_len-1] == 10)) enc[enc_len-1] = 0;
+ enc[enc_len] = 0;
+ syslog(LOG_DEBUG, "netconfig: writing key '%s' (length=%d)", keyname, enc_len);
+ CtdlSetConfigStr(keyname, enc);
+ free(enc);
+ }
+}
+
+
+// Given a room number, attempt to load the netconfig configdb entry for that room.
+// If it returns NULL, there is no netconfig.
+// Otherwise the caller owns the returned memory and is responsible for freeing it.
+char *LoadRoomNetConfigFile(long roomnum) {
+ char keyname[25];
+ char *encoded_netconfig = NULL;
+ char *decoded_netconfig = NULL;
+
+ netcfg_keyname(keyname, roomnum);
+ encoded_netconfig = CtdlGetConfigStr(keyname);
+ if (!encoded_netconfig) return NULL;
+
+ decoded_netconfig = malloc(strlen(encoded_netconfig)); // yeah, way bigger than it needs to be, but safe
+ CtdlDecodeBase64(decoded_netconfig, encoded_netconfig, strlen(encoded_netconfig));
+ return decoded_netconfig;
+}
+
+
+//-----------------------------------------------------------------------------
+// Per room network configs : exchange with client
+//-----------------------------------------------------------------------------
+void cmd_gnet(char *argbuf) {
+ if ( (CC->room.QRflags & QR_MAILBOX) && (CC->user.usernum == atol(CC->room.QRname)) ) {
+ // users can edit the netconfigs for their own mailbox rooms
+ }
+ else if (CtdlAccessCheck(ac_room_aide)) return;
+
+ cprintf("%d Network settings for room #%ld <%s>\n", LISTING_FOLLOWS, CC->room.QRnumber, CC->room.QRname);
+
+ char *c = LoadRoomNetConfigFile(CC->room.QRnumber);
+ if (c) {
+ int len = strlen(c);
+ client_write(c, len); // Can't use cprintf() here, it has a limit of 1024 bytes
+ if (c[len] != '\n') {
+ client_write(HKEY("\n"));
+ }
+ free(c);
+ }
+ cprintf("000\n");
+}
+
+
+void cmd_snet(char *argbuf) {
+ StrBuf *Line = NULL;
+ StrBuf *TheConfig = NULL;
+ int rc;
+
+ unbuffer_output();
+ Line = NewStrBuf();
+ TheConfig = NewStrBuf();
+ cprintf("%d send new netconfig now\n", SEND_LISTING);
+
+ while (rc = CtdlClientGetLine(Line), (rc >= 0)) {
+ if ((rc == 3) && (strcmp(ChrPtr(Line), "000") == 0))
+ break;
+
+ StrBufAppendBuf(TheConfig, Line, 0);
+ StrBufAppendBufPlain(TheConfig, HKEY("\n"), 0);
+ }
+ FreeStrBuf(&Line);
+
+ begin_critical_section(S_NETCONFIGS);
+ SaveRoomNetConfigFile(CC->room.QRnumber, ChrPtr(TheConfig));
+ end_critical_section(S_NETCONFIGS);
+ FreeStrBuf(&TheConfig);
+}
+
+
+// Convert any legacy configuration files in the "netconfigs" directory
+void convert_legacy_netcfg_files(void) {
+ DIR *dh = NULL;
+ struct dirent *dit = NULL;
+ char filename[PATH_MAX];
+ long roomnum;
+ FILE *fp;
+ long len;
+ char *v;
+
+ dh = opendir(ctdl_netcfg_dir);
+ if (!dh) return;
+
+ syslog(LOG_INFO, "netconfig: legacy netconfig files exist - converting them!");
+
+ while (dit = readdir(dh), dit != NULL) { // yes, we use the non-reentrant version; we're not in threaded mode yet
+ roomnum = atol(dit->d_name);
+ if (roomnum > 0) {
+ snprintf(filename, sizeof filename, "%s/%ld", ctdl_netcfg_dir, roomnum);
+ fp = fopen(filename, "r");
+ if (fp) {
+ fseek(fp, 0L, SEEK_END);
+ len = ftell(fp);
+ if (len > 0) {
+ v = malloc(len);
+ if (v) {
+ rewind(fp);
+ if (fread(v, len, 1, fp)) {
+ SaveRoomNetConfigFile(roomnum, v);
+ unlink(filename);
+ }
+ free(v);
+ }
+ }
+ else {
+ unlink(filename); // zero length netconfig, just delete it
+ }
+ fclose(fp);
+ }
+ }
+ }
+
+ closedir(dh);
+ rmdir(ctdl_netcfg_dir);
+}
+
+
+/*
+ * Module entry point
+ */
+char *ctdl_module_init_netconfig(void) {
+ if (!threading) {
+ convert_legacy_netcfg_files();
+ CtdlRegisterProtoHook(cmd_gnet, "GNET", "Get network config");
+ CtdlRegisterProtoHook(cmd_snet, "SNET", "Set network config");
+ }
+ return "netconfig";
+}
--- /dev/null
+/* A Bison parser, made by GNU Bison 3.0.4. */
+
+/* Bison implementation for Yacc-like parsers in C
+
+ Copyright (C) 1984, 1989-1990, 2000-2015 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* As a special exception, you may create a larger work that contains
+ part or all of the Bison parser skeleton and distribute that work
+ under terms of your choice, so long as that work isn't itself a
+ parser generator using the skeleton or a modified version thereof
+ as a parser skeleton. Alternatively, if you modify or redistribute
+ the parser skeleton itself, you may (at your option) remove this
+ special exception, which will cause the skeleton and the resulting
+ Bison output files to be licensed under the GNU General Public
+ License without this special exception.
+
+ This special exception was added by the Free Software Foundation in
+ version 2.2 of Bison. */
+
+/* C LALR(1) parser skeleton written by Richard Stallman, by
+ simplifying the original so-called "semantic" parser. */
+
+/* All symbols defined below should begin with yy or YY, to avoid
+ infringing on user name space. This should be done even for local
+ variables, as they might otherwise be expanded by user macros.
+ There are some unavoidable exceptions within include files to
+ define necessary library symbols; they are noted "INFRINGES ON
+ USER NAME SPACE" below. */
+
+/* Identify Bison output. */
+#define YYBISON 1
+
+/* Bison version. */
+#define YYBISON_VERSION "3.0.4"
+
+/* Skeleton name. */
+#define YYSKELETON_NAME "yacc.c"
+
+/* Pure parsers. */
+#define YYPURE 0
+
+/* Push parsers. */
+#define YYPUSH 0
+
+/* Pull parsers. */
+#define YYPULL 1
+
+
+
+
+/* Copy the first part of user declarations. */
+#line 1 "parsedate.y" /* yacc.c:339 */
+
+/* $Revision$
+**
+** Originally written by Steven M. Bellovin <smb@research.att.com> while
+** at the University of North Carolina at Chapel Hill. Later tweaked by
+** a couple of people on Usenet. Completely overhauled by Rich $alz
+** <rsalz@osf.org> and Jim Berets <jberets@bbn.com> in August, 1990.
+** Further revised (removed obsolete constructs and cleaned up timezone
+** names) in August, 1991, by Rich. Paul Eggert <eggert@twinsun.com>
+** helped in September, 1992. Art Cancro <ajc@citadel.org> cleaned
+** it up for ANSI C in December, 1999.
+**
+** This grammar has six shift/reduce conflicts.
+**
+** This code is in the public domain and has no copyright.
+*/
+/* SUPPRESS 530 *//* Empty body for statement */
+/* SUPPRESS 593 on yyerrlab *//* Label was not used */
+/* SUPPRESS 593 on yynewstate *//* Label was not used */
+/* SUPPRESS 595 on yypvt *//* Automatic variable may be used before set */
+
+#include "sysdep.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <ctype.h>
+#include <time.h>
+#if HAVE_STRING_H
+# if !STDC_HEADERS && HAVE_MEMORY_H
+# include <memory.h>
+# endif
+# include <string.h>
+#endif
+#if HAVE_STRINGS_H
+# include <strings.h>
+#endif
+
+#include "parsedate.h"
+
+int date_lex(void);
+
+#define yyparse date_parse
+#define yylex date_lex
+#define yyerror date_error
+
+
+ /* See the LeapYears table in Convert. */
+#define EPOCH 1970
+#define END_OF_TIME 2038
+ /* Constants for general time calculations. */
+#define DST_OFFSET 1
+#define SECSPERDAY (24L * 60L * 60L)
+ /* Readability for TABLE stuff. */
+#define HOUR(x) (x * 60)
+
+#define LPAREN '('
+#define RPAREN ')'
+#define IS7BIT(x) ((unsigned int)(x) < 0200)
+
+#define SIZEOF(array) ((int)(sizeof array / sizeof array[0]))
+#define ENDOF(array) (&array[SIZEOF(array)])
+
+
+/*
+** An entry in the lexical lookup table.
+*/
+typedef struct _TABLE {
+ char *name;
+ int type;
+ time_t value;
+} TABLE;
+
+/*
+** Daylight-savings mode: on, off, or not yet known.
+*/
+typedef enum _DSTMODE {
+ DSTon, DSToff, DSTmaybe
+} DSTMODE;
+
+/*
+** Meridian: am, pm, or 24-hour style.
+*/
+typedef enum _MERIDIAN {
+ MERam, MERpm, MER24
+} MERIDIAN;
+
+
+/*
+** Global variables. We could get rid of most of them by using a yacc
+** union, but this is more efficient. (This routine predates the
+** yacc %union construct.)
+*/
+static const char *yyInput;
+static DSTMODE yyDSTmode;
+static int yyHaveDate;
+static int yyHaveRel;
+static int yyHaveTime;
+static time_t yyTimezone;
+static time_t yyDay;
+static time_t yyHour;
+static time_t yyMinutes;
+static time_t yyMonth;
+static time_t yySeconds;
+static time_t yyYear;
+static MERIDIAN yyMeridian;
+static time_t yyRelMonth;
+static time_t yyRelSeconds;
+
+
+static void date_error(char *);
+
+#line 179 "y.tab.c" /* yacc.c:339 */
+
+# ifndef YY_NULLPTR
+# if defined __cplusplus && 201103L <= __cplusplus
+# define YY_NULLPTR nullptr
+# else
+# define YY_NULLPTR 0
+# endif
+# endif
+
+/* Enabling verbose error messages. */
+#ifdef YYERROR_VERBOSE
+# undef YYERROR_VERBOSE
+# define YYERROR_VERBOSE 1
+#else
+# define YYERROR_VERBOSE 0
+#endif
+
+
+/* Debug traces. */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+#if YYDEBUG
+extern int yydebug;
+#endif
+
+/* Token type. */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+ enum yytokentype
+ {
+ tDAY = 258,
+ tDAYZONE = 259,
+ tMERIDIAN = 260,
+ tMONTH = 261,
+ tMONTH_UNIT = 262,
+ tSEC_UNIT = 263,
+ tSNUMBER = 264,
+ tUNUMBER = 265,
+ tZONE = 266
+ };
+#endif
+/* Tokens. */
+#define tDAY 258
+#define tDAYZONE 259
+#define tMERIDIAN 260
+#define tMONTH 261
+#define tMONTH_UNIT 262
+#define tSEC_UNIT 263
+#define tSNUMBER 264
+#define tUNUMBER 265
+#define tZONE 266
+
+/* Value type. */
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+
+union YYSTYPE
+{
+#line 114 "parsedate.y" /* yacc.c:355 */
+
+ time_t Number;
+ enum _MERIDIAN Meridian;
+
+#line 243 "y.tab.c" /* yacc.c:355 */
+};
+
+typedef union YYSTYPE YYSTYPE;
+# define YYSTYPE_IS_TRIVIAL 1
+# define YYSTYPE_IS_DECLARED 1
+#endif
+
+
+extern YYSTYPE yylval;
+
+int yyparse (void);
+
+
+
+/* Copy the second part of user declarations. */
+
+#line 260 "y.tab.c" /* yacc.c:358 */
+
+#ifdef short
+# undef short
+#endif
+
+#ifdef YYTYPE_UINT8
+typedef YYTYPE_UINT8 yytype_uint8;
+#else
+typedef unsigned char yytype_uint8;
+#endif
+
+#ifdef YYTYPE_INT8
+typedef YYTYPE_INT8 yytype_int8;
+#else
+typedef signed char yytype_int8;
+#endif
+
+#ifdef YYTYPE_UINT16
+typedef YYTYPE_UINT16 yytype_uint16;
+#else
+typedef unsigned short int yytype_uint16;
+#endif
+
+#ifdef YYTYPE_INT16
+typedef YYTYPE_INT16 yytype_int16;
+#else
+typedef short int yytype_int16;
+#endif
+
+#ifndef YYSIZE_T
+# ifdef __SIZE_TYPE__
+# define YYSIZE_T __SIZE_TYPE__
+# elif defined size_t
+# define YYSIZE_T size_t
+# elif ! defined YYSIZE_T
+# include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+# define YYSIZE_T size_t
+# else
+# define YYSIZE_T unsigned int
+# endif
+#endif
+
+#define YYSIZE_MAXIMUM ((YYSIZE_T) -1)
+
+#ifndef YY_
+# if defined YYENABLE_NLS && YYENABLE_NLS
+# if ENABLE_NLS
+# include <libintl.h> /* INFRINGES ON USER NAME SPACE */
+# define YY_(Msgid) dgettext ("bison-runtime", Msgid)
+# endif
+# endif
+# ifndef YY_
+# define YY_(Msgid) Msgid
+# endif
+#endif
+
+#ifndef YY_ATTRIBUTE
+# if (defined __GNUC__ \
+ && (2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__))) \
+ || defined __SUNPRO_C && 0x5110 <= __SUNPRO_C
+# define YY_ATTRIBUTE(Spec) __attribute__(Spec)
+# else
+# define YY_ATTRIBUTE(Spec) /* empty */
+# endif
+#endif
+
+#ifndef YY_ATTRIBUTE_PURE
+# define YY_ATTRIBUTE_PURE YY_ATTRIBUTE ((__pure__))
+#endif
+
+#ifndef YY_ATTRIBUTE_UNUSED
+# define YY_ATTRIBUTE_UNUSED YY_ATTRIBUTE ((__unused__))
+#endif
+
+#if !defined _Noreturn \
+ && (!defined __STDC_VERSION__ || __STDC_VERSION__ < 201112)
+# if defined _MSC_VER && 1200 <= _MSC_VER
+# define _Noreturn __declspec (noreturn)
+# else
+# define _Noreturn YY_ATTRIBUTE ((__noreturn__))
+# endif
+#endif
+
+/* Suppress unused-variable warnings by "using" E. */
+#if ! defined lint || defined __GNUC__
+# define YYUSE(E) ((void) (E))
+#else
+# define YYUSE(E) /* empty */
+#endif
+
+#if defined __GNUC__ && 407 <= __GNUC__ * 100 + __GNUC_MINOR__
+/* Suppress an incorrect diagnostic about yylval being uninitialized. */
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")\
+ _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END \
+ _Pragma ("GCC diagnostic pop")
+#else
+# define YY_INITIAL_VALUE(Value) Value
+#endif
+#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END
+#endif
+#ifndef YY_INITIAL_VALUE
+# define YY_INITIAL_VALUE(Value) /* Nothing. */
+#endif
+
+
+#if ! defined yyoverflow || YYERROR_VERBOSE
+
+/* The parser invokes alloca or malloc; define the necessary symbols. */
+
+# ifdef YYSTACK_USE_ALLOCA
+# if YYSTACK_USE_ALLOCA
+# ifdef __GNUC__
+# define YYSTACK_ALLOC __builtin_alloca
+# elif defined __BUILTIN_VA_ARG_INCR
+# include <alloca.h> /* INFRINGES ON USER NAME SPACE */
+# elif defined _AIX
+# define YYSTACK_ALLOC __alloca
+# elif defined _MSC_VER
+# include <malloc.h> /* INFRINGES ON USER NAME SPACE */
+# define alloca _alloca
+# else
+# define YYSTACK_ALLOC alloca
+# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+ /* Use EXIT_SUCCESS as a witness for stdlib.h. */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# endif
+# endif
+# endif
+
+# ifdef YYSTACK_ALLOC
+ /* Pacify GCC's 'empty if-body' warning. */
+# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0)
+# ifndef YYSTACK_ALLOC_MAXIMUM
+ /* The OS might guarantee only one guard page at the bottom of the stack,
+ and a page size can be as small as 4096 bytes. So we cannot safely
+ invoke alloca (N) if N exceeds 4096. Use a slightly smaller number
+ to allow for a few compiler-allocated temporary stack slots. */
+# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */
+# endif
+# else
+# define YYSTACK_ALLOC YYMALLOC
+# define YYSTACK_FREE YYFREE
+# ifndef YYSTACK_ALLOC_MAXIMUM
+# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM
+# endif
+# if (defined __cplusplus && ! defined EXIT_SUCCESS \
+ && ! ((defined YYMALLOC || defined malloc) \
+ && (defined YYFREE || defined free)))
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# ifndef YYMALLOC
+# define YYMALLOC malloc
+# if ! defined malloc && ! defined EXIT_SUCCESS
+void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# ifndef YYFREE
+# define YYFREE free
+# if ! defined free && ! defined EXIT_SUCCESS
+void free (void *); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# endif
+#endif /* ! defined yyoverflow || YYERROR_VERBOSE */
+
+
+#if (! defined yyoverflow \
+ && (! defined __cplusplus \
+ || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL)))
+
+/* A type that is properly aligned for any stack member. */
+union yyalloc
+{
+ yytype_int16 yyss_alloc;
+ YYSTYPE yyvs_alloc;
+};
+
+/* The size of the maximum gap between one aligned stack and the next. */
+# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1)
+
+/* The size of an array large to enough to hold all stacks, each with
+ N elements. */
+# define YYSTACK_BYTES(N) \
+ ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE)) \
+ + YYSTACK_GAP_MAXIMUM)
+
+# define YYCOPY_NEEDED 1
+
+/* Relocate STACK from its old location to the new one. The
+ local variables YYSIZE and YYSTACKSIZE give the old and new number of
+ elements in the stack, and YYPTR gives the new location of the
+ stack. Advance YYPTR to a properly aligned location for the next
+ stack. */
+# define YYSTACK_RELOCATE(Stack_alloc, Stack) \
+ do \
+ { \
+ YYSIZE_T yynewbytes; \
+ YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \
+ Stack = &yyptr->Stack_alloc; \
+ yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \
+ yyptr += yynewbytes / sizeof (*yyptr); \
+ } \
+ while (0)
+
+#endif
+
+#if defined YYCOPY_NEEDED && YYCOPY_NEEDED
+/* Copy COUNT objects from SRC to DST. The source and destination do
+ not overlap. */
+# ifndef YYCOPY
+# if defined __GNUC__ && 1 < __GNUC__
+# define YYCOPY(Dst, Src, Count) \
+ __builtin_memcpy (Dst, Src, (Count) * sizeof (*(Src)))
+# else
+# define YYCOPY(Dst, Src, Count) \
+ do \
+ { \
+ YYSIZE_T yyi; \
+ for (yyi = 0; yyi < (Count); yyi++) \
+ (Dst)[yyi] = (Src)[yyi]; \
+ } \
+ while (0)
+# endif
+# endif
+#endif /* !YYCOPY_NEEDED */
+
+/* YYFINAL -- State number of the termination state. */
+#define YYFINAL 2
+/* YYLAST -- Last index in YYTABLE. */
+#define YYLAST 40
+
+/* YYNTOKENS -- Number of terminals. */
+#define YYNTOKENS 15
+/* YYNNTS -- Number of nonterminals. */
+#define YYNNTS 9
+/* YYNRULES -- Number of rules. */
+#define YYNRULES 30
+/* YYNSTATES -- Number of states. */
+#define YYNSTATES 44
+
+/* YYTRANSLATE[YYX] -- Symbol number corresponding to YYX as returned
+ by yylex, with out-of-bounds checking. */
+#define YYUNDEFTOK 2
+#define YYMAXUTOK 266
+
+#define YYTRANSLATE(YYX) \
+ ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK)
+
+/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM
+ as returned by yylex, without out-of-bounds checking. */
+static const yytype_uint8 yytranslate[] =
+{
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 14, 2, 2, 13, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 12, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11
+};
+
+#if YYDEBUG
+ /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */
+static const yytype_uint16 yyrline[] =
+{
+ 0, 128, 128, 129, 132, 141, 145, 148, 153, 165,
+ 171, 178, 184, 194, 198, 202, 210, 216, 237, 241,
+ 253, 257, 262, 266, 271, 278, 281, 284, 287, 292,
+ 295
+};
+#endif
+
+#if YYDEBUG || YYERROR_VERBOSE || 0
+/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
+ First, the terminals, then, starting at YYNTOKENS, nonterminals. */
+static const char *const yytname[] =
+{
+ "$end", "error", "$undefined", "tDAY", "tDAYZONE", "tMERIDIAN",
+ "tMONTH", "tMONTH_UNIT", "tSEC_UNIT", "tSNUMBER", "tUNUMBER", "tZONE",
+ "':'", "'/'", "','", "$accept", "spec", "item", "time", "zone",
+ "numzone", "date", "rel", "o_merid", YY_NULLPTR
+};
+#endif
+
+# ifdef YYPRINT
+/* YYTOKNUM[NUM] -- (External) token number corresponding to the
+ (internal) symbol number NUM (which must be that of a token). */
+static const yytype_uint16 yytoknum[] =
+{
+ 0, 256, 257, 258, 259, 260, 261, 262, 263, 264,
+ 265, 266, 58, 47, 44
+};
+# endif
+
+#define YYPACT_NINF -29
+
+#define yypact_value_is_default(Yystate) \
+ (!!((Yystate) == (-29)))
+
+#define YYTABLE_NINF -1
+
+#define yytable_value_is_error(Yytable_value) \
+ 0
+
+ /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
+ STATE-NUM. */
+static const yytype_int8 yypact[] =
+{
+ -29, 1, -29, -11, 11, 20, 12, -29, 4, -29,
+ -29, 13, 16, -29, -29, -29, 21, -29, -29, 22,
+ 23, -29, -29, -29, 5, -29, -29, 28, 25, -29,
+ 17, 24, -29, 26, -29, 29, -29, -29, 30, -29,
+ 0, -29, -29, -29
+};
+
+ /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM.
+ Performed when YYTABLE does not specify something else to do. Zero
+ means the default is an error. */
+static const yytype_uint8 yydefact[] =
+{
+ 2, 0, 1, 0, 0, 0, 29, 3, 4, 6,
+ 7, 0, 20, 27, 25, 30, 22, 28, 26, 0,
+ 0, 8, 14, 17, 13, 5, 16, 0, 0, 23,
+ 29, 18, 15, 0, 21, 0, 10, 9, 0, 24,
+ 29, 19, 12, 11
+};
+
+ /* YYPGOTO[NTERM-NUM]. */
+static const yytype_int8 yypgoto[] =
+{
+ -29, -29, -29, -29, -29, -24, -29, -29, -28
+};
+
+ /* YYDEFGOTO[NTERM-NUM]. */
+static const yytype_int8 yydefgoto[] =
+{
+ -1, 1, 7, 8, 25, 26, 9, 10, 21
+};
+
+ /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If
+ positive, shift that token. If negative, reduce the rule whose
+ number is the opposite. If YYTABLE_NINF, syntax error. */
+static const yytype_uint8 yytable[] =
+{
+ 32, 2, 37, 11, 3, 15, 36, 4, 22, 23,
+ 5, 6, 43, 23, 23, 24, 42, 15, 16, 17,
+ 18, 12, 15, 27, 19, 20, 23, 13, 14, 35,
+ 28, 29, 30, 31, 33, 34, 39, 38, 0, 40,
+ 41
+};
+
+static const yytype_int8 yycheck[] =
+{
+ 24, 0, 30, 14, 3, 5, 30, 6, 4, 9,
+ 9, 10, 40, 9, 9, 11, 40, 5, 6, 7,
+ 8, 10, 5, 10, 12, 13, 9, 7, 8, 12,
+ 14, 10, 10, 10, 6, 10, 10, 13, -1, 10,
+ 10
+};
+
+ /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing
+ symbol of state STATE-NUM. */
+static const yytype_uint8 yystos[] =
+{
+ 0, 16, 0, 3, 6, 9, 10, 17, 18, 21,
+ 22, 14, 10, 7, 8, 5, 6, 7, 8, 12,
+ 13, 23, 4, 9, 11, 19, 20, 10, 14, 10,
+ 10, 10, 20, 6, 10, 12, 20, 23, 13, 10,
+ 10, 10, 20, 23
+};
+
+ /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */
+static const yytype_uint8 yyr1[] =
+{
+ 0, 15, 16, 16, 17, 17, 17, 17, 18, 18,
+ 18, 18, 18, 19, 19, 19, 19, 20, 21, 21,
+ 21, 21, 21, 21, 21, 22, 22, 22, 22, 23,
+ 23
+};
+
+ /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */
+static const yytype_uint8 yyr2[] =
+{
+ 0, 2, 0, 2, 1, 2, 1, 1, 2, 4,
+ 4, 6, 6, 1, 1, 2, 1, 1, 3, 5,
+ 2, 4, 2, 3, 5, 2, 2, 2, 2, 0,
+ 1
+};
+
+
+#define yyerrok (yyerrstatus = 0)
+#define yyclearin (yychar = YYEMPTY)
+#define YYEMPTY (-2)
+#define YYEOF 0
+
+#define YYACCEPT goto yyacceptlab
+#define YYABORT goto yyabortlab
+#define YYERROR goto yyerrorlab
+
+
+#define YYRECOVERING() (!!yyerrstatus)
+
+#define YYBACKUP(Token, Value) \
+do \
+ if (yychar == YYEMPTY) \
+ { \
+ yychar = (Token); \
+ yylval = (Value); \
+ YYPOPSTACK (yylen); \
+ yystate = *yyssp; \
+ goto yybackup; \
+ } \
+ else \
+ { \
+ yyerror (YY_("syntax error: cannot back up")); \
+ YYERROR; \
+ } \
+while (0)
+
+/* Error token number */
+#define YYTERROR 1
+#define YYERRCODE 256
+
+
+
+/* Enable debugging if requested. */
+#if YYDEBUG
+
+# ifndef YYFPRINTF
+# include <stdio.h> /* INFRINGES ON USER NAME SPACE */
+# define YYFPRINTF fprintf
+# endif
+
+# define YYDPRINTF(Args) \
+do { \
+ if (yydebug) \
+ YYFPRINTF Args; \
+} while (0)
+
+/* This macro is provided for backward compatibility. */
+#ifndef YY_LOCATION_PRINT
+# define YY_LOCATION_PRINT(File, Loc) ((void) 0)
+#endif
+
+
+# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \
+do { \
+ if (yydebug) \
+ { \
+ YYFPRINTF (stderr, "%s ", Title); \
+ yy_symbol_print (stderr, \
+ Type, Value); \
+ YYFPRINTF (stderr, "\n"); \
+ } \
+} while (0)
+
+
+/*----------------------------------------.
+| Print this symbol's value on YYOUTPUT. |
+`----------------------------------------*/
+
+static void
+yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep)
+{
+ FILE *yyo = yyoutput;
+ YYUSE (yyo);
+ if (!yyvaluep)
+ return;
+# ifdef YYPRINT
+ if (yytype < YYNTOKENS)
+ YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep);
+# endif
+ YYUSE (yytype);
+}
+
+
+/*--------------------------------.
+| Print this symbol on YYOUTPUT. |
+`--------------------------------*/
+
+static void
+yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep)
+{
+ YYFPRINTF (yyoutput, "%s %s (",
+ yytype < YYNTOKENS ? "token" : "nterm", yytname[yytype]);
+
+ yy_symbol_value_print (yyoutput, yytype, yyvaluep);
+ YYFPRINTF (yyoutput, ")");
+}
+
+/*------------------------------------------------------------------.
+| yy_stack_print -- Print the state stack from its BOTTOM up to its |
+| TOP (included). |
+`------------------------------------------------------------------*/
+
+static void
+yy_stack_print (yytype_int16 *yybottom, yytype_int16 *yytop)
+{
+ YYFPRINTF (stderr, "Stack now");
+ for (; yybottom <= yytop; yybottom++)
+ {
+ int yybot = *yybottom;
+ YYFPRINTF (stderr, " %d", yybot);
+ }
+ YYFPRINTF (stderr, "\n");
+}
+
+# define YY_STACK_PRINT(Bottom, Top) \
+do { \
+ if (yydebug) \
+ yy_stack_print ((Bottom), (Top)); \
+} while (0)
+
+
+/*------------------------------------------------.
+| Report that the YYRULE is going to be reduced. |
+`------------------------------------------------*/
+
+static void
+yy_reduce_print (yytype_int16 *yyssp, YYSTYPE *yyvsp, int yyrule)
+{
+ unsigned long int yylno = yyrline[yyrule];
+ int yynrhs = yyr2[yyrule];
+ int yyi;
+ YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n",
+ yyrule - 1, yylno);
+ /* The symbols being reduced. */
+ for (yyi = 0; yyi < yynrhs; yyi++)
+ {
+ YYFPRINTF (stderr, " $%d = ", yyi + 1);
+ yy_symbol_print (stderr,
+ yystos[yyssp[yyi + 1 - yynrhs]],
+ &(yyvsp[(yyi + 1) - (yynrhs)])
+ );
+ YYFPRINTF (stderr, "\n");
+ }
+}
+
+# define YY_REDUCE_PRINT(Rule) \
+do { \
+ if (yydebug) \
+ yy_reduce_print (yyssp, yyvsp, Rule); \
+} while (0)
+
+/* Nonzero means print parse trace. It is left uninitialized so that
+ multiple parsers can coexist. */
+int yydebug;
+#else /* !YYDEBUG */
+# define YYDPRINTF(Args)
+# define YY_SYMBOL_PRINT(Title, Type, Value, Location)
+# define YY_STACK_PRINT(Bottom, Top)
+# define YY_REDUCE_PRINT(Rule)
+#endif /* !YYDEBUG */
+
+
+/* YYINITDEPTH -- initial size of the parser's stacks. */
+#ifndef YYINITDEPTH
+# define YYINITDEPTH 200
+#endif
+
+/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only
+ if the built-in stack extension method is used).
+
+ Do not make this value too large; the results are undefined if
+ YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH)
+ evaluated with infinite-precision integer arithmetic. */
+
+#ifndef YYMAXDEPTH
+# define YYMAXDEPTH 10000
+#endif
+
+
+#if YYERROR_VERBOSE
+
+# ifndef yystrlen
+# if defined __GLIBC__ && defined _STRING_H
+# define yystrlen strlen
+# else
+/* Return the length of YYSTR. */
+static YYSIZE_T
+yystrlen (const char *yystr)
+{
+ YYSIZE_T yylen;
+ for (yylen = 0; yystr[yylen]; yylen++)
+ continue;
+ return yylen;
+}
+# endif
+# endif
+
+# ifndef yystpcpy
+# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE
+# define yystpcpy stpcpy
+# else
+/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in
+ YYDEST. */
+static char *
+yystpcpy (char *yydest, const char *yysrc)
+{
+ char *yyd = yydest;
+ const char *yys = yysrc;
+
+ while ((*yyd++ = *yys++) != '\0')
+ continue;
+
+ return yyd - 1;
+}
+# endif
+# endif
+
+# ifndef yytnamerr
+/* Copy to YYRES the contents of YYSTR after stripping away unnecessary
+ quotes and backslashes, so that it's suitable for yyerror. The
+ heuristic is that double-quoting is unnecessary unless the string
+ contains an apostrophe, a comma, or backslash (other than
+ backslash-backslash). YYSTR is taken from yytname. If YYRES is
+ null, do not copy; instead, return the length of what the result
+ would have been. */
+static YYSIZE_T
+yytnamerr (char *yyres, const char *yystr)
+{
+ if (*yystr == '"')
+ {
+ YYSIZE_T yyn = 0;
+ char const *yyp = yystr;
+
+ for (;;)
+ switch (*++yyp)
+ {
+ case '\'':
+ case ',':
+ goto do_not_strip_quotes;
+
+ case '\\':
+ if (*++yyp != '\\')
+ goto do_not_strip_quotes;
+ /* Fall through. */
+ default:
+ if (yyres)
+ yyres[yyn] = *yyp;
+ yyn++;
+ break;
+
+ case '"':
+ if (yyres)
+ yyres[yyn] = '\0';
+ return yyn;
+ }
+ do_not_strip_quotes: ;
+ }
+
+ if (! yyres)
+ return yystrlen (yystr);
+
+ return yystpcpy (yyres, yystr) - yyres;
+}
+# endif
+
+/* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message
+ about the unexpected token YYTOKEN for the state stack whose top is
+ YYSSP.
+
+ Return 0 if *YYMSG was successfully written. Return 1 if *YYMSG is
+ not large enough to hold the message. In that case, also set
+ *YYMSG_ALLOC to the required number of bytes. Return 2 if the
+ required number of bytes is too large to store. */
+static int
+yysyntax_error (YYSIZE_T *yymsg_alloc, char **yymsg,
+ yytype_int16 *yyssp, int yytoken)
+{
+ YYSIZE_T yysize0 = yytnamerr (YY_NULLPTR, yytname[yytoken]);
+ YYSIZE_T yysize = yysize0;
+ enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 };
+ /* Internationalized format string. */
+ const char *yyformat = YY_NULLPTR;
+ /* Arguments of yyformat. */
+ char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM];
+ /* Number of reported tokens (one for the "unexpected", one per
+ "expected"). */
+ int yycount = 0;
+
+ /* There are many possibilities here to consider:
+ - If this state is a consistent state with a default action, then
+ the only way this function was invoked is if the default action
+ is an error action. In that case, don't check for expected
+ tokens because there are none.
+ - The only way there can be no lookahead present (in yychar) is if
+ this state is a consistent state with a default action. Thus,
+ detecting the absence of a lookahead is sufficient to determine
+ that there is no unexpected or expected token to report. In that
+ case, just report a simple "syntax error".
+ - Don't assume there isn't a lookahead just because this state is a
+ consistent state with a default action. There might have been a
+ previous inconsistent state, consistent state with a non-default
+ action, or user semantic action that manipulated yychar.
+ - Of course, the expected token list depends on states to have
+ correct lookahead information, and it depends on the parser not
+ to perform extra reductions after fetching a lookahead from the
+ scanner and before detecting a syntax error. Thus, state merging
+ (from LALR or IELR) and default reductions corrupt the expected
+ token list. However, the list is correct for canonical LR with
+ one exception: it will still contain any token that will not be
+ accepted due to an error action in a later state.
+ */
+ if (yytoken != YYEMPTY)
+ {
+ int yyn = yypact[*yyssp];
+ yyarg[yycount++] = yytname[yytoken];
+ if (!yypact_value_is_default (yyn))
+ {
+ /* Start YYX at -YYN if negative to avoid negative indexes in
+ YYCHECK. In other words, skip the first -YYN actions for
+ this state because they are default actions. */
+ int yyxbegin = yyn < 0 ? -yyn : 0;
+ /* Stay within bounds of both yycheck and yytname. */
+ int yychecklim = YYLAST - yyn + 1;
+ int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS;
+ int yyx;
+
+ for (yyx = yyxbegin; yyx < yyxend; ++yyx)
+ if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR
+ && !yytable_value_is_error (yytable[yyx + yyn]))
+ {
+ if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM)
+ {
+ yycount = 1;
+ yysize = yysize0;
+ break;
+ }
+ yyarg[yycount++] = yytname[yyx];
+ {
+ YYSIZE_T yysize1 = yysize + yytnamerr (YY_NULLPTR, yytname[yyx]);
+ if (! (yysize <= yysize1
+ && yysize1 <= YYSTACK_ALLOC_MAXIMUM))
+ return 2;
+ yysize = yysize1;
+ }
+ }
+ }
+ }
+
+ switch (yycount)
+ {
+# define YYCASE_(N, S) \
+ case N: \
+ yyformat = S; \
+ break
+ YYCASE_(0, YY_("syntax error"));
+ YYCASE_(1, YY_("syntax error, unexpected %s"));
+ YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s"));
+ YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s"));
+ YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s"));
+ YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"));
+# undef YYCASE_
+ }
+
+ {
+ YYSIZE_T yysize1 = yysize + yystrlen (yyformat);
+ if (! (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM))
+ return 2;
+ yysize = yysize1;
+ }
+
+ if (*yymsg_alloc < yysize)
+ {
+ *yymsg_alloc = 2 * yysize;
+ if (! (yysize <= *yymsg_alloc
+ && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM))
+ *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM;
+ return 1;
+ }
+
+ /* Avoid sprintf, as that infringes on the user's name space.
+ Don't have undefined behavior even if the translation
+ produced a string with the wrong number of "%s"s. */
+ {
+ char *yyp = *yymsg;
+ int yyi = 0;
+ while ((*yyp = *yyformat) != '\0')
+ if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount)
+ {
+ yyp += yytnamerr (yyp, yyarg[yyi++]);
+ yyformat += 2;
+ }
+ else
+ {
+ yyp++;
+ yyformat++;
+ }
+ }
+ return 0;
+}
+#endif /* YYERROR_VERBOSE */
+
+/*-----------------------------------------------.
+| Release the memory associated to this symbol. |
+`-----------------------------------------------*/
+
+static void
+yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep)
+{
+ YYUSE (yyvaluep);
+ if (!yymsg)
+ yymsg = "Deleting";
+ YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp);
+
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ YYUSE (yytype);
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+}
+
+
+
+
+/* The lookahead symbol. */
+int yychar;
+
+/* The semantic value of the lookahead symbol. */
+YYSTYPE yylval;
+/* Number of syntax errors so far. */
+int yynerrs;
+
+
+/*----------.
+| yyparse. |
+`----------*/
+
+int
+yyparse (void)
+{
+ int yystate;
+ /* Number of tokens to shift before error messages enabled. */
+ int yyerrstatus;
+
+ /* The stacks and their tools:
+ 'yyss': related to states.
+ 'yyvs': related to semantic values.
+
+ Refer to the stacks through separate pointers, to allow yyoverflow
+ to reallocate them elsewhere. */
+
+ /* The state stack. */
+ yytype_int16 yyssa[YYINITDEPTH];
+ yytype_int16 *yyss;
+ yytype_int16 *yyssp;
+
+ /* The semantic value stack. */
+ YYSTYPE yyvsa[YYINITDEPTH];
+ YYSTYPE *yyvs;
+ YYSTYPE *yyvsp;
+
+ YYSIZE_T yystacksize;
+
+ int yyn;
+ int yyresult;
+ /* Lookahead token as an internal (translated) token number. */
+ int yytoken = 0;
+ /* The variables used to return semantic value and location from the
+ action routines. */
+ YYSTYPE yyval;
+
+#if YYERROR_VERBOSE
+ /* Buffer for error messages, and its allocated size. */
+ char yymsgbuf[128];
+ char *yymsg = yymsgbuf;
+ YYSIZE_T yymsg_alloc = sizeof yymsgbuf;
+#endif
+
+#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N))
+
+ /* The number of symbols on the RHS of the reduced rule.
+ Keep to zero when no symbol should be popped. */
+ int yylen = 0;
+
+ yyssp = yyss = yyssa;
+ yyvsp = yyvs = yyvsa;
+ yystacksize = YYINITDEPTH;
+
+ YYDPRINTF ((stderr, "Starting parse\n"));
+
+ yystate = 0;
+ yyerrstatus = 0;
+ yynerrs = 0;
+ yychar = YYEMPTY; /* Cause a token to be read. */
+ goto yysetstate;
+
+/*------------------------------------------------------------.
+| yynewstate -- Push a new state, which is found in yystate. |
+`------------------------------------------------------------*/
+ yynewstate:
+ /* In all cases, when you get here, the value and location stacks
+ have just been pushed. So pushing a state here evens the stacks. */
+ yyssp++;
+
+ yysetstate:
+ *yyssp = yystate;
+
+ if (yyss + yystacksize - 1 <= yyssp)
+ {
+ /* Get the current used size of the three stacks, in elements. */
+ YYSIZE_T yysize = yyssp - yyss + 1;
+
+#ifdef yyoverflow
+ {
+ /* Give user a chance to reallocate the stack. Use copies of
+ these so that the &'s don't force the real ones into
+ memory. */
+ YYSTYPE *yyvs1 = yyvs;
+ yytype_int16 *yyss1 = yyss;
+
+ /* Each stack pointer address is followed by the size of the
+ data in use in that stack, in bytes. This used to be a
+ conditional around just the two extra args, but that might
+ be undefined if yyoverflow is a macro. */
+ yyoverflow (YY_("memory exhausted"),
+ &yyss1, yysize * sizeof (*yyssp),
+ &yyvs1, yysize * sizeof (*yyvsp),
+ &yystacksize);
+
+ yyss = yyss1;
+ yyvs = yyvs1;
+ }
+#else /* no yyoverflow */
+# ifndef YYSTACK_RELOCATE
+ goto yyexhaustedlab;
+# else
+ /* Extend the stack our own way. */
+ if (YYMAXDEPTH <= yystacksize)
+ goto yyexhaustedlab;
+ yystacksize *= 2;
+ if (YYMAXDEPTH < yystacksize)
+ yystacksize = YYMAXDEPTH;
+
+ {
+ yytype_int16 *yyss1 = yyss;
+ union yyalloc *yyptr =
+ (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize));
+ if (! yyptr)
+ goto yyexhaustedlab;
+ YYSTACK_RELOCATE (yyss_alloc, yyss);
+ YYSTACK_RELOCATE (yyvs_alloc, yyvs);
+# undef YYSTACK_RELOCATE
+ if (yyss1 != yyssa)
+ YYSTACK_FREE (yyss1);
+ }
+# endif
+#endif /* no yyoverflow */
+
+ yyssp = yyss + yysize - 1;
+ yyvsp = yyvs + yysize - 1;
+
+ YYDPRINTF ((stderr, "Stack size increased to %lu\n",
+ (unsigned long int) yystacksize));
+
+ if (yyss + yystacksize - 1 <= yyssp)
+ YYABORT;
+ }
+
+ YYDPRINTF ((stderr, "Entering state %d\n", yystate));
+
+ if (yystate == YYFINAL)
+ YYACCEPT;
+
+ goto yybackup;
+
+/*-----------.
+| yybackup. |
+`-----------*/
+yybackup:
+
+ /* Do appropriate processing given the current state. Read a
+ lookahead token if we need one and don't already have one. */
+
+ /* First try to decide what to do without reference to lookahead token. */
+ yyn = yypact[yystate];
+ if (yypact_value_is_default (yyn))
+ goto yydefault;
+
+ /* Not known => get a lookahead token if don't already have one. */
+
+ /* YYCHAR is either YYEMPTY or YYEOF or a valid lookahead symbol. */
+ if (yychar == YYEMPTY)
+ {
+ YYDPRINTF ((stderr, "Reading a token: "));
+ yychar = yylex ();
+ }
+
+ if (yychar <= YYEOF)
+ {
+ yychar = yytoken = YYEOF;
+ YYDPRINTF ((stderr, "Now at end of input.\n"));
+ }
+ else
+ {
+ yytoken = YYTRANSLATE (yychar);
+ YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc);
+ }
+
+ /* If the proper action on seeing token YYTOKEN is to reduce or to
+ detect an error, take that action. */
+ yyn += yytoken;
+ if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken)
+ goto yydefault;
+ yyn = yytable[yyn];
+ if (yyn <= 0)
+ {
+ if (yytable_value_is_error (yyn))
+ goto yyerrlab;
+ yyn = -yyn;
+ goto yyreduce;
+ }
+
+ /* Count tokens shifted since error; after three, turn off error
+ status. */
+ if (yyerrstatus)
+ yyerrstatus--;
+
+ /* Shift the lookahead token. */
+ YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc);
+
+ /* Discard the shifted token. */
+ yychar = YYEMPTY;
+
+ yystate = yyn;
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ *++yyvsp = yylval;
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+ goto yynewstate;
+
+
+/*-----------------------------------------------------------.
+| yydefault -- do the default action for the current state. |
+`-----------------------------------------------------------*/
+yydefault:
+ yyn = yydefact[yystate];
+ if (yyn == 0)
+ goto yyerrlab;
+ goto yyreduce;
+
+
+/*-----------------------------.
+| yyreduce -- Do a reduction. |
+`-----------------------------*/
+yyreduce:
+ /* yyn is the number of a rule to reduce with. */
+ yylen = yyr2[yyn];
+
+ /* If YYLEN is nonzero, implement the default value of the action:
+ '$$ = $1'.
+
+ Otherwise, the following line sets YYVAL to garbage.
+ This behavior is undocumented and Bison
+ users should not rely upon it. Assigning to YYVAL
+ unconditionally makes the parser a bit smaller, and it avoids a
+ GCC warning that YYVAL may be used uninitialized. */
+ yyval = yyvsp[1-yylen];
+
+
+ YY_REDUCE_PRINT (yyn);
+ switch (yyn)
+ {
+ case 4:
+#line 132 "parsedate.y" /* yacc.c:1646 */
+ {
+ yyHaveTime++;
+#ifdef lint
+ /* I am compulsive about lint natterings... */
+ if (yyHaveTime == -1) {
+ YYERROR;
+ }
+#endif /* lint */
+ }
+#line 1366 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 5:
+#line 141 "parsedate.y" /* yacc.c:1646 */
+ {
+ yyHaveTime++;
+ yyTimezone = (yyvsp[0].Number);
+ }
+#line 1375 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 6:
+#line 145 "parsedate.y" /* yacc.c:1646 */
+ {
+ yyHaveDate++;
+ }
+#line 1383 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 7:
+#line 148 "parsedate.y" /* yacc.c:1646 */
+ {
+ yyHaveRel = 1;
+ }
+#line 1391 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 8:
+#line 153 "parsedate.y" /* yacc.c:1646 */
+ {
+ if ((yyvsp[-1].Number) < 100) {
+ yyHour = (yyvsp[-1].Number);
+ yyMinutes = 0;
+ }
+ else {
+ yyHour = (yyvsp[-1].Number) / 100;
+ yyMinutes = (yyvsp[-1].Number) % 100;
+ }
+ yySeconds = 0;
+ yyMeridian = (yyvsp[0].Meridian);
+ }
+#line 1408 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 9:
+#line 165 "parsedate.y" /* yacc.c:1646 */
+ {
+ yyHour = (yyvsp[-3].Number);
+ yyMinutes = (yyvsp[-1].Number);
+ yySeconds = 0;
+ yyMeridian = (yyvsp[0].Meridian);
+ }
+#line 1419 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 10:
+#line 171 "parsedate.y" /* yacc.c:1646 */
+ {
+ yyHour = (yyvsp[-3].Number);
+ yyMinutes = (yyvsp[-1].Number);
+ yyTimezone = (yyvsp[0].Number);
+ yyMeridian = MER24;
+ yyDSTmode = DSToff;
+ }
+#line 1431 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 11:
+#line 178 "parsedate.y" /* yacc.c:1646 */
+ {
+ yyHour = (yyvsp[-5].Number);
+ yyMinutes = (yyvsp[-3].Number);
+ yySeconds = (yyvsp[-1].Number);
+ yyMeridian = (yyvsp[0].Meridian);
+ }
+#line 1442 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 12:
+#line 184 "parsedate.y" /* yacc.c:1646 */
+ {
+ yyHour = (yyvsp[-5].Number);
+ yyMinutes = (yyvsp[-3].Number);
+ yySeconds = (yyvsp[-1].Number);
+ yyTimezone = (yyvsp[0].Number);
+ yyMeridian = MER24;
+ yyDSTmode = DSToff;
+ }
+#line 1455 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 13:
+#line 194 "parsedate.y" /* yacc.c:1646 */
+ {
+ (yyval.Number) = (yyvsp[0].Number);
+ yyDSTmode = DSToff;
+ }
+#line 1464 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 14:
+#line 198 "parsedate.y" /* yacc.c:1646 */
+ {
+ (yyval.Number) = (yyvsp[0].Number);
+ yyDSTmode = DSTon;
+ }
+#line 1473 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 15:
+#line 202 "parsedate.y" /* yacc.c:1646 */
+ {
+ /* Only allow "GMT+300" and "GMT-0800" */
+ if ((yyvsp[-1].Number) != 0) {
+ YYABORT;
+ }
+ (yyval.Number) = (yyvsp[0].Number);
+ yyDSTmode = DSToff;
+ }
+#line 1486 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 16:
+#line 210 "parsedate.y" /* yacc.c:1646 */
+ {
+ (yyval.Number) = (yyvsp[0].Number);
+ yyDSTmode = DSToff;
+ }
+#line 1495 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 17:
+#line 216 "parsedate.y" /* yacc.c:1646 */
+ {
+ int i;
+
+ /* Unix and GMT and numeric timezones -- a little confusing. */
+ if ((yyvsp[0].Number) < 0) {
+ /* Don't work with negative modulus. */
+ (yyvsp[0].Number) = -(yyvsp[0].Number);
+ if ((yyvsp[0].Number) > 9999 || (i = (yyvsp[0].Number) % 100) >= 60) {
+ YYABORT;
+ }
+ (yyval.Number) = ((yyvsp[0].Number) / 100) * 60 + i;
+ }
+ else {
+ if ((yyvsp[0].Number) > 9999 || (i = (yyvsp[0].Number) % 100) >= 60) {
+ YYABORT;
+ }
+ (yyval.Number) = -(((yyvsp[0].Number) / 100) * 60 + i);
+ }
+ }
+#line 1519 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 18:
+#line 237 "parsedate.y" /* yacc.c:1646 */
+ {
+ yyMonth = (yyvsp[-2].Number);
+ yyDay = (yyvsp[0].Number);
+ }
+#line 1528 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 19:
+#line 241 "parsedate.y" /* yacc.c:1646 */
+ {
+ if ((yyvsp[-4].Number) > 100) {
+ yyYear = (yyvsp[-4].Number);
+ yyMonth = (yyvsp[-2].Number);
+ yyDay = (yyvsp[0].Number);
+ }
+ else {
+ yyMonth = (yyvsp[-4].Number);
+ yyDay = (yyvsp[-2].Number);
+ yyYear = (yyvsp[0].Number);
+ }
+ }
+#line 1545 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 20:
+#line 253 "parsedate.y" /* yacc.c:1646 */
+ {
+ yyMonth = (yyvsp[-1].Number);
+ yyDay = (yyvsp[0].Number);
+ }
+#line 1554 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 21:
+#line 257 "parsedate.y" /* yacc.c:1646 */
+ {
+ yyMonth = (yyvsp[-3].Number);
+ yyDay = (yyvsp[-2].Number);
+ yyYear = (yyvsp[0].Number);
+ }
+#line 1564 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 22:
+#line 262 "parsedate.y" /* yacc.c:1646 */
+ {
+ yyDay = (yyvsp[-1].Number);
+ yyMonth = (yyvsp[0].Number);
+ }
+#line 1573 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 23:
+#line 266 "parsedate.y" /* yacc.c:1646 */
+ {
+ yyDay = (yyvsp[-2].Number);
+ yyMonth = (yyvsp[-1].Number);
+ yyYear = (yyvsp[0].Number);
+ }
+#line 1583 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 24:
+#line 271 "parsedate.y" /* yacc.c:1646 */
+ {
+ yyDay = (yyvsp[-2].Number);
+ yyMonth = (yyvsp[-1].Number);
+ yyYear = (yyvsp[0].Number);
+ }
+#line 1593 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 25:
+#line 278 "parsedate.y" /* yacc.c:1646 */
+ {
+ yyRelSeconds += (yyvsp[-1].Number) * (yyvsp[0].Number);
+ }
+#line 1601 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 26:
+#line 281 "parsedate.y" /* yacc.c:1646 */
+ {
+ yyRelSeconds += (yyvsp[-1].Number) * (yyvsp[0].Number);
+ }
+#line 1609 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 27:
+#line 284 "parsedate.y" /* yacc.c:1646 */
+ {
+ yyRelMonth += (yyvsp[-1].Number) * (yyvsp[0].Number);
+ }
+#line 1617 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 28:
+#line 287 "parsedate.y" /* yacc.c:1646 */
+ {
+ yyRelMonth += (yyvsp[-1].Number) * (yyvsp[0].Number);
+ }
+#line 1625 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 29:
+#line 292 "parsedate.y" /* yacc.c:1646 */
+ {
+ (yyval.Meridian) = MER24;
+ }
+#line 1633 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+ case 30:
+#line 295 "parsedate.y" /* yacc.c:1646 */
+ {
+ (yyval.Meridian) = (yyvsp[0].Meridian);
+ }
+#line 1641 "y.tab.c" /* yacc.c:1646 */
+ break;
+
+
+#line 1645 "y.tab.c" /* yacc.c:1646 */
+ default: break;
+ }
+ /* User semantic actions sometimes alter yychar, and that requires
+ that yytoken be updated with the new translation. We take the
+ approach of translating immediately before every use of yytoken.
+ One alternative is translating here after every semantic action,
+ but that translation would be missed if the semantic action invokes
+ YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or
+ if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an
+ incorrect destructor might then be invoked immediately. In the
+ case of YYERROR or YYBACKUP, subsequent parser actions might lead
+ to an incorrect destructor call or verbose syntax error message
+ before the lookahead is translated. */
+ YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc);
+
+ YYPOPSTACK (yylen);
+ yylen = 0;
+ YY_STACK_PRINT (yyss, yyssp);
+
+ *++yyvsp = yyval;
+
+ /* Now 'shift' the result of the reduction. Determine what state
+ that goes to, based on the state we popped back to and the rule
+ number reduced by. */
+
+ yyn = yyr1[yyn];
+
+ yystate = yypgoto[yyn - YYNTOKENS] + *yyssp;
+ if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp)
+ yystate = yytable[yystate];
+ else
+ yystate = yydefgoto[yyn - YYNTOKENS];
+
+ goto yynewstate;
+
+
+/*--------------------------------------.
+| yyerrlab -- here on detecting error. |
+`--------------------------------------*/
+yyerrlab:
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = yychar == YYEMPTY ? YYEMPTY : YYTRANSLATE (yychar);
+
+ /* If not already recovering from an error, report this error. */
+ if (!yyerrstatus)
+ {
+ ++yynerrs;
+#if ! YYERROR_VERBOSE
+ yyerror (YY_("syntax error"));
+#else
+# define YYSYNTAX_ERROR yysyntax_error (&yymsg_alloc, &yymsg, \
+ yyssp, yytoken)
+ {
+ char const *yymsgp = YY_("syntax error");
+ int yysyntax_error_status;
+ yysyntax_error_status = YYSYNTAX_ERROR;
+ if (yysyntax_error_status == 0)
+ yymsgp = yymsg;
+ else if (yysyntax_error_status == 1)
+ {
+ if (yymsg != yymsgbuf)
+ YYSTACK_FREE (yymsg);
+ yymsg = (char *) YYSTACK_ALLOC (yymsg_alloc);
+ if (!yymsg)
+ {
+ yymsg = yymsgbuf;
+ yymsg_alloc = sizeof yymsgbuf;
+ yysyntax_error_status = 2;
+ }
+ else
+ {
+ yysyntax_error_status = YYSYNTAX_ERROR;
+ yymsgp = yymsg;
+ }
+ }
+ yyerror (yymsgp);
+ if (yysyntax_error_status == 2)
+ goto yyexhaustedlab;
+ }
+# undef YYSYNTAX_ERROR
+#endif
+ }
+
+
+
+ if (yyerrstatus == 3)
+ {
+ /* If just tried and failed to reuse lookahead token after an
+ error, discard it. */
+
+ if (yychar <= YYEOF)
+ {
+ /* Return failure if at end of input. */
+ if (yychar == YYEOF)
+ YYABORT;
+ }
+ else
+ {
+ yydestruct ("Error: discarding",
+ yytoken, &yylval);
+ yychar = YYEMPTY;
+ }
+ }
+
+ /* Else will try to reuse lookahead token after shifting the error
+ token. */
+ goto yyerrlab1;
+
+
+/*---------------------------------------------------.
+| yyerrorlab -- error raised explicitly by YYERROR. |
+`---------------------------------------------------*/
+yyerrorlab:
+
+ /* Pacify compilers like GCC when the user code never invokes
+ YYERROR and the label yyerrorlab therefore never appears in user
+ code. */
+ if (/*CONSTCOND*/ 0)
+ goto yyerrorlab;
+
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYERROR. */
+ YYPOPSTACK (yylen);
+ yylen = 0;
+ YY_STACK_PRINT (yyss, yyssp);
+ yystate = *yyssp;
+ goto yyerrlab1;
+
+
+/*-------------------------------------------------------------.
+| yyerrlab1 -- common code for both syntax error and YYERROR. |
+`-------------------------------------------------------------*/
+yyerrlab1:
+ yyerrstatus = 3; /* Each real token shifted decrements this. */
+
+ for (;;)
+ {
+ yyn = yypact[yystate];
+ if (!yypact_value_is_default (yyn))
+ {
+ yyn += YYTERROR;
+ if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR)
+ {
+ yyn = yytable[yyn];
+ if (0 < yyn)
+ break;
+ }
+ }
+
+ /* Pop the current state because it cannot handle the error token. */
+ if (yyssp == yyss)
+ YYABORT;
+
+
+ yydestruct ("Error: popping",
+ yystos[yystate], yyvsp);
+ YYPOPSTACK (1);
+ yystate = *yyssp;
+ YY_STACK_PRINT (yyss, yyssp);
+ }
+
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ *++yyvsp = yylval;
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+
+ /* Shift the error token. */
+ YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp);
+
+ yystate = yyn;
+ goto yynewstate;
+
+
+/*-------------------------------------.
+| yyacceptlab -- YYACCEPT comes here. |
+`-------------------------------------*/
+yyacceptlab:
+ yyresult = 0;
+ goto yyreturn;
+
+/*-----------------------------------.
+| yyabortlab -- YYABORT comes here. |
+`-----------------------------------*/
+yyabortlab:
+ yyresult = 1;
+ goto yyreturn;
+
+#if !defined yyoverflow || YYERROR_VERBOSE
+/*-------------------------------------------------.
+| yyexhaustedlab -- memory exhaustion comes here. |
+`-------------------------------------------------*/
+yyexhaustedlab:
+ yyerror (YY_("memory exhausted"));
+ yyresult = 2;
+ /* Fall through. */
+#endif
+
+yyreturn:
+ if (yychar != YYEMPTY)
+ {
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = YYTRANSLATE (yychar);
+ yydestruct ("Cleanup: discarding lookahead",
+ yytoken, &yylval);
+ }
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYABORT or YYACCEPT. */
+ YYPOPSTACK (yylen);
+ YY_STACK_PRINT (yyss, yyssp);
+ while (yyssp != yyss)
+ {
+ yydestruct ("Cleanup: popping",
+ yystos[*yyssp], yyvsp);
+ YYPOPSTACK (1);
+ }
+#ifndef yyoverflow
+ if (yyss != yyssa)
+ YYSTACK_FREE (yyss);
+#endif
+#if YYERROR_VERBOSE
+ if (yymsg != yymsgbuf)
+ YYSTACK_FREE (yymsg);
+#endif
+ return yyresult;
+}
+#line 300 "parsedate.y" /* yacc.c:1906 */
+
+
+/* Month and day table. */
+static TABLE MonthDayTable[] = {
+ { "january", tMONTH, 1 },
+ { "february", tMONTH, 2 },
+ { "march", tMONTH, 3 },
+ { "april", tMONTH, 4 },
+ { "may", tMONTH, 5 },
+ { "june", tMONTH, 6 },
+ { "july", tMONTH, 7 },
+ { "august", tMONTH, 8 },
+ { "september", tMONTH, 9 },
+ { "october", tMONTH, 10 },
+ { "november", tMONTH, 11 },
+ { "december", tMONTH, 12 },
+ /* The value of the day isn't used... */
+ { "sunday", tDAY, 0 },
+ { "monday", tDAY, 0 },
+ { "tuesday", tDAY, 0 },
+ { "wednesday", tDAY, 0 },
+ { "thursday", tDAY, 0 },
+ { "friday", tDAY, 0 },
+ { "saturday", tDAY, 0 },
+};
+
+/* Time units table. */
+static TABLE UnitsTable[] = {
+ { "year", tMONTH_UNIT, 12 },
+ { "month", tMONTH_UNIT, 1 },
+ { "week", tSEC_UNIT, 7L * 24 * 60 * 60 },
+ { "day", tSEC_UNIT, 1L * 24 * 60 * 60 },
+ { "hour", tSEC_UNIT, 60 * 60 },
+ { "minute", tSEC_UNIT, 60 },
+ { "min", tSEC_UNIT, 60 },
+ { "second", tSEC_UNIT, 1 },
+ { "sec", tSEC_UNIT, 1 },
+};
+
+/* Timezone table. */
+static TABLE TimezoneTable[] = {
+ { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */
+ { "ut", tZONE, HOUR( 0) }, /* Universal */
+ { "utc", tZONE, HOUR( 0) }, /* Universal Coordinated */
+ { "cut", tZONE, HOUR( 0) }, /* Coordinated Universal */
+ { "z", tZONE, HOUR( 0) }, /* Greenwich Mean */
+ { "wet", tZONE, HOUR( 0) }, /* Western European */
+ { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */
+ { "nst", tZONE, HOUR(3)+30 }, /* Newfoundland Standard */
+ { "ndt", tDAYZONE, HOUR(3)+30 }, /* Newfoundland Daylight */
+ { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */
+ { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */
+ { "est", tZONE, HOUR( 5) }, /* Eastern Standard */
+ { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */
+ { "cst", tZONE, HOUR( 6) }, /* Central Standard */
+ { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */
+ { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */
+ { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */
+ { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */
+ { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */
+ { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */
+ { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */
+ { "akst", tZONE, HOUR( 9) }, /* Alaska Standard */
+ { "akdt", tDAYZONE, HOUR( 9) }, /* Alaska Daylight */
+ { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */
+ { "hast", tZONE, HOUR(10) }, /* Hawaii-Aleutian Standard */
+ { "hadt", tDAYZONE, HOUR(10) }, /* Hawaii-Aleutian Daylight */
+ { "ces", tDAYZONE, -HOUR(1) }, /* Central European Summer */
+ { "cest", tDAYZONE, -HOUR(1) }, /* Central European Summer */
+ { "mez", tZONE, -HOUR(1) }, /* Middle European */
+ { "mezt", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
+ { "cet", tZONE, -HOUR(1) }, /* Central European */
+ { "met", tZONE, -HOUR(1) }, /* Middle European */
+ { "eet", tZONE, -HOUR(2) }, /* Eastern Europe */
+ { "msk", tZONE, -HOUR(3) }, /* Moscow Winter */
+ { "msd", tDAYZONE, -HOUR(3) }, /* Moscow Summer */
+ { "wast", tZONE, -HOUR(8) }, /* West Australian Standard */
+ { "wadt", tDAYZONE, -HOUR(8) }, /* West Australian Daylight */
+ { "hkt", tZONE, -HOUR(8) }, /* Hong Kong */
+ { "cct", tZONE, -HOUR(8) }, /* China Coast */
+ { "jst", tZONE, -HOUR(9) }, /* Japan Standard */
+ { "kst", tZONE, -HOUR(9) }, /* Korean Standard */
+ { "kdt", tZONE, -HOUR(9) }, /* Korean Daylight */
+ { "cast", tZONE, -(HOUR(9)+30) }, /* Central Australian Standard */
+ { "cadt", tDAYZONE, -(HOUR(9)+30) }, /* Central Australian Daylight */
+ { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */
+ { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */
+ { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */
+ { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */
+
+ /* For completeness we include the following entries. */
+#if 0
+
+ /* Duplicate names. Either they conflict with a zone listed above
+ * (which is either more likely to be seen or just been in circulation
+ * longer), or they conflict with another zone in this section and
+ * we could not reasonably choose one over the other. */
+ { "fst", tZONE, HOUR( 2) }, /* Fernando De Noronha Standard */
+ { "fdt", tDAYZONE, HOUR( 2) }, /* Fernando De Noronha Daylight */
+ { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */
+ { "est", tZONE, HOUR( 3) }, /* Eastern Standard (Brazil) */
+ { "edt", tDAYZONE, HOUR( 3) }, /* Eastern Daylight (Brazil) */
+ { "wst", tZONE, HOUR( 4) }, /* Western Standard (Brazil) */
+ { "wdt", tDAYZONE, HOUR( 4) }, /* Western Daylight (Brazil) */
+ { "cst", tZONE, HOUR( 5) }, /* Chile Standard */
+ { "cdt", tDAYZONE, HOUR( 5) }, /* Chile Daylight */
+ { "ast", tZONE, HOUR( 5) }, /* Acre Standard */
+ { "adt", tDAYZONE, HOUR( 5) }, /* Acre Daylight */
+ { "cst", tZONE, HOUR( 5) }, /* Cuba Standard */
+ { "cdt", tDAYZONE, HOUR( 5) }, /* Cuba Daylight */
+ { "est", tZONE, HOUR( 6) }, /* Easter Island Standard */
+ { "edt", tDAYZONE, HOUR( 6) }, /* Easter Island Daylight */
+ { "sst", tZONE, HOUR(11) }, /* Samoa Standard */
+ { "ist", tZONE, -HOUR(2) }, /* Israel Standard */
+ { "idt", tDAYZONE, -HOUR(2) }, /* Israel Daylight */
+ { "idt", tDAYZONE, -(HOUR(3)+30) }, /* Iran Daylight */
+ { "ist", tZONE, -(HOUR(3)+30) }, /* Iran Standard */
+ { "cst", tZONE, -HOUR(8) }, /* China Standard */
+ { "cdt", tDAYZONE, -HOUR(8) }, /* China Daylight */
+ { "sst", tZONE, -HOUR(8) }, /* Singapore Standard */
+
+ /* Dubious (e.g., not in Olson's TIMEZONE package) or obsolete. */
+ { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */
+ { "wat", tZONE, -HOUR(1) }, /* West Africa */
+ { "at", tZONE, HOUR( 2) }, /* Azores */
+ { "gst", tZONE, -HOUR(10) }, /* Guam Standard */
+ { "nft", tZONE, HOUR(3)+30 }, /* Newfoundland */
+ { "idlw", tZONE, HOUR(12) }, /* International Date Line West */
+ { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */
+ { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
+ { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */
+ { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */
+ { "fwt", tZONE, -HOUR(1) }, /* French Winter */
+ { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */
+ { "bt", tZONE, -HOUR(3) }, /* Baghdad */
+ { "it", tZONE, -(HOUR(3)+30) }, /* Iran */
+ { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */
+ { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */
+ { "ist", tZONE, -(HOUR(5)+30) }, /* Indian Standard */
+ { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */
+ { "nst", tZONE, -HOUR(7) }, /* North Sumatra */
+ { "sst", tZONE, -HOUR(7) }, /* South Sumatra */
+ { "jt", tZONE, -(HOUR(7)+30) }, /* Java (3pm in Cronusland!) */
+ { "nzt", tZONE, -HOUR(12) }, /* New Zealand */
+ { "idle", tZONE, -HOUR(12) }, /* International Date Line East */
+ { "cat", tZONE, HOUR(10) }, /* -- expired 1967 */
+ { "nt", tZONE, HOUR(11) }, /* -- expired 1967 */
+ { "ahst", tZONE, HOUR(10) }, /* -- expired 1983 */
+ { "hdt", tDAYZONE, HOUR(10) }, /* -- expired 1986 */
+#endif /* 0 */
+};
+
+
+/* ARGSUSED */
+static void
+date_error(char *s)
+{
+ /* NOTREACHED */
+}
+
+
+static time_t
+ToSeconds(time_t Hours, time_t Minutes, time_t Seconds, MERIDIAN Meridian)
+{
+ if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 61)
+ return -1;
+ if (Meridian == MER24) {
+ if (Hours < 0 || Hours > 23)
+ return -1;
+ }
+ else {
+ if (Hours < 1 || Hours > 12)
+ return -1;
+ if (Hours == 12)
+ Hours = 0;
+ if (Meridian == MERpm)
+ Hours += 12;
+ }
+ return (Hours * 60L + Minutes) * 60L + Seconds;
+}
+
+
+static time_t
+Convert(time_t Month, time_t Day, time_t Year,
+ time_t Hours, time_t Minutes, time_t Seconds,
+ MERIDIAN Meridian, DSTMODE dst)
+{
+ static int DaysNormal[13] = {
+ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+ };
+ static int DaysLeap[13] = {
+ 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+ };
+ static int LeapYears[] = {
+ 1972, 1976, 1980, 1984, 1988, 1992, 1996,
+ 2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036
+ };
+ register int *yp;
+ register int *mp;
+ register time_t Julian;
+ register int i;
+ time_t tod;
+
+ if (Year < 0)
+ Year = -Year;
+ if (Year < 100)
+ Year += 1900;
+ if (Year < EPOCH)
+ Year += 100;
+ for (mp = DaysNormal, yp = LeapYears; yp < ENDOF(LeapYears); yp++)
+ if (Year == *yp) {
+ mp = DaysLeap;
+ break;
+ }
+ if (Year < EPOCH || Year > END_OF_TIME
+ || Month < 1 || Month > 12
+ /* NOSTRICT *//* conversion from long may lose accuracy */
+ || Day < 1 || Day > mp[(int)Month])
+ return -1;
+
+ Julian = Day - 1 + (Year - EPOCH) * 365;
+ for (yp = LeapYears; yp < ENDOF(LeapYears); yp++, Julian++)
+ if (Year <= *yp)
+ break;
+ for (i = 1; i < Month; i++)
+ Julian += *++mp;
+ Julian *= SECSPERDAY;
+ Julian += yyTimezone * 60L;
+ if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
+ return -1;
+ Julian += tod;
+ tod = Julian;
+ if (dst == DSTon || (dst == DSTmaybe && localtime(&tod)->tm_isdst))
+ Julian -= DST_OFFSET * 60L * 60L;
+ return Julian;
+}
+
+
+static time_t
+DSTcorrect(time_t Start, time_t Future)
+{
+ time_t StartDay;
+ time_t FutureDay;
+
+ StartDay = (localtime(&Start)->tm_hour + 1) % 24;
+ FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
+ return (Future - Start) + (StartDay - FutureDay) * DST_OFFSET * 60L * 60L;
+}
+
+
+static time_t
+RelativeMonth(time_t Start, time_t RelMonth)
+{
+ struct tm *tm;
+ time_t Month;
+ time_t Year;
+
+ tm = localtime(&Start);
+ Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
+ Year = Month / 12;
+ Month = Month % 12 + 1;
+ return DSTcorrect(Start,
+ Convert(Month, (time_t)tm->tm_mday, Year,
+ (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
+ MER24, DSTmaybe));
+}
+
+
+static int
+LookupWord(char *buff, register int length)
+{
+ register char *p;
+ register char *q;
+ register TABLE *tp;
+ register int c;
+
+ p = buff;
+ c = p[0];
+
+ /* See if we have an abbreviation for a month. */
+ if (length == 3 || (length == 4 && p[3] == '.'))
+ for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++) {
+ q = tp->name;
+ if (c == q[0] && p[1] == q[1] && p[2] == q[2]) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+ else
+ for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++)
+ if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ /* Try for a timezone. */
+ for (tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
+ if (c == tp->name[0] && p[1] == tp->name[1]
+ && strcmp(p, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ /* Try the units table. */
+ for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
+ if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ /* Strip off any plural and try the units table again. */
+ if (--length > 0 && p[length] == 's') {
+ p[length] = '\0';
+ for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
+ if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+ p[length] = 's';
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ p[length] = 's';
+ }
+ length++;
+
+ /* Drop out any periods. */
+ for (p = buff, q = (char*)buff; *q; q++)
+ if (*q != '.')
+ *p++ = *q;
+ *p = '\0';
+
+ /* Try the meridians. */
+ if (buff[1] == 'm' && buff[2] == '\0') {
+ if (buff[0] == 'a') {
+ yylval.Meridian = MERam;
+ return tMERIDIAN;
+ }
+ if (buff[0] == 'p') {
+ yylval.Meridian = MERpm;
+ return tMERIDIAN;
+ }
+ }
+
+ /* If we saw any periods, try the timezones again. */
+ if (p - buff != length) {
+ c = buff[0];
+ for (p = buff, tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
+ if (c == tp->name[0] && p[1] == tp->name[1]
+ && strcmp(p, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+
+ /* Unknown word -- assume GMT timezone. */
+ yylval.Number = 0;
+ return tZONE;
+}
+
+
+int
+date_lex(void)
+{
+ register char c;
+ register char *p;
+ char buff[20];
+ register int sign;
+ register int i;
+ register int nesting;
+
+ for ( ; ; ) {
+ /* Get first character after the whitespace. */
+ for ( ; ; ) {
+ while (isspace(*yyInput))
+ yyInput++;
+ c = *yyInput;
+
+ /* Ignore RFC 822 comments, typically time zone names. */
+ if (c != LPAREN)
+ break;
+ for (nesting = 1; (c = *++yyInput) != RPAREN || --nesting; )
+ if (c == LPAREN)
+ nesting++;
+ else if (!IS7BIT(c) || c == '\0' || c == '\r'
+ || (c == '\\' && ((c = *++yyInput) == '\0' || !IS7BIT(c))))
+ /* Lexical error: bad comment. */
+ return '?';
+ yyInput++;
+ }
+
+ /* A number? */
+ if (isdigit(c) || c == '-' || c == '+') {
+ if (c == '-' || c == '+') {
+ sign = c == '-' ? -1 : 1;
+ yyInput++;
+ if (!isdigit(*yyInput))
+ /* Skip the plus or minus sign. */
+ continue;
+ }
+ else
+ sign = 0;
+ for (i = 0; (c = *yyInput++) != '\0' && isdigit(c); )
+ i = 10 * i + c - '0';
+ yyInput--;
+ yylval.Number = sign < 0 ? -i : i;
+ return sign ? tSNUMBER : tUNUMBER;
+ }
+
+ /* A word? */
+ if (isalpha(c)) {
+ for (p = buff; (c = *yyInput++) == '.' || isalpha(c); )
+ if (p < &buff[sizeof buff - 1])
+ *p++ = isupper(c) ? tolower(c) : c;
+ *p = '\0';
+ yyInput--;
+ return LookupWord(buff, p - buff);
+ }
+
+ return *yyInput++;
+ }
+}
+
+
+time_t
+parsedate(const char *p)
+{
+ extern int date_parse(void);
+ time_t Start;
+
+ yyInput = p; /* well, its supposed to be const... */
+
+ yyYear = 0;
+ yyMonth = 0;
+ yyDay = 0;
+ yyTimezone = 0;
+ yyDSTmode = DSTmaybe;
+ yyHour = 0;
+ yyMinutes = 0;
+ yySeconds = 0;
+ yyMeridian = MER24;
+ yyRelSeconds = 0;
+ yyRelMonth = 0;
+ yyHaveDate = 0;
+ yyHaveRel = 0;
+ yyHaveTime = 0;
+
+ if (date_parse() || yyHaveTime > 1 || yyHaveDate > 1)
+ return -1;
+
+ if (yyHaveDate || yyHaveTime) {
+ Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
+ yyMeridian, yyDSTmode);
+ if (Start < 0)
+ return -1;
+ }
+ else
+ return -1;
+
+ Start += yyRelSeconds;
+ if (yyRelMonth)
+ Start += RelativeMonth(Start, yyRelMonth);
+
+ /* Have to do *something* with a legitimate -1 so it's distinguishable
+ * from the error return value. (Alternately could set errno on error.) */
+ return Start == -1 ? 0 : Start;
+}
+
+
+#ifdef TEST
+
+#if YYDEBUG
+extern int yydebug;
+#endif /* YYDEBUG */
+
+/* ARGSUSED */
+int
+main(int ac, char *av[])
+{
+ char buff[128];
+ time_t d;
+
+#if YYDEBUG
+ yydebug = 1;
+#endif /* YYDEBUG */
+
+ (void)printf("Enter date, or blank line to exit.\n\t> ");
+ for ( ; ; ) {
+ (void)printf("\t> ");
+ (void)fflush(stdout);
+ if (fgets(buff, sizeof buff, stdin) == NULL || buff[0] == '\n')
+ break;
+#if YYDEBUG
+ if (strcmp(buff, "yydebug") == 0) {
+ yydebug = !yydebug;
+ printf("yydebug = %s\n", yydebug ? "on" : "off");
+ continue;
+ }
+#endif /* YYDEBUG */
+ d = parsedate(buff, (TIMEINFO *)NULL);
+ if (d == -1)
+ (void)printf("Bad format - couldn't convert.\n");
+ else
+ (void)printf("%s", ctime(&d));
+ }
+
+ exit(0);
+ /* NOTREACHED */
+}
+#endif /* TEST */
--- /dev/null
+
+time_t parsedate(const char *);
--- /dev/null
+%{
+/* $Revision$
+**
+** Originally written by Steven M. Bellovin <smb@research.att.com> while
+** at the University of North Carolina at Chapel Hill. Later tweaked by
+** a couple of people on Usenet. Completely overhauled by Rich $alz
+** <rsalz@osf.org> and Jim Berets <jberets@bbn.com> in August, 1990.
+** Further revised (removed obsolete constructs and cleaned up timezone
+** names) in August, 1991, by Rich. Paul Eggert <eggert@twinsun.com>
+** helped in September, 1992. Art Cancro <ajc@citadel.org> cleaned
+** it up for ANSI C in December, 1999.
+**
+** This grammar has six shift/reduce conflicts.
+**
+** This code is in the public domain and has no copyright.
+*/
+/* SUPPRESS 530 *//* Empty body for statement */
+/* SUPPRESS 593 on yyerrlab *//* Label was not used */
+/* SUPPRESS 593 on yynewstate *//* Label was not used */
+/* SUPPRESS 595 on yypvt *//* Automatic variable may be used before set */
+
+#include "sysdep.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <ctype.h>
+#include <time.h>
+#if HAVE_STRING_H
+# if !STDC_HEADERS && HAVE_MEMORY_H
+# include <memory.h>
+# endif
+# include <string.h>
+#endif
+#if HAVE_STRINGS_H
+# include <strings.h>
+#endif
+
+#include "parsedate.h"
+
+int date_lex(void);
+
+#define yyparse date_parse
+#define yylex date_lex
+#define yyerror date_error
+
+
+ /* See the LeapYears table in Convert. */
+#define EPOCH 1970
+#define END_OF_TIME 2038
+ /* Constants for general time calculations. */
+#define DST_OFFSET 1
+#define SECSPERDAY (24L * 60L * 60L)
+ /* Readability for TABLE stuff. */
+#define HOUR(x) (x * 60)
+
+#define LPAREN '('
+#define RPAREN ')'
+#define IS7BIT(x) ((unsigned int)(x) < 0200)
+
+#define SIZEOF(array) ((int)(sizeof array / sizeof array[0]))
+#define ENDOF(array) (&array[SIZEOF(array)])
+
+
+/*
+** An entry in the lexical lookup table.
+*/
+typedef struct _TABLE {
+ char *name;
+ int type;
+ time_t value;
+} TABLE;
+
+/*
+** Daylight-savings mode: on, off, or not yet known.
+*/
+typedef enum _DSTMODE {
+ DSTon, DSToff, DSTmaybe
+} DSTMODE;
+
+/*
+** Meridian: am, pm, or 24-hour style.
+*/
+typedef enum _MERIDIAN {
+ MERam, MERpm, MER24
+} MERIDIAN;
+
+
+/*
+** Global variables. We could get rid of most of them by using a yacc
+** union, but this is more efficient. (This routine predates the
+** yacc %union construct.)
+*/
+static const char *yyInput;
+static DSTMODE yyDSTmode;
+static int yyHaveDate;
+static int yyHaveRel;
+static int yyHaveTime;
+static time_t yyTimezone;
+static time_t yyDay;
+static time_t yyHour;
+static time_t yyMinutes;
+static time_t yyMonth;
+static time_t yySeconds;
+static time_t yyYear;
+static MERIDIAN yyMeridian;
+static time_t yyRelMonth;
+static time_t yyRelSeconds;
+
+
+static void date_error(char *);
+%}
+
+%union {
+ time_t Number;
+ enum _MERIDIAN Meridian;
+}
+
+%token tDAY tDAYZONE tMERIDIAN tMONTH tMONTH_UNIT tSEC_UNIT tSNUMBER
+%token tUNUMBER tZONE
+
+%type <Number> tDAYZONE tMONTH tMONTH_UNIT tSEC_UNIT
+%type <Number> tSNUMBER tUNUMBER tZONE numzone zone
+%type <Meridian> tMERIDIAN o_merid
+
+%%
+
+spec : /* NULL */
+ | spec item
+ ;
+
+item : time {
+ yyHaveTime++;
+#ifdef lint
+ /* I am compulsive about lint natterings... */
+ if (yyHaveTime == -1) {
+ YYERROR;
+ }
+#endif /* lint */
+ }
+ | time zone {
+ yyHaveTime++;
+ yyTimezone = $2;
+ }
+ | date {
+ yyHaveDate++;
+ }
+ | rel {
+ yyHaveRel = 1;
+ }
+ ;
+
+time : tUNUMBER o_merid {
+ if ($1 < 100) {
+ yyHour = $1;
+ yyMinutes = 0;
+ }
+ else {
+ yyHour = $1 / 100;
+ yyMinutes = $1 % 100;
+ }
+ yySeconds = 0;
+ yyMeridian = $2;
+ }
+ | tUNUMBER ':' tUNUMBER o_merid {
+ yyHour = $1;
+ yyMinutes = $3;
+ yySeconds = 0;
+ yyMeridian = $4;
+ }
+ | tUNUMBER ':' tUNUMBER numzone {
+ yyHour = $1;
+ yyMinutes = $3;
+ yyTimezone = $4;
+ yyMeridian = MER24;
+ yyDSTmode = DSToff;
+ }
+ | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
+ yyHour = $1;
+ yyMinutes = $3;
+ yySeconds = $5;
+ yyMeridian = $6;
+ }
+ | tUNUMBER ':' tUNUMBER ':' tUNUMBER numzone {
+ yyHour = $1;
+ yyMinutes = $3;
+ yySeconds = $5;
+ yyTimezone = $6;
+ yyMeridian = MER24;
+ yyDSTmode = DSToff;
+ }
+ ;
+
+zone : tZONE {
+ $$ = $1;
+ yyDSTmode = DSToff;
+ }
+ | tDAYZONE {
+ $$ = $1;
+ yyDSTmode = DSTon;
+ }
+ | tZONE numzone {
+ /* Only allow "GMT+300" and "GMT-0800" */
+ if ($1 != 0) {
+ YYABORT;
+ }
+ $$ = $2;
+ yyDSTmode = DSToff;
+ }
+ | numzone {
+ $$ = $1;
+ yyDSTmode = DSToff;
+ }
+ ;
+
+numzone : tSNUMBER {
+ int i;
+
+ /* Unix and GMT and numeric timezones -- a little confusing. */
+ if ($1 < 0) {
+ /* Don't work with negative modulus. */
+ $1 = -$1;
+ if ($1 > 9999 || (i = $1 % 100) >= 60) {
+ YYABORT;
+ }
+ $$ = ($1 / 100) * 60 + i;
+ }
+ else {
+ if ($1 > 9999 || (i = $1 % 100) >= 60) {
+ YYABORT;
+ }
+ $$ = -(($1 / 100) * 60 + i);
+ }
+ }
+ ;
+
+date : tUNUMBER '/' tUNUMBER {
+ yyMonth = $1;
+ yyDay = $3;
+ }
+ | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
+ if ($1 > 100) {
+ yyYear = $1;
+ yyMonth = $3;
+ yyDay = $5;
+ }
+ else {
+ yyMonth = $1;
+ yyDay = $3;
+ yyYear = $5;
+ }
+ }
+ | tMONTH tUNUMBER {
+ yyMonth = $1;
+ yyDay = $2;
+ }
+ | tMONTH tUNUMBER ',' tUNUMBER {
+ yyMonth = $1;
+ yyDay = $2;
+ yyYear = $4;
+ }
+ | tUNUMBER tMONTH {
+ yyDay = $1;
+ yyMonth = $2;
+ }
+ | tUNUMBER tMONTH tUNUMBER {
+ yyDay = $1;
+ yyMonth = $2;
+ yyYear = $3;
+ }
+ | tDAY ',' tUNUMBER tMONTH tUNUMBER {
+ yyDay = $3;
+ yyMonth = $4;
+ yyYear = $5;
+ }
+ ;
+
+rel : tSNUMBER tSEC_UNIT {
+ yyRelSeconds += $1 * $2;
+ }
+ | tUNUMBER tSEC_UNIT {
+ yyRelSeconds += $1 * $2;
+ }
+ | tSNUMBER tMONTH_UNIT {
+ yyRelMonth += $1 * $2;
+ }
+ | tUNUMBER tMONTH_UNIT {
+ yyRelMonth += $1 * $2;
+ }
+ ;
+
+o_merid : /* NULL */ {
+ $$ = MER24;
+ }
+ | tMERIDIAN {
+ $$ = $1;
+ }
+ ;
+
+%%
+
+/* Month and day table. */
+static TABLE MonthDayTable[] = {
+ { "january", tMONTH, 1 },
+ { "february", tMONTH, 2 },
+ { "march", tMONTH, 3 },
+ { "april", tMONTH, 4 },
+ { "may", tMONTH, 5 },
+ { "june", tMONTH, 6 },
+ { "july", tMONTH, 7 },
+ { "august", tMONTH, 8 },
+ { "september", tMONTH, 9 },
+ { "october", tMONTH, 10 },
+ { "november", tMONTH, 11 },
+ { "december", tMONTH, 12 },
+ /* The value of the day isn't used... */
+ { "sunday", tDAY, 0 },
+ { "monday", tDAY, 0 },
+ { "tuesday", tDAY, 0 },
+ { "wednesday", tDAY, 0 },
+ { "thursday", tDAY, 0 },
+ { "friday", tDAY, 0 },
+ { "saturday", tDAY, 0 },
+};
+
+/* Time units table. */
+static TABLE UnitsTable[] = {
+ { "year", tMONTH_UNIT, 12 },
+ { "month", tMONTH_UNIT, 1 },
+ { "week", tSEC_UNIT, 7L * 24 * 60 * 60 },
+ { "day", tSEC_UNIT, 1L * 24 * 60 * 60 },
+ { "hour", tSEC_UNIT, 60 * 60 },
+ { "minute", tSEC_UNIT, 60 },
+ { "min", tSEC_UNIT, 60 },
+ { "second", tSEC_UNIT, 1 },
+ { "sec", tSEC_UNIT, 1 },
+};
+
+/* Timezone table. */
+static TABLE TimezoneTable[] = {
+ { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */
+ { "ut", tZONE, HOUR( 0) }, /* Universal */
+ { "utc", tZONE, HOUR( 0) }, /* Universal Coordinated */
+ { "cut", tZONE, HOUR( 0) }, /* Coordinated Universal */
+ { "z", tZONE, HOUR( 0) }, /* Greenwich Mean */
+ { "wet", tZONE, HOUR( 0) }, /* Western European */
+ { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */
+ { "nst", tZONE, HOUR(3)+30 }, /* Newfoundland Standard */
+ { "ndt", tDAYZONE, HOUR(3)+30 }, /* Newfoundland Daylight */
+ { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */
+ { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */
+ { "est", tZONE, HOUR( 5) }, /* Eastern Standard */
+ { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */
+ { "cst", tZONE, HOUR( 6) }, /* Central Standard */
+ { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */
+ { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */
+ { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */
+ { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */
+ { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */
+ { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */
+ { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */
+ { "akst", tZONE, HOUR( 9) }, /* Alaska Standard */
+ { "akdt", tDAYZONE, HOUR( 9) }, /* Alaska Daylight */
+ { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */
+ { "hast", tZONE, HOUR(10) }, /* Hawaii-Aleutian Standard */
+ { "hadt", tDAYZONE, HOUR(10) }, /* Hawaii-Aleutian Daylight */
+ { "ces", tDAYZONE, -HOUR(1) }, /* Central European Summer */
+ { "cest", tDAYZONE, -HOUR(1) }, /* Central European Summer */
+ { "mez", tZONE, -HOUR(1) }, /* Middle European */
+ { "mezt", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
+ { "cet", tZONE, -HOUR(1) }, /* Central European */
+ { "met", tZONE, -HOUR(1) }, /* Middle European */
+ { "eet", tZONE, -HOUR(2) }, /* Eastern Europe */
+ { "msk", tZONE, -HOUR(3) }, /* Moscow Winter */
+ { "msd", tDAYZONE, -HOUR(3) }, /* Moscow Summer */
+ { "wast", tZONE, -HOUR(8) }, /* West Australian Standard */
+ { "wadt", tDAYZONE, -HOUR(8) }, /* West Australian Daylight */
+ { "hkt", tZONE, -HOUR(8) }, /* Hong Kong */
+ { "cct", tZONE, -HOUR(8) }, /* China Coast */
+ { "jst", tZONE, -HOUR(9) }, /* Japan Standard */
+ { "kst", tZONE, -HOUR(9) }, /* Korean Standard */
+ { "kdt", tZONE, -HOUR(9) }, /* Korean Daylight */
+ { "cast", tZONE, -(HOUR(9)+30) }, /* Central Australian Standard */
+ { "cadt", tDAYZONE, -(HOUR(9)+30) }, /* Central Australian Daylight */
+ { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */
+ { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */
+ { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */
+ { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */
+
+ /* For completeness we include the following entries. */
+#if 0
+
+ /* Duplicate names. Either they conflict with a zone listed above
+ * (which is either more likely to be seen or just been in circulation
+ * longer), or they conflict with another zone in this section and
+ * we could not reasonably choose one over the other. */
+ { "fst", tZONE, HOUR( 2) }, /* Fernando De Noronha Standard */
+ { "fdt", tDAYZONE, HOUR( 2) }, /* Fernando De Noronha Daylight */
+ { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */
+ { "est", tZONE, HOUR( 3) }, /* Eastern Standard (Brazil) */
+ { "edt", tDAYZONE, HOUR( 3) }, /* Eastern Daylight (Brazil) */
+ { "wst", tZONE, HOUR( 4) }, /* Western Standard (Brazil) */
+ { "wdt", tDAYZONE, HOUR( 4) }, /* Western Daylight (Brazil) */
+ { "cst", tZONE, HOUR( 5) }, /* Chile Standard */
+ { "cdt", tDAYZONE, HOUR( 5) }, /* Chile Daylight */
+ { "ast", tZONE, HOUR( 5) }, /* Acre Standard */
+ { "adt", tDAYZONE, HOUR( 5) }, /* Acre Daylight */
+ { "cst", tZONE, HOUR( 5) }, /* Cuba Standard */
+ { "cdt", tDAYZONE, HOUR( 5) }, /* Cuba Daylight */
+ { "est", tZONE, HOUR( 6) }, /* Easter Island Standard */
+ { "edt", tDAYZONE, HOUR( 6) }, /* Easter Island Daylight */
+ { "sst", tZONE, HOUR(11) }, /* Samoa Standard */
+ { "ist", tZONE, -HOUR(2) }, /* Israel Standard */
+ { "idt", tDAYZONE, -HOUR(2) }, /* Israel Daylight */
+ { "idt", tDAYZONE, -(HOUR(3)+30) }, /* Iran Daylight */
+ { "ist", tZONE, -(HOUR(3)+30) }, /* Iran Standard */
+ { "cst", tZONE, -HOUR(8) }, /* China Standard */
+ { "cdt", tDAYZONE, -HOUR(8) }, /* China Daylight */
+ { "sst", tZONE, -HOUR(8) }, /* Singapore Standard */
+
+ /* Dubious (e.g., not in Olson's TIMEZONE package) or obsolete. */
+ { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */
+ { "wat", tZONE, -HOUR(1) }, /* West Africa */
+ { "at", tZONE, HOUR( 2) }, /* Azores */
+ { "gst", tZONE, -HOUR(10) }, /* Guam Standard */
+ { "nft", tZONE, HOUR(3)+30 }, /* Newfoundland */
+ { "idlw", tZONE, HOUR(12) }, /* International Date Line West */
+ { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */
+ { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
+ { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */
+ { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */
+ { "fwt", tZONE, -HOUR(1) }, /* French Winter */
+ { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */
+ { "bt", tZONE, -HOUR(3) }, /* Baghdad */
+ { "it", tZONE, -(HOUR(3)+30) }, /* Iran */
+ { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */
+ { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */
+ { "ist", tZONE, -(HOUR(5)+30) }, /* Indian Standard */
+ { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */
+ { "nst", tZONE, -HOUR(7) }, /* North Sumatra */
+ { "sst", tZONE, -HOUR(7) }, /* South Sumatra */
+ { "jt", tZONE, -(HOUR(7)+30) }, /* Java (3pm in Cronusland!) */
+ { "nzt", tZONE, -HOUR(12) }, /* New Zealand */
+ { "idle", tZONE, -HOUR(12) }, /* International Date Line East */
+ { "cat", tZONE, HOUR(10) }, /* -- expired 1967 */
+ { "nt", tZONE, HOUR(11) }, /* -- expired 1967 */
+ { "ahst", tZONE, HOUR(10) }, /* -- expired 1983 */
+ { "hdt", tDAYZONE, HOUR(10) }, /* -- expired 1986 */
+#endif /* 0 */
+};
+
+
+/* ARGSUSED */
+static void
+date_error(char *s)
+{
+ /* NOTREACHED */
+}
+
+
+static time_t
+ToSeconds(time_t Hours, time_t Minutes, time_t Seconds, MERIDIAN Meridian)
+{
+ if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 61)
+ return -1;
+ if (Meridian == MER24) {
+ if (Hours < 0 || Hours > 23)
+ return -1;
+ }
+ else {
+ if (Hours < 1 || Hours > 12)
+ return -1;
+ if (Hours == 12)
+ Hours = 0;
+ if (Meridian == MERpm)
+ Hours += 12;
+ }
+ return (Hours * 60L + Minutes) * 60L + Seconds;
+}
+
+
+static time_t
+Convert(time_t Month, time_t Day, time_t Year,
+ time_t Hours, time_t Minutes, time_t Seconds,
+ MERIDIAN Meridian, DSTMODE dst)
+{
+ static int DaysNormal[13] = {
+ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+ };
+ static int DaysLeap[13] = {
+ 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+ };
+ static int LeapYears[] = {
+ 1972, 1976, 1980, 1984, 1988, 1992, 1996,
+ 2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036
+ };
+ register int *yp;
+ register int *mp;
+ register time_t Julian;
+ register int i;
+ time_t tod;
+
+ if (Year < 0)
+ Year = -Year;
+ if (Year < 100)
+ Year += 1900;
+ if (Year < EPOCH)
+ Year += 100;
+ for (mp = DaysNormal, yp = LeapYears; yp < ENDOF(LeapYears); yp++)
+ if (Year == *yp) {
+ mp = DaysLeap;
+ break;
+ }
+ if (Year < EPOCH || Year > END_OF_TIME
+ || Month < 1 || Month > 12
+ /* NOSTRICT *//* conversion from long may lose accuracy */
+ || Day < 1 || Day > mp[(int)Month])
+ return -1;
+
+ Julian = Day - 1 + (Year - EPOCH) * 365;
+ for (yp = LeapYears; yp < ENDOF(LeapYears); yp++, Julian++)
+ if (Year <= *yp)
+ break;
+ for (i = 1; i < Month; i++)
+ Julian += *++mp;
+ Julian *= SECSPERDAY;
+ Julian += yyTimezone * 60L;
+ if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
+ return -1;
+ Julian += tod;
+ tod = Julian;
+ if (dst == DSTon || (dst == DSTmaybe && localtime(&tod)->tm_isdst))
+ Julian -= DST_OFFSET * 60L * 60L;
+ return Julian;
+}
+
+
+static time_t
+DSTcorrect(time_t Start, time_t Future)
+{
+ time_t StartDay;
+ time_t FutureDay;
+
+ StartDay = (localtime(&Start)->tm_hour + 1) % 24;
+ FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
+ return (Future - Start) + (StartDay - FutureDay) * DST_OFFSET * 60L * 60L;
+}
+
+
+static time_t
+RelativeMonth(time_t Start, time_t RelMonth)
+{
+ struct tm *tm;
+ time_t Month;
+ time_t Year;
+
+ tm = localtime(&Start);
+ Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
+ Year = Month / 12;
+ Month = Month % 12 + 1;
+ return DSTcorrect(Start,
+ Convert(Month, (time_t)tm->tm_mday, Year,
+ (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
+ MER24, DSTmaybe));
+}
+
+
+static int
+LookupWord(char *buff, register int length)
+{
+ register char *p;
+ register char *q;
+ register TABLE *tp;
+ register int c;
+
+ p = buff;
+ c = p[0];
+
+ /* See if we have an abbreviation for a month. */
+ if (length == 3 || (length == 4 && p[3] == '.'))
+ for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++) {
+ q = tp->name;
+ if (c == q[0] && p[1] == q[1] && p[2] == q[2]) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+ else
+ for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++)
+ if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ /* Try for a timezone. */
+ for (tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
+ if (c == tp->name[0] && p[1] == tp->name[1]
+ && strcmp(p, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ /* Try the units table. */
+ for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
+ if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ /* Strip off any plural and try the units table again. */
+ if (--length > 0 && p[length] == 's') {
+ p[length] = '\0';
+ for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
+ if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+ p[length] = 's';
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ p[length] = 's';
+ }
+ length++;
+
+ /* Drop out any periods. */
+ for (p = buff, q = (char*)buff; *q; q++)
+ if (*q != '.')
+ *p++ = *q;
+ *p = '\0';
+
+ /* Try the meridians. */
+ if (buff[1] == 'm' && buff[2] == '\0') {
+ if (buff[0] == 'a') {
+ yylval.Meridian = MERam;
+ return tMERIDIAN;
+ }
+ if (buff[0] == 'p') {
+ yylval.Meridian = MERpm;
+ return tMERIDIAN;
+ }
+ }
+
+ /* If we saw any periods, try the timezones again. */
+ if (p - buff != length) {
+ c = buff[0];
+ for (p = buff, tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
+ if (c == tp->name[0] && p[1] == tp->name[1]
+ && strcmp(p, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+
+ /* Unknown word -- assume GMT timezone. */
+ yylval.Number = 0;
+ return tZONE;
+}
+
+
+int
+date_lex(void)
+{
+ register char c;
+ register char *p;
+ char buff[20];
+ register int sign;
+ register int i;
+ register int nesting;
+
+ for ( ; ; ) {
+ /* Get first character after the whitespace. */
+ for ( ; ; ) {
+ while (isspace(*yyInput))
+ yyInput++;
+ c = *yyInput;
+
+ /* Ignore RFC 822 comments, typically time zone names. */
+ if (c != LPAREN)
+ break;
+ for (nesting = 1; (c = *++yyInput) != RPAREN || --nesting; )
+ if (c == LPAREN)
+ nesting++;
+ else if (!IS7BIT(c) || c == '\0' || c == '\r'
+ || (c == '\\' && ((c = *++yyInput) == '\0' || !IS7BIT(c))))
+ /* Lexical error: bad comment. */
+ return '?';
+ yyInput++;
+ }
+
+ /* A number? */
+ if (isdigit(c) || c == '-' || c == '+') {
+ if (c == '-' || c == '+') {
+ sign = c == '-' ? -1 : 1;
+ yyInput++;
+ if (!isdigit(*yyInput))
+ /* Skip the plus or minus sign. */
+ continue;
+ }
+ else
+ sign = 0;
+ for (i = 0; (c = *yyInput++) != '\0' && isdigit(c); )
+ i = 10 * i + c - '0';
+ yyInput--;
+ yylval.Number = sign < 0 ? -i : i;
+ return sign ? tSNUMBER : tUNUMBER;
+ }
+
+ /* A word? */
+ if (isalpha(c)) {
+ for (p = buff; (c = *yyInput++) == '.' || isalpha(c); )
+ if (p < &buff[sizeof buff - 1])
+ *p++ = isupper(c) ? tolower(c) : c;
+ *p = '\0';
+ yyInput--;
+ return LookupWord(buff, p - buff);
+ }
+
+ return *yyInput++;
+ }
+}
+
+
+time_t
+parsedate(const char *p)
+{
+ extern int date_parse(void);
+ time_t Start;
+
+ yyInput = p; /* well, its supposed to be const... */
+
+ yyYear = 0;
+ yyMonth = 0;
+ yyDay = 0;
+ yyTimezone = 0;
+ yyDSTmode = DSTmaybe;
+ yyHour = 0;
+ yyMinutes = 0;
+ yySeconds = 0;
+ yyMeridian = MER24;
+ yyRelSeconds = 0;
+ yyRelMonth = 0;
+ yyHaveDate = 0;
+ yyHaveRel = 0;
+ yyHaveTime = 0;
+
+ if (date_parse() || yyHaveTime > 1 || yyHaveDate > 1)
+ return -1;
+
+ if (yyHaveDate || yyHaveTime) {
+ Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
+ yyMeridian, yyDSTmode);
+ if (Start < 0)
+ return -1;
+ }
+ else
+ return -1;
+
+ Start += yyRelSeconds;
+ if (yyRelMonth)
+ Start += RelativeMonth(Start, yyRelMonth);
+
+ /* Have to do *something* with a legitimate -1 so it's distinguishable
+ * from the error return value. (Alternately could set errno on error.) */
+ return Start == -1 ? 0 : Start;
+}
+
+
+#ifdef TEST
+
+#if YYDEBUG
+extern int yydebug;
+#endif /* YYDEBUG */
+
+/* ARGSUSED */
+int
+main(int ac, char *av[])
+{
+ char buff[128];
+ time_t d;
+
+#if YYDEBUG
+ yydebug = 1;
+#endif /* YYDEBUG */
+
+ (void)printf("Enter date, or blank line to exit.\n\t> ");
+ for ( ; ; ) {
+ (void)printf("\t> ");
+ (void)fflush(stdout);
+ if (fgets(buff, sizeof buff, stdin) == NULL || buff[0] == '\n')
+ break;
+#if YYDEBUG
+ if (strcmp(buff, "yydebug") == 0) {
+ yydebug = !yydebug;
+ printf("yydebug = %s\n", yydebug ? "on" : "off");
+ continue;
+ }
+#endif /* YYDEBUG */
+ d = parsedate(buff, (TIMEINFO *)NULL);
+ if (d == -1)
+ (void)printf("Bad format - couldn't convert.\n");
+ else
+ (void)printf("%s", ctime(&d));
+ }
+
+ exit(0);
+ /* NOTREACHED */
+}
+#endif /* TEST */
--- /dev/null
+// Server functions which perform operations on room objects.
+//
+// Copyright (c) 1987-2022 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.
+
+#include <stdio.h>
+#include <libcitadel.h>
+
+#include "citserver.h"
+#include "ctdl_module.h"
+#include "config.h"
+#include "control.h"
+#include "user_ops.h"
+#include "room_ops.h"
+
+struct floor *floorcache[MAXFLOORS];
+
+// Determine whether the currently logged in session has permission to read
+// messages in the current room.
+int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
+ if ( (!(CC->logged_in))
+ && (!(CC->internal_pgm))
+ && (!CtdlGetConfigInt("c_guest_logins"))
+ ) {
+ return(om_not_logged_in);
+ }
+ return(om_ok);
+}
+
+
+// Check to see whether we have permission to post a message in the current
+// room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
+// returns 0 on success.
+int CtdlDoIHavePermissionToPostInThisRoom(
+ char *errmsgbuf,
+ size_t n,
+ PostType PostPublic,
+ int is_reply
+) {
+ int ra;
+
+ if (!(CC->logged_in) && (PostPublic == POST_LOGGED_IN)) {
+ snprintf(errmsgbuf, n, "Not logged in.");
+ return (ERROR + NOT_LOGGED_IN);
+ }
+ else if (PostPublic == CHECK_EXIST) {
+ return (0); // evaluate whether a recipient exists
+ }
+ else if (!(CC->logged_in)) {
+ if ((CC->room.QRflags & QR_READONLY)) {
+ snprintf(errmsgbuf, n, "Not logged in.");
+ return (ERROR + NOT_LOGGED_IN);
+ }
+ return (0);
+ }
+
+ if ((CC->user.axlevel < AxProbU) && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
+ snprintf(errmsgbuf, n, "Need to be validated to enter (except in %s> to sysop)", MAILROOM);
+ return (ERROR + HIGHER_ACCESS_REQUIRED);
+ }
+
+ CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
+
+ if (ra & UA_POSTALLOWED) {
+ strcpy(errmsgbuf, "OK to post or reply here");
+ return(0);
+ }
+
+ if ( (ra & UA_REPLYALLOWED) && (is_reply) ) {
+ // To be thorough, we ought to check to see if the message they are
+ // replying to is actually a valid one in this room, but unless this
+ // actually becomes a problem we'll go with high performance instead.
+ strcpy(errmsgbuf, "OK to reply here");
+ return(0);
+ }
+
+ if ( (ra & UA_REPLYALLOWED) && (!is_reply) ) {
+ // Clarify what happened with a better error message
+ snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
+ return (ERROR + HIGHER_ACCESS_REQUIRED);
+ }
+
+ snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
+ return (ERROR + HIGHER_ACCESS_REQUIRED);
+
+}
+
+
+// Check whether the current user has permission to delete messages from
+// the current room (returns 1 for yes, 0 for no)
+int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
+ int ra;
+ CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
+ if (ra & UA_DELETEALLOWED) return(1);
+ return(0);
+}
+
+
+// Retrieve access control information for any user/room pair.
+// Yes, it has a couple of gotos. If you don't like that, go die in a car fire.
+void CtdlRoomAccess(struct ctdlroom *roombuf, struct ctdluser *userbuf, int *result, int *view) {
+ int retval = 0;
+ visit vbuf;
+ int is_me = 0;
+ int is_guest = 0;
+
+ if (userbuf == &CC->user) {
+ is_me = 1;
+ }
+
+ if ((is_me) && (CtdlGetConfigInt("c_guest_logins")) && (!CC->logged_in)) {
+ is_guest = 1;
+ }
+
+ // for internal programs, always do everything
+ if (((CC->internal_pgm)) && (roombuf->QRflags & QR_INUSE)) {
+ retval = (UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED);
+ vbuf.v_view = 0;
+ goto SKIP_EVERYTHING;
+ }
+
+ // If guest mode is enabled, always grant access to the Lobby
+ if ((is_guest) && (!strcasecmp(roombuf->QRname, BASEROOM))) {
+ retval = (UA_KNOWN | UA_GOTOALLOWED);
+ vbuf.v_view = 0;
+ goto SKIP_EVERYTHING;
+ }
+
+ // Locate any applicable user/room relationships
+ if (is_guest) {
+ memset(&vbuf, 0, sizeof vbuf);
+ }
+ else {
+ CtdlGetRelationship(&vbuf, userbuf, roombuf);
+ }
+
+ // Force the properties of the Aide room
+ if (!strcasecmp(roombuf->QRname, CtdlGetConfigStr("c_aideroom"))) {
+ if (userbuf->axlevel >= AxAideU) {
+ retval = UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED;
+ } else {
+ retval = 0;
+ }
+ goto NEWMSG;
+ }
+
+ // If this is a public room, it's accessible...
+ if ( ((roombuf->QRflags & QR_PRIVATE) == 0)
+ && ((roombuf->QRflags & QR_MAILBOX) == 0)
+ ) {
+ retval = retval | UA_KNOWN | UA_GOTOALLOWED;
+ }
+
+ // If this is a preferred users only room, check access level
+ if (roombuf->QRflags & QR_PREFONLY) {
+ if (userbuf->axlevel < AxPrefU) {
+ retval = retval & ~UA_KNOWN & ~UA_GOTOALLOWED;
+ }
+ }
+
+ // For private rooms, check the generation number matchups
+ if ( (roombuf->QRflags & QR_PRIVATE)
+ && ((roombuf->QRflags & QR_MAILBOX) == 0)
+ ) {
+
+ // An explicit match means the user belongs in this room
+ if (vbuf.v_flags & V_ACCESS) {
+ retval = retval | UA_KNOWN | UA_GOTOALLOWED;
+ }
+ // Otherwise, check if this is a guess-name or passworded
+ // room. If it is, a goto may at least be attempted
+ else if ( (roombuf->QRflags & QR_PRIVATE)
+ || (roombuf->QRflags & QR_PASSWORDED)
+ ) {
+ retval = retval & ~UA_KNOWN;
+ retval = retval | UA_GOTOALLOWED;
+ }
+ }
+
+ // For mailbox rooms, also check the namespace. Also, mailbox owners can delete their messages
+ if ( (roombuf->QRflags & QR_MAILBOX) && (atol(roombuf->QRname) != 0)) {
+ if (userbuf->usernum == atol(roombuf->QRname)) {
+ retval = retval | UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED;
+ }
+ // An explicit match means the user belongs in this room
+ if (vbuf.v_flags & V_ACCESS) {
+ retval = retval | UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED;
+ }
+ }
+
+ // For non-mailbox rooms...
+ else {
+ // User is allowed to post in the room unless:
+ // - User is not validated
+ // - User has no net privileges and it is a shared network room
+ // - It is a read-only room
+ // - It is a blog room (in which case we only allow replies to existing messages)
+ int post_allowed = 1;
+ int reply_allowed = 1;
+ if (userbuf->axlevel < AxProbU) {
+ post_allowed = 0;
+ reply_allowed = 0;
+ }
+ if ((userbuf->axlevel < AxNetU) && (roombuf->QRflags & QR_NETWORK)) {
+ post_allowed = 0;
+ reply_allowed = 0;
+ }
+ if (roombuf->QRflags & QR_READONLY) {
+ post_allowed = 0;
+ reply_allowed = 0;
+ }
+ if (roombuf->QRdefaultview == VIEW_BLOG) {
+ post_allowed = 0;
+ }
+ if (post_allowed) {
+ retval = retval | UA_POSTALLOWED | UA_REPLYALLOWED;
+ }
+ if (reply_allowed) {
+ retval = retval | UA_REPLYALLOWED;
+ }
+
+ // If "collaborative deletion" is active for this room, any user who can post
+ // is also allowed to delete
+ if (roombuf->QRflags2 & QR2_COLLABDEL) {
+ if (retval & UA_POSTALLOWED) {
+ retval = retval | UA_DELETEALLOWED;
+ }
+ }
+
+ }
+
+ // Check to see if the user has forgotten this room
+ if (vbuf.v_flags & V_FORGET) {
+ retval = retval & ~UA_KNOWN;
+ if ( ( ((roombuf->QRflags & QR_PRIVATE) == 0)
+ && ((roombuf->QRflags & QR_MAILBOX) == 0)
+ ) || ( (roombuf->QRflags & QR_MAILBOX)
+ && (atol(roombuf->QRname) == CC->user.usernum))
+ ) {
+ retval = retval | UA_ZAPPED;
+ }
+ }
+
+ // If user is explicitly locked out of this room, deny everything
+ if (vbuf.v_flags & V_LOCKOUT) {
+ retval = retval & ~UA_KNOWN & ~UA_GOTOALLOWED & ~UA_POSTALLOWED & ~UA_REPLYALLOWED;
+ }
+
+ // Aides get access to all private rooms
+ if ( (userbuf->axlevel >= AxAideU)
+ && ((roombuf->QRflags & QR_MAILBOX) == 0)
+ ) {
+ if (vbuf.v_flags & V_FORGET) {
+ retval = retval | UA_GOTOALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED;
+ }
+ else {
+ retval = retval | UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED;
+ }
+ }
+
+ // Aides can gain access to mailboxes as well, but they don't show by default.
+ if ( (userbuf->axlevel >= AxAideU)
+ && (roombuf->QRflags & QR_MAILBOX)
+ ) {
+ retval = retval | UA_GOTOALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED;
+ }
+
+ // Aides and Room Aides have admin privileges
+ if ( (userbuf->axlevel >= AxAideU)
+ || (userbuf->usernum == roombuf->QRroomaide)
+ ) {
+ retval = retval | UA_ADMINALLOWED | UA_DELETEALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED;
+ }
+
+NEWMSG: // By the way, we also check for the presence of new messages
+ if (is_msg_in_sequence_set(vbuf.v_seen, roombuf->QRhighest) == 0) {
+ retval = retval | UA_HASNEWMSGS;
+ }
+
+ // System rooms never show up in the list.
+ if (roombuf->QRflags2 & QR2_SYSTEM) {
+ retval = retval & ~UA_KNOWN;
+ }
+
+SKIP_EVERYTHING:
+ // Now give the caller the information it wants.
+ if (result != NULL) *result = retval;
+ if (view != NULL) *view = vbuf.v_view;
+}
+
+
+// Self-checking stuff for a room record read into memory
+void room_sanity_check(struct ctdlroom *qrbuf) {
+ // Mailbox rooms are always on the lowest floor
+ if (qrbuf->QRflags & QR_MAILBOX) {
+ qrbuf->QRfloor = 0;
+ }
+ // Listing order of 0 is illegal except for base rooms
+ if (qrbuf->QRorder == 0) {
+ if ( !(qrbuf->QRflags & QR_MAILBOX)
+ && strncasecmp(qrbuf->QRname, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)
+ && strncasecmp(qrbuf->QRname, CtdlGetConfigStr("c_aideroom"), ROOMNAMELEN)
+ ) {
+ qrbuf->QRorder = 64;
+ }
+ }
+}
+
+
+// CtdlGetRoom() - retrieve room data from disk
+int CtdlGetRoom(struct ctdlroom *qrbuf, const char *room_name) {
+ struct cdbdata *cdbqr;
+ char lowercase_name[ROOMNAMELEN];
+ char personal_lowercase_name[ROOMNAMELEN];
+ const char *sptr;
+ char *dptr, *eptr;
+
+ dptr = lowercase_name;
+ sptr = room_name;
+ eptr = (dptr + (sizeof lowercase_name - 1));
+ while (!IsEmptyStr(sptr) && (dptr < eptr)) {
+ *dptr = tolower(*sptr);
+ sptr++; dptr++;
+ }
+ *dptr = '\0';
+
+ memset(qrbuf, 0, sizeof(struct ctdlroom));
+
+ if (IsEmptyStr(lowercase_name)) {
+ return(1); // empty room name , not valid
+ }
+
+ // First, try the public namespace
+ cdbqr = cdb_fetch(CDB_ROOMS, lowercase_name, strlen(lowercase_name));
+
+ // If that didn't work, try the user's personal namespace
+ if (cdbqr == NULL) {
+ snprintf(personal_lowercase_name, sizeof personal_lowercase_name, "%010ld.%s", CC->user.usernum, lowercase_name);
+ cdbqr = cdb_fetch(CDB_ROOMS, personal_lowercase_name, strlen(personal_lowercase_name));
+ }
+ if (cdbqr != NULL) {
+ memcpy(qrbuf, cdbqr->ptr, ((cdbqr->len > sizeof(struct ctdlroom)) ? sizeof(struct ctdlroom) : cdbqr->len));
+ cdb_free(cdbqr);
+ room_sanity_check(qrbuf);
+ return (0);
+ }
+ else {
+ return (1);
+ }
+}
+
+
+// CtdlGetRoomLock() - same as getroom() but locks the record (if supported)
+int CtdlGetRoomLock(struct ctdlroom *qrbuf, const char *room_name) {
+ register int retval;
+ retval = CtdlGetRoom(qrbuf, room_name);
+ if (retval == 0) begin_critical_section(S_ROOMS);
+ return(retval);
+}
+
+
+// b_putroom() - back end to putroom() and b_deleteroom()
+// (if the supplied buffer is NULL, delete the room record)
+void b_putroom(struct ctdlroom *qrbuf, char *room_name) {
+ char lowercase_name[ROOMNAMELEN];
+ char *aptr, *bptr;
+ long len;
+
+ aptr = room_name;
+ bptr = lowercase_name;
+ while (!IsEmptyStr(aptr)) {
+ *bptr = tolower(*aptr);
+ aptr++;
+ bptr++;
+ }
+ *bptr='\0';
+
+ len = bptr - lowercase_name;
+ if (qrbuf == NULL) {
+ cdb_delete(CDB_ROOMS, lowercase_name, len);
+ }
+ else {
+ time(&qrbuf->QRmtime);
+ cdb_store(CDB_ROOMS, lowercase_name, len, qrbuf, sizeof(struct ctdlroom));
+ }
+}
+
+
+// CtdlPutRoom() - store room data to disk
+void CtdlPutRoom(struct ctdlroom *qrbuf) {
+ b_putroom(qrbuf, qrbuf->QRname);
+}
+
+
+// b_deleteroom() - delete a room record from disk
+void b_deleteroom(char *room_name) {
+ b_putroom(NULL, room_name);
+}
+
+
+// CtdlPutRoomLock() - same as CtdlPutRoom() but unlocks the record (if supported)
+void CtdlPutRoomLock(struct ctdlroom *qrbuf) {
+ CtdlPutRoom(qrbuf);
+ end_critical_section(S_ROOMS);
+}
+
+
+// CtdlGetFloorByName() - retrieve the number of the named floor
+// return < 0 if not found else return floor number
+int CtdlGetFloorByName(const char *floor_name) {
+ int a;
+ struct floor *flbuf = NULL;
+
+ for (a = 0; a < MAXFLOORS; ++a) {
+ flbuf = CtdlGetCachedFloor(a);
+
+ // check to see if it already exists
+ if ((!strcasecmp(flbuf->f_name, floor_name)) && (flbuf->f_flags & F_INUSE)) {
+ return a;
+ }
+ }
+ return -1;
+}
+
+
+// CtdlGetFloorByNameLock() - retrieve floor number for given floor and lock the floor list.
+int CtdlGetFloorByNameLock(const char *floor_name) {
+ begin_critical_section(S_FLOORTAB);
+ return CtdlGetFloorByName(floor_name);
+}
+
+
+/*
+ * CtdlGetAvailableFloor() - Return number of first unused floor
+ * return < 0 if none available
+ */
+int CtdlGetAvailableFloor(void) {
+ int a;
+ struct floor *flbuf = NULL;
+
+ for (a = 0; a < MAXFLOORS; a++) {
+ flbuf = CtdlGetCachedFloor(a);
+
+ /* check to see if it already exists */
+ if ((flbuf->f_flags & F_INUSE) == 0) {
+ return a;
+ }
+ }
+ return -1;
+}
+
+
+/*
+ * CtdlGetFloor() - retrieve floor data from disk
+ */
+void CtdlGetFloor(struct floor *flbuf, int floor_num) {
+ struct cdbdata *cdbfl;
+
+ memset(flbuf, 0, sizeof(struct floor));
+ cdbfl = cdb_fetch(CDB_FLOORTAB, &floor_num, sizeof(int));
+ if (cdbfl != NULL) {
+ memcpy(flbuf, cdbfl->ptr, ((cdbfl->len > sizeof(struct floor)) ? sizeof(struct floor) : cdbfl->len));
+ cdb_free(cdbfl);
+ } else {
+ if (floor_num == 0) {
+ safestrncpy(flbuf->f_name, "Main Floor", sizeof flbuf->f_name);
+ flbuf->f_flags = F_INUSE;
+ flbuf->f_ref_count = 3;
+ }
+ }
+}
+
+
+/*
+ * lgetfloor() - same as CtdlGetFloor() but locks the record (if supported)
+ */
+void lgetfloor(struct floor *flbuf, int floor_num) {
+ begin_critical_section(S_FLOORTAB);
+ CtdlGetFloor(flbuf, floor_num);
+}
+
+
+/*
+ * CtdlGetCachedFloor() - Get floor record from *cache* (loads from disk if needed)
+ *
+ * This is strictly a performance hack.
+ */
+struct floor *CtdlGetCachedFloor(int floor_num) {
+ static int initialized = 0;
+ int i;
+ int fetch_new = 0;
+ struct floor *fl = NULL;
+
+ begin_critical_section(S_FLOORCACHE);
+ if (initialized == 0) {
+ for (i=0; i<MAXFLOORS; ++i) {
+ floorcache[floor_num] = NULL;
+ }
+ initialized = 1;
+ }
+ if (floorcache[floor_num] == NULL) {
+ fetch_new = 1;
+ }
+ end_critical_section(S_FLOORCACHE);
+
+ if (fetch_new) {
+ fl = malloc(sizeof(struct floor));
+ CtdlGetFloor(fl, floor_num);
+ begin_critical_section(S_FLOORCACHE);
+ if (floorcache[floor_num] != NULL) {
+ free(floorcache[floor_num]);
+ }
+ floorcache[floor_num] = fl;
+ end_critical_section(S_FLOORCACHE);
+ }
+
+ return(floorcache[floor_num]);
+}
+
+
+/*
+ * CtdlPutFloor() - store floor data on disk
+ */
+void CtdlPutFloor(struct floor *flbuf, int floor_num) {
+ /* If we've cached this, clear it out, 'cuz it's WRONG now! */
+ begin_critical_section(S_FLOORCACHE);
+ if (floorcache[floor_num] != NULL) {
+ free(floorcache[floor_num]);
+ floorcache[floor_num] = malloc(sizeof(struct floor));
+ memcpy(floorcache[floor_num], flbuf, sizeof(struct floor));
+ }
+ end_critical_section(S_FLOORCACHE);
+
+ cdb_store(CDB_FLOORTAB, &floor_num, sizeof(int),
+ flbuf, sizeof(struct floor));
+}
+
+
+/*
+ * CtdlPutFloorLock() - same as CtdlPutFloor() but unlocks the record (if supported)
+ */
+void CtdlPutFloorLock(struct floor *flbuf, int floor_num) {
+ CtdlPutFloor(flbuf, floor_num);
+ end_critical_section(S_FLOORTAB);
+
+}
+
+
+/*
+ * lputfloor() - same as CtdlPutFloor() but unlocks the record (if supported)
+ */
+void lputfloor(struct floor *flbuf, int floor_num) {
+ CtdlPutFloorLock(flbuf, floor_num);
+}
+
+
+/*
+ * Iterate through the room table, performing a callback for each room.
+ */
+void CtdlForEachRoom(ForEachRoomCallBack callback_func, void *in_data) {
+ struct ctdlroom qrbuf;
+ struct cdbdata *cdbqr;
+
+ cdb_rewind(CDB_ROOMS);
+
+ while (cdbqr = cdb_next_item(CDB_ROOMS), cdbqr != NULL) {
+ memset(&qrbuf, 0, sizeof(struct ctdlroom));
+ memcpy(&qrbuf, cdbqr->ptr, ((cdbqr->len > sizeof(struct ctdlroom)) ? sizeof(struct ctdlroom) : cdbqr->len) );
+ cdb_free(cdbqr);
+ room_sanity_check(&qrbuf);
+ if (qrbuf.QRflags & QR_INUSE) {
+ callback_func(&qrbuf, in_data);
+ }
+ }
+}
+
+
+/*
+ * delete_msglist() - delete room message pointers
+ */
+void delete_msglist(struct ctdlroom *whichroom) {
+ struct cdbdata *cdbml;
+
+ /* Make sure the msglist we're deleting actually exists, otherwise
+ * libdb will complain when we try to delete an invalid record
+ */
+ cdbml = cdb_fetch(CDB_MSGLISTS, &whichroom->QRnumber, sizeof(long));
+ if (cdbml != NULL) {
+ cdb_free(cdbml);
+
+ /* Go ahead and delete it */
+ cdb_delete(CDB_MSGLISTS, &whichroom->QRnumber, sizeof(long));
+ }
+}
+
+
+/*
+ * Message pointer compare function for sort_msglist()
+ */
+int sort_msglist_cmp(const void *m1, const void *m2) {
+ if ((*(const long *)m1) > (*(const long *)m2)) return(1);
+ if ((*(const long *)m1) < (*(const long *)m2)) return(-1);
+ return(0);
+}
+
+
+/*
+ * sort message pointers
+ * (returns new msg count)
+ */
+int sort_msglist(long listptrs[], int oldcount) {
+ int numitems;
+ int i = 0;
+
+ numitems = oldcount;
+ if (numitems < 2) {
+ return (oldcount);
+ }
+
+ /* do the sort */
+ qsort(listptrs, numitems, sizeof(long), sort_msglist_cmp);
+
+ /* and yank any nulls */
+ while ((i < numitems) && (listptrs[i] == 0L)) i++;
+
+ if (i > 0) {
+ memmove(&listptrs[0], &listptrs[i], (sizeof(long) * (numitems - i)));
+ numitems-=i;
+ }
+
+ return (numitems);
+}
+
+
+/*
+ * Determine whether a given room is non-editable.
+ */
+int CtdlIsNonEditable(struct ctdlroom *qrbuf) {
+
+ /* Mail> rooms are non-editable */
+ if ( (qrbuf->QRflags & QR_MAILBOX) && (!strcasecmp(&qrbuf->QRname[11], MAILROOM)) ) {
+ return (1);
+ }
+
+ /* Everything else is editable */
+ return (0);
+}
+
+
+/*
+ * Make the specified room the current room for this session. No validation
+ * or access control is done here -- the caller should make sure that the
+ * specified room exists and is ok to access.
+ */
+void CtdlUserGoto(char *where, int display_result, int transiently,
+ int *retmsgs, int *retnew, long *retoldest, long *retnewest)
+{
+ int a;
+ int new_messages = 0;
+ int old_messages = 0;
+ int total_messages = 0;
+ long oldest_message = 0;
+ long newest_message = 0;
+ int info = 0;
+ int rmailflag;
+ int raideflag;
+ int newmailcount = 0;
+ visit vbuf;
+ char truncated_roomname[ROOMNAMELEN];
+ struct cdbdata *cdbfr;
+ long *msglist = NULL;
+ int num_msgs = 0;
+ unsigned int original_v_flags;
+ int num_sets;
+ int s;
+ char setstr[128], lostr[64], histr[64];
+ long lo, hi;
+ int is_trash = 0;
+
+ /* If the supplied room name is NULL, the caller wants us to know that
+ * it has already copied the room record into CC->room, so
+ * we can skip the extra database fetch.
+ */
+ if (where != NULL) {
+ safestrncpy(CC->room.QRname, where, sizeof CC->room.QRname);
+ CtdlGetRoom(&CC->room, where);
+ }
+
+ /* Take care of all the formalities. */
+
+ begin_critical_section(S_USERS);
+ CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+ original_v_flags = vbuf.v_flags;
+
+ /* Know the room ... but not if it's the page log room, or if the
+ * caller specified that we're only entering this room transiently.
+ */
+ int add_room_to_known_list = 1;
+ if (transiently == 1) {
+ add_room_to_known_list = 0;
+ }
+ char *c_logpages = CtdlGetConfigStr("c_logpages");
+ if ( (c_logpages != NULL) && (!strcasecmp(CC->room.QRname, c_logpages)) ) {
+ add_room_to_known_list = 0;
+ }
+ if (add_room_to_known_list) {
+ vbuf.v_flags = vbuf.v_flags & ~V_FORGET & ~V_LOCKOUT;
+ vbuf.v_flags = vbuf.v_flags | V_ACCESS;
+ }
+
+ /* Only rewrite the database record if we changed something */
+ if (vbuf.v_flags != original_v_flags) {
+ CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
+ }
+ end_critical_section(S_USERS);
+
+ /* Check for new mail */
+ newmailcount = NewMailCount();
+
+ /* Set info to 1 if the room banner is new since our last visit.
+ * Some clients only want to display it when it changes.
+ */
+ if (CC->room.msgnum_info > vbuf.v_lastseen) {
+ info = 1;
+ }
+
+ cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
+ if (cdbfr != NULL) {
+ msglist = (long *) cdbfr->ptr;
+ cdbfr->ptr = NULL; /* CtdlUserGoto() now owns this memory */
+ num_msgs = cdbfr->len / sizeof(long);
+ cdb_free(cdbfr);
+ }
+
+ total_messages = 0;
+ for (a=0; a<num_msgs; ++a) {
+ if (msglist[a] > 0L) ++total_messages;
+ }
+
+ if (total_messages > 0) {
+ oldest_message = msglist[0];
+ newest_message = msglist[num_msgs - 1];
+ }
+
+ num_sets = num_tokens(vbuf.v_seen, ',');
+ for (s=0; s<num_sets; ++s) {
+ extract_token(setstr, vbuf.v_seen, s, ',', sizeof setstr);
+
+ extract_token(lostr, setstr, 0, ':', sizeof lostr);
+ if (num_tokens(setstr, ':') >= 2) {
+ extract_token(histr, setstr, 1, ':', sizeof histr);
+ if (!strcmp(histr, "*")) {
+ snprintf(histr, sizeof histr, "%ld", LONG_MAX);
+ }
+ }
+ else {
+ strcpy(histr, lostr);
+ }
+ lo = atol(lostr);
+ hi = atol(histr);
+
+ for (a=0; a<num_msgs; ++a) if (msglist[a] > 0L) {
+ if ((msglist[a] >= lo) && (msglist[a] <= hi)) {
+ ++old_messages;
+ msglist[a] = 0L;
+ }
+ }
+ }
+ new_messages = total_messages - old_messages;
+
+ if (msglist != NULL) free(msglist);
+
+ if (CC->room.QRflags & QR_MAILBOX)
+ rmailflag = 1;
+ else
+ rmailflag = 0;
+
+ if ((CC->room.QRroomaide == CC->user.usernum) || (CC->user.axlevel >= AxAideU))
+ raideflag = 1;
+ else
+ raideflag = 0;
+
+ safestrncpy(truncated_roomname, CC->room.QRname, sizeof truncated_roomname);
+ if ( (CC->room.QRflags & QR_MAILBOX) && (atol(CC->room.QRname) == CC->user.usernum) ) {
+ safestrncpy(truncated_roomname, &truncated_roomname[11], sizeof truncated_roomname);
+ }
+
+ if (!strcasecmp(truncated_roomname, USERTRASHROOM)) {
+ is_trash = 1;
+ }
+
+ if (retmsgs != NULL) *retmsgs = total_messages;
+ if (retnew != NULL) *retnew = new_messages;
+ if (retoldest != NULL) *retoldest = oldest_message;
+ if (retnewest != NULL) *retnewest = newest_message;
+ syslog(LOG_DEBUG, "room_ops: %s : %d new of %d total messages, oldest=%ld, newest=%ld",
+ CC->room.QRname, new_messages, total_messages, oldest_message, newest_message
+ );
+
+ CC->curr_view = (int)vbuf.v_view;
+
+ if (display_result) {
+ cprintf("%d%c%s|%d|%d|%d|%d|%ld|%ld|%d|%d|%d|%d|%d|%d|%d|%d|%ld|\n",
+ CIT_OK, CtdlCheckExpress(),
+ truncated_roomname,
+ (int)new_messages,
+ (int)total_messages,
+ (int)info,
+ (int)CC->room.QRflags,
+ (long)CC->room.QRhighest,
+ (long)vbuf.v_lastseen,
+ (int)rmailflag,
+ (int)raideflag,
+ (int)newmailcount,
+ (int)CC->room.QRfloor,
+ (int)vbuf.v_view,
+ (int)CC->room.QRdefaultview,
+ (int)is_trash,
+ (int)CC->room.QRflags2,
+ (long)CC->room.QRmtime
+ );
+ }
+}
+
+
+/*
+ * Handle some of the macro named rooms
+ */
+void convert_room_name_macros(char *towhere, size_t maxlen) {
+ if (!strcasecmp(towhere, "_BASEROOM_")) {
+ safestrncpy(towhere, CtdlGetConfigStr("c_baseroom"), maxlen);
+ }
+ else if (!strcasecmp(towhere, "_MAIL_")) {
+ safestrncpy(towhere, MAILROOM, maxlen);
+ }
+ else if (!strcasecmp(towhere, "_TRASH_")) {
+ safestrncpy(towhere, USERTRASHROOM, maxlen);
+ }
+ else if (!strcasecmp(towhere, "_DRAFTS_")) {
+ safestrncpy(towhere, USERDRAFTROOM, maxlen);
+ }
+ else if (!strcasecmp(towhere, "_BITBUCKET_")) {
+ safestrncpy(towhere, CtdlGetConfigStr("c_twitroom"), maxlen);
+ }
+ else if (!strcasecmp(towhere, "_CALENDAR_")) {
+ safestrncpy(towhere, USERCALENDARROOM, maxlen);
+ }
+ else if (!strcasecmp(towhere, "_TASKS_")) {
+ safestrncpy(towhere, USERTASKSROOM, maxlen);
+ }
+ else if (!strcasecmp(towhere, "_CONTACTS_")) {
+ safestrncpy(towhere, USERCONTACTSROOM, maxlen);
+ }
+ else if (!strcasecmp(towhere, "_NOTES_")) {
+ safestrncpy(towhere, USERNOTESROOM, maxlen);
+ }
+}
+
+
+/*
+ * Back end function to rename a room.
+ * You can also specify which floor to move the room to, or specify -1 to
+ * keep the room on the same floor it was on.
+ *
+ * If you are renaming a mailbox room, you must supply the namespace prefix
+ * in *at least* the old name!
+ */
+int CtdlRenameRoom(char *old_name, char *new_name, int new_floor) {
+ int old_floor = 0;
+ struct ctdlroom qrbuf;
+ struct ctdlroom qrtmp;
+ int ret = 0;
+ struct floor *fl;
+ struct floor flbuf;
+ long owner = 0L;
+ char actual_old_name[ROOMNAMELEN];
+
+ syslog(LOG_DEBUG, "room_ops: CtdlRenameRoom(%s, %s, %d)", old_name, new_name, new_floor);
+
+ if (new_floor >= 0) {
+ fl = CtdlGetCachedFloor(new_floor);
+ if ((fl->f_flags & F_INUSE) == 0) {
+ return(crr_invalid_floor);
+ }
+ }
+
+ begin_critical_section(S_ROOMS);
+
+ if ( (CtdlGetRoom(&qrtmp, new_name) == 0) && (strcasecmp(new_name, old_name)) ) {
+ ret = crr_already_exists;
+ }
+
+ else if (CtdlGetRoom(&qrbuf, old_name) != 0) {
+ ret = crr_room_not_found;
+ }
+
+ else if ( (CC->user.axlevel < AxAideU) && (!CC->internal_pgm)
+ && (CC->user.usernum != qrbuf.QRroomaide)
+ && ( (((qrbuf.QRflags & QR_MAILBOX) == 0) || (atol(qrbuf.QRname) != CC->user.usernum))) ) {
+ ret = crr_access_denied;
+ }
+
+ else if (CtdlIsNonEditable(&qrbuf)) {
+ ret = crr_noneditable;
+ }
+
+ else {
+ /* Rename it */
+ safestrncpy(actual_old_name, qrbuf.QRname, sizeof actual_old_name);
+ if (qrbuf.QRflags & QR_MAILBOX) {
+ owner = atol(qrbuf.QRname);
+ }
+ if ( (owner > 0L) && (atol(new_name) == 0L) ) {
+ snprintf(qrbuf.QRname, sizeof(qrbuf.QRname),
+ "%010ld.%s", owner, new_name);
+ }
+ else {
+ safestrncpy(qrbuf.QRname, new_name,
+ sizeof(qrbuf.QRname));
+ }
+
+ /* Reject change of floor for baseroom/aideroom */
+ if (!strncasecmp(old_name, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN) ||
+ !strncasecmp(old_name, CtdlGetConfigStr("c_aideroom"), ROOMNAMELEN))
+ {
+ new_floor = 0;
+ }
+
+ /* Take care of floor stuff */
+ old_floor = qrbuf.QRfloor;
+ if (new_floor < 0) {
+ new_floor = old_floor;
+ }
+ qrbuf.QRfloor = new_floor;
+ CtdlPutRoom(&qrbuf);
+
+ begin_critical_section(S_CONFIG);
+
+ /* If baseroom/aideroom name changes, update config */
+ if (!strncasecmp(old_name, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)) {
+ CtdlSetConfigStr("c_baseroom", new_name);
+ }
+ if (!strncasecmp(old_name, CtdlGetConfigStr("c_aideroom"), ROOMNAMELEN)) {
+ CtdlSetConfigStr("c_aideroom", new_name);
+ }
+
+ end_critical_section(S_CONFIG);
+
+ /* If the room name changed, then there are now two room
+ * records, so we have to delete the old one.
+ */
+ if (strcasecmp(new_name, old_name)) {
+ b_deleteroom(actual_old_name);
+ }
+
+ ret = crr_ok;
+ }
+
+ end_critical_section(S_ROOMS);
+
+ /* Adjust the floor reference counts if necessary */
+ if (new_floor != old_floor) {
+ lgetfloor(&flbuf, old_floor);
+ --flbuf.f_ref_count;
+ lputfloor(&flbuf, old_floor);
+ syslog(LOG_DEBUG, "room_ops: reference count for floor %d is now %d", old_floor, flbuf.f_ref_count);
+ lgetfloor(&flbuf, new_floor);
+ ++flbuf.f_ref_count;
+ lputfloor(&flbuf, new_floor);
+ syslog(LOG_DEBUG, "room_ops: reference count for floor %d is now %d", new_floor, flbuf.f_ref_count);
+ }
+
+ /* ...and everybody say "YATTA!" */
+ return(ret);
+}
+
+
+/*
+ * Asynchronously schedule a room for deletion. By placing the room into an invalid private namespace,
+ * the room will appear deleted to the user(s), but the session doesn't need to block while waiting for
+ * database operations to complete. Instead, the room gets purged when THE DREADED AUTO-PURGER makes
+ * its next run. Aren't we so clever?!!
+ */
+void CtdlScheduleRoomForDeletion(struct ctdlroom *qrbuf) {
+ char old_name[ROOMNAMELEN];
+ static int seq = 0;
+
+ syslog(LOG_NOTICE, "room_ops: scheduling room <%s> for deletion", qrbuf->QRname);
+
+ safestrncpy(old_name, qrbuf->QRname, sizeof old_name);
+ CtdlGetRoom(qrbuf, qrbuf->QRname);
+
+ /* Turn the room into a private mailbox owned by a user who doesn't
+ * exist. This will immediately make the room invisible to everyone,
+ * and qualify the room for purging.
+ */
+ snprintf(qrbuf->QRname, sizeof qrbuf->QRname, "9999999999.%08lx.%04d.%s",
+ time(NULL),
+ ++seq,
+ old_name
+ );
+ qrbuf->QRflags |= QR_MAILBOX;
+ time(&qrbuf->QRgen); /* Use a timestamp as the new generation number */
+ CtdlPutRoom(qrbuf);
+ b_deleteroom(old_name);
+}
+
+
+/*
+ * Back end processing to delete a room and everything associated with it
+ * (This one is synchronous and should only get called by THE DREADED
+ * AUTO-PURGER in serv_expire.c. All user-facing code should call
+ * the asynchronous schedule_room_for_deletion() instead.)
+ */
+void CtdlDeleteRoom(struct ctdlroom *qrbuf) {
+ struct floor flbuf;
+ char configdbkeyname[25];
+
+ syslog(LOG_NOTICE, "room_ops: deleting room <%s>", qrbuf->QRname);
+
+ // Delete the room's network configdb entry
+ netcfg_keyname(configdbkeyname, qrbuf->QRnumber);
+ CtdlDelConfig(configdbkeyname);
+
+ // Delete the messages in the room
+ // (Careful: this opens an S_ROOMS critical section!)
+ CtdlDeleteMessages(qrbuf->QRname, NULL, 0, "");
+
+ // Flag the room record as not in use
+ CtdlGetRoomLock(qrbuf, qrbuf->QRname);
+ qrbuf->QRflags = 0;
+ CtdlPutRoomLock(qrbuf);
+
+ // then decrement the reference count for the floor
+ lgetfloor(&flbuf, (int) (qrbuf->QRfloor));
+ flbuf.f_ref_count = flbuf.f_ref_count - 1;
+ lputfloor(&flbuf, (int) (qrbuf->QRfloor));
+
+ // Delete the room record from the database!
+ b_deleteroom(qrbuf->QRname);
+}
+
+
+/*
+ * Check access control for deleting a room
+ */
+int CtdlDoIHavePermissionToDeleteThisRoom(struct ctdlroom *qr) {
+
+ if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
+ return(0);
+ }
+
+ if (CtdlIsNonEditable(qr)) {
+ return(0);
+ }
+
+ /*
+ * For mailboxes, check stuff
+ */
+ if (qr->QRflags & QR_MAILBOX) {
+
+ if (strlen(qr->QRname) < 12) return(0); /* bad name */
+
+ if (atol(qr->QRname) != CC->user.usernum) {
+ return(0); /* not my room */
+ }
+
+ /* Can't delete your Mail> room */
+ if (!strcasecmp(&qr->QRname[11], MAILROOM)) return(0);
+
+ /* Otherwise it's ok */
+ return(1);
+ }
+
+ /*
+ * For normal rooms, just check for admin or room admin status.
+ */
+ return(is_room_aide());
+}
+
+
+/*
+ * Internal code to create a new room (returns room flags)
+ *
+ * Room types: 0=public, 1=hidden, 2=passworded, 3=invitation-only,
+ * 4=mailbox, 5=mailbox, but caller supplies namespace
+ */
+unsigned CtdlCreateRoom(char *new_room_name,
+ int new_room_type,
+ char *new_room_pass,
+ int new_room_floor,
+ int really_create,
+ int avoid_access,
+ int new_room_view)
+{
+ struct ctdlroom qrbuf;
+ struct floor flbuf;
+ visit vbuf;
+
+ syslog(LOG_DEBUG, "room_ops: CtdlCreateRoom(name=%s, type=%d, view=%d)", new_room_name, new_room_type, new_room_view);
+
+ if (CtdlGetRoom(&qrbuf, new_room_name) == 0) {
+ syslog(LOG_DEBUG, "room_ops: cannot create room <%s> - already exists", new_room_name);
+ return(0);
+ }
+
+ memset(&qrbuf, 0, sizeof(struct ctdlroom));
+ safestrncpy(qrbuf.QRpasswd, new_room_pass, sizeof qrbuf.QRpasswd);
+ qrbuf.QRflags = QR_INUSE;
+ if (new_room_type > 0)
+ qrbuf.QRflags = (qrbuf.QRflags | QR_PRIVATE);
+ if (new_room_type == 1)
+ qrbuf.QRflags = (qrbuf.QRflags | QR_GUESSNAME);
+ if (new_room_type == 2)
+ qrbuf.QRflags = (qrbuf.QRflags | QR_PASSWORDED);
+ if ( (new_room_type == 4) || (new_room_type == 5) ) {
+ qrbuf.QRflags = (qrbuf.QRflags | QR_MAILBOX);
+ /* qrbuf.QRflags2 |= QR2_SUBJECTREQ; */
+ }
+
+ /* If the user is requesting a personal room, set up the room
+ * name accordingly (prepend the user number)
+ */
+ if (new_room_type == 4) {
+ CtdlMailboxName(qrbuf.QRname, sizeof qrbuf.QRname, &CC->user, new_room_name);
+ }
+ else {
+ safestrncpy(qrbuf.QRname, new_room_name, sizeof qrbuf.QRname);
+ }
+
+ /* If the room is private, and the system administrator has elected
+ * to automatically grant room admin privileges, do so now.
+ */
+ if ((qrbuf.QRflags & QR_PRIVATE) && (CREATAIDE == 1)) {
+ qrbuf.QRroomaide = CC->user.usernum;
+ }
+ /* Blog owners automatically become room admins of their blogs.
+ * (In the future we will offer a site-wide configuration setting to suppress this behavior.)
+ */
+ else if (new_room_view == VIEW_BLOG) {
+ qrbuf.QRroomaide = CC->user.usernum;
+ }
+ /* Otherwise, set the room admin to undefined.
+ */
+ else {
+ qrbuf.QRroomaide = (-1L);
+ }
+
+ /*
+ * If the caller is only interested in testing whether this will work,
+ * return now without creating the room.
+ */
+ if (!really_create) return (qrbuf.QRflags);
+
+ qrbuf.QRnumber = get_new_room_number();
+ qrbuf.QRhighest = 0L; /* No messages in this room yet */
+ time(&qrbuf.QRgen); /* Use a timestamp as the generation number */
+ qrbuf.QRfloor = new_room_floor;
+ qrbuf.QRdefaultview = new_room_view;
+
+ /* save what we just did... */
+ CtdlPutRoom(&qrbuf);
+
+ /* bump the reference count on whatever floor the room is on */
+ lgetfloor(&flbuf, (int) qrbuf.QRfloor);
+ flbuf.f_ref_count = flbuf.f_ref_count + 1;
+ lputfloor(&flbuf, (int) qrbuf.QRfloor);
+
+ /* Grant the creator access to the room unless the avoid_access
+ * parameter was specified.
+ */
+ if ( (CC->logged_in) && (avoid_access == 0) ) {
+ CtdlGetRelationship(&vbuf, &CC->user, &qrbuf);
+ vbuf.v_flags = vbuf.v_flags & ~V_FORGET & ~V_LOCKOUT;
+ vbuf.v_flags = vbuf.v_flags | V_ACCESS;
+ CtdlSetRelationship(&vbuf, &CC->user, &qrbuf);
+ }
+
+ /* resume our happy day */
+ return (qrbuf.QRflags);
+}
--- /dev/null
+int is_known (struct ctdlroom *roombuf, int roomnum, struct ctdluser *userbuf);
+int has_newmsgs (struct ctdlroom *roombuf, int roomnum, struct ctdluser *userbuf);
+int is_zapped (struct ctdlroom *roombuf, int roomnum, struct ctdluser *userbuf);
+void b_putroom(struct ctdlroom *qrbuf, char *room_name);
+void b_deleteroom(char *);
+void lgetfloor (struct floor *flbuf, int floor_num);
+void lputfloor (struct floor *flbuf, int floor_num);
+int sort_msglist (long int *listptrs, int oldcount);
+void list_roomname(struct ctdlroom *qrbuf, int ra, int current_view, int default_view);
+void convert_room_name_macros(char *towhere, size_t maxlen);
+
+typedef enum _POST_TYPE{
+ POST_LOGGED_IN,
+ POST_EXTERNAL,
+ CHECK_EXIST,
+ POST_LMTP
+} PostType;
+
+int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n, PostType PostPublic, int is_reply);
+int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void);
+int CtdlDoIHavePermissionToReadMessagesInThisRoom(void);
--- /dev/null
+/*
+ * Citadel Extension Loader
+ * Originally written by Brian Costello <btx@calyx.net>
+ *
+ * Copyright (c) 1987-2021 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.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <libcitadel.h>
+#include "sysdep_decls.h"
+#include "modules/crypto/serv_crypto.h" /* Needed until a universal crypto startup hook is implimented for CtdlStartTLS */
+#include "serv_extensions.h"
+#include "ctdl_module.h"
+#include "config.h"
+
+
+/*
+ * Structure defentitions for hook tables
+ */
+
+typedef struct FixedOutputHook FixedOutputHook;
+struct FixedOutputHook {
+ FixedOutputHook *next;
+ char content_type[64];
+ void (*h_function_pointer) (char *, int);
+};
+FixedOutputHook *FixedOutputTable = NULL;
+
+
+/*
+ * SessionFunctionHook extensions are used for any type of hook for which
+ * the context in which it's being called (which is determined by the event
+ * type) will make it obvious for the hook function to know where to look for
+ * pertinent data.
+ */
+typedef struct SessionFunctionHook SessionFunctionHook;
+struct SessionFunctionHook {
+ SessionFunctionHook *next;
+ int Priority;
+ void (*h_function_pointer) (void);
+ int eventtype;
+};
+SessionFunctionHook *SessionHookTable = NULL;
+
+
+/*
+ * UserFunctionHook extensions are used for any type of hook which implements
+ * an operation on a user or username (potentially) other than the one
+ * operating the current session.
+ */
+typedef struct UserFunctionHook UserFunctionHook;
+struct UserFunctionHook {
+ UserFunctionHook *next;
+ void (*h_function_pointer) (struct ctdluser *usbuf);
+ int eventtype;
+};
+UserFunctionHook *UserHookTable = NULL;
+
+
+/*
+ * MessageFunctionHook extensions are used for hooks which implement handlers
+ * for various types of message operations (save, read, etc.)
+ */
+typedef struct MessageFunctionHook MessageFunctionHook;
+struct MessageFunctionHook {
+ MessageFunctionHook *next;
+ int (*h_function_pointer) (struct CtdlMessage *msg, struct recptypes *recps);
+ int eventtype;
+};
+MessageFunctionHook *MessageHookTable = NULL;
+
+
+/*
+ * DeleteFunctionHook extensions are used for hooks which get called when a
+ * message is about to be deleted.
+ */
+typedef struct DeleteFunctionHook DeleteFunctionHook;
+struct DeleteFunctionHook {
+ DeleteFunctionHook *next;
+ void (*h_function_pointer) (char *target_room, long msgnum);
+};
+DeleteFunctionHook *DeleteHookTable = NULL;
+
+
+/*
+ * ExpressMessageFunctionHook extensions are used for hooks which implement
+ * the sending of an instant message through various channels. Any function
+ * registered should return the number of recipients to whom the message was
+ * successfully transmitted.
+ */
+typedef struct XmsgFunctionHook XmsgFunctionHook;
+struct XmsgFunctionHook {
+ XmsgFunctionHook *next;
+ int (*h_function_pointer) (char *, char *, char *, char *);
+ int order;
+};
+XmsgFunctionHook *XmsgHookTable = NULL;
+
+
+/*
+ * RoomFunctionHook extensions are used for hooks which impliment room
+ * processing functions when new messages are added.
+ */
+typedef struct RoomFunctionHook RoomFunctionHook;
+struct RoomFunctionHook {
+ RoomFunctionHook *next;
+ int (*fcn_ptr) (struct ctdlroom *);
+};
+RoomFunctionHook *RoomHookTable = NULL;
+
+
+typedef struct SearchFunctionHook SearchFunctionHook;
+struct SearchFunctionHook {
+ SearchFunctionHook *next;
+ void (*fcn_ptr) (int *, long **, const char *);
+ char *name;
+};
+SearchFunctionHook *SearchFunctionHookTable = NULL;
+
+ServiceFunctionHook *ServiceHookTable = NULL;
+
+typedef struct ProtoFunctionHook ProtoFunctionHook;
+struct ProtoFunctionHook {
+ void (*handler) (char *cmdbuf);
+ const char *cmd;
+ const char *desc;
+};
+
+HashList *ProtoHookList = NULL;
+
+
+#define ERR_PORT (1 << 1)
+
+
+static StrBuf *portlist = NULL;
+static StrBuf *errormessages = NULL;
+
+
+long DetailErrorFlags;
+ConstStr Empty = {HKEY("")};
+char *ErrSubject = "Startup Problems";
+ConstStr ErrGeneral[] = {
+ {HKEY("Citadel had trouble on starting up. ")},
+ {HKEY(" This means, Citadel won't be the service provider for a specific service you configured it to.\n\n"
+ "If you don't want Citadel to provide these services, turn them off in WebCit via: ")},
+ {HKEY("To make both ways actualy take place restart the citserver with \"sendcommand down\"\n\n"
+ "The errors returned by the system were:\n")},
+ {HKEY("You can recheck the above if you follow this faq item:\n"
+ "http://www.citadel.org/doku.php?id=faq:mastering_your_os:net#netstat")}
+};
+
+ConstStr ErrPortShort = { HKEY("We couldn't bind all ports you configured to be provided by Citadel Server.\n")};
+ConstStr ErrPortWhere = { HKEY("\"Admin->System Preferences->Network\".\n\nThe failed ports and sockets are: ")};
+ConstStr ErrPortHint = { HKEY("If you want Citadel to provide you with that functionality, "
+ "check the output of \"netstat -lnp\" on Linux, or \"netstat -na\" on BSD"
+ " and disable the program that binds these ports.\n")};
+
+
+void LogPrintMessages(long err)
+{
+ StrBuf *Message;
+ StrBuf *List, *DetailList;
+ ConstStr *Short, *Where, *Hint;
+
+
+ Message = NewStrBufPlain(NULL, StrLength(portlist) + StrLength(errormessages));
+
+ DetailErrorFlags = DetailErrorFlags & ~err;
+
+ switch (err)
+ {
+ case ERR_PORT:
+ Short = &ErrPortShort;
+ Where = &ErrPortWhere;
+ Hint = &ErrPortHint;
+ List = portlist;
+ DetailList = errormessages;
+ break;
+ default:
+ Short = &Empty;
+ Where = &Empty;
+ Hint = &Empty;
+ List = NULL;
+ DetailList = NULL;
+ }
+
+ StrBufAppendBufPlain(Message, CKEY(ErrGeneral[0]), 0);
+ StrBufAppendBufPlain(Message, CKEY(*Short), 0);
+ StrBufAppendBufPlain(Message, CKEY(ErrGeneral[1]), 0);
+ StrBufAppendBufPlain(Message, CKEY(*Where), 0);
+ StrBufAppendBuf(Message, List, 0);
+ StrBufAppendBufPlain(Message, HKEY("\n\n"), 0);
+ StrBufAppendBufPlain(Message, CKEY(*Hint), 0);
+ StrBufAppendBufPlain(Message, HKEY("\n\n"), 0);
+ StrBufAppendBufPlain(Message, CKEY(ErrGeneral[2]), 0);
+ StrBufAppendBuf(Message, DetailList, 0);
+ StrBufAppendBufPlain(Message, HKEY("\n\n"), 0);
+ StrBufAppendBufPlain(Message, CKEY(ErrGeneral[3]), 0);
+
+ syslog(LOG_ERR, "extensions: %s", ChrPtr(Message));
+ syslog(LOG_ERR, "extensions: %s", ErrSubject);
+ quickie_message("Citadel", NULL, NULL, AIDEROOM, ChrPtr(Message), FMT_FIXED, ErrSubject);
+
+ FreeStrBuf(&Message);
+ FreeStrBuf(&List);
+ FreeStrBuf(&DetailList);
+}
+
+
+void AddPortError(char *Port, char *ErrorMessage)
+{
+ long len;
+
+ DetailErrorFlags |= ERR_PORT;
+
+ len = StrLength(errormessages);
+ if (len > 0) StrBufAppendBufPlain(errormessages, HKEY("; "), 0);
+ else errormessages = NewStrBuf();
+ StrBufAppendBufPlain(errormessages, ErrorMessage, -1, 0);
+
+
+ len = StrLength(portlist);
+ if (len > 0) StrBufAppendBufPlain(portlist, HKEY(";"), 0);
+ else portlist = NewStrBuf();
+ StrBufAppendBufPlain(portlist, Port, -1, 0);
+}
+
+
+int DLoader_Exec_Cmd(char *cmdbuf)
+{
+ void *vP;
+ ProtoFunctionHook *p;
+
+ if (GetHash(ProtoHookList, cmdbuf, 4, &vP) && (vP != NULL)) {
+ p = (ProtoFunctionHook*) vP;
+ p->handler(&cmdbuf[5]);
+ return 1;
+ }
+ return 0;
+}
+
+
+void CtdlRegisterProtoHook(void (*handler) (char *), char *cmd, char *desc)
+{
+ ProtoFunctionHook *p;
+
+ if (ProtoHookList == NULL)
+ ProtoHookList = NewHash (1, FourHash);
+
+
+ p = (ProtoFunctionHook *)
+ malloc(sizeof(ProtoFunctionHook));
+
+ if (p == NULL) {
+ fprintf(stderr, "can't malloc new ProtoFunctionHook\n");
+ exit(EXIT_FAILURE);
+ }
+ p->handler = handler;
+ p->cmd = cmd;
+ p->desc = desc;
+
+ Put(ProtoHookList, cmd, 4, p, NULL);
+ syslog(LOG_DEBUG, "extensions: registered server command %s (%s)", cmd, desc);
+}
+
+
+void CtdlRegisterSessionHook(void (*fcn_ptr) (void), int EventType, int Priority)
+{
+ SessionFunctionHook *newfcn;
+
+ newfcn = (SessionFunctionHook *)
+ malloc(sizeof(SessionFunctionHook));
+ newfcn->Priority = Priority;
+ newfcn->h_function_pointer = fcn_ptr;
+ newfcn->eventtype = EventType;
+
+ SessionFunctionHook **pfcn;
+ pfcn = &SessionHookTable;
+ while ((*pfcn != NULL) &&
+ ((*pfcn)->Priority < newfcn->Priority) &&
+ ((*pfcn)->next != NULL))
+ pfcn = &(*pfcn)->next;
+
+ newfcn->next = *pfcn;
+ *pfcn = newfcn;
+
+ syslog(LOG_DEBUG, "extensions: registered a new session function (type %d Priority %d)", EventType, Priority);
+}
+
+
+void CtdlUnregisterSessionHook(void (*fcn_ptr) (void), int EventType)
+{
+ SessionFunctionHook *cur, *p, *last;
+ last = NULL;
+ cur = SessionHookTable;
+ while (cur != NULL) {
+ if ((fcn_ptr == cur->h_function_pointer) &&
+ (EventType == cur->eventtype))
+ {
+ syslog(LOG_DEBUG, "extensions: unregistered session function (type %d)", EventType);
+ p = cur->next;
+
+ free(cur);
+ cur = NULL;
+
+ if (last != NULL)
+ last->next = p;
+ else
+ SessionHookTable = p;
+ cur = p;
+ }
+ else {
+ last = cur;
+ cur = cur->next;
+ }
+ }
+}
+
+
+void CtdlRegisterUserHook(void (*fcn_ptr) (ctdluser *), int EventType)
+{
+
+ UserFunctionHook *newfcn;
+
+ newfcn = (UserFunctionHook *)
+ malloc(sizeof(UserFunctionHook));
+ newfcn->next = UserHookTable;
+ newfcn->h_function_pointer = fcn_ptr;
+ newfcn->eventtype = EventType;
+ UserHookTable = newfcn;
+
+ syslog(LOG_DEBUG, "extensions: registered a new user function (type %d)",
+ EventType);
+}
+
+
+void CtdlUnregisterUserHook(void (*fcn_ptr) (struct ctdluser *), int EventType)
+{
+ UserFunctionHook *cur, *p, *last;
+ last = NULL;
+ cur = UserHookTable;
+ while (cur != NULL) {
+ if ((fcn_ptr == cur->h_function_pointer) &&
+ (EventType == cur->eventtype))
+ {
+ syslog(LOG_DEBUG, "extensions: unregistered user function (type %d)", EventType);
+ p = cur->next;
+
+ free(cur);
+ cur = NULL;
+
+ if (last != NULL)
+ last->next = p;
+ else
+ UserHookTable = p;
+ cur = p;
+ }
+ else {
+ last = cur;
+ cur = cur->next;
+ }
+ }
+}
+
+
+void CtdlRegisterMessageHook(int (*handler)(struct CtdlMessage *, struct recptypes *), int EventType)
+{
+
+ MessageFunctionHook *newfcn;
+
+ newfcn = (MessageFunctionHook *)
+ malloc(sizeof(MessageFunctionHook));
+ newfcn->next = MessageHookTable;
+ newfcn->h_function_pointer = handler;
+ newfcn->eventtype = EventType;
+ MessageHookTable = newfcn;
+
+ syslog(LOG_DEBUG, "extensions: registered a new message function (type %d)", EventType);
+}
+
+
+void CtdlUnregisterMessageHook(int (*handler)(struct CtdlMessage *, struct recptypes *), int EventType)
+{
+ MessageFunctionHook *cur, *p, *last;
+ last = NULL;
+ cur = MessageHookTable;
+ while (cur != NULL) {
+ if ((handler == cur->h_function_pointer) &&
+ (EventType == cur->eventtype))
+ {
+ syslog(LOG_DEBUG, "extensions: unregistered message function (type %d)", EventType);
+ p = cur->next;
+ free(cur);
+ cur = NULL;
+
+ if (last != NULL)
+ last->next = p;
+ else
+ MessageHookTable = p;
+ cur = p;
+ }
+ else {
+ last = cur;
+ cur = cur->next;
+ }
+ }
+}
+
+
+void CtdlRegisterRoomHook(int (*fcn_ptr)(struct ctdlroom *))
+{
+ RoomFunctionHook *newfcn;
+
+ newfcn = (RoomFunctionHook *)
+ malloc(sizeof(RoomFunctionHook));
+ newfcn->next = RoomHookTable;
+ newfcn->fcn_ptr = fcn_ptr;
+ RoomHookTable = newfcn;
+
+ syslog(LOG_DEBUG, "extensions: registered a new room function");
+}
+
+
+void CtdlUnregisterRoomHook(int (*fcn_ptr)(struct ctdlroom *))
+{
+ RoomFunctionHook *cur, *p, *last;
+ last = NULL;
+ cur = RoomHookTable;
+ while (cur != NULL)
+ {
+ if (fcn_ptr == cur->fcn_ptr) {
+ syslog(LOG_DEBUG, "extensions: unregistered room function");
+ p = cur->next;
+
+ free(cur);
+ cur = NULL;
+
+ if (last != NULL)
+ last->next = p;
+ else
+ RoomHookTable = p;
+ cur = p;
+ }
+ else {
+ last = cur;
+ cur = cur->next;
+ }
+ }
+}
+
+
+void CtdlRegisterDeleteHook(void (*handler)(char *, long) )
+{
+ DeleteFunctionHook *newfcn;
+
+ newfcn = (DeleteFunctionHook *)
+ malloc(sizeof(DeleteFunctionHook));
+ newfcn->next = DeleteHookTable;
+ newfcn->h_function_pointer = handler;
+ DeleteHookTable = newfcn;
+
+ syslog(LOG_DEBUG, "extensions: registered a new delete function");
+}
+
+
+void CtdlUnregisterDeleteHook(void (*handler)(char *, long) )
+{
+ DeleteFunctionHook *cur, *p, *last;
+
+ last = NULL;
+ cur = DeleteHookTable;
+ while (cur != NULL) {
+ if (handler == cur->h_function_pointer )
+ {
+ syslog(LOG_DEBUG, "extensions: unregistered delete function");
+ p = cur->next;
+ free(cur);
+
+ if (last != NULL)
+ last->next = p;
+ else
+ DeleteHookTable = p;
+
+ cur = p;
+ }
+ else {
+ last = cur;
+ cur = cur->next;
+ }
+ }
+}
+
+
+void CtdlRegisterFixedOutputHook(char *content_type, void (*handler)(char *, int) )
+{
+ FixedOutputHook *newfcn;
+
+ newfcn = (FixedOutputHook *)
+ malloc(sizeof(FixedOutputHook));
+ newfcn->next = FixedOutputTable;
+ newfcn->h_function_pointer = handler;
+ safestrncpy(newfcn->content_type, content_type, sizeof newfcn->content_type);
+ FixedOutputTable = newfcn;
+
+ syslog(LOG_DEBUG, "extensions: registered a new fixed output function for %s", newfcn->content_type);
+}
+
+
+void CtdlUnregisterFixedOutputHook(char *content_type)
+{
+ FixedOutputHook *cur, *p, *last;
+
+ last = NULL;
+ cur = FixedOutputTable;
+ while (cur != NULL) {
+ /* This will also remove duplicates if any */
+ if (!strcasecmp(content_type, cur->content_type)) {
+ syslog(LOG_DEBUG, "extensions: unregistered fixed output function for %s", content_type);
+ p = cur->next;
+ free(cur);
+
+ if (last != NULL)
+ last->next = p;
+ else
+ FixedOutputTable = p;
+
+ cur = p;
+ }
+ else
+ {
+ last = cur;
+ cur = cur->next;
+ }
+ }
+}
+
+
+/* returns nonzero if we found a hook and used it */
+int PerformFixedOutputHooks(char *content_type, char *content, int content_length)
+{
+ FixedOutputHook *fcn;
+
+ for (fcn = FixedOutputTable; fcn != NULL; fcn = fcn->next) {
+ if (!strcasecmp(content_type, fcn->content_type)) {
+ (*fcn->h_function_pointer) (content, content_length);
+ return(1);
+ }
+ }
+ return(0);
+}
+
+
+void CtdlRegisterXmsgHook(int (*fcn_ptr) (char *, char *, char *, char *), int order)
+{
+
+ XmsgFunctionHook *newfcn;
+
+ newfcn = (XmsgFunctionHook *) malloc(sizeof(XmsgFunctionHook));
+ newfcn->next = XmsgHookTable;
+ newfcn->order = order;
+ newfcn->h_function_pointer = fcn_ptr;
+ XmsgHookTable = newfcn;
+ syslog(LOG_DEBUG, "extensions: registered a new x-msg function (priority %d)", order);
+}
+
+
+void CtdlUnregisterXmsgHook(int (*fcn_ptr) (char *, char *, char *, char *), int order)
+{
+ XmsgFunctionHook *cur, *p, *last;
+
+ last = NULL;
+ cur = XmsgHookTable;
+ while (cur != NULL) {
+ /* This will also remove duplicates if any */
+ if (fcn_ptr == cur->h_function_pointer &&
+ order == cur->order) {
+ syslog(LOG_DEBUG, "extensions: unregistered x-msg function (priority %d)", order);
+ p = cur->next;
+ free(cur);
+
+ if (last != NULL) {
+ last->next = p;
+ }
+ else {
+ XmsgHookTable = p;
+ }
+ cur = p;
+ }
+ else {
+ last = cur;
+ cur = cur->next;
+ }
+ }
+}
+
+
+void CtdlRegisterServiceHook(int tcp_port,
+ char *sockpath,
+ void (*h_greeting_function) (void),
+ void (*h_command_function) (void),
+ void (*h_async_function) (void),
+ const char *ServiceName)
+{
+ ServiceFunctionHook *newfcn;
+ char *message;
+
+ newfcn = (ServiceFunctionHook *) malloc(sizeof(ServiceFunctionHook));
+ message = (char*) malloc (SIZ + SIZ);
+
+ newfcn->next = ServiceHookTable;
+ newfcn->tcp_port = tcp_port;
+ newfcn->sockpath = sockpath;
+ newfcn->h_greeting_function = h_greeting_function;
+ newfcn->h_command_function = h_command_function;
+ newfcn->h_async_function = h_async_function;
+ newfcn->ServiceName = ServiceName;
+
+ if (sockpath != NULL) {
+ newfcn->msock = ctdl_uds_server(sockpath, CtdlGetConfigInt("c_maxsessions"));
+ snprintf(message, SIZ, "extensions: unix domain socket '%s': ", sockpath);
+ }
+ else if (tcp_port <= 0) { /* port -1 to disable */
+ syslog(LOG_INFO, "extensions: service %s has been manually disabled, skipping", ServiceName);
+ free (message);
+ free(newfcn);
+ return;
+ }
+ else {
+ newfcn->msock = ctdl_tcp_server(CtdlGetConfigStr("c_ip_addr"), tcp_port, CtdlGetConfigInt("c_maxsessions"));
+ snprintf(message, SIZ, "extensions: TCP port %s:%d: (%s) ",
+ CtdlGetConfigStr("c_ip_addr"), tcp_port, ServiceName);
+ }
+
+ if (newfcn->msock > 0) {
+ ServiceHookTable = newfcn;
+ strcat(message, "registered.");
+ syslog(LOG_INFO, "%s", message);
+ }
+ else {
+ AddPortError(message, "failed");
+ strcat(message, "FAILED.");
+ syslog(LOG_ERR, "%s", message);
+ free(newfcn);
+ }
+ free(message);
+}
+
+
+void CtdlUnregisterServiceHook(int tcp_port, char *sockpath,
+ void (*h_greeting_function) (void),
+ void (*h_command_function) (void),
+ void (*h_async_function) (void)
+ )
+{
+ ServiceFunctionHook *cur, *p, *last;
+
+ last = NULL;
+ cur = ServiceHookTable;
+ while (cur != NULL) {
+ /* This will also remove duplicates if any */
+ if (h_greeting_function == cur->h_greeting_function &&
+ h_command_function == cur->h_command_function &&
+ h_async_function == cur->h_async_function &&
+ tcp_port == cur->tcp_port &&
+ !(sockpath && cur->sockpath && strcmp(sockpath, cur->sockpath)) )
+ {
+ if (cur->msock > 0)
+ close(cur->msock);
+ if (sockpath) {
+ syslog(LOG_INFO, "extensions: closed UNIX domain socket %s", sockpath);
+ unlink(sockpath);
+ } else if (tcp_port) {
+ syslog(LOG_INFO, "extensions: closed TCP port %d", tcp_port);
+ } else {
+ syslog(LOG_INFO, "extensions: unregistered service \"%s\"", cur->ServiceName);
+ }
+ p = cur->next;
+ free(cur);
+ if (last != NULL)
+ last->next = p;
+ else
+ ServiceHookTable = p;
+ cur = p;
+ }
+ else {
+ last = cur;
+ cur = cur->next;
+ }
+ }
+}
+
+
+void CtdlShutdownServiceHooks(void)
+{
+ /* sort of a duplicate of close_masters() but called earlier */
+ ServiceFunctionHook *cur;
+
+ cur = ServiceHookTable;
+ while (cur != NULL)
+ {
+ if (cur->msock != -1)
+ {
+ close(cur->msock);
+ cur->msock = -1;
+ if (cur->sockpath != NULL){
+ syslog(LOG_INFO, "extensions: [%s] Closed UNIX domain socket %s", cur->ServiceName, cur->sockpath);
+ unlink(cur->sockpath);
+ } else {
+ syslog(LOG_INFO, "extensions: [%s] closing service", cur->ServiceName);
+ }
+ }
+ cur = cur->next;
+ }
+}
+
+
+void CtdlRegisterSearchFuncHook(void (*fcn_ptr)(int *, long **, const char *), char *name)
+{
+ SearchFunctionHook *newfcn;
+
+ if (!name || !fcn_ptr) {
+ return;
+ }
+
+ newfcn = (SearchFunctionHook *)
+ malloc(sizeof(SearchFunctionHook));
+ newfcn->next = SearchFunctionHookTable;
+ newfcn->name = name;
+ newfcn->fcn_ptr = fcn_ptr;
+ SearchFunctionHookTable = newfcn;
+
+ syslog(LOG_DEBUG, "extensions: registered a new search function (%s)", name);
+}
+
+void CtdlUnregisterSearchFuncHook(void (*fcn_ptr)(int *, long **, const char *), char *name)
+{
+ SearchFunctionHook *cur, *p, *last;
+
+ last = NULL;
+ cur = SearchFunctionHookTable;
+ while (cur != NULL) {
+ if (fcn_ptr &&
+ (cur->fcn_ptr == fcn_ptr) &&
+ name && !strcmp(name, cur->name))
+ {
+ syslog(LOG_DEBUG, "extensions: unregistered search function(%s)", name);
+ p = cur->next;
+ free (cur);
+ if (last != NULL)
+ last->next = p;
+ else
+ SearchFunctionHookTable = p;
+ cur = p;
+ }
+ else {
+ last = cur;
+ cur = cur->next;
+ }
+ }
+}
+
+
+void CtdlModuleDoSearch(int *num_msgs, long **search_msgs, const char *search_string, const char *func_name)
+{
+ SearchFunctionHook *fcn = NULL;
+
+ for (fcn = SearchFunctionHookTable; fcn != NULL; fcn = fcn->next) {
+ if (!func_name || !strcmp(func_name, fcn->name)) {
+ (*fcn->fcn_ptr) (num_msgs, search_msgs, search_string);
+ return;
+ }
+ }
+ *num_msgs = 0;
+}
+
+
+void PerformSessionHooks(int EventType)
+{
+ SessionFunctionHook *fcn = NULL;
+
+ for (fcn = SessionHookTable; fcn != NULL; fcn = fcn->next) {
+ if (fcn->eventtype == EventType) {
+ if (EventType == EVT_TIMER) {
+ pthread_setspecific(MyConKey, NULL); /* for every hook */
+ }
+ (*fcn->h_function_pointer) ();
+ }
+ }
+}
+
+void PerformUserHooks(ctdluser *usbuf, int EventType)
+{
+ UserFunctionHook *fcn = NULL;
+
+ for (fcn = UserHookTable; fcn != NULL; fcn = fcn->next) {
+ if (fcn->eventtype == EventType) {
+ (*fcn->h_function_pointer) (usbuf);
+ }
+ }
+}
+
+int PerformMessageHooks(struct CtdlMessage *msg, struct recptypes *recps, int EventType)
+{
+ MessageFunctionHook *fcn = NULL;
+ int total_retval = 0;
+
+ /* Other code may elect to protect this message from server-side
+ * handlers; if this is the case, don't do anything.
+ */
+ if (msg->cm_flags & CM_SKIP_HOOKS) {
+ return(0);
+ }
+
+ /* Otherwise, run all the hooks appropriate to this event type.
+ */
+ for (fcn = MessageHookTable; fcn != NULL; fcn = fcn->next) {
+ if (fcn->eventtype == EventType) {
+ total_retval = total_retval + (*fcn->h_function_pointer) (msg, recps);
+ }
+ }
+
+ /* Return the sum of the return codes from the hook functions. If
+ * this is an EVT_BEFORESAVE event, a nonzero return code will cause
+ * the save operation to abort.
+ */
+ return total_retval;
+}
+
+
+int PerformRoomHooks(struct ctdlroom *target_room)
+{
+ RoomFunctionHook *fcn;
+ int total_retval = 0;
+
+ syslog(LOG_DEBUG, "extensions: performing room hooks for <%s>", target_room->QRname);
+
+ for (fcn = RoomHookTable; fcn != NULL; fcn = fcn->next) {
+ total_retval = total_retval + (*fcn->fcn_ptr) (target_room);
+ }
+
+ /* Return the sum of the return codes from the hook functions.
+ */
+ return total_retval;
+}
+
+
+void PerformDeleteHooks(char *room, long msgnum)
+{
+ DeleteFunctionHook *fcn;
+
+ for (fcn = DeleteHookTable; fcn != NULL; fcn = fcn->next) {
+ (*fcn->h_function_pointer) (room, msgnum);
+ }
+}
+
+
+int PerformXmsgHooks(char *sender, char *sender_email, char *recp, char *msg)
+{
+ XmsgFunctionHook *fcn;
+ int total_sent = 0;
+ int p;
+
+ for (p=0; p<MAX_XMSG_PRI; ++p) {
+ for (fcn = XmsgHookTable; fcn != NULL; fcn = fcn->next) {
+ if (fcn->order == p) {
+ total_sent +=
+ (*fcn->h_function_pointer)
+ (sender, sender_email, recp, msg);
+ }
+ }
+ /* Break out of the loop if a higher-priority function
+ * successfully delivered the message. This prevents duplicate
+ * deliveries to local users simultaneously signed onto
+ * remote services.
+ */
+ if (total_sent) break;
+ }
+ return total_sent;
+}
+
+
+/*
+ * Dirty hack until we impliment a hook mechanism for this
+ */
+void CtdlModuleStartCryptoMsgs(char *ok_response, char *nosup_response, char *error_response)
+{
+#ifdef HAVE_OPENSSL
+ CtdlStartTLS (ok_response, nosup_response, error_response);
+#endif
+}
+
+
+CTDL_MODULE_INIT(modules)
+{
+ if (!threading) {
+ }
+ return "modules";
+}
--- /dev/null
+
+#ifndef SERV_EXTENSIONS_H
+#define SERV_EXTENSIONS_H
+
+#include "server.h"
+
+/*
+ * ServiceFunctionHook extensions are used for hooks which implement various
+ * protocols (either on TCP or on unix domain sockets) directly in the Citadel server.
+ */
+typedef struct ServiceFunctionHook ServiceFunctionHook;
+struct ServiceFunctionHook {
+ ServiceFunctionHook *next;
+ int tcp_port;
+ char *sockpath;
+ void (*h_greeting_function) (void) ;
+ void (*h_command_function) (void) ;
+ void (*h_async_function) (void) ;
+ int msock;
+ const char* ServiceName; /* this is just for debugging and logging purposes. */
+};
+extern ServiceFunctionHook *ServiceHookTable;
+
+void initialize_server_extensions(void);
+int DLoader_Exec_Cmd(char *cmdbuf);
+char *Dynamic_Module_Init(void);
+
+void PerformSessionHooks(int EventType);
+
+void PerformUserHooks(struct ctdluser *usbuf, int EventType);
+
+int PerformXmsgHooks(char *, char *, char *, char *);
+
+int PerformMessageHooks(struct CtdlMessage *, struct recptypes *recps, int EventType);
+
+int PerformRoomHooks(struct ctdlroom *);
+
+void PerformDeleteHooks(char *, long);
+
+
+
+
+
+int PerformFixedOutputHooks(char *, char *, int);
+
+void netcfg_keyname(char *keybuf, long roomnum);
+
+#endif /* SERV_EXTENSIONS_H */
--- /dev/null
+void extract_inet_email_addrs(char *, size_t, char *, size_t, struct vCard *v, int local_addrs_only);
+struct vCard *vcard_get_user(struct ctdluser *u);
+
+enum {
+ V2L_WRITE,
+ V2L_DELETE
+};
+
--- /dev/null
+/*
+ * Main declarations file for the Citadel server
+ *
+ * Copyright (c) 1987-2022 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.
+ */
+
+#ifndef SERVER_H
+#define SERVER_H
+
+#ifdef __GNUC__
+#define INLINE __inline__
+#else
+#define INLINE
+#endif
+
+#include "citadel.h"
+#ifdef HAVE_OPENSSL
+#define OPENSSL_NO_KRB5 /* work around redhat b0rken ssl headers */
+#include <openssl/ssl.h>
+#endif
+
+/*
+ * New format for a message in memory
+ */
+struct CtdlMessage {
+ int cm_magic; /* Self-check (NOT SAVED TO DISK) */
+ char cm_anon_type; /* Anonymous or author-visible */
+ char cm_format_type; /* Format type */
+ char *cm_fields[256]; /* Data fields */
+ long cm_lengths[256]; /* size of datafields */
+ unsigned int cm_flags; /* How to handle (NOT SAVED TO DISK) */
+};
+
+#define CTDLMESSAGE_MAGIC 0x159d
+#define CM_SKIP_HOOKS 0x01 /* Don't run server-side handlers */
+
+
+/* Data structure returned by validate_recipients() */
+struct recptypes {
+ int recptypes_magic;
+ int num_local;
+ int num_internet;
+ int num_room;
+ int num_error;
+ char *errormsg;
+ char *recp_local;
+ char *recp_internet;
+ char *recp_room;
+ char *recp_orgroom;
+ char *display_recp;
+ char *bounce_to;
+ char *envelope_from;
+ char *sending_room;
+};
+
+#define RECPTYPES_MAGIC 0xfeeb
+
+#define CTDLEXIT_SHUTDOWN 0 // Normal shutdown; do NOT auto-restart
+
+/*
+ * Exit codes 101 through 109 are used for conditions in which
+ * we deliberately do NOT want the service to automatically
+ * restart.
+ */
+#define CTDLEXIT_CONFIG 101 // Could not read system configuration
+#define CTDLEXIT_HOME 103 // Citadel home directory not found
+#define CTDLEXIT_DB 105 // Unable to initialize database
+#define CTDLEXIT_LIBCITADEL 106 // Incorrect version of libcitadel
+#define CTDL_EXIT_UNSUP_AUTH 107 // Unsupported auth mode configured
+#define CTDLEXIT_UNUSER 108 // Could not determine uid to run as
+#define CTDLEXIT_CRYPTO 109 // Problem initializing SSL or TLS
+
+/*
+ * Reasons why a session would be terminated (set CC->kill_me to these values)
+ */
+enum {
+ KILLME_NOT,
+ KILLME_UNKNOWN,
+ KILLME_CLIENT_LOGGED_OUT,
+ KILLME_IDLE,
+ KILLME_CLIENT_DISCONNECTED,
+ KILLME_AUTHFAILED,
+ KILLME_SERVER_SHUTTING_DOWN,
+ KILLME_MAX_SESSIONS_EXCEEDED,
+ KILLME_ADMIN_TERMINATE,
+ KILLME_SELECT_INTERRUPTED,
+ KILLME_SELECT_FAILED,
+ KILLME_WRITE_FAILED,
+ KILLME_SIMULATION_WORKER,
+ KILLME_NOLOGIN,
+ KILLME_NO_CRYPTO,
+ KILLME_READSTRING_FAILED,
+ KILLME_MALLOC_FAILED,
+ KILLME_QUOTA,
+ KILLME_READ_FAILED,
+ KILLME_SPAMMER,
+ KILLME_XML_PARSER
+};
+
+
+#define CS_STEALTH 1 /* stealth mode */
+#define CS_CHAT 2 /* chat mode */
+#define CS_POSTING 4 /* Posting */
+
+extern int ScheduledShutdown;
+extern uid_t ctdluid;
+extern int sanity_diag_mode;
+
+struct ExpressMessage {
+ struct ExpressMessage *next;
+ time_t timestamp; /* When this message was sent */
+ unsigned flags; /* Special instructions */
+ char sender[256]; /* Name of sending user */
+ char sender_email[256]; /* Email or JID of sending user */
+ char *text; /* Message text (if applicable) */
+};
+
+#define EM_BROADCAST 1 /* Broadcast message */
+#define EM_GO_AWAY 2 /* Server requests client log off */
+#define EM_CHAT 4 /* Server requests client enter chat */
+
+/*
+ * Various things we need to lock and unlock
+ */
+enum {
+ S_USERS,
+ S_ROOMS,
+ S_SESSION_TABLE,
+ S_FLOORTAB,
+ S_CHATQUEUE,
+ S_CONTROL,
+ S_NETDB,
+ S_SUPPMSGMAIN,
+ S_CONFIG,
+ S_HOUSEKEEPING,
+ S_DIRECTORY,
+ S_NETCONFIGS,
+ S_FLOORCACHE,
+ S_ATBF,
+ S_JOURNAL_QUEUE,
+ S_CHKPWD,
+ S_LOG,
+ S_NETSPOOL,
+ S_XMPP_QUEUE,
+ S_SCHEDULE_LIST,
+ S_SINGLE_USER,
+ S_LDAP,
+ S_IM_LOGS,
+ S_OPENSSL,
+ MAX_SEMAPHORES
+};
+
+
+/*
+ * message transfer formats
+ */
+enum {
+ MT_CITADEL, /* Citadel proprietary */
+ MT_RFC822, /* RFC822 */
+ MT_MIME, /* MIME-formatted message */
+ MT_DOWNLOAD, /* Download a component */
+ MT_SPEW_SECTION /* Download a component in a single operation */
+};
+
+/*
+ * Message format types in the database
+ */
+#define FMT_CITADEL 0 /* Citadel vari-format (proprietary) */
+#define FMT_FIXED 1 /* Fixed format (proprietary) */
+#define FMT_RFC822 4 /* Standard (headers are in M field) */
+
+
+/*
+ * Citadel DataBases (define one for each cdb we need to open)
+ */
+enum {
+ CDB_MSGMAIN, /* message base */
+ CDB_USERS, /* user file */
+ CDB_ROOMS, /* room index */
+ CDB_FLOORTAB, /* floor index */
+ CDB_MSGLISTS, /* room message lists */
+ CDB_VISIT, /* user/room relationships */
+ CDB_DIRECTORY, /* address book directory */
+ CDB_USETABLE, /* network use table */
+ CDB_BIGMSGS, /* larger message bodies */
+ CDB_FULLTEXT, /* full text search index */
+ CDB_EUIDINDEX, /* locate msgs by EUID */
+ CDB_USERSBYNUMBER, /* index of users by number */
+ CDB_EXTAUTH, /* associates OpenIDs with users */
+ CDB_CONFIG, /* system configuration database */
+ MAXCDB /* total number of CDB's defined */
+};
+
+struct cdbdata {
+ size_t len;
+ char *ptr;
+};
+
+
+/*
+ * Event types can't be enum'ed, because they must remain consistent between
+ * builds (to allow for binary modules built somewhere else)
+ */
+#define EVT_STOP 0 /* Session is terminating */
+#define EVT_START 1 /* Session is starting */
+#define EVT_LOGIN 2 /* A user is logging in */
+#define EVT_NEWROOM 3 /* Changing rooms */
+#define EVT_LOGOUT 4 /* A user is logging out */
+#define EVT_SETPASS 5 /* Setting or changing password */
+#define EVT_CMD 6 /* Called after each server command */
+#define EVT_RWHO 7 /* An RWHO command is being executed */
+#define EVT_ASYNC 8 /* Doing asynchronous messages */
+#define EVT_STEALTH 9 /* Entering stealth mode */
+#define EVT_UNSTEALTH 10 /* Exiting stealth mode */
+
+#define EVT_TIMER 50 /* Timer events are called once per minute
+ and are not tied to any session */
+#define EVT_HOUSE 51 /* as needed houskeeping stuff */
+#define EVT_SHUTDOWN 52 /* Server is shutting down */
+
+#define EVT_PURGEUSER 100 /* Deleting a user */
+#define EVT_NEWUSER 102 /* Creating a user */
+
+#define EVT_BEFORESAVE 201
+#define EVT_AFTERSAVE 202
+#define EVT_SMTPSCAN 203 /* called before submitting a msg from SMTP */
+#define EVT_AFTERUSRMBOXSAVE 204 /* called afte a message was saved into a users inbox */
+/* Priority levels for paging functions (lower is better) */
+enum {
+ XMSG_PRI_LOCAL, /* Other users on -this- server */
+ XMSG_PRI_REMOTE, /* Other users on a Citadel network (future) */
+ XMSG_PRI_FOREIGN, /* Contacts on foreign instant message hosts */
+ MAX_XMSG_PRI
+};
+
+
+/* Defines the relationship of a user to a particular room */
+typedef struct __visit {
+ long v_roomnum;
+ long v_roomgen;
+ long v_usernum;
+ long v_lastseen;
+ unsigned int v_flags;
+ char v_seen[SIZ];
+ char v_answered[SIZ];
+ int v_view;
+} visit;
+
+#define V_FORGET 1 /* User has zapped this room */
+#define V_LOCKOUT 2 /* User is locked out of this room */
+#define V_ACCESS 4 /* Access is granted to this room */
+
+
+/* Supplementary data for a message on disk
+ * These are kept separate from the message itself for one of two reasons:
+ * 1. Either their values may change at some point after initial save, or
+ * 2. They are merely caches of data which exist somewhere else, for speed.
+ * DO NOT PUT BIG DATA IN HERE ... we need this struct to be tiny for lots of quick r/w
+ */
+struct MetaData {
+ long meta_msgnum; /* Message number in *local* message base */
+ int meta_refcount; /* Number of rooms pointing to this msg */
+ char meta_content_type[64]; /* Cached MIME content-type */
+ long meta_rfc822_length; /* Cache of RFC822-translated msg length */
+};
+
+
+/* Calls to AdjRefCount() are queued and deferred, so the user doesn't
+ * have to wait for various disk-intensive operations to complete synchronously.
+ * This is the record format.
+ */
+struct arcq {
+ long arcq_msgnum; /* Message number being adjusted */
+ int arcq_delta; /* Adjustment ( usually 1 or -1 ) */
+};
+
+
+/*
+ * Serialization routines use this struct to return a pointer and a length
+ */
+struct ser_ret {
+ size_t len;
+ unsigned char *ser;
+};
+
+
+/*
+ * The S_USETABLE database is used in several modules now, so we define its format here.
+ */
+struct UseTable {
+ char ut_msgid[SIZ];
+ time_t ut_timestamp;
+};
+
+
+/*
+ * These one-byte field headers are found in the Citadel message store.
+ */
+typedef enum _MsgField {
+ eAuthor = 'A',
+ eBig_message = 'B',
+ eExclusiveID = 'E',
+ erFc822Addr = 'F',
+ emessageId = 'I',
+ eJournal = 'J',
+ eReplyTo = 'K',
+ eListID = 'L',
+ eMesageText = 'M',
+ eOriginalRoom = 'O',
+ eMessagePath = 'P',
+ eRecipient = 'R',
+ eTimestamp = 'T',
+ eMsgSubject = 'U',
+ eenVelopeTo = 'V',
+ eWeferences = 'W',
+ eCarbonCopY = 'Y',
+ eErrorMsg = '0',
+ eSuppressIdx = '1',
+ eExtnotify = '2',
+ eVltMsgNum = '3'
+} eMsgField;
+
+#endif /* SERVER_H */
--- /dev/null
+// citserver's main() function lives here.
+//
+// Copyright (c) 1987-2022 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.
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <grp.h>
+#include <sys/file.h>
+#include <libcitadel.h>
+#include "citserver.h"
+#include "modules_init.h"
+#include "config.h"
+#include "control.h"
+#include "serv_extensions.h"
+#include "citadel_dirs.h"
+#include "user_ops.h"
+
+uid_t ctdluid = 0;
+const char *CitadelServiceUDS="citadel-UDS";
+const char *CitadelServiceTCP="citadel-TCP";
+int sanity_diag_mode = 0;
+
+
+// Create or remove a lock file, so we only have one Citadel Server running at a time.
+// Set 'op' to nonzero to lock, zero to unlock.
+void ctdl_lockfile(int op) {
+ static char lockfilename[PATH_MAX];
+ static FILE *fp;
+
+ if (op) {
+ syslog(LOG_DEBUG, "main: creating lockfile");
+ snprintf(lockfilename, sizeof lockfilename, "%s/citadel.lock", ctdl_run_dir);
+ fp = fopen(lockfilename, "w");
+ if (!fp) {
+ syslog(LOG_ERR, "%s: %m", lockfilename);
+ exit(CTDLEXIT_DB);
+ }
+ if (flock(fileno(fp), (LOCK_EX|LOCK_NB)) != 0) {
+ syslog(LOG_ERR, "main: cannot lock %s , is another citserver running?", lockfilename);
+ exit(CTDLEXIT_DB);
+ }
+ return;
+ }
+
+ syslog(LOG_DEBUG, "main: removing lockfile");
+ unlink(lockfilename);
+ flock(fileno(fp), LOCK_UN);
+ fclose(fp);
+}
+
+
+// Here's where it all begins.
+int main(int argc, char **argv) {
+
+ char facility[32];
+ int a; // General-purpose variables
+ struct passwd pw, *pwp = NULL;
+ char pwbuf[SIZ];
+ int drop_root_perms = 1;
+ int max_log_level = LOG_INFO;
+ char *ctdldir = CTDLDIR;
+ int syslog_facility = LOG_DAEMON;
+ uid_t u = 0;
+ struct passwd *p = NULL;
+#ifdef HAVE_RUN_DIR
+ struct stat filestats;
+#endif
+
+ // Tell 'em who's in da house
+ syslog(LOG_INFO, " ");
+ syslog(LOG_INFO, " ");
+ syslog(LOG_INFO, "*** Citadel server engine ***\n");
+ syslog(LOG_INFO, "Version %d (build %s) ***", REV_LEVEL, BUILD_ID);
+ syslog(LOG_INFO, "Copyright (C) 1987-2022 by the Citadel development team.");
+ syslog(LOG_INFO, " ");
+ syslog(LOG_INFO, "This program is open source software: you can redistribute it and/or");
+ syslog(LOG_INFO, "modify it under the terms of the GNU General Public License, version 3.");
+ syslog(LOG_INFO, " ");
+ syslog(LOG_INFO, "This program is distributed in the hope that it will be useful,");
+ syslog(LOG_INFO, "but WITHOUT ANY WARRANTY; without even the implied warranty of");
+ syslog(LOG_INFO, "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the");
+ syslog(LOG_INFO, "GNU General Public License for more details.");
+ syslog(LOG_INFO, " ");
+ syslog(LOG_INFO, "%s", libcitadel_version_string());
+
+ // parse command-line arguments
+ while ((a=getopt(argc, argv, "cl:dh:x:t:B:Dru:s:")) != EOF) switch(a) {
+
+ // test this binary for compatibility and exit
+ case 'c':
+ fprintf(stderr, "%s: binary compatibility confirmed\n", argv[0]);
+ exit(0);
+ break;
+
+ // identify the desired syslog facility
+ case 'l':
+ safestrncpy(facility, optarg, sizeof(facility));
+ syslog_facility = SyslogFacility(facility);
+ break;
+
+ // run in the background if -d was specified
+ case 'd':
+ running_as_daemon = 1;
+ break;
+
+ // specify the data directory
+ case 'h':
+ ctdldir = optarg;
+ break;
+
+ // identify the desired logging severity level
+ case 'x':
+ max_log_level = atoi(optarg);
+ break;
+
+ // deprecated
+ case 't':
+ break;
+
+ // deprecated
+ case 'B':
+ break;
+
+ // deprecated
+ case 'D':
+ break;
+
+ // -r tells the server not to drop root permissions.
+ // Don't use this unless you know what you're doing.
+ case 'r':
+ drop_root_perms = 0;
+ break;
+
+ // -u tells the server what uid to run under...
+ case 'u':
+ u = atoi(optarg);
+ if (u > 0) {
+ ctdluid = u;
+ }
+ else {
+ p = getpwnam(optarg);
+ if (p) {
+ u = p->pw_uid;
+ }
+ }
+ if (u > 0) {
+ ctdluid = u;
+ }
+ break;
+
+ // -s tells the server to behave differently during sanity checks
+ case 's':
+ sanity_diag_mode = atoi(optarg);
+ break;
+
+ // any other parameter makes it crash and burn
+ default:
+ fprintf(stderr, "citserver: usage: "
+ "citserver "
+ "[-l LogFacility] "
+ "[-x MaxLogLevel] "
+ "[-d] [-r] "
+ "[-u user] "
+ "[-h HomeDir]\n"
+ );
+ exit(1);
+ }
+
+ if (chdir(ctdldir) != 0) {
+ syslog(LOG_ERR, "main: unable to change directory to [%s]: %m", ctdldir);
+ exit(CTDLEXIT_HOME);
+ }
+ else {
+ syslog(LOG_INFO, "main: running in data directory %s", ctdldir);
+ }
+
+ if ((ctdluid == 0) && (drop_root_perms == 0)) {
+ fprintf(stderr, "citserver: cannot determine user to run as; please specify -r or -u options\n");
+ exit(CTDLEXIT_UNUSER);
+ }
+
+ // Last ditch effort to determine the user name ... if there's a user called "citadel" then use that
+ if (ctdluid == 0) {
+ p = getpwnam("citadel");
+ if (!p) {
+ p = getpwnam("bbs");
+ }
+ if (!p) {
+ p = getpwnam("guest");
+ }
+ if (p) {
+ u = p->pw_uid;
+ }
+ if (u > 0) {
+ ctdluid = u;
+ }
+ }
+
+ // initialize the master context
+ InitializeMasterCC();
+ InitializeMasterTSD();
+
+ setlogmask(LOG_UPTO(max_log_level));
+ openlog("citserver",
+ ( running_as_daemon ? (LOG_PID) : (LOG_PID | LOG_PERROR) ),
+ syslog_facility
+ );
+
+ // daemonize, if we were asked to
+ if (running_as_daemon) {
+ start_daemon(0);
+ drop_root_perms = 1;
+ }
+
+ if ((mkdir(ctdl_run_dir, 0755) != 0) && (errno != EEXIST)) {
+ syslog(LOG_ERR, "main: unable to create run directory [%s]: %m", ctdl_run_dir);
+ }
+
+ if (chown(ctdl_run_dir, ctdluid, (pwp==NULL)?-1:pw.pw_gid) != 0) {
+ syslog(LOG_ERR, "main: unable to set the access rights for [%s]: %m", ctdl_run_dir);
+ }
+
+ ctdl_lockfile(1);
+ init_sysdep(); // Initialize...
+ master_startup(); // Do non system dependent startup functions
+ check_control(); // Check/sanitize/initialize control record, fix user indexes
+ syslog(LOG_INFO, "main: upgrading modules"); // Run any upgrade entry points
+ pre_startup_upgrades();
+
+ // Load the user for the masterCC or create them if they don't exist
+ if (CtdlGetUser(&masterCC.user, "SYS_Citadel")) {
+ // User doesn't exist. We can't use create user here as the user number needs to be 0
+ strcpy (masterCC.user.fullname, "SYS_Citadel") ;
+ CtdlPutUser(&masterCC.user);
+ CtdlGetUser(&masterCC.user, "SYS_Citadel"); // Just to be safe
+ }
+
+ // Bind the server to a Unix-domain socket (user client access)
+ CtdlRegisterServiceHook(0,
+ file_citadel_socket,
+ citproto_begin_session,
+ do_command_loop,
+ do_async_loop,
+ CitadelServiceUDS);
+
+ // Bind the server to a Unix-domain socket (admin client access)
+ CtdlRegisterServiceHook(0,
+ file_citadel_admin_socket,
+ citproto_begin_admin_session,
+ do_command_loop,
+ do_async_loop,
+ CitadelServiceUDS);
+ chmod(file_citadel_admin_socket, S_IRWXU); // for your eyes only
+
+ // Bind the server to our favorite TCP port (usually 504).
+ CtdlRegisterServiceHook(CtdlGetConfigInt("c_port_number"),
+ NULL,
+ citproto_begin_session,
+ do_command_loop,
+ do_async_loop,
+ CitadelServiceTCP);
+
+ // Load any server-side extensions available here.
+ syslog(LOG_INFO, "main: initializing server extensions");
+ initialize_modules(0);
+
+ // If we need host auth, start our chkpwd daemon.
+ if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_HOST) {
+ start_chkpwd_daemon();
+ }
+
+ // check, whether we're fired up another time after a crash.
+ // if, post an aide message, so the admin has a chance to react.
+ checkcrash();
+
+ // Now that we've bound the sockets, change to the Citadel user id and its corresponding group ids
+ if (drop_root_perms) {
+ cdb_chmod_data(); // make sure we own our data files
+ getpwuid_r(ctdluid, &pw, pwbuf, sizeof(pwbuf), &pwp);
+ if (pwp == NULL)
+ syslog(LOG_ERR, "main: WARNING, getpwuid(%ld): %m Group IDs will be incorrect.", (long)CTDLUID);
+ else {
+ initgroups(pw.pw_name, pw.pw_gid);
+ if (setgid(pw.pw_gid)) {
+ syslog(LOG_ERR, "main: setgid(%ld): %m", (long)pw.pw_gid);
+ }
+ }
+ syslog(LOG_INFO, "main: changing uid to %ld", (long)CTDLUID);
+ if (setuid(CTDLUID) != 0) {
+ syslog(LOG_ERR, "main: setuid() failed: %m");
+ }
+#if defined (HAVE_SYS_PRCTL_H) && defined (PR_SET_DUMPABLE)
+ prctl(PR_SET_DUMPABLE, 1);
+#endif
+ }
+
+ // We want to check for idle sessions once per minute
+ CtdlRegisterSessionHook(terminate_idle_sessions, EVT_TIMER, PRIO_CLEANUP + 1);
+
+ // Go into multithreaded mode. When this call exits, the server is stopping.
+ go_threading();
+
+ // Get ready to shut down the server.
+ int exit_code = master_cleanup(exit_signal);
+ ctdl_lockfile(0);
+ if (restart_server) {
+ syslog(LOG_INFO, "main: *** CITADEL SERVER IS RESTARTING ***");
+ execv(argv[0], argv);
+ }
+ return(exit_code);
+}
--- /dev/null
+// Server-side utility functions
+
+#include "sysdep.h"
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <libcitadel.h>
+
+#include "citadel.h"
+#include "support.h"
+
+// strproc() - make a string 'nice'
+void strproc(char *string) {
+ int a, b;
+
+ if (string == NULL) return;
+ if (IsEmptyStr(string)) return;
+
+ // Convert non-printable characters to blanks
+ for (a=0; !IsEmptyStr(&string[a]); ++a) {
+ if (string[a]<32) string[a]=32;
+ if (string[a]>126) string[a]=32;
+ }
+
+ // a is now the length of our string.
+ // Remove leading and trailing blanks
+ while( (string[a-1]<33) && (!IsEmptyStr(string)) )
+ string[--a]=0;
+ b = 0;
+ while( (string[b]<33) && (!IsEmptyStr(&string[b])) )
+ b++;
+ if (b > 0)
+ memmove(string,&string[b], a - b + 1);
+
+ // Remove double blanks
+ for (a=0; !IsEmptyStr(&string[a]); ++a) {
+ if ((string[a]==32)&&(string[a+1]==32)) {
+ strcpy(&string[a],&string[a+1]);
+ a=0;
+ }
+ }
+
+ // remove characters which would interfere with the network
+ for (a=0; !IsEmptyStr(&string[a]); ++a) {
+ while (string[a]=='!') strcpy(&string[a],&string[a+1]);
+ while (string[a]=='@') strcpy(&string[a],&string[a+1]);
+ while (string[a]=='_') strcpy(&string[a],&string[a+1]);
+ while (string[a]==',') strcpy(&string[a],&string[a+1]);
+ while (string[a]=='%') strcpy(&string[a],&string[a+1]);
+ while (string[a]=='|') strcpy(&string[a],&string[a+1]);
+ }
+
+}
+
+
+// get a line of text from a file
+// ignores lines starting with #
+int getstring(FILE *fp, char *string) {
+ int a,c;
+ do {
+ strcpy(string,"");
+ a=0;
+ do {
+ c=getc(fp);
+ if (c<0) {
+ string[a]=0;
+ return(-1);
+ }
+ string[a++]=c;
+ } while(c!=10);
+ string[a-1]=0;
+ } while(string[0]=='#');
+ return(strlen(string));
+}
--- /dev/null
+#include <stdio.h>
+
+void strproc (char *string);
+int getstring (FILE *fp, char *string);
--- /dev/null
+// Tuning of various parameters of the system.
+// Normally you don't want to mess with any of this.
+//
+// Copyright (c) 1987-2021 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.
+
+/*
+ * NLI is the string that shows up in a <W>ho's online listing for sessions
+ * that are active, but for which no user has yet authenticated.
+ */
+#define NLI "(not logged in)"
+
+/*
+ * Maximum number of floors on the system.
+ * WARNING! *Never* change this value once your system is up; THINGS WILL DIE!
+ * Also, do not set it higher than 127.
+ */
+#define MAXFLOORS 16
+
+/*
+ * Standard buffer size for string datatypes. DO NOT CHANGE! Not only does
+ * there exist a minimum buffer size for certain protocols (such as IMAP), but
+ * fixed-length buffers are now stored in some of the data structures on disk,
+ * so if you change the buffer size you'll fux0r your database.
+ */
+#define SIZ 4096
+
+/*
+ * If the body of a message is beyond this size, it will be stored in
+ * a separate table.
+ */
+#define BIGMSG 1024
+
+/*
+ * SMTP delivery timeouts (measured in seconds)
+ * If outbound SMTP deliveries cannot be completed due to transient errors
+ * within SMTP_DELIVER_WARN seconds, the sender will receive a warning message
+ * indicating that the message has not yet been delivered but Citadel will
+ * keep trying. After SMTP_DELIVER_FAIL seconds, Citadel will advise the
+ * sender that the deliveries have failed.
+ */
+#define SMTP_DELIVER_WARN 14400 // warn after four hours
+#define SMTP_DELIVER_FAIL 432000 // fail after five days
+
+/*
+ * Who bounced messages appear to be from
+ */
+#define BOUNCESOURCE "Citadel Mail Delivery Subsystem"
+
+/*
+ * The names of rooms which are automatically created by the system
+ */
+#define BASEROOM "Lobby"
+#define MAILROOM "Mail"
+#define SENTITEMS "Sent Items"
+#define AIDEROOM "Aide"
+#define USERCONFIGROOM "My Citadel Config"
+#define USERCALENDARROOM "Calendar"
+#define USERTASKSROOM "Tasks"
+#define USERCONTACTSROOM "Contacts"
+#define USERNOTESROOM "Notes"
+#define USERDRAFTROOM "Drafts"
+#define USERTRASHROOM "Trash"
+#define PAGELOGROOM "Sent/Received Pages"
+#define SYSCONFIGROOM "Local System Configuration"
+#define SMTP_SPOOLOUT_ROOM "__CitadelSMTPspoolout__"
+#define ADDRESS_BOOK_ROOM "Global Address Book"
+
+/*
+ * How long (in seconds) to retain message entries in the use table
+ */
+#define USETABLE_RETAIN 864000L /* 10 days */
+
+/*
+ * The size of per-thread stacks. If set too low, citserver will randomly crash.
+ */
+#define THREADSTACKSIZE 0x100000
+
+/*
+ * How many messages may the full text indexer scan before flushing its
+ * tables to disk?
+ */
+#define FT_MAX_CACHE 25
--- /dev/null
+// Citadel "system dependent" stuff.
+//
+// Here's where we (hopefully) have most parts of the Citadel server that
+// might need tweaking when run on different operating system variants.
+//
+// Copyright (c) 1987-2021 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.
+
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <sys/syslog.h>
+#include <execinfo.h>
+#include <netdb.h>
+#include <sys/un.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#define SHOW_ME_VAPPEND_PRINTF
+#include <libcitadel.h>
+#include "citserver.h"
+#include "config.h"
+#include "ctdl_module.h"
+#include "sysdep_decls.h"
+#include "modules/crypto/serv_crypto.h" // Needed for init_ssl, client_write_ssl, client_read_ssl
+#include "housekeeping.h"
+#include "context.h"
+
+// Signal handler to shut down the server.
+volatile int exit_signal = 0;
+volatile int shutdown_and_halt = 0;
+volatile int restart_server = 0;
+volatile int running_as_daemon = 0;
+
+
+static RETSIGTYPE signal_cleanup(int signum) {
+ syslog(LOG_DEBUG, "sysdep: caught signal %d", signum);
+ exit_signal = signum;
+ server_shutting_down = 1;
+}
+
+
+// Some initialization stuff...
+void init_sysdep(void) {
+ sigset_t set;
+
+ // Avoid vulnerabilities related to FD_SETSIZE if we can.
+#ifdef FD_SETSIZE
+#ifdef RLIMIT_NOFILE
+ struct rlimit rl;
+ getrlimit(RLIMIT_NOFILE, &rl);
+ rl.rlim_cur = FD_SETSIZE;
+ rl.rlim_max = FD_SETSIZE;
+ setrlimit(RLIMIT_NOFILE, &rl);
+#endif
+#endif
+
+ // If we've got OpenSSL, we're going to use it.
+#ifdef HAVE_OPENSSL
+ init_ssl();
+#endif
+
+ if (pthread_key_create(&ThreadKey, NULL) != 0) { // TSD for threads
+ syslog(LOG_ERR, "pthread_key_create() : %m");
+ abort();
+ }
+
+ if (pthread_key_create(&MyConKey, NULL) != 0) { // TSD for sessions
+ syslog(LOG_CRIT, "sysdep: can't create TSD key: %m");
+ abort();
+ }
+
+ // Interript, hangup, and terminate signals should cause the server to shut down.
+ sigemptyset(&set);
+ sigaddset(&set, SIGINT);
+ sigaddset(&set, SIGHUP);
+ sigaddset(&set, SIGTERM);
+ sigprocmask(SIG_UNBLOCK, &set, NULL);
+
+ signal(SIGINT, signal_cleanup);
+ signal(SIGHUP, signal_cleanup);
+ signal(SIGTERM, signal_cleanup);
+
+ // Do not shut down the server on broken pipe signals, otherwise the
+ // whole Citadel service would come down whenever a single client
+ // socket breaks.
+ signal(SIGPIPE, SIG_IGN);
+}
+
+
+// This is a generic function to set up a master socket for listening on
+// a TCP port. The server shuts down if the bind fails. (IPv4/IPv6 version)
+//
+// ip_addr IP address to bind
+// port_number port number to bind
+// queue_len number of incoming connections to allow in the queue
+int ctdl_tcp_server(char *ip_addr, int port_number, int queue_len) {
+ struct protoent *p;
+ struct sockaddr_in6 sin6;
+ struct sockaddr_in sin4;
+ int s, i, b;
+ int ip_version = 6;
+
+ memset(&sin6, 0, sizeof(sin6));
+ memset(&sin4, 0, sizeof(sin4));
+ sin6.sin6_family = AF_INET6;
+ sin4.sin_family = AF_INET;
+
+ if ( (ip_addr == NULL) // any IPv6
+ || (IsEmptyStr(ip_addr))
+ || (!strcmp(ip_addr, "*"))
+ ) {
+ ip_version = 6;
+ sin6.sin6_addr = in6addr_any;
+ }
+ else if (!strcmp(ip_addr, "0.0.0.0")) // any IPv4
+ {
+ ip_version = 4;
+ sin4.sin_addr.s_addr = INADDR_ANY;
+ }
+ else if ((strchr(ip_addr, '.')) && (!strchr(ip_addr, ':'))) // specific IPv4
+ {
+ ip_version = 4;
+ if (inet_pton(AF_INET, ip_addr, &sin4.sin_addr) <= 0) {
+ syslog(LOG_ALERT, "tcpserver: inet_pton: %m");
+ return (-1);
+ }
+ }
+ else // specific IPv6
+ {
+ ip_version = 6;
+ if (inet_pton(AF_INET6, ip_addr, &sin6.sin6_addr) <= 0) {
+ syslog(LOG_ALERT, "tcpserver: inet_pton: %m");
+ return (-1);
+ }
+ }
+
+ if (port_number == 0) {
+ syslog(LOG_ALERT, "tcpserver: no port number was specified");
+ return (-1);
+ }
+ sin6.sin6_port = htons((u_short) port_number);
+ sin4.sin_port = htons((u_short) port_number);
+
+ p = getprotobyname("tcp");
+ if (p == NULL) {
+ syslog(LOG_ALERT, "tcpserver: getprotobyname: %m");
+ return (-1);
+ }
+
+ s = socket( ((ip_version == 6) ? PF_INET6 : PF_INET), SOCK_STREAM, (p->p_proto));
+ if (s < 0) {
+ syslog(LOG_ALERT, "tcpserver: socket: %m");
+ return (-1);
+ }
+ // Set some socket options that make sense.
+ i = 1;
+ setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
+
+ if (ip_version == 6) {
+ b = bind(s, (struct sockaddr *) &sin6, sizeof(sin6));
+ }
+ else {
+ b = bind(s, (struct sockaddr *) &sin4, sizeof(sin4));
+ }
+
+ if (b < 0) {
+ syslog(LOG_ALERT, "tcpserver: bind: %m");
+ return (-1);
+ }
+
+ fcntl(s, F_SETFL, O_NONBLOCK);
+
+ if (listen(s, ((queue_len >= 5) ? queue_len : 5) ) < 0) {
+ syslog(LOG_ALERT, "tcpserver: listen: %m");
+ return (-1);
+ }
+ return (s);
+}
+
+
+// Create a Unix domain socket and listen on it
+int ctdl_uds_server(char *sockpath, int queue_len) {
+ struct sockaddr_un addr;
+ int s;
+ int i;
+ int actual_queue_len;
+#ifdef HAVE_STRUCT_UCRED
+ int passcred = 1;
+#endif
+
+ actual_queue_len = queue_len;
+ if (actual_queue_len < 5) actual_queue_len = 5;
+
+ i = unlink(sockpath);
+ if ((i != 0) && (errno != ENOENT)) {
+ syslog(LOG_ERR, "udsserver: %m");
+ return(-1);
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ safestrncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
+
+ s = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (s < 0) {
+ syslog(LOG_ERR, "udsserver: socket: %m");
+ return(-1);
+ }
+
+ if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ syslog(LOG_ERR, "udsserver: bind: %m");
+ return(-1);
+ }
+
+ // set to nonblock - we need this for some obscure situations
+ if (fcntl(s, F_SETFL, O_NONBLOCK) < 0) {
+ syslog(LOG_ERR, "udsserver: fcntl: %m");
+ close(s);
+ return(-1);
+ }
+
+ if (listen(s, actual_queue_len) < 0) {
+ syslog(LOG_ERR, "udsserver: listen: %m");
+ return(-1);
+ }
+
+#ifdef HAVE_STRUCT_UCRED
+ setsockopt(s, SOL_SOCKET, SO_PASSCRED, &passcred, sizeof(passcred));
+#endif
+
+ chmod(sockpath, S_ISGID|S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH);
+ return(s);
+}
+
+
+// The following functions implement output buffering on operating systems which
+// support it (such as Linux and various BSD flavors).
+#ifndef HAVE_DARWIN
+#ifdef TCP_CORK
+# define HAVE_TCP_BUFFERING
+#else
+# ifdef TCP_NOPUSH
+# define HAVE_TCP_BUFFERING
+# define TCP_CORK TCP_NOPUSH
+# endif
+#endif // TCP_CORK
+#endif // HAVE_DARWIN
+
+static unsigned on = 1, off = 0;
+
+void buffer_output(void) {
+#ifdef HAVE_TCP_BUFFERING
+#ifdef HAVE_OPENSSL
+ if (!CC->redirect_ssl)
+#endif
+ setsockopt(CC->client_socket, IPPROTO_TCP, TCP_CORK, &on, 4);
+#endif
+}
+
+void unbuffer_output(void) {
+#ifdef HAVE_TCP_BUFFERING
+#ifdef HAVE_OPENSSL
+ if (!CC->redirect_ssl)
+#endif
+ setsockopt(CC->client_socket, IPPROTO_TCP, TCP_CORK, &off, 4);
+#endif
+}
+
+void flush_output(void) {
+#ifdef HAVE_TCP_BUFFERING
+ struct CitContext *CCC = CC;
+ setsockopt(CCC->client_socket, IPPROTO_TCP, TCP_CORK, &off, 4);
+ setsockopt(CCC->client_socket, IPPROTO_TCP, TCP_CORK, &on, 4);
+#endif
+}
+
+
+// close the client socket
+void client_close(void) {
+ CitContext *CCC = CC;
+
+ if (!CCC) return;
+ if (CCC->client_socket <= 0) return;
+ syslog(LOG_DEBUG, "sysdep: closing socket %d", CCC->client_socket);
+ close(CCC->client_socket);
+ CCC->client_socket = -1 ;
+}
+
+
+// Send binary data to the client.
+int client_write(const char *buf, int nbytes)
+{
+ int bytes_written = 0;
+ int retval;
+#ifndef HAVE_TCP_BUFFERING
+ int old_buffer_len = 0;
+#endif
+ fd_set wset;
+ CitContext *Ctx;
+ int fdflags;
+
+ if (nbytes < 1) return(0);
+
+ Ctx = CC;
+
+ if (Ctx->redirect_buffer != NULL) {
+ StrBufAppendBufPlain(Ctx->redirect_buffer,
+ buf, nbytes, 0);
+ return 0;
+ }
+
+#ifdef HAVE_OPENSSL
+ if (Ctx->redirect_ssl) {
+ client_write_ssl(buf, nbytes);
+ return 0;
+ }
+#endif
+ if (Ctx->client_socket == -1) return -1;
+
+ fdflags = fcntl(Ctx->client_socket, F_GETFL);
+
+ while ((bytes_written < nbytes) && (Ctx->client_socket != -1)){
+ if ((fdflags & O_NONBLOCK) == O_NONBLOCK) {
+ FD_ZERO(&wset);
+ FD_SET(Ctx->client_socket, &wset);
+ if (select(1, NULL, &wset, NULL, NULL) == -1) {
+ if (errno == EINTR)
+ {
+ syslog(LOG_DEBUG, "sysdep: client_write(%d bytes) select() interrupted.", nbytes-bytes_written);
+ if (server_shutting_down) {
+ CC->kill_me = KILLME_SELECT_INTERRUPTED;
+ return (-1);
+ } else {
+ // can't trust fd's and stuff so we need to re-create them
+ continue;
+ }
+ } else {
+ syslog(LOG_ERR, "sysdep: client_write(%d bytes) select failed: %m", nbytes - bytes_written);
+ client_close();
+ Ctx->kill_me = KILLME_SELECT_FAILED;
+ return -1;
+ }
+ }
+ }
+
+ retval = write(Ctx->client_socket, &buf[bytes_written], nbytes - bytes_written);
+ if (retval < 1) {
+ syslog(LOG_ERR, "sysdep: client_write(%d bytes) failed: %m", nbytes - bytes_written);
+ client_close();
+ Ctx->kill_me = KILLME_WRITE_FAILED;
+ return -1;
+ }
+ bytes_written = bytes_written + retval;
+ }
+ return 0;
+}
+
+void cputbuf(const StrBuf *Buf) {
+ client_write(ChrPtr(Buf), StrLength(Buf));
+}
+
+
+// Send formatted printable data to the client.
+// Implemented in terms of client_write() so it's technically not sysdep...
+void cprintf(const char *format, ...) {
+ va_list arg_ptr;
+ char buf[1024];
+
+ va_start(arg_ptr, format);
+ if (vsnprintf(buf, sizeof buf, format, arg_ptr) == -1)
+ buf[sizeof buf - 2] = '\n';
+ client_write(buf, strlen(buf));
+ va_end(arg_ptr);
+}
+
+
+// Read data from the client socket.
+//
+// sock socket fd to read from
+// buf buffer to read into
+// bytes number of bytes to read
+// timeout Number of seconds to wait before timing out
+//
+// Possible return values:
+// 1 Requested number of bytes has been read.
+// 0 Request timed out.
+// -1 Connection is broken, or other error.
+int client_read_blob(StrBuf *Target, int bytes, int timeout) {
+ CitContext *CCC=CC;
+ const char *Error;
+ int retval = 0;
+
+#ifdef HAVE_OPENSSL
+ if (CCC->redirect_ssl) {
+ retval = client_read_sslblob(Target, bytes, timeout);
+ if (retval < 0) {
+ syslog(LOG_ERR, "sysdep: client_read_blob() failed");
+ }
+ }
+ else
+#endif
+ {
+ retval = StrBufReadBLOBBuffered(Target,
+ CCC->RecvBuf.Buf,
+ &CCC->RecvBuf.ReadWritePointer,
+ &CCC->client_socket,
+ 1,
+ bytes,
+ O_TERM,
+ &Error
+ );
+ if (retval < 0) {
+ syslog(LOG_ERR, "sysdep: client_read_blob() failed: %s", Error);
+ client_close();
+ return retval;
+ }
+ }
+ return retval;
+}
+
+
+// to make client_read_random_blob() more efficient, increase buffer size.
+// just use in greeting function, else your buffer may be flushed
+void client_set_inbound_buf(long N)
+{
+ CitContext *CCC=CC;
+ FlushStrBuf(CCC->RecvBuf.Buf);
+ ReAdjustEmptyBuf(CCC->RecvBuf.Buf, N * SIZ, N * SIZ);
+}
+
+int client_read_random_blob(StrBuf *Target, int timeout)
+{
+ CitContext *CCC=CC;
+ int rc;
+
+ rc = client_read_blob(Target, 1, timeout);
+ if (rc > 0)
+ {
+ long len;
+ const char *pch;
+
+ len = StrLength(CCC->RecvBuf.Buf);
+ pch = ChrPtr(CCC->RecvBuf.Buf);
+
+ if (len > 0)
+ {
+ if (CCC->RecvBuf.ReadWritePointer != NULL) {
+ len -= CCC->RecvBuf.ReadWritePointer - pch;
+ pch = CCC->RecvBuf.ReadWritePointer;
+ }
+ StrBufAppendBufPlain(Target, pch, len, 0);
+ FlushStrBuf(CCC->RecvBuf.Buf);
+ CCC->RecvBuf.ReadWritePointer = NULL;
+ return StrLength(Target);
+ }
+ return rc;
+ }
+ else
+ return rc;
+}
+
+int client_read_to(char *buf, int bytes, int timeout)
+{
+ CitContext *CCC=CC;
+ int rc;
+
+ rc = client_read_blob(CCC->MigrateBuf, bytes, timeout);
+ if (rc < 0)
+ {
+ *buf = '\0';
+ return rc;
+ }
+ else
+ {
+ memcpy(buf,
+ ChrPtr(CCC->MigrateBuf),
+ StrLength(CCC->MigrateBuf) + 1);
+ FlushStrBuf(CCC->MigrateBuf);
+ return rc;
+ }
+}
+
+
+int HaveMoreLinesWaiting(CitContext *CCC) {
+ if ((CCC->kill_me != 0) ||
+ ( (CCC->RecvBuf.ReadWritePointer == NULL) &&
+ (StrLength(CCC->RecvBuf.Buf) == 0) &&
+ (CCC->client_socket != -1)) )
+ return 0;
+ else
+ return 1;
+}
+
+
+// Read data from the client socket with default timeout.
+// (This is implemented in terms of client_read_to() and could be
+// justifiably moved out of sysdep.c)
+INLINE int client_read(char *buf, int bytes) {
+ return(client_read_to(buf, bytes, CtdlGetConfigInt("c_sleeping")));
+}
+
+int CtdlClientGetLine(StrBuf *Target) {
+ CitContext *CCC=CC;
+ const char *Error;
+ int rc;
+
+ FlushStrBuf(Target);
+#ifdef HAVE_OPENSSL
+ if (CCC->redirect_ssl) {
+ rc = client_readline_sslbuffer(Target, CCC->RecvBuf.Buf, &CCC->RecvBuf.ReadWritePointer, 1);
+ return rc;
+ }
+ else
+#endif
+ {
+ rc = StrBufTCP_read_buffered_line_fast(Target,
+ CCC->RecvBuf.Buf,
+ &CCC->RecvBuf.ReadWritePointer,
+ &CCC->client_socket,
+ 5,
+ 1,
+ &Error
+ );
+ return rc;
+ }
+}
+
+
+// Get a LF-terminated line of text from the client.
+// (This is implemented in terms of client_read() and could be
+// justifiably moved out of sysdep.c)
+int client_getln(char *buf, int bufsize) {
+ int i, retval;
+ CitContext *CCC=CC;
+ const char *pCh;
+
+ retval = CtdlClientGetLine(CCC->MigrateBuf);
+ if (retval < 0)
+ return(retval >= 0);
+
+
+ i = StrLength(CCC->MigrateBuf);
+ pCh = ChrPtr(CCC->MigrateBuf);
+ // Strip the trailing LF, and the trailing CR if present.
+ if (bufsize <= i)
+ i = bufsize - 1;
+ while ( (i > 0)
+ && ( (pCh[i - 1]==13)
+ || ( pCh[i - 1]==10)) ) {
+ i--;
+ }
+ memcpy(buf, pCh, i);
+ buf[i] = 0;
+
+ FlushStrBuf(CCC->MigrateBuf);
+ if (retval < 0) {
+ safestrncpy(&buf[i], "000", bufsize - i);
+ }
+ return(retval >= 0);
+}
+
+
+// Cleanup any contexts that are left lying around
+void close_masters(void) {
+ struct ServiceFunctionHook *serviceptr;
+ const char *Text;
+
+ // close all protocol master sockets
+ for (serviceptr = ServiceHookTable; serviceptr != NULL;
+ serviceptr = serviceptr->next ) {
+
+ if (serviceptr->tcp_port > 0) {
+ if (serviceptr->msock == -1) {
+ Text = "not closing again";
+ }
+ else {
+ Text = "Closing";
+ }
+ syslog(LOG_INFO, "sysdep: %s %d listener on port %d",
+ Text,
+ serviceptr->msock,
+ serviceptr->tcp_port
+ );
+ serviceptr->tcp_port = 0;
+ }
+
+ if (serviceptr->sockpath != NULL) {
+ if (serviceptr->msock == -1) {
+ Text = "not closing again";
+ }
+ else {
+ Text = "Closing";
+ }
+ syslog(LOG_INFO, "sysdep: %s %d listener on '%s'",
+ Text,
+ serviceptr->msock,
+ serviceptr->sockpath
+ );
+ }
+
+ if (serviceptr->msock != -1) {
+ close(serviceptr->msock);
+ serviceptr->msock = -1;
+ }
+
+ // If it's a Unix domain socket, remove the file.
+ if (serviceptr->sockpath != NULL) {
+ unlink(serviceptr->sockpath);
+ serviceptr->sockpath = NULL;
+ }
+ }
+}
+
+
+// The system-dependent part of master_cleanup() - close the master socket.
+void sysdep_master_cleanup(void) {
+ close_masters();
+ context_cleanup();
+}
+
+
+
+pid_t current_child;
+void graceful_shutdown(int signum) {
+ kill(current_child, signum);
+ unlink(file_pid_file);
+ exit(0);
+}
+
+int nFireUps = 0;
+int nFireUpsNonRestart = 0;
+pid_t ForkedPid = 1;
+
+// Start running as a daemon.
+void start_daemon(int unused) {
+ int status = 0;
+ pid_t child = 0;
+ FILE *fp;
+ int do_restart = 0;
+ current_child = 0;
+
+ // Close stdin/stdout/stderr and replace them with /dev/null.
+ // We don't just call close() because we don't want these fd's
+ // to be reused for other files.
+ child = fork();
+ if (child != 0) {
+ exit(0);
+ }
+
+ signal(SIGHUP, SIG_IGN);
+ signal(SIGINT, SIG_IGN);
+ signal(SIGQUIT, SIG_IGN);
+
+ setsid();
+ umask(0);
+ if ( (freopen("/dev/null", "r", stdin) != stdin) ||
+ (freopen("/dev/null", "w", stdout) != stdout) ||
+ (freopen("/dev/null", "w", stderr) != stderr)
+ ) {
+ syslog(LOG_ERR, "sysdep: unable to reopen stdio: %m");
+ }
+
+ do {
+ current_child = fork();
+ signal(SIGTERM, graceful_shutdown);
+ if (current_child < 0) {
+ perror("fork");
+ exit(errno);
+ }
+ else if (current_child == 0) {
+ return; // continue starting citadel.
+ }
+ else {
+ fp = fopen(file_pid_file, "w");
+ if (fp != NULL) {
+ fprintf(fp, ""F_PID_T"\n", getpid());
+ fclose(fp);
+ }
+ waitpid(current_child, &status, 0);
+ }
+ nFireUpsNonRestart = nFireUps;
+
+ // Exit code 0 means the watcher should exit
+ if (WIFEXITED(status) && (WEXITSTATUS(status) == CTDLEXIT_SHUTDOWN)) {
+ do_restart = 0;
+ }
+
+ // Exit code 101-109 means the watcher should exit
+ else if (WIFEXITED(status) && (WEXITSTATUS(status) >= 101) && (WEXITSTATUS(status) <= 109)) {
+ do_restart = 0;
+ }
+
+ // Any other exit code, or no exit code, means we should restart.
+ else {
+ do_restart = 1;
+ nFireUps++;
+ ForkedPid = current_child;
+ }
+
+ } while (do_restart);
+
+ unlink(file_pid_file);
+ exit(WEXITSTATUS(status));
+}
+
+
+void checkcrash(void) {
+ if (nFireUpsNonRestart != nFireUps) {
+ StrBuf *CrashMail;
+ CrashMail = NewStrBuf();
+ syslog(LOG_ALERT, "sysdep: posting crash message");
+ StrBufPrintf(CrashMail,
+ " \n"
+ " The Citadel server process (citserver) terminated unexpectedly."
+ "\n \n"
+ " This could be the result of a bug in the server program, or some external "
+ "factor.\n \n"
+ " You can obtain more information about this by enabling core dumps.\n \n"
+ " For more information, please see:\n \n"
+ " http://citadel.org/doku.php?id=faq:mastering_your_os:gdb#how.do.i.make.my.system.produce.core-files"
+ "\n \n"
+
+ " If you have already done this, the core dump is likely to be found at %score.%d\n"
+ ,
+ ctdl_run_dir, ForkedPid);
+ CtdlAideMessage(ChrPtr(CrashMail), "Citadel server process terminated unexpectedly");
+ FreeStrBuf(&CrashMail);
+ }
+}
+
+
+// Generic routine to convert a login name to a full name (gecos)
+// Returns nonzero if a conversion took place
+int convert_login(char NameToConvert[]) {
+ struct passwd *pw;
+ unsigned int a;
+
+ pw = getpwnam(NameToConvert);
+ if (pw == NULL) {
+ return(0);
+ }
+ else {
+ strcpy(NameToConvert, pw->pw_gecos);
+ for (a=0; a<strlen(NameToConvert); ++a) {
+ if (NameToConvert[a] == ',') NameToConvert[a] = 0;
+ }
+ return(1);
+ }
+}
+
+
+void HuntBadSession(void) {
+ int highest;
+ CitContext *ptr;
+ fd_set readfds;
+ struct timeval tv;
+ struct ServiceFunctionHook *serviceptr;
+
+ // Next, add all of the client sockets
+ begin_critical_section(S_SESSION_TABLE);
+ for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
+ if ((ptr->state == CON_SYS) && (ptr->client_socket == 0))
+ continue;
+ // Initialize the fdset.
+ FD_ZERO(&readfds);
+ highest = 0;
+ tv.tv_sec = 0; // wake up every second if no input
+ tv.tv_usec = 0;
+
+ // Don't select on dead sessions, only truly idle ones
+ if ( (ptr->state == CON_IDLE)
+ && (ptr->kill_me == 0)
+ && (ptr->client_socket > 0)
+ ) {
+ FD_SET(ptr->client_socket, &readfds);
+ if (ptr->client_socket > highest)
+ highest = ptr->client_socket;
+
+ if ((select(highest + 1, &readfds, NULL, NULL, &tv) < 0) && (errno == EBADF))
+ {
+ // Gotcha!
+ syslog(LOG_ERR,
+ "sysdep: killing session CC[%d] bad FD: [%d] User[%s] Host[%s:%s]",
+ ptr->cs_pid,
+ ptr->client_socket,
+ ptr->curr_user,
+ ptr->cs_host,
+ ptr->cs_addr
+ );
+ ptr->kill_me = 1;
+ ptr->client_socket = -1;
+ break;
+ }
+ }
+ }
+ end_critical_section(S_SESSION_TABLE);
+
+ // First, add the various master sockets to the fdset.
+ for (serviceptr = ServiceHookTable; serviceptr != NULL; serviceptr = serviceptr->next ) {
+
+ // Initialize the fdset.
+ highest = 0;
+ tv.tv_sec = 0; // wake up every second if no input
+ tv.tv_usec = 0;
+
+ FD_SET(serviceptr->msock, &readfds);
+ if (serviceptr->msock > highest) {
+ highest = serviceptr->msock;
+ }
+ if ((select(highest + 1, &readfds, NULL, NULL, &tv) < 0) &&
+ (errno == EBADF))
+ {
+ // Gotcha! server socket dead? commit suicide!
+ syslog(LOG_ERR, "sysdep: found bad FD: %d and its a server socket! Shutting Down!", serviceptr->msock);
+ server_shutting_down = 1;
+ break;
+ }
+ }
+}
+
+
+// This loop just keeps going and going and going...
+void *worker_thread(void *blah) {
+ int highest;
+ CitContext *ptr;
+ CitContext *bind_me = NULL;
+ fd_set readfds;
+ int retval = 0;
+ struct timeval tv;
+ int force_purge = 0;
+ struct ServiceFunctionHook *serviceptr;
+ int ssock; // Descriptor for client socket
+ CitContext *con = NULL; // Temporary context pointer
+ int i;
+
+ pthread_mutex_lock(&ThreadCountMutex);
+ ++num_workers;
+ pthread_mutex_unlock(&ThreadCountMutex);
+
+ while (!server_shutting_down) {
+
+ // make doubly sure we're not holding any stale db handles which might cause a deadlock
+ cdb_check_handles();
+do_select: force_purge = 0;
+ bind_me = NULL; // Which session shall we handle?
+
+ // Initialize the fdset
+ FD_ZERO(&readfds);
+ highest = 0;
+
+ // First, add the various master sockets to the fdset.
+ for (serviceptr = ServiceHookTable; serviceptr != NULL; serviceptr = serviceptr->next ) {
+ FD_SET(serviceptr->msock, &readfds);
+ if (serviceptr->msock > highest) {
+ highest = serviceptr->msock;
+ }
+ }
+
+ // Next, add all of the client sockets.
+ begin_critical_section(S_SESSION_TABLE);
+ for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
+ if ((ptr->state == CON_SYS) && (ptr->client_socket == 0))
+ continue;
+
+ // Don't select on dead sessions, only truly idle ones
+ if ( (ptr->state == CON_IDLE)
+ && (ptr->kill_me == 0)
+ && (ptr->client_socket > 0)
+ ) {
+ FD_SET(ptr->client_socket, &readfds);
+ if (ptr->client_socket > highest)
+ highest = ptr->client_socket;
+ }
+ if ((bind_me == NULL) && (ptr->state == CON_READY)) {
+ bind_me = ptr;
+ ptr->state = CON_EXECUTING;
+ break;
+ }
+ if ((bind_me == NULL) && (ptr->state == CON_GREETING)) {
+ bind_me = ptr;
+ ptr->state = CON_STARTING;
+ break;
+ }
+ }
+ end_critical_section(S_SESSION_TABLE);
+
+ if (bind_me) {
+ goto SKIP_SELECT;
+ }
+
+ // If we got this far, it means that there are no sessions
+ // which a previous thread marked for attention, so we go
+ // ahead and get ready to select().
+
+ if (!server_shutting_down) {
+ tv.tv_sec = 1; // wake up every second if no input
+ tv.tv_usec = 0;
+ retval = select(highest + 1, &readfds, NULL, NULL, &tv);
+ }
+ else {
+ --num_workers;
+ return NULL;
+ }
+
+ // Now figure out who made this select() unblock.
+ // First, check for an error or exit condition.
+ if (retval < 0) {
+ if (errno == EBADF) {
+ syslog(LOG_ERR, "sysdep: select() failed: %m");
+ HuntBadSession();
+ goto do_select;
+ }
+ if (errno != EINTR) {
+ syslog(LOG_ERR, "sysdep: exiting: %m");
+ server_shutting_down = 1;
+ continue;
+ } else {
+ if (server_shutting_down) {
+ --num_workers;
+ return(NULL);
+ }
+ goto do_select;
+ }
+ }
+ else if (retval == 0) {
+ if (server_shutting_down) {
+ --num_workers;
+ return(NULL);
+ }
+ }
+
+ // Next, check to see if it's a new client connecting on a master socket.
+
+ else if ((retval > 0) && (!server_shutting_down)) for (serviceptr = ServiceHookTable; serviceptr != NULL; serviceptr = serviceptr->next) {
+
+ if (FD_ISSET(serviceptr->msock, &readfds)) {
+ ssock = accept(serviceptr->msock, NULL, 0);
+ if (ssock >= 0) {
+ syslog(LOG_DEBUG, "sysdep: new client socket %d", ssock);
+
+ // The master socket is non-blocking but the client
+ // sockets need to be blocking, otherwise certain
+ // operations barf on FreeBSD. Not a fatal error.
+ if (fcntl(ssock, F_SETFL, 0) < 0) {
+ syslog(LOG_ERR, "sysdep: Can't set socket to blocking: %m");
+ }
+
+ // New context will be created already
+ // set up in the CON_EXECUTING state.
+ con = CreateNewContext();
+
+ // Assign our new socket number to it.
+ con->tcp_port = serviceptr->tcp_port;
+ con->client_socket = ssock;
+ con->h_command_function = serviceptr->h_command_function;
+ con->h_async_function = serviceptr->h_async_function;
+ con->h_greeting_function = serviceptr->h_greeting_function;
+ con->ServiceName = serviceptr->ServiceName;
+
+ // Connections on a local client are always from the same host
+ if (serviceptr->sockpath != NULL) {
+ con->is_local_client = 1;
+ }
+
+ // Set the SO_REUSEADDR socket option
+ i = 1;
+ setsockopt(ssock, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
+ con->state = CON_GREETING;
+ retval--;
+ if (retval == 0)
+ break;
+ }
+ }
+ }
+
+ // It must be a client socket. Find a context that has data
+ // waiting on its socket *and* is in the CON_IDLE state. Any
+ // active sockets other than our chosen one are marked as
+ // CON_READY so the next thread that comes around can just bind
+ // to one without having to select() again.
+ begin_critical_section(S_SESSION_TABLE);
+ for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
+ int checkfd = ptr->client_socket;
+ if ((checkfd != -1) && (ptr->state == CON_IDLE) ){
+ if (FD_ISSET(checkfd, &readfds)) {
+ ptr->input_waiting = 1;
+ if (!bind_me) {
+ bind_me = ptr; // I choose you!
+ bind_me->state = CON_EXECUTING;
+ }
+ else {
+ ptr->state = CON_READY;
+ }
+ } else if ((ptr->is_async) && (ptr->async_waiting) && (ptr->h_async_function)) {
+ if (!bind_me) {
+ bind_me = ptr; // I choose you!
+ bind_me->state = CON_EXECUTING;
+ }
+ else {
+ ptr->state = CON_READY;
+ }
+ }
+ }
+ }
+ end_critical_section(S_SESSION_TABLE);
+
+SKIP_SELECT:
+ // We're bound to a session
+ pthread_mutex_lock(&ThreadCountMutex);
+ ++active_workers;
+ pthread_mutex_unlock(&ThreadCountMutex);
+
+ if (bind_me != NULL) {
+ become_session(bind_me);
+
+ if (bind_me->state == CON_STARTING) {
+ bind_me->state = CON_EXECUTING;
+ begin_session(bind_me);
+ bind_me->h_greeting_function();
+ }
+ // If the client has sent a command, execute it.
+ if (CC->input_waiting) {
+ CC->h_command_function();
+
+ while (HaveMoreLinesWaiting(CC))
+ CC->h_command_function();
+
+ CC->input_waiting = 0;
+ }
+
+ // If there are asynchronous messages waiting and the client supports it, do those now
+ if ((CC->is_async) && (CC->async_waiting) && (CC->h_async_function != NULL)) {
+ CC->h_async_function();
+ CC->async_waiting = 0;
+ }
+
+ force_purge = CC->kill_me;
+ become_session(NULL);
+ bind_me->state = CON_IDLE;
+ }
+
+ dead_session_purge(force_purge);
+ do_housekeeping();
+
+ pthread_mutex_lock(&ThreadCountMutex);
+ --active_workers;
+ if ((active_workers + CtdlGetConfigInt("c_min_workers") < num_workers) &&
+ (num_workers > CtdlGetConfigInt("c_min_workers")))
+ {
+ num_workers--;
+ pthread_mutex_unlock(&ThreadCountMutex);
+ return (NULL);
+ }
+ pthread_mutex_unlock(&ThreadCountMutex);
+ }
+
+ // If control reaches this point, the server is shutting down
+ pthread_mutex_lock(&ThreadCountMutex);
+ --num_workers;
+ pthread_mutex_unlock(&ThreadCountMutex);
+ return(NULL);
+}
+
+
+// SyslogFacility()
+// Translate text facility name to syslog.h defined value.
+int SyslogFacility(char *name)
+{
+ int i;
+ struct
+ {
+ int facility;
+ char *name;
+ } facTbl[] =
+ {
+ { LOG_KERN, "kern" },
+ { LOG_USER, "user" },
+ { LOG_MAIL, "mail" },
+ { LOG_DAEMON, "daemon" },
+ { LOG_AUTH, "auth" },
+ { LOG_SYSLOG, "syslog" },
+ { LOG_LPR, "lpr" },
+ { LOG_NEWS, "news" },
+ { LOG_UUCP, "uucp" },
+ { LOG_LOCAL0, "local0" },
+ { LOG_LOCAL1, "local1" },
+ { LOG_LOCAL2, "local2" },
+ { LOG_LOCAL3, "local3" },
+ { LOG_LOCAL4, "local4" },
+ { LOG_LOCAL5, "local5" },
+ { LOG_LOCAL6, "local6" },
+ { LOG_LOCAL7, "local7" },
+ { 0, NULL }
+ };
+ for(i = 0; facTbl[i].name != NULL; i++) {
+ if(!strcasecmp(name, facTbl[i].name))
+ return facTbl[i].facility;
+ }
+ return LOG_DAEMON;
+}
--- /dev/null
+
+
+/* define this to the Citadel home directory */
+#define CTDLDIR "/root/citadel/citadel"
+
+/* define, if the user suplied a data-directory to use. */
+/* #undef DATA_DIR */
+
+/* whether we have NLS support */
+#define ENABLE_NLS /**/
+
+/* whats the matching format string for pid_t? */
+#define F_PID_T "%d"
+
+/* whats the matching format string for uid_t? */
+#define F_UID_T "%d"
+
+/* whats the matching format string for xpid_t? */
+#define F_XPID_T "%x"
+
+/* Define to 1 if the `getpgrp' function requires zero arguments. */
+#define GETPGRP_VOID 1
+
+/* Define to 1 if you have the <arpa/nameser_compat.h> header file. */
+#define HAVE_ARPA_NAMESER_COMPAT_H 1
+
+/* Define to 1 if you have the <arpa/nameser.h> header file. */
+#define HAVE_ARPA_NAMESER_H 1
+
+/* Define to 1 if you have the `connect' function. */
+#define HAVE_CONNECT 1
+
+/* Define to 1 if you have the <db.h> header file. */
+#define HAVE_DB_H 1
+
+/* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'.
+ */
+#define HAVE_DIRENT_H 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the `gethostbyname' function. */
+#define HAVE_GETHOSTBYNAME 1
+
+/* Define to 1 if you have the `getloadavg' function. */
+#define HAVE_GETLOADAVG 1
+
+/* Define to 1 if you have the `getpwnam_r' function. */
+#define HAVE_GETPWNAM_R 1
+
+/* Define to 1 if you have the `getpwuid_r' function. */
+#define HAVE_GETPWUID_R 1
+
+/* Define to 1 if you have the `getspnam' function. */
+#define HAVE_GETSPNAM 1
+
+/* Define to 1 if you have the `gettext' function. */
+#define HAVE_GETTEXT 1
+
+/* Define to 1 if you have the `getutxline' function. */
+#define HAVE_GETUTXLINE 1
+
+/* whether we have iconv for charset conversion */
+#define HAVE_ICONV /**/
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `nsl' library (-lnsl). */
+/* #undef HAVE_LIBNSL */
+
+/* Define to 1 if you have the `pthread' library (-lpthread). */
+#define HAVE_LIBPTHREAD 1
+
+/* Define to 1 if you have the `rt' library (-lrt). */
+#define HAVE_LIBRT 1
+
+/* Define to 1 if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define to 1 if you have the <malloc.h> header file. */
+#define HAVE_MALLOC_H 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `mkdir' function. */
+#define HAVE_MKDIR 1
+
+/* Define to 1 if you have the `mkfifo' function. */
+#define HAVE_MKFIFO 1
+
+/* Define to 1 if you have the `mktime' function. */
+#define HAVE_MKTIME 1
+
+/* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */
+/* #undef HAVE_NDIR_H */
+
+/* Define to 1 if you have the <netinet/in.h> header file. */
+#define HAVE_NETINET_IN_H 1
+
+/* Define to 1 if you have the <paths.h> header file. */
+#define HAVE_PATHS_H 1
+
+/* define this if you have the pthread_cancel() function */
+#define HAVE_PTHREAD_CANCEL /**/
+
+/* Define to 1 if you have the <pthread.h> header file. */
+#define HAVE_PTHREAD_H 1
+
+/* define this if you have the resolv.h header file. */
+#define HAVE_RESOLV_H /**/
+
+/* Define to 1 if you have the `rmdir' function. */
+#define HAVE_RMDIR 1
+
+/* Define to 1 if you have the `select' function. */
+#define HAVE_SELECT 1
+
+/* Define to 1 if you have the `socket' function. */
+#define HAVE_SOCKET 1
+
+/* enable alternate spool dir? */
+/* #undef HAVE_SPOOL_DIR */
+
+/* should we activate an alternate static text location? */
+/* #undef HAVE_STATICDATA_DIR */
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the `strcasecmp' function. */
+#define HAVE_STRCASECMP 1
+
+/* Define to 1 if you have the `strerror' function. */
+#define HAVE_STRERROR 1
+
+/* Define to 1 if you have the `strftime_l' function. */
+#define HAVE_STRFTIME_L 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strncasecmp' function. */
+#define HAVE_STRNCASECMP 1
+
+/* Define to 1 if `tm_gmtoff' is a member of `struct tm'. */
+#define HAVE_STRUCT_TM_TM_GMTOFF 1
+
+/* Define if struct ucred is present from sys/socket.h */
+#define HAVE_STRUCT_UCRED 1
+
+/* Define to 1 if you have the <syscall.h> header file. */
+#define HAVE_SYSCALL_H 1
+
+/* Define to 1 if you have the <syslog.h> header file. */
+#define HAVE_SYSLOG_H 1
+
+/* Define to 1 if you have the <sys/dir.h> header file, and it defines `DIR'.
+ */
+/* #undef HAVE_SYS_DIR_H */
+
+/* Define to 1 if you have the <sys/ioctl.h> header file. */
+#define HAVE_SYS_IOCTL_H 1
+
+/* Define to 1 if you have the <sys/ndir.h> header file, and it defines `DIR'.
+ */
+/* #undef HAVE_SYS_NDIR_H */
+
+/* Define to 1 if you have the <sys/prctl.h> header file. */
+#define HAVE_SYS_PRCTL_H 1
+
+/* Define to 1 if you have the <sys/select.h> header file. */
+#define HAVE_SYS_SELECT_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/syscall.h> header file. */
+#define HAVE_SYS_SYSCALL_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have <sys/wait.h> that is POSIX.1 compatible. */
+#define HAVE_SYS_WAIT_H 1
+
+/* Define to 1 if you have the <termios.h> header file. */
+#define HAVE_TERMIOS_H 1
+
+/* Define if you don't have `tm_gmtoff' but do have the external variable
+ `timezone'. */
+/* #undef HAVE_TIMEZONE */
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the `uselocale' function. */
+#define HAVE_USELOCALE 1
+
+/* should we put our helper binaries to another location? */
+/* #undef HAVE_UTILBIN_DIR */
+
+/* Define to 1 if you have the <utmpx.h> header file. */
+#define HAVE_UTMPX_H 1
+
+/* Define to 1 if you have the <utmp.h> header file. */
+#define HAVE_UTMP_H 1
+
+/* define this if struct utmp has an ut_host member */
+#define HAVE_UT_HOST /**/
+
+/* define this if struct utmp has an ut_type member */
+#define HAVE_UT_TYPE /**/
+
+/* Define to 1 if you have the `vprintf' function. */
+#define HAVE_VPRINTF 1
+
+/* Define to 1 if you have the `vw_printw' function. */
+/* #undef HAVE_VW_PRINTW */
+
+/* Define to 1 if you have the `wcolor_set' function. */
+/* #undef HAVE_WCOLOR_SET */
+
+/* Define to 1 if you have the `wresize' function. */
+/* #undef HAVE_WRESIZE */
+
+/* define, if the user suplied a helpfile-directory to use. */
+/* #undef HELP_DIR */
+
+/* where to find our pot files */
+#define LOCALEDIR "/root/citadel/citadel"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "http://uncensored.citadel.org"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "Citadel"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "Citadel 951"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "citadel"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "951"
+
+/* Define as the return type of signal handlers (`int' or `void'). */
+#define RETSIGTYPE void
+
+/* define, where the config should go in unix style */
+/* #undef RUN_DIR */
+
+/* The size of `char', as computed by sizeof. */
+#define SIZEOF_CHAR 1
+
+/* The size of `int', as computed by sizeof. */
+#define SIZEOF_INT 4
+
+/* The size of `loff_t', as computed by sizeof. */
+#define SIZEOF_LOFF_T 8
+
+/* The size of `long', as computed by sizeof. */
+#define SIZEOF_LONG 8
+
+/* The size of `short', as computed by sizeof. */
+#define SIZEOF_SHORT 2
+
+/* The size of `size_t', as computed by sizeof. */
+#define SIZEOF_SIZE_T 8
+
+/* do we need to use solaris call syntax? */
+/* #undef SOLARIS_GETPWUID */
+
+/* where do we place our spool dirs? */
+/* #undef SPOOL_DIR */
+
+/* were should we put our keys? */
+#define SSL_DIR "/root/citadel/citadel/keys"
+
+/* where do we put our static text data? */
+/* #undef STATICDATA_DIR */
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
+#define TIME_WITH_SYS_TIME 1
+
+/* Define to 1 if your <sys/time.h> declares `struct tm'. */
+/* #undef TM_IN_SYS_TIME */
+
+/* Enable extensions on AIX 3, Interix. */
+#ifndef _ALL_SOURCE
+# define _ALL_SOURCE 1
+#endif
+/* Enable GNU extensions on systems that have them. */
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE 1
+#endif
+/* Enable threading extensions on Solaris. */
+#ifndef _POSIX_PTHREAD_SEMANTICS
+# define _POSIX_PTHREAD_SEMANTICS 1
+#endif
+/* Enable extensions on HP NonStop. */
+#ifndef _TANDEM_SOURCE
+# define _TANDEM_SOURCE 1
+#endif
+/* Enable general extensions on Solaris. */
+#ifndef __EXTENSIONS__
+# define __EXTENSIONS__ 1
+#endif
+
+
+/* were to put our helper programs */
+/* #undef UTILBIN_DIR */
+
+/* Define to 1 if on MINIX. */
+/* #undef _MINIX */
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+ this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef pid_t */
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+/* #undef size_t */
--- /dev/null
+
+#ifndef SYSDEP_DECLS_H
+#define SYSDEP_DECLS_H
+
+#include <stdarg.h>
+#include "sysdep.h"
+
+#ifdef HAVE_PTHREAD_H
+#include <pthread.h>
+#endif
+
+#include <db.h>
+
+#if DB_VERSION_MAJOR < 5
+#error Citadel requires Berkeley DB v5 or newer. Please upgrade.
+#endif
+
+#include "server.h"
+#include "database.h"
+
+#if SIZEOF_SIZE_T == SIZEOF_INT
+#define SIZE_T_FMT "%d"
+#else
+#define SIZE_T_FMT "%ld"
+#endif
+
+#if SIZEOF_LOFF_T == SIZEOF_LONG
+#define LOFF_T_FMT "%ld"
+#else
+#define LOFF_T_FMT "%lld"
+#endif
+
+void cputbuf(const StrBuf *Buf);
+
+#ifdef __GNUC__
+void cprintf (const char *format, ...) __attribute__((__format__(__printf__,1,2)));
+#else
+void cprintf (const char *format, ...);
+#endif
+
+void init_sysdep (void);
+int ctdl_tcp_server(char *ip_addr, int port_number, int queue_len);
+int ctdl_uds_server(char *sockpath, int queue_len);
+void buffer_output(void);
+void unbuffer_output(void);
+void flush_output(void);
+int client_write (const char *buf, int nbytes);
+int client_read_to (char *buf, int bytes, int timeout);
+int client_read (char *buf, int bytes);
+int client_getln (char *buf, int maxbytes);
+int CtdlClientGetLine(StrBuf *Target);
+int client_read_blob(StrBuf *Target, int bytes, int timeout);
+void client_set_inbound_buf(long N);
+int client_read_random_blob(StrBuf *Target, int timeout);
+void client_close(void);
+void sysdep_master_cleanup (void);
+void kill_session (int session_to_kill);
+void start_daemon (int do_close_stdio);
+void checkcrash(void);
+int convert_login (char *NameToConvert);
+void init_master_fdset(void);
+void *worker_thread(void *);
+
+extern volatile int exit_signal;
+extern volatile int shutdown_and_halt;
+extern volatile int running_as_daemon;
+extern volatile int restart_server;
+
+extern int verbosity;
+extern int rescan[];
+
+
+extern int SyslogFacility(char *name);
+
+#endif /* SYSDEP_DECLS_H */
--- /dev/null
+/*
+ * Thread handling stuff for Citadel server
+ *
+ * Copyright (c) 1987-2021 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.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <libcitadel.h>
+#include "modules_init.h"
+#include "serv_extensions.h"
+#include "ctdl_module.h"
+#include "config.h"
+#include "context.h"
+#include "threads.h"
+
+int num_workers = 0; /* Current number of worker threads */
+int active_workers = 0; /* Number of ACTIVE worker threads */
+pthread_key_t ThreadKey;
+pthread_mutex_t Critters[MAX_SEMAPHORES]; /* Things needing locking */
+struct thread_tsd masterTSD;
+int server_shutting_down = 0; /* set to nonzero during shutdown */
+pthread_mutex_t ThreadCountMutex;
+
+void InitializeSemaphores(void)
+{
+ int i;
+
+ /* Set up a bunch of semaphores to be used for critical sections */
+ for (i=0; i<MAX_SEMAPHORES; ++i) {
+ pthread_mutex_init(&Critters[i], NULL);
+ }
+}
+
+
+/*
+ * Obtain a semaphore lock to begin a critical section.
+ * but only if no one else has one
+ */
+int try_critical_section(int which_one)
+{
+ /* For all types of critical sections except those listed here,
+ * ensure nobody ever tries to do a critical section within a
+ * transaction; this could lead to deadlock.
+ */
+ if ( (which_one != S_FLOORCACHE)
+ && (which_one != S_NETCONFIGS)
+ ) {
+ cdb_check_handles();
+ }
+ return (pthread_mutex_trylock(&Critters[which_one]));
+}
+
+
+/*
+ * Obtain a semaphore lock to begin a critical section.
+ */
+void begin_critical_section(int which_one)
+{
+ /* For all types of critical sections except those listed here,
+ * ensure nobody ever tries to do a critical section within a
+ * transaction; this could lead to deadlock.
+ */
+ if ( (which_one != S_FLOORCACHE)
+ && (which_one != S_NETCONFIGS)
+ ) {
+ cdb_check_handles();
+ }
+ pthread_mutex_lock(&Critters[which_one]);
+}
+
+
+/*
+ * Release a semaphore lock to end a critical section.
+ */
+void end_critical_section(int which_one)
+{
+ pthread_mutex_unlock(&Critters[which_one]);
+}
+
+
+/*
+ * Return a pointer to our thread-specific (not session-specific) data.
+ */
+struct thread_tsd *MyThread(void) {
+ struct thread_tsd *c = (struct thread_tsd *) pthread_getspecific(ThreadKey) ;
+ if (!c) {
+ return &masterTSD;
+ }
+ return c;
+}
+
+
+/*
+ * Called by CtdlThreadCreate()
+ * We have to pass through here before starting our thread in order to create a set of data
+ * that is thread-specific rather than session-specific.
+ */
+void *CTC_backend(void *supplied_start_routine)
+{
+ struct thread_tsd *mytsd;
+ void *(*start_routine)(void*) = supplied_start_routine;
+
+ mytsd = (struct thread_tsd *) malloc(sizeof(struct thread_tsd));
+ memset(mytsd, 0, sizeof(struct thread_tsd));
+ pthread_setspecific(ThreadKey, (const void *) mytsd);
+
+ start_routine(NULL);
+ // free(mytsd);
+ return(NULL);
+}
+
+
+/*
+ * Function to create a thread.
+ */
+void CtdlThreadCreate(void *(*start_routine)(void*))
+{
+ pthread_t thread;
+ pthread_attr_t attr;
+ int ret = 0;
+
+ ret = pthread_attr_init(&attr);
+ ret = pthread_attr_setstacksize(&attr, THREADSTACKSIZE);
+ ret = pthread_create(&thread, &attr, CTC_backend, (void *)start_routine);
+ if (ret != 0) syslog(LOG_ERR, "pthread_create() : %m");
+}
+
+
+void InitializeMasterTSD(void) {
+ memset(&masterTSD, 0, sizeof(struct thread_tsd));
+}
+
+
+/*
+ * Initialize the thread system
+ */
+void go_threading(void) {
+ pthread_mutex_init(&ThreadCountMutex, NULL);
+
+ /* Second call to module init functions now that threading is up */
+ initialize_modules(1);
+
+ /* Begin with one worker thread. We will expand the pool if necessary */
+ CtdlThreadCreate(worker_thread);
+
+ /* The supervisor thread monitors worker threads and spawns more of them if it finds that
+ * they are all in use.
+ */
+ while (!server_shutting_down) {
+ if ((active_workers == num_workers) && (num_workers < CtdlGetConfigInt("c_max_workers"))) {
+ CtdlThreadCreate(worker_thread);
+ }
+ usleep(1000000);
+ }
+
+ /* When we get to this point we are getting ready to shut down our Citadel server */
+ terminate_all_sessions(); /* close all client sockets */
+ CtdlShutdownServiceHooks(); /* close all listener sockets to prevent new connections */
+ PerformSessionHooks(EVT_SHUTDOWN); /* run any registered shutdown hooks */
+
+ /* We used to wait for all threads to exit. Fuck that. The only thing important is that the databases are
+ * cleanly unmounted. After that, exit the whole program.
+ */
+}
--- /dev/null
+
+#ifndef THREADS_H
+#define THREADS_H
+
+#include "sysdep.h"
+
+#ifdef HAVE_PTHREAD_H
+#include <pthread.h>
+#endif
+
+#include <sys/time.h>
+#include <string.h>
+
+#include <db.h>
+
+#include "server.h"
+#include "sysdep_decls.h"
+
+/*
+ * Things we need to keep track of per-thread instead of per-session
+ */
+struct thread_tsd {
+ DB_TXN *tid; /* Transaction handle */
+ DBC *cursors[MAXCDB]; /* Cursors, for traversals... */
+};
+
+extern pthread_key_t ThreadKey;
+extern struct thread_tsd masterTSD;
+#define TSD MyThread()
+
+extern int num_workers;
+extern int active_workers;
+extern int server_shutting_down;
+
+struct thread_tsd *MyThread(void);
+int try_critical_section (int which_one);
+void begin_critical_section (int which_one);
+void end_critical_section (int which_one);
+void go_threading(void);
+void InitializeMasterTSD(void);
+void CtdlThreadCreate(void *(*start_routine)(void*));
+
+
+extern pthread_mutex_t ThreadCountMutex;;
+
+#endif // THREADS_H
--- /dev/null
+
+/*
+ This file defines typedefs for 8, 16, and 32 bit integers. They are:
+ cit_int8_t default 8-bit int
+ cit_int16_t default 16-bit int
+ cit_int32_t default 32-bit int
+ cit_int64_t default 64-bit int (not implemented yet)
+ cit_sint8_t signed 8-bit int
+ cit_sint16_t signed 16-bit int
+ cit_sint32_t signed 32-bit int
+ cit_sint64_t signed 64-bit int (not implemented yet)
+ cit_uint8_t unsigned 8-bit int
+ cit_uint16_t unsigned 16-bit int
+ cit_uint32_t unsigned 32-bit int
+ cit_uint64_t unsigned 64-bit int (not implemented yet)
+
+ The sizes are determined during the configure process; see the
+ AC_CHECK_SIZEOF macros in configure.in. In no way do we assume that any
+ given datatype is any particular width, e.g. we don't assume short is two
+ bytes; we check for it specifically.
+
+ This might seem excessively paranoid, but I've seen some WEIRD systems
+ and some bizarre compilers (Domain/OS for instance) in my time.
+*/
+
+#ifndef _CITADEL_UX_TYPESIZE_H
+#define _CITADEL_UX_TYPESIZE_H
+
+/* Include sysdep.h if not already included */
+#ifndef CTDLDIR
+# include "sysdep.h"
+#endif
+
+/* 8-bit - If this fails, your compiler is broken */
+#if SIZEOF_CHAR == 1
+typedef char cit_int8_t;
+typedef signed char cit_sint8_t;
+typedef unsigned char cit_uint8_t;
+#else
+# error Unable to find an 8-bit integer datatype
+#endif
+
+/* 16-bit - If this fails, your compiler is broken */
+#if SIZEOF_SHORT == 2
+typedef short cit_int16_t;
+typedef signed short cit_sint16_t;
+typedef unsigned short cit_uint16_t;
+#elif SIZEOF_INT == 2
+typedef int cit_int16_t;
+typedef signed int cit_sint16_t;
+typedef unsigned int cit_uint16_t;
+#else
+# error Unable to find a 16-bit integer datatype
+#endif
+
+/* 32-bit - If this fails, your compiler is broken */
+#if SIZEOF_INT == 4
+typedef int cit_int32_t;
+typedef signed int cit_sint32_t;
+typedef unsigned int cit_uint32_t;
+#elif SIZEOF_LONG == 4
+typedef long cit_int32_t;
+typedef signed long cit_sint32_t;
+typedef unsigned long cit_uint32_t;
+#else
+# error Unable to find a 32-bit integer datatype
+#endif
+
+#endif /* _CITADEL_UX_TYPESIZE_H */
--- /dev/null
+// Server functions which perform operations on user objects.
+//
+// Copyright (c) 1987-2022 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.
+
+#include <stdlib.h>
+#include <unistd.h>
+#include "sysdep.h"
+#include <stdio.h>
+#include <sys/stat.h>
+#include <libcitadel.h>
+#include "control.h"
+#include "support.h"
+#include "citserver.h"
+#include "config.h"
+#include "citadel_ldap.h"
+#include "ctdl_module.h"
+#include "user_ops.h"
+#include "internet_addressing.h"
+
+// These pipes are used to talk to the chkpwd daemon, which is forked during startup
+int chkpwd_write_pipe[2];
+int chkpwd_read_pipe[2];
+
+
+// makeuserkey() - convert a username into the format used as a database key
+// "key" must be a buffer of at least USERNAME_SIZE
+// (Key format is the username with all non-alphanumeric characters removed, and converted to lower case.)
+void makeuserkey(char *key, const char *username) {
+ int i;
+ int keylen = 0;
+
+ if (IsEmptyStr(username)) {
+ key[0] = 0;
+ return;
+ }
+
+ int len = strlen(username);
+ for (i=0; ((i<=len) && (i<USERNAME_SIZE-1)); ++i) {
+ if (isalnum((username[i]))) {
+ key[keylen++] = tolower(username[i]);
+ }
+ }
+ key[keylen++] = 0;
+}
+
+
+// Compare two usernames to see if they are the same user after being keyed for the database
+// Usage is identical to strcmp()
+int CtdlUserCmp(char *s1, char *s2) {
+ char k1[USERNAME_SIZE];
+ char k2[USERNAME_SIZE];
+
+ makeuserkey(k1, s1);
+ makeuserkey(k2, s2);
+ return(strcmp(k1,k2));
+}
+
+
+// CtdlGetUser() retrieve named user into supplied buffer.
+// returns 0 on success
+int CtdlGetUser(struct ctdluser *usbuf, char *name) {
+ char usernamekey[USERNAME_SIZE];
+ struct cdbdata *cdbus;
+
+ if (usbuf != NULL) {
+ memset(usbuf, 0, sizeof(struct ctdluser));
+ }
+
+ makeuserkey(usernamekey, name);
+ if (IsEmptyStr(usernamekey)) {
+ return(1); // empty user name
+ }
+ cdbus = cdb_fetch(CDB_USERS, usernamekey, strlen(usernamekey));
+
+ if (cdbus == NULL) { // user not found
+ return(1);
+ }
+ if (usbuf != NULL) {
+ memcpy(usbuf, cdbus->ptr, ((cdbus->len > sizeof(struct ctdluser)) ? sizeof(struct ctdluser) : cdbus->len));
+ }
+ cdb_free(cdbus);
+ return(0);
+}
+
+
+int CtdlLockGetCurrentUser(void) {
+ return CtdlGetUser(&CC->user, CC->curr_user);
+}
+
+
+// CtdlGetUserLock() - same as getuser() but locks the record
+int CtdlGetUserLock(struct ctdluser *usbuf, char *name) {
+ int retcode;
+
+ retcode = CtdlGetUser(usbuf, name);
+ if (retcode == 0) {
+ begin_critical_section(S_USERS);
+ }
+ return(retcode);
+}
+
+
+// CtdlPutUser() - write user buffer into the correct place on disk
+void CtdlPutUser(struct ctdluser *usbuf) {
+ char usernamekey[USERNAME_SIZE];
+ makeuserkey(usernamekey, usbuf->fullname);
+ usbuf->version = REV_LEVEL;
+ cdb_store(CDB_USERS, usernamekey, strlen(usernamekey), usbuf, sizeof(struct ctdluser));
+}
+
+
+void CtdlPutCurrentUserLock() {
+ CtdlPutUser(&CC->user);
+}
+
+
+// CtdlPutUserLock() - same as putuser() but locks the record
+void CtdlPutUserLock(struct ctdluser *usbuf) {
+ CtdlPutUser(usbuf);
+ end_critical_section(S_USERS);
+}
+
+
+// rename_user() - this is tricky because the user's display name is the database key
+// Returns 0 on success or nonzero if there was an error...
+int rename_user(char *oldname, char *newname) {
+ int retcode = RENAMEUSER_OK;
+ struct ctdluser usbuf;
+
+ char oldnamekey[USERNAME_SIZE];
+ char newnamekey[USERNAME_SIZE];
+
+ // Create the database keys...
+ makeuserkey(oldnamekey, oldname);
+ makeuserkey(newnamekey, newname);
+
+ // Lock up and get going
+ begin_critical_section(S_USERS);
+
+ // We cannot rename a user who is currently logged in
+ if (CtdlIsUserLoggedIn(oldname)) {
+ end_critical_section(S_USERS);
+ return RENAMEUSER_LOGGED_IN;
+ }
+
+ if (CtdlGetUser(&usbuf, newname) == 0) {
+ retcode = RENAMEUSER_ALREADY_EXISTS;
+ }
+ else {
+
+ if (CtdlGetUser(&usbuf, oldname) != 0) {
+ retcode = RENAMEUSER_NOT_FOUND;
+ }
+ else { // Sanity checks succeeded. Now rename the user.
+ if (usbuf.usernum == 0) {
+ syslog(LOG_DEBUG, "user_ops: can not rename user \"Citadel\".");
+ retcode = RENAMEUSER_NOT_FOUND;
+ }
+ else {
+ syslog(LOG_DEBUG, "user_ops: renaming <%s> to <%s>", oldname, newname);
+ cdb_delete(CDB_USERS, oldnamekey, strlen(oldnamekey));
+ safestrncpy(usbuf.fullname, newname, sizeof usbuf.fullname);
+ CtdlPutUser(&usbuf);
+ cdb_store(CDB_USERSBYNUMBER, &usbuf.usernum, sizeof(long), usbuf.fullname, strlen(usbuf.fullname)+1 );
+ retcode = RENAMEUSER_OK;
+ }
+ }
+
+ }
+
+ end_critical_section(S_USERS);
+ return(retcode);
+}
+
+
+// Convert a username into the format used as a database key prior to version 928
+// This only gets called by reindex_user_928()
+void makeuserkey_pre928(char *key, const char *username) {
+ int i;
+
+ int len = strlen(username);
+
+ if (len >= USERNAME_SIZE) {
+ syslog(LOG_INFO, "Username too long: %s", username);
+ len = USERNAME_SIZE - 1;
+ }
+ for (i=0; i<=len; ++i) {
+ key[i] = tolower(username[i]);
+ }
+}
+
+
+// Read a user record using the pre-v928 index format, and write it back using the v928-and-higher index format.
+// This ONLY gets called during an upgrade from version <928 to version >=928.
+void reindex_user_928(char *username, void *out_data) {
+
+ char oldkey[USERNAME_SIZE];
+ char newkey[USERNAME_SIZE];
+ struct cdbdata *cdbus;
+ struct ctdluser usbuf;
+
+ makeuserkey_pre928(oldkey, username);
+ makeuserkey(newkey, username);
+
+ syslog(LOG_DEBUG, "user_ops: reindex_user_928: %s <%s> --> <%s>", username, oldkey, newkey);
+
+ // Fetch the user record using the old index format
+ cdbus = cdb_fetch(CDB_USERS, oldkey, strlen(oldkey));
+ if (cdbus == NULL) {
+ syslog(LOG_INFO, "user_ops: <%s> not found, were they already reindexed?", username);
+ return;
+ }
+ memcpy(&usbuf, cdbus->ptr, ((cdbus->len > sizeof(struct ctdluser)) ? sizeof(struct ctdluser) : cdbus->len));
+ cdb_free(cdbus);
+
+ // delete the old record
+ cdb_delete(CDB_USERS, oldkey, strlen(oldkey));
+
+ // write the new record
+ cdb_store(CDB_USERS, newkey, strlen(newkey), &usbuf, sizeof(struct ctdluser));
+}
+
+
+// Index-generating function used by Ctdl[Get|Set]Relationship
+int GenerateRelationshipIndex(char *IndexBuf,
+ long RoomID,
+ long RoomGen,
+ long UserID
+) {
+ struct {
+ long iRoomID;
+ long iRoomGen;
+ long iUserID;
+ } TheIndex;
+
+ TheIndex.iRoomID = RoomID;
+ TheIndex.iRoomGen = RoomGen;
+ TheIndex.iUserID = UserID;
+
+ memcpy(IndexBuf, &TheIndex, sizeof(TheIndex));
+ return(sizeof(TheIndex));
+}
+
+
+// Back end for CtdlSetRelationship()
+void put_visit(visit *newvisit) {
+ char IndexBuf[32];
+ int IndexLen = 0;
+
+ memset (IndexBuf, 0, sizeof (IndexBuf));
+ // Generate an index
+ IndexLen = GenerateRelationshipIndex(IndexBuf, newvisit->v_roomnum, newvisit->v_roomgen, newvisit->v_usernum);
+
+ // Store the record
+ cdb_store(CDB_VISIT, IndexBuf, IndexLen,
+ newvisit, sizeof(visit)
+ );
+}
+
+
+// Define a relationship between a user and a room
+void CtdlSetRelationship(visit *newvisit, struct ctdluser *rel_user, struct ctdlroom *rel_room) {
+ // We don't use these in Citadel because they're implicit by the
+ // index, but they must be present if the database is exported.
+ newvisit->v_roomnum = rel_room->QRnumber;
+ newvisit->v_roomgen = rel_room->QRgen;
+ newvisit->v_usernum = rel_user->usernum;
+
+ put_visit(newvisit);
+}
+
+
+// Locate a relationship between a user and a room
+void CtdlGetRelationship(visit *vbuf, struct ctdluser *rel_user, struct ctdlroom *rel_room) {
+ char IndexBuf[32];
+ int IndexLen;
+ struct cdbdata *cdbvisit;
+
+ // Generate an index
+ IndexLen = GenerateRelationshipIndex(IndexBuf, rel_room->QRnumber, rel_room->QRgen, rel_user->usernum);
+
+ // Clear out the buffer
+ memset(vbuf, 0, sizeof(visit));
+
+ cdbvisit = cdb_fetch(CDB_VISIT, IndexBuf, IndexLen);
+ if (cdbvisit != NULL) {
+ memcpy(vbuf, cdbvisit->ptr, ((cdbvisit->len > sizeof(visit)) ? sizeof(visit) : cdbvisit->len));
+ cdb_free(cdbvisit);
+ }
+ else {
+ // If this is the first time the user has seen this room, set the view to be the default for the room.
+ vbuf->v_view = rel_room->QRdefaultview;
+ }
+
+ // Set v_seen if necessary
+ if (vbuf->v_seen[0] == 0) {
+ snprintf(vbuf->v_seen, sizeof vbuf->v_seen, "*:%ld", vbuf->v_lastseen);
+ }
+}
+
+
+void CtdlMailboxName(char *buf, size_t n, const struct ctdluser *who, const char *prefix) {
+ snprintf(buf, n, "%010ld.%s", who->usernum, prefix);
+}
+
+
+// Check to see if the specified user has Internet mail permission
+// (returns nonzero if permission is granted)
+int CtdlCheckInternetMailPermission(struct ctdluser *who) {
+
+ // Do not allow twits to send Internet mail
+ if (who->axlevel <= AxProbU) return(0);
+
+ // Globally enabled?
+ if (CtdlGetConfigInt("c_restrict") == 0) return(1);
+
+ // User flagged ok?
+ if (who->flags & US_INTERNET) return(2);
+
+ // Admin level access?
+ if (who->axlevel >= AxAideU) return(3);
+
+ // No mail for you!
+ return(0);
+}
+
+
+// This is a convenience function which follows the Citadel protocol semantics for most commands.
+// If the current user does not have the requested access level, it outputs a protocol-friendly error message
+// and then returns (-1). This allows calling functions to complete an access level check in one line of code.
+int CtdlAccessCheck(int required_level) {
+ if (CC->internal_pgm) return(0);
+ if (required_level >= ac_internal) {
+ cprintf("%d This is not a user-level command.\n", ERROR + HIGHER_ACCESS_REQUIRED);
+ return(-1);
+ }
+
+ if ((required_level >= ac_logged_in_or_guest) && (CC->logged_in == 0) && (CtdlGetConfigInt("c_guest_logins") == 0)) {
+ cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
+ return(-1);
+ }
+
+ if ((required_level >= ac_logged_in) && (CC->logged_in == 0)) {
+ cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
+ return(-1);
+ }
+
+ if (CC->user.axlevel >= AxAideU) return(0);
+ if (required_level >= ac_aide) {
+ cprintf("%d This command requires Admin access.\n",
+ ERROR + HIGHER_ACCESS_REQUIRED);
+ return(-1);
+ }
+
+ if (is_room_aide()) return(0);
+ if (required_level >= ac_room_aide) {
+ cprintf("%d This command requires Admin or Room Admin access.\n",
+ ERROR + HIGHER_ACCESS_REQUIRED);
+ return(-1);
+ }
+
+ // Do not generate any output if we succeeded -- the calling function will handle that.
+ return(0);
+}
+
+
+// Is the user currently logged in an Admin?
+int is_aide(void) {
+ if (CC->user.axlevel >= AxAideU) {
+ return(1);
+ }
+ else {
+ return(0);
+ }
+}
+
+
+// Is the user currently logged in an Admin *or* the room Admin for this room?
+int is_room_aide(void) {
+ if (!CC->logged_in) {
+ return(0);
+ }
+
+ if ((CC->user.axlevel >= AxAideU) || (CC->room.QRroomaide == CC->user.usernum)) {
+ return(1);
+ }
+ else {
+ return(0);
+ }
+}
+
+
+// CtdlGetUserByNumber() - get user by number, returns 0 if user was found
+// Note: fetching a user this way requires one additional database operation.
+int CtdlGetUserByNumber(struct ctdluser *usbuf, long number) {
+ struct cdbdata *cdbun;
+ int r;
+
+ cdbun = cdb_fetch(CDB_USERSBYNUMBER, &number, sizeof(long));
+ if (cdbun == NULL) {
+ syslog(LOG_INFO, "user_ops: %ld not found", number);
+ return(-1);
+ }
+
+ syslog(LOG_INFO, "user_ops: %ld maps to %s", number, cdbun->ptr);
+ r = CtdlGetUser(usbuf, cdbun->ptr);
+ cdb_free(cdbun);
+ return(r);
+}
+
+
+// Helper function for rebuild_usersbynumber()
+void rebuild_ubn_for_user(char *username, void *data) {
+ struct ctdluser u;
+
+ syslog(LOG_DEBUG, "user_ops: rebuilding usersbynumber index for %s", username);
+ if (CtdlGetUser(&u, username) == 0) {
+ cdb_store(CDB_USERSBYNUMBER, &(u.usernum), sizeof(long), u.fullname, strlen(u.fullname)+1);
+ }
+}
+
+
+// Rebuild the users-by-number index
+void rebuild_usersbynumber(void) {
+ cdb_trunc(CDB_USERSBYNUMBER); // delete the old indices
+ ForEachUser(rebuild_ubn_for_user, NULL); // enumerate the users
+}
+
+
+// getuserbyuid() Get user by system uid (for PAM mode authentication)
+// Returns 0 if user was found
+// This now uses an extauth index.
+int getuserbyuid(struct ctdluser *usbuf, uid_t number) {
+ struct cdbdata *cdbextauth;
+ long usernum = 0;
+ StrBuf *claimed_id;
+
+ claimed_id = NewStrBuf();
+ StrBufPrintf(claimed_id, "uid:%d", number);
+ cdbextauth = cdb_fetch(CDB_EXTAUTH, ChrPtr(claimed_id), StrLength(claimed_id));
+ FreeStrBuf(&claimed_id);
+ if (cdbextauth == NULL) {
+ return(-1);
+ }
+
+ memcpy(&usernum, cdbextauth->ptr, sizeof(long));
+ cdb_free(cdbextauth);
+
+ if (!CtdlGetUserByNumber(usbuf, usernum)) {
+ return(0);
+ }
+
+ return(-1);
+}
+
+
+// Back end for cmd_user() and its ilk
+int CtdlLoginExistingUser(const char *trythisname) {
+ char username[SIZ];
+ int found_user;
+
+ syslog(LOG_DEBUG, "user_ops: CtdlLoginExistingUser(%s)", trythisname);
+
+ if ((CC->logged_in)) {
+ return login_already_logged_in;
+ }
+
+ if (trythisname == NULL) return login_not_found;
+
+ if (!strncasecmp(trythisname, "SYS_", 4)) {
+ syslog(LOG_DEBUG, "user_ops: system user \"%s\" is not allowed to log in.", trythisname);
+ return login_not_found;
+ }
+
+ // Continue attempting user validation...
+ safestrncpy(username, trythisname, sizeof (username));
+ striplt(username);
+
+ if (IsEmptyStr(username)) {
+ return login_not_found;
+ }
+
+ // host auth mode...
+ if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_HOST) {
+ struct passwd pd;
+ struct passwd *tempPwdPtr;
+ char pwdbuffer[256];
+
+ syslog(LOG_DEBUG, "user_ops: asking host about <%s>", username);
+#ifdef HAVE_GETPWNAM_R
+#ifdef SOLARIS_GETPWUID
+ syslog(LOG_DEBUG, "user_ops: calling getpwnam_r()");
+ tempPwdPtr = getpwnam_r(username, &pd, pwdbuffer, sizeof pwdbuffer);
+#else // SOLARIS_GETPWUID
+ syslog(LOG_DEBUG, "user_ops: calling getpwnam_r()");
+ getpwnam_r(username, &pd, pwdbuffer, sizeof pwdbuffer, &tempPwdPtr);
+#endif // SOLARIS_GETPWUID
+#else // HAVE_GETPWNAM_R
+ syslog(LOG_DEBUG, "user_ops: SHOULD NEVER GET HERE!!!");
+ tempPwdPtr = NULL;
+#endif // HAVE_GETPWNAM_R
+ if (tempPwdPtr == NULL) {
+ syslog(LOG_DEBUG, "user_ops: no such user <%s>", username);
+ return login_not_found;
+ }
+
+ // Locate the associated Citadel account. If not found, make one attempt to create it.
+ found_user = getuserbyuid(&CC->user, pd.pw_uid);
+ if (found_user != 0) {
+ create_user(username, CREATE_USER_DO_NOT_BECOME_USER, pd.pw_uid);
+ found_user = getuserbyuid(&CC->user, pd.pw_uid);
+ }
+ syslog(LOG_DEBUG, "user_ops: found it: uid=%ld, gecos=%s here: %d", (long)pd.pw_uid, pd.pw_gecos, found_user);
+
+ }
+
+ // LDAP auth mode...
+ else if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) {
+
+ uid_t ldap_uid;
+ char ldap_cn[256];
+ char ldap_dn[256];
+
+ found_user = CtdlTryUserLDAP(username, ldap_dn, sizeof ldap_dn, ldap_cn, sizeof ldap_cn, &ldap_uid);
+ if (found_user != 0) {
+ return login_not_found;
+ }
+
+ found_user = getuserbyuid(&CC->user, ldap_uid);
+ if (found_user != 0) {
+ create_user(ldap_cn, CREATE_USER_DO_NOT_BECOME_USER, ldap_uid);
+ found_user = getuserbyuid(&CC->user, ldap_uid);
+ }
+
+ if (found_user == 0) {
+ if (CC->ldap_dn != NULL) free(CC->ldap_dn);
+ CC->ldap_dn = strdup(ldap_dn);
+ }
+
+ }
+
+ // native auth mode...
+ else {
+ struct recptypes *valid = NULL;
+
+ // First, try to log in as if the supplied name is a display name
+ found_user = CtdlGetUser(&CC->user, username);
+
+ // If that didn't work, try to log in as if the supplied name * is an e-mail address
+ if (found_user != 0) {
+ valid = validate_recipients(username, NULL, 0);
+ if (valid != NULL) {
+ if (valid->num_local == 1) {
+ found_user = CtdlGetUser(&CC->user, valid->recp_local);
+ }
+ free_recipients(valid);
+ }
+ }
+ }
+
+ // Did we find something?
+ if (found_user == 0) {
+ if (((CC->nologin)) && (CC->user.axlevel < AxAideU)) {
+ return login_too_many_users;
+ }
+ else {
+ safestrncpy(CC->curr_user, CC->user.fullname, sizeof CC->curr_user);
+ return login_ok;
+ }
+ }
+ return login_not_found;
+}
+
+
+// session startup code which is common to both cmd_pass() and cmd_newu()
+void do_login(void) {
+ CC->logged_in = 1;
+ syslog(LOG_NOTICE, "user_ops: <%s> logged in", CC->curr_user);
+
+ CtdlGetUserLock(&CC->user, CC->curr_user);
+ ++(CC->user.timescalled);
+ CC->previous_login = CC->user.lastcall;
+ time(&CC->user.lastcall);
+
+ // If this user's name is the name of the system administrator
+ // (as specified in setup), automatically assign access level 6.
+ if ( (!IsEmptyStr(CtdlGetConfigStr("c_sysadm"))) && (!strcasecmp(CC->user.fullname, CtdlGetConfigStr("c_sysadm"))) ) {
+ CC->user.axlevel = AxAideU;
+ }
+
+ // If we're authenticating off the host system, automatically give root the highest level of access.
+ if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_HOST) {
+ if (CC->user.uid == 0) {
+ CC->user.axlevel = AxAideU;
+ }
+ }
+ CtdlPutUserLock(&CC->user);
+
+ // If we are using LDAP authentication, extract the user's email addresses from the directory.
+ if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) {
+ char new_emailaddrs[512];
+ if (CtdlGetConfigInt("c_ldap_sync_email_addrs") > 0) {
+ if (extract_email_addresses_from_ldap(CC->ldap_dn, new_emailaddrs) == 0) {
+ CtdlSetEmailAddressesForUser(CC->user.fullname, new_emailaddrs);
+ }
+ }
+ }
+
+ // If the user does not have any email addresses assigned, generate one.
+ if (IsEmptyStr(CC->user.emailaddrs)) {
+ AutoGenerateEmailAddressForUser(&CC->user);
+ }
+
+ // Populate the user principal identity, which is consistent and never aliased
+ strcpy(CC->cs_principal_id, "");
+ makeuserkey(CC->cs_principal_id, CC->user.fullname);
+ strcat(CC->cs_principal_id, "@");
+ strcat(CC->cs_principal_id, CtdlGetConfigStr("c_fqdn"));
+
+ // Populate cs_inet_email and cs_inet_other_emails with valid email addresses from the user record
+ strcpy(CC->cs_inet_email, CC->user.emailaddrs);
+ char *firstsep = strstr(CC->cs_inet_email, "|");
+ if (firstsep) {
+ strcpy(CC->cs_inet_other_emails, firstsep+1);
+ *firstsep = 0;
+ }
+ else {
+ CC->cs_inet_other_emails[0] = 0;
+ }
+
+ // Create any personal rooms required by the system.
+ // (Technically, MAILROOM should be there already, but just in case...)
+ CtdlCreateRoom(MAILROOM, 4, "", 0, 1, 0, VIEW_MAILBOX);
+ CtdlCreateRoom(SENTITEMS, 4, "", 0, 1, 0, VIEW_MAILBOX);
+ CtdlCreateRoom(USERTRASHROOM, 4, "", 0, 1, 0, VIEW_MAILBOX);
+ CtdlCreateRoom(USERDRAFTROOM, 4, "", 0, 1, 0, VIEW_MAILBOX);
+
+ // Run any startup routines registered by loadable modules
+ PerformSessionHooks(EVT_LOGIN);
+
+ // Enter the lobby
+ CtdlUserGoto(CtdlGetConfigStr("c_baseroom"), 0, 0, NULL, NULL, NULL, NULL);
+}
+
+
+void logged_in_response(void) {
+ cprintf("%d %s|%d|%ld|%ld|%u|%ld|%ld\n",
+ CIT_OK, CC->user.fullname, CC->user.axlevel,
+ CC->user.timescalled, CC->user.posted,
+ CC->user.flags, CC->user.usernum,
+ CC->previous_login
+ );
+}
+
+
+void CtdlUserLogout(void) {
+
+ syslog(LOG_DEBUG, "user_ops: CtdlUserLogout() logging out <%s> from session %d", CC->curr_user, CC->cs_pid);
+
+ // Run any hooks registered by modules...
+ PerformSessionHooks(EVT_LOGOUT);
+
+ // Clear out some session data. Most likely, the CitContext for this
+ // session is about to get nuked when the session disconnects, but
+ // since it's possible to log in again without reconnecting, we cannot
+ // make that assumption.
+ CC->logged_in = 0;
+
+ // Check to see if the user was deleted while logged in and purge them if necessary
+ if ((CC->user.axlevel == AxDeleted) && (CC->user.usernum)) {
+ purge_user(CC->user.fullname);
+ }
+
+ // Clear out the user record in memory so we don't behave like a ghost
+ memset(&CC->user, 0, sizeof(struct ctdluser));
+ CC->curr_user[0] = 0;
+ CC->cs_inet_email[0] = 0;
+ CC->cs_inet_other_emails[0] = 0;
+ CC->cs_inet_fn[0] = 0;
+
+ // Free any output buffers
+ unbuffer_output();
+}
+
+
+// Validate a password on the host unix system by talking to the chkpwd daemon
+static int validpw(uid_t uid, const char *pass) {
+ char buf[256];
+ int rv = 0;
+
+ if (IsEmptyStr(pass)) {
+ syslog(LOG_DEBUG, "user_ops: refusing to chkpwd for uid=%d with empty password", uid);
+ return 0;
+ }
+
+ syslog(LOG_DEBUG, "user_ops: validating password for uid=%d using chkpwd...", uid);
+
+ begin_critical_section(S_CHKPWD);
+ rv = write(chkpwd_write_pipe[1], &uid, sizeof(uid_t));
+ if (rv == -1) {
+ syslog(LOG_ERR, "user_ops: communication with chkpwd broken: %m");
+ end_critical_section(S_CHKPWD);
+ return 0;
+ }
+ rv = write(chkpwd_write_pipe[1], pass, 256);
+ if (rv == -1) {
+ syslog(LOG_ERR, "user_ops: communication with chkpwd broken: %m");
+ end_critical_section(S_CHKPWD);
+ return 0;
+ }
+ rv = read(chkpwd_read_pipe[0], buf, 4);
+ if (rv == -1) {
+ syslog(LOG_ERR, "user_ops: ommunication with chkpwd broken: %m");
+ end_critical_section(S_CHKPWD);
+ return 0;
+ }
+ end_critical_section(S_CHKPWD);
+
+ if (!strncmp(buf, "PASS", 4)) {
+ syslog(LOG_DEBUG, "user_ops: chkpwd pass");
+ return(1);
+ }
+
+ syslog(LOG_DEBUG, "user_ops: chkpwd fail");
+ return 0;
+}
+
+
+// Start up the chkpwd daemon so validpw() has something to talk to
+void start_chkpwd_daemon(void) {
+ pid_t chkpwd_pid;
+ struct stat filestats;
+ int i;
+
+ syslog(LOG_DEBUG, "user_ops: starting chkpwd daemon for host authentication mode");
+
+ if ((stat(file_chkpwd, &filestats)==-1) || (filestats.st_size==0)) {
+ syslog(LOG_ERR, "user_ops: %s: %m", file_chkpwd);
+ abort();
+ }
+ if (pipe(chkpwd_write_pipe) != 0) {
+ syslog(LOG_ERR, "user_ops: unable to create pipe for chkpwd daemon: %m");
+ abort();
+ }
+ if (pipe(chkpwd_read_pipe) != 0) {
+ syslog(LOG_ERR, "user_ops: unable to create pipe for chkpwd daemon: %m");
+ abort();
+ }
+
+ chkpwd_pid = fork();
+ if (chkpwd_pid < 0) {
+ syslog(LOG_ERR, "user_ops: unable to fork chkpwd daemon: %m");
+ abort();
+ }
+ if (chkpwd_pid == 0) {
+ dup2(chkpwd_write_pipe[0], 0);
+ dup2(chkpwd_read_pipe[1], 1);
+ for (i=2; i<256; ++i) close(i);
+ execl(file_chkpwd, file_chkpwd, NULL);
+ syslog(LOG_ERR, "user_ops: unable to exec chkpwd daemon: %m");
+ abort();
+ exit(errno);
+ }
+}
+
+
+int CtdlTryPassword(const char *password, long len) {
+ int code;
+
+ if ((CC->logged_in)) {
+ syslog(LOG_WARNING, "user_ops: CtdlTryPassword: already logged in");
+ return pass_already_logged_in;
+ }
+ if (!strcmp(CC->curr_user, NLI)) {
+ syslog(LOG_WARNING, "user_ops: CtdlTryPassword: no user selected");
+ return pass_no_user;
+ }
+ if (CtdlGetUser(&CC->user, CC->curr_user)) {
+ syslog(LOG_ERR, "user_ops: CtdlTryPassword: internal error");
+ return pass_internal_error;
+ }
+ if (password == NULL) {
+ syslog(LOG_INFO, "user_ops: CtdlTryPassword: NULL password string supplied");
+ return pass_wrong_password;
+ }
+
+ // host auth mode...
+ else if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_HOST) {
+ if (validpw(CC->user.uid, password)) {
+ code = 0;
+
+ // sooper-seekrit hack: populate the password field in the
+ // citadel database with the password that the user typed,
+ // if it's correct. This allows most sites to convert from
+ // host auth to native auth if they want to. If you think
+ // this is a security hazard, comment it out.
+
+ CtdlGetUserLock(&CC->user, CC->curr_user);
+ safestrncpy(CC->user.password, password, sizeof CC->user.password);
+ CtdlPutUserLock(&CC->user);
+
+ // (sooper-seekrit hack ends here)
+ }
+ else {
+ code = (-1);
+ }
+ }
+
+ // LDAP auth mode...
+ else if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) {
+
+ if ((CC->ldap_dn) && (!CtdlTryPasswordLDAP(CC->ldap_dn, password))) {
+ code = 0;
+ }
+ else {
+ code = (-1);
+ }
+ }
+
+ // native auth mode...
+ else {
+ char *pw;
+
+ pw = (char*) malloc(len + 1);
+ memcpy(pw, password, len + 1);
+ strproc(pw);
+ strproc(CC->user.password);
+ code = strcasecmp(CC->user.password, pw);
+ if (code != 0) {
+ strproc(pw);
+ strproc(CC->user.password);
+ code = strcasecmp(CC->user.password, pw);
+ }
+ free (pw);
+ }
+
+ if (!code) {
+ do_login();
+ return pass_ok;
+ }
+ else {
+ syslog(LOG_WARNING, "user_ops: bad password specified for <%s> Service <%s> Port <%ld> Remote <%s / %s>",
+ CC->curr_user,
+ CC->ServiceName,
+ CC->tcp_port,
+ CC->cs_host,
+ CC->cs_addr
+ );
+ return pass_wrong_password;
+ }
+}
+
+
+// Delete a user record *and* all of its related resources.
+int purge_user(char pname[]) {
+ struct ctdluser usbuf;
+ char usernamekey[USERNAME_SIZE];
+
+ makeuserkey(usernamekey, pname);
+
+ // If the name is empty we can't find them in the DB any way so just return
+ if (IsEmptyStr(pname)) {
+ return(ERROR + NO_SUCH_USER);
+ }
+
+ if (CtdlGetUser(&usbuf, pname) != 0) {
+ syslog(LOG_ERR, "user_ops: cannot purge user <%s> - not found", pname);
+ return(ERROR + NO_SUCH_USER);
+ }
+
+ // Don't delete a user who is currently logged in. Instead, just
+ // set the access level to 0, and let the account get swept up
+ // during the next purge.
+ if (CtdlIsUserLoggedInByNum(usbuf.usernum)) {
+ syslog(LOG_WARNING, "user_ops: <%s> is logged in; not deleting", pname);
+ usbuf.axlevel = AxDeleted;
+ CtdlPutUser(&usbuf);
+ return(1);
+ }
+
+ syslog(LOG_NOTICE, "user_ops: deleting <%s>", pname);
+
+ // Perform any purge functions registered by server extensions
+ PerformUserHooks(&usbuf, EVT_PURGEUSER);
+
+ // delete any existing user/room relationships
+ cdb_delete(CDB_VISIT, &usbuf.usernum, sizeof(long));
+
+ // delete the users-by-number index record
+ cdb_delete(CDB_USERSBYNUMBER, &usbuf.usernum, sizeof(long));
+
+ // delete the user entry
+ cdb_delete(CDB_USERS, usernamekey, strlen(usernamekey));
+
+ return(0);
+}
+
+
+// This is the back end processing that happens when we create a new user account.
+int internal_create_user(char *username, struct ctdluser *usbuf, uid_t uid) {
+ if (!CtdlGetUser(usbuf, username)) {
+ return(ERROR + ALREADY_EXISTS);
+ }
+
+ // Go ahead and initialize a new user record
+ memset(usbuf, 0, sizeof(struct ctdluser));
+ safestrncpy(usbuf->fullname, username, sizeof usbuf->fullname);
+ strcpy(usbuf->password, "");
+ usbuf->uid = uid;
+
+ // These are the default flags on new accounts
+ usbuf->flags = US_LASTOLD | US_DISAPPEAR | US_PAGINATOR | US_FLOORS;
+
+ usbuf->timescalled = 0;
+ usbuf->posted = 0;
+ usbuf->axlevel = CtdlGetConfigInt("c_initax");
+ usbuf->lastcall = time(NULL);
+
+ // fetch a new user number
+ usbuf->usernum = get_new_user_number();
+
+ // add user to the database
+ CtdlPutUser(usbuf);
+ cdb_store(CDB_USERSBYNUMBER, &usbuf->usernum, sizeof(long), usbuf->fullname, strlen(usbuf->fullname)+1);
+
+ // If non-native auth, index by uid
+ if ((usbuf->uid > 0) && (usbuf->uid != NATIVE_AUTH_UID)) {
+ StrBuf *claimed_id = NewStrBuf();
+ StrBufPrintf(claimed_id, "uid:%d", usbuf->uid);
+ attach_extauth(usbuf, claimed_id);
+ FreeStrBuf(&claimed_id);
+ }
+
+ return(0);
+}
+
+
+// create_user() - back end processing to create a new user
+//
+// Set 'newusername' to the desired account name.
+// Set 'become_user' to CREATE_USER_BECOME_USER if this is self-service account creation and we want to
+// actually log in as the user we just created, otherwise set it to CREATE_USER_DO_NOT_BECOME_USER
+// Set 'uid' to some uid_t value to associate the account with an external auth user, or (-1) for native auth
+int create_user(char *username, int become_user, uid_t uid) {
+ struct ctdluser usbuf;
+ struct ctdlroom qrbuf;
+ char mailboxname[ROOMNAMELEN];
+ char buf[SIZ];
+ int retval;
+
+ strproc(username);
+ if ((retval = internal_create_user(username, &usbuf, uid)) != 0) {
+ return retval;
+ }
+
+ // Give the user a private mailbox and a configuration room.
+ // Make the latter an invisible system room.
+ CtdlMailboxName(mailboxname, sizeof mailboxname, &usbuf, MAILROOM);
+ CtdlCreateRoom(mailboxname, 5, "", 0, 1, 1, VIEW_MAILBOX);
+
+ CtdlMailboxName(mailboxname, sizeof mailboxname, &usbuf, USERCONFIGROOM);
+ CtdlCreateRoom(mailboxname, 5, "", 0, 1, 1, VIEW_BBS);
+ if (CtdlGetRoomLock(&qrbuf, mailboxname) == 0) {
+ qrbuf.QRflags2 |= QR2_SYSTEM;
+ CtdlPutRoomLock(&qrbuf);
+ }
+
+ // Perform any create functions registered by server extensions
+ PerformUserHooks(&usbuf, EVT_NEWUSER);
+
+ // Everything below this line can be bypassed if administratively
+ // creating a user, instead of doing self-service account creation
+
+ if (become_user == CREATE_USER_BECOME_USER) {
+ // Now become the user we just created
+ memcpy(&CC->user, &usbuf, sizeof(struct ctdluser));
+ safestrncpy(CC->curr_user, username, sizeof CC->curr_user);
+ do_login();
+
+ // Check to make sure we're still who we think we are
+ if (CtdlGetUser(&CC->user, CC->curr_user)) {
+ return(ERROR + INTERNAL_ERROR);
+ }
+ }
+
+ snprintf(buf, SIZ,
+ "New user account <%s> has been created, from host %s [%s].\n",
+ username,
+ CC->cs_host,
+ CC->cs_addr
+ );
+ CtdlAideMessage(buf, "User Creation Notice");
+ syslog(LOG_NOTICE, "user_ops: <%s> created", username);
+ return(0);
+}
+
+
+// set password - back end api code
+void CtdlSetPassword(char *new_pw) {
+ CtdlGetUserLock(&CC->user, CC->curr_user);
+ safestrncpy(CC->user.password, new_pw, sizeof(CC->user.password));
+ CtdlPutUserLock(&CC->user);
+ syslog(LOG_INFO, "user_ops: password changed for <%s>", CC->curr_user);
+ PerformSessionHooks(EVT_SETPASS);
+}
+
+
+// API function for cmd_invt_kick() and anything else that needs to
+// invite or kick out a user to/from a room.
+//
+// Set iuser to the name of the user, and op to 1=invite or 0=kick
+int CtdlInvtKick(char *iuser, int op) {
+ struct ctdluser USscratch;
+ visit vbuf;
+ char bbb[SIZ];
+
+ if (CtdlGetUser(&USscratch, iuser) != 0) {
+ return(1);
+ }
+
+ CtdlGetRelationship(&vbuf, &USscratch, &CC->room);
+ if (op == 1) {
+ vbuf.v_flags = vbuf.v_flags & ~V_FORGET & ~V_LOCKOUT;
+ vbuf.v_flags = vbuf.v_flags | V_ACCESS;
+ }
+ if (op == 0) {
+ vbuf.v_flags = vbuf.v_flags & ~V_ACCESS;
+ vbuf.v_flags = vbuf.v_flags | V_FORGET | V_LOCKOUT;
+ }
+ CtdlSetRelationship(&vbuf, &USscratch, &CC->room);
+
+ // post a message in Aide> saying what we just did
+ snprintf(bbb, sizeof bbb, "%s has been %s \"%s\" by %s.\n",
+ iuser,
+ ((op == 1) ? "invited to" : "kicked out of"),
+ CC->room.QRname,
+ (CC->logged_in ? CC->user.fullname : "an administrator")
+ );
+ CtdlAideMessage(bbb,"User Admin Message");
+ return(0);
+}
+
+
+// Forget (Zap) the current room (API call)
+// Returns 0 on success
+int CtdlForgetThisRoom(void) {
+ visit vbuf;
+
+ // On some systems, Admins are not allowed to forget rooms
+ if (is_aide() && (CtdlGetConfigInt("c_aide_zap") == 0)
+ && ((CC->room.QRflags & QR_MAILBOX) == 0) ) {
+ return(1);
+ }
+
+ CtdlGetUserLock(&CC->user, CC->curr_user);
+ CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
+
+ vbuf.v_flags = vbuf.v_flags | V_FORGET;
+ vbuf.v_flags = vbuf.v_flags & ~V_ACCESS;
+
+ CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
+ CtdlPutUserLock(&CC->user);
+
+ // Return to the Lobby, so we don't end up in an undefined room
+ CtdlUserGoto(CtdlGetConfigStr("c_baseroom"), 0, 0, NULL, NULL, NULL, NULL);
+ return(0);
+}
+
+
+// Traverse the user file and perform a callback for each user record.
+// (New improved version that runs in two phases so that callbacks can perform writes without having a r/o cursor open)
+void ForEachUser(void (*CallBack) (char *, void *out_data), void *in_data) {
+ struct cdbdata *cdbus;
+ struct ctdluser *usptr;
+
+ struct feu {
+ struct feu *next;
+ char username[USERNAME_SIZE];
+ };
+ struct feu *ufirst = NULL;
+ struct feu *ulast = NULL;
+ struct feu *f = NULL;
+
+ cdb_rewind(CDB_USERS);
+
+ // Phase 1 : build a linked list of all our user account names
+ while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL) {
+ usptr = (struct ctdluser *) cdbus->ptr;
+
+ if (strlen(usptr->fullname) > 0) {
+ f = malloc(sizeof(struct feu));
+ f->next = NULL;
+ strncpy(f->username, usptr->fullname, USERNAME_SIZE);
+
+ if (ufirst == NULL) {
+ ufirst = f;
+ ulast = f;
+ }
+ else {
+ ulast->next = f;
+ ulast = f;
+ }
+ }
+ }
+
+ // Phase 2 : perform the callback for each user while de-allocating the list
+ while (ufirst != NULL) {
+ (*CallBack) (ufirst->username, in_data);
+ f = ufirst;
+ ufirst = ufirst->next;
+ free(f);
+ }
+}
+
+
+// Count the number of new mail messages the user has
+int NewMailCount() {
+ int num_newmsgs = 0;
+ num_newmsgs = CC->newmail;
+ CC->newmail = 0;
+ return(num_newmsgs);
+}
+
+
+// Count the number of new mail messages the user has
+int InitialMailCheck() {
+ int num_newmsgs = 0;
+ int a;
+ char mailboxname[ROOMNAMELEN];
+ struct ctdlroom mailbox;
+ visit vbuf;
+ struct cdbdata *cdbfr;
+ long *msglist = NULL;
+ int num_msgs = 0;
+
+ CtdlMailboxName(mailboxname, sizeof mailboxname, &CC->user, MAILROOM);
+ if (CtdlGetRoom(&mailbox, mailboxname) != 0)
+ return(0);
+ CtdlGetRelationship(&vbuf, &CC->user, &mailbox);
+
+ cdbfr = cdb_fetch(CDB_MSGLISTS, &mailbox.QRnumber, sizeof(long));
+
+ if (cdbfr != NULL) {
+ msglist = malloc(cdbfr->len);
+ memcpy(msglist, cdbfr->ptr, cdbfr->len);
+ num_msgs = cdbfr->len / sizeof(long);
+ cdb_free(cdbfr);
+ }
+ if (num_msgs > 0)
+ for (a = 0; a < num_msgs; ++a) {
+ if (msglist[a] > 0L) {
+ if (msglist[a] > vbuf.v_lastseen) {
+ ++num_newmsgs;
+ }
+ }
+ }
+ if (msglist != NULL)
+ free(msglist);
+
+ return(num_newmsgs);
+}
--- /dev/null
+// Header file for server functions which perform operations on user objects.
+//
+// Copyright (c) 1987-2022 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.
+
+#ifndef __USER_OPS_H__
+#define __USER_OPS_H__
+
+#include <ctype.h>
+#include <syslog.h>
+
+int hash (char *str);
+int is_aide (void);
+int is_room_aide (void);
+int CtdlCheckInternetMailPermission(struct ctdluser *who);
+void rebuild_usersbynumber(void);
+void session_startup (void);
+void logged_in_response(void);
+int purge_user (char *pname);
+int getuserbyuid(struct ctdluser *usbuf, uid_t number);
+
+int create_user(char *newusername, int become_user, uid_t uid);
+enum {
+ CREATE_USER_DO_NOT_BECOME_USER,
+ CREATE_USER_BECOME_USER
+};
+#define NATIVE_AUTH_UID (-1)
+
+void do_login(void);
+int CtdlInvtKick(char *iuser, int op);
+void ForEachUser(void (*CallBack) (char *, void *out_data), void *in_data);
+int NewMailCount(void);
+int InitialMailCheck(void);
+void put_visit(visit *newvisit);
+int GenerateRelationshipIndex(char *IndexBuf, long RoomID, long RoomGen, long UserID);
+int CtdlAssociateSystemUser(char *screenname, char *loginname);
+
+void CtdlSetPassword(char *new_pw);
+
+int CtdlForgetThisRoom(void);
+
+void cmd_newu (char *cmdbuf);
+void start_chkpwd_daemon(void);
+
+
+#define RENAMEUSER_OK 0 // Operation succeeded
+#define RENAMEUSER_LOGGED_IN 1 // Cannot rename a user who is currently logged in
+#define RENAMEUSER_NOT_FOUND 2 // The old user name does not exist
+#define RENAMEUSER_ALREADY_EXISTS 3 // An account with the desired new name already exists
+
+int rename_user(char *oldname, char *newname);
+void reindex_user_928(char *username, void *out_data);
+
+void makeuserkey(char *key, const char *username);
+int CtdlUserCmp(char *s1, char *s2);
+int internal_create_user(char *username, struct ctdluser *usbuf, uid_t uid);
+
+#endif
+++ /dev/null
-// citserver's main() function lives here.
-//
-// Copyright (c) 1987-2022 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.
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <grp.h>
-#include <sys/file.h>
-#include <libcitadel.h>
-#include "citserver.h"
-#include "svn_revision.h"
-#include "modules_init.h"
-#include "config.h"
-#include "control.h"
-#include "serv_extensions.h"
-#include "citadel_dirs.h"
-#include "user_ops.h"
-
-uid_t ctdluid = 0;
-const char *CitadelServiceUDS="citadel-UDS";
-const char *CitadelServiceTCP="citadel-TCP";
-int sanity_diag_mode = 0;
-
-
-// Create or remove a lock file, so we only have one Citadel Server running at a time.
-// Set 'op' to nonzero to lock, zero to unlock.
-void ctdl_lockfile(int op) {
- static char lockfilename[PATH_MAX];
- static FILE *fp;
-
- if (op) {
- syslog(LOG_DEBUG, "main: creating lockfile");
- snprintf(lockfilename, sizeof lockfilename, "%s/citadel.lock", ctdl_run_dir);
- fp = fopen(lockfilename, "w");
- if (!fp) {
- syslog(LOG_ERR, "%s: %m", lockfilename);
- exit(CTDLEXIT_DB);
- }
- if (flock(fileno(fp), (LOCK_EX|LOCK_NB)) != 0) {
- syslog(LOG_ERR, "main: cannot lock %s , is another citserver running?", lockfilename);
- exit(CTDLEXIT_DB);
- }
- return;
- }
-
- syslog(LOG_DEBUG, "main: removing lockfile");
- unlink(lockfilename);
- flock(fileno(fp), LOCK_UN);
- fclose(fp);
-}
-
-
-// Here's where it all begins.
-int main(int argc, char **argv) {
-
- char facility[32];
- int a; // General-purpose variables
- struct passwd pw, *pwp = NULL;
- char pwbuf[SIZ];
- int drop_root_perms = 1;
- int max_log_level = LOG_INFO;
- char *ctdldir = CTDLDIR;
- int syslog_facility = LOG_DAEMON;
- uid_t u = 0;
- struct passwd *p = NULL;
-#ifdef HAVE_RUN_DIR
- struct stat filestats;
-#endif
-
- // Tell 'em who's in da house
- syslog(LOG_INFO, " ");
- syslog(LOG_INFO, " ");
- syslog(LOG_INFO, "*** Citadel server engine ***\n");
- syslog(LOG_INFO, "Version %d (build %s) ***", REV_LEVEL, svn_revision());
- syslog(LOG_INFO, "Copyright (C) 1987-2022 by the Citadel development team.");
- syslog(LOG_INFO, " ");
- syslog(LOG_INFO, "This program is open source software: you can redistribute it and/or");
- syslog(LOG_INFO, "modify it under the terms of the GNU General Public License, version 3.");
- syslog(LOG_INFO, " ");
- syslog(LOG_INFO, "This program is distributed in the hope that it will be useful,");
- syslog(LOG_INFO, "but WITHOUT ANY WARRANTY; without even the implied warranty of");
- syslog(LOG_INFO, "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the");
- syslog(LOG_INFO, "GNU General Public License for more details.");
- syslog(LOG_INFO, " ");
- syslog(LOG_INFO, "%s", libcitadel_version_string());
-
- // parse command-line arguments
- while ((a=getopt(argc, argv, "cl:dh:x:t:B:Dru:s:")) != EOF) switch(a) {
-
- // test this binary for compatibility and exit
- case 'c':
- fprintf(stderr, "%s: binary compatibility confirmed\n", argv[0]);
- exit(0);
- break;
-
- // identify the desired syslog facility
- case 'l':
- safestrncpy(facility, optarg, sizeof(facility));
- syslog_facility = SyslogFacility(facility);
- break;
-
- // run in the background if -d was specified
- case 'd':
- running_as_daemon = 1;
- break;
-
- // specify the data directory
- case 'h':
- ctdldir = optarg;
- break;
-
- // identify the desired logging severity level
- case 'x':
- max_log_level = atoi(optarg);
- break;
-
- // deprecated
- case 't':
- break;
-
- // deprecated
- case 'B':
- break;
-
- // deprecated
- case 'D':
- break;
-
- // -r tells the server not to drop root permissions.
- // Don't use this unless you know what you're doing.
- case 'r':
- drop_root_perms = 0;
- break;
-
- // -u tells the server what uid to run under...
- case 'u':
- u = atoi(optarg);
- if (u > 0) {
- ctdluid = u;
- }
- else {
- p = getpwnam(optarg);
- if (p) {
- u = p->pw_uid;
- }
- }
- if (u > 0) {
- ctdluid = u;
- }
- break;
-
- // -s tells the server to behave differently during sanity checks
- case 's':
- sanity_diag_mode = atoi(optarg);
- break;
-
- // any other parameter makes it crash and burn
- default:
- fprintf(stderr, "citserver: usage: "
- "citserver "
- "[-l LogFacility] "
- "[-x MaxLogLevel] "
- "[-d] [-r] "
- "[-u user] "
- "[-h HomeDir]\n"
- );
- exit(1);
- }
-
- if (chdir(ctdldir) != 0) {
- syslog(LOG_ERR, "main: unable to change directory to [%s]: %m", ctdldir);
- exit(CTDLEXIT_HOME);
- }
- else {
- syslog(LOG_INFO, "main: running in data directory %s", ctdldir);
- }
-
- if ((ctdluid == 0) && (drop_root_perms == 0)) {
- fprintf(stderr, "citserver: cannot determine user to run as; please specify -r or -u options\n");
- exit(CTDLEXIT_UNUSER);
- }
-
- // Last ditch effort to determine the user name ... if there's a user called "citadel" then use that
- if (ctdluid == 0) {
- p = getpwnam("citadel");
- if (!p) {
- p = getpwnam("bbs");
- }
- if (!p) {
- p = getpwnam("guest");
- }
- if (p) {
- u = p->pw_uid;
- }
- if (u > 0) {
- ctdluid = u;
- }
- }
-
- // initialize the master context
- InitializeMasterCC();
- InitializeMasterTSD();
-
- setlogmask(LOG_UPTO(max_log_level));
- openlog("citserver",
- ( running_as_daemon ? (LOG_PID) : (LOG_PID | LOG_PERROR) ),
- syslog_facility
- );
-
- // daemonize, if we were asked to
- if (running_as_daemon) {
- start_daemon(0);
- drop_root_perms = 1;
- }
-
- if ((mkdir(ctdl_run_dir, 0755) != 0) && (errno != EEXIST)) {
- syslog(LOG_ERR, "main: unable to create run directory [%s]: %m", ctdl_run_dir);
- }
-
- if (chown(ctdl_run_dir, ctdluid, (pwp==NULL)?-1:pw.pw_gid) != 0) {
- syslog(LOG_ERR, "main: unable to set the access rights for [%s]: %m", ctdl_run_dir);
- }
-
- ctdl_lockfile(1);
- init_sysdep(); // Initialize...
- master_startup(); // Do non system dependent startup functions
- check_control(); // Check/sanitize/initialize control record, fix user indexes
- syslog(LOG_INFO, "main: upgrading modules"); // Run any upgrade entry points
- upgrade_modules();
-
- // Load the user for the masterCC or create them if they don't exist
- if (CtdlGetUser(&masterCC.user, "SYS_Citadel")) {
- // User doesn't exist. We can't use create user here as the user number needs to be 0
- strcpy (masterCC.user.fullname, "SYS_Citadel") ;
- CtdlPutUser(&masterCC.user);
- CtdlGetUser(&masterCC.user, "SYS_Citadel"); // Just to be safe
- }
-
- // Bind the server to a Unix-domain socket (user client access)
- CtdlRegisterServiceHook(0,
- file_citadel_socket,
- citproto_begin_session,
- do_command_loop,
- do_async_loop,
- CitadelServiceUDS);
-
- // Bind the server to a Unix-domain socket (admin client access)
- CtdlRegisterServiceHook(0,
- file_citadel_admin_socket,
- citproto_begin_admin_session,
- do_command_loop,
- do_async_loop,
- CitadelServiceUDS);
- chmod(file_citadel_admin_socket, S_IRWXU); // for your eyes only
-
- // Bind the server to our favorite TCP port (usually 504).
- CtdlRegisterServiceHook(CtdlGetConfigInt("c_port_number"),
- NULL,
- citproto_begin_session,
- do_command_loop,
- do_async_loop,
- CitadelServiceTCP);
-
- // Load any server-side extensions available here.
- syslog(LOG_INFO, "main: initializing server extensions");
- initialise_modules(0);
-
- // If we need host auth, start our chkpwd daemon.
- if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_HOST) {
- start_chkpwd_daemon();
- }
-
- // check, whether we're fired up another time after a crash.
- // if, post an aide message, so the admin has a chance to react.
- checkcrash();
-
- // Now that we've bound the sockets, change to the Citadel user id and its corresponding group ids
- if (drop_root_perms) {
- cdb_chmod_data(); // make sure we own our data files
- getpwuid_r(ctdluid, &pw, pwbuf, sizeof(pwbuf), &pwp);
- if (pwp == NULL)
- syslog(LOG_ERR, "main: WARNING, getpwuid(%ld): %m Group IDs will be incorrect.", (long)CTDLUID);
- else {
- initgroups(pw.pw_name, pw.pw_gid);
- if (setgid(pw.pw_gid)) {
- syslog(LOG_ERR, "main: setgid(%ld): %m", (long)pw.pw_gid);
- }
- }
- syslog(LOG_INFO, "main: changing uid to %ld", (long)CTDLUID);
- if (setuid(CTDLUID) != 0) {
- syslog(LOG_ERR, "main: setuid() failed: %m");
- }
-#if defined (HAVE_SYS_PRCTL_H) && defined (PR_SET_DUMPABLE)
- prctl(PR_SET_DUMPABLE, 1);
-#endif
- }
-
- // We want to check for idle sessions once per minute
- CtdlRegisterSessionHook(terminate_idle_sessions, EVT_TIMER, PRIO_CLEANUP + 1);
-
- // Go into multithreaded mode. When this call exits, the server is stopping.
- go_threading();
-
- // Get ready to shut down the server.
- int exit_code = master_cleanup(exit_signal);
- ctdl_lockfile(0);
- if (restart_server) {
- syslog(LOG_INFO, "main: *** CITADEL SERVER IS RESTARTING ***");
- execv(argv[0], argv);
- }
- return(exit_code);
-}
+++ /dev/null
-// Server-side utility functions
-
-#include "sysdep.h"
-#include <stdio.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <libcitadel.h>
-
-#include "citadel.h"
-#include "support.h"
-
-// strproc() - make a string 'nice'
-void strproc(char *string) {
- int a, b;
-
- if (string == NULL) return;
- if (IsEmptyStr(string)) return;
-
- // Convert non-printable characters to blanks
- for (a=0; !IsEmptyStr(&string[a]); ++a) {
- if (string[a]<32) string[a]=32;
- if (string[a]>126) string[a]=32;
- }
-
- // a is now the length of our string.
- // Remove leading and trailing blanks
- while( (string[a-1]<33) && (!IsEmptyStr(string)) )
- string[--a]=0;
- b = 0;
- while( (string[b]<33) && (!IsEmptyStr(&string[b])) )
- b++;
- if (b > 0)
- memmove(string,&string[b], a - b + 1);
-
- // Remove double blanks
- for (a=0; !IsEmptyStr(&string[a]); ++a) {
- if ((string[a]==32)&&(string[a+1]==32)) {
- strcpy(&string[a],&string[a+1]);
- a=0;
- }
- }
-
- // remove characters which would interfere with the network
- for (a=0; !IsEmptyStr(&string[a]); ++a) {
- while (string[a]=='!') strcpy(&string[a],&string[a+1]);
- while (string[a]=='@') strcpy(&string[a],&string[a+1]);
- while (string[a]=='_') strcpy(&string[a],&string[a+1]);
- while (string[a]==',') strcpy(&string[a],&string[a+1]);
- while (string[a]=='%') strcpy(&string[a],&string[a+1]);
- while (string[a]=='|') strcpy(&string[a],&string[a+1]);
- }
-
-}
-
-
-// get a line of text from a file
-// ignores lines starting with #
-int getstring(FILE *fp, char *string) {
- int a,c;
- do {
- strcpy(string,"");
- a=0;
- do {
- c=getc(fp);
- if (c<0) {
- string[a]=0;
- return(-1);
- }
- string[a++]=c;
- } while(c!=10);
- string[a-1]=0;
- } while(string[0]=='#');
- return(strlen(string));
-}
+++ /dev/null
-#include <stdio.h>
-
-void strproc (char *string);
-int getstring (FILE *fp, char *string);
+++ /dev/null
-
-
-const char *svn_revision(void);
-
-
+++ /dev/null
-// Tuning of various parameters of the system.
-// Normally you don't want to mess with any of this.
-//
-// Copyright (c) 1987-2021 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.
-
-/*
- * NLI is the string that shows up in a <W>ho's online listing for sessions
- * that are active, but for which no user has yet authenticated.
- */
-#define NLI "(not logged in)"
-
-/*
- * Maximum number of floors on the system.
- * WARNING! *Never* change this value once your system is up; THINGS WILL DIE!
- * Also, do not set it higher than 127.
- */
-#define MAXFLOORS 16
-
-/*
- * Standard buffer size for string datatypes. DO NOT CHANGE! Not only does
- * there exist a minimum buffer size for certain protocols (such as IMAP), but
- * fixed-length buffers are now stored in some of the data structures on disk,
- * so if you change the buffer size you'll fux0r your database.
- */
-#define SIZ 4096
-
-/*
- * If the body of a message is beyond this size, it will be stored in
- * a separate table.
- */
-#define BIGMSG 1024
-
-/*
- * SMTP delivery timeouts (measured in seconds)
- * If outbound SMTP deliveries cannot be completed due to transient errors
- * within SMTP_DELIVER_WARN seconds, the sender will receive a warning message
- * indicating that the message has not yet been delivered but Citadel will
- * keep trying. After SMTP_DELIVER_FAIL seconds, Citadel will advise the
- * sender that the deliveries have failed.
- */
-#define SMTP_DELIVER_WARN 14400 // warn after four hours
-#define SMTP_DELIVER_FAIL 432000 // fail after five days
-
-/*
- * Who bounced messages appear to be from
- */
-#define BOUNCESOURCE "Citadel Mail Delivery Subsystem"
-
-/*
- * The names of rooms which are automatically created by the system
- */
-#define BASEROOM "Lobby"
-#define MAILROOM "Mail"
-#define SENTITEMS "Sent Items"
-#define AIDEROOM "Aide"
-#define USERCONFIGROOM "My Citadel Config"
-#define USERCALENDARROOM "Calendar"
-#define USERTASKSROOM "Tasks"
-#define USERCONTACTSROOM "Contacts"
-#define USERNOTESROOM "Notes"
-#define USERDRAFTROOM "Drafts"
-#define USERTRASHROOM "Trash"
-#define PAGELOGROOM "Sent/Received Pages"
-#define SYSCONFIGROOM "Local System Configuration"
-#define SMTP_SPOOLOUT_ROOM "__CitadelSMTPspoolout__"
-#define ADDRESS_BOOK_ROOM "Global Address Book"
-
-/*
- * How long (in seconds) to retain message entries in the use table
- */
-#define USETABLE_RETAIN 864000L /* 10 days */
-
-/*
- * The size of per-thread stacks. If set too low, citserver will randomly crash.
- */
-#define THREADSTACKSIZE 0x100000
-
-/*
- * How many messages may the full text indexer scan before flushing its
- * tables to disk?
- */
-#define FT_MAX_CACHE 25
+++ /dev/null
-// Citadel "system dependent" stuff.
-//
-// Here's where we (hopefully) have most parts of the Citadel server that
-// might need tweaking when run on different operating system variants.
-//
-// Copyright (c) 1987-2021 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.
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <sys/stat.h>
-#include <errno.h>
-#include <signal.h>
-#include <stdio.h>
-#include <syslog.h>
-#include <sys/syslog.h>
-#include <execinfo.h>
-#include <netdb.h>
-#include <sys/un.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <netinet/tcp.h>
-#include <arpa/inet.h>
-#define SHOW_ME_VAPPEND_PRINTF
-#include <libcitadel.h>
-#include "citserver.h"
-#include "config.h"
-#include "ctdl_module.h"
-#include "sysdep_decls.h"
-#include "modules/crypto/serv_crypto.h" // Needed for init_ssl, client_write_ssl, client_read_ssl
-#include "housekeeping.h"
-#include "context.h"
-
-// Signal handler to shut down the server.
-volatile int exit_signal = 0;
-volatile int shutdown_and_halt = 0;
-volatile int restart_server = 0;
-volatile int running_as_daemon = 0;
-
-
-static RETSIGTYPE signal_cleanup(int signum) {
- syslog(LOG_DEBUG, "sysdep: caught signal %d", signum);
- exit_signal = signum;
- server_shutting_down = 1;
-}
-
-
-// Some initialization stuff...
-void init_sysdep(void) {
- sigset_t set;
-
- // Avoid vulnerabilities related to FD_SETSIZE if we can.
-#ifdef FD_SETSIZE
-#ifdef RLIMIT_NOFILE
- struct rlimit rl;
- getrlimit(RLIMIT_NOFILE, &rl);
- rl.rlim_cur = FD_SETSIZE;
- rl.rlim_max = FD_SETSIZE;
- setrlimit(RLIMIT_NOFILE, &rl);
-#endif
-#endif
-
- // If we've got OpenSSL, we're going to use it.
-#ifdef HAVE_OPENSSL
- init_ssl();
-#endif
-
- if (pthread_key_create(&ThreadKey, NULL) != 0) { // TSD for threads
- syslog(LOG_ERR, "pthread_key_create() : %m");
- abort();
- }
-
- if (pthread_key_create(&MyConKey, NULL) != 0) { // TSD for sessions
- syslog(LOG_CRIT, "sysdep: can't create TSD key: %m");
- abort();
- }
-
- // Interript, hangup, and terminate signals should cause the server to shut down.
- sigemptyset(&set);
- sigaddset(&set, SIGINT);
- sigaddset(&set, SIGHUP);
- sigaddset(&set, SIGTERM);
- sigprocmask(SIG_UNBLOCK, &set, NULL);
-
- signal(SIGINT, signal_cleanup);
- signal(SIGHUP, signal_cleanup);
- signal(SIGTERM, signal_cleanup);
-
- // Do not shut down the server on broken pipe signals, otherwise the
- // whole Citadel service would come down whenever a single client
- // socket breaks.
- signal(SIGPIPE, SIG_IGN);
-}
-
-
-// This is a generic function to set up a master socket for listening on
-// a TCP port. The server shuts down if the bind fails. (IPv4/IPv6 version)
-//
-// ip_addr IP address to bind
-// port_number port number to bind
-// queue_len number of incoming connections to allow in the queue
-int ctdl_tcp_server(char *ip_addr, int port_number, int queue_len) {
- struct protoent *p;
- struct sockaddr_in6 sin6;
- struct sockaddr_in sin4;
- int s, i, b;
- int ip_version = 6;
-
- memset(&sin6, 0, sizeof(sin6));
- memset(&sin4, 0, sizeof(sin4));
- sin6.sin6_family = AF_INET6;
- sin4.sin_family = AF_INET;
-
- if ( (ip_addr == NULL) // any IPv6
- || (IsEmptyStr(ip_addr))
- || (!strcmp(ip_addr, "*"))
- ) {
- ip_version = 6;
- sin6.sin6_addr = in6addr_any;
- }
- else if (!strcmp(ip_addr, "0.0.0.0")) // any IPv4
- {
- ip_version = 4;
- sin4.sin_addr.s_addr = INADDR_ANY;
- }
- else if ((strchr(ip_addr, '.')) && (!strchr(ip_addr, ':'))) // specific IPv4
- {
- ip_version = 4;
- if (inet_pton(AF_INET, ip_addr, &sin4.sin_addr) <= 0) {
- syslog(LOG_ALERT, "tcpserver: inet_pton: %m");
- return (-1);
- }
- }
- else // specific IPv6
- {
- ip_version = 6;
- if (inet_pton(AF_INET6, ip_addr, &sin6.sin6_addr) <= 0) {
- syslog(LOG_ALERT, "tcpserver: inet_pton: %m");
- return (-1);
- }
- }
-
- if (port_number == 0) {
- syslog(LOG_ALERT, "tcpserver: no port number was specified");
- return (-1);
- }
- sin6.sin6_port = htons((u_short) port_number);
- sin4.sin_port = htons((u_short) port_number);
-
- p = getprotobyname("tcp");
- if (p == NULL) {
- syslog(LOG_ALERT, "tcpserver: getprotobyname: %m");
- return (-1);
- }
-
- s = socket( ((ip_version == 6) ? PF_INET6 : PF_INET), SOCK_STREAM, (p->p_proto));
- if (s < 0) {
- syslog(LOG_ALERT, "tcpserver: socket: %m");
- return (-1);
- }
- // Set some socket options that make sense.
- i = 1;
- setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
-
- if (ip_version == 6) {
- b = bind(s, (struct sockaddr *) &sin6, sizeof(sin6));
- }
- else {
- b = bind(s, (struct sockaddr *) &sin4, sizeof(sin4));
- }
-
- if (b < 0) {
- syslog(LOG_ALERT, "tcpserver: bind: %m");
- return (-1);
- }
-
- fcntl(s, F_SETFL, O_NONBLOCK);
-
- if (listen(s, ((queue_len >= 5) ? queue_len : 5) ) < 0) {
- syslog(LOG_ALERT, "tcpserver: listen: %m");
- return (-1);
- }
- return (s);
-}
-
-
-// Create a Unix domain socket and listen on it
-int ctdl_uds_server(char *sockpath, int queue_len) {
- struct sockaddr_un addr;
- int s;
- int i;
- int actual_queue_len;
-#ifdef HAVE_STRUCT_UCRED
- int passcred = 1;
-#endif
-
- actual_queue_len = queue_len;
- if (actual_queue_len < 5) actual_queue_len = 5;
-
- i = unlink(sockpath);
- if ((i != 0) && (errno != ENOENT)) {
- syslog(LOG_ERR, "udsserver: %m");
- return(-1);
- }
-
- memset(&addr, 0, sizeof(addr));
- addr.sun_family = AF_UNIX;
- safestrncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
-
- s = socket(AF_UNIX, SOCK_STREAM, 0);
- if (s < 0) {
- syslog(LOG_ERR, "udsserver: socket: %m");
- return(-1);
- }
-
- if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
- syslog(LOG_ERR, "udsserver: bind: %m");
- return(-1);
- }
-
- // set to nonblock - we need this for some obscure situations
- if (fcntl(s, F_SETFL, O_NONBLOCK) < 0) {
- syslog(LOG_ERR, "udsserver: fcntl: %m");
- close(s);
- return(-1);
- }
-
- if (listen(s, actual_queue_len) < 0) {
- syslog(LOG_ERR, "udsserver: listen: %m");
- return(-1);
- }
-
-#ifdef HAVE_STRUCT_UCRED
- setsockopt(s, SOL_SOCKET, SO_PASSCRED, &passcred, sizeof(passcred));
-#endif
-
- chmod(sockpath, S_ISGID|S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH);
- return(s);
-}
-
-
-// The following functions implement output buffering on operating systems which
-// support it (such as Linux and various BSD flavors).
-#ifndef HAVE_DARWIN
-#ifdef TCP_CORK
-# define HAVE_TCP_BUFFERING
-#else
-# ifdef TCP_NOPUSH
-# define HAVE_TCP_BUFFERING
-# define TCP_CORK TCP_NOPUSH
-# endif
-#endif // TCP_CORK
-#endif // HAVE_DARWIN
-
-static unsigned on = 1, off = 0;
-
-void buffer_output(void) {
-#ifdef HAVE_TCP_BUFFERING
-#ifdef HAVE_OPENSSL
- if (!CC->redirect_ssl)
-#endif
- setsockopt(CC->client_socket, IPPROTO_TCP, TCP_CORK, &on, 4);
-#endif
-}
-
-void unbuffer_output(void) {
-#ifdef HAVE_TCP_BUFFERING
-#ifdef HAVE_OPENSSL
- if (!CC->redirect_ssl)
-#endif
- setsockopt(CC->client_socket, IPPROTO_TCP, TCP_CORK, &off, 4);
-#endif
-}
-
-void flush_output(void) {
-#ifdef HAVE_TCP_BUFFERING
- struct CitContext *CCC = CC;
- setsockopt(CCC->client_socket, IPPROTO_TCP, TCP_CORK, &off, 4);
- setsockopt(CCC->client_socket, IPPROTO_TCP, TCP_CORK, &on, 4);
-#endif
-}
-
-
-// close the client socket
-void client_close(void) {
- CitContext *CCC = CC;
-
- if (!CCC) return;
- if (CCC->client_socket <= 0) return;
- syslog(LOG_DEBUG, "sysdep: closing socket %d", CCC->client_socket);
- close(CCC->client_socket);
- CCC->client_socket = -1 ;
-}
-
-
-// Send binary data to the client.
-int client_write(const char *buf, int nbytes)
-{
- int bytes_written = 0;
- int retval;
-#ifndef HAVE_TCP_BUFFERING
- int old_buffer_len = 0;
-#endif
- fd_set wset;
- CitContext *Ctx;
- int fdflags;
-
- if (nbytes < 1) return(0);
-
- Ctx = CC;
-
- if (Ctx->redirect_buffer != NULL) {
- StrBufAppendBufPlain(Ctx->redirect_buffer,
- buf, nbytes, 0);
- return 0;
- }
-
-#ifdef HAVE_OPENSSL
- if (Ctx->redirect_ssl) {
- client_write_ssl(buf, nbytes);
- return 0;
- }
-#endif
- if (Ctx->client_socket == -1) return -1;
-
- fdflags = fcntl(Ctx->client_socket, F_GETFL);
-
- while ((bytes_written < nbytes) && (Ctx->client_socket != -1)){
- if ((fdflags & O_NONBLOCK) == O_NONBLOCK) {
- FD_ZERO(&wset);
- FD_SET(Ctx->client_socket, &wset);
- if (select(1, NULL, &wset, NULL, NULL) == -1) {
- if (errno == EINTR)
- {
- syslog(LOG_DEBUG, "sysdep: client_write(%d bytes) select() interrupted.", nbytes-bytes_written);
- if (server_shutting_down) {
- CC->kill_me = KILLME_SELECT_INTERRUPTED;
- return (-1);
- } else {
- // can't trust fd's and stuff so we need to re-create them
- continue;
- }
- } else {
- syslog(LOG_ERR, "sysdep: client_write(%d bytes) select failed: %m", nbytes - bytes_written);
- client_close();
- Ctx->kill_me = KILLME_SELECT_FAILED;
- return -1;
- }
- }
- }
-
- retval = write(Ctx->client_socket, &buf[bytes_written], nbytes - bytes_written);
- if (retval < 1) {
- syslog(LOG_ERR, "sysdep: client_write(%d bytes) failed: %m", nbytes - bytes_written);
- client_close();
- Ctx->kill_me = KILLME_WRITE_FAILED;
- return -1;
- }
- bytes_written = bytes_written + retval;
- }
- return 0;
-}
-
-void cputbuf(const StrBuf *Buf) {
- client_write(ChrPtr(Buf), StrLength(Buf));
-}
-
-
-// Send formatted printable data to the client.
-// Implemented in terms of client_write() so it's technically not sysdep...
-void cprintf(const char *format, ...) {
- va_list arg_ptr;
- char buf[1024];
-
- va_start(arg_ptr, format);
- if (vsnprintf(buf, sizeof buf, format, arg_ptr) == -1)
- buf[sizeof buf - 2] = '\n';
- client_write(buf, strlen(buf));
- va_end(arg_ptr);
-}
-
-
-// Read data from the client socket.
-//
-// sock socket fd to read from
-// buf buffer to read into
-// bytes number of bytes to read
-// timeout Number of seconds to wait before timing out
-//
-// Possible return values:
-// 1 Requested number of bytes has been read.
-// 0 Request timed out.
-// -1 Connection is broken, or other error.
-int client_read_blob(StrBuf *Target, int bytes, int timeout) {
- CitContext *CCC=CC;
- const char *Error;
- int retval = 0;
-
-#ifdef HAVE_OPENSSL
- if (CCC->redirect_ssl) {
- retval = client_read_sslblob(Target, bytes, timeout);
- if (retval < 0) {
- syslog(LOG_ERR, "sysdep: client_read_blob() failed");
- }
- }
- else
-#endif
- {
- retval = StrBufReadBLOBBuffered(Target,
- CCC->RecvBuf.Buf,
- &CCC->RecvBuf.ReadWritePointer,
- &CCC->client_socket,
- 1,
- bytes,
- O_TERM,
- &Error
- );
- if (retval < 0) {
- syslog(LOG_ERR, "sysdep: client_read_blob() failed: %s", Error);
- client_close();
- return retval;
- }
- }
- return retval;
-}
-
-
-// to make client_read_random_blob() more efficient, increase buffer size.
-// just use in greeting function, else your buffer may be flushed
-void client_set_inbound_buf(long N)
-{
- CitContext *CCC=CC;
- FlushStrBuf(CCC->RecvBuf.Buf);
- ReAdjustEmptyBuf(CCC->RecvBuf.Buf, N * SIZ, N * SIZ);
-}
-
-int client_read_random_blob(StrBuf *Target, int timeout)
-{
- CitContext *CCC=CC;
- int rc;
-
- rc = client_read_blob(Target, 1, timeout);
- if (rc > 0)
- {
- long len;
- const char *pch;
-
- len = StrLength(CCC->RecvBuf.Buf);
- pch = ChrPtr(CCC->RecvBuf.Buf);
-
- if (len > 0)
- {
- if (CCC->RecvBuf.ReadWritePointer != NULL) {
- len -= CCC->RecvBuf.ReadWritePointer - pch;
- pch = CCC->RecvBuf.ReadWritePointer;
- }
- StrBufAppendBufPlain(Target, pch, len, 0);
- FlushStrBuf(CCC->RecvBuf.Buf);
- CCC->RecvBuf.ReadWritePointer = NULL;
- return StrLength(Target);
- }
- return rc;
- }
- else
- return rc;
-}
-
-int client_read_to(char *buf, int bytes, int timeout)
-{
- CitContext *CCC=CC;
- int rc;
-
- rc = client_read_blob(CCC->MigrateBuf, bytes, timeout);
- if (rc < 0)
- {
- *buf = '\0';
- return rc;
- }
- else
- {
- memcpy(buf,
- ChrPtr(CCC->MigrateBuf),
- StrLength(CCC->MigrateBuf) + 1);
- FlushStrBuf(CCC->MigrateBuf);
- return rc;
- }
-}
-
-
-int HaveMoreLinesWaiting(CitContext *CCC) {
- if ((CCC->kill_me != 0) ||
- ( (CCC->RecvBuf.ReadWritePointer == NULL) &&
- (StrLength(CCC->RecvBuf.Buf) == 0) &&
- (CCC->client_socket != -1)) )
- return 0;
- else
- return 1;
-}
-
-
-// Read data from the client socket with default timeout.
-// (This is implemented in terms of client_read_to() and could be
-// justifiably moved out of sysdep.c)
-INLINE int client_read(char *buf, int bytes) {
- return(client_read_to(buf, bytes, CtdlGetConfigInt("c_sleeping")));
-}
-
-int CtdlClientGetLine(StrBuf *Target) {
- CitContext *CCC=CC;
- const char *Error;
- int rc;
-
- FlushStrBuf(Target);
-#ifdef HAVE_OPENSSL
- if (CCC->redirect_ssl) {
- rc = client_readline_sslbuffer(Target, CCC->RecvBuf.Buf, &CCC->RecvBuf.ReadWritePointer, 1);
- return rc;
- }
- else
-#endif
- {
- rc = StrBufTCP_read_buffered_line_fast(Target,
- CCC->RecvBuf.Buf,
- &CCC->RecvBuf.ReadWritePointer,
- &CCC->client_socket,
- 5,
- 1,
- &Error
- );
- return rc;
- }
-}
-
-
-// Get a LF-terminated line of text from the client.
-// (This is implemented in terms of client_read() and could be
-// justifiably moved out of sysdep.c)
-int client_getln(char *buf, int bufsize) {
- int i, retval;
- CitContext *CCC=CC;
- const char *pCh;
-
- retval = CtdlClientGetLine(CCC->MigrateBuf);
- if (retval < 0)
- return(retval >= 0);
-
-
- i = StrLength(CCC->MigrateBuf);
- pCh = ChrPtr(CCC->MigrateBuf);
- // Strip the trailing LF, and the trailing CR if present.
- if (bufsize <= i)
- i = bufsize - 1;
- while ( (i > 0)
- && ( (pCh[i - 1]==13)
- || ( pCh[i - 1]==10)) ) {
- i--;
- }
- memcpy(buf, pCh, i);
- buf[i] = 0;
-
- FlushStrBuf(CCC->MigrateBuf);
- if (retval < 0) {
- safestrncpy(&buf[i], "000", bufsize - i);
- }
- return(retval >= 0);
-}
-
-
-// Cleanup any contexts that are left lying around
-void close_masters(void) {
- struct ServiceFunctionHook *serviceptr;
- const char *Text;
-
- // close all protocol master sockets
- for (serviceptr = ServiceHookTable; serviceptr != NULL;
- serviceptr = serviceptr->next ) {
-
- if (serviceptr->tcp_port > 0) {
- if (serviceptr->msock == -1) {
- Text = "not closing again";
- }
- else {
- Text = "Closing";
- }
- syslog(LOG_INFO, "sysdep: %s %d listener on port %d",
- Text,
- serviceptr->msock,
- serviceptr->tcp_port
- );
- serviceptr->tcp_port = 0;
- }
-
- if (serviceptr->sockpath != NULL) {
- if (serviceptr->msock == -1) {
- Text = "not closing again";
- }
- else {
- Text = "Closing";
- }
- syslog(LOG_INFO, "sysdep: %s %d listener on '%s'",
- Text,
- serviceptr->msock,
- serviceptr->sockpath
- );
- }
-
- if (serviceptr->msock != -1) {
- close(serviceptr->msock);
- serviceptr->msock = -1;
- }
-
- // If it's a Unix domain socket, remove the file.
- if (serviceptr->sockpath != NULL) {
- unlink(serviceptr->sockpath);
- serviceptr->sockpath = NULL;
- }
- }
-}
-
-
-// The system-dependent part of master_cleanup() - close the master socket.
-void sysdep_master_cleanup(void) {
- close_masters();
- context_cleanup();
-}
-
-
-
-pid_t current_child;
-void graceful_shutdown(int signum) {
- kill(current_child, signum);
- unlink(file_pid_file);
- exit(0);
-}
-
-int nFireUps = 0;
-int nFireUpsNonRestart = 0;
-pid_t ForkedPid = 1;
-
-// Start running as a daemon.
-void start_daemon(int unused) {
- int status = 0;
- pid_t child = 0;
- FILE *fp;
- int do_restart = 0;
- current_child = 0;
-
- // Close stdin/stdout/stderr and replace them with /dev/null.
- // We don't just call close() because we don't want these fd's
- // to be reused for other files.
- child = fork();
- if (child != 0) {
- exit(0);
- }
-
- signal(SIGHUP, SIG_IGN);
- signal(SIGINT, SIG_IGN);
- signal(SIGQUIT, SIG_IGN);
-
- setsid();
- umask(0);
- if ( (freopen("/dev/null", "r", stdin) != stdin) ||
- (freopen("/dev/null", "w", stdout) != stdout) ||
- (freopen("/dev/null", "w", stderr) != stderr)
- ) {
- syslog(LOG_ERR, "sysdep: unable to reopen stdio: %m");
- }
-
- do {
- current_child = fork();
- signal(SIGTERM, graceful_shutdown);
- if (current_child < 0) {
- perror("fork");
- exit(errno);
- }
- else if (current_child == 0) {
- return; // continue starting citadel.
- }
- else {
- fp = fopen(file_pid_file, "w");
- if (fp != NULL) {
- fprintf(fp, ""F_PID_T"\n", getpid());
- fclose(fp);
- }
- waitpid(current_child, &status, 0);
- }
- nFireUpsNonRestart = nFireUps;
-
- // Exit code 0 means the watcher should exit
- if (WIFEXITED(status) && (WEXITSTATUS(status) == CTDLEXIT_SHUTDOWN)) {
- do_restart = 0;
- }
-
- // Exit code 101-109 means the watcher should exit
- else if (WIFEXITED(status) && (WEXITSTATUS(status) >= 101) && (WEXITSTATUS(status) <= 109)) {
- do_restart = 0;
- }
-
- // Any other exit code, or no exit code, means we should restart.
- else {
- do_restart = 1;
- nFireUps++;
- ForkedPid = current_child;
- }
-
- } while (do_restart);
-
- unlink(file_pid_file);
- exit(WEXITSTATUS(status));
-}
-
-
-void checkcrash(void) {
- if (nFireUpsNonRestart != nFireUps) {
- StrBuf *CrashMail;
- CrashMail = NewStrBuf();
- syslog(LOG_ALERT, "sysdep: posting crash message");
- StrBufPrintf(CrashMail,
- " \n"
- " The Citadel server process (citserver) terminated unexpectedly."
- "\n \n"
- " This could be the result of a bug in the server program, or some external "
- "factor.\n \n"
- " You can obtain more information about this by enabling core dumps.\n \n"
- " For more information, please see:\n \n"
- " http://citadel.org/doku.php?id=faq:mastering_your_os:gdb#how.do.i.make.my.system.produce.core-files"
- "\n \n"
-
- " If you have already done this, the core dump is likely to be found at %score.%d\n"
- ,
- ctdl_run_dir, ForkedPid);
- CtdlAideMessage(ChrPtr(CrashMail), "Citadel server process terminated unexpectedly");
- FreeStrBuf(&CrashMail);
- }
-}
-
-
-// Generic routine to convert a login name to a full name (gecos)
-// Returns nonzero if a conversion took place
-int convert_login(char NameToConvert[]) {
- struct passwd *pw;
- unsigned int a;
-
- pw = getpwnam(NameToConvert);
- if (pw == NULL) {
- return(0);
- }
- else {
- strcpy(NameToConvert, pw->pw_gecos);
- for (a=0; a<strlen(NameToConvert); ++a) {
- if (NameToConvert[a] == ',') NameToConvert[a] = 0;
- }
- return(1);
- }
-}
-
-
-void HuntBadSession(void) {
- int highest;
- CitContext *ptr;
- fd_set readfds;
- struct timeval tv;
- struct ServiceFunctionHook *serviceptr;
-
- // Next, add all of the client sockets
- begin_critical_section(S_SESSION_TABLE);
- for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
- if ((ptr->state == CON_SYS) && (ptr->client_socket == 0))
- continue;
- // Initialize the fdset.
- FD_ZERO(&readfds);
- highest = 0;
- tv.tv_sec = 0; // wake up every second if no input
- tv.tv_usec = 0;
-
- // Don't select on dead sessions, only truly idle ones
- if ( (ptr->state == CON_IDLE)
- && (ptr->kill_me == 0)
- && (ptr->client_socket > 0)
- ) {
- FD_SET(ptr->client_socket, &readfds);
- if (ptr->client_socket > highest)
- highest = ptr->client_socket;
-
- if ((select(highest + 1, &readfds, NULL, NULL, &tv) < 0) && (errno == EBADF))
- {
- // Gotcha!
- syslog(LOG_ERR,
- "sysdep: killing session CC[%d] bad FD: [%d] User[%s] Host[%s:%s]",
- ptr->cs_pid,
- ptr->client_socket,
- ptr->curr_user,
- ptr->cs_host,
- ptr->cs_addr
- );
- ptr->kill_me = 1;
- ptr->client_socket = -1;
- break;
- }
- }
- }
- end_critical_section(S_SESSION_TABLE);
-
- // First, add the various master sockets to the fdset.
- for (serviceptr = ServiceHookTable; serviceptr != NULL; serviceptr = serviceptr->next ) {
-
- // Initialize the fdset.
- highest = 0;
- tv.tv_sec = 0; // wake up every second if no input
- tv.tv_usec = 0;
-
- FD_SET(serviceptr->msock, &readfds);
- if (serviceptr->msock > highest) {
- highest = serviceptr->msock;
- }
- if ((select(highest + 1, &readfds, NULL, NULL, &tv) < 0) &&
- (errno == EBADF))
- {
- // Gotcha! server socket dead? commit suicide!
- syslog(LOG_ERR, "sysdep: found bad FD: %d and its a server socket! Shutting Down!", serviceptr->msock);
- server_shutting_down = 1;
- break;
- }
- }
-}
-
-
-// This loop just keeps going and going and going...
-void *worker_thread(void *blah) {
- int highest;
- CitContext *ptr;
- CitContext *bind_me = NULL;
- fd_set readfds;
- int retval = 0;
- struct timeval tv;
- int force_purge = 0;
- struct ServiceFunctionHook *serviceptr;
- int ssock; // Descriptor for client socket
- CitContext *con = NULL; // Temporary context pointer
- int i;
-
- pthread_mutex_lock(&ThreadCountMutex);
- ++num_workers;
- pthread_mutex_unlock(&ThreadCountMutex);
-
- while (!server_shutting_down) {
-
- // make doubly sure we're not holding any stale db handles which might cause a deadlock
- cdb_check_handles();
-do_select: force_purge = 0;
- bind_me = NULL; // Which session shall we handle?
-
- // Initialize the fdset
- FD_ZERO(&readfds);
- highest = 0;
-
- // First, add the various master sockets to the fdset.
- for (serviceptr = ServiceHookTable; serviceptr != NULL; serviceptr = serviceptr->next ) {
- FD_SET(serviceptr->msock, &readfds);
- if (serviceptr->msock > highest) {
- highest = serviceptr->msock;
- }
- }
-
- // Next, add all of the client sockets.
- begin_critical_section(S_SESSION_TABLE);
- for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
- if ((ptr->state == CON_SYS) && (ptr->client_socket == 0))
- continue;
-
- // Don't select on dead sessions, only truly idle ones
- if ( (ptr->state == CON_IDLE)
- && (ptr->kill_me == 0)
- && (ptr->client_socket > 0)
- ) {
- FD_SET(ptr->client_socket, &readfds);
- if (ptr->client_socket > highest)
- highest = ptr->client_socket;
- }
- if ((bind_me == NULL) && (ptr->state == CON_READY)) {
- bind_me = ptr;
- ptr->state = CON_EXECUTING;
- break;
- }
- if ((bind_me == NULL) && (ptr->state == CON_GREETING)) {
- bind_me = ptr;
- ptr->state = CON_STARTING;
- break;
- }
- }
- end_critical_section(S_SESSION_TABLE);
-
- if (bind_me) {
- goto SKIP_SELECT;
- }
-
- // If we got this far, it means that there are no sessions
- // which a previous thread marked for attention, so we go
- // ahead and get ready to select().
-
- if (!server_shutting_down) {
- tv.tv_sec = 1; // wake up every second if no input
- tv.tv_usec = 0;
- retval = select(highest + 1, &readfds, NULL, NULL, &tv);
- }
- else {
- --num_workers;
- return NULL;
- }
-
- // Now figure out who made this select() unblock.
- // First, check for an error or exit condition.
- if (retval < 0) {
- if (errno == EBADF) {
- syslog(LOG_ERR, "sysdep: select() failed: %m");
- HuntBadSession();
- goto do_select;
- }
- if (errno != EINTR) {
- syslog(LOG_ERR, "sysdep: exiting: %m");
- server_shutting_down = 1;
- continue;
- } else {
- if (server_shutting_down) {
- --num_workers;
- return(NULL);
- }
- goto do_select;
- }
- }
- else if (retval == 0) {
- if (server_shutting_down) {
- --num_workers;
- return(NULL);
- }
- }
-
- // Next, check to see if it's a new client connecting on a master socket.
-
- else if ((retval > 0) && (!server_shutting_down)) for (serviceptr = ServiceHookTable; serviceptr != NULL; serviceptr = serviceptr->next) {
-
- if (FD_ISSET(serviceptr->msock, &readfds)) {
- ssock = accept(serviceptr->msock, NULL, 0);
- if (ssock >= 0) {
- syslog(LOG_DEBUG, "sysdep: new client socket %d", ssock);
-
- // The master socket is non-blocking but the client
- // sockets need to be blocking, otherwise certain
- // operations barf on FreeBSD. Not a fatal error.
- if (fcntl(ssock, F_SETFL, 0) < 0) {
- syslog(LOG_ERR, "sysdep: Can't set socket to blocking: %m");
- }
-
- // New context will be created already
- // set up in the CON_EXECUTING state.
- con = CreateNewContext();
-
- // Assign our new socket number to it.
- con->tcp_port = serviceptr->tcp_port;
- con->client_socket = ssock;
- con->h_command_function = serviceptr->h_command_function;
- con->h_async_function = serviceptr->h_async_function;
- con->h_greeting_function = serviceptr->h_greeting_function;
- con->ServiceName = serviceptr->ServiceName;
-
- // Connections on a local client are always from the same host
- if (serviceptr->sockpath != NULL) {
- con->is_local_client = 1;
- }
-
- // Set the SO_REUSEADDR socket option
- i = 1;
- setsockopt(ssock, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
- con->state = CON_GREETING;
- retval--;
- if (retval == 0)
- break;
- }
- }
- }
-
- // It must be a client socket. Find a context that has data
- // waiting on its socket *and* is in the CON_IDLE state. Any
- // active sockets other than our chosen one are marked as
- // CON_READY so the next thread that comes around can just bind
- // to one without having to select() again.
- begin_critical_section(S_SESSION_TABLE);
- for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
- int checkfd = ptr->client_socket;
- if ((checkfd != -1) && (ptr->state == CON_IDLE) ){
- if (FD_ISSET(checkfd, &readfds)) {
- ptr->input_waiting = 1;
- if (!bind_me) {
- bind_me = ptr; // I choose you!
- bind_me->state = CON_EXECUTING;
- }
- else {
- ptr->state = CON_READY;
- }
- } else if ((ptr->is_async) && (ptr->async_waiting) && (ptr->h_async_function)) {
- if (!bind_me) {
- bind_me = ptr; // I choose you!
- bind_me->state = CON_EXECUTING;
- }
- else {
- ptr->state = CON_READY;
- }
- }
- }
- }
- end_critical_section(S_SESSION_TABLE);
-
-SKIP_SELECT:
- // We're bound to a session
- pthread_mutex_lock(&ThreadCountMutex);
- ++active_workers;
- pthread_mutex_unlock(&ThreadCountMutex);
-
- if (bind_me != NULL) {
- become_session(bind_me);
-
- if (bind_me->state == CON_STARTING) {
- bind_me->state = CON_EXECUTING;
- begin_session(bind_me);
- bind_me->h_greeting_function();
- }
- // If the client has sent a command, execute it.
- if (CC->input_waiting) {
- CC->h_command_function();
-
- while (HaveMoreLinesWaiting(CC))
- CC->h_command_function();
-
- CC->input_waiting = 0;
- }
-
- // If there are asynchronous messages waiting and the client supports it, do those now
- if ((CC->is_async) && (CC->async_waiting) && (CC->h_async_function != NULL)) {
- CC->h_async_function();
- CC->async_waiting = 0;
- }
-
- force_purge = CC->kill_me;
- become_session(NULL);
- bind_me->state = CON_IDLE;
- }
-
- dead_session_purge(force_purge);
- do_housekeeping();
-
- pthread_mutex_lock(&ThreadCountMutex);
- --active_workers;
- if ((active_workers + CtdlGetConfigInt("c_min_workers") < num_workers) &&
- (num_workers > CtdlGetConfigInt("c_min_workers")))
- {
- num_workers--;
- pthread_mutex_unlock(&ThreadCountMutex);
- return (NULL);
- }
- pthread_mutex_unlock(&ThreadCountMutex);
- }
-
- // If control reaches this point, the server is shutting down
- pthread_mutex_lock(&ThreadCountMutex);
- --num_workers;
- pthread_mutex_unlock(&ThreadCountMutex);
- return(NULL);
-}
-
-
-// SyslogFacility()
-// Translate text facility name to syslog.h defined value.
-int SyslogFacility(char *name)
-{
- int i;
- struct
- {
- int facility;
- char *name;
- } facTbl[] =
- {
- { LOG_KERN, "kern" },
- { LOG_USER, "user" },
- { LOG_MAIL, "mail" },
- { LOG_DAEMON, "daemon" },
- { LOG_AUTH, "auth" },
- { LOG_SYSLOG, "syslog" },
- { LOG_LPR, "lpr" },
- { LOG_NEWS, "news" },
- { LOG_UUCP, "uucp" },
- { LOG_LOCAL0, "local0" },
- { LOG_LOCAL1, "local1" },
- { LOG_LOCAL2, "local2" },
- { LOG_LOCAL3, "local3" },
- { LOG_LOCAL4, "local4" },
- { LOG_LOCAL5, "local5" },
- { LOG_LOCAL6, "local6" },
- { LOG_LOCAL7, "local7" },
- { 0, NULL }
- };
- for(i = 0; facTbl[i].name != NULL; i++) {
- if(!strcasecmp(name, facTbl[i].name))
- return facTbl[i].facility;
- }
- return LOG_DAEMON;
-}
+++ /dev/null
-
-#ifndef SYSDEP_DECLS_H
-#define SYSDEP_DECLS_H
-
-#include <stdarg.h>
-#include "sysdep.h"
-
-#ifdef HAVE_PTHREAD_H
-#include <pthread.h>
-#endif
-
-#include <db.h>
-
-#if DB_VERSION_MAJOR < 5
-#error Citadel requires Berkeley DB v5 or newer. Please upgrade.
-#endif
-
-#include "server.h"
-#include "database.h"
-
-#if SIZEOF_SIZE_T == SIZEOF_INT
-#define SIZE_T_FMT "%d"
-#else
-#define SIZE_T_FMT "%ld"
-#endif
-
-#if SIZEOF_LOFF_T == SIZEOF_LONG
-#define LOFF_T_FMT "%ld"
-#else
-#define LOFF_T_FMT "%lld"
-#endif
-
-void cputbuf(const StrBuf *Buf);
-
-#ifdef __GNUC__
-void cprintf (const char *format, ...) __attribute__((__format__(__printf__,1,2)));
-#else
-void cprintf (const char *format, ...);
-#endif
-
-void init_sysdep (void);
-int ctdl_tcp_server(char *ip_addr, int port_number, int queue_len);
-int ctdl_uds_server(char *sockpath, int queue_len);
-void buffer_output(void);
-void unbuffer_output(void);
-void flush_output(void);
-int client_write (const char *buf, int nbytes);
-int client_read_to (char *buf, int bytes, int timeout);
-int client_read (char *buf, int bytes);
-int client_getln (char *buf, int maxbytes);
-int CtdlClientGetLine(StrBuf *Target);
-int client_read_blob(StrBuf *Target, int bytes, int timeout);
-void client_set_inbound_buf(long N);
-int client_read_random_blob(StrBuf *Target, int timeout);
-void client_close(void);
-void sysdep_master_cleanup (void);
-void kill_session (int session_to_kill);
-void start_daemon (int do_close_stdio);
-void checkcrash(void);
-int convert_login (char *NameToConvert);
-void init_master_fdset(void);
-void *worker_thread(void *);
-
-extern volatile int exit_signal;
-extern volatile int shutdown_and_halt;
-extern volatile int running_as_daemon;
-extern volatile int restart_server;
-
-extern int verbosity;
-extern int rescan[];
-
-
-extern int SyslogFacility(char *name);
-
-#endif /* SYSDEP_DECLS_H */
+++ /dev/null
-/*
- * Thread handling stuff for Citadel server
- *
- * Copyright (c) 1987-2021 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.
- */
-
-#include <errno.h>
-#include <stdio.h>
-#include <syslog.h>
-#include <libcitadel.h>
-#include "modules_init.h"
-#include "serv_extensions.h"
-#include "ctdl_module.h"
-#include "config.h"
-#include "context.h"
-#include "threads.h"
-
-int num_workers = 0; /* Current number of worker threads */
-int active_workers = 0; /* Number of ACTIVE worker threads */
-pthread_key_t ThreadKey;
-pthread_mutex_t Critters[MAX_SEMAPHORES]; /* Things needing locking */
-struct thread_tsd masterTSD;
-int server_shutting_down = 0; /* set to nonzero during shutdown */
-pthread_mutex_t ThreadCountMutex;
-
-void InitializeSemaphores(void)
-{
- int i;
-
- /* Set up a bunch of semaphores to be used for critical sections */
- for (i=0; i<MAX_SEMAPHORES; ++i) {
- pthread_mutex_init(&Critters[i], NULL);
- }
-}
-
-
-/*
- * Obtain a semaphore lock to begin a critical section.
- * but only if no one else has one
- */
-int try_critical_section(int which_one)
-{
- /* For all types of critical sections except those listed here,
- * ensure nobody ever tries to do a critical section within a
- * transaction; this could lead to deadlock.
- */
- if ( (which_one != S_FLOORCACHE)
- && (which_one != S_NETCONFIGS)
- ) {
- cdb_check_handles();
- }
- return (pthread_mutex_trylock(&Critters[which_one]));
-}
-
-
-/*
- * Obtain a semaphore lock to begin a critical section.
- */
-void begin_critical_section(int which_one)
-{
- /* For all types of critical sections except those listed here,
- * ensure nobody ever tries to do a critical section within a
- * transaction; this could lead to deadlock.
- */
- if ( (which_one != S_FLOORCACHE)
- && (which_one != S_NETCONFIGS)
- ) {
- cdb_check_handles();
- }
- pthread_mutex_lock(&Critters[which_one]);
-}
-
-
-/*
- * Release a semaphore lock to end a critical section.
- */
-void end_critical_section(int which_one)
-{
- pthread_mutex_unlock(&Critters[which_one]);
-}
-
-
-/*
- * Return a pointer to our thread-specific (not session-specific) data.
- */
-struct thread_tsd *MyThread(void) {
- struct thread_tsd *c = (struct thread_tsd *) pthread_getspecific(ThreadKey) ;
- if (!c) {
- return &masterTSD;
- }
- return c;
-}
-
-
-/*
- * Called by CtdlThreadCreate()
- * We have to pass through here before starting our thread in order to create a set of data
- * that is thread-specific rather than session-specific.
- */
-void *CTC_backend(void *supplied_start_routine)
-{
- struct thread_tsd *mytsd;
- void *(*start_routine)(void*) = supplied_start_routine;
-
- mytsd = (struct thread_tsd *) malloc(sizeof(struct thread_tsd));
- memset(mytsd, 0, sizeof(struct thread_tsd));
- pthread_setspecific(ThreadKey, (const void *) mytsd);
-
- start_routine(NULL);
- // free(mytsd);
- return(NULL);
-}
-
-
-/*
- * Function to create a thread.
- */
-void CtdlThreadCreate(void *(*start_routine)(void*))
-{
- pthread_t thread;
- pthread_attr_t attr;
- int ret = 0;
-
- ret = pthread_attr_init(&attr);
- ret = pthread_attr_setstacksize(&attr, THREADSTACKSIZE);
- ret = pthread_create(&thread, &attr, CTC_backend, (void *)start_routine);
- if (ret != 0) syslog(LOG_ERR, "pthread_create() : %m");
-}
-
-
-void InitializeMasterTSD(void) {
- memset(&masterTSD, 0, sizeof(struct thread_tsd));
-}
-
-
-/*
- * Initialize the thread system
- */
-void go_threading(void) {
- pthread_mutex_init(&ThreadCountMutex, NULL);
-
- /* Second call to module init functions now that threading is up */
- initialise_modules(1);
-
- /* Begin with one worker thread. We will expand the pool if necessary */
- CtdlThreadCreate(worker_thread);
-
- /* The supervisor thread monitors worker threads and spawns more of them if it finds that
- * they are all in use.
- */
- while (!server_shutting_down) {
- if ((active_workers == num_workers) && (num_workers < CtdlGetConfigInt("c_max_workers"))) {
- CtdlThreadCreate(worker_thread);
- }
- usleep(1000000);
- }
-
- /* When we get to this point we are getting ready to shut down our Citadel server */
- terminate_all_sessions(); /* close all client sockets */
- CtdlShutdownServiceHooks(); /* close all listener sockets to prevent new connections */
- PerformSessionHooks(EVT_SHUTDOWN); /* run any registered shutdown hooks */
-
- /* We used to wait for all threads to exit. Fuck that. The only thing important is that the databases are
- * cleanly unmounted. After that, exit the whole program.
- */
-}
+++ /dev/null
-
-#ifndef THREADS_H
-#define THREADS_H
-
-#include "sysdep.h"
-
-#ifdef HAVE_PTHREAD_H
-#include <pthread.h>
-#endif
-
-#include <sys/time.h>
-#include <string.h>
-
-#include <db.h>
-
-#include "server.h"
-#include "sysdep_decls.h"
-
-/*
- * Things we need to keep track of per-thread instead of per-session
- */
-struct thread_tsd {
- DB_TXN *tid; /* Transaction handle */
- DBC *cursors[MAXCDB]; /* Cursors, for traversals... */
-};
-
-extern pthread_key_t ThreadKey;
-extern struct thread_tsd masterTSD;
-#define TSD MyThread()
-
-extern int num_workers;
-extern int active_workers;
-extern int server_shutting_down;
-
-struct thread_tsd *MyThread(void);
-int try_critical_section (int which_one);
-void begin_critical_section (int which_one);
-void end_critical_section (int which_one);
-void go_threading(void);
-void InitializeMasterTSD(void);
-void CtdlThreadCreate(void *(*start_routine)(void*));
-
-
-extern pthread_mutex_t ThreadCountMutex;;
-
-#endif // THREADS_H
+++ /dev/null
-
-/*
- This file defines typedefs for 8, 16, and 32 bit integers. They are:
- cit_int8_t default 8-bit int
- cit_int16_t default 16-bit int
- cit_int32_t default 32-bit int
- cit_int64_t default 64-bit int (not implemented yet)
- cit_sint8_t signed 8-bit int
- cit_sint16_t signed 16-bit int
- cit_sint32_t signed 32-bit int
- cit_sint64_t signed 64-bit int (not implemented yet)
- cit_uint8_t unsigned 8-bit int
- cit_uint16_t unsigned 16-bit int
- cit_uint32_t unsigned 32-bit int
- cit_uint64_t unsigned 64-bit int (not implemented yet)
-
- The sizes are determined during the configure process; see the
- AC_CHECK_SIZEOF macros in configure.in. In no way do we assume that any
- given datatype is any particular width, e.g. we don't assume short is two
- bytes; we check for it specifically.
-
- This might seem excessively paranoid, but I've seen some WEIRD systems
- and some bizarre compilers (Domain/OS for instance) in my time.
-*/
-
-#ifndef _CITADEL_UX_TYPESIZE_H
-#define _CITADEL_UX_TYPESIZE_H
-
-/* Include sysdep.h if not already included */
-#ifndef CTDLDIR
-# include "sysdep.h"
-#endif
-
-/* 8-bit - If this fails, your compiler is broken */
-#if SIZEOF_CHAR == 1
-typedef char cit_int8_t;
-typedef signed char cit_sint8_t;
-typedef unsigned char cit_uint8_t;
-#else
-# error Unable to find an 8-bit integer datatype
-#endif
-
-/* 16-bit - If this fails, your compiler is broken */
-#if SIZEOF_SHORT == 2
-typedef short cit_int16_t;
-typedef signed short cit_sint16_t;
-typedef unsigned short cit_uint16_t;
-#elif SIZEOF_INT == 2
-typedef int cit_int16_t;
-typedef signed int cit_sint16_t;
-typedef unsigned int cit_uint16_t;
-#else
-# error Unable to find a 16-bit integer datatype
-#endif
-
-/* 32-bit - If this fails, your compiler is broken */
-#if SIZEOF_INT == 4
-typedef int cit_int32_t;
-typedef signed int cit_sint32_t;
-typedef unsigned int cit_uint32_t;
-#elif SIZEOF_LONG == 4
-typedef long cit_int32_t;
-typedef signed long cit_sint32_t;
-typedef unsigned long cit_uint32_t;
-#else
-# error Unable to find a 32-bit integer datatype
-#endif
-
-#endif /* _CITADEL_UX_TYPESIZE_H */
+++ /dev/null
-// Server functions which perform operations on user objects.
-//
-// Copyright (c) 1987-2022 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.
-
-#include <stdlib.h>
-#include <unistd.h>
-#include "sysdep.h"
-#include <stdio.h>
-#include <sys/stat.h>
-#include <libcitadel.h>
-#include "control.h"
-#include "support.h"
-#include "citserver.h"
-#include "config.h"
-#include "citadel_ldap.h"
-#include "ctdl_module.h"
-#include "user_ops.h"
-#include "internet_addressing.h"
-
-// These pipes are used to talk to the chkpwd daemon, which is forked during startup
-int chkpwd_write_pipe[2];
-int chkpwd_read_pipe[2];
-
-
-// makeuserkey() - convert a username into the format used as a database key
-// "key" must be a buffer of at least USERNAME_SIZE
-// (Key format is the username with all non-alphanumeric characters removed, and converted to lower case.)
-void makeuserkey(char *key, const char *username) {
- int i;
- int keylen = 0;
-
- if (IsEmptyStr(username)) {
- key[0] = 0;
- return;
- }
-
- int len = strlen(username);
- for (i=0; ((i<=len) && (i<USERNAME_SIZE-1)); ++i) {
- if (isalnum((username[i]))) {
- key[keylen++] = tolower(username[i]);
- }
- }
- key[keylen++] = 0;
-}
-
-
-// Compare two usernames to see if they are the same user after being keyed for the database
-// Usage is identical to strcmp()
-int CtdlUserCmp(char *s1, char *s2) {
- char k1[USERNAME_SIZE];
- char k2[USERNAME_SIZE];
-
- makeuserkey(k1, s1);
- makeuserkey(k2, s2);
- return(strcmp(k1,k2));
-}
-
-
-// CtdlGetUser() retrieve named user into supplied buffer.
-// returns 0 on success
-int CtdlGetUser(struct ctdluser *usbuf, char *name) {
- char usernamekey[USERNAME_SIZE];
- struct cdbdata *cdbus;
-
- if (usbuf != NULL) {
- memset(usbuf, 0, sizeof(struct ctdluser));
- }
-
- makeuserkey(usernamekey, name);
- if (IsEmptyStr(usernamekey)) {
- return(1); // empty user name
- }
- cdbus = cdb_fetch(CDB_USERS, usernamekey, strlen(usernamekey));
-
- if (cdbus == NULL) { // user not found
- return(1);
- }
- if (usbuf != NULL) {
- memcpy(usbuf, cdbus->ptr, ((cdbus->len > sizeof(struct ctdluser)) ? sizeof(struct ctdluser) : cdbus->len));
- }
- cdb_free(cdbus);
- return(0);
-}
-
-
-int CtdlLockGetCurrentUser(void) {
- return CtdlGetUser(&CC->user, CC->curr_user);
-}
-
-
-// CtdlGetUserLock() - same as getuser() but locks the record
-int CtdlGetUserLock(struct ctdluser *usbuf, char *name) {
- int retcode;
-
- retcode = CtdlGetUser(usbuf, name);
- if (retcode == 0) {
- begin_critical_section(S_USERS);
- }
- return(retcode);
-}
-
-
-// CtdlPutUser() - write user buffer into the correct place on disk
-void CtdlPutUser(struct ctdluser *usbuf) {
- char usernamekey[USERNAME_SIZE];
- makeuserkey(usernamekey, usbuf->fullname);
- usbuf->version = REV_LEVEL;
- cdb_store(CDB_USERS, usernamekey, strlen(usernamekey), usbuf, sizeof(struct ctdluser));
-}
-
-
-void CtdlPutCurrentUserLock() {
- CtdlPutUser(&CC->user);
-}
-
-
-// CtdlPutUserLock() - same as putuser() but locks the record
-void CtdlPutUserLock(struct ctdluser *usbuf) {
- CtdlPutUser(usbuf);
- end_critical_section(S_USERS);
-}
-
-
-// rename_user() - this is tricky because the user's display name is the database key
-// Returns 0 on success or nonzero if there was an error...
-int rename_user(char *oldname, char *newname) {
- int retcode = RENAMEUSER_OK;
- struct ctdluser usbuf;
-
- char oldnamekey[USERNAME_SIZE];
- char newnamekey[USERNAME_SIZE];
-
- // Create the database keys...
- makeuserkey(oldnamekey, oldname);
- makeuserkey(newnamekey, newname);
-
- // Lock up and get going
- begin_critical_section(S_USERS);
-
- // We cannot rename a user who is currently logged in
- if (CtdlIsUserLoggedIn(oldname)) {
- end_critical_section(S_USERS);
- return RENAMEUSER_LOGGED_IN;
- }
-
- if (CtdlGetUser(&usbuf, newname) == 0) {
- retcode = RENAMEUSER_ALREADY_EXISTS;
- }
- else {
-
- if (CtdlGetUser(&usbuf, oldname) != 0) {
- retcode = RENAMEUSER_NOT_FOUND;
- }
- else { // Sanity checks succeeded. Now rename the user.
- if (usbuf.usernum == 0) {
- syslog(LOG_DEBUG, "user_ops: can not rename user \"Citadel\".");
- retcode = RENAMEUSER_NOT_FOUND;
- }
- else {
- syslog(LOG_DEBUG, "user_ops: renaming <%s> to <%s>", oldname, newname);
- cdb_delete(CDB_USERS, oldnamekey, strlen(oldnamekey));
- safestrncpy(usbuf.fullname, newname, sizeof usbuf.fullname);
- CtdlPutUser(&usbuf);
- cdb_store(CDB_USERSBYNUMBER, &usbuf.usernum, sizeof(long), usbuf.fullname, strlen(usbuf.fullname)+1 );
- retcode = RENAMEUSER_OK;
- }
- }
-
- }
-
- end_critical_section(S_USERS);
- return(retcode);
-}
-
-
-// Convert a username into the format used as a database key prior to version 928
-// This only gets called by reindex_user_928()
-void makeuserkey_pre928(char *key, const char *username) {
- int i;
-
- int len = strlen(username);
-
- if (len >= USERNAME_SIZE) {
- syslog(LOG_INFO, "Username too long: %s", username);
- len = USERNAME_SIZE - 1;
- }
- for (i=0; i<=len; ++i) {
- key[i] = tolower(username[i]);
- }
-}
-
-
-// Read a user record using the pre-v928 index format, and write it back using the v928-and-higher index format.
-// This ONLY gets called during an upgrade from version <928 to version >=928.
-void reindex_user_928(char *username, void *out_data) {
-
- char oldkey[USERNAME_SIZE];
- char newkey[USERNAME_SIZE];
- struct cdbdata *cdbus;
- struct ctdluser usbuf;
-
- makeuserkey_pre928(oldkey, username);
- makeuserkey(newkey, username);
-
- syslog(LOG_DEBUG, "user_ops: reindex_user_928: %s <%s> --> <%s>", username, oldkey, newkey);
-
- // Fetch the user record using the old index format
- cdbus = cdb_fetch(CDB_USERS, oldkey, strlen(oldkey));
- if (cdbus == NULL) {
- syslog(LOG_INFO, "user_ops: <%s> not found, were they already reindexed?", username);
- return;
- }
- memcpy(&usbuf, cdbus->ptr, ((cdbus->len > sizeof(struct ctdluser)) ? sizeof(struct ctdluser) : cdbus->len));
- cdb_free(cdbus);
-
- // delete the old record
- cdb_delete(CDB_USERS, oldkey, strlen(oldkey));
-
- // write the new record
- cdb_store(CDB_USERS, newkey, strlen(newkey), &usbuf, sizeof(struct ctdluser));
-}
-
-
-// Index-generating function used by Ctdl[Get|Set]Relationship
-int GenerateRelationshipIndex(char *IndexBuf,
- long RoomID,
- long RoomGen,
- long UserID
-) {
- struct {
- long iRoomID;
- long iRoomGen;
- long iUserID;
- } TheIndex;
-
- TheIndex.iRoomID = RoomID;
- TheIndex.iRoomGen = RoomGen;
- TheIndex.iUserID = UserID;
-
- memcpy(IndexBuf, &TheIndex, sizeof(TheIndex));
- return(sizeof(TheIndex));
-}
-
-
-// Back end for CtdlSetRelationship()
-void put_visit(visit *newvisit) {
- char IndexBuf[32];
- int IndexLen = 0;
-
- memset (IndexBuf, 0, sizeof (IndexBuf));
- // Generate an index
- IndexLen = GenerateRelationshipIndex(IndexBuf, newvisit->v_roomnum, newvisit->v_roomgen, newvisit->v_usernum);
-
- // Store the record
- cdb_store(CDB_VISIT, IndexBuf, IndexLen,
- newvisit, sizeof(visit)
- );
-}
-
-
-// Define a relationship between a user and a room
-void CtdlSetRelationship(visit *newvisit, struct ctdluser *rel_user, struct ctdlroom *rel_room) {
- // We don't use these in Citadel because they're implicit by the
- // index, but they must be present if the database is exported.
- newvisit->v_roomnum = rel_room->QRnumber;
- newvisit->v_roomgen = rel_room->QRgen;
- newvisit->v_usernum = rel_user->usernum;
-
- put_visit(newvisit);
-}
-
-
-// Locate a relationship between a user and a room
-void CtdlGetRelationship(visit *vbuf, struct ctdluser *rel_user, struct ctdlroom *rel_room) {
- char IndexBuf[32];
- int IndexLen;
- struct cdbdata *cdbvisit;
-
- // Generate an index
- IndexLen = GenerateRelationshipIndex(IndexBuf, rel_room->QRnumber, rel_room->QRgen, rel_user->usernum);
-
- // Clear out the buffer
- memset(vbuf, 0, sizeof(visit));
-
- cdbvisit = cdb_fetch(CDB_VISIT, IndexBuf, IndexLen);
- if (cdbvisit != NULL) {
- memcpy(vbuf, cdbvisit->ptr, ((cdbvisit->len > sizeof(visit)) ? sizeof(visit) : cdbvisit->len));
- cdb_free(cdbvisit);
- }
- else {
- // If this is the first time the user has seen this room, set the view to be the default for the room.
- vbuf->v_view = rel_room->QRdefaultview;
- }
-
- // Set v_seen if necessary
- if (vbuf->v_seen[0] == 0) {
- snprintf(vbuf->v_seen, sizeof vbuf->v_seen, "*:%ld", vbuf->v_lastseen);
- }
-}
-
-
-void CtdlMailboxName(char *buf, size_t n, const struct ctdluser *who, const char *prefix) {
- snprintf(buf, n, "%010ld.%s", who->usernum, prefix);
-}
-
-
-// Check to see if the specified user has Internet mail permission
-// (returns nonzero if permission is granted)
-int CtdlCheckInternetMailPermission(struct ctdluser *who) {
-
- // Do not allow twits to send Internet mail
- if (who->axlevel <= AxProbU) return(0);
-
- // Globally enabled?
- if (CtdlGetConfigInt("c_restrict") == 0) return(1);
-
- // User flagged ok?
- if (who->flags & US_INTERNET) return(2);
-
- // Admin level access?
- if (who->axlevel >= AxAideU) return(3);
-
- // No mail for you!
- return(0);
-}
-
-
-// This is a convenience function which follows the Citadel protocol semantics for most commands.
-// If the current user does not have the requested access level, it outputs a protocol-friendly error message
-// and then returns (-1). This allows calling functions to complete an access level check in one line of code.
-int CtdlAccessCheck(int required_level) {
- if (CC->internal_pgm) return(0);
- if (required_level >= ac_internal) {
- cprintf("%d This is not a user-level command.\n", ERROR + HIGHER_ACCESS_REQUIRED);
- return(-1);
- }
-
- if ((required_level >= ac_logged_in_or_guest) && (CC->logged_in == 0) && (CtdlGetConfigInt("c_guest_logins") == 0)) {
- cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
- return(-1);
- }
-
- if ((required_level >= ac_logged_in) && (CC->logged_in == 0)) {
- cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
- return(-1);
- }
-
- if (CC->user.axlevel >= AxAideU) return(0);
- if (required_level >= ac_aide) {
- cprintf("%d This command requires Admin access.\n",
- ERROR + HIGHER_ACCESS_REQUIRED);
- return(-1);
- }
-
- if (is_room_aide()) return(0);
- if (required_level >= ac_room_aide) {
- cprintf("%d This command requires Admin or Room Admin access.\n",
- ERROR + HIGHER_ACCESS_REQUIRED);
- return(-1);
- }
-
- // Do not generate any output if we succeeded -- the calling function will handle that.
- return(0);
-}
-
-
-// Is the user currently logged in an Admin?
-int is_aide(void) {
- if (CC->user.axlevel >= AxAideU) {
- return(1);
- }
- else {
- return(0);
- }
-}
-
-
-// Is the user currently logged in an Admin *or* the room Admin for this room?
-int is_room_aide(void) {
- if (!CC->logged_in) {
- return(0);
- }
-
- if ((CC->user.axlevel >= AxAideU) || (CC->room.QRroomaide == CC->user.usernum)) {
- return(1);
- }
- else {
- return(0);
- }
-}
-
-
-// CtdlGetUserByNumber() - get user by number, returns 0 if user was found
-// Note: fetching a user this way requires one additional database operation.
-int CtdlGetUserByNumber(struct ctdluser *usbuf, long number) {
- struct cdbdata *cdbun;
- int r;
-
- cdbun = cdb_fetch(CDB_USERSBYNUMBER, &number, sizeof(long));
- if (cdbun == NULL) {
- syslog(LOG_INFO, "user_ops: %ld not found", number);
- return(-1);
- }
-
- syslog(LOG_INFO, "user_ops: %ld maps to %s", number, cdbun->ptr);
- r = CtdlGetUser(usbuf, cdbun->ptr);
- cdb_free(cdbun);
- return(r);
-}
-
-
-// Helper function for rebuild_usersbynumber()
-void rebuild_ubn_for_user(char *username, void *data) {
- struct ctdluser u;
-
- syslog(LOG_DEBUG, "user_ops: rebuilding usersbynumber index for %s", username);
- if (CtdlGetUser(&u, username) == 0) {
- cdb_store(CDB_USERSBYNUMBER, &(u.usernum), sizeof(long), u.fullname, strlen(u.fullname)+1);
- }
-}
-
-
-// Rebuild the users-by-number index
-void rebuild_usersbynumber(void) {
- cdb_trunc(CDB_USERSBYNUMBER); // delete the old indices
- ForEachUser(rebuild_ubn_for_user, NULL); // enumerate the users
-}
-
-
-// getuserbyuid() Get user by system uid (for PAM mode authentication)
-// Returns 0 if user was found
-// This now uses an extauth index.
-int getuserbyuid(struct ctdluser *usbuf, uid_t number) {
- struct cdbdata *cdbextauth;
- long usernum = 0;
- StrBuf *claimed_id;
-
- claimed_id = NewStrBuf();
- StrBufPrintf(claimed_id, "uid:%d", number);
- cdbextauth = cdb_fetch(CDB_EXTAUTH, ChrPtr(claimed_id), StrLength(claimed_id));
- FreeStrBuf(&claimed_id);
- if (cdbextauth == NULL) {
- return(-1);
- }
-
- memcpy(&usernum, cdbextauth->ptr, sizeof(long));
- cdb_free(cdbextauth);
-
- if (!CtdlGetUserByNumber(usbuf, usernum)) {
- return(0);
- }
-
- return(-1);
-}
-
-
-// Back end for cmd_user() and its ilk
-int CtdlLoginExistingUser(const char *trythisname) {
- char username[SIZ];
- int found_user;
-
- syslog(LOG_DEBUG, "user_ops: CtdlLoginExistingUser(%s)", trythisname);
-
- if ((CC->logged_in)) {
- return login_already_logged_in;
- }
-
- if (trythisname == NULL) return login_not_found;
-
- if (!strncasecmp(trythisname, "SYS_", 4)) {
- syslog(LOG_DEBUG, "user_ops: system user \"%s\" is not allowed to log in.", trythisname);
- return login_not_found;
- }
-
- // Continue attempting user validation...
- safestrncpy(username, trythisname, sizeof (username));
- striplt(username);
-
- if (IsEmptyStr(username)) {
- return login_not_found;
- }
-
- // host auth mode...
- if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_HOST) {
- struct passwd pd;
- struct passwd *tempPwdPtr;
- char pwdbuffer[256];
-
- syslog(LOG_DEBUG, "user_ops: asking host about <%s>", username);
-#ifdef HAVE_GETPWNAM_R
-#ifdef SOLARIS_GETPWUID
- syslog(LOG_DEBUG, "user_ops: calling getpwnam_r()");
- tempPwdPtr = getpwnam_r(username, &pd, pwdbuffer, sizeof pwdbuffer);
-#else // SOLARIS_GETPWUID
- syslog(LOG_DEBUG, "user_ops: calling getpwnam_r()");
- getpwnam_r(username, &pd, pwdbuffer, sizeof pwdbuffer, &tempPwdPtr);
-#endif // SOLARIS_GETPWUID
-#else // HAVE_GETPWNAM_R
- syslog(LOG_DEBUG, "user_ops: SHOULD NEVER GET HERE!!!");
- tempPwdPtr = NULL;
-#endif // HAVE_GETPWNAM_R
- if (tempPwdPtr == NULL) {
- syslog(LOG_DEBUG, "user_ops: no such user <%s>", username);
- return login_not_found;
- }
-
- // Locate the associated Citadel account. If not found, make one attempt to create it.
- found_user = getuserbyuid(&CC->user, pd.pw_uid);
- if (found_user != 0) {
- create_user(username, CREATE_USER_DO_NOT_BECOME_USER, pd.pw_uid);
- found_user = getuserbyuid(&CC->user, pd.pw_uid);
- }
- syslog(LOG_DEBUG, "user_ops: found it: uid=%ld, gecos=%s here: %d", (long)pd.pw_uid, pd.pw_gecos, found_user);
-
- }
-
- // LDAP auth mode...
- else if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) {
-
- uid_t ldap_uid;
- char ldap_cn[256];
- char ldap_dn[256];
-
- found_user = CtdlTryUserLDAP(username, ldap_dn, sizeof ldap_dn, ldap_cn, sizeof ldap_cn, &ldap_uid);
- if (found_user != 0) {
- return login_not_found;
- }
-
- found_user = getuserbyuid(&CC->user, ldap_uid);
- if (found_user != 0) {
- create_user(ldap_cn, CREATE_USER_DO_NOT_BECOME_USER, ldap_uid);
- found_user = getuserbyuid(&CC->user, ldap_uid);
- }
-
- if (found_user == 0) {
- if (CC->ldap_dn != NULL) free(CC->ldap_dn);
- CC->ldap_dn = strdup(ldap_dn);
- }
-
- }
-
- // native auth mode...
- else {
- struct recptypes *valid = NULL;
-
- // First, try to log in as if the supplied name is a display name
- found_user = CtdlGetUser(&CC->user, username);
-
- // If that didn't work, try to log in as if the supplied name * is an e-mail address
- if (found_user != 0) {
- valid = validate_recipients(username, NULL, 0);
- if (valid != NULL) {
- if (valid->num_local == 1) {
- found_user = CtdlGetUser(&CC->user, valid->recp_local);
- }
- free_recipients(valid);
- }
- }
- }
-
- // Did we find something?
- if (found_user == 0) {
- if (((CC->nologin)) && (CC->user.axlevel < AxAideU)) {
- return login_too_many_users;
- }
- else {
- safestrncpy(CC->curr_user, CC->user.fullname, sizeof CC->curr_user);
- return login_ok;
- }
- }
- return login_not_found;
-}
-
-
-// session startup code which is common to both cmd_pass() and cmd_newu()
-void do_login(void) {
- CC->logged_in = 1;
- syslog(LOG_NOTICE, "user_ops: <%s> logged in", CC->curr_user);
-
- CtdlGetUserLock(&CC->user, CC->curr_user);
- ++(CC->user.timescalled);
- CC->previous_login = CC->user.lastcall;
- time(&CC->user.lastcall);
-
- // If this user's name is the name of the system administrator
- // (as specified in setup), automatically assign access level 6.
- if ( (!IsEmptyStr(CtdlGetConfigStr("c_sysadm"))) && (!strcasecmp(CC->user.fullname, CtdlGetConfigStr("c_sysadm"))) ) {
- CC->user.axlevel = AxAideU;
- }
-
- // If we're authenticating off the host system, automatically give root the highest level of access.
- if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_HOST) {
- if (CC->user.uid == 0) {
- CC->user.axlevel = AxAideU;
- }
- }
- CtdlPutUserLock(&CC->user);
-
- // If we are using LDAP authentication, extract the user's email addresses from the directory.
- if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) {
- char new_emailaddrs[512];
- if (CtdlGetConfigInt("c_ldap_sync_email_addrs") > 0) {
- if (extract_email_addresses_from_ldap(CC->ldap_dn, new_emailaddrs) == 0) {
- CtdlSetEmailAddressesForUser(CC->user.fullname, new_emailaddrs);
- }
- }
- }
-
- // If the user does not have any email addresses assigned, generate one.
- if (IsEmptyStr(CC->user.emailaddrs)) {
- AutoGenerateEmailAddressForUser(&CC->user);
- }
-
- // Populate the user principal identity, which is consistent and never aliased
- strcpy(CC->cs_principal_id, "");
- makeuserkey(CC->cs_principal_id, CC->user.fullname);
- strcat(CC->cs_principal_id, "@");
- strcat(CC->cs_principal_id, CtdlGetConfigStr("c_fqdn"));
-
- // Populate cs_inet_email and cs_inet_other_emails with valid email addresses from the user record
- strcpy(CC->cs_inet_email, CC->user.emailaddrs);
- char *firstsep = strstr(CC->cs_inet_email, "|");
- if (firstsep) {
- strcpy(CC->cs_inet_other_emails, firstsep+1);
- *firstsep = 0;
- }
- else {
- CC->cs_inet_other_emails[0] = 0;
- }
-
- // Create any personal rooms required by the system.
- // (Technically, MAILROOM should be there already, but just in case...)
- CtdlCreateRoom(MAILROOM, 4, "", 0, 1, 0, VIEW_MAILBOX);
- CtdlCreateRoom(SENTITEMS, 4, "", 0, 1, 0, VIEW_MAILBOX);
- CtdlCreateRoom(USERTRASHROOM, 4, "", 0, 1, 0, VIEW_MAILBOX);
- CtdlCreateRoom(USERDRAFTROOM, 4, "", 0, 1, 0, VIEW_MAILBOX);
-
- // Run any startup routines registered by loadable modules
- PerformSessionHooks(EVT_LOGIN);
-
- // Enter the lobby
- CtdlUserGoto(CtdlGetConfigStr("c_baseroom"), 0, 0, NULL, NULL, NULL, NULL);
-}
-
-
-void logged_in_response(void) {
- cprintf("%d %s|%d|%ld|%ld|%u|%ld|%ld\n",
- CIT_OK, CC->user.fullname, CC->user.axlevel,
- CC->user.timescalled, CC->user.posted,
- CC->user.flags, CC->user.usernum,
- CC->previous_login
- );
-}
-
-
-void CtdlUserLogout(void) {
-
- syslog(LOG_DEBUG, "user_ops: CtdlUserLogout() logging out <%s> from session %d", CC->curr_user, CC->cs_pid);
-
- // Run any hooks registered by modules...
- PerformSessionHooks(EVT_LOGOUT);
-
- // Clear out some session data. Most likely, the CitContext for this
- // session is about to get nuked when the session disconnects, but
- // since it's possible to log in again without reconnecting, we cannot
- // make that assumption.
- CC->logged_in = 0;
-
- // Check to see if the user was deleted while logged in and purge them if necessary
- if ((CC->user.axlevel == AxDeleted) && (CC->user.usernum)) {
- purge_user(CC->user.fullname);
- }
-
- // Clear out the user record in memory so we don't behave like a ghost
- memset(&CC->user, 0, sizeof(struct ctdluser));
- CC->curr_user[0] = 0;
- CC->cs_inet_email[0] = 0;
- CC->cs_inet_other_emails[0] = 0;
- CC->cs_inet_fn[0] = 0;
-
- // Free any output buffers
- unbuffer_output();
-}
-
-
-// Validate a password on the host unix system by talking to the chkpwd daemon
-static int validpw(uid_t uid, const char *pass) {
- char buf[256];
- int rv = 0;
-
- if (IsEmptyStr(pass)) {
- syslog(LOG_DEBUG, "user_ops: refusing to chkpwd for uid=%d with empty password", uid);
- return 0;
- }
-
- syslog(LOG_DEBUG, "user_ops: validating password for uid=%d using chkpwd...", uid);
-
- begin_critical_section(S_CHKPWD);
- rv = write(chkpwd_write_pipe[1], &uid, sizeof(uid_t));
- if (rv == -1) {
- syslog(LOG_ERR, "user_ops: communication with chkpwd broken: %m");
- end_critical_section(S_CHKPWD);
- return 0;
- }
- rv = write(chkpwd_write_pipe[1], pass, 256);
- if (rv == -1) {
- syslog(LOG_ERR, "user_ops: communication with chkpwd broken: %m");
- end_critical_section(S_CHKPWD);
- return 0;
- }
- rv = read(chkpwd_read_pipe[0], buf, 4);
- if (rv == -1) {
- syslog(LOG_ERR, "user_ops: ommunication with chkpwd broken: %m");
- end_critical_section(S_CHKPWD);
- return 0;
- }
- end_critical_section(S_CHKPWD);
-
- if (!strncmp(buf, "PASS", 4)) {
- syslog(LOG_DEBUG, "user_ops: chkpwd pass");
- return(1);
- }
-
- syslog(LOG_DEBUG, "user_ops: chkpwd fail");
- return 0;
-}
-
-
-// Start up the chkpwd daemon so validpw() has something to talk to
-void start_chkpwd_daemon(void) {
- pid_t chkpwd_pid;
- struct stat filestats;
- int i;
-
- syslog(LOG_DEBUG, "user_ops: starting chkpwd daemon for host authentication mode");
-
- if ((stat(file_chkpwd, &filestats)==-1) || (filestats.st_size==0)) {
- syslog(LOG_ERR, "user_ops: %s: %m", file_chkpwd);
- abort();
- }
- if (pipe(chkpwd_write_pipe) != 0) {
- syslog(LOG_ERR, "user_ops: unable to create pipe for chkpwd daemon: %m");
- abort();
- }
- if (pipe(chkpwd_read_pipe) != 0) {
- syslog(LOG_ERR, "user_ops: unable to create pipe for chkpwd daemon: %m");
- abort();
- }
-
- chkpwd_pid = fork();
- if (chkpwd_pid < 0) {
- syslog(LOG_ERR, "user_ops: unable to fork chkpwd daemon: %m");
- abort();
- }
- if (chkpwd_pid == 0) {
- dup2(chkpwd_write_pipe[0], 0);
- dup2(chkpwd_read_pipe[1], 1);
- for (i=2; i<256; ++i) close(i);
- execl(file_chkpwd, file_chkpwd, NULL);
- syslog(LOG_ERR, "user_ops: unable to exec chkpwd daemon: %m");
- abort();
- exit(errno);
- }
-}
-
-
-int CtdlTryPassword(const char *password, long len) {
- int code;
-
- if ((CC->logged_in)) {
- syslog(LOG_WARNING, "user_ops: CtdlTryPassword: already logged in");
- return pass_already_logged_in;
- }
- if (!strcmp(CC->curr_user, NLI)) {
- syslog(LOG_WARNING, "user_ops: CtdlTryPassword: no user selected");
- return pass_no_user;
- }
- if (CtdlGetUser(&CC->user, CC->curr_user)) {
- syslog(LOG_ERR, "user_ops: CtdlTryPassword: internal error");
- return pass_internal_error;
- }
- if (password == NULL) {
- syslog(LOG_INFO, "user_ops: CtdlTryPassword: NULL password string supplied");
- return pass_wrong_password;
- }
-
- // host auth mode...
- else if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_HOST) {
- if (validpw(CC->user.uid, password)) {
- code = 0;
-
- // sooper-seekrit hack: populate the password field in the
- // citadel database with the password that the user typed,
- // if it's correct. This allows most sites to convert from
- // host auth to native auth if they want to. If you think
- // this is a security hazard, comment it out.
-
- CtdlGetUserLock(&CC->user, CC->curr_user);
- safestrncpy(CC->user.password, password, sizeof CC->user.password);
- CtdlPutUserLock(&CC->user);
-
- // (sooper-seekrit hack ends here)
- }
- else {
- code = (-1);
- }
- }
-
- // LDAP auth mode...
- else if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) {
-
- if ((CC->ldap_dn) && (!CtdlTryPasswordLDAP(CC->ldap_dn, password))) {
- code = 0;
- }
- else {
- code = (-1);
- }
- }
-
- // native auth mode...
- else {
- char *pw;
-
- pw = (char*) malloc(len + 1);
- memcpy(pw, password, len + 1);
- strproc(pw);
- strproc(CC->user.password);
- code = strcasecmp(CC->user.password, pw);
- if (code != 0) {
- strproc(pw);
- strproc(CC->user.password);
- code = strcasecmp(CC->user.password, pw);
- }
- free (pw);
- }
-
- if (!code) {
- do_login();
- return pass_ok;
- }
- else {
- syslog(LOG_WARNING, "user_ops: bad password specified for <%s> Service <%s> Port <%ld> Remote <%s / %s>",
- CC->curr_user,
- CC->ServiceName,
- CC->tcp_port,
- CC->cs_host,
- CC->cs_addr
- );
- return pass_wrong_password;
- }
-}
-
-
-// Delete a user record *and* all of its related resources.
-int purge_user(char pname[]) {
- struct ctdluser usbuf;
- char usernamekey[USERNAME_SIZE];
-
- makeuserkey(usernamekey, pname);
-
- // If the name is empty we can't find them in the DB any way so just return
- if (IsEmptyStr(pname)) {
- return(ERROR + NO_SUCH_USER);
- }
-
- if (CtdlGetUser(&usbuf, pname) != 0) {
- syslog(LOG_ERR, "user_ops: cannot purge user <%s> - not found", pname);
- return(ERROR + NO_SUCH_USER);
- }
-
- // Don't delete a user who is currently logged in. Instead, just
- // set the access level to 0, and let the account get swept up
- // during the next purge.
- if (CtdlIsUserLoggedInByNum(usbuf.usernum)) {
- syslog(LOG_WARNING, "user_ops: <%s> is logged in; not deleting", pname);
- usbuf.axlevel = AxDeleted;
- CtdlPutUser(&usbuf);
- return(1);
- }
-
- syslog(LOG_NOTICE, "user_ops: deleting <%s>", pname);
-
- // Perform any purge functions registered by server extensions
- PerformUserHooks(&usbuf, EVT_PURGEUSER);
-
- // delete any existing user/room relationships
- cdb_delete(CDB_VISIT, &usbuf.usernum, sizeof(long));
-
- // delete the users-by-number index record
- cdb_delete(CDB_USERSBYNUMBER, &usbuf.usernum, sizeof(long));
-
- // delete the user entry
- cdb_delete(CDB_USERS, usernamekey, strlen(usernamekey));
-
- return(0);
-}
-
-
-// This is the back end processing that happens when we create a new user account.
-int internal_create_user(char *username, struct ctdluser *usbuf, uid_t uid) {
- if (!CtdlGetUser(usbuf, username)) {
- return(ERROR + ALREADY_EXISTS);
- }
-
- // Go ahead and initialize a new user record
- memset(usbuf, 0, sizeof(struct ctdluser));
- safestrncpy(usbuf->fullname, username, sizeof usbuf->fullname);
- strcpy(usbuf->password, "");
- usbuf->uid = uid;
-
- // These are the default flags on new accounts
- usbuf->flags = US_LASTOLD | US_DISAPPEAR | US_PAGINATOR | US_FLOORS;
-
- usbuf->timescalled = 0;
- usbuf->posted = 0;
- usbuf->axlevel = CtdlGetConfigInt("c_initax");
- usbuf->lastcall = time(NULL);
-
- // fetch a new user number
- usbuf->usernum = get_new_user_number();
-
- // add user to the database
- CtdlPutUser(usbuf);
- cdb_store(CDB_USERSBYNUMBER, &usbuf->usernum, sizeof(long), usbuf->fullname, strlen(usbuf->fullname)+1);
-
- // If non-native auth, index by uid
- if ((usbuf->uid > 0) && (usbuf->uid != NATIVE_AUTH_UID)) {
- StrBuf *claimed_id = NewStrBuf();
- StrBufPrintf(claimed_id, "uid:%d", usbuf->uid);
- attach_extauth(usbuf, claimed_id);
- FreeStrBuf(&claimed_id);
- }
-
- return(0);
-}
-
-
-// create_user() - back end processing to create a new user
-//
-// Set 'newusername' to the desired account name.
-// Set 'become_user' to CREATE_USER_BECOME_USER if this is self-service account creation and we want to
-// actually log in as the user we just created, otherwise set it to CREATE_USER_DO_NOT_BECOME_USER
-// Set 'uid' to some uid_t value to associate the account with an external auth user, or (-1) for native auth
-int create_user(char *username, int become_user, uid_t uid) {
- struct ctdluser usbuf;
- struct ctdlroom qrbuf;
- char mailboxname[ROOMNAMELEN];
- char buf[SIZ];
- int retval;
-
- strproc(username);
- if ((retval = internal_create_user(username, &usbuf, uid)) != 0) {
- return retval;
- }
-
- // Give the user a private mailbox and a configuration room.
- // Make the latter an invisible system room.
- CtdlMailboxName(mailboxname, sizeof mailboxname, &usbuf, MAILROOM);
- CtdlCreateRoom(mailboxname, 5, "", 0, 1, 1, VIEW_MAILBOX);
-
- CtdlMailboxName(mailboxname, sizeof mailboxname, &usbuf, USERCONFIGROOM);
- CtdlCreateRoom(mailboxname, 5, "", 0, 1, 1, VIEW_BBS);
- if (CtdlGetRoomLock(&qrbuf, mailboxname) == 0) {
- qrbuf.QRflags2 |= QR2_SYSTEM;
- CtdlPutRoomLock(&qrbuf);
- }
-
- // Perform any create functions registered by server extensions
- PerformUserHooks(&usbuf, EVT_NEWUSER);
-
- // Everything below this line can be bypassed if administratively
- // creating a user, instead of doing self-service account creation
-
- if (become_user == CREATE_USER_BECOME_USER) {
- // Now become the user we just created
- memcpy(&CC->user, &usbuf, sizeof(struct ctdluser));
- safestrncpy(CC->curr_user, username, sizeof CC->curr_user);
- do_login();
-
- // Check to make sure we're still who we think we are
- if (CtdlGetUser(&CC->user, CC->curr_user)) {
- return(ERROR + INTERNAL_ERROR);
- }
- }
-
- snprintf(buf, SIZ,
- "New user account <%s> has been created, from host %s [%s].\n",
- username,
- CC->cs_host,
- CC->cs_addr
- );
- CtdlAideMessage(buf, "User Creation Notice");
- syslog(LOG_NOTICE, "user_ops: <%s> created", username);
- return(0);
-}
-
-
-// set password - back end api code
-void CtdlSetPassword(char *new_pw) {
- CtdlGetUserLock(&CC->user, CC->curr_user);
- safestrncpy(CC->user.password, new_pw, sizeof(CC->user.password));
- CtdlPutUserLock(&CC->user);
- syslog(LOG_INFO, "user_ops: password changed for <%s>", CC->curr_user);
- PerformSessionHooks(EVT_SETPASS);
-}
-
-
-// API function for cmd_invt_kick() and anything else that needs to
-// invite or kick out a user to/from a room.
-//
-// Set iuser to the name of the user, and op to 1=invite or 0=kick
-int CtdlInvtKick(char *iuser, int op) {
- struct ctdluser USscratch;
- visit vbuf;
- char bbb[SIZ];
-
- if (CtdlGetUser(&USscratch, iuser) != 0) {
- return(1);
- }
-
- CtdlGetRelationship(&vbuf, &USscratch, &CC->room);
- if (op == 1) {
- vbuf.v_flags = vbuf.v_flags & ~V_FORGET & ~V_LOCKOUT;
- vbuf.v_flags = vbuf.v_flags | V_ACCESS;
- }
- if (op == 0) {
- vbuf.v_flags = vbuf.v_flags & ~V_ACCESS;
- vbuf.v_flags = vbuf.v_flags | V_FORGET | V_LOCKOUT;
- }
- CtdlSetRelationship(&vbuf, &USscratch, &CC->room);
-
- // post a message in Aide> saying what we just did
- snprintf(bbb, sizeof bbb, "%s has been %s \"%s\" by %s.\n",
- iuser,
- ((op == 1) ? "invited to" : "kicked out of"),
- CC->room.QRname,
- (CC->logged_in ? CC->user.fullname : "an administrator")
- );
- CtdlAideMessage(bbb,"User Admin Message");
- return(0);
-}
-
-
-// Forget (Zap) the current room (API call)
-// Returns 0 on success
-int CtdlForgetThisRoom(void) {
- visit vbuf;
-
- // On some systems, Admins are not allowed to forget rooms
- if (is_aide() && (CtdlGetConfigInt("c_aide_zap") == 0)
- && ((CC->room.QRflags & QR_MAILBOX) == 0) ) {
- return(1);
- }
-
- CtdlGetUserLock(&CC->user, CC->curr_user);
- CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
-
- vbuf.v_flags = vbuf.v_flags | V_FORGET;
- vbuf.v_flags = vbuf.v_flags & ~V_ACCESS;
-
- CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
- CtdlPutUserLock(&CC->user);
-
- // Return to the Lobby, so we don't end up in an undefined room
- CtdlUserGoto(CtdlGetConfigStr("c_baseroom"), 0, 0, NULL, NULL, NULL, NULL);
- return(0);
-}
-
-
-// Traverse the user file and perform a callback for each user record.
-// (New improved version that runs in two phases so that callbacks can perform writes without having a r/o cursor open)
-void ForEachUser(void (*CallBack) (char *, void *out_data), void *in_data) {
- struct cdbdata *cdbus;
- struct ctdluser *usptr;
-
- struct feu {
- struct feu *next;
- char username[USERNAME_SIZE];
- };
- struct feu *ufirst = NULL;
- struct feu *ulast = NULL;
- struct feu *f = NULL;
-
- cdb_rewind(CDB_USERS);
-
- // Phase 1 : build a linked list of all our user account names
- while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL) {
- usptr = (struct ctdluser *) cdbus->ptr;
-
- if (strlen(usptr->fullname) > 0) {
- f = malloc(sizeof(struct feu));
- f->next = NULL;
- strncpy(f->username, usptr->fullname, USERNAME_SIZE);
-
- if (ufirst == NULL) {
- ufirst = f;
- ulast = f;
- }
- else {
- ulast->next = f;
- ulast = f;
- }
- }
- }
-
- // Phase 2 : perform the callback for each user while de-allocating the list
- while (ufirst != NULL) {
- (*CallBack) (ufirst->username, in_data);
- f = ufirst;
- ufirst = ufirst->next;
- free(f);
- }
-}
-
-
-// Count the number of new mail messages the user has
-int NewMailCount() {
- int num_newmsgs = 0;
- num_newmsgs = CC->newmail;
- CC->newmail = 0;
- return(num_newmsgs);
-}
-
-
-// Count the number of new mail messages the user has
-int InitialMailCheck() {
- int num_newmsgs = 0;
- int a;
- char mailboxname[ROOMNAMELEN];
- struct ctdlroom mailbox;
- visit vbuf;
- struct cdbdata *cdbfr;
- long *msglist = NULL;
- int num_msgs = 0;
-
- CtdlMailboxName(mailboxname, sizeof mailboxname, &CC->user, MAILROOM);
- if (CtdlGetRoom(&mailbox, mailboxname) != 0)
- return(0);
- CtdlGetRelationship(&vbuf, &CC->user, &mailbox);
-
- cdbfr = cdb_fetch(CDB_MSGLISTS, &mailbox.QRnumber, sizeof(long));
-
- if (cdbfr != NULL) {
- msglist = malloc(cdbfr->len);
- memcpy(msglist, cdbfr->ptr, cdbfr->len);
- num_msgs = cdbfr->len / sizeof(long);
- cdb_free(cdbfr);
- }
- if (num_msgs > 0)
- for (a = 0; a < num_msgs; ++a) {
- if (msglist[a] > 0L) {
- if (msglist[a] > vbuf.v_lastseen) {
- ++num_newmsgs;
- }
- }
- }
- if (msglist != NULL)
- free(msglist);
-
- return(num_newmsgs);
-}
+++ /dev/null
-// Header file for server functions which perform operations on user objects.
-//
-// Copyright (c) 1987-2022 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.
-
-#ifndef __USER_OPS_H__
-#define __USER_OPS_H__
-
-#include <ctype.h>
-#include <syslog.h>
-
-int hash (char *str);
-int is_aide (void);
-int is_room_aide (void);
-int CtdlCheckInternetMailPermission(struct ctdluser *who);
-void rebuild_usersbynumber(void);
-void session_startup (void);
-void logged_in_response(void);
-int purge_user (char *pname);
-int getuserbyuid(struct ctdluser *usbuf, uid_t number);
-
-int create_user(char *newusername, int become_user, uid_t uid);
-enum {
- CREATE_USER_DO_NOT_BECOME_USER,
- CREATE_USER_BECOME_USER
-};
-#define NATIVE_AUTH_UID (-1)
-
-void do_login(void);
-int CtdlInvtKick(char *iuser, int op);
-void ForEachUser(void (*CallBack) (char *, void *out_data), void *in_data);
-int NewMailCount(void);
-int InitialMailCheck(void);
-void put_visit(visit *newvisit);
-int GenerateRelationshipIndex(char *IndexBuf, long RoomID, long RoomGen, long UserID);
-int CtdlAssociateSystemUser(char *screenname, char *loginname);
-
-void CtdlSetPassword(char *new_pw);
-
-int CtdlForgetThisRoom(void);
-
-void cmd_newu (char *cmdbuf);
-void start_chkpwd_daemon(void);
-
-
-#define RENAMEUSER_OK 0 // Operation succeeded
-#define RENAMEUSER_LOGGED_IN 1 // Cannot rename a user who is currently logged in
-#define RENAMEUSER_NOT_FOUND 2 // The old user name does not exist
-#define RENAMEUSER_ALREADY_EXISTS 3 // An account with the desired new name already exists
-
-int rename_user(char *oldname, char *newname);
-void reindex_user_928(char *username, void *out_data);
-
-void makeuserkey(char *key, const char *username);
-int CtdlUserCmp(char *s1, char *s2);
-int internal_create_user(char *username, struct ctdluser *usbuf, uid_t uid);
-
-#endif