/* * kissdx - KiSS PC-Link Daemon eXtended (based on kissd) * * Copyright (C) 2005 Stelian Pop * 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 // declare gethostname() #include // declare gehostbyname() #include // declare int errno #include #include #include #include "config.h" #include "kissdx.h" #include "utils.h" #include "connection.h" // Set up known PREFIX if not defined in makefile #ifndef PREFIX #ifdef FreeBSD #define PREFIX "/usr/local/" #endif #endif // ********************************************************************** // Global variables // ********************************************************************** config_settings_t config; // Holds the current config settings as read from kissdx.conf (or overridden later) config_overrides_t config_overrides; // Holds the non-config overrides (all zero means no overriding) // Rule: Each parameter that could be changed by the Admin interface (Http or CLI) need a constant const char *CFG_KISSDX_SRV_VERSION = "KISSDX_SRV_VERSION"; const char *CFG_KISSDX_SRV_VERSIONDATE = "KISSDX_SRV_VERSIONDATE"; const char *CFG_KISSDX_LNCH_ARGUMENT = "LAUNCH_COMMANDLINE_ARGUMENT"; // ./kissdx -d -o1 customOption1 ==> "-d const char *CFG_KISSDX_LNCH_CUSTOPTION = "LAUNCH_COMMANDLINE_CUSTOPTION"; // get custom option declared at launch time const char *CFG_KISSDX_LNCH_DATETIME = "LAUNCH_DATETIME"; const char *CFG_VERBOSE_LOG_LEVEL = "VERBOSE_LOG_LEVEL"; const char *CFG_LIST_HIDDEN_ENTRY = "LIST_HIDDEN_ENTRY"; const char *CFG_DISPLAY_SEQ_NUMBER = "DISPLAY_SEQ_NUMBER"; const char *CFG_CONFIG_AUTOLOAD = "CONFIG_AUTOLOAD"; //... const char *CFG_MST_PATH_AUDIO = "MST_PATH_AUDIO"; const char *CFG_MST_PATH_PICTURE = "MST_PATH_PICTURE"; const char *CFG_MST_PATH_VIDEO = "MST_PATH_VIDEO"; // Set of supported (Option=Value) in kissdx.conf const char *conf_opt_recentlyusedfoldername = "recentlyusedfoldername"; const char *conf_opt_adminserver_port = "adminserver_port"; // Default is 8003 const char *conf_opt_serversignature = "serversignature"; // Configurable server signature const char *conf_opt_configautoload = "configautoload"; // Default NO (UDP reload config) const char *conf_opt_renamefiletypes = "renamefiletypes"; // Default blank (no renaming) const char *conf_opt_listhiddenentries = "listhiddenentries"; // Default NO (hide entries starting with '.') const char *conf_opt_displaysequencenumbers = "displaysequencenumbers"; // Default NO (don't add sequence no.) // Set of text constant defined by default language const char *DefLang_recentlyUsedFolderName = "[Recently used]"; const char *Default_serversignature = "{ShortHostName} - kissdx {Version},{VersionDate} {ConfigReloadStatus}"; // ************************************************************************************** static int validate_media_path(const char *path, const char *persistentstoragepath) { if (*persistentstoragepath && !strncmp(path, persistentstoragepath, strlen(persistentstoragepath))) { log("Media directory inside persistent storage directory is not allowed: %s", path); return 1; } return 0; } // ************************************************************************************** static int stat_outdated_path(const char *path, const char *subdir, const char *error_msg) { // if path/subdir exist then return 1 + message error char fullpath[PATH_MAX]; struct stat statbuf; strcpy(fullpath, path); strncat(fullpath, "/", sizeof fullpath); strncat(fullpath, subdir, sizeof fullpath); if (stat(fullpath, &statbuf) == 0) { loglevel(LOGERROR,"%s: '%s'",error_msg, fullpath); return 1; } return 0; } // ************************************************************************************** static int path_exists(const char *path) { struct stat statbuf; return (stat(path, &statbuf) == 0); } // ************************************************************************************** //** parse_config() analyses kissdx.conf and populates the global variables in config_p. //* The objective is to parse kissdx.conf (all options) and run a consistency check afterwards. //* This way, the order of the option definitions in kissdx.conf is not an issue. int parse_config(config_settings_t *config_p, const char *overridingFilePath) { int is_config_error = 0; /// Internal function to convert the given value from the config character set to the server character set char* convert_to_server_character_set(char* value) { return strcpy(value, convert_charset_direct(value, config_p->config_character_set, config_p->server_character_set)); } /// Internal function to convert the given value from the config character set to the client (player) character set char* convert_to_client_character_set(char* value) { return strcpy(value, convert_charset_direct(value, config_p->config_character_set, config_p->client_character_set)); } /// Internal function to validate and store a config file option that /// must be a valid and (optionally) existing path. /// If targetextrapathlist_p is supplied, multiple config settings are allowed and we store extra paths there. void process_path_option ( char* targetbuffer, int targetbuffersize, mediapathlist_t* targetextrapathlist_p, const char* option, const char* value, int* rel_path_p, int path_must_exist) { /// Add a new mediapath_t entry to the supplied list, managing memory as needed void add_mediapath(mediapathlist_t* pathlist_p, char* path) { if (!pathlist_p->list) { assert(pathlist_p->count == 0); // Sanity check pathlist_p->list = xmalloc(sizeof(mediapath_t*)); } else { pathlist_p->list = xrealloc(pathlist_p->list, (pathlist_p->count + 1) * sizeof(mediapath_t*)); } mediapath_t* mediapath_p = xmalloc(sizeof(mediapath_t)); const char *defaultname = "Root"; const char *p = strrchr(path, '/'); p = p? p + 1 : path; mediapath_p->name = xmalloc(strlen(*p? p : defaultname) + 3); strcpy(mediapath_p->name, "["); strcat(mediapath_p->name, *p? p : defaultname); strcat(mediapath_p->name, "]"); mediapath_p->path = path; pathlist_p->list[pathlist_p->count++] = mediapath_p; } char *pathbuf; /// Convert the configured path /// from this format: "C:\folder\FOLDER\File.ext" /// to this: "/cygdrive/c/folder/FOLDER/File.ext" /// We do this regardless of platform, assuming it will only occur on Windows with Cygwin. void convert_pathbuf_to_cygwin_syntax(void) { const char* cygwin_prefix = "/cygdrive"; int cygwin_prefix_len = strlen(cygwin_prefix); int pathbuf_len = strlen(pathbuf); int combined_len = cygwin_prefix_len + pathbuf_len; if (!strncmp(pathbuf + 1, ":\\", 2)) { pathbuf = xrealloc(pathbuf, combined_len + 1); memmove(pathbuf + cygwin_prefix_len, pathbuf, pathbuf_len + 1); memmove(pathbuf, cygwin_prefix, cygwin_prefix_len); char *driveletter_p = pathbuf + cygwin_prefix_len; *(driveletter_p + 1) = '\0'; make_lowercase(driveletter_p); *(driveletter_p + 1) = *driveletter_p; *driveletter_p = '/'; string_substitute(pathbuf, "\\", "/", combined_len + 1); } } const char *pConverted = convert_charset_direct(value, config_p->config_character_set, config_p->server_character_set); int buflen = min(strlen(pConverted) + 1, targetbuffersize); pathbuf = xmalloc(buflen); strncpy(pathbuf, pConverted, buflen); *(pathbuf + buflen - 1) = '\0'; clean_pathname(pathbuf); convert_pathbuf_to_cygwin_syntax(); // Remove trailing slash if present in path char *lastpos = pathbuf + strlen(pathbuf) - 1; if (lastpos > pathbuf && *lastpos == '/') *lastpos = '\0'; *rel_path_p = (*pathbuf != '/'); // if not starting with '/' then set "relative path" flag if (path_must_exist && !path_exists(pathbuf)) { log("Error in %s option in config file: Path '%s' does not exist.", option, pathbuf); is_config_error = 1; } else { if (!*targetbuffer) { // Store path in targetbuffer and free our allocated working buffer strcpy(targetbuffer, pathbuf); free(pathbuf); } else { // Store extra paths in *targetextrapathlist if applicable, using our allocated buffer directly. if (targetextrapathlist_p) { add_mediapath(targetextrapathlist_p, pathbuf); } else { log("Error in %s option in config file: This option can only be specified once.", option); is_config_error = 1; } } } } /// Internal function to get the option and value parts from a line in the config file. /// Return TRUE if we have an option/value pair, FALSE if not (e.g. for # comment lines). /// NOTE: Original buffer is modified on return! /// Requirement: option and value should be at least 512 bytes in size for safe operation. int parse_config_line(char *buffer, char *option, char *value) { char *p; // === Remove trailing white space from the line. // === Accept CR, LF or CRLF line terminators (support Mac/Unix/Linux/DOS/Windows). for(p = buffer + strlen(buffer) - 1; p >= buffer && (*p == '\r' || *p == '\n' || *p == '\t' || *p == ' '); p--) {;} *(p+1) = '\n'; *(p+2) = '\0'; // === Skip leading white space at the start of the line. for(p = buffer; *p == ' ' || *p == '\t'; p++); // === Ignore comment lines and empty lines. if (*p == '#' || *p == '\n' || *p == '\0') return 0; // === Automatic parsing with sscanf filter if (sscanf(p, "%511s = %511[^\n]\n", option, value) != 2) { log("Conf Fatal : Invalid line in config file: [%s]. Expected [Option] = [Value]", buffer); is_config_error = 1; return 0; } return 1; } FILE *f; char *homeUnixVar; // used to emulate equivalent of "~/" char *configpath; char buffer[512]; // used for both / research config file path and parsing kissdx.conf const char *configfile_search00 = ".kissdx"; // ($HOME)/.kissdx const char *configfile_search01 = "./kissdx.conf"; const char *configfile_search02 = #ifdef PREFIX PREFIX"/etc/kissdx.conf"; #else "/etc/kissdx.conf"; #endif // deprecated configuration file location detected to help diagnostic const char *configfile_deprec01 = ".kissd"; // ($HOME)/.kissd const char *configfile_deprec02 = #ifdef PREFIX PREFIX"/etc/kissd.conf"; #else "/etc/kissd.conf"; #endif // === Set all config options to their default values if (overridingFilePath == NULL) { memset(config_p, 0, sizeof (*config_p)); // all strings/ints/struct members are NULL/0 memset(&config_overrides, 0, sizeof (config_overrides)); // all strings/ints/struct members are NULL/0 //- strcpy(config_p->isofileextensions, "iso"); strcpy(config_p->subtitle_catchall_pattern, "{name}*.*"); config_p->max_recent_files = 30; config_p->picturemaxzoompercent = 100; config_p->picturecachetrimminginterval = 5; strcpy(config_p->recentlyusedfoldername, DefLang_recentlyUsedFolderName); config_p->listenaddress.s_addr = INADDR_ANY; config_p->dvd_access_method = dvdam_libdvdread; // Default: Use libdvdread config_p->loglevel = 1; // Default is ERROR Only log level // Prevent missing 'serversignature' option in kissdx.conf strncpy(config_p->serversignature, Default_serversignature, sizeof(config_p->serversignature)); // Search for kissdx.conf / // Cascading research priority should be as following: 0 =first research // 0- location specified in command argument : kissdx -c Fullpath_To_kissdx_conf_File // 1- ~/.kissdx // 2- ./kissdx.conf // 3- [PREFIX]/etc/kissdx.conf // === Check for existence of kissdx.conf if path was given in -c option if ((homeUnixVar = getenv("HOME")) == NULL) homeUnixVar = "/"; snprintf(buffer, sizeof(buffer), "%s/%s", homeUnixVar, configfile_search00); configpath = options.config_file; // Command line option -c (Single Exclusive mode) if (configpath != NULL) { if ((f = fopen(configpath, "r")) == NULL) { // config file specified by the user is unsuable => msg in log log("Configuration file unable to be read : '%s'", configpath); return 1; } else { // Configuration file specified in command line is ready strncpy(config_p->config_file, configpath, sizeof(config_p->config_file)); config_p->config_file[sizeof(config_p->config_file) - 1] = '\0'; } // end search config in command line } // Automatic detection of configuration file (no specific log message) else if ((f = fopen(buffer, "r")) != NULL) { // found in ~/.kissdx (use of buffer to emulate ~/) strncpy(config_p->config_file, buffer, sizeof(config_p->config_file)); config_p->config_file[sizeof(config_p->config_file) - 1] = '\0'; } else if ((f = fopen(configfile_search01, "r")) != NULL) { // found in ./kissdx.conf strncpy(config_p->config_file, configfile_search01, sizeof(config_p->config_file)); config_p->config_file[sizeof(config_p->config_file) - 1] = '\0'; } else if ((f = fopen(configfile_search02, "r")) != NULL) { // found in [PREFIX]/etc/kissdx.conf strncpy(config_p->config_file, configfile_search02, sizeof(config_p->config_file)); config_p->config_file[sizeof(config_p->config_file) - 1] = '\0'; } else { // Configuration file research of kissdx.conf failed log("No configuration file is available (%s or %s or %s).", configfile_search00, configfile_search01, configfile_search02); is_config_error = 1; } // Failed to find kissdx.conf, Try to detect a kissd.conf deprecated configuration // ~/.kissd.conf or [PREFIX]/etc/kissd.conf if (is_config_error) { snprintf(buffer, sizeof(buffer), "%s/%s", homeUnixVar, configfile_deprec01); if ((f = fopen(buffer, "r")) != NULL) { // deprecated ~/.kissd.conf found log("Deprecated configuration file is not supported '%s'",buffer); fclose(f); return 1; } else if ((f = fopen(configfile_deprec02, "r")) != NULL) { // deprecated [PREFIX]/etc/.kissd.conf found log("Deprecated configuration file is not supported '%s'",configfile_deprec02); fclose(f); return 1; } // If no configuration is found, then kissdx could not start return 1; } loglevel(LOGSTD,"Service kissdx '%s-%s' analyses configuration file '%s'", KISSDX_VERSION,KISSDX_VERSION_DATE,config_p->config_file); } else { if ((f = fopen(overridingFilePath, "r")) == NULL) { // config file specified by the caller is unsuable => msg in log log("Configuration file unable to be read : '%s'", overridingFilePath); return 1; } } // ************************************************************************************************************** // === Parsing of config file, each line should be a known command, (if not, FATAL ERROR is triggered) // ************************************************************************************************************** // === Initial parsing ONLY to find the conversion character sets, which will be used // === to convert the value of certain settings as they are read from the config file. while (fgets(buffer, sizeof(buffer), f) != NULL) { char option[512], value[512]; // Get the option and value if (!parse_config_line(buffer, option, value)) continue; if (!strcasecmp(option, "server_character_set")) { strncpy(config_p->server_character_set, value, sizeof(config_p->server_character_set)); config_p->server_character_set[sizeof(config_p->server_character_set) - 1] = '\0'; } else if (!strcasecmp(option, "client_character_set")) { strncpy(config_p->client_character_set, value, sizeof(config_p->client_character_set)); config_p->client_character_set[sizeof(config_p->client_character_set) - 1] = '\0'; } else if (!strcasecmp(option, "config_character_set")) { strncpy(config_p->config_character_set, value, sizeof(config_p->config_character_set)); config_p->config_character_set[sizeof(config_p->config_character_set) - 1] = '\0'; } else if (!strcasecmp(option, "playlist_character_set")) { strncpy(config_p->playlist_character_set, value, sizeof(config_p->playlist_character_set)); config_p->playlist_character_set[sizeof(config_p->playlist_character_set) - 1] = '\0'; } } // === Go back to start of file to read all lines again below if (fseek(f, 0, SEEK_SET) != 0) { log("Err: Could not reset file position in config file: %s", strerror(errno)); fclose(f); return 1; } // === Process all other settings while (fgets(buffer, sizeof(buffer), f) != NULL) { char option[512], value[512]; int rel_path = 0; // Get the option and value if (!parse_config_line(buffer, option, value)) continue; // === Check (Option=Value) against known commands and set config_p accordingly if (!strcasecmp(option, "audiopath")) { process_path_option(config_p->audiopath, sizeof(config_p->audiopath), &config_p->extra_audiopath_list, option, value, &rel_path, 1); } else if (!strcasecmp(option, "videopath")) { process_path_option(config_p->videopath, sizeof(config_p->videopath), &config_p->extra_videopath_list, option, value, &rel_path, 1); } else if (!strcasecmp(option, "picturepath")) { process_path_option(config_p->picturepath, sizeof(config_p->picturepath), &config_p->extra_picturepath_list, option, value, &rel_path, 1); } else if (!strcasecmp(option, "persistentstoragepath")) { process_path_option(config_p->persistentstoragepath, sizeof(config_p->persistentstoragepath), NULL, option, value, &rel_path, 0); } else if (!strcasecmp(option, "pidfilepath")) { process_path_option(config_p->pidfilepath, sizeof(config_p->pidfilepath), NULL, option, value, &rel_path, 0); } else if (!strcasecmp(option, "kmlforwardurl")) { strncpy(config_p->kmlfwdurl, convert_to_client_character_set(value), sizeof(config_p->kmlfwdurl)); config_p->kmlfwdurl[sizeof(config_p->kmlfwdurl) - 1] = '\0'; } else if (!strcasecmp(option, "pretrigger")) { process_path_option(config_p->pretrigger, sizeof(config_p->pretrigger), NULL, option, value, &rel_path, 1); } else if (!strcasecmp(option, "posttrigger")) { process_path_option(config_p->posttrigger, sizeof(config_p->posttrigger), NULL, option, value, &rel_path, 1); } else if (!strcasecmp(option, "directorypretrigger")) { process_path_option(config_p->directorypretrigger, sizeof(config_p->directorypretrigger), NULL, option, value, &rel_path, 1); } else if (!strcasecmp(option, "directoryposttrigger")) { process_path_option(config_p->directoryposttrigger, sizeof(config_p->directoryposttrigger), NULL, option, value, &rel_path, 1); } else if (!strcasecmp(option, "audiofileextensions")) { strncpy(config_p->audiofileextensions, convert_to_server_character_set(value), sizeof(config_p->audiofileextensions)); config_p->audiofileextensions[sizeof(config_p->audiofileextensions) - 1] = '\0'; make_lowercase(config_p->audiofileextensions); } else if (!strcasecmp(option, "videofileextensions")) { strncpy(config_p->videofileextensions, convert_to_server_character_set(value), sizeof(config_p->videofileextensions)); config_p->videofileextensions[sizeof(config_p->videofileextensions) - 1] = '\0'; make_lowercase(config_p->videofileextensions); } else if (!strcasecmp(option, "picturefileextensions")) { strncpy(config_p->picturefileextensions, convert_to_server_character_set(value), sizeof(config_p->picturefileextensions)); config_p->picturefileextensions[sizeof(config_p->picturefileextensions) - 1] = '\0'; make_lowercase(config_p->picturefileextensions); } else if (!strcasecmp(option, "isofileextensions")) { strncpy(config_p->isofileextensions, convert_to_server_character_set(value), sizeof(config_p->isofileextensions)); config_p->isofileextensions[sizeof(config_p->isofileextensions) - 1] = '\0'; make_lowercase(config_p->isofileextensions); } else if (!strcasecmp(option, "max_recent_files")) { config_p->max_recent_files = abs(atoi(value)); } else if (!strcasecmp(option, "server_character_set")) { // This setting has already been read } else if (!strcasecmp(option, "client_character_set")) { // This setting has already been read } else if (!strcasecmp(option, "config_character_set")) { // This setting has already been read } else if (!strcasecmp(option, "playlist_character_set")) { // This setting has already been read } else if (!strcasecmp(option, "subtitlefilemapping")) { if (!strncmp(value, "*:", 2) && strlen(value) > 2) { strncpy(config_p->subtitle_catchall_pattern, convert_to_server_character_set(value + 2), sizeof(config_p->subtitle_catchall_pattern)); config_p->subtitle_catchall_pattern[sizeof(config_p->subtitle_catchall_pattern) - 1] = '\0'; } else { log("Error in %s option in config file: %s", option, "Must start with '*:' followed by a filename pattern."); is_config_error = 1; } } else if (!strcasecmp(option, "picturetargetwidth")) { config_p->picturetargetwidth = abs(atoi(value)); } else if (!strcasecmp(option, "picturetargetheight")) { config_p->picturetargetheight = abs(atoi(value)); } else if (!strcasecmp(option, "picturemaxzoompercent")) { config_p->picturemaxzoompercent = abs(atoi(value)); } else if (!strcasecmp(option, "picturecachesize")) { config_p->picturecachesize = atoi(value); } else if (!strcasecmp(option, "picturecachetrimminginterval")) { config_p->picturecachetrimminginterval = abs(atoi(value)); } else if (!strcasecmp(option, "networktimeoutinterval")) { config_p->networktimeoutinterval = max(atoi(value), 0); } else if (!strcasecmp(option, "enablehiddenfilestext")) { if (*value != '\0') { if (!strchr(value, '/') && !strchr(value, '\\')) { strncpy(config_p->enablehiddenfilestext, convert_to_server_character_set(value), sizeof(config_p->enablehiddenfilestext)); config_p->enablehiddenfilestext[sizeof(config_p->enablehiddenfilestext) - 1] = '\0'; } else { log("Error in %s option in config file: %s", option, "Can not contain file system delimiters '/' and '\\'."); is_config_error = 1; } } else { log("Error in %s option in config file: Value is missing.", option); is_config_error = 1; } } else if (!strcasecmp(option, "enablehiddenfilesminutes")) { config_p->enablehiddenfilesminutes = max(atoi(value), 0); } // OPT[recentlyusedfoldername] = Alternate text for recently used folder name/ automatic truncated to 127 else if (!strcasecmp(option, conf_opt_recentlyusedfoldername)) { if (*value != '\0') { strncpy(config_p->recentlyusedfoldername, convert_to_server_character_set(value), sizeof(config_p->recentlyusedfoldername)); config_p->recentlyusedfoldername[sizeof(config_p->recentlyusedfoldername) - 1] = '\0'; } else { log("Error in %s option in config file: Value is missing.", option); is_config_error = 1; } } // OPT[listenaddress] = Local interface IP to restrict TCP Listener (does not impact UDP) // notice this address is one of kissdx hosting server, NOT the one of the player else if (!strcasecmp(option, "listenaddress")) { struct in_addr address; if (inet_aton(value, &address)) config_p->listenaddress = address; else { log("Error in %s option in config file: Invalid IP address %s", option, value); is_config_error = 1; } } // OPT[adminserver_port] = TCP port to bind the admin server listener // (support receipt of STOP, FORCESTOP, RELOAD_CONFIG ..) else if (!strcasecmp(option, conf_opt_adminserver_port)) { if (!adminserver_port_CLO) { // If adminserver_port is not defined in command line options , then kissdx.conf could be read if (*value != '\0') { config_p->adminserver_port = abs(atoi(value)); } else { log("Error in %s option in config file: Value is missing.", option); is_config_error = 1; } } // else : Command line options already defines a value, then no need to read kissdx.conf } // OPT[configautoload] = Enable reload configuration on reception of UDP:Broadcast else if (!strcasecmp(option, conf_opt_configautoload)) { if (*value != '\0') { config_p->configautoload = (!strcasecmp(value, "yes")); } else { log("Error in %s option in config file: Value is missing.", option); is_config_error = 1; } } // OPT[serversignature] = Set the text for serversignature else if (!strcasecmp(option, conf_opt_serversignature)) { if (*value != '\0') { strncpy(config_p->serversignature, value, sizeof(config_p->serversignature)); config_p->serversignature[sizeof(config_p->serversignature) - 1] = '\0'; } else { log("Error in %s option in config file: Value is missing.", option); is_config_error = 1; } } // OPT[dvdaccessmethod] = else if (!strcasecmp(option, "dvdaccessmethod")) { if (*value) { config_p->dvd_access_method = !strcasecmp(value, "libdvdnav")? dvdam_libdvdnav : dvdam_libdvdread; } else { log("Error in %s option in config file: Value is missing.", option); is_config_error = 1; } } // OPT[renamefiletypes] = else if (!strcasecmp(option, conf_opt_renamefiletypes)) { if (*value != '\0') { strncpy(config_p->renamefiletypes, value, sizeof(config_p->renamefiletypes)); config_p->renamefiletypes[sizeof(config_p->renamefiletypes) - 1] = '\0'; } else { log("Error in %s option in config file: Value is missing.", option); is_config_error = 1; } } // OPT[listhiddenentries] = else if (!strcasecmp(option, conf_opt_listhiddenentries)) { if (*value != '\0') { config_p->listhiddenentries = (!strcasecmp(value, "yes")); } else { log("Error in %s option in config file: Value is missing.", option); is_config_error = 1; } } // OPT[displaysequencenumbers] = else if (!strcasecmp(option, conf_opt_displaysequencenumbers)) { if (*value != '\0') { config_p->displaysequencenumbers = (!strcasecmp(value, "yes")); } else { log("Error in %s option in config file: Value is missing.", option); is_config_error = 1; } } // OPT[loglevel] = else if (!strcasecmp(option, "loglevel")) { // is overridden by an command line option -lx if (atoi(value) != 0) { // if option is integer, get it. Consistency check occurs later. config_p->loglevel = atoi(value); if (config_p->loglevelloglevel>LOGDEBUG) config_p->loglevel=LOGERROR; } else { // value is ascii int arglen = strlen(value); if (!strncasecmp(value, "error",arglen)) config_p->loglevel = LOGERROR; else if (!strncasecmp(value, "warning",arglen)) config_p->loglevel = LOGWARN; else if (!strncasecmp(value, "protocol",arglen)) config_p->loglevel = LOGPROTO; else if (!strncasecmp(value, "information",arglen)) config_p->loglevel = LOGINFO; else if (!strncasecmp(value, "debug",arglen)) config_p->loglevel = LOGDEBUG; else { is_config_error = 1; loglevel(LOGERROR,"Error: Unknown value for option loglevel:'%s'. Refer help screen.",value); } } } // bogusfilesize = // Only valid in override files, not in kissdx.conf! else if (!strcasecmp(option, "bogusfilesize")) { if (overridingFilePath != NULL) { config_overrides.bogusfilesize = atoll(value); if (config_overrides.bogusfilesize < 0) config_overrides.bogusfilesize = 0; } else { is_config_error = 1; loglevel(LOGERROR,"Error: Option only allowed in kissdx.override file: '%s'.", option); } } // ==================================================================== // None of known option is detected => Error else { loglevel(LOGERROR,"Unknown option in config file: %s", option); is_config_error = 1; } if (rel_path) { log("Paths must be absolute refs in config file: %s", value); is_config_error = 1; } } // === End of parsing config file // ************************************************************************************************************** // === Command line options are prior on configuration file options // Once configuration parsing is finished, Command line options should override info in config object // ************************************************************************************************************** if (adminserver_port_CLO) { // source = command line parsing : short options.adminserver_port config_p->adminserver_port = options.adminserver_port; } if (loglevel_CLO) { //either command line (-l5, or-v or -l debug) ==> Priority on command line options config_p->loglevel = options.loglevel; } // ************************************************************************************************************** // === Consistency check of all options read from kissdx.conf // ************************************************************************************************************** //= Check loglevel in [LOGERROR..LOGDEBUG] if (config_p->loglevel < 0 || config_p->loglevel > LOGDEBUG ) { loglevel(LOGERROR,"Error: Option loglevel expected [1..5] or [Error...Debug]. Read:'%d'",config_p->loglevel); is_config_error = 1; } //= Check path to media if (config_p->audiopath[0] == '\0') { loglevel(LOGERROR,"Error: Option %s","audiopath not set in config file"); is_config_error = 1; } if (config_p->videopath[0] == '\0') { loglevel(LOGERROR,"Error: Option %s","videopath not set in config file"); is_config_error = 1; } if (config_p->picturepath[0] == '\0') { loglevel(LOGERROR,"Error: Option %s","picturepath not set in config file"); is_config_error = 1; } if (config_p->persistentstoragepath[0] != '\0' && !is_config_error) { // Check whether any media directories are inside the persistent storage directory. If they are, it's a config error (we risk name collisions for .recent/.piccache). if (validate_media_path(config_p->audiopath, config_p->persistentstoragepath)) is_config_error = 1; if (validate_media_path(config_p->videopath, config_p->persistentstoragepath)) is_config_error = 1; if (validate_media_path(config_p->picturepath, config_p->persistentstoragepath)) is_config_error = 1; if (!is_config_error) { // Look for .recent directories inside media directories. If they are still there, it's a config error. int is_recent_there = 0; if (stat_outdated_path(config_p->audiopath, ".recent", "History folder into audio media repository is not supported")) is_recent_there = 1; if (stat_outdated_path(config_p->videopath, ".recent", "History folder into video media repository is not supported")) is_recent_there = 1; if (stat_outdated_path(config_p->picturepath, ".recent", "History folder into picture media repository is not supported")) is_recent_there = 1; if (is_recent_there) { loglevel(LOGERROR,"Error: Existing history folder should deleted or be moved to %s","the persistent cache path"); loglevel(LOGERROR,"Error: Current persistent cache path is '%s'", config_p->persistentstoragepath); is_config_error = 1; } if (stat_outdated_path(config_p->picturepath, ".piccache", "Cache folder into picture media repository is not supported")) { loglevel(LOGERROR,"Error: The .piccache directory must be deleted or moved to %s", config_p->persistentstoragepath); is_config_error = 1; } // Create the .recent directories now (whether error or not) to aid user in moving stuff (and to aid ourselves later...) char path[PATH_MAX]; if (mkdir(config_p->persistentstoragepath, standard_dir_filemode) >= 0) chmod(config_p->persistentstoragepath, standard_dir_filemode); strcpy(path, config_p->persistentstoragepath); strncat(path, "/audio", sizeof path); if (mkdir(path, standard_dir_filemode) >= 0) chmod(path, standard_dir_filemode); strncat(path, "/.recent", sizeof path); if (mkdir(path, standard_dir_filemode) >= 0) chmod(path, standard_dir_filemode); strcpy(path, config_p->persistentstoragepath); strncat(path, "/video", sizeof path); if (mkdir(path, standard_dir_filemode) >= 0) chmod(path, standard_dir_filemode); strncat(path, "/.recent", sizeof path); if (mkdir(path, standard_dir_filemode) >= 0) chmod(path, standard_dir_filemode); strcpy(path, config_p->persistentstoragepath); strncat(path, "/picture", sizeof path); if (mkdir(path, standard_dir_filemode) >= 0) chmod(path, standard_dir_filemode); strncat(path, "/.recent", sizeof path); if (mkdir(path, standard_dir_filemode) >= 0) chmod(path, standard_dir_filemode); } } // end if Persistent option enable (persistentstoragepath is defined) // serversignature first validation ================================================================================================ // empty value is not affordable due to KiSS player needs to display somethings in "Search PCLink Server" menu // maybe some special characters should be avoided, like CR, LF, TAB, BS, etc.? // serversignature // Replace the special tokens in the pattern with our substitutes // create substitutes values, and run string_substitute char serversignature_substitute[255]; char* fullhostname = serversignature_substitute; // serversignature : {FullHostName} and {ShortHostName} // FullHostName is also known as : FQDN (Fully Qualified Domain Name) if (gethostname(serversignature_substitute, sizeof(serversignature_substitute)) < 0) { loglevel(LOGERROR,"Error: gethostname: %s", strerror(errno)); return 1; // exit gracefully } // Section for future IP retrieval if needed (mainly 127.0.0.1 is returned) /* char hostip[17]; struct hostent *pHostEnt; struct sockaddr_in tmpHostnameSockAddr; //placeholder for the ip address pHostEnt = gethostbyname(fullhostname); loglevel(LOGDEBUG,"Debug Hostname pHostEnt %s :%s",fullhostname, strerror(errno)); struct hostent *pFilterHostEnt; pFilterHostEnt = gethostbyaddr(&config_p->listenaddress,sizeof(config_p->listenaddress),PF_INET); loglevel(LOGERROR,"Hostname pHostEnt from host IP %s :%s", inet_ntoa(config_p->listenaddress), strerror(errno)); loglevel(LOGINFO,"Hostname (%s) =? ListenAddress (%s)", pHostEnt->h_name, pFilterHostEnt->h_name); //memcpy(&tmpHostnameSockAddr.sin_addr, pHostEnt->h_addr, pHostEnt->h_length); //h_addr is a synonym of h_addr_list[0] memcpy(&tmpHostnameSockAddr.sin_addr, pHostEnt->h_addr_list[0], pHostEnt->h_length); strcpy(hostip,inet_ntoa(tmpHostnameSockAddr.sin_addr)); logv(">>>>>>>Ip Address of the machine is %s\n",hostip); // memcpy(&tmpHostnameSockAddr.sin_addr, pHostEnt->h_addr_list[1], pHostEnt->h_length); //strcpy(hostip,inet_ntoa(tmpHostnameSockAddr.sin_addr)); //logv("Ip Address of the machine is %s\n",hostip); */ //=========================== string_substitute(config_p->serversignature, "{FullHostName}", fullhostname, sizeof config_p->serversignature); // crop hostname xxx.yyy.zzzz to xxx char shorthostname[255]; char* p = strchr(fullhostname, '.'); int len = p? p - fullhostname : sizeof(shorthostname) - 1; strncpy(shorthostname, fullhostname, len); *(shorthostname + len) = '\0'; string_substitute(config_p->serversignature, "{ShortHostName}", shorthostname, sizeof config_p->serversignature); // serversignature : {Version} = Version of kissdx currently running (vX.XX-XX) string_substitute(config_p->serversignature, "{Version}" , KISSDX_VERSION , sizeof config_p->serversignature); // serversignature : {VersionDate} = Date of this version (yyyy-mm-dd) string_substitute(config_p->serversignature, "{VersionDate}" , KISSDX_VERSION_DATE , sizeof config_p->serversignature); // serversignature ====== end of substitution ======================================================================================== // kissdx.c : udp_responder() will use config.serversignature as answer to KiSS players // Close file handle on kissdx.conf fclose(f); if (is_config_error) { loglevel(LOGERROR,"Error: %s","Failure in configuration file preventing kissdx to start."); return 1; } // ***************************************************************** show_current_config_Verbose(config_p); // Acknowledge of command line options validated if (adminserver_port_CLO) { // source = command line parsing : short options.adminserver_port config_p->adminserver_port = options.adminserver_port; } if (loglevel_CLO) loglevel(LOGSTD,"Command line options validated %s:'%d:%s'","LogLevel", config_p->loglevel, config_p->loglevel == LOGERROR? LOGNAME_ERROR : config_p->loglevel == LOGWARN? LOGNAME_WARN : config_p->loglevel == LOGPROTO? LOGNAME_PROTO : config_p->loglevel == LOGINFO? LOGNAME_INFO : config_p->loglevel == LOGDEBUG? LOGNAME_DEBUG : "**unknown**" ); return 0; } // *************************************************************************************** /// Free allocated memory used by the supplied config struct and reset it to an unused state void free_config(config_settings_t *config_p) { void free_mediapathlist(mediapathlist_t* mediapathlist_p) { if (mediapathlist_p) { mediapath_t** list = mediapathlist_p->list; if (list) { for (int i = 0; i < mediapathlist_p->count; i++) { if (list[i]->name) { free(list[i]->name); list[i]->name = NULL; } if (list[i]->path) { free(list[i]->path); list[i]->path = NULL; } } free(list); mediapathlist_p->list = NULL; } } } if (config_p) { free_mediapathlist(&config_p->extra_audiopath_list); free_mediapathlist(&config_p->extra_videopath_list); free_mediapathlist(&config_p->extra_picturepath_list); } memset(config_p, 0, sizeof (*config_p)); // all strings/ints/struct members are NULL/0 } void show_current_config_Verbose(const config_settings_t *config_p) { char *separator_line = "============================================================="; char *section_separator_pre = "========== Section ["; char *section_separator_post = "] ========"; if (config_p->loglevel == LOGDEBUG) { log("%s",separator_line); log0("Settings loaded in memory"); log("kissdx version = %s / %s", KISSDX_VERSION, KISSDX_VERSION_DATE); log("Configuration file = '%s'", config_p->config_file); log("%s",separator_line); // -- Media streaming server log("%s %s %s",section_separator_pre,"Path",section_separator_post); log("audiopath = '%s'", config_p->audiopath); for (int i = 0; i < config_p->extra_audiopath_list.count; i++) log("audiopath = '%s'", config_p->extra_audiopath_list.list[i]->path); log("videopath = '%s'", config_p->videopath); for (int i = 0; i < config_p->extra_videopath_list.count; i++) log("videopath = '%s'", config_p->extra_videopath_list.list[i]->path); log("picturepath = '%s'", config_p->picturepath); for (int i = 0; i < config_p->extra_picturepath_list.count; i++) log("picturepath = '%s'", config_p->extra_picturepath_list.list[i]->path); log("persistentstoragepath = '%s'", config_p->persistentstoragepath); log("pidfilepath = '%s'", config_p->pidfilepath); log("%s %s %s",section_separator_pre,"Media extension",section_separator_post); log("audiofileextensions = '%s'", config_p->audiofileextensions); log("videofileextensions = '%s'", config_p->videofileextensions); log("picturefileextensions = '%s'", config_p->picturefileextensions); log("isofileextensions = '%s'", config_p->isofileextensions); log("%s %s %s",section_separator_pre,"Pre.Post Treatment",section_separator_post); log("pretrigger = '%s'", config_p->pretrigger); log("posttrigger = '%s'", config_p->posttrigger); log("directorypretrigger = '%s'", config_p->directorypretrigger); log("directoryposttrigger = '%s'", config_p->directoryposttrigger); log("%s %s %s",section_separator_pre,"DVD ISO settings",section_separator_post); log("subtitle_catchall_pattern = %s", config_p->subtitle_catchall_pattern); log("%s %s %s",section_separator_pre,"Picture Treatment",section_separator_post); log("picturetargetwidth = %d", config_p->picturetargetwidth); log("picturetargetheight = %d", config_p->picturetargetheight); log("picturemaxzoompercent = %d", config_p->picturemaxzoompercent); log("picturecachesize = %d", config_p->picturecachesize); log("picturecachetrimminginterval = %d", config_p->picturecachetrimminginterval); log("%s %s %s",section_separator_pre,"Dark side of the moon",section_separator_post); log("enablehiddenfilestext = '%s'", config_p->enablehiddenfilestext); log("enablehiddenfilesminutes = %d", config_p->enablehiddenfilesminutes); log("%s %s %s",section_separator_pre,"Internationalisation",section_separator_post); log("server_character_set = '%s'", config_p->server_character_set); log("client_character_set = '%s'", config_p->client_character_set); log("config_character_set = '%s'", config_p->config_character_set); log("playlist_character_set = '%s'", config_p->playlist_character_set); log("recentlyusedfoldername = '%s'", config_p->recentlyusedfoldername); log("max_recent_files = %d", config_p->max_recent_files); log("%s %s %s",section_separator_pre,"Firewall and redirection",section_separator_post); log("listenaddress = '%s'", inet_ntoa(config_p->listenaddress)); log("kmlforwardurl = '%s'", config_p->kmlfwdurl); log("%s %s %s",section_separator_pre,"Daemon administration",section_separator_post); log("adminserver_port = %d", config_p->adminserver_port); log("networktimeoutinterval = %d", config_p->networktimeoutinterval); log("serversignature = '%s'", config_p->serversignature); log("configautoload = %s", config_p->configautoload? "yes" : "no"); log("dvdaccessmethod = %s", config_p->dvd_access_method == dvdam_libdvdread? "libdvdread" : config_p->dvd_access_method == dvdam_libdvdnav ? "libdvdnav" : "**unknown**" ); // section should be kept... Which one for this part ? log("renamefiletypes = '%s'", config_p->renamefiletypes); log("listhiddenentries = %s", config_p->listhiddenentries? "yes" : "no"); log("displaysequencenumbers = %s", config_p->displaysequencenumbers? "yes" : "no"); log("verbose loglevel = %d:%s", config_p->loglevel, config_p->loglevel == LOGERROR? LOGNAME_ERROR : config_p->loglevel == LOGWARN? LOGNAME_WARN : config_p->loglevel == LOGPROTO? LOGNAME_PROTO : config_p->loglevel == LOGINFO? LOGNAME_INFO : config_p->loglevel == LOGDEBUG? LOGNAME_DEBUG : "**unknown**" ); log("%s",separator_line); } } //===================================================================== void send_txt_config (int sd_client, const config_settings_t *config_p) { // Send current configuration settings over tcp socket resquest // based and linked to show_current_config_Verbose() char *separator_line = "============================================================="; char tmp_conf_line[512]; snprintf(tmp_conf_line, sizeof tmp_conf_line, "%s",separator_line); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "Settings loaded in memory %s", " "); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "kissdx version = %s / %s", KISSDX_VERSION, KISSDX_VERSION_DATE); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "Configuration file = %s", config_p->config_file); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "%s",separator_line); send_line(sd_client, tmp_conf_line ); // -- Media streaming server snprintf(tmp_conf_line, sizeof tmp_conf_line, "audiopath = %s", config_p->audiopath); send_line(sd_client, tmp_conf_line ); for (int i = 0; i < config_p->extra_audiopath_list.count; i++) { snprintf(tmp_conf_line, sizeof tmp_conf_line, "audiopath = %s", config_p->extra_audiopath_list.list[i]->path); send_line(sd_client, tmp_conf_line ); } snprintf(tmp_conf_line, sizeof tmp_conf_line, "videopath = %s", config_p->videopath); send_line(sd_client, tmp_conf_line ); for (int i = 0; i < config_p->extra_videopath_list.count; i++) { snprintf(tmp_conf_line, sizeof tmp_conf_line, "videopath = %s", config_p->extra_videopath_list.list[i]->path); send_line(sd_client, tmp_conf_line ); } snprintf(tmp_conf_line, sizeof tmp_conf_line, "picturepath = %s", config_p->picturepath); send_line(sd_client, tmp_conf_line ); for (int i = 0; i < config_p->extra_picturepath_list.count; i++) { snprintf(tmp_conf_line, sizeof tmp_conf_line, "picturepath = %s", config_p->extra_picturepath_list.list[i]->path); send_line(sd_client, tmp_conf_line ); } snprintf(tmp_conf_line, sizeof tmp_conf_line, "persistentstoragepath = %s", config_p->persistentstoragepath); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "pidfilepath = %s", config_p->pidfilepath); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "kmlforwardurl = %s", config_p->kmlfwdurl); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "pretrigger = %s", config_p->pretrigger); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "posttrigger = %s", config_p->posttrigger); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "directorypretrigger = %s", config_p->directorypretrigger); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "directoryposttrigger = %s", config_p->directoryposttrigger); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "listenaddress = %s", inet_ntoa(config_p->listenaddress)); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "audiofileextensions = %s", config_p->audiofileextensions); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "videofileextensions = %s", config_p->videofileextensions); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "picturefileextensions = %s", config_p->picturefileextensions); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "isofileextensions = %s", config_p->isofileextensions); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "max_recent_files = %d", config_p->max_recent_files); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "server_character_set = %s", config_p->server_character_set); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "client_character_set = %s", config_p->client_character_set); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "config_character_set = %s", config_p->config_character_set); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "playlist_character_set = %s", config_p->playlist_character_set); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "subtitle_catchall_pattern = %s", config_p->subtitle_catchall_pattern); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "picturetargetwidth = %d", config_p->picturetargetwidth); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "picturetargetheight = %d", config_p->picturetargetheight); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "picturemaxzoompercent = %d", config_p->picturemaxzoompercent); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "picturecachesize = %d", config_p->picturecachesize); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "picturecachetrimminginterval = %d", config_p->picturecachetrimminginterval); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "networktimeoutinterval = %d", config_p->networktimeoutinterval); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "enablehiddenfilestext = %s", config_p->enablehiddenfilestext); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "enablehiddenfilesminutes = %d", config_p->enablehiddenfilesminutes); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "recentlyusedfoldername = %s", config_p->recentlyusedfoldername); send_line(sd_client, tmp_conf_line ); // -- Admin server snprintf(tmp_conf_line, sizeof tmp_conf_line, "adminserver_port = %d", options.adminserver_port); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "serversignature = %s", config_p->serversignature); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "configautoload = %s", config_p->configautoload? "yes" : "no"); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "dvdaccessmethod = %s", config_p->dvd_access_method == dvdam_libdvdread? "libdvdread" : config_p->dvd_access_method == dvdam_libdvdnav ? "libdvdnav" : "**unknown**" ); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "renamefiletypes = %s", config_p->renamefiletypes); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "listhiddenentries = %s", config_p->listhiddenentries? "yes" : "no"); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "displaysequencenumbers = %s", config_p->displaysequencenumbers? "yes" : "no"); send_line(sd_client, tmp_conf_line ); snprintf(tmp_conf_line, sizeof tmp_conf_line, "verboseloglevel = %d:%s", config_p->loglevel, config_p->loglevel == LOGERROR? LOGNAME_ERROR : config_p->loglevel == LOGWARN? LOGNAME_WARN : config_p->loglevel == LOGPROTO? LOGNAME_PROTO : config_p->loglevel == LOGINFO? LOGNAME_INFO : config_p->loglevel == LOGDEBUG? LOGNAME_DEBUG : "**unknown**" ); send_line(sd_client, tmp_conf_line ); // ==================================================================== }