diff -urN cmus/Makefile cmus-new/Makefile
--- cmus/Makefile	2008-06-01 04:11:07.000000000 -0400
+++ cmus-new/Makefile	2008-08-22 21:25:37.000000000 -0400
@@ -9,8 +9,8 @@
 
 CMUS_LIBS = $(PTHREAD_LIBS) $(NCURSES_LIBS) $(ICONV_LIBS) $(DL_LIBS) -lm $(COMPAT_LIBS)
 
-input.o main.o ui_curses.o: .version
-input.o main.o ui_curses.o: CFLAGS += -DVERSION=\"$(VERSION)\"
+as.o input.o main.o ui_curses.o: .version
+as.o input.o main.o ui_curses.o: CFLAGS += -DVERSION=\"$(VERSION)\"
 main.o server.o: CFLAGS += -DDEFAULT_PORT=3000
 
 .version: Makefile
@@ -19,14 +19,14 @@
 
 # programs {{{
 cmus-y := \
-	ape.o browser.o buffer.o cache.o cmdline.o cmus.o command_mode.o comment.o \
+	ape.o as.o browser.o buffer.o cache.o cmdline.o cmus.o command_mode.o comment.o \
 	debug.o editable.o expr.o filters.o \
 	format_print.o gbuf.o glob.o help.o history.o http.o id3.o input.o job.o \
-	keys.o keyval.o lib.o load_dir.o locking.o mergesort.o misc.o options.o \
+	keys.o keyval.o lib.o load_dir.o locking.o network.o md5.o mergesort.o misc.o options.o \
 	output.o pcm.o pl.o play_queue.o player.o \
 	read_wrapper.o server.o search.o \
-	search_mode.o spawn.o tabexp.o tabexp_file.o \
-	track.o track_info.o tree.o uchar.o ui_curses.o \
+	search_mode.o spawn.o strlib.o tabexp.o tabexp_file.o \
+	track.o track_info.o tree.o uchar.o ui_curses.o window.o \
 	utf8_encode.lo window.o worker.o xstrjoin.o
 
 $(cmus-y): CFLAGS += $(PTHREAD_CFLAGS) $(NCURSES_CFLAGS) $(ICONV_CFLAGS) $(DL_CFLAGS)
