/*
 *	cddb.c
 *	31.5.99 tn@xcdroast.org
 *
 *	stuff to access the cddb-database via the cddbp-protocol
 *	standalone version
 *
 *	cddbtool.c
 *
 *	http-protocol / proxy support
 *	10/10/2002 Martin Haunfelder
 *	mailto:mhaunfelder@gmx.de
 *
 *	12/11/2002 tn: proxy-authentication support inspired by GNU wget 1.6 sources
 *
 *	04/03/2017 tn: CDDB Proto 6 communication.
 *
 *
 *  Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
 *
 *  This file is part of xcdroast.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include "largefile.h"

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <locale.h>
#if defined(linux) || defined(__CYGWIN32__)
#include <getopt.h>
#endif
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include "xcdrdata.h"
#include "xcdroast.h"
#include "main.h"

#ifndef INADDR_NONE
#define INADDR_NONE 0xFFFFFFFF
#endif

#define CDDB_HTTP_CMD	"GET http://%s/~cddb/cddb.cgi?cmd="
#define CDDB_HTTP_KEEP	" HTTP/1.0\r\nHost: %s\r\nConnection: Keep-Alive\r\nProxy-Connection: Keep-Alive\r\n%s\r\n"
#define CDDB_HTTP_HELLO	"hello=%s+%s+xcdroast+%s&proto=6"CDDB_HTTP_KEEP
#define CDDB_HTTP_STAT	CDDB_HTTP_CMD"stat&"CDDB_HTTP_HELLO
#define CDDB_HTTP_MOTD	CDDB_HTTP_CMD"motd&"CDDB_HTTP_HELLO
#define CDDB_HTTP_QUERY	CDDB_HTTP_CMD"cddb+query+%s&"CDDB_HTTP_HELLO
#define CDDB_HTTP_READ	CDDB_HTTP_CMD"cddb+read+%s+%s&"CDDB_HTTP_HELLO


cddb_match_t **cddbmatch;
cddb_connect_t *cddbconnect;
gint matchnr;
gint oldtnr;
gint olddtitle;
gint verbose=0;


/* How many bytes it will take to store LEN bytes in base64.  */
#define BASE64_LENGTH(len) (4 * (((len) + 2) / 3))

/*
 * Encode the string S of length LENGTH to base64 format and place it
 * to STORE.  STORE will be 0-terminated, and must point to a writable
 * buffer of at least 1+BASE64_LENGTH(length) bytes.
 */
static void base64_encode (const char *s, char *store, int length) {

/* Conversion table.  */
static char tbl[64] = {
    'A','B','C','D','E','F','G','H',
    'I','J','K','L','M','N','O','P',
    'Q','R','S','T','U','V','W','X',
    'Y','Z','a','b','c','d','e','f',
    'g','h','i','j','k','l','m','n',
    'o','p','q','r','s','t','u','v',
    'w','x','y','z','0','1','2','3',
    '4','5','6','7','8','9','+','/'
};
int i;
unsigned char *p = (unsigned char *)store;

  	/* Transform the 3x8 bits to 4x6 bits, as required by base64.  */
  	for (i = 0; i < length; i += 3) {
      		*p++ = tbl[s[0] >> 2];
      		*p++ = tbl[((s[0] & 3) << 4) + (s[1] >> 4)];
      		*p++ = tbl[((s[1] & 0xf) << 2) + (s[2] >> 6)];
      		*p++ = tbl[s[2] & 0x3f];
      		s += 3;
    	}

  	/* Pad the result if necessary...  */
  	if (i == length + 1)
    		*(p - 1) = '=';
  	else if (i == length + 2)
    		*(p - 1) = *(p - 2) = '=';

  	/* ...and zero-terminate it.  */
  	*p = '\0';
}


/*
 * Create the authentication header contents for the `Basic' scheme.
 * This is done by encoding the string `USER:PASS' in base64 and
 * prepending `HEADER: Basic ' to it.
 */
