/* * Copyright 2006-2008 Ondrej Jirman * * This file is part of libxr. * * Libxr is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 2 of the License, or (at your option) any * later version. * * Libxr 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 Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with libxr. If not, see . */ #include "User.xrs.h" #include "VM.xrs.h" #include "Node.xrs.h" #include "Session.xrs.h" #include "system.xrs.h" #include "zentific-defines.h" #include "zentific-xmlrpc.h" #include "zentific-xr-types.h" #include "zentific-types.h" #include "zentific-common.h" #include "db_sources.h" zentific_config *config = NULL; struct DB *db; static xr_server* server = NULL; int main(int argc, char **argv) { initialize(argc, argv); //FIXME check config //FIXME drop to lower privileges here GError* err = NULL; xr_servlet_def* servlets[8] = { __UserServlet_def(), __NodeServlet_def(), __VMServlet_def(), __systemServlet_def(), __SessionServlet_def(), __ZentificServlet_def(), __SchedulerServlet_def(), NULL }; if (!g_thread_supported()) g_thread_init(NULL); if( config->XML_DEBUG ) { debugLog(DBG_INFO, "XML and HTTP debug traces enabled."); xr_debug_enabled = XR_DEBUG_CALL | XR_DEBUG_HTTP; } GString *bindto = g_string_new(NULL); g_string_append_printf(bindto, "%s:%d", config->LISTEN, config->PORT); //xr_server_simple("server.pem", 5, "*:4444", servlets, &err); //xr_server_simple(NULL, config->THREADS, bindto->str, servlets, &err); if(config->USE_SSL) xr_server_start(config->SSL_CERT, config->THREADS, bindto->str, servlets, &err); else xr_server_start(NULL, config->THREADS, bindto->str, servlets, &err); if (err) g_print("error: %s\n", err->message); cleanup_exit(0); return !!err; } gboolean initialize(int argc, char **argv){ config = malloc(sizeof(zentific_config)*1); //set defaults config->MY_UUID = NULL; config->PID_FILE = new_str("%s/run/zentific/%s", DEFAULT_VAR_PATH, DEFAULT_PID_FILE); config->WEB_ROOT = new_str(ZENTIFIC_WEB_ROOT); config->LISTEN = new_str(DEFAULT_LISTEN); config->CONF_FILE = new_str(DEFAULT_CONF_FILE); config->LOG_FILE = new_str(DEFAULT_LOG_FILE); config->LOG_PREFIX = new_str(DEFAULT_LOG_FILE); //FIXME add many of these to the command line options config->UTILS_PATH = new_str(DEFAULT_UTILS_PATH); config->VAR_PATH = new_str(DEFAULT_VAR_PATH); config->DB_MODULES_PATH = new_str(DEFAULT_DB_MODULES_PATH); config->DB_MODULE = new_str(DEFAULT_DB_MODULE); config->MODULES_PATH = new_str(DEFAULT_MODULES_PATH); config->SSL_PATH = new_str(DEFAULT_SSL_PATH); config->SSL_CERT = new_str(DEFAULT_SSL_CERT); config->KEY_PATH = new_str(DEFAULT_KEY_PATH); config->VM_CONF_PATH = new_str(DEFAULT_VM_CONF_PATH); //FIXME revert to DBG_INFO config->LOG_LEVEL = DBG_DEVEL; config->PORT = DEFAULT_PORT; config->THREADS = DEFAULT_THREADS; config->DAEMONIZE = TRUE; config->USE_SSL = FALSE; config->SELF_HOST = FALSE; config->IS_VM = FALSE; config->XML_DEBUG = FALSE; //initial log setting. can update later if needed debugInit(config->LOG_LEVEL, config->LOG_FILE); // Validate configuration file and make sure that we CAN run here //FIXME if config file can be set on command line, need that to be parsed before this. parse_config(config); int i=0; int opt_ind=1; //opterr=0 means dont print error via getopt if option parameter is missing //TODO: this was commented to satisfy a -Wall compile time warning. revert or not? //it is an extern variable, default is nonzero. check manpage for getopt / getopt_long //int opterr=0; extern int opterr; opterr = 0; //FIXME not all config params are command-line-assignable static struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "pidfile", required_argument, NULL, 'P' }, { "logfile", required_argument, NULL, 'L' }, { "loglevel", required_argument, NULL, 'l' }, { "webroot", required_argument, NULL, 'W' }, { "version", no_argument, NULL, 'V' }, { "verbosity", no_argument, NULL, 'v' }, { "port", required_argument, NULL, 'p' }, { "ssl", required_argument, NULL, 's' }, { "SSLpath", required_argument, NULL, 'a' }, { "selfhost", no_argument, NULL, 'S' }, { "nodaemon", no_argument, NULL, 'n' }, { "threads", required_argument, NULL, 't' }, { "xmldebug", no_argument, NULL, 'X' }, { "uuid", required_argument, NULL, 'U' }, { 0,0,0,0 }, }; //NOTE usage example //sscanf(optarg, "%255[^:]:%255s", username, password); /* Get and process arguments */ while (i != -1) { i = getopt_long(argc, argv, ":hP:L:l:W:U:Vvp:s:nSt:X", long_options, &opt_ind); //i = getopt_long(argc, argv, ":hH:D:P:vp:nXVs:d:A:", long_options, &opt_ind); switch (i) { case 'p': //FIXME: typechecking config->PORT = atoi(optarg); break; case 'S': config->SELF_HOST = TRUE; break; case 'X': config->DAEMONIZE = FALSE; config->XML_DEBUG = TRUE; break; case 't': config->THREADS = atoi(optarg); break; case 'W': strncpy(config->WEB_ROOT, optarg, PATH_MAX); config->WEB_ROOT[PATH_MAX+1] = '\0'; break; case 'n': config->DAEMONIZE = FALSE; break; case 'h': usage(); exit(0); break; case 'V': version(); exit(0); break; case 's': //FIXME strcasecmp instead unless that's not portable // it was in bsd 4.4 though, so should be fine if( strcmp( strlwr(optarg), "yes") == 0 ) { config->USE_SSL = 1; } else if( strcmp( strlwr(optarg), "no") == 0 ) { config->USE_SSL = 0; } else { debugLog(DBG_ERROR, "ERROR: SSL command line parameter '%s' is invalid. Terminating.\n", optarg); usage(); exit(1); } break; case 'v': //add a v for increased verbosity config->LOG_LEVEL++; break; case 'l': //FIXME parse all options if( strcmp(optarg, "DEVEL") ) config->LOG_LEVEL = DBG_DEVEL; break; case 'U': config->MY_UUID = strdup(optarg); break; case 'L': config->LOG_FILE = strdup(optarg); break; case 'P': config->PID_FILE = strdup(optarg); break; case ':': if(!optarg & (i!=-1) ){ debugLog(DBG_ERROR, "ERROR: Option '%c' requires an argument.\n\n",optopt); usage(); exit(1); } break; case '?': usage(); exit(1); break; default: break; } } // Final loading step is to make sure all required values are set // FIXME:function to check options. char *msg = "Initializing.."; debugLog(DBG_INFO, msg); //FIXME config->db = db_init(config->DB_MODULES_PATH, config->DB_MODULE); if(!config->db){ debugLog(DBG_ERROR, "Database object failure, cannot continue."); cleanup_exit(1); } db = config->db; // After we're sure we can run, detach from the TTY int daemonized = 0; if ( config->DAEMONIZE == TRUE ){ debugLog(DBG_INFO, "Attempting to daemonize."); daemonized = daemonize(1); } else { debugLog(DBG_INFO, "Running in non-daemonized mode."); } if(daemonized == -1){ debugLog(DBG_ERROR, "ERROR: Could not daemonize. "); cleanup_exit(1); } //We're may not be heading into the bg, but create the PID file regardless // need to do pidfile AFTER daemonizing to get right pid, but before closing descriptors (which would hide errors) int madepid = 0; madepid = make_pidfile(config->PID_FILE); if(madepid == -2){ debugLog(DBG_ERROR, "ERROR: Could not write pid file. Daemon already running or previously exited uncleanly."); exit(1); } else if (madepid == -1){ debugLog(DBG_ERROR, "ERROR: Could not write pid file. Check permissions and filesystem status."); cleanup_exit(1); } //Verify running as root; if not, exit if(geteuid() != 0){ debugLog(DBG_INFO, "INFO: Root privileges are required to accurately detect whether the server is running inside a VM."); //TODO move any privileged auth actions into the scheduler?? debugLog(DBG_INFO, "INFO: Root privileges are required for PAM authentication for any authentication module requiring access to protected resources (e.g. /etc/shadow)."); } else { // Detect whether we're running in a VM debugLog(DBG_INFO, "Detecting virtualization platform."); //requires root privs config->IS_VM = running_as_vm(); if( config->IS_VM ) { //requires root privs config->MY_UUID = (char*)get_my_uuid(); debugLog(DBG_INFO, "Virtualization detected. Running in a vm, uuid=%s\n", config->MY_UUID); //TODO //save the uuid of this xmlrpc-server's vm into the db //FIXME and/or NOTE // if using multiple frontend interface servers, // it is not sufficient for each server process to be aware // of its own VM uuid; it must be aware of the others also // thus, insert this uuid into the db or set a flag. } } int detached = 0; if ( config->DAEMONIZE ) detached = detach_daemon(); if(detached == -1 ){ debugLog(DBG_ERROR, "ERROR: Could not detach daemonize; daemon already running or previously exited uncleanly. Check pidfile."); cleanup_exit(-1); } if ( !config->DAEMONIZE ) { display_config(); } struct sigaction act; act.sa_handler = cleanup_exit; act.sa_flags = SA_RESTART; sigemptyset(&act.sa_mask); if (sigaction(SIGINT, &act, NULL) < 0 // || sigaction(SIGHUP, &act, NULL) < 0 || sigaction(SIGTERM, &act, NULL) < 0){ debugLog(DBG_ERROR, "ERROR: could not establish signal handlers.\n"); } // Handle a SIGHUP by reloading the configuration file // If this fails, then the process will simply ignore the SIGHUPs if ( signal(SIGHUP, config_reload) == SIG_IGN ) { signal(SIGHUP, SIG_IGN); //fixme: is this true? debugLog(DBG_ERROR, "Warning: Signal handling for SIGHUP not initialized in debug mode.\n"); } // Handle a SIGINT or SIGTERM by cleaning up the PID file and exitting //if ( signal(SIGINT, cleanup_exit) == SIG_IGN || signal(SIGTERM, cleanup_exit) == SIG_IGN ) { /* if ( signal(SIGINT, cleanup_exit) == SIG_IGN ) { signal(SIGINT, SIG_DFL); debugLog(DBG_ERROR, "Warning: Signal handling for SIGINT not initialized in debug mode.\n"); }*/ debugLog(DBG_INFO, "Initialization complete."); return FALSE; } gboolean parse_config(zentific_config *config){ /* load the config file */ FILE *fp = fopen(config->CONF_FILE, "r"); /* error checking */ if ( fp == NULL ) { debugLog(DBG_ERROR, "Unable to load config file '%s.'\n", config->CONF_FILE); return FALSE; } char line[1024]; int line_count = 0; while ( fgets(line, 1024, fp) != NULL ) { // Keep track of line numbers for error reporting line_count++; // Remove all leading whitespace from the line char *ptr = line; // this is guaranteed to end at some point because fgets guarantees the last char is '\0' while ( ptr[0] == ' ' || ptr[0] == '\t' ) ptr++; // Ignore lines beginning with a #, as they are comments if ( ptr[0] != '#' && ptr[0] != '\n' && ptr[0] != '\r' && ptr[0] != '\0' ) { int c; // Make sure that there is an actual variable assignment in the line if ( strchr(line, '=') == NULL ) { fprintf(stderr, "ERROR: mal-formed line in config file on line [%d].\n", line_count); exit(1); } // Separate the assignment around the first equals sign char *name = ptr; char *value = ptr; for ( c = 0; c < strlen(ptr); c++ ) { if ( ptr[c] == '=' ) { ptr[c] = '\0'; /*old if ( ptr[c+1] == '\"' || ptr[c+1] == '\'' ){ int chunklen = (strrchr(&ptr[c+2], '\"') -&ptr[c+2]) / sizeof(char); char *out = (char *)calloc(sizeof(char)*(chunklen+1), 0); strncpy(out, &ptr[c+2], chunklen); value = out; } else { int i; value = &ptr[c+1]; for ( i = 0; i < strlen(value); i++ ) if ( value[i] == '\n' || value[i] == '\r' ) value[i] = '\0'; debugLog(DBG_INFO, "Value: '%s'", value); }*/ char *valstart = NULL; char *valend = NULL; if( ( (valstart = strchr(&ptr[c+1], '"')) || (valstart = strchr(&ptr[c+1], '\'')) ) && ( (valend = strrchr(&ptr[c+1], '"')) || (valend = strrchr(&ptr[c+1], '\''))) ) { //check for comments between valstart and valend. if exist, alter the end ptr char *comment = strchr(valstart, '#'); if(comment){ //if comment found before closing quote if(valend-comment>0){ valend = strrchr(comment, '"'); if(!valend) valend = strrchr(comment, '\''); } //FIXME what if readjusted valend=valstart? } //provide an end to the value with a null // (this replaces the quote) valend[0] = '\0'; //advance valstart past quote valstart++; value = valstart; } else { value = &ptr[c+1]; } } else if ( ptr[c] == '\r' || ptr[c] == '\n' ) { ptr[c] = '\0'; } } //booleans if ( strcmp(name, "SELF_HOST") == 0 ) { if(strcasecmp(value, "yes") == 0) config->SELF_HOST = TRUE; else if(strcasecmp(value, "no") == 0) config->SELF_HOST = FALSE; } else if ( strcmp(name, "DAEMONIZE") == 0 ) { if(strcasecmp(value, "yes") == 0) config->DAEMONIZE = TRUE; else if(strcasecmp(value, "no") == 0) config->DAEMONIZE = FALSE; } else if ( strcmp(name, "USE_SSL") == 0 ) { if(strcasecmp(value, "yes") == 0) config->USE_SSL = TRUE; else if(strcasecmp(value, "no") == 0) config->USE_SSL = FALSE; } else if ( strcmp(name, "XML_DEBUG") == 0 ) { if(strcasecmp(value, "yes") == 0) config->XML_DEBUG = TRUE; else if(strcasecmp(value, "no") == 0) config->XML_DEBUG = FALSE; } //numeric tweaks else if ( strcmp(name, "PORT") == 0 ) { int port = 0; if(sscanf(value, "%d", &port) == 1) config->PORT = port; } else if ( strcmp(name, "THREADS") == 0 ) { int threads = 0; if(sscanf(value, "%d", &threads) == 1) config->THREADS = threads; } else if ( strcmp(name, "LOG_LEVEL") == 0 ) { int loglvl = 0; if(sscanf(value, "%d", &loglvl) == 1) config->LOG_LEVEL = loglvl; } //path data, strings, etc else if ( strcmp(name, "UTILS_PATH") == 0 ) { config->UTILS_PATH = strdup(value); } else if ( strcmp(name, "VM_CONF_PATH") == 0 ) { config->VM_CONF_PATH = strdup(value); } else if ( strcmp(name, "VAR_PATH") == 0 ) { config->VAR_PATH = strdup(value); } else if ( strcmp(name, "MODULES_PATH") == 0 ) { config->MODULES_PATH = strdup(value); } else if ( strcmp(name, "DB_MODULES_PATH") == 0 ) { config->DB_MODULES_PATH = strdup(value); } else if ( strcmp(name, "DB_MODULE") == 0 ) { config->DB_MODULE = strdup(value); } else if ( strcmp(name, "LISTEN") == 0 ) { config->LISTEN = strdup(value); } else if ( strcmp(name, "KEY_PATH") == 0 ) { config->KEY_PATH = strdup(value); } else if ( strcmp(name, "SSL_PATH") == 0 ) { config->SSL_PATH = strdup(value); } else if ( strcmp(name, "WEB_ROOT") == 0 ) { config->WEB_ROOT = strdup(value); } else if ( strcmp(name, "PID_FILE") == 0 ) { config->PID_FILE = strdup(value); } else if ( strcmp(name, "LOG_FILE") == 0 ) { config->LOG_FILE = strdup(value); } else if ( strcmp(name, "LOG_TYPE") == 0 ) { config->LOG_TYPE = strdup(value); } else if ( strcmp(name, "LOG_PREFIX") == 0 ) { config->LOG_PREFIX = strdup(value); } else if ( strcmp(name, "MY_UUID") == 0 ) { if(!is_uuid(value)){ fprintf(stderr, "ERROR: mal-formed line in config file on line [%d].\n", line_count); exit(1); } else { config->MY_UUID = strdup(value); } } else { debugLog(DBG_ERROR, "Unknown variable: %s, Value: %s\n", name, value); } } } fclose(fp); return TRUE; } static void usage(){ printf("\n\nZentific-xmlrpc, version %s \n" "----------------------------------------------------------------------\n" "Usage: zentific-xmlrpc [OPTION]\n\n" "-h(elp) : This message\n" "-H(ost) : Send node updates to host\n" "-P(idfile) : Record PID in specified filename\n" "-W(ebroot) : Web root used in self-serving mode\n" "-L(ogfile) : Destination path for log entries\n" "-l(oglevel) : Record log entries of level or higher\n" "-s(sl) : Use SSL {yes|no}\n" "-SSLp(a)th : Use SSL {yes|no}\n" "-U(uid) : UUID for the vm in which Zentific-xmlrpc runs\n" "-t(hreads) : Use X number of threads. Default %d\n" "-p(ort) : Bind to port X. Default %d\n" "-V(ersion) : Info for this version, bugs, contact, etc.\n" "-v(erbosity) : Level of debugging output.\n" "-n(odaemon) : Do not daemonize.\n" "-X(MLdebug) : Display xmlrpc/http traces; implies -n.\n" "\n", ZENTIFIC_XMLRPC_VER, DEFAULT_THREADS, DEFAULT_PORT ); printf("----------------------------------------------------------------------\n"); } static void version(void) { printf("Zentific XML-RPC server - version %s - changeset %s\n" "\n%s\n\n""%s\n\n""%s\n\n", ZENTIFIC_XMLRPC_VER, ZENTIFIC_CHANGESET, ZENTIFIC_COPYRIGHT, ZENTIFIC_DISCLAIMER, ZENTIFIC_CONTACT); } //Display configuration data void display_config() { //FIXME fprintf(stdout, "Displaying config data:\n\nFIXME\n\n"); } // This action assumes that sending a SIGHUP will override all config options // with the values inside of the config file (if existing) void config_reload(int signum) { //FIXME } // Called on a SIGINT so we can clean up before exitting void cleanup_exit(int signum) { xr_server_stop(server); debugLog(DBG_INFO,"Cleaning up before exiting."); db_uninit(config->db); if(signum == 1){ debugLog(DBG_ERROR, "Exiting due to errors."); } else { debugLog(DBG_INFO,"Exiting cleanly."); } //signum == -1 when pidfile exists and we're exiting due to // already-running daemon or previous unclean shutdown pending investigation if(signum != -1){ debugLog(DBG_INFO,"Removing pidfile."); unlink(config->PID_FILE); } debugLog(DBG_INFO, "Closing xr session."); xr_fini(); // Since we overrode the default SIGINT action, we have to force the exit on // our own now. exit(signum); } gboolean xr_server_start(const char* cert, int threads, const char* bind, xr_servlet_def** servlets, GError** err) { if (!g_thread_supported()) g_thread_init(NULL); g_return_val_if_fail(server == NULL, FALSE); g_return_val_if_fail(threads > 0, FALSE); g_return_val_if_fail(bind != NULL, FALSE); g_return_val_if_fail(servlets != NULL, FALSE); g_return_val_if_fail(err == NULL || *err == NULL, FALSE); /* handle in init struct sigaction act; act.sa_handler = _sh; act.sa_flags = SA_RESTART; sigemptyset(&act.sa_mask); if (sigaction(SIGINT, &act, NULL) < 0 || sigaction(SIGHUP, &act, NULL) < 0 || sigaction(SIGTERM, &act, NULL) < 0) return FALSE; */ server = xr_server_new(cert, threads, err); if (server == NULL) return FALSE; if (!xr_server_bind(server, bind, err)){ xr_server_free(server); return FALSE; } if (servlets){ while (*servlets){ xr_server_register_servlet(server, *servlets); servlets++; } } if (!xr_server_run(server, err)){ xr_server_free(server); return FALSE; } xr_server_free(server); return TRUE; }