diff -urN cmus/as.c cmus-new/as.c
--- cmus/as.c	1969-12-31 19:00:00.000000000 -0500
+++ cmus-new/as.c	2008-08-22 21:25:37.000000000 -0400
@@ -0,0 +1,930 @@
+/*
+ * Copyright 2004-2006 Timo Hirvonen
+ *
+ * as.[ch] by Frank Terbeck <ft@bewatermyfriend.org>
+ * and Clay Barnes <clay@hci-matters.com>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+/*
+ * see as.h and http://www.audioscrobbler.net
+ */
+
+#include <pthread.h>
+#include <time.h>
+
+#include "as.h"
+#include "cmus.h"
+#include "debug.h"
+#include "list.h"
+#include "locking.h"
+#include "md5.h"
+#include "network.h"
+#include "player.h"
+#include "strlib.h"
+#include "utils.h"
+
+struct as_authinfo as_authinfo;
+struct as_hsinfo as_hsinfo;
+const char *as_numbers[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
+time_t as_lasttimestamp = 0;
+int as_enable = 0;
+int running = 0;
+
+static pthread_mutex_t as_mutex = CMUS_MUTEX_INITIALIZER;
+static pthread_t as_thread;
+static LIST_HEAD(as_queue_head);
+
+static unsigned int as_count_entries(void)
+{ /*{{{*/
+	/* make sure as_lock() was called. */
+	struct as_queue *entry;
+	int count=0;
+	list_for_each_entry(entry, &as_queue_head, node) {
+		++count;
+	}
+	return(count);
+} /*}}}*/
+
+static struct as_queue *as_get_last_entry(void)
+{ /*{{{*/
+	/* make sure as_lock() was called. */
+	struct as_queue *entry;
+	list_for_each_entry(entry, &as_queue_head, node) { }
+	return(entry);
+} /*}}}*/
+
+static void as_urlencode(string_t *str)
+{ /*{{{*/
+	const char *transtab[] =
+	{ /*{{{*/
+		" ",	"%20", /* 00 01 */
+		"!",    "%21", /* 02 03 */
+		"\"",	"%22", /* 04 05 */
+		"#",	"%23", /* 06 07 */
+		"%",	"%25", /* 08 09 */
+		"&",	"%26", /* 10 11 */
+		"'",	"%27", /* 12 13 */
+		"(",	"%28", /* 14 15 */
+		")",	"%29", /* 16 17 */
+		"*",	"%2A", /* 18 19 */
+		"-",    "%2D", /* 20 21 */
+		"/",	"%2F", /* 22 23 */
+		":",	"%3A", /* 24 25 */
+		";",	"%3B", /* 26 27 */
+		"<",	"%3C", /* 28 29 */
+		"=",	"%3D", /* 30 31 */
+		">",	"%3E", /* 32 33 */
+		"?",	"%3F", /* 34 35 */
+		"@",	"%40", /* 36 37 */
+		"[",	"%5B", /* 38 39 */
+		"\\",	"%5C", /* 40 41 */
+		"]",	"%5D", /* 42 43 */
+		"^",	"%5E", /* 44 45 */
+		"`",	"%60", /* 46 47 */
+		"{",	"%7B", /* 48 49 */
+		"|",	"%7C", /* 50 51 */
+		"}",	"%7D", /* 52 53 */
+		"~",	"%7E", /* 54 55 */
+	}; /*}}}*/
+	int i, e, idx=-1;
+	for (i=0; i<=str->len-1; ++i) {
+		if (mustquote(str->s[i])) {
+			for (e=0; e<=55; e+=2) {
+				if (transtab[e][0] == str->s[i]) {
+					idx=e+1;
+					break;
+				}
+			}
+			if (idx >= 0)
+				str_nreplace(str, i, 1, transtab[idx], 3);
+		}
+	}
+} /*}}}*/
+
+static void as_update_timestamp(void)
+{ /*{{{*/
+	as_lasttimestamp=time(NULL);
+} /*}}}*/
+
+static void as_handshake(void)
+{ /*{{{*/
+	int sockfd;
+	unsigned long int idx, odx;
+	char buf[AS_BUFSIZE+1];
+	ssize_t n;
+	string_t msg, tmp;
+
+	/* no authentication values set, don't do anything */
+	if (as_authinfo.user[0] == '\0' || as_authinfo.pass[0] == '\0')
+		return;
+
+	/* prepare handshake request */
+	str_init(&msg);
+	str_init(&tmp);
+	str_ncat(&msg, "GET ", 4); str_scat(&msg, AS_HS_URI);
+	str_ncat(&msg, "&p=", 3);  str_scat(&msg, AS_PROTO);
+	str_ncat(&msg, "&c=", 3);  str_ncat(&msg, AS_CLIENT_ID, 3);
+	str_ncat(&msg, "&v=", 3);  str_scat(&msg, VERSION);         /* cmus' Version, that is */
+	as_lock();
+	str_scat(&tmp, as_authinfo.user);
+	as_unlock();
+	as_urlencode(&tmp);
+	str_ncat(&msg, "&u=", 3);  str_cat(&msg, &tmp);
+	str_ncat(&msg, " HTTP/1.1\r\nHost: ", 17); str_scat(&msg, AS_HOSTNAME);
+	str_ncat(&msg, "\r\nConnection: CLOSE\r\n\r\n", 23);
+
+	/* connect() */
+	if ( (sockfd = tcp_connect(AS_HOSTNAME, AS_HOSTPORT)) < 0 ) {
+		d_print("tcp_connect() failed!\n");
+		as_hsinfo.failed++;
+		goto cleanup;
+	}
+
+	/* submit our handshake request */
+	xwriten(sockfd, msg.s, msg.len);
+
+	msg.s[0] = '\0';
+	msg.len  = 0;
+
+	/* read server's response */
+	for (;;) {
+		if ( (n = xreadn(sockfd, buf, AS_BUFSIZE) ) <= 0)
+			break;
+		buf[n] = '\0';
+		str_ncat(&msg, buf, n);
+	}
+	xclose(sockfd);
+
+#ifdef AS_HEAVY_DEBUG
+	d_print("Serverreply:\n\"%s\"\n", msg.s);
+#endif
+
+	/* parse the response we read */
+	as_lock();
+	if (str_ngetidx(&msg, 0, "\r\n\r\n", 4, &idx)) {
+		str_nreplace(&msg, 0, idx+4, "", 0);
+	}
+	else {
+		d_print("broken reply, couldn't find end of headers (\\r\\n\\r\\n)\n");
+		as_hsinfo.failed++;
+		goto cleanup;
+	}
+
+	/*
+	 * UPTODATE/UPDATE/FAILED/BADUSER
+	 */
+	idx=odx=0;
+	if (!str_ngetidx(&msg, odx, "\n", 1, &idx)) {
+		d_print("Broken input: \"%s\"\n", msg.s);
+		as_hsinfo.failed++;
+		goto cleanup;
+	}
+	str_ncpy(&tmp, msg.s+odx, idx-odx);
+	if      (str_ngetidx(&tmp, 0, "UPTODATE", 8, NULL)) {
+		d_print("received UPTODATE, good.\n");
+	}
+	else if (str_ngetidx(&tmp, 0, "UPDATE", 6, NULL)) {
+		d_print("received UPDATE, get a new client, baby.\n");
+	}
+	else if (str_ngetidx(&tmp, 0, "FAILED", 6, NULL)) {
+		d_print("received FAILED, crap :-/\n");
+		as_hsinfo.failed++;
+		goto cleanup;
+	}
+	else if (str_ngetidx(&tmp, 0, "BADUSER", 7, NULL)) {
+		d_print("received BADUSER, err, check as_user setting.\n");
+		as_hsinfo.failed++;
+		goto cleanup;
+	}
+	else {
+		d_print("Unknown response: \"%s\"\n", tmp.s);
+		as_hsinfo.failed++;
+		goto cleanup;
+	}
+
+	/*
+	 * -challenge-
+	 */
+	odx=idx+1;
+	if (!str_ngetidx(&msg, odx, "\n", 1, &idx)) {
+		d_print("Broken input: \"%s\"\n", msg.s);
+		as_hsinfo.failed++;
+		goto cleanup;
+	}
+	str_ncpy(&tmp, msg.s+odx, idx-odx);
+	str_cpy(&(as_hsinfo.challenge), &tmp);
+
+	/*
+	 * -url-to-submit-script
+	 */
+	odx=idx+1;
+	if (!str_ngetidx(&msg, odx, "\n", 1, &idx)) {
+		d_print("Broken input: \"%s\"\n", msg.s);
+		as_hsinfo.failed++;
+		goto cleanup;
+	}
+	str_ncpy(&tmp, msg.s+odx, idx-odx);
+	odx=idx+1;
+	if (str_ngetidx(&tmp, 0, "://", 3, &idx)) {
+		if (idx+3 <= tmp.len)
+			str_nreplace(&tmp, 0, idx+3, "", 0);
+	}
+	else
+		d_print("hrm, no service mnemonic? (%s)\n)", tmp.s);
+
+	if (str_ngetidx(&tmp, 0, "/", 1, &idx) && (idx <= tmp.len)) {
+		str_ncpy(&as_hsinfo.path, tmp.s+idx, tmp.len-idx);
+		str_nreplace(&tmp, idx, tmp.len-idx, "", 0);
+	}
+	else {
+		d_print("um, no path? (%s)\n", tmp.s);
+		as_hsinfo.failed++;
+	}
+
+	if (str_ngetidx(&tmp, 0, ":", 1, &idx)) {
+		strncpy(as_hsinfo.port, tmp.s+idx+1, (tmp.len-idx < AS_MAXTMPLEN ? tmp.len-idx : AS_MAXTMPLEN));
+		as_hsinfo.port[(tmp.len-idx < AS_MAXTMPLEN ? tmp.len-idx : AS_MAXTMPLEN)] = '\0';
+	}
+	else {
+		d_print("well, no port definition? That, would be okay, default: 80. (%s)", tmp.s);
+		as_hsinfo.port[0] = '8'; as_hsinfo.port[1] = '0'; as_hsinfo.port[2] = '\0';
+	}
+	str_ncpy(&(as_hsinfo.host), tmp.s, idx);
+
+#ifdef AS_HEAVY_DEBUG
+	d_print("debug as_hsinfo:\n\thost: \"%s\"\n\tpath: \"%s\"\n\tport: \"%s\"\n",
+			as_hsinfo.host.s, as_hsinfo.path.s, as_hsinfo.port);
+#endif
+
+	/*
+	 * -INTERVAL-
+	 */
+	odx=idx+1;
+	if (!str_ngetidx(&msg, odx, "\n", 1, &idx)) {
+		d_print("Broken input: \"%s\"\n", msg.s);
+		goto cleanup;
+	}
+	str_ncpy(&tmp, msg.s+odx, idx-odx);
+	if (str_ngetidx(&tmp, 0, "INTERVAL ", 9, NULL)) {
+		as_hsinfo.interval=atoi(tmp.s+9);
+		d_print("Our Interval is: %d\n", (int)as_hsinfo.interval);
+	}
+
+	/* handshake suceeded, all values set. */
+	as_hsinfo.need_handshake=0;
+
+cleanup:
+	as_unlock();
+	str_free(&tmp);
+	str_free(&msg);
+} /*}}}*/
+
+static int as_submit_allowed(void)
+{ /*{{{*/
+	/*
+	 * honor INTERVAL response from the server
+	 */
+	const time_t now = time(NULL);
+	time_t at;
+	as_lock();
+	at = as_hsinfo.interval + as_lasttimestamp;
+	as_unlock();
+	if (now >= at)
+		return(1);
+	return(0);
+} /*}}}*/
+
+static int as_submit_forced(void)
+{ /*{{{*/
+	/*
+	 * queue maximum is 10, so we force submission at 8 entries
+	 */
+	unsigned int n;
+	as_lock();
+	n = as_count_entries();
+	as_unlock();
+	if (n >= AS_FORCE_SUBMIT)
+		return(1);
+	return(0);
+} /*}}}*/
+
+static void as_del_queue(struct list_head *item)
+{ /*{{{*/
+	/* make sure as_lock() was called. */
+	struct as_queue *tmp;
+
+	tmp=list_entry(item, struct as_queue, node);
+	d_print("     string: %s\n",  tmp->s.s);
+	d_print("     NEEDRN: %d\n",  tmp->needrenumber);
+	d_print("     SUBMIT: %d\n",  tmp->sc);
+	d_print("     num   : %d\n",  tmp->num);
+	str_free(&(tmp->s));
+	list_del(item);
+	free(tmp);
+} /*}}}*/
+
+static void as_destroy_queue(int scmask)
+{ /*{{{*/
+	/* make sure as_lock() was called. */
+	struct as_queue *pos, *tmp;
+
+	list_for_each_entry_safe(pos, tmp, &as_queue_head, node) {
+		if (pos->sc & scmask)
+			as_del_queue(&(pos->node));
+	}
+} /*}}}*/
+
+static void as_renumberentry(string_t *entry, unsigned short int num)
+{ /*{{{*/
+	unsigned long idx, odx = 0;
+	while (str_ngetidx(entry, odx, "]=", 2, &idx)) {
+		if (idx > 0) {
+			d_print("replacing %c with %c\n", entry->s[idx-1], as_numbers[num][0]);
+			entry->s[idx-1] = as_numbers[num][0];
+		}
+		odx=idx+2;
+	}
+} /*}}}*/
+
+static unsigned short int as_uint2string(unsigned int integer, char *str)
+{ /*{{{*/
+	unsigned short int dn, i, digit;
+	unsigned int tmp;
+
+	if (integer >= 0 && integer <= 9) {
+		dn=1;
+		str[0] = as_numbers[integer][0];
+		str[1] = '\0';
+	}
+	else {
+		dn=0;
+		tmp=integer;
+		while (tmp) {
+			++dn;
+			tmp/=10;
+		}
+	}
+	for (i=dn; i > 0 ; --i) {
+		digit=integer%10;
+		integer/=10;
+		str[i-1] = as_numbers[digit][0];
+	}
+	str[dn] = '\0';
+	return(dn);
+} /*}}}*/
+
+static void as_digest2hex(unsigned char *hex, const unsigned char *digest)
+{ /*{{{*/
+	char hc[] = "0123456789abcdef";
+	unsigned int i, e=0;
+
+	memset(hex, 0, 33*sizeof(unsigned char));
+
+	for(i = 0; i < 16; i++) {
+		hex[e++] = hc[(digest[i] >> 4) & 0x0f];
+		hex[e++] = hc[ digest[i]       & 0x0f];
+	}
+	hex[32] = '\0';
+} /*}}}*/
+
+static void as_submit(void)
+{ /*{{{*/
+	string_t msg_header, msg_content, stmp, msg;
+	struct as_queue *entry;
+	unsigned char digest[16], tmp[65], md5response[33];
+	char i2s[AS_MAXTMPLEN];
+	char buf[AS_BUFSIZE];
+	unsigned long int idx, odx, submissions=0;
+	ssize_t n;
+	int sockfd;
+	int testval;
+	int ign=0;
+
+	/*
+	 * we need authentication setup, data to submit in our queue
+	 * and the handshake must have taken place already. abort otherwise.
+	 */
+	as_lock();
+	testval = (as_authinfo.user[0] == '\0' || as_authinfo.pass[0] == '\0'
+		   || as_hsinfo.need_handshake == 1 || list_empty(&as_queue_head));
+	as_unlock();
+	if (testval)
+		return;
+
+	/*
+	 * creating the md5response described in the protocol v1.1 definition
+	 *   - create md5 checksum of $as_pass and covert it to hex
+	 *   - append the md5challenge received in handshake to the just created string
+	 *   - create md5 checksum of the concatenated string and convert it to hex.
+	 * That's it.
+	 */
+	as_lock();
+	md5_csum(as_authinfo.pass, strlen(as_authinfo.pass), digest);
+	as_digest2hex(tmp, digest);
+	strncpy(tmp+32, as_hsinfo.challenge.s, 32);
+	tmp[64] = '\0';
+	md5_csum(tmp, 64, digest);
+	as_digest2hex(md5response, digest);
+
+	str_init(&msg_header);
+	str_init(&msg_content);
+	str_init(&stmp);
+	str_init(&msg);
+	/*
+	 * assemble our payload
+	 */
+	str_ncat(&msg_content, "u=",    2); str_scat(&msg_content, as_authinfo.user);
+	str_ncat(&msg_content, "&s=",   3); str_scat(&msg_content, md5response);
+	list_for_each_entry(entry, &as_queue_head, node) {
+		if (ign && entry->sc == AS_SC_DO_SUBMIT) {
+			entry->num-=ign;
+			entry->needrenumber=1;
+		}
+		if (entry->needrenumber) {
+			as_renumberentry(&(entry->s), entry->num);
+			entry->needrenumber=0;
+		}
+		if (entry->sc == AS_SC_DO_SUBMIT) {
+			str_cat(&msg_content, &(entry->s));
+			entry->sc = AS_SC_SUBMITTED;
+			++submissions;
+			d_print("scrobbling: \"%s\"\n", entry->s.s);
+		}
+		else {
+			++ign;
+			d_print("ignoring: \"%s\"\n", entry->s.s);
+		}
+	}
+	if (!submissions) {
+		/* no submissions, eh? */
+		as_unlock();
+		goto cleanup;
+	}
+	as_uint2string(msg_content.len, i2s); /* note the length of our payload for later use */
+	str_ncat(&msg_content, "\n", 1);
+
+	/* create http/1.1 header for our submission */
+	str_ncat(&msg_header, "POST ", 5);  str_cat(&msg_header, &(as_hsinfo.path)); str_ncat(&msg_header, " HTTP/1.1\r\n", 11);
+	str_ncat(&msg_header, "Host: ", 6); str_cat(&msg_header, &(as_hsinfo.host)); str_ncat(&msg_header, "\r\n", 2);
+	str_ncat(&msg_header, AS_UASTRING, AS_UASTRINGLEN);
+	str_ncat(&msg_header, "Pragma: no-cache", 16);
+	str_ncat(&msg_header, "\r\nContent-Type: application/x-www-form-urlencoded", 49);
+	str_ncat(&msg_header, "\r\nContent-Length: ", 18); str_scat(&msg_header, i2s);
+	str_ncat(&msg_header, "\r\nConnection: CLOSE\r\n\r\n", 23);
+
+	/* turn payload's special chars to utf8 */
+	/*str_lat1_to_utf8(&msg, &msg_content);
+	str_cpy(&msg_content, &msg);*/
+
+#ifdef AS_HEAVY_DEBUG
+	d_print("POSTING: \"%s%s\"\n", msg_header.s, msg_content.s);
+#endif
+
+	as_unlock();
+
+	/*
+	 * connect to the server we got in the handshake
+	 */
+	if ( (sockfd = tcp_connect(as_hsinfo.host.s, as_hsinfo.port)) < 0 ) {
+		d_print("tcp_connect() failed!\n");
+		as_lock();
+		as_hsinfo.failed++;
+		as_unlock();
+		goto cleanup;
+	}
+
+	/*
+	 * send our submission to the server
+	 */
+	str_cpy(&msg, &msg_header);
+	str_cat(&msg, &msg_content);
+	xwriten(sockfd, msg.s, msg.len);
+	str_free(&msg);
+
+	msg_content.s[0] = '\0';
+	msg_content.len  = 0;
+
+	/* read response to our submission */
+	for (;;) {
+		if ( (n = xreadn(sockfd, buf, AS_BUFSIZE) ) <= 0)
+			break;
+		buf[n] = '\0';
+		str_ncat(&msg_content, buf, n);
+	}
+	xclose(sockfd);
+
+#ifdef AS_HEAVY_DEBUG
+	d_print("Serverreply:\n\"%s\"\n", msg_content.s);
+#endif
+
+	/* parse the response */
+	as_lock();
+	if (str_ngetidx(&msg_content, 0, "\r\n\r\n", 4, &idx)) {
+		str_nreplace(&msg_content, 0, idx+4, "", 0);
+	}
+	else {
+		d_print("broken reply, couldn't find end of headers (\\r\\n\\r\\n)\n");
+		as_hsinfo.failed++;
+		as_unlock();
+		goto cleanup;
+	}
+	odx=0;
+	if (!str_ngetidx(&msg_content, odx, "\n", 1, &idx)) {
+		d_print("Broken input: \"%s\"\n", msg_content.s);
+		as_hsinfo.failed++;
+		as_unlock();
+		goto cleanup;
+	}
+	str_ncpy(&stmp, msg_content.s+odx, idx-odx);
+
+	/* OK/FAILED/BADAUTH */
+	if      (str_ngetidx(&stmp, 0, "OK", 2, NULL)) {
+		d_print("received an OK, good.\n");
+	}
+	else if (str_ngetidx(&stmp, 0, "FAILED", 6, NULL)) {
+		stmp.s[idx] = '\0';
+		d_print("received FAILED (%s)\n", stmp.s+odx);
+		as_hsinfo.failed++;
+		as_unlock();
+		goto cleanup;
+	}
+	else if (str_ngetidx(&stmp, 0, "BADAUTH", 7, NULL)) {
+		d_print("received BADUSER, err, check as_user setting.\n");
+		as_hsinfo.failed++;
+		as_unlock();
+		goto cleanup;
+	}
+	else {
+		d_print("Unknown response: \"%s\"\n", stmp.s);
+		as_hsinfo.failed++;
+		as_unlock();
+		goto cleanup;
+	}
+
+	/* INTERVAL is optional here. */
+	odx=idx+1;
+	if (str_ngetidx(&msg_content, odx, "\n", 1, &idx)) {
+		str_ncpy(&stmp, msg_content.s+odx, idx-odx);
+		if (str_ngetidx(&stmp, 0, "INTERVAL ", 9, NULL)) {
+			as_hsinfo.interval=atoi(stmp.s+9);
+			d_print("Our Interval is: %d\n", (int)as_hsinfo.interval);
+		}
+	}
+	as_unlock();
+
+	/*
+	 * submission suceeded
+	 * destroy the part of the queue, that holds these
+	 */
+	as_destroy_queue(AS_SC_SUBMITTED);
+cleanup:
+	if (as_hsinfo.failed) {
+		/* resetting AS_SC_SUBMITTED, because something went wrong */
+		list_for_each_entry(entry, &as_queue_head, node) {
+			if (entry->sc == AS_SC_SUBMITTED)
+				entry->sc = AS_SC_DO_SUBMIT;
+		}
+	}
+	str_free(&msg);
+	str_free(&stmp);
+	str_free(&msg_header);
+	str_free(&msg_content);
+} /*}}}*/
+
+static void as_gettimestring(char *str, int maxlen)
+{ /*{{{*/
+	const time_t now = time(NULL);
+	const struct tm *timeinfo = gmtime(&now);
+	strftime(str, maxlen, "%Y%%2d%m%%2d%d%%20%H%%3a%M%%3a%S", timeinfo);
+} /*}}}*/
+
+static void as_add_queue(string_t *trackartist,
+			 string_t *trackalbum,
+			 string_t *trackname,
+			 int tracklen, int submissioncode)
+{ /*{{{*/
+	/*
+	 * Description:
+	 *  This adds a track to the submission-queue.
+	 *  Only 10 Tracks are allowed to be submitted at once.
+	 *  So, if we run past this limit, delete this first item of the list
+	 *  and append this track to the end of the list.
+	 */
+	struct as_queue *ptr, *tmp;
+	char tmpstr[AS_MAXTMPLEN+1];
+	int tmplen;
+	int queue_size;
+
+	d_print("adding track to as_queue:\n");
+	d_print("     Artist: %s\n",  trackartist->s);
+	d_print("     Album : %s\n",  trackalbum->s);
+	d_print("     Name  : %s\n",  trackname->s);
+	d_print("     Length: %d\n",  tracklen);
+
+	if (!(ptr=malloc(sizeof(struct as_queue)))) {
+		d_print("uark! out of memory? I give up.");
+		return;
+	}
+
+	as_lock();
+
+	if (list_empty(&as_queue_head)) {
+		ptr->num=0;
+		list_add_tail(&(ptr->node), &as_queue_head);
+	}
+	else {
+		/* let's find our place in the queue */
+		queue_size=as_count_entries();
+		tmp = as_get_last_entry();
+		if (queue_size >= AS_QUEUE_MAX) {
+			as_del_queue(as_queue_head.next);
+			ptr->num=AS_QUEUE_MAX;
+			list_add_tail(&(ptr->node), &as_queue_head);
+			list_for_each_entry(tmp, &as_queue_head, node) {
+				tmp->num--;
+				tmp->needrenumber=1;
+			}
+		}
+		else {
+			list_add_tail(&(ptr->node), &as_queue_head);
+			ptr->num=queue_size;
+		}
+	}
+
+	str_init(&(ptr->s));
+	ptr->sc=submissioncode;
+	ptr->needrenumber=0;
+	ptr->len=tracklen;
+	/* set up the request string */
+	as_urlencode(trackartist);
+	as_urlencode(trackalbum);
+	as_urlencode(trackname);
+	as_addtag(ptr->s, trackartist, "&a[");
+	as_addtag(ptr->s, trackname,   "&t[");
+	as_addtag(ptr->s, trackalbum,  "&b[");
+	/*
+	 * MusicBrainz ID, I'm just sending an empty one, because the
+	 * protocol definition says "don't skip it"
+	 */
+	str_readya(&(ptr->s), 6);
+	str_ncat(  &(ptr->s), "&m[", 3);
+	str_ncat(  &(ptr->s), as_numbers[ptr->num], 1);
+	str_ncat(  &(ptr->s), "]=", 2);
+	tmplen = as_uint2string(ptr->len, tmpstr);
+	as_addntag(ptr->s, tmpstr, tmplen, "&l[");
+	as_gettimestring(tmpstr, AS_MAXTMPLEN);
+	as_addntag(ptr->s, tmpstr, AS_MAXTMPLEN-1, "&i[");
+	as_unlock();
+} /*}}}*/
+
+void as_hook(enum as_actions action)
+{ /*{{{*/
+	/*
+	 * When do we need to call as_hook()?
+	 *  + stop
+	 *  + seek
+	 *  + next
+	 *  + prev
+	 *  + automatic song change
+	 *
+	 * What do we need to know?
+	 *  + track length
+	 *  + track position (we query player_info for that)
+	 *  + the action that called us
+	 *  + track name
+	 *  + artist
+	 *  + album
+	 */
+	static int lastwasinvalid   = 0; /* these 2 static ints are there */
+	static int alreadysubmitted = 0; /* to handle AS_SEEK properly.   */
+	int trackpos, tracklen;
+	int testval;
+	const char *tmp;
+	string_t trackartist, trackalbum, trackname;
+
+#ifdef AS_HEAVY_DEBUG
+	d_print("-- ping -----------------HOOOOK!1!!-------------------------------------\n");
+#endif
+
+	as_lock();
+	testval=running;
+	as_unlock();
+	if (!testval)
+		return;
+
+	if (lastwasinvalid) {
+		/*
+		 * last was invalid; if something else than SEEK
+		 * was done, this would submit the invalid track, so we
+		 * reset lastwasinvalid here and return.
+		 *
+		 * if SEEK was invoked again, we just return, because
+		 * the track was marked as invalid before.
+		 */
+		if (action != AS_SEEK)
+			lastwasinvalid = 0;
+		return;
+	}
+	if (alreadysubmitted) {
+		if (action != AS_SEEK)
+			alreadysubmitted = 0;
+		return;
+	}
+
+	player_info_lock();
+	trackpos=player_info.pos;
+
+	if (player_info.ti == NULL) {
+		player_info_unlock();
+		return;
+	}
+
+	tracklen=player_info.ti->duration;
+
+	if (tracklen < AS_TRACK_MINLEN) { /* track is not long enough */
+		player_info_unlock();
+		return;
+	}
+	if (trackpos < AS_TRACK_MINSECS && (tracklen/2 > trackpos)) { /* track did not play long enough */
+		/*
+		 * if we were called in NEXT, PREV, STOP or *_ENTER
+		 * we already did skip to the next track (or stopped),
+		 * which means, we don't need to save this one for later review.
+		 * So, we return.
+		 */
+		player_info_unlock();
+		if (action != AS_SEEK)
+			return;
+
+		lastwasinvalid = 1;
+		return;
+	}
+
+	str_init(&trackartist);
+	str_init(&trackalbum);
+	str_init(&trackname);
+	tmp=keyvals_get_val(player_info.ti->comments, "artist");
+	str_scpy(&trackartist, tmp);
+	tmp=keyvals_get_val(player_info.ti->comments, "album");
+	str_scpy(&trackalbum, tmp);
+	tmp=keyvals_get_val(player_info.ti->comments, "title");
+	str_scpy(&trackname, tmp);
+	player_info_unlock();
+
+	if (trackartist.len == 0 || trackalbum.len == 0 || trackname.len == 0) {
+		/* One of the tags we must supply is empty */
+		return;
+	}
+
+	as_add_queue(&trackartist, &trackalbum, &trackname, tracklen, AS_SC_DO_SUBMIT);
+
+	if (action == AS_SEEK)
+		alreadysubmitted = 1;
+
+	str_free(&trackartist);
+	str_free(&trackalbum);
+	str_free(&trackname);
+} /*}}}*/
+
+static void as_sleep(void)
+{ /*{{{*/
+	struct timespec r;
+	r.tv_sec=AS_SLEEPSEC;
+	r.tv_nsec=AS_SLEEPNANO;
+	nanosleep(&r, NULL);
+} /*}}}*/
+
+void *as_loop(void *arg)
+{ /*{{{*/
+	/*
+	 * main audio scrobbler loop
+	 *  Here, all communication is done.
+	 *  Needed information is provided by *as_queue,
+	 *  which is filled by as_hook()
+	 *
+	 *  TODO: AS_SC_DONT_SUBMIT cases must be handled (eg. the user used seek on a track before it fitted our needs).
+	 */
+	static int last_as_state=0;
+	int testval;
+	struct as_queue *e;
+
+	for (;;) {
+#ifdef AS_HEAVY_DEBUG
+		d_print("-- ping ----------------------------------------------------------------\n");
+#endif
+		as_update_timestamp();
+		as_lock();
+		testval = list_empty(&as_queue_head);
+		as_unlock();
+		if (testval) {
+			/* nothing to submit */
+			if (!running) {
+				/*
+				 * running isn't set,
+				 * seems like the user switched us off
+				 */
+				return(NULL);
+			}
+			as_sleep();
+		}
+		else {
+			as_lock();
+			list_for_each_entry(e, &as_queue_head, node) {
+				d_print("--DEBUG--\n");
+				d_print("     Number: %d\n",   e->num);
+				d_print("     SCode : %d\n",   e->sc);
+				d_print("     Length: %d\n",   e->len);
+				d_print("     NEEDRN: %d\n",   e->needrenumber);
+				d_print("     string: %s\n\n", e->s.s);
+			}
+			as_unlock();
+			as_sleep();
+			as_lock();
+			testval = as_hsinfo.need_handshake;
+			as_unlock();
+			if (testval) {
+				as_handshake();
+			}
+			as_lock();
+			testval = as_hsinfo.need_handshake;
+			as_unlock();
+			if (!testval) {
+				/*
+				 * we are enabled, got data to submit and don't need to handshake.
+				 * check, if we may (or need to force to) submit our data,
+				 * and do so, if needed.
+				 *
+				 * XXX: as_submit_allowed() and as_submit_forced() do locking on their own.
+				 */
+				if (as_submit_allowed() || as_submit_forced()) {
+					as_submit();
+				}
+			}
+		}
+
+		as_lock();
+		if (!as_enable && !last_as_state) {
+			/*
+			 * user didn't enable us in the first place.
+			 * so, set running to 0 and rerun loop, so we can exit
+			 */
+			running=0;
+		}
+		else if (!as_enable && last_as_state) {
+			/* as_enable was on before, so terminate session */
+			running=0;
+			last_as_state=0;
+		}
+		else if (!last_as_state && as_enable) {
+			/* (re)start as session */
+			running=1;
+			as_hsinfo.need_handshake=1;
+			last_as_state=1;
+		}
+		as_unlock();
+	}
+} /*}}}*/
+
+void as_init(void)
+{ /*{{{*/
+	int rc;
+	as_lock();
+	running=1;
+	str_init(&(as_hsinfo.challenge));
+	str_init(&(as_hsinfo.host));
+	str_init(&(as_hsinfo.path));
+	as_hsinfo.need_handshake = 1;
+	as_hsinfo.interval = 0;
+	as_hsinfo.failed = 0;
+	as_lasttimestamp = 0;
+	as_unlock();
+	rc = pthread_create(&as_thread, NULL, as_loop, NULL);
+	BUG_ON(rc);
+} /*}}}*/
+
+void as_exit(void)
+{ /*{{{*/
+	as_lock();
+	running = 0;
+	as_destroy_queue(AS_SC_ALL);
+	str_free(&(as_hsinfo.challenge));
+	str_free(&(as_hsinfo.host));
+	str_free(&(as_hsinfo.path));
+	as_unlock();
+	pthread_join(as_thread, NULL);
+} /*}}}*/
diff -urN cmus/as.h cmus-new/as.h
--- cmus/as.h	1969-12-31 19:00:00.000000000 -0500
+++ cmus-new/as.h	2008-08-22 21:25:37.000000000 -0400
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2004-2006 Timo Hirvonen
+ *
+ * as.[ch] by Frank Terbeck <ft@bewatermyfriend.org>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef x__AS_H
+#define x__AS_H
+
+/*
+ * audioscrobbler code for cmus {{{
+ *   (read: get cmus properly working with http://last.fm/
+ *
+ * When are songs submitted?
+ *  + Each song should be posted to the server when it is 50%
+ *    or 240 seconds complete, whichever comes first.
+ *
+ *  + If a user seeks (i.e. manually changes position) within a song
+ *    before the song is due to be submitted, do not submit that song.
+ *
+ *  + Songs with a duration of less than 30 seconds should not be
+ *    submitted (so, we don't do that).
+ *
+ *  + Do not try to guess info from the filename - if there are no
+ *    tags, don't submit the file (okeydoke, herman).
+ *
+ *  + If a MusicBrainz ID is present in the file (as defined here),
+ *    then you should send it (whatever that is...[1]).
+ *
+ *  + If a user is playing a stream instead of a regular file, do
+ *    not submit that stream/song.
+ *
+ *********************************************************************
+ *
+ * [1] About musicbrainz.org:
+ *      I don't know what that is, nor do I care. Feel free to
+ *      implement support for it, if you need it desperately.
+ *}}}
+ */
+
+#include "list.h"
+#include "options.h"
+#include "strlib.h"
+#include "track_info.h"
+
+/* #defines */
+
+/* We are currently testing things, so "tst" is okay. */
+#define AS_UASTRING        "User-Agent: AudioScrobbler/1.1 cmus-scrobbler code v0.01\r\n"
+#define AS_UASTRINGLEN       58
+#define AS_CLIENT_ID       "tst"
+#define AS_PROTO           "1.1"
+
+#define AS_HOSTNAME        "post.audioscrobbler.com"
+#define AS_HOSTPORT        "80"
+#define AS_HS_URI          "/?hs=true"
+
+#define AS_BUFSIZE         4096
+
+/* AS_QUEUE_MAX must of an element of [1..10] */
+#define AS_QUEUE_MAX         10
+
+/*
+ * AS_FORCE_SUBMIT:
+ * if our queue holds this many entries, force submission,
+ * no matter what the last INTERVAL response said.
+ */
+#define AS_FORCE_SUBMIT       8
+
+/* AS_DO_SUBMIT marks a track for submission */
+#define AS_SC_DO_SUBMIT       1
+
+/* AS_DONT_SUBMIT is used if seek was used at the beginning of a track */
+#define AS_SC_DONT_SUBMIT     2
+
+/* AS_SC_SUBMITTED marks tracks, that where included in a submission msg */
+#define AS_SC_SUBMITTED       4
+
+/* AS_SC_ALL is the same as ORing together all other AS_SC_* macros */
+#define AS_SC_ALL            15
+
+/* The minimum length of a track to be considered for submission */
+#define AS_TRACK_MINLEN      30
+
+/* A track must have run 50% of its time or AS_TRACK_MINSECS seconds */
+#define AS_TRACK_MINSECS    240
+
+/* AS_SLEEP* sets up the resolution of as_loop() */
+#define AS_SLEEPSEC           5
+#define AS_SLEEPNANO          0
+
+/*
+ * for temporary static strings, that need to be long enough
+ * to hold an url_encoded time string (25+1); this is used for
+ * strings that are converted integers.
+ */
+#define AS_MAXTMPLEN         30
+
+/* data types */
+enum as_actions {
+	AS_AUTONEXT=1,
+	AS_SEEK,
+	AS_NEXT,
+	AS_PREV,
+	AS_STOP,
+	AS_TREE_ENTER,
+	AS_SORTED_ENTER,
+	AS_PL_ENTER
+};
+
+struct as_queue {
+	struct list_head node;
+	string_t s;
+	unsigned int len;
+	unsigned short int sc;           /* AS_SC_* */
+	unsigned short int needrenumber; /* we must renumber 'string_t s'  */
+	unsigned short int num;          /* number of the current entry    */
+};
+
+struct as_hsinfo {
+	unsigned int need_handshake;
+	unsigned int failed;
+	time_t       interval;
+	string_t challenge;
+	string_t host;
+	string_t path;
+	char     port[AS_MAXTMPLEN+1];   /* yes, port as a string. tcp_connect expects its port as char[] */
+};
+
+struct as_authinfo {
+	char user[OPTION_MAX_SIZE];
+	char pass[OPTION_MAX_SIZE];
+};
+
+/* function prototypes */
+void as_hook(enum as_actions action);
+void *as_loop(void *arg);
+void as_exit(void);
+void as_init(void);
+
+/* macros */
+#define as_addtag(dest, src, tag)			\
+	str_readya(&(dest), 6);				\
+	str_ncat(  &(dest), tag, 3);			\
+	str_ncat(  &(dest), as_numbers[ptr->num], 1);	\
+	str_ncat(  &(dest), "]=", 2);			\
+	str_readya(&(dest), src->len);			\
+	str_cat(   &(dest), src)
+
+#define as_addntag(dest, src, len, tag)			\
+	str_readya(&(dest), 6);				\
+	str_ncat(  &(dest), tag, 3);			\
+	str_ncat(  &(dest), as_numbers[ptr->num], 1);	\
+	str_ncat(  &(dest), "]=", 2);			\
+	str_readya(&(dest), len);			\
+	str_ncat(  &(dest), src, len)
+
+#define mustquote(c) \
+	c == ' '  || \
+	c == '!'  || \
+	c == '\"' || \
+	c == '#'  || \
+	c == '%'  || \
+	c == '&'  || \
+	c == '\'' || \
+	c == '('  || \
+	c == ')'  || \
+	c == '*'  || \
+	c == '-'  || \
+	c == '/'  || \
+	c == ':'  || \
+	c == ';'  || \
+	c == '<'  || \
+	c == '='  || \
+	c == '>'  || \
+	c == '?'  || \
+	c == '@'  || \
+	c == '['  || \
+	c == '\\' || \
+	c == ']'  || \
+	c == '^'  || \
+	c == '`'  || \
+	c == '{'  || \
+	c == '|'  || \
+	c == '}'  || \
+	c == '~'
+
+#define as_lock()   cmus_mutex_lock(&as_mutex)
+#define as_unlock() cmus_mutex_unlock(&as_mutex)
+
+/* exported variables */
+extern int as_enable;
+extern struct as_authinfo as_authinfo;
+
+#endif /* x__AS_H */
diff -urN cmus/cmus.c cmus-new/cmus.c
--- cmus/cmus.c	2008-06-01 04:11:07.000000000 -0400
+++ cmus-new/cmus.c	2008-08-22 21:25:37.000000000 -0400
@@ -15,6 +15,7 @@
 #include "xmalloc.h"
 #include "xstrjoin.h"
 #include "debug.h"
+#include "as.h"
 #include "load_dir.h"
 #include "ui_curses.h"
 #include "cache.h"
diff -urN cmus/command_mode.c cmus-new/command_mode.c
--- cmus/command_mode.c	2008-06-01 04:11:07.000000000 -0400
+++ cmus-new/command_mode.c	2008-08-22 21:25:37.000000000 -0400
@@ -46,6 +46,7 @@
 #include "list.h"
 #include "debug.h"
 #include "load_dir.h"
+#include "as.h"
 #include "config/datadir.h"
 #include "help.h"
 
@@ -464,6 +465,8 @@
 	int relative = 0;
 	int seek = 0, sign = 1, count;
 
+	as_hook(AS_SEEK);
+
 	switch (*arg) {
 	case '-':
 		sign = -1;
@@ -1226,6 +1229,7 @@
 
 static void cmd_p_next(char *arg)
 {
+	as_hook(AS_NEXT);
 	cmus_next();
 }
 
@@ -1245,11 +1249,13 @@
 
 static void cmd_p_prev(char *arg)
 {
+	as_hook(AS_PREV);
 	cmus_prev();
 }
 
 static void cmd_p_stop(char *arg)
 {
+	as_hook(AS_STOP);
 	player_stop();
 }
 
diff -urN cmus/lib.c cmus-new/lib.c
--- cmus/lib.c	2008-06-01 04:11:07.000000000 -0400
+++ cmus-new/lib.c	2008-08-22 21:25:37.000000000 -0400
@@ -8,6 +8,7 @@
 #include "options.h"
 #include "xmalloc.h"
 #include "debug.h"
+#include "as.h"
 
 #include <pthread.h>
 #include <string.h>
@@ -358,6 +359,7 @@
 	if (list_empty(&lib_editable.head))
 		return NULL;
 
+	as_hook(AS_SORTED_ENTER);
 	window_get_sel(lib_editable.win, &sel);
 	return lib_set_track(iter_to_sorted_track(&sel));
 }
diff -urN cmus/md5.c cmus-new/md5.c
--- cmus/md5.c	1969-12-31 19:00:00.000000000 -0500
+++ cmus-new/md5.c	2008-08-22 21:25:37.000000000 -0400
@@ -0,0 +1,408 @@
+/*
+ *  RFC 1321 compliant MD5 implementation
+ *
+ *  Copyright (C) 2003-2006  Christophe Devine
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License, version 2.1 as published by the Free Software Foundation.
+ *
+ *  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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+/*
+ *  The MD5 algorithm was designed by Ron Rivest in 1991.
+ *
+ *  http://www.ietf.org/rfc/rfc1321.txt
+ */
+
+#ifndef _CRT_SECURE_NO_DEPRECATE
+#define _CRT_SECURE_NO_DEPRECATE 1
+#endif
+
+#include <string.h>
+#include <stdio.h>
+
+#include "md5.h"
+
+/* 
+ * 32-bit integer manipulation macros (little endian)
+ */
+#ifndef GET_UINT32_LE
+#define GET_UINT32_LE(n,b,i)                    \
+{                                               \
+    (n) = ( (ulong) (b)[(i)    ]       )        \
+        | ( (ulong) (b)[(i) + 1] <<  8 )        \
+        | ( (ulong) (b)[(i) + 2] << 16 )        \
+        | ( (ulong) (b)[(i) + 3] << 24 );       \
+}
+#endif
+#ifndef PUT_UINT32_LE
+#define PUT_UINT32_LE(n,b,i)                    \
+{                                               \
+    (b)[(i)    ] = (uchar) ( (n)       );       \
+    (b)[(i) + 1] = (uchar) ( (n) >>  8 );       \
+    (b)[(i) + 2] = (uchar) ( (n) >> 16 );       \
+    (b)[(i) + 3] = (uchar) ( (n) >> 24 );       \
+}
+#endif
+
+/*
+ * Core MD5 functions
+ */
+void md5_starts( md5_context *ctx )
+{
+    ctx->total[0] = 0;
+    ctx->total[1] = 0;
+
+    ctx->state[0] = 0x67452301;
+    ctx->state[1] = 0xEFCDAB89;
+    ctx->state[2] = 0x98BADCFE;
+    ctx->state[3] = 0x10325476;
+}
+
+static void md5_process( md5_context *ctx, uchar data[64] )
+{
+    ulong X[16], A, B, C, D;
+
+    GET_UINT32_LE( X[0],  data,  0 );
+    GET_UINT32_LE( X[1],  data,  4 );
+    GET_UINT32_LE( X[2],  data,  8 );
+    GET_UINT32_LE( X[3],  data, 12 );
+    GET_UINT32_LE( X[4],  data, 16 );
+    GET_UINT32_LE( X[5],  data, 20 );
+    GET_UINT32_LE( X[6],  data, 24 );
+    GET_UINT32_LE( X[7],  data, 28 );
+    GET_UINT32_LE( X[8],  data, 32 );
+    GET_UINT32_LE( X[9],  data, 36 );
+    GET_UINT32_LE( X[10], data, 40 );
+    GET_UINT32_LE( X[11], data, 44 );
+    GET_UINT32_LE( X[12], data, 48 );
+    GET_UINT32_LE( X[13], data, 52 );
+    GET_UINT32_LE( X[14], data, 56 );
+    GET_UINT32_LE( X[15], data, 60 );
+
+#define S(x,n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n)))
+
+#define P(a,b,c,d,k,s,t)                                \
+{                                                       \
+    a += F(b,c,d) + X[k] + t; a = S(a,s) + b;           \
+}
+
+    A = ctx->state[0];
+    B = ctx->state[1];
+    C = ctx->state[2];
+    D = ctx->state[3];
+
+#define F(x,y,z) (z ^ (x & (y ^ z)))
+
+    P( A, B, C, D,  0,  7, 0xD76AA478 );
+    P( D, A, B, C,  1, 12, 0xE8C7B756 );
+    P( C, D, A, B,  2, 17, 0x242070DB );
+    P( B, C, D, A,  3, 22, 0xC1BDCEEE );
+    P( A, B, C, D,  4,  7, 0xF57C0FAF );
+    P( D, A, B, C,  5, 12, 0x4787C62A );
+    P( C, D, A, B,  6, 17, 0xA8304613 );
+    P( B, C, D, A,  7, 22, 0xFD469501 );
+    P( A, B, C, D,  8,  7, 0x698098D8 );
+    P( D, A, B, C,  9, 12, 0x8B44F7AF );
+    P( C, D, A, B, 10, 17, 0xFFFF5BB1 );
+    P( B, C, D, A, 11, 22, 0x895CD7BE );
+    P( A, B, C, D, 12,  7, 0x6B901122 );
+    P( D, A, B, C, 13, 12, 0xFD987193 );
+    P( C, D, A, B, 14, 17, 0xA679438E );
+    P( B, C, D, A, 15, 22, 0x49B40821 );
+
+#undef F
+
+#define F(x,y,z) (y ^ (z & (x ^ y)))
+
+    P( A, B, C, D,  1,  5, 0xF61E2562 );
+    P( D, A, B, C,  6,  9, 0xC040B340 );
+    P( C, D, A, B, 11, 14, 0x265E5A51 );
+    P( B, C, D, A,  0, 20, 0xE9B6C7AA );
+    P( A, B, C, D,  5,  5, 0xD62F105D );
+    P( D, A, B, C, 10,  9, 0x02441453 );
+    P( C, D, A, B, 15, 14, 0xD8A1E681 );
+    P( B, C, D, A,  4, 20, 0xE7D3FBC8 );
+    P( A, B, C, D,  9,  5, 0x21E1CDE6 );
+    P( D, A, B, C, 14,  9, 0xC33707D6 );
+    P( C, D, A, B,  3, 14, 0xF4D50D87 );
+    P( B, C, D, A,  8, 20, 0x455A14ED );
+    P( A, B, C, D, 13,  5, 0xA9E3E905 );
+    P( D, A, B, C,  2,  9, 0xFCEFA3F8 );
+    P( C, D, A, B,  7, 14, 0x676F02D9 );
+    P( B, C, D, A, 12, 20, 0x8D2A4C8A );
+
+#undef F
+    
+#define F(x,y,z) (x ^ y ^ z)
+
+    P( A, B, C, D,  5,  4, 0xFFFA3942 );
+    P( D, A, B, C,  8, 11, 0x8771F681 );
+    P( C, D, A, B, 11, 16, 0x6D9D6122 );
+    P( B, C, D, A, 14, 23, 0xFDE5380C );
+    P( A, B, C, D,  1,  4, 0xA4BEEA44 );
+    P( D, A, B, C,  4, 11, 0x4BDECFA9 );
+    P( C, D, A, B,  7, 16, 0xF6BB4B60 );
+    P( B, C, D, A, 10, 23, 0xBEBFBC70 );
+    P( A, B, C, D, 13,  4, 0x289B7EC6 );
+    P( D, A, B, C,  0, 11, 0xEAA127FA );
+    P( C, D, A, B,  3, 16, 0xD4EF3085 );
+    P( B, C, D, A,  6, 23, 0x04881D05 );
+    P( A, B, C, D,  9,  4, 0xD9D4D039 );
+    P( D, A, B, C, 12, 11, 0xE6DB99E5 );
+    P( C, D, A, B, 15, 16, 0x1FA27CF8 );
+    P( B, C, D, A,  2, 23, 0xC4AC5665 );
+
+#undef F
+
+#define F(x,y,z) (y ^ (x | ~z))
+
+    P( A, B, C, D,  0,  6, 0xF4292244 );
+    P( D, A, B, C,  7, 10, 0x432AFF97 );
+    P( C, D, A, B, 14, 15, 0xAB9423A7 );
+    P( B, C, D, A,  5, 21, 0xFC93A039 );
+    P( A, B, C, D, 12,  6, 0x655B59C3 );
+    P( D, A, B, C,  3, 10, 0x8F0CCC92 );
+    P( C, D, A, B, 10, 15, 0xFFEFF47D );
+    P( B, C, D, A,  1, 21, 0x85845DD1 );
+    P( A, B, C, D,  8,  6, 0x6FA87E4F );
+    P( D, A, B, C, 15, 10, 0xFE2CE6E0 );
+    P( C, D, A, B,  6, 15, 0xA3014314 );
+    P( B, C, D, A, 13, 21, 0x4E0811A1 );
+    P( A, B, C, D,  4,  6, 0xF7537E82 );
+    P( D, A, B, C, 11, 10, 0xBD3AF235 );
+    P( C, D, A, B,  2, 15, 0x2AD7D2BB );
+    P( B, C, D, A,  9, 21, 0xEB86D391 );
+
+#undef F
+
+    ctx->state[0] += A;
+    ctx->state[1] += B;
+    ctx->state[2] += C;
+    ctx->state[3] += D;
+}
+
+void md5_update( md5_context *ctx, uchar *input, uint length )
+{
+    ulong left, fill;
+
+    if( ! length ) return;
+
+    left = ctx->total[0] & 0x3F;
+    fill = 64 - left;
+
+    ctx->total[0] += length;
+    ctx->total[0] &= 0xFFFFFFFF;
+
+    if( ctx->total[0] < length )
+        ctx->total[1]++;
+
+    if( left && length >= fill )
+    {
+        memcpy( (void *) (ctx->buffer + left),
+                (void *) input, fill );
+        md5_process( ctx, ctx->buffer );
+        length -= fill;
+        input  += fill;
+        left = 0;
+    }
+
+    while( length >= 64 )
+    {
+        md5_process( ctx, input );
+        length -= 64;
+        input  += 64;
+    }
+
+    if( length )
+    {
+        memcpy( (void *) (ctx->buffer + left),
+                (void *) input, length );
+    }
+}
+
+static uchar md5_padding[64] =
+{
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+void md5_finish( md5_context *ctx, uchar digest[16] )
+{
+    ulong last, padn;
+    ulong high, low;
+    uchar msglen[8];
+
+    high = ( ctx->total[0] >> 29 )
+         | ( ctx->total[1] <<  3 );
+    low  = ( ctx->total[0] <<  3 );
+
+    PUT_UINT32_LE( low,  msglen, 0 );
+    PUT_UINT32_LE( high, msglen, 4 );
+
+    last = ctx->total[0] & 0x3F;
+    padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last );
+
+    md5_update( ctx, md5_padding, padn );
+    md5_update( ctx, msglen, 8 );
+
+    PUT_UINT32_LE( ctx->state[0], digest,  0 );
+    PUT_UINT32_LE( ctx->state[1], digest,  4 );
+    PUT_UINT32_LE( ctx->state[2], digest,  8 );
+    PUT_UINT32_LE( ctx->state[3], digest, 12 );
+}
+
+/*
+ * Output MD5(file contents), returns 0 if successful.
+ */
+int md5_file( char *filename, uchar digest[16] )
+{
+    FILE *f;
+    size_t n;
+    md5_context ctx;
+    uchar buf[1024];
+
+    if( ( f = fopen( filename, "rb" ) ) == NULL )
+        return( 1 );
+
+    md5_starts( &ctx );
+
+    while( ( n = fread( buf, 1, sizeof( buf ), f ) ) > 0 )
+        md5_update( &ctx, buf, (uint) n );
+
+    md5_finish( &ctx, digest );
+
+    fclose( f );
+    return( 0 );
+}
+
+/*
+ * Output MD5(buf)
+ */
+void md5_csum( uchar *buf, uint buflen, uchar digest[16] )
+{
+    md5_context ctx;
+
+    md5_starts( &ctx );
+    md5_update( &ctx, buf, buflen );
+    md5_finish( &ctx, digest );
+}
+
+/*
+ * Output HMAC-MD5(key,buf)
+ */
+void md5_hmac( uchar *key, uint keylen, uchar *buf, uint buflen,
+               uchar digest[16] )
+{
+    uint i;
+    md5_context ctx;
+    uchar k_ipad[64];
+    uchar k_opad[64];
+    uchar tmpbuf[16];
+
+    memset( k_ipad, 0x36, 64 );
+    memset( k_opad, 0x5C, 64 );
+
+    for( i = 0; i < keylen; i++ )
+    {
+        if( i >= 64 ) break;
+
+        k_ipad[i] ^= key[i];
+        k_opad[i] ^= key[i];
+    }
+
+    md5_starts( &ctx );
+    md5_update( &ctx, k_ipad, 64 );
+    md5_update( &ctx, buf, buflen );
+    md5_finish( &ctx, tmpbuf );
+
+    md5_starts( &ctx );
+    md5_update( &ctx, k_opad, 64 );
+    md5_update( &ctx, tmpbuf, 16 );
+    md5_finish( &ctx, digest );
+
+    memset( k_ipad, 0, 64 );
+    memset( k_opad, 0, 64 );
+    memset( tmpbuf, 0, 16 );
+    memset( &ctx, 0, sizeof( md5_context ) );
+}
+
+#ifdef SELF_TEST
+/* 
+ * RFC 1321 test vectors
+ */
+static char *md5_test_str[7] =
+{
+    "",
+    "a",
+    "abc",
+    "message digest",
+    "abcdefghijklmnopqrstuvwxyz",
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
+    "12345678901234567890123456789012345678901234567890123456789012" \
+        "345678901234567890"
+};
+
+static uchar md5_test_sum[7][16] =
+{
+    { 0xD4, 0x1D, 0x8C, 0xD9, 0x8F, 0x00, 0xB2, 0x04,
+      0xE9, 0x80, 0x09, 0x98, 0xEC, 0xF8, 0x42, 0x7E },
+    { 0x0C, 0xC1, 0x75, 0xB9, 0xC0, 0xF1, 0xB6, 0xA8,
+      0x31, 0xC3, 0x99, 0xE2, 0x69, 0x77, 0x26, 0x61 },
+    { 0x90, 0x01, 0x50, 0x98, 0x3C, 0xD2, 0x4F, 0xB0,
+      0xD6, 0x96, 0x3F, 0x7D, 0x28, 0xE1, 0x7F, 0x72 },
+    { 0xF9, 0x6B, 0x69, 0x7D, 0x7C, 0xB7, 0x93, 0x8D,
+      0x52, 0x5A, 0x2F, 0x31, 0xAA, 0xF1, 0x61, 0xD0 },
+    { 0xC3, 0xFC, 0xD3, 0xD7, 0x61, 0x92, 0xE4, 0x00,
+      0x7D, 0xFB, 0x49, 0x6C, 0xCA, 0x67, 0xE1, 0x3B },
+    { 0xD1, 0x74, 0xAB, 0x98, 0xD2, 0x77, 0xD9, 0xF5,
+      0xA5, 0x61, 0x1C, 0x2C, 0x9F, 0x41, 0x9D, 0x9F },
+    { 0x57, 0xED, 0xF4, 0xA2, 0x2B, 0xE3, 0xC9, 0x55,
+      0xAC, 0x49, 0xDA, 0x2E, 0x21, 0x07, 0xB6, 0x7A }
+};
+
+/*
+ * Checkup routine
+ */
+int md5_self_test( void )
+{
+    int i;
+    uchar md5sum[16];
+
+    for( i = 0; i < 7; i++ )
+    {
+        printf( "  MD5 test #%d: ", i + 1 );
+
+        md5_csum( (uchar *) md5_test_str[i],
+                    strlen( md5_test_str[i] ), md5sum );
+
+        if( memcmp( md5sum, md5_test_sum[i], 16 ) != 0 )
+        {
+            printf( "failed\n" );
+            return( 1 );
+        }
+
+        printf( "passed\n" );
+    }
+
+    printf( "\n" );
+    return( 0 );
+}
+#else
+int md5_self_test( void )
+{
+    printf( "MD5 self-test not available\n\n" );
+    return( 1 );
+}
+#endif
diff -urN cmus/md5.h cmus-new/md5.h
--- cmus/md5.h	1969-12-31 19:00:00.000000000 -0500
+++ cmus-new/md5.h	2008-08-22 21:25:37.000000000 -0400
@@ -0,0 +1,57 @@
+#ifndef _MD5_H
+#define _MD5_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef _STD_TYPES
+#define _STD_TYPES
+
+#define uchar   unsigned char
+#define uint    unsigned int
+#define ulong   unsigned long int
+
+#endif
+
+typedef struct
+{
+    ulong total[2];
+    ulong state[4];
+    uchar buffer[64];
+}
+md5_context;
+
+/*
+ * Core MD5 functions
+ */
+void md5_starts( md5_context *ctx );
+void md5_update( md5_context *ctx, uchar *input, uint length );
+void md5_finish( md5_context *ctx, uchar digest[16] );
+
+/*
+ * Output MD5(file contents), returns 0 if successful.
+ */
+int md5_file( char *filename, uchar digest[16] );
+
+/*
+ * Output MD5(buf)
+ */
+void md5_csum( uchar *buf, uint buflen, uchar digest[16] );
+
+/*
+ * Output HMAC-MD5(key,buf)
+ */
+void md5_hmac( uchar *key, uint keylen, uchar *buf, uint buflen,
+               uchar digest[16] );
+
+/*
+ * Checkup routine
+ */
+int md5_self_test( void );
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* md5.h */
diff -urN cmus/network.c cmus-new/network.c
--- cmus/network.c	1969-12-31 19:00:00.000000000 -0500
+++ cmus-new/network.c	2008-08-22 21:25:37.000000000 -0400
@@ -0,0 +1,305 @@
+/*
+ * most of this code is base on or copied from the
+ * "UNIX Network Programming" Book by W.Richard Stevens, Bill Fenner
+ * and Andrew M. Rudoff (ISBN: 0-13-141155-1)
+ */
+
+#include "debug.h"
+#include "network.h"
+
+/*
+ * tcp_connect()
+ *   connect to host:serv
+ */
+int
+tcp_connect(const char *host, const char *serv)
+{ /*{{{*/
+	int    sockfd, n;
+	struct addrinfo hints, *res, *ressave;
+
+	memset(&hints, 0, sizeof(struct addrinfo));
+	hints.ai_family = AF_UNSPEC;
+	hints.ai_socktype = SOCK_STREAM;
+
+	if ( (n = getaddrinfo(host, serv, &hints, &res)) != 0) {
+		errno_msg("tcp_connect() error for %s, %s: %s",
+			  host, serv, gai_strerror(n));
+		return(-1);
+	}
+	ressave = res;
+
+	do {
+		sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+		if (sockfd < 0)
+			continue; /* ignore this one */
+
+		if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
+			break;    /* success */
+
+		xclose(sockfd);  /* ignore this one */
+	} while ( (res = res->ai_next) != NULL);
+
+	if (res == NULL)  /* errno set from final connect() */
+		errno_msg("tcp_connect() error for %s, %s", host, serv);
+
+	freeaddrinfo(ressave);
+
+	return(sockfd);
+} /*}}}*/
+
+/*
+ * sock_ntop()
+ *   protocol (IPv4 vs. IPv6) independent version of inet_ntop
+ */
+char *
+sock_ntop(const struct sockaddr *sa, socklen_t salen)
+{ /*{{{*/
+	char   portstr[8];
+	static char str[128];   /* Unix domain is largest */
+	struct sockaddr_in  *sin  = (struct sockaddr_in  *) sa;
+#ifdef AF_INET6
+	struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa;
+#endif
+
+	switch (sa->sa_family) {
+	case AF_INET:
+		if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL)
+			return(NULL);
+		if (ntohs(sin->sin_port) != 0) {
+			snprintf(portstr, sizeof(portstr), ":%d", ntohs(sin->sin_port));
+			strcat(str, portstr);
+		}
+		return(str);
+
+#ifdef AF_INET6
+	case AF_INET6:
+		str[0] = '[';
+		if (inet_ntop(AF_INET6, &sin6->sin6_addr, str + 1, sizeof(str) - 1) == NULL)
+			return(NULL);
+		if (ntohs(sin6->sin6_port) != 0) {
+			snprintf(portstr, sizeof(portstr), "]:%d", ntohs(sin6->sin6_port));
+			strcat(str, portstr);
+			return(str);
+		}
+		return (str + 1);
+#endif
+
+	default:
+		snprintf(str, sizeof(str), "sock_ntop: unknown AF_xxx: %d, len %d",
+			 sa->sa_family, salen);
+		return(str);
+	}
+	return (NULL);
+} /*}}}*/
+
+/* wrapper for sock_ntop(), that handles error codes */
+char *
+xsock_ntop(const struct sockaddr *sa, socklen_t salen)
+{ /*{{{*/
+	char *ptr;
+
+	if ( (ptr = sock_ntop(sa, salen)) == NULL)
+		errno_msg("sock_ntop() error");
+	return(ptr);
+} /*}}}*/
+
+/*
+ * sock_ntop_host()
+ *   similar to sock_ntop(), but only returns the host portion (not the port)
+ */
+char *
+sock_ntop_host(const struct sockaddr *sa, socklen_t salen)
+{ /*{{{*/
+	static char str[128];   /* Unix domain is largest */
+	struct sockaddr_in  *sin = (struct sockaddr_in *) sa;
+#ifdef AF_INET6
+	struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa;
+#endif
+
+	switch (sa->sa_family) {
+	case AF_INET:
+		if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL)
+			return(NULL);
+		return(str);
+
+#ifdef AF_INET6
+	case AF_INET6:
+		if (inet_ntop(AF_INET6, &sin6->sin6_addr, str, sizeof(str)) == NULL)
+			return(NULL);
+		return(str);
+#endif
+
+	default:
+		snprintf(str, sizeof(str), "sock_ntop_host: unknown AF_xxx: %d, len %d",
+		sa->sa_family, salen);
+		return(str);
+	}
+	return (NULL);
+} /*}}}*/
+
+/* wrapper for sock_ntop_host(), that handles error codes */
+char *
+xsock_ntop_host(const struct sockaddr *sa, socklen_t salen)
+{ /*{{{*/
+	char  *ptr;
+
+	if ( (ptr = sock_ntop_host(sa, salen)) == NULL)
+		errno_msg("sock_ntop_host() error");  /* inet_ntop() sets errno */
+	return(ptr);
+} /*}}}*/
+
+/*
+ * sock_get_port()
+ *   protocol independent way to get the port of a socket
+ */
+int
+sock_get_port(const struct sockaddr *sa, socklen_t salen)
+{ /*{{{*/
+	struct sockaddr_in  *sin  = (struct sockaddr_in  *) sa;
+#ifdef AF_INET6
+	struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa;
+#endif
+
+	switch (sa->sa_family) {
+	case AF_INET:
+		return(sin->sin_port);
+
+#ifdef AF_INET6
+	case AF_INET6:
+		return(sin6->sin6_port);
+#endif
+	}
+	return(-1);
+} /*}}}*/
+
+/*
+ * sock_set_addr()
+ *   protocol independent way to set the address of a socket
+ */
+void
+sock_set_addr(struct sockaddr *sa, socklen_t salen, const void *addr)
+{ /*{{{*/
+	struct sockaddr_in  *sin  = (struct sockaddr_in  *) sa;
+#ifdef AF_INET6
+	struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa;
+#endif
+
+	switch (sa->sa_family) {
+	case AF_INET:
+		memcpy(&sin->sin_addr, addr, sizeof(struct in_addr));
+	return;
+
+#ifdef AF_INET6
+	case AF_INET6:
+		memcpy(&sin6->sin6_addr, addr, sizeof(struct in6_addr));
+	return;
+#endif
+	}
+} /*}}}*/
+
+/*
+ * sock_set_addr()
+ *   protocol independent way to set the port of a socket
+ */
+void
+sock_set_port(struct sockaddr *sa, socklen_t salen, int port)
+{ /*{{{*/
+	struct sockaddr_in  *sin  = (struct sockaddr_in  *) sa;
+#ifdef AF_INET6
+	struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa;
+#endif
+
+	switch (sa->sa_family) {
+	case AF_INET:
+		sin->sin_port = port;
+		return;
+
+#ifdef AF_INET6
+	case AF_INET6:
+		sin6->sin6_port = port;
+		return;
+#endif
+	}
+	return;
+} /*}}}*/
+
+/*
+ * readn()
+ *   read 'n' bytes from a filedescriptor
+ */
+ssize_t
+readn(int fd, void *vptr, size_t n)
+{ /*{{{*/
+	size_t  nleft;
+	ssize_t nread;
+	char  *ptr;
+
+	ptr = vptr;
+	nleft = n;
+	while (nleft > 0) {
+		if ( (nread = read(fd, ptr, nleft)) < 0) {
+			if (errno == EINTR)
+				nread = 0;    /* and call read() again */
+			else
+				return(-1);
+		}
+		else if (nread == 0) {
+			break; /* EOF */
+		}
+
+		nleft -= nread;
+		ptr   += nread;
+	}
+	return(n - nleft);    /* return >= 0 */
+} /*}}}*/
+
+ssize_t
+xreadn(int fd, void *ptr, size_t nbytes)
+{ /*{{{*/
+	ssize_t   n;
+
+	if ( (n = readn(fd, ptr, nbytes)) < 0)
+		errno_msg("readn() error");
+	return(n);
+} /*}}}*/
+
+/*
+ * writen
+ *   write 'n' bytes to a descriptor
+ */
+ssize_t
+writen(int fd, const void *vptr, size_t n)
+{ /*{{{*/
+	size_t    nleft;
+	ssize_t   nwritten;
+	const char  *ptr;
+
+	ptr = vptr;
+	nleft = n;
+	while (nleft > 0) {
+		if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
+			if (nwritten < 0 && errno == EINTR)
+				nwritten = 0;   /* and call write() again */
+			else
+				return(-1);     /* error */
+		}
+
+		nleft -= nwritten;
+		ptr   += nwritten;
+	}
+	return(n);
+} /*}}}*/
+
+void
+xwriten(int fd, void *ptr, size_t nbytes)
+{ /*{{{*/
+	if (writen(fd, ptr, nbytes) != nbytes)
+		errno_msg("writen() error");
+} /*}}}*/
+
+void
+xclose(int fd)
+{ /*{{{*/
+	if (close(fd) == -1)
+		errno_msg("close() error");
+} /*}}}*/
diff -urN cmus/network.h cmus-new/network.h
--- cmus/network.h	1969-12-31 19:00:00.000000000 -0500
+++ cmus-new/network.h	2008-08-22 21:25:37.000000000 -0400
@@ -0,0 +1,37 @@
+#ifndef x__NETWORK_H
+#define x__NETWORK_H
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "compiler.h"
+
+#define MAX_ERR_LINE 512
+
+int      tcp_connect(const char *host, const char *serv);
+char    *sock_ntop(const struct sockaddr *sa, socklen_t salen);
+char    *sock_ntop_host(const struct sockaddr *sa, socklen_t salen);
+int      sock_get_port(const struct sockaddr *sa, socklen_t salen);
+void     sock_set_addr(struct sockaddr *sa, socklen_t salen, const void *ptr);
+void     sock_set_port(struct sockaddr *sa, socklen_t salen, int port);
+ssize_t  readn(int fd, void *vptr, size_t n);
+ssize_t  writen(int fd, const void *vptr, size_t n);
+
+/* wrapper functions */
+char    *xsock_ntop(const struct sockaddr *sa, socklen_t salen);
+char    *xsock_ntop_host(const struct sockaddr *sa, socklen_t salen);
+ssize_t  xreadn(int fd, void *ptr, size_t nbytes);
+void     xwriten(int fd, void *ptr, size_t nbytes);
+void     xclose(int fd);
+
+/* helper functions */
+void errno_msg(const char *fmt, ...) __FORMAT(1, 2);
+
+#endif /* x__NETWORK_H */
diff -urN cmus/options.c cmus-new/options.c
--- cmus/options.c	2008-06-01 04:11:07.000000000 -0400
+++ cmus-new/options.c	2008-08-22 21:25:37.000000000 -0400
@@ -21,6 +21,7 @@
 #include "file.h"
 #include "prog.h"
 #include "output.h"
