minissdpd-1.5.20211105/Changelog.txt010064400017500000024000000072131414106654500160660ustar00nanardstaff$Id: Changelog.txt,v 1.53 2021/11/04 22:57:01 nanard Exp $ 2021/01/15: Add submit_to_minissdpd.py 2020/06/06: define NO_BACKGROUND_NO_PIDFILE for a systemd friendly mode send the startup M-SEARCH message on the right interface(s) 2018/12/18: updateDevice() remove element from the list when realloc fails 2018/05/08: fix test shell scripts for *BSD 2018/02/23: Fix build with IPv6 disabled and SSDP_LISTEN_ON_SPECIFIC_ADDR enabled 2018/02/03: Properly parse several requests read() at once Ignore the version of devices while answering to requests 2016/11/11: Fix for Solaris build 2016/03/01: Fix broken overflow test (p+l > buf+n) thanks to Salva Peiro VERSION 1.5: 2016/01/13: add "notification" mode (command 5) 2015/08/06: disable multicast loop add -f command line option to filter for a specific device type VERSION 1.4: 2015/08/06: added command 0 (version) 2015/07/21: set multicast TTL to 2 by default and configurable 2015/05/27: support larger buffer size (useful for type 3 requests) VERSION 1.3: 2014/12/05: clean up select call() fix non blocking write to sockets 2014/12/04: Fixes removing of devices on ssdp:byebye handle ssdp:update messages 2014/11/28: revert "listen on only 1 IPv4 if only 1 interface is specified" because it prevents broadcast messages to be received Change the list of LAN addresses/interfaces (code taken from miniupnpd) Check that the peer is from a LAN for each SSDP packet 2014/11/06: listen on only 1 IPv4 if only 1 interface is specified also when ENABLE_IPV6 is not defined 2014/09/06: freebsd-glue for Debian/kFreeBSD use LDFLAGS when linking binary 2014/05/01: listen on only 1 IPv4 if only 1 interface is specified 2014/02/03: silently ignore EAGAIN, EWOULDBLOCK, EINTR of recv calls Discover devices on the network at startup 2013/08/19: Translate README in english 2012/09/27: Rename all include guards to not clash with C99 (7.1.3 Reserved identifiers). VERSION 1.2: 2012/05/21: Clean signal handling Set sockets non blocking 2012/05/18: Improve ProcessInterfaceWatch() under BSD. 2012/05/15: Improve ProcessInterfaceWatch() under linux. 2012/05/02: Clean CLFAGS in Makefile. Remove a few signed/unsigned compares 2012/04/09: Added -ansi to compilation flags. Handle ssdp:update messages and update logging 2012/01/02: Install manpage. Fix installation under Mac OS X. 2011/10/07: unlink unix socket before binding. set SO_REUSEADDR on SSDP socket. daemonize after init VERSION 1.1: 2011/07/30: fixes. More overflow checks 2011/07/29: added a lot of buffer overflow checks. Check malloc() failure, etc. Better cleanup in case of crash at start. network interface watch to add/drop multicast membership when the interface get live. 2011/06/18: Starting to add support for UPnP Device Architecture v1.1 2011/05/23: Added IPv6 support. -i option now understands interface names as well as addresses. VERSION 1.0: 2008/10/07: added codelength.h Fixing response to M-SEARCH Doc update 2008/10/06: UPnP server support (answering M-SEARCH) 2008/10/04: listening on several interfaces. 2008/10/01: use of daemon() instead of home made daemonize. 2007/12/19: added uuid in responses 3 types of requests supported. preventing buffer overflow 2007/12/18: It is now possible to change the location of both pid file and unix socket. 2007/10/08: Added a man page 2007/09/27: Support for install in different location $ PREFIX=... make install 2007/09/23: added a script for use in /etc/init.d improved Makefile creating /var/run/minissdpd.pid adding synthetic messages for new devices/removed devices 2007/09/19: Take SSDP announce packets lifetime into account. minissdpd-1.5.20211105/daemonize.c010064400017500000024000000041351326716305100155530ustar00nanardstaff/* $Id: daemonize.c,v 1.14 2018/04/22 19:36:58 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include #include #include #include #include #include #include #include "daemonize.h" #include "config.h" #ifndef USE_DAEMON int daemonize(void) { int pid, i; switch(fork()) { /* fork error */ case -1: perror("fork()"); exit(1); /* child process */ case 0: /* obtain a new process group */ if( (pid = setsid()) < 0) { perror("setsid()"); exit(1); } /* close all descriptors */ for (i=getdtablesize();i>=0;--i) close(i); i = open("/dev/null", O_RDWR); /* open stdin */ dup(i); /* stdout */ dup(i); /* stderr */ umask(027); chdir("/"); /* chdir to /tmp ? */ return pid; /* parent process */ default: exit(0); } } #endif int writepidfile(const char * fname, int pid) { char pidstring[16]; int pidstringlen; int pidfile; if(!fname || fname[0] == '\0') return -1; if( (pidfile = open(fname, O_WRONLY|O_CREAT, 0644)) < 0) { syslog(LOG_ERR, "Unable to open pidfile for writing %s: %m", fname); return -1; } pidstringlen = snprintf(pidstring, sizeof(pidstring), "%d\n", pid); if(pidstringlen <= 0) { syslog(LOG_ERR, "Unable to write to pidfile %s: snprintf(): FAILED", fname); close(pidfile); return -1; } else { if(write(pidfile, pidstring, pidstringlen) < 0) syslog(LOG_ERR, "Unable to write to pidfile %s: %m", fname); } close(pidfile); return 0; } int checkforrunning(const char * fname) { char buffer[64]; int pidfile; pid_t pid; if(!fname || fname[0] == '\0') return -1; if( (pidfile = open(fname, O_RDONLY)) < 0) return 0; memset(buffer, 0, 64); if(read(pidfile, buffer, 63)) { if( (pid = atol(buffer)) > 0) { if(!kill(pid, 0)) { close(pidfile); return -2; } } } close(pidfile); return 0; } minissdpd-1.5.20211105/listifaces.c010064400017500000024000000053501246562410600157300ustar00nanardstaff/* $Id: listifaces.c,v 1.7 2015/02/08 08:51:54 nanard Exp $ */ /* (c) 2006-2015 Thomas BERNARD * http://miniupnp.free.fr/ http://miniupnp.tuxfamily.org/ */ #include #include #include #include #include #include #include #include #include #include "upnputils.h" /* hexdump */ void printhex(const unsigned char * p, int n) { int i; while(n>0) { for(i=0; i<16; i++) printf("%02x ", p[i]); printf("| "); for(i=0; i<16; i++) { putchar((p[i]>=32 && p[i]<127)?p[i]:'.'); } printf("\n"); p+=16; n -= 16; } } /* List network interfaces */ void listifaces(void) { struct ifconf ifc; char * buf = NULL; int buflen; int s, i; int j; char saddr[256/*INET_ADDRSTRLEN*/]; #ifdef __linux__ buflen = sizeof(struct ifreq)*10; #else buflen = 0; #endif /*s = socket(PF_INET, SOCK_DGRAM, 0);*/ s = socket(AF_INET, SOCK_DGRAM, 0); do { char * tmp; #ifdef __linux__ buflen += buflen; #endif if(buflen > 0) { tmp = realloc(buf, buflen); if(!tmp) { fprintf(stderr, "error allocating %d bytes.\n", buflen); close(f); free(buf); return; } buf = tmp; } ifc.ifc_len = buflen; ifc.ifc_buf = (caddr_t)buf; if(ioctl(s, SIOCGIFCONF, &ifc) < 0) { perror("ioctl"); close(s); free(buf); return; } printf("buffer length=%d - buffer used=%d - sizeof(struct ifreq)=%d\n", buflen, ifc.ifc_len, (int)sizeof(struct ifreq)); printf("IFNAMSIZ=%d ", IFNAMSIZ); printf("sizeof(struct sockaddr)=%d sizeof(struct sockaddr_in)=%d\n", (int)sizeof(struct sockaddr), (int)sizeof(struct sockaddr_in) ); #ifndef __linux__ if(buflen == 0) buflen = ifc.ifc_len; else break; } while(1); #else } while(buflen <= ifc.ifc_len); #endif printhex((const unsigned char *)ifc.ifc_buf, ifc.ifc_len); printf("off index fam name address\n"); for(i = 0, j = 0; iifr_addr))->sin_addr), saddr, sizeof(saddr));*/ saddr[0] = '\0'; /* inet_ntop(ifrp->ifr_addr.sa_family, &(ifrp->ifr_addr.sa_data[2]), saddr, sizeof(saddr)); */ sockaddr_to_string(&ifrp->ifr_addr, saddr, sizeof(saddr)); printf("0x%03x %2d %2d %-16s %s\n", i, j, ifrp->ifr_addr.sa_family, ifrp->ifr_name, saddr); /*ifrp->ifr_addr.sa_len is only available on BSD */ #ifdef __linux__ i += sizeof(struct ifreq); #else if(ifrp->ifr_addr.sa_len == 0) break; i += IFNAMSIZ + ifrp->ifr_addr.sa_len; #endif } free(buf); close(s); } int main(int argc, char * * argv) { (void)argc; (void)argv; listifaces(); return 0; } minissdpd-1.5.20211105/minissdpd.c010064400017500000024000001333101414106654500155720ustar00nanardstaff/* $Id: minissdpd.c,v 1.61 2021/11/04 23:27:28 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * (c) 2007-2021 Thomas Bernard * website : http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* for chmod : */ #include /* unix sockets */ #include /* for getpwnam() and getgrnam() */ #if 0 #include #include #endif /* LOG_PERROR does not exist on Solaris */ #ifndef LOG_PERROR #define LOG_PERROR 0 #endif /* LOG_PERROR */ #include "getifaddr.h" #include "upnputils.h" #include "openssdpsocket.h" #include "daemonize.h" #include "codelength.h" #include "ifacewatch.h" #include "minissdpdtypes.h" #include "asyncsendto.h" #define SET_MAX(max, x) if((x) > (max)) (max) = (x) #ifndef MIN #define MIN(x,y) (((x)<(y))?(x):(y)) #endif /* current request management structure */ struct reqelem { int socket; int is_notify; /* has subscribed to notifications */ LIST_ENTRY(reqelem) entries; unsigned char * output_buffer; int output_buffer_offset; int output_buffer_len; }; /* device data structures */ struct header { const char * p; /* string pointer */ int l; /* string length */ }; #define HEADER_NT 0 #define HEADER_USN 1 #define HEADER_LOCATION 2 struct device { struct device * next; time_t t; /* validity time */ struct header headers[3]; /* NT, USN and LOCATION headers */ char data[]; }; /* Services stored for answering to M-SEARCH */ struct service { char * st; /* Service type */ char * usn; /* Unique identifier */ char * server; /* Server string */ char * location; /* URL */ LIST_ENTRY(service) entries; }; LIST_HEAD(servicehead, service) servicelisthead; #define NTS_SSDP_ALIVE 1 #define NTS_SSDP_BYEBYE 2 #define NTS_SSDP_UPDATE 3 /* request types */ enum request_type { MINISSDPD_GET_VERSION = 0, MINISSDPD_SEARCH_TYPE = 1, MINISSDPD_SEARCH_USN = 2, MINISSDPD_SEARCH_ALL = 3, MINISSDPD_SUBMIT = 4, MINISSDPD_NOTIF = 5 }; /* discovered device list kept in memory */ struct device * devlist = 0; /* bootid and configid */ unsigned int upnp_bootid = 1; unsigned int upnp_configid = 1337; /* LAN interfaces/addresses */ struct lan_addr_list lan_addrs; /* connected clients */ LIST_HEAD(reqstructhead, reqelem) reqlisthead; /* functions prototypes */ #define NOTIF_NEW 1 #define NOTIF_UPDATE 2 #define NOTIF_REMOVE 3 static void sendNotifications(int notif_type, const struct device * dev, const struct service * serv); /* functions */ /* parselanaddr() * parse address with mask * ex: 192.168.1.1/24 or 192.168.1.1/255.255.255.0 * * Can also use the interface name (ie eth0) * * return value : * 0 : ok * -1 : error */ static int parselanaddr(struct lan_addr_s * lan_addr, const char * str) { const char * p; int n; char tmp[16]; memset(lan_addr, 0, sizeof(struct lan_addr_s)); p = str; while(*p && *p != '/' && !isspace(*p)) p++; n = p - str; if(!isdigit(str[0]) && n < (int)sizeof(lan_addr->ifname)) { /* not starting with a digit : suppose it is an interface name */ memcpy(lan_addr->ifname, str, n); lan_addr->ifname[n] = '\0'; if(getifaddr(lan_addr->ifname, lan_addr->str, sizeof(lan_addr->str), &lan_addr->addr, &lan_addr->mask) < 0) goto parselan_error; /*printf("%s => %s\n", lan_addr->ifname, lan_addr->str);*/ } else { if(n>15) goto parselan_error; memcpy(lan_addr->str, str, n); lan_addr->str[n] = '\0'; if(!inet_aton(lan_addr->str, &lan_addr->addr)) goto parselan_error; } if(*p == '/') { const char * q = ++p; while(*p && isdigit(*p)) p++; if(*p=='.') { /* parse mask in /255.255.255.0 format */ while(*p && (*p=='.' || isdigit(*p))) p++; n = p - q; if(n>15) goto parselan_error; memcpy(tmp, q, n); tmp[n] = '\0'; if(!inet_aton(tmp, &lan_addr->mask)) goto parselan_error; } else { /* it is a /24 format */ int nbits = atoi(q); if(nbits > 32 || nbits < 0) goto parselan_error; lan_addr->mask.s_addr = htonl(nbits ? (0xffffffffu << (32 - nbits)) : 0); } } else if(lan_addr->mask.s_addr == 0) { /* by default, networks are /24 */ lan_addr->mask.s_addr = htonl(0xffffff00u); } #ifdef ENABLE_IPV6 if(lan_addr->ifname[0] != '\0') { lan_addr->index = if_nametoindex(lan_addr->ifname); if(lan_addr->index == 0) fprintf(stderr, "Cannot get index for network interface %s", lan_addr->ifname); } else { fprintf(stderr, "Error: please specify LAN network interface by name instead of IPv4 address : %s\n", str); return -1; } #endif /* ENABLE_IPV6 */ return 0; parselan_error: fprintf(stderr, "Error parsing address/mask (or interface name) : %s\n", str); return -1; } static int write_buffer(struct reqelem * req) { if(req->output_buffer && req->output_buffer_len > 0) { int n = write(req->socket, req->output_buffer + req->output_buffer_offset, req->output_buffer_len); if(n >= 0) { req->output_buffer_offset += n; req->output_buffer_len -= n; } else if(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) { return 0; } return n; } else { return 0; } } static int add_to_buffer(struct reqelem * req, const unsigned char * data, int len) { unsigned char * tmp; if(req->output_buffer_offset > 0) { memmove(req->output_buffer, req->output_buffer + req->output_buffer_offset, req->output_buffer_len); req->output_buffer_offset = 0; } tmp = realloc(req->output_buffer, req->output_buffer_len + len); if(tmp == NULL) { syslog(LOG_ERR, "%s: failed to allocate %d bytes", __func__, req->output_buffer_len + len); return -1; } req->output_buffer = tmp; memcpy(req->output_buffer + req->output_buffer_len, data, len); req->output_buffer_len += len; return len; } static int write_or_buffer(struct reqelem * req, const unsigned char * data, int len) { if(write_buffer(req) < 0) return -1; if(req->output_buffer && req->output_buffer_len > 0) { return add_to_buffer(req, data, len); } else { int n = write(req->socket, data, len); if(n == len) return len; if(n < 0) { if(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) { n = add_to_buffer(req, data, len); if(n < 0) return n; } else { return n; } } else { n = add_to_buffer(req, data + n, len - n); if(n < 0) return n; } } return len; } static const char * nts_to_str(int nts) { switch(nts) { case NTS_SSDP_ALIVE: return "ssdp:alive"; case NTS_SSDP_BYEBYE: return "ssdp:byebye"; case NTS_SSDP_UPDATE: return "ssdp:update"; } return "unknown"; } /* updateDevice() : * adds or updates the device to the list. * return value : * 0 : the device was updated (or nothing done) * 1 : the device was new */ static int updateDevice(const struct header * headers, time_t t) { struct device ** pp = &devlist; struct device * p = *pp; /* = devlist; */ while(p) { if( p->headers[HEADER_NT].l == headers[HEADER_NT].l && (0==memcmp(p->headers[HEADER_NT].p, headers[HEADER_NT].p, headers[HEADER_NT].l)) && p->headers[HEADER_USN].l == headers[HEADER_USN].l && (0==memcmp(p->headers[HEADER_USN].p, headers[HEADER_USN].p, headers[HEADER_USN].l)) ) { /*printf("found! %d\n", (int)(t - p->t));*/ syslog(LOG_DEBUG, "device updated : %.*s", headers[HEADER_USN].l, headers[HEADER_USN].p); p->t = t; /* update Location ! */ if(headers[HEADER_LOCATION].l > p->headers[HEADER_LOCATION].l) { struct device * tmp; tmp = realloc(p, sizeof(struct device) + headers[0].l+headers[1].l+headers[2].l); if(!tmp) /* allocation error */ { syslog(LOG_ERR, "updateDevice() : memory allocation error"); *pp = p->next; /* remove "p" from the list */ free(p); return 0; } p = tmp; *pp = p; } memcpy(p->data + p->headers[0].l + p->headers[1].l, headers[2].p, headers[2].l); /* TODO : check p->headers[HEADER_LOCATION].l */ return 0; } pp = &p->next; p = *pp; /* p = p->next; */ } syslog(LOG_INFO, "new device discovered : %.*s", headers[HEADER_USN].l, headers[HEADER_USN].p); /* add */ { char * pc; int i; p = malloc( sizeof(struct device) + headers[0].l+headers[1].l+headers[2].l ); if(!p) { syslog(LOG_ERR, "updateDevice(): cannot allocate memory"); return -1; } p->next = devlist; p->t = t; pc = p->data; for(i = 0; i < 3; i++) { p->headers[i].p = pc; p->headers[i].l = headers[i].l; memcpy(pc, headers[i].p, headers[i].l); pc += headers[i].l; } devlist = p; sendNotifications(NOTIF_NEW, p, NULL); } return 1; } /* removeDevice() : * remove a device from the list * return value : * 0 : no device removed * -1 : device removed */ static int removeDevice(const struct header * headers) { struct device ** pp = &devlist; struct device * p = *pp; /* = devlist */ while(p) { if( p->headers[HEADER_NT].l == headers[HEADER_NT].l && (0==memcmp(p->headers[HEADER_NT].p, headers[HEADER_NT].p, headers[HEADER_NT].l)) && p->headers[HEADER_USN].l == headers[HEADER_USN].l && (0==memcmp(p->headers[HEADER_USN].p, headers[HEADER_USN].p, headers[HEADER_USN].l)) ) { syslog(LOG_INFO, "remove device : %.*s", headers[HEADER_USN].l, headers[HEADER_USN].p); sendNotifications(NOTIF_REMOVE, p, NULL); *pp = p->next; free(p); return -1; } pp = &p->next; p = *pp; /* p = p->next; */ } syslog(LOG_WARNING, "device not found for removing : %.*s", headers[HEADER_USN].l, headers[HEADER_USN].p); return 0; } /* sent notifications to client having subscribed */ static void sendNotifications(int notif_type, const struct device * dev, const struct service * serv) { struct reqelem * req; unsigned int m; unsigned char rbuf[RESPONSE_BUFFER_SIZE]; unsigned char * rp; for(req = reqlisthead.lh_first; req; req = req->entries.le_next) { if(!req->is_notify) continue; rbuf[0] = '\xff'; /* special code for notifications */ rbuf[1] = (unsigned char)notif_type; rbuf[2] = 0; rp = rbuf + 3; if(dev) { /* response : * 1 - Location * 2 - NT (device/service type) * 3 - usn */ m = dev->headers[HEADER_LOCATION].l; CODELENGTH(m, rp); memcpy(rp, dev->headers[HEADER_LOCATION].p, dev->headers[HEADER_LOCATION].l); rp += dev->headers[HEADER_LOCATION].l; m = dev->headers[HEADER_NT].l; CODELENGTH(m, rp); memcpy(rp, dev->headers[HEADER_NT].p, dev->headers[HEADER_NT].l); rp += dev->headers[HEADER_NT].l; m = dev->headers[HEADER_USN].l; CODELENGTH(m, rp); memcpy(rp, dev->headers[HEADER_USN].p, dev->headers[HEADER_USN].l); rp += dev->headers[HEADER_USN].l; rbuf[2]++; } if(serv) { /* response : * 1 - Location * 2 - NT (device/service type) * 3 - usn */ m = strlen(serv->location); CODELENGTH(m, rp); memcpy(rp, serv->location, m); rp += m; m = strlen(serv->st); CODELENGTH(m, rp); memcpy(rp, serv->st, m); rp += m; m = strlen(serv->usn); CODELENGTH(m, rp); memcpy(rp, serv->usn, m); rp += m; rbuf[2]++; } if(rbuf[2] > 0) { if(write_or_buffer(req, rbuf, rp - rbuf) < 0) { syslog(LOG_ERR, "(s=%d) write: %m", req->socket); /*goto error;*/ } } } } /* SendSSDPMSEARCHResponse() : * build and send response to M-SEARCH SSDP packets. */ static void SendSSDPMSEARCHResponse(int s, const struct sockaddr * sockname, const char * st, size_t st_len, const char * usn, const char * server, const char * location) { int l, n; char buf[1024]; socklen_t sockname_len; /* * follow guideline from document "UPnP Device Architecture 1.0" * uppercase is recommended. * DATE: is recommended * SERVER: OS/ver UPnP/1.0 miniupnpd/1.0 * - check what to put in the 'Cache-Control' header * * have a look at the document "UPnP Device Architecture v1.1 */ l = snprintf(buf, sizeof(buf), "HTTP/1.1 200 OK\r\n" "CACHE-CONTROL: max-age=120\r\n" /*"DATE: ...\r\n"*/ "ST: %.*s\r\n" "USN: %s\r\n" "EXT:\r\n" "SERVER: %s\r\n" "LOCATION: %s\r\n" "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" /* UDA v1.1 */ "01-NLS: %u\r\n" /* same as BOOTID. UDA v1.1 */ "BOOTID.UPNP.ORG: %u\r\n" /* UDA v1.1 */ "CONFIGID.UPNP.ORG: %u\r\n" /* UDA v1.1 */ "\r\n", (int)st_len, st, usn, server, location, upnp_bootid, upnp_bootid, upnp_configid); #ifdef ENABLE_IPV6 sockname_len = (sockname->sa_family == PF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in); #else /* ENABLE_IPV6 */ sockname_len = sizeof(struct sockaddr_in); #endif /* ENABLE_IPV6 */ n = sendto_or_schedule(s, buf, l, 0, sockname, sockname_len); if(n < 0) { syslog(LOG_ERR, "%s: sendto(udp): %m", __func__); } } /* Process M-SEARCH requests */ static void processMSEARCH(int s, const char * st, size_t st_len, const struct sockaddr * addr) { struct service * serv; #ifdef ENABLE_IPV6 char buf[64]; #endif /* ENABLE_IPV6 */ if(!st || st_len==0) return; #ifdef ENABLE_IPV6 sockaddr_to_string(addr, buf, sizeof(buf)); syslog(LOG_INFO, "SSDP M-SEARCH from %s ST:%.*s", buf, (int)st_len, st); #else /* ENABLE_IPV6 */ syslog(LOG_INFO, "SSDP M-SEARCH from %s:%d ST: %.*s", inet_ntoa(((const struct sockaddr_in *)addr)->sin_addr), ntohs(((const struct sockaddr_in *)addr)->sin_port), (int)st_len, st); #endif /* ENABLE_IPV6 */ if(st_len==8 && (0==memcmp(st, "ssdp:all", 8))) { /* send a response for all services */ for(serv = servicelisthead.lh_first; serv; serv = serv->entries.le_next) { SendSSDPMSEARCHResponse(s, addr, serv->st, strlen(serv->st), serv->usn, serv->server, serv->location); } } else if(st_len > 5 && (0==memcmp(st, "uuid:", 5))) { /* find a matching UUID value */ for(serv = servicelisthead.lh_first; serv; serv = serv->entries.le_next) { if(0 == strncmp(serv->usn, st, st_len)) { SendSSDPMSEARCHResponse(s, addr, serv->st, strlen(serv->st), serv->usn, serv->server, serv->location); } } } else { size_t l; int st_ver = 0; char atoi_buffer[8]; /* remove version at the end of the ST string */ for (l = st_len; l > 0; l--) { if (st[l-1] == ':') { memset(atoi_buffer, 0, sizeof(atoi_buffer)); memcpy(atoi_buffer, st + l, MIN((sizeof(atoi_buffer) - 1), st_len - l)); st_ver = atoi(atoi_buffer); break; } } if (l == 0) l = st_len; /* answer for each matching service */ /* From UPnP Device Architecture v1.1 : * 1.3.2 [...] Updated versions of device and service types * are REQUIRED to be full backward compatible with * previous versions. Devices MUST respond to M-SEARCH * requests for any supported version. For example, if a * device implements “urn:schemas-upnporg:service:xyz:2”, * it MUST respond to search requests for both that type * and “urn:schemas-upnp-org:service:xyz:1”. The response * MUST specify the same version as was contained in the * search request. [...] */ for(serv = servicelisthead.lh_first; serv; serv = serv->entries.le_next) { if(0 == strncmp(serv->st, st, l)) { syslog(LOG_DEBUG, "Found matching service : %s %s (v %d)", serv->st, serv->location, st_ver); SendSSDPMSEARCHResponse(s, addr, st, st_len, serv->usn, serv->server, serv->location); } } } } /** * helper function. * reject any non ASCII or non printable character. */ static int containsForbiddenChars(const unsigned char * p, int len) { while(len > 0) { if(*p < ' ' || *p >= '\x7f') return 1; p++; len--; } return 0; } #define METHOD_MSEARCH 1 #define METHOD_NOTIFY 2 /* ParseSSDPPacket() : * parse a received SSDP Packet and call * updateDevice() or removeDevice() as needed * return value : * -1 : a device was removed * 0 : no device removed nor added * 1 : a device was added. */ static int ParseSSDPPacket(int s, const char * p, ssize_t n, const struct sockaddr * addr, const char * searched_device) { const char * linestart; const char * lineend; const char * nameend; const char * valuestart; struct header headers[3]; int i, r = 0; int methodlen; int nts = -1; int method = -1; unsigned int lifetime = 180; /* 3 minutes by default */ const char * st = NULL; int st_len = 0; /* first check from what subnet is the sender */ if(get_lan_for_peer(addr) == NULL) { char addr_str[64]; sockaddr_to_string(addr, addr_str, sizeof(addr_str)); syslog(LOG_WARNING, "peer %s is not from a LAN", addr_str); return 0; } /* do the parsing */ memset(headers, 0, sizeof(headers)); for(methodlen = 0; methodlen < n && (isalpha(p[methodlen]) || p[methodlen]=='-'); methodlen++); if(methodlen==8 && 0==memcmp(p, "M-SEARCH", 8)) method = METHOD_MSEARCH; else if(methodlen==6 && 0==memcmp(p, "NOTIFY", 6)) method = METHOD_NOTIFY; else if(methodlen==4 && 0==memcmp(p, "HTTP", 4)) { /* answer to a M-SEARCH => process it as a NOTIFY * with NTS: ssdp:alive */ method = METHOD_NOTIFY; nts = NTS_SSDP_ALIVE; } linestart = p; while(linestart < p + n - 2) { /* start parsing the line : detect line end */ lineend = linestart; while(lineend < p + n && *lineend != '\n' && *lineend != '\r') lineend++; /*printf("line: '%.*s'\n", lineend - linestart, linestart);*/ /* detect name end : ':' character */ nameend = linestart; while(nameend < lineend && *nameend != ':') nameend++; /* detect value */ if(nameend < lineend) valuestart = nameend + 1; else valuestart = nameend; /* trim spaces */ while(valuestart < lineend && isspace(*valuestart)) valuestart++; /* suppress leading " if needed */ if(valuestart < lineend && *valuestart=='\"') valuestart++; if(nameend > linestart && valuestart < lineend) { int l = nameend - linestart; /* header name length */ int m = lineend - valuestart; /* header value length */ /* suppress tailing spaces */ while(m>0 && isspace(valuestart[m-1])) m--; /* suppress tailing ' if needed */ if(m>0 && valuestart[m-1] == '\"') m--; i = -1; /*printf("--%.*s: (%d)%.*s--\n", l, linestart, m, m, valuestart);*/ if(l==2 && 0==strncasecmp(linestart, "nt", 2)) i = HEADER_NT; else if(l==3 && 0==strncasecmp(linestart, "usn", 3)) i = HEADER_USN; else if(l==3 && 0==strncasecmp(linestart, "nts", 3)) { if(m==10 && 0==strncasecmp(valuestart, "ssdp:alive", 10)) nts = NTS_SSDP_ALIVE; else if(m==11 && 0==strncasecmp(valuestart, "ssdp:byebye", 11)) nts = NTS_SSDP_BYEBYE; else if(m==11 && 0==strncasecmp(valuestart, "ssdp:update", 11)) nts = NTS_SSDP_UPDATE; } else if(l==8 && 0==strncasecmp(linestart, "location", 8)) i = HEADER_LOCATION; else if(l==13 && 0==strncasecmp(linestart, "cache-control", 13)) { /* parse "name1=value1, name_alone, name2=value2" string */ const char * name = valuestart; /* name */ const char * val; /* value */ int rem = m; /* remaining bytes to process */ while(rem > 0) { val = name; while(val < name + rem && *val != '=' && *val != ',') val++; if(val >= name + rem) break; if(*val == '=') { while(val < name + rem && (*val == '=' || isspace(*val))) val++; if(val >= name + rem) break; if(0==strncasecmp(name, "max-age", 7)) lifetime = (unsigned int)strtoul(val, 0, 0); /* move to the next name=value pair */ while(rem > 0 && *name != ',') { rem--; name++; } /* skip spaces */ while(rem > 0 && (*name == ',' || isspace(*name))) { rem--; name++; } } else { rem -= (val - name); name = val; while(rem > 0 && (*name == ',' || isspace(*name))) { rem--; name++; } } } /*syslog(LOG_DEBUG, "**%.*s**%u", m, valuestart, lifetime);*/ } else if(l==2 && 0==strncasecmp(linestart, "st", 2)) { st = valuestart; st_len = m; if(method == METHOD_NOTIFY) i = HEADER_NT; /* it was a M-SEARCH response */ } if(i>=0) { headers[i].p = valuestart; headers[i].l = m; } } linestart = lineend; while((linestart < p + n) && (*linestart == '\n' || *linestart == '\r')) linestart++; } #if 0 printf("NTS=%d\n", nts); for(i=0; i<3; i++) { if(headers[i].p) printf("%d-'%.*s'\n", i, headers[i].l, headers[i].p); } #endif syslog(LOG_DEBUG,"SSDP request: '%.*s' (%d) %s %s=%.*s", methodlen, p, method, nts_to_str(nts), (method==METHOD_NOTIFY)?"nt":"st", (method==METHOD_NOTIFY)?headers[HEADER_NT].l:st_len, (method==METHOD_NOTIFY)?headers[HEADER_NT].p:st); switch(method) { case METHOD_NOTIFY: if(nts==NTS_SSDP_ALIVE || nts==NTS_SSDP_UPDATE) { if(headers[HEADER_NT].p && headers[HEADER_USN].p && headers[HEADER_LOCATION].p) { /* filter if needed */ if(searched_device && 0 != memcmp(headers[HEADER_NT].p, searched_device, headers[HEADER_NT].l)) break; r = updateDevice(headers, time(NULL) + lifetime); } else { syslog(LOG_WARNING, "missing header nt=%p usn=%p location=%p", headers[HEADER_NT].p, headers[HEADER_USN].p, headers[HEADER_LOCATION].p); } } else if(nts==NTS_SSDP_BYEBYE) { if(headers[HEADER_NT].p && headers[HEADER_USN].p) { r = removeDevice(headers); } else { syslog(LOG_WARNING, "missing header nt=%p usn=%p", headers[HEADER_NT].p, headers[HEADER_USN].p); } } break; case METHOD_MSEARCH: processMSEARCH(s, st, st_len, addr); break; default: { char addr_str[64]; sockaddr_to_string(addr, addr_str, sizeof(addr_str)); syslog(LOG_WARNING, "method %.*s, don't know what to do (from %s)", methodlen, p, addr_str); } } return r; } /* OpenUnixSocket() * open the unix socket and call bind() and listen() * return -1 in case of error */ static int OpenUnixSocket(const char * path) { struct sockaddr_un addr; int s; int rv; s = socket(AF_UNIX, SOCK_STREAM, 0); if(s < 0) { syslog(LOG_ERR, "socket(AF_UNIX): %m"); return -1; } /* unlink the socket pseudo file before binding */ rv = unlink(path); if(rv < 0 && errno != ENOENT) { syslog(LOG_ERR, "unlink(unixsocket, \"%s\"): %m", path); close(s); return -1; } addr.sun_family = AF_UNIX; strncpy(addr.sun_path, path, sizeof(addr.sun_path)); if(bind(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) { syslog(LOG_ERR, "bind(unixsocket, \"%s\"): %m", path); close(s); return -1; } else if(listen(s, 5) < 0) { syslog(LOG_ERR, "listen(unixsocket): %m"); close(s); return -1; } /* Change rights so everyone can communicate with us */ if(chmod(path, 0666) < 0) { syslog(LOG_WARNING, "chmod(\"%s\"): %m", path); } return s; } static ssize_t processRequestSub(struct reqelem * req, const unsigned char * buf, ssize_t n); /* processRequest() : * process the request coming from a unix socket */ void processRequest(struct reqelem * req) { ssize_t n, r; unsigned char buf[2048]; const unsigned char * p; n = read(req->socket, buf, sizeof(buf)); if(n<0) { if(errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) return; /* try again later */ syslog(LOG_ERR, "(s=%d) processRequest(): read(): %m", req->socket); goto error; } if(n==0) { syslog(LOG_INFO, "(s=%d) request connection closed", req->socket); goto error; } p = buf; while (n > 0) { r = processRequestSub(req, p, n); if (r < 0) goto error; p += r; n -= r; } return; error: close(req->socket); req->socket = -1; } static ssize_t processRequestSub(struct reqelem * req, const unsigned char * buf, ssize_t n) { unsigned int l, m; unsigned int baselen; /* without the version */ const unsigned char * p; enum request_type type; struct device * d = devlist; unsigned char rbuf[RESPONSE_BUFFER_SIZE]; unsigned char * rp; unsigned char nrep = 0; time_t t; struct service * newserv = NULL; struct service * serv; t = time(NULL); type = buf[0]; p = buf + 1; DECODELENGTH_CHECKLIMIT(l, p, buf + n); if(l > (unsigned)(buf+n-p)) { syslog(LOG_WARNING, "bad request (length encoding l=%u n=%u)", l, (unsigned)n); goto error; } if(l == 0 && type != MINISSDPD_SEARCH_ALL && type != MINISSDPD_GET_VERSION && type != MINISSDPD_NOTIF) { syslog(LOG_WARNING, "bad request (length=0, type=%d)", type); goto error; } syslog(LOG_INFO, "(s=%d) request type=%d str='%.*s'", req->socket, type, l, p); switch(type) { case MINISSDPD_GET_VERSION: rp = rbuf; CODELENGTH((sizeof(MINISSDPD_VERSION) - 1), rp); memcpy(rp, MINISSDPD_VERSION, sizeof(MINISSDPD_VERSION) - 1); rp += (sizeof(MINISSDPD_VERSION) - 1); if(write_or_buffer(req, rbuf, rp - rbuf) < 0) { syslog(LOG_ERR, "(s=%d) write: %m", req->socket); goto error; } p += l; break; case MINISSDPD_SEARCH_TYPE: /* request by type */ case MINISSDPD_SEARCH_USN: /* request by USN (unique id) */ case MINISSDPD_SEARCH_ALL: /* everything */ rp = rbuf+1; /* From UPnP Device Architecture v1.1 : * 1.3.2 [...] Updated versions of device and service types * are REQUIRED to be full backward compatible with * previous versions. Devices MUST respond to M-SEARCH * requests for any supported version. For example, if a * device implements “urn:schemas-upnporg:service:xyz:2”, * it MUST respond to search requests for both that type * and “urn:schemas-upnp-org:service:xyz:1”. The response * MUST specify the same version as was contained in the * search request. [...] */ baselen = l; /* remove the version */ while(baselen > 0) { if(p[baselen-1] == ':') break; if(!(p[baselen-1] >= '0' && p[baselen-1] <= '9')) break; baselen--; } while(d && (nrep < 255)) { if(d->t < t) { syslog(LOG_INFO, "outdated device"); } else { /* test if we can put more responses in the buffer */ if(d->headers[HEADER_LOCATION].l + d->headers[HEADER_NT].l + d->headers[HEADER_USN].l + 6 + (rp - rbuf) >= (int)sizeof(rbuf)) break; if( (type==MINISSDPD_SEARCH_TYPE && 0==memcmp(d->headers[HEADER_NT].p, p, baselen)) ||(type==MINISSDPD_SEARCH_USN && 0==memcmp(d->headers[HEADER_USN].p, p, l)) ||(type==MINISSDPD_SEARCH_ALL) ) { /* response : * 1 - Location * 2 - NT (device/service type) * 3 - usn */ m = d->headers[HEADER_LOCATION].l; CODELENGTH(m, rp); memcpy(rp, d->headers[HEADER_LOCATION].p, d->headers[HEADER_LOCATION].l); rp += d->headers[HEADER_LOCATION].l; m = d->headers[HEADER_NT].l; CODELENGTH(m, rp); memcpy(rp, d->headers[HEADER_NT].p, d->headers[HEADER_NT].l); rp += d->headers[HEADER_NT].l; m = d->headers[HEADER_USN].l; CODELENGTH(m, rp); memcpy(rp, d->headers[HEADER_USN].p, d->headers[HEADER_USN].l); rp += d->headers[HEADER_USN].l; nrep++; } } d = d->next; } /* Also look in service list */ for(serv = servicelisthead.lh_first; serv && (nrep < 255); serv = serv->entries.le_next) { /* test if we can put more responses in the buffer */ if(strlen(serv->location) + strlen(serv->st) + strlen(serv->usn) + 6 + (rp - rbuf) >= sizeof(rbuf)) break; if( (type==MINISSDPD_SEARCH_TYPE && 0==strncmp(serv->st, (const char *)p, l)) ||(type==MINISSDPD_SEARCH_USN && 0==strncmp(serv->usn, (const char *)p, l)) ||(type==MINISSDPD_SEARCH_ALL) ) { /* response : * 1 - Location * 2 - NT (device/service type) * 3 - usn */ m = strlen(serv->location); CODELENGTH(m, rp); memcpy(rp, serv->location, m); rp += m; m = strlen(serv->st); CODELENGTH(m, rp); memcpy(rp, serv->st, m); rp += m; m = strlen(serv->usn); CODELENGTH(m, rp); memcpy(rp, serv->usn, m); rp += m; nrep++; } } rbuf[0] = nrep; syslog(LOG_DEBUG, "(s=%d) response : %d device%s", req->socket, nrep, (nrep > 1) ? "s" : ""); if(write_or_buffer(req, rbuf, rp - rbuf) < 0) { syslog(LOG_ERR, "(s=%d) write: %m", req->socket); goto error; } p += l; break; case MINISSDPD_SUBMIT: /* submit service */ newserv = malloc(sizeof(struct service)); if(!newserv) { syslog(LOG_ERR, "cannot allocate memory"); goto error; } memset(newserv, 0, sizeof(struct service)); /* set pointers to NULL */ if(containsForbiddenChars(p, l)) { syslog(LOG_ERR, "bad request (st contains forbidden chars)"); goto error; } newserv->st = malloc(l + 1); if(!newserv->st) { syslog(LOG_ERR, "cannot allocate memory"); goto error; } memcpy(newserv->st, p, l); newserv->st[l] = '\0'; p += l; if(p >= buf + n) { syslog(LOG_WARNING, "bad request (missing usn)"); goto error; } DECODELENGTH_CHECKLIMIT(l, p, buf + n); if(l > (unsigned)(buf+n-p)) { syslog(LOG_WARNING, "bad request (length encoding)"); goto error; } if(containsForbiddenChars(p, l)) { syslog(LOG_ERR, "bad request (usn contains forbidden chars)"); goto error; } syslog(LOG_INFO, "usn='%.*s'", l, p); newserv->usn = malloc(l + 1); if(!newserv->usn) { syslog(LOG_ERR, "cannot allocate memory"); goto error; } memcpy(newserv->usn, p, l); newserv->usn[l] = '\0'; p += l; DECODELENGTH_CHECKLIMIT(l, p, buf + n); if(l > (unsigned)(buf+n-p)) { syslog(LOG_WARNING, "bad request (length encoding)"); goto error; } if(containsForbiddenChars(p, l)) { syslog(LOG_ERR, "bad request (server contains forbidden chars)"); goto error; } syslog(LOG_INFO, "server='%.*s'", l, p); newserv->server = malloc(l + 1); if(!newserv->server) { syslog(LOG_ERR, "cannot allocate memory"); goto error; } memcpy(newserv->server, p, l); newserv->server[l] = '\0'; p += l; DECODELENGTH_CHECKLIMIT(l, p, buf + n); if(l > (unsigned)(buf+n-p)) { syslog(LOG_WARNING, "bad request (length encoding)"); goto error; } if(containsForbiddenChars(p, l)) { syslog(LOG_ERR, "bad request (location contains forbidden chars)"); goto error; } syslog(LOG_INFO, "location='%.*s'", l, p); newserv->location = malloc(l + 1); if(!newserv->location) { syslog(LOG_ERR, "cannot allocate memory"); goto error; } memcpy(newserv->location, p, l); newserv->location[l] = '\0'; p += l; /* look in service list for duplicate */ for(serv = servicelisthead.lh_first; serv; serv = serv->entries.le_next) { if(0 == strcmp(newserv->usn, serv->usn) && 0 == strcmp(newserv->st, serv->st)) { syslog(LOG_INFO, "Service already in the list. Updating..."); free(newserv->st); free(newserv->usn); free(serv->server); serv->server = newserv->server; free(serv->location); serv->location = newserv->location; free(newserv); newserv = NULL; return (p - buf); } } /* Inserting new service */ LIST_INSERT_HEAD(&servicelisthead, newserv, entries); sendNotifications(NOTIF_NEW, NULL, newserv); newserv = NULL; break; case MINISSDPD_NOTIF: /* switch socket to notify */ rbuf[0] = '\0'; if(write_or_buffer(req, rbuf, 1) < 0) { syslog(LOG_ERR, "(s=%d) write: %m", req->socket); goto error; } req->is_notify = 1; p += l; break; default: syslog(LOG_WARNING, "Unknown request type %d", type); rbuf[0] = '\0'; if(write_or_buffer(req, rbuf, 1) < 0) { syslog(LOG_ERR, "(s=%d) write: %m", req->socket); goto error; } } return (p - buf); error: if(newserv) { free(newserv->st); free(newserv->usn); free(newserv->server); free(newserv->location); free(newserv); newserv = NULL; } return -1; } static volatile sig_atomic_t quitting = 0; /* SIGTERM signal handler */ static void sigterm(int sig) { (void)sig; /*int save_errno = errno;*/ /*signal(sig, SIG_IGN);*/ #if 0 /* calling syslog() is forbidden in a signal handler according to * signal(3) */ syslog(LOG_NOTICE, "received signal %d, good-bye", sig); #endif quitting = 1; /*errno = save_errno;*/ } #define PORT 1900 #define XSTR(s) STR(s) #define STR(s) #s #define UPNP_MCAST_ADDR "239.255.255.250" /* for IPv6 */ #define UPNP_MCAST_LL_ADDR "FF02::C" /* link-local */ #define UPNP_MCAST_SL_ADDR "FF05::C" /* site-local */ /* send the M-SEARCH request for devices * either all devices (third argument is NULL or "*") or a specific one */ static void ssdpDiscover(int s, int ipv6, const char * search) { static const char MSearchMsgFmt[] = "M-SEARCH * HTTP/1.1\r\n" "HOST: %s:" XSTR(PORT) "\r\n" "ST: %s\r\n" "MAN: \"ssdp:discover\"\r\n" "MX: %u\r\n" "\r\n"; char bufr[512]; int n; int mx = 3; int linklocal = 1; struct sockaddr_storage sockudp_w; { n = snprintf(bufr, sizeof(bufr), MSearchMsgFmt, ipv6 ? (linklocal ? "[" UPNP_MCAST_LL_ADDR "]" : "[" UPNP_MCAST_SL_ADDR "]") : UPNP_MCAST_ADDR, (search ? search : "ssdp:all"), mx); memset(&sockudp_w, 0, sizeof(struct sockaddr_storage)); if(ipv6) { struct sockaddr_in6 * p = (struct sockaddr_in6 *)&sockudp_w; p->sin6_family = AF_INET6; p->sin6_port = htons(PORT); inet_pton(AF_INET6, linklocal ? UPNP_MCAST_LL_ADDR : UPNP_MCAST_SL_ADDR, &(p->sin6_addr)); } else { struct sockaddr_in * p = (struct sockaddr_in *)&sockudp_w; p->sin_family = AF_INET; p->sin_port = htons(PORT); p->sin_addr.s_addr = inet_addr(UPNP_MCAST_ADDR); } n = sendto_or_schedule(s, bufr, n, 0, (const struct sockaddr *)&sockudp_w, ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)); if (n < 0) { syslog(LOG_ERR, "%s: sendto(s=%d, ipv6=%d): %m", __func__, s, ipv6); } } } /* main(): program entry point */ int main(int argc, char * * argv) { int ret = 0; #ifndef NO_BACKGROUND_NO_PIDFILE int pid; #endif struct sigaction sa; char buf[1500]; ssize_t n; int s_ssdp = -1; /* udp socket receiving ssdp packets */ #ifdef ENABLE_IPV6 int s_ssdp6 = -1; /* udp socket receiving ssdp packets IPv6*/ #else /* ENABLE_IPV6 */ #define s_ssdp6 (-1) #endif /* ENABLE_IPV6 */ int s_unix = -1; /* unix socket communicating with clients */ int s_ifacewatch = -1; /* socket to receive Route / network interface config changes */ struct reqelem * req; struct reqelem * reqnext; fd_set readfds; fd_set writefds; struct timeval now; int max_fd; struct lan_addr_s * lan_addr; int i; const char * sockpath = "/var/run/minissdpd.sock"; #ifndef NO_BACKGROUND_NO_PIDFILE const char * pidfilename = "/var/run/minissdpd.pid"; #endif int debug_flag = 0; #ifdef ENABLE_IPV6 int ipv6 = 0; #endif /* ENABLE_IPV6 */ int deltadev = 0; struct sockaddr_in sendername; socklen_t sendername_len; #ifdef ENABLE_IPV6 struct sockaddr_in6 sendername6; socklen_t sendername6_len; #endif /* ENABLE_IPV6 */ unsigned char ttl = 2; /* UDA says it should default to 2 */ const char * searched_device = NULL; /* if not NULL, search/filter a specific device type */ int opt; LIST_INIT(&reqlisthead); LIST_INIT(&servicelisthead); LIST_INIT(&lan_addrs); /* process command line */ #define OPTSTRING "d6i:s:p:t:f:" while ((opt = getopt(argc, argv, "di:s:t:f:" #ifdef ENABLE_IPV6 "6" #endif #ifndef NO_BACKGROUND_NO_PIDFILE "p:" #endif )) != -1) { switch(opt) { case 'd': debug_flag = 1; break; #ifdef ENABLE_IPV6 case '6': ipv6 = 1; break; #endif /* ENABLE_IPV6 */ case 'i': lan_addr = malloc(sizeof(struct lan_addr_s)); if(lan_addr == NULL) { fprintf(stderr, "malloc(%d) FAILED\n", (int)sizeof(struct lan_addr_s)); break; } if(parselanaddr(lan_addr, optarg) != 0) { fprintf(stderr, "can't parse \"%s\" as a valid " #ifndef ENABLE_IPV6 "address or " #endif "interface name\n", optarg); free(lan_addr); } else { LIST_INSERT_HEAD(&lan_addrs, lan_addr, list); } break; case 's': sockpath = optarg; break; #ifndef NO_BACKGROUND_NO_PIDFILE case 'p': pidfilename = optarg; break; #endif case 't': ttl = (unsigned char)atoi(optarg); break; case 'f': searched_device = optarg; break; } } if(lan_addrs.lh_first == NULL) { fprintf(stderr, "Usage: %s [-d] " #ifdef ENABLE_IPV6 "[-6] " #endif /* ENABLE_IPV6 */ "[-s socket] " #ifndef NO_BACKGROUND_NO_PIDFILE "[-p pidfile] " #endif "[-t TTL] " "[-f device] " "-i [-i ] ...\n", argv[0]); fprintf(stderr, "\n is " #ifndef ENABLE_IPV6 "either an IPv4 address with mask such as\n" " 192.168.1.42/255.255.255.0, or " #endif "an interface name such as eth0.\n"); fprintf(stderr, "\n By default, socket will be open as %s\n" #ifndef NO_BACKGROUND_NO_PIDFILE " and pid written to file %s\n", sockpath, pidfilename #else ,sockpath #endif ); return 1; } /* open log */ openlog("minissdpd", LOG_CONS|LOG_PID|(debug_flag?LOG_PERROR:0), LOG_MINISSDPD); if(!debug_flag) /* speed things up and ignore LOG_INFO and LOG_DEBUG */ setlogmask(LOG_UPTO(LOG_NOTICE)); #ifndef NO_BACKGROUND_NO_PIDFILE if(checkforrunning(pidfilename) < 0) { syslog(LOG_ERR, "MiniSSDPd is already running. EXITING"); return 1; } #endif upnp_bootid = (unsigned int)time(NULL); /* set signal handlers */ memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = sigterm; if(sigaction(SIGTERM, &sa, NULL)) { syslog(LOG_ERR, "Failed to set SIGTERM handler. EXITING"); ret = 1; goto quit; } if(sigaction(SIGINT, &sa, NULL)) { syslog(LOG_ERR, "Failed to set SIGINT handler. EXITING"); ret = 1; goto quit; } /* open route/interface config changes socket */ s_ifacewatch = OpenAndConfInterfaceWatchSocket(); /* open UDP socket(s) for receiving SSDP packets */ s_ssdp = OpenAndConfSSDPReceiveSocket(0, ttl); if(s_ssdp < 0) { syslog(LOG_ERR, "Cannot open socket for receiving SSDP messages, exiting"); ret = 1; goto quit; } #ifdef ENABLE_IPV6 if(ipv6) { s_ssdp6 = OpenAndConfSSDPReceiveSocket(1, ttl); if(s_ssdp6 < 0) { syslog(LOG_ERR, "Cannot open socket for receiving SSDP messages (IPv6), exiting"); ret = 1; goto quit; } } #endif /* ENABLE_IPV6 */ /* Open Unix socket to communicate with other programs on * the same machine */ s_unix = OpenUnixSocket(sockpath); if(s_unix < 0) { syslog(LOG_ERR, "Cannot open unix socket for communicating with clients. Exiting"); ret = 1; goto quit; } /* drop privileges */ #if 0 /* if we drop privileges, how to unlink(/var/run/minissdpd.sock) ? */ if(getuid() == 0) { struct passwd * user; struct group * group; user = getpwnam("nobody"); if(!user) { syslog(LOG_ERR, "getpwnam(\"%s\") : %m", "nobody"); ret = 1; goto quit; } group = getgrnam("nogroup"); if(!group) { syslog(LOG_ERR, "getgrnam(\"%s\") : %m", "nogroup"); ret = 1; goto quit; } if(setgid(group->gr_gid) < 0) { syslog(LOG_ERR, "setgit(%d) : %m", group->gr_gid); ret = 1; goto quit; } if(setuid(user->pw_uid) < 0) { syslog(LOG_ERR, "setuid(%d) : %m", user->pw_uid); ret = 1; goto quit; } } #endif #ifndef NO_BACKGROUND_NO_PIDFILE /* daemonize or in any case get pid ! */ if(debug_flag) pid = getpid(); else { #ifdef USE_DAEMON if(daemon(0, 0) < 0) perror("daemon()"); pid = getpid(); #else /* USE_DAEMON */ pid = daemonize(); #endif /* USE_DAEMON */ } writepidfile(pidfilename, pid); #endif /* send M-SEARCH ssdp:all Requests */ if(s_ssdp >= 0) { for(lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next) { #ifndef HAVE_IP_MREQN struct in_addr mc_if; mc_if.s_addr = lan_addr->addr.s_addr; /*inet_addr(addr);*/ #else struct ip_mreqn mc_if; mc_if.imr_address.s_addr = lan_addr->addr.s_addr; /*inet_addr(addr);*/ #ifdef ENABLE_IPV6 mc_if.imr_ifindex = lan_addr->index; #else /* ENABLE_IPV6 */ mc_if.imr_ifindex = if_nametoindex(lan_addr->ifname); #endif /* ENABLE_IPV6 */ #endif /* HAVE_IP_MREQN */ if(setsockopt(s_ssdp, IPPROTO_IP, IP_MULTICAST_IF, &mc_if, sizeof(mc_if)) < 0) { syslog(LOG_WARNING, "setsockopt(IP_MULTICAST_IF): %m"); } ssdpDiscover(s_ssdp, 0, searched_device); /* XXX if ssdpDiscover() doesn't send the SSDP packet at once, * we should wait here */ } } if(s_ssdp6 >= 0) ssdpDiscover(s_ssdp6, 1, searched_device); /* Main loop */ while(!quitting) { /* fill readfds fd_set */ FD_ZERO(&readfds); FD_ZERO(&writefds); FD_SET(s_unix, &readfds); max_fd = s_unix; if(s_ssdp >= 0) { FD_SET(s_ssdp, &readfds); SET_MAX(max_fd, s_ssdp); } #ifdef ENABLE_IPV6 if(s_ssdp6 >= 0) { FD_SET(s_ssdp6, &readfds); SET_MAX(max_fd, s_ssdp6); } #endif /* ENABLE_IPV6 */ if(s_ifacewatch >= 0) { FD_SET(s_ifacewatch, &readfds); SET_MAX(max_fd, s_ifacewatch); } for(req = reqlisthead.lh_first; req; req = req->entries.le_next) { if(req->socket >= 0) { FD_SET(req->socket, &readfds); SET_MAX(max_fd, req->socket); } if(req->output_buffer_len > 0) { FD_SET(req->socket, &writefds); SET_MAX(max_fd, req->socket); } } gettimeofday(&now, NULL); i = get_sendto_fds(&writefds, &max_fd, &now); /* select call */ if(select(max_fd + 1, &readfds, &writefds, 0, 0) < 0) { if(errno != EINTR) { syslog(LOG_ERR, "select: %m"); break; /* quit */ } continue; /* try again */ } if(try_sendto(&writefds) < 0) { syslog(LOG_ERR, "try_sendto: %m"); break; } #ifdef ENABLE_IPV6 if((s_ssdp6 >= 0) && FD_ISSET(s_ssdp6, &readfds)) { sendername6_len = sizeof(struct sockaddr_in6); n = recvfrom(s_ssdp6, buf, sizeof(buf), 0, (struct sockaddr *)&sendername6, &sendername6_len); if(n<0) { /* EAGAIN, EWOULDBLOCK, EINTR : silently ignore (try again next time) * other errors : log to LOG_ERR */ if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) syslog(LOG_ERR, "recvfrom: %m"); } else { /* Parse and process the packet received */ /*printf("%.*s", n, buf);*/ i = ParseSSDPPacket(s_ssdp6, buf, n, (struct sockaddr *)&sendername6, searched_device); syslog(LOG_DEBUG, "** i=%d deltadev=%d **", i, deltadev); if(i==0 || (i*deltadev < 0)) { if(deltadev > 0) syslog(LOG_NOTICE, "%d new devices added", deltadev); else if(deltadev < 0) syslog(LOG_NOTICE, "%d devices removed (good-bye!)", -deltadev); deltadev = i; } else if((i*deltadev) >= 0) { deltadev += i; } } } #endif /* ENABLE_IPV6 */ if((s_ssdp >= 0) && FD_ISSET(s_ssdp, &readfds)) { sendername_len = sizeof(struct sockaddr_in); n = recvfrom(s_ssdp, buf, sizeof(buf), 0, (struct sockaddr *)&sendername, &sendername_len); if(n<0) { /* EAGAIN, EWOULDBLOCK, EINTR : silently ignore (try again next time) * other errors : log to LOG_ERR */ if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) syslog(LOG_ERR, "recvfrom: %m"); } else { /* Parse and process the packet received */ /*printf("%.*s", n, buf);*/ i = ParseSSDPPacket(s_ssdp, buf, n, (struct sockaddr *)&sendername, searched_device); syslog(LOG_DEBUG, "** i=%d deltadev=%d **", i, deltadev); if(i==0 || (i*deltadev < 0)) { if(deltadev > 0) syslog(LOG_NOTICE, "%d new devices added", deltadev); else if(deltadev < 0) syslog(LOG_NOTICE, "%d devices removed (good-bye!)", -deltadev); deltadev = i; } else if((i*deltadev) >= 0) { deltadev += i; } } } /* processing unix socket requests */ for(req = reqlisthead.lh_first; req;) { reqnext = req->entries.le_next; if((req->socket >= 0) && FD_ISSET(req->socket, &readfds)) { processRequest(req); } if((req->socket >= 0) && FD_ISSET(req->socket, &writefds)) { write_buffer(req); } if(req->socket < 0) { LIST_REMOVE(req, entries); free(req->output_buffer); free(req); } req = reqnext; } /* processing new requests */ if(FD_ISSET(s_unix, &readfds)) { struct reqelem * tmp; int s = accept(s_unix, NULL, NULL); if(s < 0) { syslog(LOG_ERR, "accept(s_unix): %m"); } else { syslog(LOG_INFO, "(s=%d) new request connection", s); if(!set_non_blocking(s)) syslog(LOG_WARNING, "Failed to set new socket non blocking : %m"); tmp = malloc(sizeof(struct reqelem)); if(!tmp) { syslog(LOG_ERR, "cannot allocate memory for request"); close(s); } else { memset(tmp, 0, sizeof(struct reqelem)); tmp->socket = s; LIST_INSERT_HEAD(&reqlisthead, tmp, entries); } } } /* processing route/network interface config changes */ if((s_ifacewatch >= 0) && FD_ISSET(s_ifacewatch, &readfds)) { ProcessInterfaceWatch(s_ifacewatch, s_ssdp, s_ssdp6); } } syslog(LOG_DEBUG, "quitting..."); finalize_sendto(); /* closing and cleaning everything */ quit: if(s_ssdp >= 0) { close(s_ssdp); s_ssdp = -1; } #ifdef ENABLE_IPV6 if(s_ssdp6 >= 0) { close(s_ssdp6); s_ssdp6 = -1; } #endif /* ENABLE_IPV6 */ if(s_unix >= 0) { close(s_unix); s_unix = -1; if(unlink(sockpath) < 0) syslog(LOG_ERR, "unlink(%s): %m", sockpath); } if(s_ifacewatch >= 0) { close(s_ifacewatch); s_ifacewatch = -1; } /* empty LAN interface/address list */ while(lan_addrs.lh_first != NULL) { lan_addr = lan_addrs.lh_first; LIST_REMOVE(lan_addrs.lh_first, list); free(lan_addr); } /* empty device list */ while(devlist != NULL) { struct device * next = devlist->next; free(devlist); devlist = next; } /* empty service list */ while(servicelisthead.lh_first != NULL) { struct service * serv = servicelisthead.lh_first; LIST_REMOVE(servicelisthead.lh_first, entries); free(serv->st); free(serv->usn); free(serv->server); free(serv->location); free(serv); } #ifndef NO_BACKGROUND_NO_PIDFILE if(unlink(pidfilename) < 0) syslog(LOG_ERR, "unlink(%s): %m", pidfilename); #endif closelog(); return ret; } minissdpd-1.5.20211105/openssdpsocket.c010064400017500000024000000143721373445570100166550ustar00nanardstaff/* $Id: openssdpsocket.c,v 1.19 2020/09/28 20:58:47 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2018 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include "config.h" #include #include #include #include #include #include #include #include #include "openssdpsocket.h" #include "upnputils.h" #include "minissdpdtypes.h" extern struct lan_addr_list lan_addrs; /* SSDP ip/port */ #define SSDP_PORT (1900) #define SSDP_MCAST_ADDR ("239.255.255.250") /* Link Local and Site Local SSDP IPv6 multicast addresses */ #define LL_SSDP_MCAST_ADDR ("FF02::C") #define SL_SSDP_MCAST_ADDR ("FF05::C") /** * Add the multicast membership for SSDP on the interface * @param s the socket * @param ifaddr the IPv4 address or interface name * @param ipv6 IPv6 or IPv4 * return -1 on error, 0 on success */ int AddDropMulticastMembership(int s, struct lan_addr_s * lan_addr, int ipv6, int drop) { int ret = 0; struct ip_mreq imr; /* Ip multicast membership */ #ifdef ENABLE_IPV6 struct ipv6_mreq mr; #else /* ENABLE_IPV6 */ (void)ipv6; #endif /* ENABLE_IPV6 */ if(s <= 0) return -1; /* nothing to do */ #ifdef ENABLE_IPV6 if(ipv6) { memset(&mr, 0, sizeof(mr)); inet_pton(AF_INET6, LL_SSDP_MCAST_ADDR, &mr.ipv6mr_multiaddr); mr.ipv6mr_interface = lan_addr->index; if(setsockopt(s, IPPROTO_IPV6, drop ? IPV6_LEAVE_GROUP : IPV6_JOIN_GROUP, &mr, sizeof(struct ipv6_mreq)) < 0) { syslog(LOG_ERR, "setsockopt(s=%d, %s)(%s, %s): %m", s, drop ? "IPV6_LEAVE_GROUP" : "IPV6_JOIN_GROUP", LL_SSDP_MCAST_ADDR, lan_addr->ifname); ret = -1; } inet_pton(AF_INET6, SL_SSDP_MCAST_ADDR, &mr.ipv6mr_multiaddr); if(setsockopt(s, IPPROTO_IPV6, drop ? IPV6_LEAVE_GROUP : IPV6_JOIN_GROUP, &mr, sizeof(struct ipv6_mreq)) < 0) { syslog(LOG_ERR, "setsockopt(s=%d, %s)(%s, %s): %m", s, drop ? "IPV6_LEAVE_GROUP" : "IPV6_JOIN_GROUP", SL_SSDP_MCAST_ADDR, lan_addr->ifname); ret = -1; } } else { #endif /* ENABLE_IPV6 */ /* setting up imr structure */ imr.imr_multiaddr.s_addr = inet_addr(SSDP_MCAST_ADDR); imr.imr_interface.s_addr = lan_addr->addr.s_addr; if(imr.imr_interface.s_addr == INADDR_NONE) { syslog(LOG_ERR, "no IPv4 address for interface %s", lan_addr->ifname); return -1; } if (setsockopt(s, IPPROTO_IP, drop ? IP_DROP_MEMBERSHIP : IP_ADD_MEMBERSHIP, (void *)&imr, sizeof(struct ip_mreq)) < 0) { syslog(LOG_ERR, "setsockopt(s=%d, %s)(%s): %m", s, drop ? "IP_DROP_MEMBERSHIP" : "IP_ADD_MEMBERSHIP", lan_addr->ifname); return -1; } #ifdef ENABLE_IPV6 } #endif /* ENABLE_IPV6 */ return ret; } int OpenAndConfSSDPReceiveSocket(int ipv6, unsigned char ttl) { int s; int opt = 1; unsigned char loopchar = 0; #ifdef ENABLE_IPV6 struct sockaddr_storage sockname; #else /* ENABLE_IPV6 */ struct sockaddr_in sockname; #endif /* ENABLE_IPV6 */ socklen_t sockname_len; struct lan_addr_s * lan_addr; #ifndef ENABLE_IPV6 if(ipv6) { syslog(LOG_ERR, "%s: please compile with ENABLE_IPV6 to allow ipv6=1", __func__); return -1; } #endif /* ENABLE_IPV6 */ #ifdef ENABLE_IPV6 if( (s = socket(ipv6 ? PF_INET6 : PF_INET, SOCK_DGRAM, 0)) < 0) #else /* ENABLE_IPV6 */ if( (s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) #endif /* ENABLE_IPV6 */ { syslog(LOG_ERR, "socket(udp): %m"); return -1; } if(!set_non_blocking(s)) { syslog(LOG_WARNING, "Failed to set SSDP socket non blocking : %m"); } #ifdef ENABLE_IPV6 memset(&sockname, 0, sizeof(struct sockaddr_storage)); if(ipv6) { #ifdef IPV6_V6ONLY if(setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&opt, sizeof(opt)) < 0) { syslog(LOG_WARNING, "setsockopt(IPV6_V6ONLY): %m"); } #endif /* IPV6_V6ONLY */ struct sockaddr_in6 * sa = (struct sockaddr_in6 *)&sockname; sa->sin6_family = AF_INET6; sa->sin6_port = htons(SSDP_PORT); sa->sin6_addr = in6addr_any; sockname_len = sizeof(struct sockaddr_in6); } else { struct sockaddr_in * sa = (struct sockaddr_in *)&sockname; sa->sin_family = AF_INET; sa->sin_port = htons(SSDP_PORT); #ifdef SSDP_LISTEN_ON_SPECIFIC_ADDR if(lan_addrs.lh_first != NULL && lan_addrs.lh_first->list.le_next == NULL) { sa->sin_addr.s_addr = lan_addrs.lh_first->addr.s_addr; if(sa->sin_addr.s_addr == INADDR_NONE) { syslog(LOG_ERR, "no IPv4 address for interface %s", lan_addrs.lh_first->ifname); close(s); return -1; } } else #endif /* SSDP_LISTEN_ON_SPECIFIC_ADDR */ sa->sin_addr.s_addr = htonl(INADDR_ANY); sockname_len = sizeof(struct sockaddr_in); } #else /* ENABLE_IPV6 */ memset(&sockname, 0, sizeof(struct sockaddr_in)); sockname.sin_family = AF_INET; sockname.sin_port = htons(SSDP_PORT); #ifdef SSDP_LISTEN_ON_SPECIFIC_ADDR if(lan_addrs.lh_first != NULL && lan_addrs.lh_first->list.le_next == NULL) { sockname.sin_addr.s_addr = lan_addrs.lh_first->addr.s_addr; if(sockname.sin_addr.s_addr == INADDR_NONE) { syslog(LOG_ERR, "no IPv4 address for interface %s", lan_addrs.lh_first->ifname); close(s); return -1; } } else #endif /* SSDP_LISTEN_ON_SPECIFIC_ADDR */ sockname.sin_addr.s_addr = htonl(INADDR_ANY); sockname_len = sizeof(struct sockaddr_in); #endif /* ENABLE_IPV6 */ if(setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&loopchar, sizeof(loopchar)) < 0) { syslog(LOG_WARNING, "setsockopt(IP_MULTICAST_LOOP): %m"); } if(setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) { syslog(LOG_WARNING, "setsockopt(IP_MULTICAST_TTL): %m"); } if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { syslog(LOG_WARNING, "setsockopt(SO_REUSEADDR): %m"); } if(bind(s, (struct sockaddr *)&sockname, sockname_len) < 0) { syslog(LOG_ERR, "bind(udp%s): %m", ipv6 ? "6" : ""); close(s); return -1; } for(lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next) { if(AddDropMulticastMembership(s, lan_addr, ipv6, 0) < 0) { syslog(LOG_WARNING, "Failed to add IPv%d multicast membership for interface %s.", ipv6 ? 6 : 4, lan_addr->ifname); } } return s; } minissdpd-1.5.20211105/testminissdpd.c010064400017500000024000000121511323543256300164710ustar00nanardstaff/* $Id: testminissdpd.c,v 1.15 2018/02/03 22:16:16 nanard Exp $ */ /* Project : miniupnp * website : http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * Author : Thomas BERNARD * copyright (c) 2005-2018 Thomas Bernard * This software is subjet to the conditions detailed in the * provided LICENCE file. */ #include #include #include #include #include #include #include #include "codelength.h" #include "printresponse.h" void printversion(const unsigned char * resp, int n) { int l; const unsigned char * p; p = resp; DECODELENGTH(l, p); if(resp + n < p + l) { printf("get version error\n"); } printf("MiniSSDPd version : %.*s\n", l, p); } #define SENDCOMMAND(command, size) write(s, command, size); \ printf("Command written type=%u\n", (unsigned char)command[0]); int connect_unix_socket(const char * sockpath) { int s; struct sockaddr_un addr; s = socket(AF_UNIX, SOCK_STREAM, 0); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, sockpath, sizeof(addr.sun_path)); if(connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) { fprintf(stderr, "connecting to %s : ", addr.sun_path); perror("connect"); exit(1); } printf("Connected to %s\n", addr.sun_path); return s; } /* test program for minissdpd */ int main(int argc, char * * argv) { const unsigned char command0[] = { 0x00, 0x00 }; char command1[] = "\x01\x00urn:schemas-upnp-org:device:InternetGatewayDevice"; char command2[] = "\x02\x00uuid:fc4ec57e-b051-11db-88f8-0060085db3f6::upnp:rootdevice"; const unsigned char command3[] = { 0x03, 0x00 }; /* old versions of minissdpd would reject a command with * a zero length string argument */ char command3compat[] = "\x03\x00ssdp:all"; char command4[] = "\x04\x00test:test:test"; const unsigned char bad_command[] = { 0xff, 0xff }; const unsigned char overflow[] = { 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; const unsigned char command5[] = { 0x05, 0x00 }; const unsigned char bad_command4[] = { 0x04, 0x01, 0x60, 0x8f, 0xff, 0xff, 0xff, 0x7f}; int s; int i; void * tmp; unsigned char * resp = NULL; size_t respsize = 0; unsigned char buf[4096]; ssize_t n; int total = 0; const char * sockpath = "/var/run/minissdpd.sock"; for(i=0; i 0) { printversion(buf, n); } else { printf("Command 0 (get version) not supported\n"); close(s); s = connect_unix_socket(sockpath); } n = SENDCOMMAND(command1, sizeof(command1) - 1); n = read(s, buf, sizeof(buf)); printf("Response received %d bytes\n", (int)n); printresponse(buf, n); if(n == 0) { close(s); s = connect_unix_socket(sockpath); } n = SENDCOMMAND(command2, sizeof(command2) - 1); n = read(s, buf, sizeof(buf)); printf("Response received %d bytes\n", (int)n); printresponse(buf, n); if(n == 0) { close(s); s = connect_unix_socket(sockpath); } buf[0] = 0; /* Slight hack for printing num devices when 0 */ n = SENDCOMMAND(command3, sizeof(command3)); n = read(s, buf, sizeof(buf)); if(n == 0) { printf("command3 failed, testing compatible one\n"); close(s); s = connect_unix_socket(sockpath); n = SENDCOMMAND(command3compat, sizeof(command3compat) - 1); n = read(s, buf, sizeof(buf)); } printf("Response received %d bytes\n", (int)n); printf("Number of devices %d\n", (int)buf[0]); while(n > 0) { tmp = realloc(resp, respsize + n); if(tmp == NULL) { fprintf(stderr, "memory allocation error\n"); break; } resp = tmp; respsize += n; if (n > 0) { memcpy(resp + total, buf, n); total += n; } if (n < (ssize_t)sizeof(buf)) { break; } n = read(s, buf, sizeof(buf)); printf("response received %d bytes\n", (int)n); } if(resp != NULL) { printresponse(resp, total); free(resp); resp = NULL; } if(n == 0) { close(s); s = connect_unix_socket(sockpath); } n = SENDCOMMAND(command4, sizeof(command4)); /* no response for request type 4 */ n = SENDCOMMAND(bad_command, sizeof(bad_command)); n = read(s, buf, sizeof(buf)); printf("Response received %d bytes\n", (int)n); printresponse(buf, n); if(n == 0) { close(s); s = connect_unix_socket(sockpath); } n = SENDCOMMAND(overflow, sizeof(overflow)); n = read(s, buf, sizeof(buf)); printf("Response received %d bytes\n", (int)n); printresponse(buf, n); if(n == 0) { close(s); s = connect_unix_socket(sockpath); } n = SENDCOMMAND(command5, sizeof(command5)); n = read(s, buf, sizeof(buf)); printf("Response received %d bytes\n", (int)n); printresponse(buf, n); if(n == 0) { close(s); s = connect_unix_socket(sockpath); } n = SENDCOMMAND(bad_command4, sizeof(bad_command4)); n = read(s, buf, sizeof(buf)); printf("Response received %d bytes\n", (int)n); printresponse(buf, n); close(s); return 0; } minissdpd-1.5.20211105/daemonize.h010064400017500000024000000015151203340734000155470ustar00nanardstaff/* $Id: daemonize.h,v 1.8 2012/09/27 16:00:44 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef DAEMONIZE_H_INCLUDED #define DAEMONIZE_H_INCLUDED #include "config.h" #ifndef USE_DAEMON /* daemonize() * "fork" to background, detach from terminal, etc... * returns: pid of the daemon, exits upon failure */ int daemonize(void); #endif /* writepidfile() * write the pid to a file */ int writepidfile(const char * fname, int pid); /* checkforrunning() * check for another instance running * returns: 0 only instance * -1 invalid filename * -2 another instance running */ int checkforrunning(const char * fname); #endif minissdpd-1.5.20211105/openssdpsocket.h010064400017500000024000000017021255624710300166470ustar00nanardstaff/* $Id: openssdpsocket.h,v 1.7 2015/07/21 15:39:38 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2015 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef OPENSSDPSOCKET_H_INCLUDED #define OPENSSDPSOCKET_H_INCLUDED #include "minissdpdtypes.h" /** * Open a socket and configure it for receiving SSDP packets * * @param ipv6 open INET6 or INET socket * @param ttl multicast TTL * @return socket */ int OpenAndConfSSDPReceiveSocket(int ipv6, unsigned char ttl); /** * Add or Drop the multicast membership for SSDP on the interface * @param s the socket * @param lan_addr the LAN address or interface name * @param ipv6 IPv6 or IPv4 * @param drop 0 to add, 1 to drop * return -1 on error, 0 on success */ int AddDropMulticastMembership(int s, struct lan_addr_s * lan_addr, int ipv6, int drop); #endif minissdpd-1.5.20211105/Makefile010064400017500000024000000072011414106654500150730ustar00nanardstaff# $Id: Makefile,v 1.31 2021/11/04 22:58:22 nanard Exp $ # MiniUPnP project # author: Thomas Bernard # website: http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ # for use with GNU Make (gmake) # install with : # $ DESTDIR=/tmp/dummylocation make install # or # $ INSTALLPREFIX=/usr/local make install # or # make install (miniupnpd will be put in /usr/sbin) # # install target is made for linux... sorry BSD users... #CFLAGS = -g -O0 CFLAGS ?= -Os CFLAGS += -Wall CFLAGS += -W -Wstrict-prototypes CFLAGS += -fno-strict-aliasing -fno-common CFLAGS += -D_GNU_SOURCE #CFLAGS += -ansi CC ?= gcc RM = rm -f INSTALL = install OS = $(shell $(CC) -dumpmachine) ifneq (, $(findstring linux, $(OS))) LDLIBS += -lnfnetlink endif ifeq ($(DEB_HOST_ARCH_OS), kfreebsd) LDLIBS += -lfreebsd-glue else ifneq (, $(findstring sun, $(OS))) CFLAGS += -D_XOPEN_SOURCE CFLAGS += -D_XOPEN_SOURCE_EXTENDED=1 CFLAGS += -D__EXTENSIONS__ LDFLAGS += -lsocket -lnsl -lresolv endif endif #EXECUTABLES = minissdpd testminissdpd listifaces EXECUTABLES = minissdpd testminissdpd testcodelength \ showminissdpdnotif MINISSDPDOBJS = minissdpd.o openssdpsocket.o daemonize.o upnputils.o \ ifacewatch.o getroute.o getifaddr.o asyncsendto.o TESTMINISSDPDOBJS = testminissdpd.o printresponse.o SHOWMINISSDPDNOTIFOBJS = showminissdpdnotif.o printresponse.o ALLOBJS = $(MINISSDPDOBJS) $(TESTMINISSDPDOBJS) \ $(SHOWMINISSDPDNOTIFOBJS) \ testcodelength.o # install directories ifeq ($(strip $(PREFIX)),) INSTALLPREFIX ?= /usr else INSTALLPREFIX ?= $(PREFIX) endif SBININSTALLDIR = $(INSTALLPREFIX)/sbin MANINSTALLDIR = $(INSTALLPREFIX)/share/man .PHONY: all clean install depend check test all: $(EXECUTABLES) test: check clean: $(RM) $(ALLOBJS) $(EXECUTABLES) install: minissdpd $(INSTALL) -d $(DESTDIR)$(SBININSTALLDIR) $(INSTALL) minissdpd $(DESTDIR)$(SBININSTALLDIR) $(INSTALL) -d $(DESTDIR)$(MANINSTALLDIR)/man1 $(INSTALL) minissdpd.1 $(DESTDIR)$(MANINSTALLDIR)/man1/minissdpd.1 ifeq (, $(findstring darwin, $(OS))) $(INSTALL) -d $(DESTDIR)/etc/init.d $(INSTALL) minissdpd.init.d.script $(DESTDIR)/etc/init.d/minissdpd endif check: validateminissdpd validatecodelength validateminissdpd: testminissdpd minissdpd ./testminissdpd.sh touch $@ validatecodelength: testcodelength ./testcodelength touch $@ minissdpd: $(MINISSDPDOBJS) testminissdpd: $(TESTMINISSDPDOBJS) showminissdpdnotif: $(SHOWMINISSDPDNOTIFOBJS) testcodelength: testcodelength.o listifaces: listifaces.o upnputils.o config.h: VERSION @tmp=`grep -n MINISSDPD_VERSION $@` ; \ line=`echo $$tmp | cut -d: -f1` ; \ old_version=`echo $$tmp | cut -d\\" -f2` ; \ new_version=`cat VERSION` ; \ if [ "$$new_version" != "$$old_version" ] ; then \ echo "updating VERSION in $@ from $$old_version to $$new_version"; \ sed "$${line}s/$${old_version}/$${new_version}/" $@ > $@.temp ; \ mv $@.temp $@ ; \ fi depend: makedepend -f$(MAKEFILE_LIST) -Y \ $(ALLOBJS:.o=.c) 2>/dev/null # DO NOT DELETE minissdpd.o: config.h getifaddr.h upnputils.h openssdpsocket.h minissdpd.o: minissdpdtypes.h daemonize.h codelength.h ifacewatch.h minissdpd.o: asyncsendto.h openssdpsocket.o: config.h openssdpsocket.h minissdpdtypes.h upnputils.h daemonize.o: daemonize.h config.h upnputils.o: config.h upnputils.h getroute.h minissdpdtypes.h ifacewatch.o: config.h openssdpsocket.h minissdpdtypes.h upnputils.h getroute.o: getroute.h upnputils.h getifaddr.o: config.h getifaddr.h asyncsendto.o: asyncsendto.h upnputils.h testminissdpd.o: codelength.h printresponse.h printresponse.o: codelength.h showminissdpdnotif.o: codelength.h printresponse.h printresponse.o: codelength.h testcodelength.o: codelength.h minissdpd-1.5.20211105/minissdpd.init.d.script010064400017500000024000000022611252760572600200460ustar00nanardstaff#!/bin/sh # $Id: minissdpd.init.d.script,v 1.3 2015/05/22 10:57:42 nanard Exp $ # MiniUPnP project # author: Thomas Bernard # website: http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ MINISSDPD=/usr/sbin/minissdpd PIDFILE=/var/run/minissdpd.pid # get default interface IF=`route | grep default |awk -- '{ print $8 }'` ARGS="-i $IF" test -f $MINISSDPD || exit 0 . /lib/lsb/init-functions case "$1" in start) log_daemon_msg "Starting minissdpd" "minissdpd" start-stop-daemon --start --quiet --pidfile $PIDFILE \ --exec $MINISSDPD -- $ARGS $LSBNAMES log_end_msg $? ;; stop) log_daemon_msg "Stopping minissdpd" "minissdpd" start-stop-daemon --stop --quiet --pidfile $PIDFILE log_end_msg $? ;; restart|reload|force-reload) log_daemon_msg "Restarting minissdpd" "minissdpd" start-stop-daemon --stop --retry 5 --quiet --pidfile $PIDFILE start-stop-daemon --start --quiet --pidfile $PIDFILE \ --exec $MINISSDPD -- $ARGS $LSBNAMES log_end_msg $? ;; *) log_action_msg "Usage: /etc/init.d/minissdpd {start|stop|restart|reload|force-reload}" exit 2 ;; esac exit 0 minissdpd-1.5.20211105/LICENSE010064400017500000024000000026761414106654500144530ustar00nanardstaffCopyright (c) 2007-2021, 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. minissdpd-1.5.20211105/minissdpd.1010064400017500000024000000024001323543256300155030ustar00nanardstaff.TH "minissdpd" 1 .SH NAME minissdpd \- daemon keeping track of UPnP devices up .SH SYNOPSIS .B minissdpd .RB [ -d "] [" -6 "] [" "-s \fIsocket" "] [" "-p \fIpidfile" "] [" "-t \fITTL" "] [" "-f \fIdevice" ] " -i \fR<\fIinterface\fR> " [ "-i \fR<\fIinterface2\fR>" "] ..." .SH DESCRIPTION minissdpd listen for SSDP traffic and keeps track of what are the UPnP devices up on the network. The list of the UPnP devices is accessed by programs looking for devices, skipping the UPnP discovery process. .SH OPTIONS .TP .B \-d debug : do not go to background, output messages to console and do not filter out low priority messages. .TP .B \-6 IPv6 : Enable IPv6 in addition to IPv4. .TP .BI \-s " socket" path of the unix socket open for communicating with other processes. By default /var/run/minissdpd.sock is used. .TP .BI \-p " pidfile" path of the file where pid is written at startup. By default /var/run/minissdpd.pid is used. .TP .BI \-t " TTL" TTL of the package. By default 2 is used according to UDA. .TP .BI \-f " device" search/filter a specific device type. .TP .BI \-i " interface" name or IP address of the interface used to listen to SSDP packets coming on port 1900, multicast address 239.255.255.250. .SH "SEE ALSO" miniupnpd(1) miniupnpc(3) .SH BUGS No known bugs. minissdpd-1.5.20211105/config.h010064400017500000024000000025551366677724500151010ustar00nanardstaff/* $Id: config.h,v 1.12 2020/06/06 20:20:47 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2006-2020 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef CONFIG_H_INCLUDED #define CONFIG_H_INCLUDED #define MINISSDPD_VERSION "1.5" /* use BSD daemon() ? */ #define USE_DAEMON /* set the syslog facility to use. See man syslog(3) and syslog.conf(5). */ #define LOG_MINISSDPD LOG_DAEMON /* enable IPv6 */ #define ENABLE_IPV6 /* The size of unix socket response buffer */ #define RESPONSE_BUFFER_SIZE (1024 * 4) /* Uncomment the following line in order to make minissdpd * listen on 1.2.3.4:1900 instead of *:1900 * FOR TESTING PURPOSE ONLY * Note : it prevents multicast packets to be received, * at least with linux * As miniSSDPd needs to receive SSDP packets both multicasted * and unicasted, we cannot bind to 239.255.255.250 neither */ /*#define SSDP_LISTEN_ON_SPECIFIC_ADDR*/ /* When NO_BACKGROUND_NO_PIDFILE is defined, minissdpd does not go to * background and does not create any pidfile */ /*#define NO_BACKGROUND_NO_PIDFILE*/ /* define HAVE_IP_MREQN to use struct ip_mreqn instead of struct ip_mreq * for setsockopt(IP_MULTICAST_IF). Available with Linux 2.4+, * FreeBSD, etc. */ /*#define HAVE_IP_MREQN*/ #endif minissdpd-1.5.20211105/README010064400017500000024000000037521323543256300143220ustar00nanardstaff * MiniSSDPd - SSDP daemon (c) Thomas Bernard http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ https://github.com/miniupnp/miniupnp/ MiniSSDPd is a daemon that : 1 - keeps track of all UPnP devices announcing themselves on the network. its database can be queried by local processes using a protocol based on a unix socket. That enables local processes to quickly discover UPnP devices without broadcasting SSDP message and waiting several seconds for a response. 2 - keeps a database of local UPnP devices hosted on the machine and answering SSDP searchs on their behalf. It enables to run several UPnP devices, like an IGD and a MediaServer, on the same machine. to build, use GNU Make. * protocol : Connect to the unix socket. Sent request, get response. close unix socket connection. * Request format : 1st byte : request type 0 - version 1 - type 2 - USN (unique id) 3 - everything 4 - submit service (see below) 5 - switch connection to notification mode n bytes : string length : 1 byte if < 128 else the upper bit indicate that one additional byte should be read, etc. (see codelength.h) n bytes = string Response format : request type 0 (version) : n bytes string length n bytes = version string request type 1 / 2 / 3 / 5 : 1st byte : number of services/devices, from 0 to 254. 255 is a special value, see below For each service/device : URL : n bytes string length n bytes = Location string ST: n bytes string length n bytes = type string USN: n bytes string length n bytes = identifier string if the 1st byte is 255, the format is as follows : 1st byte = 255 2nd byte = notification type (1=NEW, 2=UPDATE, 3=REMOVE) 3rd byte = number of services/devices, from 0 to 255. request type 4 = submit service 1st byte = 4 (k,n) bytes : length and string "ST" (service type) (k,n) bytes : length and string "USN" (k,n) bytes : length and string "Server" (k,n) bytes : length and string "Location" No answer minissdpd-1.5.20211105/testcodelength.c010064400017500000024000000013031323543256300166100ustar00nanardstaff/* $Id: testcodelength.c,v 1.4 2018/02/03 22:16:58 nanard Exp $ */ /* Project : miniupnp * Author : Thomas BERNARD * copyright (c) 2005-2018 Thomas Bernard * This software is subjet to the conditions detailed in the * provided LICENCE file. */ #include #include "codelength.h" int main(int argc, char * * argv) { unsigned char buf[256]; unsigned char * p; long i, j; (void)argc; (void)argv; for(i = 1; i < 1000000000; i *= 2) { /* encode i, decode to j */ printf("%ld ", i); p = buf; CODELENGTH(i, p); p = buf; DECODELENGTH(j, p); if(i != j) { fprintf(stderr, "Error ! encoded %ld, decoded %ld.\n", i, j); return 1; } } printf("Test successful\n"); return 0; } minissdpd-1.5.20211105/codelength.h010064400017500000024000000030251255150331000157050ustar00nanardstaff/* $Id: codelength.h,v 1.5 2015/07/09 12:40:18 nanard Exp $ */ /* Project : miniupnp * Author : Thomas BERNARD * copyright (c) 2005-2015 Thomas Bernard * This software is subjet to the conditions detailed in the * provided LICENCE file. */ #ifndef CODELENGTH_H_INCLUDED #define CODELENGTH_H_INCLUDED /* Encode length by using 7bit per Byte : * Most significant bit of each byte specifies that the * following byte is part of the code */ /* n : unsigned * p : unsigned char * */ #define DECODELENGTH(n, p) n = 0; \ do { n = (n << 7) | (*p & 0x7f); } \ while((*(p++)&0x80) && (n<(1<<25))); /* n : unsigned * READ : function/macro to read one byte (unsigned char) */ #define DECODELENGTH_READ(n, READ) \ n = 0; \ do { \ unsigned char c; \ READ(c); \ n = (n << 7) | (c & 0x07f); \ if(!(c&0x80)) break; \ } while(n<(1<<25)); /* n : unsigned * p : unsigned char * * p_limit : unsigned char * */ #define DECODELENGTH_CHECKLIMIT(n, p, p_limit) \ n = 0; \ do { \ if((p) >= (p_limit)) break; \ n = (n << 7) | (*(p) & 0x7f); \ } while((*((p)++)&0x80) && (n<(1<<25))); /* n : unsigned * p : unsigned char * */ #define CODELENGTH(n, p) if(n>=268435456) *(p++) = (n >> 28) | 0x80; \ if(n>=2097152) *(p++) = (n >> 21) | 0x80; \ if(n>=16384) *(p++) = (n >> 14) | 0x80; \ if(n>=128) *(p++) = (n >> 7) | 0x80; \ *(p++) = n & 0x7f; #endif /* CODELENGTH_H_INCLUDED */ minissdpd-1.5.20211105/upnputils.c010064400017500000024000000115361353017754300156520ustar00nanardstaff/* $Id: upnputils.c,v 1.4 2019/08/24 09:05:56 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2006-2019 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include "config.h" #include #include #include #include #include #include #include #include #include #ifdef AF_LINK #include #endif #include #include #include "upnputils.h" #include "getroute.h" #include "minissdpdtypes.h" extern struct lan_addr_list lan_addrs; int sockaddr_to_string(const struct sockaddr * addr, char * str, size_t size) { char buffer[64]; unsigned short port = 0; int n = -1; switch(addr->sa_family) { case AF_INET6: inet_ntop(addr->sa_family, &((struct sockaddr_in6 *)addr)->sin6_addr, buffer, sizeof(buffer)); port = ntohs(((struct sockaddr_in6 *)addr)->sin6_port); n = snprintf(str, size, "[%s]:%hu", buffer, port); break; case AF_INET: inet_ntop(addr->sa_family, &((struct sockaddr_in *)addr)->sin_addr, buffer, sizeof(buffer)); port = ntohs(((struct sockaddr_in *)addr)->sin_port); n = snprintf(str, size, "%s:%hu", buffer, port); break; #ifdef AF_LINK #if defined(__sun) /* solaris does not seem to have link_ntoa */ /* #define link_ntoa _link_ntoa */ #define link_ntoa(x) "dummy-link_ntoa" #endif case AF_LINK: { struct sockaddr_dl * sdl = (struct sockaddr_dl *)addr; n = snprintf(str, size, "index=%hu type=%d %s", sdl->sdl_index, sdl->sdl_type, link_ntoa(sdl)); } break; #endif default: n = snprintf(str, size, "unknown address family %d", addr->sa_family); #if 0 n = snprintf(str, size, "unknown address family %d " "%02x %02x %02x %02x %02x %02x %02x %02x", addr->sa_family, addr->sa_data[0], addr->sa_data[1], (unsigned)addr->sa_data[2], addr->sa_data[3], addr->sa_data[4], addr->sa_data[5], (unsigned)addr->sa_data[6], addr->sa_data[7]); #endif } return n; } int set_non_blocking(int fd) { int flags = fcntl(fd, F_GETFL); if(flags < 0) return 0; if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) return 0; return 1; } struct lan_addr_s * get_lan_for_peer(const struct sockaddr * peer) { struct lan_addr_s * lan_addr = NULL; #ifdef DEBUG char dbg_str[64]; #endif /* DEBUG */ #ifdef ENABLE_IPV6 if(peer->sa_family == AF_INET6) { struct sockaddr_in6 * peer6 = (struct sockaddr_in6 *)peer; if(IN6_IS_ADDR_V4MAPPED(&peer6->sin6_addr)) { struct in_addr peer_addr; memcpy(&peer_addr, &peer6->sin6_addr.s6_addr[12], 4); for(lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next) { if( (peer_addr.s_addr & lan_addr->mask.s_addr) == (lan_addr->addr.s_addr & lan_addr->mask.s_addr)) break; } } else { int index = -1; if(peer6->sin6_scope_id > 0) index = (int)peer6->sin6_scope_id; else { if(get_src_for_route_to(peer, NULL, NULL, &index) < 0) return NULL; } syslog(LOG_DEBUG, "%s looking for LAN interface index=%d", "get_lan_for_peer()", index); for(lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next) { syslog(LOG_DEBUG, "ifname=%s index=%u str=%s addr=%08x mask=%08x", lan_addr->ifname, lan_addr->index, lan_addr->str, ntohl(lan_addr->addr.s_addr), ntohl(lan_addr->mask.s_addr)); if(index == (int)lan_addr->index) break; } } } else if(peer->sa_family == AF_INET) { #endif /* ENABLE_IPV6 */ for(lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next) { if( (((const struct sockaddr_in *)peer)->sin_addr.s_addr & lan_addr->mask.s_addr) == (lan_addr->addr.s_addr & lan_addr->mask.s_addr)) break; } #ifdef ENABLE_IPV6 } #endif /* ENABLE_IPV6 */ #ifdef DEBUG sockaddr_to_string(peer, dbg_str, sizeof(dbg_str)); if(lan_addr) { syslog(LOG_DEBUG, "%s: %s found in LAN %s %s", "get_lan_for_peer()", dbg_str, lan_addr->ifname, lan_addr->str); } else { syslog(LOG_DEBUG, "%s: %s not found !", "get_lan_for_peer()", dbg_str); } #endif /* DEBUG */ return lan_addr; } #if defined(CLOCK_MONOTONIC_FAST) #define UPNP_CLOCKID CLOCK_MONOTONIC_FAST #elif defined(CLOCK_MONOTONIC) #define UPNP_CLOCKID CLOCK_MONOTONIC #endif int upnp_gettimeofday(struct timeval * tv) { #if defined(CLOCK_MONOTONIC_FAST) || defined(CLOCK_MONOTONIC) struct timespec ts; int ret_code = clock_gettime(UPNP_CLOCKID, &ts); if (ret_code == 0) { tv->tv_sec = ts.tv_sec; tv->tv_usec = ts.tv_nsec / 1000; } return ret_code; #else return gettimeofday(tv, NULL); #endif } minissdpd-1.5.20211105/upnputils.h010064400017500000024000000026561414106654600156610ustar00nanardstaff/* $Id: upnputils.h,v 1.5 2021/11/04 22:57:20 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2011-2021 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef UPNPUTILS_H_INCLUDED #define UPNPUTILS_H_INCLUDED /** * convert a struct sockaddr to a human readable string. * [ipv6]:port or ipv4:port * return the number of characters used (as snprintf) */ int sockaddr_to_string(const struct sockaddr * addr, char * str, size_t size); /** * set the file description as non blocking * return 0 in case of failure, 1 in case of success */ int set_non_blocking(int fd); /** * get the LAN which the peer belongs to */ struct lan_addr_s * get_lan_for_peer(const struct sockaddr * peer); /** * get the time for upnp * Similar to a monotonic gettimeofday(tv, NULL) */ int upnp_gettimeofday(struct timeval * tv); /** * define portability macros */ #if defined(__sun) static __inline size_t _sa_len(const struct sockaddr *addr) { if (addr->sa_family == AF_INET) return (sizeof(struct sockaddr_in)); else if (addr->sa_family == AF_INET6) return (sizeof(struct sockaddr_in6)); else return (sizeof(struct sockaddr)); } # define SA_LEN(sa) (_sa_len(sa)) #else #if !defined(SA_LEN) # define SA_LEN(sa) ((sa)->sa_len) #endif #endif #endif minissdpd-1.5.20211105/VERSION010064400017500000024000000000041324401730400144650ustar00nanardstaff1.5 minissdpd-1.5.20211105/ifacewatch.h010064400017500000024000000007201243612037200156730ustar00nanardstaff/* $Id: ifacewatch.h,v 1.4 2014/11/28 16:20:57 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2011-2014 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef IFACEWATCH_H_INCLUDED #define IFACEWATCH_H_INCLUDED int OpenAndConfInterfaceWatchSocket(void); int ProcessInterfaceWatch(int s, int s_ssdp, int s_ssdp6); #endif minissdpd-1.5.20211105/ifacewatch.c010064400017500000024000000226661326716305000157060ustar00nanardstaff/* $Id: ifacewatch.c,v 1.20 2018/04/22 19:39:34 nanard Exp $ */ /* MiniUPnP project * (c) 2011-2018 Thomas Bernard * website : http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include "config.h" #include #include #include #include #include #include #include #include #include #ifdef __linux__ #include #include #else /* __linux__ */ #include #ifdef AF_LINK #include #endif #endif /* __linux__ */ #include #include #include "config.h" #include "openssdpsocket.h" #include "upnputils.h" #include "minissdpdtypes.h" extern struct lan_addr_list lan_addrs; #ifndef __linux__ #if defined(__OpenBSD__) || defined(__FreeBSD__) #define SALIGN (sizeof(long) - 1) #else #define SALIGN (sizeof(int32_t) - 1) #endif #define SA_RLEN(sa) (SA_LEN(sa) ? ((SA_LEN(sa) + SALIGN) & ~SALIGN) : (SALIGN + 1)) #endif int OpenAndConfInterfaceWatchSocket(void) { int s; #ifdef __linux__ struct sockaddr_nl addr; s = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); #else /* __linux__*/ /*s = socket(PF_ROUTE, SOCK_RAW, AF_INET);*/ s = socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC); /* The family parameter may be AF_UNSPEC which will provide routing informa- * tion for all address families, or can be restricted to a specific address * family by specifying which one is desired. There can be more than one * routing socket open per system. */ #endif if(s < 0) { syslog(LOG_ERR, "%s socket: %m", "OpenAndConfInterfaceWatchSocket"); return -1; } if(!set_non_blocking(s)) { syslog(LOG_WARNING, "%s failed to set socket non blocking : %m", "OpenAndConfInterfaceWatchSocket"); } #ifdef __linux__ memset(&addr, 0, sizeof(addr)); addr.nl_family = AF_NETLINK; addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR; if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { syslog(LOG_ERR, "bind(netlink): %m"); close(s); return -1; } #endif return s; } /** * Process the message and add/drop multicast membership if needed */ int ProcessInterfaceWatch(int s, int s_ssdp, int s_ssdp6) { struct lan_addr_s * lan_addr; ssize_t len; char buffer[4096]; #ifdef __linux__ struct iovec iov; struct msghdr hdr; struct nlmsghdr *nlhdr; struct ifaddrmsg *ifa; struct rtattr *rta; int ifa_len; #ifndef ENABLE_IPV6 (void)s_ssdp6; #endif iov.iov_base = buffer; iov.iov_len = sizeof(buffer); memset(&hdr, 0, sizeof(hdr)); hdr.msg_iov = &iov; hdr.msg_iovlen = 1; len = recvmsg(s, &hdr, 0); if(len < 0) { syslog(LOG_ERR, "recvmsg(s, &hdr, 0): %m"); return -1; } for(nlhdr = (struct nlmsghdr *)buffer; NLMSG_OK(nlhdr, len); nlhdr = NLMSG_NEXT(nlhdr, len)) { int is_del = 0; char address[48]; char ifname[IFNAMSIZ]; address[0] = '\0'; ifname[0] = '\0'; if(nlhdr->nlmsg_type == NLMSG_DONE) break; switch(nlhdr->nlmsg_type) { /* case RTM_NEWLINK: */ /* case RTM_DELLINK: */ case RTM_DELADDR: is_del = 1; case RTM_NEWADDR: /* http://linux-hacks.blogspot.fr/2009/01/sample-code-to-learn-netlink.html */ ifa = (struct ifaddrmsg *)NLMSG_DATA(nlhdr); rta = (struct rtattr *)IFA_RTA(ifa); ifa_len = IFA_PAYLOAD(nlhdr); syslog(LOG_DEBUG, "%s %s index=%d fam=%d prefixlen=%d flags=%d scope=%d", "ProcessInterfaceWatchNotify", is_del ? "RTM_DELADDR" : "RTM_NEWADDR", ifa->ifa_index, ifa->ifa_family, ifa->ifa_prefixlen, ifa->ifa_flags, ifa->ifa_scope); for(;RTA_OK(rta, ifa_len); rta = RTA_NEXT(rta, ifa_len)) { /*RTA_DATA(rta)*/ /*rta_type : IFA_ADDRESS, IFA_LOCAL, etc. */ char tmp[128]; memset(tmp, 0, sizeof(tmp)); switch(rta->rta_type) { case IFA_ADDRESS: case IFA_LOCAL: case IFA_BROADCAST: case IFA_ANYCAST: inet_ntop(ifa->ifa_family, RTA_DATA(rta), tmp, sizeof(tmp)); if(rta->rta_type == IFA_ADDRESS) strncpy(address, tmp, sizeof(address)); break; case IFA_LABEL: strncpy(tmp, RTA_DATA(rta), sizeof(tmp)); strncpy(ifname, tmp, sizeof(ifname)); break; case IFA_CACHEINFO: { struct ifa_cacheinfo *cache_info; cache_info = RTA_DATA(rta); snprintf(tmp, sizeof(tmp), "valid=%u preferred=%u", cache_info->ifa_valid, cache_info->ifa_prefered); } break; default: strncpy(tmp, "*unknown*", sizeof(tmp)); } syslog(LOG_DEBUG, " rta_len=%d rta_type=%d '%s'", rta->rta_len, rta->rta_type, tmp); } syslog(LOG_INFO, "%s: %s/%d %s", is_del ? "RTM_DELADDR" : "RTM_NEWADDR", address, ifa->ifa_prefixlen, ifname); for(lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next) { #ifdef ENABLE_IPV6 if((0 == strcmp(address, lan_addr->str)) || (0 == strcmp(ifname, lan_addr->ifname)) || (ifa->ifa_index == lan_addr->index)) { #else if((0 == strcmp(address, lan_addr->str)) || (0 == strcmp(ifname, lan_addr->ifname))) { #endif if(ifa->ifa_family == AF_INET) AddDropMulticastMembership(s_ssdp, lan_addr, 0, is_del); #ifdef ENABLE_IPV6 else if(ifa->ifa_family == AF_INET6) AddDropMulticastMembership(s_ssdp6, lan_addr, 1, is_del); #endif break; } } break; default: syslog(LOG_DEBUG, "unknown nlmsg_type=%d", nlhdr->nlmsg_type); } } #else /* __linux__ */ struct rt_msghdr * rtm; struct ifa_msghdr * ifam; int is_del = 0; char tmp[64]; char * p; struct sockaddr * sa; int addr; char address[48]; char ifname[IFNAMSIZ]; int family = AF_UNSPEC; int prefixlen = 0; #ifndef ENABLE_IPV6 (void)s_ssdp6; #endif address[0] = '\0'; ifname[0] = '\0'; len = recv(s, buffer, sizeof(buffer), 0); if(len < 0) { syslog(LOG_ERR, "%s recv: %m", "ProcessInterfaceWatchNotify"); return -1; } rtm = (struct rt_msghdr *)buffer; switch(rtm->rtm_type) { case RTM_DELADDR: is_del = 1; case RTM_NEWADDR: ifam = (struct ifa_msghdr *)buffer; syslog(LOG_DEBUG, "%s %s len=%d/%hu index=%hu addrs=%x flags=%x", "ProcessInterfaceWatchNotify", is_del?"RTM_DELADDR":"RTM_NEWADDR", (int)len, ifam->ifam_msglen, ifam->ifam_index, ifam->ifam_addrs, ifam->ifam_flags); p = buffer + sizeof(struct ifa_msghdr); addr = 1; while(p < buffer + len) { sa = (struct sockaddr *)p; while(!(addr & ifam->ifam_addrs) && (addr <= ifam->ifam_addrs)) addr = addr << 1; sockaddr_to_string(sa, tmp, sizeof(tmp)); syslog(LOG_DEBUG, " %s", tmp); switch(addr) { case RTA_DST: case RTA_GATEWAY: break; case RTA_NETMASK: if(sa->sa_family == AF_INET #if defined(__OpenBSD__) || (sa->sa_family == 0 && sa->sa_len <= sizeof(struct sockaddr_in)) #endif ) { uint32_t sin_addr = ntohl(((struct sockaddr_in *)sa)->sin_addr.s_addr); while((prefixlen < 32) && ((sin_addr & (1 << (31 - prefixlen))) != 0)) prefixlen++; } else if(sa->sa_family == AF_INET6 #if defined(__OpenBSD__) || (sa->sa_family == 0 && sa->sa_len == sizeof(struct sockaddr_in6)) #endif ) { int i = 0; uint8_t * q = ((struct sockaddr_in6 *)sa)->sin6_addr.s6_addr; while((*q == 0xff) && (i < 16)) { prefixlen += 8; q++; i++; } if(i < 16) { i = 0; while((i < 8) && ((*q & (1 << (7 - i))) != 0)) i++; prefixlen += i; } } break; case RTA_GENMASK: break; case RTA_IFP: #ifdef AF_LINK if(sa->sa_family == AF_LINK) { struct sockaddr_dl * sdl = (struct sockaddr_dl *)sa; memset(ifname, 0, sizeof(ifname)); memcpy(ifname, sdl->sdl_data, sdl->sdl_nlen); } #endif break; case RTA_IFA: family = sa->sa_family; if(sa->sa_family == AF_INET) { inet_ntop(sa->sa_family, &((struct sockaddr_in *)sa)->sin_addr, address, sizeof(address)); } else if(sa->sa_family == AF_INET6) { inet_ntop(sa->sa_family, &((struct sockaddr_in6 *)sa)->sin6_addr, address, sizeof(address)); } break; case RTA_AUTHOR: break; case RTA_BRD: break; } #if 0 syslog(LOG_DEBUG, " %d.%d.%d.%d %02x%02x%02x%02x", (uint8_t)p[0], (uint8_t)p[1], (uint8_t)p[2], (uint8_t)p[3], (uint8_t)p[0], (uint8_t)p[1], (uint8_t)p[2], (uint8_t)p[3]); syslog(LOG_DEBUG, " %d.%d.%d.%d %02x%02x%02x%02x", (uint8_t)p[4], (uint8_t)p[5], (uint8_t)p[6], (uint8_t)p[7], (uint8_t)p[4], (uint8_t)p[5], (uint8_t)p[6], (uint8_t)p[7]); #endif p += SA_RLEN(sa); addr = addr << 1; } syslog(LOG_INFO, "%s: %s/%d %s", is_del ? "RTM_DELADDR" : "RTM_NEWADDR", address, prefixlen, ifname); for(lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next) { #ifdef ENABLE_IPV6 if((0 == strcmp(address, lan_addr->str)) || (0 == strcmp(ifname, lan_addr->ifname)) || (ifam->ifam_index == lan_addr->index)) { #else if((0 == strcmp(address, lan_addr->str)) || (0 == strcmp(ifname, lan_addr->ifname))) { #endif if(family == AF_INET) AddDropMulticastMembership(s_ssdp, lan_addr, 0, is_del); #ifdef ENABLE_IPV6 else if(family == AF_INET6) AddDropMulticastMembership(s_ssdp6, lan_addr, 1, is_del); #endif break; } } break; default: syslog(LOG_DEBUG, "Unknown RTM message : rtm->rtm_type=%d len=%d", rtm->rtm_type, (int)len); } #endif return 0; } minissdpd-1.5.20211105/README.fr010064400017500000024000000024031264546567600147370ustar00nanardstaffprotocole : connection à la socket unix. envoie d'une requete, retour d'une reponse. fermeture de la connexion. format de requete : 1 octet : type de la requete 0 - version 1 - type 2 - USN (id unique) 3 - tout n octets longueur de la chaine : 1 octet si < 128 sinon le bit haut indique s'il existe un octet suplementaire, etc... n octets = chaine format reponse : 1 octet : nombre de reponses (de 0 à 254) pour chaque rep : URL : n octets longueur de la chaine n octets = chaine Location ST: n octets longueur de la chaine n octets = chaine type USN: n octets longueur de la chaine n octets = chaine identifiant si le 1er octet est 255, alors le format est le suivant : 1 octet : 255 1 octet : type de notification 1 = NOTIF_NEW, 2 = NOTIF_NEW, 3 = NOTIF_REMOVE 1 octet : nombre de reponses (0 à 255) puis comme ci dessus pour chaque réponse * Type de requete 4 = submit service 1 octet = 4 (k,n) octets : longueur et chaine "ST" (service type) (k,n) octets : longueur et chaine "USN" (k,n) octets : longueur et chaine "Server" (k,n) octets : longueur et chaine "Location" Pas de reponse * Type de requete 5 = mode notification Reste connecté et reçoit au fur et à mesure les nouvelles connections réponses au format normal minissdpd-1.5.20211105/getifaddr.c010064400017500000024000000177331373445570200155470ustar00nanardstaff/* $Id: getifaddr.c,v 1.27 2020/09/28 21:32:33 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2006-2019 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include #include #include #include #include #include #include #include #if defined(sun) #include #endif #include "config.h" #include "getifaddr.h" #if defined(USE_GETIFADDRS) || defined(ENABLE_IPV6) || defined(ENABLE_PCP) #include #endif int getifaddr(const char * ifname, char * buf, int len, struct in_addr * addr, struct in_addr * mask) { #ifndef USE_GETIFADDRS /* use ioctl SIOCGIFADDR. Works only for ip v4 */ /* SIOCGIFADDR struct ifreq * */ int s; struct ifreq ifr; int ifrlen; struct sockaddr_in * ifaddr; ifrlen = sizeof(ifr); if(!ifname || ifname[0]=='\0') return -1; s = socket(PF_INET, SOCK_DGRAM, 0); if(s < 0) { syslog(LOG_ERR, "socket(PF_INET, SOCK_DGRAM): %m"); return -1; } strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); ifr.ifr_name[IFNAMSIZ-1] = '\0'; if(ioctl(s, SIOCGIFFLAGS, &ifr, &ifrlen) < 0) { syslog(LOG_DEBUG, "ioctl(s, SIOCGIFFLAGS, ...): %m"); close(s); return -1; } if ((ifr.ifr_flags & IFF_UP) == 0) { syslog(LOG_DEBUG, "network interface %s is down", ifname); close(s); return -1; } strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); ifr.ifr_name[IFNAMSIZ-1] = '\0'; if(ioctl(s, SIOCGIFADDR, &ifr, &ifrlen) < 0) { syslog(LOG_ERR, "ioctl(s, SIOCGIFADDR, ...): %m"); close(s); return -1; } ifaddr = (struct sockaddr_in *)&ifr.ifr_addr; if(addr) *addr = ifaddr->sin_addr; if(buf) { if(!inet_ntop(AF_INET, &ifaddr->sin_addr, buf, len)) { syslog(LOG_ERR, "inet_ntop(): %m"); close(s); return -1; } } if(mask) { strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); ifr.ifr_name[IFNAMSIZ-1] = '\0'; if(ioctl(s, SIOCGIFNETMASK, &ifr, &ifrlen) < 0) { syslog(LOG_ERR, "ioctl(s, SIOCGIFNETMASK, ...): %m"); close(s); return -1; } #ifdef ifr_netmask *mask = ((struct sockaddr_in *)&ifr.ifr_netmask)->sin_addr; #else *mask = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr; #endif } close(s); #else /* ifndef USE_GETIFADDRS */ /* Works for all address families (both ip v4 and ip v6) */ struct ifaddrs * ifap; struct ifaddrs * ife; if(!ifname || ifname[0]=='\0') return -1; if(getifaddrs(&ifap)<0) { syslog(LOG_ERR, "getifaddrs: %m"); return -1; } for(ife = ifap; ife; ife = ife->ifa_next) { /* skip other interfaces if one was specified */ if(ifname && (0 != strcmp(ifname, ife->ifa_name))) continue; if(ife->ifa_addr == NULL) continue; switch(ife->ifa_addr->sa_family) { case AF_INET: if(buf) { inet_ntop(ife->ifa_addr->sa_family, &((struct sockaddr_in *)ife->ifa_addr)->sin_addr, buf, len); } if(addr) *addr = ((struct sockaddr_in *)ife->ifa_addr)->sin_addr; if(mask) *mask = ((struct sockaddr_in *)ife->ifa_netmask)->sin_addr; break; /* case AF_INET6: inet_ntop(ife->ifa_addr->sa_family, &((struct sockaddr_in6 *)ife->ifa_addr)->sin6_addr, buf, len); */ } } freeifaddrs(ifap); #endif return 0; } #ifdef ENABLE_PCP int getifaddr_in6(const char * ifname, int af, struct in6_addr * addr) { #if defined(ENABLE_IPV6) || defined(USE_GETIFADDRS) struct ifaddrs * ifap; struct ifaddrs * ife; #ifdef ENABLE_IPV6 const struct sockaddr_in6 * tmpaddr; #endif /* ENABLE_IPV6 */ int found = 0; if(!ifname || ifname[0]=='\0') return -1; if(getifaddrs(&ifap)<0) { syslog(LOG_ERR, "getifaddrs: %m"); return -1; } for(ife = ifap; ife && !found; ife = ife->ifa_next) { /* skip other interfaces if one was specified */ if(ifname && (0 != strcmp(ifname, ife->ifa_name))) continue; if(ife->ifa_addr == NULL) continue; if (ife->ifa_addr->sa_family != af) continue; switch(ife->ifa_addr->sa_family) { case AF_INET: /* IPv4-mapped IPv6 address ::ffff:1.2.3.4 */ memset(addr->s6_addr, 0, 10); addr->s6_addr[10] = 0xff; addr->s6_addr[11] = 0xff; memcpy(addr->s6_addr + 12, &(((struct sockaddr_in *)ife->ifa_addr)->sin_addr.s_addr), 4); found = 1; break; #ifdef ENABLE_IPV6 case AF_INET6: tmpaddr = (const struct sockaddr_in6 *)ife->ifa_addr; if(!IN6_IS_ADDR_LOOPBACK(&tmpaddr->sin6_addr) && !IN6_IS_ADDR_LINKLOCAL(&tmpaddr->sin6_addr)) { memcpy(addr->s6_addr, &tmpaddr->sin6_addr, 16); found = 1; } break; #endif /* ENABLE_IPV6 */ } } freeifaddrs(ifap); return (found ? 0 : -1); #else /* defined(ENABLE_IPV6) || defined(USE_GETIFADDRS) */ /* IPv4 only */ struct in_addr addr4; if(af != AF_INET) return -1; if(getifaddr(ifname, NULL, 0, &addr4, NULL) < 0) return -1; /* IPv4-mapped IPv6 address ::ffff:1.2.3.4 */ memset(addr->s6_addr, 0, 10); addr->s6_addr[10] = 0xff; addr->s6_addr[11] = 0xff; memcpy(addr->s6_addr + 12, &addr4.s_addr, 4); return 0; #endif } #endif /* ENABLE_PCP */ #ifdef ENABLE_IPV6 int find_ipv6_addr(const char * ifname, char * dst, int n) { struct ifaddrs * ifap; struct ifaddrs * ife; const struct sockaddr_in6 * addr; char buf[64]; int r = 0; if(!dst) return -1; if(getifaddrs(&ifap)<0) { syslog(LOG_ERR, "getifaddrs: %m"); return -1; } for(ife = ifap; ife; ife = ife->ifa_next) { /* skip other interfaces if one was specified */ if(ifname && (0 != strcmp(ifname, ife->ifa_name))) continue; if(ife->ifa_addr == NULL) continue; if(ife->ifa_addr->sa_family == AF_INET6) { addr = (const struct sockaddr_in6 *)ife->ifa_addr; if(!IN6_IS_ADDR_LOOPBACK(&addr->sin6_addr) && !IN6_IS_ADDR_LINKLOCAL(&addr->sin6_addr)) { inet_ntop(ife->ifa_addr->sa_family, &addr->sin6_addr, buf, sizeof(buf)); /* add brackets */ snprintf(dst, n, "[%s]", buf); r = 1; } } } freeifaddrs(ifap); return r; } #endif /* List of IP address blocks which are private / reserved and therefore not suitable for public external IP addresses */ /* If interface has IP address from one of this block, then it is either behind NAT or port forwarding is impossible */ #define IP(a, b, c, d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) #define MSK(m) (32-(m)) static const struct { uint32_t address; uint32_t rmask; } reserved[] = { { IP( 0, 0, 0, 0), MSK( 8) }, /* RFC1122 "This host on this network" */ { IP( 10, 0, 0, 0), MSK( 8) }, /* RFC1918 Private-Use */ { IP(100, 64, 0, 0), MSK(10) }, /* RFC6598 Shared Address Space */ { IP(127, 0, 0, 0), MSK( 8) }, /* RFC1122 Loopback */ { IP(169, 254, 0, 0), MSK(16) }, /* RFC3927 Link-Local */ { IP(172, 16, 0, 0), MSK(12) }, /* RFC1918 Private-Use */ { IP(192, 0, 0, 0), MSK(24) }, /* RFC6890 IETF Protocol Assignments */ { IP(192, 0, 2, 0), MSK(24) }, /* RFC5737 Documentation (TEST-NET-1) */ { IP(192, 31, 196, 0), MSK(24) }, /* RFC7535 AS112-v4 */ { IP(192, 52, 193, 0), MSK(24) }, /* RFC7450 AMT */ { IP(192, 88, 99, 0), MSK(24) }, /* RFC7526 6to4 Relay Anycast */ { IP(192, 168, 0, 0), MSK(16) }, /* RFC1918 Private-Use */ { IP(192, 175, 48, 0), MSK(24) }, /* RFC7534 Direct Delegation AS112 Service */ { IP(198, 18, 0, 0), MSK(15) }, /* RFC2544 Benchmarking */ { IP(198, 51, 100, 0), MSK(24) }, /* RFC5737 Documentation (TEST-NET-2) */ { IP(203, 0, 113, 0), MSK(24) }, /* RFC5737 Documentation (TEST-NET-3) */ { IP(224, 0, 0, 0), MSK( 4) }, /* RFC1112 Multicast */ { IP(240, 0, 0, 0), MSK( 4) }, /* RFC1112 Reserved for Future Use + RFC919 Limited Broadcast */ }; #undef IP #undef MSK int addr_is_reserved(struct in_addr * addr) { uint32_t address = ntohl(addr->s_addr); size_t i; for (i = 0; i < sizeof(reserved)/sizeof(reserved[0]); ++i) { if ((address >> reserved[i].rmask) == (reserved[i].address >> reserved[i].rmask)) return 1; } return 0; } minissdpd-1.5.20211105/getifaddr.h010064400017500000024000000020541331765521100155340ustar00nanardstaff/* $Id: getifaddr.h,v 1.11 2018/07/06 11:47:29 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2018 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef GETIFADDR_H_INCLUDED #define GETIFADDR_H_INCLUDED struct in_addr; struct in6_addr; /* getifaddr() * take a network interface name and write the * ip v4 address as text in the buffer * returns: 0 success, -1 failure */ int getifaddr(const char * ifname, char * buf, int len, struct in_addr * addr, struct in_addr * mask); int getifaddr_in6(const char * ifname, int af, struct in6_addr* addr); /* find a non link local IP v6 address for the interface. * if ifname is NULL, look for all interfaces */ int find_ipv6_addr(const char * ifname, char * dst, int n); /* check if address is in private / reserved block (e.g. local area network) */ int addr_is_reserved(struct in_addr * addr); #endif minissdpd-1.5.20211105/getroute.c010064400017500000024000000161261326716305000154400ustar00nanardstaff/* $Id: getroute.c,v 1.6 2018/04/22 19:39:34 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2014 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include #include #include #include #include #include #ifdef __linux__ /*#include */ #include #include #include #else /* __linux__ */ #include #include #include #ifdef AF_LINK #include #endif /* AF_LINK */ #endif /* __linux__ */ #include "getroute.h" #include "upnputils.h" #include "config.h" /* get_src_for_route_to() function is only called in code * enabled with ENABLE_IPV6 */ #ifdef ENABLE_IPV6 int get_src_for_route_to(const struct sockaddr * dst, void * src, size_t * src_len, int * index) { #if __linux__ int fd = -1; struct nlmsghdr *h; int status; struct { struct nlmsghdr n; struct rtmsg r; char buf[1024]; } req; struct sockaddr_nl nladdr; struct iovec iov = { .iov_base = (void*) &req.n, }; struct msghdr msg = { .msg_name = &nladdr, .msg_namelen = sizeof(nladdr), .msg_iov = &iov, .msg_iovlen = 1, }; const struct sockaddr_in * dst4; const struct sockaddr_in6 * dst6; memset(&req, 0, sizeof(req)); req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); req.n.nlmsg_flags = NLM_F_REQUEST; req.n.nlmsg_type = RTM_GETROUTE; req.r.rtm_family = dst->sa_family; req.r.rtm_table = 0; req.r.rtm_protocol = 0; req.r.rtm_scope = 0; req.r.rtm_type = 0; req.r.rtm_src_len = 0; req.r.rtm_dst_len = 0; req.r.rtm_tos = 0; { char dst_str[128]; sockaddr_to_string(dst, dst_str, sizeof(dst_str)); syslog(LOG_DEBUG, "get_src_for_route_to (%s)", dst_str); } /* add address */ if(dst->sa_family == AF_INET) { dst4 = (const struct sockaddr_in *)dst; nfnl_addattr_l(&req.n, sizeof(req), RTA_DST, &dst4->sin_addr, 4); req.r.rtm_dst_len = 32; } else { dst6 = (const struct sockaddr_in6 *)dst; nfnl_addattr_l(&req.n, sizeof(req), RTA_DST, &dst6->sin6_addr, 16); req.r.rtm_dst_len = 128; } fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (fd < 0) { syslog(LOG_ERR, "socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE) : %m"); return -1; } memset(&nladdr, 0, sizeof(nladdr)); nladdr.nl_family = AF_NETLINK; req.n.nlmsg_seq = 1; iov.iov_len = req.n.nlmsg_len; status = sendmsg(fd, &msg, 0); if (status < 0) { syslog(LOG_ERR, "sendmsg(rtnetlink) : %m"); goto error; } memset(&req, 0, sizeof(req)); for(;;) { iov.iov_len = sizeof(req); status = recvmsg(fd, &msg, 0); if(status < 0) { if (errno == EINTR || errno == EAGAIN) continue; syslog(LOG_ERR, "recvmsg(rtnetlink) %m"); goto error; } if(status == 0) { syslog(LOG_ERR, "recvmsg(rtnetlink) EOF"); goto error; } for (h = (struct nlmsghdr*)&req.n; status >= (int)sizeof(*h); ) { int len = h->nlmsg_len; int l = len - sizeof(*h); if (l<0 || len>status) { if (msg.msg_flags & MSG_TRUNC) { syslog(LOG_ERR, "Truncated message"); } syslog(LOG_ERR, "malformed message: len=%d", len); goto error; } if(nladdr.nl_pid != 0 || h->nlmsg_seq != 1/*seq*/) { syslog(LOG_ERR, "wrong seq = %d\n", h->nlmsg_seq); /* Don't forget to skip that message. */ status -= NLMSG_ALIGN(len); h = (struct nlmsghdr*)((char*)h + NLMSG_ALIGN(len)); continue; } if(h->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *err = (struct nlmsgerr*)NLMSG_DATA(h); syslog(LOG_ERR, "NLMSG_ERROR %d : %s", err->error, strerror(-err->error)); goto error; } if(h->nlmsg_type == RTM_NEWROUTE) { struct rtattr * rta; int len = h->nlmsg_len; len -= NLMSG_LENGTH(sizeof(struct rtmsg)); for(rta = RTM_RTA(NLMSG_DATA((h))); RTA_OK(rta, len); rta = RTA_NEXT(rta,len)) { unsigned char * data = RTA_DATA(rta); if(rta->rta_type == RTA_PREFSRC) { if(src_len && src) { if(*src_len < RTA_PAYLOAD(rta)) { syslog(LOG_WARNING, "cannot copy src: %u<%lu", (unsigned)*src_len, (unsigned long)RTA_PAYLOAD(rta)); goto error; } *src_len = RTA_PAYLOAD(rta); memcpy(src, data, RTA_PAYLOAD(rta)); } } else if(rta->rta_type == RTA_OIF) { if(index) memcpy(index, data, sizeof(int)); } } close(fd); return 0; } status -= NLMSG_ALIGN(len); h = (struct nlmsghdr*)((char*)h + NLMSG_ALIGN(len)); } } syslog(LOG_WARNING, "get_src_for_route_to() : src not found"); error: if(fd >= 0) close(fd); return -1; #else /* __linux__ */ int found = 0; int s; int l, i; char * p; struct sockaddr * sa; struct { struct rt_msghdr m_rtm; char m_space[512]; } m_rtmsg; #define rtm m_rtmsg.m_rtm if(dst == NULL) return -1; #ifdef __APPLE__ if(dst->sa_family == AF_INET6) { syslog(LOG_ERR, "Sorry, get_src_for_route_to() is known to fail with IPV6 on OS X..."); return -1; } #endif s = socket(PF_ROUTE, SOCK_RAW, dst->sa_family); if(s < 0) { syslog(LOG_ERR, "socket(PF_ROUTE) failed : %m"); return -1; } memset(&rtm, 0, sizeof(rtm)); rtm.rtm_type = RTM_GET; rtm.rtm_flags = RTF_UP; rtm.rtm_version = RTM_VERSION; rtm.rtm_seq = 1; rtm.rtm_addrs = RTA_DST; /* destination address */ memcpy(m_rtmsg.m_space, dst, sizeof(struct sockaddr)); rtm.rtm_msglen = sizeof(struct rt_msghdr) + sizeof(struct sockaddr); if(write(s, &m_rtmsg, rtm.rtm_msglen) < 0) { syslog(LOG_ERR, "write: %m"); close(s); return -1; } do { l = read(s, &m_rtmsg, sizeof(m_rtmsg)); if(l<0) { syslog(LOG_ERR, "read: %m"); close(s); return -1; } syslog(LOG_DEBUG, "read l=%d seq=%d pid=%d", l, rtm.rtm_seq, rtm.rtm_pid); } while(l > 0 && (rtm.rtm_pid != getpid() || rtm.rtm_seq != 1)); close(s); p = m_rtmsg.m_space; if(rtm.rtm_addrs) { for(i=1; i<0x8000; i <<= 1) { if(i & rtm.rtm_addrs) { char tmp[256] = { 0 }; sa = (struct sockaddr *)p; sockaddr_to_string(sa, tmp, sizeof(tmp)); syslog(LOG_DEBUG, "type=%d sa_len=%d sa_family=%d %s", i, SA_LEN(sa), sa->sa_family, tmp); if((i == RTA_DST || i == RTA_GATEWAY) && (src_len && src)) { size_t len = 0; void * paddr = NULL; if(sa->sa_family == AF_INET) { paddr = &((struct sockaddr_in *)sa)->sin_addr; len = sizeof(struct in_addr); } else if(sa->sa_family == AF_INET6) { paddr = &((struct sockaddr_in6 *)sa)->sin6_addr; len = sizeof(struct in6_addr); } if(paddr) { if(*src_len < len) { syslog(LOG_WARNING, "cannot copy src. %u<%u", (unsigned)*src_len, (unsigned)len); return -1; } memcpy(src, paddr, len); *src_len = len; found = 1; } } #ifdef AF_LINK if(sa->sa_family == AF_LINK) { struct sockaddr_dl * sdl = (struct sockaddr_dl *)sa; if(index) *index = sdl->sdl_index; } #endif p += SA_LEN(sa); } } } return found ? 0 : -1; #endif /* __linux__ */ } #endif /* ENABLE_IPV6 */ minissdpd-1.5.20211105/getroute.h010064400017500000024000000007621210443263100154350ustar00nanardstaff/* $Id: getroute.h,v 1.3 2013/02/06 10:50:04 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2013 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef GETROUTE_H_INCLUDED #define GETROUTE_H_INCLUDED int get_src_for_route_to(const struct sockaddr * dst, void * src, size_t * src_len, int * index); #endif minissdpd-1.5.20211105/minissdpdtypes.h010064400017500000024000000015751243612035200166630ustar00nanardstaff/* $Id: minissdpdtypes.h,v 1.1 2014/11/28 16:20:58 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2014 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef MINISSDPDTYPES_H_INCLUDED #define MINISSDPDTYPES_H_INCLUDED #include "config.h" #include #include #include /* structure and list for storing lan addresses * with ascii representation and mask */ struct lan_addr_s { char ifname[IFNAMSIZ]; /* example: eth0 */ #ifdef ENABLE_IPV6 unsigned int index; /* use if_nametoindex() */ #endif /* ENABLE_IPV6 */ char str[16]; /* example: 192.168.0.1 */ struct in_addr addr, mask; /* ip/mask */ LIST_ENTRY(lan_addr_s) list; }; LIST_HEAD(lan_addr_list, lan_addr_s); #endif /* MINISSDPDTYPES_H_INCLUDED */ minissdpd-1.5.20211105/asyncsendto.c010064400017500000024000000234001376024217600161320ustar00nanardstaff/* $Id: asyncsendto.c,v 1.12 2020/11/11 12:13:26 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2020 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "asyncsendto.h" #include "upnputils.h" enum send_state {ESCHEDULED=1, EWAITREADY=2, ESENDNOW=3} state; /* state diagram for a packet : * * | * V * -> ESCHEDULED -> ESENDNOW -> sent * ^ | * | V * EWAITREADY -> sent */ struct scheduled_send { LIST_ENTRY(scheduled_send) entries; struct timeval ts; enum send_state state; int sockfd; const void * buf; size_t len; int flags; const struct sockaddr *dest_addr; socklen_t addrlen; const struct sockaddr_in6 *src_addr; char data[]; }; static LIST_HEAD(listhead, scheduled_send) send_list = { NULL }; /* * ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, * const struct sockaddr *dest_addr, socklen_t addrlen); */ static ssize_t send_from_to(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr_in6 *src_addr, const struct sockaddr *dest_addr, socklen_t addrlen) { #ifdef IPV6_PKTINFO if(src_addr) { struct iovec iov; struct in6_pktinfo ipi6; uint8_t c[CMSG_SPACE(sizeof(ipi6))]; struct msghdr msg; struct cmsghdr* cmsg; iov.iov_base = (void *)buf; iov.iov_len = len; memset(&msg, 0, sizeof(msg)); msg.msg_iov = &iov; msg.msg_iovlen = 1; ipi6.ipi6_addr = src_addr->sin6_addr; ipi6.ipi6_ifindex = src_addr->sin6_scope_id; msg.msg_control = c; msg.msg_controllen = sizeof(c); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = IPPROTO_IPV6; cmsg->cmsg_type = IPV6_PKTINFO; cmsg->cmsg_len = CMSG_LEN(sizeof(ipi6)); memcpy(CMSG_DATA(cmsg), &ipi6, sizeof(ipi6)); msg.msg_name = (void *)dest_addr; msg.msg_namelen = addrlen; return sendmsg(sockfd, &msg, flags); } else { #endif /* IPV6_PKTINFO */ return sendto(sockfd, buf, len, flags, dest_addr, addrlen); #ifdef IPV6_PKTINFO } #endif /* IPV6_PKTINFO */ } /* delay = milli seconds */ ssize_t sendto_schedule2(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen, const struct sockaddr_in6 *src_addr, unsigned int delay) { enum send_state state; ssize_t n; size_t alloc_len; struct timeval tv; struct scheduled_send * elt; if(delay == 0) { /* first try to send at once */ n = send_from_to(sockfd, buf, len, flags, src_addr, dest_addr, addrlen); if(n >= 0) return n; else if(errno == EAGAIN || errno == EWOULDBLOCK) { /* use select() on this socket */ state = EWAITREADY; } else if(errno == EINTR) { state = ESENDNOW; } else { /* uncatched error */ return n; } } else { state = ESCHEDULED; } /* schedule */ if(upnp_gettimeofday(&tv) < 0) { return -1; } /* allocate enough space for structure + buffers */ alloc_len = sizeof(struct scheduled_send) + len + addrlen; if(src_addr) alloc_len += sizeof(struct sockaddr_in6); elt = malloc(alloc_len); if(elt == NULL) { syslog(LOG_ERR, "malloc failed to allocate %u bytes", (unsigned)alloc_len); return -1; } elt->state = state; /* time the packet should be sent */ elt->ts.tv_sec = tv.tv_sec + (delay / 1000); elt->ts.tv_usec = tv.tv_usec + (delay % 1000) * 1000; if(elt->ts.tv_usec > 1000000) { elt->ts.tv_sec++; elt->ts.tv_usec -= 1000000; } elt->sockfd = sockfd; elt->flags = flags; memcpy(elt->data, dest_addr, addrlen); elt->dest_addr = (struct sockaddr *)elt->data; elt->addrlen = addrlen; if(src_addr) { elt->src_addr = (struct sockaddr_in6 *)(elt->data + addrlen); memcpy((void *)elt->src_addr, src_addr, sizeof(struct sockaddr_in6)); elt->buf = (void *)(elt->data + addrlen + sizeof(struct sockaddr_in6)); } else { elt->src_addr = NULL; elt->buf = (void *)(elt->data + addrlen); } elt->len = len; memcpy((void *)elt->buf, buf, len); /* insert */ LIST_INSERT_HEAD( &send_list, elt, entries); return 0; } /* try to send at once, and queue the packet if needed */ ssize_t sendto_or_schedule(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) { return sendto_schedule2(sockfd, buf, len, flags, dest_addr, addrlen, NULL, 0); } ssize_t sendto_or_schedule2(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen, const struct sockaddr_in6 *src_addr) { return sendto_schedule2(sockfd, buf, len, flags, dest_addr, addrlen, src_addr, 0); } /* get_next_scheduled_send() return number of scheduled send in list */ int get_next_scheduled_send(struct timeval * next_send) { int n = 0; struct scheduled_send * elt; if(next_send == NULL) return -1; for(elt = send_list.lh_first; elt != NULL; elt = elt->entries.le_next) { if(n == 0 || (elt->ts.tv_sec < next_send->tv_sec) || (elt->ts.tv_sec == next_send->tv_sec && elt->ts.tv_usec < next_send->tv_usec)) { next_send->tv_sec = elt->ts.tv_sec; next_send->tv_usec = elt->ts.tv_usec; } n++; } return n; } /* update writefds for select() call * return the number of packets to try to send at once */ int get_sendto_fds(fd_set * writefds, int * max_fd, const struct timeval * now) { int n = 0; struct scheduled_send * elt; for(elt = send_list.lh_first; elt != NULL; elt = elt->entries.le_next) { if(elt->state == EWAITREADY) { /* last sendto() call returned EAGAIN/EWOULDBLOCK */ FD_SET(elt->sockfd, writefds); if(elt->sockfd > *max_fd) *max_fd = elt->sockfd; n++; } else if((elt->ts.tv_sec < now->tv_sec) || (elt->ts.tv_sec == now->tv_sec && elt->ts.tv_usec <= now->tv_usec)) { /* we waited long enough, now send ! */ elt->state = ESENDNOW; n++; } } return n; } /* executed sendto() when needed */ int try_sendto(fd_set * writefds) { int ret = 0; ssize_t n; struct scheduled_send * elt; struct scheduled_send * next; for(elt = send_list.lh_first; elt != NULL; elt = next) { next = elt->entries.le_next; if((elt->state == ESENDNOW) || (elt->state == EWAITREADY && FD_ISSET(elt->sockfd, writefds))) { #ifdef DEBUG syslog(LOG_DEBUG, "%s: %d bytes on socket %d", "try_sendto", (int)elt->len, elt->sockfd); #endif n = send_from_to(elt->sockfd, elt->buf, elt->len, elt->flags, elt->src_addr, elt->dest_addr, elt->addrlen); /*n = sendto(elt->sockfd, elt->buf, elt->len, elt->flags, elt->dest_addr, elt->addrlen);*/ if(n < 0) { if(errno == EINTR) { /* retry at once */ elt->state = ESENDNOW; continue; } else if(errno == EAGAIN || errno == EWOULDBLOCK) { /* retry once the socket is ready for writing */ elt->state = EWAITREADY; continue; } else { char addr_str[64]; /* uncatched error */ if(sockaddr_to_string(elt->dest_addr, addr_str, sizeof(addr_str)) <= 0) addr_str[0] = '\0'; syslog(LOG_ERR, "%s(sock=%d, len=%u, dest=%s): sendto: %m", "try_sendto", elt->sockfd, (unsigned)elt->len, addr_str); ret--; } } else if((int)n != (int)elt->len) { syslog(LOG_WARNING, "%s: %d bytes sent out of %d", "try_sendto", (int)n, (int)elt->len); } /* remove from the list */ LIST_REMOVE(elt, entries); free(elt); } } return ret; } /* maximum execution time for finalize_sendto() in milliseconds */ #define FINALIZE_SENDTO_DELAY (500) /* empty the list */ void finalize_sendto(void) { ssize_t n; struct scheduled_send * elt; struct scheduled_send * next; fd_set writefds; struct timeval deadline; struct timeval now; struct timeval timeout; int max_fd; if(upnp_gettimeofday(&deadline) < 0) { syslog(LOG_ERR, "gettimeofday: %m"); return; } deadline.tv_usec += FINALIZE_SENDTO_DELAY*1000; if(deadline.tv_usec > 1000000) { deadline.tv_sec++; deadline.tv_usec -= 1000000; } while(send_list.lh_first) { FD_ZERO(&writefds); max_fd = -1; for(elt = send_list.lh_first; elt != NULL; elt = next) { next = elt->entries.le_next; syslog(LOG_DEBUG, "finalize_sendto(): %d bytes on socket %d", (int)elt->len, elt->sockfd); n = send_from_to(elt->sockfd, elt->buf, elt->len, elt->flags, elt->src_addr, elt->dest_addr, elt->addrlen); /*n = sendto(elt->sockfd, elt->buf, elt->len, elt->flags, elt->dest_addr, elt->addrlen);*/ if(n < 0) { if(errno==EAGAIN || errno==EWOULDBLOCK) { FD_SET(elt->sockfd, &writefds); if(elt->sockfd > max_fd) max_fd = elt->sockfd; continue; } syslog(LOG_WARNING, "finalize_sendto(): socket=%d sendto: %m", elt->sockfd); } /* remove from the list */ LIST_REMOVE(elt, entries); free(elt); } /* check deadline */ if(upnp_gettimeofday(&now) < 0) { syslog(LOG_ERR, "gettimeofday: %m"); return; } if(now.tv_sec > deadline.tv_sec || (now.tv_sec == deadline.tv_sec && now.tv_usec > deadline.tv_usec)) { /* deadline ! */ while((elt = send_list.lh_first) != NULL) { LIST_REMOVE(elt, entries); free(elt); } return; } /* compute timeout value */ timeout.tv_sec = deadline.tv_sec - now.tv_sec; timeout.tv_usec = deadline.tv_usec - now.tv_usec; if(timeout.tv_usec < 0) { timeout.tv_sec--; timeout.tv_usec += 1000000; } if(max_fd >= 0) { if(select(max_fd + 1, NULL, &writefds, NULL, &timeout) < 0) { syslog(LOG_ERR, "select: %m"); return; } } } } minissdpd-1.5.20211105/asyncsendto.h010064400017500000024000000035311317665377200161530ustar00nanardstaff/* $Id: asyncsendto.h,v 1.3 2017/11/02 15:48:29 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2017 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #ifndef ASYNCSENDTO_H_INCLUDED #define ASYNCSENDTO_H_INCLUDED /* for fd_set */ #include /* sendto_schedule() : see sendto(2) * schedule sendto() call after delay (milliseconds) */ ssize_t sendto_schedule2(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen, const struct sockaddr_in6 *src_addr, unsigned int delay); #define sendto_schedule(sockfd, buf, len, flags, dest_addr, addrlen, delay) \ sendto_schedule2(sockfd, buf, len, flags, dest_addr, addrlen, NULL, delay) /* sendto_schedule() : see sendto(2) * try sendto() at once and schedule if EINTR/EAGAIN/EWOULDBLOCK */ ssize_t sendto_or_schedule(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); /* same as sendto_schedule() except it will try to set source address * (for IPV6 only) */ ssize_t sendto_or_schedule2(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen, const struct sockaddr_in6 *src_addr); /* get_next_scheduled_send() * return number of scheduled sendto * set next_send to timestamp to send next packet */ int get_next_scheduled_send(struct timeval * next_send); /* execute sendto() for needed packets */ int try_sendto(fd_set * writefds); /* set writefds before select() */ int get_sendto_fds(fd_set * writefds, int * max_fd, const struct timeval * now); /* empty the list */ void finalize_sendto(void); #endif minissdpd-1.5.20211105/testminissdpd.sh010075500017500000024000000006701343002535200166560ustar00nanardstaff#!/bin/sh # $Id: testminissdpd.sh,v 1.9 2019/02/10 13:40:40 nanard Exp $ # (c) 2017 Thomas Bernard OS=`uname -s` # if set, 1st argument is network interface if [ -n "$1" ] ; then IF=$1 else case $OS in *BSD | Darwin | SunOS) IF=lo0 ;; *) IF=lo ;; esac fi SOCKET=`mktemp -t minissdpdsocketXXXXXX` PID="${SOCKET}.pid" ./minissdpd -s $SOCKET -p $PID -i $IF || exit 1 ./testminissdpd -s $SOCKET || exit 2 kill `cat $PID` minissdpd-1.5.20211105/printresponse.c010064400017500000024000000041101264740756200165150ustar00nanardstaff/* $Id: printresponse.c,v 1.2 2016/01/19 10:24:59 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * Project : miniupnp * website : http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * Author : Thomas BERNARD * copyright (c) 2005-2016 Thomas Bernard * This software is subjet to the conditions detailed in the * provided LICENCE file. */ #include #include "codelength.h" #define NOTIF_NEW 1 #define NOTIF_UPDATE 2 #define NOTIF_REMOVE 3 void printresponse(const unsigned char * resp, int n) { int i, l; int notif_type; unsigned int nresp; const unsigned char * p; if(n == 0) return; /* first, hexdump the response : */ for(i = 0; i < n; i += 16) { printf("%06x | ", i); for(l = i; l < n && l < (i + 16); l++) printf("%02x ", resp[l]); while(l < (i + 16)) { printf(" "); l++; } printf("| "); for(l = i; l < n && l < (i + 16); l++) putchar((resp[l] >= ' ' && resp[l] < 128) ? resp[l] : '.'); putchar('\n'); } for(p = resp; p < resp + n; ) { /* now parse and display all devices of response */ nresp = p[0]; /* 1st byte : number of devices in response */ if(nresp == 0xff) { /* notification */ notif_type = p[1]; nresp = p[2]; printf("Notification : "); switch(notif_type) { case NOTIF_NEW: printf("new\n"); break; case NOTIF_UPDATE: printf("update\n"); break; case NOTIF_REMOVE: printf("remove\n"); break; default: printf("**UNKNOWN**\n"); } p += 3; } else { p++; } for(i = 0; i < (int)nresp; i++) { if(p >= resp + n) goto error; /*l = *(p++);*/ DECODELENGTH(l, p); if(p + l > resp + n) goto error; printf("%d - %.*s\n", i, l, p); /* URL */ p += l; if(p >= resp + n) goto error; /*l = *(p++);*/ DECODELENGTH(l, p); if(p + l > resp + n) goto error; printf(" %.*s\n", l, p); /* ST */ p += l; if(p >= resp + n) goto error; /*l = *(p++);*/ DECODELENGTH(l, p); if(p + l > resp + n) goto error; printf(" %.*s\n", l, p); /* USN */ p += l; } } return; error: printf("*** WARNING : TRUNCATED RESPONSE ***\n"); } minissdpd-1.5.20211105/printresponse.h010064400017500000024000000006231264546564300165310ustar00nanardstaff/* $Id: printresponse.h,v 1.1 2016/01/13 15:22:11 nanard Exp $ */ /* Project : miniupnp * Author : Thomas BERNARD * copyright (c) 2016 Thomas Bernard * This software is subjet to the conditions detailed in the * provided LICENCE file. */ #ifndef PRINTRESPONSE_H_INCLUDED #define PRINTRESPONSE_H_INCLUDED void printresponse(const unsigned char * resp, int n); #endif /* PRINTRESPONSE_H_INCLUDED */ minissdpd-1.5.20211105/showminissdpdnotif.c010064400017500000024000000040321264740756200175400ustar00nanardstaff/* $Id: showminissdpdnotif.c,v 1.2 2016/01/19 10:25:24 nanard Exp $ */ /* vim: shiftwidth=4 tabstop=4 noexpandtab * MiniUPnP project * (c) 2016 Thomas Bernard * website : http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include #include #include #include #include #include #include "codelength.h" #include "printresponse.h" static volatile sig_atomic_t quitting = 0; static void sighandler(int sig) { (void)sig; quitting = 1; } int main(int argc, char * * argv) { int i; int s; struct sockaddr_un addr; const char * sockpath = "/var/run/minissdpd.sock"; unsigned char buffer[4096]; ssize_t n; const char command5[] = { 0x05, 0x00 }; struct sigaction sa; for(i=0; i 0x7F: l = l >> 7 c = (l & 0x7F) | 0x80 encodedlen = c.to_bytes(1, 'little') + encodedlen return encodedlen + s def submit_to_minissdpd(st, usn, server, url, sockpath="/var/run/minissdpd.sock"): """ submits the specified service to MiniSSDPD (if running)""" # First check if sockpath exists i.e. MiniSSDPD is running if not os.path.exists(sockpath): return -1, f"Error: {sockpath} does not exist. Is minissdpd running?" # OK, submit sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: sock.connect(sockpath) sock.send(b'\x04' + codelength(st) + codelength(usn) + codelength(server) + codelength(url)) except socket.error as msg: print(msg) return -1, msg finally: sock.close() return 0, "OK" if __name__ == "__main__": # Example usage rc, message = submit_to_minissdpd( b'urn:schemas-upnp-org:device:InternetGatewayDevice:1', b'uuid:73616d61-6a6b-7a74-650a-0d24d4a5d636::urn:schemas-upnp-org:device:InternetGatewayDevice:1', b'MyServer/0.0', b'http://192.168.0.1:1234/rootDesc.xml', ) if rc == 0: print("OK: submitting to MiniSSDPD went well") else: print("Not OK. Error message is:", message)