view src/protocols/novell/novell.c @ 8713:7024b595b6ae

[gaim-migrate @ 9467] " Expansion from my original protocol prefs to plugin prefs. Things are handled a bit different in this iteration of it, but I have already modified msn and jabber to use it, and included an example plugin to show how to use it. It will also generate pages with doxygen. The example plugin doesn't not contain any translatable strings seeing as we're in the string freeze. And it's an example, whats the point of translating it..? Also, I tweaked the documentation for 2 functions in gtkprefs, gaim_gtk_prefs_dropdown and gaim_gtk_prefs_dropdown_from_list. Nothing major in that, just made it say that the list should be a list of pairs label/value. Also there's 5 new files that will need to be added to cvs: src/pluginpref.h src/pluginpref.c src/gtkpluginpref.h src/gtkpluginpref.c plugins/pluginpref_example.c the tarball already has them structured correctly and contains the diff" --Gary Kramlich - amc_grim and the german translator pointed out that sean missed the novell file for POTFILES.in committer: Tailor Script <tailor@pidgin.im>
author Luke Schierer <lschiere@pidgin.im>
date Mon, 19 Apr 2004 21:12:45 +0000
parents 046dd8ef2920
children ee04a6089cbf
line wrap: on
line source

/*
 * novell.c
 *
 * Copyright © 2004 Unpublished Work of Novell, Inc. All Rights Reserved.
 *
 * THIS WORK IS AN UNPUBLISHED WORK OF NOVELL, INC. NO PART OF THIS WORK MAY BE
 * USED, PRACTICED, PERFORMED, COPIED, DISTRIBUTED, REVISED, MODIFIED,
 * TRANSLATED, ABRIDGED, CONDENSED, EXPANDED, COLLECTED, COMPILED, LINKED,
 * RECAST, TRANSFORMED OR ADAPTED WITHOUT THE PRIOR WRITTEN CONSENT OF NOVELL,
 * INC. ANY USE OR EXPLOITATION OF THIS WORK WITHOUT AUTHORIZATION COULD SUBJECT
 * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
 *
 * AS BETWEEN [GAIM] AND NOVELL, NOVELL GRANTS [GAIM] THE RIGHT TO REPUBLISH
 * THIS WORK UNDER THE GPL (GNU GENERAL PUBLIC LICENSE) WITH ALL RIGHTS AND
 * LICENSES THEREUNDER.  IF YOU HAVE RECEIVED THIS WORK DIRECTLY OR INDIRECTLY
 * FROM [GAIM] AS PART OF SUCH A REPUBLICATION, YOU HAVE ALL RIGHTS AND LICENSES
 * GRANTED BY [GAIM] UNDER THE GPL.  IN CONNECTION WITH SUCH A REPUBLICATION, IF
 * ANYTHING IN THIS NOTICE CONFLICTS WITH THE TERMS OF THE GPL, SUCH TERMS
 * PREVAIL.
 *
 */

#include "internal.h"
#include "accountopt.h"
#include "debug.h"
#include "prpl.h"
#include "server.h"
#include "nmuser.h"
#include "notify.h"
#include "util.h"
#include "sslconn.h"
#include "request.h"
#include "network.h"

#define DEFAULT_PORT			8300
#define NOVELL_CONNECT_STEPS	4

static GaimPlugin *my_protocol = NULL;

static gboolean
_is_disconnect_error(NMERR_T err);

static gboolean
_check_for_disconnect(NMUser * user, NMERR_T err);

static void
_send_message(NMUser * user, NMMessage * message);

static void
_update_buddy_status(GaimBuddy * buddy, int status, int gmt);

static void
_remove_gaim_buddies(NMUser * user);

static void
_add_contacts_to_gaim_blist(NMUser * user, NMFolder * folder);

static void
_add_gaim_buddies(NMUser * user);

static void
_show_info(GaimConnection * gc, NMUserRecord * user_record);

/*******************************************************************************
 * Response callbacks
 *******************************************************************************/

/* Handle login response */
static void
_login_resp_cb(NMUser * user, NMERR_T ret_code,
			   gpointer resp_data, gpointer user_data)
{
	GaimConnection *gc;
	const char *alias;
	NMERR_T rc;

	if (user == NULL)
		return;

	gc = gaim_account_get_connection(user->client_data);
	if (gc == NULL)
		return;

	if (ret_code == NM_OK) {

		/* Set alias for user if not set (use Full Name) */
		alias = gaim_account_get_alias(user->client_data);
		if (alias == NULL || *alias == '\0') {
			alias = nm_user_record_get_full_name(user->user_record);

			if (alias)
				gaim_account_set_alias(user->client_data, alias);
		}

		/* Tell Gaim that we are connected */
		gaim_connection_set_state(gc, GAIM_CONNECTED);
		serv_finish_login(gc);

		/* Sync the contact list. This is pretty simplistic right now,
		 * we just remove all of the GaimBuddy from the client side list
		 * for this account and then add in all of the contacts from the
		 * server side list.
		 */
		_remove_gaim_buddies(user);
		_add_gaim_buddies(user);

		rc = nm_send_set_status(user, NM_STATUS_AVAILABLE, NULL, NULL, NULL,
								NULL);
		_check_for_disconnect(user, rc);

	} else {

		char *err = g_strdup_printf(_("Login failed (0x%X)."), ret_code);

		gaim_connection_error(gc, err);
		g_free(err);

	}
}

/* Handle getstatus response*/
static void
_get_status_resp_cb(NMUser * user, NMERR_T ret_code,
					gpointer resp_data, gpointer user_data)
{
	GaimBuddy *buddy;
	GSList *buddies;
	GSList *bnode;
	NMUserRecord *user_record = (NMUserRecord *) resp_data;
	int status;

	if (user == NULL || user_record == NULL)
		return;

	if (ret_code == NM_OK) {

		/* Find all Gaim buddies and update their statuses */
		const char *name = nm_user_record_get_display_id(user_record);

		if (name) {
			buddies = gaim_find_buddies((GaimAccount *) user->client_data, name);
			for (bnode = buddies; bnode; bnode = bnode->next) {
				buddy = (GaimBuddy *) bnode->data;
				if (buddy) {
					status = nm_user_record_get_status(user_record);
					_update_buddy_status(buddy, status, time(0));
				}
			}
		}

	} else {

		gaim_debug(GAIM_DEBUG_INFO, "novell",
				   "_get_status_resp_cb(): rc = 0x%X\n", ret_code);

	}
}

/* Show an error if the rename failed */
static void
_rename_contact_resp_cb(NMUser * user, NMERR_T ret_code,
						gpointer resp_data, gpointer user_data)
{
	if (ret_code != NM_OK) {
		gaim_debug(GAIM_DEBUG_INFO, "novell",
				   "_rename_contact_resp_cb(): rc = 0x%X\n", ret_code);
	}
}

/* Handle the getdetails response and send the message */
static void
_get_details_resp_send_msg(NMUser * user, NMERR_T ret_code,
						   gpointer resp_data, gpointer user_data)
{
	GaimConversation *gconv;
	GaimConnection *gc;
	NMUserRecord *user_record = NULL;
	NMContact *cntct = NULL;
	NMConference *conf;
	NMMessage *msg = user_data;
	const char *dn = NULL;
	const char *name;

	if (user == NULL || msg == NULL)
		return;

	if (ret_code == NM_OK) {
		user_record = (NMUserRecord *) resp_data;
		if (user_record) {

			/* Set the title for the conversation */
			gconv =	gaim_find_conversation_with_account(nm_user_record_get_display_id(user_record),
														(GaimAccount *) user->client_data);
			if (gconv) {

				dn = nm_user_record_get_dn(user_record);
				if (dn) {
					cntct = nm_find_contact(user, dn);
				}

				if (cntct) {
					gaim_conversation_set_title(gconv,
												nm_contact_get_display_name(cntct));
				} else {

					/* Not in the contact list, try to user full name */
					name = (char *) nm_user_record_get_full_name(user_record);
					if (name)
						gaim_conversation_set_title(gconv, name);
				}
			}

			/* Add the user record to particpant list */
			conf = nm_message_get_conference(msg);
			if (conf) {
				nm_conference_add_participant(conf, user_record);
				_send_message(user, msg);
			}
		}

	} else {

		gc = gaim_account_get_connection(user->client_data);
		if (gc != NULL) {
			char *err = g_strdup_printf(_("Unable to send message."
										  " Could not get details for user (0x%X)."),
										ret_code);

			gaim_notify_error(gc, NULL, err, NULL);
			g_free(err);
		}

		if (msg)
			nm_release_message(msg);
	}
}

