/* $Id: cddb_conn.c,v 1.39 2006/10/15 11:53:11 airborne Exp $ Copyright (C) 2003, 2004, 2005 Kris Verbeeck This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "cddb/cddb_ni.h" #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_NETDB_H #include #endif #ifdef HAVE_ARPA_INET_H #include #endif #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_UNISTD_H #include #endif /* --- prototypes --- */ /** * Send handshake to CDDB server. */ static int cddb_handshake(cddb_conn_t *c); /** * Reset proxy authentication credentials. */ static void cddb_set_http_proxy_auth(cddb_conn_t *c, const char *username, const char *password); /* --- construction / destruction --- */ cddb_conn_t *cddb_new(void) { cddb_conn_t *c; const char *s; libcddb_init(); /* initialize globals if not yet done */ c = (cddb_conn_t*)malloc(sizeof(cddb_conn_t)); if (c) { c->buf_size = DEFAULT_BUF_SIZE; c->line = (char*)malloc(c->buf_size); c->cname = strdup(CLIENT_NAME); c->cversion = strdup(CLIENT_VERSION); c->is_connected = FALSE; c->socket = -1; c->cache_fp = NULL; c->server_name = strdup(DEFAULT_SERVER); c->server_port = DEFAULT_PORT; c->timeout = DEFAULT_TIMEOUT; c->http_path_query = strdup(DEFAULT_PATH_QUERY); c->http_path_submit = strdup(DEFAULT_PATH_SUBMIT); c->is_http_enabled = FALSE; c->is_http_proxy_enabled = FALSE; c->http_proxy_server = NULL; c->http_proxy_server_port = DEFAULT_PROXY_PORT; c->http_proxy_username = NULL; c->http_proxy_password = NULL; c->http_proxy_auth = NULL; c->use_cache = CACHE_ON; /* construct cache dir '$HOME/[DEFAULT_CACHE]' */ s = getenv("HOME"); c->cache_dir = (char*)malloc(strlen(s) + 1 + sizeof(DEFAULT_CACHE) + 1); sprintf(c->cache_dir, "%s/%s", s, DEFAULT_CACHE); c->cache_read = FALSE; /* use anonymous@localhost */ c->user = strdup(DEFAULT_USER); c->hostname = strdup(DEFAULT_HOST); c->errnum = CDDB_ERR_OK; // XXX: handle out-of-memory c->query_data = list_new((elem_destroy_cb*)cddb_disc_destroy); c->sites_data = list_new((elem_destroy_cb*)cddb_site_destroy); c->charset = malloc(sizeof(struct cddb_iconv_s)); c->charset->cd_to_freedb = NULL; c->charset->cd_from_freedb = NULL; c->srch.fields = SEARCH_ARTIST | SEARCH_TITLE; c->srch.cats = SEARCH_ALL; } else { cddb_log_crit(cddb_error_str(CDDB_ERR_OUT_OF_MEMORY)); } return c; } static void cddb_close_iconv(cddb_conn_t *c) { if (c->charset) { #ifdef HAVE_ICONV_H if (c->charset->cd_to_freedb) { iconv_close(c->charset->cd_to_freedb); } if (c->charset->cd_from_freedb) { iconv_close(c->charset->cd_from_freedb); } #endif /* HAVE_ICONV_H */ } } void cddb_destroy(cddb_conn_t *c) { if (c) { cddb_disconnect(c); FREE_NOT_NULL(c->line); FREE_NOT_NULL(c->cname); FREE_NOT_NULL(c->cversion); FREE_NOT_NULL(c->server_name); FREE_NOT_NULL(c->http_path_query); FREE_NOT_NULL(c->http_path_submit); FREE_NOT_NULL(c->http_proxy_server); FREE_NOT_NULL(c->http_proxy_username); FREE_NOT_NULL(c->http_proxy_password); FREE_NOT_NULL(c->cache_dir); FREE_NOT_NULL(c->user); FREE_NOT_NULL(c->hostname); list_destroy(c->query_data); list_destroy(c->sites_data); cddb_close_iconv(c); FREE_NOT_NULL(c->charset); free(c); } } /* --- getters & setters --- */ int cddb_set_charset(cddb_conn_t *c, const char *charset) { #ifdef HAVE_ICONV_H cddb_close_iconv(c); c->charset->cd_to_freedb = iconv_open(SERVER_CHARSET, charset); if (c->charset->cd_to_freedb == (iconv_t)-1) { c->charset->cd_to_freedb = NULL; cddb_errno_set(c, CDDB_ERR_INVALID_CHARSET); return FALSE; } c->charset->cd_from_freedb = iconv_open(charset, SERVER_CHARSET); if (c->charset->cd_from_freedb == (iconv_t)-1) { iconv_close(c->charset->cd_to_freedb); c->charset->cd_to_freedb = NULL; c->charset->cd_from_freedb = NULL; cddb_errno_set(c, CDDB_ERR_INVALID_CHARSET); return FALSE; } cddb_errno_set(c, CDDB_ERR_OK); return TRUE; #else cddb_errno_set(c, CDDB_ERR_ICONV_FAIL); return FALSE; #endif /* HAVE_ICONV_H */ } void cddb_set_buf_size(cddb_conn_t *c, unsigned int size) { FREE_NOT_NULL(c->line); c->buf_size = size; c->line = (char*)malloc(c->buf_size); } cddb_error_t cddb_set_site(cddb_conn_t *c, const cddb_site_t *site) { cddb_error_t rv; const char *server, *path; unsigned int port; cddb_protocol_t proto; ASSERT_NOT_NULL(c); if ((rv = cddb_site_get_address(site, &server, &port)) != CDDB_ERR_OK) { cddb_errno_set(c, rv); return rv; } if ((proto = cddb_site_get_protocol(site)) == PROTO_UNKNOWN) { cddb_errno_set(c, CDDB_ERR_INVALID); return CDDB_ERR_INVALID; } if ((rv = cddb_site_get_query_path(site, &path)) != CDDB_ERR_OK) { cddb_errno_set(c, rv); return rv; } cddb_set_server_name(c, server); cddb_set_server_port(c, port); if (proto == PROTO_CDDBP) { cddb_http_disable(c); } else { cddb_http_enable(c); cddb_set_http_path_query(c, path); } return cddb_errno_set(c, CDDB_ERR_OK); } const char *cddb_get_server_name(const cddb_conn_t *c) { const char *server = NULL; if (c) { server = c->server_name; } RETURN_STR_OR_EMPTY(server); } void cddb_set_server_name(cddb_conn_t *c, const char *server) { FREE_NOT_NULL(c->server_name); c->server_name = strdup(server); } unsigned int cddb_get_server_port(const cddb_conn_t *c) { if (c) { return c->server_port; } return 0; } void cddb_set_server_port(cddb_conn_t *c, int port) { c->server_port = port; } unsigned int cddb_get_timeout(const cddb_conn_t *c) { if (c) { return c->timeout; } return 0; } void cddb_set_timeout(cddb_conn_t *c, unsigned int t) { if (c) { c->timeout = t; } } const char *cddb_get_http_path_query(const cddb_conn_t *c) { const char *path = NULL; if (c) { path = c->http_path_query; } RETURN_STR_OR_EMPTY(path); } void cddb_set_http_path_query(cddb_conn_t *c, const char *path) { FREE_NOT_NULL(c->http_path_query); c->http_path_query = strdup(path); } const char *cddb_get_http_path_submit(const cddb_conn_t *c) { const char *path = NULL; if (c) { path = c->http_path_submit; } RETURN_STR_OR_EMPTY(path); } void cddb_set_http_path_submit(cddb_conn_t *c, const char *path) { FREE_NOT_NULL(c->http_path_submit); c->http_path_submit = strdup(path); } unsigned int cddb_is_http_enabled(const cddb_conn_t *c) { if (c) { return c->is_http_enabled; } return FALSE; } void cddb_http_enable(cddb_conn_t *c) { c->is_http_enabled = TRUE; cddb_errno_set(c, CDDB_ERR_OK); } void cddb_http_disable(cddb_conn_t *c) { c->is_http_enabled = FALSE; cddb_errno_set(c, CDDB_ERR_OK); } unsigned int cddb_is_http_proxy_enabled(const cddb_conn_t *c) { if (c) { return c->is_http_proxy_enabled; } return FALSE; } void cddb_http_proxy_enable(cddb_conn_t *c) { /* enabling HTTP proxy implies HTTP, but not vice versa */ cddb_http_enable(c); c->is_http_proxy_enabled = TRUE; cddb_errno_set(c, CDDB_ERR_OK); } void cddb_http_proxy_disable(cddb_conn_t *c) { c->is_http_proxy_enabled = FALSE; cddb_errno_set(c, CDDB_ERR_OK); } const char *cddb_get_http_proxy_server_name(const cddb_conn_t *c) { const char *server = NULL; if (c) { server = c->http_proxy_server; } RETURN_STR_OR_EMPTY(server); } void cddb_set_http_proxy_server_name(cddb_conn_t *c, const char *server) { FREE_NOT_NULL(c->http_proxy_server); c->http_proxy_server = strdup(server); } unsigned int cddb_get_http_proxy_server_port(const cddb_conn_t *c) { if (c) { return c->http_proxy_server_port; } return 0; } void cddb_set_http_proxy_server_port(cddb_conn_t *c, int port) { c->http_proxy_server_port = port; } static void cddb_set_http_proxy_auth(cddb_conn_t *c, const char *username, const char *password) { int len; char *auth, *auth_b64; FREE_NOT_NULL(c->http_proxy_auth); len = 0; if (username != NULL) { len += strlen(username); } if (password != NULL) { len += strlen(password); } len += 2; /* colon and 0-byte */; auth = (char*)malloc(len); snprintf(auth, len, "%s:%s", (username ? username : ""), (password ? password : "")); auth_b64 = (char*)malloc(len * 2); /* certainly big enough */ cddb_b64_encode(auth_b64, auth); c->http_proxy_auth = strdup(auth_b64); free(auth_b64); free(auth); } const char *cddb_get_http_proxy_username(const cddb_conn_t *c) { const char *user = NULL; if (c) { user = c->http_proxy_username; } RETURN_STR_OR_EMPTY(user); } void cddb_set_http_proxy_username(cddb_conn_t *c, const char *username) { FREE_NOT_NULL(c->http_proxy_username); if (username) { c->http_proxy_username = strdup(username); } /* remake authentication credentials */ cddb_set_http_proxy_auth(c, c->http_proxy_username, c->http_proxy_password); } const char *cddb_get_http_proxy_password(const cddb_conn_t *c) { const char *password = NULL; if (c) { password = c->http_proxy_password; } RETURN_STR_OR_EMPTY(password); } void cddb_set_http_proxy_password(cddb_conn_t *c, const char *password) { FREE_NOT_NULL(c->http_proxy_password); if (password) { c->http_proxy_password = strdup(password); } /* remake authentication credentials */ cddb_set_http_proxy_auth(c, c->http_proxy_username, c->http_proxy_password); } void cddb_set_http_proxy_credentials(cddb_conn_t* c, const char *username, const char* password) { /* remove cleartext credentials */ FREE_NOT_NULL(c->http_proxy_username); FREE_NOT_NULL(c->http_proxy_password); /* remake authentication credentials */ cddb_set_http_proxy_auth(c, username, password); } cddb_error_t cddb_errno(const cddb_conn_t *c) { if (c) { return c->errnum; } return CDDB_ERR_INVALID; } void cddb_set_client(cddb_conn_t *c, const char *cname, const char *cversion) { if (cname && cversion) { FREE_NOT_NULL(c->cname); FREE_NOT_NULL(c->cversion); c->cname = strdup(cname); c->cversion = strdup(cversion); } } int cddb_set_email_address(cddb_conn_t *c, const char *email) { char *at; int len; cddb_log_debug("cddb_set_email_address()"); if ((email == NULL) || ((at = strchr(email, '@')) == NULL) || (at == email) || (*(at+1) == '\0')) { cddb_errno_log_error(c, CDDB_ERR_EMAIL_INVALID); return FALSE; } /* extract user name */ FREE_NOT_NULL(c->user); len = at - email; c->user = malloc(len + 1); strncpy(c->user, email, len); c->user[len] = '\0'; /* extract host name */ at++; FREE_NOT_NULL(c->hostname); c->hostname = strdup(at); cddb_log_debug("...user name = '%s'", c->user); cddb_log_debug("...host name = '%s'", c->hostname); return TRUE; } cddb_cache_mode_t cddb_cache_mode(const cddb_conn_t *c) { if (c) { return c->use_cache; } return CACHE_OFF; } void cddb_cache_enable(cddb_conn_t *c) { if (c) { c->use_cache = CACHE_ON; } } void cddb_cache_only(cddb_conn_t *c) { if (c) { c->use_cache = CACHE_ONLY; } } void cddb_cache_disable(cddb_conn_t *c) { if (c) { c->use_cache = CACHE_OFF; } } const char *cddb_cache_get_dir(const cddb_conn_t *c) { if (c) { return c->cache_dir;; } return NULL; } int cddb_cache_set_dir(cddb_conn_t *c, const char *dir) { char *home; cddb_log_debug("cddb_cache_set_dir()"); if (dir) { FREE_NOT_NULL(c->cache_dir); if (dir[0] == '~') { /* expand ~ to $HOME */ home = getenv("HOME"); if (home) { c->cache_dir = (char*)malloc(strlen(home) + strlen(dir)); sprintf(c->cache_dir, "%s%s", home, dir + 1); } } else { c->cache_dir = strdup(dir); } } return TRUE; } const cddb_site_t *cddb_first_site(cddb_conn_t *c) { elem_t *e; e = list_first(c->sites_data); if (e) { return element_data(e); } return NULL; } const cddb_site_t *cddb_next_site(cddb_conn_t *c) { elem_t *e; e = list_next(c->sites_data); if (e) { return element_data(e); } return NULL; } void cddb_search_set_fields(cddb_conn_t *c, unsigned int fields) { c->srch.fields = fields; } void cddb_search_set_categories(cddb_conn_t *c, unsigned int cats) { c->srch.cats = cats; } /* --- connecting / disconnecting --- */ static int cddb_handshake(cddb_conn_t *c) { char *msg; int code; cddb_log_debug("cddb_handshake()"); /* check sign-on banner */ switch (code = cddb_get_response_code(c, &msg)) { case -1: return FALSE; case 200: /* read/write */ case 201: /* read only */ break; case 432: case 433: case 434: cddb_errno_log_error(c, CDDB_ERR_PERMISSION_DENIED); return FALSE; } /* send hello and check response */ if (!cddb_send_cmd(c, CMD_HELLO, c->user, c->hostname, c->cname, c->cversion)) { return FALSE; } switch (code = cddb_get_response_code(c, &msg)) { case -1: return FALSE; case 200: /* ok */ case 402: /* already shook hands */ break; case 431: cddb_errno_log_error(c, CDDB_ERR_PERMISSION_DENIED); return FALSE; } /* set protocol level */ if (!cddb_send_cmd(c, CMD_PROTO, DEFAULT_PROTOCOL_VERSION)) { return FALSE; } switch (code = cddb_get_response_code(c, &msg)) { case -1: return FALSE; case 200: /* ok */ case 201: /* ok */ case 502: /* protocol already set */ break; case 501: /* illegal protocol level */ /* ignore */ break; } cddb_errno_set(c, CDDB_ERR_OK); return TRUE; } int cddb_connect(cddb_conn_t *c) { int rv = TRUE; cddb_log_debug("cddb_connect()"); if (!CONNECTION_OK(c)) { struct hostent *he; /* resolve host name */ if (c->is_http_proxy_enabled) { /* use HTTP proxy server name */ he = timeout_gethostbyname(c->http_proxy_server, c->timeout); c->sa.sin_port = htons(c->http_proxy_server_port); } else { /* use CDDB server name */ he = timeout_gethostbyname(c->server_name, c->timeout); c->sa.sin_port = htons(c->server_port); } if (he == NULL) { cddb_errno_log_error(c, CDDB_ERR_UNKNOWN_HOST_NAME); return FALSE; } /* initialize socket address */ c->sa.sin_family = AF_INET; c->sa.sin_addr = *((struct in_addr*)he->h_addr); memset(&(c->sa.sin_zero), 0, sizeof(c->sa.sin_zero)); if ((c->socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) { cddb_errno_log_error(c, CDDB_ERR_CONNECT); return FALSE; } rv = timeout_connect(c->socket, (struct sockaddr*)&(c->sa), sizeof(struct sockaddr), c->timeout); if (rv == -1) { cddb_errno_log_error(c, CDDB_ERR_CONNECT); return FALSE; } if (!c->is_http_enabled) { /* send handshake message to CDDB server (CDDBP only) */ return cddb_handshake(c); } } cddb_errno_set(c, CDDB_ERR_OK); return TRUE; } void cddb_disconnect(cddb_conn_t *c) { cddb_log_debug("cddb_disconnect()"); if (CONNECTION_OK(c)) { close(c->socket); c->socket = -1; } cddb_errno_set(c, CDDB_ERR_OK); } /* --- miscellaneous --- */ void cddb_clone_proxy(cddb_conn_t *dst, cddb_conn_t *src) { if (cddb_is_http_proxy_enabled(src)) { /* XXX: optimize? */ FREE_NOT_NULL(dst->http_proxy_server); if (src->http_proxy_server) { dst->http_proxy_server = strdup(src->http_proxy_server); } dst->http_proxy_server_port = src->http_proxy_server_port; FREE_NOT_NULL(dst->http_proxy_auth); if (src->http_proxy_auth) { dst->http_proxy_auth = strdup(src->http_proxy_auth); } cddb_http_proxy_enable(dst); } }