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 * Copyright (C) 2003  Dave Clemans
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 *
27 * See the file "COPYING" for more details.
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
54static struct aBeacon* topBeacon = NULL;
55
56/* OpenAndConfHTTPSocket() :
57 * setup the socket used to handle incoming HTTP connections. */
58int
59OpenAndConfTivoBeaconSocket()
60{
61	int s;
62	int i = 1;
63	struct sockaddr_in beacon;
64
65	if( (s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
66	{
67		DPRINTF(E_ERROR, L_TIVO, "socket(http): %s\n", strerror(errno));
68		return -1;
69	}
70
71	if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0)
72	{
73		DPRINTF(E_WARN, L_TIVO, "setsockopt(http, SO_REUSEADDR): %s\n", strerror(errno));
74	}
75
76	memset(&beacon, 0, sizeof(struct sockaddr_in));
77	beacon.sin_family = AF_INET;
78	beacon.sin_port = htons(2190);
79	beacon.sin_addr.s_addr = htonl(INADDR_ANY);
80
81	if(bind(s, (struct sockaddr *)&beacon, sizeof(struct sockaddr_in)) < 0)
82	{
83		DPRINTF(E_ERROR, L_TIVO, "bind(http): %s\n", strerror(errno));
84		close(s);
85		return -1;
86	}
87	i = 1;
88	if(setsockopt(s, SOL_SOCKET, SO_BROADCAST, &i, sizeof(i)) < 0 )
89	{
90		DPRINTF(E_WARN, L_TIVO, "setsockopt(http, SO_BROADCAST): %s\n", strerror(errno));
91		close(s);
92		return -1;
93	}
94
95	return s;
96}
97
98/*
99 * Returns the interface broadcast address to be used for beacons
100 */
101uint32_t
102getBcastAddress(void)
103{
104	int i, rval;
105	int s = socket(PF_INET, SOCK_STREAM, 0);
106	struct sockaddr_in sin;
107	struct sockaddr_in addr;
108	struct ifreq ifr;
109
110	for (i=1; i > 0; i++)
111	{
112		ifr.ifr_ifindex = i;
113		if( ioctl(s, SIOCGIFNAME, &ifr) < 0 )
114			break;
115		if(ioctl(s, SIOCGIFADDR, &ifr, sizeof(struct ifreq)) < 0)
116			continue;
117		memcpy(&addr, &ifr.ifr_addr, sizeof(addr));
118		if(strcmp(inet_ntoa(addr.sin_addr), lan_addr[0].str) == 0)
119		{
120			rval = ioctl(s, SIOCGIFBRDADDR, &ifr);
121			if( rval < 0 )
122			{
123				close(s);
124				return INADDR_BROADCAST;
125			}
126			memcpy(&sin, &ifr.ifr_broadaddr, sizeof(sin));
127			close(s);
128			DPRINTF(E_DEBUG, L_TIVO, "Interface: %s broadcast addr %s \n", ifr.ifr_name, inet_ntoa(sin.sin_addr));
129			return ntohl((uint32_t)(sin.sin_addr.s_addr));
130		}
131	}
132
133	return INADDR_BROADCAST;
134}
135
136/*
137 * Send outgoing beacon to the specified address
138 * This will either be a specific or broadcast address
139 */
140void
141sendBeaconMessage(int fd, struct sockaddr_in * client, int len, int broadcast)
142{
143	char * mesg;
144	int mesg_len;
145
146	mesg_len = asprintf(&mesg, "TiVoConnect=1\n"
147	                           "swversion=%s\n"
148	                           "method=%s\n"
149	                           "identity=%s\n"
150	                           "machine=%s\n"
151	                           "platform=pc/minidlna\n"
152	                           "services=TiVoMediaServer:%d/http\n",
153	                           "1.0",
154	                           broadcast ? "broadcast" : "connected",
155	                           uuidvalue, friendly_name, runtime_vars.port);
156	DPRINTF(E_DEBUG, L_TIVO, "Sending TiVo beacon\n");
157	sendto(fd, mesg, mesg_len, 0, (struct sockaddr*)client, len);
158	free(mesg);
159}
160
161/*
162 * Parse and save a received beacon packet from another server, or from
163 * a TiVo.
164 *
165 * Returns true if this was a broadcast beacon msg
166 */
167int
168rcvBeaconMessage(char * beacon)
169{
170	char * tivoConnect = NULL;
171	char * swVersion = NULL;
172	char * method = NULL;
173	char * identity = NULL;
174	char * machine = NULL;
175	char * platform = NULL;
176	char * services = NULL;
177	char * cp;
178	char * scp;
179	char * tokptr;
180	struct aBeacon * b;
181	int len;
182	time_t current;
183	char buf[32];
184	static time_t lastSummary = 0;
185
186	cp = strtok_r( beacon, "=\r\n", &tokptr );
187	while( cp != NULL )
188	{
189		scp = cp;
190		cp = strtok_r( NULL, "=\r\n", &tokptr );
191		if( strcasecmp( scp, "tivoconnect" ) == 0 )
192			tivoConnect = cp;
193		else if( strcasecmp( scp, "swversion" ) == 0 )
194			swVersion = 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	current = time( NULL );
212	for( b = topBeacon; b != NULL; b = b->next )
213	{
214		if( strcasecmp( machine, b->machine ) == 0 ||
215		    strcasecmp( identity, b->identity ) == 0 )
216		{
217			break;
218		}
219	}
220	if( b == NULL )
221	{
222		b = ( struct aBeacon* ) calloc( 1, sizeof( *b ) );
223		b->next = NULL;
224
225		if ( machine )
226		{
227			b->machine = ( char* ) malloc( strlen( machine ) + 1 );
228			strcpy( b->machine, machine );
229		}
230		if ( identity )
231		{
232			b->identity = ( char* ) malloc( strlen( identity ) + 1 );
233			strcpy( b->identity, identity );
234		}
235		if ( swVersion )
236		{
237			b->swversion = ( char* ) malloc( strlen( swVersion ) + 1 );
238			strcpy( b->swversion, swVersion );
239		}
240		if ( method )
241		{
242			b->method = ( char* ) malloc( strlen( method ) + 1 );
243			strcpy( b->method, method );
244		}
245		if ( platform )
246		{
247			b->platform = ( char* ) malloc( strlen( platform ) + 1 );
248			strcpy( b->platform, platform );
249		}
250		if ( services )
251		{
252			b->services = ( char* ) malloc( strlen( services ) + 1 );
253			strcpy( b->services, services );
254		}
255
256		b->next = topBeacon;
257		topBeacon = b;
258
259		printf( "Received new beacon: machine(%s) platform(%s) services(%s)\n",
260		         b->machine ? b->machine : "-",
261		         b->platform ? b->platform : "-",
262		         b->services ? b->services : "-" );
263	}
264	b->lastSeen = current;
265
266	if( lastSummary == 0 )
267		lastSummary = current;
268	if( lastSummary + 1800 < current )
269	{  /* Give a summary of received server beacons every half hour or so */
270		len = 0;
271		for( b = topBeacon; b != NULL; b = b->next )
272		{
273			len += strlen( b->machine ) + 32;
274		}
275		scp = ( char* ) malloc( len + 128 );
276		strcpy( scp, "Known servers: " );
277		for( b = topBeacon; b != NULL; b = b->next )
278		{
279			strcat( scp, b->machine );
280			sprintf( buf, "(%ld)", current - b->lastSeen );
281			strcat( scp, buf );
282			if( b->next != NULL )
283				strcat( scp, "," );
284		}
285		strcat(scp, "\n");
286		printf("%s\n", scp);
287		free(scp);
288		lastSummary = current;
289	}
290
291	if( strcasecmp( method, "broadcast" ) == 0 )
292		return 1;
293	return 0;
294}
295#endif // TIVO_SUPPORT
296