/* Set up the new GaimBuddy based on the response from getdetails */
static void
_get_details_resp_setup_buddy(NMUser * user, NMERR_T ret_code,
							  gpointer resp_data, gpointer user_data)
{
	NMUserRecord *user_record;
	NMContact *contact;
	GaimBuddy *buddy;
	const char *alias;
	NMERR_T rc = NM_OK;

	if (user == NULL || resp_data == NULL || user_data == NULL)
		return;

	contact = user_data;

	if (ret_code == NM_OK) {
		user_record = resp_data;

		buddy = nm_contact_get_data(contact);

		nm_contact_set_user_record(contact, user_record);

		/* Set the display id */
		gaim_blist_rename_buddy(buddy,
								nm_user_record_get_display_id(user_record));

		alias = gaim_get_buddy_alias(buddy);
		if (alias == NULL || (strcmp(alias, buddy->name) == 0)) {
			gaim_blist_alias_buddy(buddy,
								   nm_user_record_get_full_name(user_record));

			/* Tell the server about the new display name */
			rc = nm_send_rename_contact(user, contact,
										nm_user_record_get_full_name(user_record),
										NULL, NULL);
			_check_for_disconnect(user, rc);

		}


		/* Get initial status for the buddy */
		rc = nm_send_get_status(user, resp_data, _get_status_resp_cb, NULL);
		_check_for_disconnect(user, rc);

/*		nm_release_contact(contact);*/

	}

	if (contact)
		nm_release_contact(contact);
}

/* Add the new contact into the GaimBuddy list */
static void
_create_contact_resp_cb(NMUser * user, NMERR_T ret_code,
						gpointer resp_data, gpointer user_data)
{
	NMContact *tmp_contact = (NMContact *) user_data;
	NMContact *new_contact = NULL;
	NMFolder *folder = NULL;
	GaimGroup *group;
	GaimBuddy *buddy;
	const char *folder_name = NULL;
	NMERR_T rc = NM_OK;

	if (user == NULL)
		return;

	if (ret_code == NM_OK) {

		new_contact = (NMContact *) resp_data;
		if (new_contact == NULL || tmp_contact == NULL)
			return;

		/* Get the userid and folder name for the new contact */
		folder = nm_find_folder_by_id(user,
									  nm_contact_get_parent_id(new_contact));
		if (folder) {
			folder_name = nm_folder_get_name(folder);
		}

		/* Re-add the buddy now that we got the okay from the server */
		if (folder_name && (group = gaim_find_group(folder_name))) {

			const char *alias = nm_contact_get_display_name(tmp_contact);
			const char *display_id = nm_contact_get_display_id(new_contact);

			if (display_id == NULL)
				display_id = nm_contact_get_dn(new_contact);

			if (alias && strcmp(alias, display_id)) {

				/* The user requested an alias, tell the server about it. */
				rc = nm_send_rename_contact(user, new_contact, alias,
											_rename_contact_resp_cb, NULL);
				_check_for_disconnect(user, rc);

			} else {

				alias = "";

			}

			/* Add it to the gaim buddy list if it is not there */
			buddy = gaim_find_buddy_in_group(user->client_data, display_id, group);
			if (buddy == NULL) {
				buddy = gaim_buddy_new(user->client_data, display_id, alias);
				gaim_blist_add_buddy(buddy, NULL, group, NULL);
			}

			/* Save the new buddy as part of the contact object */
			nm_contact_set_data(new_contact, (gpointer) buddy);

			/* We need details for the user before we can setup the
			 * new Gaim buddy. We always call this because the
			 * 'createcontact' response fields do not always contain
			 * everything that we need.
			 */
			nm_contact_add_ref(new_contact);

			rc = nm_send_get_details(user, nm_contact_get_dn(new_contact),
									 _get_details_resp_setup_buddy, new_contact);
			_check_for_disconnect(user, rc);

		}

	} else {
		GaimConnection *gc = gaim_account_get_connection(user->client_data);
		const char *name = nm_contact_get_dn(tmp_contact);
		char *err;

		err =
			g_strdup_printf(_("Unable to add %s to your buddy list (0x%X)."),
							name, ret_code);
		gaim_notify_error(gc, NULL, err, NULL);
		g_free(err);

	}

	if (tmp_contact)
		nm_release_contact(tmp_contact);
}

/* Show an error if we failed to send the message */
static void
_send_message_resp_cb(NMUser * user, NMERR_T ret_code,
					  gpointer resp_data, gpointer user_data)
{
	GaimConnection *gc;
	char *err = NULL;

	if (user == NULL)
		return;

	if (ret_code != NM_OK) {
		gc = gaim_account_get_connection(user->client_data);

		/* TODO: Improve this! message to who or for what conference? */
		err = g_strdup_printf(_("Unable to send message (0x%X)."),
							  ret_code);
		gaim_notify_error(gc, NULL, err, NULL);
		g_free(err);
	}
}

/* Show an error if the remove failed */
static void
_remove_contact_resp_cb(NMUser * user, NMERR_T ret_code,
						gpointer resp_data, gpointer user_data)
{
	if (ret_code != NM_OK) {
		/* TODO: Display an error? */

		gaim_debug(GAIM_DEBUG_INFO, "novell",
				   "_remove_contact_resp_cb(): rc = 0x%x\n", ret_code);
	}
}

/* Show an error if the remove failed */
static void
_remove_folder_resp_cb(NMUser * user, NMERR_T ret_code,
					   gpointer resp_data, gpointer user_data)
{
	if (ret_code != NM_OK) {
		/* TODO: Display an error? */

		gaim_debug(GAIM_DEBUG_INFO, "novell",
				   "_remove_folder_resp_cb(): rc = 0x%x\n", ret_code);
	}
}

/* Show an error if the move failed */
static void
_move_contact_resp_cb(NMUser * user, NMERR_T ret_code,
					  gpointer resp_data, gpointer user_data)
{
	if (ret_code != NM_OK) {
		/* TODO: Display an error? */

		gaim_debug(GAIM_DEBUG_INFO, "novell",
				   "_move_contact_resp_cb(): rc = 0x%x\n", ret_code);
	}
}

/* Show an error if the rename failed */
static void
_rename_folder_resp_cb(NMUser * user, NMERR_T ret_code,
					   gpointer resp_data, gpointer user_data)
{
	if (ret_code != NM_OK) {
		/* TODO: Display an error? */

		gaim_debug(GAIM_DEBUG_INFO, "novell",
				   "_rename_folder_resp_cb(): rc = 0x%x\n", ret_code);
	}
}

/* If the createconf was successful attempt to send the message,
 * otherwise display an error message to the user.
 */
static void
_createconf_resp_send_msg(NMUser * user, NMERR_T ret_code,
						  gpointer resp_data, gpointer user_data)
{
	NMConference *conf;
	NMMessage *msg = user_data;

	if (user == NULL || msg == NULL)
		return;

	if (ret_code == NM_OK) {
		_send_message(user, msg);
	} else {

		if ((conf = nm_message_get_conference(msg))) {

			GaimConnection *gc = gaim_account_get_connection(user->client_data);
			const char *name = NULL;
			char *err;
			NMUserRecord *ur;

			ur = nm_conference_get_participant(conf, 0);
			if (ur)
				name = nm_user_record_get_userid(ur);

			if (name)
				err = g_strdup_printf(_("Unable to send message to %s."
										" Could not create the conference (0x%X)."),
									  name, ret_code);
			else
				err = g_strdup_printf(_("Unable to send message."
										" Could not create the conference (0x%X)."),
									  ret_code);

			gaim_notify_error(gc, NULL, err, NULL);
			g_free(err);
		}

		if (msg)
			nm_release_message(msg);
	}
}

/* Move contact to newly created folder */
static void
_create_folder_resp_move_contact(NMUser * user, NMERR_T ret_code,
								 gpointer resp_data, gpointer user_data)
{
	NMContact *contact = user_data;
	NMFolder *new_folder;
	char *folder_name = resp_data;
	NMERR_T rc = NM_OK;

	if (user == NULL || folder_name == NULL || contact == NULL) {

		if (folder_name)
			g_free(folder_name);

		return;
	}

	if (ret_code == NM_OK || ret_code == 0xD126) {
		new_folder = nm_find_folder(user, folder_name);
		if (new_folder) {

			/* Tell the server to move the contact to the new folder */
/*			rc = nm_send_move_contact(user, contact, new_folder,
			_move_contact_resp_cb, NULL); */

			rc = nm_send_create_contact(user, new_folder, contact,
										NULL, NULL);

			_check_for_disconnect(user, rc);

		}
	} else {
		GaimConnection *gc = gaim_account_get_connection(user->client_data);
		char *err = g_strdup_printf(_("Unable to move user %s"
									  " to folder %s in the server side list."
									  " Error while creating folder (0x%X)."),
									nm_contact_get_dn(contact),
									folder_name,
									ret_code);

		gaim_notify_error(gc, NULL, err, NULL);
		g_free(err);
	}

	if (folder_name)
		g_free(folder_name);
}