+#include "as.h"
 #include "config/datadir.h"
 
 #include <stdio.h>
@@ -259,6 +260,30 @@
 		editable_set_sort_keys(&lib_editable, keys);
 }
 
+static void get_as_pass(unsigned int id, char *buf)
+{
+	strncpy(buf, as_authinfo.pass, OPTION_MAX_SIZE-1);
+	buf[OPTION_MAX_SIZE-1]='\0';
+}
+
+static void set_as_pass(unsigned int id, const char *buf)
+{
+	strncpy(as_authinfo.pass, buf, OPTION_MAX_SIZE-1);
+	as_authinfo.pass[OPTION_MAX_SIZE-1]='\0';
+}
+
+static void get_as_user(unsigned int id, char *buf)
+{
+	strncpy(buf, as_authinfo.user, OPTION_MAX_SIZE-1);
+	buf[OPTION_MAX_SIZE-1]='\0';
+}
+
+static void set_as_user(unsigned int id, const char *buf)
+{
+	strncpy(as_authinfo.user, buf, OPTION_MAX_SIZE-1);
+	as_authinfo.pass[OPTION_MAX_SIZE-1]='\0';
+}
+
 static void get_pl_sort(unsigned int id, char *buf)
 {
 	strcpy(buf, pl_editable.sort_str);
@@ -395,6 +420,31 @@
 	auto_reshuffle ^= 1;
 }
 
