void ParseUPnPClient(char *location) { char buf[8192]; struct sockaddr_in dest; int s, n, do_headers = 0, nread = 0; struct timeval tv; char *addr, *path, *port_str; long port = 80; char *off = NULL, *p; int content_len = sizeof(buf); struct NameValueParserData xml; int client; int type = 0; char *model, *serial, *name; if (strncmp(location, "http://", 7) != 0) return; path = location + 7; port_str = strsep(&path, "/"); if (!path) return; addr = strsep(&port_str, ":"); if (port_str) { port = strtol(port_str, NULL, 10); if (!port) port = 80; } memset(&dest, '\0', sizeof(dest)); if (!inet_aton(addr, &dest.sin_addr)) return; /* Check if the client is already in cache */ dest.sin_family = AF_INET; dest.sin_port = htons(port); s = socket(PF_INET, SOCK_STREAM, 0); if (s < 0) return; tv.tv_sec = 0; tv.tv_usec = 500000; setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); if (connect(s, (struct sockaddr*)&dest, sizeof(struct sockaddr_in)) < 0) goto close; n = snprintf(buf, sizeof(buf), "GET /%s HTTP/1.0\r\n" "HOST: %s:%ld\r\n\r\n", path, addr, port); if (write(s, buf, n) < 1) goto close; while ((n = read(s, buf+nread, sizeof(buf)-nread-1)) > 0) { nread += n; buf[nread] = '\0'; n = nread; p = buf; while (!off && (n-- > 0)) { if (p[0] == '\r' && p[1] == '\n' && p[2] == '\r' && p[3] == '\n') { off = p + 4; do_headers = 1; } p++; } if (!off) continue; if (do_headers) { p = buf; if (strncmp(p, "HTTP/", 5) != 0) goto close; while (*p != ' ' && *p != '\t') p++; /* If we don't get a 200 status, ignore it */ if (strtol(p, NULL, 10) != 200) goto close; p = strcasestr(p, "Content-Length:"); if (p) content_len = strtol(p+15, NULL, 10); do_headers = 0; } if ((buf + nread - off) >= content_len) break; } close: close(s); if (!off) return; nread -= off - buf; ParseNameValue(off, nread, &xml, 0); model = GetValueFromNameValueList(&xml, "modelName"); serial = GetValueFromNameValueList(&xml, "serialNumber"); name = GetValueFromNameValueList(&xml, "friendlyName"); if (model) { int i; DPRINTF(E_DEBUG, L_SSDP, "Model: %s\n", model); for (i = 0; client_types[i].name; i++) { if (client_types[i].match_type != EModelName) continue; if (strstr(model, client_types[i].match) != NULL) { type = i; break; } } /* Special Samsung handling. It's very hard to tell Series A from B */ if (type > 0 && client_types[type].type == ESamsungSeriesB) { if (serial) { DPRINTF(E_DEBUG, L_SSDP, "Serial: %s\n", serial); /* The Series B I saw was 20081224DMR. Series A should be older than that. */ if (atoi(serial) < 20081201) type = 0; } else { type = 0; } } if (type == 0 && name != NULL) { for (i = 0; client_types[i].name; i++) { if (client_types[i].match_type != EFriendlyNameSSDP) continue; if (strcmp(name, client_types[i].match) == 0) { type = i; break; } } } } ClearNameValueList(&xml); if (!type) return; /* Add this client to the cache if it's not there already. */ client = SearchClientCache(dest.sin_addr, 1); if (client < 0) { AddClientCache(dest.sin_addr, type); } else { clients[client].type = type; clients[client].age = time(NULL); } }
/* ProcessSSDPRequest() * process SSDP M-SEARCH requests and responds to them */ void ProcessSSDPRequest(int s, unsigned short port) { int n; char bufr[1500]; socklen_t len_r; struct sockaddr_in sendername; int i; char *st = NULL, *mx = NULL, *man = NULL, *mx_end = NULL; int man_len = 0; len_r = sizeof(struct sockaddr_in); n = recvfrom(s, bufr, sizeof(bufr)-1, 0, (struct sockaddr *)&sendername, &len_r); if (n < 0) { DPRINTF(E_ERROR, L_SSDP, "recvfrom(udp): %s\n", strerror(errno)); return; } bufr[n] = '\0'; n -= 2; if (memcmp(bufr, "NOTIFY", 6) == 0) { char *loc = NULL, *srv = NULL, *nts = NULL, *nt = NULL; int loc_len = 0; //DEBUG DPRINTF(E_DEBUG, L_SSDP, "Received SSDP notify:\n%.*s", n, bufr); for (i = 0; i < n; i++) { if( bufr[i] == '*' ) break; } if (strcasestrc(bufr+i, "HTTP/1.1", '\r') == NULL) return; while (i < n) { while ((i < n) && (bufr[i] != '\r' || bufr[i+1] != '\n')) i++; i += 2; if (strncasecmp(bufr+i, "SERVER:", 7) == 0) { srv = bufr+i+7; while (*srv == ' ' || *srv == '\t') srv++; } else if (strncasecmp(bufr+i, "LOCATION:", 9) == 0) { loc = bufr+i+9; while (*loc == ' ' || *loc == '\t') loc++; while (loc[loc_len]!='\r' && loc[loc_len]!='\n') loc_len++; } else if (strncasecmp(bufr+i, "NTS:", 4) == 0) { nts = bufr+i+4; while (*nts == ' ' || *nts == '\t') nts++; } else if (strncasecmp(bufr+i, "NT:", 3) == 0) { nt = bufr+i+3; while(*nt == ' ' || *nt == '\t') nt++; } } if (!loc || !srv || !nt || !nts || (strncmp(nts, "ssdp:alive", 10) != 0) || (strncmp(nt, "urn:schemas-upnp-org:device:MediaRenderer", 41) != 0)) return; loc[loc_len] = '\0'; if ((strncmp(srv, "Allegro-Software-RomPlug", 24) == 0) || /* Roku */ (strstr(loc, "SamsungMRDesc.xml") != NULL) || /* Samsung TV */ (strstrc(srv, "DigiOn DiXiM", '\r') != NULL)) /* Marantz Receiver */ { /* Check if the client is already in cache */ i = SearchClientCache(sendername.sin_addr, 1); if (i >= 0) { if (clients[i].type < EStandardDLNA150 && clients[i].type != ESamsungSeriesA) { clients[i].age = time(NULL); return; } } ParseUPnPClient(loc); } } else if (memcmp(bufr, "M-SEARCH", 8) == 0) { int st_len = 0, mx_len = 0, mx_val = 0; //DPRINTF(E_DEBUG, L_SSDP, "Received SSDP request:\n%.*s\n", n, bufr); for (i = 0; i < n; i++) { if (bufr[i] == '*') break; } if (strcasestrc(bufr+i, "HTTP/1.1", '\r') == NULL) return; while (i < n) { while ((i < n) && (bufr[i] != '\r' || bufr[i+1] != '\n')) i++; i += 2; if (strncasecmp(bufr+i, "ST:", 3) == 0) { st = bufr+i+3; st_len = 0; while (*st == ' ' || *st == '\t') st++; while (st[st_len]!='\r' && st[st_len]!='\n') st_len++; } else if (strncasecmp(bufr+i, "MX:", 3) == 0) { mx = bufr+i+3; mx_len = 0; while (*mx == ' ' || *mx == '\t') mx++; while (mx[mx_len]!='\r' && mx[mx_len]!='\n') mx_len++; mx_val = strtol(mx, &mx_end, 10); } else if (strncasecmp(bufr+i, "MAN:", 4) == 0) { man = bufr+i+4; man_len = 0; while (*man == ' ' || *man == '\t') man++; while (man[man_len]!='\r' && man[man_len]!='\n') man_len++; } } /*DPRINTF(E_INFO, L_SSDP, "SSDP M-SEARCH packet received from %s:%d\n", inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port) );*/ if (GETFLAG(DLNA_STRICT_MASK) && (ntohs(sendername.sin_port) <= 1024 || ntohs(sendername.sin_port) == 1900)) { DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad source port %d]\n", inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port)); } else if (!man || (strncmp(man, "\"ssdp:discover\"", 15) != 0)) { DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad %s header '%.*s']\n", inet_ntoa(sendername.sin_addr), "MAN", man_len, man); } else if (!mx || mx == mx_end || mx_val < 0) { DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad %s header '%.*s']\n", inet_ntoa(sendername.sin_addr), "MX", mx_len, mx); } else if (st && (st_len > 0)) { int l; int lan_addr_index = 0; /* find in which sub network the client is */ for (i = 0; i < n_lan_addr; i++) { if((sendername.sin_addr.s_addr & lan_addr[i].mask.s_addr) == (lan_addr[i].addr.s_addr & lan_addr[i].mask.s_addr)) { lan_addr_index = i; break; } } if (n_lan_addr == i) { DPRINTF(E_DEBUG, L_SSDP, "Ignoring SSDP M-SEARCH on other interface [%s]\n", inet_ntoa(sendername.sin_addr)); return; } DPRINTF(E_INFO, L_SSDP, "SSDP M-SEARCH from %s:%d ST: %.*s, MX: %.*s, MAN: %.*s\n", inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port), st_len, st, mx_len, mx, man_len, man); /* Responds to request with a device as ST header */ for (i = 0; known_service_types[i]; i++) { l = strlen(known_service_types[i]); if ((l <= st_len) && (memcmp(st, known_service_types[i], l) == 0)) { if (st_len != l) { /* Check version number - must always be 1 currently. */ if ((st[l-1] == ':') && (st[l] == '1')) l++; while (l < st_len) { if (!isspace(st[l])) { DPRINTF(E_DEBUG, L_SSDP, "Ignoring SSDP M-SEARCH with bad extra data [%s]\n", inet_ntoa(sendername.sin_addr)); break; } l++; } if (l != st_len) break; } _usleep(random()>>20); SendSSDPAnnounce2(s, sendername, i, lan_addr[lan_addr_index].str, port); break; } } /* Responds to request with ST: ssdp:all */ /* strlen("ssdp:all") == 8 */ if ((st_len == 8) && (memcmp(st, "ssdp:all", 8) == 0)) { for (i=0; known_service_types[i]; i++) { l = strlen(known_service_types[i]); SendSSDPAnnounce2(s, sendername, i, lan_addr[lan_addr_index].str, port); } } }
void ParseUPnPClient(char *location) { char buf[8192]; struct sockaddr_in dest; int s, n, do_headers = 0, nread = 0; struct timeval tv; char *addr, *path, *port_str; long port = 80; char *off = NULL, *p; int content_len = sizeof(buf); struct NameValueParserData xml; int client; enum client_types type = 0; uint32_t flags = 0; char *model, *serial, *name; if (strncmp(location, "http://", 7) != 0) return; path = location + 7; port_str = strsep(&path, "/"); if (!path) return; addr = strsep(&port_str, ":"); if (port_str) { port = strtol(port_str, NULL, 10); if (!port) port = 80; } memset(&dest, '\0', sizeof(dest)); if (!inet_aton(addr, &dest.sin_addr)) return; /* Check if the client is already in cache */ dest.sin_family = AF_INET; dest.sin_port = htons(port); s = socket(PF_INET, SOCK_STREAM, 0); if( s < 0 ) return; tv.tv_sec = 0; tv.tv_usec = 500000; setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); if( connect(s, (struct sockaddr*)&dest, sizeof(struct sockaddr_in)) < 0 ) goto close; n = snprintf(buf, sizeof(buf), "GET /%s HTTP/1.0\r\n" "HOST: %s:%ld\r\n\r\n", path, addr, port); if( write(s, buf, n) < 1 ) goto close; while( (n = read(s, buf+nread, sizeof(buf)-nread-1)) > 0 ) { nread += n; buf[nread] = '\0'; n = nread; p = buf; while( !off && n-- > 0 ) { if(p[0]=='\r' && p[1]=='\n' && p[2]=='\r' && p[3]=='\n') { off = p + 4; do_headers = 1; } p++; } if( !off ) continue; if( do_headers ) { p = buf; if( strncmp(p, "HTTP/", 5) != 0 ) goto close; while(*p != ' ' && *p != '\t') p++; /* If we don't get a 200 status, ignore it */ if( strtol(p, NULL, 10) != 200 ) goto close; if( (p = strcasestr(p, "Content-Length:")) ) content_len = strtol(p+15, NULL, 10); do_headers = 0; } if( buf + nread - off >= content_len ) break; } close: close(s); if( !off ) return; nread -= off - buf; ParseNameValue(off, nread, &xml); model = GetValueFromNameValueList(&xml, "modelName"); serial = GetValueFromNameValueList(&xml, "serialNumber"); name = GetValueFromNameValueList(&xml, "friendlyName"); if( model ) { DPRINTF(E_DEBUG, L_SSDP, "Model: %s\n", model); if( strstr(model, "Roku SoundBridge") ) { type = ERokuSoundBridge; flags |= FLAG_MS_PFS; flags |= FLAG_AUDIO_ONLY; flags |= FLAG_MIME_WAV_WAV; } else if( strcmp(model, "Samsung DTV DMR") == 0 && serial ) { DPRINTF(E_DEBUG, L_SSDP, "Serial: %s\n", serial); /* The Series B I saw was 20081224DMR. Series A should be older than that. */ if( atoi(serial) > 20081200 ) { type = ESamsungSeriesB; flags |= FLAG_SAMSUNG; flags |= FLAG_DLNA; flags |= FLAG_NO_RESIZE; } } else { if( name && (strcmp(name, "marantz DMP") == 0) ) { type = EMarantzDMP; flags |= FLAG_DLNA; flags |= FLAG_MIME_WAV_WAV; } } } ClearNameValueList(&xml); if( !type ) return; /* Add this client to the cache if it's not there already. */ client = SearchClientCache(dest.sin_addr, 1); if( client < 0 ) { for( client=0; client<CLIENT_CACHE_SLOTS; client++ ) { if( clients[client].addr.s_addr ) continue; get_remote_mac(dest.sin_addr, clients[client].mac); clients[client].addr = dest.sin_addr; DPRINTF(E_DEBUG, L_SSDP, "Added client [%d/%s/%02X:%02X:%02X:%02X:%02X:%02X] to cache slot %d.\n", type, inet_ntoa(clients[client].addr), clients[client].mac[0], clients[client].mac[1], clients[client].mac[2], clients[client].mac[3], clients[client].mac[4], clients[client].mac[5], client); break; } } clients[client].type = type; clients[client].flags = flags; clients[client].age = time(NULL); }