/* Add contact to newly create folder */
static void
_create_folder_resp_add_contact(NMUser * user, NMERR_T ret_code,
								gpointer resp_data, gpointer user_data)
{
	NMContact *contact = (NMContact *) user_data;
	NMFolder *folder;
	char *folder_name = (char *) resp_data;
	NMERR_T rc = NM_OK;

	if (user == NULL || folder_name == NULL || contact == NULL) {

		if (contact)
			nm_release_contact(contact);

		if (folder_name)
			g_free(folder_name);

		return;
	}

	if (ret_code == NM_OK || ret_code == 0xD126) {
		folder = nm_find_folder(user, folder_name);
		if (folder) {

			rc = nm_send_create_contact(user, folder, contact,
										_create_contact_resp_cb, contact);
			_check_for_disconnect(user, rc);
		}
	} else {
		GaimConnection *gc = gaim_account_get_connection(user->client_data);
		const char *name = nm_contact_get_dn(contact);
		char *err =
			g_strdup_printf(_("Unable to add %s to your buddy list."
							 " Error creating folder in server side list (0x%X)."),
							name, ret_code);

		gaim_notify_error(gc, NULL, err, NULL);

		nm_release_contact(contact);
		g_free(err);
	}

	g_free(folder_name);
}

static void
_join_conf_resp_cb(NMUser * user, NMERR_T ret_code,
				   gpointer resp_data, gpointer user_data)
{
	GaimConversation *chat;
	GaimConnection *gc;
	NMUserRecord *ur;
	NMConference *conference = user_data;
	const char *name;
	char *conf_name;
	int i, count;

	if (user == NULL || conference == NULL)
		return;

	gc = gaim_account_get_connection(user->client_data);

	if (ret_code == NM_OK) {
		conf_name = g_strdup_printf(_("GroupWise Conference %d"),
									++user->conference_count);
		chat = serv_got_joined_chat(gc, user->conference_count, conf_name);
		if (chat) {

			nm_conference_set_data(conference, (gpointer) chat);

			count = nm_conference_get_participant_count(conference);
			for (i = 0; i < count; i++) {
				ur = nm_conference_get_participant(conference, i);
				if (ur) {
					name = nm_user_record_get_display_id(ur);
					gaim_conv_chat_add_user(GAIM_CONV_CHAT(chat), name, NULL);
				}
			}
		}
		g_free(conf_name);
	}
}

/* Show info returned by getdetails */
static void
_get_details_resp_show_info(NMUser * user, NMERR_T ret_code,
							gpointer resp_data, gpointer user_data)
{
	GaimConnection *gc;
	NMUserRecord *user_record;
	char *name;
	char *err;

	if (user == NULL)
		return;

	name = user_data;

	if (ret_code == NM_OK) {
		user_record = (NMUserRecord *) resp_data;
		if (user_record) {
			_show_info(gaim_account_get_connection(user->client_data),
					   user_record);
		}
	} else {
		gc = gaim_account_get_connection(user->client_data);
		err =
			g_strdup_printf(_("Could not get details for user %s (0x%X)."), name,
							ret_code);
		gaim_notify_error(gc, NULL, err, NULL);
		g_free(err);
	}

	if (name)
		g_free(name);
}

/*******************************************************************************
 * Helper functions
 ******************************************************************************/

static char *
_user_agent_string()
{

#if !defined(_WIN32)

	const char *sysname = "";
	const char *release = "";
	const char *template = "Gaim/%s (%s; %s)";
	struct utsname u;

	if (uname(&u) == 0) {
		sysname = u.sysname;
		release = u.release;
	} else {
		sysname = "Linux";
		release = "Unknown";
	}

	return g_strdup_printf(template, VERSION, sysname, release);

#else

	const char *sysname = "";
	const char *template = "Gaim/%s (%s; %d.%d)";
	OSVERSIONINFO os_info;
	SYSTEM_INFO sys_info;

	os_info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	GetVersionEx(&os_info);
	GetSystemInfo(&sys_info);

	if (os_info.dwPlatformId == VER_PLATFORM_WIN32_NT)  {
		switch (os_info.dwMajorVersion) {
			case 3:
			case 4:
				sysname = "Windows NT";
				break;
			case 5:
				switch (os_info.dwMinorVersion) {
					case 0:
						sysname = "Windows 2000";
						break;
					case 1:
						sysname = "Windows XP";
						break;
					case 2:
						sysname = "Windows Server 2003";
						break;
					default:
						sysname = "Windows";
						break;
				}
				break;
			default:
				sysname = "Windows";
				break;
		}

	}	else if (os_info.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) {
		switch (os_info.dwMinorVersion) {
			case 0:
				sysname = "Windows 95";
				break;
			case 10:
				sysname = "Windows 98";
				break;
			case 90:
				sysname = "Windows ME";
				break;
			default:
				sysname = "Windows";
				break;
		}
	} else {
		sysname = "Windows";
	}

	return g_strdup_printf(template, VERSION, sysname,
						   os_info.dwMajorVersion, os_info.dwMinorVersion);

#endif


}

static gboolean
_is_disconnect_error(NMERR_T err)
{
	return (err == NMERR_TCP_WRITE ||
			err == NMERR_TCP_READ || err == NMERR_PROTOCOL);
}

static gboolean
_check_for_disconnect(NMUser * user, NMERR_T err)
{
	GaimConnection *gc = gaim_account_get_connection(user->client_data);

	if (_is_disconnect_error(err)) {

		gaim_connection_error(gc, _("Error communicating with server."
									" Closing connection."));
		return TRUE;

	}

	return FALSE;
}

/* Check to see if the conference is instantiated, if so send the message.
 * If not send the create conference -- the response handler for the createconf
 * will call this function again.
 */
static void
_send_message(NMUser * user, NMMessage * message)
{
	NMConference *conf;
	NMERR_T rc = NM_OK;

	conf = nm_message_get_conference(message);
	if (conf) {
		/* We have a conference make sure that the
		   server knows about it already. */
		if (nm_conference_is_instantiated(conf)) {

			/* We have everything that we need...finally! */
			rc = nm_send_message(user, message, _send_message_resp_cb);
			_check_for_disconnect(user, rc);

			nm_release_message(message);

		} else {
			rc = nm_send_create_conference(user, conf,
										   _createconf_resp_send_msg, message);
			_check_for_disconnect(user, rc);
		}
	}
}

/* Update the status of the given buddy in the Gaim buddy list */
static void
_update_buddy_status(GaimBuddy * buddy, int status, int gmt)
{
	GaimConnection *gc = gaim_account_get_connection(buddy->account);
	int gstatus = status << 1;
	int idle = 0;
	int loggedin = 1;

	switch (status) {
		case NM_STATUS_AVAILABLE:
			/*nothing to do */
			break;
		case NM_STATUS_AWAY:
		case NM_STATUS_BUSY:
			gstatus |= UC_UNAVAILABLE;
			break;
		case NM_STATUS_OFFLINE:
			loggedin = 0;
			gstatus |= UC_UNAVAILABLE;
			break;
		case NM_STATUS_AWAY_IDLE:
			idle = gmt;
			gstatus |= UC_UNAVAILABLE;
			break;
		default:
			gstatus |= UC_UNAVAILABLE;
			loggedin = 0;
			break;
	}

	serv_got_update(gc, buddy->name, loggedin, 0, 0, idle, gstatus);
}

/* Iterate through the cached Gaim buddy list and remove all buddies
 * for this account.
 */
static void
_remove_gaim_buddies(NMUser * user)
{
	GaimBlistNode *gnode;
	GaimBlistNode *cnode;
	GaimBlistNode *bnode;
	GaimGroup *group;
	GaimBuddy *buddy;
	GaimBuddyList *blist;
	GSList *rem_list = NULL;
	GSList *l;

	if ((blist = gaim_get_blist())) {
		for (gnode = blist->root; gnode; gnode = gnode->next) {
			if (!GAIM_BLIST_NODE_IS_GROUP(gnode))
				continue;
			group = (GaimGroup *) gnode;
			for (cnode = gnode->child; cnode; cnode = cnode->next) {
				if (!GAIM_BLIST_NODE_IS_CONTACT(cnode))
					continue;
				for (bnode = cnode->child; bnode; bnode = bnode->next) {
					if (!GAIM_BLIST_NODE_IS_BUDDY(bnode))
						continue;
					buddy = (GaimBuddy *) bnode;
					if (buddy->account == user->client_data) {
						rem_list = g_slist_append(rem_list, buddy);
					}
				}
			}
		}

		if (rem_list) {
			for (l = rem_list; l; l = l->next) {
				gaim_blist_remove_buddy(l->data);
			}
			g_slist_free(rem_list);
		}
	}
}

