/* * kissdx - KiSS PC-Link Daemon eXtended (based on kissd 0.11) * * 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 "kissd.h" void global_param_init(void); void config_file_read(void); char *c_opt = NULL; int a_opt = 0, d_opt = 0, k_opt = 0, v_opt = 0; char audiopath[PATH_MAX] = "\0"; char videopath[PATH_MAX] = "\0"; char picturepath[PATH_MAX] = "\0"; char kmlurl[PATH_MAX] = "\0"; char pretrigger[PATH_MAX] = "\0"; char posttrigger[PATH_MAX] = "\0"; char audiofileextensions[255] = "\0"; char videofileextensions[255] = "\0"; char picturefileextensions[255] = "\0"; #define PORT_PCLINK 8000 #define PORT_KML 8888 static void server_shutdown(int signal) { log("%s", "shutting down kissd..."); exit(0); } static void kill_children(int signal) { while(waitpid(0, NULL, WNOHANG) > 0) ; } void make_lowercase(char *buf) { static const char *upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; static const char *lower = "abcdefghijklmnopqrstuvwxyz"; char *p, *q; for (p = buf; *p != '\0'; p++) if ((q = strchr(upper, *p))) *p = lower[q - upper]; } static int parse_config(void) { FILE *f; char *home, *configpath; char buffer[512]; configpath = c_opt; if (c_opt != NULL) { configpath = c_opt; if ((f = fopen(configpath, "r")) == NULL) { printf("unable to open: %s", configpath); return 1; } } else { if ((home = getenv("HOME")) == NULL) home = "/"; snprintf(buffer, sizeof(buffer), "%s/.kissd.conf", home); configpath = buffer; if ((f = fopen(configpath, "r")) == NULL) { configpath = "/etc/kissd.conf"; if ((f = fopen(configpath, "r")) == NULL) { printf("config file not found!\n"); printf("searched $HOME/.kissd.conf and /etc/kissd.conf\n"); return 1; } } } if (v_opt) printf("Using config file %s\n", configpath); while (fgets(buffer, sizeof(buffer), f) != NULL) { char option[512], value[512]; char *b = buffer; for(; *b == ' ' || *b == '\t' || *b == '\n'; b++); if (b[0] == '#') continue; if (strlen(b) == 0) continue; if (sscanf(b, " %s = %s ", option, value) != 2) { printf("invalid line in config file: %s\n", buffer); return 1; } if (strcmp(option, "audiopath") == 0) { strncpy(audiopath, value, sizeof(audiopath)); audiopath[sizeof(audiopath) - 1] = '\0'; } else if (strcmp(option, "videopath") == 0) { strncpy(videopath, value, sizeof(videopath)); audiopath[sizeof(audiopath) - 1] = '\0'; } else if (strcmp(option, "picturepath") == 0) { strncpy(picturepath, value, sizeof(picturepath)); picturepath[sizeof(picturepath) - 1] = '\0'; } else if (strcmp(option, "kmlurl") == 0) { strncpy(kmlurl, value, sizeof(kmlurl)); kmlurl[sizeof(kmlurl) - 1] = '\0'; } else if (strcmp(option, "pretrigger") == 0) { strncpy(pretrigger, value, sizeof(pretrigger)); pretrigger[sizeof(pretrigger) - 1] = '\0'; } else if (strcmp(option, "posttrigger") == 0) { strncpy(posttrigger, value, sizeof(posttrigger)); posttrigger[sizeof(posttrigger) - 1] = '\0'; } else if (strcasecmp(option, "audiofileextensions") == 0) { strncpy(audiofileextensions, value, sizeof(audiofileextensions)); audiofileextensions[sizeof(audiofileextensions) - 1] = '\0'; make_lowercase(audiofileextensions); } else if (strcasecmp(option, "videofileextensions") == 0) { strncpy(videofileextensions, value, sizeof(videofileextensions)); videofileextensions[sizeof(videofileextensions) - 1] = '\0'; make_lowercase(videofileextensions); } else if (strcasecmp(option, "picturefileextensions") == 0) { strncpy(picturefileextensions, value, sizeof(picturefileextensions)); picturefileextensions[sizeof(picturefileextensions) - 1] = '\0'; make_lowercase(picturefileextensions); } else { printf("unknown option in config file: %s\n", option); return 1; } if (strcmp(option, "kmlurl") && strcmp(option, "audiofileextensions") && strcmp(option, "videofileextensions") && strcmp(option, "picturefileextensions") && value[0] != '/') { printf("paths must be absolute refs in config file: %s\n", value); return 1; } } if (audiopath[0] == '\0') { printf("audiopath not set in config file\n"); return 1; } if (videopath[0] == '\0') { printf("videopath not set in config file\n"); return 1; } if (picturepath[0] == '\0') { printf("picturepath not set in config file\n"); return 1; } if (k_opt && kmlurl[0] == '\0') { printf("kmlurl not set in config file\n"); return 1; } clean_pathname(audiopath); clean_pathname(videopath); clean_pathname(picturepath); fclose(f); return 0; } 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]; log("Received identity request from %s", inet_ntoa(sin.sin_addr)); if (gethostname(hostname, sizeof(hostname)) < 0) { log("gethostname: %s", strerror(errno)); return; // exit gracefully } strcat(hostname, " running kissdx-"); strcat(hostname, KISSD_VERSION); if (sendto(sd, hostname, strlen(hostname), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0) { log("sendto: %s", strerror(errno)); } } } static void do_daemon(void) { int sd_server, sd_client; int sd_pclink, sd_kml = -1; int sd_udp_responder; struct sockaddr_in pin; socklen_t addrlen; fd_set set; signal(SIGINT, server_shutdown); signal(SIGCHLD, kill_children); if (d_opt) openlog("kissd", LOG_PID, LOG_DAEMON); log("%s", "starting kissd..."); create_socket(&sd_pclink, PORT_PCLINK, SOCK_STREAM); create_socket(&sd_udp_responder, PORT_PCLINK, SOCK_DGRAM); if (k_opt) create_socket(&sd_kml, PORT_KML, SOCK_STREAM); again: FD_ZERO(&set); FD_SET(sd_pclink, &set); FD_SET(sd_udp_responder, &set); if (k_opt) FD_SET(sd_kml, &set); memset(&pin, 0, sizeof(pin)); memset(&addrlen, 0, sizeof(addrlen)); if (select(max(max(sd_pclink, sd_kml), sd_udp_responder) + 1, &set, NULL, NULL, NULL) < 0) { if (errno == EINTR) goto again; log("select: %s", strerror(errno)); exit(1); } if (FD_ISSET(sd_udp_responder, &set)) { udp_responder(sd_udp_responder); goto again; } if (FD_ISSET(sd_pclink, &set)) sd_server = sd_pclink; else sd_server = sd_kml; if ((sd_client = accept(sd_server, (struct sockaddr *)&pin, &addrlen)) < 0) { log("accept: %s", strerror(errno)); exit(1); } switch (fork()) { case -1: log("fork: %s", strerror(errno)); exit(1); case 0: close(sd_server); if (v_opt) log("%s", "KiSS started connection"); if (FD_ISSET(sd_pclink, &set)) handle_request(sd_client); else handle_kmlrequest(sd_client); if (v_opt) log("%s", "KiSS closed connection"); 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 [OPTION]\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 } }; while (1) { c = getopt_long(argc, argv, "ac:dhkv", long_options, NULL); if (c == -1) break; switch (c) { case 'a': a_opt = 1; break; case 'c': c_opt = optarg; break; case 'd': d_opt = 1; break; case 'k': k_opt = 1; break; case 'v': v_opt = 1; break; default: usage(argv[0]); exit(1); break; } } if (parse_config()) exit(1); if (d_opt) { switch(fork()) { case 0: close(0); close(1); close(2); do_daemon(); break; case -1: perror("fork"); exit(1); } } else do_daemon(); exit(0); }