# HG changeset patch
# Node ID 45269b780591
# Parent  5df699e43f3f
diff --git a/libpurple/protocols/zephyr/meson.build b/libpurple/protocols/zephyr/meson.build
--- a/libpurple/protocols/zephyr/meson.build
+++ b/libpurple/protocols/zephyr/meson.build
@@ -53,8 +53,13 @@
 	'sysdep.h',
 	'zephyr.c',
 	'zephyr.h',
+	'zephyr_account.h',
 	'zephyr_html.c',
-	'zephyr_html.h'
+	'zephyr_html.h',
+	'zephyr_tzc.c',
+	'zephyr_tzc.h',
+	'zephyr_zeph02.c',
+	'zephyr_zeph02.h'
 ]
 
 extdep = krb4
diff --git a/libpurple/protocols/zephyr/zephyr.c b/libpurple/protocols/zephyr/zephyr.c
--- a/libpurple/protocols/zephyr/zephyr.c
+++ b/libpurple/protocols/zephyr/zephyr.c
@@ -36,7 +36,10 @@
 
 #include "internal.h"
 #include "zephyr.h"
+#include "zephyr_account.h"
 #include "zephyr_html.h"
+#include "zephyr_tzc.h"
+#include "zephyr_zeph02.h"
 
 #define ZEPHYR_FALLBACK_CHARSET "ISO-8859-1"
 
@@ -60,46 +63,8 @@
 #endif
 
 typedef struct _zephyr_triple zephyr_triple;
-typedef struct _zephyr_account zephyr_account;
 
-typedef gssize (*PollableInputStreamReadFunc)(GPollableInputStream *stream, void *bufcur, GError **error);
 typedef gboolean (*ZephyrLoginFunc)(zephyr_account *zephyr);
-typedef Code_t (*ZephyrSubscribeToFunc)(zephyr_account *zephyr, ZSubscription_t *sub);
-typedef gboolean (*ZephyrRequestLocationsFunc)(zephyr_account *zephyr, gchar *who);
-
-typedef enum {
-	PURPLE_ZEPHYR_NONE, /* Non-kerberized ZEPH0.2 */
-	PURPLE_ZEPHYR_KRB4, /* ZEPH0.2 w/ KRB4 support */
-	PURPLE_ZEPHYR_TZC,  /* tzc executable proxy */
-	PURPLE_ZEPHYR_INTERGALACTIC_KRB4 /* Kerberized ZEPH0.3 */
-} zephyr_connection_type;
-
-struct _zephyr_account {
-	PurpleAccount* account;
-	char *username;
-	char *realm;
-	char *encoding;
-	char* galaxy; /* not yet useful */
-	char* krbtkfile; /* not yet useful */
-	guint32 nottimer;
-	guint32 loctimer;
-	GList *pending_zloc_names;
-	GSList *subscrips;
-	int last_id;
-	unsigned short port;
-	char ourhost[HOST_NAME_MAX + 1];
-	char ourhostcanon[HOST_NAME_MAX + 1];
-	zephyr_connection_type connection_type;
-	char *exposure;
-	GSubprocess *tzc_proc;
-	GOutputStream *tzc_stdin;
-	GInputStream *tzc_stdout;
-	gchar *away;
-	ZephyrSubscribeToFunc subscribe_to;
-	ZephyrRequestLocationsFunc request_locations;
-};
-
-#define MAXCHILDREN 20
 
 struct _zephyr_triple {
 	ZSubscription_t sub;
@@ -112,59 +77,7 @@
 extern const char *username;
 #endif
 
-static char *local_zephyr_normalize(const zephyr_account *zephyr, const char *);
 static void zephyr_chat_set_topic(PurpleConnection *gc, int id, const char *topic);
-static char *zephyr_tzc_deescape_str(const char *message);
-
-static inline gboolean
-use_tzc(const zephyr_account *zephyr)
-{
-	return zephyr->connection_type == PURPLE_ZEPHYR_TZC;
-}
-
-static inline gboolean
-use_zeph02(const zephyr_account *zephyr)
-{
-	return zephyr->connection_type == PURPLE_ZEPHYR_NONE ||
-	       zephyr->connection_type == PURPLE_ZEPHYR_KRB4;
-}
-
-static gboolean
-zephyr_write_message(zephyr_account *zephyr, const gchar *message)
-{
-	GError *error = NULL;
-	gboolean success;
-
-	success = g_output_stream_write_all(zephyr->tzc_stdin, message, strlen(message),
-	                                    NULL, NULL, &error);
-	if (!success) {
-		purple_debug_error("zephyr", "Unable to write a message: %s", error->message);
-		g_error_free(error);
-	}
-	return success;
-}
-
-static Code_t
-subscribe_to_tzc(zephyr_account *zephyr, ZSubscription_t *sub)
-{
-	Code_t ret_val = -1;
-	gchar *zsubstr;
-
-	/* ((tzcfodder . subscribe) ("class" "instance" "recipient")) */
-	zsubstr = g_strdup_printf("((tzcfodder . subscribe) (\"%s\" \"%s\" \"%s\"))\n",
-	                          sub->zsub_class, sub->zsub_classinst, sub->zsub_recipient);
-	if (zephyr_write_message(zephyr, zsubstr)) {
-		ret_val = ZERR_NONE;
-	}
-	g_free(zsubstr);
-	return ret_val;
-}
-
-static Code_t
-subscribe_to_zeph02(G_GNUC_UNUSED zephyr_account *zephyr, ZSubscription_t *sub)
-{
-	return ZSubscribeTo(sub, 1, 0);
-}
 
 static char *
 zephyr_strip_local_realm(const zephyr_account *zephyr, const char *user)
@@ -187,24 +100,6 @@
 	}
 }
 
-/* this is so bad, and if Zephyr weren't so fucked up to begin with I
- * wouldn't do this. but it is so i will. */
-
-/* just for debugging */
-static void handle_unknown(ZNotice_t *notice)
-{
-	purple_debug_error("zephyr","z_packet: %s\n", notice->z_packet);
-	purple_debug_error("zephyr","z_version: %s\n", notice->z_version);
-	purple_debug_error("zephyr","z_kind: %d\n", (int)(notice->z_kind));
-	purple_debug_error("zephyr","z_class: %s\n", notice->z_class);
-	purple_debug_error("zephyr","z_class_inst: %s\n", notice->z_class_inst);
-	purple_debug_error("zephyr","z_opcode: %s\n", notice->z_opcode);
-	purple_debug_error("zephyr","z_sender: %s\n", notice->z_sender);
-	purple_debug_error("zephyr","z_recipient: %s\n", notice->z_recipient);
-	purple_debug_error("zephyr","z_message: %s\n", notice->z_message);
-	purple_debug_error("zephyr","z_message_len: %d\n", notice->z_message_len);
-}
-
 
 static zephyr_triple *
 zephyr_triple_new(zephyr_account *zephyr, const ZSubscription_t *sub)
@@ -289,29 +184,31 @@
    Converts strings to utf-8 if necessary using user specified encoding
 */
 
-static gchar *zephyr_recv_convert(PurpleConnection *gc, gchar *string)
+static gchar *
+convert_to_utf8(const gchar *string, const gchar *from_encoding)
 {
 	gchar *utf8;
 	GError *err = NULL;
-	zephyr_account *zephyr = purple_connection_get_protocol_data(gc);
+
 	if (g_utf8_validate(string, -1, NULL)) {
 		return g_strdup(string);
-	} else {
-		utf8 = g_convert(string, -1, "UTF-8", zephyr->encoding, NULL, NULL, &err);
-		if (err) {
-			purple_debug_error("zephyr", "recv conversion error: %s\n", err->message);
-			utf8 = g_strdup(_("(There was an error converting this message.	 Check the 'Encoding' option in the Account Editor)"));
-			g_error_free(err);
-		}
+	}
 
-		return utf8;
+	utf8 = g_convert(string, -1, "UTF-8", from_encoding, NULL, NULL, &err);
+	if (err) {
+		purple_debug_error("zephyr", "recv conversion error: %s\n", err->message);
+		utf8 = g_strdup(_("(There was an error converting this message.	 Check the 'Encoding' option in the Account Editor)"));
+		g_error_free(err);
 	}
+
+	return utf8;
 }
 
-static gboolean pending_zloc(zephyr_account *zephyr, const char *who)
+gboolean
+pending_zloc(zephyr_account *zephyr, const char *who)
 {
 	GList *curr;
-	char* normalized_who = local_zephyr_normalize(zephyr,who);
+	char* normalized_who = zephyr_normalize_local_realm(zephyr,who);
 
 	curr = g_list_find_custom(zephyr->pending_zloc_names, normalized_who, (GCompareFunc)g_ascii_strcasecmp);
 	g_free(normalized_who);
@@ -323,27 +220,7 @@
 	return TRUE;
 }
 
-/* Called when the server notifies us a message couldn't get sent */
-
-static void
-message_failed(PurpleConnection *gc, ZNotice_t *notice)
-{
-	if (g_ascii_strcasecmp(notice->z_class, "message")) {
-		gchar* chat_failed = g_strdup_printf(
-			_("Unable to send to chat %s,%s,%s"),
-			notice->z_class, notice->z_class_inst,
-			notice->z_recipient);
-		purple_notify_error(gc,"",chat_failed,NULL,
-			purple_request_cpar_from_connection(gc));
-		g_free(chat_failed);
-	} else {
-		purple_notify_error(gc, notice->z_recipient,
-			_("User is offline"), NULL,
-			purple_request_cpar_from_connection(gc));
-	}
-}
-
-static PurpleBuddy *
+PurpleBuddy *
 find_buddy(const zephyr_account *zephyr, const char *user)
 {
 	PurpleBuddy *buddy = purple_blist_find_buddy(zephyr->account, user);
@@ -365,7 +242,8 @@
 	zephyr_chat_set_topic(gc, zt->id, instance);
 }
 
