Subversion Repositories kissdx

Rev

Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

/*
 * kissdx - KiSS PC-Link Daemon eXtended (based on kissd)
 *
 * Copyright (C) 
 * Portions Copyright (C) 2006 Vidar Tysse <vidar@vidartysse.net>
 * Portions Copyright (C) 2007 Olivier Kahn <okahn@free.fr>
 * 
 * Heavily based on kiss4lin,
 * Copyright (C) 2004 Jacob Kolding <dacobi@users.sourceforge.net>
 *
 * 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <dirent.h>
#include <time.h>
#include <signal.h>

#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 *admincommand_STOP           = "STOP";           // stop daemon master & let child serving current media
const char *admincommand_FORCESTOP      = "FORCESTOP";      // stop daemon master and any child running
const char *admincommand_RELOADCONFIG   = "RELOAD_CONFIG";  // invoke a reload config to mediacenter
const char *admincommand_STATUS         = "STATUS";         // give back a status of daemon (in text,html, kml ?...)
const char *admincommand_GET_TXT_CONFIG = "GET_TXT_CONFIG"; // give back a text edition of kissdx server config

// ========================================================
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
                    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)
{
    int scandir_selector(const struct dirent *entry)
    {
        const char *thisname = entry->d_name; 
        if (!strncmp(thisname, commandserver_filename_prefix, strlen(commandserver_filename_prefix)))
            return 1;   // This is residue from a previous session
        return 0;
    }

    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
    strncpy(scandir_info.scandir_basename_pattern,  commandserver_filename_prefix_pattern, 
                                                                sizeof(scandir_info.scandir_basename_pattern));
    strncpy(scandir_info.scandir_file_extensions ,  "" ,        sizeof(scandir_info.scandir_file_extensions) );
    strncpy(scandir_info.scandir_dirname,           commandserver_path, sizeof(scandir_info.scandir_dirname));
    scandir_info.scandir_directory_limited = 0; // Activation of mode 'File' research
    
    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, scandir_selector, alphasort)) < 0) {
        loglevel(LOGERROR,"Error: setup_local_commandserver_socket: scandir %s: %s", commandserver_path, strerror(errno));
        return -1;
    }
    /*
    
    // 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) {
    logv("INFO handle_admin_command STOP: SIGTERM to daemon pid: %d", daemon_pid);
    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(void) {
    logv0("INFO handle_admin_command FORCESTOP: SIGTERM to kissdx process group ");
    // if kill(pid == 0) =>  All processes in the same process group as the sender.
    kill(0, SIGTERM);
}
// ========================================================
// Admin command : RELOAD_CONFIG
void handle_admincommand_reloadconfig(void) {
    logv0("INFO handle_admin_command RELOAD_CONFIG: Parse kissdx.conf ");
    loglevel(LOGINFO,"Info: Serving administration command '%s'",admincommand_RELOADCONFIG);
    
    config_settings_t new_config;
    int config_error = parse_config(&new_config);
    if (!config_error) {
        free_config(&config);
        memcpy(&config, &new_config, sizeof (config));
    } else {
        log("Loading new config failed from   : %s",new_config.config_file);
        log("Keep previous configuration from : %s",config.config_file);
        show_current_config_Verbose(&config);   // Show the old (still current) configuration in verbose mode
    }
    logv0("..... Ready for streaming business");        
}

// ========================================================
// Admin command : GET_TXT_CONFIG
void handle_admincommand_gettxtconfig (int sd_client) {
    // Should send back to socket (sd_client) the configuration settings
    logv0("INFO handle_admin_command GET_TXT_CONFIG: Send back configuration settings ");
    loglevel(LOGINFO,"Info: Serving administration command '%s'",admincommand_GET_TXT_CONFIG);
    send_txt_config (sd_client, &config);
    // should display : config_settings_t config;
    // like show_current_config_Verbose(&config);   // Show the current configuration in verbose mode
    
    // ??? send of EOF ??
    //close (sd_client) is done by kissdx.c:dodaemon() loop 
}


// ========================================================

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
    // 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
    char buffer[size];                  // command line read from socket
    char *token01,*token02,*token03 ;   // command line parsed "<token01> <token02> <token03>"
    const char* token_delimiters = " "; // command line parsing delimiter = " "

    // read command line from socket 
    if ((len = recv(sd_admcmd, buffer, size, 0)) < 0) {
        log("ERR: handle_admin_command.recv: %s", strerror(errno));
        return adm_err;
    }

    if (len == 0) {
        logv("ERR: handle_admin_command: EOF on socket %d", sd_admcmd);
        return adm_err;
    }
    logv("Parse admin command: 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';
    logv("Admin command 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) ;

    // DEBUG
    //logv("cmdserver.c token01 [%s]",token01);
    //logv("cmdserver.c token02 [%s]",token02);
    //logv("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 LIST_HIDDEN_ENTRIES ON 
        if ( (!strcasecmp(token01, "SET")) && (!strcasecmp(token02, "LIST_HIDDEN_ENTRIES")) && (!strcasecmp(token03, "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");
            return adm_unknown;
        }

        // SET LIST_HIDDEN_ENTRIES OFF
        if ( (!strcasecmp(token01, "SET")) && (!strcasecmp(token02, "LIST_HIDDEN_ENTRIES")) && (!strcasecmp(token03, "OFF")) )  {
            config.listhiddenentries = 0;
            logv0("Config setting listhiddenentries has been turned OFF");
            return adm_unknown;
        }
    } // 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;
        }

        // GET_TXT_CONFIG (commmand received over Tcp)
        if ( (!strcasecmp(token01, admincommand_GET_TXT_CONFIG))  ) {
            logv("Admin command detected: %s", admincommand_GET_TXT_CONFIG);
            return adm_gettxtconfig;
        }       

    } // endif 2 tokens exists

    // ============= XXX   situation ======================================
    if  (token01 && !token02 && !token03) {
        // STOP
        if ( (!strcasecmp(token01, "STOP"))  )  {
            // action handle_admcmd_stop(..)
            logv0("Admin command detected: STOP");
            return adm_stop;
        }

        // FORCESTOP
        if ( (!strcasecmp(token01, "FORCESTOP"))  ) {
            // action handle_admcmd_forcestop(..)
            logv0("Admin command detected: FORCESTOP");
            return adm_forcestop;
        }

        // RELOAD_CONFIG
        if ( (!strcasecmp(token01, "RELOAD_CONFIG"))  ) {
            // action handle_admcmd_reload_config(..)
            logv0("Admin command detected: RELOAD_CONFIG");
            return adm_reloadconfig;
        }

        // RELOAD_CONFIG
        if ( (!strcasecmp(token01, "SET_LIST_HIDDEN_ENTRIES_ON"))  )    {
            logv0("Admin command detected: SET_LIST_HIDDEN_ENTRIES_ON");
            return adm_unknown;
        }
        // RELOAD_CONFIG
        if ( (!strcasecmp(token01, "SET_LIST_HIDDEN_ENTRIES_OFF"))  )   {
            logv0("Admin command detected: SET_LIST_HIDDEN_ENTRIES_OFF");
            return adm_unknown;
        }


    } // if 1 tokens exists

    // Default return for non void function
    return adm_unknown;
}
// ========================================================================================

Generated by GNU Enscript 1.6.5.