1/* $Id: minissdp.c,v 1.12 2010/01/13 21:15:11 jmaggard Exp $ */ 2/* MiniUPnP project 3 * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ 4 * (c) 2006 Thomas Bernard 5 * This software is subject to the conditions detailed 6 * in the LICENCE file provided within the distribution */ 7 8#include <stdio.h> 9#include <stdlib.h> 10#include <string.h> 11#include <unistd.h> 12#include <sys/socket.h> 13#include <netinet/in.h> 14#include <arpa/inet.h> 15#include <errno.h> 16 17#include "config.h" 18#include "upnpdescstrings.h" 19#include "minidlnapath.h" 20#include "upnphttp.h" 21#include "upnpglobalvars.h" 22#include "minissdp.h" 23#include "log.h" 24 25/* SSDP ip/port */ 26#define SSDP_PORT (1900) 27#define SSDP_MCAST_ADDR ("239.255.255.250") 28 29static int 30AddMulticastMembership(int s, in_addr_t ifaddr/*const char * ifaddr*/) 31{ 32 struct ip_mreq imr; /* Ip multicast membership */ 33 34 /* setting up imr structure */ 35 imr.imr_multiaddr.s_addr = inet_addr(SSDP_MCAST_ADDR); 36 /*imr.imr_interface.s_addr = htonl(INADDR_ANY);*/ 37 imr.imr_interface.s_addr = ifaddr; /*inet_addr(ifaddr);*/ 38 39 if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&imr, sizeof(struct ip_mreq)) < 0) 40 { 41 DPRINTF(E_ERROR, L_SSDP, "setsockopt(udp, IP_ADD_MEMBERSHIP): %s\n", strerror(errno)); 42 return -1; 43 } 44 45 return 0; 46} 47 48int 49OpenAndConfSSDPReceiveSocket() 50{ 51 int s; 52 int i = 1; 53 struct sockaddr_in sockname; 54 55 if( (s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) 56 { 57 DPRINTF(E_ERROR, L_SSDP, "socket(udp): %s\n", strerror(errno)); 58 return -1; 59 } 60 61 if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0) 62 { 63 DPRINTF(E_WARN, L_SSDP, "setsockopt(udp, SO_REUSEADDR): %s\n", strerror(errno)); 64 } 65 66 memset(&sockname, 0, sizeof(struct sockaddr_in)); 67 sockname.sin_family = AF_INET; 68 sockname.sin_port = htons(SSDP_PORT); 69 /* NOTE : it seems it doesnt work when binding on the specific address */ 70 /*sockname.sin_addr.s_addr = inet_addr(UPNP_MCAST_ADDR);*/ 71 sockname.sin_addr.s_addr = htonl(INADDR_ANY); 72 /*sockname.sin_addr.s_addr = inet_addr(ifaddr);*/ 73 74 if(bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0) 75 { 76 DPRINTF(E_ERROR, L_SSDP, "bind(udp): %s\n", strerror(errno)); 77 close(s); 78 return -1; 79 } 80 81 i = n_lan_addr; 82 while(i>0) 83 { 84 i--; 85 if(AddMulticastMembership(s, lan_addr[i].addr.s_addr) < 0) 86 { 87 DPRINTF(E_WARN, L_SSDP, 88 "Failed to add multicast membership for address %s\n", 89 lan_addr[i].str ); 90 } 91 } 92 93 return s; 94} 95 96/* open the UDP socket used to send SSDP notifications to 97 * the multicast group reserved for them */ 98static int 99OpenAndConfSSDPNotifySocket(in_addr_t addr) 100{ 101 int s; 102 unsigned char loopchar = 0; 103 int bcast = 1; 104 struct in_addr mc_if; 105 struct sockaddr_in sockname; 106 107 if( (s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) 108 { 109 DPRINTF(E_ERROR, L_SSDP, "socket(udp_notify): %s\n", strerror(errno)); 110 return -1; 111 } 112 113 mc_if.s_addr = addr; /*inet_addr(addr);*/ 114 115 if(setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&loopchar, sizeof(loopchar)) < 0) 116 { 117 DPRINTF(E_ERROR, L_SSDP, "setsockopt(udp_notify, IP_MULTICAST_LOOP): %s\n", strerror(errno)); 118 close(s); 119 return -1; 120 } 121 122 if(setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, (char *)&mc_if, sizeof(mc_if)) < 0) 123 { 124 DPRINTF(E_ERROR, L_SSDP, "setsockopt(udp_notify, IP_MULTICAST_IF): %s\n", strerror(errno)); 125 close(s); 126 return -1; 127 } 128 129 if(setsockopt(s, SOL_SOCKET, SO_BROADCAST, &bcast, sizeof(bcast)) < 0) 130 { 131 DPRINTF(E_ERROR, L_SSDP, "setsockopt(udp_notify, SO_BROADCAST): %s\n", strerror(errno)); 132 close(s); 133 return -1; 134 } 135 136 memset(&sockname, 0, sizeof(struct sockaddr_in)); 137 sockname.sin_family = AF_INET; 138 sockname.sin_addr.s_addr = addr; /*inet_addr(addr);*/ 139 140 if (bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0) 141 { 142 DPRINTF(E_ERROR, L_SSDP, "bind(udp_notify): %s\n", strerror(errno)); 143 close(s); 144 return -1; 145 } 146 147 return s; 148} 149 150int 151OpenAndConfSSDPNotifySockets(int * sockets) 152{ 153 int i, j; 154 for(i=0; i<n_lan_addr; i++) 155 { 156 sockets[i] = OpenAndConfSSDPNotifySocket(lan_addr[i].addr.s_addr); 157 if(sockets[i] < 0) 158 { 159 for(j=0; j<i; j++) 160 { 161 close(sockets[j]); 162 sockets[j] = -1; 163 } 164 return -1; 165 } 166 } 167 return 0; 168} 169 170/* 171 * response from a LiveBox (Wanadoo) 172HTTP/1.1 200 OK 173CACHE-CONTROL: max-age=1800 174DATE: Thu, 01 Jan 1970 04:03:23 GMT 175EXT: 176LOCATION: http://192.168.0.1:49152/gatedesc.xml 177SERVER: Linux/2.4.17, UPnP/1.0, Intel SDK for UPnP devices /1.2 178ST: upnp:rootdevice 179USN: uuid:75802409-bccb-40e7-8e6c-fa095ecce13e::upnp:rootdevice 180 181 * response from a Linksys 802.11b : 182HTTP/1.1 200 OK 183Cache-Control:max-age=120 184Location:http://192.168.5.1:5678/rootDesc.xml 185Server:NT/5.0 UPnP/1.0 186ST:upnp:rootdevice 187USN:uuid:upnp-InternetGatewayDevice-1_0-0090a2777777::upnp:rootdevice 188EXT: 189 */ 190 191static const char * const known_service_types[] = 192{ 193 uuidvalue, 194 "upnp:rootdevice", 195 "urn:schemas-upnp-org:device:MediaServer:", 196 "urn:schemas-upnp-org:service:ContentDirectory:", 197 "urn:schemas-upnp-org:service:ConnectionManager:", 198 "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:", 199 0 200}; 201 202/* not really an SSDP "announce" as it is the response 203 * to a SSDP "M-SEARCH" */ 204static void 205SendSSDPAnnounce2(int s, struct sockaddr_in sockname, int st_no, 206 const char * host, unsigned short port) 207{ 208 int l, n; 209 char buf[512]; 210 /* TODO : 211 * follow guideline from document "UPnP Device Architecture 1.0" 212 * put in uppercase. 213 * DATE: is recommended 214 * SERVER: OS/ver UPnP/1.0 minidlna/1.0 215 * - check what to put in the 'Cache-Control' header 216 * */ 217 char szTime[30]; 218 time_t tTime = time(NULL); 219 strftime(szTime, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&tTime)); 220 221 l = snprintf(buf, sizeof(buf), "HTTP/1.1 200 OK\r\n" 222 "CACHE-CONTROL: max-age=%u\r\n" 223 "DATE: %s\r\n" 224 "ST: %s%s\r\n" 225 "USN: %s%s%s%s\r\n" 226 "EXT:\r\n" 227 "SERVER: " MINIDLNA_SERVER_STRING "\r\n" 228 "LOCATION: http://%s:%u" ROOTDESC_PATH "\r\n" 229 "Content-Length: 0\r\n" 230 "\r\n", 231 (runtime_vars.notify_interval<<1)+10, 232 szTime, 233 known_service_types[st_no], (st_no>1?"1":""), 234 uuidvalue, (st_no>0?"::":""), (st_no>0?known_service_types[st_no]:""), (st_no>1?"1":""), 235 host, (unsigned int)port); 236 //DEBUG DPRINTF(E_DEBUG, L_SSDP, "Sending M-SEARCH response:\n%s", buf); 237 n = sendto(s, buf, l, 0, 238 (struct sockaddr *)&sockname, sizeof(struct sockaddr_in) ); 239 if(n < 0) 240 { 241 DPRINTF(E_ERROR, L_SSDP, "sendto(udp): %s\n", strerror(errno)); 242 } 243} 244 245static void 246SendSSDPNotifies(int s, const char * host, unsigned short port, 247 unsigned int lifetime) 248{ 249 struct sockaddr_in sockname; 250 int l, n, dup, i=0; 251 char bufr[512]; 252 253 memset(&sockname, 0, sizeof(struct sockaddr_in)); 254 sockname.sin_family = AF_INET; 255 sockname.sin_port = htons(SSDP_PORT); 256 sockname.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR); 257 258 for( dup=0; dup<2; dup++ ) 259 { 260 if( dup ) 261 usleep(200000); 262 i=0; 263 while(known_service_types[i]) 264 { 265 l = snprintf(bufr, sizeof(bufr), 266 "NOTIFY * HTTP/1.1\r\n" 267 "HOST:%s:%d\r\n" 268 "CACHE-CONTROL:max-age=%u\r\n" 269 "LOCATION:http://%s:%d" ROOTDESC_PATH"\r\n" 270 "SERVER: " MINIDLNA_SERVER_STRING "\r\n" 271 "NT:%s%s\r\n" 272 "USN:%s%s%s%s\r\n" 273 "NTS:ssdp:alive\r\n" 274 "\r\n", 275 SSDP_MCAST_ADDR, SSDP_PORT, 276 lifetime, 277 host, port, 278 known_service_types[i], (i>1?"1":""), 279 uuidvalue, (i>0?"::":""), (i>0?known_service_types[i]:""), (i>1?"1":"") ); 280 if(l>=sizeof(bufr)) 281 { 282 DPRINTF(E_WARN, L_SSDP, "SendSSDPNotifies(): truncated output\n"); 283 l = sizeof(bufr); 284 } 285 //DEBUG DPRINTF(E_DEBUG, L_SSDP, "Sending NOTIFY:\n%s", bufr); 286 n = sendto(s, bufr, l, 0, 287 (struct sockaddr *)&sockname, sizeof(struct sockaddr_in) ); 288 if(n < 0) 289 { 290 DPRINTF(E_ERROR, L_SSDP, "sendto(udp_notify=%d, %s): %s\n", s, host, strerror(errno)); 291 } 292 i++; 293 } 294 } 295} 296 297void 298SendSSDPNotifies2(int * sockets, 299 unsigned short port, 300 unsigned int lifetime) 301/*SendSSDPNotifies2(int * sockets, struct lan_addr_s * lan_addr, int n_lan_addr, 302 unsigned short port, 303 unsigned int lifetime)*/ 304{ 305 int i; 306 DPRINTF(E_DEBUG, L_SSDP, "Sending SSDP notifies\n"); 307 for(i=0; i<n_lan_addr; i++) 308 { 309 SendSSDPNotifies(sockets[i], lan_addr[i].str, port, lifetime); 310 } 311} 312 313/* ProcessSSDPRequest() 314 * process SSDP M-SEARCH requests and responds to them */ 315void 316ProcessSSDPRequest(int s, unsigned short port) 317/*ProcessSSDPRequest(int s, struct lan_addr_s * lan_addr, int n_lan_addr, 318 unsigned short port)*/ 319{ 320 int n; 321 char bufr[1500]; 322 socklen_t len_r; 323 struct sockaddr_in sendername; 324 int i, l; 325 int lan_addr_index = 0; 326 char * st = NULL, * mx = NULL, * man = NULL, * mx_end = NULL; 327 int st_len = 0, mx_len = 0, man_len = 0, mx_val = 0; 328 len_r = sizeof(struct sockaddr_in); 329 330 n = recvfrom(s, bufr, sizeof(bufr), 0, 331 (struct sockaddr *)&sendername, &len_r); 332 if(n < 0) 333 { 334 DPRINTF(E_ERROR, L_SSDP, "recvfrom(udp): %s\n", strerror(errno)); 335 return; 336 } 337 338 if(memcmp(bufr, "NOTIFY", 6) == 0) 339 { 340 /* ignore NOTIFY packets. We could log the sender and device type */ 341 return; 342 } 343 else if(memcmp(bufr, "M-SEARCH", 8) == 0) 344 { 345 //DEBUG DPRINTF(E_DEBUG, L_SSDP, "Received SSDP request:\n%.*s", n, bufr); 346 for(i=0; i < n; i++) 347 { 348 if( bufr[i] == '*' ) 349 break; 350 } 351 if( !strcasestr(bufr+i, "HTTP/1.1") ) 352 { 353 return; 354 } 355 while(i < n) 356 { 357 while((i < n - 1) && (bufr[i] != '\r' || bufr[i+1] != '\n')) 358 i++; 359 i += 2; 360 if((i < n - 3) && (strncasecmp(bufr+i, "ST:", 3) == 0)) 361 { 362 st = bufr+i+3; 363 st_len = 0; 364 while(*st == ' ' || *st == '\t') st++; 365 while(st[st_len]!='\r' && st[st_len]!='\n') st_len++; 366 } 367 else if(strncasecmp(bufr+i, "MX:", 3) == 0) 368 { 369 mx = bufr+i+3; 370 mx_len = 0; 371 while(*mx == ' ' || *mx == '\t') mx++; 372 while(mx[mx_len]!='\r' && mx[mx_len]!='\n') mx_len++; 373 mx_val = strtol(mx, &mx_end, 10); 374 } 375 else if(strncasecmp(bufr+i, "MAN:", 4) == 0) 376 { 377 man = bufr+i+4; 378 man_len = 0; 379 while(*man == ' ' || *man == '\t') man++; 380 while(man[man_len]!='\r' && man[man_len]!='\n') man_len++; 381 } 382 } 383 /*DPRINTF(E_INFO, L_SSDP, "SSDP M-SEARCH packet received from %s:%d\n", 384 inet_ntoa(sendername.sin_addr), 385 ntohs(sendername.sin_port) );*/ 386 if( ntohs(sendername.sin_port) <= 1024 || ntohs(sendername.sin_port) == 1900 ) 387 { 388 DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad source port %d]\n", 389 inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port)); 390 } 391 else if( !man || (strncmp(man, "\"ssdp:discover\"", 15) != 0) ) 392 { 393 DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad MAN header %.*s]\n", 394 inet_ntoa(sendername.sin_addr), man_len, man); 395 } 396 else if( !mx || mx == mx_end || mx_val < 0 ) { 397 DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad MX header %.*s]\n", 398 inet_ntoa(sendername.sin_addr), mx_len, mx); 399 } 400 else if( st && (st_len > 0) ) 401 { 402 DPRINTF(E_INFO, L_SSDP, "SSDP M-SEARCH from %s:%d ST: %.*s, MX: %.*s, MAN: %.*s\n", 403 inet_ntoa(sendername.sin_addr), 404 ntohs(sendername.sin_port), 405 st_len, st, mx_len, mx, man_len, man); 406 /* find in which sub network the client is */ 407 for(i = 0; i<n_lan_addr; i++) 408 { 409 if( (sendername.sin_addr.s_addr & lan_addr[i].mask.s_addr) 410 == (lan_addr[i].addr.s_addr & lan_addr[i].mask.s_addr)) 411 { 412 lan_addr_index = i; 413 break; 414 } 415 } 416 /* Responds to request with a device as ST header */ 417 for(i = 0; known_service_types[i]; i++) 418 { 419 l = strlen(known_service_types[i]); 420 if(l<=st_len && (0 == memcmp(st, known_service_types[i], l))) 421 { 422 /* Check version number - must always be 1 currently. */ 423 if( (st[st_len-2] == ':') && (atoi(st+st_len-1) != 1) ) 424 break; 425 usleep(random()>>20); 426 SendSSDPAnnounce2(s, sendername, 427 i, 428 lan_addr[lan_addr_index].str, port); 429 break; 430 } 431 } 432 /* Responds to request with ST: ssdp:all */ 433 /* strlen("ssdp:all") == 8 */ 434 if(st_len==8 && (0 == memcmp(st, "ssdp:all", 8))) 435 { 436 for(i=0; known_service_types[i]; i++) 437 { 438 l = (int)strlen(known_service_types[i]); 439 SendSSDPAnnounce2(s, sendername, 440 i, 441 lan_addr[lan_addr_index].str, port); 442 } 443 } 444 } 445 else 446 { 447 DPRINTF(E_INFO, L_SSDP, "Invalid SSDP M-SEARCH from %s:%d\n", 448 inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port)); 449 } 450 } 451 else 452 { 453 DPRINTF(E_WARN, L_SSDP, "Unknown udp packet received from %s:%d\n", 454 inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port)); 455 } 456} 457 458/* This will broadcast ssdp:byebye notifications to inform 459 * the network that UPnP is going down. */ 460int 461SendSSDPGoodbye(int * sockets, int n_sockets) 462{ 463 struct sockaddr_in sockname; 464 int n, l; 465 int i, j; 466 char bufr[512]; 467 468 memset(&sockname, 0, sizeof(struct sockaddr_in)); 469 sockname.sin_family = AF_INET; 470 sockname.sin_port = htons(SSDP_PORT); 471 sockname.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR); 472 473 for(j=0; j<n_sockets; j++) 474 { 475 for(i=0; known_service_types[i]; i++) 476 { 477 l = snprintf(bufr, sizeof(bufr), 478 "NOTIFY * HTTP/1.1\r\n" 479 "HOST:%s:%d\r\n" 480 "NT:%s%s\r\n" 481 "USN:%s%s%s%s\r\n" 482 "NTS:ssdp:byebye\r\n" 483 "\r\n", 484 SSDP_MCAST_ADDR, SSDP_PORT, 485 known_service_types[i], (i>1?"1":""), 486 uuidvalue, (i>0?"::":""), (i>0?known_service_types[i]:""), (i>1?"1":"") ); 487 //DEBUG DPRINTF(E_DEBUG, L_SSDP, "Sending NOTIFY:\n%s", bufr); 488 n = sendto(sockets[j], bufr, l, 0, 489 (struct sockaddr *)&sockname, sizeof(struct sockaddr_in) ); 490 if(n < 0) 491 { 492 DPRINTF(E_ERROR, L_SSDP, "sendto(udp_shutdown=%d): %s\n", sockets[j], strerror(errno)); 493 return -1; 494 } 495 } 496 } 497 return 0; 498} 499