/* Add all of the contacts in the given folder to the Gaim buddy list */
static void
_add_contacts_to_gaim_blist(NMUser * user, NMFolder * folder)
{
	NMUserRecord *user_record = NULL;
	NMContact *contact = NULL;
	GaimBuddy *buddy = NULL;
	NMERR_T cnt = 0, i;
	const char *text = NULL;
	const char *name = NULL;
	int status = 0;

	/* Get each contact for this folder */
	cnt = nm_folder_get_contact_count(folder);
	for (i = 0; i < cnt; i++) {
		contact = nm_folder_get_contact(folder, i);
		if (contact) {

			name = nm_contact_get_display_id(contact);
			if (name) {
				/* Add it to the gaim buddy list */
				buddy = gaim_buddy_new(user->client_data,
									   name,
									   nm_contact_get_display_name(contact));

				/* Does the Gaim group exist already? */
				GaimGroup *group = gaim_find_group(nm_folder_get_name(folder));

				if (group == NULL) {
					group = gaim_group_new(nm_folder_get_name(folder));
					gaim_blist_add_group(group, NULL);
				}

				/* Set the initial status for the buddy */
				user_record = nm_contact_get_user_record(contact);
				if (user_record) {
					status = nm_user_record_get_status(user_record);
					text = nm_user_record_get_status_text(user_record);
				}

				gaim_blist_add_buddy(buddy, NULL, group, NULL);
				_update_buddy_status(buddy, status, time(0));

				/* Save the new buddy as part of the contact object */
				nm_contact_set_data(contact, (gpointer) buddy);
			}

		} else {
			/* NULL contact. This should not happen, but
			 * let's break out of the loop.
			 */
			break;
		}
	}

}

/* Add all of the server side contacts to the Gaim buddy list. */
static void
_add_gaim_buddies(NMUser * user)
{
	NMERR_T cnt = 0, i;
	NMFolder *root_folder = NULL;
	NMFolder *folder = NULL;

	root_folder = nm_get_root_folder(user);
	if (root_folder) {

		/* Add contacts for the sub folders */
		cnt = nm_folder_get_subfolder_count(root_folder);
		for (i = 0; i < cnt; i++) {
			folder = nm_folder_get_subfolder(root_folder, i);
			if (folder) {
				_add_contacts_to_gaim_blist(user, folder);
			}
		}

		/* Add contacts for the root folder */
		_add_contacts_to_gaim_blist(user, root_folder);
	}
}

/* Display a dialog box showing the properties for the given user record */
static void
_show_info(GaimConnection * gc, NMUserRecord * user_record)
{
	GString *info_text;
	int count, i;
	NMProperty *property;
	const char *tag, *value;

	info_text = g_string_new("");

	tag = _("Userid");
	value = nm_user_record_get_userid(user_record);
	if (value) {
		g_string_append_printf(info_text, "<b>%s:</b> %s<br/>\n", tag, value);
	}

/*	tag = _("DN");
	value = nm_user_record_get_dn(user_record);
	if (value) {
	g_string_append_printf(info_text, "<b>%s:</b> %s<br/>\n",
	tag, value);
	}
*/

	tag = _("Full name");
	value = nm_user_record_get_full_name(user_record);
	if (value) {
		g_string_append_printf(info_text, "<b>%s:</b> %s<br/>\n", tag, value);
	}

	count = nm_user_record_get_property_count(user_record);
	for (i = 0; i < count; i++) {
		property = nm_user_record_get_property(user_record, i);
		if (property) {
			tag = nm_property_get_tag(property);
			value = nm_property_get_value(property);
			if (tag && value) {
				g_string_append_printf(info_text, "<b>%s:</b> %s<br/>\n",
									   tag, value);
			}
			nm_release_property(property);
		}
	}

	gaim_notify_formatted(NULL, "Title", _("User Properties"),
						  NULL, info_text->str, NULL, NULL);

	g_string_free(info_text, TRUE);
}

/* Send a join conference, the first item in the parms list is the
 * NMUser object and the second item is the conference to join.
 * This callback is passed to gaim_request_action when we ask the
 * user if they want to join the conference.
 */
static void
_join_conference_cb(GSList * parms)
{
	NMUser *user;
	NMConference *conference;
	NMERR_T rc = NM_OK;

	if (parms == NULL || g_slist_length(parms) != 2)
		return;

	user = g_slist_nth_data(parms, 0);
	conference = g_slist_nth_data(parms, 1);

	if (user && conference) {
		rc = nm_send_join_conference(user, conference,
									 _join_conf_resp_cb, conference);
		_check_for_disconnect(user, rc);
	}

	g_slist_free(parms);
}

/* Send a reject conference, the first item in the parms list is the
 * NMUser object and the second item is the conference to reject.
 * This callback is passed to gaim_request_action when we ask the
 * user if they want to joing the conference.
 */
static void
_reject_conference_cb(GSList * parms)
{
	NMUser *user;
	NMConference *conference;
	NMERR_T rc = NM_OK;

	if (parms == NULL || g_slist_length(parms) != 2)
		return;

	user = g_slist_nth_data(parms, 0);
	conference = g_slist_nth_data(parms, 1);

	if (user && conference) {
		rc = nm_send_reject_conference(user, conference, NULL, NULL);
		_check_for_disconnect(user, rc);
	}

	g_slist_free(parms);
}

/*******************************************************************************
 * Connect and recv callbacks
 ******************************************************************************/

static void
novell_ssl_connect_error(GaimSslConnection * gsc,
						 GaimSslErrorType error, gpointer data)
{
	gaim_connection_error((GaimConnection *)data,
						  _("Unable to make SSL connection to server."));
}

static void
novell_ssl_recv_cb(gpointer data, GaimSslConnection * gsc,
				   GaimInputCondition condition)
{
	GaimConnection *gc = data;
	NMUser *user;
	NMERR_T rc;

	if (gc == NULL)
		return;

	user = gc->proto_data;
	if (user == NULL)
		return;

	rc = nm_process_new_data(user);
	if (rc != NM_OK) {

		if (_is_disconnect_error(rc)) {
			gaim_connection_error(gc,
								  _("Error communicating with server."
									" Closing connection."));
		} else {

			char *error;

			error = g_strdup_printf(_("Error processing event or response."
									  " (0x%X)"), rc);
			gaim_notify_error(gc, NULL, error, NULL);
			g_free(error);

		}

	}
}

static void
novell_ssl_connected_cb(gpointer data, GaimSslConnection * gsc,
						GaimInputCondition cond)
{
	GaimConnection *gc = data;
	NMUser *user;
	NMConn *conn;
	NMERR_T rc = 0;
	const char *pwd = NULL;
	const char *my_addr = NULL;
	char *ua = NULL;

	if (gc == NULL || gsc == NULL)
		return;

	user = gc->proto_data;
	if ((user == NULL) || (conn = user->conn) == NULL)
		return;

	conn->ssl_conn = g_new0(NMSSLConn, 1);
	conn->ssl_conn->data = gsc;
	conn->ssl_conn->read = (nm_ssl_read_cb) gaim_ssl_read;
	conn->ssl_conn->write = (nm_ssl_write_cb) gaim_ssl_write;

	gaim_connection_update_progress(gc, _("Authenticating..."),
									2, NOVELL_CONNECT_STEPS);

	my_addr = gaim_network_get_ip_for_account(user->client_data, gsc->fd);
	pwd = gaim_account_get_password(user->client_data);
	ua = _user_agent_string();

	rc = nm_send_login(user, pwd, my_addr, ua, _login_resp_cb, NULL);
	if (rc == NM_OK) {
		conn->connected = TRUE;
		gaim_ssl_input_add(gsc, novell_ssl_recv_cb, gc);
	} else {
		gaim_connection_error(gc, _("Unable to connect to server."));
	}

	gaim_connection_update_progress(gc, _("Waiting for response..."),
									3, NOVELL_CONNECT_STEPS);

	g_free(ua);
}

/*******************************************************************************
 * Event callback and event handlers
 ******************************************************************************/