static gchar *basic_authentication_encode (gchar *str, gchar *userpass) {
gchar tmp[MAXLINE];

  	base64_encode (userpass, tmp, strlen(userpass));
  	g_snprintf (str, MAXLINE, "Proxy-Authorization: Basic %s\r\n", tmp);
  	return str;
}


/*
 * extract the 3 digit code from a cddb-answer
 */
gint get_cddb_code(gchar *line) {
gchar tmp[MAXLINE];

	if (line == NULL || strlen(line) < 3) {
		return 0;
	}

	strncpy(tmp,line,3);
	tmp[3] = '\0';
	
	return(atoi(tmp));
}


/*
 * connect to the cddb-server and return socket-fd: 
 * return -1 on "hostname lookup failure" 
 *        -2 on "can't open stream socket"
 *	  -3 on "connection refused"
 *	  -4 on "no response from server"
 *	  -5 on "timeout while connect"
 */
gint connect_cddb_server(cddb_connect_t *cddbconnect) {
struct sockaddr_in serv_addr;
struct hostent *myhost;
gchar tmp[MAXLINE];
gint sockfd,n;

gchar *connectserver=cddbconnect->server;
gint connectport=cddbconnect->port;

	if (cddbconnect->useproxy)
	{
		connectserver=cddbconnect->proxy;
		connectport=cddbconnect->proxyport;
	}

	/* resolv ipnumber from hostname if neccessary */
	if ((serv_addr.sin_addr.s_addr = inet_addr(connectserver)) 
		== INADDR_NONE) {
		/* no valid ip-nummer, look up name */
		if ((myhost = gethostbyname(connectserver)) == NULL) {
			return -1;
		}
		memcpy(&serv_addr.sin_addr, myhost->h_addr, myhost->h_length);
	}

	/* fill structure serv_addr */
	serv_addr.sin_family      = AF_INET;
	serv_addr.sin_port        = htons(connectport);

	/* open tcp socket (internet stream socket) */
	if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		return -2;
	}

	/* connect */
	if (connect(sockfd, (struct sockaddr *) &serv_addr,
		sizeof(serv_addr)) < 0) {
		close(sockfd);
		return -3;
	}

 	if (!cddbconnect->usehttp) {
		/* read connect response */
		n = read_line2(sockfd, tmp, MAXLINE, 1);
		if (n == -2) {
			/* timeout */
			close(sockfd);
			return -5;
		}
		if (n < 0) {
			g_print("-11: Error: read error on socket when connecting\n");
			fflush(stdout);
			close(sockfd);
			return -4;
		}
	} 

	return sockfd;
}


/*
 * perform cddbp-handshaking
 * return 0 on success, 1 on error
 */
gint cddb_handshake(gint sockfd, cddb_connect_t *cddbconnect) {
gchar tmp[MAXLINE];
gint n, code = 0;

	if (cddbconnect->usehttp)
		g_snprintf(tmp,MAXLINE,CDDB_HTTP_STAT,cddbconnect->server,
			cddbconnect->username,cddbconnect->hostname,
			XCDROAST_VERSION, cddbconnect->server, 
			cddbconnect->authstring);
	else
		g_snprintf(tmp,MAXLINE,"cddb hello %s %s xcdroast %s",
			cddbconnect->username,cddbconnect->hostname,
			XCDROAST_VERSION);

	/* now send string */
	if (writen(sockfd,tmp,strlen(tmp), 1) != strlen(tmp)) {
		g_print("-10: Error: write error on socket when cddb handshaking\n");
		fflush(stdout);
		return 1;
	}

	/* read response */
	if (cddbconnect->usehttp) {
		while ((n = read_line2(sockfd, tmp, MAXLINE, 1)) && 
			((code=get_cddb_code(tmp))==0));

		if (code == 210) 
			return 0;

		return 1;
	} 

	n = read_line2(sockfd, tmp, MAXLINE, 1);
	if (n < 0) {
		g_print("-11: Error: read error on socket when cddb handshaking\n");
		fflush(stdout);
		return 1;
	}

	code = get_cddb_code(tmp);
	switch(code) {
		/* all ok */
		case 200:
			return 0;

		/* handshake failed */
		case 431:
			return 1;

		/* alread shook hands */
		case 402:
			return 0;	

		default:
			return 1;
	}
}

