X-Git-Url: https://code.citadel.org/?p=citadel.git;a=blobdiff_plain;f=citadel%2Fconfig.c;h=2507abfee037e042aee267c0ca7fc7a4c0277738;hp=35a25b8cba7867feec00d0b518b2847f2bef7bcf;hb=e329db30593524cc2d8851a4500bac41f2340354;hpb=819acff4ee67e603054de2790c171158265898c7 diff --git a/citadel/config.c b/citadel/config.c index 35a25b8cb..2507abfee 100644 --- a/citadel/config.c +++ b/citadel/config.c @@ -1,126 +1,516 @@ /* - * $Id$ + * Read and write the citadel.config file * - * This function reads the citadel.config file. It should be called at - * the beginning of EVERY Citadel program. + * 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 "sysdep.h" #include -#include #include #include -#include -#include -#include -#include "citadel.h" -#include "server.h" -#include "serv_extensions.h" +#include +#include +#include +#include #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_managesieve_port", 2020); + 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); + } +} + + +/* + * 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", ""); + 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_managesieve_port", 2020); + 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)); +} -struct config config; /* - * get_config() is called during the initialization of any program which - * directly accesses Citadel data files. It verifies the system's integrity - * and reads citadel.config into memory. + * Migrate a supplied legacy configuration to the new in-db format. + * No individual site should ever have to do this more than once. */ -void get_config(void) { +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_managesieve_port" , lconfig->c_managesieve_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; - struct stat st; + 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); + } if (chdir(ctdl_bbsbase_dir) != 0) { fprintf(stderr, - "This program could not be started.\n" - "Unable to change directory to %s\n" - "Error: %s\n", + "This program could not be started.\nUnable to change directory to %s\nError: %s\n", ctdl_bbsbase_dir, - strerror(errno)); + strerror(errno) + ); exit(CTDLEXIT_HOME); } + + memset(&lconfig, 0, sizeof(struct legacy_config)); cfp = fopen(file_citadel_config, "rb"); - if (cfp == NULL) { - fprintf(stderr, "This program could not be started.\n" - "Unable to open %s\n" - "Error: %s\n", - file_citadel_config, - strerror(errno)); - exit(CTDLEXIT_CONFIG); - } - fread((char *) &config, sizeof(struct config), 1, cfp); - if (fstat(fileno(cfp), &st)) { - perror(file_citadel_config); - exit(CTDLEXIT_CONFIG); - } -#ifndef __CYGWIN__ - if (st.st_uid != CTDLUID) { - fprintf(stderr, "%s must be owned by uid="F_UID_T" but "F_UID_T" owns it!\n", - file_citadel_config, CTDLUID, st.st_uid); - exit(CTDLEXIT_CONFIG); - } - int desired_mode = (S_IFREG | S_IRUSR | S_IWUSR) ; - if (st.st_mode != desired_mode) { - fprintf(stderr, "%s must be set to permissions mode %03o but they are %03o\n", - file_citadel_config, desired_mode, st.st_mode); - exit(CTDLEXIT_CONFIG); + 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)); + } -#endif - fclose(cfp); - if (config.c_setup_level < REV_MIN) { - fprintf(stderr, "config: Your data files are out of date. "); - fprintf(stderr, "Run setup to update them.\n"); - fprintf(stderr, - " This program requires level %d.%02d\n", - (REV_LEVEL / 100), (REV_LEVEL % 100)); - fprintf(stderr, - " Data files are currently at %d.%02d\n", - (config.c_setup_level / 100), - (config.c_setup_level % 100)); - exit(CTDLEXIT_OOD); + /* New installation? Set up configuration */ + if (CtdlGetConfigLong("c_config_created_or_migrated") <= 0) { + brand_new_installation_set_defaults(); } + /* Only allow LDAP auth mode if we actually have LDAP support */ +#ifndef HAVE_LDAP + if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) { + fprintf(stderr, "Your system is configured for LDAP authentication,\n" + "but you are running a server built without OpenLDAP support.\n"); + exit(CTDL_EXIT_UNSUP_AUTH); + } +#endif + /* 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 (config.c_maxmsglen <= 0) - config.c_maxmsglen = 10485760; - if (config.c_maxmsglen < 8192) - config.c_maxmsglen = 8192; + 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 */ + /* + * 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 + } - if (config.c_min_workers < 3) /* no less than 3 */ - config.c_min_workers = 5; + /* 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 - if (config.c_max_workers == 0) /* default maximum */ - config.c_max_workers = 256; + /* 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 - if (config.c_max_workers < config.c_min_workers) /* max >= min */ - config.c_max_workers = config.c_min_workers; + /* LDAP sync frequency */ + if (CtdlGetConfigLong("c_ldap_sync_freq") == 0) CtdlSetConfigLong("c_ldap_sync_freq", 300); // every 5 minutes default - /* Networking more than once every five minutes just isn't sane */ - if (config.c_net_freq == 0L) - config.c_net_freq = 3600L; /* once per hour default */ - if (config.c_net_freq < 300L) - config.c_net_freq = 300L; + /* "create new user" only works with native authentication mode */ + if (CtdlGetConfigInt("c_auth_mode") != AUTHMODE_NATIVE) { + CtdlSetConfigInt("c_disable_newu", 1); + } } /* - * Occasionally, we will need to write the config file, because some operations - * change site-wide parameters. + * Called when Citadel server is shutting down. + * Clears out the config hash table. */ -void put_config(void) +void shutdown_config_system(void) { - FILE *cfp; + 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); - if ((cfp = fopen(file_citadel_config, "rb+")) == NULL) - perror(file_citadel_config); + /* 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; +} + + +char *CtdlGetSysConfig(char *sysconfname) { + char hold_rm[ROOMNAMELEN]; + long msgnum; + 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; + } + + /* We want the last (and probably only) config in this room */ + begin_critical_section(S_CONFIG); + config_msgnum = (-1L); + CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL, CtdlGetSysConfigBackend, NULL); + msgnum = config_msgnum; + end_critical_section(S_CONFIG); + + if (msgnum < 0L) { + conf = NULL; + } else { - fwrite((char *) &config, sizeof(struct config), 1, cfp); - fclose(cfp); + msg = CtdlFetchMessage(msgnum, 1, 1); + if (msg != NULL) { + conf = strdup(msg->cm_fields[eMesageText]); + CM_Free(msg); + } + else { + conf = NULL; + } + } + + CtdlGetRoom(&CC->room, hold_rm); + + if (conf != NULL) { + do { + extract_token(buf, conf, 0, '\n', sizeof buf); + strcpy(conf, &conf[strlen(buf)+1]); + } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) ); } + + return(conf); +} + + +void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) { + CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0); }