1 /*
2  *   libcddb - CDDB Interface Library for xmcd/cda
3  *
4  *	This library implements an interface to access the "classic"
5  *	CDDB1 services.
6  *
7  *   Copyright (C) 1993-2004  Ti Kan
8  *   E-mail: xmcd@amb.org
9  *
10  *   This program is free software; you can redistribute it and/or modify
11  *   it under the terms of the GNU General Public License as published by
12  *   the Free Software Foundation; either version 2 of the License, or
13  *   (at your option) any later version.
14  *
15  *   This program is distributed in the hope that it will be useful,
16  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *   GNU General Public License for more details.
19  *
20  *   You should have received a copy of the GNU General Public License
21  *   along with this program; if not, write to the Free Software
22  *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23  *
24  */
25 #ifndef lint
26 static char     *_fcddb_c_ident_ = "@(#)fcddb.c	1.110 04/04/20";
27 #endif
28 
29 #include "fcddb.h"
30 #include "common_d/version.h"
31 #include "cddbp.h"
32 #include "genretbl.h"
33 #include "regiontbl.h"
34 #include "langtbl.h"
35 #include "roletbl.h"
36 
37 #ifndef __VMS
38 /* UNIX */
39 
40 #ifndef NOREMOTE
41 #include <sys/socket.h>
42 #include <sys/time.h>
43 #include <netinet/in.h>
44 #include <arpa/inet.h>
45 #include <netdb.h>
46 #if defined(_AIX) || defined(__QNX__)
47 #include <sys/select.h>
48 #endif
49 #endif	/* NOREMOTE */
50 
51 /* Directory path name convention */
52 #define DIR_BEG			'/'		/* Directory start */
53 #define DIR_SEP			'/'		/* Directory separator */
54 #define DIR_END			'/'		/* Directory end */
55 #define CUR_DIR			"."		/* Current directory */
56 
57 /* Defines used by fcddb_runcmd */
58 #define STR_SHPATH		"/bin/sh"	/* Path to shell */
59 #define STR_SHNAME		"sh"		/* Name of shell */
60 #define STR_SHARG		"-c"		/* Shell arg */
61 
62 #else
63 /* OpenVMS */
64 
65 #ifndef NOREMOTE
66 #include <socket.h>
67 #include <time.h>
68 #include <in.h>
69 #include <inet.h>
70 #include <netdb.h>
71 #endif	/* NOREMOTE */
72 
73 /* Directory path name convention */
74 #define DIR_BEG			'['		/* Directory start */
75 #define DIR_SEP			'.'		/* Directory separator */
76 #define DIR_END			']'		/* Directory end */
77 #define CUR_DIR			"[]"		/* Current directory */
78 
79 #endif	/* __VMS */
80 
81 /* Max host name length */
82 #ifndef HOST_NAM_SZ
83 #define HOST_NAM_SZ		64
84 #endif
85 
86 /* Minimum/maximum macros */
87 #define FCDDB2_MIN(a,b)		(((a) > (b)) ? (b) : (a))
88 #define FCDDB2_MAX(a,b)		(((a) > (b)) ? (a) : (b))
89 
90 /* Skip white space macro */
91 #define SKIP_SPC(p)		while (*(p) == ' ' || *(p) == '\t') (p)++;
92 
93 /* CDDBD status code macros */
94 #define STATCODE_1ST(p)		((p)[0])
95 #define STATCODE_2ND(p)		((p)[1])
96 #define STATCODE_3RD(p)		((p)[2])
97 #define STATCODE_CHECK(p)	\
98 	(STATCODE_1ST(p) != '\0' && isdigit((int) STATCODE_1ST(p)) && \
99 	 STATCODE_2ND(p) != '\0' && isdigit((int) STATCODE_2ND(p)) && \
100 	 STATCODE_3RD(p) != '\0' && isdigit((int) STATCODE_3RD(p)))
101 
102 /* HTTP status codes */
103 #define HTTP_PROXYAUTH_FAIL	407
104 
105 /* Number of seconds per day */
106 #define SECS_PER_DAY		(24 * 60 * 60)
107 
108 /* Number of seconds to wait for an external command to complete */
109 #define CMD_TIMEOUT_SECS	60
110 
111 
112 bool_t		fcddb_debug;			/* Debug flag */
113 FILE		*fcddb_errfp;			/* Debug message file stream */
114 fcddb_gmap_t	*fcddb_gmap_head = NULL;	/* Genre map list head */
115 
116 STATIC char	fcddb_hellostr[HOST_NAM_SZ + (STR_BUF_SZ * 2)],
117 		fcddb_extinfo[HOST_NAM_SZ + (STR_BUF_SZ * 2)],
118 		*fcddb_auth_buf = NULL;
119 
120 /*
121  * fcddb_sum
122  *	Compute a checksum number for use by fcddb_discid
123  *
124  * Args:
125  *	n - A numeric value
126  *
127  * Return:
128  *	The checksum of n
129  */
130 STATIC int
131 fcddb_sum(int n)
132 {
133  	int	ret;
134 
135 	/* For backward compatibility this algorithm must not change */
136 	for (ret = 0; n > 0; n /= 10)
137 		ret += n % 10;
138 
139 	return (ret);
140 }
141 
142 
143 /*
144  * fcddb_line_filter
145  *	Line filter to prevent multi-line strings from being stored in
146  *	single-line fields.
147  *
148  * Args:
149  *	str - The field string
150  *
151  * Return:
152  *	Nothing
153  */
154 STATIC void
155 fcddb_line_filter(char *str)
156 {
157 	if (str == NULL)
158 		return;
159 
160 	for (; *str != '\0'; str++) {
161 		if (*str == '\\' && *(str+1) == 'n') {
162 			*str = '\0';
163 			break;
164 		}
165 	}
166 }
167 
168 
169 /*
170  * fcddb_iso8859_to_utf8
171  *	Convert the input ISO-8859-1 string to UTF-8 and return the
172  *	resultant string.  The output string buffer is dynamically allocated
173  *	and should be freed by the caller via MEM_FREE when done.
174  *
175  * Args:
176  *	str - source string (ISO-8859-1)
177  *
178  * Return:
179  *	Return string (UTF-8)
180  */
181 STATIC char *
182 fcddb_iso8859_to_utf8(char *str)
183 {
184 	unsigned char	*p,
185 			*q,
186 			*tmpbuf;
187 	char		*retbuf;
188 
189 	tmpbuf = (unsigned char *) MEM_ALLOC(
190 		"utf8_to_buf", strlen(str) * 6 + 1
191 	);
192 	if (tmpbuf == NULL)
193 		return NULL;
194 
195 	p = (unsigned char *) str;
196 	q = (unsigned char *) tmpbuf;
197 	while (*p != '\0') {
198 		if (*p <= 0x7f) {
199 			/* Simple US-ASCII character: just copy it */
200 			*q++ = *p++;
201 		}
202 		else {
203 			/* ISO8859 is byte-oriented, so this needs to
204 			 * only handle the "extended ASCII" characters
205 			 * 0xc0 to 0xfd.
206 			 */
207 			*q++ = (0xc0 | ((int) (0xc0 & *p) >> 6));
208 			*q++ = 0x80 | (*p & 0x3f);
209 			p++;
210 		}
211 	}
212 	*q = '\0';
213 
214 	retbuf = (char *) MEM_ALLOC(
215 		"utf8_retbuf", strlen((char *) tmpbuf) + 1
216 	);
217 	if (retbuf != NULL)
218 		(void) strcpy(retbuf, (char *) tmpbuf);
219 
220 	MEM_FREE(tmpbuf);
221 	return (retbuf);
222 }
223 
224 
225 /*
226  * fcddb_utf8_to_iso8859
227  *	Convert the input UTF-8 string to ISO8859-1 and return the
228  *	resultant string.  The output string buffer is dynamically allocated
229  *	and should be freed by the caller via MEM_FREE when done.
230  *
231  * Args:
232  *	str - source string (UTF-8)
233  * Return:
234  *
235  *	Return string (ISO-8859-1)
236  */
237 STATIC char *
238 fcddb_utf8_to_iso8859(char *str)
239 {
240 	unsigned char	*p,
241 			*q,
242 			*tmpbuf;
243 	char		*retbuf;
244 
245 	tmpbuf = (unsigned char *) MEM_ALLOC(
246 		"iso8859_to_buf", strlen(str) + 1
247 	);
248 	if (tmpbuf == NULL)
249 		return NULL;
250 
251 	p = (unsigned char *) str;
252 	q = (unsigned char *) tmpbuf;
253 	while (*p != '\0') {
254 		if (*p <= 0x7f) {
255 			/* Simple US-ASCII character: just copy it */
256 			*q++ = *p++;
257 		}
258 		else if (*p >= 0xc0 && *p <= 0xfd) {
259 			int	n = 0;
260 
261 			if ((*p & 0xe0) == 0xc0) {
262 				n = 2;
263 				if (*(p+1) >= 0x80 && *(p+1) <= 0xfd) {
264 				    /* OK */
265 				    *q = (((*p & 0x03) << 6) |
266 					   (*(p+1) & 0x3f));
267 
268 				    if ((*p & 0x1c) != 0) {
269 					/* Decodes to more than 8 bits */
270 					*q = '_';
271 				    }
272 				    else if (*q <= 0x7f) {
273 					/* Not the shortest encoding:
274 					 * illegal.
275 					 */
276 					*q = '_';
277 				    }
278 				}
279 				else {
280 				    /* Malformed UTF-8 character */
281 				    *q = '_';
282 				}
283 				q++;
284 			}
285 			else if ((*p & 0xf0) == 0xe0) {
286 				/* Three-byte sequence */
287 				n = 3;
288 			}
289 			else if ((*p & 0xf8) == 0xf0) {
290 				/* Four-byte sequence */
291 				n = 4;
292 			}
293 			else if ((*p & 0xfc) == 0xf8) {
294 				/* Five-byte sequence */
295 				n = 5;
296 			}
297 			else if ((*p & 0xfe) == 0xfc) {
298 				/* Six-byte sequence */
299 				n = 6;
300 			}
301 
302 			if (n > 2)
303 				*q++ = '_';
304 
305 			while (n > 0) {
306 				if (*(++p) == '\0')
307 					break;
308 				n--;
309 			}
310 		}
311 		else {
312 			/* Malformed UTF-8 sequence: skip */
313 			p++;
314 		}
315 	}
316 	*q = '\0';
317 
318 	retbuf = (char *) MEM_ALLOC(
319 		"iso8859_retbuf", strlen((char *) tmpbuf) + 1
320 	);
321 	if (retbuf != NULL)
322 		(void) strcpy(retbuf, (char *) tmpbuf);
323 
324 	MEM_FREE(tmpbuf);
325 	return (retbuf);
326 }
327 
328 
329 /*
330  * fcddb_strcasecmp
331  *	Compare two strings a la strcmp(), except it is case-insensitive.
332  *
333  * Args:
334  *	s1 - The first text string.
335  *	s2 - The second text string.
336  *
337  * Return:
338  *	Compare value.  See strcmp(3).
339  */
340 STATIC int
341 fcddb_strcasecmp(char *s1, char *s2)
342 {
343 	char	*buf1,
344 		*buf2,
345 		*p;
346 	int	ret;
347 
348 	if (s1 == NULL || s2 == NULL)
349 		return 0;
350 
351 	/* Allocate tmp buffers */
352 	buf1 = (char *) MEM_ALLOC("strcasecmp_buf1", strlen(s1)+1);
353 	buf2 = (char *) MEM_ALLOC("strcasecmp_buf2", strlen(s2)+1);
354 	if (buf1 == NULL || buf2 == NULL)
355 		return -1;	/* Shrug */
356 
357 	/* Convert both strings to lower case and store in tmp buffer */
358 	for (p = buf1; *s1 != '\0'; s1++, p++)
359 		*p = (char) ((isupper((int) *s1)) ? tolower((int) *s1) : *s1);
360 	*p = '\0';
361 	for (p = buf2; *s2 != '\0'; s2++, p++)
362 		*p = (char) ((isupper((int) *s2)) ? tolower((int) *s2) : *s2);
363 	*p = '\0';
364 
365 	ret = strcmp(buf1, buf2);
366 
367 	MEM_FREE(buf1);
368 	MEM_FREE(buf2);
369 
370 	return (ret);
371 }
372 
373 
374 #ifdef __VMS
375 
376 /*
377  * fcddb_vms_dirconv
378  *	Convert VMS directory notation from disk:[a.b.c] to disk:[a.b]c.dir
379  *	syntax.
380  *
381  * Args:
382  *	path - Input directory path string
383  *
384  * Return:
385  *	The output path string.  The string buffer should be deallocated
386  *	by the caller when done.  If an error is encountered, NULL is
387  *	returned.
388  */
389 STATIC char *
390 fcddb_vms_dirconv(char *path)
391 {
392 	char	*cp,
393 		*cp2,
394 		*cp3,
395 		*buf,
396 		tmp[STR_BUF_SZ];
397 
398 	buf = (char *) MEM_ALLOC("vms_dirconv", strlen(path) + 16);
399 	if (buf == NULL)
400 		return NULL;
401 
402 	(void) strcpy(buf, path);
403 
404 	if ((cp = strchr(buf, DIR_BEG)) != NULL) {
405 		if ((cp2 = strrchr(buf, DIR_END)) != NULL) {
406 			if ((cp3 = strrchr(buf, DIR_SEP)) != NULL) {
407 				if (fcddb_strcasecmp(cp3, ".dir") == 0) {
408 					/* Already in the desired form */
409 					return (buf);
410 				}
411 				*cp2 = '\0';
412 				*cp3 = DIR_END;
413 				(void) strcat(cp3, ".dir");
414 				return (buf);
415 			}
416 			else {
417 				if ((cp = strchr(buf, ':')) == NULL)
418 					cp = buf;
419 				else
420 					cp++;
421 
422 				if (strcmp(cp, "[000000]") == 0)
423 					return (buf);
424 
425 				*cp2 = '\0';
426 				(void) strcpy(tmp, cp+1);
427 				(void) sprintf(cp, "[000000]%s.dir", tmp);
428 				return (buf);
429 			}
430 		}
431 		else {
432 			/* Invalid path */
433 			return NULL;
434 		}
435 	}
436 	else {
437 		if ((cp2 = strrchr(buf, DIR_SEP)) != NULL) {
438 			if ((cp3 = strchr(buf, ':')) == NULL)
439 				cp3 = buf;
440 			else
441 				cp3++;
442 
443 			if (fcddb_strcasecmp(cp2, ".dir") == 0) {
444 				(void) sprintf(tmp, "[000000]%s", cp3);
445 				strcpy(cp3, tmp);
446 				return (buf);
447 			}
448 			else {
449 				/* Invalid path */
450 				return NULL;
451 			}
452 		}
453 		else {
454 			if ((cp3 = strchr(buf, ':')) != NULL) {
455 				(void) strcpy(cp3+1, "[000000]");
456 				return (buf);
457 			}
458 
459 			/* Invalid path */
460 			return NULL;
461 		}
462 	}
463 	/*NOTREACHED*/
464 }
465 
466 #endif	/* __VMS */
467 
468 
469 /*
470  * fcddb_ckmkdir
471  *	Check the specified directory path, if it doesn't exist,
472  *	attempt to create it.
473  *
474  * Args:
475  *	path - Directory path to check or create
476  *	mode - Directory permissions
477  *
478  * Return:
479  *	CddbResult status code
480  */
481 STATIC CddbResult
482 fcddb_ckmkdir(char *path, mode_t mode)
483 {
484 	char		*dpath = NULL;
485 	struct stat	stbuf;
486 
487 #ifdef __VMS
488 	if ((dpath = fcddb_vms_dirconv(path)) == NULL)
489 		return CDDBTRNCannotCreateFile;
490 #else
491 	if ((dpath = fcddb_strdup(path)) == NULL)
492 		return CDDBTRNOutOfMemory;
493 #endif
494 
495 	if (stat(dpath, &stbuf) < 0) {
496 		if (errno == ENOENT) {
497 			if (mkdir(path, mode) < 0) {
498 				FCDDBDBG(fcddb_errfp, "fcddb_ckmkdir: %s "
499 					"mkdir failed, errno=%d\n",
500 					path, errno
501 				);
502 				MEM_FREE(dpath);
503 				return CDDBTRNCannotCreateFile;
504 			}
505 			(void) chmod(path, mode);
506 		}
507 		else {
508 			FCDDBDBG(fcddb_errfp,
509 				"fcddb_ckmkdir: %s stat failed, errno=%d\n",
510 				path, errno
511 			);
512 			MEM_FREE(dpath);
513 			return CDDBTRNCannotCreateFile;
514 		}
515 	}
516 	else if (!S_ISDIR(stbuf.st_mode)) {
517 		FCDDBDBG(fcddb_errfp, "fcddb_ckmkdir: "
518 			"%s is not a directory! (mode=0x%x)\n",
519 			path, (int) stbuf.st_mode
520 		);
521 		MEM_FREE(dpath);
522 		return CDDBTRNCannotCreateFile;
523 	}
524 
525 	MEM_FREE(dpath);
526 	return Cddb_OK;
527 }
528 
529 
530 /*
531  * fcddb_strcat
532  *	Similar to strcat() except this handles special meta characters
533  *	in CDDB data.
534  *
535  * Args:
536  *	s1 - target string
537  *	s2 - source string
538  *
539  * Return:
540  *	Pointer to target string if successful, or NULL on failure
541  */
542 STATIC char *
543 fcddb_strcat(char *s1, char *s2)
544 {
545 	int	n;
546 	char	*cp = s1;
547 	bool_t	proc_slash;
548 
549 	if (s1 == NULL || s2 == NULL)
550 		return NULL;
551 
552 	/* Concatenate two strings, with special handling for newline
553 	 * and tab characters.
554 	 */
555 	proc_slash = FALSE;
556 	n = strlen(s1);
557 	s1 += n;
558 
559 	if (n > 0 && *(s1 - 1) == '\\') {
560 		proc_slash = TRUE;	/* Handle broken escape sequences */
561 		s1--;
562 	}
563 
564 	for (; *s2 != '\0'; s1++, s2++) {
565 		if (*s2 == '\\') {
566 			if (proc_slash) {
567 				proc_slash = FALSE;
568 				continue;
569 			}
570 			proc_slash = TRUE;
571 			s2++;
572 		}
573 
574 		if (proc_slash) {
575 			proc_slash = FALSE;
576 
577 			switch (*s2) {
578 			case 'n':
579 				*s1 = '\n';
580 				break;
581 			case 't':
582 				*s1 = '\t';
583 				break;
584 			case '\\':
585 				*s1 = '\\';
586 				break;
587 			case '\0':
588 				*s1 = '\\';
589 				s2--;
590 				break;
591 			default:
592 				*s1++ = '\\';
593 				*s1 = *s2;
594 				break;
595 			}
596 		}
597 		else
598 			*s1 = *s2;
599 	}
600 	*s1 = '\0';
601 
602 	return (cp);
603 }
604 
605 
606 /*
607  * fcddb_http_xlat
608  *	String translator that handles HTTP character escape sequences
609  *
610  * Args:
611  *	s1 - source string
612  *	s2 - target string
613  *
614  * Return:
615  *	Nothing
616  */
617 STATIC void
618 fcddb_http_xlat(char *s1, char *s2)
619 {
620 	char	*p,
621 		*q;
622 
623 	for (p = s1, q = s2; *p != '\0'; p++) {
624 		switch (*p) {
625 		case '?':
626 		case '=':
627 		case '+':
628 		case '&':
629 		case ' ':
630 		case '%':
631 			(void) sprintf(q, "%%%02X", *p);
632 			q += 3;
633 			break;
634 		default:
635 			*q = *p;
636 			q++;
637 			break;
638 		}
639 	}
640 	*q = '\0';
641 }
642 
643 
644 /*
645  * Data used by fcddb_b64encode
646  */
647 STATIC char	b64map[] = {
648 	'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
649 	'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
650 	'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
651 	'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
652 	'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
653 	'8', '9', '+', '/'
654 };
655 
656 #define B64_PAD		'='
657 
658 
659 /*
660  * fcddb_b64encode
661  *	Base64 encoding function
662  *
663  * Args:
664  *	ibuf - Input string buffer
665  *	len - Number of characters
666  *	obuf - Output string buffer
667  *	brklines - Whether to break output into multiple lines
668  *
669  * Return:
670  *	Nothing
671  */
672 STATIC void
673 fcddb_b64encode(byte_t *ibuf, int len, byte_t *obuf, bool_t brklines)
674 {
675 	int	i, j, k, n,
676 		c[4];
677 	byte_t	sbuf[4];
678 
679 	for (i = k = 0; (i + 3) <= len; i += 3, ibuf += 3) {
680 		c[0] = ((int) ibuf[0] >> 2);
681 		c[1] = ((((int) ibuf[0] & 0x03) << 4) |
682 			(((int) ibuf[1] & 0xf0) >> 4));
683 		c[2] = ((((int) ibuf[1] & 0x0f) << 2) |
684 			(((int) ibuf[2] & 0xc0) >> 6));
685 		c[3] = ((int) ibuf[2] & 0x3f);
686 
687 		for (j = 0; j < 4; j++)
688 			*obuf++ = b64map[c[j]];
689 
690 		if (brklines && ++k == 16) {
691 			k = 0;
692 			*obuf++ = '\n';
693 		}
694 	}
695 
696 	if (i < len) {
697 		n = len - i;
698 		(void) strncpy((char *) sbuf, (char *) ibuf, n);
699 		for (j = n; j < 3; j++)
700 			sbuf[j] = (unsigned char) 0;
701 
702 		n++;
703 		ibuf = sbuf;
704 		c[0] = ((int) ibuf[0] >> 2);
705 		c[1] = ((((int) ibuf[0] & 0x03) << 4) |
706 			(((int) ibuf[1] & 0xf0) >> 4));
707 		c[2] = ((((int) ibuf[1] & 0x0f) << 2) |
708 			(((int) ibuf[2] & 0xc0) >> 6));
709 		c[3] = ((int) ibuf[2] & 0x3f);
710 
711 		for (j = 0; j < 4; j++)
712 			*obuf++ = (j < n) ? b64map[c[j]] : B64_PAD;
713 
714 		if (brklines && ++k == 16)
715 			*obuf++ = '\n';
716 	}
717 
718 	if (brklines)
719 		*obuf++ = '\n';
720 
721 	*obuf = '\0';
722 }
723 
724 
725 /*
726  * fcddb_genre_addcateg
727  *	Add a new category to the genre map table.
728  *
729  * Args:
730  *	categ - The category name string
731  *
732  * Return:
733  *	Nothing.
734  */
735 STATIC void
736 fcddb_genre_addcateg(char *categ)
737 {
738 	fcddb_gmap_t	*gmp,
739 			*gmp2;
740 
741 	gmp2 = (fcddb_gmap_t *)(void *) MEM_ALLOC(
742 		"CddbGenremap", sizeof(fcddb_gmap_t)
743 	);
744 	if (gmp2 == NULL)
745 		return;	/* Can't add: out of memory */
746 
747 	(void) memset(gmp2, 0, sizeof(fcddb_gmap_t));
748 
749 	/* Map to unclassifiable */
750 	gmp2->cddb1name = fcddb_strdup(categ);
751 	gmp2->meta.id = "220";
752 	gmp2->meta.name = "Unclassifiable";
753 	gmp2->sub.id = "221";
754 	gmp2->sub.name = "General Unclassifiable";
755 	gmp2->flags = CDDB_GMAP_DUP | CDDB_GMAP_DYN;
756 	gmp2->next = NULL;
757 
758 	if (fcddb_gmap_head == NULL) {
759 		fcddb_gmap_head = gmp2;
760 	}
761 	else for (gmp = fcddb_gmap_head; gmp != NULL; gmp = gmp->next) {
762 		if (gmp->next == NULL) {
763 			gmp->next = gmp2;
764 			break;
765 		}
766 	}
767 }
768 
769 
770 /*
771  * fcddb_clear_url
772  *	Clear a URL structure
773  *
774  * Args:
775  *	up - Pointer to the URL structure
776  *
777  * Return:
778  *	Nothing
779  */
780 STATIC void
781 fcddb_clear_url(cddb_url_t *up)
782 {
783 	if (up->type != NULL) {
784 		MEM_FREE(up->type);
785 		up->type = NULL;
786 	}
787 	if (up->href != NULL) {
788 		MEM_FREE(up->href);
789 		up->href = NULL;
790 	}
791 	if (up->displaylink != NULL) {
792 		MEM_FREE(up->displaylink);
793 		up->displaylink = NULL;
794 	}
795 	if (up->displaytext != NULL) {
796 		MEM_FREE(up->displaytext);
797 		up->displaytext = NULL;
798 	}
799 	if (up->category != NULL) {
800 		MEM_FREE(up->category);
801 		up->category = NULL;
802 	}
803 	if (up->description != NULL) {
804 		MEM_FREE(up->description);
805 		up->description = NULL;
806 	}
807 }
808 
809 
810 /*
811  * fcddb_clear_urllist
812  *	Clear a URL list structure
813  *
814  * Args:
815  *	up - Pointer to the URL list structure
816  *
817  * Return:
818  *	Nothing
819  */
820 STATIC void
821 fcddb_clear_urllist(cddb_urllist_t *ulp)
822 {
823 	cddb_url_t	*up,
824 			*nextp;
825 
826 
827 	for (up = ulp->urls; up != NULL; up = nextp) {
828 		nextp = up->next;
829 		fcddb_clear_url(up);
830 		MEM_FREE(up);
831 	}
832 	ulp->urls = NULL;
833 	ulp->count = 0;
834 }
835 
836 
837 /*
838  * fcddb_clear_urlmanager
839  *	Clear a URL manager structure
840  *
841  * Args:
842  *	up - Pointer to the URL manager structure
843  *
844  * Return:
845  *	Nothing
846  */
847 STATIC void
848 fcddb_clear_urlmanager(cddb_urlmanager_t *mp)
849 {
850 	cddb_urllist_t	*ulp = &mp->urllist;
851 
852 	fcddb_clear_urllist(ulp);
853 	mp->control = NULL;
854 }
855 
856 
857 /*
858  * fcddb_clear_fullname
859  *	Clear a full name structure
860  *
861  * Args:
862  *	up - Pointer to the full name structure
863  *
864  * Return:
865  *	Nothing
866  */
867 STATIC void
868 fcddb_clear_fullname(cddb_fullname_t *fnp)
869 {
870 	if (fnp->name != NULL) {
871 		MEM_FREE(fnp->name);
872 		fnp->name = NULL;
873 	}
874 	if (fnp->lastname != NULL) {
875 		MEM_FREE(fnp->lastname);
876 		fnp->lastname = NULL;
877 	}
878 	if (fnp->firstname != NULL) {
879 		MEM_FREE(fnp->firstname);
880 		fnp->firstname = NULL;
881 	}
882 	if (fnp->the != NULL) {
883 		MEM_FREE(fnp->the);
884 		fnp->the = NULL;
885 	}
886 }
887 
888 
889 /*
890  * fcddb_clear_credit
891  *	Clear a credit structure
892  *
893  * Args:
894  *	up - Pointer to the credit structure
895  *
896  * Return:
897  *	Nothing
898  */
899 STATIC void
900 fcddb_clear_credit(cddb_credit_t *cp)
901 {
902 	fcddb_clear_fullname(&cp->fullname);
903 	if (cp->notes != NULL) {
904 		MEM_FREE(cp->notes);
905 		cp->notes = NULL;
906 	}
907 	cp->role = NULL;
908 	cp->next = NULL;
909 }
910 
911 
912 /*
913  * fcddb_clear_credits
914  *	Clear a credits structure
915  *
916  * Args:
917  *	up - Pointer to the credits structure
918  *
919  * Return:
920  *	Nothing
921  */
922 STATIC void
923 fcddb_clear_credits(cddb_credits_t *csp)
924 {
925 	cddb_credit_t	*cp,
926 			*nextp;
927 
928 	for (cp = csp->credits; cp != NULL; cp = nextp) {
929 		nextp = cp->next;
930 		fcddb_clear_credit(cp);
931 		MEM_FREE(cp);
932 	}
933 	csp->credits = NULL;
934 	csp->count = 0;
935 }
936 
937 
938 /*
939  * fcddb_clear_segment
940  *	Clear a segment structure
941  *
942  * Args:
943  *	up - Pointer to the segment structure
944  *
945  * Return:
946  *	Nothing
947  */
948 STATIC void
949 fcddb_clear_segment(cddb_segment_t *sp)
950 {
951 	fcddb_clear_credits(&sp->credits);
952 
953 	if (sp->name != NULL) {
954 		MEM_FREE(sp->name);
955 		sp->name = NULL;
956 	}
957 	if (sp->notes != NULL) {
958 		MEM_FREE(sp->notes);
959 		sp->notes = NULL;
960 	}
961 	if (sp->starttrack != NULL) {
962 		MEM_FREE(sp->starttrack);
963 		sp->starttrack = NULL;
964 	}
965 	if (sp->startframe != NULL) {
966 		MEM_FREE(sp->startframe);
967 		sp->startframe = NULL;
968 	}
969 	if (sp->endtrack != NULL) {
970 		MEM_FREE(sp->endtrack);
971 		sp->endtrack = NULL;
972 	}
973 	if (sp->endframe != NULL) {
974 		MEM_FREE(sp->endframe);
975 		sp->endframe = NULL;
976 	}
977 	sp->control = NULL;
978 	sp->next = NULL;
979 }
980 
981 
982 /*
983  * fcddb_clear_segments
984  *	Clear a segments structure
985  *
986  * Args:
987  *	up - Pointer to the segments structure
988  *
989  * Return:
990  *	Nothing
991  */
992 STATIC void
993 fcddb_clear_segments(cddb_segments_t *ssp)
994 {
995 	cddb_segment_t	*sp,
996 			*nextp;
997 
998 	for (sp = ssp->segments; sp != NULL; sp = nextp) {
999 		nextp = sp->next;
1000 		fcddb_clear_segment(sp);
1001 		MEM_FREE(sp);
1002 	}
1003 	ssp->segments = NULL;
1004 	ssp->count = 0;
1005 }
1006 
1007 
1008 /*
1009  * fcddb_clear_genretree
1010  *	Clear a genre tree structure
1011  *
1012  * Args:
1013  *	up - Pointer to the genre tree structure
1014  *
1015  * Return:
1016  *	Nothing
1017  */
1018 STATIC void
1019 fcddb_clear_genretree(cddb_genretree_t *gp)
1020 {
1021 	cddb_genre_t	*p,
1022 			*q,
1023 			*nextp,
1024 			*nextq;
1025 	fcddb_gmap_t	*gmp,
1026 			*gmp2;
1027 
1028 	/* Free genre tree */
1029 	nextp = NULL;
1030 	for (p = gp->genres; p != NULL; p = nextp) {
1031 		nextp = p->nextmeta;
1032 
1033 		nextq = NULL;
1034 		for (q = p->next; q != NULL; q = nextq) {
1035 			nextq = q->next;
1036 
1037 			if (q->id != NULL)
1038 				MEM_FREE(q->id);
1039 			if (q->name != NULL)
1040 				MEM_FREE(q->name);
1041 
1042 			MEM_FREE(q);
1043 		}
1044 
1045 		if (p->id != NULL)
1046 			MEM_FREE(p->id);
1047 		if (p->name != NULL)
1048 			MEM_FREE(p->name);
1049 
1050 		MEM_FREE(p);
1051 	}
1052 	gp->genres = NULL;
1053 	gp->count = 0;
1054 
1055 	/* Deallocate dynamic genre mapping if applicable */
1056 	gmp2 = NULL;
1057 	for (gmp = fcddb_gmap_head; gmp != NULL; gmp = gmp2) {
1058 		gmp2 = gmp->next;
1059 
1060 		if ((gmp->flags & CDDB_GMAP_DYN) != 0) {
1061 			if (gmp->cddb1name != NULL)
1062 				MEM_FREE(gmp->cddb1name);
1063 			if (gmp->meta.id != NULL)
1064 				MEM_FREE(gmp->meta.id);
1065 			if (gmp->meta.name != NULL)
1066 				MEM_FREE(gmp->meta.name);
1067 			if (gmp->sub.id != NULL)
1068 				MEM_FREE(gmp->sub.id);
1069 			if (gmp->sub.name != NULL)
1070 				MEM_FREE(gmp->sub.name);
1071 			MEM_FREE(gmp);
1072 		}
1073 	}
1074 	fcddb_gmap_head = NULL;
1075 }
1076 
1077 
1078 /*
1079  * fcddb_clear_roletree
1080  *	Clear a role tree structure
1081  *
1082  * Args:
1083  *	up - Pointer to the role tree structure
1084  *
1085  * Return:
1086  *	Nothing
1087  */
1088 STATIC void
1089 fcddb_clear_roletree(cddb_roletree_t *rtp)
1090 {
1091 	cddb_rolelist_t	*rlp,
1092 			*nextp;
1093 
1094 	nextp = NULL;
1095 	for (rlp = rtp->rolelists; rlp != NULL; rlp = nextp) {
1096 		nextp = rlp->next;
1097 		MEM_FREE(rlp);
1098 	}
1099 	rtp->rolelists = NULL;
1100 	rtp->count = 0;
1101 }
1102 
1103 
1104 /*
1105  * fcddb_clear_userinfo
1106  *	Clear a user info structure
1107  *
1108  * Args:
1109  *	up - Pointer to the user info structure
1110  *
1111  * Return:
1112  *	Nothing
1113  */
1114 STATIC void
1115 fcddb_clear_userinfo(cddb_userinfo_t *up)
1116 {
1117 	if (up->userhandle != NULL) {
1118 		MEM_FREE(up->userhandle);
1119 		up->userhandle = NULL;
1120 	}
1121 	if (up->password != NULL) {
1122 		MEM_FREE(up->password);
1123 		up->password = NULL;
1124 	}
1125 	if (up->passwordhint != NULL) {
1126 		MEM_FREE(up->passwordhint);
1127 		up->passwordhint = NULL;
1128 	}
1129 	if (up->emailaddress != NULL) {
1130 		MEM_FREE(up->emailaddress);
1131 		up->emailaddress = NULL;
1132 	}
1133 	if (up->regionid != NULL) {
1134 		MEM_FREE(up->regionid);
1135 		up->regionid = NULL;
1136 	}
1137 	if (up->postalcode != NULL) {
1138 		MEM_FREE(up->postalcode);
1139 		up->postalcode = NULL;
1140 	}
1141 	if (up->age != NULL) {
1142 		MEM_FREE(up->age);
1143 		up->age = NULL;
1144 	}
1145 	if (up->sex != NULL) {
1146 		MEM_FREE(up->sex);
1147 		up->sex = NULL;
1148 	}
1149 	up->allowemail = 0;
1150 	up->allowstats = 0;
1151 }
1152 
1153 
1154 /*
1155  * fcddb_clear_options
1156  *	Clear a options structure
1157  *
1158  * Args:
1159  *	up - Pointer to the options structure
1160  *
1161  * Return:
1162  *	Nothing
1163  */
1164 STATIC void
1165 fcddb_clear_options(cddb_options_t *op)
1166 {
1167 	if (op->proxyserver != NULL) {
1168 		MEM_FREE(op->proxyserver);
1169 		op->proxyserver = NULL;
1170 	}
1171 	if (op->proxyusername != NULL) {
1172 		MEM_FREE(op->proxyusername);
1173 		op->proxyusername = NULL;
1174 	}
1175 	if (op->proxypassword != NULL) {
1176 		MEM_FREE(op->proxypassword);
1177 		op->proxypassword = NULL;
1178 	}
1179 	if (op->localcachepath != NULL) {
1180 		MEM_FREE(op->localcachepath);
1181 		op->localcachepath = NULL;
1182 	}
1183 	op->proxyport = 0;
1184 	op->servertimeout = 0;
1185 	op->testsubmitmode = 0;
1186 	op->localcachetimeout = 0;
1187 }
1188 
1189 
1190 /*
1191  * fcddb_clear_track
1192  *	Clear a track structure
1193  *
1194  * Args:
1195  *	up - Pointer to the track structure
1196  *
1197  * Return:
1198  *	Nothing
1199  */
1200 STATIC void
1201 fcddb_clear_track(cddb_track_t *tp)
1202 {
1203 	fcddb_clear_credits(&tp->credits);
1204 
1205 	if (tp->title != NULL) {
1206 		MEM_FREE(tp->title);
1207 		tp->title = NULL;
1208 	}
1209 	if (tp->notes != NULL) {
1210 		MEM_FREE(tp->notes);
1211 		tp->notes = NULL;
1212 	}
1213 	tp->control = NULL;
1214 }
1215 
1216 
1217 /*
1218  * fcddb_clear_tracks
1219  *	Clear a tracks structure
1220  *
1221  * Args:
1222  *	up - Pointer to the tracks structure
1223  *
1224  * Return:
1225  *	Nothing
1226  */
1227 STATIC void
1228 fcddb_clear_tracks(cddb_tracks_t *tsp)
1229 {
1230 	int	i;
1231 
1232 	for (i = 0; i < MAXTRACK; i++)
1233 		fcddb_clear_track(&tsp->track[i]);
1234 
1235 	tsp->count = 0;
1236 }
1237 
1238 
1239 /*
1240  * fcddb_clear_disc
1241  *	Clear a disc structure
1242  *
1243  * Args:
1244  *	up - Pointer to the disc structure
1245  *
1246  * Return:
1247  *	Nothing
1248  */
1249 STATIC void
1250 fcddb_clear_disc(cddb_disc_t *dp)
1251 {
1252 	fcddb_idlist_t	*ip,
1253 			*nextp;
1254 
1255 	fcddb_clear_credits(&dp->credits);
1256 	fcddb_clear_segments(&dp->segments);
1257 	fcddb_clear_fullname(&dp->fullname);
1258 	fcddb_clear_tracks(&dp->tracks);
1259 
1260 	if (dp->category != NULL) {
1261 		MEM_FREE(dp->category);
1262 		dp->category = NULL;
1263 	}
1264 	if (dp->discid != NULL) {
1265 		MEM_FREE(dp->discid);
1266 		dp->discid = NULL;
1267 	}
1268 	if (dp->toc != NULL) {
1269 		MEM_FREE(dp->toc);
1270 		dp->toc = NULL;
1271 	}
1272 	if (dp->title != NULL) {
1273 		MEM_FREE(dp->title);
1274 		dp->title = NULL;
1275 	}
1276 	if (dp->notes != NULL) {
1277 		MEM_FREE(dp->notes);
1278 		dp->notes = NULL;
1279 	}
1280 	if (dp->revision != NULL) {
1281 		MEM_FREE(dp->revision);
1282 		dp->revision = NULL;
1283 	}
1284 	if (dp->submitter != NULL) {
1285 		MEM_FREE(dp->submitter);
1286 		dp->submitter = NULL;
1287 	}
1288 
1289 	dp->next = NULL;
1290 	dp->genre = NULL;
1291 	dp->control = NULL;
1292 
1293 	nextp = NULL;
1294 	for (ip = dp->idlist; ip != NULL; ip = nextp) {
1295 		nextp = ip->next;
1296 		MEM_FREE(ip);
1297 	}
1298 	dp->idlist = NULL;
1299 }
1300 
1301 
1302 /*
1303  * fcddb_clear_discs
1304  *	Clear a discs structure
1305  *
1306  * Args:
1307  *	up - Pointer to the discs structure
1308  *
1309  * Return:
1310  *	Nothing
1311  */
1312 STATIC void
1313 fcddb_clear_discs(cddb_discs_t *dsp)
1314 {
1315 	cddb_disc_t	*dp,
1316 			*nextp;
1317 
1318 	nextp = NULL;
1319 	for (dp = dsp->discs; dp != NULL; dp = nextp) {
1320 		nextp = dp->next;
1321 		fcddb_clear_disc(dp);
1322 		MEM_FREE(dp);
1323 	}
1324 
1325 	dsp->discs = NULL;
1326 	dsp->count = 0;
1327 }
1328 
1329 
1330 /*
1331  * fcddb_clear_control
1332  *	Clear a control structure
1333  *
1334  * Args:
1335  *	up - Pointer to the control structure
1336  *
1337  * Return:
1338  *	Nothing
1339  */
1340 STATIC void
1341 fcddb_clear_control(cddb_control_t *cp)
1342 {
1343 	if (cp->hostname != NULL) {
1344 		MEM_FREE(cp->hostname);
1345 		cp->hostname = NULL;
1346 	}
1347 	if (cp->clientname != NULL) {
1348 		MEM_FREE(cp->clientname);
1349 		cp->clientname = NULL;
1350 	}
1351 	if (cp->clientid != NULL) {
1352 		MEM_FREE(cp->clientid);
1353 		cp->clientid = NULL;
1354 	}
1355 	if (cp->clientver != NULL) {
1356 		MEM_FREE(cp->clientver);
1357 		cp->clientver = NULL;
1358 	}
1359 	fcddb_clear_userinfo(&cp->userinfo);
1360 	fcddb_clear_options(&cp->options);
1361 	fcddb_clear_disc(&cp->disc);
1362 	fcddb_clear_discs(&cp->discs);
1363 	fcddb_clear_genretree(&cp->genretree);
1364 	fcddb_clear_roletree(&cp->roletree);
1365 	fcddb_clear_urllist(&cp->urllist);
1366 }
1367 
1368 
1369 /*
1370  * fcddb_putentry
1371  *	Write an entry into a CDDB file, used by fcddb_write_cddb()
1372  *
1373  * Args:
1374  *	fp - FILE stream pointer
1375  *	idstr - The entry identifier
1376  *	entry - The entry string
1377  *
1378  * Return:
1379  *	Nothing
1380  */
1381 STATIC void
1382 fcddb_putentry(FILE *fp, char *idstr, char *entry)
1383 {
1384 	int	i,
1385 		n;
1386 	char	*cp,
1387 		tmpbuf[STR_BUF_SZ];
1388 
1389 	if (entry == NULL) {
1390 		/* Null entry */
1391 		(void) sprintf(tmpbuf, "%s=\r\n", idstr);
1392 		(void) fputs(tmpbuf, fp);
1393 	}
1394 	else {
1395 		/* Write entry to file, splitting into multiple lines
1396 		 * if necessary.  Special handling for newline and tab
1397 		 * characters.
1398 		 */
1399 		cp = entry;
1400 
1401 		do {
1402 			(void) sprintf(tmpbuf, "%s=", idstr);
1403 			(void) fputs(tmpbuf, fp);
1404 
1405 			n = FCDDB2_MIN((int) strlen(cp), STR_BUF_SZ);
1406 
1407 			for (i = 0; i < n; i++, cp++) {
1408 				switch (*cp) {
1409 				case '\n':
1410 					(void) fputs("\\n", fp);
1411 					break;
1412 				case '\t':
1413 					(void) fputs("\\t", fp);
1414 					break;
1415 				case '\\':
1416 					(void) fputs("\\\\", fp);
1417 					break;
1418 				default:
1419 					(void) fputc(*cp, fp);
1420 					break;
1421 				}
1422 			}
1423 
1424 			(void) fputs("\r\n", fp);
1425 			if (*cp == '\0')
1426 				break;
1427 
1428 		} while (n == STR_BUF_SZ);
1429 	}
1430 }
1431 
1432 
1433 /*
1434  * fcddb_write_cddb
1435  *	Write a CDDB file
1436  *
1437  * Args:
1438  *	path - file path
1439  *	dp - Pointer to disc structure
1440  *	issubmit - Whether this function is invoked during a submit
1441  *
1442  * Return:
1443  *	CddbResult status code
1444  */
1445 STATIC CddbResult
1446 fcddb_write_cddb(char *path, cddb_disc_t *dp, bool_t issubmit)
1447 {
1448 	cddb_control_t	*cp = (cddb_control_t *) dp->control;
1449 	FILE		*fp;
1450 	CddbResult	ret;
1451 	int		i,
1452 			revision,
1453 			ntrks;
1454 	unsigned int	discid_val = 0;
1455 	fcddb_idlist_t	*ip;
1456 	char		*dir,
1457 			*up,
1458 			idstr[12],
1459 			tmpbuf[STR_BUF_SZ * 2];
1460 	unsigned int	addr[MAXTRACK];
1461 
1462 	if (cp == NULL)
1463 		return Cddb_E_INVALIDARG;
1464 
1465 	(void) sscanf(dp->discid, "%x", (unsigned int *) &discid_val);
1466 
1467 	dir = fcddb_dirname(path);
1468 	FCDDBDBG(fcddb_errfp, "fcddb_write_cddb: making %s\n", dir);
1469 	if ((ret = fcddb_mkdir(dir, 0755)) != Cddb_OK)
1470 		return (ret);
1471 
1472 	(void) unlink(path);
1473 
1474 	FCDDBDBG(fcddb_errfp, "fcddb_write_cddb: writing %s\n", path);
1475 	if ((fp = fopen(path, "w")) == NULL) {
1476 		FCDDBDBG(fcddb_errfp,
1477 			"fcddb_write_cddb: cannot open file for writing: "
1478 			"errno=%d\n", errno);
1479 		return CDDBTRNCannotCreateFile;
1480 	}
1481 
1482 	/* File header */
1483 	(void) fputs(
1484 		"# xmcd CD database file\r\n"
1485 		"# Copyright (C) 2001 CDDB, Inc.\r\n#\r\n",
1486 		fp
1487 	);
1488 
1489 	ntrks = fcddb_parse_toc((char *) dp->toc, addr);
1490 	if (ntrks == 0 || ntrks != (int) (discid_val & 0xff)) {
1491 		(void) fclose(fp);
1492 		(void) unlink(path);
1493 		return Cddb_E_INVALIDARG;
1494 	}
1495 
1496 	/* Track frame offsets */
1497 	(void) fputs("# Track frame offsets:\r\n", fp);
1498 	for (i = 0; i < ntrks; i++) {
1499 		(void) sprintf(tmpbuf, "#\t%u\r\n", addr[i]);
1500 		(void) fputs(tmpbuf, fp);
1501 	}
1502 
1503 	/* Disc length */
1504 	(void) sprintf(tmpbuf, "#\r\n# Disc length: %u seconds\r\n#\r\n",
1505 		       addr[ntrks] / FRAME_PER_SEC);
1506 	(void) fputs(tmpbuf, fp);
1507 
1508 	/* Revision */
1509 	if (dp->revision == NULL)
1510 		revision = 1;
1511 	else {
1512 		revision = atoi((char *) dp->revision);
1513 		if (revision >= 0 && issubmit)
1514 			revision++;
1515 	}
1516 	(void) sprintf(tmpbuf, "# Revision: %d\r\n", revision);
1517 	(void) fputs(tmpbuf, fp);
1518 
1519 	/* Submitter */
1520 	if (issubmit || dp->submitter == NULL)
1521 		(void) sprintf(tmpbuf, "# Submitted via: %s %s\r\n#\r\n",
1522 			       cp->clientname, cp->clientver);
1523 	else
1524 		(void) sprintf(tmpbuf, "# Submitted via: %s\r\n#\r\n",
1525 			       dp->submitter);
1526 	(void) fputs(tmpbuf, fp);
1527 
1528 	/* Disc IDs */
1529 	(void) sprintf(tmpbuf, "DISCID=%s", dp->discid);
1530 	i = 1;
1531 	for (ip = dp->idlist; ip != NULL; ip = ip->next) {
1532 		if (ip->discid == discid_val)
1533 			/* This is our own ID, which we already processed */
1534 			continue;
1535 
1536 		if (i == 0)
1537 			(void) sprintf(tmpbuf, "DISCID=%08x", ip->discid);
1538 		else
1539 			(void) sprintf(tmpbuf, "%s,%08x", tmpbuf, ip->discid);
1540 
1541 		i++;
1542 		if (i == 8) {
1543 			(void) strcat(tmpbuf, "\r\n");
1544 			(void) fputs(tmpbuf, fp);
1545 			i = 0;
1546 		}
1547 	}
1548 	if (i != 0) {
1549 		(void) strcat(tmpbuf, "\r\n");
1550 		(void) fputs(tmpbuf, fp);
1551 	}
1552 
1553 	/* Disc artist/title */
1554 	(void) sprintf(tmpbuf, "%s%s%s",
1555 		       (dp->fullname.name == NULL) ?
1556 				"" : dp->fullname.name,
1557 		       (dp->fullname.name != NULL && dp->title != NULL) ?
1558 				" / " : "",
1559 		       (dp->title == NULL) ?
1560 				"" : dp->title);
1561 	if ((up = fcddb_utf8_to_iso8859(tmpbuf)) == NULL) {
1562 		(void) fclose(fp);
1563 		(void) unlink(path);
1564 		return CDDBTRNOutOfMemory;
1565 	}
1566 	fcddb_putentry(fp, "DTITLE", up);
1567 	MEM_FREE(up);
1568 
1569 	/* Track titles */
1570 	for (i = 0; i < ntrks; i++) {
1571 		(void) sprintf(idstr, "TTITLE%u", i);
1572 		up = fcddb_utf8_to_iso8859(dp->tracks.track[i].title);
1573 		if (up == NULL) {
1574 			(void) fclose(fp);
1575 			(void) unlink(path);
1576 			return CDDBTRNOutOfMemory;
1577 		}
1578 		fcddb_putentry(fp, idstr, up);
1579 		MEM_FREE(up);
1580 	}
1581 
1582 	/* Disc notes */
1583 	if ((up = fcddb_utf8_to_iso8859(dp->notes)) == NULL) {
1584 		(void) fclose(fp);
1585 		(void) unlink(path);
1586 		return CDDBTRNOutOfMemory;
1587 	}
1588 	fcddb_putentry(fp, "EXTD", up);
1589 	MEM_FREE(up);
1590 
1591 	/* Track notes */
1592 	for (i = 0; i < ntrks; i++) {
1593 		up = fcddb_utf8_to_iso8859(dp->tracks.track[i].notes);
1594 		if (up == NULL) {
1595 			(void) fclose(fp);
1596 			(void) unlink(path);
1597 			return CDDBTRNOutOfMemory;
1598 		}
1599 		(void) sprintf(idstr, "EXTT%u", i);
1600 		fcddb_putentry(fp, idstr, up);
1601 		MEM_FREE(up);
1602 	}
1603 
1604 	/* Track program sequence */
1605 	fcddb_putentry(fp, "PLAYORDER", NULL);
1606 
1607 	(void) fclose(fp);
1608 
1609 	return Cddb_OK;
1610 }
1611 
1612 
1613 /*
1614  * fcddb_parse_cddb_data
1615  *	Parse CDDB data and fill the disc structure
1616  *
1617  * Args:
1618  *	cp - Pointer to the control structure
1619  *	buf - CDDB data
1620  *	discid - The disc ID
1621  *	category - The category
1622  *	toc - The TOC
1623  *	wrcache - Whether to write to cache
1624  *	dp - Pointer to disc structure
1625  *
1626  * Return:
1627  *	CddbResult status code
1628  */
1629 STATIC CddbResult
1630 fcddb_parse_cddb_data(
1631 	cddb_control_t	*cp,
1632 	char		*buf,
1633 	char		*discid,
1634 	char		*category,
1635 	char		*toc,
1636 	bool_t		wrcache,
1637 	cddb_disc_t	*dp
1638 )
1639 {
1640 	char		*p,
1641 			*q,
1642 			*r,
1643 			*up,
1644 			filepath[FILE_PATH_SZ];
1645 	fcddb_idlist_t	*ip;
1646 	int		n;
1647 	unsigned int	discid_val = 0;
1648 	CddbResult	ret;
1649 
1650 	FCDDBDBG(fcddb_errfp, "fcddb_parse_cddb_data: %s/%s\n",
1651 		 category, discid);
1652 
1653 	(void) sscanf(discid, "%x", (unsigned int *) &discid_val);
1654 
1655 	ret = Cddb_OK;
1656 	p = buf;
1657 	for (;;) {
1658 		SKIP_SPC(p);
1659 
1660 		if (*p == '.' || *p == '\0')
1661 			break;			/* Done */
1662 
1663 		if ((q = strchr(p, '\n')) != NULL)
1664 			*q = '\0';
1665 
1666 		n = strlen(p) - 1;
1667 		if (*(p + n) == '\r')
1668 			*(p + n) = '\0';	/* Zap carriage return */
1669 
1670 		if (*p == '#') {		/* Comment */
1671 			if (strncmp(p, "# Revision: ", 12) == 0)
1672 				dp->revision = fcddb_strdup(p + 12);
1673 			else if (strncmp(p, "# Submitted via: ", 17) == 0)
1674 				dp->submitter = fcddb_strdup(p + 17);
1675 		}
1676 		else if (strncmp(p, "DISCID=", 7) == 0) {
1677 			p += 7;
1678 			while ((r = strchr(p, ',')) != NULL) {
1679 				*r = '\0';
1680 				ip = (fcddb_idlist_t *) MEM_ALLOC(
1681 					"idlist",
1682 					sizeof(fcddb_idlist_t)
1683 				);
1684 				if (ip == NULL) {
1685 					ret = CDDBTRNOutOfMemory;
1686 					break;
1687 				}
1688 				(void) sscanf(p, "%x",
1689 					      (unsigned int *) &ip->discid);
1690 				ip->next = dp->idlist;
1691 				dp->idlist = ip;
1692 				p = r+1;
1693 			}
1694 			if (ret != Cddb_OK)
1695 				break;
1696 
1697 			ip = (fcddb_idlist_t *) MEM_ALLOC(
1698 				"idlist",
1699 				sizeof(fcddb_idlist_t)
1700 			);
1701 			if (ip == NULL) {
1702 				ret = CDDBTRNOutOfMemory;
1703 				break;
1704 			}
1705 			(void) sscanf(p, "%x", (unsigned int *) &ip->discid);
1706 			ip->next = dp->idlist;
1707 			dp->idlist = ip;
1708 		}
1709 		else if (strncmp(p, "DTITLE=", 7) == 0) {
1710 			/* Disc artist / title */
1711 			p += 7;
1712 
1713 			fcddb_line_filter(p);
1714 			if ((up = fcddb_iso8859_to_utf8(p)) == NULL) {
1715 				ret = CDDBTRNOutOfMemory;
1716 				break;
1717 			}
1718 
1719 			/* Put it all in artist field first,
1720 			 * we'll separate artist and title later.
1721 			 */
1722 			if (dp->fullname.name == NULL) {
1723 				dp->fullname.name = (char *) MEM_ALLOC(
1724 					"dp->fullname.name",
1725 					strlen(up) + 1
1726 				);
1727 				if (dp->fullname.name != NULL)
1728 					dp->fullname.name[0] = '\0';
1729 			}
1730 			else {
1731 				dp->fullname.name = (char *) MEM_REALLOC(
1732 					"dp->fullname.name",
1733 					dp->fullname.name,
1734 					strlen(dp->fullname.name) +
1735 						strlen(up) + 1
1736 				);
1737 			}
1738 
1739 			if (dp->fullname.name == NULL) {
1740 				MEM_FREE(up);
1741 				ret = CDDBTRNOutOfMemory;
1742 				break;
1743 			}
1744 
1745 			(void) fcddb_strcat(dp->fullname.name, up);
1746 			MEM_FREE(up);
1747 		}
1748 		else if (sscanf(p, "TTITLE%d=", &n) > 0) {
1749 			/* Track title */
1750 			p = strchr(p, '=');
1751 			p++;
1752 
1753 			if (n >= (int) (discid_val & 0xff))
1754 				continue;
1755 
1756 			fcddb_line_filter(p);
1757 			if ((up = fcddb_iso8859_to_utf8(p)) == NULL) {
1758 				ret = CDDBTRNOutOfMemory;
1759 				break;
1760 			}
1761 
1762 			if (dp->tracks.track[n].title == NULL) {
1763 				dp->tracks.track[n].title = (char *) MEM_ALLOC(
1764 					"track[n].title",
1765 					strlen(up) + 1
1766 				);
1767 				if (dp->tracks.track[n].title != NULL)
1768 					dp->tracks.track[n].title[0] = '\0';
1769 			}
1770 			else {
1771 				dp->tracks.track[n].title = (char *)
1772 				    MEM_REALLOC(
1773 					"track[n].title",
1774 					dp->tracks.track[n].title,
1775 					strlen(dp->tracks.track[n].title) +
1776 						strlen(up) + 1
1777 				    );
1778 			}
1779 
1780 			if (dp->tracks.track[n].title == NULL) {
1781 				MEM_FREE(up);
1782 				ret = CDDBTRNOutOfMemory;
1783 				break;
1784 			}
1785 
1786 			(void) fcddb_strcat(dp->tracks.track[n].title, up);
1787 			MEM_FREE(up);
1788 		}
1789 		else if (strncmp(p, "EXTD=", 5) == 0) {
1790 			/* Disc notes */
1791 			p += 5;
1792 
1793 			if ((up = fcddb_iso8859_to_utf8(p)) == NULL) {
1794 				ret = CDDBTRNOutOfMemory;
1795 				break;
1796 			}
1797 
1798 			if (dp->notes == NULL) {
1799 				dp->notes = (char *) MEM_ALLOC(
1800 					"dp->notes",
1801 					strlen(up) + 1
1802 				);
1803 				if (dp->notes != NULL)
1804 					dp->notes[0] = '\0';
1805 			}
1806 			else {
1807 				dp->notes = (char *) MEM_REALLOC(
1808 					"dp->notes",
1809 					dp->notes,
1810 					strlen(dp->notes) +
1811 						strlen(up) + 1
1812 				);
1813 			}
1814 
1815 			if (dp->notes == NULL) {
1816 				MEM_FREE(up);
1817 				ret = CDDBTRNOutOfMemory;
1818 				break;
1819 			}
1820 
1821 			(void) fcddb_strcat(dp->notes, up);
1822 			MEM_FREE(up);
1823 		}
1824 		else if (sscanf(p, "EXTT%d=", &n) > 0) {
1825 			/* Track notes */
1826 			p = strchr(p, '=');
1827 			p++;
1828 
1829 			if (n >= (int) (discid_val & 0xff))
1830 				continue;
1831 
1832 			if ((up = fcddb_iso8859_to_utf8(p)) == NULL) {
1833 				ret = CDDBTRNOutOfMemory;
1834 				break;
1835 			}
1836 
1837 			if (dp->tracks.track[n].notes == NULL) {
1838 				dp->tracks.track[n].notes = (char *)
1839 				    MEM_ALLOC(
1840 					"track[n].notes",
1841 					strlen(up) + 1
1842 				    );
1843 
1844 				if (dp->tracks.track[n].notes != NULL)
1845 					dp->tracks.track[n].notes[0] = '\0';
1846 			}
1847 			else {
1848 				dp->tracks.track[n].notes = (char *)
1849 				    MEM_REALLOC(
1850 					"track[n].notes",
1851 					dp->tracks.track[n].notes,
1852 					strlen(dp->tracks.track[n].notes) +
1853 						strlen(up) + 1
1854 				    );
1855 			}
1856 
1857 			if (dp->tracks.track[n].notes == NULL) {
1858 				MEM_FREE(up);
1859 				ret = CDDBTRNOutOfMemory;
1860 				break;
1861 			}
1862 
1863 			(void) fcddb_strcat(dp->tracks.track[n].notes, up);
1864 			MEM_FREE(up);
1865 		}
1866 
1867 		p = q + 1;
1868 	}
1869 
1870 	if (ret != Cddb_OK)
1871 		return (ret);
1872 
1873 	/* Separate into disc artist & title fields */
1874 	if ((p = strchr(dp->fullname.name, '/')) != NULL &&
1875 	     p > (dp->fullname.name + 1) &&
1876 	    *(p-1) == ' ' && *(p+1) == ' ' && *(p+2) != '\0') {
1877 		q = dp->fullname.name;
1878 
1879 		/* Artist */
1880 		*(p-1) = '\0';
1881 		dp->fullname.name = (CddbStr) fcddb_strdup(q);
1882 
1883 		/* Title */
1884 		dp->title = (CddbStr) fcddb_strdup(p+2);
1885 
1886 		MEM_FREE(q);
1887 	}
1888 	else {
1889 		/* Move the whole string to title */
1890 		dp->title = dp->fullname.name;
1891 		dp->fullname.name = NULL;
1892 	}
1893 
1894 	/* Set up other fields */
1895 	dp->control = (void *) cp;
1896 	dp->discid = fcddb_strdup(discid);
1897 	dp->toc = (CddbStr) fcddb_strdup(toc);
1898 	dp->tracks.count = (long) (discid_val & 0xff);
1899 	dp->category = fcddb_strdup(category);
1900 	dp->genre = fcddb_genre_categ2gp(cp, category);
1901 
1902 	if (wrcache) {
1903 #ifdef __VMS
1904 		(void) sprintf(filepath, "%s.%s]%s.",
1905 			       cp->options.localcachepath, category, discid);
1906 #else
1907 		(void) sprintf(filepath, "%s/%s/%s",
1908 			       cp->options.localcachepath, category, discid);
1909 #endif
1910 
1911 		(void) fcddb_write_cddb(filepath, dp, FALSE);
1912 	}
1913 
1914 	return Cddb_OK;
1915 }
1916 
1917 
1918 /*
1919  * fcddb_connect
1920  *	Make a network connection to the server
1921  *
1922  * Args:
1923  *	host - Server host name
1924  *	port - Server port
1925  *	fd - Return file descriptor
1926  *
1927  * Return:
1928  *	CddbResult status code
1929  */
1930 #ifdef NOREMOTE
1931 /*ARGSUSED*/
1932 #endif
1933 STATIC CddbResult
1934 fcddb_connect(char *host, unsigned short port, int *fd)
1935 {
1936 #ifndef NOREMOTE
1937 	struct hostent		*hp;
1938 	struct in_addr		ad;
1939 	struct sockaddr_in	sin;
1940 
1941 	sin.sin_port = htons(port);
1942 	sin.sin_family = AF_INET;
1943 
1944 	FCDDBDBG(fcddb_errfp, "fcddb_connect: %s:%d\n", host, (int) port);
1945 	/* Find server host address */
1946 	if ((hp = gethostbyname(host)) != NULL) {
1947 		(void) memcpy((char *) &sin.sin_addr, hp->h_addr,
1948 			      hp->h_length);
1949 	}
1950 	else {
1951 		if ((ad.s_addr = inet_addr(host)) != (unsigned long) -1)
1952 			(void) memcpy((char *) &sin.sin_addr,
1953 				      (char *) &ad.s_addr, sizeof(ad.s_addr));
1954 		else
1955 			return CDDBTRNHostNotFound;
1956 	}
1957 
1958 	/* Open socket */
1959 	if ((*fd = SOCKET(AF_INET, SOCK_STREAM, 0)) < 0) {
1960 		*fd = -1;
1961 		return ((CddbResult) (errno == EINTR ?
1962 			CDDBTRNServerTimeout : CDDBTRNSockCreateErr));
1963 	}
1964 
1965 	/* Connect to server */
1966 	if (CONNECT(*fd, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
1967 		(void) close(*fd);
1968 		*fd = -1;
1969 		return ((CddbResult) (errno == EINTR ?
1970 			CDDBTRNServerTimeout : CDDBTRNSockOpenErr));
1971 	}
1972 
1973 	return Cddb_OK;
1974 #else
1975 	return CDDBCTLDisabled;
1976 #endif	/* NOREMOTE */
1977 }
1978 
1979 
1980 /*
1981  * fcddb_sendcmd
1982  *	Send a command to the server, and get server response
1983  *
1984  * Args:
1985  *	fd - file descriptor returned by fcddb_connect()
1986  *	buf - command and response buffer (this function may reallocate
1987  *	      the buffer depending on response data size)
1988  *	buflen - The buffer size
1989  *	isproxy - Whether we're using a proxy server
1990  *
1991  * Return:
1992  *	CddbResult status code
1993  */
1994 #ifdef NOREMOTE
1995 /*ARGSUSED*/
1996 #endif
1997 STATIC CddbResult
1998 fcddb_sendcmd(int fd, char **buf, size_t *buflen, bool_t isproxy)
1999 {
2000 #ifndef NOREMOTE
2001 	int	len,
2002 		tot,
2003 		ret,
2004 		rbuflen;
2005 	char	*p;
2006 
2007 	p = *buf;
2008 	len = strlen(p);
2009 	tot = 0;
2010 
2011 	/* Send command to server */
2012 	FCDDBDBG(fcddb_errfp,
2013 		 "fcddb_sendcmd: Sending command:\n------\n%s\n------\n",
2014 		 *buf);
2015 
2016 	while (len > 0) {
2017 		if ((ret = send(fd, p, len, 0)) < 0) {
2018 			return ((CddbResult) (errno == EINTR ?
2019 				CDDBTRNServerTimeout : CDDBTRNSendFailed));
2020 		}
2021 
2022 		p += ret;
2023 		tot += ret;
2024 		len -= ret;
2025 	}
2026 
2027 	if (tot == 0)
2028 		return CDDBTRNSendFailed;
2029 
2030 	/* Read server response to command */
2031 	p = *buf;
2032 	rbuflen = *buflen;
2033 
2034 	for (;;) {
2035 		/* Check remaining buffer size, enlarge if needed */
2036 		if (rbuflen <= 64) {
2037 			int	offset = p - *buf;
2038 
2039 			*buf = (char *) MEM_REALLOC(
2040 				"http_buf",
2041 				*buf,
2042 				*buflen + CDDBP_CMDLEN
2043 			);
2044 			if (*buf == NULL)
2045 				return CDDBTRNOutOfMemory;
2046 
2047 			rbuflen += CDDBP_CMDLEN;
2048 			*buflen += CDDBP_CMDLEN;
2049 			p = *buf + offset;
2050 		}
2051 
2052 		/* Receive response from server */
2053 		if ((len = recv(fd, p, rbuflen-1, 0)) < 0) {
2054 			return ((CddbResult) (errno == EINTR ?
2055 				CDDBTRNServerTimeout : CDDBTRNRecvFailed));
2056 		}
2057 
2058 		*(p + len) = '\0';	/* Mark end of data */
2059 		if (len == 0)
2060 			break;
2061 
2062 		rbuflen -= len;
2063 		p += len;
2064 	}
2065 
2066 	FCDDBDBG(fcddb_errfp,
2067 		 "fcddb_sendcmd: Server response:\n------\n%s\n------\n",
2068 		 *buf);
2069 
2070 	/* Check for proxy authorization failure */
2071 	if (isproxy && strncmp(*buf, "HTTP/", 5) == 0) {
2072 		p = strchr((*buf)+5, ' ');
2073 		if (p != NULL && isdigit((int) *(p+1)) &&
2074 		    (atoi(p+1) == HTTP_PROXYAUTH_FAIL)) {
2075 			/* Need proxy authorization */
2076 			return CDDBTRNHTTPProxyError;
2077 		}
2078 	}
2079 
2080 	return Cddb_OK;
2081 #else
2082 	return CDDBCTLDisabled;
2083 #endif	/* NOREMOTE */
2084 }
2085 
2086 
2087 /*
2088  * fcddb_disconnect
2089  *	Disconnect from a server
2090  *
2091  * Args:
2092  *	fd - File descriptor returned from fcddb_connect()
2093  *
2094  * Return:
2095  *	Nothing
2096  */
2097 STATIC void
2098 fcddb_disconnect(int fd)
2099 {
2100 	(void) close(fd);
2101 }
2102 
2103 
2104 /*
2105  * fcddb_query_cddb
2106  *	Send a query command to the server
2107  *
2108  * Args:
2109  *	cp - Pointer to the control structure
2110  *	discid - The disc ID
2111  *	toc - The TOC
2112  *	addr - Array of track offset frames
2113  *	conhost - Server host
2114  *	conport - Server port
2115  *	isproxy - Whether we're using a proxy server
2116  *	category - Return category string
2117  *	matchcode - Return match code
2118  *
2119  * Return:
2120  *	CddbResult status code
2121  */
2122 #ifdef NOREMOTE
2123 /*ARGSUSED*/
2124 #endif
2125 STATIC CddbResult
2126 fcddb_query_cddb(
2127 	cddb_control_t		*cp,
2128 	char			*discid,
2129 	char			*toc,
2130 	unsigned int		*addr,
2131 	char			*conhost,
2132 	unsigned short		conport,
2133 	bool_t			isproxy,
2134 	char			*category,
2135 	int			*matchcode
2136 )
2137 {
2138 	int		i;
2139 	unsigned int	discid_val = 0;
2140 	size_t		buflen;
2141 	FILE		*fp;
2142 	fcddb_gmap_t	*gmp;
2143 	cddb_disc_t	*dp;
2144 	char		*buf,
2145 			*p,
2146 			*q,
2147 			*r,
2148 			*s,
2149 			*categp = NULL,
2150 			filepath[FILE_PATH_SZ];
2151 	time_t		t;
2152 	struct stat	stbuf;
2153 #ifndef NOREMOTE
2154 	CddbResult	ret;
2155 	int		fd,
2156 			ntrks;
2157 	char		urlstr[HOST_NAM_SZ + FILE_PATH_SZ + 12];
2158 	bool_t		valid_resp;
2159 #endif
2160 
2161 	fcddb_clear_discs(&cp->discs);
2162 
2163 	(void) sscanf(discid, "%x", (unsigned int *) &discid_val);
2164 
2165 	buflen = CDDBP_CMDLEN;
2166 	if ((buf = (char *) MEM_ALLOC("http_buf", buflen)) == NULL) {
2167 		*matchcode = MATCH_NONE;
2168 		return CDDBTRNOutOfMemory;
2169 	}
2170 
2171 	t = time(NULL);
2172 
2173 	/* Try to locate the entry in the local cache */
2174 	for (gmp = fcddb_gmap_head; gmp != NULL; gmp = gmp->next) {
2175 #ifdef __VMS
2176 		(void) sprintf(filepath, "%s.%s]%s.",
2177 			       cp->options.localcachepath,
2178 			       gmp->cddb1name, discid);
2179 #else
2180 		(void) sprintf(filepath, "%s/%s/%s",
2181 			       cp->options.localcachepath,
2182 			       gmp->cddb1name, discid);
2183 #endif
2184 
2185 		if (stat(filepath, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
2186 			continue;
2187 
2188 #ifndef NOREMOTE
2189 		if ((cp->options.localcacheflags & CACHE_DONT_CONNECT) == 0 &&
2190 		    (t - stbuf.st_mtime) >
2191 			(int) (cp->options.localcachetimeout * SECS_PER_DAY)) {
2192 
2193 			/* If any potential cached match is expired,
2194 			 * force a server query.
2195 			 */
2196 			fcddb_clear_discs(&cp->discs);
2197 			break;
2198 		}
2199 #endif
2200 
2201 		fp = NULL;
2202 		if ((fp = fopen(filepath, "r")) != NULL &&
2203 		    fgets(buf, buflen, fp) != NULL &&
2204 		    strncmp(buf, "# xmcd", 6) == 0) {
2205 
2206 			/* Don't use fcddb_obj_alloc here because
2207 			 * we don't want CddbReleaseObject to
2208 			 * free it up.
2209 			 */
2210 			dp = (cddb_disc_t *) MEM_ALLOC(
2211 				"CddbDisc", sizeof(cddb_disc_t)
2212 			);
2213 			if (dp == NULL) {
2214 				MEM_FREE(buf);
2215 				*matchcode = MATCH_NONE;
2216 				return CDDBTRNOutOfMemory;
2217 			}
2218 			(void) memset(dp, 0, sizeof(cddb_disc_t));
2219 			(void) strcpy(dp->objtype, "CddbDisc");
2220 
2221 			/* Category: save and convert to genre */
2222 			dp->category = categp = fcddb_strdup(gmp->cddb1name);
2223 			dp->genre = fcddb_genre_categ2gp(cp, gmp->cddb1name);
2224 
2225 			/* Disc ID */
2226 			dp->discid = fcddb_strdup(discid);
2227 
2228 			/* Artist/title */
2229 			while (fgets(buf, buflen, fp) != NULL) {
2230 				if (buf[0] == '#' ||
2231 				    strncmp(buf, "DTITLE=", 7) != 0)
2232 					continue;
2233 
2234 				p = buf + 7;
2235 				fcddb_line_filter(p);
2236 
2237 				if ((q = strchr(p, '\r')) != NULL ||
2238 				    (q = strchr(p, '\n')) != NULL)
2239 					*q = '\0';
2240 
2241 				/* Put it all in artist first */
2242 				if (dp->fullname.name == NULL) {
2243 					dp->fullname.name = (char *)
2244 					MEM_ALLOC(
2245 						"dp->fullname.name",
2246 						strlen(p) + 1
2247 					);
2248 					if (dp->fullname.name != NULL)
2249 						dp->fullname.name[0] = '\0';
2250 				}
2251 				else {
2252 					dp->fullname.name = (char *)
2253 					MEM_REALLOC(
2254 						"dp->fullname.name",
2255 						dp->fullname.name,
2256 						strlen(dp->fullname.name) +
2257 							strlen(p) + 1
2258 					);
2259 				}
2260 
2261 				if (dp->fullname.name == NULL) {
2262 					MEM_FREE(buf);
2263 					*matchcode = MATCH_NONE;
2264 					return CDDBTRNOutOfMemory;
2265 				}
2266 
2267 				(void) fcddb_strcat(dp->fullname.name, p);
2268 			}
2269 
2270 			/* Separate into disc artist & title fields */
2271 			r = strchr(dp->fullname.name, '/');
2272 			if (r != NULL && r > (dp->fullname.name + 1) &&
2273 			    *(r-1) == ' ' && *(r+1) == ' '
2274 			    && *(r+2) != '\0') {
2275 				s = dp->fullname.name;
2276 
2277 				/* Artist */
2278 				*(r-1) = '\0';
2279 				dp->fullname.name = (CddbStr) fcddb_strdup(s);
2280 
2281 				/* Title */
2282 				dp->title = (CddbStr) fcddb_strdup(r+2);
2283 
2284 				MEM_FREE(s);
2285 			}
2286 			else {
2287 				/* Move the whole string to title */
2288 				dp->title = dp->fullname.name;
2289 				dp->fullname.name = NULL;
2290 			}
2291 
2292 			/* TOC */
2293 			dp->toc = (CddbStr) fcddb_strdup(toc);
2294 
2295 			/* Add to discs list */
2296 			dp->next = cp->discs.discs;
2297 			cp->discs.discs = dp;
2298 			cp->discs.count++;
2299 		}
2300 
2301 		if (fp != NULL)
2302 			(void) fclose(fp);
2303 	}
2304 
2305 	if (cp->discs.count == 1) {
2306 		/* Matched one local cache entry */
2307 		*matchcode = MATCH_EXACT;
2308 		(void) strcpy(category, categp);
2309 		fcddb_clear_discs(&cp->discs);
2310 		MEM_FREE(buf);
2311 
2312 		FCDDBDBG(fcddb_errfp,
2313 			 "\nfcddb_query_cddb: Exact match in cache %s/%s\n",
2314 			 category, discid);
2315 		return Cddb_OK;
2316 	}
2317 	else if (cp->discs.count > 1) {
2318 		/* Matched multiple local cache entries */
2319 		*matchcode = MATCH_MULTIPLE;
2320 		MEM_FREE(buf);
2321 
2322 		FCDDBDBG(fcddb_errfp,
2323 			 "\nfcddb_query_cddb: Multiple matches in cache\n");
2324 		return Cddb_OK;
2325 	}
2326 
2327 #ifdef NOREMOTE
2328 	*matchcode = MATCH_NONE;
2329 	MEM_FREE(buf);
2330 
2331 	FCDDBDBG(fcddb_errfp, "\nfcddb_query_cddb: No match in cache\n");
2332 	return Cddb_OK;
2333 #else
2334 	if ((cp->options.localcacheflags & CACHE_DONT_CONNECT) != 0) {
2335 		*matchcode = MATCH_NONE;
2336 		MEM_FREE(buf);
2337 
2338 		FCDDBDBG(fcddb_errfp,
2339 			"\nfcddb_query_cddb: No match in cache\n");
2340 		return Cddb_OK;
2341 	}
2342 
2343 	/*
2344 	 * Set up CDDB query command string
2345 	 */
2346 
2347 	FCDDBDBG(fcddb_errfp, "\nfcddb_query_cddb: server query\n");
2348 
2349 	ntrks = (int) (discid_val & 0xff);
2350 	urlstr[0] = '\0';
2351 
2352 	if (isproxy) {
2353 		(void) sprintf(urlstr, "http://%s:%d",
2354 			       CDDB_SERVER_HOST, HTTP_PORT);
2355 	}
2356 
2357 	(void) strcat(urlstr, CDDB_CGI_PATH);
2358 
2359 	(void) sprintf(buf, "GET %s?cmd=cddb+query+%s+%d",
2360 		       urlstr, discid, ntrks);
2361 
2362 	for (i = 0; i < ntrks; i++)
2363 		(void) sprintf(buf, "%s+%u", buf, addr[i]);
2364 
2365 	(void) sprintf(buf, "%s+%u&%s&proto=4 HTTP/1.0\r\n", buf,
2366 		       addr[ntrks] / FRAME_PER_SEC,
2367 		       fcddb_hellostr);
2368 
2369 	if (isproxy && fcddb_auth_buf != NULL) {
2370 		(void) sprintf(buf, "%sProxy-Authorization: Basic %s\r\n", buf,
2371 			       fcddb_auth_buf);
2372 	}
2373 
2374 	(void) sprintf(buf, "%s%s%s\r\n%s\r\n", buf,
2375 		       "Host: ", CDDB_SERVER_HOST,
2376 		       fcddb_extinfo);
2377 
2378 	/* Open network connection to server */
2379 	if ((ret = fcddb_connect(conhost, conport, &fd)) != Cddb_OK) {
2380 		if (buf != NULL)
2381 			MEM_FREE(buf);
2382 		*matchcode = MATCH_NONE;
2383 		return (ret);
2384 	}
2385 
2386 	/*
2387 	 * Send the command to the CDDB server and get response code
2388 	 */
2389 	if ((ret = fcddb_sendcmd(fd, &buf, &buflen, isproxy)) != Cddb_OK) {
2390 		if (buf != NULL)
2391 			MEM_FREE(buf);
2392 		*matchcode = MATCH_NONE;
2393 		return (ret);
2394 	}
2395 
2396 	/* Close server connection */
2397 	fcddb_disconnect(fd);
2398 
2399 	/* Check for CDDB command response */
2400 	valid_resp = FALSE;
2401 	p = buf;
2402 	while ((q = strchr(p, '\n')) != NULL) {
2403 		*q = '\0';
2404 
2405 		if (STATCODE_CHECK(p)) {
2406 			valid_resp = TRUE;
2407 			*q = '\n';
2408 			break;
2409 		}
2410 
2411 		*q = '\n';
2412 		p = q + 1;
2413 		SKIP_SPC(p);
2414 	}
2415 
2416 	if (!valid_resp) {
2417 		/* Server error */
2418 		return CDDBTRNBadResponseSyntax;
2419 	}
2420 
2421 	/*
2422 	 * Check command status
2423 	 */
2424 
2425 	switch (STATCODE_1ST(p)) {
2426 	case STAT_GEN_OK:
2427 	case STAT_GEN_OKCONT:
2428 		switch (STATCODE_3RD(p)) {
2429 		case STAT_QURY_EXACT:
2430 			if (STATCODE_2ND(p) == STAT_SUB_READY) {
2431 				/* Single exact match */
2432 				p += 4;
2433 				SKIP_SPC(p);
2434 
2435 				if (*p == '\0' ||
2436 				    (q = strchr(p, ' ')) == NULL) {
2437 					/* Invalid server response */
2438 					ret = CDDBTRNBadResponseSyntax;
2439 					break;
2440 				}
2441 				*q = '\0';
2442 
2443 				/* Get category */
2444 				(void) strncpy(category, p,
2445 						FILE_BASE_SZ - 1);
2446 				category[FILE_BASE_SZ - 1] = '\0';
2447 
2448 				*q = ' ';
2449 
2450 				*matchcode = MATCH_EXACT;
2451 				ret = Cddb_OK;
2452 				break;
2453 			}
2454 			/*FALLTHROUGH*/
2455 
2456 		case STAT_QURY_INEXACT:
2457 			if (STATCODE_2ND(p) != STAT_SUB_OUTPUT) {
2458 				/* Invalid server response */
2459 				ret = CDDBTRNBadResponseSyntax;
2460 				break;
2461 			}
2462 
2463 			ret = Cddb_OK;
2464 
2465 			p += 4;
2466 			SKIP_SPC(p);
2467 			if (*p == '\0' || (q = strchr(p, '\n')) == NULL) {
2468 				/* Invalid server response */
2469 				ret = CDDBTRNBadResponseSyntax;
2470 				break;
2471 			}
2472 
2473 			for (;;) {
2474 				p = q + 1;
2475 				SKIP_SPC(p);
2476 				if ((q = strchr(p, '\n')) != NULL)
2477 					*q = '\0';
2478 
2479 				if (*p == '.' || *p == '\0')
2480 					/* Done */
2481 					break;
2482 
2483 				/* Zap carriage return if needed */
2484 				i = strlen(p) - 1;
2485 				if (*(p+i) == '\r')
2486 					*(p+i) = '\0';
2487 
2488 				/* Don't use fcddb_obj_alloc here because
2489 				 * we don't want CddbReleaseObject to
2490 				 * free it up.
2491 				 */
2492 				dp = (cddb_disc_t *) MEM_ALLOC(
2493 					"CddbDisc", sizeof(cddb_disc_t)
2494 				);
2495 				if (dp == NULL) {
2496 					*matchcode = MATCH_NONE;
2497 					return CDDBTRNOutOfMemory;
2498 				}
2499 				(void) memset(dp, 0, sizeof(cddb_disc_t));
2500 				(void) strcpy(dp->objtype, "CddbDisc");
2501 
2502 				r = p;
2503 				if ((s = strchr(r, ' ')) == NULL) {
2504 					/* Invalid server response */
2505 					MEM_FREE(dp);
2506 					*matchcode = MATCH_NONE;
2507 					return CDDBTRNBadResponseSyntax;
2508 				}
2509 				*s = '\0';
2510 
2511 				/* If the server returned an unrecognized
2512 				 * category, add it to the table.
2513 				 */
2514 				if (fcddb_genre_categ2id(r) == NULL)
2515 					fcddb_genre_addcateg(r);
2516 
2517 				/* Category: save and convert to genre */
2518 				dp->category = fcddb_strdup(r);
2519 				dp->genre = fcddb_genre_categ2gp(cp, r);
2520 
2521 				*s = ' ';
2522 				r = s + 1;
2523 				SKIP_SPC(r);
2524 				if ((s = strchr(r, ' ')) == NULL) {
2525 					/* Invalid server response */
2526 					MEM_FREE(dp);
2527 					*matchcode = MATCH_NONE;
2528 					return CDDBTRNBadResponseSyntax;
2529 				}
2530 				*s = '\0';
2531 
2532 				/* Disc ID */
2533 				dp->discid = fcddb_strdup(r);
2534 
2535 				*s = ' ';
2536 				r = s + 1;
2537 				SKIP_SPC(r);
2538 				if (*r == '\0') {
2539 					/* Invalid server response */
2540 					MEM_FREE(dp);
2541 					*matchcode = MATCH_NONE;
2542 					return CDDBTRNBadResponseSyntax;
2543 				}
2544 
2545 				/* Artist / Title */
2546 
2547 				/* Put it all in artist first */
2548 				dp->fullname.name = (CddbStr) fcddb_strdup(r);
2549 
2550 				/* Separate into disc artist & title fields */
2551 				r = strchr(dp->fullname.name, '/');
2552 				if (r != NULL && r > (dp->fullname.name + 1) &&
2553 				    *(r-1) == ' ' && *(r+1) == ' '
2554 				    && *(r+2) != '\0') {
2555 					s = dp->fullname.name;
2556 
2557 					/* Artist */
2558 					*(r-1) = '\0';
2559 					dp->fullname.name =
2560 						(CddbStr) fcddb_strdup(s);
2561 
2562 					/* Title */
2563 					dp->title =
2564 						(CddbStr) fcddb_strdup(r+2);
2565 
2566 					MEM_FREE(s);
2567 				}
2568 				else {
2569 					/* Move the whole string to title */
2570 					dp->title = dp->fullname.name;
2571 					dp->fullname.name = NULL;
2572 				}
2573 
2574 				/* TOC */
2575 				dp->toc = (CddbStr) fcddb_strdup(toc);
2576 
2577 				if (dp != NULL) {
2578 					/* Add to discs list */
2579 					dp->next = cp->discs.discs;
2580 					cp->discs.discs = dp;
2581 					cp->discs.count++;
2582 				}
2583 			}
2584 
2585 			*matchcode = MATCH_MULTIPLE;
2586 			break;
2587 
2588 		case STAT_QURY_NONE:
2589 			ret = Cddb_OK;
2590 			break;
2591 		}
2592 		break;
2593 
2594 	case STAT_GEN_CLIENT:
2595 		switch (STATCODE_3RD(p)) {
2596 		case STAT_QURY_AUTHFAIL:
2597 			if (STATCODE_2ND(p) != STAT_SUB_CLOSE) {
2598 				/* Invalid server response */
2599 				ret = CDDBTRNBadResponseSyntax;
2600 				break;
2601 			}
2602 
2603 			if (isproxy) {
2604 				ret = CDDBTRNHTTPProxyError;
2605 				break;
2606 			}
2607 			break;
2608 
2609 		default:
2610 			break;
2611 		}
2612 		/*FALLTHROUGH*/
2613 
2614 	case STAT_GEN_OKFAIL:
2615 	case STAT_GEN_INFO:
2616 	case STAT_GEN_ERROR:
2617 	default:
2618 		/* Error */
2619 		ret = CDDBTRNRecordNotFound;
2620 		break;
2621 	}
2622 
2623 	if (buf != NULL)
2624 		MEM_FREE(buf);
2625 
2626 	if (ret != Cddb_OK)
2627 		*matchcode = MATCH_NONE;
2628 
2629 	return (ret);
2630 #endif	/* NOREMOTE */
2631 }
2632 
2633 
2634 /*
2635  * fcddb_region_init
2636  *	Initialize the region list
2637  *
2638  * Args:
2639  *	cp - Pointer to the control structure
2640  *
2641  * Return:
2642  *	CddbResult status code
2643  */
2644 STATIC CddbResult
2645 fcddb_region_init(cddb_control_t *cp)
2646 {
2647 	cddb_region_t	*rp,
2648 			*prevp;
2649 	int		i;
2650 
2651 	prevp = NULL;
2652 	for (i = 0; fcddb_region_tbl[i].id != NULL; i++) {
2653 		rp = &fcddb_region_tbl[i];
2654 		(void) strcpy(rp->objtype, "CddbRegion");
2655 
2656 		if (i == 0)
2657 			cp->regionlist.regions = rp;
2658 		else
2659 			prevp->next = rp;
2660 
2661 		cp->regionlist.count++;
2662 		prevp = rp;
2663 	}
2664 
2665 	return Cddb_OK;
2666 }
2667 
2668 
2669 /*
2670  * fcddb_language_init
2671  *	Initialize the language list
2672  *
2673  * Args:
2674  *	cp - Pointer to the control structure
2675  *
2676  * Return:
2677  *	CddbResult status code
2678  */
2679 STATIC CddbResult
2680 fcddb_language_init(cddb_control_t *cp)
2681 {
2682 	cddb_language_t	*lp,
2683 			*prevp;
2684 	int		i;
2685 
2686 	prevp = NULL;
2687 	for (i = 0; fcddb_language_tbl[i].id != NULL; i++) {
2688 		lp = &fcddb_language_tbl[i];
2689 		(void) strcpy(lp->objtype, "CddbLanguage");
2690 
2691 		if (i == 0)
2692 			cp->languagelist.languages = lp;
2693 		else
2694 			prevp->next = lp;
2695 
2696 		cp->languagelist.count++;
2697 		prevp = lp;
2698 	}
2699 
2700 	return Cddb_OK;
2701 }
2702 
2703 
2704 /*
2705  * fcddb_genre_init
2706  *	Initialize the genre tree
2707  *
2708  * Args:
2709  *	cp - Pointer to the control structure
2710  *
2711  * Return:
2712  *	CddbResult status code
2713  */
2714 STATIC CddbResult
2715 fcddb_genre_init(cddb_control_t *cp)
2716 {
2717 	fcddb_gmap_t	*gmp = NULL,
2718 			*gmp2 = NULL;
2719 	cddb_genre_t	*gp,
2720 			*gp2,
2721 			*gp3;
2722 	FILE		*fp;
2723 	int		i,
2724 			mapvers = 0;
2725 	unsigned int	flags;
2726 	char		*p,
2727 			*q,
2728 			mapfile[FILE_PATH_SZ],
2729 			buf[STR_BUF_SZ * 2],
2730 			cddb1name[20],
2731 			meta_id[8],
2732 			meta_name[80],
2733 			sub_id[8],
2734 			sub_name[80];
2735 	bool_t		use_builtin = TRUE;
2736 
2737 #ifdef __VMS
2738 	(void) sprintf(mapfile, "%s]genre.map", cp->options.localcachepath);
2739 #else
2740 	(void) sprintf(mapfile, "%s/genre.map", cp->options.localcachepath);
2741 #endif
2742 
2743 	if ((fp = fopen(mapfile, "r")) != NULL) {
2744 		FCDDBDBG(fcddb_errfp, "fcddb_genre_init: reading %s\n",
2745 			 mapfile);
2746 
2747 		for (i = 1; fgets(buf, sizeof(buf)-1, fp) != NULL; i++) {
2748 			/* Make sure string is null terminated */
2749 			buf[sizeof(buf)-1] = '\0';
2750 
2751 			/* Zap newline if necessary */
2752 			if (buf[strlen(buf)-1] == '\n')
2753 				buf[strlen(buf)-1] = '\0';
2754 
2755 			if (buf[0] == '#' || buf[0] == '\n' || buf[0] == '\r'){
2756 				/* Comment or blank line */
2757 				(void) sscanf(buf, "# version=%d", &mapvers);
2758 				continue;
2759 			}
2760 
2761 			if (mapvers != GENREMAP_VERSION) {
2762 				(void) fprintf(fcddb_errfp,
2763 					    "Ignored invalid genre map "
2764 					    "table file: %s\n",
2765 					    mapfile
2766 				);
2767 				break;
2768 			}
2769 
2770 			use_builtin = FALSE;
2771 
2772 			/* Read file entry */
2773 			q = buf;
2774 			if ((p = strchr(q, ':')) == NULL) {
2775 				(void) fprintf(fcddb_errfp,
2776 					"Genre map table file: %s\n"
2777 					"Error in line %d.\n",
2778 					mapfile, i);
2779 				continue;
2780 			}
2781 			*p = '\0';
2782 			strncpy(cddb1name, q, sizeof(cddb1name)-1);
2783 			cddb1name[sizeof(cddb1name)-1] = '\0';
2784 
2785 			q = p+1;
2786 			if ((p = strchr(q, ':')) == NULL) {
2787 				(void) fprintf(fcddb_errfp,
2788 					"Genre map table file: %s\n"
2789 					"Error in line %d.\n",
2790 					mapfile, i);
2791 				continue;
2792 			}
2793 			*p = '\0';
2794 			strncpy(meta_id, q, sizeof(meta_id)-1);
2795 			meta_id[sizeof(meta_id)-1] = '\0';
2796 
2797 			q = p+1;
2798 			if ((p = strchr(q, ':')) == NULL) {
2799 				(void) fprintf(fcddb_errfp,
2800 					"Genre map table file: %s\n"
2801 					"Error in line %d.\n",
2802 					mapfile, i);
2803 				continue;
2804 			}
2805 			*p = '\0';
2806 			strncpy(meta_name, q, sizeof(meta_name)-1);
2807 			meta_name[sizeof(meta_name)-1] = '\0';
2808 
2809 			q = p+1;
2810 			if ((p = strchr(q, ':')) == NULL) {
2811 				(void) fprintf(fcddb_errfp,
2812 					"Genre map table file: %s\n"
2813 					"Error in line %d.\n",
2814 					mapfile, i);
2815 				continue;
2816 			}
2817 			*p = '\0';
2818 			strncpy(sub_id, q, sizeof(sub_id)-1);
2819 			sub_id[sizeof(sub_id)-1] = '\0';
2820 
2821 			q = p+1;
2822 			if ((p = strchr(q, ':')) == NULL) {
2823 				(void) fprintf(fcddb_errfp,
2824 					"Genre map table file: %s\n"
2825 					"Error in line %d.\n",
2826 					mapfile, i);
2827 				continue;
2828 			}
2829 			*p = '\0';
2830 			strncpy(sub_name, q, sizeof(sub_name)-1);
2831 			sub_name[sizeof(sub_name)-1] = '\0';
2832 
2833 			q = p+1;
2834 			if (sscanf(q, "%x", (unsigned int *) &flags) != 1) {
2835 				(void) fprintf(fcddb_errfp,
2836 					"Genre map table file: %s\n"
2837 					"Error in line %d.\n",
2838 					mapfile, i);
2839 				continue;
2840 			}
2841 
2842 			/* Allocate map entry */
2843 			gmp = (fcddb_gmap_t *) MEM_ALLOC(
2844 				"CddbGenremap", sizeof(fcddb_gmap_t)
2845 			);
2846 			if (gmp == NULL) {
2847 				(void) fclose(fp);
2848 				return CDDBTRNOutOfMemory;
2849 			}
2850 			(void) memset(gmp, 0, sizeof(fcddb_gmap_t));
2851 
2852 			gmp->cddb1name = fcddb_strdup(cddb1name);
2853 			gmp->meta.id = fcddb_strdup(meta_id);
2854 			gmp->meta.name = fcddb_strdup(meta_name);
2855 			gmp->sub.id = fcddb_strdup(sub_id);
2856 			gmp->sub.name = fcddb_strdup(sub_name);
2857 			gmp->flags = flags | CDDB_GMAP_DYN;
2858 			gmp->next = NULL;
2859 
2860 			/* Add to list */
2861 			if (fcddb_gmap_head == NULL)
2862 				fcddb_gmap_head = gmp;
2863 			else
2864 				gmp2->next = gmp;
2865 
2866 			gmp2 = gmp;
2867 		}
2868 
2869 		(void) fclose(fp);
2870 	}
2871 
2872 	if (use_builtin) {
2873 		/* Can't initialize using genre table file, use the
2874 		 * built-in table instead.
2875 		 */
2876 		FCDDBDBG(fcddb_errfp,
2877 			 "fcddb_genre_init: using internal genre map table\n");
2878 
2879 		fcddb_gmap_head = &fcddb_genre_map[0];
2880 		for (i = 0; fcddb_genre_map[i].cddb1name != NULL; i++) {
2881 			gmp = &fcddb_genre_map[i];
2882 
2883 			/* Turn this table into a linked list */
2884 			gmp->next = &fcddb_genre_map[i+1];
2885 		}
2886 		if (gmp != NULL)
2887 			gmp->next = NULL;
2888 
2889 		/* Write out the genre table file */
2890 		if ((fp = fopen(mapfile, "w")) != NULL) {
2891 			FCDDBDBG(fcddb_errfp,
2892 				 "fcddb_genre_init: writing %s\n", mapfile);
2893 
2894 			/* Write out header */
2895 			(void) fprintf(fp,
2896 				"# xmcd %s.%s genre mapping table\n# %s\n",
2897 				VERSION_MAJ, VERSION_MIN, COPYRIGHT);
2898 			(void) fprintf(fp, "# version=%d\n", GENREMAP_VERSION);
2899 			(void) fprintf(fp, "#\n# %s:%s:%s:%s:%s:%s\n#\n",
2900 				"cddb1name",
2901 				"meta_id", "meta_name",
2902 				"sub_id", "sub_name",
2903 				"flags"
2904 			);
2905 
2906 			/* Write out map entries */
2907 			for (gmp = fcddb_gmap_head; gmp != NULL;
2908 			     gmp = gmp->next) {
2909 				(void) fprintf(fp, "%s:%s:%s:%s:%s:%x\n",
2910 					gmp->cddb1name,
2911 					gmp->meta.id,
2912 					gmp->meta.name,
2913 					gmp->sub.id,
2914 					gmp->sub.name,
2915 					gmp->flags
2916 				);
2917 			}
2918 
2919 			(void) fclose(fp);
2920 		}
2921 	}
2922 
2923 	/* Set up genre tree */
2924 	gp3 = NULL;
2925 	cp->genretree.count = 0;
2926 	for (gmp = fcddb_gmap_head; gmp != NULL; gmp = gmp->next) {
2927 		if ((gmp->flags & CDDB_GMAP_DUP) != 0)
2928 			continue;
2929 
2930 		/* Don't use fcddb_obj_alloc() here because we don't want
2931 		 * CddbReleaseObject() to free these.
2932 		 */
2933 		gp = (cddb_genre_t *) MEM_ALLOC(
2934 			"CddbGenre",
2935 			sizeof(cddb_genre_t)
2936 		);
2937 		if (gp == NULL)
2938 			return CDDBTRNOutOfMemory;
2939 
2940 		(void) memset(gp, 0, sizeof(cddb_genre_t));
2941 		(void) strcpy(gp->objtype, "CddbGenre");
2942 
2943 		gp->id   = (CddbStr) fcddb_strdup(gmp->meta.id);
2944 		gp->name = (CddbStr) fcddb_strdup(gmp->meta.name);
2945 
2946 		gp2 = (cddb_genre_t *) MEM_ALLOC(
2947 			"CddbGenre",
2948 			sizeof(cddb_genre_t)
2949 		);
2950 		if (gp2 == NULL)
2951 			return CDDBTRNOutOfMemory;
2952 
2953 		(void) memset(gp2, 0, sizeof(cddb_genre_t));
2954 		(void) strcpy(gp2->objtype, "CddbGenre");
2955 
2956 		gp2->id   = (CddbStr) fcddb_strdup(gmp->sub.id);
2957 		gp2->name = (CddbStr) fcddb_strdup(gmp->sub.name);
2958 		gp->next  = gp2;
2959 
2960 		if (cp->genretree.genres == NULL)
2961 			cp->genretree.genres = gp;
2962 		else
2963 			gp3->nextmeta = gp;
2964 
2965 		gp3 = gp;
2966 
2967 		cp->genretree.count++;
2968 	}
2969 
2970 	return Cddb_OK;
2971 }
2972 
2973 
2974 /*
2975  * fcddb_role_init
2976  *	Initialize the role tree
2977  *
2978  * Args:
2979  *	cp - Pointer to the control structure
2980  *
2981  * Return:
2982  *	CddbResult status code
2983  */
2984 STATIC CddbResult
2985 fcddb_role_init(cddb_control_t *cp)
2986 {
2987 	cddb_role_t	*rp,
2988 			*prevrp;
2989 	cddb_rolelist_t	*rlp,
2990 			*prevlp;
2991 	int		i;
2992 
2993 	prevrp = NULL;
2994 	prevlp = NULL;
2995 	rlp = NULL;
2996 	for (i = 0; fcddb_role_tbl[i].id != NULL; i++) {
2997 		rp = &fcddb_role_tbl[i];
2998 
2999 		if (rp->roletype == NULL)
3000 			continue;
3001 
3002 		if (rp->roletype[0] == 'm') {
3003 			rlp = (cddb_rolelist_t *) MEM_ALLOC(
3004 				"CddbRoleList", sizeof(cddb_rolelist_t)
3005 			);
3006 			if (rlp == NULL)
3007 				break;
3008 
3009 			(void) memset(rlp, 0, sizeof(cddb_rolelist_t));
3010 			(void) strcpy(rlp->objtype, "CddbRoleList");
3011 
3012 			rlp->metarole = rp;
3013 
3014 			if (cp->roletree.rolelists == NULL)
3015 				cp->roletree.rolelists = rlp;
3016 			else
3017 				prevlp->next = rlp;
3018 
3019 			cp->roletree.count++;
3020 			prevlp = rlp;
3021 		}
3022 		else if (rp->roletype[0] == 's') {
3023 			if (rlp == NULL)
3024 				break;
3025 
3026 			if (rlp->subroles == NULL)
3027 				rlp->subroles = rp;
3028 			else
3029 				prevrp->next = rp;
3030 
3031 			rlp->count++;
3032 			prevrp = rp;
3033 		}
3034 		(void) strcpy(rp->objtype, "CddbRole");
3035 	}
3036 
3037 	return Cddb_OK;
3038 }
3039 
3040 
3041 /*
3042  * fcddb_url_init
3043  *	Initialize the URL list
3044  *
3045  * Args:
3046  *	cp - Pointer to the control structure
3047  *
3048  * Return:
3049  *	CddbResult status code
3050  */
3051 STATIC CddbResult
3052 fcddb_url_init(cddb_control_t *cp)
3053 {
3054 	cddb_url_t	*up;
3055 
3056 	/* Hard-wire the CDDB web site */
3057 	up = (cddb_url_t *) MEM_ALLOC("CddbURL", sizeof(cddb_url_t));
3058 	if (up == NULL)
3059 		return CDDBTRNOutOfMemory;
3060 
3061 	(void) memset(up, 0, sizeof(cddb_url_t));
3062 	(void) strcpy(up->objtype, "CddbURL");
3063 	up->type = (CddbStr) fcddb_strdup("menu");
3064 	up->href = (CddbStr) fcddb_strdup("http://www.cddb.com/");
3065 	up->displaytext =
3066 		(CddbStr) fcddb_strdup("CDDB, the #1 Music Info Source");
3067 	up->category = (CddbStr) fcddb_strdup("Music Sites");
3068 
3069 	cp->urllist.urls = up;
3070 	cp->urllist.count++;
3071 
3072 	return Cddb_OK;
3073 }
3074 
3075 
3076 /*
3077  * fcddb_region_halt
3078  *	Clean up region table
3079  *
3080  * Args:
3081  *	None
3082  *
3083  * Return:
3084  *	Nothing
3085  */
3086 STATIC void
3087 fcddb_region_halt(void)
3088 {
3089 	int		i;
3090 	cddb_region_t	*rp;
3091 
3092 	for (i = 0; fcddb_region_tbl[i].id != NULL; i++) {
3093 		rp = &fcddb_region_tbl[i];
3094 		rp->objtype[0] = '\0';
3095 		rp->next = NULL;
3096 	}
3097 }
3098 
3099 
3100 /*
3101  * fcddb_language_halt
3102  *	Clean up language table
3103  *
3104  * Args:
3105  *	None
3106  *
3107  * Return:
3108  *	Nothing
3109  */
3110 STATIC void
3111 fcddb_language_halt(void)
3112 {
3113 	int		i;
3114 	cddb_language_t	*lp;
3115 
3116 	for (i = 0; fcddb_language_tbl[i].id != NULL; i++) {
3117 		lp = &fcddb_language_tbl[i];
3118 		lp->objtype[0] = '\0';
3119 		lp->next = NULL;
3120 	}
3121 }
3122 
3123 
3124 /*
3125  * fcddb_role_halt
3126  *	Clean up role table
3127  *
3128  * Args:
3129  *	None
3130  *
3131  * Return:
3132  *	Nothing
3133  */
3134 STATIC void
3135 fcddb_role_halt(void)
3136 {
3137 	int		i;
3138 	cddb_role_t	*rp;
3139 
3140 	for (i = 0; fcddb_role_tbl[i].id != NULL; i++) {
3141 		rp = &fcddb_role_tbl[i];
3142 		rp->objtype[0] = '\0';
3143 		rp->next = NULL;
3144 	}
3145 }
3146 
3147 
3148 /*
3149  * fcddb_onsig
3150  *	Generic signal handler, intended to just cause an interruption
3151  *	to a blocked system call and no more.
3152  *
3153  * Args:
3154  *	signo - signal number
3155  *
3156  * Return:
3157  *	Nothing
3158  */
3159 /*ARGSUSED*/
3160 void
3161 fcddb_onsig(int signo)
3162 {
3163 	/* Do nothing: we just want the side effect of causing the program
3164 	 * to return from a blocked system call with errno set to EINTR.
3165 	 */
3166 	FCDDBDBG(fcddb_errfp, "\nfcddb_onsig: Command timeout\n");
3167 
3168 #if !defined(USE_SIGACTION) && !defined(BSDCOMPAT)
3169 	(void) fcddb_signal(signo, fcddb_onsig);
3170 #endif
3171 }
3172 
3173 
3174 /*
3175  * fcddb_signal
3176  *	Wrapper around signal disposition functions to avoid
3177  *	the scattering of OS-dependent code.
3178  *
3179  * Args:
3180  *	Same as signal(2).
3181  *	sig - The signal number
3182  *	disp - The signal disposition
3183  *
3184  * Return:
3185  *	The previous signal disposition.
3186  */
3187 void
3188 (*fcddb_signal(int sig, void (*disp)(int)))(int)
3189 {
3190 #ifdef USE_SIGACTION
3191 	struct sigaction	sigact,
3192 				oldact;
3193 
3194 	sigact.sa_handler = disp;
3195 	sigemptyset(&sigact.sa_mask);
3196 	sigact.sa_flags = 0;
3197 	if (sigaction(sig, &sigact, &oldact) < 0) {
3198 		perror("util_signal: sigaction failed");
3199 		_exit(1);
3200 	}
3201 	return (oldact.sa_handler);
3202 #endif
3203 #ifdef USE_SIGSET
3204 	return (sigset(sig, disp));
3205 #endif
3206 #ifdef USE_SIGNAL
3207 	return (signal(sig, disp));
3208 #endif
3209 }
3210 
3211 
3212 /*
3213  * fcddb_init
3214  *	Initialize the fcddb module
3215  *
3216  * Args:
3217  *	cp - Pointer to the control structure
3218  *
3219  * Return:
3220  *	CddbResult status code
3221  */
3222 CddbResult
3223 fcddb_init(cddb_control_t *cp)
3224 {
3225 	CddbResult	ret;
3226 	char		tmpuser[STR_BUF_SZ],
3227 			tmphost[HOST_NAM_SZ + STR_BUF_SZ];
3228 
3229 	/* Initialize debug message stream */
3230 	fcddb_errfp = stderr;
3231 
3232 	/* Initialize regions */
3233 	if ((ret = fcddb_region_init(cp)) != Cddb_OK)
3234 		return (ret);
3235 
3236 	/* Initialize languages */
3237 	if ((ret = fcddb_language_init(cp)) != Cddb_OK)
3238 		return (ret);
3239 
3240 	/* Initialize genres */
3241 	if ((ret = fcddb_genre_init(cp)) != Cddb_OK)
3242 		return (ret);
3243 
3244 	/* Initialize roles */
3245 	if ((ret = fcddb_role_init(cp)) != Cddb_OK)
3246 		return (ret);
3247 
3248 	/* Initialize URLs */
3249 	if ((ret = fcddb_url_init(cp)) != Cddb_OK)
3250 		return (ret);
3251 
3252 	/* Misc initializations */
3253 	fcddb_http_xlat(cp->userinfo.userhandle, tmpuser),
3254 	fcddb_http_xlat(cp->hostname, tmphost),
3255 
3256 	(void) sprintf(fcddb_hellostr, "hello=%.64s+%.64s+%s+%s",
3257 		       tmpuser, tmphost, cp->clientname, cp->clientver);
3258 	(void) sprintf(fcddb_extinfo,
3259 		       /* Believe it or not, this is how MS Internet Explorer
3260 		        * does it, so don't blame me...
3261 		        */
3262 		       "User-Agent: %s (compatible; %s %s)\r\nAccept: %s\r\n",
3263 		       "Mozilla/5.0",
3264 		       cp->clientname,
3265 		       cp->clientver,
3266 		       "text/plain");
3267 
3268 	/* For SOCKS support */
3269 	SOCKSINIT(cp->clientname);
3270 
3271 	return Cddb_OK;
3272 }
3273 
3274 
3275 /*
3276  * fcddb_halt
3277  *	Shut down the fcddb module
3278  *
3279  * Args:
3280  *	cp - Pointer to the control structure
3281  *
3282  * Return:
3283  *	CddbResult status code
3284  */
3285 /*ARGSUSED*/
3286 CddbResult
3287 fcddb_halt(cddb_control_t *cp)
3288 {
3289 	/* Clean up internal tables */
3290 	fcddb_region_halt();
3291 	fcddb_language_halt();
3292 	fcddb_role_halt();
3293 
3294 	if (fcddb_auth_buf != NULL) {
3295 		MEM_FREE(fcddb_auth_buf);
3296 		fcddb_auth_buf = NULL;
3297 	}
3298 
3299 	return Cddb_OK;
3300 }
3301 
3302 
3303 /*
3304  * fcddb_obj_alloc
3305  *	Allocate a CDDB object
3306  *
3307  * Args:
3308  *	objtype - The object type to allocate
3309  *	len - The size of the object
3310  *
3311  * Return:
3312  *	Pointer to allocated memory
3313  */
3314 void *
3315 fcddb_obj_alloc(char *objtype, size_t len)
3316 {
3317 	void	*retp;
3318 	char	*cp;
3319 
3320 	if (objtype == NULL || strlen(objtype) >= len)
3321 		return NULL;
3322 
3323 	retp = MEM_ALLOC(objtype, len);
3324 	if (retp != NULL) {
3325 		cp = (char *) retp;
3326 		(void) memset(cp, 0, len);
3327 		(void) sprintf(cp, "+%s", objtype);
3328 	}
3329 
3330 	return (retp);
3331 }
3332 
3333 
3334 /*
3335  * fcddb_obj_free
3336  *	Free CDDB object
3337  *
3338  * Args:
3339  *	obj - Pointer to object previously allocated via fcddb_obj_alloc()
3340  *
3341  * Return:
3342  *	Nothing
3343  */
3344 void
3345 fcddb_obj_free(void *obj)
3346 {
3347 	char	*cp = (char *) obj;
3348 
3349 	if (cp == NULL)
3350 		return;
3351 
3352 	if (cp[0] == '+') {
3353 		/* Need to add to this list if fcddb_obj_alloc is used
3354 		 * to allocate other types of objects
3355 		 */
3356 		if (strcmp(cp+1, "CddbControl") == 0)
3357 			fcddb_clear_control((cddb_control_t *) obj);
3358 		else if (strcmp(cp+1, "CddbDisc") == 0)
3359 			fcddb_clear_disc((cddb_disc_t *) obj);
3360 		else if (strcmp(cp+1, "CddbDiscs") == 0)
3361 			fcddb_clear_discs((cddb_discs_t *) obj);
3362 		else if (strcmp(cp+1, "CddbOptions") == 0)
3363 			fcddb_clear_options((cddb_options_t *) obj);
3364 		else if (strcmp(cp+1, "CddbUserInfo") == 0)
3365 			fcddb_clear_userinfo((cddb_userinfo_t *) obj);
3366 		else if (strcmp(cp+1, "CddbGenreTree") == 0)
3367 			fcddb_clear_genretree((cddb_genretree_t *) obj);
3368 		else if (strcmp(cp+1, "CddbRoleTree") == 0)
3369 			fcddb_clear_roletree((cddb_roletree_t *) obj);
3370 		else if (strcmp(cp+1, "CddbURLManager") == 0)
3371 			fcddb_clear_urlmanager((cddb_urlmanager_t *) obj);
3372 		else if (strcmp(cp+1, "CddbCredit") == 0)
3373 			fcddb_clear_credit((cddb_credit_t *) obj);
3374 		else if (strcmp(cp+1, "CddbSegment") == 0)
3375 			fcddb_clear_segment((cddb_segment_t *) obj);
3376 
3377 		MEM_FREE(obj);
3378 	}
3379 }
3380 
3381 
3382 /*
3383  * fcddb_runcmd
3384  *	Spawn an external command
3385  *
3386  * Args:
3387  *	cmd - The command string
3388  *
3389  * Return:
3390  *	The command exit status
3391  */
3392 int
3393 fcddb_runcmd(char *cmd)
3394 {
3395 	int		ret;
3396 #ifndef __VMS
3397 	int		fd,
3398 			saverr = 0;
3399 	pid_t		cpid;
3400 	waitret_t	wstat;
3401 	unsigned int	oa;
3402 	void		(*oh)(int);
3403 
3404 	FCDDBDBG(fcddb_errfp, "fcddb_runcmd: [%s]\n", cmd);
3405 
3406 	/* Fork child to invoke external command */
3407 	switch (cpid = FORK()) {
3408 	case 0:
3409 		break;
3410 
3411 	case -1:
3412 		/* Fork failed */
3413 		return EXE_ERR;
3414 
3415 	default:
3416 		/* Parent process: wait for child to exit */
3417 		oh = fcddb_signal(SIGALRM, fcddb_onsig);
3418 		oa = alarm(CMD_TIMEOUT_SECS);
3419 
3420 		errno = 0;
3421 		ret = WAITPID(cpid, &wstat, 0);
3422 		saverr = errno;
3423 
3424 		(void) alarm(oa);
3425 		(void) fcddb_signal(SIGALRM, oh);
3426 
3427 		if (ret < 0) {
3428 			if (saverr == EINTR) {
3429 				FCDDBDBG(fcddb_errfp,
3430 					"Timed out waiting for program "
3431 					"to exit (pid=%d)\n", (int) cpid);
3432 			}
3433 
3434 			ret = (saverr << 8);
3435 		}
3436 		else if (WIFEXITED(wstat))
3437 			ret = WEXITSTATUS(wstat);
3438 		else
3439 			ret = EXE_ERR;
3440 
3441 		FCDDBDBG(fcddb_errfp, "Command exit status %d\n", ret);
3442 		return (ret);
3443 	}
3444 
3445 	/* Child process */
3446 	for (fd = 3; fd < 255; fd++) {
3447 		/* Close unneeded file descriptors */
3448 		(void) close(fd);
3449 	}
3450 
3451 	/* Restore SIGTERM */
3452 	(void) fcddb_signal(SIGTERM, SIG_DFL);
3453 
3454 	/* Ignore SIGALRM */
3455 	(void) fcddb_signal(SIGALRM, SIG_IGN);
3456 
3457 	/* Exec a shell to run the command */
3458 	(void) execl(STR_SHPATH, STR_SHNAME, STR_SHARG, cmd, NULL);
3459 	_exit(EXE_ERR);
3460 	/*NOTREACHED*/
3461 #else
3462 	/* Do the command */
3463 	ret = system(cmd);
3464 
3465 	FCDDBDBG(fcddb_errfp, "Command exit status %d\n", ret);
3466 	return (ret);
3467 #endif	/* __VMS */
3468 }
3469 
3470 
3471 /*
3472  * fcddb_set_auth
3473  *	Set the HTTP/1.1 proxy-authorization string
3474  *
3475  * Args:
3476  *	name - The proxy user name
3477  *	passwd - The proxy password
3478  *
3479  * Return:
3480  *	CddbResult status code
3481  */
3482 CddbResult
3483 fcddb_set_auth(char *name, char *passwd)
3484 {
3485 	int	i,
3486 		j,
3487 		n1,
3488 		n2;
3489 	char	*buf;
3490 
3491 	if (name == NULL || passwd == NULL)
3492 		return Cddb_E_INVALIDARG;
3493 
3494 	i = strlen(name);
3495 	j = strlen(passwd);
3496 	n1 = i + j + 2;
3497 	n2 = (n1 * 4 / 3) + 8;
3498 
3499 	if ((buf = (char *) MEM_ALLOC("strbuf", n1)) == NULL)
3500 		return CDDBTRNOutOfMemory;
3501 
3502 	(void) sprintf(buf, "%s:%s", name, passwd);
3503 
3504 	(void) memset(name, 0, i);
3505 	(void) memset(passwd, 0, j);
3506 
3507 	if (fcddb_auth_buf != NULL)
3508 		MEM_FREE(fcddb_auth_buf);
3509 
3510 	fcddb_auth_buf = (char *) MEM_ALLOC("fcddb_auth_buf", n2);
3511 	if (fcddb_auth_buf == NULL)
3512 		return CDDBTRNOutOfMemory;
3513 
3514 	/* Base64 encode the name/password pair */
3515 	fcddb_b64encode(
3516 		(byte_t *) buf,
3517 		strlen(buf),
3518 		(byte_t *) fcddb_auth_buf,
3519 		FALSE
3520 	);
3521 
3522 	(void) memset(buf, 0, n1);
3523 	MEM_FREE(buf);
3524 	return Cddb_OK;
3525 }
3526 
3527 
3528 /*
3529  * fcddb_strdup
3530  *	fcddb internal version of strdup().
3531  *
3532  * Args:
3533  *	str - source string
3534  *
3535  * Return:
3536  *	Return string
3537  */
3538 char *
3539 fcddb_strdup(char *str)
3540 {
3541 	char	*buf;
3542 
3543 	if (str == NULL)
3544 		return NULL;
3545 
3546 	buf = (char *) MEM_ALLOC("strdup_buf", strlen(str) + 1);
3547 	if (buf != NULL)
3548 		(void) strcpy(buf, str);
3549 
3550 	return (buf);
3551 }
3552 
3553 
3554 /*
3555  * fcddb_basename
3556  *	fcddb internal version of basename()
3557  *
3558  * Args:
3559  *	path - File path
3560  *
3561  * Return:
3562  *	Base name of file path
3563  */
3564 char *
3565 fcddb_basename(char *path)
3566 {
3567 	char		*p;
3568 #ifdef __VMS
3569 	char		*q;
3570 #endif
3571 	static char	buf[FILE_PATH_SZ];
3572 
3573 	if (path == NULL)
3574 		return NULL;
3575 
3576 	if ((int) strlen(path) >= FILE_PATH_SZ)
3577 		/* Error: path name too long */
3578 		return NULL;
3579 
3580 	(void) strcpy(buf, path);
3581 
3582 	if ((p = strrchr(buf, DIR_END)) == NULL)
3583 		return (buf);
3584 
3585 	p++;
3586 
3587 #ifdef __VMS
3588 	if (*p == '\0') {
3589 		/* The supplied path is a directory - special handling */
3590 		*--p = '\0';
3591 		if ((q = strrchr(buf, DIR_SEP)) != NULL)
3592 			p = q + 1;
3593 		else if ((q = strrchr(buf, DIR_BEG)) != NULL)
3594 			p = q + 1;
3595 		else if ((q = strrchr(buf, ':')) != NULL)
3596 			p = q + 1;
3597 		else {
3598 			p = buf;
3599 			*p = '\0';
3600 		}
3601 	}
3602 #endif
3603 
3604 	return (p);
3605 }
3606 
3607 
3608 /*
3609  * fcddb_dirname
3610  *	fcddb internal version of dirname()
3611  *
3612  * Args:
3613  *	path - File path
3614  *
3615  * Return:
3616  *	Directory name of file path
3617  */
3618 char *
3619 fcddb_dirname(char *path)
3620 {
3621 	char		*p;
3622 #ifdef __VMS
3623 	char		*q;
3624 #endif
3625 	static char	buf[FILE_PATH_SZ];
3626 
3627 	if (path == NULL)
3628 		return NULL;
3629 
3630 	if ((int) strlen(path) >= FILE_PATH_SZ)
3631 		/* Error: path name too long */
3632 		return NULL;
3633 
3634 	(void) strcpy(buf, path);
3635 
3636 	if ((p = strrchr(buf, DIR_END)) == NULL)
3637 		return (buf);
3638 
3639 #ifdef __VMS
3640 	if (*++p == '\0') {
3641 		/* The supplied path is a directory - special handling */
3642 		if ((q = strrchr(buf, DIR_SEP)) != NULL) {
3643 			*q = DIR_END;
3644 			*++q = '\0';
3645 		}
3646 		else if ((q = strrchr(buf, ':')) != NULL)
3647 			*++q = '\0';
3648 		else
3649 			p = buf;
3650 	}
3651 	*p = '\0';
3652 #else
3653 	if (p == buf)
3654 		*++p = '\0';
3655 	else
3656 		*p = '\0';
3657 #endif
3658 
3659 	return (buf);
3660 }
3661 
3662 
3663 /*
3664  * fcddb_mkdir
3665  *	Wrapper for the mkdir() call.  Will make all needed parent
3666  *	directories leading to the target directory if they don't
3667  *	already exist.
3668  *
3669  * Args:
3670  *	path - The directory path to make
3671  *	mode - The permissions
3672  *
3673  * Return:
3674  *	CddbResult status code
3675  */
3676 CddbResult
3677 fcddb_mkdir(char *path, mode_t mode)
3678 {
3679 	CddbResult	ret;
3680 	char		*cp,
3681 			*mypath;
3682 #ifdef __VMS
3683 	char		*cp2,
3684 			dev[STR_BUF_SZ],
3685 			vpath[STR_BUF_SZ],
3686 			chkpath[STR_BUF_SZ];
3687 #endif
3688 
3689 	if ((mypath = fcddb_strdup(path)) == NULL)
3690 		return CDDBTRNOutOfMemory;
3691 
3692 #ifndef __VMS
3693 /* UNIX */
3694 
3695 	/* Make parent directories, if needed */
3696 	cp = mypath;
3697 	while (*(cp+1) == DIR_BEG)
3698 		cp++;	/* Skip extra initial '/'s if absolute path */
3699 
3700 	while ((cp = strchr(cp+1, DIR_SEP)) != NULL) {
3701 		*cp = '\0';
3702 
3703 		if ((ret = fcddb_ckmkdir(mypath, mode)) != Cddb_OK) {
3704 			MEM_FREE(mypath);
3705 			return (ret);
3706 		}
3707 
3708 		*cp = DIR_SEP;
3709 	}
3710 
3711 	if ((ret = fcddb_ckmkdir(mypath, mode)) != Cddb_OK) {
3712 		MEM_FREE(mypath);
3713 		return (ret);
3714 	}
3715 
3716 	(void) chmod(mypath, mode);
3717 
3718 #else
3719 /* VMS */
3720 
3721 	dev[0] = vpath[0] = '\0';
3722 
3723 	/* Zap out the version number */
3724 	if ((cp = strchr(mypath, ';')) != NULL)
3725 		*cp = '\0';
3726 
3727 	/* Separate into disk and path parts */
3728 
3729 	cp = mypath;
3730 	if ((cp = strchr(cp+1, ':')) == NULL)
3731 		cp = mypath;
3732 	else {
3733 		*cp = '\0';
3734 		(void) strcpy(dev, mypath);
3735 		*cp++ = ':';
3736 	}
3737 
3738 	/* Check path syntax, and convert from
3739 	 * disk:[a.b.c]d.dir syntax to disk:[a.b.c.d]
3740 	 * if needed.
3741 	 */
3742 	cp2 = cp;
3743 	if ((cp = strchr(cp, DIR_BEG)) == NULL) {
3744 		cp = cp2;
3745 		if ((cp2 = strrchr(cp+1, '.')) != NULL) {
3746 			if (fcddb_strcasecmp(cp2, ".dir") == 0) {
3747 				*cp2 = '\0';
3748 				(void) strcpy(vpath, cp);
3749 			}
3750 			else {
3751 				FCDDBDBG(fcddb_errfp, "fcddb_mkdir: "
3752 					"Illegal directory path: %s\n",
3753 					mypath
3754 				);
3755 				return CDDBTRNCannotCreateFile;
3756 			}
3757 		}
3758 	}
3759 	else {
3760 		(void) strcpy(vpath, ++cp);
3761 		if ((cp = strrchr(vpath, DIR_END)) != NULL) {
3762 			*cp ='\0';
3763 			if ((cp2 = strchr(cp+1, '.')) != NULL) {
3764 				if (fcddb_strcasecmp(cp2, ".dir") == 0) {
3765 					*cp2 = '\0';
3766 					*cp = DIR_SEP;
3767 				}
3768 				else {
3769 					FCDDBDBG(fcddb_errfp, "fcddb_mkdir: "
3770 						"Illegal directory path: %s\n",
3771 						mypath
3772 					);
3773 					return CDDBTRNCannotCreateFile;
3774 				}
3775 			}
3776 			else if (*(cp+1) != '\0') {
3777 				FCDDBDBG(fcddb_errfp, "fcddb_mkdir: "
3778 					"Illegal directory path: %s\n",
3779 					mypath
3780 				);
3781 				return CDDBTRNCannotCreateFile;
3782 			}
3783 		}
3784 	}
3785 
3786 	cp = vpath;
3787 	while ((cp = strchr(cp, DIR_SEP)) != NULL) {
3788 		*cp = '\0';
3789 
3790 		chkpath[0] = '\0';
3791 
3792 		(void) sprintf(chkpath, "%s%s[%s]",
3793 			dev, dev[0] == '\0' ? "" : ":", vpath
3794 		);
3795 
3796 		*cp++ = DIR_SEP;
3797 
3798 		if ((ret = fcddb_ckmkdir(chkpath, mode)) != Cddb_OK) {
3799 			MEM_FREE(mypath);
3800 			return (ret);
3801 		}
3802 	}
3803 
3804 	if (vpath[0] == '\0') {
3805 		(void) sprintf(chkpath, "%s%s[000000]",
3806 			dev, dev[0] == '\0' ? "" : ":"
3807 		);
3808 
3809 		if ((ret = fcddb_ckmkdir(chkpath, mode)) != Cddb_OK) {
3810 			MEM_FREE(mypath);
3811 			return (ret);
3812 		}
3813 
3814 		(void) chmod(chkpath, mode);
3815 	}
3816 	else if (strcmp(vpath, "000000") != 0) {
3817 		(void) sprintf(chkpath, "%s%s[%s]",
3818 			dev, dev[0] == '\0' ? "" : ":", vpath
3819 		);
3820 
3821 		if ((ret = fcddb_ckmkdir(chkpath, mode)) != Cddb_OK) {
3822 			MEM_FREE(mypath);
3823 			return (ret);
3824 		}
3825 
3826 		(void) chmod(chkpath, mode);
3827 	}
3828 
3829 #endif	/* __VMS */
3830 
3831 	MEM_FREE(mypath);
3832 	return Cddb_OK;
3833 }
3834 
3835 
3836 /*
3837  * fcddb_username
3838  *	Return user login name
3839  *
3840  * Args:
3841  *	None
3842  *
3843  * Return:
3844  *	The login name
3845  */
3846 char *
3847 fcddb_username(void)
3848 {
3849 	char		*cp;
3850 #ifdef __VMS
3851 	cp = getlogin();
3852 	if (cp != NULL)
3853 		return (cp);
3854 #else
3855 	struct passwd	*pw;
3856 
3857 	/* Get login name from the password file if possible */
3858 	setpwent();
3859 	if ((pw = getpwuid(getuid())) != NULL) {
3860 		endpwent();
3861 		return (pw->pw_name);
3862 	}
3863 	endpwent();
3864 
3865 	/* Try the LOGNAME environment variable */
3866 	if ((cp = (char *) getenv("LOGNAME")) != NULL)
3867 		return (cp);
3868 
3869 	/* Try the USER environment variable */
3870 	if ((cp = (char *) getenv("USER")) != NULL)
3871 		return (cp);
3872 #endif
3873 	/* If we still can't get the login name, just set it
3874 	 * to "nobody" (shrug).
3875 	 */
3876 	return ("nobody");
3877 }
3878 
3879 
3880 /*
3881  * fcddb_homedir
3882  *	Return the user's home directory
3883  *
3884  * Args:
3885  *	None
3886  *
3887  * Return:
3888  *	The user's home directory
3889  */
3890 char *
3891 fcddb_homedir(void)
3892 {
3893 #ifndef __VMS
3894 	struct passwd	*pw;
3895 	char		*cp;
3896 
3897 	/* Get home directory from the password file if possible */
3898 	setpwent();
3899 	if ((pw = getpwuid(getuid())) != NULL) {
3900 		endpwent();
3901 		return (pw->pw_dir);
3902 	}
3903 	endpwent();
3904 
3905 	/* Try the HOME environment variable */
3906 	if ((cp = (char *) getenv("HOME")) != NULL)
3907 		return (cp);
3908 
3909 	/* If we still can't get the home directory, just set it to the
3910 	 * current directory (shrug).
3911 	 */
3912 	return (CUR_DIR);
3913 #else
3914 	char		*cp;
3915 	static char	buf[FILE_PATH_SZ];
3916 
3917 	if ((cp = (char *) getenv("HOME")) != NULL &&
3918 	    (int) strlen(cp) < sizeof(buf)) {
3919 		(void) strcpy(buf, cp);
3920 		buf[strlen(buf)-1] = '\0';	/* Drop the "]" */
3921 	}
3922 	else
3923 		(void) strcpy(buf, "SYS$DISK:[");
3924 
3925 	return (buf);
3926 #endif	/* __VMS */
3927 }
3928 
3929 
3930 /*
3931  * fcddb_hostname
3932  *	Get the host name (with fully qualified domain name if possible)
3933  *
3934  * Args:
3935  *	None
3936  *
3937  * Return:
3938  *	Nothing
3939  */
3940 char *
3941 fcddb_hostname(void)
3942 {
3943 #ifndef NOREMOTE
3944 	struct hostent	*he;
3945 	char		**ap,
3946 			hname[HOST_NAM_SZ+1];
3947 	static char	buf[HOST_NAM_SZ+1];
3948 
3949 	buf[0] = '\0';
3950 
3951 	/* Try to determine host name */
3952 	if (gethostname(hname, HOST_NAM_SZ) < 0 ||
3953 	    (he = gethostbyname(hname)) == NULL) {
3954 		struct utsname	un;
3955 
3956 		if (uname(&un) < 0)
3957 			(void) strcpy(buf, "localhost"); /* shrug */
3958 		else
3959 			(void) strncpy(buf, un.nodename, HOST_NAM_SZ);
3960 	}
3961 	else {
3962 		/* Look for a a fully-qualified hostname
3963 		 * (with domain)
3964 		 */
3965 		if (strchr(he->h_name, '.') != NULL)
3966 			(void) strcpy(buf, he->h_name);
3967 		else {
3968 			for (ap = he->h_aliases; *ap != NULL; ap++) {
3969 				if (strchr(*ap, '.') != NULL) {
3970 				    (void) strncpy(buf, *ap, HOST_NAM_SZ);
3971 				    break;
3972 				}
3973 			}
3974 		}
3975 
3976 		if (buf[0] == '\0')
3977 			(void) strcpy(buf, hname);
3978 	}
3979 
3980 	buf[HOST_NAM_SZ] = '\0';
3981 	return (buf);
3982 #else
3983 	struct utsname	un;
3984 	static char	buf[HOST_NAM_SZ+1];
3985 
3986 	if (uname(&un) < 0)
3987 		(void) strcpy(buf, "localhost"); /* shrug */
3988 	else
3989 		(void) strncpy(buf, un.nodename, HOST_NAM_SZ);
3990 
3991 	buf[HOST_NAM_SZ] = '\0';
3992 	return (buf);
3993 #endif
3994 }
3995 
3996 
3997 /*
3998  * fcddb_genre_id2categ
3999  *	Convert CDDB2 genre ID to CDDB1 category string.  Since this could
4000  *	yield multiple matches, only the first matched mapping is returned.
4001  *
4002  * Args:
4003  *	id - The genre ID string
4004  *
4005  * Return:
4006  *	The category string
4007  */
4008 char *
4009 fcddb_genre_id2categ(char *id)
4010 {
4011 	fcddb_gmap_t	*gmp;
4012 
4013 	if (id == NULL)
4014 		return NULL;
4015 
4016 	for (gmp = fcddb_gmap_head; gmp != NULL; gmp = gmp->next) {
4017 		if (strcmp(gmp->sub.id, id) == 0)
4018 			return (gmp->cddb1name);
4019 	}
4020 	return NULL;
4021 }
4022 
4023 
4024 /*
4025  * fcddb_genre_id2gp
4026  *	Convert CDDB2 genre ID to a genre structure pointer
4027  *
4028  * Args:
4029  *	cp - Pointer to the control structure
4030  *	id - The genre ID string
4031  *
4032  * Return:
4033  *	Pointer to the genre structure
4034  */
4035 cddb_genre_t *
4036 fcddb_genre_id2gp(cddb_control_t *cp, char *id)
4037 {
4038 	cddb_genre_t	*p,
4039 			*q;
4040 
4041 	if (cp == NULL || id == NULL)
4042 		return NULL;
4043 
4044 	for (p = cp->genretree.genres; p != NULL; p = p->nextmeta) {
4045 		if (p->id != NULL && strcmp(p->id, id) == 0)
4046 			return (p);
4047 
4048 		for (q = p->next; q != NULL; q = q->next) {
4049 			if (q->id != NULL && strcmp(q->id, id) == 0)
4050 				return (q);
4051 		}
4052 	}
4053 
4054 	return NULL;
4055 }
4056 
4057 
4058 /*
4059  * fcddb_genre_categ2id
4060  *	Convert CDDB1 category string to a CDDB2 genre ID
4061  *
4062  * Args:
4063  *	categ - The category name string
4064  *
4065  * Return:
4066  *	The genre ID string
4067  */
4068 char *
4069 fcddb_genre_categ2id(char *categ)
4070 {
4071 	fcddb_gmap_t	*gmp;
4072 
4073 	if (categ == NULL)
4074 		return NULL;
4075 
4076 	for (gmp = fcddb_gmap_head; gmp != NULL; gmp = gmp->next) {
4077 		if (strcmp(gmp->cddb1name, categ) == 0)
4078 			return (gmp->sub.id);
4079 	}
4080 
4081 	return NULL;
4082 }
4083 
4084 
4085 /*
4086  * fcddb_genre_categ2gp
4087  *	Convert CDDB1 category string to a genre structure pointer
4088  *
4089  * Args:
4090  *	cp - Pointer to the control structure
4091  *	categ - The category string
4092  *
4093  * Return:
4094  *	Pointer to the genre structure
4095  */
4096 cddb_genre_t *
4097 fcddb_genre_categ2gp(cddb_control_t *cp, char *categ)
4098 {
4099 	fcddb_gmap_t	*gmp;
4100 	char		*id;
4101 
4102 	if (cp == NULL || categ == NULL)
4103 		return NULL;
4104 
4105 	id = NULL;
4106 	for (gmp = fcddb_gmap_head; gmp != NULL; gmp = gmp->next) {
4107 		if (strcmp(gmp->cddb1name, categ) == 0) {
4108 			id = gmp->sub.id;
4109 			break;
4110 		}
4111 	}
4112 
4113 	if (id == NULL)
4114 		return NULL;
4115 
4116 	return (fcddb_genre_id2gp(cp, id));
4117 }
4118 
4119 
4120 /*
4121  * fcddb_parse_toc
4122  *	Parse a TOC string and populate an array of frame offsets
4123  *
4124  * Args:
4125  *	tocstr - The TOC
4126  *	addr - Return array of frame offsets
4127  *
4128  * Return:
4129  *	The number of tracks
4130  */
4131 int
4132 fcddb_parse_toc(char *tocstr, unsigned int *addr)
4133 {
4134 	char	*p,
4135 		*q,
4136 		sav;
4137 	int	i;
4138 
4139 	if (tocstr == NULL || addr == NULL)
4140 		return 0;
4141 
4142 	i = 0;
4143 	p = tocstr;
4144 	SKIP_SPC(p);
4145 
4146 	/* Starting frames for each track */
4147 	while ((q = strchr(p, ' ')) != NULL || (q = strchr(p, '\t')) != NULL) {
4148 		sav = *q;
4149 		*q = '\0';
4150 		addr[i++] = (unsigned int) atoi(p);
4151 		*q = sav;
4152 		p = q+1;
4153 		SKIP_SPC(p);
4154 	}
4155 	addr[i] = (unsigned int) atoi(p);	/* Lead out */
4156 
4157 	return (i);
4158 }
4159 
4160 
4161 /*
4162  * fcddb_discid
4163  *	Compute CDDB1 disc ID
4164  *
4165  * Args:
4166  *	ntrks - Number of tracks
4167  *	addr - Array of frame offsets
4168  *
4169  * Return:
4170  *	The CDDB1 disc ID string
4171  */
4172 char *
4173 fcddb_discid(int ntrks, unsigned int *addr)
4174 {
4175 	int		i,
4176 			t = 0,
4177 			n = 0;
4178 	unsigned int	discid_val;
4179 	char		discid[12];
4180 
4181 	/* For backward compatibility this algorithm must not change */
4182 
4183 	for (i = 0; i < ntrks; i++)
4184 		n += fcddb_sum(addr[i] / FRAME_PER_SEC);
4185 
4186 	t = (addr[ntrks] / FRAME_PER_SEC) - (addr[0] / FRAME_PER_SEC);
4187 
4188 	discid_val = ((n % 0xff) << 24 | t << 8 | ntrks);
4189 	(void) sprintf(discid, "%08x", discid_val);
4190 
4191 	return (fcddb_strdup(discid));
4192 }
4193 
4194 
4195 /*
4196  * fcddb_read_cddb
4197  *	Send a read command to the CDDB server
4198  *
4199  * Args:
4200  *	cp - Pointer to the control structure
4201  *	discid - The disc ID
4202  *	toc - The TOC
4203  *	category - The CDDB1 category
4204  *	conhost - Server host
4205  *	conport - The server port
4206  *	isproxy - Whether we're using a proxy server
4207  *
4208  * Return:
4209  *	CddbResult status code
4210  */
4211 #ifdef NOREMOTE
4212 /*ARGSUSED*/
4213 #endif
4214 CddbResult
4215 fcddb_read_cddb(
4216 	cddb_control_t		*cp,
4217 	char			*discid,
4218 	char			*toc,
4219 	char			*category,
4220 	char			*conhost,
4221 	unsigned short		conport,
4222 	bool_t			isproxy
4223 )
4224 {
4225 	CddbResult	ret;
4226 	int		fd,
4227 			n,
4228 			i;
4229 	size_t		buflen,
4230 			contentlen;
4231 	char		*buf,
4232 			*p,
4233 			filepath[FILE_PATH_SZ];
4234 	time_t		t;
4235 	struct stat	stbuf;
4236 #ifndef NOREMOTE
4237 	char		*q,
4238 			urlstr[HOST_NAM_SZ + FILE_PATH_SZ + 12];
4239 	bool_t		valid_resp;
4240 #endif
4241 
4242 #ifdef __VMS
4243 	(void) sprintf(filepath, "%s.%s]%s.",
4244 		       cp->options.localcachepath, category, discid);
4245 #else
4246 	(void) sprintf(filepath, "%s/%s/%s",
4247 		       cp->options.localcachepath, category, discid);
4248 #endif
4249 
4250 	t = time(NULL);
4251 
4252 	/*
4253 	 * Attempt to read from local cache
4254 	 */
4255 	FCDDBDBG(fcddb_errfp, "\nfcddb_read_cddb: Checking cache\n");
4256 
4257 	ret = CDDBTRNRecordNotFound;
4258 
4259 	if (stat(filepath, &stbuf) == 0 && S_ISREG(stbuf.st_mode)
4260 #ifndef NOREMOTE
4261 	    &&
4262 	    (((cp->options.localcacheflags & CACHE_DONT_CONNECT) != 0) ||
4263 	     ((t - stbuf.st_mtime) <=
4264 		    (int) (cp->options.localcachetimeout * SECS_PER_DAY)))
4265 #endif
4266 	   ) {
4267 		/*
4268 		 * Use local cache
4269 		 */
4270 
4271 		contentlen = stbuf.st_size;
4272 		buflen = contentlen + 1;
4273 		buf = (char *) MEM_ALLOC("cddb_buf", buflen);
4274 		if (buf == NULL)
4275 			return CDDBTRNOutOfMemory;
4276 
4277 		ret = Cddb_OK;
4278 
4279 		FCDDBDBG(fcddb_errfp, "fcddb_read_cddb: cache read %s/%s\n",
4280 			 category, discid);
4281 
4282 		/* Open local cache file */
4283 		if ((fd = open(filepath, O_RDONLY)) < 0) {
4284 			ret = CDDBTRNRecordNotFound;
4285 		}
4286 		else {
4287 			/* Read cache data into buf */
4288 
4289 			for (i = contentlen, p = buf; i > 0; i -= n, p += n) {
4290 				if ((n = read(fd, p, i)) < 0) {
4291 					MEM_FREE(buf);
4292 					ret = CDDBTRNRecordNotFound;
4293 					break;
4294 				}
4295 			}
4296 			*p = '\0';
4297 
4298 			(void) close(fd);
4299 
4300 			if (ret == Cddb_OK) {
4301 				ret = fcddb_parse_cddb_data(
4302 					cp, buf, discid, category, toc, FALSE,
4303 					&cp->disc
4304 				);
4305 			}
4306 		}
4307 
4308 		MEM_FREE(buf);
4309 		buf = NULL;
4310 
4311 		if (ret == Cddb_OK)
4312 			return (ret);
4313 
4314 		/* Fall through */
4315 	}
4316 
4317 #ifdef NOREMOTE
4318 	return (ret);
4319 #else
4320 	if ((cp->options.localcacheflags & CACHE_DONT_CONNECT) != 0)
4321 		return (ret);
4322 
4323 	/*
4324 	 * Do CDDB server read
4325 	 */
4326 
4327 	FCDDBDBG(fcddb_errfp, "fcddb_read_cddb: server read %s/%s\n",
4328 		 category, discid);
4329 
4330 	buflen = CDDBP_CMDLEN;
4331 	if ((buf = (char *) MEM_ALLOC("http_buf", buflen)) == NULL)
4332 		return CDDBTRNOutOfMemory;
4333 
4334 	/*
4335 	 * Set up CDDB read command string
4336 	 */
4337 
4338 	urlstr[0] = '\0';
4339 
4340 	if (isproxy) {
4341 		(void) sprintf(urlstr, "http://%s:%d",
4342 			       CDDB_SERVER_HOST, HTTP_PORT);
4343 	}
4344 
4345 	(void) strcat(urlstr, CDDB_CGI_PATH);
4346 
4347 	(void) sprintf(buf,
4348 		       "GET %s?cmd=cddb+read+%s+%s&%s&proto=4 HTTP/1.0\r\n",
4349 		       urlstr, category, discid, fcddb_hellostr);
4350 
4351 	if (isproxy && fcddb_auth_buf != NULL) {
4352 		(void) sprintf(buf, "%sProxy-Authorization: Basic %s\r\n", buf,
4353 			       fcddb_auth_buf);
4354 	}
4355 
4356 	(void) sprintf(buf, "%s%s%s\r\n%s\r\n", buf,
4357 		       "Host: ", CDDB_SERVER_HOST,
4358 		       fcddb_extinfo);
4359 
4360 	/* Open network connection to server */
4361 	if ((ret = fcddb_connect(conhost, conport, &fd)) != Cddb_OK) {
4362 		if (buf != NULL)
4363 			MEM_FREE(buf);
4364 		return (ret);
4365 	}
4366 
4367 	/*
4368 	 * Send the command to the CDDB server and get response code
4369 	 */
4370 	if ((ret = fcddb_sendcmd(fd, &buf, &buflen, isproxy)) != Cddb_OK) {
4371 		if (buf != NULL)
4372 			MEM_FREE(buf);
4373 		return (ret);
4374 	}
4375 
4376 	/* Close server connection */
4377 	fcddb_disconnect(fd);
4378 
4379 	/* Check for CDDB command response */
4380 	valid_resp = FALSE;
4381 	p = buf;
4382 	while ((q = strchr(p, '\n')) != NULL) {
4383 		*q = '\0';
4384 
4385 		if (STATCODE_CHECK(p)) {
4386 			valid_resp = TRUE;
4387 			*q = '\n';
4388 			break;
4389 		}
4390 
4391 		*q = '\n';
4392 		p = q + 1;
4393 		SKIP_SPC(p);
4394 	}
4395 
4396 	if (!valid_resp) {
4397 		/* Server error */
4398 		return CDDBTRNBadResponseSyntax;
4399 	}
4400 
4401 	/*
4402 	 * Check command status
4403 	 */
4404 	ret = Cddb_OK;
4405 
4406 	switch (STATCODE_1ST(p)) {
4407 	case STAT_GEN_OK:
4408 	case STAT_GEN_OKCONT:
4409 		if (STATCODE_2ND(p) == '1') {
4410 			if ((q = strchr(p, '\n')) == NULL) {
4411 				ret = CDDBTRNBadResponseSyntax;
4412 				break;
4413 			}
4414 
4415 			p = q + 1;
4416 			SKIP_SPC(p);
4417 			if (*p == '\0') {
4418 				ret = CDDBTRNBadResponseSyntax;
4419 				break;
4420 			}
4421 
4422 			ret = fcddb_parse_cddb_data(
4423 				cp, p, discid, category, toc, TRUE, &cp->disc
4424 			);
4425 			break;
4426 		}
4427 
4428 		/*FALLTHROUGH*/
4429 
4430 	case STAT_GEN_INFO:
4431 	case STAT_GEN_OKFAIL:
4432 	case STAT_GEN_ERROR:
4433 	case STAT_GEN_CLIENT:
4434 	default:
4435 		/* Error */
4436 		ret = CDDBTRNRecordNotFound;
4437 		break;
4438 	}
4439 
4440 	if (buf != NULL)
4441 		MEM_FREE(buf);
4442 
4443 	return (ret);
4444 #endif
4445 }
4446 
4447 
4448 /*
4449  * fcddb_lookup_cddb
4450  *	High-level function to perform a CDDB server lookup
4451  *
4452  * Args:
4453  *	cp - Pointer to the control structure
4454  *	discid - The disc ID
4455  *	addr - Array of frame offsets
4456  *	toc - The TOC
4457  *	conhost - The server host
4458  *	conport - The server port
4459  *	isproxy - Whether we're using a proxy server
4460  *	matchcode - Return matchcode
4461  *
4462  * Return:
4463  *	CddbResult status code
4464  */
4465 CddbResult
4466 fcddb_lookup_cddb(
4467 	cddb_control_t		*cp,
4468 	char			*discid,
4469 	unsigned int		*addr,
4470 	char			*toc,
4471 	char			*conhost,
4472 	unsigned short		conport,
4473 	bool_t			isproxy,
4474 	int			*matchcode
4475 )
4476 {
4477 	CddbResult	ret;
4478 	char		category[FILE_BASE_SZ];
4479 
4480 	*matchcode = MATCH_NONE;
4481 	fcddb_clear_disc(&cp->disc);
4482 
4483 	if ((ret = fcddb_query_cddb(cp, discid, toc, addr, conhost, conport,
4484 			       isproxy, category, matchcode)) != Cddb_OK)
4485 		return (ret);
4486 
4487 	if (*matchcode == MATCH_EXACT)
4488 		ret = fcddb_read_cddb(cp, discid, toc, category,
4489 				      conhost, conport, isproxy);
4490 	else
4491 		ret = Cddb_OK;
4492 
4493 	return (ret);
4494 }
4495 
4496 
4497 /*
4498  * fcddb_submit_cddb
4499  *	Submit a disc entry to CDDB
4500  *
4501  * Args:
4502  *	cp - Pointer to the control structure
4503  *	dp - Pointer to the disc structure
4504  *	conhost - The server host
4505  *	conport - The server port
4506  *	isproxy - Whether we're using a proxy server
4507  *
4508  * Return:
4509  *	CddbResult status code
4510  */
4511 #ifdef NOREMOTE
4512 /*ARGSUSED*/
4513 #endif
4514 CddbResult
4515 fcddb_submit_cddb(
4516 	cddb_control_t	*cp,
4517 	cddb_disc_t	*dp,
4518 	char		*conhost,
4519 	unsigned short	conport,
4520 	bool_t		isproxy
4521 )
4522 {
4523 	CddbResult	ret;
4524 	char		filepath[FILE_PATH_SZ];
4525 	struct stat	stbuf;
4526 	fcddb_gmap_t	*gmp;
4527 	char		*p;
4528 #ifndef NOREMOTE
4529 	int		fd,
4530 			n,
4531 			i;
4532 	size_t		contentlen = 0,
4533 			buflen;
4534 	char		*buf,
4535 			*q,
4536 			urlstr[HOST_NAM_SZ + FILE_PATH_SZ + 12];
4537 	bool_t		valid_resp;
4538 #endif
4539 
4540 	/* Check if the genre has changed */
4541 	if (dp->category != NULL) {
4542 		if ((p = fcddb_genre_categ2id(dp->category)) != NULL &&
4543 		    strcmp(p, dp->genre->id) != 0) {
4544 			MEM_FREE(dp->category);
4545 			dp->category = NULL;
4546 		}
4547 	}
4548 
4549 	if (dp->category == NULL) {
4550 		/* Find the appropriate category: look for an existing
4551 		 * local cache file.
4552 		 */
4553 		for (gmp = fcddb_gmap_head; gmp != NULL; gmp = gmp->next) {
4554 			if (strcmp(gmp->sub.id, dp->genre->id) != 0)
4555 				continue;
4556 #ifdef __VMS
4557 			(void) sprintf(filepath, "%s.%s]%s.",
4558 				       cp->options.localcachepath,
4559 				       gmp->cddb1name, dp->discid);
4560 #else
4561 			(void) sprintf(filepath, "%s/%s/%s",
4562 				       cp->options.localcachepath,
4563 				       gmp->cddb1name, dp->discid);
4564 #endif
4565 			if (stat(filepath, &stbuf) < 0)
4566 				continue;
4567 
4568 			if (S_ISREG(stbuf.st_mode)) {
4569 				dp->category = fcddb_strdup(gmp->cddb1name);
4570 				break;
4571 			}
4572 		}
4573 
4574 		if (dp->category == NULL) {
4575 			/* Try reverse mapping the genre */
4576 			if ((p = fcddb_genre_id2categ(dp->genre->id)) != NULL)
4577 				dp->category = fcddb_strdup(p);
4578 		}
4579 
4580 		if (dp->category == NULL) {
4581 			/* Still can't find a matching category: fail */
4582 			return Cddb_E_INVALIDARG;
4583 		}
4584 	}
4585 
4586 #ifdef __VMS
4587 	(void) sprintf(filepath, "%s.%s]%s.",
4588 		       cp->options.localcachepath, dp->category, dp->discid);
4589 #else
4590 	(void) sprintf(filepath, "%s/%s/%s",
4591 		       cp->options.localcachepath, dp->category, dp->discid);
4592 #endif
4593 
4594 	if ((ret = fcddb_write_cddb(filepath, dp, TRUE)) != Cddb_OK)
4595 		return (ret);
4596 
4597 	if (stat(filepath, &stbuf) < 0)
4598 		return CDDBTRNFileWriteError;
4599 
4600 #ifdef NOREMOTE
4601 	return Cddb_OK;
4602 #else
4603 	if ((cp->options.localcacheflags & CACHE_SUBMIT_OFFLINE) != 0)
4604 		return Cddb_OK;
4605 
4606 	contentlen = (size_t) stbuf.st_size;
4607 
4608 	buflen = CDDBP_CMDLEN + contentlen;
4609 	if ((buf = (char *) MEM_ALLOC("http_buf", buflen)) == NULL)
4610 		return CDDBTRNOutOfMemory;
4611 
4612 	FCDDBDBG(fcddb_errfp, "\nfcddb_submit_cddb: %s/%s\n",
4613 		 dp->category, dp->discid);
4614 
4615 	/*
4616 	 * Set up CDDB submit command string
4617 	 */
4618 
4619 	urlstr[0] = '\0';
4620 
4621 	if (isproxy) {
4622 		(void) sprintf(urlstr, "http://%s:%d",
4623 			       CDDB_SUBMIT_HOST, HTTP_PORT);
4624 	}
4625 
4626 	(void) strcat(urlstr, CDDB_SUBMIT_CGI_PATH);
4627 
4628 	(void) sprintf(buf, "POST %s HTTP/1.0\r\n", urlstr);
4629 
4630 	if (isproxy && fcddb_auth_buf != NULL) {
4631 		(void) sprintf(buf, "%sProxy-Authorization: Basic %s\r\n", buf,
4632 			       fcddb_auth_buf);
4633 	}
4634 
4635 	(void) sprintf(buf,
4636 		       "%s%s%s\r\n%s%s%s\r\n%s%s\r\n"
4637 		       "%s%s@%s\r\n%s%s\r\n%s%u\r\n\r\n", buf,
4638 		       "Host: ", CDDB_SUBMIT_HOST,
4639 		       fcddb_extinfo,
4640 		       "Category: ", dp->category,
4641 		       "Discid: ", dp->discid,
4642 		       "User-Email: ", cp->userinfo.userhandle, cp->hostname,
4643 		       "Submit-Mode: ",
4644 				cp->options.testsubmitmode ? "test" : "submit",
4645 		       "Content-Length: ", (unsigned int) contentlen
4646 	);
4647 
4648 	/* Append CDDB file data to buf here */
4649 	if ((fd = open(filepath, O_RDONLY)) < 0) {
4650 		MEM_FREE(buf);
4651 		return CDDBTRNRecordNotFound;
4652 	}
4653 
4654 	for (i = contentlen, p = buf + strlen(buf); i > 0; i -= n, p += n) {
4655 		if ((n = read(fd, p, i)) < 0) {
4656 			MEM_FREE(buf);
4657 			return CDDBTRNRecordNotFound;
4658 		}
4659 	}
4660 	*p = '\0';
4661 
4662 	(void) close(fd);
4663 
4664 	/* Open network connection to server */
4665 	if ((ret = fcddb_connect(conhost, conport, &fd)) != Cddb_OK) {
4666 		if (buf != NULL)
4667 			MEM_FREE(buf);
4668 		return (ret);
4669 	}
4670 
4671 	/*
4672 	 * Send the command to the CDDB server and get response code
4673 	 */
4674 	if ((fcddb_sendcmd(fd, &buf, &buflen, isproxy)) != Cddb_OK) {
4675 		if (buf != NULL)
4676 			MEM_FREE(buf);
4677 		return (ret);
4678 	}
4679 
4680 	/* Close server connection */
4681 	fcddb_disconnect(fd);
4682 
4683 	/* Check for CDDB command response */
4684 	valid_resp = FALSE;
4685 	p = buf;
4686 	while ((q = strchr(p, '\n')) != NULL) {
4687 		*q = '\0';
4688 
4689 		if (STATCODE_CHECK(p)) {
4690 			valid_resp = TRUE;
4691 			*q = '\n';
4692 			break;
4693 		}
4694 
4695 		*q = '\n';
4696 		p = q + 1;
4697 		SKIP_SPC(p);
4698 	}
4699 
4700 	if (!valid_resp) {
4701 		/* Server error */
4702 		return CDDBTRNBadResponseSyntax;
4703 	}
4704 
4705 	/*
4706 	 * Check command status
4707 	 */
4708 	switch (STATCODE_1ST(p)) {
4709 	case STAT_GEN_OK:
4710 		ret = Cddb_OK;
4711 		break;
4712 
4713 	case STAT_GEN_ERROR:
4714 		ret = CDDBSVCMissingField;
4715 		break;
4716 
4717 	case STAT_GEN_OKFAIL:
4718 		ret = CDDBSVCServiceError;
4719 		break;
4720 
4721 	case STAT_GEN_OKCONT:
4722 	case STAT_GEN_INFO:
4723 	default:
4724 		/* Error */
4725 		ret = CDDBTRNBadResponseSyntax;
4726 		break;
4727 	}
4728 
4729 	if (buf != NULL)
4730 		MEM_FREE(buf);
4731 
4732 	return (ret);
4733 #endif	/* NOREMOTE */
4734 }
4735 
4736 
4737 /*
4738  * fcddb_flush_cddb
4739  *	Flush local cachestore
4740  *
4741  * Args:
4742  *	cp - Pointer to the control structure
4743  *
4744  * Return:
4745  *	CddbResult status code
4746  */
4747 #ifdef NOREMOTE
4748 /*ARGSUSED*/
4749 #endif
4750 CddbResult
4751 fcddb_flush_cddb(cddb_control_t *cp, CDDBFlushFlags flags)
4752 {
4753 #ifndef NOREMOTE
4754 	fcddb_gmap_t	*gmp;
4755 	char		cmd[FILE_PATH_SZ + STR_BUF_SZ];
4756 
4757 	if (flags == FLUSH_DEFAULT || (flags & FLUSH_CACHE_MEDIA)) {
4758 		for (gmp = fcddb_gmap_head; gmp != NULL; gmp = gmp->next) {
4759 #ifndef __VMS
4760 			(void) sprintf(cmd,
4761 				    "rm -rf %s/%s >/dev/null 2>&1",
4762 				    cp->options.localcachepath,
4763 				    gmp->cddb1name);
4764 
4765 			if (fcddb_runcmd(cmd) != 0)
4766 				return CDDBTRNFileDeleteError;
4767 #else
4768 			/* For VMS simply removing the file doesn't
4769 			 * produce the desired effect, so do nothing
4770 			 * for now.
4771 			 */
4772 #endif
4773 		}
4774 	}
4775 #endif
4776 	return Cddb_OK;
4777 }
4778 
4779 
4780