static void
_evt_receive_message(NMUser * user, NMEvent * event)
{
	NMUserRecord *user_record = NULL;
	NMContact *contact = NULL;
	GaimConversation *gconv;
	NMConference *conference;
	GaimConvImFlags imflags;

	conference = nm_event_get_conference(event);
	if (conference) {

		GaimConversation *chat = nm_conference_get_data(conference);

		/* Is this a single person 'conversation' or a conference? */
		if (chat == NULL && nm_conference_get_participant_count(conference) == 1) {

			user_record = nm_find_user_record(user, nm_event_get_source(event));
			if (user_record) {

				imflags = 0;
				if (nm_event_get_type(event) == NMEVT_RECEIVE_AUTOREPLY)
					imflags |= GAIM_CONV_IM_AUTO_RESP;

				serv_got_im(gaim_account_get_connection(user->client_data),
							nm_user_record_get_display_id(user_record),
							nm_event_get_text(event), imflags,
							nm_event_get_gmt(event));

				gconv =	gaim_find_conversation_with_account(
					nm_user_record_get_display_id(user_record),
					(GaimAccount *) user->client_data);
				if (gconv) {

					contact = nm_find_contact(user, nm_event_get_source(event));
					if (contact) {

						gaim_conversation_set_title(
							gconv,
							nm_contact_get_display_name(contact));


					} else {

						const char *name =
							nm_user_record_get_full_name(user_record);

						if (name == NULL)
							name = nm_user_record_get_userid(user_record);

						gaim_conversation_set_title(gconv, name);
					}

				}

			} else {
				/* this should not happen, see the event code.
				 * the event code will get the contact details from
				 * the server if it does not have them before calling
				 * the event callback.
				 */
			}

		} else if (chat) {

			/* get the contact for send if we have one */
			NMContact *contact = nm_find_contact(user,
												 nm_event_get_source(event));

			/* get the user record for the sender */
			user_record = nm_find_user_record(user, nm_event_get_source(event));
			if (user_record) {
				const char *name = nm_contact_get_display_name(contact);

				if (name == NULL) {
					name = nm_user_record_get_full_name(user_record);
					if (name == NULL)
						name = nm_user_record_get_display_id(user_record);
				}

				serv_got_chat_in(gaim_account_get_connection(user->client_data),
								 gaim_conv_chat_get_id(GAIM_CONV_CHAT(chat)),
								 name,
								 0, nm_event_get_text(event),
								 nm_event_get_gmt(event));
			}
		}
	}
}

static void
_evt_conference_left(NMUser * user, NMEvent * event)
{
	GaimConversation *chat;
	NMConference *conference;

	conference = nm_event_get_conference(event);
	if (conference) {
		chat = nm_conference_get_data(conference);
		if (chat) {
			NMUserRecord *ur = nm_find_user_record(user,
												   nm_event_get_source(event));

			if (ur)
				gaim_conv_chat_remove_user(GAIM_CONV_CHAT(chat),
										   nm_user_record_get_display_id(ur),
										   NULL);
		}
	}
}

static void
_evt_conference_invite(NMUser * user, NMEvent * event)
{
	NMUserRecord *ur;
	GSList *parms = NULL;
	const char *title = NULL;
	const char *secondary = NULL;
	const char *name = NULL;
	char *primary = NULL;
	time_t gmt;

	ur = nm_find_user_record(user, nm_event_get_source(event));
	if (ur)
		name = nm_user_record_get_full_name(ur);

	if (name == NULL)
		name = nm_event_get_source(event);

	gmt = nm_event_get_gmt(event);
	title = _("Invitation to Conversation");
	primary = g_strdup_printf(_("Invitation from: %s\n\nSent: %s"),
							  name, asctime(localtime(&gmt)));
	secondary = _("Would you like to join the conversation?");

	/* Set up parms list for the callbacks
	 * We need to send the NMUser object and
	 * the NMConference object to the callbacks
	 */
	parms = NULL;
	parms = g_slist_append(parms, user);
	parms = g_slist_append(parms, nm_event_get_conference(event));

	/* Prompt the user */
	gaim_request_action(NULL, title, primary, secondary, -1, parms, 2,
						_("Yes"), G_CALLBACK(_join_conference_cb),
						_("No"), G_CALLBACK(_reject_conference_cb));

	g_free(primary);
}


static void
_evt_conference_joined(NMUser * user, NMEvent * event)
{
	GaimConversation *chat = NULL;
	GaimConnection *gc;
	NMConference *conference = NULL;
	NMUserRecord *ur = NULL;
	const char *name;
	char *conf_name;

	gc = gaim_account_get_connection(user->client_data);
	if (gc == NULL)
		return;

	conference = nm_event_get_conference(event);
	if (conference) {
		chat = nm_conference_get_data(conference);
		if (nm_conference_get_participant_count(conference) == 2 && chat == NULL) {
			ur = nm_conference_get_participant(conference, 0);
			if (ur) {
				conf_name = g_strdup_printf(_("GroupWise Conference %d"),
											++user->conference_count);
				chat =
					serv_got_joined_chat(gc, user->conference_count, conf_name);
				g_free(conf_name);
				if (chat) {

					nm_conference_set_data(conference, (gpointer) chat);

					name = nm_user_record_get_display_id(ur);
					gaim_conv_chat_add_user(GAIM_CONV_CHAT(chat), name, NULL);

				}
			}
		}

		if (chat != NULL) {
			ur = nm_find_user_record(user, nm_event_get_source(event));
			if (ur) {
				name = nm_user_record_get_display_id(ur);
				gaim_conv_chat_add_user(GAIM_CONV_CHAT(chat), name, NULL);
			}
		}
	}
}

static void
_evt_status_change(NMUser * user, NMEvent * event)
{
	GaimBuddy *buddy = NULL;
	GSList *buddies;
	GSList *bnode;
	NMUserRecord *user_record;
	const char *display_id;
	int status;

	user_record = nm_event_get_user_record(event);
	if (user_record) {

		/* Retrieve new status */
		status = nm_user_record_get_status(user_record);

		/* Update status for buddy in all folders */
		display_id = nm_user_record_get_display_id(user_record);
		buddies = gaim_find_buddies(user->client_data, display_id);
		for (bnode = buddies; bnode; bnode = bnode->next) {
			buddy = (GaimBuddy *) bnode->data;
			if (buddy) {
				_update_buddy_status(buddy, status, nm_event_get_gmt(event));
			}
		}

		g_slist_free(buddies);

	}
}

static void
_evt_user_disconnect(NMUser * user, NMEvent * event)
{
	GaimConnection *gc;

	gc = gaim_account_get_connection((GaimAccount *) user->client_data);
	if (gc)
		gaim_connection_error(gc, _("You have been logged out because you"
									" logged in at another workstation."));
}

static void
_evt_user_typing(NMUser * user, NMEvent * event)
{
	GaimConnection *gc;
	NMUserRecord *user_record = NULL;

	gc = gaim_account_get_connection((GaimAccount *) user->client_data);
	if (gc) {
		user_record = nm_find_user_record(user, nm_event_get_source(event));
		if (user_record) {
			serv_got_typing(gc, nm_user_record_get_display_id(user_record),
							30, GAIM_TYPING);
		}
	}
}

static void
_evt_user_not_typing(NMUser * user, NMEvent * event)
{
	GaimConnection *gc;
	NMUserRecord *user_record;

	gc = gaim_account_get_connection((GaimAccount *) user->client_data);
	if (gc) {
		user_record = nm_find_user_record(user, nm_event_get_source(event));
		if (user_record) {
			serv_got_typing_stopped(gc,
									nm_user_record_get_display_id(user_record));
		}
	}
}

static void
_evt_undeliverable_status(NMUser * user, NMEvent * event)
{
	NMUserRecord *ur;
	GaimConversation *gconv;
	char *str;

	ur = nm_find_user_record(user, nm_event_get_source(event));
	if (ur) {
		gconv =
			gaim_find_conversation_with_account(nm_user_record_get_display_id(ur),
												user->client_data);
		if (gconv) {
			const char *name = nm_user_record_get_full_name(ur);

			if (name == NULL) {
				name = nm_user_record_get_display_id(ur);
			}
			str = g_strdup_printf(_("%s appears to be offline and did not receive"
									" the message that you just sent."), name);
			gaim_conversation_write(gconv, NULL, str,
									GAIM_MESSAGE_SYSTEM, time(NULL));
			g_free(str);
		}
	}
}