+static void get_as_enable(unsigned int id, char *buf)
+{
+	strcpy(buf, bool_names[as_enable]);
+}
+
+static void set_as_enable(unsigned int id, const char *buf)
+{
+	int old=as_enable;
+	if (parse_bool(buf, &as_enable) && !old) {
+		/* switch from off to on */
+		as_init();
+		return;
+	}
+	if (!as_enable && old) {
+		/* switch from on to off */
+		as_exit();
+	}
+}
+
+static void toggle_as_enable(unsigned int id)
+{
+	if (as_enable) set_as_enable(id, "false");
+	else           set_as_enable(id, "true");
+}
+
 static void get_continue(unsigned int id, char *buf)
 {
 	strcpy(buf, bool_names[player_cont]);
@@ -816,6 +866,9 @@
 	opt_toggle_cb toggle;
 } simple_options[] = {
 	DT(aaa_mode)
+	DT(as_enable)
+	DN(as_pass)
+	DN(as_user)
 	DT(auto_reshuffle)
 	DN(buffer_seconds)
 	DT(confirm_run)
@@ -884,9 +937,10 @@
 	{ "format_playlist",	" %-20a %02n. %t%= %y %d " },
 	{ "format_title",	"%a - %l - %t (%y)" },
 	{ "format_trackwin",	" %02n. %t%= %y %d " },
-
 	{ "lib_sort"	,	"artist album discnumber tracknumber title filename" },
 	{ "pl_sort",		"" },
+	{ "as_pass",		"" },
+	{ "as_user",		"" },
 	{ "id3_default_charset","ISO-8859-1" },
 	{ NULL, NULL }
 };
