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