/*
 * perform cddbp-proto
 * return 0 on success, 1 on error
 */
gint cddb_proto(gint sockfd, cddb_connect_t *cddbconnect) {
gchar tmp[MAXLINE];
gint n, code = 0;

	g_snprintf(tmp,MAXLINE,"proto 6");

	/* now send string */
	if (writen(sockfd,tmp,strlen(tmp), 1) != strlen(tmp)) {
		g_print("-10: Error: write error on socket when cddb proto\n");
		fflush(stdout);
		return 1;
	}

	/* read response */
	n = read_line2(sockfd, tmp, MAXLINE, 1);
	if (n < 0) {
		g_print("-11: Error: read error on socket when cddb proto\n");
		fflush(stdout);
		return 1;
	}

	code = get_cddb_code(tmp);
	switch(code) {
		/* all ok */
		case 200:
		case 201:
			return 0;

		/* proto failed */
		case 501:
			return 1;

		/* proto already set */
		case 502:
			return 0;	

		default:
			return 1;
	}
}


/*
 * extract a match of the cddb-query command
 */
void extract_cddb_match(gchar *match, gint exact) {
gchar *p1;

	/* allocate memory and free old */
	if (cddbmatch[matchnr] != NULL) {
		g_free(cddbmatch[matchnr]->categ);
		g_free(cddbmatch[matchnr]->discid);
		g_free(cddbmatch[matchnr]->dtitle);
		g_free(cddbmatch[matchnr]);
	}
	cddbmatch[matchnr] = g_new0(cddb_match_t,1);

	cddbmatch[matchnr]->exact = exact;
	p1 = strtok(match," ");
	cddbmatch[matchnr]->categ = g_strdup(p1);
	p1 = strtok(NULL," ");
	cddbmatch[matchnr]->discid = g_strdup(p1);
	p1 = strtok(NULL,"");
	cddbmatch[matchnr]->dtitle = g_strdup(p1);
	
	matchnr++;
}


/*
 * perform cddbp-query
 * return 0 on success, 1 on error
 */
