/* upnpd project * * * upnpd device * * This file is part of upnpd. * * * Portions of the code from the MiniUPnP project: * * Copyright (c) 2006-2007, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "upnpglobalvars.h" #include "upnphttp.h" #include "upnpdescgen.h" #include "upnpdpath.h" #include "getifaddr.h" #include "upnpsoap.h" #include "options.h" #include "minissdp.h" #include "upnpdtypes.h" #include "daemonize.h" #include "upnpevents.h" #include "config.h" /*OpenAndConfHTTPSocket() * setup the socket used to handle incoming HTTP connections. */ static int OpenAndConfHTTPSocket (unsigned short port) { int s; int i = 1; struct sockaddr_in listenname; if ((s = socket (PF_INET, SOCK_STREAM, 0)) < 0) { syslog (LOG_ERR, "socket(http): %m"); return -1; } if (setsockopt (s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof (i)) < 0) { syslog (LOG_WARNING, "setsockopt(http, SO_REUSEADDR): %m"); } memset (&listenname, 0, sizeof (struct sockaddr_in)); listenname.sin_family = AF_INET; listenname.sin_port = htons (port); listenname.sin_addr.s_addr = htonl (INADDR_ANY); if (bind (s, (struct sockaddr *) &listenname, sizeof (struct sockaddr_in)) < 0) { syslog (LOG_ERR, "bind(http): %m"); close (s); return -1; } if (listen (s, 6) < 0) { syslog (LOG_ERR, "listen(http): %m"); close (s); return -1; } return s; } /* Handler for the SIGTERM signal (kill) * SIGINT is also handled */ static void sigterm (int sig) { signal (sig, SIG_IGN); /* Ignore this signal while we are quitting */ syslog (LOG_NOTICE, "received signal %d, good-bye", sig); quitting = 1; } /* record the startup time, for returning uptime */ static void set_startup_time (void) { startup_time = time (NULL); } /* parselanaddr() * parse address with mask * ex: 192.168.1.1/24 * return value : * 0 : ok * -1 : error */ static int parselanaddr (struct lan_addr_s *lan_addr, const char *str) { const char *p; int nbits = 24; int n; p = str; while (*p && *p != '/' && !isspace (*p)) p++; n = p - str; if (*p == '/') { nbits = atoi (++p); while (*p && !isspace (*p)) p++; } if (n > 15) { syslog (LOG_WARNING, "Error parsing address/mask %s\n", str); return -1; } memcpy (lan_addr->str, str, n); lan_addr->str[n] = '\0'; if (!inet_aton (lan_addr->str, &lan_addr->addr)) { syslog (LOG_WARNING, "Error parsing address/mask: %s\n", str); return -1; } lan_addr->mask.s_addr = htonl (nbits ? (0xffffffff << (32 - nbits)) : 0); return 0; } void getfriendlyname (char *buf, int len) { char *dot = NULL; char *hn = calloc (1, 256); int off; if (gethostname (hn, 256) == 0) { strncpy (buf, hn, len - 1); buf[len-1] = '\0'; dot = strchr (buf, '.'); if (dot) *dot = '\0'; } else { strcpy (buf, "Unknown"); } free (hn); off = strlen (buf); off += snprintf (buf + off, len - off, ": "); } /*skpi spaces configuration file * 2) read command line arguments * 3) daemonize * 4) open syslog * 5) check and write pid file * 6) set startup time stamp;;eo * 7) compute presentation URL * 8) set signal handlers */ static int init (int argc, char * *argv) { int i; int pid = 0; int debug_flag = 0; int options_flag = 0; struct sigaction sa; char ext_ip_addr[INET_ADDRSTRLEN] = { '\0' }; const char *presurl = 0; const char *optionsfile = "/usr/local/etc/upnpd.conf"; char mac_str[13]; openlog ("upnpd", LOG_ODELAY, LOG_USER); /* first check if "-f" option is used */ for (i = 2; i < argc; i++) { if (0 == strcmp (argv[i - 1], "-f")) { optionsfile = argv[i]; options_flag = 1; break; } } /* set up uuid based on mac address */ if (getsyshwaddr (mac_str, sizeof (mac_str)) < 0) { syslog (LOG_WARNING, "No MAC address found. Falling back to generic UUID.\n"); strcpy (mac_str, "554e4b4e4f57"); } strcpy (uuidvalue + 5, "4d696e69-444c-164e-9d41-"); strncat (uuidvalue, mac_str, 12); getfriendlyname (friendly_name, FRIENDLYNAME_MAX_LEN); runtime_vars.port = -1; runtime_vars.notify_interval = 3; /* seconds between SSDP announces */ /* read options file first since * command line arguments have final say */ if (readoptionsfile (optionsfile) < 0) { /* only error if file exists or using -f */ if (access (optionsfile, F_OK) == 0 || options_flag) syslog (LOG_WARNING, "Error reading configuration file.\n"); } else { for (i = 0; i < num_options; i++) { switch (ary_options[i].id) { case UPNPIFNAME: if (getifaddr (ary_options[i].value, ext_ip_addr, INET_ADDRSTRLEN) >= 0) { if (*ext_ip_addr && parselanaddr (&lan_addr[n_lan_addr], ext_ip_addr) == 0) n_lan_addr++; } else fprintf (stderr, "Interface %s not found, ignoring.\n", ary_options[i].value); break; case UPNPLISTENING_IP: if (n_lan_addr < MAX_LAN_ADDR) { if (parselanaddr (&lan_addr[n_lan_addr], ary_options[i].value) == 0) n_lan_addr++; } else { fprintf (stderr, "Too many listening ips (max: %d), ignoring %s\n", MAX_LAN_ADDR, ary_options[i].value); } break; case UPNPPORT: runtime_vars.port = atoi (ary_options[i].value); break; case UPNPPRESENTATIONURL: presurl = ary_options[i].value; break; case UPNPNOTIFY_INTERVAL: runtime_vars.notify_interval = atoi (ary_options[i].value); break; case UPNPSERIAL: strncpy (serialnumber, ary_options[i].value, SERIALNUMBER_MAX_LEN); serialnumber[SERIALNUMBER_MAX_LEN - 1] = '\0'; break; case UPNPMODEL_NAME: strncpy (modelname, ary_options[i].value, MODELNAME_MAX_LEN); modelname[MODELNAME_MAX_LEN - 1] = '\0'; break; case UPNPMODEL_NUMBER: strncpy (modelnumber, ary_options[i].value, MODELNUMBER_MAX_LEN); modelnumber[MODELNUMBER_MAX_LEN - 1] = '\0'; break; case UPNPFRIENDLYNAME: strncpy (friendly_name, ary_options[i].value, FRIENDLYNAME_MAX_LEN); friendly_name[FRIENDLYNAME_MAX_LEN - 1] = '\0'; break; default: fprintf (stderr, "Unknown option in file %s\n", optionsfile); } } } /* command line arguments processing */ for (i = 1; i < argc; i++) { if (argv[i][0] != '-') { fprintf (stderr, "Unknown option: %s\n", argv[i]); } else if (strcmp (argv[i], "--help") == 0) { runtime_vars.port = 0; break; } else switch (argv[i][1]) { case 't': if (i + 1 < argc) runtime_vars.notify_interval = atoi (argv[++i]); else fprintf (stderr, "Option -%c takes one argument.\n", argv[i][1]); break; case 's': if (i + 1 < argc) strncpy (serialnumber, argv[++i], SERIALNUMBER_MAX_LEN); else fprintf (stderr, "Option -%c takes one argument.\n", argv[i][1]); serialnumber[SERIALNUMBER_MAX_LEN - 1] = '\0'; break; case 'm': if (i + 1 < argc) strncpy (modelnumber, argv[++i], MODELNUMBER_MAX_LEN); else fprintf (stderr, "Option -%c takes one argument.\n", argv[i][1]); modelnumber[MODELNUMBER_MAX_LEN - 1] = '\0'; break; case 'p': if (i + 1 < argc) runtime_vars.port = atoi (argv[++i]); else fprintf (stderr, "Option -%c takes one argument.\n", argv[i][1]); break; case 'P': if (i + 1 < argc) pidfilename = argv[++i]; else fprintf (stderr, "Option -%c takes one argument.\n", argv[i][1]); break; case 'd': debug_flag = 1; break; case 'w': if (i + 1 < argc) presurl = argv[++i]; else fprintf (stderr, "Option -%c takes one argument.\n", argv[i][1]); break; case 'a': if (i + 1 < argc) { int address_already_there = 0; int j; i++; for (j = 0; j < n_lan_addr; j++) { struct lan_addr_s tmpaddr; parselanaddr (&tmpaddr, argv[i]); if (0 == strcmp (lan_addr[j].str, tmpaddr.str)) address_already_there = 1; } if (address_already_there) break; if (n_lan_addr < MAX_LAN_ADDR) { if (parselanaddr (&lan_addr[n_lan_addr], argv[i]) == 0) n_lan_addr++; } else { fprintf (stderr, "Too many listening ips (max: %d), ignoring %s\n", MAX_LAN_ADDR, argv[i]); } } else fprintf (stderr, "Option -%c takes one argument.\n", argv[i][1]); break; case 'i': if (i + 1 < argc) { int address_already_there = 0; int j; i++; if (getifaddr (argv[i], ext_ip_addr, INET_ADDRSTRLEN) < 0) { fprintf (stderr, "Network interface '%s' not found.\n", argv[i]); exit (-1); } for (j = 0; j < n_lan_addr; j++) { struct lan_addr_s tmpaddr; parselanaddr (&tmpaddr, ext_ip_addr); if (0 == strcmp (lan_addr[j].str, tmpaddr.str)) address_already_there = 1; } if (address_already_there) break; if (n_lan_addr < MAX_LAN_ADDR) { if (parselanaddr (&lan_addr[n_lan_addr], ext_ip_addr) == 0) n_lan_addr++; } else { printf ("Too many listening ips (max: %d), ignoring %s\n", MAX_LAN_ADDR, argv[i]); } } else { printf ("Option -%c takes one argument.\n", argv[i][1]); } break; case 'f': i++; /* discarding, the config file is already read */ break; case 'h': runtime_vars.port = 0; // triggers help display break; case 'V': printf ("Version " UPNPD_VERSION "\n"); exit (0); break; default: fprintf (stderr, "Unknown option: %s\n", argv[i]); } } /* If no IP was specified, try to detect one */ if (n_lan_addr < 1) { if ((getsysaddr (ext_ip_addr, INET_ADDRSTRLEN) < 0) && (getifaddr ("eth0", ext_ip_addr, INET_ADDRSTRLEN) < 0) && (getifaddr ("eth1", ext_ip_addr, INET_ADDRSTRLEN) < 0)) { syslog (LOG_WARNING, "No IP address automatically detected!\n"); } if (*ext_ip_addr && parselanaddr (&lan_addr[n_lan_addr], ext_ip_addr) == 0) { n_lan_addr++; } } if ((n_lan_addr == 0) || (runtime_vars.port <= 0)) { fprintf (stderr, "Usage:\n\t" "%s [-d] [-f config_file]\n" "\t\t[-a listening_ip] [-p port]\n" /*"[-l logfile] " not functionnal */ "\t\t[-s serial] [-m model_number] \n" "\t\t[-t notify_interval] [-P pid_filename]\n" "\t\t[-w url] [-R] [-V] [-h]\n" "\nNotes:\n\tNotify interval is in seconds. Default is 895 seconds.\n" "\tDefault pid file is %s.\n" "\tWith -d upnpd will run in debug mode (not daemonize).\n" "\t-w sets the presentation url. Default is http address on port 80\n" "\t-h displays this text\n" "\t-R forces a full rescan\n" "\t-V print the version number\n\n" "\tShould check if the configuration file is available \n", argv[0], pidfilename); return 1; } if (debug_flag) { pid = getpid (); } else { } if (checkforrunning (pidfilename) < 0) { syslog (LOG_ERR, "upnpd is already running. EXITING"); return 1; } set_startup_time (); /* presentation url */ if (presurl) { strncpy (presentationurl, presurl, PRESENTATIONURL_MAX_LEN); presentationurl[PRESENTATIONURL_MAX_LEN - 1] = '\0'; } else { snprintf (presentationurl, PRESENTATIONURL_MAX_LEN, "http://%s:%d/", lan_addr[0].str, runtime_vars.port); } /* set signal handler */ signal (SIGCLD, SIG_IGN); memset (&sa, 0, sizeof (struct sigaction)); sa.sa_handler = sigterm; if (sigaction (SIGTERM, &sa, NULL)) { syslog (LOG_WARNING, "Failed to set %s handler. EXITING", "SIGTERM"); return 1; } if (sigaction (SIGINT, &sa, NULL)) { syslog (LOG_WARNING, "Failed to set %s handler. EXITING", "SIGINT"); return 1; } if (signal (SIGPIPE, SIG_IGN) == SIG_ERR) { syslog (LOG_WARNING, "Failed to ignore SIGPIPE signals"); } writepidfile (pidfilename, pid); return 0; } /* === main === */ /* process HTTP or SSDP requests */ int main (int argc, char * *argv) { int i, send = 0, loop = 0; int sudp = -1, shttpl = -1; int snotify[MAX_LAN_ADDR]; LIST_HEAD (httplisthead, upnphttp) upnphttphead; struct upnphttp *e = 0; struct upnphttp *next; fd_set readset; /* for select() */ fd_set writeset; struct timeval timeout, timeofday, lastnotifytime = { 0, 0 }; int max_fd = -1; if (init (argc, argv) != 0) { syslog (LOG_WARNING, "init error"); return 1; } LIST_INIT (&upnphttphead); sudp = OpenAndConfSSDPReceiveSocket (); if (sudp < 0) { syslog (LOG_ERR, "Failed to open socket for receiving SSDP. EXITING\n"); } /* open socket for HTTP connections. Listen on the 1st LAN address */ shttpl = OpenAndConfHTTPSocket (runtime_vars.port); if (shttpl < 0) { syslog (LOG_ERR, "Failed to open socket for HTTP. EXITING\n"); } else { syslog (LOG_WARNING, "HTTP Listening on port %d\n", runtime_vars.port); } /* open socket for sending notifications */ if (OpenAndConfSSDPNotifySockets (snotify) < 0) { syslog (LOG_ERR, "Failed to open sockets for sending SSDP notify messages. EXITING"); } else { syslog (LOG_ERR, "succeeded to open sockets for sending SSDP notify messages."); } /* main loop */ while (!quitting) { /* Check if we need to send SSDP NOTIFY messages and do it if * needed */ if (gettimeofday (&timeofday, 0) < 0) { syslog (LOG_WARNING, "gettimeofday(): %s\n", strerror (errno)); timeout.tv_sec = runtime_vars.notify_interval; timeout.tv_usec = 0; } else { /* the comparaison is not very precise but who cares ? */ if (timeofday.tv_sec >= (lastnotifytime.tv_sec + runtime_vars.notify_interval)) { send++; SendSSDPNotifies2 (snotify, (unsigned short) runtime_vars.port, (runtime_vars.notify_interval << 1) + 10); memcpy (&lastnotifytime, &timeofday, sizeof (struct timeval)); timeout.tv_sec = runtime_vars.notify_interval; timeout.tv_usec = 0; } else { timeout.tv_sec = lastnotifytime.tv_sec + runtime_vars.notify_interval - timeofday.tv_sec; if (timeofday.tv_usec > lastnotifytime.tv_usec) { timeout.tv_usec = 1000000 + lastnotifytime.tv_usec - timeofday.tv_usec; timeout.tv_sec--; } else { timeout.tv_usec = lastnotifytime.tv_usec - timeofday.tv_usec; } } } /* select open sockets (SSDP, HTTP listen, and all HTTP soap sockets) */ FD_ZERO (&readset); if (sudp >= 0) { FD_SET (sudp, &readset); max_fd = MAX (max_fd, sudp); } if (shttpl >= 0) { FD_SET (shttpl, &readset); max_fd = MAX (max_fd, shttpl); } i = 0; /* active HTTP connections count */ for (e = upnphttphead.lh_first; e != NULL; e = e->entries.le_next) { if ((e->socket >= 0) && (e->state <= 2)) { FD_SET (e->socket, &readset); max_fd = MAX (max_fd, e->socket); i++; } } #ifdef DEBUG /* for debug */ if (i > 1) { syslog (LOG_WARNING, "%d active incoming HTTP connections\n", i); } #endif FD_ZERO (&writeset); upnpevents_selectfds (&readset, &writeset, &max_fd); if (select (max_fd + 1, &readset, &writeset, 0, &timeout) < 0) { if (quitting) goto shutdown; syslog (LOG_ERR, "select(all): %s\n", strerror (errno)); syslog (LOG_WARNING, "Failed to select open sockets. EXITING\n"); } upnpevents_processfds (&readset, &writeset); /* process SSDP packets */ if (sudp >= 0 && FD_ISSET (sudp, &readset)) { ProcessSSDPRequest (sudp, (unsigned short) runtime_vars.port); } /* process active HTTP connections */ for (e = upnphttphead.lh_first; e != NULL; e = e->entries.le_next) { if ((e->socket >= 0) && (e->state <= 2) && (FD_ISSET (e->socket, &readset))) { Process_upnphttp (e); syslog (LOG_WARNING, "processing over"); } } /* process incoming HTTP connections */ if (shttpl >= 0 && FD_ISSET (shttpl, &readset)) { int shttp; socklen_t clientnamelen; struct sockaddr_in clientname; clientnamelen = sizeof (struct sockaddr_in); shttp = accept (shttpl, (struct sockaddr *) &clientname, &clientnamelen); if (shttp < 0) { syslog (LOG_WARNING, "accept(http): %s\n", strerror (errno)); } else { struct upnphttp *tmp = 0; syslog (LOG_WARNING, "HTTP connection from %s:%d\n", inet_ntoa (clientname.sin_addr), ntohs (clientname.sin_port)); /* Create a new upnphttp object and add it to * the active upnphttp object list */ tmp = New_upnphttp (shttp); if (tmp) { tmp->clientaddr = clientname.sin_addr; LIST_INSERT_HEAD (&upnphttphead, tmp, entries); } else { syslog (LOG_WARNING, "New_upnphttp() failed\n"); close (shttp); } } } /* delete finished HTTP connections */ for (e = upnphttphead.lh_first; e != NULL;) { next = e->entries.le_next; if (e->state >= 100) { LIST_REMOVE (e, entries); Delete_upnphttp (e); } e = next; } } shutdown: /* close out open sockets */ while (upnphttphead.lh_first != NULL) { e = upnphttphead.lh_first; LIST_REMOVE (e, entries); Delete_upnphttp (e); } if (sudp >= 0) close (sudp); if (shttpl >= 0) close (shttpl); if (SendSSDPGoodbye (snotify, n_lan_addr) < 0) { syslog (LOG_WARNING, "Failed to broadcast good-bye notifications\n"); } else { syslog (LOG_WARNING, "Sent good-bye notifications\n"); } for (i = 0; i < n_lan_addr; i++) close (snotify[i]); if (unlink (pidfilename) < 0) { syslog (LOG_WARNING, "Failed to remove pidfile %s: %s\n", pidfilename, strerror (errno)); } freeoptions (); exit (EXIT_SUCCESS); }