static void
_event_callback(NMUser * user, NMEvent * event)
{
	if (user == NULL || event == NULL)
		return;

	switch (nm_event_get_type(event)) {
		case NMEVT_STATUS_CHANGE:
			_evt_status_change(user, event);
			break;
		case NMEVT_RECEIVE_AUTOREPLY:
		case NMEVT_RECEIVE_MESSAGE:
			_evt_receive_message(user, event);
			break;
		case NMEVT_USER_DISCONNECT:
			_evt_user_disconnect(user, event);
			break;
		case NMEVT_USER_TYPING:
			_evt_user_typing(user, event);
			break;
		case NMEVT_USER_NOT_TYPING:
			_evt_user_not_typing(user, event);
			break;
		case NMEVT_SERVER_DISCONNECT:
			/* Nothing to do? */
			break;
		case NMEVT_INVALID_RECIPIENT:
			break;
		case NMEVT_UNDELIVERABLE_STATUS:
			_evt_undeliverable_status(user, event);
			break;
		case NMEVT_CONFERENCE_INVITE_NOTIFY:
			/* Someone else has been invited to join a
			 * conference that we are currently a part of
			 */
			/* TODO: show the invite notify in chat window */
			break;
		case NMEVT_CONFERENCE_INVITE:
			/* We have been invited to join a conference */
			_evt_conference_invite(user, event);
			break;
		case NMEVT_CONFERENCE_JOINED:
			/* Some one has joined a conference that we
			 * are a part of
			 */
			_evt_conference_joined(user, event);
			break;
		case NMEVT_CONFERENCE_LEFT:
			/* Someone else has left a conference that we
			 * are currently a part of
			 */
			_evt_conference_left(user, event);
			break;
		default:
			gaim_debug(GAIM_DEBUG_INFO, "novell",
					   "_event_callback(): unhandled event, %d\n",
					   nm_event_get_type(event));
			break;
	}
}

/*******************************************************************************
 * Prpl Ops
 ******************************************************************************/

static void
novell_login(GaimAccount * account)
{
	GaimConnection *gc;
	NMUser *user = NULL;
	const char *server;
	const char *name;
	int port;

	if (account == NULL)
		return;

	gc = gaim_account_get_connection(account);
	if (gc == NULL)
		return;

	server = gaim_account_get_string(account, "server", NULL);
	if (server == NULL || *server == '\0') {

		/* TODO: Would be nice to prompt if not set!
		 *  gaim_request_fields(gc, _("Server Address"),...);
		 */

		/* ...but for now just error out with a nice message. */
		gaim_connection_error(gc, _("Unable to connect to server."
									" Please enter the address of the server"
									" you wish to connect to."));
		return;
	}

	port = gaim_account_get_int(account, "port", DEFAULT_PORT);
	name = gaim_account_get_username(account);

	user = nm_initialize_user(name, server, port, account, _event_callback);
	if (user) {
		/* save user */
		gc->proto_data = user;

		/* connect to the server */
		gaim_connection_update_progress(gc, _("Connecting"),
										1, NOVELL_CONNECT_STEPS);

		user->conn->use_ssl = TRUE;
		if (gaim_ssl_connect(user->client_data, user->conn->addr,
							 user->conn->port, novell_ssl_connected_cb,
							 novell_ssl_connect_error, gc) == NULL) {
			gaim_connection_error(gc, _("Error."
										" SSL support is not installed."));
		}
	}
}

static void
novell_close(GaimConnection * gc)
{
	NMUser *user;
	NMConn *conn;

	if (gc == NULL)
		return;

	user = gc->proto_data;
	if (user) {
		conn = user->conn;
		if (conn) {
			if (conn->use_ssl && user->conn->ssl_conn) {
				gaim_ssl_close(user->conn->ssl_conn->data);
			} else {
				gaim_input_remove(gc->inpa);
				close(conn->fd);
			}
		}
		nm_deinitialize_user(user);
	}
	gc->proto_data = NULL;
}

static int
novell_send_im(GaimConnection * gc, const char *name,
			   const char *message_body, GaimConvImFlags flags)
{
	NMUserRecord *user_record = NULL;
	NMConference *conf = NULL;
	NMMessage *message;
	NMUser *user;
	const char *dn = NULL;
	gboolean done = TRUE, created_conf = FALSE;
	NMERR_T rc = NM_OK;

	if (gc == NULL || name == NULL ||
		message_body == NULL || *message_body == '\0')
		return 0;

	user = gc->proto_data;
	if (user == NULL)
		return 0;

	/* Create a new message */
	message = nm_create_message(gaim_markup_strip_html(message_body));

	/* Need to get the DN for the buddy so we can look up the convo */
	dn = nm_lookup_dn(user, name);

	/* Do we already know about the sender? */
	user_record = nm_find_user_record(user, dn);
	if (user_record) {

		/* Do we already have an instantiated conference? */
		conf = nm_find_conversation(user, dn);
		if (conf == NULL) {

			/* If not, create a blank conference */
			conf = nm_create_conference(NULL);
			created_conf = TRUE;

			nm_conference_add_participant(conf, user_record);
		}

		nm_message_set_conference(message, conf);

		/* Make sure conference is instatiated */
		if (!nm_conference_is_instantiated(conf)) {

			/* It is not, so send the createconf. We will
			 * have to finish sending the message when we
			 * get the response with the new conference guid.
			 */
			rc = nm_send_create_conference(user, conf,
										   _createconf_resp_send_msg, message);
			_check_for_disconnect(user, rc);

			done = FALSE;
		}

	} else {

		/* If we don't have details for the user, then we don't have
		 * a conference yet. So create one and send the getdetails
		 * to the server. We will have to finish sending the message
		 * when we get the response from the server.
		 */
		conf = nm_create_conference(NULL);
		created_conf = TRUE;

		nm_message_set_conference(message, conf);

		rc = nm_send_get_details(user, name, _get_details_resp_send_msg, message);
		_check_for_disconnect(user, rc);

		done = FALSE;
	}

	if (done) {

		/* Did we find everything we needed? */
		rc = nm_send_message(user, message, _send_message_resp_cb);
		_check_for_disconnect(user, rc);

		nm_release_message(message);
	}

	if (created_conf && conf)
		nm_release_conference(conf);

	return 1;
}

static int
novell_send_typing(GaimConnection * gc, const char *name, int typing)
{
	NMConference *conf = NULL;
	NMUser *user;
	const char *dn = NULL;
	NMERR_T rc = NM_OK;

	if (gc == NULL || name == NULL)
		return -1;

	user = gc->proto_data;
	if (user == NULL)
		return -1;

	/* Need to get the DN for the buddy so we can look up the convo */
	dn = nm_lookup_dn(user, name);
	if (dn) {

		/* Now find the conference in our list */
		conf = nm_find_conversation(user, dn);
		if (conf) {

			rc = nm_send_typing(user, conf,
								((typing == GAIM_TYPING) ? TRUE : FALSE), NULL);
			_check_for_disconnect(user, rc);

		}

	}

	return 0;
}

static void
novell_convo_closed(GaimConnection * gc, const char *who)
{
	NMUser *user;
	NMConference *conf;
	const char *dn;
	NMERR_T rc = NM_OK;

	if (gc == NULL || who == NULL)
		return;

	user = gc->proto_data;
	if (user && (dn = nm_lookup_dn(user, who))) {
		conf = nm_find_conversation(user, dn);
		if (conf) {
			rc = nm_send_leave_conference(user, conf, NULL, NULL);
			_check_for_disconnect(user, rc);
		}
	}
}

static void
novell_chat_leave(GaimConnection * gc, int id)
{
	NMConference *conference;
	NMUser *user;
	GaimConversation *chat;
	GSList *cnode;
	NMERR_T rc = NM_OK;

	if (gc == NULL)
		return;

	user = gc->proto_data;
	if (user == NULL)
		return;

	for (cnode = user->conferences; cnode != NULL; cnode = cnode->next) {
		conference = cnode->data;
		if (conference && (chat = nm_conference_get_data(conference))) {
			if (gaim_conv_chat_get_id(GAIM_CONV_CHAT(chat)) == id) {
				rc = nm_send_leave_conference(user, conference, NULL, NULL);
				_check_for_disconnect(user, rc);
				break;
			}
		}
	}

	serv_got_chat_left(gc, id);
}