gint cddb_query(gint sockfd, gchar *querystring, cddb_connect_t *cddbconnect) {
gchar tmp[MAXLINE];
gchar *tmpp;
gint n, code;

	matchnr = 0;
	oldtnr = -1;
	olddtitle = -1;

	/* construct query */
	if (cddbconnect->usehttp) {

		tmpp=strstr(querystring, "cddb query ");

		if (tmpp) {
			tmpp=strchr(querystring,' ');
			querystring=strchr(++tmpp,' ');

			while ( (tmpp=strchr(querystring, ' ') ) ) *tmpp='+';

			++querystring;
		}  else {
			g_print("-12: Error: Malformed query!\n");
			fflush(stdout);
			return 1;
		}

		g_snprintf(tmp, MAXLINE,CDDB_HTTP_QUERY,cddbconnect->server, 
			querystring,cddbconnect->username,cddbconnect->hostname,
			XCDROAST_VERSION, cddbconnect->server,
			cddbconnect->authstring);
	} else {
		strcpy(tmp,querystring);
	}

	/* now send string */
	if (writen(sockfd,tmp,strlen(tmp), 1) != strlen(tmp)) {
		g_print("-10: Error: write error on socket when cddb query\n");
		fflush(stdout);
		return 1;
	}

	/* read response */
	if (cddbconnect->usehttp) {
		while ((n = read_line2(sockfd, tmp, MAXLINE, 1)) && 
			((code=get_cddb_code(tmp))==0));
	} else {
		n = read_line2(sockfd, tmp, MAXLINE, 1);
	}

	if (n < 0) {
		g_print("-11: Error: read error on socket when cddb query\n");
		fflush(stdout);
		return 1;
	}

	code = get_cddb_code(tmp);
	switch(code) {
		/* exact match */
		case 200:
			if (cddbconnect->usehttp) {
				if ((n=read_line2(sockfd,tmp,MAXLINE,1)) < 0) {
					g_print("-11: Error: read error on socket when cddb query\n");
					fflush(stdout);
					return 1;
				}
			}

			extract_cddb_match(tmp+4,1);
			return 0;

		/* no match found */
		case 202:
			return 0; 
		/* Database entry is corrupt */
		case 403:
			return 0;
		/* No handshake */
		case 409:
			return 1;
		/* list of matches found */
		case 210:
		case 211: {
			while (matchnr < MAXCDDB) {
				n = read_line2(sockfd, tmp, MAXLINE, 1);
				if (n < 0) {
					g_print("-11: Error: read error on socket when cddb query\n");
					fflush(stdout);
					return 1;
				}
				if (tmp[0] == '.') {
					/* done with list */
					return 0;
				}

				if (code == 210) {
					extract_cddb_match(tmp,1);
				} else {
					extract_cddb_match(tmp,0);
				}
			
			}	
		}
		default: 
			return 1;
	}
}


/*
 * parse the received cddb-entry into info-structures
 * return 0 on success, 1 on error
 */
gint parse_cddb_entry(gint sockfd) {
gchar tmp[MAXLINE];
gchar tmp2[MAXLINE];
gchar *p1;
gint n, tnr;

	while (1) {
		n = read_line2(sockfd, tmp, MAXLINE, 1);
		if (n < 0) {
			g_print("-11: Error: read error on socket when cddb read\n");
			fflush(stdout);
			return 1;
		}
	
		/* end marker found */
		if (tmp[0] == '.') {
			/* no more tracks, close old track */
			g_print("\"\n");
			fflush(stdout);
			return 0;
		}

		if (strncmp(tmp,"DTITLE",6) == 0) {
			p1 = strtok(tmp,"=");
			p1 = strtok(NULL,"");
			strcpy(tmp2,p1);
			/* strip_string(tmp2); */

			if (olddtitle == -1) {
				g_print("07: \"%s", convert_escape(tmp2));
				olddtitle = 0;
			} else {
				/* continue old line */
				g_print("%s", convert_escape(tmp2));
			}
			fflush(stdout);
			continue;
		}

		if (strncmp(tmp,"TTITLE",6) == 0) {
			strcpy(tmp2,tmp+6);
			p1 = strtok(tmp2,"=");
			tnr = atoi(p1);

			/* close disctitle line now? */
			if (olddtitle != -1) {
				g_print("\"\n");
				fflush(stdout);
				olddtitle = -1;
			}

			/* got a new track? close line of previous one */
			if (oldtnr != -1 && oldtnr != tnr) {
				g_print("\"\n");
				fflush(stdout);
			}

			p1 = strtok(NULL,"");
			strcpy(tmp,p1);
			strip_string(tmp);

			if (oldtnr != -1 && oldtnr == tnr) {
				/* continue old line */
				g_print("%s", convert_escape(tmp));
			} else {
				/* new track line (to be continue) */
				g_print("08: \"%s", convert_escape(tmp));
			}	
			oldtnr = tnr;

			continue;
		}
	}
}


/*
 * perform cddbp-read
 * return 0 on success, 1 on error
 */
