/* * kissdx - KiSS PC-Link Daemon eXtended (based on kissd) * * Copyright (C) * Portions Copyright (C) 2006 Vidar Tysse * Portions Copyright (C) 2007 Olivier Kahn * * Heavily based on kiss4lin, * Copyright (C) 2004 Jacob Kolding * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include #include #include #include #include #include #include #include #include #include #include "cmdserver.h" #include "cmdclient.h" #include "kissdx.h" #include "config.h" #include "utils.h" // ======================================================== // Global constants int standard_filemode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; // User.RW + Grp.R + Other.R int standard_dir_filemode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; // User.RWX + Grp.RX + Other.RX // Prefix for socket name in /tmp : suffixe is generated by XXXXXX (mkstemp() fct) const char *commandserver_filename_prefix = "kissdx-loccmdskt-"; // kissdx Local Command Socket const char *ADMCMD_SET = "ADMCMD_SET"; // Client request : ADMCMD_SET|VERBOSE_LOG|WARNING\n const char *ADMCMD_GET = "ADMCMD_GET"; // Client request : ADMCMD_GET|VERBOSE_LOG|\n const char *ADMCMD_ACTION = "ADMCMD_ACTION"; // Client request : ADMCMD_ACTION|STOP|\n const char *ADMCMD_ACK = "ADMCMD_ACK"; // Server response: ADMCMD_ACK|STOP|OK\n const char *ADMCMD_SUCC = "OK"; const char *ADMCMD_FAIL = "ERR"; const char *ADMCMD_EOL = "EOL||"; const char *ADMCMD_STOP = "STOP"; const char *ADMCMD_FORCESTOP = "FORCESTOP"; const char *ADMCMD_RELOADCONFIG = "RELOAD_CONFIG"; const char *ADMCMD_GETTXTCONFIG = "GET_TXT_CONFIG"; const char *ADMCMD_GETXMLCONFIG = "GET_XML_CONFIG"; const char *ADMCMD_GETDICT = "GET_DICT"; const char *ADMCMD_GETSTATUS = "STATUS"; const char *ADMCMD_UNKNOWN = "UNKNOWN"; // Unknown admin command // ********************************************** // Dictionary of supported admincommand /* ********************************************** * Standard exchange of admin command is : * ==> ADMCMD_GET|COMMAND|' * <== ADMCMD_ACK|COMMAND|VALUE ' + \n 'EOL||' * Getter/Setter are focused on a specifique parameter * Action are more global scope */ const char *ADMCMD_DESC_RELOADCONFIG = "Trigger a configuration reload from kissdx.conf"; const char *ADMCMD_DESC_FORCESTOP = "Trigger a forced stop of server and all streaming"; const char *ADMCMD_DESC_STOP = "Trigger a server stop and finish current streaming"; const char *ADMCMD_DESC_GETTXTCONFIG = "Retrieve current in-memory configuration in text format"; const char *ADMCMD_DESC_GETDICT = "Retrieve dictionary of supported admin commands"; const char *ADMCMD_DESC_GETSTATUS = "Retrieve current media streaming status"; const char *ADMCMD_DESC_GET_KISSDX_SRV_VERSION = "Retrieve kissdx version"; const char *ADMCMD_DESC_GET_KISSDX_SRV_VERSIONDATE = "Retrieve kissdx version date"; const char *ADMCMD_DESC_GET_LNCH_CUSTOPTION = "Retrieve command line custom option"; const char *ADMCMD_DESC_GET_LNCH_ARGUMENT = "Retrieve command line argument at launch time"; const char *ADMCMD_DESC_GET_LNCH_DATETIME = "Retrieve timestamp at launch time"; const char *ADMCMD_DESC_GET_VERBOSE_LOG_LEVEL = "Retrieve current verbose loglevel"; const char *ADMCMD_DESC_GET_CONFIG_AUTOLOAD = "Retrieve current configuration autoload setting"; const char *ADMCMD_DESC_GET_DISPLAY_SEQ_NUMBER = "Retrieve current setting for prefix sequence numbers in song names"; const char *ADMCMD_DESC_GET_LIST_HIDDEN_ENTRY = "Retrieve current setting for listing of hidden entries"; const char *ADMCMD_DESC_SET_VERBOSE_LOG_LEVEL = "Set a new value for loglevel"; const char *ADMCMD_DESC_SET_CONFIG_AUTOLOAD = "Enable or disable automatic configuration reload on receipt of UDP packet"; const char *ADMCMD_DESC_SET_DISPLAY_SEQ_NUMBER = "Enable or disable prefix sequence numbers in song names"; const char *ADMCMD_DESC_SET_LIST_HIDDEN_ENTRY = "Enable or disable listing of hidden entries"; /* PROTOCOLE = ADMCMD_ACTION =========================================================================== RELOAD_CONFIG <== OK (defined in ADMCMD_RELOADCONFIG:cmdserver.c) FORCESTOP <== OK (defined in ADMCMD_FORCESTOP:cmdserver.c) STOP <== OK (defined in ADMCMD_STOP:cmdserver.c) GET_TXT_CONFIG <== OK (defined in ADMCMD_GETTXTCONFIG:cmdserver.c) tbd GET_XML_CONFIG <== OK (defined in ADMCMD_GETXMLCONFIG:cmdserver.c) GET_DICT <== OK (defined in ADMCMD_GETDICT:cmdserver.c) tbd GET_STATUS <== OK (defined in ADMCMD_GETSTATUS:cmdserver.c) = ADMCMD_GET =============================================================================== KISSDX_SRV_VERSION <== v0.14.0.b2 (defined in CFG_KISSDX_SRV_VERSION:config.c) KISSDX_SRV_VERSIONDATE <== Jan 22 2009 (defined in CFG_KISSDX_SRV_VERSIONDATE:config.c) LAUNCH_COMMANDLINE_CUSTOPTION <== xxxx (defined in CFG_KISSDX_LNCH_CUSTOPTION:config.c) LAUNCH_COMMANDLINE_ARGUMENT <== -d -v -c ./kissdx.conf (defined in CFG_KISSDX_LNCH_ARGUMENT:config.c) LAUNCH_DATETIME <== 2009-02-07 14:54:14 (defined in CFG_KISSDX_LNCH_DATETIME:config.c) VERBOSE_LOG_LEVEL <== Error (defined in CFG_VERBOSE_LOG_LEVEL:config.c) CONFIG_AUTOLOAD <== yes (defined in CFG_CONFIG_AUTOLOAD:config.c) DISPLAY_SEQ_NUMBER <== yes (defined in CFG_DISPLAY_SEQ_NUMBER:config.c) LIST_HIDDEN_ENTRY <== yes (defined in CFG_LIST_HIDDEN_ENTRY:config.c) = ADMCMD_SET =============================================================================== VERBOSE_LOG_LEVEL ==> Error CONFIG_AUTOLOAD ==> yes DISPLAY_SEQ_NUMBER ==> yes LIST_HIDDEN_ENTRY ==> yes */ // ======================================================== static void create_local_socket(int *sock, const char* path, int type) { struct sockaddr_un sun; int yes = 1; /* get a local domain socket */ if ((*sock = socket(PF_LOCAL, type, 0)) < 0) { log("local socket: %s", strerror(errno)); exit(1); } /* lose the pesky "address already in use" error message */ if (setsockopt(*sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) < 0) { log("local setsockopt: %s", strerror(errno)); exit(1); } /* complete the socket structure */ memset(&sun, 0, sizeof(sun)); sun.sun_family = AF_LOCAL; strncpy (sun.sun_path, path, sizeof (sun.sun_path)); /* bind the socket to the path */ if (bind(*sock, (struct sockaddr *) &sun, SUN_LEN (&sun)) < 0) { log("local bind: %s", strerror(errno)); exit(1); } /* everything done for a udp socket */ if (type == SOCK_DGRAM) { return; } /* listen up to 5 incoming connection queue = simultanous TCP call */ if (listen(*sock, 5) < 0) { log("local listen: %s", strerror(errno)); exit(1); } } // ======================================================== // Daemon master receive an action : Options set change / Core action ... void handle_local_command(int sd_child) { int len; int size = LOCAL_COMMAND_SIZE; char buffer[size]; char *token; const char* token_delimiters = " "; if ((len = recv(sd_child, buffer, size, 0)) < 0) { log("handle_local_command.recv: %s", strerror(errno)); return; } if (len == 0) { logv("handle_local_command: EOF on socket %d", sd_child); return; } logv("handle_local_command: Read %d bytes from socket %d", len, sd_child); while (len > 0 && (buffer[len - 1] == '\r' || buffer[len - 1] == '\n')) len--; /* properly end the string and prevent buffer overflows */ buffer[len < size ? len : len - 1] = '\0'; logv("local command received: [%s]", buffer); // Interpret the local command and if valid, act on it if ((token = strtok(buffer, token_delimiters))) { if (!strcasecmp(token, "SET")) { if ((token = strtok(NULL, token_delimiters))) { if (!strcasecmp(token, "LIST_HIDDEN_ENTRIES")) { if ((token = strtok(NULL, token_delimiters))) { if (!strcasecmp(token, "ON")) { config.listhiddenentries = 1; // Save current time to automatically reset config.listhiddenentries after a timeout period. last_time_list_hidden_entries_on = time(NULL); logv0("Config setting listhiddenentries has been turned ON"); } else if (!strcasecmp(token, "OFF")) { config.listhiddenentries = 0; logv0("Config setting listhiddenentries has been turned OFF"); } else log("Unknown SET LIST_HIDDEN_ENTRIES value: %s", token); } else log0("Missing SET LIST_HIDDEN_ENTRIES value"); } else if (!strcasecmp(token, "IMAGE_SCALING")) { if ((token = strtok(NULL, token_delimiters))) { if (!strcasecmp(token, "ON")) { is_image_scaling_enabled = 1; logv0("IMAGE_SCALING has been turned ON"); } else if (!strcasecmp(token, "OFF")) { is_image_scaling_enabled = 0; logv0("IMAGE_SCALING has been turned OFF"); } else log("Unknown SET IMAGE_SCALING value: %s", token); } else log0("Missing SET IMAGE_SCALING value"); } else log("Unknown SET target: %s", token); } else log0("Missing SET target"); } else log("Unknown local command: %s", token); } else log0("Missing local command"); } // ======================================================== void setup_local_commandserver_socket(int * sd_loccmd) { struct dirent **namelist; int num_files, n, fd; char commandserver_filename_prefix_pattern[128]; strncpy(commandserver_filename_prefix_pattern, commandserver_filename_prefix , sizeof commandserver_filename_prefix_pattern); strcat(commandserver_filename_prefix_pattern,"*"); // Get at the temp directory strncpy(commandserver_path, P_tmpdir, PATH_MAX); if (strlen(commandserver_path) > 0 && commandserver_path[strlen(commandserver_path) - 1] != '/') strncat(commandserver_path, "/", PATH_MAX); // Remove temp files remaining from a previous session // Basename - Extension - Dirname - File/Dir mode setup_scandir_selector(commandserver_filename_prefix_pattern, "", commandserver_path, SCANDIR_FILE_MODE); loglevel(LOGDEBUG,"Debug: setup_local_commandserver_socket scandir prepared: pattern:'%s' ext:'%s' dirname:'%s'", scandir_info.scandir_basename_pattern,scandir_info.scandir_file_extensions,scandir_info.scandir_dirname); if ((num_files = scandir(commandserver_path, &namelist, (void*)scandir_selector, (void*)alphasort)) < 0) { loglevel(LOGERROR,"Error: setup_local_commandserver_socket: scandir %s: %s", commandserver_path, strerror(errno)); return; } /* // Remove temp files remaining from a previous session if ((num_files = scandir(commandserver_path, &namelist, scandir_selector, alphasort)) < 0) { log("setup_local_commandserver_socket: scandir %s: %s", commandserver_path, strerror(errno)); } */ else { for (n = 0; n < num_files; n++) { char fullname[PATH_MAX]; strcpy(fullname, commandserver_path); strncat(fullname, namelist[n]->d_name, sizeof(fullname)); loglevel(LOGINFO,"Info: setup_local_commandserver_socket remove oldies: '%s'",fullname); unlink(fullname); free(namelist[n]); } free(namelist); } strncat(commandserver_path, commandserver_filename_prefix, PATH_MAX); strncat(commandserver_path, "XXXXXX", PATH_MAX); if ((fd = mkstemp(commandserver_path)) < 0) { log("Cannot create temporary file for local command socket: %s", strerror(errno)); return; } close(fd); unlink(commandserver_path); create_local_socket(sd_loccmd, commandserver_path, SOCK_STREAM); loglevel(LOGDEBUG,"Debug: setup_local_commandserver_socket created: '%s'",commandserver_path); } // ======================================================== // Admin command : STOP // ==> Send a gracefull SIGTERM to Master daemon (myself) // Child processes will terminate at the end of streaming void handle_admincommand_stop(pid_t daemon_pid, int sd_client) { /* => ADMCMD_ACTION|STOP|\n <= ADMCMD_ACK|STOP|OK\n <= (or) ADMCMD_ACK|STOP|ERR|Error message\n <= EOL||\n <= close connection by the server */ loglevel(LOGDEBUG,"Debug: handle_admin_command STOP: %s","Send back configuration settings "); loglevel(LOGINFO,"Info: Serving administration command '%s'",ADMCMD_STOP); send_admincommand_ack(sd_client,(char *) ADMCMD_STOP,(char *) ADMCMD_SUCC, ""); kill(daemon_pid, SIGTERM); } // ======================================================== // Admin command : FORCESTOP // ==> Send a gracefull SIGTERM to Master daemon process group // Child processes will terminate right now void handle_admincommand_forcestop(int sd_client) { /* => ADMCMD_ACTION|FORCESTOP|\n <= ADMCMD_ACK|FORCESTOP|OK\n <= (or) ADMCMD_ACK|FORCESTOP|ERR|Error message\n <= EOL||\n <= close connection by the server */ loglevel(LOGDEBUG,"Debug: handle_admin_command FORCESTOP: %s","Send back configuration settings "); loglevel(LOGINFO,"Info: Serving administration command '%s'",ADMCMD_FORCESTOP); send_admincommand_ack(sd_client,(char *) ADMCMD_FORCESTOP,(char *) ADMCMD_SUCC, ""); kill(0, SIGTERM); } // ======================================================== // Admin command : RELOAD_CONFIG from kissdx.conf void handle_admincommand_reloadconfig(int sd_client) { /* => ADMCMD_ACTION|RELOAD_CONFIG|\n <= ADMCMD_ACK|RELOAD_CONFIG|OK\n <= (or) ADMCMD_ACK|RELOAD_CONFIG|ERR|Error message\n <= EOL||\n <= close connection by the server */ loglevel(LOGDEBUG,"Debug: handle_admin_command RELOAD_CONFIG: %s","Parse kissdx.conf "); loglevel(LOGINFO,"Info: Serving administration command '%s'",ADMCMD_RELOADCONFIG); config_settings_t new_config; int config_error = parse_config(&new_config, NULL); if (!config_error) { free_config(&config); memcpy(&config, &new_config, sizeof (config)); } else { loglevel(LOGERROR,"Error: Loading new config failed from : %s",new_config.config_file); loglevel(LOGERROR,"Error: Keep previous configuration from : %s",config.config_file); show_current_config_Verbose(&config); // Show the old (still current) configuration in verbose mode } loglevel(LOGSTD,"Service kissdx '%s-%s' is ready for streaming business ...",KISSDX_VERSION,KISSDX_VERSION_DATE); send_admincommand_ack(sd_client,(char *) ADMCMD_RELOADCONFIG,(char *) ADMCMD_SUCC, ""); } // ======================================================== // Admin command : GET_TXT_CONFIG from config_settings_t config; void handle_admincommand_gettxtconfig(int sd_client) { /* => ADMCMD_ACTION|GET_TXT_CONFIG|\n <= ADMCMD_ACK|GET_TXT_CONFIG|OK\n <= (or) ADMCMD_ACK|GET_TXT_CONFIG|ERR|Error message\n <= EOL||\n <= close connection by the server */ loglevel(LOGDEBUG,"Debug: handle_admin_command GET_TXT_CONFIG: %s","Send back configuration settings "); loglevel(LOGINFO,"Info: Serving administration command '%s'",ADMCMD_GETTXTCONFIG); send_txt_config (sd_client, &config); send_admincommand_ack(sd_client,(char *) ADMCMD_GETTXTCONFIG,(char *) ADMCMD_SUCC, ""); } // ======================================================== // Admin command : GET_DICT : The listing of supported vocaburary; void handle_admincommand_getdict(int sd_client) { // ADMCMD PROTOCOL : DICTONNARY // ==> ADMCMD_ACTION|GET_DICT|\n // <== CommandName|ShortDescription // <== CommandName|ShortDescription ... // <== ADMCMD_ACK|GET_DICT|OK + \nEOL|| // <== ADMCMD_ACK|GET_DICT|ERR|Error message\n // <== close connection by the server loglevel(LOGDEBUG,"Debug: handle_admin_command GET_DICT: %s","Send back admcmd dictionnary "); loglevel(LOGINFO,"Info: Serving administration command '%s'",ADMCMD_GETDICT); char tmp_line[512]; const char logmsg[] = " Send to client "; // REMIND KISSDX VERSION ===================================================================================================== snprintf(tmp_line, sizeof tmp_line, "%s|%s|",CFG_KISSDX_SRV_VERSION, KISSDX_VERSION); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug:%s:'%s'",logmsg,tmp_line); snprintf(tmp_line, sizeof tmp_line, "%s|%s|",CFG_KISSDX_SRV_VERSIONDATE, KISSDX_VERSION_DATE); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug:%s:'%s'",logmsg,tmp_line); // ADMIN ACTION =============================================================================================================== snprintf(tmp_line, sizeof tmp_line, "%s|%s|%s|",ADMCMD_ACTION, ADMCMD_RELOADCONFIG, ADMCMD_DESC_RELOADCONFIG); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug:%s:'%s'",logmsg,tmp_line); snprintf(tmp_line, sizeof tmp_line, "%s|%s|%s|",ADMCMD_ACTION, ADMCMD_FORCESTOP, ADMCMD_DESC_FORCESTOP); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug:%s:'%s'",logmsg,tmp_line); snprintf(tmp_line, sizeof tmp_line, "%s|%s|%s|",ADMCMD_ACTION, ADMCMD_STOP, ADMCMD_DESC_STOP); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug:%s:'%s'",logmsg,tmp_line); snprintf(tmp_line, sizeof tmp_line, "%s|%s|%s|",ADMCMD_ACTION, ADMCMD_GETTXTCONFIG, ADMCMD_DESC_GETTXTCONFIG); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug:%s:'%s'",logmsg,tmp_line); snprintf(tmp_line, sizeof tmp_line, "%s|%s|%s|",ADMCMD_ACTION, ADMCMD_GETDICT, ADMCMD_DESC_GETDICT); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug:%s:'%s'",logmsg,tmp_line); snprintf(tmp_line, sizeof tmp_line, "%s|%s|%s|",ADMCMD_ACTION, ADMCMD_GETSTATUS, ADMCMD_DESC_GETSTATUS); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug:%s:'%s'",logmsg,tmp_line); // ADMIN GET =============================================================================================================== snprintf(tmp_line, sizeof tmp_line, "%s|%s|%s|",ADMCMD_GET, CFG_KISSDX_SRV_VERSION, ADMCMD_DESC_GET_KISSDX_SRV_VERSION); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug:%s:'%s'",logmsg,tmp_line); snprintf(tmp_line, sizeof tmp_line, "%s|%s|%s|",ADMCMD_GET, CFG_KISSDX_SRV_VERSIONDATE, ADMCMD_DESC_GET_KISSDX_SRV_VERSIONDATE); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug:%s:'%s'",logmsg,tmp_line); snprintf(tmp_line, sizeof tmp_line, "%s|%s|%s|",ADMCMD_GET, CFG_KISSDX_LNCH_ARGUMENT, ADMCMD_DESC_GET_LNCH_ARGUMENT); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug:%s:'%s'",logmsg,tmp_line); snprintf(tmp_line, sizeof tmp_line, "%s|%s|%s|",ADMCMD_GET, CFG_KISSDX_LNCH_DATETIME, ADMCMD_DESC_GET_LNCH_DATETIME); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug:%s:'%s'",logmsg,tmp_line); snprintf(tmp_line, sizeof tmp_line, "%s|%s|%s|",ADMCMD_GET, CFG_KISSDX_LNCH_CUSTOPTION, ADMCMD_DESC_GET_LNCH_CUSTOPTION); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug:%s:'%s'",logmsg,tmp_line); // -------------------------------------------------------------------------------------------------- snprintf(tmp_line, sizeof tmp_line, "%s|%s|%s|",ADMCMD_GET, CFG_VERBOSE_LOG_LEVEL, ADMCMD_DESC_GET_VERBOSE_LOG_LEVEL); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug:%s:'%s'",logmsg,tmp_line); snprintf(tmp_line, sizeof tmp_line, "%s|%s|%s|",ADMCMD_GET, CFG_CONFIG_AUTOLOAD, ADMCMD_DESC_GET_CONFIG_AUTOLOAD); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug:%s:'%s'",logmsg,tmp_line); snprintf(tmp_line, sizeof tmp_line, "%s|%s|%s|",ADMCMD_GET, CFG_DISPLAY_SEQ_NUMBER, ADMCMD_DESC_GET_DISPLAY_SEQ_NUMBER); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug:%s:'%s'",logmsg,tmp_line); snprintf(tmp_line, sizeof tmp_line, "%s|%s|%s|",ADMCMD_GET, CFG_LIST_HIDDEN_ENTRY, ADMCMD_DESC_GET_LIST_HIDDEN_ENTRY); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug:%s:'%s'",logmsg,tmp_line); // ADMIN SET =============================================================================================================== snprintf(tmp_line, sizeof tmp_line, "%s|%s|%s|",ADMCMD_SET, CFG_VERBOSE_LOG_LEVEL, ADMCMD_DESC_SET_VERBOSE_LOG_LEVEL); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug:%s:'%s'",logmsg,tmp_line); snprintf(tmp_line, sizeof tmp_line, "%s|%s|%s|",ADMCMD_SET, CFG_CONFIG_AUTOLOAD, ADMCMD_DESC_SET_CONFIG_AUTOLOAD); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug:%s:'%s'",logmsg,tmp_line); snprintf(tmp_line, sizeof tmp_line, "%s|%s|%s|",ADMCMD_SET, CFG_DISPLAY_SEQ_NUMBER, ADMCMD_DESC_SET_DISPLAY_SEQ_NUMBER); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug:%s:'%s'",logmsg,tmp_line); snprintf(tmp_line, sizeof tmp_line, "%s|%s|%s|",ADMCMD_SET, CFG_LIST_HIDDEN_ENTRY, ADMCMD_DESC_SET_LIST_HIDDEN_ENTRY); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug:%s:'%s'",logmsg,tmp_line); send_admincommand_ack(sd_client,(char *) ADMCMD_GETDICT,(char *) ADMCMD_SUCC, ""); } // ======================================================== void send_admincommand_ack (int sd_client, char *admcmd, char *ack_type, char *msg) { // ack_type should be in [ADMCMD_SUCC,ADMCMD_SUCC] // do not add an ending \n to sendline: it's done by function send_line() char tmp_line[512]; snprintf(tmp_line, sizeof tmp_line, "%s|%s|%s %s",ADMCMD_ACK, admcmd, ack_type, msg); send_line(sd_client, tmp_line ); loglevel(LOGDEBUG,"Debug: Send to client :'%s'",tmp_line); snprintf(tmp_line, sizeof tmp_line, "%s",ADMCMD_EOL); send_line(sd_client, tmp_line ); loglevel(LOGDEBUG,"Debug: Send to client :'%s'",tmp_line); } // ======================================================== enum admin_request_t parse_admcmd_request(int sd_admcmd, char *opt_admcmd, int opt_admcmd_size) { // Parse admin command received in TCP and launch corresponding handler // - sd_admcmd is the TCP socket to read and/or send back acknowledge // - opt_admcmd store back, the admin command line received over TCP is stored in options.admincommand // support maximum 3 parameters separeted by space : xxx yyy zzz int len; // lenght of command line received int size = LOCAL_COMMAND_SIZE; // maximum line size to be received (512) char buffer[size]; // command line read from socket char *token01,*token02,*token03 ; // command line parsed " " const char* token_delimiters = "|"; // command line parsing delimiter = " " char msg[128]; // read command line from socket LOCAL_COMMAND_SIZE if ((len = recv(sd_admcmd, buffer, size, 0)) < 0) { loglevel(LOGERROR,"Error: Parse admcmd request recv: %s", strerror(errno)); return adm_err; } if (len == 0) { loglevel(LOGERROR,"Error: Parse admcmd request EOF on socket %d", sd_admcmd); return adm_err; } loglevel(LOGINFO,"Info: Parse admcmd request: Read %d bytes from socket %d", len, sd_admcmd); // remove ending \r & \n while (len > 0 && (buffer[len - 1] == '\r' || buffer[len - 1] == '\n')) len--; /* properly end the string and prevent buffer overflows */ buffer[len < size ? len : len - 1] = '\0'; loglevel(LOGINFO,"Info: Parse admcmd request: received: [%s]", buffer); // Save command line argument (admin command) to options.admincommand[512] memset(opt_admcmd, 0, opt_admcmd_size); strncpy(opt_admcmd, buffer, opt_admcmd_size); token01 = strtok(buffer, token_delimiters) ; token02 = strtok(NULL, token_delimiters) ; token03 = strtok(NULL, token_delimiters) ; loglevel(LOGINFO,"Info: Parse admcmd request: parsed: [%s][%s][%s]", token01,token02,token03); // DEBUG // Internal test : SET VERBOSE LEVEL 1 || //loglevel(LOGDEBUG,"Debug: cmdserver.c token01 [%s]",token01); //loglevel(LOGDEBUG,"Debug: cmdserver.c token02 [%s]",token02); //loglevel(LOGDEBUG,"Debug: cmdserver.c token03 [%s]",token03); /*if (token01) { logv0("if (token01)= true "); } if (token02) { logv0("if (token02)= true "); } if (token03) { logv0("if (token03)= true "); } */ // ============= XXX YYY ZZZ situation ====================================== if (token01 && token02 && token03) { // ======================================================================================= // == SET CONFIG OPTIONS section // ======================================================================================= // SET LIST_HIDDEN_ENTRIES NO if ( (!strcasecmp(token01, ADMCMD_SET)) && (!strcasecmp(token02, CFG_LIST_HIDDEN_ENTRY)) && (!strcasecmp(token03, "NO")) ) { // CONFIG change request (So simple that no need of handle_cfg_listhiddenentry(..) loglevel(LOGDEBUG,"Debug: Admin command detected: %s", ADMCMD_SET); // -- loglevel(LOGDEBUG,"Debug: handle_admin_command %s = %s",CFG_LIST_HIDDEN_ENTRY,"NO"); loglevel(LOGINFO, "Info: Serving administration command '%s %s'",ADMCMD_GET, CFG_LIST_HIDDEN_ENTRY); config.listhiddenentries = 0; send_admincommand_ack(sd_admcmd,(char *)CFG_LIST_HIDDEN_ENTRY ,(char *)ADMCMD_SUCC, "NO"); return adm_set; } // SET LIST_HIDDEN_ENTRIES YES if ( (!strcasecmp(token01, ADMCMD_SET)) && (!strcasecmp(token02, CFG_LIST_HIDDEN_ENTRY)) && (!strcasecmp(token03, "YES")) ) { // CONFIG change request (So simple that no need of handle_cfg_listhiddenentry(..) loglevel(LOGDEBUG,"Debug: Admin command detected: %s", ADMCMD_SET); // -- loglevel(LOGDEBUG,"Debug: handle_admin_command %s = %s",CFG_LIST_HIDDEN_ENTRY,"YES"); loglevel(LOGINFO, "Info: Serving administration command '%s %s'",ADMCMD_SET, CFG_LIST_HIDDEN_ENTRY); last_time_list_hidden_entries_on = time(NULL); config.listhiddenentries = 1; send_admincommand_ack(sd_admcmd,(char *)CFG_LIST_HIDDEN_ENTRY ,(char *)ADMCMD_SUCC, "YES"); return adm_set; } // SET CONFIG_AUTOLOAD NO if ( (!strcasecmp(token01, ADMCMD_SET)) && (!strcasecmp(token02, CFG_CONFIG_AUTOLOAD)) && (!strcasecmp(token03, "NO")) ) { // CONFIG change request (So simple that no need of handle_cfg_listhiddenentry(..) loglevel(LOGDEBUG,"Debug: Admin command detected: %s", ADMCMD_SET); // -- loglevel(LOGDEBUG,"Debug: handle_admin_command %s = %s",CFG_CONFIG_AUTOLOAD,"NO"); loglevel(LOGINFO, "Info: Serving administration command '%s %s'",ADMCMD_SET, CFG_CONFIG_AUTOLOAD); config.configautoload = 0; send_admincommand_ack(sd_admcmd,(char *)CFG_CONFIG_AUTOLOAD ,(char *)ADMCMD_SUCC, "NO"); return adm_set; }// SET CONFIG_AUTOLOAD YES if ( (!strcasecmp(token01, ADMCMD_SET)) && (!strcasecmp(token02, CFG_CONFIG_AUTOLOAD)) && (!strcasecmp(token03, "YES")) ) { // CONFIG change request (So simple that no need of handle_cfg_listhiddenentry(..) loglevel(LOGDEBUG,"Debug: Admin command detected: %s", ADMCMD_SET); // -- loglevel(LOGDEBUG,"Debug: handle_admin_command %s = %s",CFG_CONFIG_AUTOLOAD,"YES"); loglevel(LOGINFO, "Info: Serving administration command '%s %s'",ADMCMD_SET, CFG_CONFIG_AUTOLOAD); config.configautoload = 1; send_admincommand_ack(sd_admcmd,(char *)CFG_CONFIG_AUTOLOAD ,(char *)ADMCMD_SUCC, "YES"); return adm_set; } // SET DISPLAY_SEQ_NUMBER NO if ( (!strcasecmp(token01, ADMCMD_SET)) && (!strcasecmp(token02, CFG_DISPLAY_SEQ_NUMBER)) && (!strcasecmp(token03, "NO")) ) { // CONFIG change request (So simple that no need of handle_cfg_listhiddenentry(..) loglevel(LOGDEBUG,"Debug: Admin command detected: %s", ADMCMD_SET); // -- loglevel(LOGDEBUG,"Debug: handle_admin_command %s = %s",CFG_DISPLAY_SEQ_NUMBER,"NO"); loglevel(LOGINFO, "Info: Serving administration command '%s %s'",ADMCMD_SET, CFG_DISPLAY_SEQ_NUMBER); config.displaysequencenumbers = 0; send_admincommand_ack(sd_admcmd,(char *)CFG_CONFIG_AUTOLOAD ,(char *)ADMCMD_SUCC, "NO"); return adm_set; } // SET DISPLAY_SEQ_NUMBER YES if ( (!strcasecmp(token01, ADMCMD_SET)) && (!strcasecmp(token02, CFG_DISPLAY_SEQ_NUMBER)) && (!strcasecmp(token03, "YES")) ) { // CONFIG change request (So simple that no need of handle_cfg_listhiddenentry(..) loglevel(LOGDEBUG,"Debug: Admin command detected: %s", ADMCMD_SET); // -- loglevel(LOGDEBUG,"Debug: handle_admin_command %s = %s",CFG_DISPLAY_SEQ_NUMBER,"YES"); loglevel(LOGINFO, "Info: Serving administration command '%s %s'",ADMCMD_SET, CFG_DISPLAY_SEQ_NUMBER); config.displaysequencenumbers = 1; send_admincommand_ack(sd_admcmd,(char *)CFG_DISPLAY_SEQ_NUMBER ,(char *)ADMCMD_SUCC, "YES"); return adm_set; } // SET VERBOSELEVEL n if ( (!strcasecmp(token01, ADMCMD_SET)) && (!strcasecmp(token02, CFG_VERBOSE_LOG_LEVEL)) ) { short flag_ack_ok = 0; // flag set to 1 if loglevel supplied value is correct (then ACK_SUCC to send) if (atoi(token03) == 0) { // VerboseLogLevel is ascii short arglen = strlen(token03); if (!strcasecmp(token03, "0")) {config.loglevel = LOGERROR; flag_ack_ok = 1;} else if (!strncasecmp(token03, "error",arglen)) {config.loglevel = LOGERROR; flag_ack_ok = 1;} else if (!strncasecmp(token03, "warning",arglen)) {config.loglevel = LOGWARN; flag_ack_ok = 1;} else if (!strncasecmp(token03, "protocol",arglen)) {config.loglevel = LOGPROTO; flag_ack_ok = 1;} else if (!strncasecmp(token03, "information",arglen)) {config.loglevel = LOGINFO; flag_ack_ok = 1;} else if (!strncasecmp(token03, "debug",arglen)) {config.loglevel = LOGDEBUG; flag_ack_ok = 1;} else { // unknown ascii value or zero value //loglevel(LOGERROR,"Error: Admin command request %s rejected: '%s'. Refer help screen.",CFG_VERBOSE_LOG_LEVEL,token03); } } else if ( atoi(token03)<=LOGSTD || atoi(token03)>LOGDEBUG) { // unknown value supplied, then no change is done to loglevel option. //loglevel(LOGERROR,"Error: verbose log level expected [1..5|error..debug]; read '%s'", token03); } else if (atoi(token03)>LOGSTD && atoi(token03)<=LOGDEBUG) { config.loglevel = atoi(token03); flag_ack_ok = 1; } // Response to client admin command if (flag_ack_ok) { char loglevelname[32]; snprintf(loglevelname, sizeof loglevelname, "%s", config.loglevel == LOGERROR? LOGNAME_ERROR : config.loglevel == LOGWARN? LOGNAME_WARN : config.loglevel == LOGPROTO? LOGNAME_PROTO : config.loglevel == LOGINFO? LOGNAME_INFO : config.loglevel == LOGDEBUG? LOGNAME_DEBUG : "**unknown**"); loglevel(LOGDEBUG,"Debug: handle_admin_command %s = %d:%s",CFG_VERBOSE_LOG_LEVEL,config.loglevel,loglevelname); loglevel(LOGSTD, "Info: Serving administration command '%s %s:%s'",ADMCMD_SET,CFG_VERBOSE_LOG_LEVEL,loglevelname); send_admincommand_ack(sd_admcmd,(char *)CFG_VERBOSE_LOG_LEVEL ,(char *)ADMCMD_SUCC, loglevelname); return adm_set; } else { // adm request rejected snprintf(msg, sizeof msg, "Error: verbose log level expected [1..5|error..debug]; read '%s'", token03); loglevel(LOGDEBUG,"Debug: handle_admin_command %s = %s",CFG_VERBOSE_LOG_LEVEL,token03); loglevel(LOGERROR,"Error: Serving administration command '%s %s:%s' expected [1..5|error..debug]", ADMCMD_SET, CFG_VERBOSE_LOG_LEVEL, token03); send_admincommand_ack(sd_admcmd,(char *)CFG_VERBOSE_LOG_LEVEL ,(char *)ADMCMD_FAIL, msg); return adm_set; } } // if set verbose level admin command } // endif 3 tokens exists // ============= XXX YYY situation ====================================== if (token01 && token02 && !token03) { // ARG1 ARG2 if ( (!strcasecmp(token01, "ARG1")) && (!strcasecmp(token02, "ARG2")) ) { // Action for .... logv0("Option ARG1 ARG2 detected"); return adm_unknown; } // STOP if ( (!strcasecmp(token01, ADMCMD_ACTION)) && (!strcasecmp(token02, ADMCMD_STOP)) ) { // action handle_admcmd_stop(..) loglevel(LOGDEBUG,"Debug: Admin command detected: %s", ADMCMD_STOP); return adm_stop; } // FORCESTOP if ( (!strcasecmp(token01, ADMCMD_ACTION)) && (!strcasecmp(token02, ADMCMD_FORCESTOP)) ) { // action handle_admcmd_forcestop(..) loglevel(LOGDEBUG,"Debug: Admin command detected: %s", ADMCMD_FORCESTOP); return adm_forcestop; } // GET_TXT_CONFIG (commmand received over Tcp) if ( (!strcasecmp(token01, ADMCMD_ACTION)) && (!strcasecmp(token02, ADMCMD_GETTXTCONFIG)) ) { loglevel(LOGDEBUG,"Debug: Admin command detected: %s", ADMCMD_GETTXTCONFIG); return adm_gettxtconfig; } // ADMCMD_ACTION|RELOAD_CONFIG|\n if ( (!strcasecmp(token01, ADMCMD_ACTION)) && (!strcasecmp(token02, ADMCMD_RELOADCONFIG)) ) { loglevel(LOGDEBUG,"Debug: Admin command detected: %s", ADMCMD_RELOADCONFIG); return adm_reloadconfig; } // ADMCMD_ACTION|GET_DICT|\n" if ( (!strcasecmp(token01, ADMCMD_ACTION)) && (!strcasecmp(token02, ADMCMD_GETDICT)) ) { loglevel(LOGDEBUG,"Debug: Admin command detected: %s", ADMCMD_GETDICT); return adm_getdict; } // ======================================================================================= // == GET CONFIG OPTIONS section // ======================================================================================= // GET Configuration value (generic) if ( (!strcasecmp(token01, ADMCMD_GET)) ) { loglevel(LOGINFO, "Info: Serving administration command '%s %s'",ADMCMD_GET,token02); handle_admincommand_get_config_value(sd_admcmd,token02,token03); return adm_get; } } // endif 2 tokens exists // ============= XXX situation ====================================== if (token01 && !token02 && !token03) { // } // if 1 tokens exists // Default return for non void function snprintf(msg, sizeof msg, "Error: Unknown admin command '%s|%s|%s'",token01, token02, token03); //loglevel(LOGERROR,"Error: Serving administration command '%s'", msg); send_admincommand_ack(sd_admcmd,(char *)ADMCMD_UNKNOWN ,(char *)ADMCMD_FAIL, msg); return adm_unknown; } //===================================================================== void handle_admincommand_get_config_value (int sd_client, char *cfg_option, char *cfg_param) { char tmp_line[512]; if (!strcasecmp(cfg_option, CFG_VERBOSE_LOG_LEVEL)) send_admincommand_ack(sd_client,cfg_option, config.loglevel == LOGERROR? LOGNAME_ERROR : config.loglevel == LOGWARN? LOGNAME_WARN : config.loglevel == LOGPROTO? LOGNAME_PROTO : config.loglevel == LOGINFO? LOGNAME_INFO : config.loglevel == LOGDEBUG? LOGNAME_DEBUG : "**unknown**",""); if (!strcasecmp(cfg_option, CFG_LIST_HIDDEN_ENTRY)) send_admincommand_ack(sd_client,cfg_option,config.listhiddenentries?"yes":"no",""); if (!strcasecmp(cfg_option, CFG_DISPLAY_SEQ_NUMBER)) send_admincommand_ack(sd_client,cfg_option,config.displaysequencenumbers?"yes":"no",""); if (!strcasecmp(cfg_option, CFG_CONFIG_AUTOLOAD)) send_admincommand_ack(sd_client,cfg_option,config.configautoload?"yes":"no",""); if (!strcasecmp(cfg_option, CFG_KISSDX_SRV_VERSION)) send_admincommand_ack(sd_client,cfg_option,KISSDX_VERSION,""); if (!strcasecmp(cfg_option, CFG_KISSDX_SRV_VERSIONDATE)) send_admincommand_ack(sd_client,cfg_option,KISSDX_VERSION_DATE,""); // ADMCMD_GET = LAUNCH_COMMANDLINE_CUSTOPTION if (!strcasecmp(cfg_option, CFG_KISSDX_LNCH_CUSTOPTION)) { snprintf(tmp_line, sizeof tmp_line, "%s|%s|%d|%s",ADMCMD_ACK, CFG_KISSDX_LNCH_CUSTOPTION,1,options.custoption1); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug: Send to client :'%s'",tmp_line); snprintf(tmp_line, sizeof tmp_line, "%s|%s|%d|%s",ADMCMD_ACK, CFG_KISSDX_LNCH_CUSTOPTION,2,options.custoption2); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug: Send to client :'%s'",tmp_line); snprintf(tmp_line, sizeof tmp_line, "%s|%s|%d|%s",ADMCMD_ACK, CFG_KISSDX_LNCH_CUSTOPTION,3,options.custoption3); send_line(sd_client, tmp_line );loglevel(LOGDEBUG,"Debug: Send to client :'%s'",tmp_line); send_admincommand_ack(sd_client,(char *) CFG_KISSDX_LNCH_CUSTOPTION,(char *) ADMCMD_SUCC, ""); } // ADMCMD_GET = LAUNCH_COMMANDLINE_ARGUMENT if (!strcasecmp(cfg_option, CFG_KISSDX_LNCH_ARGUMENT)) send_admincommand_ack(sd_client,cfg_option,launchedArgument,""); // ADMCMD_GET = LAUNCH_DATETIME if (!strcasecmp(cfg_option, CFG_KISSDX_LNCH_DATETIME)) send_admincommand_ack(sd_client,cfg_option,launchedTimeDate,""); } // ========================================================================================