static int
novell_chat_send(GaimConnection * gc, int id, const char *text)
{
	NMConference *conference;
	GaimConversation *chat;
	GSList *cnode;
	NMMessage *message;
	NMUser *user;
	NMERR_T rc = NM_OK;
	const char *name;
	char *str;

	if (gc == NULL || text == NULL)
		return -1;

	user = gc->proto_data;
	if (user == NULL)
		return -1;

	message = nm_create_message(gaim_markup_strip_html(text));

	for (cnode = user->conferences; cnode != NULL; cnode = cnode->next) {
		conference = cnode->data;
		if (conference && (chat = nm_conference_get_data(conference))) {
			if (gaim_conv_chat_get_id(GAIM_CONV_CHAT(chat)) == id) {

				nm_message_set_conference(message, conference);

				rc = nm_send_message(user, message, _send_message_resp_cb);
				nm_release_message(message);

				if (!_check_for_disconnect(user, rc)) {

					/* Use the account alias if it is set */
					name = gaim_account_get_alias(user->client_data);
					if (name == NULL || *name == '\0') {

						/* If there is no account alias, try full name */
						name = nm_user_record_get_full_name(user->user_record);
						if (name == NULL || *name == '\0') {

							/* Fall back to the username that we are signed in with */
							name = gaim_account_get_username(user->client_data);
						}
					}

					serv_got_chat_in(gc, id, name, 0, text, time(NULL));
					return 0;
				} else
					return -1;

			}
		}
	}

	/* The conference was not found, must be closed */
	chat = gaim_find_chat(gc, id);
	if (chat) {
		str = g_strdup_printf(_("This conference has been closed."
								" No more messages can be sent."));
		gaim_conversation_write(chat, NULL, str, GAIM_MESSAGE_SYSTEM, time(NULL));
		g_free(str);
	}

	return -1;
}

static void
novell_add_buddy(GaimConnection * gc, const char *name, GaimGroup * group)
{
	GaimBuddy *buddy;
	NMFolder *folder = NULL;
	NMContact *contact;
	NMUser *user;
	NMERR_T rc = NM_OK;

	if (gc == NULL || name == NULL || group == NULL)
		return;

	user = (NMUser *) gc->proto_data;
	if (user == NULL)
		return;

	contact = nm_create_contact();
	nm_contact_set_dn(contact, name);

	/* Remove the GaimBuddy (we will add it back after adding it
	 * to the server side list). Save the alias if there is one.
	 */
	buddy = gaim_find_buddy_in_group(user->client_data, name, group);
	if (buddy) {
		const char *alias = gaim_get_buddy_alias(buddy);

		if (alias && strcmp(alias, name))
			nm_contact_set_display_name(contact, gaim_get_buddy_alias(buddy));
		gaim_blist_remove_buddy(buddy);
		buddy = NULL;
	}


	folder = nm_find_folder(user, group->name);
	if (folder) {

		/* We have everything that we need, so send the createcontact */
		rc = nm_send_create_contact(user, folder, contact,
									_create_contact_resp_cb, contact);

	} else {

		/* Need to create the folder before we can add the contact */
		rc = nm_send_create_folder(user, group->name,
								   _create_folder_resp_add_contact, contact);
	}

	_check_for_disconnect(user, rc);

}

static void
novell_remove_buddy(GaimConnection * gc, const char *name, const char *group_name)
{
	NMContact *contact;
	NMFolder *folder;
	NMUser *user;
	const char *dn;
	NMERR_T rc = NM_OK;

	if (gc == NULL || name == NULL || group_name == NULL)
		return;

	user = (NMUser *) gc->proto_data;
	if (user && (dn = nm_lookup_dn(user, name))) {

		folder = nm_find_folder(user, group_name);
		if (folder) {
			contact = nm_folder_find_contact(folder, dn);
			if (contact) {

				/* Remove the buddy from the contact */
				nm_contact_set_data(contact, NULL);

				/* Tell the server to remove the contact */
				rc = nm_send_remove_contact(user, folder, contact,
											_remove_contact_resp_cb, NULL);
				_check_for_disconnect(user, rc);
			}
		}
	}
}

static void
novell_remove_group(GaimConnection * gc, const char *name)
{
	NMUser *user;
	NMERR_T rc = NM_OK;

	if (gc == NULL || name == NULL)
		return;

	user = (NMUser *) gc->proto_data;
	if (user) {
		NMFolder *folder = nm_find_folder(user, name);

		if (folder) {
			rc = nm_send_remove_folder(user, folder,
									   _remove_folder_resp_cb, NULL);
			_check_for_disconnect(user, rc);
		}
	}
}

static void
novell_alias_buddy(GaimConnection * gc, const char *name, const char *alias)
{
	NMContact *contact;
	NMUser *user;
	GList *contacts = NULL;
	GList *cnode = NULL;
	const char *dn = NULL;
	NMERR_T rc = NM_OK;

	if (gc == NULL || name == NULL || alias == NULL)
		return;

	user = (NMUser *) gc->proto_data;
	if (user && (dn = nm_lookup_dn(user, name))) {

		/* Alias all of instances of the contact */
		contacts = nm_find_contacts(user, dn);
		for (cnode = contacts; cnode != NULL; cnode = cnode->next) {
			contact = (NMContact *) cnode->data;
			if (contact) {
				GaimGroup *group;
				GaimBuddy *buddy;
				NMFolder *folder;

				/* Alias the Gaim buddy? */
				folder = nm_find_folder_by_id(user,
											  nm_contact_get_parent_id(contact));
				if (folder &&
					(group = gaim_find_group(nm_folder_get_name(folder)))) {
					buddy = gaim_find_buddy_in_group(user->client_data,
													 name, group);
					if (buddy && strcmp(buddy->alias, alias))
						gaim_blist_alias_buddy(buddy, alias);

				}
				/* Tell the server to alias the contact */
				rc = nm_send_rename_contact(user, contact, alias,
											_rename_contact_resp_cb, NULL);
				_check_for_disconnect(user, rc);
			}
		}
		if (contacts)
			g_list_free(contacts);
	}
}

static void
novell_group_buddy(GaimConnection * gc,
				   const char *name, const char *old_group_name,
				   const char *new_group_name)
{
	NMFolder *old_folder;
	NMFolder *new_folder;
	NMContact *contact;
	NMUser *user;
	const char *dn;
	NMERR_T rc = NM_OK;

	if (gc == NULL || name == NULL ||
		old_group_name == NULL || new_group_name == NULL)
		return;

	user = (NMUser *) gc->proto_data;
	if (user && (dn = nm_lookup_dn(user, name))) {

		/* Find the old folder */
		old_folder = nm_find_folder(user, old_group_name);
		if (old_folder && (contact = nm_folder_find_contact(old_folder, dn))) {

			/* Find the new folder */
			new_folder = nm_find_folder(user, new_group_name);
			if (new_folder) {

				/* Tell the server to move the contact to the new folder */
				rc = nm_send_move_contact(user, contact, new_folder,
										  _move_contact_resp_cb, NULL);

			} else {

				nm_contact_add_ref(contact);

				/* Remove the old contact first */
				nm_send_remove_contact(user, old_folder, contact,
									   _remove_contact_resp_cb, NULL);

				/* New folder does not exist yet, so create it  */
				rc = nm_send_create_folder(user, new_group_name,
										   _create_folder_resp_move_contact,
										   contact);
			}

			_check_for_disconnect(user, rc);
		}
	}
}

static void
novell_rename_group(GaimConnection * gc, const char *old_name,
					const char *new_name, GList * tobemoved)
{
	NMERR_T rc = NM_OK;
	NMUser *user;

	if (gc == NULL || old_name == NULL || new_name == NULL || tobemoved == NULL) {
		return;
	}

	user = gc->proto_data;
	if (user) {
		/* Does new folder exist already? */
		if (nm_find_folder(user, new_name)) {
			/* Gaim currently calls novell_group_buddy() for
			 * for all buddies in the group, so we don't
			 * need to worry about this situation.
			 */
			return;
		}

		NMFolder *folder = nm_find_folder(user, old_name);

		if (folder) {
			rc = nm_send_rename_folder(user, folder, new_name,
									   _rename_folder_resp_cb, NULL);
			_check_for_disconnect(user, rc);
		}
	}
}

static void
novell_list_emblems(GaimBuddy * buddy, char **se, char **sw, char **nw, char **ne)
{
	int status = buddy->uc >> 1;

	switch (status) {
		case NM_STATUS_AVAILABLE:
			*se = "";
			break;
		case NM_STATUS_AWAY:
			*se = "away";
			break;
		case NM_STATUS_BUSY:
			*se = "occupied";
			break;
		case NM_STATUS_UNKNOWN:
			*se = "error";
			break;
	}
}

static const char *
novell_list_icon(GaimAccount * account, GaimBuddy * buddy)
{
	return "novell";
}