gint cddb_read(gint sockfd, gint nr, cddb_connect_t *cddbconnect) {
gchar tmp[MAXLINE];
gint n, code;

	if (cddbmatch[nr] == 0) {
		/* no such match */
		return 1;
	}

	/* construct query */
	if (cddbconnect->usehttp) {
		g_snprintf(tmp, MAXLINE,CDDB_HTTP_READ, cddbconnect->server, 
			cddbmatch[nr]->categ, cddbmatch[nr]->discid,
			cddbconnect->username,cddbconnect->hostname,
			XCDROAST_VERSION, cddbconnect->server,
			cddbconnect->authstring);
	 } else {
		g_snprintf(tmp, MAXLINE, "cddb read %s %s", 
			cddbmatch[nr]->categ, cddbmatch[nr]->discid);
	}

	/* now send string */
	if (writen(sockfd,tmp,strlen(tmp), 1) != strlen(tmp)) {
		g_print("-10: Error: write error on socket when cddb read\n");
		fflush(stdout);
		return 1;
	}

	/* read response */
	if (cddbconnect->usehttp) {
	        while ((n = read_line2(sockfd, tmp, MAXLINE, 1)) && 
			((code=get_cddb_code(tmp))==0));
	} else {
		n = read_line2(sockfd, tmp, MAXLINE, 1);
		if (n < 0) {
			g_print("-11: Error: read error on socket when cddb read\n");
			fflush(stdout);
			return 1;
		}
	}

	code = get_cddb_code(tmp);
	switch(code) {
		/* database entry follow */
		case 210:
			parse_cddb_entry(sockfd);
			return 0;
		/* no entry found */
		case 401:
			return 1;
		/* server error */
		case 402:
			return 1;
		/* Database entry is corrupt */
		case 403:
			return 1;
		/* No handshake */
		case 409:
			return 1;
		/* Access limit exceeded, explanation follows */
		case 417: {
			while (1) {
				n = read_line2(sockfd, tmp, MAXLINE, 1);
				if (n < 0) {
					g_print("-11: Error: read error on socket when cddb query\n");
					fflush(stdout);
					return 1;
				}
				if (tmp[0] == '.') {
					/* done with list */
					return 1;
				}
				/* output error-message line of server */
				g_print("-12: Server: %s\n",tmp);	
				fflush(stdout);
			}
		}
		default: 
			return 1;
	}
}


/*
 * perform cddbp-quit
 * return 0 on success, 1 on error
 */
gint cddb_quit(gint sockfd) {
gchar tmp[MAXLINE];

	/* construct query */
	strcpy(tmp,"quit");

	/* now send string */
	if (writen(sockfd,tmp,strlen(tmp), 1) != strlen(tmp)) {
		g_print("-10: Error: write error on socket when cddb quit\n");
		fflush(stdout);
		return 1;
	}

	return 0;
}


/*
 * close the socket when user canceled-out
 */
void close_cddb(gint sock) {

	if (sock > 0) {
		close(sock);
	}

}


/*
 * do the cddb-work and output info to keep user informed
 * return 1 on trouble, 0 if all ok
 */
