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