xref: /openbsd/usr.bin/cdio/cddb.c (revision cecf84d4)
1 /* $OpenBSD: cddb.c,v 1.20 2015/01/16 06:40:06 deraadt 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/socket.h>
28 #include <netinet/in.h>
29 #include <sys/cdio.h>
30 #include <err.h>
31 #include <netdb.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include <limits.h>
37 #include <vis.h>
38 #include "extern.h"
39 
40 unsigned long	cddb_sum(unsigned long);
41 void		send_hello(FILE *);
42 void		send_query(FILE *, int, struct cd_toc_entry *);
43 int		further_query(FILE *, char *);
44 int		connect_to(const char *, const char *);
45 int		parse_connect_to(const char *, const char *);
46 char *		get_line(FILE *);
47 char *		get_answer(FILE *);
48 void		verify_track_names(char **, int, struct cd_toc_entry *);
49 void		safe_copy(char **, const char *);
50 
51 unsigned long
52 cddb_sum(unsigned long v)
53 {
54 	unsigned long sum = 0;
55 
56 	while (v > 0) {
57 		sum += v % 10;
58 		v /= 10;
59 	}
60 	return (sum);
61 }
62 
63 unsigned long
64 cddb_discid(int n, struct cd_toc_entry *e)
65 {
66 	unsigned long sum;
67 	int i;
68 
69 	sum = 0;
70 	for (i =0; i < n; i++)
71 		sum += cddb_sum(entry2time(e+i));
72 	return (((sum % 0xff) << 24) |
73 	    ((entry2time(e+n) - entry2time(e)) << 8) | n);
74 }
75 
76 void
77 send_hello(FILE *cout)
78 {
79 	char hostname[HOST_NAME_MAX+1];
80 
81 	if (gethostname(hostname, sizeof(hostname)) == -1)
82 		strlcpy(hostname, "unknown", sizeof hostname);
83 	fprintf(cout, "CDDB HELLO %s %s cdio " VERSION "\r\n",
84 	    getlogin(), hostname);
85 	fflush(cout);
86 }
87 
88 void
89 send_query(FILE *f, int n, struct cd_toc_entry *e)
90 {
91 	int i;
92 
93 	fprintf(f, "cddb query %8lx %d", cddb_discid(n, e), n);
94 	for (i = 0; i < n; i++)
95 		fprintf(f, " %lu", entry2frames(e+i));
96 	fprintf(f, " %lu\r\n", (entry2frames(e+n)-entry2frames(e)) /75);
97 }
98 
99 #define MAXSIZE 256
100 char copy_buffer[MAXSIZE];
101 
102 void
103 safe_copy(char **p, const char *title)
104 {
105 	strnvis(copy_buffer, title, MAXSIZE-1, VIS_TAB|VIS_NL);
106 	if (*p == NULL)
107 		*p = strdup(copy_buffer);
108 	else {
109 		char *n;
110 
111 		if (asprintf(&n, "%s%s", *p, copy_buffer) == -1)
112 			return;
113 		free(*p);
114 		*p = n;
115 	}
116 }
117 
118 int
119 further_query(FILE *cout, char *line)
120 {
121 	char *key;
122 	char *title;
123 
124 	key = strchr(line, ' ');
125 	if (!key)
126 		return 0;
127 	*key++ = 0;
128 	title = strchr(key, ' ');
129 	if (!title)
130 		return 0;
131 	*title++ = 0;
132 	strnvis(copy_buffer, title, MAXSIZE-1, VIS_TAB|VIS_NL);
133 	printf("%s", copy_buffer);
134 	strnvis(copy_buffer, line, MAXSIZE-1, VIS_TAB|VIS_NL);
135 	printf("(%s)\n", copy_buffer);
136 	fprintf(cout, "CDDB READ %s %s\r\n", line, key);
137 	fflush(cout);
138 	return 1;
139 }
140 
141 
142 int
143 connect_to(const char *host, const char *serv)
144 {
145 	int s = -1;
146 	struct addrinfo hints, *res0 = NULL, *res;
147 	int error;
148 
149 	memset(&hints, 0, sizeof hints);
150 	hints.ai_family = PF_UNSPEC;
151 	hints.ai_socktype = SOCK_STREAM;
152 
153 	error = getaddrinfo(host, serv, &hints, &res0);
154 	if (error) {
155 		warnx("%s", gai_strerror(error));
156 		return -1;
157 	}
158 
159 	for (res = res0; res; res = res->ai_next) {
160 		s = socket(res->ai_family, res->ai_socktype,
161 		    res->ai_protocol);
162 		if (s == -1)
163 			continue;
164 		if (connect(s, res->ai_addr, res->ai_addrlen) == -1) {
165 			close(s);
166 			s = -1;
167 			continue;
168 		}
169 		break;
170 	}
171 	if (s == -1)
172 		warn("cddb");
173 	freeaddrinfo(res0);
174 	return s;
175 }
176 
177 int
178 parse_connect_to(const char *host_port, const char *port)
179 {
180 	int s;
181 	char *last, *host;
182 
183 	host = (char *)host_port;
184 
185 	last = strrchr(host_port, ':');
186 	if (last != 0 && !(last != host && last[-1] == ':')) {
187 		port = last + 1;
188 		host = malloc(last - host_port + 1);
189 		if (!host)
190 			return -1;
191 		memcpy(host, host_port, last-host_port);
192 		host[last-host_port] = 0;
193 	}
194 	s = connect_to(host, port);
195 	if (host != host_port)
196 		free(host);
197 	return s;
198 }
199 
200 char *
201 get_line(FILE *cin)
202 {
203 	char *line;
204 	size_t len;
205 
206 	line = fgetln(cin, &len);
207 	if (!line)
208 		return NULL;
209 	if (len == 0)
210 		return NULL;
211 	if (line[len-1] == '\n')
212 		line[--len] = 0;
213 	if (len != 0 && line[len-1] == '\r')
214 		line[--len] = 0;
215 	if (line[len] != 0)
216 		return NULL;
217 	return line;
218 }
219 
220 char *
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
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 **
244 cddb(const char *host_port, int n, struct cd_toc_entry *e, char *arg)
245 {
246 	int s = -1;
247 	int s2 = -1;
248 	FILE *cin = NULL;
249 	FILE *cout = NULL;
250 	char *type;
251 	char *line;
252 	char **result = NULL;
253 	int i;
254 	const char *errstr;
255 
256 	s = parse_connect_to(host_port, "cddb");
257 	if (s == -1)
258 		goto end;
259 	s2 = dup(s);
260 	if (s2 == -1)
261 		goto end;
262 	cin = fdopen(s, "r");
263 	if (!cin) {
264 		warn("cddb: fdopen");
265 		goto end;
266 	}
267 	s = -1;
268 	cout = fdopen(s2, "w");
269 	if (!cout) {
270 		warn("cddb: fdopen");
271 		goto end;
272 	}
273 	s2 = -1;
274 	line = get_answer(cin);
275 	if (!line) {
276 		warnx("cddb: won't talk to us");
277 		goto end;
278 	}
279 
280 	send_hello(cout);
281 	line = get_answer(cin);
282 	if (!line) {
283 		warnx("cddb: problem in hello");
284 		goto end;
285 	}
286 
287 	send_query(cout, n, e);
288 	fflush(cout);
289 	line = get_answer(cin);
290 	if (!line) {
291 		warnx("cddb: problem in query");
292 		goto end;
293 	}
294 	type = strchr(line, ' ');
295 	if (!type)
296 		goto end;
297 	*type++ = 0;
298 	/* no match or other issue */
299 	if (strcmp(line, "202") == 0) {
300 		printf("cddb: No match in database\n");
301 		goto end;
302 	}
303 	if (strcmp(line, "211") == 0 || strcmp(line, "212") == 0) {
304 		int number = strtonum(arg, 0, INT_MAX, &errstr);
305 		if (errstr != NULL && *arg != '\0') {
306 			warnx("cddb: invalid index");
307 			goto end;
308 		}
309 		if (number == 0) {
310 			if (strcmp(line, "211") == 0)
311 				printf("cddb: multiple matches\n");
312 			else {
313 				printf("cddb: inexact match\n");
314 				number = 1;
315 			}
316 		}
317 		if (number == 0) {
318 			for (i = 1;; i++) {
319 				line = get_line(cin);
320 				if (!line || strcmp(line, ".") == 0)
321 					goto end;
322 				printf("%d: %s\n", i, line);
323 			}
324 		} else {
325 			int ok = 0;
326 
327 			for (i = 1;; i++) {
328 				line = get_line(cin);
329 				if (!line)
330 					break;
331 				if (strcmp(line, ".") == 0)
332 					break;
333 				if (i == number)
334 					ok = further_query(cout, line);
335 			}
336 			if (!ok)
337 				goto end;
338 		}
339 	} else if (strcmp(line, "200") != 0 || !further_query(cout, type))
340 		goto end;
341 	result = calloc(sizeof(char *), n + 1);
342 	if (!result)
343 		goto end;
344 	for (i = 0; i <= n; i++)
345 		result[i] = NULL;
346 	line = get_answer(cin);
347 	if (!line)
348 		goto end2;
349 	for (;;) {
350 		int k;
351 		char *end;
352 
353 		line = get_line(cin);
354 		if (!line)
355 			goto end2;
356 		if (strcmp(line, ".") == 0)
357 			break;
358 		if (strncmp(line, "TTITLE", 6) != 0)
359 			continue;
360 		line += 6;
361 		end = strchr(line, '=');
362 		if (end == NULL)
363 			continue;
364 		*end++ = '\0';
365 		k = strtonum(line, 0, n - 1, &errstr);
366 		if (errstr != NULL)
367 			continue;
368 		safe_copy(&result[k], end);
369 	}
370 	fprintf(cout, "QUIT\r\n");
371 	verify_track_names(result, n, e);
372 	goto end;
373 end2:
374 	free(result);
375 	result = NULL;
376 end:
377 	if (cout)
378 		fclose(cout);
379 	if (cin)
380 		fclose(cin);
381 	if (s != -1)
382 		close(s);
383 	if (s2 != -1)
384 		close(s2);
385 	return result;
386 }
387 
388 void
389 free_names(char **names)
390 {
391 	int i;
392 
393 	for (i = 0; names[i]; i++)
394 		free(names[i]);
395 	free(names);
396 }
397