gint start_cddb_query(gint *sockpnt, cddb_connect_t *cddbconnect, 
	gchar *querystring) {
gint i,code;
gint sock;
gchar tmptmp[MAXLINE];

	/* connect */
	if (cddbconnect->useproxy) {
		g_print("00: Connecting to %s:%d via proxy %s:%d\n",
			cddbconnect->server, cddbconnect->port, 
			cddbconnect->proxy, cddbconnect->proxyport);
	} else {
		g_print("00: Connecting to %s:%d\n", 
			cddbconnect->server, cddbconnect->port);
	}
	fflush(stdout);

	sock = connect_cddb_server(cddbconnect);
	*sockpnt = sock;

	if (sock < 0) {
		switch (sock) {
		case -1: 
			/* lookup error */
			g_print("-1: Error: Hostname lookup failure\n");
			break;
		case -2:
			/* socket error */
			g_print("-2: Error: Can't open stream socket\n");
			break;
		case -3:
			/* connection refused */
			g_print("-3: Error: Connection refused\n");
			break;
		case -4: 
			/* no response from server */
			g_print("-4: Error: No response from server\n");
			break;
		case -5:
			/* timeout */
			g_print("-5: Error: No answer within timeout\n");
			break;
		}
		fflush(stdout);
		return 1;
	}	


	/* connect was ok, now send handshake */
	g_print("01: Connect ok: Sending handshake/proto\n");
	fflush(stdout);

	code=cddb_handshake(sock, cddbconnect); 
	
	/* send proto (not needed for http) */
	if (!cddbconnect->usehttp && code == 0) {
		code=cddb_proto(sock, cddbconnect); 
	}

	if (code != 0) {
		g_print("-6: Handshake failed - No valid CDDB-server?\n");
		fflush(stdout);
		return 1;
	}

	/* reopen http/proxy connect */
	if (cddbconnect->usehttp || cddbconnect->useproxy) {
		close(sock);
		sock = connect_cddb_server(cddbconnect);
		*sockpnt = sock;
	}

	/* start query */
	g_print("02: Sending CDDB-query\n");
	fflush(stdout);

	code=cddb_query (sock, querystring, cddbconnect);
	if (code != 0) {
		g_print("-7: Error: CDDB-query failed\n");
		fflush(stdout);
		return 1;
	}

	/* now the global match-structure contains all hits */

	if (matchnr == 0) {
		/* no matches found */
		g_print("03: No matches found - unknown CD\n");
		fflush(stdout);
		/* all the user can do now is to cancel */
		return 1;
	} else {
		/* output number of matches */
		g_print("%% %d\n",matchnr);
		fflush(stdout);

		/* what type of matches */
		if (cddbmatch[0]->exact == 0) {
			/* close */
			g_print("04: Found %d close matches - Please select (enter number and press enter)\n", matchnr);
			fflush(stdout);
		} else {
			/* exact */
			g_print("05: Found %d exact matches - Please select (enter number and press enter)\n", matchnr);
			fflush(stdout);
		}
	
		/* now add all matches to clist */
		for (i = 0; i < matchnr; i++) {
			strcpy(tmptmp,cddbmatch[i]->dtitle);
			g_print("#%02d: \"%s\"\n", i, 
				convert_escape(tmptmp));
		}	
		g_print("#--\n");
		fflush(stdout);
	}

	/* so..now wait until the user select a match */
	return 0;
}


/*
 * user selected a match - fetch the corresponding data
 * return 1 on trouble, 0 if all ok
 */
gint continue_cddb_query(gint sock, gint match, cddb_connect_t *cddbconnect) {

	/* get data only when user manually not forbid it */
	if (match != -1) {
		g_print("06: Requesting data - Please wait\n");
		fflush(stdout);

		/* get the data */
		if (cddb_read(sock, match, cddbconnect) != 0) {
			g_print("-8: Error: CDDB-read failed\n");
			fflush(stdout);
			return 1;
		}
	}

	/* cddb_quit only necessary for native cddb-protocol, 
	   but not http encapsulation */
	if (!cddbconnect->usehttp) {
		if (cddb_quit(sock) != 0) {
			g_print("-9: Warning: CDDB-logout failed\n");
			fflush(stdout);
			return 1;
		}
	}
	close_cddb(sock);

	return 0;
}


/*
 * get optional http_proxy data from environment variables
 */