diff -urN cmus/pl.c cmus-new/pl.c
--- cmus/pl.c	2008-06-01 04:11:07.000000000 -0400
+++ cmus-new/pl.c	2008-08-22 21:25:37.000000000 -0400
@@ -6,6 +6,7 @@
 #include "editable.h"
 #include "options.h"
 #include "xmalloc.h"
+#include "as.h"
 
 struct editable pl_editable;
 struct simple_track *pl_cur_track = NULL;
@@ -86,6 +87,7 @@
 	if (list_empty(&pl_editable.head))
 		return NULL;
 
+	as_hook(AS_PL_ENTER);
 	window_get_sel(pl_editable.win, &sel);
 	return set_track(iter_to_simple_track(&sel));
 }
diff -urN cmus/player.c cmus-new/player.c
--- cmus/player.c	2008-06-01 04:11:07.000000000 -0400
+++ cmus-new/player.c	2008-08-22 21:25:37.000000000 -0400
@@ -26,6 +26,7 @@
 #include "xmalloc.h"
 #include "debug.h"
 #include "compiler.h"
+#include "as.h"
 
 #include <stdlib.h>
 #include <pthread.h>
@@ -299,6 +300,7 @@
 
 static inline int get_next(struct track_info **ti)
 {
+	as_hook(AS_AUTONEXT);
 	return player_cbs->get_next(ti);
 }
 
