/* * kissdx - KiSS PC-Link Daemon eXtended (based on kissd) * * Copyright (C) 2005 Stelian Pop * Portions Copyright (C) 2006 Vidar Tysse * * 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 #include #include #include #include #include #include #include #include #include "kissdx.h" #include "utils.h" #include "config.h" #define PORT_PCLINK 8000 #define PORT_KML 8888 // Global constants int standard_filemode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; int standard_dir_filemode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; const char *commandserver_filename_prefix = "kissdx-loccmdskt-"; // Global variables option_set_t options; time_t last_time_list_hidden_entries_on = (time_t)0; int is_socket_timed_out = 0; char commandserver_path[L_tmpnam] = ""; char * extended_logfile_format(const char *format) { static char extended_format[1024]; if (options.verbose) { char timestr[32]; time_t current_time = time(NULL); strftime(timestr, sizeof timestr, "%F %T", localtime(¤t_time)); snprintf(extended_format, sizeof extended_format, "[%5d] %s %s", getpid(), timestr, format); } else { strncpy(extended_format, format, sizeof extended_format); } return extended_format; } static void server_shutdown(int signal) { log("%s", "shutting down kissdx..."); // Delete temporary files if (*commandserver_path) unlink(commandserver_path); // Exit normally exit(0); } static void kill_children(int signal) { int pid, preserved_errno = errno; do { do { errno = 0; pid = waitpid(-1, NULL, WNOHANG); } while (pid <= 0 && errno == EINTR); if (pid > 0) logv("Child process [%5d] exited", pid); } while (pid > 0); errno = preserved_errno; } 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; } /* show that we are willing to listen */ if (listen(*sock, 5) < 0) { log("local listen: %s", strerror(errno)); exit(1); } } static void create_socket(int *sock, int port, int type) { struct sockaddr_in sin; int yes = 1; /* get an internet domain socket */ if ((*sock = socket(AF_INET, type, 0)) < 0) { log("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("setsockopt: %s", strerror(errno)); exit(1); } /* complete the socket structure */ memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; sin.sin_port = htons(port); /* bind the socket to the port number */ if (bind(*sock, (struct sockaddr *) &sin, sizeof(sin)) < 0) { log("bind: %s", strerror(errno)); exit(1); } /* everything done for a udp socket */ if (type == SOCK_DGRAM) { return; } /* show that we are willing to listen */ if (listen(*sock, 5) < 0) { log("listen: %s", strerror(errno)); exit(1); } } static void udp_responder (int sd) { struct sockaddr_in sin; socklen_t sinlen = sizeof(sin); char buf[128]; /* complete the socket structure */ memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; sin.sin_port = htons(PORT_PCLINK); if (recvfrom(sd, buf, sizeof(buf), 0, (struct sockaddr*)&sin, &sinlen) < 0) { log("recvfrom: %s", strerror(errno)); return; // exit gracefully } if (strstr(buf, "ARE_YOU_KISS_PCLINK_SERVER?")) { char hostname[255]; logv("Received identity request from %s", inet_ntoa(sin.sin_addr)); // Re-read config file to get at any changes. Keep current configuration if error. config_settings_t new_config; int config_error = parse_config(&new_config); if (!config_error) memcpy(&config, &new_config, sizeof (config)); else show_current_config_Verbose(&config); // Show the old (still current) configuration in verbose mode if (gethostname(hostname, sizeof(hostname)) < 0) { log("gethostname: %s", strerror(errno)); return; // exit gracefully } strcat(hostname, " - kissdx "); strcat(hostname, KISSD_VERSION); strcat(hostname, " "); strcat(hostname, config_error? "(conf!)" : "(OK)"); if (sendto(sd, hostname, strlen(hostname), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0) { log("sendto: %s", strerror(errno)); } } } static 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")) { options.list_hidden_entries = 1; // Save current time to automatically reset options.list_hidden_entries after a timeout period. last_time_list_hidden_entries_on = time(NULL); logv0("Option list_hidden_entries has been turned ON"); } else if (!strcasecmp(token, "OFF")) { options.list_hidden_entries = 0; logv0("Option list_hidden_entries 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"); } // Set up a local domain socket to listen for commands from forked subprocesses. // While we're at it, clear out any commandserver tempfiles from previous killed sessions. static void setup_local_commandserver_socket(int * sd_commandserver_p) { 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; strcpy(commandserver_path, P_tmpdir); if (strlen(commandserver_path) > 0 && commandserver_path[strlen(commandserver_path) - 1] != '/') strcat(commandserver_path, "/"); 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); strcat(fullname, namelist[n]->d_name); unlink(fullname); free(namelist[n]); } free(namelist); } strcat(commandserver_path, commandserver_filename_prefix); strcat(commandserver_path, "XXXXXX"); 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_commandserver_p, commandserver_path, SOCK_STREAM); } static void do_daemon(void) { int sd_server, sd_client; int sd_pclink, sd_kml = -1; int sd_udp_responder; int sd_commandserver; struct sockaddr_in pin; socklen_t addrlen; fd_set set; signal(SIGINT, server_shutdown); signal(SIGCHLD, kill_children); if (options.daemonize) openlog("kissdx", LOG_PID, LOG_DAEMON); log("%s", "starting kissdx..."); create_socket(&sd_pclink, PORT_PCLINK, SOCK_STREAM); create_socket(&sd_udp_responder, PORT_PCLINK, SOCK_DGRAM); if (options.kml_forwarder) create_socket(&sd_kml, PORT_KML, SOCK_STREAM); setup_local_commandserver_socket(&sd_commandserver); again: FD_ZERO(&set); FD_SET(sd_pclink, &set); FD_SET(sd_udp_responder, &set); FD_SET(sd_commandserver, &set); if (options.kml_forwarder) FD_SET(sd_kml, &set); memset(&pin, 0, sizeof(pin)); memset(&addrlen, 0, sizeof(addrlen)); if (select(max(max(max(sd_pclink, sd_kml), sd_udp_responder), sd_commandserver) + 1, &set, NULL, NULL, NULL) < 0) { if (errno == EINTR) goto again; log("select: %s", strerror(errno)); exit(1); } // Automatically reset options.list_hidden_entries after a timeout period. if (config.enablehiddenfilesminutes && last_time_list_hidden_entries_on != (time_t)0) { if (difftime(time(NULL), last_time_list_hidden_entries_on) / 60.0 >= (double)config.enablehiddenfilesminutes) { options.list_hidden_entries = 0; last_time_list_hidden_entries_on = (time_t)0; } } if (FD_ISSET(sd_udp_responder, &set)) { udp_responder(sd_udp_responder); } if (FD_ISSET(sd_commandserver, &set)) { do { sd_client = accept(sd_commandserver, (struct sockaddr *)&pin, &addrlen); } while (sd_client < 0 && errno == EINTR); if (sd_client < 0) { log("local command accept: %s", strerror(errno)); exit(1); } handle_local_command(sd_client); } sd_server = -1; if (FD_ISSET(sd_pclink, &set)) sd_server = sd_pclink; else if (options.kml_forwarder) if (FD_ISSET(sd_kml, &set)) sd_server = sd_kml; if (sd_server != -1) { do { sd_client = accept(sd_server, (struct sockaddr *)&pin, &addrlen); } while (sd_client < 0 && errno == EINTR); if (sd_client < 0) { log("accept: %s", strerror(errno)); exit(1); } switch (fork()) { case -1: log("fork: %s", strerror(errno)); exit(1); case 0: close(sd_server); logv("%s", "KiSS started connection"); if (FD_ISSET(sd_pclink, &set)) handle_request(sd_client); else handle_kmlrequest(sd_client); if (is_socket_timed_out) shutdown(sd_client, SHUT_RDWR); close(sd_client); logv("%s closed connection", is_socket_timed_out? "kissdx shut down and" : "KiSS"); _exit(0); default: close(sd_client); break; } } goto again; /* not reached */ } static void usage(const char *program) { fprintf(stderr, "KiSS PC-Link daemon " KISSD_VERSION "\n\n" "Usage: %s [options]\n" "\nOptions:\n" "\t-a, --all\t\tdo not hide entries starting with .\n" "\t-c, --config=FILE\tpath to the configuration file\n" "\t-d, --daemon\t\tfork into background and log using syslog\n" "\t-h, --help\t\tprint this on-line help\n" "\t-k, --kml\t\tforward KML requests to another URL\n" "\t-v, --verbose\t\tlog every request and response\n", program); } int main(int argc, char *argv[]) { int c; static struct option long_options[] = { { "all", 0, NULL, 'a' }, { "config", 1, NULL, 'c' }, { "daemon", 0, NULL, 'd' }, { "help", 0, NULL, 'h' }, { "kml", 0, NULL, 'k' }, { "verbose", 0, NULL, 'v' }, { NULL, 0, NULL, 0 } }; setlocale (LC_ALL, ""); memset(&options, 0, sizeof (options)); while (1) { c = getopt_long(argc, argv, "ac:dhkv", long_options, NULL); if (c == -1) break; switch (c) { case 'a': options.list_hidden_entries = 1; break; case 'c': options.config_file = optarg; break; case 'd': options.daemonize = 1; break; case 'k': options.kml_forwarder = 1; break; case 'v': options.verbose = 1; break; default: usage(argv[0]); exit(1); break; } } if (parse_config(&config)) exit(1); if (options.daemonize) { int fd; switch(fork()) { case 0: break; case -1: perror("fork"); exit(1); default: _exit(0); } if (setsid() < 0) { perror("setsid"); } switch(fork()) { case 0: break; case -1: perror("fork 2"); exit(1); default: _exit(0); } chdir("/"); // Avoid tying up access to whatever dir we were run from close(0); close(1); close(2); if ((fd = open("/dev/null", O_RDONLY)) != 0) { dup2(fd, 0); close(fd); } if ((fd = open("/dev/null", O_WRONLY)) != 1) { dup2(fd, 1); close(fd); } if ((fd = open("/dev/null", O_WRONLY)) != 2) { dup2(fd, 2); close(fd); } do_daemon(); } else do_daemon(); exit(0); }