gint get_from_environment(cddb_connect_t *cddbconnect) {
gchar env[MAXLINE];
gchar *envp; 

	if ((envp=getenv("http_proxy"))) {

		/* strip off leading "http://" */
		if (strncasecmp(envp,"http://",sizeof("http://"))>0) {
			if (verbose>=3) 
				g_print("00: Stripping \"http:\\\\\" from http_proxy env.\n");
			strncpy(env,envp+sizeof("http://")-1, MAXLINE-sizeof("http://"));
		} else {
			strncpy(env, envp, MAXLINE);
		}

		/* get port (proxy:PORT) */
		if ((envp=strchr(env,':'))) {
			*envp='\0';
			envp++;
			cddbconnect->proxyport=atoi(envp);
		}

		/* get THE proxy (PROXY:port) */		
		g_free(cddbconnect->proxy);
		cddbconnect->proxy = g_strdup(env);

		/* and set defaults to use http via proxy */
		cddbconnect->useproxy=1;
		cddbconnect->usehttp=1;
		cddbconnect->port=80;

		return 0;
	}
	return 1;
}


/*
 * print usage
 */
void usage(gchar *cmd) {

	g_print("Usage: %s [options] (Version: %s)\n",cmd, XCDROAST_VERSION);
	g_print("Options:\n");
	g_print("\t-s <cddb-server>\n");
	g_print("\t-p <cddb-port>\n");
	g_print("\t-U <proxy-server> or <user:password@proxy-server>\n");
	g_print("\t-P <proxy-port>\n");
	g_print("\t-H use http\n");
	g_print("\t-E use 'http_proxy' environment variable\n");
	g_print("\t-u <id username>\n");
	g_print("\t-h <id hostname>\n");
	g_print("\t-m <match nr> get info for this match number\n\t   (Do not wait for user input - set to -1 for match list only)\n");
	g_print("\t-q <cddb-query-string>\n");
	g_print("\t-v increase verbosity\n");
	g_print("\nEnvironment variables:\n");
	g_print("\tCDDB_USE_HTTP=yes\tuse http protocol (same as -H)\n");
	g_print("\tCDDB_USE_PROXY=yes\tuse proxy (use -U/-P or set 'http_proxy')\n");
	g_print("\tCDDB_USE_SERVER\t\tcddb server to use (e.g. 'freedb.freedb.de')\n");
	g_print("\n HTTP/PROXY SUPPORT IS ALPHA, USAGE SUBJECT TO CHANGE, USE AT OWN RISK,\n Report bugs to <mhaunfelder@gmx.de>\n");
}


/*
 * main programm
 */