diff -urN cmus/strlib.c cmus-new/strlib.c
--- cmus/strlib.c	1969-12-31 19:00:00.000000000 -0500
+++ cmus-new/strlib.c	2008-08-22 21:25:37.000000000 -0400
@@ -0,0 +1,357 @@
+/*
+ * strlib.[ch] - a string handling library
+ *  See strlib.h for notes about libowfat (the code this is based on).
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "debug.h"
+#include "strlib.h"
+
+/*
+ * {con,de}structor
+ */
+void str_init(string_t *st)
+{ /*{{{*/
+	st->s=0;
+	st->len=st->all=0;
+} /*}}}*/
+
+void str_free(string_t *st)
+{ /*{{{*/
+	if (st->s && st->all > 0) free(st->s);
+	st->s=0;
+	st->len=st->all=0;
+} /*}}}*/
+
+/*
+ * allocation functions
+ */
+int str_ready(string_t *st, unsigned long int len)
+{ /*{{{*/
+	/*
+	 * makes sure st can take up at least len bytes.
+	 * this code allocates a few more bytes tthan needed,
+	 * so that small additions can be made without realloc()s
+	 * return 1 on success 0 otherwise (as in: realloc() fails)
+	 */
+	int c=len+(len>>3)+30;
+	char *tmp;
+	if (!st->s || st->all<len) {
+		/* mem needed */
+		if (!(tmp = realloc(st->s, c))) return 0;
+		st->all=c;
+		st->s=tmp;
+	}
+	return 1;
+} /*}}}*/
+
+int str_readya(string_t *st, unsigned long int len)
+{ /*{{{*/
+	/*
+	 * adds at least len bytes to the current length of *st
+	 */
+	if (st->s) {
+		if (st->len + len < len) return 0;
+		return str_ready(st, st->len + len);
+	}
+	else
+		return str_ready(st, len);
+} /*}}}*/
+
+/*
+ * string copy functions
+ */
+int str_ncpy(string_t *st, const char *buf, unsigned long int len)
+{ /*{{{*/
+	/*
+	 * copy len bytes from *buf into *st
+	 * return 1 on success, 0 otherwise.
+	 */
+	unsigned long int i;
+	if (buf == NULL || len == 0) {
+		st->len=0;
+		if (st->all != 0)
+			st->s[0] = '\0';
+		return(1);
+	}
+	if (!str_ready(st, len)) return(0);
+	/*
+	 * FIXME: these copying loops should be put into a sep. function
+	 *        to keep the code maintainable.
+	 */
+	for (i=0; i <= len - 1; i++) {
+		st->s[i] = buf[i];
+	}
+	st->s[len] = '\0';
+	st->len=len;
+	return(1);
+} /*}}}*/
+
+int str_cpy(string_t *st0, string_t *st1)
+{ /*{{{*/
+	/*
+	 * copy *st1's string into *st0
+	 */
+	return(str_ncpy(st0, st1->s, st1->len));
+} /*}}}*/
+
+/*
+ * concatenation functions
+ */
+int str_ncat(string_t *st, const char *buf, unsigned long int len)
+{ /*{{{*/
+	/*
+	 * add *buf to *st
+	 */
+	unsigned long int i;
+	if (len == 0 || buf == NULL) {
+		return(1);
+	}
+	if (!str_readya(st, len+1)) return(0);
+	for (i=st->len; i <= st->len + len - 1; i++) {
+		st->s[i] = buf[i - st->len];
+	}
+	st->len+=len;
+	st->s[st->len]='\0';
+	return(1);
+} /*}}}*/
+
+int str_cat(string_t *st0, string_t *st1)
+{ /*{{{*/
+	/*
+	 * add *st1 to *st0 (*st1 must be allocated already);
+	 */
+	return(str_ncat(st0, st1->s, st1->len));
+} /*}}}*/
+
+/*
+ * comparison functions
+ */
+int str_compstart(string_t *st, const char *buf)
+{ /*{{{*/
+	/* 
+	 * check if *st starts with *buf.
+	 * return 1 if that's the case, 0 otherwise
+	 */
+	unsigned long int i;
+	unsigned long int len=strlen(buf) - 1;
+	if (st->len <= len) return(0);
+	for (i=0; i<=len; i++)
+		if (st->s[i]!=buf[i]) return(0);
+	return(1);
+} /*}}}*/
+
+int str_scomp(string_t *st, const char *buf)
+{ /*{{{*/
+	/*
+	 * lexically compare *st with the *buf character array.
+	 * return -1, 1, or 0
+	 */
+	unsigned long int i, j;
+	for (i=0;;++i) {
+		if (i==st->len)
+			return( !buf[i] ? 0 : -1 );
+		if (!buf[i])
+			return 1;
+		j = (unsigned char)(st->s[i]) - (unsigned char)(buf[i]);
+		if (j)
+			return( j>0 ? 1 : -1 );
+	}
+	return(0);                                                                                                                                
+} /*}}}*/
+
+int str_comp(string_t *st0, string_t *st1)
+{ /*{{{*/
+	/*
+	 * lexically compare *st0 with *st1
+	 * return -1, 1 or 0
+	 */
+	return(str_scomp(st0, st1->s));
+} /*}}}*/
+
+/*
+ * replacement functions
+ */
+int str_nreplace(string_t *st, unsigned long int idx, unsigned long int offset, const char *replacement, unsigned long int rlen)
+{ /*{{{*/
+	/*
+	 * replace offset bytes from idx in *st with len bytes from
+	 * replacement; return 1 on success, 0 on memory problems, negative
+	 * values upon logical errors in parameters.
+	 */
+	char *rest;
+	int restlen;
+
+	if (idx > st->len)
+		return(-2);
+	if (idx+offset > st->len)
+		return(-3);
+
+	restlen = st->len - idx - offset;
+	if (!(rest = malloc(restlen * sizeof(char) + 1)))
+		return(0);
+
+	strncpy(rest, st->s+idx+offset, restlen); /* st->s+st->len is '\0' */
+	rest[restlen] = '\0';
+	if (rlen > offset) {
+		/* we might need to allocate more memory */
+		if (!str_readya(st, rlen-offset)) {
+			free(rest);
+			return(0);
+		}
+	}
+	st->s[idx] = '\0';
+	st->len = idx;
+	str_ncat(st, replacement, rlen);
+	str_ncat(st, rest, restlen);
+	free(rest);
+	return(1);
+} /*}}}*/
+
+int str_replace(string_t *st, unsigned long int idx, unsigned long int offset, string_t *replacement)
+{ /*{{{*/
+	return(str_nreplace(st, idx, offset, replacement->s, replacement->len));
+} /*}}}*/
+
+/*
+ * search functions
+ */
+int
+str_ngetidx(string_t *haystack, unsigned long int offset, const char *needle, unsigned long int nlen, unsigned long int *idx)
+{ /*{{{*/
+	unsigned long int i, j;
+	if (nlen == 0 || haystack->len == 0 || haystack->len < offset || nlen > haystack->len-offset)
+		return(0);
+
+	for (i=0; i<= haystack->len-offset-1; ++i) {
+		if (haystack->s[offset+i] == needle[0]) {
+			/* potential hit */
+			if (nlen == 1) {
+				if (idx != NULL)
+					*idx=i+offset;
+				return(1);
+			}
+			for (j=1; j<=nlen-1; ++j) {
+				if (haystack->s[offset+i+j] != needle[j])
+					break;
+				if (haystack->s[offset+i+j] == needle[j] && j == nlen-1) {
+					if (idx != NULL)
+						*idx=i+offset;
+					return(1);
+				}
+			}
+		}
+	}
+	return (0);
+} /*}}}*/
+
+int
+str_getidx(string_t *haystack, unsigned long int offset, string_t *needle, unsigned long int *idx)
+{ /*{{{*/
+	return(str_ngetidx(haystack, offset, needle->s, needle->len, idx));
+} /*}}}*/
+
+/*
+ * conversion functions
+ */
+int
+str_nlat1_to_utf8(string_t *converted, const unsigned char *input, unsigned long len)
+{ /*{{{*/
+	unsigned long int nc=0, olen=0, ilen;
+	unsigned char *in;
+
+	in = (unsigned char *)input;
+	ilen = 0;
+	while (ilen++ <= len)
+		if (*(in++) > 0x7f)
+			++nc;
+
+	if (nc) {
+		if (!str_readya(converted, nc))
+			return(0);
+	}
+	else
+		return(1);
+
+	in = (unsigned char *)input;
+	ilen = 0;
+	while (ilen++ <= len) {
+		if (*in > 0x7f) {
+			converted->s[olen++] = 0xC0 | (*in >> 6);
+			*in = (0x80 | (*in & 0x3F));
+		}
+		converted->s[olen++] = *in;
+		in++;
+	}
+	converted->s[olen] = '\0';
+	converted->len = olen;
+	return(1);
+} /*}}}*/
+
+int
+str_lat1_to_utf8(string_t *converted, string_t *input)
+{ /*{{{*/
+	return(str_nlat1_to_utf8(converted, input->s, input->len));
+} /*}}}*/
+
+/*
+ * Perl lookalikes
+ */
+int str_chop(string_t *st)
+{ /*{{{*/
+	/*
+	 * remove last character of *st
+	 * return removed byte or -1 if *st if empty
+	 */
+	int r;
+	if (!st->len)
+		return(-1);
+	r=st->s[st->len-1];
+	st->s[st->len--]=0;
+	return(r);
+} /*}}}*/                                                                                                                                           
+                                                                                                                                            
+int str_chomp(string_t *st)
+{ /*{{{*/
+	/*
+	 * remove "\r" "\n" "\r\n" from *st's end.
+	 * return count of rem. chars (poss. values: 0,1,2);
+	 */
+	if (!st->len)
+		return(0);
+	if (st->len==1) {
+		if (st->s[st->len-1] == '\r' || st->s[st->len-1] == '\n') {
+			st->s[0]=0; st->len=0;
+			return(1);
+		}
+		else
+			return(0);
+	}
+	if (st->s[st->len-1] == '\r' || st->s[st->len-1] == '\n') {
+		if (st->s[st->len-1] == '\n' && st->s[st->len-2]) {
+			st->s[st->len-2]=0;
+			st->len-=2;
+			return(2);
+		}
+		st->s[--st->len]=0;
+		return(1);
+	}
+	return(0);
+} /*}}}*/
diff -urN cmus/strlib.h cmus-new/strlib.h
--- cmus/strlib.h	1969-12-31 19:00:00.000000000 -0500
+++ cmus-new/strlib.h	2008-08-22 21:25:37.000000000 -0400
@@ -0,0 +1,88 @@
+/*
+ * strlib.[ch] - a string handling library
+ *  Some time ago, I've been writing a few functions to handle strings,
+ *  mainly as an exercise. When someone hinted me at fefe's libowfat[1],
+ *  I found that the basic idea was quite similar, but libowfat was
+ *  doing a lot of things quite smart, so I rewrote my functions while
+ *  looking at the libowfat sources. So, this code is heavily based
+ *  on these sources.
+ *
+ * Note, that libowfat is not only about strings, it does lots of
+ * things, you might find useful. Take a look at it!
+ *
+ * [1] http://www.fefe.de/libowfat/
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef x__STRLIB_H
+#define x__STRLIB_H
+
+/* internal structure */
+typedef struct {
+	/*
+	 *  s:    string
+	 *  len:  length of string
+	 *  all:  allocated bytes for *s
+	 */
+	char *s;
+	unsigned long int len;
+	unsigned long int all;
+} string_t;
+
+/* {con,de}structor */
+void  str_init(string_t *st);
+void  str_free(string_t *st);
+
+/* allocation functions */
+int   str_ready(string_t *st, unsigned long int len);
+int   str_readya(string_t *st, unsigned long int len);
+
+/* string copy functions */
+int   str_ncpy(string_t *st, const char *buf, unsigned long int len);
+#define str_scpy(st, buf) str_ncpy(st, buf, ( buf == NULL ? 0 : strlen(buf) ))
+int   str_cpy(string_t *st0, string_t *st1);
+
+/* concatenation functions */
+int   str_ncat(string_t *st, const char *buf, unsigned long int len);
+#define str_scat(st, buf) str_ncat(st, buf, ( buf == NULL ? 0 : strlen(buf) ))
+int   str_cat(string_t *st0, string_t *st1);
+
+/* comparison functions */
+int   str_compstart(string_t *st, const char *buf);
+int   str_scomp(string_t *st, const char *buf);
+int   str_comp(string_t *st0, string_t *st1);
+
+/* replacement functions */
+int str_nreplace(string_t *st, unsigned long int idx, unsigned long int offset, const char *replacement, unsigned long int rlen);
+#define str_sreplace(st, idx, offset, repl) str_nreplace(st, idx, offset, repl, strlen(repl))
+int str_replace(string_t *st, unsigned long int idx, unsigned long int offset, string_t *replacement);
+
+/* search functions */
+int str_ngetidx(string_t *haystack, unsigned long int offset, const char *needle, unsigned long int nlen, unsigned long int *idx);
+#define str_sgetidx(hs, offset, n, idx) str_ngetidx(hs, offset, n, strlen(n), idx)
+int str_getidx(string_t *haystack, unsigned long int offset, string_t *needle, unsigned long int *idx);
+
+/* conversion functions */
+int str_nlat1_to_utf8(string_t *converted, const unsigned char *input, unsigned long len);
+#define str_slat1_to_utf8(c, s) str_nlat1_to_utf8(c, s, strlen(s))
+int str_lat1_to_utf8(string_t *converted, string_t *input);
+
+/* Perl lookalikes */
+int   str_chop(string_t *st);
+int   str_chomp(string_t *st);
+
+#endif /* x__STRLIB_H */
diff -urN cmus/tree.c cmus-new/tree.c
--- cmus/tree.c	2008-06-01 04:11:07.000000000 -0400
+++ cmus-new/tree.c	2008-08-22 21:25:56.000000000 -0400
@@ -7,6 +7,7 @@
 #include "xmalloc.h"
 #include "comment.h"
 #include "utils.h"
