1 /* $OpenBSD: cddb.c,v 1.15 2008/04/27 23:06:40 fgsch Exp $ */
2 /*
3  * Copyright (c) 2002 Marc Espie.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE OPENBSD PROJECT AND CONTRIBUTORS
15  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17  * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE OPENBSD
18  * PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 #include <sys/param.h>
28 #include <sys/socket.h>
29 #include <netinet/in.h>
30 #include <sys/cdio.h>
31 #include <err.h>
32 #include <netdb.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37 #include <vis.h>
38 #include "extern.h"
39 
40 __RCSID("$MirOS: src/usr.bin/cdio/cddb.c,v 1.2 2010/03/06 19:57:40 tg Exp $");
41 
42 unsigned long	cddb_sum(unsigned long);
43 void		send_hello(FILE *);
44 void		send_query(FILE *, int, struct cd_toc_entry *);
45 int		further_query(FILE *, char *);
46 int		connect_to(const char *, const char *);
47 int		parse_connect_to(const char *, const char *);
48 char *		get_line(FILE *);
49 char *		get_answer(FILE *);
50 void		verify_track_names(char **, int, struct cd_toc_entry *);
51 void		safe_copy(char **, const char *);
52 
53 unsigned long
cddb_sum(unsigned long v)54 cddb_sum(unsigned long v)
55 {
56 	unsigned long sum = 0;
57 
58 	while (v > 0) {
59 		sum += v % 10;
60 		v /= 10;
61 	}
62 	return (sum);
63 }
64 
65 unsigned long
cddb_discid(int n,struct cd_toc_entry * e)66 cddb_discid(int n, struct cd_toc_entry *e)
67 {
68 	unsigned long sum;
69 	int i;
70 
71 	sum = 0;
72 	for (i =0; i < n; i++)
73 		sum += cddb_sum(entry2time(e+i));
74 	return (((sum % 0xff) << 24) |
75 	    ((entry2time(e+n) - entry2time(e)) << 8) | n);
76 }
77 
78 void
send_hello(FILE * cout)79 send_hello(FILE *cout)
80 {
81 	char hostname[MAXHOSTNAMELEN];
82 
83 	if (gethostname(hostname, sizeof(hostname)) == -1)
84 		strlcpy(hostname, "unknown", sizeof hostname);
85 	fprintf(cout, "CDDB HELLO %s %s cdio " VERSION "\r\n",
86 	    getlogin(), hostname);
87 	fflush(cout);
88 }
89 
90 void
send_query(FILE * f,int n,struct cd_toc_entry * e)91 send_query(FILE *f, int n, struct cd_toc_entry *e)
92 {
93 	int i;
94 
95 	fprintf(f, "cddb query %8lx %d", cddb_discid(n, e), n);
96 	for (i = 0; i < n; i++)
97 		fprintf(f, " %lu", entry2frames(e+i));
98 	fprintf(f, " %lu\r\n", (entry2frames(e+n)-entry2frames(e)) /75);
99 }
100 
101 #define MAXSIZE 256
102 char copy_buffer[MAXSIZE];
103 
104 void
safe_copy(char ** p,const char * title)105 safe_copy(char **p, const char *title)
106 {
107 	strnvis(copy_buffer, title, MAXSIZE-1, VIS_TAB|VIS_NL);
108 	if (*p == NULL)
109 		*p = strdup(copy_buffer);
110 	else {
111 		char *n;
112 
113 		if (asprintf(&n, "%s%s", *p, copy_buffer) == -1)
114 			return;
115 		free(*p);
116 		*p = n;
117 	}
118 }
119 
120 int
further_query(FILE * cout,char * line)121 further_query(FILE *cout, char *line)
122 {
123 	char *key;
124 	char *title;
125 
126 	key = strchr(line, ' ');
127 	if (!key)
128 		return 0;
129 	*key++ = 0;
130 	title = strchr(key, ' ');
131 	if (!title)
132 		return 0;
133 	*title++ = 0;
134 	strnvis(copy_buffer, title, MAXSIZE-1, VIS_TAB|VIS_NL);
135 	printf("%s", copy_buffer);
136 	strnvis(copy_buffer, line, MAXSIZE-1, VIS_TAB|VIS_NL);
137 	printf("(%s)\n", copy_buffer);
138 	fprintf(cout, "CDDB READ %s %s\r\n", line, key);
139 	fflush(cout);
140 	return 1;
141 }
142 
143 
144 int
connect_to(const char * host,const char * serv)145 connect_to(const char *host, const char *serv)
146 {
147 	int s = -1;
148 	struct addrinfo hints, *res0 = NULL, *res;
149 	int error;
150 
151 	memset(&hints, 0, sizeof hints);
152 	hints.ai_family = PF_UNSPEC;
153 	hints.ai_socktype = SOCK_STREAM;
154 
155 	error = getaddrinfo(host, serv, &hints, &res0);
156 	if (error) {
157 		warnx("%s", gai_strerror(error));
158 		return -1;
159 	}
160 
161 	for (res = res0; res; res = res->ai_next) {
162 		s = socket(res->ai_family, res->ai_socktype,
163 		    res->ai_protocol);
164 		if (s == -1)
165 			continue;
166 		if (connect(s, res->ai_addr, res->ai_addrlen) == -1) {
167 			close(s);
168 			s = -1;
169 			continue;
170 		}
171 		break;
172 	}
173 	if (s == -1)
174 		warn("cddb");
175 	freeaddrinfo(res0);
176 	return s;
177 }
178 
179 int
parse_connect_to(const char * host_port,const char * port)180 parse_connect_to(const char *host_port, const char *port)
181 {
182 	int s;
183 	char *last, *host;
184 
185 	host = (char *)host_port;
186 
187 	last = strrchr(host_port, ':');
188 	if (last != 0 && !(last != host && last[-1] == ':')) {
189 		port = last + 1;
190 		host = malloc(last - host_port + 1);
191 		if (!host)
192 			return -1;
193 		memcpy(host, host_port, last-host_port);
194 		host[last-host_port] = 0;
195 	}
196 	s = connect_to(host, port);
197 	if (host != host_port)
198 		free(host);
199 	return s;
200 }
201 
202 char *
get_line(FILE * cin)203 get_line(FILE *cin)
204 {
205 	char *line;
206 	size_t len;
207 
208 	line = fgetln(cin, &len);
209 	if (!line)
210 		return NULL;
211 	if (len == 0)
212 		return NULL;
213 	if (line[len-1] == '\n')
214 		line[--len] = 0;
215 	if (len != 0 && line[len-1] == '\r')
216 		line[--len] = 0;
217 	return line;
218 }
219 
220 char *
get_answer(FILE * cin)221 get_answer(FILE *cin)
222 {
223 	char *line;
224 
225 	line = get_line(cin);
226 	if (!line || *line != '2')
227 		return NULL;
228 	else
229 		return line;
230 }
231 
232 void
verify_track_names(char ** names,int n,struct cd_toc_entry * e)233 verify_track_names(char **names, int n, struct cd_toc_entry *e)
234 {
235 	int i;
236 
237 	for (i = 0; i < n; i++) {
238 		if (names[i] == 0)
239 			names[i] = strdup(e->control & 4 ? "data" : "audio");
240 	}
241 }
242 
243 char **
cddb(const char * host_port,int n,struct cd_toc_entry * e,char * arg)244 cddb(const char *host_port, int n, struct cd_toc_entry *e, char *arg)
245 {
246 	int s = -1;
247 	FILE *cin = NULL;
248 	FILE *cout = NULL;
249 	char *type;
250 	char *line;
251 	char **result = NULL;
252 	int i;
253 	const char *errstr;
254 
255 	s = parse_connect_to(host_port, "cddb");
256 	if (s == -1)
257 		goto end;
258 	cin = fdopen(s, "r");
259 	if (!cin) {
260 		warn("cddb: fdopen");
261 		goto end;
262 	}
263 	cout = fdopen(s, "w");
264 	s = -1;
265 	if (!cout) {
266 		warn("cddb: fdopen");
267 		goto end;
268 	}
269 	line = get_answer(cin);
270 	if (!line) {
271 		warnx("cddb: won't talk to us");
272 		goto end;
273 	}
274 
275 	send_hello(cout);
276 	line = get_answer(cin);
277 	if (!line) {
278 		warnx("cddb: problem in hello");
279 		goto end;
280 	}
281 
282 	send_query(cout, n, e);
283 	fflush(cout);
284 	line = get_answer(cin);
285 	if (!line) {
286 		warnx("cddb: problem in query");
287 		goto end;
288 	}
289 	type = strchr(line, ' ');
290 	if (!type)
291 		goto end;
292 	*type++ = 0;
293 	/* no match or other issue */
294 	if (strcmp(line, "202") == 0) {
295 		printf("cddb: No match in database\n");
296 		goto end;
297 	}
298 	if (strcmp(line, "211") == 0 || strcmp(line, "212") == 0) {
299 		int number = strtonum(arg, 0, INT_MAX, &errstr);
300 		if (errstr != NULL && *arg != '\0') {
301 			warnx("cddb: invalid index");
302 			goto end;
303 		}
304 		if (number == 0) {
305 			if (strcmp(line, "211") == 0)
306 				printf("cddb: multiple matches\n");
307 			else {
308 				printf("cddb: inexact match\n");
309 				number = 1;
310 			}
311 		}
312 		if (number == 0) {
313 			for (i = 1;; i++) {
314 				line = get_line(cin);
315 				if (!line || strcmp(line, ".") == 0)
316 					goto end;
317 				printf("%d: %s\n", i, line);
318 			}
319 		} else {
320 			int ok = 0;
321 
322 			for (i = 1;; i++) {
323 				line = get_line(cin);
324 				if (!line)
325 					break;
326 				if (strcmp(line, ".") == 0)
327 					break;
328 				if (i == number)
329 					ok = further_query(cout, line);
330 			}
331 			if (!ok)
332 				goto end;
333 		}
334 	} else if (strcmp(line, "200") != 0 || !further_query(cout, type))
335 		goto end;
336 	result = calloc(sizeof(char *), n + 1);
337 	if (!result)
338 		goto end;
339 	for (i = 0; i <= n; i++)
340 		result[i] = NULL;
341 	line = get_answer(cin);
342 	if (!line)
343 		goto end2;
344 	for (;;) {
345 		long k;
346 		char *end;
347 
348 		line = get_line(cin);
349 		if (!line)
350 			goto end2;
351 		if (strcmp(line, ".") == 0)
352 			break;
353 		if (strncmp(line, "TTITLE", 6) != 0)
354 			continue;
355 		line += 6;
356 		k = strtol(line, &end, 10);
357 		if (*end++ != '=')
358 			continue;
359 		if (k >= n)
360 			continue;
361 		safe_copy(&result[k], end);
362 	}
363 	fprintf(cout, "QUIT\r\n");
364 	verify_track_names(result, n, e);
365 	goto end;
366 end2:
367 	free(result);
368 	result = NULL;
369 end:
370 	if (cout)
371 		fclose(cout);
372 	if (cin)
373 		fclose(cin);
374 	if (s != -1)
375 		close(s);
376 	return result;
377 }
378 
379 void
free_names(char ** names)380 free_names(char **names)
381 {
382 	int i;
383 
384 	for (i = 0; names[i]; i++)
385 		free(names[i]);
386 	free(names);
387 }
388