gint main(gint argc, gchar **argv) {
gint c;
gchar selectbuffer[MAXLINE];
gchar querystring[MAXLINE];
gchar tmp[MAXLINE], tmp2[MAXLINE];
gint ret, match, len;
gint sock;
gint automatch;
gchar *envp, *p1;

	setlocale(LC_ALL, "en_US.utf8");

	/* allocate cddbconnect structure */
	cddbconnect = g_new0(cddb_connect_t, 1);

	/* do some defaults */
	cddbconnect->server = g_strdup("freedb.freedb.org");
	cddbconnect->username = g_strdup("anonymous");
	cddbconnect->hostname = g_strdup("localhost");
	cddbconnect->proxy = g_strdup("0.0.0.0");
	cddbconnect->authstring = g_strdup("");
	cddbconnect->port=888;
	cddbconnect->proxyport = 80;
	cddbconnect->useproxy = 0;
	cddbconnect->usehttp = 0;
	cddbconnect->useauth = 0;
	strcpy(querystring, "");
	automatch = -2;

	cddbmatch = g_new0(cddb_match_t *,MAXCDDB);

	/* check for environment variables */
	if ((envp=getenv("CDDB_USE_HTTP"))) {
		if (!strncasecmp(envp,"yes",3)) {
			cddbconnect->usehttp=1;
			cddbconnect->port=80;
		}
		if (verbose >= 1) 
			g_print("00: Environment variable: \"CDDB_USE_HTTP\" found\n");
	}

	if ((envp=getenv("CDDB_USE_PROXY"))) {
		if (!strncasecmp(envp,"yes",3)) {
			if (verbose >= 1) 
				g_print("00: Environment variable: \"CDDB_USE_PROXY\" found\n");
			if (get_from_environment(cddbconnect)) 
				g_print("00: `http_proxy' environment variable not set!\n");
		}
	}

	if ((envp=getenv("CDDB_USE_SERVER"))) {
		if (verbose>=1) 
			g_print("00: Environment variable: \"CDDB_USE_SERVER\" found\n");
		g_free(cddbconnect->server);
		cddbconnect->server = g_strdup(envp);
	}

	/* get options */
	while ((c = getopt(argc,argv,"s:p:P:u:h:q:m:U:HEv")) != EOF) 
	switch((gchar)c) {

	case 's':	
		g_free(cddbconnect->server);
		cddbconnect->server = g_strdup(optarg);
		break;

	case 'u':	
		g_free(cddbconnect->username);
		cddbconnect->username = g_strdup(optarg);
		break;

	case 'h':	
		g_free(cddbconnect->hostname);
		cddbconnect->hostname = g_strdup(optarg);
		break;

	case 'q':	
		strncpy(querystring,optarg,MAXLINE);
		break;

	case 'p':
		cddbconnect->port = atoi(optarg);
		break;

	case 'm':
		automatch = atoi(optarg);
		break;

	case 'U':
		g_free(cddbconnect->proxy);
		cddbconnect->proxy = g_strdup(optarg);
		cddbconnect->useproxy=1;
		cddbconnect->usehttp=1;
		cddbconnect->port=80;
		break;

	case 'P':
		cddbconnect->proxyport=atoi(optarg);
		cddbconnect->useproxy=1;
		cddbconnect->usehttp=1;
		break;

	case 'H':
		cddbconnect->usehttp=1;
		cddbconnect->port=80;
		break;

	case 'E':
		if (get_from_environment(cddbconnect)) {
			g_warning("'http_proxy' environment variable not set!\n");
		}
		break;

	case 'v':
		verbose++;
		break;

	default:
		usage(argv[0]);
		exit(-1);
	}


	/* any parameters at all given? */
	if (strcmp(querystring,"") == 0) {
		usage(argv[0]);
		g_free (cddbconnect);
		exit(-1);
	}

	/* check if we want to do proxy-auth */
	p1 = strstr(cddbconnect->proxy,"@");
	if (p1) {
		/* extract authstring */
		len = p1 - cddbconnect->proxy;
		if (len < MAXLINE) {
			strncpy(tmp, cddbconnect->proxy, len);
			tmp[len] = '\0';

			if (strlen(tmp) > 0) {

				/* encode */
				basic_authentication_encode(tmp2, tmp);

				g_free(cddbconnect->authstring);
				cddbconnect->authstring = g_strdup(tmp2);

				cddbconnect->useauth = 1;
			} 
		}

		/* now remove authstring from proxy-string */
		len = strlen(cddbconnect->proxy) - (p1 - cddbconnect->proxy);
		if (len < MAXLINE) {
			strncpy(tmp, p1+1, len);
			tmp[len] = '\0';
			g_free(cddbconnect->proxy);
			cddbconnect->proxy = g_strdup(tmp);
		}
	}

	ret = start_cddb_query(&sock, cddbconnect , querystring);
	if (ret == 0) {
		/* ok, now let the user select an match */
		if (automatch == -2) {
			read_line2(STDIN_FILENO, selectbuffer, MAXLINE, 0);
			match = atoi(selectbuffer);
		} else {
			match = automatch;
		} 

	     	if (cddbconnect->usehttp || cddbconnect->useproxy)
		{
			if (verbose>=2) 
				g_print("00: Reopen http/proxy connect!\n");
			close(sock);
			sock = connect_cddb_server(cddbconnect);
		}

		ret = continue_cddb_query(sock, match, cddbconnect);
		if (ret != 0) {
			/* some error */

			g_free (cddbconnect);
			exit(1);
		}
	}

	g_free (cddbconnect);
	return 0;
} 