-static void handle_message(PurpleConnection *gc, ZNotice_t *notice_p)
+void
+handle_message(PurpleConnection *gc, ZNotice_t *notice_p)
 {
 	ZNotice_t notice;
 	zephyr_account* zephyr = purple_connection_get_protocol_data(gc);
@@ -380,6 +258,7 @@
 			char *user;
 			PurpleBuddy *b;
 			const char *bname;
+			const gchar *name;
 
 			/* XXX add real error reporting */
 			if (ZParseLocations(&notice, NULL, &nlocs, &user) != ZERR_NONE)
@@ -387,16 +266,14 @@
 
 			b = find_buddy(zephyr, user);
 			bname = b ? purple_buddy_get_name(b) : NULL;
+			name = b ? bname : user;
 			if ((b && pending_zloc(zephyr,bname)) || pending_zloc(zephyr,user)) {
-				ZLocations_t locs;
-				int one = 1;
 				PurpleNotifyUserInfo *user_info = purple_notify_user_info_new();
-				char *tmp;
 				const char *balias;
 
 				/* TODO: Check whether it's correct to call add_pair_html,
 				         or if we should be using add_pair_plaintext */
-				purple_notify_user_info_add_pair_html(user_info, _("User"), (b ? bname : user));
+				purple_notify_user_info_add_pair_html(user_info, _("User"), name);
 				balias = purple_buddy_get_local_alias(b);
 				if (b && balias)
 					purple_notify_user_info_add_pair_plaintext(user_info, _("Alias"), balias);
@@ -406,6 +283,9 @@
 				}
 				for (; nlocs > 0; nlocs--) {
 					/* XXX add real error reporting */
+					ZLocations_t locs;
+					int one = 1;
+					char *tmp;
 
 					ZGetLocations(&locs, &one);
 					/* TODO: Need to escape locs.host and locs.time? */
@@ -413,61 +293,57 @@
 					purple_notify_user_info_add_pair_html(user_info, _("Location"), tmp);
 					g_free(tmp);
 				}
-				purple_notify_userinfo(gc, (b ? bname : user),
-						     user_info, NULL, NULL);
+				purple_notify_userinfo(gc, name, user_info, NULL, NULL);
 				purple_notify_user_info_destroy(user_info);
 			} else {
-				if (nlocs>0)
-					purple_protocol_got_user_status(purple_connection_get_account(gc), b ? bname : user, "available", NULL);
-				else
-					purple_protocol_got_user_status(purple_connection_get_account(gc), b ? bname : user, "offline", NULL);
+				purple_protocol_got_user_status(purple_connection_get_account(gc), name, (nlocs > 0) ? "available" : "offline", NULL);
 			}
 
 			g_free(user);
 		}
 	} else {
-		char *buf, *buf2, *buf3;
-		char *send_inst;
-		PurpleChatConversation *gcc;
-		char *ptr = (char *) notice.z_message + (strlen(notice.z_message) + 1);
+		char *buf;
 		int len;
 		char *stripped_sender;
 		int signature_length = strlen(notice.z_message);
-		PurpleMessageFlags flags = 0;
-		gchar *tmpescape;
 
 		/* Need to deal with 0 length  messages to handle typing notification (OPCODE) ping messages */
 		/* One field zephyrs would have caused purple to crash */
 		if ( (notice.z_message_len == 0) || (signature_length >= notice.z_message_len - 1)) {
 			len = 0;
-			purple_debug_info("zephyr","message_size %d %d %d\n",len,notice.z_message_len,signature_length);
-			buf3 = g_strdup("");
+			buf = g_strdup("");
 
 		} else {
+			char *tmpbuf;
+			char *ptr = (char *) notice.z_message + (strlen(notice.z_message) + 1);
+			gchar *tmpescape;
+
 			len =  notice.z_message_len - ( signature_length +1);
-			purple_debug_info("zephyr","message_size %d %d %d\n",len,notice.z_message_len,signature_length);
-			buf = g_malloc(len + 1);
-			g_snprintf(buf, len + 1, "%s", ptr);
-			g_strchomp(buf);
-			tmpescape = g_markup_escape_text(buf, -1);
-			g_free(buf);
-			buf2 = zephyr_to_html(tmpescape);
-			buf3 = zephyr_recv_convert(gc, buf2);
-			g_free(buf2);
+			tmpbuf = g_malloc(len + 1);
+			g_snprintf(tmpbuf, len + 1, "%s", ptr);
+			g_strchomp(tmpbuf);
+			tmpescape = g_markup_escape_text(tmpbuf, -1);
+			g_free(tmpbuf);
+			tmpbuf = zephyr_to_html(tmpescape);
+			buf = convert_to_utf8(tmpbuf, zephyr->encoding);
+			g_free(tmpbuf);
 			g_free(tmpescape);
 		}
+		purple_debug_info("zephyr", "message_size %d %d %d\n", len, notice.z_message_len, signature_length);
 
 		stripped_sender = zephyr_strip_local_realm(zephyr,notice.z_sender);
 
 		if (!g_ascii_strcasecmp(notice.z_class, "MESSAGE") && !g_ascii_strcasecmp(notice.z_class_inst, "PERSONAL")
 		    && !g_ascii_strcasecmp(notice.z_recipient,zephyr->username)) {
+			PurpleMessageFlags flags = 0;
+
 			if (!g_ascii_strcasecmp(notice.z_message, "Automated reply:"))
 				flags |= PURPLE_MESSAGE_AUTO_RESP;
 
 			if (!g_ascii_strcasecmp(notice.z_opcode,"PING"))
 				purple_serv_got_typing(gc,stripped_sender,ZEPHYR_TYPING_RECV_TIMEOUT, PURPLE_IM_TYPING);
 			else
-				purple_serv_got_im(gc, stripped_sender, buf3, flags, time(NULL));
+				purple_serv_got_im(gc, stripped_sender, buf, flags, time(NULL));
 
 		} else {
 			ZSubscription_t sub = {
@@ -478,6 +354,7 @@
 			zephyr_triple *zt;
 			gchar *send_inst_utf8;
 			GSList *l = g_slist_find_custom(zephyr->subscrips, &sub, (GCompareFunc)zephyr_triple_subset);
+			PurpleChatConversation *gcc;
 
 			if (!l) {
 				/* This is a server supplied subscription */
@@ -494,8 +371,8 @@
 			if (!g_ascii_strcasecmp(notice.z_class_inst,"PERSONAL"))
 				send_inst_utf8 = g_strdup(stripped_sender);
 			else {
-				send_inst = g_strdup_printf("[%s] %s",notice.z_class_inst,stripped_sender);
-				send_inst_utf8 = zephyr_recv_convert(gc,send_inst);
+				char *send_inst = g_strdup_printf("[%s] %s", notice.z_class_inst, stripped_sender);
+				send_inst_utf8 = convert_to_utf8(send_inst, zephyr->encoding);
 				g_free(send_inst);
 				if (!send_inst_utf8) {
 					purple_debug_error("zephyr","Failed to convert instance for sender %s.\n", stripped_sender);
@@ -518,416 +395,53 @@
 				g_object_unref(inet_addr);
 			}
 			purple_serv_got_chat_in(gc, zt->id, send_inst_utf8,
-				PURPLE_MESSAGE_RECV, buf3, time(NULL));
+			        PURPLE_MESSAGE_RECV, buf, time(NULL));
 			g_free(send_inst_utf8);
 		}
 		g_free(stripped_sender);
-		g_free(buf3);
-	}
-}
-
-static gchar *
-tree_child_contents(GNode *tree, int index)
-{
-	GNode *child = g_node_nth_child(tree, index);
-	return child ? child->data : "";
-}
-
-static GNode *
-find_node(GNode *ptree, gchar *key)
-{
-	guint num_children;
-	gchar* tc;
-
-	if (!ptree || ! key)
-		return NULL;
-
-	num_children = g_node_n_children(ptree);
-	tc = tree_child_contents(ptree, 0);
-
-	/* g_strcasecmp() is deprecated.  What is the encoding here??? */
-	if (num_children > 0 && tc && !g_ascii_strcasecmp(tc, key)) {
-		return ptree;
-	} else {
-		GNode *result = NULL;
-		guint i;
-		for (i = 0; i < num_children; i++) {
-			result = find_node(g_node_nth_child(ptree, i), key);
-			if(result != NULL) {
-				break;
-			}
-		}
-		return result;
+		g_free(buf);
 	}
 }
 
-static GNode *
-parse_buffer(const gchar *source, gboolean do_parse)
+static void
+check_loc_buddy(PurpleBuddy *buddy, zephyr_account *zephyr)
 {
-	GNode *ptree = g_node_new(NULL);
-
-	if (do_parse) {
-		unsigned int p = 0;
-		while(p < strlen(source)) {
-			unsigned int end;
-			gchar *newstr;
-
-			/* Eat white space: */
-			if(g_ascii_isspace(source[p]) || source[p] == '\001') {
-				p++;
-				continue;
-			}
-
-			/* Skip comments */
-			if(source[p] == ';') {
-				while(source[p] != '\n' && p < strlen(source)) {
-					p++;
-				}
-				continue;
-			}
-
-			if(source[p] == '(') {
-				int nesting = 0;
-				gboolean in_quote = FALSE;
-				gboolean escape_next = FALSE;
-				p++;
-				end = p;
-				while(!(source[end] == ')' && nesting == 0 && !in_quote) && end < strlen(source)) {
-					if(!escape_next) {
-						if(source[end] == '\\') {
-							escape_next = TRUE;
-						}
-						if(!in_quote) {
-							if(source[end] == '(') {
-								nesting++;
-							}
-							if(source[end] == ')') {
-								nesting--;
-							}
-						}
-						if(source[end] == '"') {
-							in_quote = !in_quote;
-						}
-					} else {
-						escape_next = FALSE;
-					}
-					end++;
-				}
-				do_parse = TRUE;
-
-			} else {
-				gchar end_char;
-				if(source[p] == '"') {
-					end_char = '"';
-					p++;
-				} else {
-					end_char = ' ';
-				}
-				do_parse = FALSE;
+	const char *bname = purple_buddy_get_name(buddy);
+	char *chk = zephyr_normalize_local_realm(zephyr, bname);
+#ifdef WIN32
+	int numlocs;
 
-				end = p;
-				while(source[end] != end_char && end < strlen(source)) {
-					if(source[end] == '\\')
-						end++;
-					end++;
-				}
-			}
-			newstr = g_new0(gchar, end+1-p);
-			strncpy(newstr,source+p,end-p);
-			if (g_node_n_children(ptree) < MAXCHILDREN) {
-				/* In case we surpass maxchildren, ignore this */
-				g_node_append(ptree, parse_buffer(newstr, do_parse));
-			} else {
-				purple_debug_error("zephyr","too many children in tzc output. skipping\n");
-			}
-			g_free(newstr);
-			p = end + 1;
-		}
-	} else {
-		/* XXX does this have to be strdup'd */
-		ptree->data = g_strdup(source);
-	}
-
-	return ptree;
-}
-
-static gchar *
-read_from_tzc(zephyr_account *zephyr, PollableInputStreamReadFunc read_func)
-{
-	GPollableInputStream *stream = G_POLLABLE_INPUT_STREAM(zephyr->tzc_stdout);
-	gsize bufsize = 2048;
-	gchar *buf = g_new(gchar, bufsize);
-	gchar *bufcur = buf;
-	gboolean selected = FALSE;
-
-	while (TRUE) {
-		GError *error = NULL;
-		if (read_func(stream, bufcur, &error) < 0) {
-			if (error->code == G_IO_ERROR_WOULD_BLOCK ||
-			    error->code == G_IO_ERROR_TIMED_OUT) {
-				g_error_free(error);
-				break;
-			}
-			purple_debug_error("zephyr", "couldn't read: %s", error->message);
-			purple_connection_error(purple_account_get_connection(zephyr->account), PURPLE_CONNECTION_ERROR_NETWORK_ERROR, "couldn't read");
-			g_error_free(error);
-			g_free(buf);
-			return NULL;
-		}
-		selected = TRUE;
-		bufcur++;
-		if ((bufcur - buf) > (bufsize - 1)) {
-			if ((buf = g_realloc(buf, bufsize * 2)) == NULL) {
-				purple_debug_error("zephyr","Ran out of memory\n");
-				exit(-1);
-			} else {
-				bufcur = buf + bufsize;
-				bufsize *= 2;
-			}
-		}
-	}
-	*bufcur = '\0';
-
-	if (!selected) {
-		g_free(buf);
-		buf = NULL;
-	}
-	return buf;
-}
-
-static gssize
-pollable_input_stream_read(GPollableInputStream *stream, void *bufcur, GError **error)
-{
-	return g_pollable_input_stream_read_nonblocking(stream, bufcur, 1, NULL, error);
-}
+	ZLocateUser(chk, &numlocs, ZAUTH);
+	for (int i = 0; i < numlocs; i++) {
+		ZLocations_t locations;
+		int one = 1;
 
-static gint check_notify_tzc(gpointer data)
-{
-	PurpleConnection *gc = (PurpleConnection *)data;
-	zephyr_account* zephyr = purple_connection_get_protocol_data(gc);
-	GNode *newparsetree = NULL;
-	gchar *buf = read_from_tzc(zephyr, pollable_input_stream_read);
-
-	if (buf != NULL) {
-		newparsetree = parse_buffer(buf, TRUE);
-		g_free(buf);
+		ZGetLocations(&locations, &one);
+		serv_got_update(zgc, bname, 1, 0, 0, 0, 0);
 	}
-
-	if (newparsetree != NULL) {
-		gchar *spewtype;
-		if ( (spewtype =  tree_child_contents(find_node(newparsetree, "tzcspew"), 2)) ) {
-			if (!g_ascii_strncasecmp(spewtype,"message",7)) {
-				ZNotice_t notice;
-				GNode *msgnode = g_node_nth_child(find_node(newparsetree, "message"), 2);
-				/*char *zsig = g_strdup(" ");*/ /* purple doesn't care about zsigs */
-				char *msg  = zephyr_tzc_deescape_str(tree_child_contents(msgnode, 1));
-				size_t bufsize = strlen(msg) + 3;
-				char *buf = g_new0(char,bufsize);
-				g_snprintf(buf,1+strlen(msg)+2," %c%s",'\0',msg);
-				memset((char *)&notice, 0, sizeof(notice));
-				notice.z_kind = ACKED;
-				notice.z_port = 0;
-				notice.z_opcode = tree_child_contents(find_node(newparsetree, "opcode"), 2);
-				notice.z_class = zephyr_tzc_deescape_str(tree_child_contents(find_node(newparsetree, "class"), 2));
-				notice.z_class_inst = tree_child_contents(find_node(newparsetree, "instance"), 2);
-				notice.z_recipient = local_zephyr_normalize(zephyr, tree_child_contents(find_node(newparsetree, "recipient"), 2));
-				notice.z_sender = local_zephyr_normalize(zephyr, tree_child_contents(find_node(newparsetree, "sender"), 2));
-				notice.z_default_format = "Class $class, Instance $instance:\n" "To: @bold($recipient) at $time $date\n" "From: @bold($1) <$sender>\n\n$2";
-				notice.z_message_len = strlen(msg) + 3;
-				notice.z_message = buf;
-				handle_message(gc, &notice);
-				g_free(msg);
-				/*g_free(zsig);*/
-				g_free(buf);
-			}
-			else if (!g_ascii_strncasecmp(spewtype,"zlocation",9)) {
-				/* check_loc or zephyr_zloc respectively */
-				/* XXX fix */
-				char *user;
-				PurpleBuddy *b;
-				const char *bname;
-				const gchar *name;
-				gboolean has_locations;
-				GNode *locations;
-				gchar *locval;
-
-				user = tree_child_contents(find_node(newparsetree, "user"), 2);
-				b = find_buddy(zephyr, user);
-				bname = b ? purple_buddy_get_name(b) : NULL;
-				name = b ? bname : user;
-
-				locations = g_node_nth_child(g_node_nth_child(find_node(newparsetree, "locations"), 2), 0);
-				locval = tree_child_contents(g_node_nth_child(locations, 0), 2);
-				has_locations = (locval && *locval && !purple_strequal(locval, " "));
-				if ((b && pending_zloc(zephyr, bname)) || pending_zloc(zephyr, user)) {
-					PurpleNotifyUserInfo *user_info = purple_notify_user_info_new();
-					char *tmp;
-					const char *balias;
-
-					/* TODO: Check whether it's correct to call add_pair_html,
-					         or if we should be using add_pair_plaintext */
-					purple_notify_user_info_add_pair_html(user_info, _("User"), name);
-
-					balias = b ? purple_buddy_get_local_alias(b) : NULL;
-					if (balias)
-						purple_notify_user_info_add_pair_plaintext(user_info, _("Alias"), balias);
+#else
 
-					if (!has_locations) {
-						purple_notify_user_info_add_pair_plaintext(user_info, NULL, _("Hidden or not logged-in"));
-					} else {
-						/* TODO: Need to escape the two strings that make up tmp? */
-						tmp = g_strdup_printf(_("<br>At %s since %s"), locval,
-						                      tree_child_contents(g_node_nth_child(locations, 2), 2));
-						purple_notify_user_info_add_pair_html(user_info, _("Location"), tmp);
-						g_free(tmp);
-					}
-
-					purple_notify_userinfo(gc, name, user_info, NULL, NULL);
-					purple_notify_user_info_destroy(user_info);
-				} else {
-					purple_protocol_got_user_status(zephyr->account, name, has_locations ? "available" : "offline", NULL);
-				}
-			}
-			else if (!g_ascii_strncasecmp(spewtype,"subscribed",10)) {
-			}
-			else if (!g_ascii_strncasecmp(spewtype,"start",5)) {
-			}
-			else if (!g_ascii_strncasecmp(spewtype,"error",5)) {
-				/* XXX handle */
-			}
-		} else {
-		}
-	} else {
-	}
-
-	g_node_destroy(newparsetree);
-	return TRUE;
-}
-
-static gint check_notify_zeph02(gpointer data)
-{
+	purple_debug_info("zephyr", "chk: %s b->name %s\n", chk, bname);
 	/* XXX add real error reporting */
-	PurpleConnection *gc = (PurpleConnection*) data;
-	while (ZPending()) {
-		ZNotice_t notice;
-		/* XXX add real error reporting */
-
-		if (ZReceiveNotice(&notice, NULL) != ZERR_NONE) {
-			return TRUE;
-		}
+	/* doesn't matter if this fails or not; we'll just move on to the next one */
+	zephyr->request_locations(zephyr, chk);
+#endif /* WIN32 */
 
-		switch (notice.z_kind) {
-		case UNSAFE:
-		case UNACKED:
-		case ACKED:
-			handle_message(gc, &notice);
-			break;
-		case SERVACK:
-			if (!(g_ascii_strcasecmp(notice.z_message, ZSRVACK_NOTSENT))) {
-				message_failed(gc, &notice);
-			}
-			break;
-		case CLIENTACK:
-			purple_debug_error("zephyr", "Client ack received\n");
-			handle_unknown(&notice); /* XXX: is it really unknown? */
-			break;
-		default:
-			/* we'll just ignore things for now */
-			handle_unknown(&notice);
-			purple_debug_error("zephyr", "Unhandled notice.\n");
-			break;
-		}
-		/* XXX add real error reporting */
-		ZFreeNotice(&notice);
-	}
-
-	return TRUE;
+	g_free(chk);
 }
 
 static gboolean
-request_locations_tzc(zephyr_account *zephyr, gchar *who)
-{
-	gchar *zlocstr;
-	gboolean result;
-
-	zlocstr = g_strdup_printf("((tzcfodder . zlocate) \"%s\")\n", who);
-	result = zephyr_write_message(zephyr, zlocstr);
-	g_free(zlocstr);
-	return result;
-}
-
-static gboolean
-request_locations_zeph02(G_GNUC_UNUSED zephyr_account *zephyr, gchar *who)
+check_loc(gpointer data)
 {
-	ZAsyncLocateData_t ald;
-	Code_t zerr;
-
-	zerr = ZRequestLocations(who, &ald, UNACKED, ZAUTH);
-	g_free(ald.user);
-	g_free(ald.version);
-	return zerr == ZERR_NONE;
-}
-
-#ifdef WIN32
-
-static gint check_loc(gpointer data)
-{
-	GSList *buddies;
-	ZLocations_t locations;
-	PurpleConnection *gc = data;
-	zephyr_account *zephyr = purple_connection_get_protocol_data(gc);
-	PurpleAccount *account = purple_connection_get_account(gc);
-	int numlocs;
-	int one = 1;
+	zephyr_account *zephyr = (zephyr_account *)data;
+	GSList *buddies = purple_blist_find_buddies(zephyr->account, NULL);
 
-	for (buddies = purple_blist_find_buddies(account, NULL); buddies;
-			buddies = g_slist_delete_link(buddies, buddies)) {
-		PurpleBuddy *b = buddies->data;
-		char *chk;
-		const char *bname = purple_buddy_get_name(b);
-		chk = local_zephyr_normalize(zephyr, bname);
-		ZLocateUser(chk,&numlocs, ZAUTH);
-		if (numlocs) {
-			int i;
-			for(i=0;i<numlocs;i++) {
-				ZGetLocations(&locations,&one);
-				serv_got_update(zgc,bname,1,0,0,0,0);
-			}
-		}
-	}
-
-	return TRUE;
-}
-
-#else
+	g_slist_foreach(buddies, (GFunc)check_loc_buddy, zephyr);
+	g_slist_free(buddies);
 
-static gint check_loc(gpointer data)
-{
-	GSList *buddies;
-	PurpleConnection *gc = (PurpleConnection *)data;
-	zephyr_account *zephyr = purple_connection_get_protocol_data(gc);
-	PurpleAccount *account = purple_connection_get_account(gc);
-
-	for (buddies = purple_blist_find_buddies(account, NULL); buddies;
-			buddies = g_slist_delete_link(buddies, buddies)) {
-		PurpleBuddy *b = buddies->data;
-
-		char *chk;
-		const char *name = purple_buddy_get_name(b);
-
-		chk = local_zephyr_normalize(zephyr,name);
-		purple_debug_info("zephyr","chk: %s b->name %s\n",chk,name);
-		/* XXX add real error reporting */
-		/* doesn't matter if this fails or not; we'll just move on to the next one */
-		zephyr->request_locations(zephyr, chk);
-		g_free(chk);
-	}
-
-	return TRUE;
+	return G_SOURCE_CONTINUE;
 }
 
-#endif /* WIN32 */
-
 static const gchar *
 get_exposure_level(void)
 {
@@ -1060,7 +574,7 @@
 					/* There should be some sort of error report listing classes that couldn't be subbed to.
 					   Not important right now though */
 
-					if (zephyr->subscribe_to(zephyr, &sub) != ZERR_NONE) {
+					if (!zephyr->subscribe_to(zephyr, &sub)) {
 						purple_debug_error("zephyr", "Couldn't subscribe to %s, %s, %s\n",
 						                   sub.zsub_class, sub.zsub_classinst, sub.zsub_recipient);
 					}
@@ -1131,110 +645,7 @@
 	return g_strdup(EXPOSE_REALMVIS);
 }
 
-static gssize
-pollable_input_stream_read_with_timeout(GPollableInputStream *stream,
-                                        void *bufcur, GError **error)
-{
-	const gint64 timeout = 10 * G_USEC_PER_SEC;
-	gint64 now = g_get_monotonic_time();
-
-	while (g_get_monotonic_time() < now + timeout) {
-		GError *local_error = NULL;
-		gssize ret = g_pollable_input_stream_read_nonblocking(
-		        stream, bufcur, 1, NULL, &local_error);
-		if (ret == 1) {
-			return ret;
-		} else {
-			if (local_error->code == G_IO_ERROR_WOULD_BLOCK) {
-				/* Keep on waiting if this is a blocking error. */
-				g_clear_error(&local_error);
-			} else {
-				g_propagate_error(error, local_error);
-				return ret;
-			}
-		}
-	}
-
-	g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
-	                    "tzc did not respond in time");
-	return -1;
-}
-
-static GSubprocess *
-get_tzc_process(const zephyr_account *zephyr)
-{
-	GSubprocess *tzc_process = NULL;
-	const gchar *tzc_cmd;
-	gchar **tzc_cmd_array = NULL;
-	GError *error = NULL;
-	gboolean found_ps = FALSE;
-	gint i;
-
-	/* tzc_command should really be of the form
-	   path/to/tzc -e %s
-	   or
-	   ssh username@hostname pathtotzc -e %s
-	   -- this should not require a password, and ideally should be
-	   kerberized ssh --
-	   or
-	   fsh username@hostname pathtotzc -e %s
-	*/
-	tzc_cmd = purple_account_get_string(zephyr->account, "tzc_command", "/usr/bin/tzc -e %s");
-	if (!g_shell_parse_argv(tzc_cmd, NULL, &tzc_cmd_array, &error)) {
-		purple_debug_error("zephyr", "Unable to parse tzc_command: %s", error->message);
-		purple_connection_error(
-		        purple_account_get_connection(zephyr->account),
-		        PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
-		        "invalid tzc_command setting");
-		g_error_free(error);
-		return NULL;
-	}
-	for (i = 0; tzc_cmd_array[i] != NULL; i++) {
-		if (purple_strequal(tzc_cmd_array[i], "%s")) {
-			g_free(tzc_cmd_array[i]);
-			tzc_cmd_array[i] = g_strdup(zephyr->exposure);
-			found_ps = TRUE;
-		}
-	}
-
-	if (!found_ps) {
-		purple_debug_error("zephyr", "tzc exited early");
-		purple_connection_error(
-		        purple_account_get_connection(zephyr->account),
-		        PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-		        "invalid output by tzc (or bad parsing code)");
-		g_strfreev(tzc_cmd_array);
-		return NULL;
-	}
-
-	tzc_process = g_subprocess_newv(
-	        (const gchar *const *)tzc_cmd_array,
-	        G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE,
-	        &error);
-	if (tzc_process == NULL) {
-		purple_debug_error("zephyr", "tzc exited early: %s", error->message);
-		purple_connection_error(
-		        purple_account_get_connection(zephyr->account),
-		        PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-		        "invalid output by tzc (or bad parsing code)");
-		g_error_free(error);
-	}
-
-	g_strfreev(tzc_cmd_array);
-	return tzc_process;
-}
-
-static gint
-get_paren_level(gint paren_level, gchar ch)
-{
-	switch (ch) {
-		case '(': return paren_level + 1;
-		case ')': return paren_level - 1;
-		default: return paren_level;
-	}
-}
-
-static gchar *
+gchar *
 get_zephyr_realm(PurpleAccount *account, const gchar *local_realm)
 {
 	const char *realm = purple_account_get_string(account, "realm", "");
@@ -1245,151 +656,6 @@
 	return g_strdup(realm);
 }
 
-static void
-parse_tzc_login_data(zephyr_account *zephyr, const gchar *buf, gint buflen)
-{
-	gchar *str = g_strndup(buf, buflen);
-
-	purple_debug_info("zephyr", "tempstr parsed");
-
-	/* str should now be a string containing all characters from
-	 * buf after the first ( to the one before the last paren ).
-	 * We should have the following possible lisp strings but we don't care
-	 * (tzcspew . start) (version . "something") (pid . number)
-	 * We care about 'zephyrid . "username@REALM.NAME"' and
-	 * 'exposure . "SOMETHING"' */
-	if (!g_ascii_strncasecmp(str, "zephyrid", 8)) {
-		gchar **strv;
-		gchar *username;
-		const char *at;
-
-		purple_debug_info("zephyr", "zephyrid found");
-
-		strv = g_strsplit(str + 8, "\"", -1);
-		username = strv[1] ? strv[1] : "";
-		zephyr->username = g_strdup(username);
-
-		at = strchr(username, '@');
-		if (at != NULL) {
-			zephyr->realm = g_strdup(at + 1);
-		} else {
-			zephyr->realm = get_zephyr_realm(zephyr->account, "local-realm");
-		}
-
-		g_strfreev(strv);
-	} else {
-		purple_debug_info("zephyr", "something that's not zephyr id found %s", str);
-	}
-
-	/* We don't care about anything else yet */
-	g_free(str);
-}
-
-static gboolean
-login_tzc(zephyr_account *zephyr)
-{
-	gchar *buf = NULL;
-	const gchar *bufend = NULL;
-	const gchar *ptr;
-	const gchar *tmp;
-	gint parenlevel = 0;
-
-	zephyr->tzc_proc = get_tzc_process(zephyr);
-	if (zephyr->tzc_proc == NULL) {
-		return FALSE;
-	}
-	zephyr->tzc_stdin = g_subprocess_get_stdin_pipe(zephyr->tzc_proc);
-	zephyr->tzc_stdout = g_subprocess_get_stdout_pipe(zephyr->tzc_proc);
-
-	purple_debug_info("zephyr", "about to read from tzc");
-	buf = read_from_tzc(zephyr, pollable_input_stream_read_with_timeout);
-	if (buf == NULL) {
-		return FALSE;
-	}
-	bufend = buf + strlen(buf);
-	ptr = buf;
-
-	/* ignore all tzcoutput till we've received the first ( */
-	while (ptr < bufend && (*ptr != '(')) {
-		ptr++;
-	}
-	if (ptr >= bufend) {
-		purple_connection_error(
-		        purple_account_get_connection(zephyr->account),
-		        PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-		        "invalid output by tzc (or bad parsing code)");
-		g_free(buf);
-		return FALSE;
-	}
-
-	do {
-		parenlevel = get_paren_level(parenlevel, *ptr);
-		purple_debug_info("zephyr", "tzc parenlevel is %d", parenlevel);
-		switch (parenlevel) {
-			case 1:
-				/* Search for next beginning (, or for the ending */
-				do {
-					ptr++;
-				} while ((ptr < bufend) && (*ptr != '(') && (*ptr != ')'));
-				if (ptr >= bufend) {
-					purple_debug_error("zephyr", "tzc parsing error");
-				}
-				break;
-			case 2:
-				/* You are probably at
-				   (foo . bar ) or (foo . "bar") or (foo . chars) or (foo . numbers) or (foo . () )
-				   Parse all the data between the first and last f, and move past )
-				*/
-				tmp = ptr;
-				do {
-					ptr++;
-					parenlevel = get_paren_level(parenlevel, *ptr);
-				} while (parenlevel > 1);
-				parse_tzc_login_data(zephyr, tmp + 1, ptr - tmp);
-				ptr++;
-				break;
-			default:
-				purple_debug_info("zephyr", "parenlevel is not 1 or 2");
-				/* This shouldn't be happening */
-				break;
-		}
-	} while (ptr < bufend && parenlevel != 0);
-	purple_debug_info("zephyr", "tzc startup done");
-	g_free(buf);
-
-	return TRUE;
-}
-
-static gboolean
-login_zeph02(zephyr_account *zephyr)
-{
-	PurpleConnection *pc = purple_account_get_connection(zephyr->account);
-
-	/* XXX Should actually try to report the com_err determined error */
-	if (ZInitialize() != ZERR_NONE) {
-		purple_connection_error(pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-		                        "Couldn't initialize zephyr");
-		return FALSE;
-	}
-
-	if (ZOpenPort(&(zephyr->port)) != ZERR_NONE) {
-		purple_connection_error(pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-		                        "Couldn't open port");
-		return FALSE;
-	}
-
-	if (ZSetLocation(zephyr->exposure) != ZERR_NONE) {
-		purple_connection_error(pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-		                        "Couldn't set location");
-		return FALSE;
-	}
-
-	zephyr->username = g_strdup(ZGetSender());
-	zephyr->realm = get_zephyr_realm(zephyr->account, ZGetRealm());
-
-	return TRUE;
-}
-
 static void zephyr_login(PurpleAccount * account)
 {
 	PurpleConnection *gc;
@@ -1414,16 +680,24 @@
 
 	if (purple_account_get_bool(account, "use_tzc", FALSE)) {
 		zephyr->connection_type = PURPLE_ZEPHYR_TZC;
-		login = login_tzc;
-		check_notify = check_notify_tzc;
-		zephyr->subscribe_to = subscribe_to_tzc;
-		zephyr->request_locations = request_locations_tzc;
+		login = tzc_login;
+		check_notify = tzc_check_notify;
+		zephyr->subscribe_to = tzc_subscribe_to;
+		zephyr->request_locations = tzc_request_locations;
+		zephyr->send_message = tzc_send_message;
+		zephyr->set_location = tzc_set_location;
+		zephyr->get_subs_from_server = tzc_get_subs_from_server;
+		zephyr->close = tzc_close;
 	} else {
 		zephyr->connection_type = PURPLE_ZEPHYR_KRB4;
-		login = login_zeph02;
-		check_notify = check_notify_zeph02;
-		zephyr->subscribe_to = subscribe_to_zeph02;
-		zephyr->request_locations = request_locations_zeph02;
+		login = zeph02_login;
+		check_notify = zeph02_check_notify;
+		zephyr->subscribe_to = zeph02_subscribe_to;
+		zephyr->request_locations = zeph02_request_locations;
+		zephyr->send_message = zeph02_send_message;
+		zephyr->set_location = zeph02_set_location;
+		zephyr->get_subs_from_server = zeph02_get_subs_from_server;
+		zephyr->close = zeph02_close;
 	}
 
 	zephyr->encoding = (char *)purple_account_get_string(account, "encoding", ZEPHYR_FALLBACK_CHARSET);
@@ -1444,7 +718,7 @@
 	sub.zsub_classinst = "PERSONAL";
 	sub.zsub_recipient = zephyr->username;
 
-	if (zephyr->subscribe_to(zephyr, &sub) != ZERR_NONE) {
+	if (!zephyr->subscribe_to(zephyr, &sub)) {
 		/* XXX don't translate this yet. It could be written better */
 		/* XXX error messages could be handled with more detail */
 		purple_notify_error(gc, NULL,
@@ -1463,7 +737,7 @@
 	}
 
 	zephyr->nottimer = g_timeout_add(100, check_notify, gc);
-	zephyr->loctimer = g_timeout_add_seconds(20, check_loc, gc);
+	zephyr->loctimer = g_timeout_add_seconds(20, check_loc, zephyr);
 }
 
 static void write_zsubs(zephyr_account *zephyr)
@@ -1581,40 +855,35 @@
 	if (zephyr->loctimer)
 		g_source_remove(zephyr->loctimer);
 	zephyr->loctimer = 0;
-	if (use_zeph02(zephyr)) {
-		if (ZCancelSubscriptions(0) != ZERR_NONE) {
-			return;
-		}
-		if (ZUnsetLocation() != ZERR_NONE) {
-			return;
-		}
-		if (ZClosePort() != ZERR_NONE) {
-			return;
-		}
-	} else {
-		/* assume tzc */
-#ifdef G_OS_UNIX
-		GError *error = NULL;
-		g_subprocess_send_signal(zephyr->tzc_proc, SIGTERM);
-		if (!g_subprocess_wait(zephyr->tzc_proc, NULL, &error)) {
-			purple_debug_error("zephyr",
-			                   "error while attempting to close tzc: %s",
-			                   error->message);
-			g_error_free(error);
-		}
-#else
-		g_subprocess_force_exit(zephyr->tzc_proc);
-#endif
-		zephyr->tzc_stdin = NULL;
-		zephyr->tzc_stdout = NULL;
-		g_clear_object(&zephyr->tzc_proc);
-	}
+	zephyr->close(zephyr);
 }
 
-static gboolean zephyr_send_message(zephyr_account *zephyr, gchar *zclass,
-                                    gchar *instance, gchar *recipient,
-                                    const gchar *im, const gchar *sig,
-                                    gchar *opcode);
+static gboolean
+zephyr_send_message(zephyr_account *zephyr, gchar *zclass, gchar *instance,
+                    gchar *recipient, const gchar *im, const gchar *sig,
+                    gchar *opcode)
+{
+	/* (From the tzc source)
+	 * emacs sends something of the form:
+	 * ((class . "MESSAGE")
+	 *  (auth . t)
+	 *  (recipients ("PERSONAL" . "bovik") ("test" . ""))
+	 *  (sender . "bovik")
+	 *  (message . ("Harry Bovik" "my zgram"))
+	 * )
+	 */
+	char *tmp_buf;
+	char *html_buf;
+	gboolean result;
+
+	tmp_buf = html_to_zephyr(im);
+	html_buf = purple_unescape_html(tmp_buf);
+	g_free(tmp_buf);
+
+	result = zephyr->send_message(zephyr, zclass, instance, recipient, html_buf, sig, opcode);
+	g_free(html_buf);
+	return result;
+}
 
 static gint
 zephyr_triple_cmp_id(gconstpointer data, gconstpointer user_data)
@@ -1661,9 +930,9 @@
 		inst = g_strdup("PERSONAL");
 
 	if (!g_ascii_strcasecmp(zt->sub.zsub_recipient, "*")) {
-		recipient = local_zephyr_normalize(zephyr, "");
+		recipient = zephyr_normalize_local_realm(zephyr, "");
 	} else {
-		recipient = local_zephyr_normalize(zephyr, zt->sub.zsub_recipient);
+		recipient = zephyr_normalize_local_realm(zephyr, zt->sub.zsub_recipient);
 	}
 	zephyr_send_message(zephyr, zt->sub.zsub_class, inst, recipient,
 		purple_message_get_contents(msg), sig, "");
@@ -1682,133 +951,16 @@
 		sig = zephyr_get_signature();
 	}
 	zephyr_send_message(zephyr, "MESSAGE", "PERSONAL",
-		local_zephyr_normalize(zephyr, purple_message_get_recipient(msg)),
+	        zephyr_normalize_local_realm(zephyr, purple_message_get_recipient(msg)),
 		purple_message_get_contents(msg), sig, "");
 
 	return 1;
 }
 
 
-/* Munge the outgoing zephyr so that any quotes or backslashes are
-   escaped and do not confuse tzc: */
-static char *
-zephyr_tzc_escape_msg(const char *message)
-{
-	gsize msglen;
-	char *newmsg;
-
-	if (!message || !*message) {
-		return g_strdup("");
-	}
-
-	msglen = strlen(message);
-	newmsg = g_new0(char, msglen*2 + 1);
-	for (gsize pos = 0, pos2 = 0; pos < msglen; pos++, pos2++) {
-		if (message[pos] == '\\' || message[pos] == '"') {
-			newmsg[pos2] = '\\';
-			pos2++;
-		}
-		newmsg[pos2] = message[pos];
-	}
-
-	return newmsg;
-}
-
-static char *
-zephyr_tzc_deescape_str(const char *message)
-{
-	gsize msglen;
-	char *newmsg;
-
-	if (!message || !*message) {
-		return g_strdup("");
-	}
-
-	msglen = strlen(message);
-	newmsg = g_new0(char, msglen + 1);
-	for (gsize pos = 0, pos2 = 0; pos < msglen; pos++, pos2++) {
-		if (message[pos] == '\\') {
-			pos++;
-		}
-		newmsg[pos2] = message[pos];
-	}
-
-	return newmsg;
-}
-
-static gboolean
-zephyr_send_message(zephyr_account *zephyr, gchar *zclass, gchar *instance,
-                    gchar *recipient, const gchar *im, const gchar *sig,
-                    gchar *opcode)
-{
-
-	/* (From the tzc source)
-	 * emacs sends something of the form:
-	 * ((class . "MESSAGE")
-	 *  (auth . t)
-	 *  (recipients ("PERSONAL" . "bovik") ("test" . ""))
-	 *  (sender . "bovik")
-	 *  (message . ("Harry Bovik" "my zgram"))
-	 * )
-	 */
-	char *tmp_buf;
-	char *html_buf;
-	tmp_buf = html_to_zephyr(im);
-	html_buf = purple_unescape_html(tmp_buf);
-	g_free(tmp_buf);
-
-	if(use_tzc(zephyr)) {
-		char* zsendstr;
-		/* CMU cclub tzc doesn't grok opcodes for now  */
-		char* tzc_sig = zephyr_tzc_escape_msg(sig);
-		char *tzc_body = zephyr_tzc_escape_msg(html_buf);
-		zsendstr = g_strdup_printf("((tzcfodder . send) (class . \"%s\") (auth . t) (recipients (\"%s\" . \"%s\")) (message . (\"%s\" \"%s\"))	) \n",
-					   zclass, instance, recipient, tzc_sig, tzc_body);
-
-		if (!zephyr_write_message(zephyr, zsendstr)) {
-			g_free(tzc_sig);
-			g_free(tzc_body);
-			g_free(zsendstr);
-			g_free(html_buf);
-			return FALSE;
-		}
-		g_free(tzc_sig);
-		g_free(tzc_body);
-		g_free(zsendstr);
-	} else if (use_zeph02(zephyr)) {
-		ZNotice_t notice;
-		char *buf = g_strdup_printf("%s%c%s", sig, '\0', html_buf);
-		memset((char *)&notice, 0, sizeof(notice));
-
-		notice.z_kind = ACKED;
-		notice.z_port = 0;
-		notice.z_class = zclass;
-		notice.z_class_inst = instance;
-		notice.z_recipient = recipient;
-		notice.z_sender = 0;
-		notice.z_default_format = "Class $class, Instance $instance:\n" "To: @bold($recipient) at $time $date\n" "From: @bold($1) <$sender>\n\n$2";
-		notice.z_message_len = strlen(html_buf) + strlen(sig) + 2;
-		notice.z_message = buf;
-		notice.z_opcode = g_strdup(opcode);
-		purple_debug_info("zephyr","About to send notice\n");
-		if (ZSendNotice(&notice, ZAUTH) != ZERR_NONE) {
-			/* XXX handle errors here */
-			g_free(buf);
-			g_free(html_buf);
-			return FALSE;
-		}
-		purple_debug_info("zephyr","notice sent\n");
-		g_free(buf);
-	}
-
-	g_free(html_buf);
-
-	return TRUE;
-}
-
 /* Basically the inverse of zephyr_strip_local_realm */
-static char *
-local_zephyr_normalize(const zephyr_account *zephyr, const char *orig)
+char *
+zephyr_normalize_local_realm(const zephyr_account *zephyr, const char *orig)
 {
 	if (*orig == '\0' || strchr(orig, '@')) {
 		return g_strdup(orig);
@@ -1835,7 +987,7 @@
 	if (gc == NULL)
 		return NULL;
 
-	tmp = local_zephyr_normalize(purple_connection_get_protocol_data(gc), who);
+	tmp = zephyr_normalize_local_realm(purple_connection_get_protocol_data(gc), who);
 
 	if (strlen(tmp) >= sizeof(buf)) {
 		g_free(tmp);
@@ -1853,7 +1005,7 @@
             const gchar *who)
 {
 	zephyr_account *zephyr = purple_connection_get_protocol_data(gc);
-	gchar *normalized_who = local_zephyr_normalize(zephyr, who);
+	gchar *normalized_who = zephyr_normalize_local_realm(zephyr, who);
 
 	if (zephyr->request_locations(zephyr, normalized_who)) {
 		zephyr->pending_zloc_names = g_list_append(zephyr->pending_zloc_names, normalized_who);
@@ -1864,20 +1016,6 @@
 }
 
 static void
-zephyr_set_location(zephyr_account *zephyr, char *exposure)
-{
-	if (use_zeph02(zephyr)) {
-		ZSetLocation(exposure);
-	}
-	else {
-		gchar *zexpstr = g_strdup_printf("((tzcfodder . set-location) (hostname . \"%s\") (exposure . \"%s\"))\n",
-		                                 zephyr->ourhost, exposure);
-		zephyr_write_message(zephyr, zexpstr);
-		g_free(zexpstr);
-	}
-}
-
-static void
 zephyr_set_status(PurpleProtocolServer *protocol_server,
                   PurpleAccount *account, PurpleStatus *status)
 {
@@ -1892,11 +1030,11 @@
 		zephyr->away = g_strdup(purple_status_get_attr_string(status,"message"));
 	}
 	else if (primitive == PURPLE_STATUS_AVAILABLE) {
-		zephyr_set_location(zephyr, zephyr->exposure);
+		zephyr->set_location(zephyr, zephyr->exposure);
 	}
 	else if (primitive == PURPLE_STATUS_INVISIBLE) {
 		/* XXX handle errors */
-		zephyr_set_location(zephyr, EXPOSE_OPSTAFF);
+		zephyr->set_location(zephyr, EXPOSE_OPSTAFF);
 	}
 }
 
@@ -2028,7 +1166,7 @@
 		return;
 	}
 
-	if (zephyr->subscribe_to(zephyr, sub) != ZERR_NONE) {
+	if (!zephyr->subscribe_to(zephyr, sub)) {
 		/* Called when the server notifies us a message couldn't get sent */
 		/* XXX output better subscription information */
 		gchar *subscribe_failed = g_strdup_printf(_("Attempt to subscribe to %s,%s,%s failed"),
@@ -2129,7 +1267,7 @@
 	/* XXX We probably should care if this fails. Or maybe we don't want to */
 	if (!who) {
 		purple_debug_info("zephyr", "who is null\n");
-		recipient = local_zephyr_normalize(zephyr, "");
+		recipient = zephyr_normalize_local_realm(zephyr, "");
 	} else {
 		char *comma = strrchr(who, ',');
 		/* Don't ping broadcast (chat) recipients */
@@ -2139,7 +1277,7 @@
 			return 0;
 		}
 
-		recipient = local_zephyr_normalize(zephyr, who);
+		recipient = zephyr_normalize_local_realm(zephyr, who);
 	}
 
 	purple_debug_info("zephyr", "about to send typing notification to %s", recipient);
@@ -2172,7 +1310,7 @@
 
 	gcc = purple_conversations_find_chat_with_account(zt->name, purple_connection_get_account(gc));
 
-	topic_utf8 = zephyr_recv_convert(gc,(gchar *)topic);
+	topic_utf8 = convert_to_utf8(topic, zephyr->encoding);
 	purple_chat_conversation_set_topic(gcc, zephyr->username, topic_utf8);
 	g_free(topic_utf8);
 }
@@ -2196,7 +1334,7 @@
 	if (!g_ascii_strcasecmp(args[0],"*"))
 		return PURPLE_CMD_RET_FAILED;  /* "*" is not a valid argument */
 	else
-		recipient = local_zephyr_normalize(zephyr,args[0]);
+		recipient = zephyr_normalize_local_realm(zephyr,args[0]);
 
 	if (strlen(recipient) < 1) {
 		g_free(recipient);
@@ -2411,46 +1549,7 @@
 {
 	PurpleConnection *gc = action->connection;
 	zephyr_account *zephyr = purple_connection_get_protocol_data(gc);
-	gchar *title;
-	int retval, nsubs, one,i;
-	ZSubscription_t subs;
-	if (use_zeph02(zephyr)) {
-		GString *subout;
-
-		if (zephyr->port == 0) {
-			purple_debug_error("zephyr", "error while retrieving port\n");
-			return;
-		}
-		if ((retval = ZRetrieveSubscriptions(zephyr->port,&nsubs)) != ZERR_NONE) {
-			/* XXX better error handling */
-			purple_debug_error("zephyr", "error while retrieving subscriptions from server\n");
-			return;
-		}
-
-		title = g_strdup_printf("Server subscriptions for %s",
-		                        zephyr->username);
-		subout = g_string_new("Subscription list<br>");
-		for(i=0;i<nsubs;i++) {
-			one = 1;
-			if ((retval = ZGetSubscriptions(&subs,&one)) != ZERR_NONE) {
-				/* XXX better error handling */
-				g_free(title);
-				g_string_free(subout, TRUE);
-				purple_debug_error("zephyr", "error while retrieving individual subscription\n");
-				return;
-			}
-			g_string_append_printf(subout, "Class %s Instance %s Recipient %s<br>",
-					       subs.zsub_class, subs.zsub_classinst,
-					       subs.zsub_recipient);
-		}
-		purple_notify_formatted(gc, title, title, NULL,  subout->str, NULL, NULL);
-		g_free(title);
-		g_string_free(subout, TRUE);
-	} else {
-		/* XXX fix */
-		purple_notify_error(gc, "", "tzc doesn't support this action",
-			NULL, purple_request_cpar_from_connection(gc));
-	}
+	zephyr->get_subs_from_server(zephyr, gc);
 }
 
 
diff --git a/libpurple/protocols/zephyr/zephyr_account.h b/libpurple/protocols/zephyr/zephyr_account.h
new file mode 100644
--- /dev/null
+++ b/libpurple/protocols/zephyr/zephyr_account.h
@@ -0,0 +1,84 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef PURPLE_ZEPHYR_ZEPHYR_ACCOUNT_H
+#define PURPLE_ZEPHYR_ZEPHYR_ACCOUNT_H
+
+#include <glib.h>
+
+#include <purple.h>
+
+#include "internal.h"
+
+typedef struct _zephyr_account zephyr_account;
+
+typedef enum {
+        PURPLE_ZEPHYR_NONE, /* Non-kerberized ZEPH0.2 */
+        PURPLE_ZEPHYR_KRB4, /* ZEPH0.2 w/ KRB4 support */
+        PURPLE_ZEPHYR_TZC,  /* tzc executable proxy */
+        PURPLE_ZEPHYR_INTERGALACTIC_KRB4 /* Kerberized ZEPH0.3 */
+} zephyr_connection_type;
+
+typedef gboolean (*ZephyrSubscribeToFunc)(zephyr_account *zephyr, ZSubscription_t *sub);
+typedef gboolean (*ZephyrRequestLocationsFunc)(zephyr_account *zephyr, gchar *who);
+typedef gboolean (*ZephyrSendMessageFunc)(zephyr_account *zephyr, gchar *zclass, gchar *instance, gchar *recipient,
+                                          const gchar *html_buf, const gchar *sig, const gchar *opcode);
+typedef void (*ZephyrSetLocationFunc)(zephyr_account *zephyr, char *exposure);
+typedef void (*ZephyrGetSubsFromServerFunc)(zephyr_account *zephyr, PurpleConnection *gc);
+typedef void (*ZephyrCloseFunc)(zephyr_account *zephyr);
+
+struct _zephyr_account {
+        PurpleAccount* account;
+        char *username;
+        char *realm;
+        char *encoding;
+        char* galaxy; /* not yet useful */
+        char* krbtkfile; /* not yet useful */
+        guint32 nottimer;
+        guint32 loctimer;
+        GList *pending_zloc_names;
+        GSList *subscrips;
+        int last_id;
+        unsigned short port;
+        char ourhost[HOST_NAME_MAX + 1];
+        char ourhostcanon[HOST_NAME_MAX + 1];
+        zephyr_connection_type connection_type;
+        char *exposure;
+        GSubprocess *tzc_proc;
+        GOutputStream *tzc_stdin;
+        GInputStream *tzc_stdout;
+        gchar *away;
+        ZephyrSubscribeToFunc subscribe_to;
+        ZephyrRequestLocationsFunc request_locations;
+        ZephyrSendMessageFunc send_message;
+        ZephyrSetLocationFunc set_location;
+        ZephyrGetSubsFromServerFunc get_subs_from_server;
+        ZephyrCloseFunc close;
+};
+
+gchar *get_zephyr_realm(PurpleAccount *account, const gchar *local_realm);
+void handle_message(PurpleConnection *gc, ZNotice_t *notice_p);
+gboolean pending_zloc(zephyr_account *zephyr, const char *who);
+PurpleBuddy *find_buddy(const zephyr_account *zephyr, const char *user);
+char *zephyr_normalize_local_realm(const zephyr_account *zephyr, const char *orig);
+
+#endif /* PURPLE_ZEPHYR_ZEPHYR_ACCOUNT_H */
diff --git a/libpurple/protocols/zephyr/zephyr_tzc.h b/libpurple/protocols/zephyr/zephyr_tzc.h
new file mode 100644
--- /dev/null
+++ b/libpurple/protocols/zephyr/zephyr_tzc.h
@@ -0,0 +1,38 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef PURPLE_ZEPHYR_ZEPHYR_TZC_H
+#define PURPLE_ZEPHYR_ZEPHYR_TZC_H
+
+#include "zephyr_account.h"
+
+gboolean tzc_login(zephyr_account *zephyr);
+gint tzc_check_notify(gpointer data);
+gboolean tzc_subscribe_to(zephyr_account *zephyr, ZSubscription_t *sub);
+gboolean tzc_request_locations(zephyr_account *zephyr, gchar *who);
+gboolean tzc_send_message(zephyr_account *zephyr, gchar *zclass, gchar *instance, gchar *recipient,
+                          const gchar *html_buf, const gchar *sig, const gchar *opcode);
+void tzc_set_location(zephyr_account *zephyr, char *exposure);
+void tzc_get_subs_from_server(zephyr_account *zephyr, PurpleConnection *gc);
+void tzc_close(zephyr_account *zephyr);
+
+#endif /* PURPLE_ZEPHYR_ZEPHYR_TZC_H */
diff --git a/libpurple/protocols/zephyr/zephyr_tzc.c b/libpurple/protocols/zephyr/zephyr_tzc.c
new file mode 100644
--- /dev/null
+++ b/libpurple/protocols/zephyr/zephyr_tzc.c
@@ -0,0 +1,657 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n-lib.h>
+
+#include "zephyr_tzc.h"
+
+#define MAXCHILDREN 20
+
+typedef gssize (*PollableInputStreamReadFunc)(GPollableInputStream *stream, void *bufcur, GError **error);
+
+static gchar *
+tzc_read(zephyr_account *zephyr, PollableInputStreamReadFunc read_func)
+{
+	GPollableInputStream *stream = G_POLLABLE_INPUT_STREAM(zephyr->tzc_stdout);
+	gsize bufsize = 2048;
+	gchar *buf = g_new(gchar, bufsize);
+	gchar *bufcur = buf;
+	gboolean selected = FALSE;
+
+	while (TRUE) {
+		GError *error = NULL;
+		if (read_func(stream, bufcur, &error) < 0) {
+			if (error->code == G_IO_ERROR_WOULD_BLOCK ||
+			    error->code == G_IO_ERROR_TIMED_OUT) {
+				g_error_free(error);
+				break;
+			}
+			purple_debug_error("zephyr", "couldn't read: %s", error->message);
+			purple_connection_error(purple_account_get_connection(zephyr->account), PURPLE_CONNECTION_ERROR_NETWORK_ERROR, "couldn't read");
+			g_error_free(error);
+			g_free(buf);
+			return NULL;
+		}
+		selected = TRUE;
+		bufcur++;
+		if ((bufcur - buf) > (bufsize - 1)) {
+			if ((buf = g_realloc(buf, bufsize * 2)) == NULL) {
+				purple_debug_error("zephyr","Ran out of memory\n");
+				exit(-1);
+			} else {
+				bufcur = buf + bufsize;
+				bufsize *= 2;
+			}
+		}
+	}
+	*bufcur = '\0';
+
+	if (!selected) {
+		g_free(buf);
+		buf = NULL;
+	}
+	return buf;
+}
+
+static gboolean
+tzc_write(zephyr_account *zephyr, const gchar *format, ...)
+{
+	va_list args;
+	gchar *message;
+	GError *error = NULL;
+	gboolean success;
+
+	va_start(args, format);
+	message = g_strdup_vprintf(format, args);
+	va_end(args);
+
+	success = g_output_stream_write_all(zephyr->tzc_stdin, message, strlen(message),
+	                                    NULL, NULL, &error);
+	if (!success) {
+		purple_debug_error("zephyr", "Unable to write a message: %s", error->message);
+		g_error_free(error);
+	}
+	g_free(message);
+	return success;
+}
+
+/* Munge the outgoing zephyr so that any quotes or backslashes are
+   escaped and do not confuse tzc: */
+static char *
+tzc_escape_msg(const char *message)
+{
+	gsize msglen;
+	char *newmsg;
+
+	if (!message || !*message) {
+		return g_strdup("");
+	}
+
+	msglen = strlen(message);
+	newmsg = g_new0(char, msglen*2 + 1);
+	for (gsize pos = 0, pos2 = 0; pos < msglen; pos++, pos2++) {
+		if (message[pos] == '\\' || message[pos] == '"') {
+			newmsg[pos2] = '\\';
+			pos2++;
+		}
+		newmsg[pos2] = message[pos];
+	}
+
+	return newmsg;
+}
+
+static char *
+tzc_deescape_str(const char *message)
+{
+	gsize msglen;
+	char *newmsg;
+
+	if (!message || !*message) {
+		return g_strdup("");
+	}
+
+	msglen = strlen(message);
+	newmsg = g_new0(char, msglen + 1);
+	for (gsize pos = 0, pos2 = 0; pos < msglen; pos++, pos2++) {
+		if (message[pos] == '\\') {
+			pos++;
+		}
+		newmsg[pos2] = message[pos];
+	}
+
+	return newmsg;
+}
+
+static GSubprocess *
+get_tzc_process(const zephyr_account *zephyr)
+{
+	GSubprocess *tzc_process = NULL;
+	const gchar *tzc_cmd;
+	gchar **tzc_cmd_array = NULL;
+	GError *error = NULL;
+	gboolean found_ps = FALSE;
+	gint i;
+
+	/* tzc_command should really be of the form
+	   path/to/tzc -e %s
+	   or
+	   ssh username@hostname pathtotzc -e %s
+	   -- this should not require a password, and ideally should be
+	   kerberized ssh --
+	   or
+	   fsh username@hostname pathtotzc -e %s
+	*/
+	tzc_cmd = purple_account_get_string(zephyr->account, "tzc_command", "/usr/bin/tzc -e %s");
+	if (!g_shell_parse_argv(tzc_cmd, NULL, &tzc_cmd_array, &error)) {
+		purple_debug_error("zephyr", "Unable to parse tzc_command: %s", error->message);
+		purple_connection_error(
+		        purple_account_get_connection(zephyr->account),
+		        PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
+		        "invalid tzc_command setting");
+		g_error_free(error);
+		return NULL;
+	}
+	for (i = 0; tzc_cmd_array[i] != NULL; i++) {
+		if (purple_strequal(tzc_cmd_array[i], "%s")) {
+			g_free(tzc_cmd_array[i]);
+			tzc_cmd_array[i] = g_strdup(zephyr->exposure);
+			found_ps = TRUE;
+		}
+	}
+
+	if (!found_ps) {
+		purple_debug_error("zephyr", "tzc exited early");
+		purple_connection_error(
+		        purple_account_get_connection(zephyr->account),
+		        PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+		        "invalid output by tzc (or bad parsing code)");
+		g_strfreev(tzc_cmd_array);
+		return NULL;
+	}
+
+	tzc_process = g_subprocess_newv(
+	        (const gchar *const *)tzc_cmd_array,
+	        G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE,
+	        &error);
+	if (tzc_process == NULL) {
+		purple_debug_error("zephyr", "tzc exited early: %s", error->message);
+		purple_connection_error(
+		        purple_account_get_connection(zephyr->account),
+		        PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+		        "invalid output by tzc (or bad parsing code)");
+		g_error_free(error);
+	}
+
+	g_strfreev(tzc_cmd_array);
+	return tzc_process;
+}
+
+static gssize
+pollable_input_stream_read(GPollableInputStream *stream, void *bufcur, GError **error)
+{
+	return g_pollable_input_stream_read_nonblocking(stream, bufcur, 1, NULL, error);
+}
+
+static gssize
+pollable_input_stream_read_with_timeout(GPollableInputStream *stream,
+                                        void *bufcur, GError **error)
+{
+	const gint64 timeout = 10 * G_USEC_PER_SEC;
+	gint64 now = g_get_monotonic_time();
+
+	while (g_get_monotonic_time() < now + timeout) {
+		GError *local_error = NULL;
+		gssize ret = g_pollable_input_stream_read_nonblocking(
+		        stream, bufcur, 1, NULL, &local_error);
+		if (ret == 1) {
+			return ret;
+		} else {
+			if (local_error->code == G_IO_ERROR_WOULD_BLOCK) {
+				/* Keep on waiting if this is a blocking error. */
+				g_clear_error(&local_error);
+			} else {
+				g_propagate_error(error, local_error);
+				return ret;
+			}
+		}
+	}
+
+	g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
+	                    "tzc did not respond in time");
+	return -1;
+}
+
+static gint
+get_paren_level(gint paren_level, gchar ch)
+{
+	switch (ch) {
+	        case '(': return paren_level + 1;
+	        case ')': return paren_level - 1;
+	        default: return paren_level;
+	}
+}
+
+static void
+parse_tzc_login_data(zephyr_account *zephyr, const gchar *buf, gint buflen)
+{
+	gchar *str = g_strndup(buf, buflen);
+
+	purple_debug_info("zephyr", "tempstr parsed");
+
+	/* str should now be a string containing all characters from
+	 * buf after the first ( to the one before the last paren ).
+	 * We should have the following possible lisp strings but we don't care
+	 * (tzcspew . start) (version . "something") (pid . number)
+	 * We care about 'zephyrid . "username@REALM.NAME"' and
+	 * 'exposure . "SOMETHING"' */
+	if (!g_ascii_strncasecmp(str, "zephyrid", 8)) {
+		gchar **strv;
+		gchar *username;
+		const char *at;
+
+		purple_debug_info("zephyr", "zephyrid found");
+
+		strv = g_strsplit(str + 8, "\"", -1);
+		username = strv[1] ? strv[1] : "";
+		zephyr->username = g_strdup(username);
+
+		at = strchr(username, '@');
+		if (at != NULL) {
+			zephyr->realm = g_strdup(at + 1);
+		} else {
+			zephyr->realm = get_zephyr_realm(zephyr->account, "local-realm");
+		}
+
+		g_strfreev(strv);
+	} else {
+		purple_debug_info("zephyr", "something that's not zephyr id found %s", str);
+	}
+
+	/* We don't care about anything else yet */
+	g_free(str);
+}
+
+static gchar *
+tree_child_contents(GNode *tree, int index)
+{
+	GNode *child = g_node_nth_child(tree, index);
+	return child ? child->data : "";
+}
+
+static GNode *
+find_node(GNode *ptree, gchar *key)
+{
+	guint num_children;
+	gchar* tc;
+
+	if (!ptree || ! key)
+		return NULL;
+
+	num_children = g_node_n_children(ptree);
+	tc = tree_child_contents(ptree, 0);
+
+	/* g_strcasecmp() is deprecated.  What is the encoding here??? */
+	if (num_children > 0 && tc && !g_ascii_strcasecmp(tc, key)) {
+		return ptree;
+	} else {
+		GNode *result = NULL;
+		guint i;
+		for (i = 0; i < num_children; i++) {
+			result = find_node(g_node_nth_child(ptree, i), key);
+			if(result != NULL) {
+				break;
+			}
+		}
+		return result;
+	}
+}
+
+static GNode *
+parse_buffer(const gchar *source, gboolean do_parse)
+{
+	GNode *ptree = g_node_new(NULL);
+
+	if (do_parse) {
+		unsigned int p = 0;
+		while(p < strlen(source)) {
+			unsigned int end;
+			gchar *newstr;
+
+			/* Eat white space: */
+			if(g_ascii_isspace(source[p]) || source[p] == '\001') {
+				p++;
+				continue;
+			}
+
+			/* Skip comments */
+			if(source[p] == ';') {
+				while(source[p] != '\n' && p < strlen(source)) {
+					p++;
+				}
+				continue;
+			}
+
+			if(source[p] == '(') {
+				int nesting = 0;
+				gboolean in_quote = FALSE;
+				gboolean escape_next = FALSE;
+				p++;
+				end = p;
+				while(!(source[end] == ')' && nesting == 0 && !in_quote) && end < strlen(source)) {
+					if(!escape_next) {
+						if(source[end] == '\\') {
+							escape_next = TRUE;
+						}
+						if(!in_quote) {
+							if(source[end] == '(') {
+								nesting++;
+							}
+							if(source[end] == ')') {
+								nesting--;
+							}
+						}
+						if(source[end] == '"') {
+							in_quote = !in_quote;
+						}
+					} else {
+						escape_next = FALSE;
+					}
+					end++;
+				}
+				do_parse = TRUE;
+
+			} else {
+				gchar end_char;
+				if(source[p] == '"') {
+					end_char = '"';
+					p++;
+				} else {
+					end_char = ' ';
+				}
+				do_parse = FALSE;
+
+				end = p;
+				while(source[end] != end_char && end < strlen(source)) {
+					if(source[end] == '\\')
+						end++;
+					end++;
+				}
+			}
+			newstr = g_new0(gchar, end+1-p);
+			strncpy(newstr,source+p,end-p);
+			if (g_node_n_children(ptree) < MAXCHILDREN) {
+				/* In case we surpass maxchildren, ignore this */
+				g_node_append(ptree, parse_buffer(newstr, do_parse));
+			} else {
+				purple_debug_error("zephyr","too many children in tzc output. skipping\n");
+			}
+			g_free(newstr);
+			p = end + 1;
+		}
+	} else {
+		/* XXX does this have to be strdup'd */
+		ptree->data = g_strdup(source);
+	}
+
+	return ptree;
+}
+
+gboolean
+tzc_login(zephyr_account *zephyr)
+{
+	gchar *buf = NULL;
+	const gchar *bufend = NULL;
+	const gchar *ptr;
+	const gchar *tmp;
+	gint parenlevel = 0;
+
+	zephyr->tzc_proc = get_tzc_process(zephyr);
+	if (zephyr->tzc_proc == NULL) {
+		return FALSE;
+	}
+	zephyr->tzc_stdin = g_subprocess_get_stdin_pipe(zephyr->tzc_proc);
+	zephyr->tzc_stdout = g_subprocess_get_stdout_pipe(zephyr->tzc_proc);
+
+	purple_debug_info("zephyr", "about to read from tzc");
+	buf = tzc_read(zephyr, pollable_input_stream_read_with_timeout);
+	if (buf == NULL) {
+		return FALSE;
+	}
+	bufend = buf + strlen(buf);
+	ptr = buf;
+
+	/* ignore all tzcoutput till we've received the first ( */
+	while (ptr < bufend && (*ptr != '(')) {
+		ptr++;
+	}
+	if (ptr >= bufend) {
+		purple_connection_error(
+		        purple_account_get_connection(zephyr->account),
+		        PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+		        "invalid output by tzc (or bad parsing code)");
+		g_free(buf);
+		return FALSE;
+	}
+
+	do {
+		parenlevel = get_paren_level(parenlevel, *ptr);
+		purple_debug_info("zephyr", "tzc parenlevel is %d", parenlevel);
+		switch (parenlevel) {
+		        case 1:
+			        /* Search for next beginning (, or for the ending */
+			        do {
+				        ptr++;
+			        } while ((ptr < bufend) && (*ptr != '(') && (*ptr != ')'));
+			        if (ptr >= bufend) {
+					purple_debug_error("zephyr", "tzc parsing error");
+				}
+			        break;
+		        case 2:
+			        /* You are probably at
+				   (foo . bar ) or (foo . "bar") or (foo . chars) or (foo . numbers) or (foo . () )
+				   Parse all the data between the first and last f, and move past )
+				*/
+			        tmp = ptr;
+				do {
+					ptr++;
+					parenlevel = get_paren_level(parenlevel, *ptr);
+				} while (parenlevel > 1);
+				parse_tzc_login_data(zephyr, tmp + 1, ptr - tmp);
+				ptr++;
+			        break;
+		        default:
+			        purple_debug_info("zephyr", "parenlevel is not 1 or 2");
+				/* This shouldn't be happening */
+			        break;
+		}
+	} while (ptr < bufend && parenlevel != 0);
+	purple_debug_info("zephyr", "tzc startup done");
+	g_free(buf);
+
+	return TRUE;
+}
+
+gint
+tzc_check_notify(gpointer data)
+{
+	PurpleConnection *gc = (PurpleConnection *)data;
+	zephyr_account* zephyr = purple_connection_get_protocol_data(gc);
+	GNode *newparsetree = NULL;
+	gchar *buf = tzc_read(zephyr, pollable_input_stream_read);
+
+	if (buf != NULL) {
+		newparsetree = parse_buffer(buf, TRUE);
+		g_free(buf);
+	}
+
+	if (newparsetree != NULL) {
+		gchar *spewtype;
+		if ( (spewtype =  tree_child_contents(find_node(newparsetree, "tzcspew"), 2)) ) {
+			if (!g_ascii_strncasecmp(spewtype,"message",7)) {
+				ZNotice_t notice;
+				GNode *msgnode = g_node_nth_child(find_node(newparsetree, "message"), 2);
+				/*char *zsig = g_strdup(" ");*/ /* purple doesn't care about zsigs */
+				char *msg  = tzc_deescape_str(tree_child_contents(msgnode, 1));
+				char *buf = g_strdup_printf(" %c%s", '\0', msg);
+				memset((char *)&notice, 0, sizeof(notice));
+				notice.z_kind = ACKED;
+				notice.z_port = 0;
+				notice.z_opcode = tree_child_contents(find_node(newparsetree, "opcode"), 2);
+				notice.z_class = tzc_deescape_str(tree_child_contents(find_node(newparsetree, "class"), 2));
+				notice.z_class_inst = tree_child_contents(find_node(newparsetree, "instance"), 2);
+				notice.z_recipient = zephyr_normalize_local_realm(zephyr, tree_child_contents(find_node(newparsetree, "recipient"), 2));
+				notice.z_sender = zephyr_normalize_local_realm(zephyr, tree_child_contents(find_node(newparsetree, "sender"), 2));
+				notice.z_default_format = "Class $class, Instance $instance:\n" "To: @bold($recipient) at $time $date\n" "From: @bold($1) <$sender>\n\n$2";
+				notice.z_message_len = 1 + 1 + strlen(msg) + 1;
+				notice.z_message = buf;
+				handle_message(gc, &notice);
+				g_free(msg);
+				/*g_free(zsig);*/
+				g_free(buf);
+			}
+			else if (!g_ascii_strncasecmp(spewtype,"zlocation",9)) {
+				/* check_loc or zephyr_zloc respectively */
+				/* XXX fix */
+				char *user;
+				PurpleBuddy *b;
+				const char *bname;
+				const gchar *name;
+				gboolean has_locations;
+				GNode *locations;
+				gchar *locval;
+
+				user = tree_child_contents(find_node(newparsetree, "user"), 2);
+				b = find_buddy(zephyr, user);
+				bname = b ? purple_buddy_get_name(b) : NULL;
+				name = b ? bname : user;
+
+				locations = g_node_nth_child(g_node_nth_child(find_node(newparsetree, "locations"), 2), 0);
+				locval = tree_child_contents(g_node_nth_child(locations, 0), 2);
+				has_locations = (locval && *locval && !purple_strequal(locval, " "));
+				if ((b && pending_zloc(zephyr, bname)) || pending_zloc(zephyr, user)) {
+					PurpleNotifyUserInfo *user_info = purple_notify_user_info_new();
+					const char *balias;
+
+					/* TODO: Check whether it's correct to call add_pair_html,
+						 or if we should be using add_pair_plaintext */
+					purple_notify_user_info_add_pair_html(user_info, _("User"), name);
+
+					balias = b ? purple_buddy_get_local_alias(b) : NULL;
+					if (balias)
+						purple_notify_user_info_add_pair_plaintext(user_info, _("Alias"), balias);
+
+					if (!has_locations) {
+						purple_notify_user_info_add_pair_plaintext(user_info, NULL, _("Hidden or not logged-in"));
+					} else {
+						/* TODO: Need to escape the two strings that make up tmp? */
+						char *tmp = g_strdup_printf(_("<br>At %s since %s"), locval,
+						                      tree_child_contents(g_node_nth_child(locations, 2), 2));
+						purple_notify_user_info_add_pair_html(user_info, _("Location"), tmp);
+						g_free(tmp);
+					}
+
+					purple_notify_userinfo(gc, name, user_info, NULL, NULL);
+					purple_notify_user_info_destroy(user_info);
+				} else {
+					purple_protocol_got_user_status(zephyr->account, name, has_locations ? "available" : "offline", NULL);
+				}
+			}
+			else if (!g_ascii_strncasecmp(spewtype,"subscribed",10)) {
+			}
+			else if (!g_ascii_strncasecmp(spewtype,"start",5)) {
+			}
+			else if (!g_ascii_strncasecmp(spewtype,"error",5)) {
+				/* XXX handle */
+			}
+		} else {
+		}
+	} else {
+	}
+
+	g_node_destroy(newparsetree);
+	return TRUE;
+}
+
+gboolean
+tzc_subscribe_to(zephyr_account *zephyr, ZSubscription_t *sub)
+{
+	/* ((tzcfodder . subscribe) ("class" "instance" "recipient")) */
+	return tzc_write(zephyr, "((tzcfodder . subscribe) (\"%s\" \"%s\" \"%s\"))\n",
+	                 sub->zsub_class, sub->zsub_classinst, sub->zsub_recipient);
+}
+
+gboolean
+tzc_request_locations(zephyr_account *zephyr, gchar *who)
+{
+	return tzc_write(zephyr, "((tzcfodder . zlocate) \"%s\")\n", who);
+}
+
+gboolean
+tzc_send_message(zephyr_account *zephyr, gchar *zclass, gchar *instance, gchar *recipient,
+                 const gchar *html_buf, const gchar *sig, G_GNUC_UNUSED const gchar *opcode)
+{
+	/* CMU cclub tzc doesn't grok opcodes for now  */
+	char *tzc_sig = tzc_escape_msg(sig);
+	char *tzc_body = tzc_escape_msg(html_buf);
+	gboolean result;
+
+	result = tzc_write(zephyr, "((tzcfodder . send) (class . \"%s\") (auth . t) (recipients (\"%s\" . \"%s\")) (message . (\"%s\" \"%s\"))	) \n",
+	                   zclass, instance, recipient, tzc_sig, tzc_body);
+	g_free(tzc_sig);
+	g_free(tzc_body);
+	return result;
+}
+
+void
+tzc_set_location(zephyr_account *zephyr, char *exposure)
+{
+	tzc_write(zephyr, "((tzcfodder . set-location) (hostname . \"%s\") (exposure . \"%s\"))\n",
+	          zephyr->ourhost, exposure);
+}
+
+void
+tzc_get_subs_from_server(G_GNUC_UNUSED zephyr_account *zephyr, PurpleConnection *gc)
+{
+	/* XXX fix */
+	purple_notify_error(gc, "", "tzc doesn't support this action",
+	        NULL, purple_request_cpar_from_connection(gc));
+}
+
+void
+tzc_close(zephyr_account *zephyr)
+{
+#ifdef G_OS_UNIX
+	GError *error = NULL;
+	g_subprocess_send_signal(zephyr->tzc_proc, SIGTERM);
+	if (!g_subprocess_wait(zephyr->tzc_proc, NULL, &error)) {
+		purple_debug_error("zephyr",
+		                   "error while attempting to close tzc: %s",
+		                   error->message);
+		g_error_free(error);
+	}
+#else
+	g_subprocess_force_exit(zephyr->tzc_proc);
+#endif
+	zephyr->tzc_stdin = NULL;
+	zephyr->tzc_stdout = NULL;
+	g_clear_object(&zephyr->tzc_proc);
+}
diff --git a/libpurple/protocols/zephyr/zephyr_zeph02.h b/libpurple/protocols/zephyr/zephyr_zeph02.h
new file mode 100644
--- /dev/null
+++ b/libpurple/protocols/zephyr/zephyr_zeph02.h
@@ -0,0 +1,38 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef PURPLE_ZEPHYR_ZEPHYR_ZEPH02_H
+#define PURPLE_ZEPHYR_ZEPHYR_ZEPH02_H
+
+#include "zephyr_account.h"
+
+gboolean zeph02_login(zephyr_account *zephyr);
+gint zeph02_check_notify(gpointer data);
+gboolean zeph02_subscribe_to(zephyr_account *zephyr, ZSubscription_t *sub);
+gboolean zeph02_request_locations(zephyr_account *zephyr, gchar *who);
+gboolean zeph02_send_message(zephyr_account *zephyr, gchar *zclass, gchar *instance, gchar *recipient,
+                             const gchar *html_buf, const gchar *sig, const gchar *opcode);
+void zeph02_set_location(zephyr_account *zephyr, char *exposure);
+void zeph02_get_subs_from_server(zephyr_account *zephyr, PurpleConnection *gc);
+void zeph02_close(zephyr_account *zephyr);
+
+#endif /* PURPLE_ZEPHYR_ZEPHYR_ZEPH02_H */
diff --git a/libpurple/protocols/zephyr/zephyr_zeph02.c b/libpurple/protocols/zephyr/zephyr_zeph02.c
new file mode 100644
--- /dev/null
+++ b/libpurple/protocols/zephyr/zephyr_zeph02.c
@@ -0,0 +1,241 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n-lib.h>
+
+#include "zephyr_zeph02.h"
+
+gboolean
+zeph02_login(zephyr_account *zephyr)
+{
+	PurpleConnection *pc = purple_account_get_connection(zephyr->account);
+
+	/* XXX Should actually try to report the com_err determined error */
+	if (ZInitialize() != ZERR_NONE) {
+		purple_connection_error(pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+		                        "Couldn't initialize zephyr");
+		return FALSE;
+	}
+
+	if (ZOpenPort(&(zephyr->port)) != ZERR_NONE) {
+		purple_connection_error(pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+		                        "Couldn't open port");
+		return FALSE;
+	}
+
+	if (ZSetLocation(zephyr->exposure) != ZERR_NONE) {
+		purple_connection_error(pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+		                        "Couldn't set location");
+		return FALSE;
+	}
+
+	zephyr->username = g_strdup(ZGetSender());
+	zephyr->realm = get_zephyr_realm(zephyr->account, ZGetRealm());
+
+	return TRUE;
+}
+
+/* Called when the server notifies us a message couldn't get sent */
+static void
+message_failed(PurpleConnection *gc, ZNotice_t *notice)
+{
+	gchar *title;
+	gchar *notify_msg;
+
+	if (g_ascii_strcasecmp(notice->z_class, "message")) {
+		title = "";
+		notify_msg = g_strdup_printf(_("Unable to send to chat %s,%s,%s"),
+		                             notice->z_class, notice->z_class_inst, notice->z_recipient);
+	} else {
+		title = (gchar *)notice->z_recipient;
+		notify_msg = g_strdup(_("User is offline"));
+	}
+	purple_notify_error(gc, title, notify_msg, NULL, purple_request_cpar_from_connection(gc));
+	g_free(notify_msg);
+}
+
+/* just for debugging */
+static void
+handle_unknown(ZNotice_t *notice)
+{
+	purple_debug_error("zephyr", "z_packet: %s\n", notice->z_packet);
+	purple_debug_error("zephyr", "z_version: %s\n", notice->z_version);
+	purple_debug_error("zephyr", "z_kind: %d\n", (int)(notice->z_kind));
+	purple_debug_error("zephyr", "z_class: %s\n", notice->z_class);
+	purple_debug_error("zephyr", "z_class_inst: %s\n", notice->z_class_inst);
+	purple_debug_error("zephyr", "z_opcode: %s\n", notice->z_opcode);
+	purple_debug_error("zephyr", "z_sender: %s\n", notice->z_sender);
+	purple_debug_error("zephyr", "z_recipient: %s\n", notice->z_recipient);
+	purple_debug_error("zephyr", "z_message: %s\n", notice->z_message);
+	purple_debug_error("zephyr", "z_message_len: %d\n", notice->z_message_len);
+}
+
+gint
+zeph02_check_notify(gpointer data)
+{
+	/* XXX add real error reporting */
+	PurpleConnection *gc = (PurpleConnection*) data;
+	while (ZPending()) {
+		ZNotice_t notice;
+		/* XXX add real error reporting */
+
+		if (ZReceiveNotice(&notice, NULL) != ZERR_NONE) {
+			return TRUE;
+		}
+
+		switch (notice.z_kind) {
+		case UNSAFE:
+		case UNACKED:
+		case ACKED:
+			handle_message(gc, &notice);
+			break;
+		case SERVACK:
+			if (!(g_ascii_strcasecmp(notice.z_message, ZSRVACK_NOTSENT))) {
+				message_failed(gc, &notice);
+			}
+			break;
+		case CLIENTACK:
+			purple_debug_error("zephyr", "Client ack received\n");
+			handle_unknown(&notice); /* XXX: is it really unknown? */
+			break;
+		default:
+			/* we'll just ignore things for now */
+			handle_unknown(&notice);
+			purple_debug_error("zephyr", "Unhandled notice.\n");
+			break;
+		}
+		/* XXX add real error reporting */
+		ZFreeNotice(&notice);
+	}
+
+	return TRUE;
+}
+
+gboolean
+zeph02_subscribe_to(G_GNUC_UNUSED zephyr_account *zephyr, ZSubscription_t *sub)
+{
+	return ZSubscribeTo(sub, 1, 0) == ZERR_NONE;
+}
+
+gboolean
+zeph02_request_locations(G_GNUC_UNUSED zephyr_account *zephyr, gchar *who)
+{
+	ZAsyncLocateData_t ald;
+	Code_t zerr;
+
+	zerr = ZRequestLocations(who, &ald, UNACKED, ZAUTH);
+	g_free(ald.user);
+	g_free(ald.version);
+	return zerr == ZERR_NONE;
+}
+
+gboolean
+zeph02_send_message(G_GNUC_UNUSED zephyr_account *zephyr, gchar *zclass, gchar *instance, gchar *recipient,
+                    const gchar *html_buf, const gchar *sig, const gchar *opcode)
+{
+	ZNotice_t notice;
+	char *buf = g_strdup_printf("%s%c%s", sig, '\0', html_buf);
+	gboolean result = TRUE;
+
+	memset((char *)&notice, 0, sizeof(notice));
+	notice.z_kind = ACKED;
+	notice.z_port = 0;
+	notice.z_class = zclass;
+	notice.z_class_inst = instance;
+	notice.z_recipient = recipient;
+	notice.z_sender = 0;
+	notice.z_default_format = "Class $class, Instance $instance:\n" "To: @bold($recipient) at $time $date\n" "From: @bold($1) <$sender>\n\n$2";
+	notice.z_message_len = strlen(sig) + 1 + strlen(html_buf) + 1;
+	notice.z_message = buf;
+	notice.z_opcode = g_strdup(opcode);
+
+	purple_debug_info("zephyr","About to send notice\n");
+	if (ZSendNotice(&notice, ZAUTH) != ZERR_NONE) {
+		/* XXX handle errors here */
+		result = FALSE;
+	} else {
+		purple_debug_info("zephyr", "notice sent\n");
+	}
+	g_free(buf);
+	return result;
+}
+
+void
+zeph02_set_location(G_GNUC_UNUSED zephyr_account *zephyr, char *exposure)
+{
+	ZSetLocation(exposure);
+}
+
+void
+zeph02_get_subs_from_server(zephyr_account *zephyr, PurpleConnection *gc)
+{
+	Code_t retval;
+	int nsubs;
+	GString *subout;
+
+	if (zephyr->port == 0) {
+		purple_debug_error("zephyr", "error while retrieving port\n");
+		return;
+	}
+	if ((retval = ZRetrieveSubscriptions(zephyr->port, &nsubs)) != ZERR_NONE) {
+		/* XXX better error handling */
+		purple_debug_error("zephyr", "error while retrieving subscriptions from server\n");
+		return;
+	}
+
+	subout = g_string_new("Subscription list<br>");
+	for (int i = 0; i < nsubs; i++) {
+		ZSubscription_t subs;
+		int one = 1;
+
+		if ((retval = ZGetSubscriptions(&subs, &one)) != ZERR_NONE) {
+			break;
+		}
+		g_string_append_printf(subout, "Class %s Instance %s Recipient %s<br>",
+		                       subs.zsub_class, subs.zsub_classinst, subs.zsub_recipient);
+	}
+
+	if (retval == ZERR_NONE) {
+		gchar *title = g_strdup_printf("Server subscriptions for %s", zephyr->username);
+		purple_notify_formatted(gc, title, title, NULL, subout->str, NULL, NULL);
+		g_free(title);
+	} else {
+		/* XXX better error handling */
+		purple_debug_error("zephyr", "error while retrieving individual subscription\n");
+	}
+
+	g_string_free(subout, TRUE);
+}
+
+void
+zeph02_close(G_GNUC_UNUSED zephyr_account *zephyr)
+{
+	if (ZCancelSubscriptions(0) != ZERR_NONE) {
+		return;
+	}
+	if (ZUnsetLocation() != ZERR_NONE) {
+		return;
+	}
+	if (ZClosePort() != ZERR_NONE) {
+		return;
+	}
+}
