Annotation of embedaddon/miniupnpd/minissdpd/minissdpd.c, revision 1.1
1.1 ! misho 1: /* $Id: minissdpd.c,v 1.61 2021/11/04 23:27:28 nanard Exp $ */
! 2: /* vim: tabstop=4 shiftwidth=4 noexpandtab
! 3: * MiniUPnP project
! 4: * (c) 2007-2022 Thomas Bernard
! 5: * website : http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/
! 6: * This software is subject to the conditions detailed
! 7: * in the LICENCE file provided within the distribution */
! 8:
! 9: #include "config.h"
! 10:
! 11: #include <stdlib.h>
! 12: #include <stdio.h>
! 13: #include <string.h>
! 14: #include <signal.h>
! 15: #include <errno.h>
! 16: #include <sys/time.h>
! 17: #include <sys/types.h>
! 18: #include <sys/socket.h>
! 19: #include <unistd.h>
! 20: #include <netinet/in.h>
! 21: #include <arpa/inet.h>
! 22: #include <syslog.h>
! 23: #include <ctype.h>
! 24: #include <time.h>
! 25: #include <sys/queue.h>
! 26: /* for chmod : */
! 27: #include <sys/stat.h>
! 28: /* unix sockets */
! 29: #include <sys/un.h>
! 30: /* for getpwnam() and getgrnam() */
! 31: #if 0
! 32: #include <pwd.h>
! 33: #include <grp.h>
! 34: #endif
! 35:
! 36: /* LOG_PERROR does not exist on Solaris */
! 37: #ifndef LOG_PERROR
! 38: #define LOG_PERROR 0
! 39: #endif /* LOG_PERROR */
! 40:
! 41: #include "getifaddr.h"
! 42: #include "upnputils.h"
! 43: #include "openssdpsocket.h"
! 44: #include "daemonize.h"
! 45: #include "codelength.h"
! 46: #include "ifacewatch.h"
! 47: #include "minissdpdtypes.h"
! 48: #include "asyncsendto.h"
! 49:
! 50: #define SET_MAX(max, x) if((x) > (max)) (max) = (x)
! 51: #ifndef MIN
! 52: #define MIN(x,y) (((x)<(y))?(x):(y))
! 53: #endif
! 54:
! 55: /* current request management structure */
! 56: struct reqelem {
! 57: int socket;
! 58: int is_notify; /* has subscribed to notifications */
! 59: LIST_ENTRY(reqelem) entries;
! 60: unsigned char * output_buffer;
! 61: int output_buffer_offset;
! 62: int output_buffer_len;
! 63: };
! 64:
! 65: /* device data structures */
! 66: struct header {
! 67: const char * p; /* string pointer */
! 68: int l; /* string length */
! 69: };
! 70:
! 71: #define HEADER_NT 0
! 72: #define HEADER_USN 1
! 73: #define HEADER_LOCATION 2
! 74:
! 75: struct device {
! 76: struct device * next;
! 77: time_t t; /* validity time */
! 78: struct header headers[3]; /* NT, USN and LOCATION headers */
! 79: char data[];
! 80: };
! 81:
! 82: /* Services stored for answering to M-SEARCH */
! 83: struct service {
! 84: char * st; /* Service type */
! 85: char * usn; /* Unique identifier */
! 86: char * server; /* Server string */
! 87: char * location; /* URL */
! 88: LIST_ENTRY(service) entries;
! 89: };
! 90: LIST_HEAD(servicehead, service) servicelisthead;
! 91:
! 92: #define NTS_SSDP_ALIVE 1
! 93: #define NTS_SSDP_BYEBYE 2
! 94: #define NTS_SSDP_UPDATE 3
! 95:
! 96: /* request types */
! 97: enum request_type {
! 98: MINISSDPD_GET_VERSION = 0,
! 99: MINISSDPD_SEARCH_TYPE = 1,
! 100: MINISSDPD_SEARCH_USN = 2,
! 101: MINISSDPD_SEARCH_ALL = 3,
! 102: MINISSDPD_SUBMIT = 4,
! 103: MINISSDPD_NOTIF = 5
! 104: };
! 105:
! 106: /* discovered device list kept in memory */
! 107: struct device * devlist = 0;
! 108:
! 109: /* bootid and configid */
! 110: unsigned int upnp_bootid = 1;
! 111: unsigned int upnp_configid = 1337;
! 112:
! 113: /* LAN interfaces/addresses */
! 114: struct lan_addr_list lan_addrs;
! 115:
! 116: /* connected clients */
! 117: LIST_HEAD(reqstructhead, reqelem) reqlisthead;
! 118:
! 119: /* functions prototypes */
! 120:
! 121: #define NOTIF_NEW 1
! 122: #define NOTIF_UPDATE 2
! 123: #define NOTIF_REMOVE 3
! 124: static void
! 125: sendNotifications(int notif_type, const struct device * dev, const struct service * serv);
! 126:
! 127: /* functions */
! 128:
! 129: /* parselanaddr()
! 130: * parse address with mask
! 131: * ex: 192.168.1.1/24 or 192.168.1.1/255.255.255.0
! 132: *
! 133: * Can also use the interface name (ie eth0)
! 134: *
! 135: * return value :
! 136: * 0 : ok
! 137: * -1 : error */
! 138: static int
! 139: parselanaddr(struct lan_addr_s * lan_addr, const char * str)
! 140: {
! 141: const char * p;
! 142: int n;
! 143: char tmp[16];
! 144:
! 145: memset(lan_addr, 0, sizeof(struct lan_addr_s));
! 146: p = str;
! 147: while(*p && *p != '/' && !isspace(*p))
! 148: p++;
! 149: n = p - str;
! 150: if(!isdigit(str[0]) && n < (int)sizeof(lan_addr->ifname)) {
! 151: /* not starting with a digit : suppose it is an interface name */
! 152: memcpy(lan_addr->ifname, str, n);
! 153: lan_addr->ifname[n] = '\0';
! 154: if(getifaddr(lan_addr->ifname, lan_addr->str, sizeof(lan_addr->str),
! 155: &lan_addr->addr, &lan_addr->mask) < 0)
! 156: goto parselan_error;
! 157: /*printf("%s => %s\n", lan_addr->ifname, lan_addr->str);*/
! 158: } else {
! 159: if(n>15)
! 160: goto parselan_error;
! 161: memcpy(lan_addr->str, str, n);
! 162: lan_addr->str[n] = '\0';
! 163: if(!inet_aton(lan_addr->str, &lan_addr->addr))
! 164: goto parselan_error;
! 165: }
! 166: if(*p == '/') {
! 167: const char * q = ++p;
! 168: while(*p && isdigit(*p))
! 169: p++;
! 170: if(*p=='.') {
! 171: /* parse mask in /255.255.255.0 format */
! 172: while(*p && (*p=='.' || isdigit(*p)))
! 173: p++;
! 174: n = p - q;
! 175: if(n>15)
! 176: goto parselan_error;
! 177: memcpy(tmp, q, n);
! 178: tmp[n] = '\0';
! 179: if(!inet_aton(tmp, &lan_addr->mask))
! 180: goto parselan_error;
! 181: } else {
! 182: /* it is a /24 format */
! 183: int nbits = atoi(q);
! 184: if(nbits > 32 || nbits < 0)
! 185: goto parselan_error;
! 186: lan_addr->mask.s_addr = htonl(nbits ? (0xffffffffu << (32 - nbits)) : 0);
! 187: }
! 188: } else if(lan_addr->mask.s_addr == 0) {
! 189: /* by default, networks are /24 */
! 190: lan_addr->mask.s_addr = htonl(0xffffff00u);
! 191: }
! 192: #ifdef ENABLE_IPV6
! 193: if(lan_addr->ifname[0] != '\0') {
! 194: lan_addr->index = if_nametoindex(lan_addr->ifname);
! 195: if(lan_addr->index == 0)
! 196: fprintf(stderr, "Cannot get index for network interface %s\n",
! 197: lan_addr->ifname);
! 198: } else {
! 199: fprintf(stderr,
! 200: "Error: please specify LAN network interface by name instead of IPv4 address : %s\n",
! 201: str);
! 202: return -1;
! 203: }
! 204: #endif /* ENABLE_IPV6 */
! 205: return 0;
! 206: parselan_error:
! 207: fprintf(stderr, "Error parsing address/mask (or interface name) : %s\n",
! 208: str);
! 209: return -1;
! 210: }
! 211:
! 212: static int
! 213: write_buffer(struct reqelem * req)
! 214: {
! 215: if(req->output_buffer && req->output_buffer_len > 0) {
! 216: int n = write(req->socket,
! 217: req->output_buffer + req->output_buffer_offset,
! 218: req->output_buffer_len);
! 219: if(n >= 0) {
! 220: req->output_buffer_offset += n;
! 221: req->output_buffer_len -= n;
! 222: } else if(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {
! 223: return 0;
! 224: }
! 225: return n;
! 226: } else {
! 227: return 0;
! 228: }
! 229: }
! 230:
! 231: static int
! 232: add_to_buffer(struct reqelem * req, const unsigned char * data, int len)
! 233: {
! 234: unsigned char * tmp;
! 235: if(req->output_buffer_offset > 0) {
! 236: memmove(req->output_buffer, req->output_buffer + req->output_buffer_offset, req->output_buffer_len);
! 237: req->output_buffer_offset = 0;
! 238: }
! 239: tmp = realloc(req->output_buffer, req->output_buffer_len + len);
! 240: if(tmp == NULL) {
! 241: syslog(LOG_ERR, "%s: failed to allocate %d bytes",
! 242: __func__, req->output_buffer_len + len);
! 243: return -1;
! 244: }
! 245: req->output_buffer = tmp;
! 246: memcpy(req->output_buffer + req->output_buffer_len, data, len);
! 247: req->output_buffer_len += len;
! 248: return len;
! 249: }
! 250:
! 251: static int
! 252: write_or_buffer(struct reqelem * req, const unsigned char * data, int len)
! 253: {
! 254: if(write_buffer(req) < 0)
! 255: return -1;
! 256: if(req->output_buffer && req->output_buffer_len > 0) {
! 257: return add_to_buffer(req, data, len);
! 258: } else {
! 259: int n = write(req->socket, data, len);
! 260: if(n == len)
! 261: return len;
! 262: if(n < 0) {
! 263: if(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {
! 264: n = add_to_buffer(req, data, len);
! 265: if(n < 0) return n;
! 266: } else {
! 267: return n;
! 268: }
! 269: } else {
! 270: n = add_to_buffer(req, data + n, len - n);
! 271: if(n < 0) return n;
! 272: }
! 273: }
! 274: return len;
! 275: }
! 276:
! 277: static const char *
! 278: nts_to_str(int nts)
! 279: {
! 280: switch(nts)
! 281: {
! 282: case NTS_SSDP_ALIVE:
! 283: return "ssdp:alive";
! 284: case NTS_SSDP_BYEBYE:
! 285: return "ssdp:byebye";
! 286: case NTS_SSDP_UPDATE:
! 287: return "ssdp:update";
! 288: }
! 289: return "unknown";
! 290: }
! 291:
! 292: /* updateDevice() :
! 293: * adds or updates the device to the list.
! 294: * return value :
! 295: * 0 : the device was updated (or nothing done)
! 296: * 1 : the device was new */
! 297: static int
! 298: updateDevice(const struct header * headers, time_t t)
! 299: {
! 300: struct device ** pp = &devlist;
! 301: struct device * p = *pp; /* = devlist; */
! 302: while(p)
! 303: {
! 304: if( p->headers[HEADER_NT].l == headers[HEADER_NT].l
! 305: && (0==memcmp(p->headers[HEADER_NT].p, headers[HEADER_NT].p, headers[HEADER_NT].l))
! 306: && p->headers[HEADER_USN].l == headers[HEADER_USN].l
! 307: && (0==memcmp(p->headers[HEADER_USN].p, headers[HEADER_USN].p, headers[HEADER_USN].l)) )
! 308: {
! 309: /*printf("found! %d\n", (int)(t - p->t));*/
! 310: syslog(LOG_DEBUG, "device updated : %.*s", headers[HEADER_USN].l, headers[HEADER_USN].p);
! 311: p->t = t;
! 312: /* update Location ! */
! 313: if(headers[HEADER_LOCATION].l > p->headers[HEADER_LOCATION].l)
! 314: {
! 315: struct device * tmp;
! 316: tmp = realloc(p, sizeof(struct device)
! 317: + headers[0].l+headers[1].l+headers[2].l);
! 318: if(!tmp) /* allocation error */
! 319: {
! 320: syslog(LOG_ERR, "updateDevice() : memory allocation error");
! 321: *pp = p->next; /* remove "p" from the list */
! 322: free(p);
! 323: return 0;
! 324: }
! 325: p = tmp;
! 326: *pp = p;
! 327: }
! 328: memcpy(p->data + p->headers[0].l + p->headers[1].l,
! 329: headers[2].p, headers[2].l);
! 330: /* TODO : check p->headers[HEADER_LOCATION].l */
! 331: return 0;
! 332: }
! 333: pp = &p->next;
! 334: p = *pp; /* p = p->next; */
! 335: }
! 336: syslog(LOG_INFO, "new device discovered : %.*s",
! 337: headers[HEADER_USN].l, headers[HEADER_USN].p);
! 338: /* add */
! 339: {
! 340: char * pc;
! 341: int i;
! 342: p = malloc( sizeof(struct device)
! 343: + headers[0].l+headers[1].l+headers[2].l );
! 344: if(!p) {
! 345: syslog(LOG_ERR, "updateDevice(): cannot allocate memory");
! 346: return -1;
! 347: }
! 348: p->next = devlist;
! 349: p->t = t;
! 350: pc = p->data;
! 351: for(i = 0; i < 3; i++)
! 352: {
! 353: p->headers[i].p = pc;
! 354: p->headers[i].l = headers[i].l;
! 355: memcpy(pc, headers[i].p, headers[i].l);
! 356: pc += headers[i].l;
! 357: }
! 358: devlist = p;
! 359: sendNotifications(NOTIF_NEW, p, NULL);
! 360: }
! 361: return 1;
! 362: }
! 363:
! 364: /* removeDevice() :
! 365: * remove a device from the list
! 366: * return value :
! 367: * 0 : no device removed
! 368: * -1 : device removed */
! 369: static int
! 370: removeDevice(const struct header * headers)
! 371: {
! 372: struct device ** pp = &devlist;
! 373: struct device * p = *pp; /* = devlist */
! 374: while(p)
! 375: {
! 376: if( p->headers[HEADER_NT].l == headers[HEADER_NT].l
! 377: && (0==memcmp(p->headers[HEADER_NT].p, headers[HEADER_NT].p, headers[HEADER_NT].l))
! 378: && p->headers[HEADER_USN].l == headers[HEADER_USN].l
! 379: && (0==memcmp(p->headers[HEADER_USN].p, headers[HEADER_USN].p, headers[HEADER_USN].l)) )
! 380: {
! 381: syslog(LOG_INFO, "remove device : %.*s", headers[HEADER_USN].l, headers[HEADER_USN].p);
! 382: sendNotifications(NOTIF_REMOVE, p, NULL);
! 383: *pp = p->next;
! 384: free(p);
! 385: return -1;
! 386: }
! 387: pp = &p->next;
! 388: p = *pp; /* p = p->next; */
! 389: }
! 390: syslog(LOG_WARNING, "device not found for removing : %.*s", headers[HEADER_USN].l, headers[HEADER_USN].p);
! 391: return 0;
! 392: }
! 393:
! 394: /* sent notifications to client having subscribed */
! 395: static void
! 396: sendNotifications(int notif_type, const struct device * dev, const struct service * serv)
! 397: {
! 398: struct reqelem * req;
! 399: unsigned int m;
! 400: unsigned char rbuf[RESPONSE_BUFFER_SIZE];
! 401: unsigned char * rp;
! 402:
! 403: for(req = reqlisthead.lh_first; req; req = req->entries.le_next) {
! 404: if(!req->is_notify) continue;
! 405: rbuf[0] = '\xff'; /* special code for notifications */
! 406: rbuf[1] = (unsigned char)notif_type;
! 407: rbuf[2] = 0;
! 408: rp = rbuf + 3;
! 409: if(dev) {
! 410: /* response :
! 411: * 1 - Location
! 412: * 2 - NT (device/service type)
! 413: * 3 - usn */
! 414: m = dev->headers[HEADER_LOCATION].l;
! 415: CODELENGTH(m, rp);
! 416: memcpy(rp, dev->headers[HEADER_LOCATION].p, dev->headers[HEADER_LOCATION].l);
! 417: rp += dev->headers[HEADER_LOCATION].l;
! 418: m = dev->headers[HEADER_NT].l;
! 419: CODELENGTH(m, rp);
! 420: memcpy(rp, dev->headers[HEADER_NT].p, dev->headers[HEADER_NT].l);
! 421: rp += dev->headers[HEADER_NT].l;
! 422: m = dev->headers[HEADER_USN].l;
! 423: CODELENGTH(m, rp);
! 424: memcpy(rp, dev->headers[HEADER_USN].p, dev->headers[HEADER_USN].l);
! 425: rp += dev->headers[HEADER_USN].l;
! 426: rbuf[2]++;
! 427: }
! 428: if(serv) {
! 429: /* response :
! 430: * 1 - Location
! 431: * 2 - NT (device/service type)
! 432: * 3 - usn */
! 433: m = strlen(serv->location);
! 434: CODELENGTH(m, rp);
! 435: memcpy(rp, serv->location, m);
! 436: rp += m;
! 437: m = strlen(serv->st);
! 438: CODELENGTH(m, rp);
! 439: memcpy(rp, serv->st, m);
! 440: rp += m;
! 441: m = strlen(serv->usn);
! 442: CODELENGTH(m, rp);
! 443: memcpy(rp, serv->usn, m);
! 444: rp += m;
! 445: rbuf[2]++;
! 446: }
! 447: if(rbuf[2] > 0) {
! 448: if(write_or_buffer(req, rbuf, rp - rbuf) < 0) {
! 449: syslog(LOG_ERR, "(s=%d) write: %m", req->socket);
! 450: /*goto error;*/
! 451: }
! 452: }
! 453: }
! 454: }
! 455:
! 456: /* SendSSDPMSEARCHResponse() :
! 457: * build and send response to M-SEARCH SSDP packets. */
! 458: static void
! 459: SendSSDPMSEARCHResponse(int s, const struct sockaddr * sockname,
! 460: const char * st, size_t st_len, const char * usn,
! 461: const char * server, const char * location)
! 462: {
! 463: int l, n;
! 464: char buf[1024];
! 465: socklen_t sockname_len;
! 466: /*
! 467: * follow guideline from document "UPnP Device Architecture 1.0"
! 468: * uppercase is recommended.
! 469: * DATE: is recommended
! 470: * SERVER: OS/ver UPnP/1.0 miniupnpd/1.0
! 471: * - check what to put in the 'Cache-Control' header
! 472: *
! 473: * have a look at the document "UPnP Device Architecture v1.1 */
! 474: l = snprintf(buf, sizeof(buf), "HTTP/1.1 200 OK\r\n"
! 475: "CACHE-CONTROL: max-age=120\r\n"
! 476: /*"DATE: ...\r\n"*/
! 477: "ST: %.*s\r\n"
! 478: "USN: %s\r\n"
! 479: "EXT:\r\n"
! 480: "SERVER: %s\r\n"
! 481: "LOCATION: %s\r\n"
! 482: "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" /* UDA v1.1 */
! 483: "01-NLS: %u\r\n" /* same as BOOTID. UDA v1.1 */
! 484: "BOOTID.UPNP.ORG: %u\r\n" /* UDA v1.1 */
! 485: "CONFIGID.UPNP.ORG: %u\r\n" /* UDA v1.1 */
! 486: "\r\n",
! 487: (int)st_len, st, usn,
! 488: server, location,
! 489: upnp_bootid, upnp_bootid, upnp_configid);
! 490: #ifdef ENABLE_IPV6
! 491: sockname_len = (sockname->sa_family == PF_INET6)
! 492: ? sizeof(struct sockaddr_in6)
! 493: : sizeof(struct sockaddr_in);
! 494: #else /* ENABLE_IPV6 */
! 495: sockname_len = sizeof(struct sockaddr_in);
! 496: #endif /* ENABLE_IPV6 */
! 497: n = sendto_or_schedule(s, buf, l, 0, sockname, sockname_len);
! 498: if(n < 0) {
! 499: syslog(LOG_ERR, "%s: sendto(udp): %m", __func__);
! 500: }
! 501: }
! 502:
! 503: /* Process M-SEARCH requests */
! 504: static void
! 505: processMSEARCH(int s, const char * st, size_t st_len,
! 506: const struct sockaddr * addr)
! 507: {
! 508: struct service * serv;
! 509: #ifdef ENABLE_IPV6
! 510: char buf[64];
! 511: #endif /* ENABLE_IPV6 */
! 512:
! 513: if(!st || st_len==0)
! 514: return;
! 515: #ifdef ENABLE_IPV6
! 516: sockaddr_to_string(addr, buf, sizeof(buf));
! 517: syslog(LOG_INFO, "SSDP M-SEARCH from %s ST:%.*s",
! 518: buf, (int)st_len, st);
! 519: #else /* ENABLE_IPV6 */
! 520: syslog(LOG_INFO, "SSDP M-SEARCH from %s:%d ST: %.*s",
! 521: inet_ntoa(((const struct sockaddr_in *)addr)->sin_addr),
! 522: ntohs(((const struct sockaddr_in *)addr)->sin_port),
! 523: (int)st_len, st);
! 524: #endif /* ENABLE_IPV6 */
! 525: if(st_len==8 && (0==memcmp(st, "ssdp:all", 8))) {
! 526: /* send a response for all services */
! 527: for(serv = servicelisthead.lh_first;
! 528: serv;
! 529: serv = serv->entries.le_next) {
! 530: SendSSDPMSEARCHResponse(s, addr,
! 531: serv->st, strlen(serv->st), serv->usn,
! 532: serv->server, serv->location);
! 533: }
! 534: } else if(st_len > 5 && (0==memcmp(st, "uuid:", 5))) {
! 535: /* find a matching UUID value */
! 536: for(serv = servicelisthead.lh_first;
! 537: serv;
! 538: serv = serv->entries.le_next) {
! 539: if(0 == strncmp(serv->usn, st, st_len)) {
! 540: SendSSDPMSEARCHResponse(s, addr,
! 541: serv->st, strlen(serv->st), serv->usn,
! 542: serv->server, serv->location);
! 543: }
! 544: }
! 545: } else {
! 546: size_t l;
! 547: int st_ver = 0;
! 548: char atoi_buffer[8];
! 549:
! 550: /* remove version at the end of the ST string */
! 551: for (l = st_len; l > 0; l--) {
! 552: if (st[l-1] == ':') {
! 553: memset(atoi_buffer, 0, sizeof(atoi_buffer));
! 554: memcpy(atoi_buffer, st + l, MIN((sizeof(atoi_buffer) - 1), st_len - l));
! 555: st_ver = atoi(atoi_buffer);
! 556: break;
! 557: }
! 558: }
! 559: if (l == 0)
! 560: l = st_len;
! 561: /* answer for each matching service */
! 562: /* From UPnP Device Architecture v1.1 :
! 563: * 1.3.2 [...] Updated versions of device and service types
! 564: * are REQUIRED to be full backward compatible with
! 565: * previous versions. Devices MUST respond to M-SEARCH
! 566: * requests for any supported version. For example, if a
! 567: * device implements “urn:schemas-upnporg:service:xyz:2”,
! 568: * it MUST respond to search requests for both that type
! 569: * and “urn:schemas-upnp-org:service:xyz:1”. The response
! 570: * MUST specify the same version as was contained in the
! 571: * search request. [...] */
! 572: for(serv = servicelisthead.lh_first;
! 573: serv;
! 574: serv = serv->entries.le_next) {
! 575: if(0 == strncmp(serv->st, st, l)) {
! 576: syslog(LOG_DEBUG, "Found matching service : %s %s (v %d)", serv->st, serv->location, st_ver);
! 577: SendSSDPMSEARCHResponse(s, addr,
! 578: st, st_len, serv->usn,
! 579: serv->server, serv->location);
! 580: }
! 581: }
! 582: }
! 583: }
! 584:
! 585: /**
! 586: * helper function.
! 587: * reject any non ASCII or non printable character.
! 588: */
! 589: static int
! 590: containsForbiddenChars(const unsigned char * p, int len)
! 591: {
! 592: while(len > 0) {
! 593: if(*p < ' ' || *p >= '\x7f')
! 594: return 1;
! 595: p++;
! 596: len--;
! 597: }
! 598: return 0;
! 599: }
! 600:
! 601: #define METHOD_MSEARCH 1
! 602: #define METHOD_NOTIFY 2
! 603:
! 604: /* ParseSSDPPacket() :
! 605: * parse a received SSDP Packet and call
! 606: * updateDevice() or removeDevice() as needed
! 607: * return value :
! 608: * -1 : a device was removed
! 609: * 0 : no device removed nor added
! 610: * 1 : a device was added. */
! 611: static int
! 612: ParseSSDPPacket(int s, const char * p, ssize_t n,
! 613: const struct sockaddr * addr,
! 614: const char * searched_device)
! 615: {
! 616: const char * linestart;
! 617: const char * lineend;
! 618: const char * nameend;
! 619: const char * valuestart;
! 620: struct header headers[3];
! 621: int i, r = 0;
! 622: int methodlen;
! 623: int nts = -1;
! 624: int method = -1;
! 625: unsigned int lifetime = 180; /* 3 minutes by default */
! 626: const char * st = NULL;
! 627: int st_len = 0;
! 628:
! 629: /* first check from what subnet is the sender */
! 630: if(get_lan_for_peer(addr) == NULL) {
! 631: char addr_str[64];
! 632: sockaddr_to_string(addr, addr_str, sizeof(addr_str));
! 633: syslog(LOG_WARNING, "peer %s is not from a LAN",
! 634: addr_str);
! 635: return 0;
! 636: }
! 637:
! 638: /* do the parsing */
! 639: memset(headers, 0, sizeof(headers));
! 640: for(methodlen = 0;
! 641: methodlen < n && (isalpha(p[methodlen]) || p[methodlen]=='-');
! 642: methodlen++);
! 643: if(methodlen==8 && 0==memcmp(p, "M-SEARCH", 8))
! 644: method = METHOD_MSEARCH;
! 645: else if(methodlen==6 && 0==memcmp(p, "NOTIFY", 6))
! 646: method = METHOD_NOTIFY;
! 647: else if(methodlen==4 && 0==memcmp(p, "HTTP", 4)) {
! 648: /* answer to a M-SEARCH => process it as a NOTIFY
! 649: * with NTS: ssdp:alive */
! 650: method = METHOD_NOTIFY;
! 651: nts = NTS_SSDP_ALIVE;
! 652: }
! 653: linestart = p;
! 654: while(linestart < p + n - 2) {
! 655: /* start parsing the line : detect line end */
! 656: lineend = linestart;
! 657: while(lineend < p + n && *lineend != '\n' && *lineend != '\r')
! 658: lineend++;
! 659: /*printf("line: '%.*s'\n", lineend - linestart, linestart);*/
! 660: /* detect name end : ':' character */
! 661: nameend = linestart;
! 662: while(nameend < lineend && *nameend != ':')
! 663: nameend++;
! 664: /* detect value */
! 665: if(nameend < lineend)
! 666: valuestart = nameend + 1;
! 667: else
! 668: valuestart = nameend;
! 669: /* trim spaces */
! 670: while(valuestart < lineend && isspace(*valuestart))
! 671: valuestart++;
! 672: /* suppress leading " if needed */
! 673: if(valuestart < lineend && *valuestart=='\"')
! 674: valuestart++;
! 675: if(nameend > linestart && valuestart < lineend) {
! 676: int l = nameend - linestart; /* header name length */
! 677: int m = lineend - valuestart; /* header value length */
! 678: /* suppress tailing spaces */
! 679: while(m>0 && isspace(valuestart[m-1]))
! 680: m--;
! 681: /* suppress tailing ' if needed */
! 682: if(m>0 && valuestart[m-1] == '\"')
! 683: m--;
! 684: i = -1;
! 685: /*printf("--%.*s: (%d)%.*s--\n", l, linestart,
! 686: m, m, valuestart);*/
! 687: if(l==2 && 0==strncasecmp(linestart, "nt", 2))
! 688: i = HEADER_NT;
! 689: else if(l==3 && 0==strncasecmp(linestart, "usn", 3))
! 690: i = HEADER_USN;
! 691: else if(l==3 && 0==strncasecmp(linestart, "nts", 3)) {
! 692: if(m==10 && 0==strncasecmp(valuestart, "ssdp:alive", 10))
! 693: nts = NTS_SSDP_ALIVE;
! 694: else if(m==11 && 0==strncasecmp(valuestart, "ssdp:byebye", 11))
! 695: nts = NTS_SSDP_BYEBYE;
! 696: else if(m==11 && 0==strncasecmp(valuestart, "ssdp:update", 11))
! 697: nts = NTS_SSDP_UPDATE;
! 698: }
! 699: else if(l==8 && 0==strncasecmp(linestart, "location", 8))
! 700: i = HEADER_LOCATION;
! 701: else if(l==13 && 0==strncasecmp(linestart, "cache-control", 13)) {
! 702: /* parse "name1=value1, name_alone, name2=value2" string */
! 703: const char * name = valuestart; /* name */
! 704: const char * val; /* value */
! 705: int rem = m; /* remaining bytes to process */
! 706: while(rem > 0) {
! 707: val = name;
! 708: while(val < name + rem && *val != '=' && *val != ',')
! 709: val++;
! 710: if(val >= name + rem)
! 711: break;
! 712: if(*val == '=') {
! 713: while(val < name + rem && (*val == '=' || isspace(*val)))
! 714: val++;
! 715: if(val >= name + rem)
! 716: break;
! 717: if(0==strncasecmp(name, "max-age", 7))
! 718: lifetime = (unsigned int)strtoul(val, 0, 0);
! 719: /* move to the next name=value pair */
! 720: while(rem > 0 && *name != ',') {
! 721: rem--;
! 722: name++;
! 723: }
! 724: /* skip spaces */
! 725: while(rem > 0 && (*name == ',' || isspace(*name))) {
! 726: rem--;
! 727: name++;
! 728: }
! 729: } else {
! 730: rem -= (val - name);
! 731: name = val;
! 732: while(rem > 0 && (*name == ',' || isspace(*name))) {
! 733: rem--;
! 734: name++;
! 735: }
! 736: }
! 737: }
! 738: /*syslog(LOG_DEBUG, "**%.*s**%u", m, valuestart, lifetime);*/
! 739: } else if(l==2 && 0==strncasecmp(linestart, "st", 2)) {
! 740: st = valuestart;
! 741: st_len = m;
! 742: if(method == METHOD_NOTIFY)
! 743: i = HEADER_NT; /* it was a M-SEARCH response */
! 744: }
! 745: if(i>=0) {
! 746: headers[i].p = valuestart;
! 747: headers[i].l = m;
! 748: }
! 749: }
! 750: linestart = lineend;
! 751: while((linestart < p + n) && (*linestart == '\n' || *linestart == '\r'))
! 752: linestart++;
! 753: }
! 754: #if 0
! 755: printf("NTS=%d\n", nts);
! 756: for(i=0; i<3; i++) {
! 757: if(headers[i].p)
! 758: printf("%d-'%.*s'\n", i, headers[i].l, headers[i].p);
! 759: }
! 760: #endif
! 761: syslog(LOG_DEBUG,"SSDP request: '%.*s' (%d) %s %s=%.*s",
! 762: methodlen, p, method, nts_to_str(nts),
! 763: (method==METHOD_NOTIFY)?"nt":"st",
! 764: (method==METHOD_NOTIFY)?headers[HEADER_NT].l:st_len,
! 765: (method==METHOD_NOTIFY)?headers[HEADER_NT].p:st);
! 766: switch(method) {
! 767: case METHOD_NOTIFY:
! 768: if(nts==NTS_SSDP_ALIVE || nts==NTS_SSDP_UPDATE) {
! 769: if(headers[HEADER_NT].p && headers[HEADER_USN].p && headers[HEADER_LOCATION].p) {
! 770: /* filter if needed */
! 771: if(searched_device &&
! 772: 0 != memcmp(headers[HEADER_NT].p, searched_device, headers[HEADER_NT].l))
! 773: break;
! 774: r = updateDevice(headers, time(NULL) + lifetime);
! 775: } else {
! 776: syslog(LOG_WARNING, "missing header nt=%p usn=%p location=%p",
! 777: headers[HEADER_NT].p, headers[HEADER_USN].p,
! 778: headers[HEADER_LOCATION].p);
! 779: }
! 780: } else if(nts==NTS_SSDP_BYEBYE) {
! 781: if(headers[HEADER_NT].p && headers[HEADER_USN].p) {
! 782: r = removeDevice(headers);
! 783: } else {
! 784: syslog(LOG_WARNING, "missing header nt=%p usn=%p",
! 785: headers[HEADER_NT].p, headers[HEADER_USN].p);
! 786: }
! 787: }
! 788: break;
! 789: case METHOD_MSEARCH:
! 790: processMSEARCH(s, st, st_len, addr);
! 791: break;
! 792: default:
! 793: {
! 794: char addr_str[64];
! 795: sockaddr_to_string(addr, addr_str, sizeof(addr_str));
! 796: syslog(LOG_WARNING, "method %.*s, don't know what to do (from %s)",
! 797: methodlen, p, addr_str);
! 798: }
! 799: }
! 800: return r;
! 801: }
! 802:
! 803: /* OpenUnixSocket()
! 804: * open the unix socket and call bind() and listen()
! 805: * return -1 in case of error */
! 806: static int
! 807: OpenUnixSocket(const char * path)
! 808: {
! 809: struct sockaddr_un addr;
! 810: int s;
! 811: int rv;
! 812: s = socket(AF_UNIX, SOCK_STREAM, 0);
! 813: if(s < 0)
! 814: {
! 815: syslog(LOG_ERR, "socket(AF_UNIX): %m");
! 816: return -1;
! 817: }
! 818: /* unlink the socket pseudo file before binding */
! 819: rv = unlink(path);
! 820: if(rv < 0 && errno != ENOENT)
! 821: {
! 822: syslog(LOG_ERR, "unlink(unixsocket, \"%s\"): %m", path);
! 823: close(s);
! 824: return -1;
! 825: }
! 826: addr.sun_family = AF_UNIX;
! 827: strncpy(addr.sun_path, path, sizeof(addr.sun_path));
! 828: if(bind(s, (struct sockaddr *)&addr,
! 829: sizeof(struct sockaddr_un)) < 0)
! 830: {
! 831: syslog(LOG_ERR, "bind(unixsocket, \"%s\"): %m", path);
! 832: close(s);
! 833: return -1;
! 834: }
! 835: else if(listen(s, 5) < 0)
! 836: {
! 837: syslog(LOG_ERR, "listen(unixsocket): %m");
! 838: close(s);
! 839: return -1;
! 840: }
! 841: /* Change rights so everyone can communicate with us */
! 842: if(chmod(path, 0666) < 0)
! 843: {
! 844: syslog(LOG_WARNING, "chmod(\"%s\"): %m", path);
! 845: }
! 846: return s;
! 847: }
! 848:
! 849: static ssize_t processRequestSub(struct reqelem * req, const unsigned char * buf, ssize_t n);
! 850:
! 851: /* processRequest() :
! 852: * process the request coming from a unix socket */
! 853: void processRequest(struct reqelem * req)
! 854: {
! 855: ssize_t n, r;
! 856: unsigned char buf[2048];
! 857: const unsigned char * p;
! 858:
! 859: n = read(req->socket, buf, sizeof(buf));
! 860: if(n<0) {
! 861: if(errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
! 862: return; /* try again later */
! 863: syslog(LOG_ERR, "(s=%d) processRequest(): read(): %m", req->socket);
! 864: goto error;
! 865: }
! 866: if(n==0) {
! 867: syslog(LOG_INFO, "(s=%d) request connection closed", req->socket);
! 868: goto error;
! 869: }
! 870: p = buf;
! 871: while (n > 0)
! 872: {
! 873: r = processRequestSub(req, p, n);
! 874: if (r < 0)
! 875: goto error;
! 876: p += r;
! 877: n -= r;
! 878: }
! 879: return;
! 880: error:
! 881: close(req->socket);
! 882: req->socket = -1;
! 883: }
! 884:
! 885: static ssize_t processRequestSub(struct reqelem * req, const unsigned char * buf, ssize_t n)
! 886: {
! 887: unsigned int l, m;
! 888: unsigned int baselen; /* without the version */
! 889: const unsigned char * p;
! 890: enum request_type type;
! 891: struct device * d = devlist;
! 892: unsigned char rbuf[RESPONSE_BUFFER_SIZE];
! 893: unsigned char * rp;
! 894: unsigned char nrep = 0;
! 895: time_t t;
! 896: struct service * newserv = NULL;
! 897: struct service * serv;
! 898:
! 899: t = time(NULL);
! 900: type = buf[0];
! 901: p = buf + 1;
! 902: DECODELENGTH_CHECKLIMIT(l, p, buf + n);
! 903: if(l > (unsigned)(buf+n-p)) {
! 904: syslog(LOG_WARNING, "bad request (length encoding l=%u n=%u)",
! 905: l, (unsigned)n);
! 906: goto error;
! 907: }
! 908: if(l == 0 && type != MINISSDPD_SEARCH_ALL
! 909: && type != MINISSDPD_GET_VERSION && type != MINISSDPD_NOTIF) {
! 910: syslog(LOG_WARNING, "bad request (length=0, type=%d)", type);
! 911: goto error;
! 912: }
! 913: syslog(LOG_INFO, "(s=%d) request type=%d str='%.*s'",
! 914: req->socket, type, l, p);
! 915: switch(type) {
! 916: case MINISSDPD_GET_VERSION:
! 917: rp = rbuf;
! 918: CODELENGTH((sizeof(MINISSDPD_VERSION) - 1), rp);
! 919: memcpy(rp, MINISSDPD_VERSION, sizeof(MINISSDPD_VERSION) - 1);
! 920: rp += (sizeof(MINISSDPD_VERSION) - 1);
! 921: if(write_or_buffer(req, rbuf, rp - rbuf) < 0) {
! 922: syslog(LOG_ERR, "(s=%d) write: %m", req->socket);
! 923: goto error;
! 924: }
! 925: p += l;
! 926: break;
! 927: case MINISSDPD_SEARCH_TYPE: /* request by type */
! 928: case MINISSDPD_SEARCH_USN: /* request by USN (unique id) */
! 929: case MINISSDPD_SEARCH_ALL: /* everything */
! 930: rp = rbuf+1;
! 931: /* From UPnP Device Architecture v1.1 :
! 932: * 1.3.2 [...] Updated versions of device and service types
! 933: * are REQUIRED to be full backward compatible with
! 934: * previous versions. Devices MUST respond to M-SEARCH
! 935: * requests for any supported version. For example, if a
! 936: * device implements “urn:schemas-upnporg:service:xyz:2”,
! 937: * it MUST respond to search requests for both that type
! 938: * and “urn:schemas-upnp-org:service:xyz:1”. The response
! 939: * MUST specify the same version as was contained in the
! 940: * search request. [...] */
! 941: baselen = l; /* remove the version */
! 942: while(baselen > 0) {
! 943: if(p[baselen-1] == ':')
! 944: break;
! 945: if(!(p[baselen-1] >= '0' && p[baselen-1] <= '9'))
! 946: break;
! 947: baselen--;
! 948: }
! 949: while(d && (nrep < 255)) {
! 950: if(d->t < t) {
! 951: syslog(LOG_INFO, "outdated device");
! 952: } else {
! 953: /* test if we can put more responses in the buffer */
! 954: if(d->headers[HEADER_LOCATION].l + d->headers[HEADER_NT].l
! 955: + d->headers[HEADER_USN].l + 6
! 956: + (rp - rbuf) >= (int)sizeof(rbuf))
! 957: break;
! 958: if( (type==MINISSDPD_SEARCH_TYPE && 0==memcmp(d->headers[HEADER_NT].p, p, baselen))
! 959: ||(type==MINISSDPD_SEARCH_USN && 0==memcmp(d->headers[HEADER_USN].p, p, l))
! 960: ||(type==MINISSDPD_SEARCH_ALL) ) {
! 961: /* response :
! 962: * 1 - Location
! 963: * 2 - NT (device/service type)
! 964: * 3 - usn */
! 965: m = d->headers[HEADER_LOCATION].l;
! 966: CODELENGTH(m, rp);
! 967: memcpy(rp, d->headers[HEADER_LOCATION].p, d->headers[HEADER_LOCATION].l);
! 968: rp += d->headers[HEADER_LOCATION].l;
! 969: m = d->headers[HEADER_NT].l;
! 970: CODELENGTH(m, rp);
! 971: memcpy(rp, d->headers[HEADER_NT].p, d->headers[HEADER_NT].l);
! 972: rp += d->headers[HEADER_NT].l;
! 973: m = d->headers[HEADER_USN].l;
! 974: CODELENGTH(m, rp);
! 975: memcpy(rp, d->headers[HEADER_USN].p, d->headers[HEADER_USN].l);
! 976: rp += d->headers[HEADER_USN].l;
! 977: nrep++;
! 978: }
! 979: }
! 980: d = d->next;
! 981: }
! 982: /* Also look in service list */
! 983: for(serv = servicelisthead.lh_first;
! 984: serv && (nrep < 255);
! 985: serv = serv->entries.le_next) {
! 986: /* test if we can put more responses in the buffer */
! 987: if(strlen(serv->location) + strlen(serv->st)
! 988: + strlen(serv->usn) + 6 + (rp - rbuf) >= sizeof(rbuf))
! 989: break;
! 990: if( (type==MINISSDPD_SEARCH_TYPE && 0==strncmp(serv->st, (const char *)p, l))
! 991: ||(type==MINISSDPD_SEARCH_USN && 0==strncmp(serv->usn, (const char *)p, l))
! 992: ||(type==MINISSDPD_SEARCH_ALL) ) {
! 993: /* response :
! 994: * 1 - Location
! 995: * 2 - NT (device/service type)
! 996: * 3 - usn */
! 997: m = strlen(serv->location);
! 998: CODELENGTH(m, rp);
! 999: memcpy(rp, serv->location, m);
! 1000: rp += m;
! 1001: m = strlen(serv->st);
! 1002: CODELENGTH(m, rp);
! 1003: memcpy(rp, serv->st, m);
! 1004: rp += m;
! 1005: m = strlen(serv->usn);
! 1006: CODELENGTH(m, rp);
! 1007: memcpy(rp, serv->usn, m);
! 1008: rp += m;
! 1009: nrep++;
! 1010: }
! 1011: }
! 1012: rbuf[0] = nrep;
! 1013: syslog(LOG_DEBUG, "(s=%d) response : %d device%s",
! 1014: req->socket, nrep, (nrep > 1) ? "s" : "");
! 1015: if(write_or_buffer(req, rbuf, rp - rbuf) < 0) {
! 1016: syslog(LOG_ERR, "(s=%d) write: %m", req->socket);
! 1017: goto error;
! 1018: }
! 1019: p += l;
! 1020: break;
! 1021: case MINISSDPD_SUBMIT: /* submit service */
! 1022: newserv = malloc(sizeof(struct service));
! 1023: if(!newserv) {
! 1024: syslog(LOG_ERR, "cannot allocate memory");
! 1025: goto error;
! 1026: }
! 1027: memset(newserv, 0, sizeof(struct service)); /* set pointers to NULL */
! 1028: if(containsForbiddenChars(p, l)) {
! 1029: syslog(LOG_ERR, "bad request (st contains forbidden chars)");
! 1030: goto error;
! 1031: }
! 1032: newserv->st = malloc(l + 1);
! 1033: if(!newserv->st) {
! 1034: syslog(LOG_ERR, "cannot allocate memory");
! 1035: goto error;
! 1036: }
! 1037: memcpy(newserv->st, p, l);
! 1038: newserv->st[l] = '\0';
! 1039: p += l;
! 1040: if(p >= buf + n) {
! 1041: syslog(LOG_WARNING, "bad request (missing usn)");
! 1042: goto error;
! 1043: }
! 1044: DECODELENGTH_CHECKLIMIT(l, p, buf + n);
! 1045: if(l > (unsigned)(buf+n-p)) {
! 1046: syslog(LOG_WARNING, "bad request (length encoding)");
! 1047: goto error;
! 1048: }
! 1049: if(containsForbiddenChars(p, l)) {
! 1050: syslog(LOG_ERR, "bad request (usn contains forbidden chars)");
! 1051: goto error;
! 1052: }
! 1053: syslog(LOG_INFO, "usn='%.*s'", l, p);
! 1054: newserv->usn = malloc(l + 1);
! 1055: if(!newserv->usn) {
! 1056: syslog(LOG_ERR, "cannot allocate memory");
! 1057: goto error;
! 1058: }
! 1059: memcpy(newserv->usn, p, l);
! 1060: newserv->usn[l] = '\0';
! 1061: p += l;
! 1062: DECODELENGTH_CHECKLIMIT(l, p, buf + n);
! 1063: if(l > (unsigned)(buf+n-p)) {
! 1064: syslog(LOG_WARNING, "bad request (length encoding)");
! 1065: goto error;
! 1066: }
! 1067: if(containsForbiddenChars(p, l)) {
! 1068: syslog(LOG_ERR, "bad request (server contains forbidden chars)");
! 1069: goto error;
! 1070: }
! 1071: syslog(LOG_INFO, "server='%.*s'", l, p);
! 1072: newserv->server = malloc(l + 1);
! 1073: if(!newserv->server) {
! 1074: syslog(LOG_ERR, "cannot allocate memory");
! 1075: goto error;
! 1076: }
! 1077: memcpy(newserv->server, p, l);
! 1078: newserv->server[l] = '\0';
! 1079: p += l;
! 1080: DECODELENGTH_CHECKLIMIT(l, p, buf + n);
! 1081: if(l > (unsigned)(buf+n-p)) {
! 1082: syslog(LOG_WARNING, "bad request (length encoding)");
! 1083: goto error;
! 1084: }
! 1085: if(containsForbiddenChars(p, l)) {
! 1086: syslog(LOG_ERR, "bad request (location contains forbidden chars)");
! 1087: goto error;
! 1088: }
! 1089: syslog(LOG_INFO, "location='%.*s'", l, p);
! 1090: newserv->location = malloc(l + 1);
! 1091: if(!newserv->location) {
! 1092: syslog(LOG_ERR, "cannot allocate memory");
! 1093: goto error;
! 1094: }
! 1095: memcpy(newserv->location, p, l);
! 1096: newserv->location[l] = '\0';
! 1097: p += l;
! 1098: /* look in service list for duplicate */
! 1099: for(serv = servicelisthead.lh_first;
! 1100: serv;
! 1101: serv = serv->entries.le_next) {
! 1102: if(0 == strcmp(newserv->usn, serv->usn)
! 1103: && 0 == strcmp(newserv->st, serv->st)) {
! 1104: syslog(LOG_INFO, "Service already in the list. Updating...");
! 1105: free(newserv->st);
! 1106: free(newserv->usn);
! 1107: free(serv->server);
! 1108: serv->server = newserv->server;
! 1109: free(serv->location);
! 1110: serv->location = newserv->location;
! 1111: free(newserv);
! 1112: newserv = NULL;
! 1113: return (p - buf);
! 1114: }
! 1115: }
! 1116: /* Inserting new service */
! 1117: LIST_INSERT_HEAD(&servicelisthead, newserv, entries);
! 1118: sendNotifications(NOTIF_NEW, NULL, newserv);
! 1119: newserv = NULL;
! 1120: break;
! 1121: case MINISSDPD_NOTIF: /* switch socket to notify */
! 1122: rbuf[0] = '\0';
! 1123: if(write_or_buffer(req, rbuf, 1) < 0) {
! 1124: syslog(LOG_ERR, "(s=%d) write: %m", req->socket);
! 1125: goto error;
! 1126: }
! 1127: req->is_notify = 1;
! 1128: p += l;
! 1129: break;
! 1130: default:
! 1131: syslog(LOG_WARNING, "Unknown request type %d", type);
! 1132: rbuf[0] = '\0';
! 1133: if(write_or_buffer(req, rbuf, 1) < 0) {
! 1134: syslog(LOG_ERR, "(s=%d) write: %m", req->socket);
! 1135: goto error;
! 1136: }
! 1137: }
! 1138: return (p - buf);
! 1139: error:
! 1140: if(newserv) {
! 1141: free(newserv->st);
! 1142: free(newserv->usn);
! 1143: free(newserv->server);
! 1144: free(newserv->location);
! 1145: free(newserv);
! 1146: newserv = NULL;
! 1147: }
! 1148: return -1;
! 1149: }
! 1150:
! 1151: static volatile sig_atomic_t quitting = 0;
! 1152: /* SIGTERM signal handler */
! 1153: static void
! 1154: sigterm(int sig)
! 1155: {
! 1156: (void)sig;
! 1157: /*int save_errno = errno;*/
! 1158: /*signal(sig, SIG_IGN);*/
! 1159: #if 0
! 1160: /* calling syslog() is forbidden in a signal handler according to
! 1161: * signal(3) */
! 1162: syslog(LOG_NOTICE, "received signal %d, good-bye", sig);
! 1163: #endif
! 1164: quitting = 1;
! 1165: /*errno = save_errno;*/
! 1166: }
! 1167:
! 1168: #define PORT 1900
! 1169: #define XSTR(s) STR(s)
! 1170: #define STR(s) #s
! 1171: #define UPNP_MCAST_ADDR "239.255.255.250"
! 1172: /* for IPv6 */
! 1173: #define UPNP_MCAST_LL_ADDR "FF02::C" /* link-local */
! 1174: #define UPNP_MCAST_SL_ADDR "FF05::C" /* site-local */
! 1175:
! 1176: /* send the M-SEARCH request for devices
! 1177: * either all devices (third argument is NULL or "*") or a specific one */
! 1178: static void ssdpDiscover(int s, int ipv6, const char * search)
! 1179: {
! 1180: static const char MSearchMsgFmt[] =
! 1181: "M-SEARCH * HTTP/1.1\r\n"
! 1182: "HOST: %s:" XSTR(PORT) "\r\n"
! 1183: "ST: %s\r\n"
! 1184: "MAN: \"ssdp:discover\"\r\n"
! 1185: "MX: %u\r\n"
! 1186: "\r\n";
! 1187: char bufr[512];
! 1188: int n;
! 1189: int mx = 3;
! 1190: int linklocal = 1;
! 1191: struct sockaddr_storage sockudp_w;
! 1192:
! 1193: {
! 1194: n = snprintf(bufr, sizeof(bufr),
! 1195: MSearchMsgFmt,
! 1196: ipv6 ?
! 1197: (linklocal ? "[" UPNP_MCAST_LL_ADDR "]" : "[" UPNP_MCAST_SL_ADDR "]")
! 1198: : UPNP_MCAST_ADDR,
! 1199: (search ? search : "ssdp:all"), mx);
! 1200: memset(&sockudp_w, 0, sizeof(struct sockaddr_storage));
! 1201: if(ipv6) {
! 1202: struct sockaddr_in6 * p = (struct sockaddr_in6 *)&sockudp_w;
! 1203: p->sin6_family = AF_INET6;
! 1204: p->sin6_port = htons(PORT);
! 1205: inet_pton(AF_INET6,
! 1206: linklocal ? UPNP_MCAST_LL_ADDR : UPNP_MCAST_SL_ADDR,
! 1207: &(p->sin6_addr));
! 1208: } else {
! 1209: struct sockaddr_in * p = (struct sockaddr_in *)&sockudp_w;
! 1210: p->sin_family = AF_INET;
! 1211: p->sin_port = htons(PORT);
! 1212: p->sin_addr.s_addr = inet_addr(UPNP_MCAST_ADDR);
! 1213: }
! 1214:
! 1215: n = sendto_or_schedule(s, bufr, n, 0, (const struct sockaddr *)&sockudp_w,
! 1216: ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in));
! 1217: if (n < 0) {
! 1218: syslog(LOG_ERR, "%s: sendto(s=%d, ipv6=%d): %m", __func__, s, ipv6);
! 1219: }
! 1220: }
! 1221: }
! 1222:
! 1223: /* main(): program entry point */
! 1224: int main(int argc, char * * argv)
! 1225: {
! 1226: int ret = 0;
! 1227: #ifndef NO_BACKGROUND_NO_PIDFILE
! 1228: int pid;
! 1229: #endif
! 1230: struct sigaction sa;
! 1231: char buf[1500];
! 1232: ssize_t n;
! 1233: int s_ssdp = -1; /* udp socket receiving ssdp packets */
! 1234: #ifdef ENABLE_IPV6
! 1235: int s_ssdp6 = -1; /* udp socket receiving ssdp packets IPv6*/
! 1236: #else /* ENABLE_IPV6 */
! 1237: #define s_ssdp6 (-1)
! 1238: #endif /* ENABLE_IPV6 */
! 1239: int s_unix = -1; /* unix socket communicating with clients */
! 1240: int s_ifacewatch = -1; /* socket to receive Route / network interface config changes */
! 1241: struct reqelem * req;
! 1242: struct reqelem * reqnext;
! 1243: fd_set readfds;
! 1244: fd_set writefds;
! 1245: struct timeval now;
! 1246: int max_fd;
! 1247: struct lan_addr_s * lan_addr;
! 1248: int i;
! 1249: const char * sockpath = "/var/run/minissdpd.sock";
! 1250: #ifndef NO_BACKGROUND_NO_PIDFILE
! 1251: const char * pidfilename = "/var/run/minissdpd.pid";
! 1252: #endif
! 1253: int debug_flag = 0;
! 1254: #ifdef ENABLE_IPV6
! 1255: int ipv6 = 0;
! 1256: #endif /* ENABLE_IPV6 */
! 1257: int deltadev = 0;
! 1258: struct sockaddr_in sendername;
! 1259: socklen_t sendername_len;
! 1260: #ifdef ENABLE_IPV6
! 1261: struct sockaddr_in6 sendername6;
! 1262: socklen_t sendername6_len;
! 1263: #endif /* ENABLE_IPV6 */
! 1264: unsigned char ttl = 2; /* UDA says it should default to 2 */
! 1265: const char * searched_device = NULL; /* if not NULL, search/filter a specific device type */
! 1266: int opt;
! 1267:
! 1268: LIST_INIT(&reqlisthead);
! 1269: LIST_INIT(&servicelisthead);
! 1270: LIST_INIT(&lan_addrs);
! 1271: /* process command line */
! 1272: #define OPTSTRING "d6i:s:p:t:f:"
! 1273: while ((opt = getopt(argc, argv, "di:s:t:f:"
! 1274: #ifdef ENABLE_IPV6
! 1275: "6"
! 1276: #endif
! 1277: #ifndef NO_BACKGROUND_NO_PIDFILE
! 1278: "p:"
! 1279: #endif
! 1280:
! 1281: )) != -1)
! 1282: {
! 1283: switch(opt)
! 1284: {
! 1285: case 'd':
! 1286: debug_flag = 1;
! 1287: break;
! 1288: #ifdef ENABLE_IPV6
! 1289: case '6':
! 1290: ipv6 = 1;
! 1291: break;
! 1292: #endif /* ENABLE_IPV6 */
! 1293: case 'i':
! 1294: lan_addr = malloc(sizeof(struct lan_addr_s));
! 1295: if(lan_addr == NULL) {
! 1296: fprintf(stderr, "malloc(%d) FAILED\n", (int)sizeof(struct lan_addr_s));
! 1297: break;
! 1298: }
! 1299: if(parselanaddr(lan_addr, optarg) != 0) {
! 1300: fprintf(stderr, "can't parse \"%s\" as a valid "
! 1301: #ifndef ENABLE_IPV6
! 1302: "address or "
! 1303: #endif
! 1304: "interface name\n", optarg);
! 1305: free(lan_addr);
! 1306: } else {
! 1307: LIST_INSERT_HEAD(&lan_addrs, lan_addr, list);
! 1308: }
! 1309: break;
! 1310: case 's':
! 1311: sockpath = optarg;
! 1312: break;
! 1313: #ifndef NO_BACKGROUND_NO_PIDFILE
! 1314: case 'p':
! 1315: pidfilename = optarg;
! 1316: break;
! 1317: #endif
! 1318: case 't':
! 1319: ttl = (unsigned char)atoi(optarg);
! 1320: break;
! 1321: case 'f':
! 1322: searched_device = optarg;
! 1323: break;
! 1324: }
! 1325: }
! 1326: if(lan_addrs.lh_first == NULL)
! 1327: {
! 1328: fprintf(stderr,
! 1329: "Usage: %s [-d] "
! 1330: #ifdef ENABLE_IPV6
! 1331: "[-6] "
! 1332: #endif /* ENABLE_IPV6 */
! 1333: "[-s socket] "
! 1334: #ifndef NO_BACKGROUND_NO_PIDFILE
! 1335: "[-p pidfile] "
! 1336: #endif
! 1337: "[-t TTL] "
! 1338: "[-f device] "
! 1339: "-i <interface> [-i <interface2>] ...\n",
! 1340: argv[0]);
! 1341: fprintf(stderr,
! 1342: "\n <interface> is "
! 1343: #ifndef ENABLE_IPV6
! 1344: "either an IPv4 address with mask such as\n"
! 1345: " 192.168.1.42/255.255.255.0, or "
! 1346: #endif
! 1347: "an interface name such as eth0.\n");
! 1348: fprintf(stderr,
! 1349: "\n By default, socket will be open as %s\n"
! 1350: #ifndef NO_BACKGROUND_NO_PIDFILE
! 1351: " and pid written to file %s\n",
! 1352: sockpath, pidfilename
! 1353: #else
! 1354: ,sockpath
! 1355: #endif
! 1356: );
! 1357: return 1;
! 1358: }
! 1359:
! 1360: /* open log */
! 1361: openlog("minissdpd",
! 1362: LOG_CONS|LOG_PID|(debug_flag?LOG_PERROR:0),
! 1363: LOG_MINISSDPD);
! 1364: if(!debug_flag) /* speed things up and ignore LOG_INFO and LOG_DEBUG */
! 1365: setlogmask(LOG_UPTO(LOG_NOTICE));
! 1366:
! 1367: #ifndef NO_BACKGROUND_NO_PIDFILE
! 1368: if(checkforrunning(pidfilename) < 0)
! 1369: {
! 1370: syslog(LOG_ERR, "MiniSSDPd is already running. EXITING");
! 1371: return 1;
! 1372: }
! 1373: #endif
! 1374:
! 1375: upnp_bootid = (unsigned int)time(NULL);
! 1376:
! 1377: /* set signal handlers */
! 1378: memset(&sa, 0, sizeof(struct sigaction));
! 1379: sa.sa_handler = sigterm;
! 1380: if(sigaction(SIGTERM, &sa, NULL))
! 1381: {
! 1382: syslog(LOG_ERR, "Failed to set SIGTERM handler. EXITING");
! 1383: ret = 1;
! 1384: goto quit;
! 1385: }
! 1386: if(sigaction(SIGINT, &sa, NULL))
! 1387: {
! 1388: syslog(LOG_ERR, "Failed to set SIGINT handler. EXITING");
! 1389: ret = 1;
! 1390: goto quit;
! 1391: }
! 1392: /* open route/interface config changes socket */
! 1393: s_ifacewatch = OpenAndConfInterfaceWatchSocket();
! 1394: /* open UDP socket(s) for receiving SSDP packets */
! 1395: s_ssdp = OpenAndConfSSDPReceiveSocket(0, ttl);
! 1396: if(s_ssdp < 0)
! 1397: {
! 1398: syslog(LOG_ERR, "Cannot open socket for receiving SSDP messages, exiting");
! 1399: ret = 1;
! 1400: goto quit;
! 1401: }
! 1402: #ifdef ENABLE_IPV6
! 1403: if(ipv6) {
! 1404: s_ssdp6 = OpenAndConfSSDPReceiveSocket(1, ttl);
! 1405: if(s_ssdp6 < 0)
! 1406: {
! 1407: syslog(LOG_ERR, "Cannot open socket for receiving SSDP messages (IPv6), exiting");
! 1408: ret = 1;
! 1409: goto quit;
! 1410: }
! 1411: }
! 1412: #endif /* ENABLE_IPV6 */
! 1413: /* Open Unix socket to communicate with other programs on
! 1414: * the same machine */
! 1415: s_unix = OpenUnixSocket(sockpath);
! 1416: if(s_unix < 0)
! 1417: {
! 1418: syslog(LOG_ERR, "Cannot open unix socket for communicating with clients. Exiting");
! 1419: ret = 1;
! 1420: goto quit;
! 1421: }
! 1422:
! 1423: /* drop privileges */
! 1424: #if 0
! 1425: /* if we drop privileges, how to unlink(/var/run/minissdpd.sock) ? */
! 1426: if(getuid() == 0) {
! 1427: struct passwd * user;
! 1428: struct group * group;
! 1429: user = getpwnam("nobody");
! 1430: if(!user) {
! 1431: syslog(LOG_ERR, "getpwnam(\"%s\") : %m", "nobody");
! 1432: ret = 1;
! 1433: goto quit;
! 1434: }
! 1435: group = getgrnam("nogroup");
! 1436: if(!group) {
! 1437: syslog(LOG_ERR, "getgrnam(\"%s\") : %m", "nogroup");
! 1438: ret = 1;
! 1439: goto quit;
! 1440: }
! 1441: if(setgid(group->gr_gid) < 0) {
! 1442: syslog(LOG_ERR, "setgit(%d) : %m", group->gr_gid);
! 1443: ret = 1;
! 1444: goto quit;
! 1445: }
! 1446: if(setuid(user->pw_uid) < 0) {
! 1447: syslog(LOG_ERR, "setuid(%d) : %m", user->pw_uid);
! 1448: ret = 1;
! 1449: goto quit;
! 1450: }
! 1451: }
! 1452: #endif
! 1453:
! 1454: #ifndef NO_BACKGROUND_NO_PIDFILE
! 1455: /* daemonize or in any case get pid ! */
! 1456: if(debug_flag)
! 1457: pid = getpid();
! 1458: else {
! 1459: #ifdef USE_DAEMON
! 1460: if(daemon(0, 0) < 0)
! 1461: perror("daemon()");
! 1462: pid = getpid();
! 1463: #else /* USE_DAEMON */
! 1464: pid = daemonize();
! 1465: #endif /* USE_DAEMON */
! 1466: }
! 1467:
! 1468: writepidfile(pidfilename, pid);
! 1469: #endif
! 1470:
! 1471: /* send M-SEARCH ssdp:all Requests */
! 1472: if(s_ssdp >= 0) {
! 1473: for(lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next) {
! 1474: #ifndef HAVE_IP_MREQN
! 1475: struct in_addr mc_if;
! 1476:
! 1477: mc_if.s_addr = lan_addr->addr.s_addr; /*inet_addr(addr);*/
! 1478: #else
! 1479: struct ip_mreqn mc_if;
! 1480:
! 1481: mc_if.imr_address.s_addr = lan_addr->addr.s_addr; /*inet_addr(addr);*/
! 1482: #ifdef ENABLE_IPV6
! 1483: mc_if.imr_ifindex = lan_addr->index;
! 1484: #else /* ENABLE_IPV6 */
! 1485: mc_if.imr_ifindex = if_nametoindex(lan_addr->ifname);
! 1486: #endif /* ENABLE_IPV6 */
! 1487: #endif /* HAVE_IP_MREQN */
! 1488: if(setsockopt(s_ssdp, IPPROTO_IP, IP_MULTICAST_IF, &mc_if, sizeof(mc_if)) < 0) {
! 1489: syslog(LOG_WARNING, "setsockopt(IP_MULTICAST_IF): %m");
! 1490: }
! 1491: ssdpDiscover(s_ssdp, 0, searched_device);
! 1492: /* XXX if ssdpDiscover() doesn't send the SSDP packet at once,
! 1493: * we should wait here */
! 1494: }
! 1495: }
! 1496: if(s_ssdp6 >= 0)
! 1497: ssdpDiscover(s_ssdp6, 1, searched_device);
! 1498:
! 1499: /* Main loop */
! 1500: while(!quitting) {
! 1501: /* fill readfds fd_set */
! 1502: FD_ZERO(&readfds);
! 1503: FD_ZERO(&writefds);
! 1504:
! 1505: FD_SET(s_unix, &readfds);
! 1506: max_fd = s_unix;
! 1507: if(s_ssdp >= 0) {
! 1508: FD_SET(s_ssdp, &readfds);
! 1509: SET_MAX(max_fd, s_ssdp);
! 1510: }
! 1511: #ifdef ENABLE_IPV6
! 1512: if(s_ssdp6 >= 0) {
! 1513: FD_SET(s_ssdp6, &readfds);
! 1514: SET_MAX(max_fd, s_ssdp6);
! 1515: }
! 1516: #endif /* ENABLE_IPV6 */
! 1517: if(s_ifacewatch >= 0) {
! 1518: FD_SET(s_ifacewatch, &readfds);
! 1519: SET_MAX(max_fd, s_ifacewatch);
! 1520: }
! 1521: for(req = reqlisthead.lh_first; req; req = req->entries.le_next) {
! 1522: if(req->socket >= 0) {
! 1523: FD_SET(req->socket, &readfds);
! 1524: SET_MAX(max_fd, req->socket);
! 1525: }
! 1526: if(req->output_buffer_len > 0) {
! 1527: FD_SET(req->socket, &writefds);
! 1528: SET_MAX(max_fd, req->socket);
! 1529: }
! 1530: }
! 1531: gettimeofday(&now, NULL);
! 1532: i = get_sendto_fds(&writefds, &max_fd, &now);
! 1533: /* select call */
! 1534: if(select(max_fd + 1, &readfds, &writefds, 0, 0) < 0) {
! 1535: if(errno != EINTR) {
! 1536: syslog(LOG_ERR, "select: %m");
! 1537: break; /* quit */
! 1538: }
! 1539: continue; /* try again */
! 1540: }
! 1541: if(try_sendto(&writefds) < 0) {
! 1542: syslog(LOG_ERR, "try_sendto: %m");
! 1543: break;
! 1544: }
! 1545: #ifdef ENABLE_IPV6
! 1546: if((s_ssdp6 >= 0) && FD_ISSET(s_ssdp6, &readfds))
! 1547: {
! 1548: sendername6_len = sizeof(struct sockaddr_in6);
! 1549: n = recvfrom(s_ssdp6, buf, sizeof(buf), 0,
! 1550: (struct sockaddr *)&sendername6, &sendername6_len);
! 1551: if(n<0)
! 1552: {
! 1553: /* EAGAIN, EWOULDBLOCK, EINTR : silently ignore (try again next time)
! 1554: * other errors : log to LOG_ERR */
! 1555: if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)
! 1556: syslog(LOG_ERR, "recvfrom: %m");
! 1557: }
! 1558: else
! 1559: {
! 1560: /* Parse and process the packet received */
! 1561: /*printf("%.*s", n, buf);*/
! 1562: i = ParseSSDPPacket(s_ssdp6, buf, n,
! 1563: (struct sockaddr *)&sendername6, searched_device);
! 1564: syslog(LOG_DEBUG, "** i=%d deltadev=%d **", i, deltadev);
! 1565: if(i==0 || (i*deltadev < 0))
! 1566: {
! 1567: if(deltadev > 0)
! 1568: syslog(LOG_NOTICE, "%d new devices added", deltadev);
! 1569: else if(deltadev < 0)
! 1570: syslog(LOG_NOTICE, "%d devices removed (good-bye!)", -deltadev);
! 1571: deltadev = i;
! 1572: }
! 1573: else if((i*deltadev) >= 0)
! 1574: {
! 1575: deltadev += i;
! 1576: }
! 1577: }
! 1578: }
! 1579: #endif /* ENABLE_IPV6 */
! 1580: if((s_ssdp >= 0) && FD_ISSET(s_ssdp, &readfds))
! 1581: {
! 1582: sendername_len = sizeof(struct sockaddr_in);
! 1583: n = recvfrom(s_ssdp, buf, sizeof(buf), 0,
! 1584: (struct sockaddr *)&sendername, &sendername_len);
! 1585: if(n<0)
! 1586: {
! 1587: /* EAGAIN, EWOULDBLOCK, EINTR : silently ignore (try again next time)
! 1588: * other errors : log to LOG_ERR */
! 1589: if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)
! 1590: syslog(LOG_ERR, "recvfrom: %m");
! 1591: }
! 1592: else
! 1593: {
! 1594: /* Parse and process the packet received */
! 1595: /*printf("%.*s", n, buf);*/
! 1596: i = ParseSSDPPacket(s_ssdp, buf, n,
! 1597: (struct sockaddr *)&sendername, searched_device);
! 1598: syslog(LOG_DEBUG, "** i=%d deltadev=%d **", i, deltadev);
! 1599: if(i==0 || (i*deltadev < 0))
! 1600: {
! 1601: if(deltadev > 0)
! 1602: syslog(LOG_NOTICE, "%d new devices added", deltadev);
! 1603: else if(deltadev < 0)
! 1604: syslog(LOG_NOTICE, "%d devices removed (good-bye!)", -deltadev);
! 1605: deltadev = i;
! 1606: }
! 1607: else if((i*deltadev) >= 0)
! 1608: {
! 1609: deltadev += i;
! 1610: }
! 1611: }
! 1612: }
! 1613: /* processing unix socket requests */
! 1614: for(req = reqlisthead.lh_first; req;) {
! 1615: reqnext = req->entries.le_next;
! 1616: if((req->socket >= 0) && FD_ISSET(req->socket, &readfds)) {
! 1617: processRequest(req);
! 1618: }
! 1619: if((req->socket >= 0) && FD_ISSET(req->socket, &writefds)) {
! 1620: write_buffer(req);
! 1621: }
! 1622: if(req->socket < 0) {
! 1623: LIST_REMOVE(req, entries);
! 1624: free(req->output_buffer);
! 1625: free(req);
! 1626: }
! 1627: req = reqnext;
! 1628: }
! 1629: /* processing new requests */
! 1630: if(FD_ISSET(s_unix, &readfds))
! 1631: {
! 1632: struct reqelem * tmp;
! 1633: int s = accept(s_unix, NULL, NULL);
! 1634: if(s < 0) {
! 1635: syslog(LOG_ERR, "accept(s_unix): %m");
! 1636: } else {
! 1637: syslog(LOG_INFO, "(s=%d) new request connection", s);
! 1638: if(!set_non_blocking(s))
! 1639: syslog(LOG_WARNING, "Failed to set new socket non blocking : %m");
! 1640: tmp = malloc(sizeof(struct reqelem));
! 1641: if(!tmp) {
! 1642: syslog(LOG_ERR, "cannot allocate memory for request");
! 1643: close(s);
! 1644: } else {
! 1645: memset(tmp, 0, sizeof(struct reqelem));
! 1646: tmp->socket = s;
! 1647: LIST_INSERT_HEAD(&reqlisthead, tmp, entries);
! 1648: }
! 1649: }
! 1650: }
! 1651: /* processing route/network interface config changes */
! 1652: if((s_ifacewatch >= 0) && FD_ISSET(s_ifacewatch, &readfds)) {
! 1653: ProcessInterfaceWatch(s_ifacewatch, s_ssdp, s_ssdp6);
! 1654: }
! 1655: }
! 1656: syslog(LOG_DEBUG, "quitting...");
! 1657: finalize_sendto();
! 1658:
! 1659: /* closing and cleaning everything */
! 1660: quit:
! 1661: if(s_ssdp >= 0) {
! 1662: close(s_ssdp);
! 1663: s_ssdp = -1;
! 1664: }
! 1665: #ifdef ENABLE_IPV6
! 1666: if(s_ssdp6 >= 0) {
! 1667: close(s_ssdp6);
! 1668: s_ssdp6 = -1;
! 1669: }
! 1670: #endif /* ENABLE_IPV6 */
! 1671: if(s_unix >= 0) {
! 1672: close(s_unix);
! 1673: s_unix = -1;
! 1674: if(unlink(sockpath) < 0)
! 1675: syslog(LOG_ERR, "unlink(%s): %m", sockpath);
! 1676: }
! 1677: if(s_ifacewatch >= 0) {
! 1678: close(s_ifacewatch);
! 1679: s_ifacewatch = -1;
! 1680: }
! 1681: /* empty LAN interface/address list */
! 1682: while(lan_addrs.lh_first != NULL) {
! 1683: lan_addr = lan_addrs.lh_first;
! 1684: LIST_REMOVE(lan_addrs.lh_first, list);
! 1685: free(lan_addr);
! 1686: }
! 1687: /* empty device list */
! 1688: while(devlist != NULL) {
! 1689: struct device * next = devlist->next;
! 1690: free(devlist);
! 1691: devlist = next;
! 1692: }
! 1693: /* empty service list */
! 1694: while(servicelisthead.lh_first != NULL) {
! 1695: struct service * serv = servicelisthead.lh_first;
! 1696: LIST_REMOVE(servicelisthead.lh_first, entries);
! 1697: free(serv->st);
! 1698: free(serv->usn);
! 1699: free(serv->server);
! 1700: free(serv->location);
! 1701: free(serv);
! 1702: }
! 1703: #ifndef NO_BACKGROUND_NO_PIDFILE
! 1704: if(unlink(pidfilename) < 0)
! 1705: syslog(LOG_ERR, "unlink(%s): %m", pidfilename);
! 1706: #endif
! 1707: closelog();
! 1708: return ret;
! 1709: }
! 1710:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>