static char *
novell_tooltip_text(GaimBuddy * buddy)
{
	NMUserRecord *user_record = NULL;
	GaimConnection *gc;
	NMUser *user;
	int status = 0;
	char *ret_text = NULL;
	const char *status_str = NULL;
	const char *text = NULL;

	if (buddy == NULL)
		return "";

	gc = gaim_account_get_connection(buddy->account);
	if (gc == NULL || (user = gc->proto_data) == NULL)
		return "";

	if (GAIM_BUDDY_IS_ONLINE(buddy)) {
		user_record = nm_find_user_record(user, buddy->name);
		if (user_record) {
			status = nm_user_record_get_status(user_record);
			text = nm_user_record_get_status_text(user_record);
			/* No custom text, so default it ... */
			switch (status) {
				case NM_STATUS_AVAILABLE:
					status_str = _("Available");
					break;
				case NM_STATUS_AWAY:
					status_str = _("Away");
					break;
				case NM_STATUS_BUSY:
					status_str = _("Busy");
					break;
				case NM_STATUS_AWAY_IDLE:
					status_str = _("Idle");
					break;
				case NM_STATUS_OFFLINE:
					status_str = _("Offline");
					break;
				default:
					status_str = _("Unknown");
					break;
			}

			if (text)
				ret_text = g_strdup_printf(_("<b>Status:</b> %s\n"
											 "<b>Message:</b> %s"),
										   status_str, text);
			else
				ret_text = g_strdup_printf(_("<b>Status:</b> %s"), status_str);
		}
	}

	return ret_text;
}

static void
novell_set_idle(GaimConnection * gc, int time)
{
	NMUser *user;
	NMERR_T rc = NM_OK;

	if (gc == NULL)
		return;

	user = gc->proto_data;
	if (user == NULL)
		return;

	if (time > 0)
		rc = nm_send_set_status(user, NM_STATUS_AWAY_IDLE, NULL, NULL, NULL,
								NULL);
	else
		rc = nm_send_set_status(user, NM_STATUS_AVAILABLE, NULL, NULL, NULL,
								NULL);

	_check_for_disconnect(user, rc);
}

static void
novell_get_info(GaimConnection * gc, const char *name)
{
	NMUserRecord *user_record;
	NMUser *user;
	NMERR_T rc;

	if (gc == NULL || name == NULL)
		return;

	user = (NMUser *) gc->proto_data;
	if (user) {

		user_record = nm_find_user_record(user, name);
		if (user_record) {

			_show_info(gc, user_record);

		} else {

			rc = nm_send_get_details(user, name,
									 _get_details_resp_show_info, g_strdup(name));

			_check_for_disconnect(user, rc);

		}

	}
}

static char *
novell_status_text(GaimBuddy * buddy)
{
	const char *text = NULL;
	const char *dn = NULL;

	if (buddy && buddy->account) {
		GaimConnection *gc = gaim_account_get_connection(buddy->account);

		if (gc && gc->proto_data) {
			NMUser *user = gc->proto_data;

			dn = nm_lookup_dn(user, buddy->name);
			if (dn) {
				NMUserRecord *user_record = nm_find_user_record(user, dn);

				if (user_record) {
					text = nm_user_record_get_status_text(user_record);
					if (text)
						return g_strdup(text);
				}
			}
		}
	}

	return NULL;
}

static GList *
novell_away_states(GaimConnection * gc)
{
	GList *m = NULL;

	m = g_list_append(m, _("Available"));
	m = g_list_append(m, _("Away"));
	m = g_list_append(m, _("Busy"));
	m = g_list_append(m, _("Appear Offline"));
	m = g_list_append(m, GAIM_AWAY_CUSTOM);

	return m;
}

static void
novell_set_away(GaimConnection * gc, const char *state, const char *msg)
{
	NMUser *user;
	NMSTATUS_T status = NM_STATUS_AVAILABLE;
	NMERR_T rc = NM_OK;
	char *text = NULL;
	char *tmp = NULL;
	char *p = NULL;

	if (gc == NULL)
		return;

	user = gc->proto_data;
	if (user == NULL)
		return;

	if (gc->away) {
		g_free(gc->away);
		gc->away = NULL;
	}

	if (msg != NULL) {
		status = NM_STATUS_AWAY;
		gc->away = g_strdup("");

		/* Don't want newlines in status text */
		tmp = g_strdup(msg);
		if ((p = strchr(tmp, '\n'))) {
			*p = '\0';
		}

		/* Truncate the status text if necessary */
		text = g_strdup(tmp);
		if (g_utf8_strlen(tmp, -1) > 60) {
			g_utf8_strncpy(text, tmp, 60);
			strcat(text, "...");
		}

		g_free(tmp);

	} else if (state) {
		if (!strcmp(state, _("Available"))) {
			status = NM_STATUS_AVAILABLE;
		} else if (!strcmp(state, _("Away"))) {
			status = NM_STATUS_AWAY;
			gc->away = g_strdup("");
		} else if (!strcmp(state, _("Busy"))) {
			status = NM_STATUS_BUSY;
			gc->away = g_strdup("");
		} else if (!strcmp(state, _("Appear Offline"))) {
			status = NM_STATUS_OFFLINE;
			gc->away = g_strdup("");
		} else {
			status = NM_STATUS_AVAILABLE;
			g_free(gc->away);
			gc->away = NULL;
		}
	} else if (gc->is_idle) {
		status = NM_STATUS_AWAY_IDLE;
	} else {
		status = NM_STATUS_AVAILABLE;
	}

	rc = nm_send_set_status(user, status, text, msg, NULL, NULL);
	_check_for_disconnect(user, rc);

	if (text)
		g_free(text);
}

static GaimPluginProtocolInfo prpl_info = {
	0,
	NULL,
	NULL,
	novell_list_icon,
	novell_list_emblems,
	novell_status_text,
	novell_tooltip_text,
	novell_away_states,
	NULL,						/* prpl_actions */
	NULL,						/* buddy_menu */
	NULL,						/* chat_info */
	novell_login,
	novell_close,
	novell_send_im,
	NULL,						/* set_info */
	novell_send_typing,
	novell_get_info,
	novell_set_away,
	NULL,						/* set_dir */
	NULL,						/* get_dir */
	NULL,						/* dir_search */
	novell_set_idle,
	NULL,						/* change pwd */
	novell_add_buddy,
	NULL,						/* add_buddies */
	novell_remove_buddy,
	NULL,						/* remove_buddies */
	NULL,						/* add_permit */
	NULL,						/* add_deny */
	NULL,						/* rem_permit */
	NULL,						/* rem_deny */
	NULL,						/* set_permit_deny */
	NULL,						/* warn */
	NULL,						/* join_chat */
	NULL,						/* reject_chat ?? */
	NULL,						/* chat_invite */
	novell_chat_leave,
	NULL,						/* chat_whisper */
	novell_chat_send,
	NULL,						/* keepalive */
	NULL,						/* register_user */
	NULL,						/* get_cb_info */
	NULL,						/* get_cb_away_msg */
	novell_alias_buddy,
	novell_group_buddy,
	novell_rename_group,
	NULL,						/* buddy_free */
	novell_convo_closed,
	NULL,						/* normalize */
	NULL,						/* set_buddy_icon */
	novell_remove_group,
	NULL
};

static GaimPluginInfo info = {
	2,												  /**< api_version    */
	GAIM_PLUGIN_PROTOCOL,							  /**< type           */
	NULL,											  /**< ui_requirement */
	0,												  /**< flags          */
	NULL,											  /**< dependencies   */
	GAIM_PRIORITY_DEFAULT,							  /**< priority       */

	"prpl-novell",									  /**< id             */
	"GroupWise",								 /**< name           */
	VERSION,										  /**< version        */
	/**  summary        */
	N_("Novell GroupWise Messenger Protocol Plugin"),
	/**  description    */
	N_("Novell GroupWise Messenger Protocol Plugin"),
	NULL,											  /**< author         */
	GAIM_WEBSITE,									  /**< homepage       */

	NULL,											  /**< load           */
	NULL,											  /**< unload         */
	NULL,											  /**< destroy        */

	NULL,											  /**< ui_info        */
	&prpl_info										  /**< extra_info     */
};

static void
init_plugin(GaimPlugin * plugin)
{
	GaimAccountOption *option;

	option = gaim_account_option_string_new(_("Server address"), "server", NULL);
	prpl_info.protocol_options =
		g_list_append(prpl_info.protocol_options, option);

	option = gaim_account_option_int_new(_("Server port"), "port", DEFAULT_PORT);
	prpl_info.protocol_options =
		g_list_append(prpl_info.protocol_options, option);

	my_protocol = plugin;
}

GAIM_INIT_PLUGIN(novell, init_plugin, info);