/* * Here's where it all begins. */ int main(int argc, char **argv) { uid_t UID = -1; size_t basesize = 2; /* how big should strbufs be on creation? */ pthread_t SessThread; /* Thread descriptor */ pthread_attr_t attr; /* Thread attributes */ int a; /* General-purpose variable */ char ip_addr[256]="*"; int relh=0; int home=0; char relhome[PATH_MAX]=""; char webcitdir[PATH_MAX] = DATADIR; char *pidfile = NULL; char *hdir; const char *basedir = NULL; char uds_listen_path[PATH_MAX]; /* listen on a unix domain socket? */ const char *I18nDumpFile = NULL; WildFireInitBacktrace(argv[0], 2); start_modules(); #ifdef DBG_PRINNT_HOOKS_AT_START /* dbg_PrintHash(HandlerHash, nix, NULL);*/ #endif /* 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, "WebCit was compiled against version %d\n", LIBCITADEL_VERSION_NUMBER ); return(1); } strcpy(uds_listen_path, ""); /* Parse command line */ #ifdef HAVE_OPENSSL while ((a = getopt(argc, argv, "u:h:i:p:t:T:B:x:g:dD:G:cfsS:Z:v:")) != EOF) #else while ((a = getopt(argc, argv, "u:h:i:p:t:T:B:x:g:dD:G:cfZ:v:")) != EOF) #endif switch (a) { case 'u': UID = atol(optarg); break; case 'h': hdir = strdup(optarg); relh=hdir[0]!='/'; if (!relh) { safestrncpy(webcitdir, hdir, sizeof webcitdir); } else { safestrncpy(relhome, relhome, sizeof relhome); } /* free(hdir); TODO: SHOULD WE DO THIS? */ home=1; break; case 'd': running_as_daemon = 1; break; case 'D': pidfile = strdup(optarg); running_as_daemon = 1; break; case 'g': default_landing_page = strdup(optarg); break; case 'B': /* Basesize */ basesize = atoi(optarg); if (basesize > 2) StartLibCitadel(basesize); break; case 'i': safestrncpy(ip_addr, optarg, sizeof ip_addr); break; case 'p': http_port = atoi(optarg); if (http_port == 0) { safestrncpy(uds_listen_path, optarg, sizeof uds_listen_path); } break; case 't': /* no longer used, but ignored so old scripts don't break */ break; case 'T': LoadTemplates = atoi(optarg); dbg_analyze_msg = (LoadTemplates & (1<<1)) != 0; dbg_backtrace_template_errors = (LoadTemplates & (1<<2)) != 0; break; case 'Z': DisableGzip = 1; break; case 'x': /* no longer used, but ignored so old scripts don't break */ break; case 'f': follow_xff = 1; break; case 'c': server_cookie = malloc(256); if (server_cookie != NULL) { safestrncpy(server_cookie, "Set-cookie: wcserver=", 256); if (gethostname (&server_cookie[strlen(server_cookie)], 200) != 0) { syslog(LOG_INFO, "gethostname: %s", strerror(errno)); free(server_cookie); } } break; #ifdef HAVE_OPENSSL case 's': is_https = 1; break; case 'S': is_https = 1; ssl_cipher_list = strdup(optarg); break; #endif case 'G': DumpTemplateI18NStrings = 1; I18nDump = NewStrBufPlain(HKEY("int templatestrings(void)\n{\n")); I18nDumpFile = optarg; break; case 'v': verbose=1; break; default: fprintf(stderr, "usage:\nwebcit " "[-i ip_addr] [-p http_port] " "[-c] [-f] " "[-T Templatedebuglevel] " "[-d] [-Z] [-G i18ndumpfile] " "[-u uid] [-h homedirectory] " "[-D daemonizepid] [-v] " "[-g defaultlandingpage] [-B basesize] " #ifdef HAVE_OPENSSL "[-s] [-S cipher_suites]" #endif "[remotehost [remoteport]]\n"); return 1; } /* Start the logger */ openlog("webcit", ( running_as_daemon ? (LOG_PID) : (LOG_PID | LOG_PERROR) ), LOG_DAEMON ); if (optind < argc) { ctdlhost = argv[optind]; if (++optind < argc) ctdlport = argv[optind]; } /* daemonize, if we were asked to */ if (!DumpTemplateI18NStrings && running_as_daemon) { start_daemon(pidfile); } else { signal(SIGINT, graceful_shutdown); signal(SIGHUP, graceful_shutdown); } webcit_calc_dirs_n_files(relh, basedir, home, webcitdir, relhome); LoadMimeBlacklist(); LoadIconDir(static_icon_dir); /* Tell 'em who's in da house */ syslog(LOG_NOTICE, "%s", PACKAGE_STRING); syslog(LOG_NOTICE, "Copyright (C) 1996-2015 by the citadel.org team"); syslog(LOG_NOTICE, " "); syslog(LOG_NOTICE, "This program is open source software: you can redistribute it and/or"); syslog(LOG_NOTICE, "modify it under the terms of the GNU General Public License, version 3."); syslog(LOG_NOTICE, " "); syslog(LOG_NOTICE, "This program is distributed in the hope that it will be useful,"); syslog(LOG_NOTICE, "but WITHOUT ANY WARRANTY; without even the implied warranty of"); syslog(LOG_NOTICE, "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the"); syslog(LOG_NOTICE, "GNU General Public License for more details."); syslog(LOG_NOTICE, " "); /* initialize various subsystems */ initialise_modules(); initialise2_modules(); InitTemplateCache(); if (DumpTemplateI18NStrings) { FILE *fd; StrBufAppendBufPlain(I18nDump, HKEY("}\n"), 0); if (StrLength(I18nDump) < 50) { syslog(LOG_INFO, "*******************************************************************\n"); syslog(LOG_INFO, "* No strings found in templates! Are you sure they're there? *\n"); syslog(LOG_INFO, "*******************************************************************\n"); return -1; } fd = fopen(I18nDumpFile, "w"); if (fd == NULL) { syslog(LOG_INFO, "***********************************************\n"); syslog(LOG_INFO, "* unable to open I18N dumpfile [%s] *\n", I18nDumpFile); syslog(LOG_INFO, "***********************************************\n"); return -1; } fwrite(ChrPtr(I18nDump), 1, StrLength(I18nDump), fd); fclose(fd); return 0; } /* Tell libical to return an error instead of aborting if it sees badly formed iCalendar data. */ icalerror_errors_are_fatal = 0; /* Use our own prefix on tzid's generated from system tzdata */ icaltimezone_set_tzid_prefix("/citadel.org/"); /* * Set up a place to put thread-specific data. * We only need a single pointer per thread - it points to the * wcsession struct to which the thread is currently bound. */ if (pthread_key_create(&MyConKey, NULL) != 0) { syslog(LOG_EMERG, "Can't create TSD key: %s", strerror(errno)); } InitialiseSemaphores(); /* * Set up a place to put thread-specific SSL data. * We don't stick this in the wcsession struct because SSL starts * up before the session is bound, and it gets torn down between * transactions. */ #ifdef HAVE_OPENSSL if (pthread_key_create(&ThreadSSL, NULL) != 0) { syslog(LOG_EMERG, "Can't create TSD key: %s", strerror(errno)); } #endif /* * Bind the server to our favorite port. * There is no need to check for errors, because webcit_tcp_server() * exits if it doesn't succeed. */ if (!IsEmptyStr(uds_listen_path)) { syslog(LOG_DEBUG, "Attempting to create listener socket at %s...", uds_listen_path); msock = webcit_uds_server(uds_listen_path, LISTEN_QUEUE_LENGTH); } else { syslog(LOG_DEBUG, "Attempting to bind to port %d...", http_port); msock = webcit_tcp_server(ip_addr, http_port, LISTEN_QUEUE_LENGTH); } if (msock < 0) { ShutDownWebcit(); return -msock; } syslog(LOG_INFO, "Listening on socket %d", msock); signal(SIGPIPE, SIG_IGN); pthread_mutex_init(&SessionListMutex, NULL); /* * Start up the housekeeping thread */ pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_create(&SessThread, &attr, (void *(*)(void *)) housekeeping_loop, NULL); /* * If this is an HTTPS server, fire up SSL */ #ifdef HAVE_OPENSSL if (is_https) { init_ssl(); } #endif drop_root(UID); /* Become a worker thread. More worker threads will be spawned as they are needed. */ worker_entry(); ShutDownLibCitadel(); return 0; }
/* * initialize ssl engine, load certs and initialize openssl internals */ void init_ssl(void) { const SSL_METHOD *ssl_method; RSA *rsa=NULL; X509_REQ *req = NULL; X509 *cer = NULL; EVP_PKEY *pk = NULL; EVP_PKEY *req_pkey = NULL; X509_NAME *name = NULL; FILE *fp; char buf[SIZ]; int rv = 0; if (!access("/var/run/egd-pool", F_OK)) { RAND_egd("/var/run/egd-pool"); } if (!RAND_status()) { syslog(LOG_WARNING, "PRNG not adequately seeded, won't do SSL/TLS\n"); return; } SSLCritters = malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t *)); if (!SSLCritters) { syslog(LOG_ERR, "citserver: can't allocate memory!!\n"); /* Nothing's been initialized, just die */ ShutDownWebcit(); exit(WC_EXIT_SSL); } else { int a; for (a = 0; a < CRYPTO_num_locks(); a++) { SSLCritters[a] = malloc(sizeof(pthread_mutex_t)); if (!SSLCritters[a]) { syslog(LOG_EMERG, "citserver: can't allocate memory!!\n"); /** Nothing's been initialized, just die */ ShutDownWebcit(); exit(WC_EXIT_SSL); } pthread_mutex_init(SSLCritters[a], NULL); } } /* * Initialize SSL transport layer */ SSL_library_init(); SSL_load_error_strings(); ssl_method = SSLv23_server_method(); if (!(ssl_ctx = SSL_CTX_new(ssl_method))) { syslog(LOG_WARNING, "SSL_CTX_new failed: %s\n", ERR_reason_error_string(ERR_get_error())); return; } syslog(LOG_INFO, "Requesting cipher list: %s\n", ssl_cipher_list); if (!(SSL_CTX_set_cipher_list(ssl_ctx, ssl_cipher_list))) { syslog(LOG_WARNING, "SSL_CTX_set_cipher_list failed: %s\n", ERR_reason_error_string(ERR_get_error())); return; } CRYPTO_set_locking_callback(ssl_lock); CRYPTO_set_id_callback(id_callback); /* * Get our certificates in order. (FIXME: dirify. this is a setup job.) * First, create the key/cert directory if it's not there already... */ mkdir(CTDL_CRYPTO_DIR, 0700); /* * Before attempting to generate keys/certificates, first try * link to them from the Citadel server if it's on the same host. * We ignore any error return because it either meant that there * was nothing in Citadel to link from (in which case we just * generate new files) or the target files already exist (which * is not fatal either). */ if (!strcasecmp(ctdlhost, "uds")) { sprintf(buf, "%s/keys/citadel.key", ctdlport); rv = symlink(buf, CTDL_KEY_PATH); if (!rv) syslog(LOG_DEBUG, "%s\n", strerror(errno)); sprintf(buf, "%s/keys/citadel.csr", ctdlport); rv = symlink(buf, CTDL_CSR_PATH); if (!rv) syslog(LOG_DEBUG, "%s\n", strerror(errno)); sprintf(buf, "%s/keys/citadel.cer", ctdlport); rv = symlink(buf, CTDL_CER_PATH); if (!rv) syslog(LOG_DEBUG, "%s\n", strerror(errno)); } /* * If we still don't have a private key, generate one. */ if (access(CTDL_KEY_PATH, R_OK) != 0) { syslog(LOG_INFO, "Generating RSA key pair.\n"); rsa = RSA_generate_key(1024, /* modulus size */ 65537, /* exponent */ NULL, /* no callback */ NULL /* no callback */ ); if (rsa == NULL) { syslog(LOG_WARNING, "Key generation failed: %s\n", ERR_reason_error_string(ERR_get_error())); } if (rsa != NULL) { fp = fopen(CTDL_KEY_PATH, "w"); if (fp != NULL) { chmod(CTDL_KEY_PATH, 0600); if (PEM_write_RSAPrivateKey(fp, /* the file */ rsa, /* the key */ NULL, /* no enc */ NULL, /* no passphr */ 0, /* no passphr */ NULL, /* no callbk */ NULL /* no callbk */ ) != 1) { syslog(LOG_WARNING, "Cannot write key: %s\n", ERR_reason_error_string(ERR_get_error())); unlink(CTDL_KEY_PATH); } fclose(fp); } else { syslog(LOG_WARNING, "Cannot write key: %s\n", CTDL_KEY_PATH); ShutDownWebcit(); exit(0); } RSA_free(rsa); } } /* * If there is no certificate file on disk, we will be generating a self-signed certificate * in the next step. Therefore, if we have neither a CSR nor a certificate, generate * the CSR in this step so that the next step may commence. */ if ( (access(CTDL_CER_PATH, R_OK) != 0) && (access(CTDL_CSR_PATH, R_OK) != 0) ) { syslog(LOG_INFO, "Generating a certificate signing request.\n"); /* * Read our key from the file. No, we don't just keep this * in memory from the above key-generation function, because * there is the possibility that the key was already on disk * and we didn't just generate it now. */ fp = fopen(CTDL_KEY_PATH, "r"); if (fp) { rsa = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL); fclose(fp); } if (rsa) { /** Create a public key from the private key */ if (pk=EVP_PKEY_new(), pk != NULL) { EVP_PKEY_assign_RSA(pk, rsa); if (req = X509_REQ_new(), req != NULL) { const char *env; /* Set the public key */ X509_REQ_set_pubkey(req, pk); X509_REQ_set_version(req, 0L); name = X509_REQ_get_subject_name(req); /* Tell it who we are */ /* * We used to add these fields to the subject, but * now we don't. Someone doing this for real isn't * going to use the webcit-generated CSR anyway. * X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, "US", -1, -1, 0); * X509_NAME_add_entry_by_txt(name, "ST", MBSTRING_ASC, "New York", -1, -1, 0); * X509_NAME_add_entry_by_txt(name, "L", MBSTRING_ASC, "Mount Kisco", -1, -1, 0); */ env = getenv("O"); if (env == NULL) env = "Organization name", X509_NAME_add_entry_by_txt( name, "O", MBSTRING_ASC, (unsigned char*)env, -1, -1, 0 ); env = getenv("OU"); if (env == NULL) env = "Citadel server"; X509_NAME_add_entry_by_txt( name, "OU", MBSTRING_ASC, (unsigned char*)env, -1, -1, 0 ); env = getenv("CN"); if (env == NULL) env = "*"; X509_NAME_add_entry_by_txt( name, "CN", MBSTRING_ASC, (unsigned char*)env, -1, -1, 0 ); X509_REQ_set_subject_name(req, name); /* Sign the CSR */ if (!X509_REQ_sign(req, pk, EVP_md5())) { syslog(LOG_WARNING, "X509_REQ_sign(): error\n"); } else { /* Write it to disk. */ fp = fopen(CTDL_CSR_PATH, "w"); if (fp != NULL) { chmod(CTDL_CSR_PATH, 0600); PEM_write_X509_REQ(fp, req); fclose(fp); } else { syslog(LOG_WARNING, "Cannot write key: %s\n", CTDL_CSR_PATH); ShutDownWebcit(); exit(0); } } X509_REQ_free(req); } } RSA_free(rsa); } else { syslog(LOG_WARNING, "Unable to read private key.\n"); } } /* * Generate a self-signed certificate if we don't have one. */ if (access(CTDL_CER_PATH, R_OK) != 0) { syslog(LOG_INFO, "Generating a self-signed certificate.\n"); /* Same deal as before: always read the key from disk because * it may or may not have just been generated. */ fp = fopen(CTDL_KEY_PATH, "r"); if (fp) { rsa = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL); fclose(fp); } /* This also holds true for the CSR. */ req = NULL; cer = NULL; pk = NULL; if (rsa) { if (pk=EVP_PKEY_new(), pk != NULL) { EVP_PKEY_assign_RSA(pk, rsa); } fp = fopen(CTDL_CSR_PATH, "r"); if (fp) { req = PEM_read_X509_REQ(fp, NULL, NULL, NULL); fclose(fp); } if (req) { if (cer = X509_new(), cer != NULL) { ASN1_INTEGER_set(X509_get_serialNumber(cer), 0); X509_set_issuer_name(cer, req->req_info->subject); X509_set_subject_name(cer, req->req_info->subject); X509_gmtime_adj(X509_get_notBefore(cer), 0); X509_gmtime_adj(X509_get_notAfter(cer),(long)60*60*24*SIGN_DAYS); req_pkey = X509_REQ_get_pubkey(req); X509_set_pubkey(cer, req_pkey); EVP_PKEY_free(req_pkey); /* Sign the cert */ if (!X509_sign(cer, pk, EVP_md5())) { syslog(LOG_WARNING, "X509_sign(): error\n"); } else { /* Write it to disk. */ fp = fopen(CTDL_CER_PATH, "w"); if (fp != NULL) { chmod(CTDL_CER_PATH, 0600); PEM_write_X509(fp, cer); fclose(fp); } else { syslog(LOG_WARNING, "Cannot write key: %s\n", CTDL_CER_PATH); ShutDownWebcit(); exit(0); } } X509_free(cer); } } RSA_free(rsa); } } /* * Now try to bind to the key and certificate. * Note that we use SSL_CTX_use_certificate_chain_file() which allows * the certificate file to contain intermediate certificates. */ SSL_CTX_use_certificate_chain_file(ssl_ctx, CTDL_CER_PATH); SSL_CTX_use_PrivateKey_file(ssl_ctx, CTDL_KEY_PATH, SSL_FILETYPE_PEM); if ( !SSL_CTX_check_private_key(ssl_ctx) ) { syslog(LOG_WARNING, "Cannot install certificate: %s\n", ERR_reason_error_string(ERR_get_error())); } }