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#include "utils.h" 54 55/* OpenAndConfHTTPSocket() : 56 * setup the socket used to handle incoming HTTP connections. */ 57int 58OpenAndConfTivoBeaconSocket() 59{ 60 int s; 61 int i = 1; 62 struct sockaddr_in beacon; 63 64 if( (s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) 65 { 66 DPRINTF(E_ERROR, L_TIVO, "socket(http): %s\n", strerror(errno)); 67 return -1; 68 } 69 70 if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0) 71 { 72 DPRINTF(E_WARN, L_TIVO, "setsockopt(http, SO_REUSEADDR): %s\n", strerror(errno)); 73 } 74 75 memset(&beacon, 0, sizeof(struct sockaddr_in)); 76 beacon.sin_family = AF_INET; 77 beacon.sin_port = htons(2190); 78 beacon.sin_addr.s_addr = htonl(INADDR_ANY); 79 80 if(bind(s, (struct sockaddr *)&beacon, sizeof(struct sockaddr_in)) < 0) 81 { 82 DPRINTF(E_ERROR, L_TIVO, "bind(http): %s\n", strerror(errno)); 83 close(s); 84 return -1; 85 } 86 i = 1; 87 if(setsockopt(s, SOL_SOCKET, SO_BROADCAST, &i, sizeof(i)) < 0 ) 88 { 89 DPRINTF(E_WARN, L_TIVO, "setsockopt(http, SO_BROADCAST): %s\n", strerror(errno)); 90 close(s); 91 return -1; 92 } 93 94 return s; 95} 96 97/* 98 * Returns the interface broadcast address to be used for beacons 99 */ 100uint32_t 101getBcastAddress(void) 102{ 103 int i, rval; 104 int s; 105 struct sockaddr_in sin; 106 struct sockaddr_in addr; 107 struct ifreq ifr[16]; 108 struct ifconf ifc; 109 int count = 0; 110 uint32_t ret = INADDR_BROADCAST; 111 112 s = socket(PF_INET, SOCK_STREAM, 0); 113 if (!s) 114 return ret; 115 memset(&ifc, '\0', sizeof(ifc)); 116 ifc.ifc_len = sizeof(ifr); 117 ifc.ifc_req = ifr; 118 119 if (ioctl(s, SIOCGIFCONF, &ifc) < 0) 120 { 121 DPRINTF(E_ERROR, L_TIVO, "Error getting interface list [%s]\n", strerror(errno)); 122 close(s); 123 return ret; 124 } 125 126 count = ifc.ifc_len / sizeof(struct ifreq); 127 for (i = 0; i < count; i++) 128 { 129 memcpy(&addr, &ifr[i].ifr_addr, sizeof(addr)); 130 if(strcmp(inet_ntoa(addr.sin_addr), lan_addr[0].str) == 0) 131 { 132 rval = ioctl(s, SIOCGIFBRDADDR, &ifr[i]); 133 if( rval < 0 ) 134 { 135 DPRINTF(E_ERROR, L_TIVO, "Failed to get broadcast addr on %s [%s]\n", ifr[i].ifr_name, strerror(errno)); 136 break; 137 } 138 memcpy(&sin, &ifr[i].ifr_broadaddr, sizeof(sin)); 139 DPRINTF(E_DEBUG, L_TIVO, "Interface: %s broadcast addr %s\n", ifr[i].ifr_name, inet_ntoa(sin.sin_addr)); 140 ret = ntohl((uint32_t)(sin.sin_addr.s_addr)); 141 break; 142 } 143 } 144 close(s); 145 146 return ret; 147} 148 149/* 150 * Send outgoing beacon to the specified address 151 * This will either be a specific or broadcast address 152 */ 153void 154sendBeaconMessage(int fd, struct sockaddr_in * client, int len, int broadcast) 155{ 156 char msg[512]; 157 int msg_len; 158 159 msg_len = snprintf(msg, sizeof(msg), "TiVoConnect=1\n" 160 "swversion=1.0\n" 161 "method=%s\n" 162 "identity=%s\n" 163 "machine=%s\n" 164 "platform=pc/minidlna\n" 165 "services=TiVoMediaServer:%d/http\n", 166 broadcast ? "broadcast" : "connected", 167 uuidvalue, friendly_name, runtime_vars.port); 168 if (msg_len < 0) 169 return; 170 DPRINTF(E_DEBUG, L_TIVO, "Sending TiVo beacon to %s\n", inet_ntoa(client->sin_addr)); 171 sendto(fd, msg, msg_len, 0, (struct sockaddr*)client, len); 172} 173 174/* 175 * Parse and save a received beacon packet from another server, or from 176 * a TiVo. 177 * 178 * Returns true if this was a broadcast beacon msg 179 */ 180static int 181rcvBeaconMessage(char *beacon) 182{ 183 char *tivoConnect = NULL; 184 char *method = NULL; 185 char *identity = NULL; 186 char *machine = NULL; 187 char *platform = NULL; 188 char *services = NULL; 189 char *cp; 190 char *scp; 191 char *tokptr; 192 193 cp = strtok_r(beacon, "=\r\n", &tokptr); 194 while( cp != NULL ) 195 { 196 scp = cp; 197 cp = strtok_r(NULL, "=\r\n", &tokptr); 198 if( strcasecmp(scp, "tivoconnect") == 0 ) 199 tivoConnect = cp; 200 else if( strcasecmp(scp, "method") == 0 ) 201 method = cp; 202 else if( strcasecmp(scp, "identity") == 0 ) 203 identity = cp; 204 else if( strcasecmp(scp, "machine") == 0 ) 205 machine = cp; 206 else if( strcasecmp(scp, "platform") == 0 ) 207 platform = cp; 208 else if( strcasecmp(scp, "services") == 0 ) 209 services = cp; 210 cp = strtok_r(NULL, "=\r\n", &tokptr); 211 } 212 213 if( !tivoConnect || !platform || !method ) 214 return 0; 215 216#ifdef DEBUG 217 static struct aBeacon *topBeacon = NULL; 218 struct aBeacon *b; 219 time_t current; 220 int len; 221 char buf[32]; 222 static time_t lastSummary = 0; 223 224 current = uptime(); 225 for( b = topBeacon; b != NULL; b = b->next ) 226 { 227 if( strcasecmp(machine, b->machine) == 0 || 228 strcasecmp(identity, b->identity) == 0 ) 229 break; 230 } 231 if( b == NULL ) 232 { 233 b = calloc(1, sizeof(*b)); 234 235 if( machine ) 236 b->machine = strdup(machine); 237 if( identity ) 238 b->identity = strdup(identity); 239 240 b->next = topBeacon; 241 topBeacon = b; 242 243 DPRINTF(E_DEBUG, L_TIVO, "Received new beacon: machine(%s) platform(%s) services(%s)\n", 244 machine ? machine : "-", 245 platform ? platform : "-", 246 services ? services : "-" ); 247 } 248 249 b->lastSeen = current; 250 if( !lastSummary ) 251 lastSummary = current; 252 253 if( lastSummary + 1800 < current ) 254 { /* Give a summary of received server beacons every half hour or so */ 255 len = 0; 256 for( b = topBeacon; b != NULL; b = b->next ) 257 { 258 len += strlen(b->machine) + 32; 259 } 260 scp = malloc(len + 128); 261 strcpy( scp, "Known servers: " ); 262 for( b = topBeacon; b != NULL; b = b->next ) 263 { 264 strcat(scp, b->machine); 265 sprintf(buf, "(%ld)", current - b->lastSeen); 266 strcat(scp, buf); 267 if( b->next != NULL ) 268 strcat(scp, ","); 269 } 270 strcat(scp, "\n"); 271 DPRINTF(E_DEBUG, L_TIVO, "%s\n", scp); 272 free(scp); 273 lastSummary = current; 274 } 275#endif 276 /* It's pointless to respond to a non-TiVo beacon. */ 277 if( strncmp(platform, "tcd/", 4) != 0 ) 278 return 0; 279 280 if( strcasecmp(method, "broadcast") == 0 ) 281 { 282 DPRINTF(E_DEBUG, L_TIVO, "Received new beacon: machine(%s/%s) platform(%s) services(%s)\n", 283 machine ? machine : "-", 284 identity ? identity : "-", 285 platform ? platform : "-", 286 services ? services : "-" ); 287 return 1; 288 } 289 return 0; 290} 291 292void ProcessTiVoBeacon(int s) 293{ 294 int n; 295 char *cp; 296 struct sockaddr_in sendername; 297 socklen_t len_r; 298 char bufr[1500]; 299 len_r = sizeof(struct sockaddr_in); 300 301 /* We only expect to see beacon msgs from TiVo's and possibly other tivo servers */ 302 n = recvfrom(s, bufr, sizeof(bufr), 0, 303 (struct sockaddr *)&sendername, &len_r); 304 if( n > 0 ) 305 bufr[n] = '\0'; 306 307 /* find which subnet the client is in */ 308 for(n = 0; n<n_lan_addr; n++) 309 { 310 if( (sendername.sin_addr.s_addr & lan_addr[n].mask.s_addr) 311 == (lan_addr[n].addr.s_addr & lan_addr[n].mask.s_addr)) 312 break; 313 } 314 if( n == n_lan_addr ) 315 { 316 DPRINTF(E_DEBUG, L_TIVO, "Ignoring TiVo beacon on other interface [%s]\n", 317 inet_ntoa(sendername.sin_addr)); 318 return; 319 } 320 321 for( cp = bufr; *cp; cp++ ) 322 /* do nothing */; 323 if( cp[-1] == '\r' || cp[-1] == '\n' ) 324 *--cp = '\0'; 325 if( cp[-1] == '\r' || cp[-1] == '\n' ) 326 *--cp = '\0'; 327 328 if( rcvBeaconMessage(bufr) ) 329 sendBeaconMessage(s, &sendername, len_r, 0); 330} 331#endif // TIVO_SUPPORT 332