333 lines
8.4 KiB
C
333 lines
8.4 KiB
C
/*
|
|
* Linux/C based server for TiVo Home Media Option protocol
|
|
*
|
|
* Based on version 1.5.1 of
|
|
* "TiVo Connect Automatic Machine; Discovery Protocol Specification"
|
|
* Based on version 1.1.0 of
|
|
* "TiVo Home Media Option; Music and Photos Server Protocol Specification"
|
|
*
|
|
* Dave Clemans, April 2003
|
|
*
|
|
* byRequest TiVo HMO Server
|
|
* Copyright (C) 2003 Dave Clemans
|
|
*
|
|
* This file is based on byRequest, and is part of MiniDLNA.
|
|
*
|
|
* byRequest is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* byRequest is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with byRequest. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "config.h"
|
|
#ifdef TIVO_SUPPORT
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <time.h>
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <net/if.h>
|
|
#include <sys/poll.h>
|
|
#include <netdb.h>
|
|
|
|
#include "event.h"
|
|
#include "tivo_beacon.h"
|
|
#include "upnpglobalvars.h"
|
|
#include "log.h"
|
|
|
|
/* OpenAndConfHTTPSocket() :
|
|
* setup the socket used to handle incoming HTTP connections. */
|
|
int
|
|
OpenAndConfTivoBeaconSocket()
|
|
{
|
|
int s;
|
|
int i = 1;
|
|
struct sockaddr_in beacon;
|
|
|
|
if( (s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
|
|
{
|
|
DPRINTF(E_ERROR, L_TIVO, "socket(http): %s\n", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0)
|
|
{
|
|
DPRINTF(E_WARN, L_TIVO, "setsockopt(http, SO_REUSEADDR): %s\n", strerror(errno));
|
|
}
|
|
|
|
memset(&beacon, 0, sizeof(struct sockaddr_in));
|
|
beacon.sin_family = AF_INET;
|
|
beacon.sin_port = htons(2190);
|
|
beacon.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
|
|
if(bind(s, (struct sockaddr *)&beacon, sizeof(struct sockaddr_in)) < 0)
|
|
{
|
|
DPRINTF(E_ERROR, L_TIVO, "bind(http): %s\n", strerror(errno));
|
|
close(s);
|
|
return -1;
|
|
}
|
|
i = 1;
|
|
if(setsockopt(s, SOL_SOCKET, SO_BROADCAST, &i, sizeof(i)) < 0 )
|
|
{
|
|
DPRINTF(E_WARN, L_TIVO, "setsockopt(http, SO_BROADCAST): %s\n", strerror(errno));
|
|
close(s);
|
|
return -1;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
/*
|
|
* Returns the interface broadcast address to be used for beacons
|
|
*/
|
|
uint32_t
|
|
getBcastAddress(void)
|
|
{
|
|
int i, rval;
|
|
int s;
|
|
struct sockaddr_in sin;
|
|
struct sockaddr_in addr;
|
|
struct ifreq ifr[16];
|
|
struct ifconf ifc;
|
|
int count = 0;
|
|
uint32_t ret = INADDR_BROADCAST;
|
|
|
|
s = socket(PF_INET, SOCK_STREAM, 0);
|
|
if (!s)
|
|
return ret;
|
|
memset(&ifc, '\0', sizeof(ifc));
|
|
ifc.ifc_len = sizeof(ifr);
|
|
ifc.ifc_req = ifr;
|
|
|
|
if (ioctl(s, SIOCGIFCONF, &ifc) < 0)
|
|
{
|
|
DPRINTF(E_ERROR, L_TIVO, "Error getting interface list [%s]\n", strerror(errno));
|
|
close(s);
|
|
return ret;
|
|
}
|
|
|
|
count = ifc.ifc_len / sizeof(struct ifreq);
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
memcpy(&addr, &ifr[i].ifr_addr, sizeof(addr));
|
|
if(strcmp(inet_ntoa(addr.sin_addr), lan_addr[0].str) == 0)
|
|
{
|
|
rval = ioctl(s, SIOCGIFBRDADDR, &ifr[i]);
|
|
if( rval < 0 )
|
|
{
|
|
DPRINTF(E_ERROR, L_TIVO, "Failed to get broadcast addr on %s [%s]\n", ifr[i].ifr_name, strerror(errno));
|
|
break;
|
|
}
|
|
memcpy(&sin, &ifr[i].ifr_broadaddr, sizeof(sin));
|
|
DPRINTF(E_DEBUG, L_TIVO, "Interface: %s broadcast addr %s\n", ifr[i].ifr_name, inet_ntoa(sin.sin_addr));
|
|
ret = ntohl((uint32_t)(sin.sin_addr.s_addr));
|
|
break;
|
|
}
|
|
}
|
|
close(s);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Send outgoing beacon to the specified address
|
|
* This will either be a specific or broadcast address
|
|
*/
|
|
void
|
|
sendBeaconMessage(int fd, struct sockaddr_in * client, int len, int broadcast)
|
|
{
|
|
char msg[512];
|
|
int msg_len;
|
|
|
|
msg_len = snprintf(msg, sizeof(msg), "TiVoConnect=1\n"
|
|
"swversion=1.0\n"
|
|
"method=%s\n"
|
|
"identity=%s\n"
|
|
"machine=%s\n"
|
|
"platform=pc/minidlna\n"
|
|
"services=TiVoMediaServer:%d/http\n",
|
|
broadcast ? "broadcast" : "connected",
|
|
uuidvalue, friendly_name, runtime_vars.port);
|
|
if (msg_len < 0)
|
|
return;
|
|
DPRINTF(E_DEBUG, L_TIVO, "Sending TiVo beacon to %s\n", inet_ntoa(client->sin_addr));
|
|
sendto(fd, msg, msg_len, 0, (struct sockaddr*)client, len);
|
|
}
|
|
|
|
/*
|
|
* Parse and save a received beacon packet from another server, or from
|
|
* a TiVo.
|
|
*
|
|
* Returns true if this was a broadcast beacon msg
|
|
*/
|
|
static int
|
|
rcvBeaconMessage(char *beacon)
|
|
{
|
|
char *tivoConnect = NULL;
|
|
char *method = NULL;
|
|
char *identity = NULL;
|
|
char *machine = NULL;
|
|
char *platform = NULL;
|
|
char *services = NULL;
|
|
char *cp;
|
|
char *scp;
|
|
char *tokptr;
|
|
|
|
cp = strtok_r(beacon, "=\r\n", &tokptr);
|
|
while( cp != NULL )
|
|
{
|
|
scp = cp;
|
|
cp = strtok_r(NULL, "=\r\n", &tokptr);
|
|
if( strcasecmp(scp, "tivoconnect") == 0 )
|
|
tivoConnect = cp;
|
|
else if( strcasecmp(scp, "method") == 0 )
|
|
method = cp;
|
|
else if( strcasecmp(scp, "identity") == 0 )
|
|
identity = cp;
|
|
else if( strcasecmp(scp, "machine") == 0 )
|
|
machine = cp;
|
|
else if( strcasecmp(scp, "platform") == 0 )
|
|
platform = cp;
|
|
else if( strcasecmp(scp, "services") == 0 )
|
|
services = cp;
|
|
cp = strtok_r(NULL, "=\r\n", &tokptr);
|
|
}
|
|
|
|
if( !tivoConnect || !platform || !method )
|
|
return 0;
|
|
|
|
#ifdef DEBUG
|
|
static struct aBeacon *topBeacon = NULL;
|
|
struct aBeacon *b;
|
|
time_t current;
|
|
int len;
|
|
char buf[32];
|
|
static time_t lastSummary = 0;
|
|
|
|
current = time(NULL);
|
|
for( b = topBeacon; b != NULL; b = b->next )
|
|
{
|
|
if( strcasecmp(machine, b->machine) == 0 ||
|
|
strcasecmp(identity, b->identity) == 0 )
|
|
break;
|
|
}
|
|
if( b == NULL )
|
|
{
|
|
b = calloc(1, sizeof(*b));
|
|
|
|
if( machine )
|
|
b->machine = strdup(machine);
|
|
if( identity )
|
|
b->identity = strdup(identity);
|
|
|
|
b->next = topBeacon;
|
|
topBeacon = b;
|
|
|
|
DPRINTF(E_DEBUG, L_TIVO, "Received new beacon: machine(%s) platform(%s) services(%s)\n",
|
|
machine ? machine : "-",
|
|
platform ? platform : "-",
|
|
services ? services : "-" );
|
|
}
|
|
|
|
b->lastSeen = current;
|
|
if( !lastSummary )
|
|
lastSummary = current;
|
|
|
|
if( lastSummary + 1800 < current )
|
|
{ /* Give a summary of received server beacons every half hour or so */
|
|
len = 0;
|
|
for( b = topBeacon; b != NULL; b = b->next )
|
|
{
|
|
len += strlen(b->machine) + 32;
|
|
}
|
|
scp = malloc(len + 128);
|
|
strcpy( scp, "Known servers: " );
|
|
for( b = topBeacon; b != NULL; b = b->next )
|
|
{
|
|
strcat(scp, b->machine);
|
|
sprintf(buf, "(%ld)", current - b->lastSeen);
|
|
strcat(scp, buf);
|
|
if( b->next != NULL )
|
|
strcat(scp, ",");
|
|
}
|
|
strcat(scp, "\n");
|
|
DPRINTF(E_DEBUG, L_TIVO, "%s\n", scp);
|
|
free(scp);
|
|
lastSummary = current;
|
|
}
|
|
#endif
|
|
/* It's pointless to respond to a non-TiVo beacon. */
|
|
if( strncmp(platform, "tcd/", 4) != 0 )
|
|
return 0;
|
|
|
|
if( strcasecmp(method, "broadcast") == 0 )
|
|
{
|
|
DPRINTF(E_DEBUG, L_TIVO, "Received new beacon: machine(%s/%s) platform(%s) services(%s)\n",
|
|
machine ? machine : "-",
|
|
identity ? identity : "-",
|
|
platform ? platform : "-",
|
|
services ? services : "-" );
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void ProcessTiVoBeacon(struct event *ev)
|
|
{
|
|
int s, n;
|
|
char *cp;
|
|
struct sockaddr_in sendername;
|
|
socklen_t len_r;
|
|
char bufr[1500];
|
|
len_r = sizeof(struct sockaddr_in);
|
|
|
|
s = ev->fd;
|
|
/* We only expect to see beacon msgs from TiVo's and possibly other tivo servers */
|
|
n = recvfrom(s, bufr, sizeof(bufr), 0,
|
|
(struct sockaddr *)&sendername, &len_r);
|
|
if( n > 0 )
|
|
bufr[n] = '\0';
|
|
|
|
/* find which subnet the client is in */
|
|
for(n = 0; n<n_lan_addr; n++)
|
|
{
|
|
if( (sendername.sin_addr.s_addr & lan_addr[n].mask.s_addr)
|
|
== (lan_addr[n].addr.s_addr & lan_addr[n].mask.s_addr))
|
|
break;
|
|
}
|
|
if( n == n_lan_addr )
|
|
{
|
|
DPRINTF(E_DEBUG, L_TIVO, "Ignoring TiVo beacon on other interface [%s]\n",
|
|
inet_ntoa(sendername.sin_addr));
|
|
return;
|
|
}
|
|
|
|
for( cp = bufr; *cp; cp++ )
|
|
/* do nothing */;
|
|
if( cp[-1] == '\r' || cp[-1] == '\n' )
|
|
*--cp = '\0';
|
|
if( cp[-1] == '\r' || cp[-1] == '\n' )
|
|
*--cp = '\0';
|
|
|
|
if( rcvBeaconMessage(bufr) )
|
|
sendBeaconMessage(s, &sendername, len_r, 0);
|
|
}
|
|
#endif // TIVO_SUPPORT
|