+#include "as.h"
 #include "debug.h"
 #include "mergesort.h"
 #include "options.h"
@@ -367,6 +368,7 @@
 	if (list_empty(&lib_artist_head))
 		return NULL;
 
+	as_hook(AS_TREE_ENTER);
 	tree_win_get_selected(&artist, &album);
 	if (album == NULL) {
 		/* only artist selected, track window is empty
diff -urN cmus/ui_curses.c cmus-new/ui_curses.c
--- cmus/ui_curses.c	2008-06-01 04:11:07.000000000 -0400
+++ cmus-new/ui_curses.c	2008-08-22 21:25:37.000000000 -0400
@@ -1330,6 +1330,32 @@
 	}
 }
 
+/*
+ * errno_msg()
+ *   print an error message plus the current errno message
+ */
+void
+errno_msg(const char *format, ...)
+{
+	/*
+	 * This function is a little limited, as is outputs lines of
+	 * MAX_ERR_LINE chars at most. It's useful, though, and could
+	 * probably be used more widespread.
+	 */
+	char buf[MAX_ERR_LINE];
+	va_list ap;
+	int  errno_save = errno;
+	int  n;
+
+	va_start(ap, format);
+	vsnprintf(buf, MAX_ERR_LINE, format, ap);
+	va_end(ap);
+	n = strlen(buf);
+	snprintf(buf + n, MAX_ERR_LINE - n, ": %s", strerror(errno_save));
+	strcat(buf, "\n");
+	error_msg(buf);
+}
+
 int yes_no_query(const char *format, ...)
 {
 	char buffer[512];
diff -urN cmus/ui_curses.h cmus-new/ui_curses.h
--- cmus/ui_curses.h	2008-06-01 04:11:07.000000000 -0400
+++ cmus-new/ui_curses.h	2008-08-22 21:25:37.000000000 -0400
@@ -23,6 +23,8 @@
 #include "search.h"
 #include "compiler.h"
 
+#define MAX_ERR_LINE 512
+
 enum ui_input_mode {
 	NORMAL_MODE,
 	COMMAND_MODE,
@@ -44,6 +46,7 @@
 void update_full(void);
 void info_msg(const char *format, ...) __FORMAT(1, 2);
 void error_msg(const char *format, ...) __FORMAT(1, 2);
+void errno_msg(const char *format, ...) __FORMAT(1, 2);
 int yes_no_query(const char *format, ...) __FORMAT(1, 2);
 void search_not_found(void);
 void set_view(int view);
