1/* 2 * Linux/C based server for TiVo Home Media Option protocol 3 * 4 * Based on version 1.5.1 of 5 * "TiVo Connect Automatic Machine; Discovery Protocol Specification" 6 * Based on version 1.1.0 of 7 * "TiVo Home Media Option; Music and Photos Server Protocol Specification" 8 * 9 * Dave Clemans, April 2003 10 * 11 * byRequest TiVo HMO Server 12 * Copyright (C) 2003 Dave Clemans 13 * 14 * This file is based on byRequest, and is part of MiniDLNA. 15 * 16 * byRequest is free software; you can redistribute it and/or modify 17 * it under the terms of the GNU General Public License as published by 18 * the Free Software Foundation; either version 2 of the License, or 19 * (at your option) any later version. 20 * 21 * byRequest is distributed in the hope that it will be useful, 22 * but WITHOUT ANY WARRANTY; without even the implied warranty of 23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 * GNU General Public License for more details. 25 * 26 * You should have received a copy of the GNU General Public License 27 * along with byRequest. If not, see <http://www.gnu.org/licenses/>. 28 */ 29#include "config.h" 30#ifdef TIVO_SUPPORT 31#include <stdlib.h> 32#include <stdio.h> 33#include <unistd.h> 34#include <string.h> 35#include <sys/wait.h> 36#include <sys/ioctl.h> 37#include <sys/stat.h> 38#include <fcntl.h> 39#include <errno.h> 40#include <time.h> 41 42#include <sys/param.h> 43#include <sys/socket.h> 44#include <netinet/in.h> 45#include <arpa/inet.h> 46#include <net/if.h> 47#include <sys/poll.h> 48#include <netdb.h> 49 50#include "tivo_beacon.h" 51#include "upnpglobalvars.h" 52#include "log.h" 53 54/* OpenAndConfHTTPSocket() : 55 * setup the socket used to handle incoming HTTP connections. */ 56int 57OpenAndConfTivoBeaconSocket() 58{ 59 int s; 60 int i = 1; 61 struct sockaddr_in beacon; 62 63 if( (s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) 64 { 65 DPRINTF(E_ERROR, L_TIVO, "socket(http): %s\n", strerror(errno)); 66 return -1; 67 } 68 69 if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0) 70 { 71 DPRINTF(E_WARN, L_TIVO, "setsockopt(http, SO_REUSEADDR): %s\n", strerror(errno)); 72 } 73 74 memset(&beacon, 0, sizeof(struct sockaddr_in)); 75 beacon.sin_family = AF_INET; 76 beacon.sin_port = htons(2190); 77 beacon.sin_addr.s_addr = htonl(INADDR_ANY); 78 79 if(bind(s, (struct sockaddr *)&beacon, sizeof(struct sockaddr_in)) < 0) 80 { 81 DPRINTF(E_ERROR, L_TIVO, "bind(http): %s\n", strerror(errno)); 82 close(s); 83 return -1; 84 } 85 i = 1; 86 if(setsockopt(s, SOL_SOCKET, SO_BROADCAST, &i, sizeof(i)) < 0 ) 87 { 88 DPRINTF(E_WARN, L_TIVO, "setsockopt(http, SO_BROADCAST): %s\n", strerror(errno)); 89 close(s); 90 return -1; 91 } 92 93 return s; 94} 95 96/* 97 * Returns the interface broadcast address to be used for beacons 98 */ 99uint32_t 100getBcastAddress(void) 101{ 102 int i, rval; 103 int s; 104 struct sockaddr_in sin; 105 struct sockaddr_in addr; 106 struct ifreq ifr[16]; 107 struct ifconf ifc; 108 int count = 0; 109 uint32_t ret = INADDR_BROADCAST; 110 111 s = socket(PF_INET, SOCK_STREAM, 0); 112 memset(&ifc, '\0', sizeof(ifc)); 113 ifc.ifc_len = sizeof(ifr); 114 ifc.ifc_req = ifr; 115 116 if(ioctl(s, SIOCGIFCONF, &ifc) < 0) { 117 DPRINTF(E_ERROR, L_TIVO, "Error getting interface list [%s]\n", strerror(errno)); 118 close(s); 119 return ret; 120 } 121 122 count = ifc.ifc_len / sizeof(struct ifreq); 123 for(i = 0; i < count; i++) 124 { 125 memcpy(&addr, &ifr[i].ifr_addr, sizeof(addr)); 126 if(strcmp(inet_ntoa(addr.sin_addr), lan_addr[0].str) == 0) 127 { 128 rval = ioctl(s, SIOCGIFBRDADDR, &ifr[i]); 129 if( rval < 0 ) 130 { 131 DPRINTF(E_ERROR, L_TIVO, "Failed to get broadcast addr on %s [%s]\n", ifr[i].ifr_name, strerror(errno)); 132 break; 133 } 134 memcpy(&sin, &ifr[i].ifr_broadaddr, sizeof(sin)); 135 DPRINTF(E_DEBUG, L_TIVO, "Interface: %s broadcast addr %s\n", ifr[i].ifr_name, inet_ntoa(sin.sin_addr)); 136 ret = ntohl((uint32_t)(sin.sin_addr.s_addr)); 137 break; 138 } 139 } 140 close(s); 141 142 return ret; 143} 144 145/* 146 * Send outgoing beacon to the specified address 147 * This will either be a specific or broadcast address 148 */ 149void 150sendBeaconMessage(int fd, struct sockaddr_in * client, int len, int broadcast) 151{ 152 char * mesg; 153 int mesg_len; 154 155 mesg_len = asprintf(&mesg, "TiVoConnect=1\n" 156 "swversion=1.0\n" 157 "method=%s\n" 158 "identity=%s\n" 159 "machine=%s\n" 160 "platform=pc/minidlna\n" 161 "services=TiVoMediaServer:%d/http\n", 162 broadcast ? "broadcast" : "connected", 163 uuidvalue, friendly_name, runtime_vars.port); 164 DPRINTF(E_DEBUG, L_TIVO, "Sending TiVo beacon to %s\n", inet_ntoa(client->sin_addr)); 165 sendto(fd, mesg, mesg_len, 0, (struct sockaddr*)client, len); 166 free(mesg); 167} 168 169/* 170 * Parse and save a received beacon packet from another server, or from 171 * a TiVo. 172 * 173 * Returns true if this was a broadcast beacon msg 174 */ 175int 176rcvBeaconMessage(char * beacon) 177{ 178 char * tivoConnect = NULL; 179 char * method = NULL; 180 char * identity = NULL; 181 char * machine = NULL; 182 char * platform = NULL; 183 char * services = NULL; 184 char * cp; 185 char * scp; 186 char * tokptr; 187 188 cp = strtok_r(beacon, "=\r\n", &tokptr); 189 while( cp != NULL ) 190 { 191 scp = cp; 192 cp = strtok_r( NULL, "=\r\n", &tokptr ); 193 if( strcasecmp(scp, "tivoconnect") == 0 ) 194 tivoConnect = cp; 195 else if( strcasecmp(scp, "method") == 0 ) 196 method = cp; 197 else if( strcasecmp(scp, "identity") == 0 ) 198 identity = cp; 199 else if( strcasecmp(scp, "machine") == 0 ) 200 machine = cp; 201 else if( strcasecmp(scp, "platform") == 0 ) 202 platform = cp; 203 else if( strcasecmp(scp, "services") == 0 ) 204 services = cp; 205 cp = strtok_r(NULL, "=\r\n", &tokptr); 206 } 207 208 if( tivoConnect == NULL ) 209 return 0; 210 211#ifdef DEBUG 212 static struct aBeacon* topBeacon = NULL; 213 struct aBeacon * b; 214 time_t current; 215 int len; 216 char buf[32]; 217 static time_t lastSummary = 0; 218 219 current = time(NULL); 220 for( b = topBeacon; b != NULL; b = b->next ) 221 { 222 if( strcasecmp(machine, b->machine) == 0 || 223 strcasecmp(identity, b->identity) == 0 ) 224 break; 225 } 226 if( b == NULL ) 227 { 228 b = calloc(1, sizeof(*b)); 229 230 if( machine ) 231 b->machine = strdup(machine); 232 if( identity ) 233 b->identity = strdup(identity); 234 235 b->next = topBeacon; 236 topBeacon = b; 237 238 DPRINTF(E_DEBUG, L_TIVO, "Received new beacon: machine(%s) platform(%s) services(%s)\n", 239 machine ? machine : "-", 240 platform ? platform : "-", 241 services ? services : "-" ); 242 } 243 244 b->lastSeen = current; 245 if( !lastSummary ) 246 lastSummary = current; 247 248 if( lastSummary + 1800 < current ) 249 { /* Give a summary of received server beacons every half hour or so */ 250 len = 0; 251 for( b = topBeacon; b != NULL; b = b->next ) 252 { 253 len += strlen(b->machine) + 32; 254 } 255 scp = malloc(len + 128); 256 strcpy( scp, "Known servers: " ); 257 for( b = topBeacon; b != NULL; b = b->next ) 258 { 259 strcat(scp, b->machine); 260 sprintf(buf, "(%ld)", current - b->lastSeen); 261 strcat(scp, buf); 262 if( b->next != NULL ) 263 strcat(scp, ","); 264 } 265 strcat(scp, "\n"); 266 DPRINTF(E_DEBUG, L_TIVO, "%s\n", scp); 267 free(scp); 268 lastSummary = current; 269 } 270#endif 271 /* It's pointless to respond to a non-TiVo beacon. */ 272 if( strncmp(platform, "tcd/", 4) != 0 ) 273 return 0; 274 275 if( strcasecmp(method, "broadcast") == 0 ) 276 { 277 DPRINTF(E_DEBUG, L_TIVO, "Received new beacon: machine(%s) platform(%s) services(%s)\n", 278 machine ? machine : "-", 279 platform ? platform : "-", 280 services ? services : "-" ); 281 return 1; 282 } 283 return 0; 284} 285 286void ProcessTiVoBeacon(int s) 287{ 288 int n; 289 char *cp; 290 struct sockaddr_in sendername; 291 socklen_t len_r; 292 char bufr[1500]; 293 len_r = sizeof(struct sockaddr_in); 294 295 /* We only expect to see beacon msgs from TiVo's and possibly other tivo servers */ 296 n = recvfrom(s, bufr, sizeof(bufr), 0, 297 (struct sockaddr *)&sendername, &len_r); 298 if( n > 0 ) 299 bufr[n] = '\0'; 300 301 /* find which subnet the client is in */ 302 for(n = 0; n<n_lan_addr; n++) 303 { 304 if( (sendername.sin_addr.s_addr & lan_addr[n].mask.s_addr) 305 == (lan_addr[n].addr.s_addr & lan_addr[n].mask.s_addr)) 306 break; 307 } 308 if( n == n_lan_addr ) 309 { 310 DPRINTF(E_DEBUG, L_TIVO, "Ignoring TiVo beacon on other interface [%s]\n", 311 inet_ntoa(sendername.sin_addr)); 312 return; 313 } 314 315 for( cp = bufr; *cp; cp++ ) 316 /* do nothing */; 317 if( cp[-1] == '\r' || cp[-1] == '\n' ) 318 *--cp = '\0'; 319 if( cp[-1] == '\r' || cp[-1] == '\n' ) 320 *--cp = '\0'; 321 322 if( rcvBeaconMessage(bufr) ) 323 sendBeaconMessage(s, &sendername, len_r, 0); 324} 325#endif // TIVO_SUPPORT 326