changeset 8675:9ee2542d1104

[gaim-migrate @ 9428] A GroupWise plugin from Novell. committer: Tailor Script <tailor@pidgin.im>
author Sean Egan <seanegan@gmail.com>
date Sat, 17 Apr 2004 13:55:28 +0000
parents 8c7da2e36136
children e096d797d958
files Makefile.mingw configure.ac pixmaps/status/default/Makefile.am pixmaps/status/default/novell.png src/blist.c src/protocols/Makefile.am src/protocols/novell/Makefile.am src/protocols/novell/Makefile.mingw src/protocols/novell/nmconference.c src/protocols/novell/nmconference.h src/protocols/novell/nmconn.c src/protocols/novell/nmconn.h src/protocols/novell/nmcontact.c src/protocols/novell/nmcontact.h src/protocols/novell/nmevent.c src/protocols/novell/nmevent.h src/protocols/novell/nmfield.c src/protocols/novell/nmfield.h src/protocols/novell/nmmessage.c src/protocols/novell/nmmessage.h src/protocols/novell/nmrequest.c src/protocols/novell/nmrequest.h src/protocols/novell/nmuser.c src/protocols/novell/nmuser.h src/protocols/novell/nmuserrecord.c src/protocols/novell/nmuserrecord.h src/protocols/novell/novell.c src/prpl.c src/prpl.h
diffstat 29 files changed, 10607 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile.mingw	Fri Apr 16 23:32:46 2004 +0000
+++ b/Makefile.mingw	Sat Apr 17 13:55:28 2004 +0000
@@ -23,6 +23,7 @@
 NAPSTER = $(GAIM_PROTOS)/napster
 TREPIA = $(GAIM_PROTOS)/trepia
 GG = $(GAIM_PROTOS)/gg
+NOVELL = $(GAIM_PROTOS)/novell
 PO = ./po
 
 VERSION := $(shell cat ./VERSION)
@@ -50,6 +51,7 @@
 JABBER_TYPE = PLUGIN
 NAPSTER_TYPE = PLUGIN
 GG_TYPE = PLUGIN
+NOVELL_TYPE = PLUGIN
 TREPIA_TYPE = PLUGIN
 
 all:
@@ -60,6 +62,7 @@
 	$(MAKE) TYPE='$(IRC_TYPE)' -C $(IRC) -f Makefile.mingw
 	$(MAKE) TYPE='$(JABBER_TYPE)' -C $(JABBER) -f Makefile.mingw
 	$(MAKE) TYPE='$(GG_TYPE)' -C $(GG) -f Makefile.mingw
+	$(MAKE) TYPE='$(NOVELL_TYPE)' -C $(NOVELL) -f Makefile.mingw
 	$(MAKE) -C $(GAIM_SRC) -f Makefile.mingw
 	$(MAKE) -C $(GAIM_PLUGINS) -f Makefile.mingw
 
@@ -77,6 +80,7 @@
 	$(MAKE) TYPE='$(IRC_TYPE)' -C $(IRC) -f Makefile.mingw install
 	$(MAKE) TYPE='$(JABBER_TYPE)' -C $(JABBER) -f Makefile.mingw install
 	$(MAKE) TYPE='$(GG_TYPE)' -C $(GG) -f Makefile.mingw install
+	$(MAKE) TYPE='$(NOVELL_TYPE)' -C $(NOVELL) -f Makefile.mingw install
 	cp $(NEEDED_DLLS) $(GAIM_INSTALL_DIR)
 	cp $(SOUNDS) $(GAIM_INSTALL_DIR)/sounds/gaim
 
@@ -100,6 +104,7 @@
 	$(MAKE) -C $(IRC) -f Makefile.mingw clean
 	$(MAKE) -C $(JABBER) -f Makefile.mingw clean
 	$(MAKE) -C $(GG) -f Makefile.mingw clean
+	$(MAKE) -C $(NOVELL) -f Makefile.mingw clean
 	$(MAKE) -C $(GAIM_SRC) -f Makefile.mingw clean
 	$(MAKE) -C $(GAIM_PLUGINS) -f Makefile.mingw clean
 	rm -rf config.h $(GAIM_INSTALL_DIR)
--- a/configure.ac	Fri Apr 16 23:32:46 2004 +0000
+++ b/configure.ac	Sat Apr 17 13:55:28 2004 +0000
@@ -102,7 +102,7 @@
 fi
 
 if test "x$STATIC_PRPLS" = "xall" ; then
-	STATIC_PRPLS="gg irc jabber msn napster oscar yahoo zephyr"
+	STATIC_PRPLS="gg irc jabber msn napster novell oscar yahoo zephyr"
 fi
 AC_SUBST(STATIC_PRPLS)
 STATIC_LINK_LIBS=
@@ -118,6 +118,7 @@
 		jabber)		static_jabber=yes ;;
 		msn)		static_msn=yes ;;
 		napster)	static_napster=yes ;;
+		novell)		static_novell=yes ;;
 		oscar)		static_oscar=yes ;;
 		rendezvous)	static_rendezvous=yes ;;
 		toc)		static_toc=yes ;;
@@ -132,6 +133,7 @@
 AM_CONDITIONAL(STATIC_JABBER, test "x$static_jabber" = "xyes")
 AM_CONDITIONAL(STATIC_MSN, test "x$static_msn" = "xyes")
 AM_CONDITIONAL(STATIC_NAPSTER, test "x$static_napster" = "xyes")
+AM_CONDITIONAL(STATIC_NOVELL, test "x$static_novell" = "xyes")
 AM_CONDITIONAL(STATIC_OSCAR, test "x$static_oscar" = "xyes")
 AM_CONDITIONAL(STATIC_RENDEZVOUS, test "x$static_rendezvous" = "xyes")
 AM_CONDITIONAL(STATIC_TOC, test "x$static_toc" = "xyes")
@@ -144,7 +146,7 @@
 
 AC_ARG_WITH(dynamic_prpls,   [  --with-dynamic-prpls    specify which protocols to build dynamically],[DYNAMIC_PRPLS=`echo $withval | $sedpath 's/,/ /g'`])
 if test "x$DYNAMIC_PRPLS" = "xall" ; then
-	DYNAMIC_PRPLS="gg irc jabber msn napster oscar yahoo zephyr"
+	DYNAMIC_PRPLS="gg irc jabber msn napster novell oscar yahoo zephyr"
 fi
 AC_SUBST(DYNAMIC_PRPLS)
 for i in $DYNAMIC_PRPLS ; do
@@ -154,6 +156,7 @@
 		jabber)		dynamic_jabber=yes ;;
 		msn)		dynamic_msn=yes ;;
 		napster)	dynamic_napster=yes ;;
+		novell)		dynamic_novell=yes ;;
 		oscar)		dynamic_oscar=yes ;;
 		rendezvous)	dynamic_rendezvous=yes ;;
 		toc)		dynamic_toc=yes ;;
@@ -168,6 +171,7 @@
 AM_CONDITIONAL(DYNAMIC_JABBER, test "x$dynamic_jabber" = "xyes")
 AM_CONDITIONAL(DYNAMIC_MSN, test "x$dynamic_msn" = "xyes")
 AM_CONDITIONAL(DYNAMIC_NAPSTER, test "x$dynamic_napster" = "xyes")
+AM_CONDITIONAL(DYNAMIC_NOVELL, test "x$dynamic_novell" = "xyes")
 AM_CONDITIONAL(DYNAMIC_OSCAR, test "x$dynamic_oscar" = "xyes")
 AM_CONDITIONAL(DYNAMIC_RENDEZVOUS, test "x$dynamic_rendezvous" = "xyes")
 AM_CONDITIONAL(DYNAMIC_TOC, test "x$dynamic_toc" = "xyes")
@@ -1097,6 +1101,7 @@
 		   src/protocols/jabber/Makefile
 		   src/protocols/msn/Makefile
 		   src/protocols/napster/Makefile
+   		   src/protocols/novell/Makefile
 		   src/protocols/oscar/Makefile
 		   src/protocols/rendezvous/Makefile
 		   src/protocols/toc/Makefile
--- a/pixmaps/status/default/Makefile.am	Fri Apr 16 23:32:46 2004 +0000
+++ b/pixmaps/status/default/Makefile.am	Sat Apr 17 13:55:28 2004 +0000
@@ -22,6 +22,7 @@
 	na.png \
 	napster.png \
 	notauthorized.png \
+	novell.png \
 	occupied.png \
 	offline.png \
 	rendezvous.png \
Binary file pixmaps/status/default/novell.png has changed
--- a/src/blist.c	Fri Apr 16 23:32:46 2004 +0000
+++ b/src/blist.c	Sat Apr 17 13:55:28 2004 +0000
@@ -348,6 +348,17 @@
 void  gaim_blist_rename_buddy (GaimBuddy *buddy, const char *name)
 {
 	GaimBlistUiOps *ops = gaimbuddylist->ui_ops;
+	struct _gaim_hbuddy *hb = g_new(struct _gaim_hbuddy, 1);
+
+	hb->name = g_strdup(gaim_normalize(buddy->account, buddy->name));
+	hb->account = buddy->account;
+	hb->group = ((GaimBlistNode *)buddy)->parent->parent;
+	g_hash_table_remove(gaimbuddylist->buddies, hb);
+
+	g_free(hb->name);
+	hb->name = g_strdup(gaim_normalize(buddy->account, name));
+	g_hash_table_replace(gaimbuddylist->buddies, hb, buddy);
+
 	g_free(buddy->name);
 	buddy->name = g_strdup(name);
 	if (ops)
--- a/src/protocols/Makefile.am	Fri Apr 16 23:32:46 2004 +0000
+++ b/src/protocols/Makefile.am	Sat Apr 17 13:55:28 2004 +0000
@@ -1,3 +1,3 @@
-DIST_SUBDIRS = gg irc jabber msn napster oscar rendezvous toc trepia yahoo zephyr
+DIST_SUBDIRS = gg irc jabber msn napster novell oscar rendezvous toc trepia yahoo zephyr
 
 SUBDIRS = $(DYNAMIC_PRPLS) $(STATIC_PRPLS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/novell/Makefile.am	Sat Apr 17 13:55:28 2004 +0000
@@ -0,0 +1,53 @@
+EXTRA_DIST = \
+		Makefile.mingw
+
+pkgdir = $(libdir)/gaim
+
+NOVELLSOURCES = \
+		nmfield.h \
+		nmfield.c \
+		nmconn.h \
+		nmconn.c \
+		nmconference.h \
+		nmconference.c \
+		nmcontact.h \
+		nmcontact.c \
+		nmevent.h \
+		nmevent.c \
+		nmmessage.h \
+		nmmessage.c \
+		nmrequest.h \
+		nmrequest.c \
+		nmuser.h \
+		nmuser.c \
+		nmuserrecord.h \
+		nmuserrecord.c \
+		novell.c
+
+AM_CFLAGS = $(st)
+
+libnovell_la_LDFLAGS = -module -avoid-version
+
+if STATIC_NOVELL
+
+st = -DSTATIC
+noinst_LIBRARIES = libnovell.a
+pkg_LTLIBRARIES =
+
+libnovell_a_SOURCES = $(NOVELLSOURCES)
+libnovell_a_CFLAGS = $(AM_CFLAGS)
+
+else
+
+st =
+pkg_LTLIBRARIES = libnovell.la
+noinst_LIBRARIES =
+
+libnovell_la_SOURCES = $(NOVELLSOURCES)
+
+endif
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src \
+	$(DEBUG_CFLAGS) \
+	$(GLIB_CFLAGS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/novell/Makefile.mingw	Sat Apr 17 13:55:28 2004 +0000
@@ -0,0 +1,142 @@
+#
+# Makefile.mingw
+#
+# Description: Makefile for win32 (mingw) version of libnovell
+#
+
+#
+# PATHS
+#
+
+INCLUDE_DIR :=		.
+GTK_TOP :=		../../../../win32-dev/gtk_2_0
+GAIM_TOP :=		../../..
+NOVELL_ROOT :=		.
+GAIM_INSTALL_DIR :=	$(GAIM_TOP)/win32-install-dir
+
+##
+## VARIABLE DEFINITIONS
+##
+
+TARGET = libnovell
+
+# Compiler Options
+
+CFLAGS =
+
+DEFINES =
+
+# Static or Plugin... 
+ifeq ($(TYPE),STATIC)
+  DEFINES += -DSTATIC
+  DLL_INSTALL_DIR =	$(GAIM_INSTALL_DIR)
+else
+ifeq ($(TYPE),PLUGIN)
+  DLL_INSTALL_DIR =	$(GAIM_INSTALL_DIR)/plugins
+endif
+endif
+
+
+##
+## INCLUDE  MAKEFILES
+##
+
+include $(GAIM_TOP)/src/win32/global.mak
+
+##
+## INCLUDE PATHS
+##
+
+INCLUDE_PATHS +=	-I$(NOVELL_ROOT) \
+			-I$(GTK_TOP)/include \
+			-I$(GTK_TOP)/include/gtk-2.0 \
+			-I$(GTK_TOP)/include/glib-2.0 \
+			-I$(GTK_TOP)/include/pango-1.0 \
+			-I$(GTK_TOP)/include/atk-1.0 \
+			-I$(GTK_TOP)/lib/glib-2.0/include \
+			-I$(GTK_TOP)/lib/gtk-2.0/include \
+			-I$(GAIM_TOP)/src \
+			-I$(GAIM_TOP)/src/win32 \
+			-I$(GAIM_TOP)
+
+
+LIB_PATHS =		-L$(GTK_TOP)/lib \
+			-L$(GAIM_TOP)/src
+
+
+##
+##  SOURCES, OBJECTS
+##
+
+C_SRC =	nmfield.c \
+		nmconn.c \
+		nmconference.c \
+		nmcontact.c \
+		nmevent.c \
+		nmmessage.c \
+		nmrequest.c \
+		nmuser.c \
+		nmuserrecord.c \
+		novell.c
+
+OBJECTS = $(C_SRC:%.c=%.o)
+
+
+##
+## LIBRARIES
+##
+
+LIBS =			-lgtk-win32-2.0 \
+			-lglib-2.0 \
+			-lgdk-win32-2.0 \
+			-lgmodule-2.0 \
+			-lgobject-2.0 \
+			-lws2_32 \
+			-lintl \
+			-lgaim
+
+
+##
+## RULES
+##
+
+# How to make a C file
+
+%.o: %.c
+	$(CC) $(CFLAGS) $(DEFINES) $(INCLUDE_PATHS) -o $@ -c $<
+
+##
+## TARGET DEFINITIONS
+##
+
+.PHONY: all clean
+
+all: $(TARGET).dll
+
+install:
+	cp $(NOVELL_ROOT)/$(TARGET).dll $(DLL_INSTALL_DIR)
+
+
+##
+## BUILD Dependencies
+##
+
+$(GAIM_TOP)/src/gaim.lib:
+	$(MAKE) -C $(GAIM_TOP)/src -f Makefile.mingw gaim.lib
+
+##
+## BUILD DLL
+##
+
+$(TARGET).dll: $(OBJECTS) $(GAIM_TOP)/src/gaim.lib
+	$(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -Wl,--out-implib,$(TARGET).lib -o $(TARGET).dll
+
+
+##
+## CLEAN RULES
+##
+
+clean:
+	rm -rf *.o
+	rm -rf $(TARGET).dll
+	rm -rf $(TARGET).lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/novell/nmconference.c	Sat Apr 17 13:55:28 2004 +0000
@@ -0,0 +1,232 @@
+/*
+ * nmconference.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 <string.h>
+#include "nmconference.h"
+
+static int conf_count = 0;
+
+struct _NMConference
+{
+
+	/* The conference identifier */
+	char *guid;
+
+	/* The list of participants for the conference */
+	GSList *participants;
+
+	/* Flags for the conference */
+	guint32 flags;
+
+	/* User defined data */
+	gpointer data;
+
+	/* Reference count for this object */
+	int ref_count;
+
+};
+
+
+/*******************************************************************************
+ * Conference API -- see header file for comments
+ ******************************************************************************/
+
+NMConference *
+nm_create_conference(const char *guid)
+{
+	NMConference *conf = g_new0(NMConference, 1);
+
+	if (guid) {
+		conf->guid = g_strdup(guid);
+	} else {
+		conf->guid = g_strdup(BLANK_GUID);
+	}
+	conf->ref_count = 1;
+
+	gaim_debug(GAIM_DEBUG_INFO, "novell",
+			   "Creating a conference 0x%x, total=%d\n",
+			   (guint32) conf, conf_count++);
+
+	return conf;
+}
+
+void
+nm_release_conference(NMConference * conference)
+{
+	GSList *node;
+
+	gaim_debug(GAIM_DEBUG_INFO, "novell",
+			   "In release conference 0x%x, refs=%d\n",
+			   (guint32) conference, conference->ref_count);
+	if (conference != NULL && (--conference->ref_count == 0)) {
+
+		gaim_debug(GAIM_DEBUG_INFO, "novell",
+				   "Releasing conference 0x%x, total=%d\n",
+				   (guint32) conference, --conf_count);
+
+		if (conference->guid)
+			g_free(conference->guid);
+
+		if (conference->participants) {
+			for (node = conference->participants; node; node = node->next) {
+				if (node->data) {
+					NMUserRecord *user_record = node->data;
+
+					nm_release_user_record(user_record);
+					node->data = NULL;
+				}
+			}
+
+			g_slist_free(conference->participants);
+		}
+
+		g_free(conference);
+	}
+}
+
+gboolean
+nm_conference_is_instantiated(NMConference * conference)
+{
+	if (conference == NULL)
+		return FALSE;
+
+	return (strncmp(conference->guid, BLANK_GUID, CONF_GUID_END) != 0);
+}
+
+int
+nm_conference_get_participant_count(NMConference * conference)
+{
+	if (conference == NULL)
+		return 0;
+
+	return g_slist_length(conference->participants);
+}
+
+NMUserRecord *
+nm_conference_get_participant(NMConference * conference, int index)
+{
+	if (conference == NULL)
+		return NULL;
+
+	return (NMUserRecord *) g_slist_nth_data(conference->participants, index);
+}
+
+void
+nm_conference_add_participant(NMConference * conference,
+							  NMUserRecord * user_record)
+{
+	if (conference == NULL || user_record == NULL) {
+		return;
+	}
+
+	nm_user_record_add_ref(user_record);
+	conference->participants = g_slist_append(conference->participants, user_record);
+}
+
+void
+nm_conference_remove_participant(NMConference * conference, const char *dn)
+{
+	GSList *node, *element = NULL;
+
+	if (conference == NULL || dn == NULL) {
+		return;
+	}
+
+	for (node = conference->participants; node; node = node->next) {
+		NMUserRecord *user_record = node->data;
+
+		if (user_record) {
+			if (nm_utf8_str_equal(dn, nm_user_record_get_dn(user_record))) {
+				element = node;
+				break;
+			}
+		}
+	}
+
+	if (element) {
+		nm_release_user_record((NMUserRecord *) element->data);
+		element->data = NULL;
+		conference->participants =
+			g_slist_remove_link(conference->participants, element);
+		g_slist_free_1(element);
+	}
+}
+
+void
+nm_conference_add_ref(NMConference * conference)
+{
+	if (conference)
+		conference->ref_count++;
+}
+
+void
+nm_conference_set_flags(NMConference * conference, guint32 flags)
+{
+	if (conference) {
+		conference->flags = flags;
+	}
+}
+
+void
+nm_conference_set_guid(NMConference * conference, const char *guid)
+{
+	if (conference) {
+
+		/* Release memory for old guid */
+		if (conference->guid) {
+			g_free(conference->guid);
+		}
+
+		/* Set the new guid */
+		if (guid)
+			conference->guid = g_strdup(guid);
+		else
+			conference->guid = g_strdup(BLANK_GUID);
+	}
+}
+
+void
+nm_conference_set_data(NMConference * conference, gpointer data)
+{
+	if (conference == NULL)
+		return;
+
+	conference->data = data;
+}
+
+gpointer
+nm_conference_get_data(NMConference * conference)
+{
+	if (conference == NULL)
+		return NULL;
+
+	return conference->data;
+}
+
+const char *
+nm_conference_get_guid(NMConference * conference)
+{
+	if (conference == NULL)
+		return NULL;
+
+	return conference->guid;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/novell/nmconference.h	Sat Apr 17 13:55:28 2004 +0000
@@ -0,0 +1,169 @@
+/*
+ * nmconference.h
+ *
+ * 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.
+ *
+ */
+
+#ifndef __NM_CONFERENCE_H__
+#define __NM_CONFERENCE_H__
+
+typedef struct _NMConference NMConference;
+
+#include "nmuserrecord.h"
+
+/* A blank GUID -- represents an uninstatiated conference */
+#define BLANK_GUID "[00000000-00000000-00000000-0000-0000]"
+
+/* This is how much of the conference GUIDs to compare when testing
+ * to see if two conferences are the same. We cannot compare the
+ * entire GUID because the last part is the session count.
+ */
+#define CONF_GUID_END 27
+
+/**
+ * Creates an conference object.
+ *
+ * The conference should be released by calling
+ * nm_release_conference
+ *
+ * @param guid		The GUID for the conference.
+ *
+ * @return 			The new NMConference
+ */
+NMConference *nm_create_conference(const char *guid);
+
+/**
+ * Increments the reference count for the conference.
+ *
+ * The reference to the conference should be released 
+ * by calling nm_release_conference
+ *
+ * @param conference	The conference to reference
+ */
+void nm_conference_add_ref(NMConference * conference);
+
+/**
+ * Releases the resources associated with the conference 
+ * if there are no more references to it, otherwise just
+ * decrements the reference count.
+ *
+ * @param conf		The conference to release
+ *
+ */
+void nm_release_conference(NMConference * conf);
+
+/**
+ * Set the GUID for the conference.  
+ *
+ * @param conference	The conference
+ * @param guid			The new conference GUID
+ *
+ */
+void nm_conference_set_guid(NMConference * conference, const char *guid);
+
+/**
+ * Return the GUID for the conference.  
+ *
+ * @param conference	The conference
+ *
+ * @return				The GUID for the conference
+ */
+const char *nm_conference_get_guid(NMConference * conference);
+
+/**
+ * Add a participant to the conference.
+ *
+ * @param conference	The conference
+ * @param user_record	The user record to add as a participant
+ *
+ * @return				
+ */
+void nm_conference_add_participant(NMConference * conference,
+								   NMUserRecord * user_record);
+
+/**
+ * Remove a participant to the conference.
+ *
+ * @param conference	The conference
+ * @param dn			The dn of the participant to remove
+ *
+ */
+void nm_conference_remove_participant(NMConference * conference, const char *dn);
+
+/**
+ * Return the total number of participants in the conference.
+ *
+ * @param conference	The conference
+ *
+ * @return				The number of participants for the conference
+ *
+ */
+int nm_conference_get_participant_count(NMConference * conference);
+
+/**
+ * Return a participant given an index.
+ *
+ * @param conference	The conference
+ * @param index			The index of the participant to get
+ *
+ * @return				The participant or NULL if the index is out of range.
+ *
+ */
+NMUserRecord *nm_conference_get_participant(NMConference * conference, int index);
+
+/**
+ * Check to see if the conference has been instantiated
+ *
+ * @param conference	The conference
+ *
+ * @return				TRUE if the conference has been instantiated, 
+ *						FALSE otherwise.
+ *
+ */
+gboolean nm_conference_is_instantiated(NMConference * conf);
+
+/**
+ * Set the flags for the conference.
+ *
+ * @param conference	The conference
+ * @param flags			The conference flags.
+ *
+ */
+void nm_conference_set_flags(NMConference * conference, guint32 flags);
+
+/**
+ * Set the user defined data for the conference.
+ *
+ * @param conference	The conference
+ * @param data			User defined data
+ *
+ */
+void nm_conference_set_data(NMConference * conference, gpointer data);
+
+/**
+ * Get the user defined data for the conference.
+ *
+ * @param conference	The conference
+ *
+ * @return				The data if it has been set, NULL otherwise.
+ *
+ */
+gpointer nm_conference_get_data(NMConference * conference);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/novell/nmconn.c	Sat Apr 17 13:55:28 2004 +0000
@@ -0,0 +1,631 @@
+/*
+ * nmconn.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 <glib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+#include "nmconn.h"
+
+#ifdef _WIN32
+#include <windows.h>
+#endif
+
+#define NO_ESCAPE(ch) ((ch == 0x20) || (ch >= 0x30 && ch <= 0x39) || \
+					(ch >= 0x41 && ch <= 0x5a) || (ch >= 0x61 && ch <= 0x7a))
+
+/* Read data from conn until the end of a line */
+static NMERR_T
+read_line(NMConn * conn, char *buff, int len)
+{
+	NMERR_T rc = NM_OK;
+	int total_bytes = 0;
+
+	while ((rc == NM_OK) && (total_bytes < (len - 1))) {
+		rc = nm_read_all(conn, &buff[total_bytes], 1);
+		if (rc == NM_OK) {
+			total_bytes += 1;
+			if (buff[total_bytes - 1] == '\n') {
+				break;
+			}
+		}
+	}
+	buff[total_bytes] = '\0';
+
+	return rc;
+}
+
+static char *
+url_escape_string(char *src)
+{
+	guint32 escape = 0;
+	char *p;
+	char *q;
+	char *encoded = NULL;
+	int ch;
+
+	static const char hex_table[16] = "0123456789abcdef";
+
+	if (src == NULL) {
+		return NULL;
+	}
+
+	/* Find number of chars to escape */
+	for (p = src; *p != '\0'; p++) {
+		ch = (guchar) *p;
+		if (!NO_ESCAPE(ch)) {
+			escape++;
+		}
+	}
+
+	encoded = g_malloc((p - src) + (escape * 2) + 1);
+
+	/* Escape the string */
+	for (p = src, q = encoded; *p != '\0'; p++) {
+		ch = (guchar) * p;
+		if (NO_ESCAPE(ch)) {
+			if (ch != 0x20) {
+				*q = ch;
+				q++;
+			} else {
+				*q = '+';
+				q++;
+			}
+		} else {
+			*q = '%';
+			q++;
+			
+			*q = hex_table[ch >> 4];
+			q++;
+			
+			*q = hex_table[ch & 15];
+			q++;
+		}
+	}
+	*q = '\0';
+
+	return encoded;
+}
+
+static char *
+encode_method(guint8 method)
+{
+	char *str;
+
+	switch (method) {
+		case NMFIELD_METHOD_EQUAL:
+			str = "G";
+			break;
+		case NMFIELD_METHOD_UPDATE:
+			str = "F";
+			break;
+		case NMFIELD_METHOD_GTE:
+			str = "E";
+			break;
+		case NMFIELD_METHOD_LTE:
+			str = "D";
+			break;
+		case NMFIELD_METHOD_NE:
+			str = "C";
+			break;
+		case NMFIELD_METHOD_EXIST:
+			str = "B";
+			break;
+		case NMFIELD_METHOD_NOTEXIST:
+			str = "A";
+			break;
+		case NMFIELD_METHOD_SEARCH:
+			str = "9";
+			break;
+		case NMFIELD_METHOD_MATCHBEGIN:
+			str = "8";
+			break;
+		case NMFIELD_METHOD_MATCHEND:
+			str = "7";
+			break;
+		case NMFIELD_METHOD_NOT_ARRAY:
+			str = "6";
+			break;
+		case NMFIELD_METHOD_OR_ARRAY:
+			str = "5";
+			break;
+		case NMFIELD_METHOD_AND_ARRAY:
+			str = "4";
+			break;
+		case NMFIELD_METHOD_DELETE_ALL:
+			str = "3";
+			break;
+		case NMFIELD_METHOD_DELETE:
+			str = "2";
+			break;
+		case NMFIELD_METHOD_ADD:
+			str = "1";
+			break;
+		default:					/* NMFIELD_METHOD_VALID */
+			str = "0";
+			break;
+	}
+	
+	return str;
+}
+
+int
+nm_tcp_write(NMConn * conn, const void *buff, int len)
+{
+	if (conn == NULL || buff == NULL)
+		return -1;
+
+	if (!conn->use_ssl)
+		return (write(conn->fd, buff, len));
+	else if (conn->ssl_conn && conn->ssl_conn->write)
+		return (conn->ssl_conn->write(conn->ssl_conn->data, buff, len));
+	else
+		return -1;
+}
+
+int
+nm_tcp_read(NMConn * conn, void *buff, int len)
+{
+	if (conn == NULL || buff == NULL)
+		return -1;
+
+	if (!conn->use_ssl)
+		return (read(conn->fd, buff, len));
+	else if (conn->ssl_conn && conn->ssl_conn->read)
+		return (conn->ssl_conn->read(conn->ssl_conn->data, buff, len));
+	else
+		return -1;
+}
+
+NMERR_T
+nm_read_all(NMConn * conn, char *buff, int len)
+{
+	NMERR_T rc = NM_OK;
+	int bytes_left = len;
+	int bytes_read;
+	int total_bytes = 0;
+	int retry = 10;
+
+	if (conn == NULL || buff == NULL)
+		return NMERR_BAD_PARM;
+
+	/* Keep reading until buffer is full */
+	while (bytes_left) {
+		bytes_read = nm_tcp_read(conn, &buff[total_bytes], bytes_left);
+		if (bytes_read > 0) {
+			bytes_left -= bytes_read;
+			total_bytes += bytes_read;
+		} else {
+			if (errno == EAGAIN) {
+				if (--retry == 0) {
+					rc = NMERR_TCP_READ;
+					break;
+				}
+#ifdef _WIN32
+				Sleep(1000);
+#else
+				usleep(1000);
+#endif
+			} else {
+				rc = NMERR_TCP_READ;
+				break;
+			}
+		}
+	}
+	return rc;
+}
+
+NMERR_T
+nm_write_fields(NMConn * conn, NMField * fields)
+{
+	NMERR_T rc = NM_OK;
+	NMField *field;
+	char *value = NULL;
+	char *method = NULL;
+	char buffer[512];
+	int ret;
+	int bytes_to_send;
+	int val = 0;
+
+	if (conn == NULL || fields == NULL) {
+		return NMERR_BAD_PARM;
+	}
+
+	/* Format each field as valid "post" data and write it out */
+	for (field = fields; (rc == NM_OK) && (field->tag); field++) {
+
+		/* We don't currently handle binary types */
+		if (field->method == NMFIELD_METHOD_IGNORE ||
+			field->type == NMFIELD_TYPE_BINARY) {
+			continue;
+		}
+
+		/* Write the field tag */
+		bytes_to_send = g_snprintf(buffer, sizeof(buffer), "&tag=%s", field->tag);
+		ret = nm_tcp_write(conn, buffer, bytes_to_send);
+		if (ret < 0) {
+			rc = NMERR_TCP_WRITE;
+		}
+
+		/* Write the field method */
+		if (rc == NM_OK) {
+			method = encode_method(field->method);
+			bytes_to_send = g_snprintf(buffer, sizeof(buffer), "&cmd=%s", method);
+			ret = nm_tcp_write(conn, buffer, bytes_to_send);
+			if (ret < 0) {
+				rc = NMERR_TCP_WRITE;
+			}
+		}
+
+		/* Write the field value */
+		if (rc == NM_OK) {
+			switch (field->type) {
+				case NMFIELD_TYPE_UTF8:
+				case NMFIELD_TYPE_DN:
+					
+					value = url_escape_string((char *) field->value);
+					bytes_to_send = g_snprintf(buffer, sizeof(buffer),
+											   "&val=%s", value);
+					ret = nm_tcp_write(conn, buffer, bytes_to_send);
+					if (ret < 0) {
+						rc = NMERR_TCP_WRITE;
+					}
+					
+					g_free(value);
+					
+					break;
+					
+				case NMFIELD_TYPE_ARRAY:
+				case NMFIELD_TYPE_MV:
+					
+					val = nm_count_fields((NMField *) field->value);
+					bytes_to_send = g_snprintf(buffer, sizeof(buffer),
+											   "&val=%u", val);
+					ret = nm_tcp_write(conn, buffer, bytes_to_send);
+					if (ret < 0) {
+						rc = NMERR_TCP_WRITE;
+					}
+					
+					break;
+					
+				default:
+					
+					bytes_to_send = g_snprintf(buffer, sizeof(buffer),
+											   "&val=%u", field->value);
+					ret = nm_tcp_write(conn, buffer, bytes_to_send);
+					if (ret < 0) {
+						rc = NMERR_TCP_WRITE;
+					}
+					
+					break;
+			}
+		}
+		
+		/* Write the field type */
+		if (rc == NM_OK) {
+			bytes_to_send = g_snprintf(buffer, sizeof(buffer),
+									   "&type=%u", field->type);
+			ret = nm_tcp_write(conn, buffer, bytes_to_send);
+			if (ret < 0) {
+				rc = NMERR_TCP_WRITE;
+			}
+		}
+
+		/* If the field is a sub array then post its fields */
+		if (rc == NM_OK && val > 0) {
+			if (field->type == NMFIELD_TYPE_ARRAY ||
+				field->type == NMFIELD_TYPE_MV) {
+
+				rc = nm_write_fields(conn, (NMField *) field->value);
+
+			}
+		}
+	}
+
+	return rc;
+}
+
+NMERR_T
+nm_send_request(NMConn * conn, char *cmd, NMField * fields, NMRequest ** req)
+{
+	NMERR_T rc = NM_OK;
+	char buffer[512];
+	int bytes_to_send;
+	int ret;
+	NMField *request = NULL;
+
+	if (conn == NULL || cmd == NULL)
+		return NMERR_BAD_PARM;
+
+	/* Write the post */
+	bytes_to_send = g_snprintf(buffer, sizeof(buffer),
+							   "POST /%s HTTP/1.0\r\n", cmd);
+	ret = nm_tcp_write(conn, buffer, bytes_to_send);
+	if (ret < 0) {
+		rc = NMERR_TCP_WRITE;
+	}
+
+	/* Write headers */
+	if (rc == NM_OK) {
+		if (strcmp("login", cmd) == 0) {
+			bytes_to_send = g_snprintf(buffer, sizeof(buffer),
+									   "Host: %s:%d\r\n\r\n", conn->addr, conn->port);
+			ret = nm_tcp_write(conn, buffer, bytes_to_send);
+			if (ret < 0) {
+				rc = NMERR_TCP_WRITE;
+			}
+		} else {
+			bytes_to_send = g_snprintf(buffer, sizeof(buffer), "\r\n");
+			ret = nm_tcp_write(conn, buffer, bytes_to_send);
+			if (ret < 0) {
+				rc = NMERR_TCP_WRITE;
+			}
+		}
+	}
+
+	/* Add the transaction id to the request fields */
+	if (rc == NM_OK) {
+		request = nm_copy_field_array(fields);
+		if (request) {
+			char *str = g_strdup_printf("%d", ++(conn->trans_id));
+			
+			request = nm_add_field(request, NM_A_SZ_TRANSACTION_ID, 0,
+								   NMFIELD_METHOD_VALID, 0,
+								   (guint32) str, NMFIELD_TYPE_UTF8);
+		}
+	}
+
+	/* Send the request to the server */
+	if (rc == NM_OK) {
+		rc = nm_write_fields(conn, request);
+	}
+
+	/* Write the CRLF to terminate the data */
+	if (rc == NM_OK) {
+		ret = nm_tcp_write(conn, "\r\n", strlen("\r\n"));
+		if (ret < 0) {
+			rc = NMERR_TCP_WRITE;
+		}
+	}
+
+	/* Create a request struct and return it */
+	if (rc == NM_OK) {
+		*req = nm_create_request(cmd, conn->trans_id, time(0));
+	}
+
+	if (request != NULL) {
+		nm_free_fields(&request);
+	}
+
+	return rc;
+}
+
+NMERR_T
+nm_read_header(NMConn * conn)
+{
+	NMERR_T rc = NM_OK;
+	char buffer[512];
+	char *ptr = NULL;
+	int i;
+	char rtn_buf[8];
+	int rtn_code = 0;
+
+	if (conn == NULL)
+		return NMERR_BAD_PARM;
+
+	*buffer = '\0';
+	rc = read_line(conn, buffer, sizeof(buffer));
+	if (rc == NM_OK) {
+
+		/* Find the return code */
+		ptr = strchr(buffer, ' ');
+		if (ptr != NULL) {
+			ptr++;
+
+			i = 0;
+			while (isdigit(*ptr) && (i < 3)) {
+				rtn_buf[i] = *ptr;
+				i++;
+				ptr++;
+			}
+			rtn_buf[i] = '\0';
+			
+			if (i > 0)
+				rtn_code = atoi(rtn_buf);
+		}
+	}
+
+	/* Finish reading header, in the future we might want to do more processing here */
+	/* TODO: handle more general redirects in the future */
+	while ((rc == NM_OK) && (strcmp(buffer, "\r\n") != 0)) {
+		rc = read_line(conn, buffer, sizeof(buffer));
+	}
+
+	if (rc == NM_OK && rtn_code == 301) {
+		conn->use_ssl = TRUE;
+		conn->redirect = TRUE;
+		rc = NMERR_SSL_REDIRECT;
+	}
+
+	return rc;
+}
+
+NMERR_T
+nm_read_fields(NMConn * conn, int count, NMField ** fields)
+{
+	NMERR_T rc = NM_OK;
+	guint8 type;
+	guint8 method;
+	guint32 val;
+	char tag[64];
+	NMField *sub_fields = NULL;
+	char *str = NULL;
+
+	if (conn == NULL || fields == NULL)
+		return NMERR_BAD_PARM;
+
+	do {
+		if (count !=  -1) {
+			count--;
+		}
+
+		/* Read the field type, method, and tag */
+		rc = nm_read_all(conn, &type, sizeof(type));
+		if (rc != NM_OK || type == 0)
+			break;
+
+		rc = nm_read_all(conn, &method, sizeof(method));
+		if (rc != NM_OK)
+			break;
+
+		rc = nm_read_all(conn, (char *) &val, sizeof(val));
+		if (rc != NM_OK)
+			break;
+
+		if (val > sizeof(tag)) {
+			rc = NMERR_PROTOCOL;
+			break;
+		}
+
+		rc = nm_read_all(conn, tag, val);
+		if (rc != NM_OK)
+			break;
+
+		if (type == NMFIELD_TYPE_MV || type == NMFIELD_TYPE_ARRAY) {
+
+			/* Read the subarray (first read the number of items in the array) */
+			rc = nm_read_all(conn, (char *) &val, sizeof(val));
+			if (rc != NM_OK)
+				break;
+
+			if (val > 0) {
+				rc = nm_read_fields(conn, val, &sub_fields);
+				if (rc != NM_OK)
+					break;
+			}
+
+			*fields = nm_add_field(*fields, tag, 0, method, 0,
+								   (guint32) sub_fields, type);
+
+			sub_fields = NULL;
+
+		} else if (type == NMFIELD_TYPE_UTF8 || type == NMFIELD_TYPE_DN) {
+
+			/* Read the string (first read the length) */
+			rc = nm_read_all(conn, (char *) &val, sizeof(val));
+			if (rc != NM_OK)
+				break;
+
+			if (val > 0) {
+				str = g_new0(char, val + 1);
+
+				rc = nm_read_all(conn, str, val);
+				if (rc != NM_OK)
+					break;
+			}
+
+			*fields = nm_add_field(*fields, tag, 0, method, 0,
+								   (guint32) str, type);
+			str = NULL;
+		} else {
+
+			/* Read the numerical value */
+			rc = nm_read_all(conn, (char *) &val, sizeof(val));
+			if (rc != NM_OK)
+				break;
+
+			*fields = nm_add_field(*fields, tag, 0, method, 0, val, type);
+		}
+
+	} while ((type != 0) && (count != 0));
+
+
+	if (str != NULL) {
+		g_free(str);
+	}
+
+	if (sub_fields != NULL) {
+		nm_free_fields(&sub_fields);
+	}
+
+	return rc;
+}
+
+void
+nm_conn_add_request_item(NMConn * conn, NMRequest * request)
+{
+	if (conn == NULL || request == NULL)
+		return;
+
+	nm_request_add_ref(request);
+	conn->requests = g_slist_append(conn->requests, request);
+}
+
+void
+nm_conn_remove_request_item(NMConn * conn, NMRequest * request)
+{
+	if (conn == NULL || request == NULL)
+		return;
+
+	conn->requests = g_slist_remove(conn->requests, request);
+	nm_release_request(request);
+}
+
+NMRequest *
+nm_conn_find_request(NMConn * conn, int trans_id)
+{
+	NMRequest *req = NULL;
+	GSList *itr = NULL;
+
+	if (conn == NULL)
+		return NULL;
+
+	itr = conn->requests;
+	while (itr) {
+		req = (NMRequest *) itr->data;
+		if (req != NULL && nm_request_get_trans_id(req) == trans_id) {
+			return req;
+		}
+		itr = g_slist_next(itr);
+	}
+	return NULL;
+}
+
+const char *
+nm_conn_get_addr(NMConn * conn)
+{
+	if (conn == NULL)
+		return NULL;
+	else
+		return conn->addr;
+}
+
+int
+nm_conn_get_port(NMConn * conn)
+{
+	if (conn == NULL)
+		return -1;
+	else
+		return conn->port;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/novell/nmconn.h	Sat Apr 17 13:55:28 2004 +0000
@@ -0,0 +1,204 @@
+/*
+ * nmconn.h
+ *
+ * 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.
+ *
+ */
+
+#ifndef __NM_CONN_H__
+#define __NM_CONN_H__
+
+typedef struct _NMConn NMConn;
+typedef struct _NMSSLConn NMSSLConn;
+
+#include "nmfield.h"
+#include "nmuser.h"
+
+typedef int (*nm_ssl_read_cb) (gpointer ssl_data, void *buff, int len);
+typedef int (*nm_ssl_write_cb) (gpointer ssl_data, const void *buff, int len);
+
+struct _NMConn
+{
+
+	/* The address of the server that we are connecting to. */
+	char *addr;
+
+	/* The port that we are connecting to. */
+	int port;
+
+	/* The file descriptor of the socket for the connection. */
+	int fd;
+
+	/* The transaction counter. */
+	int trans_id;
+
+	/* A list of requests currently awaiting a response. */
+	GSList *requests;
+
+	/* Are we connected? TRUE if so, FALSE if not. */
+	gboolean connected;
+
+	/* Are we running in secure mode? */
+	gboolean use_ssl;
+
+	/* Have we been redirected? */
+	gboolean redirect;
+
+	/* SSL connection  */
+	NMSSLConn *ssl_conn;
+
+};
+
+struct _NMSSLConn
+{
+
+	/*  Data to pass to the callbacks */
+	gpointer data;
+
+	/* Callbacks for reading/writing */
+	nm_ssl_read_cb read;
+	nm_ssl_write_cb write;
+
+};
+
+/**
+ * Write len bytes from the given buffer.
+ *
+ * @param conn	The connection to write to.
+ * @param buff	The buffer to write from.
+ * @param len	The number of bytes to write.
+ *
+ * @return		The number of bytes written.
+ */
+int nm_tcp_write(NMConn * conn, const void *buff, int len);
+
+/**
+ * Read at most len bytes into the given buffer.
+ *
+ * @param conn	The connection to read from.
+ * @param buff	The buffer to write to.
+ * @param len	The maximum number of bytes to read.
+ *
+ * @return		The number of bytes read.
+ */
+int nm_tcp_read(NMConn * conn, void *buff, int len);
+
+/**
+ * Read exactly len bytes into the given buffer.
+ *
+ * @param conn	The connection to read from.
+ * @param buff	The buffer to write to.
+ * @param len	The number of bytes to read.
+ *
+ * @return		NM_OK on success, NMERR_TCP_READ if read fails.
+ */
+NMERR_T nm_read_all(NMConn * conn, char *buf, int len);
+
+/**
+ * Dispatch a request to the server.
+ *
+ * @param conn		The connection.
+ * @param cmd		The request to dispatch.
+ * @param fields	The field list for the request.
+ * @param req		The request. Should be freed with nm_release_request.
+ *
+ * @return			NM_OK on success.
+ */
+NMERR_T nm_send_request(NMConn * conn, char *cmd, NMField * fields,
+						NMRequest ** req);
+
+/**
+ * Write out the given field list.
+ *
+ * @param conn		The connection to write to.
+ * @param fields	The field list to write.
+ *
+ * @return			NM_OK on success.
+ */
+NMERR_T nm_write_fields(NMConn * conn, NMField * fields);
+
+/**
+ * Read the headers for a response.
+ *
+ * @param conn		The connection to read from.
+ *
+ * @return			NM_OK on success.
+ */
+NMERR_T nm_read_header(NMConn * conn);
+
+/**
+ * Read a field list from the connection.
+ *
+ * @param conn		The connection to read from.
+ * @param count		The maximum number of fields to read (or -1 for no max).
+ * @param fields	The field list. This is an out param. It
+ *					should be freed by calling nm_free_fields
+ *					when finished.
+ *
+ * @return			NM_OK on success.
+ */
+NMERR_T nm_read_fields(NMConn * conn, int count, NMField ** fields);
+
+/**
+ * Add a request to the connections request list.
+ *
+ * @param conn		The connection.
+ * @param request	The request to add to the list.
+ */
+void nm_conn_add_request_item(NMConn * conn, NMRequest * request);
+
+/**
+ * Remove a request from the connections list.
+ *
+ * @param conn		The connection.
+ * @param request	The request to remove from the list.
+ */
+void nm_conn_remove_request_item(NMConn * conn, NMRequest * request);
+
+/**
+ * Find the request with the given transaction id in the connections
+ * request list.
+ *
+ * @param conn		The connection.
+ * @param trans_id	The transaction id of the request to return.
+ *
+ * @return 			The request, or NULL if a matching request is not
+ *					found.
+ */
+NMRequest *nm_conn_find_request(NMConn * conn, int trans_id);
+
+/**
+ * Get the server address for the connection.
+ *
+ * @param conn		The connection.
+ *
+ * @return 			The server address for the connection.
+ *
+ */
+const char *nm_conn_get_addr(NMConn * conn);
+
+/**
+ * Get the port for the connection.
+ *
+ * @param conn		The connection.
+ *
+ * @return 			The port that we are connected to.
+ */
+int nm_conn_get_port(NMConn * conn);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/novell/nmcontact.c	Sat Apr 17 13:55:28 2004 +0000
@@ -0,0 +1,962 @@
+/*
+ * nmcontact.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 <glib.h>
+#include <string.h>
+#include "nmcontact.h"
+#include "nmfield.h"
+#include "nmuser.h"
+
+struct _NMContact
+{
+	int id;
+	int parent_id;
+	int seq;
+	char *dn;
+	char *display_name;
+	NMUserRecord *user_record;
+	gpointer data;
+	int ref_count;
+};
+
+struct _NMFolder
+{
+	int id;
+	int seq;
+	char *name;
+	GSList *folders;
+	GSList *contacts;
+	int ref_count;
+};
+
+static int count = 0;
+
+static void _release_folder_contacts(NMFolder * folder);
+static void _release_folder_folders(NMFolder * folder);
+static void _add_contacts(NMUser * user, NMFolder * folder, NMField * fields);
+static void _add_folders(NMFolder * root, NMField * fields);
+
+/*********************************************************************
+ * Contact API 
+ *********************************************************************/
+
+NMContact *
+nm_create_contact()
+{
+	NMContact *contact = g_new0(NMContact, 1);
+
+	contact->ref_count = 1;
+
+	gaim_debug(GAIM_DEBUG_INFO, "novell", "Creating contact, total=%d\n",
+			   count++);
+
+	return contact;
+}
+
+/*
+ * This creates a contact for the contact list. The 
+ * field array that is passed in should be a 
+ * NM_A_FA_CONTACT array.
+ *
+ */
+NMContact *
+nm_create_contact_from_fields(NMField * fields)
+{
+	NMContact *contact;
+	NMField *field;
+
+	if ( fields == NULL || fields->tag == NULL || fields->value == 0 ||
+		 strcmp(fields->tag, NM_A_FA_CONTACT) )
+	{
+		return NULL;
+	}
+
+	contact = nm_create_contact();
+
+	if ((field = nm_locate_field(NM_A_SZ_OBJECT_ID, (NMField *) fields->value))) {
+
+		if (field->value)
+			contact->id = atoi((char *) field->value);
+
+	}
+
+	if ((field = nm_locate_field(NM_A_SZ_PARENT_ID, (NMField *) fields->value))) {
+
+		if (field->value)
+			contact->parent_id = atoi((char *) field->value);
+
+	}
+
+	if ((field =
+		 nm_locate_field(NM_A_SZ_SEQUENCE_NUMBER, (NMField *) fields->value))) {
+		
+		if (field->value)
+			contact->seq = atoi((char *) field->value);
+
+	}
+
+	if ((field =
+		 nm_locate_field(NM_A_SZ_DISPLAY_NAME, (NMField *) fields->value))) {
+		
+		if (field->value)
+			contact->display_name = g_strdup((char *) field->value);
+
+	}
+
+	if ((field = nm_locate_field(NM_A_SZ_DN, (NMField *) fields->value))) {
+
+		if (field->value)
+			contact->dn = g_strdup((char *) field->value);
+
+	}
+
+	return contact;
+}
+
+void
+nm_contact_update_list_properties(NMContact * contact, NMField * fields)
+{
+	NMField *field;
+
+	if (contact == NULL || fields == NULL || fields->value == 0)
+		return;
+
+	if ((field = nm_locate_field(NM_A_SZ_OBJECT_ID, (NMField *) fields->value))) {
+		
+		if (field->value)
+			contact->id = atoi((char *)field->value);
+
+	}
+
+	if ((field = nm_locate_field(NM_A_SZ_PARENT_ID, (NMField *) fields->value))) {
+		
+		if (field->value)
+			contact->parent_id = atoi((char *) field->value);
+
+	}
+
+	if ((field =
+		 nm_locate_field(NM_A_SZ_SEQUENCE_NUMBER, (NMField *) fields->value))) {
+
+		if (field->value)
+			contact->seq = atoi((char *) field->value);
+
+	}
+
+	if ((field =
+		 nm_locate_field(NM_A_SZ_DISPLAY_NAME, (NMField *) fields->value))) {
+		
+		if (field->value) {
+			if (contact->display_name)
+				g_free(contact->display_name);
+
+			contact->display_name = g_strdup((char *) field->value);
+		}
+
+	}
+
+	if ((field = nm_locate_field(NM_A_SZ_DN, (NMField *) fields->value))) {
+
+		if (field->value) {
+			if (contact->dn)
+				g_free(contact->dn);
+			
+			contact->dn = g_strdup((char *) field->value);
+		}
+
+	}
+}
+
+NMField *
+nm_contact_to_fields(NMContact * contact)
+{
+	NMField *fields = NULL;
+
+	if (contact == NULL)
+		return NULL;
+
+	fields = nm_add_field(fields, NM_A_SZ_OBJECT_ID, 0, NMFIELD_METHOD_VALID, 0,
+						  (guint32) g_strdup_printf("%d", contact->id),
+						  NMFIELD_TYPE_UTF8);
+
+	fields = nm_add_field(fields, NM_A_SZ_PARENT_ID, 0, NMFIELD_METHOD_VALID, 0,
+						  (guint32) g_strdup_printf("%d", contact->parent_id),
+						  NMFIELD_TYPE_UTF8);
+
+	fields =
+		nm_add_field(fields, NM_A_SZ_SEQUENCE_NUMBER, 0, NMFIELD_METHOD_VALID, 0,
+					 (guint32) g_strdup_printf("%d", contact->seq),
+					 NMFIELD_TYPE_UTF8);
+
+	if (contact->display_name != NULL) {
+		fields =
+			nm_add_field(fields, NM_A_SZ_DISPLAY_NAME, 0, NMFIELD_METHOD_VALID, 0,
+						 (guint32) g_strdup(contact->display_name),
+						 NMFIELD_TYPE_UTF8);
+	}
+
+	if (contact->dn != NULL) {
+		fields = nm_add_field(fields, NM_A_SZ_DN, 0, NMFIELD_METHOD_VALID, 0,
+							  (guint32) g_strdup(contact->dn), NMFIELD_TYPE_UTF8);
+	}
+
+	return fields;
+}
+
+void
+nm_contact_add_ref(NMContact * contact)
+{
+	if (contact)
+		contact->ref_count++;
+}
+
+void
+nm_release_contact(NMContact * contact)
+{
+	if (contact == NULL)
+		return;
+
+	if (--(contact->ref_count) == 0) {
+
+		gaim_debug(GAIM_DEBUG_INFO, "novell",
+				   "Releasing contact, total=%d\n", --count);
+
+		if (contact->display_name) {
+			g_free(contact->display_name);
+		}
+
+		if (contact->dn) {
+			g_free(contact->dn);
+		}
+
+		if (contact->user_record) {
+			nm_release_user_record(contact->user_record);
+		}
+
+		g_free(contact);
+	}
+
+}
+
+const char *
+nm_contact_get_display_name(NMContact * contact)
+{
+	if (contact == NULL)
+		return NULL;
+
+	if (contact->user_record != NULL && contact->display_name == NULL) {
+		const char *full_name, *lname, *fname, *cn, *display_id;
+
+		full_name = nm_user_record_get_full_name(contact->user_record);
+		fname = nm_user_record_get_first_name(contact->user_record);
+		lname = nm_user_record_get_last_name(contact->user_record);
+		cn = nm_user_record_get_userid(contact->user_record);
+		display_id = nm_user_record_get_display_id(contact->user_record);
+
+		/* Try to build a display name. */
+		if (full_name) {
+
+			contact->display_name = g_strdup(full_name);
+
+		} else if (fname && lname) {
+
+			contact->display_name = g_strdup_printf("%s %s", fname, lname);
+
+		} else {
+
+			/* If auth attribute is set use it */
+			if (nm_user_record_get_auth_attr(contact->user_record) && 
+				display_id != NULL)	{
+
+				contact->display_name = g_strdup(display_id);
+
+			} else {
+
+				/* Use CN or display id */
+				if (cn) {
+
+					contact->display_name = g_strdup(cn);
+
+				} else if (display_id) {
+
+					contact->display_name = g_strdup(display_id);
+
+				}
+
+			}
+
+		}
+	}
+
+	return contact->display_name;
+}
+
+void
+nm_contact_set_display_name(NMContact * contact, const char *display_name)
+{
+	if (contact == NULL)
+		return;
+
+	if (contact->display_name) {
+		g_free(contact->display_name);
+		contact->display_name = NULL;
+	}
+	
+	if (display_name)
+		contact->display_name = g_strdup(display_name);
+}
+
+void
+nm_contact_set_dn(NMContact * contact, const char *dn)
+{
+	if (contact == NULL)
+		return;
+
+	if (contact->dn) {
+		g_free(contact->dn);
+		contact->dn = NULL;
+	}
+
+	if (dn)
+		contact->dn = g_strdup(dn);
+}
+
+const char *
+nm_contact_get_dn(NMContact * contact)
+{
+	if (contact == NULL)
+		return NULL;
+
+	return contact->dn;
+}
+
+gpointer
+nm_contact_get_data(NMContact * contact)
+{
+	if (contact == NULL)
+		return NULL;
+
+	return contact->data;
+}
+
+int
+nm_contact_get_id(NMContact * contact)
+{
+	if (contact == NULL)
+		return -1;
+
+	return contact->id;
+}
+
+int
+nm_contact_get_parent_id(NMContact * contact)
+{
+	if (contact == NULL)
+		return -1;
+
+	return contact->parent_id;
+}
+
+void
+nm_contact_set_data(NMContact * contact, gpointer data)
+{
+	if (contact == NULL)
+		return;
+
+	contact->data = data;
+}
+
+void
+nm_contact_set_user_record(NMContact * contact, NMUserRecord * user_record)
+{
+	if (contact == NULL)
+		return;
+
+	if (contact->user_record) {
+		nm_release_user_record(contact->user_record);
+	}
+
+	nm_user_record_add_ref(user_record);
+	contact->user_record = user_record;
+}
+
+NMUserRecord *
+nm_contact_get_user_record(NMContact * contact)
+{
+	if (contact == NULL)
+		return NULL;
+
+	return contact->user_record;
+}
+
+const char *
+nm_contact_get_userid(NMContact * contact)
+{
+	NMUserRecord *user_record;
+	const char *userid = NULL;
+
+	if (contact == NULL)
+		return NULL;
+
+	user_record = nm_contact_get_user_record(contact);
+	if (user_record) {
+		userid = nm_user_record_get_userid(user_record);
+	}
+
+	return userid;
+}
+
+const char *
+nm_contact_get_display_id(NMContact * contact)
+{
+	NMUserRecord *user_record;
+	const char *id = NULL;
+
+	if (contact == NULL)
+		return NULL;
+
+	user_record = nm_contact_get_user_record(contact);
+	if (user_record) {
+		id = nm_user_record_get_display_id(user_record);
+	}
+
+	return id;
+}
+
+
+/*********************************************************************
+ * Folder API 
+ *********************************************************************/
+
+NMFolder *
+nm_create_folder(const char *name)
+{
+	NMFolder *folder = g_new0(NMFolder, 1);
+
+	if (name)
+		folder->name = g_strdup(name);
+
+	folder->ref_count = 1;
+
+	return folder;
+}
+
+NMFolder *
+nm_create_folder_from_fields(NMField * fields)
+{
+	NMField *field;
+	NMFolder *folder;
+
+	if (fields == NULL || fields->value == 0)
+		return NULL;
+
+	folder = g_new0(NMFolder, 1);
+
+	if ((field = nm_locate_field(NM_A_SZ_OBJECT_ID, (NMField *) fields->value))) {
+		
+		if (field->value)
+			folder->id = atoi((char *) field->value);
+	}
+
+	if ((field =
+		 nm_locate_field(NM_A_SZ_SEQUENCE_NUMBER, (NMField *) fields->value))) {
+		
+		if (field->value)
+			folder->seq = atoi((char *) field->value);
+	}
+
+	if ((field =
+		 nm_locate_field(NM_A_SZ_DISPLAY_NAME, (NMField *) fields->value))) {
+
+		if (field->value)
+			folder->name = g_strdup((char *) field->value);
+	}
+
+	folder->ref_count = 1;
+	return folder;
+}
+
+NMField *
+nm_folder_to_fields(NMFolder * folder)
+{
+	NMField *fields = NULL;
+
+	if (folder == NULL)
+		return NULL;
+
+	fields = nm_add_field(fields, NM_A_SZ_OBJECT_ID, 0, NMFIELD_METHOD_VALID, 0,
+						  (guint32) g_strdup_printf("%d", folder->id),
+						  NMFIELD_TYPE_UTF8);
+
+	fields = nm_add_field(fields, NM_A_SZ_PARENT_ID, 0, NMFIELD_METHOD_VALID, 0,
+						  (guint32) g_strdup("0"), NMFIELD_TYPE_UTF8);
+
+	fields = nm_add_field(fields, NM_A_SZ_TYPE, 0, NMFIELD_METHOD_VALID, 0,
+						  (guint32) g_strdup("1"), NMFIELD_TYPE_UTF8);
+
+	fields =
+		nm_add_field(fields, NM_A_SZ_SEQUENCE_NUMBER, 0, NMFIELD_METHOD_VALID, 0,
+					 (guint32) g_strdup_printf("%d", folder->seq),
+					 NMFIELD_TYPE_UTF8);
+
+	if (folder->name != NULL) {
+		fields =
+			nm_add_field(fields, NM_A_SZ_DISPLAY_NAME, 0, NMFIELD_METHOD_VALID, 0,
+						 (guint32) g_strdup(folder->name), NMFIELD_TYPE_UTF8);
+	}
+
+
+	return fields;
+}
+
+void
+nm_folder_update_list_properties(NMFolder * folder, NMField * fields)
+{
+	NMField *field;
+
+	if (folder == NULL || fields == NULL || fields->value == 0)
+		return;
+
+	if ((field = nm_locate_field(NM_A_SZ_OBJECT_ID, (NMField *) fields->value))) {
+		
+		if (field->value)
+			folder->id = atoi((char *) field->value);
+
+	}
+
+	if ((field =
+		 nm_locate_field(NM_A_SZ_SEQUENCE_NUMBER, (NMField *) fields->value))) {
+		
+		if (field->value)
+			folder->seq = atoi((char *) field->value);
+
+	}
+
+	if ((field =
+		 nm_locate_field(NM_A_SZ_DISPLAY_NAME, (NMField *) fields->value))) {
+
+		if (field->value) {
+			if (folder->name)
+				g_free(folder->name);
+			
+			folder->name = g_strdup((char *) field->value);
+		}
+
+	}
+
+}
+
+void
+nm_release_folder(NMFolder * folder)
+{
+	if (folder == NULL)
+		return;
+
+	if (--(folder->ref_count) == 0) {
+		if (folder->name) {
+			g_free(folder->name);
+		}
+
+		if (folder->folders) {
+			_release_folder_folders(folder);
+		}
+
+		if (folder->contacts) {
+			_release_folder_contacts(folder);
+		}
+
+		g_free(folder);
+	}
+}
+
+
+void
+nm_folder_add_ref(NMFolder * folder)
+{
+	if (folder)
+		folder->ref_count++;
+}
+
+int
+nm_folder_get_subfolder_count(NMFolder * folder)
+{
+	if (folder == NULL)
+		return 0;
+
+	if (folder->folders)
+		return g_slist_length(folder->folders);
+	else
+		return 0;
+}
+
+NMFolder *
+nm_folder_get_subfolder(NMFolder * folder, int index)
+{
+	if (folder == NULL)
+		return NULL;
+
+	if (folder->folders)
+		return (NMFolder *) g_slist_nth_data(folder->folders, index);
+	else
+		return NULL;
+}
+
+int
+nm_folder_get_contact_count(NMFolder * folder)
+{
+	if (folder == NULL)
+		return 0;
+
+	if (folder->contacts != NULL)
+		return g_slist_length(folder->contacts);
+	else
+		return 0;
+}
+
+NMContact *
+nm_folder_get_contact(NMFolder * folder, int index)
+{
+	if (folder == NULL)
+		return NULL;
+
+	if (folder->contacts)
+		return (NMContact *) g_slist_nth_data(folder->contacts, index);
+	else
+		return NULL;
+}
+
+const char *
+nm_folder_get_name(NMFolder * folder)
+{
+	if (folder == NULL)
+		return NULL;
+
+	return folder->name;
+}
+
+void
+nm_folder_set_name(NMFolder * folder, const char *name)
+{
+	if (folder == NULL || name == NULL)
+		return;
+
+	if (folder->name)
+		g_free(folder->name);
+
+	folder->name = g_strdup(name);
+}
+
+int
+nm_folder_get_id(NMFolder * folder)
+{
+	if (folder == NULL) {
+		return  -1;
+	}
+
+	return folder->id;
+}
+
+void
+nm_folder_add_folder_to_list(NMFolder * root, NMFolder * folder)
+{
+	GSList *node;
+
+	if (root == NULL || folder == NULL)
+		return;
+
+	node = root->folders;
+	while (node) {
+		if (folder->seq <= ((NMFolder *) node->data)->seq) {
+			nm_folder_add_ref(folder);
+			root->folders = g_slist_insert_before(root->folders, node, folder);
+			break;
+		}
+		node = g_slist_next(node);
+	}
+	if (node == NULL) {
+		nm_folder_add_ref(folder);
+		root->folders = g_slist_append(root->folders, folder);
+	}
+}
+
+void
+nm_folder_remove_contact(NMFolder * folder, NMContact * contact)
+{
+	if (folder == NULL || contact == NULL)
+		return;
+
+	GSList *node = folder->contacts;
+
+	while (node) {
+		if (contact->id == ((NMContact *) (node->data))->id) {
+			folder->contacts = g_slist_remove(folder->contacts, node->data);
+			nm_release_contact(contact);
+			break;
+		}
+		node = g_slist_next(node);
+	}
+}
+
+void
+nm_folder_add_contact_to_list(NMFolder * root_folder, NMContact * contact)
+{
+	GSList *node = NULL;
+	NMFolder *folder = root_folder;
+	
+	if (folder == NULL || contact == NULL)
+		return;
+
+	/* Find folder to add contact to */
+	if (contact->parent_id != 0) {
+		node = folder->folders;
+		while (node) {
+			folder = (NMFolder *) node->data;
+			if (contact->parent_id == folder->id) {
+				break;
+			}
+			folder = NULL;
+			node = g_slist_next(node);
+		}
+	}
+
+	/* Add contact to list */
+	if (folder) {
+		node = folder->contacts;
+		while (node) {
+			if (contact->seq <= ((NMContact *) (node->data))->seq) {
+				nm_contact_add_ref(contact);
+				folder->contacts =
+					g_slist_insert_before(folder->contacts, node, contact);
+				break;
+			}
+			node = g_slist_next(node);
+		}
+
+		if (node == NULL) {
+			nm_contact_add_ref(contact);
+			folder->contacts = g_slist_append(folder->contacts, contact);
+		}
+	}
+}
+
+void
+nm_folder_add_contacts_and_folders(NMUser * user, NMFolder * root,
+								   NMField * fields)
+{
+	/* Add the contacts and folders from the field array */
+	if (user && root && fields) {
+		_add_folders(root, fields);
+		_add_contacts(user, root, fields);
+	}
+}
+
+gpointer
+nm_folder_find_item_by_object_id(NMFolder * root_folder, int object_id)
+{
+	int cnt, cnt2, i, j;
+	gpointer item = NULL;
+	NMFolder *folder;
+	NMContact *contact;
+
+	if (root_folder == NULL)
+		return NULL;
+
+	/* Check all contacts for the top level folder */
+	cnt = nm_folder_get_contact_count(root_folder);
+	for (i = 0; i < cnt; i++) {
+		contact = nm_folder_get_contact(root_folder, i);
+		if (contact && (contact->id == object_id)) {
+			item = contact;
+			break;
+		}
+	}
+
+	/* If we haven't found the item yet, check the subfolders */
+	if (item == NULL) {
+		cnt = nm_folder_get_subfolder_count(root_folder);
+		for (i = 0; (i < cnt) && (item == NULL); i++) {
+			folder = nm_folder_get_subfolder(root_folder, i);
+
+			/* Check the id of this folder */
+			if (folder && (folder->id == object_id)) {
+				item = folder;
+				break;
+			}
+
+			/* Check all contacts for this folder */
+			cnt2 = nm_folder_get_contact_count(folder);
+			for (j = 0; j < cnt2; j++) {
+				contact = nm_folder_get_contact(folder, j);
+				if (contact && (contact->id == object_id)) {
+					item = contact;
+					break;
+				}
+			}
+		}
+	}
+
+	return item;
+}
+
+NMContact *
+nm_folder_find_contact_by_userid(NMFolder * folder, const char *userid)
+{
+	int cnt, i;
+	NMContact *tmp, *contact = NULL;
+
+	if (folder == NULL || userid == NULL)
+		return NULL;
+
+	cnt = nm_folder_get_contact_count(folder);
+	for (i = 0; i < cnt; i++) {
+		tmp = nm_folder_get_contact(folder, i);
+		if (tmp && nm_utf8_str_equal(nm_contact_get_userid(tmp), userid)) {
+			contact = tmp;
+			break;
+		}
+	}
+
+	return contact;
+}
+
+NMContact *
+nm_folder_find_contact(NMFolder * folder, const char *dn)
+{
+	int cnt, i;
+	NMContact *tmp, *contact = NULL;
+	
+	if (folder == NULL || dn == NULL)
+		return NULL;
+
+	cnt = nm_folder_get_contact_count(folder);
+	for (i = 0; i < cnt; i++) {
+		tmp = nm_folder_get_contact(folder, i);
+		if (tmp && nm_utf8_str_equal(nm_contact_get_dn(tmp), dn)) {
+			contact = tmp;
+			break;
+		}
+	}
+
+	return contact;
+}
+
+
+/*********************************************************************
+ * Utility functions
+ *********************************************************************/
+
+static void
+_release_folder_contacts(NMFolder * folder)
+{
+	GSList *cnode;
+	NMContact *contact;
+
+	for (cnode = folder->contacts; cnode; cnode = cnode->next) {
+		contact = cnode->data;
+		cnode->data = NULL;
+		nm_release_contact(contact);
+	}
+
+	g_slist_free(folder->contacts);
+	folder->contacts = NULL;
+}
+
+static void
+_release_folder_folders(NMFolder * folder)
+{
+	GSList *fnode;
+	NMFolder *subfolder;
+	
+	if (folder == NULL)
+		return;
+
+	for (fnode = folder->folders; fnode; fnode = fnode->next) {
+		subfolder = fnode->data;
+		fnode->data = NULL;
+		nm_release_folder(subfolder);
+	}
+
+	g_slist_free(folder->folders);
+	folder->folders = NULL;
+}
+
+static void
+_add_folders(NMFolder * root, NMField * fields)
+{
+	NMFolder *folder = NULL;
+	NMField *locate = NULL;
+
+	locate = nm_locate_field(NM_A_FA_FOLDER, fields);
+	while (locate != NULL) {
+
+		/* Create a new folder */
+		folder = nm_create_folder_from_fields(locate);
+
+		/* Add subfolder to roots folder list */
+		nm_folder_add_folder_to_list(root, folder);
+
+		/* Decrement the ref count */
+		nm_release_folder(folder);
+
+		/* Find the next folder */
+		locate = nm_locate_field(NM_A_FA_FOLDER, locate+1);
+
+	}
+}
+
+static void
+_add_contacts(NMUser * user, NMFolder * folder, NMField * fields)
+{
+	NMContact *contact = NULL;
+	NMField *locate = NULL,  *details;
+	NMUserRecord *user_record = NULL;
+
+	locate = nm_locate_field(NM_A_FA_CONTACT, fields);
+	while (locate != NULL) {
+
+		/* Create a new contact from the fields */
+		contact = nm_create_contact_from_fields(locate);
+
+		/* Add it to our contact list */
+		nm_folder_add_contact_to_list(folder, contact);
+
+		/* Update the contact cache */
+		nm_user_add_contact(user, contact);
+
+		/* Update the user record cache */
+		if ((details = nm_locate_field(NM_A_FA_USER_DETAILS,
+									   (NMField *) locate->value))) {
+			user_record = nm_find_user_record(user, nm_contact_get_dn(contact));
+			if (user_record == NULL) {
+				user_record = nm_create_user_record_from_fields(details);
+				nm_user_record_set_dn(user_record, nm_contact_get_dn(contact));
+				nm_user_add_user_record(user, user_record);
+				nm_release_user_record(user_record);
+			}
+			nm_contact_set_user_record(contact, user_record);
+		}
+
+		nm_release_contact(contact);
+
+		locate = nm_locate_field(NM_A_FA_CONTACT, locate+1);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/novell/nmcontact.h	Sat Apr 17 13:55:28 2004 +0000
@@ -0,0 +1,403 @@
+/*
+ * nmcontact.h
+ *
+ * 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.
+ *
+ */
+
+#ifndef __NM_CONTACT_H__
+#define __NM_CONTACT_H__
+
+#include <glib.h>
+
+typedef struct _NMContact NMContact;
+typedef struct _NMContactProperty NMContactProperty;
+typedef struct _NMFolder NMFolder;
+
+#include "nmfield.h"
+#include "nmuser.h"
+
+/**
+ * Creates a contact
+ *
+ * Should be released by calling nm_release_contact
+ *
+ * @return 			The new NMContact
+ *
+ */
+NMContact *nm_create_contact();
+
+/**
+ * Creates a contact from a field array representing the
+ * contact
+ *
+ * Should be released by calling nm_release_contact 
+ *
+ * @param	fields	Should be the NM_A_FA_CONTACT for
+ *					the contact
+ *
+ * @return	The new contact
+ *
+ */
+NMContact *nm_create_contact_from_fields(NMField * fields);
+
+/**
+ * Add a reference to an existing contact
+ *
+ * The reference should be released by calling
+ * nm_release_contact 
+ *
+ * @param	contact	The contact
+ *
+ */
+void nm_contact_add_ref(NMContact * contact);
+
+/**
+ * Update the contact list properties of the contact (sequence, parent id, etc.)
+ *
+ * @param	contact	The contact to update
+ * @param	fields	The fields to update from (should be a NM_A_FA_CONTACT array)
+ *
+ */
+void nm_contact_update_list_properties(NMContact * contact, NMField * fields);
+
+/**
+ * Release a contact reference
+ *
+ * @param	contact	The contact to release.
+ *
+ */
+void nm_release_contact(NMContact * contact);
+
+/**
+ * Get the display name of a contact
+ *
+ * @param	contact	The contact
+ *
+ * @return	The display name of a contact
+ *
+ */
+const char *nm_contact_get_display_name(NMContact * contact);
+
+/**
+ * Get the DN of a contact
+ *
+ * @param	contact	The contact
+ *
+ * @return	The DN of the contact
+ */
+const char *nm_contact_get_dn(NMContact * contact);
+
+/**
+ * Set the display name for a contact. This is called
+ * by nm_send_rename_contact. It should not be called 
+ * directly (it does not change the display name on the
+ * server side list -- nm_send_rename_conact does).
+ *
+ * @param	contact			The contact
+ * @param	display_name	The new display name
+ *
+ */
+void nm_contact_set_display_name(NMContact * contact, const char * display_name);
+
+/**
+ * Set the DN for the contact
+ *
+ * @param	contact	The contact
+ * @param	dn		The new DN for the contact
+ *
+ */
+void nm_contact_set_dn(NMContact * contact, const char * dn);
+
+/**
+ * Return a field array (NM_A_FA_CONTACT) representing the contact
+ *
+ * @param	contact	The contact
+ *
+ * @return	A field array representing the contact
+ */
+NMField *nm_contact_to_fields(NMContact * contact);
+
+/**
+ * Set the user record for the contact
+ *
+ * @param	contact			The contact
+ * @param	user_record		The user record
+ *
+ */
+void nm_contact_set_user_record(NMContact * contact, NMUserRecord * user_record);
+
+/**
+ * Get the user record for the contact
+ *
+ * @param	contact	The contact
+ *
+ * @return	The user record associated with the contact
+ *
+ */
+NMUserRecord *nm_contact_get_user_record(NMContact * contact);
+
+/**
+ * Get the user defined data for the contact
+ *
+ * @param	contact	The contact
+ *
+ * @return	The user defined data for the contact
+ *
+ */
+gpointer nm_contact_get_data(NMContact * contact);
+
+/**
+ * Get the Object ID for the contact
+ *
+ * @param	contact	The contact
+ *
+ * @return	The ID for the contact
+ */
+int nm_contact_get_id(NMContact * contact);
+
+/**
+ * Get the ID for the folder that the contact is in
+ *
+ * @param	contact	The contact
+ *
+ * @return	The ID of the folder that contains the contact
+ *
+ */
+int nm_contact_get_parent_id(NMContact * contact);
+
+/**
+ * Get The userid of the contact.
+ *
+ * @param	contact	The contact
+ *
+ * @return	The userid of the contact
+ *
+ */
+const char *nm_contact_get_userid(NMContact * contact);
+
+/**
+ * Get the display id of the contact
+ *
+ * @param	contact	The contact
+ *
+ * @return	The display id of the contact
+ */
+const char *nm_contact_get_display_id(NMContact * contact);
+
+/**
+ * Set the user defined data for the contact
+ *
+ * @param	contact	The contact
+ * @param	data	The user defined data
+ *
+ */
+void nm_contact_set_data(NMContact * contact, gpointer data);
+
+/**
+ * Create a folder with the given name 
+ *
+ * @param	name	The name of the folder
+ *
+ * @return 	The new folder
+ *
+ */
+NMFolder *nm_create_folder(const char *name);
+
+/**
+ * Create a folder from a NM_A_FA_FOLDER field array
+ *
+ * @param	fields	The NM_A_FA_FOLDER field array
+ *
+ * @return	The new folder
+ *
+ */
+NMFolder *nm_create_folder_from_fields(NMField * fields);
+
+/**
+ * Release a reference to a folder.
+ *
+ * @param	folder	The folder to release
+ *
+ */
+void nm_release_folder(NMFolder * folder);
+
+/**
+ * Return the number of subfolders for the given
+ * folder
+ *
+ * @param	folder	The folder
+ *
+ * @return	The number of subfolders contained by folder
+ */
+int nm_folder_get_subfolder_count(NMFolder * folder);
+
+/**
+ * Get a subfolder
+ *
+ * @param	folder	The root folder
+ * @param	index	The index of the folder to get
+ *
+ * @return	The subfolder at the given index
+ *
+ */
+NMFolder *nm_folder_get_subfolder(NMFolder * folder, int index);
+
+/**
+ * Get the number of contacts in the given folder
+ *
+ * @param	folder	The folder
+ *
+ * @return	The number of contacts contained by folder
+ *
+ */
+int nm_folder_get_contact_count(NMFolder * folder);
+
+/**
+ * Get a contact in the given folder
+ *
+ * @param	folder	The folder
+ * @param	index	The index of the contact to get
+ *
+ * @return	The contact at the given index
+ *
+ */
+NMContact *nm_folder_get_contact(NMFolder * folder, int index);
+
+/**
+ * Get the name of the folder
+ *
+ * @param	folder	The folder
+ *
+ * @return	The name of the folder.
+ *
+ */
+const char *nm_folder_get_name(NMFolder * folder);
+
+/**
+ * Set the name of a folder. Do not call this directly.
+ * It does not change the name of the folder in the
+ * server side contact list. You must call 
+ * nm_send_set_folder_name().
+ *
+ * @param	folder	The folder
+ * @param	name	The new name for the folder
+ *
+ */
+void nm_folder_set_name(NMFolder * folder, const char *name);
+
+/**
+ * Get Object ID for folder
+ *
+ * @param	folder	The folder
+ *
+ * @return	The ID of the folder
+ *
+ */
+int nm_folder_get_id(NMFolder * folder);
+
+/**
+ * Add contacts and folders from fields into root
+ *
+ * @param	user	The logged in user
+ * @param	root	The root folder
+ * @param	fields	The contact list field array
+ * 
+ */
+void nm_folder_add_contacts_and_folders(NMUser * user, NMFolder * root,
+										NMField * fields);
+/**
+ * Add a contact to the contact list.
+ *
+ * @param	root_folder	The root folder of the contact list
+ * @param	contact		The contact to add
+ *
+ */
+void nm_folder_add_contact_to_list(NMFolder * root_folder,
+									  NMContact * contact);
+
+/**
+ * Update the contact list properties of the folder (sequence, parent id, etc.)
+ *
+ * @param	folder	The folder to update
+ * @param	fields	The fields to update from (should be a NM_A_FA_FOLDER array)
+ *
+ */
+void nm_folder_update_list_properties(NMFolder * folder, NMField * fields);
+
+/**
+ * Add folder to the contact list
+ *
+ * @param	root_folder	The root folder of the contact list
+ * @param	folder		The folder to add to the contact list
+ *
+ */
+void nm_folder_add_folder_to_list(NMFolder * root_folder, NMFolder * folder);
+
+/**
+ * Find the object with the given id
+ *
+ * @param	root_folder	The root folder of the contact list
+ * @param	object_id	The object id of the object to find
+ *
+ * @return	The object with object id (either a contact or a folder)
+ */
+gpointer nm_folder_find_item_by_object_id(NMFolder * root_folder,
+										  int object_id);
+
+/**
+ * Remove a contact from the folder
+ *
+ * @param	folder	The folder
+ * @param	contact	The contact to remove
+ *
+ */
+void nm_folder_remove_contact(NMFolder * folder, NMContact * contact);
+
+/**
+ * Find a contact in a folder by DN
+ *
+ * @param	folder	The folder to search
+ * @param	dn		The DN of the contact to find
+ *
+ * @return	The contact if found, NULL otherwise
+ *
+ */
+NMContact *nm_folder_find_contact(NMFolder * folder, const char *dn);
+
+/**
+ * Find a contact in a folder by userid
+ *
+ * @param	folder	The folder to search
+ * @param	userid	The userid of the contact to find
+ *
+ * @return	The contact if found, NULL otherwise
+ *
+ */
+NMContact *nm_folder_find_contact_by_userid(NMFolder * folder,
+											const char *userid);
+/**
+ * Return a field array (NM_A_FA_FOLDER) representing the folder
+ *
+ * @param	folder	The folder
+ *
+ * @return	A field array representing the folder
+ */
+NMField *nm_folder_to_fields(NMFolder * folder);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/novell/nmevent.c	Sat Apr 17 13:55:28 2004 +0000
@@ -0,0 +1,973 @@
+/*
+ * nmevent.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 <glib.h>
+#include <string.h>
+#include <time.h>
+#include "nmevent.h"
+#include "nmfield.h"
+#include "nmconn.h"
+#include "nmuserrecord.h"
+
+struct _NMEvent
+{
+
+	/* Event type */
+	int type;
+
+	/* The DN of the event source */
+	char *source;
+
+	/* Timestamp of the event */
+	guint32 gmt;
+
+	/* Conference to associate with the event */
+	NMConference *conference;
+
+	/* User record to associate with the event */
+	NMUserRecord *user_record;
+
+	/* Text associated with the event */
+	char *text;
+
+	/* Reference count for event structure */
+	int ref_count;
+
+};
+
+/* Return a copy of src minus the RTF  */
+static char *
+_strip_rtf(const char *src, int len)
+{
+	const char *p = src;
+	char *q;
+	char *dest = g_new0(char, len + 1);
+	int level = 0;
+
+	/* Make sure we are dealing with rtf */
+	if (strncmp("{\\rtf1", src, strlen("{\\rtf1")) != 0) {
+		strncpy(dest, src, len);
+		return dest;
+	}
+	p += strlen("{\\rtf1");
+
+	q = dest;
+	while (*p != '\0') {
+		if (*p == '\\') {
+			if (level == 0) {
+				if (*(p + 1) == '\\' || *(p + 1) == '{' || *(p + 1) == '}') {
+					*q++ = *(p + 1);
+					p++;
+				} else if (*(p + 1) == 't' && *(p + 2) == 'a' && *(p + 3) == 'b') {
+					*q++ = '\t';
+					p++;
+				}
+			}
+			p++;
+		} else if (*p == '{') {
+			level++;
+			p++;
+		} else if (*p == '}') {
+			level--;
+			p++;
+		} else if (level == 0) {
+			if ((*p == ' ' || *p == '\r') && (*(p + 1) != '\\')) {
+				p++;
+
+				if (*p == '\n' &&
+					(*(p + 1) == '{' || *(p + 1) == '}' || *(p + 1) == '\\')) {
+					p++;
+				} else {
+					/* We found some text */
+					while (*p != '\0' && *p != '\\' && *p != '{' && *p != '}') {
+						*q++ = *p;
+						p++;
+					}
+				}
+			} else {
+				p++;
+			}
+		} else {
+			p++;
+		}
+	}
+
+	return dest;
+}
+
+/* Handle getdetails response and set the new user record into the event */
+static void
+_got_user_for_event(NMUser * user, NMERR_T ret_val,
+					gpointer resp_data, gpointer user_data)
+{
+	NMUserRecord *user_record;
+	NMEvent *event;
+	nm_event_cb cb;
+
+	if (user == NULL)
+		return;
+
+	user_record = resp_data;
+	event = user_data;
+
+	if (ret_val == NM_OK) {
+		if (event && user_record) {
+
+			/* Add the user record to the event structure 
+			 * and make the callback.
+			 */
+			nm_event_set_user_record(event, user_record);
+			if ((cb = nm_user_get_event_callback(user))) {
+				cb(user, event);
+			}
+		}
+
+	} else {
+		/* Cleanup resp_data */
+
+	}
+
+	/* Clean up */
+	if (event)
+		nm_release_event(event);
+
+}
+
+/* Handle getdetails response, set the new user record into the event
+ * and add the user record as a participant in the conference
+ */
+static void
+_got_user_for_conference(NMUser * user, NMERR_T ret_val,
+						 gpointer resp_data, gpointer user_data)
+{
+	NMUserRecord *user_record = resp_data;
+	NMEvent *event = user_data;
+	NMConference *conference;
+	nm_event_cb cb;
+
+	if (user == NULL)
+		return;
+
+	if (event && user_record) {
+
+		conference = nm_event_get_conference(event);
+		if (conference) {
+
+			/* Add source of event as recip of the conference */
+			nm_conference_add_participant(conference, user_record);
+
+			/* Add the user record to the event structure 
+			 * and make the callback.
+			 */
+			nm_event_set_user_record(event, user_record);
+			if ((cb = nm_user_get_event_callback(user))) {
+				cb(user, event);
+			}
+		}
+	}
+
+	if (event)
+		nm_release_event(event);
+}
+
+/* Read the receive message event, set up the event object, and
+ * get details for the event source if we don't have them yet.
+ */
+static NMERR_T
+handle_receive_message(NMUser * user, NMEvent * event, gboolean autoreply)
+{
+	NMConference *conference;
+	NMUserRecord *user_record;
+	NMConn *conn;
+	NMERR_T rc = NM_OK;
+	guint32 size = 0, flags = 0;
+	char *msg = NULL;
+	char *nortf = NULL;
+	char *guid = NULL;
+
+	conn = nm_user_get_conn(user);
+
+	/* Read the conference guid */
+	rc = nm_read_all(conn, (char *) &size, sizeof(size));
+	if (rc == NM_OK) {
+		guid = g_new0(char, size + 1);
+		rc = nm_read_all(conn, guid, size);
+	}
+
+	/* Read the conference flags */
+	if (rc == NM_OK) {
+		rc = nm_read_all(conn, (char *) &flags, sizeof(flags));
+	}
+
+	/* Read the message text */
+	if (rc == NM_OK) {
+		rc = nm_read_all(conn, (char *) &size, sizeof(size));
+		if (rc == NM_OK) {
+
+			msg = g_new0(char, size + 1);
+			rc = nm_read_all(conn, msg, size);
+
+			gaim_debug(GAIM_DEBUG_INFO, "novell", "Message is %s\n", msg);
+
+			/* Auto replies are not in RTF format! */
+			if (!autoreply) {
+
+				nortf = _strip_rtf((const char *) msg, size);
+
+				gaim_debug(GAIM_DEBUG_INFO, "novell",
+						   "Message without RTF is %s\n", nortf);
+
+				/* Store the event data */
+				nm_event_set_text(event, nortf);
+
+			} else {
+
+				/* Store the event data */
+				nm_event_set_text(event, msg);
+			}
+		}
+	}
+
+	/* Check to see if we already know about the conference */
+	conference = nm_conference_list_find(user, guid);
+	if (conference) {
+
+		nm_conference_set_flags(conference, flags);
+		nm_event_set_conference(event, conference);
+
+		/* Add a reference to the user record in our event object */
+		user_record = nm_find_user_record(user, nm_event_get_source(event));
+		if (user_record) {
+			nm_event_set_user_record(event, user_record);
+		}
+
+	} else {
+
+		/* This is a new conference, so create one and add it to our list */
+		conference = nm_create_conference(guid);
+		nm_conference_set_flags(conference, flags);
+
+		/* Add a reference to the conference in the event */
+		nm_event_set_conference(event, conference);
+
+		/* Add new conference to the conference list */
+		nm_conference_list_add(user, conference);
+
+		/* Check to see if we have details for the event source yet */
+		user_record = nm_find_user_record(user, nm_event_get_source(event));
+		if (user_record) {
+
+			/* We do so add the user record as a recipient of the conference */
+			nm_conference_add_participant(conference, user_record);
+
+			/* Add a reference to the user record in our event object */
+			nm_event_set_user_record(event, user_record);
+
+		} else {
+
+			/* Need to go to the server to get details for the user */
+			rc = nm_send_get_details(user, nm_event_get_source(event),
+									 _got_user_for_conference, event);
+			if (rc == NM_OK)
+				rc = -1;		/* Not done processing the event yet! */
+		}
+
+		nm_release_conference(conference);
+	}
+
+	if (msg)
+		g_free(msg);
+
+	if (nortf)
+		g_free(nortf);
+
+	if (guid)
+		g_free(guid);
+
+	return rc;
+}
+
+/* Read the invite event, set up the event object, and
+ * get details for the event source if we don't have them yet.
+ */
+static NMERR_T
+handle_conference_invite(NMUser * user, NMEvent * event)
+{
+	NMERR_T rc = NM_OK;
+	guint32 size = 0;
+	char *guid = NULL;
+	char *msg = NULL;
+	NMConn *conn;
+	NMUserRecord *user_record;
+
+	conn = nm_user_get_conn(user);
+
+	/* Read the conference guid */
+	rc = nm_read_all(conn, (char *) &size, sizeof(size));
+	if (rc == NM_OK) {
+		guid = g_new0(char, size + 1);
+		rc = nm_read_all(conn, guid, size);
+	}
+
+	/* Read the the message */
+	if (rc == NM_OK) {
+		rc = nm_read_all(conn, (char *) &size, sizeof(size));
+		if (rc == NM_OK) {
+			msg = g_new0(char, size + 1);
+			rc = nm_read_all(conn, msg, size);
+		}
+	}
+
+	/* Store the event data */
+	if (rc == NM_OK) {
+		NMConference *conference;
+
+		nm_event_set_text(event, msg);
+
+		conference = nm_conference_list_find(user, guid);
+		if (conference == NULL) {
+			conference = nm_create_conference(guid);
+
+			/* Add new conference to the list and the event */
+			nm_conference_list_add(user, conference);
+			nm_event_set_conference(event, conference);
+
+			/* Check to see if we have details for the event source yet */
+			user_record = nm_find_user_record(user, nm_event_get_source(event));
+			if (user_record) {
+
+				/* Add a reference to the user record in our event object */
+				nm_event_set_user_record(event, user_record);
+
+			} else {
+
+				/* Need to go to the server to get details for the user */
+				rc = nm_send_get_details(user, nm_event_get_source(event),
+										 _got_user_for_event, event);
+				if (rc == NM_OK)
+					rc = -1;	/* Not done processing the event yet! */
+			}
+
+			nm_release_conference(conference);
+
+		}
+	}
+
+	if (msg)
+		g_free(msg);
+
+	if (guid)
+		g_free(guid);
+
+	return rc;
+}
+
+/* Read the invite notify event, set up the event object, and
+ * get details for the event source if we don't have them yet.
+ */
+static NMERR_T
+handle_conference_invite_notify(NMUser * user, NMEvent * event)
+{
+	NMERR_T rc = NM_OK;
+	guint32 size = 0;
+	char *guid = NULL;
+	NMConn *conn;
+	NMConference *conference;
+	NMUserRecord *user_record;
+
+	conn = nm_user_get_conn(user);
+
+	/* Read the conference guid */
+	rc = nm_read_all(conn, (char *) &size, sizeof(size));
+	if (rc == NM_OK) {
+		guid = g_new0(char, size + 1);
+		rc = nm_read_all(conn, guid, size);
+	}
+
+	conference = nm_conference_list_find(user, guid);
+	if (conference) {
+		nm_event_set_conference(event, conference);
+
+		/* Check to see if we have details for the event source yet */
+		user_record = nm_find_user_record(user, nm_event_get_source(event));
+		if (user_record) {
+
+			/* Add a reference to the user record in our event object */
+			nm_event_set_user_record(event, user_record);
+
+		} else {
+
+			/* Need to go to the server to get details for the user */
+			rc = nm_send_get_details(user, nm_event_get_source(event),
+									 _got_user_for_event, event);
+			if (rc == NM_OK)
+				rc = -1;		/* Not done processing the event yet! */
+		}
+
+	} else {
+		rc = NMERR_CONFERENCE_NOT_FOUND;
+	}
+
+
+	if (guid)
+		g_free(guid);
+
+	return rc;
+}
+
+/* Read the conference reject event and set up the event object */
+static NMERR_T
+handle_conference_reject(NMUser * user, NMEvent * event)
+{
+	NMERR_T rc = NM_OK;
+	guint32 size = 0;
+	char *guid = NULL;
+	NMConn *conn;
+	NMConference *conference;
+
+	conn = nm_user_get_conn(user);
+
+	/* Read the conference guid */
+	rc = nm_read_all(conn, (char *) &size, sizeof(size));
+	if (rc == NM_OK) {
+		guid = g_new0(char, size + 1);
+		rc = nm_read_all(conn, guid, size);
+	}
+
+	if (rc == NM_OK) {
+		conference = nm_conference_list_find(user, guid);
+		if (conference) {
+			nm_event_set_conference(event, conference);
+		} else {
+			rc = NMERR_CONFERENCE_NOT_FOUND;
+		}
+	}
+
+	if (guid)
+		g_free(guid);
+
+	return rc;
+}
+
+/* Read the conference left event, set up the event object, and
+ * remove the conference from the list if there are no more 
+ * participants
+ */
+static NMERR_T
+handle_conference_left(NMUser * user, NMEvent * event)
+{
+	NMERR_T rc = NM_OK;
+	guint32 size = 0, flags = 0;
+	char *guid = NULL;
+	NMConference *conference;
+	NMConn *conn;
+
+	conn = nm_user_get_conn(user);
+
+	/* Read the conference guid */
+	rc = nm_read_all(conn, (char *) &size, sizeof(size));
+	if (rc == NM_OK) {
+		guid = g_new0(char, size + 1);
+		rc = nm_read_all(conn, guid, size);
+	}
+
+	/* Read the conference flags */
+	if (rc == NM_OK) {
+		rc = nm_read_all(conn, (char *) &flags, sizeof(flags));
+	}
+
+	if (rc == NM_OK) {
+		conference = nm_conference_list_find(user, guid);
+		if (conference) {
+			nm_event_set_conference(event, conference);
+			nm_conference_set_flags(conference, flags);
+			
+			nm_conference_remove_participant(conference, nm_event_get_source(event));
+			if (nm_conference_get_participant_count(conference) == 0) {
+				nm_conference_list_remove(user, conference);
+			}
+			
+		} else {
+			rc = NMERR_CONFERENCE_NOT_FOUND;
+		}
+	}
+
+	if (guid)
+		g_free(guid);
+
+	return rc;
+}
+
+/* Read the conference closed, set up the event object, and
+ * remove the conference from the list
+ */
+static NMERR_T
+handle_conference_closed(NMUser * user, NMEvent * event)
+{
+	NMERR_T rc = NM_OK;
+	guint32 size = 0;
+	char *guid = NULL;
+	NMConference *conference;
+	NMConn *conn;
+
+	conn = nm_user_get_conn(user);
+
+	/* Read the conference guid */
+	rc = nm_read_all(conn, (char *) &size, sizeof(size));
+	if (rc == NM_OK) {
+		guid = g_new0(char, size + 1);
+		rc = nm_read_all(conn, guid, size);
+	}
+
+	if (rc == NM_OK) {
+		conference = nm_conference_list_find(user, guid);
+		if (conference) {
+			nm_event_set_conference(event, conference);
+			nm_conference_list_remove(user, conference);
+		} else {
+			rc = NMERR_CONFERENCE_NOT_FOUND;
+		}
+	}
+
+	if (guid)
+		g_free(guid);
+
+	return rc;
+}
+
+/* Read the conference joined event, set up the event object, and
+ * get details for the event source if we don't have them yet.
+ */
+static NMERR_T
+handle_conference_joined(NMUser * user, NMEvent * event)
+{
+	NMERR_T rc = NM_OK;
+	guint32 size = 0, flags = 0;
+	char *guid = NULL;
+	NMConn *conn;
+	NMConference *conference;
+	NMUserRecord *user_record;
+
+	conn = nm_user_get_conn(user);
+
+	/* Read the conference guid */
+	rc = nm_read_all(conn, (char *) &size, sizeof(size));
+	if (rc == NM_OK) {
+		guid = g_new0(char, size + 1);
+		rc = nm_read_all(conn, guid, size);
+	}
+
+	/* Read the conference flags */
+	if (rc == NM_OK) {
+		rc = nm_read_all(conn, (char *) &flags, sizeof(flags));
+	}
+
+	if (rc == NM_OK) {
+		conference = nm_conference_list_find(user, guid);
+		if (conference) {
+			nm_conference_set_flags(conference, flags);
+			
+			nm_event_set_conference(event, conference);
+			
+			/* Add the new user to the participants list */
+			user_record = nm_find_user_record(user, nm_event_get_source(event));
+			if (user_record) {
+				nm_conference_add_participant(conference, user_record);
+			} else {
+				
+				/* Need to go to the server to get details for the user */
+				rc = nm_send_get_details(user, nm_event_get_source(event),
+										 _got_user_for_conference, event);
+				if (rc == NM_OK)
+					rc = -1;		/* Not done processing the event yet! */
+			}
+			
+		} else {
+			rc = NMERR_CONFERENCE_NOT_FOUND;
+		}
+	}
+
+	if (guid)
+		g_free(guid);
+
+	return rc;
+}
+
+/* Read the typing event and set up the event object */
+static NMERR_T
+handle_typing(NMUser * user, NMEvent * event)
+{
+	NMERR_T rc = NM_OK;
+	guint32 size = 0;
+	char *guid = NULL;
+	NMConference *conference;
+	NMConn *conn;
+
+	conn = nm_user_get_conn(user);
+
+	/* Read the conference guid */
+	rc = nm_read_all(conn, (char *) &size, sizeof(size));
+	if (rc == NM_OK) {
+		guid = g_new0(char, size + 1);
+		rc = nm_read_all(conn, guid, size);
+	}
+
+	if (rc == NM_OK) {
+		conference = nm_conference_list_find(user, guid);
+		if (conference) {
+			nm_event_set_conference(event, conference);
+		} else {
+			rc = NMERR_CONFERENCE_NOT_FOUND;
+		}
+	}
+
+	if (guid)
+		g_free(guid);
+
+	return rc;
+}
+
+/* Read the event, set up the event object, and update
+ * the status in the user record (for the event source)
+ */
+static NMERR_T
+handle_status_change(NMUser * user, NMEvent * event)
+{
+	NMERR_T rc = NM_OK;
+	guint16 status;
+	guint32 size;
+	char *text = NULL;
+	NMUserRecord *user_record;
+	NMConn *conn;
+
+	conn = nm_user_get_conn(user);
+
+	/* Read new status */
+	rc = nm_read_all(conn, (char *) &status, sizeof(status));
+	if (rc == NM_OK) {
+
+		/* Read the status text */
+		rc = nm_read_all(conn, (char *) &size, sizeof(size));
+		if (rc == NM_OK) {
+			if (size > 0) {
+				text = g_new0(char, size + 1);
+				rc = nm_read_all(conn, text, size);
+			}
+		}
+	}
+
+	if (rc == NM_OK) {
+		nm_event_set_text(event, text);
+
+		/* Get a reference to the user record and store the new status */
+		user_record = nm_find_user_record(user, nm_event_get_source(event));
+		if (user_record) {
+			nm_event_set_user_record(event, user_record);
+			nm_user_record_set_status(user_record, status, text);
+		}
+	}
+
+	if (text)
+		g_free(text);
+
+	return rc;
+}
+
+/* Read the undeliverable event */
+static NMERR_T
+handle_undeliverable_status(NMUser * user, NMEvent * event)
+{
+	NMERR_T rc = NM_OK;
+	guint32 size = 0;
+	char *guid = NULL;
+	NMConn *conn;
+
+	conn = nm_user_get_conn(user);
+
+	/* Read the conference guid */
+	rc = nm_read_all(conn, (char *) &size, sizeof(size));
+	if (rc == NM_OK) {
+		guid = g_new0(char, size + 1);
+		rc = nm_read_all(conn, guid, size);
+	}
+
+	if (guid)
+		g_free(guid);
+
+	return rc;
+}
+
+/*******************************************************************************
+ * Event API -- see header file for comments
+ ******************************************************************************/
+
+NMEvent *
+nm_create_event(int type, const char *source, guint32 gmt)
+{
+	NMEvent *event = g_new0(NMEvent, 1);
+
+	event->type = type;
+	event->gmt = gmt;
+
+	if (source)
+		event->source = g_strdup(source);
+
+	event->ref_count = 1;
+
+	return event;
+}
+
+void
+nm_release_event(NMEvent * event)
+{
+	if (event == NULL) {
+		return;
+	}
+
+	if (--(event->ref_count) == 0) {
+
+		if (event->source)
+			g_free(event->source);
+
+		if (event->conference)
+			nm_release_conference(event->conference);
+
+		if (event->user_record)
+			nm_release_user_record(event->user_record);
+
+		if (event->text)
+			g_free(event->text);
+
+		g_free(event);
+	}
+}
+
+
+NMConference *
+nm_event_get_conference(NMEvent * event)
+{
+	if (event)
+		return event->conference;
+	else
+		return NULL;
+}
+
+void
+nm_event_set_conference(NMEvent * event, NMConference * conference)
+{
+	if (event && conference) {
+		nm_conference_add_ref(conference);
+		event->conference = conference;
+	}
+}
+
+NMUserRecord *
+nm_event_get_user_record(NMEvent * event)
+{
+	if (event)
+		return event->user_record;
+	else
+		return NULL;
+}
+
+void
+nm_event_set_user_record(NMEvent * event, NMUserRecord * user_record)
+{
+	if (event && user_record) {
+		nm_user_record_add_ref(user_record);
+		event->user_record = user_record;
+	}
+}
+
+const char *
+nm_event_get_text(NMEvent * event)
+{
+	if (event)
+		return event->text;
+	else
+		return NULL;
+}
+
+void
+nm_event_set_text(NMEvent * event, const char *text)
+{
+	if (event) {
+		if (text)
+			event->text = g_strdup(text);
+		else
+			event->text = NULL;
+	}
+}
+
+const char *
+nm_event_get_source(NMEvent * event)
+{
+	if (event)
+		return event->source;
+	else
+		return NULL;
+}
+
+int
+nm_event_get_type(NMEvent * event)
+{
+	if (event)
+		return event->type;
+	else
+		return -1;
+}
+
+guint32
+nm_event_get_gmt(NMEvent * event)
+{
+	if (event)
+		return event->gmt;
+	else
+		return (guint32)-1;
+}
+
+NMERR_T
+nm_process_event(NMUser * user, int type)
+{
+	NMERR_T rc = NM_OK;
+	guint32 size = 0;
+	NMEvent *event = NULL;
+	char *source = NULL;
+	nm_event_cb cb;
+	NMConn *conn;
+
+	if (user == NULL)
+		return NMERR_BAD_PARM;
+
+	if (type < NMEVT_START || type > NMEVT_STOP)
+		return NMERR_PROTOCOL;
+
+	conn = nm_user_get_conn(user);
+
+	/* Read the event source */
+	rc = nm_read_all(conn, (char *) &size, sizeof(size));
+	if (rc == NM_OK) {
+		if (size > 0) {
+			source = g_new0(char, size);
+
+			rc = nm_read_all(conn, source, size);
+		}
+	}
+
+	/* Read the event data */
+	if (rc == NM_OK) {
+		event = nm_create_event(type, source, time(0));
+
+		if (event) {
+
+			switch (type) {
+			case NMEVT_STATUS_CHANGE:
+				rc = handle_status_change(user, event);
+				break;
+
+			case NMEVT_RECEIVE_MESSAGE:
+				rc = handle_receive_message(user, event, FALSE);
+				break;
+
+			case NMEVT_RECEIVE_AUTOREPLY:
+				rc = handle_receive_message(user, event, TRUE);
+				break;
+
+			case NMEVT_USER_TYPING:
+			case NMEVT_USER_NOT_TYPING:
+				rc = handle_typing(user, event);
+				break;
+
+			case NMEVT_CONFERENCE_LEFT:
+				rc = handle_conference_left(user, event);
+				break;
+
+			case NMEVT_CONFERENCE_CLOSED:
+				rc = handle_conference_closed(user, event);
+				break;
+
+			case NMEVT_CONFERENCE_JOINED:
+				rc = handle_conference_joined(user, event);
+				break;
+
+			case NMEVT_CONFERENCE_INVITE:
+				rc = handle_conference_invite(user, event);
+				break;
+
+			case NMEVT_CONFERENCE_REJECT:
+				rc = handle_conference_reject(user, event);
+				break;
+
+			case NMEVT_CONFERENCE_INVITE_NOTIFY:
+				rc = handle_conference_invite_notify(user, event);
+				break;
+
+			case NMEVT_UNDELIVERABLE_STATUS:
+				rc = handle_undeliverable_status(user, event);
+				break;
+
+			case NMEVT_INVALID_RECIPIENT:
+				/* Nothing else to read, just callback */
+				break;
+
+			case NMEVT_USER_DISCONNECT:
+				/* Nothing else to read, just callback */
+				break;
+
+			case NMEVT_SERVER_DISCONNECT:
+				/* Nothing else to read, just callback */
+				break;
+
+			case NMEVT_RECEIVE_FILE:
+			case NMEVT_CONTACT_ADD:
+				/* Safely ignored for now */
+				break;
+
+			default:
+				gaim_debug(GAIM_DEBUG_INFO, "novell",
+						   "Unknown event %d received.\n", type);
+				rc = NMERR_PROTOCOL;
+				break;
+			}
+		}
+	}
+
+	if (rc == (NMERR_T)-1) {
+		/* -1 means that we are not ready to callback yet. */
+		rc = NM_OK;
+	} else if (rc == NM_OK && (cb = nm_user_get_event_callback(user))) {
+
+		cb(user, event);
+
+		if (event)
+			nm_release_event(event);
+	} else {
+		if (event)
+			nm_release_event(event);
+	}
+
+	/* Cleanup */
+	if (source)
+		g_free(source);
+
+	return rc;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/novell/nmevent.h	Sat Apr 17 13:55:28 2004 +0000
@@ -0,0 +1,181 @@
+/*
+ * nmevent.h
+ *
+ * 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.
+ *
+ */
+
+#ifndef __NM_EVENT_H__
+#define __NM_EVENT_H__
+
+typedef struct _NMEvent NMEvent;
+
+#include "nmuser.h"
+
+/**
+ * Defines for the event types 
+ */
+#define	NMEVT_INVALID_RECIPIENT			101
+#define	NMEVT_UNDELIVERABLE_STATUS		102
+#define	NMEVT_STATUS_CHANGE				103
+#define	NMEVT_CONTACT_ADD				104
+#define	NMEVT_CONFERENCE_CLOSED			105
+#define	NMEVT_CONFERENCE_JOINED			106
+#define	NMEVT_CONFERENCE_LEFT			107
+#define	NMEVT_RECEIVE_MESSAGE			108
+#define	NMEVT_RECEIVE_FILE				109
+#define NMEVT_USER_TYPING				112
+#define NMEVT_USER_NOT_TYPING			113
+#define NMEVT_USER_DISCONNECT			114
+#define NMEVT_SERVER_DISCONNECT			115
+#define NMEVT_CONFERENCE_RENAME			116
+#define NMEVT_CONFERENCE_INVITE			117
+#define NMEVT_CONFERENCE_INVITE_NOTIFY	118
+#define NMEVT_CONFERENCE_REJECT			119
+#define NMEVT_RECEIVE_AUTOREPLY			121
+#define NMEVT_START						NMEVT_INVALID_RECIPIENT
+#define NMEVT_STOP						NMEVT_RECEIVE_AUTOREPLY
+
+/**
+ * Process the event. The event will be read, an NMEvent will
+ * be created, and the event callback will be called.
+ *
+ * @param user		The main user structure.
+ * @param type		The type of the event to read.
+ *
+ * @return			NM_OK on success
+ */
+NMERR_T nm_process_event(NMUser * user, int type);
+
+/**
+ * Creates an NMEvent
+ *
+ * The NMEvent should be released by calling
+ * nm_release_event.
+ *
+ * @param type		The event type, see defines above.
+ * @param source	The DN of the event source.
+ * @param gmt		The time that the event occurred.
+ *
+ * @return 			The new NMEvent
+ */
+NMEvent *nm_create_event(int type, const char *source, guint32 gmt);
+
+/**
+ * Releases an NMEvent
+ *
+ * @param event		The event to release
+ *
+ */
+void nm_release_event(NMEvent * event);
+
+/**
+ * Sets the conference object for the given event.
+ *
+ * @param event			The event.
+ * @param conference	The conference to associate with the event.
+ *
+ */
+void nm_event_set_conference(NMEvent * event, NMConference * conference);
+
+/**
+ * Returns the conference object associated with the given event. This should not
+ * be released. If it needs to be kept around call nm_conference_addref().
+ *
+ * @param event	The event.
+ *
+ * @return		The conference associated with the event, or NULL
+ *				if no conference has been set for the event.
+ */
+NMConference *nm_event_get_conference(NMEvent * event);
+
+/**
+ * Sets the NMUserRecord object for the given event.
+ * The user record represents the event source.
+ *
+ * @param event			The event.
+ * @param user_record	The user record to associate with the event.
+ *
+ */
+void nm_event_set_user_record(NMEvent * event, NMUserRecord * user_record);
+
+/**
+ * Returns the NMUserRecord object associated with the given event. 
+ * The user record represents the event source. This should not
+ * be released. If it needs to be kept around call
+ * nm_user_record_add_ref().
+ *
+ * @param event	The event.
+ *
+ * @return		The user record associated with the event, or NULL
+ *				if no user record has been set for the event.
+ */
+NMUserRecord *nm_event_get_user_record(NMEvent * event);
+
+/**
+ * Sets the text to associate with the given event.
+ *
+ * @param event	The event.
+ * @param text	The text to associate with the event.
+ *
+ */
+void nm_event_set_text(NMEvent * event, const char *text);
+
+/**
+ * Returns the text associated with the given event.
+ *
+ * @param event	The event.
+ *
+ * @return		The text associated with the event, or NULL
+ *				if no text has been set for the event.
+ */
+const char *nm_event_get_text(NMEvent * event);
+
+/**
+ * Returns the source of the event (this will be the full DN of the
+ * event source).
+ *
+ * @param event	The event.
+ *
+ * @return		The full DN of the event's source.
+ */
+const char *nm_event_get_source(NMEvent * event);
+
+/**
+ * Returns the type of the event. See the defines above for
+ * a list of possible event types.
+ *
+ * @param event	The event.
+ *
+ * @return		The type of the event.
+ *
+ */
+int nm_event_get_type(NMEvent * event);
+
+/**
+ * Returns the time that the event took place.
+ *
+ * @param event	The event.
+ *
+ * @return		The timestamp for the event. This is the number of
+ *			    seconds since 1/1/1970 (as returned by the time() 
+ *				system call).
+ */
+guint32 nm_event_get_gmt(NMEvent * event);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/novell/nmfield.c	Sat Apr 17 13:55:28 2004 +0000
@@ -0,0 +1,334 @@
+/*
+ * nmfield.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 <string.h>
+#include <stdio.h>
+#include "nmfield.h"
+
+/* Free a field value and tag */
+static void _free_field(NMField * field);
+
+/* Free a field value */
+static void _free_field_value(NMField * field);
+
+/* Make a deep copy of the field */
+static void _copy_field(NMField * dest, NMField * src);
+
+/* Make a deep copy of the field's value */
+static void _copy_field_value(NMField * dest, NMField * src);
+
+/* Create a string from a value -- for debugging */
+static char *_value_to_string(NMField * field);
+
+NMField *
+nm_add_field(NMField * fields, char *tag, guint32 size, guint8 method,
+			 guint8 flags, guint32 value, guint8 type)
+{
+	guint32 count, new_len;
+	NMField *field = NULL;
+
+	if (fields == NULL) {
+		fields = g_new0(NMField, 10);
+		fields->len = 10;
+		count = 0;
+	} else {
+		count = nm_count_fields(fields);
+		if (fields->len < count + 2) {
+			new_len = count + 10;
+			fields = g_realloc(fields, new_len * sizeof(NMField));
+			fields->len = new_len;
+		}
+	}
+
+	field = &(fields[count]);
+	field->tag = g_strdup(tag);
+	field->size = size;
+	field->method = method;
+	field->flags = flags;
+	field->value = value;
+	field->type = type;
+
+	/* Null terminate the field array */
+	field = &((fields)[count + 1]);
+	field->tag = NULL;
+	field->value = 0;
+
+	return fields;
+}
+
+guint32
+nm_count_fields(NMField * fields)
+{
+	guint32 count = 0;
+
+	if (fields) {
+		while (fields->tag != NULL) {
+			count++;
+			fields++;
+		}
+	}
+
+	return count;
+}
+
+void
+nm_free_fields(NMField ** fields)
+{
+	NMField *field = NULL;
+
+	if ((fields == NULL) || (*fields == NULL))
+		return;
+
+	field = *fields;
+
+	while (field->tag != NULL) {
+		_free_field(field);
+		field++;
+	}
+
+	g_free(*fields);
+	*fields = NULL;
+}
+
+
+static void
+_free_field(NMField * field)
+{
+	if (field == NULL)
+		return;
+
+	_free_field_value(field);
+	g_free(field->tag);
+}
+
+static void
+_free_field_value(NMField * field)
+{
+	if (field == NULL)
+		return;
+
+	switch (field->type) {
+	case NMFIELD_TYPE_BINARY:
+	case NMFIELD_TYPE_UTF8:
+	case NMFIELD_TYPE_DN:
+		if ((gpointer) field->value != NULL) {
+			g_free((gpointer) field->value);
+		}
+		break;
+
+	case NMFIELD_TYPE_ARRAY:
+	case NMFIELD_TYPE_MV:
+		nm_free_fields((NMField **) & field->value);
+		break;
+
+	default:
+		break;
+	}
+
+	field->size = 0;
+	field->value = 0;
+}
+
+NMField *
+nm_locate_field(char *tag, NMField * fields)
+{
+	NMField *ret_fields = NULL;
+
+	if ((fields == NULL) || (tag == NULL)) {
+		return NULL;
+	}
+
+	while (fields->tag != NULL) {
+		if (g_ascii_strcasecmp(fields->tag, tag) == 0) {
+			ret_fields = fields;
+			break;
+		}
+		fields++;
+	}
+
+	return ret_fields;
+}
+
+NMField *
+nm_copy_field_array(NMField * src)
+{
+	NMField *ptr = NULL;
+	NMField *dest = NULL;
+	int count;
+
+	if (src != NULL) {
+		count = nm_count_fields(src) + 1;
+		dest = g_new0(NMField, count);
+		dest->len = count;
+		ptr = dest;
+		while (src->tag != NULL) {
+			_copy_field(ptr, src);
+			ptr++;
+			src++;
+		}
+	}
+
+	return dest;
+}
+
+static void
+_copy_field(NMField * dest, NMField * src)
+{
+	dest->type = src->type;
+	dest->flags = src->flags;
+	dest->method = src->method;
+	dest->tag = g_strdup(src->tag);
+	_copy_field_value(dest, src);
+}
+
+static void
+_copy_field_value(NMField * dest, NMField * src)
+{
+	dest->type = src->type;
+	switch (dest->type) {
+	case NMFIELD_TYPE_UTF8:
+	case NMFIELD_TYPE_DN:
+		if (src->size == 0 && src->value != 0) {
+			src->size = strlen((char *) src->value) + 1;
+		}
+		/* fall through */
+	case NMFIELD_TYPE_BINARY:
+		if (src->size != 0 && src->value != 0) {
+			dest->value = (guint32) g_new0(char, src->size);
+
+			memcpy((gpointer) dest->value, (gpointer) src->value, src->size);
+		}
+		break;
+
+	case NMFIELD_TYPE_ARRAY:
+	case NMFIELD_TYPE_MV:
+		dest->value = 0;
+		dest->value = (guint32) nm_copy_field_array((NMField *) src->value);
+		break;
+
+	default:
+		/* numeric value */
+		dest->value = src->value;
+		break;
+	}
+
+	dest->size = src->size;
+}
+
+void
+nm_remove_field(NMField * field)
+{
+	NMField *tmp;
+	guint32 len;
+
+	if ((field != NULL) && (field->tag != NULL)) {
+		_free_field(field);
+
+		/* Move fields down */
+		tmp = field + 1;
+		while (1) {
+			/* Don't overwrite the size of the array */
+			len = field->len;
+
+			*field = *tmp;
+
+			field->len = len;
+
+			if (tmp->tag == NULL)
+				break;
+
+			field++;
+			tmp++;
+		}
+	}
+}
+
+void
+nm_print_fields(NMField * fields)
+{
+	char *str = NULL;
+	NMField *field = fields;
+
+	if (fields == NULL)
+		return;
+
+	while (field->tag != NULL) {
+		if (field->type == NMFIELD_TYPE_ARRAY || field->type == NMFIELD_TYPE_MV) {
+			printf("Subarray START: %s Method = %d\n", field->tag, field->method);
+			nm_print_fields((NMField *) field->value);
+			printf("Subarray END: %s\n", field->tag);
+		} else {
+			str = _value_to_string(field);
+			printf("Tag=%s;Value=%s\n", field->tag, str);
+			g_free(str);
+			str = NULL;
+		}
+		field++;
+	}
+
+}
+
+static char *
+_value_to_string(NMField * field)
+{
+	char *value = NULL;
+
+	if (field == NULL)
+		return NULL;
+
+	/* This is a single value attribute */
+	if (((field->type == NMFIELD_TYPE_UTF8) ||
+		 (field->type == NMFIELD_TYPE_DN)) && (field->value != 0)) {
+		value = g_strdup((const char *) field->value);
+	} else if (field->type == NMFIELD_TYPE_BINARY && field->value != 0) {
+		value = g_new0(char, field->size);
+
+		memcpy(value, (const char *) field->value, field->size);
+	} else if (field->type == NMFIELD_TYPE_BOOL) {
+		if (field->value) {
+			value = g_strdup(NM_FIELD_TRUE);
+		} else {
+			value = g_strdup(NM_FIELD_FALSE);
+		}
+	} else {
+		/* assume it is a number */
+		value = g_new0(char, 20);
+
+		switch (field->type) {
+		case NMFIELD_TYPE_BYTE:
+		case NMFIELD_TYPE_WORD:
+		case NMFIELD_TYPE_DWORD:
+			value = g_strdup_printf("%ld", (long) field->value);
+			break;
+
+		case NMFIELD_TYPE_UBYTE:
+		case NMFIELD_TYPE_UWORD:
+		case NMFIELD_TYPE_UDWORD:
+			value = g_strdup_printf("%lu", (unsigned long) field->value);
+			break;
+		}
+	}
+
+	if (value == NULL)
+		value = g_strdup("NULL");
+
+	return value;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/novell/nmfield.h	Sat Apr 17 13:55:28 2004 +0000
@@ -0,0 +1,201 @@
+/*
+ * nmfield.h
+ *
+ * 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.
+ *
+ */
+
+#ifndef NMFIELD_H
+#define NMFIELD_H
+
+#include <glib.h>
+
+typedef struct NMField_t
+{
+	char *tag;				/* Field tag */
+
+	guint8 method;			/* Method of the field */
+
+	guint8 flags;			/* Flags */
+
+	guint8 type;			/* Type of value */
+
+	guint32 size;			/* Size of value if binary */
+
+	guint32 value;			/* Value of field */
+
+	guint32 len;			/* Length of the array */
+
+} NMField;
+
+/* Field types */
+#define	NMFIELD_TYPE_INVALID			0
+#define	NMFIELD_TYPE_NUMBER				1
+#define	NMFIELD_TYPE_BINARY				2
+#define	NMFIELD_TYPE_BYTE				3
+#define	NMFIELD_TYPE_UBYTE				4
+#define	NMFIELD_TYPE_WORD				5
+#define	NMFIELD_TYPE_UWORD				6
+#define	NMFIELD_TYPE_DWORD				7
+#define	NMFIELD_TYPE_UDWORD				8
+#define	NMFIELD_TYPE_ARRAY				9
+#define	NMFIELD_TYPE_UTF8				10
+#define	NMFIELD_TYPE_BOOL				11
+#define	NMFIELD_TYPE_MV					12
+#define	NMFIELD_TYPE_DN					13
+
+/* Field methods */
+#define NMFIELD_METHOD_VALID			0
+#define NMFIELD_METHOD_IGNORE			1
+#define NMFIELD_METHOD_DELETE			2
+#define NMFIELD_METHOD_DELETE_ALL		3
+#define NMFIELD_METHOD_EQUAL			4
+#define NMFIELD_METHOD_ADD				5
+#define NMFIELD_METHOD_UPDATE			6
+#define NMFIELD_METHOD_GTE				10
+#define NMFIELD_METHOD_LTE				12
+#define NMFIELD_METHOD_NE				14
+#define NMFIELD_METHOD_EXIST			15
+#define NMFIELD_METHOD_NOTEXIST			16
+#define NMFIELD_METHOD_SEARCH			17
+#define NMFIELD_METHOD_MATCHBEGIN		19
+#define NMFIELD_METHOD_MATCHEND			20
+#define NMFIELD_METHOD_NOT_ARRAY		40
+#define NMFIELD_METHOD_OR_ARRAY			41
+#define NMFIELD_METHOD_AND_ARRAY		42
+
+/* Attribute Names (field tags) */
+#define NM_A_IP_ADDRESS					"nnmIPAddress"
+#define	NM_A_PORT						"nnmPort"
+#define	NM_A_FA_FOLDER					"NM_A_FA_FOLDER"
+#define	NM_A_FA_CONTACT					"NM_A_FA_CONTACT"
+#define	NM_A_FA_CONVERSATION			"NM_A_FA_CONVERSATION"
+#define	NM_A_FA_MESSAGE					"NM_A_FA_MESSAGE"
+#define	NM_A_FA_CONTACT_LIST			"NM_A_FA_CONTACT_LIST"
+#define	NM_A_FA_RESULTS					"NM_A_FA_RESULTS"
+#define	NM_A_FA_INFO_DISPLAY_ARRAY		"NM_A_FA_INFO_DISPLAY_ARRAY"
+#define	NM_A_FA_USER_DETAILS			"NM_A_FA_USER_DETAILS"
+#define	NM_A_SZ_OBJECT_ID				"NM_A_SZ_OBJECT_ID"
+#define	NM_A_SZ_PARENT_ID				"NM_A_SZ_PARENT_ID"
+#define	NM_A_SZ_SEQUENCE_NUMBER			"NM_A_SZ_SEQUENCE_NUMBER"
+#define	NM_A_SZ_TYPE					"NM_A_SZ_TYPE"
+#define	NM_A_SZ_STATUS					"NM_A_SZ_STATUS"
+#define	NM_A_SZ_STATUS_TEXT				"NM_A_SZ_STATUS_TEXT"
+#define	NM_A_SZ_DN						"NM_A_SZ_DN"
+#define	NM_A_SZ_DISPLAY_NAME			"NM_A_SZ_DISPLAY_NAME"
+#define	NM_A_SZ_USERID					"NM_A_SZ_USERID"
+#define NM_A_SZ_CREDENTIALS				"NM_A_SZ_CREDENTIALS"
+#define	NM_A_SZ_MESSAGE_BODY			"NM_A_SZ_MESSAGE_BODY"
+#define	NM_A_SZ_MESSAGE_TEXT			"NM_A_SZ_MESSAGE_TEXT"
+#define	NM_A_UD_MESSAGE_TYPE			"NM_A_UD_MESSAGE_TYPE"
+#define	NM_A_FA_PARTICIPANTS			"NM_A_FA_PARTICIPANTS"
+#define	NM_A_FA_INVITES					"NM_A_FA_INVITES"
+#define	NM_A_FA_EVENT					"NM_A_FA_EVENT"
+#define	NM_A_UD_COUNT					"NM_A_UD_COUNT"
+#define	NM_A_UD_DATE					"NM_A_UD_DATE"
+#define	NM_A_UD_EVENT					"NM_A_UD_EVENT"
+#define	NM_A_B_NO_CONTACTS				"NM_A_B_NO_CONTACTS"
+#define	NM_A_B_NO_CUSTOMS				"NM_A_B_NO_CUSTOMS"
+#define	NM_A_B_NO_PRIVACY				"NM_A_B_NO_PRIVACY"
+#define	NM_A_UW_STATUS					"NM_A_UW_STATUS"
+#define	NM_A_UD_OBJECT_ID				"NM_A_UD_OBJECT_ID"
+#define	NM_A_SZ_TRANSACTION_ID			"NM_A_SZ_TRANSACTION_ID"
+#define	NM_A_SZ_RESULT_CODE				"NM_A_SZ_RESULT_CODE"
+#define	NM_A_UD_BUILD					"NM_A_UD_BUILD"
+#define	NM_A_SZ_AUTH_ATTRIBUTE			"NM_A_SZ_AUTH_ATTRIBUTE"
+#define	NM_A_UD_KEEPALIVE				"NM_A_UD_KEEPALIVE"
+#define NM_A_SZ_USER_AGENT				"NM_A_SZ_USER_AGENT"
+
+#define NM_PROTOCOL_VERSION		 		2
+
+#define	NM_FIELD_TRUE					"1"
+#define	NM_FIELD_FALSE					"0"
+
+/**
+ * Count the number of fields 
+ *
+ * @param fields	Field array
+ *
+ * @return			The number of fields in the array.
+ *
+ */
+guint32 nm_count_fields(NMField * fields);
+
+/**
+ * Add a field to the field array. NOTE: field array that is passed
+ * in may be realloced so you should use the returned field array pointer
+ * not the passed in pointer after calling this function.
+ *
+ * @param fields	Field array
+ * @param tag		Tag for the new field
+ * @param size		Size of the field value (if type = binary)
+ * @param method	Field method (see method defines above)
+ * @param flags		Flags for new field
+ * @param value		The value of the field
+ * @param type		The type of the field value
+ *
+ * @return			Pointer to the updated field array
+ *
+ */
+NMField *nm_add_field(NMField * fields, char *tag, guint32 size, guint8 method,
+					  guint8 flags, guint32 value, guint8 type);
+
+/**
+ * Recursively free an array of fields and set pointer to NULL.
+ *
+ * @param fields	Pointer to a field array
+ *
+ */
+void nm_free_fields(NMField ** fields);
+
+/**
+ * Find first field with given tag in field array.
+ *
+ * Note: this will only work for 7-bit ascii tags (which is all that
+ * we use currently).
+ *
+ * @param tag		Tag to search for
+ * @param fields	Field array
+ *
+ * @return			The first matching field, or NULL if no fields match.
+ *
+ */
+NMField *nm_locate_field(char *tag, NMField * fields);
+
+/**
+ * Make a deep copy of a field array
+ *
+ * @param src		The array to copy
+ *
+ * @return			The new (copied) array, which must be freed.
+ *
+ */
+NMField *nm_copy_field_array(NMField * src);
+
+/**
+ * Remove a field and move other fields up to fill the gap
+ *
+ * @param field		The field to remove
+ *
+ */
+void nm_remove_field(NMField * field);
+
+/* Print a field array (for debugging purposes) */
+void nm_print_fields(NMField * fields);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/novell/nmmessage.c	Sat Apr 17 13:55:28 2004 +0000
@@ -0,0 +1,92 @@
+/*
+ * nmmessage.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 "nmmessage.h"
+
+struct _NMMessage
+{
+	NMConference *conference;
+	char *text;
+	gpointer data;
+	guint32 ref_count;
+};
+
+
+/** Message API **/
+
+NMMessage *
+nm_create_message(const char *text)
+{
+	NMMessage *msg = g_new0(NMMessage, 1);
+
+	if (text)
+		msg->text = g_strdup(text);
+
+	msg->ref_count = 1;
+	return msg;
+}
+
+void
+nm_release_message(NMMessage * msg)
+{
+	if (msg && (--(msg->ref_count) == 0)) {
+		if (msg->text)
+			g_free(msg->text);
+
+		if (msg->conference)
+			nm_release_conference(msg->conference);
+
+		g_free(msg);
+	}
+}
+
+const char *
+nm_message_get_text(NMMessage * msg)
+{
+	if (msg == NULL)
+		return NULL;
+
+	return msg->text;
+}
+
+void
+nm_message_set_conference(NMMessage * msg, NMConference * conf)
+{
+	if (msg == NULL || conf == NULL)
+		return;
+
+	/* Need to ref the conference first so that it doesn't
+	 * get released out from under us
+	 */
+	nm_conference_add_ref(conf);
+
+	msg->conference = conf;
+}
+
+NMConference *
+nm_message_get_conference(NMMessage * msg)
+{
+	if (msg == NULL)
+		return NULL;
+
+	return msg->conference;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/novell/nmmessage.h	Sat Apr 17 13:55:28 2004 +0000
@@ -0,0 +1,78 @@
+/*
+ * nmmessage.h
+ *
+ * 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.
+ *
+ */
+
+#ifndef __NM_MESSAGE_H__
+#define __NM_MESSAGE_H__
+
+typedef struct _NMMessage NMMessage;
+
+#include "nmconference.h"
+
+/**
+ * Creates a new message. 
+ *
+ * The returned message should be released by calling
+ * nm_release_message
+ *
+ * @param	text	The message text
+ * @return			A newly allocated message	
+ */
+NMMessage *nm_create_message(const char *text);
+
+/**
+ * Releases a message.
+ *
+ * @param	msg		The message
+ */
+void nm_release_message(NMMessage * msg);
+
+/**
+ * Returns the message text
+ *
+ * @param	msg		The message
+ * @return 			The message text
+ */
+const char *nm_message_get_text(NMMessage * msg);
+
+/**
+ * Sets the conference object for a message
+ *
+ * @param	msg		The message
+ * @param	conf	The conference to associate with the message
+ * @return			RVALUE_OK on success
+ */
+void nm_message_set_conference(NMMessage * msg, NMConference * conf);
+
+/**
+ * Returns the conference object associated with the message
+ *
+ * Note: this does not increment the reference count for the 
+ * conference and the conference should NOT be released with 
+ * nm_release_conference. If the reference needs to be kept
+ * around nm_conference_add_ref should be called.
+ *
+ * @param	msg		The message
+ * @return			The conference associated with this message
+ */
+NMConference *nm_message_get_conference(NMMessage * msg);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/novell/nmrequest.c	Sat Apr 17 13:55:28 2004 +0000
@@ -0,0 +1,153 @@
+/*
+ * nmrequest.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 "nmrequest.h"
+
+struct _NMRequest
+{
+	int trans_id;
+	char *cmd;
+	int gmt;
+	gpointer data;
+	gpointer user_define;
+	nm_response_cb callback;
+	int ref_count;
+	NMERR_T ret_code;
+};
+
+
+NMRequest *
+nm_create_request(const char *cmd, int trans_id, int gmt)
+{
+	NMRequest *req;
+
+	if (cmd == NULL)
+		return NULL;
+
+	req  = g_new0(NMRequest, 1);
+	req->cmd = g_strdup(cmd);
+	req->trans_id = trans_id;
+	req->gmt = gmt;
+	req->ref_count = 1;
+
+	return req;
+}
+
+void
+nm_release_request(NMRequest * req)
+{
+	if (req && (--req->ref_count == 0)) {
+		if (req->cmd)
+			g_free(req->cmd);
+		g_free(req);
+	}
+}
+
+void
+nm_request_add_ref(NMRequest * req)
+{
+	if (req)
+		req->ref_count++;
+}
+
+void
+nm_request_set_callback(NMRequest * req, nm_response_cb callback)
+{
+	if (req)
+		req->callback = callback;
+}
+
+void
+nm_request_set_data(NMRequest * req, gpointer data)
+{
+	if (req)
+		req->data = data;
+}
+
+void
+nm_request_set_user_define(NMRequest * req, gpointer user_define)
+{
+	if (req)
+		req->user_define = user_define;
+}
+
+int
+nm_request_get_trans_id(NMRequest * req)
+{
+	if (req)
+		return req->trans_id;
+	else
+		return -1;
+}
+
+const char *
+nm_request_get_cmd(NMRequest * req)
+{
+	if (req == NULL)
+		return NULL;
+
+	return req->cmd;
+}
+
+gpointer
+nm_request_get_data(NMRequest * req)
+{
+	if (req == NULL)
+		return NULL;
+
+	return req->data;
+}
+
+gpointer
+nm_request_get_user_define(NMRequest * req)
+{
+	if (req == NULL)
+		return NULL;
+
+	return req->user_define;
+}
+
+nm_response_cb
+nm_request_get_callback(NMRequest * req)
+{
+	if (req == NULL)
+		return NULL;
+
+	return req->callback;
+}
+
+
+void
+nm_request_set_ret_code(NMRequest * req, NMERR_T rc)
+{
+	if (req)
+		req->ret_code = rc;
+}
+
+NMERR_T
+nm_request_get_ret_code(NMRequest * req)
+{
+	if (req)
+		return req->ret_code;
+	else
+		return (NMERR_T) - 1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/novell/nmrequest.h	Sat Apr 17 13:55:28 2004 +0000
@@ -0,0 +1,151 @@
+/*
+ * nmrequest.h
+ *
+ * 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.
+ *
+ */
+
+#ifndef __NM_REQUEST_H__
+#define __NM_REQUEST_H__
+
+typedef struct _NMRequest NMRequest;
+
+#include "nmuser.h"
+
+/** 
+ *	Create a new request object. Object must be release with nm_release_object.
+ *
+ *	@param	cmd				The request command string (e.g. "login")
+ *	@param	trans_id		The request transaction id
+ *	@param	gmt				The time in seconds that the request was created
+ *
+ *	@return The new request object
+ */
+NMRequest *nm_create_request(const char *cmd, int trans_id, int gmt);
+
+/** 
+ *	Release a request object.
+ *
+ *	@param	req		The request to release
+ */
+void nm_release_request(NMRequest * req);
+
+/** 
+ *  Add a new reference to this object. This reference must be released by
+ *  a call to nm_release_object.
+ *
+ *	@param	req				The request object
+ */
+void nm_request_add_ref(NMRequest * req);
+
+/** 
+ *	Set the response callback for this request object. This is the callback
+ *  that will be made when we get a response from the server.
+ *
+ *	@param	req			The request object
+ *	@param	callback	The response callback
+ *
+ */
+void nm_request_set_callback(NMRequest * req, nm_response_cb callback);
+
+/** 
+ *	Set the response data. This will be set differently depending on 
+ *  the request type (for example to nm_send_get_details will set this
+ *  to be the newly create NMUserRecord object).
+ *
+ *	@param	req			The request object
+ *	@param	data		Pointer to some data
+ *
+ */
+void nm_request_set_data(NMRequest * req, gpointer data);
+
+/** 
+ *	Set the user defined data. This is the data that the client
+ *  passes to the various nm_send_* functions. We will pass it
+ *  back when we make the callback.
+ *
+ *	@param	req				The request object
+ *	@param	user_define		Pointer to some data	
+ *
+ */
+void nm_request_set_user_define(NMRequest * req, gpointer user_define);
+
+/** 
+ *	Set the return code. This is the return code that we recieved in 
+ *  the server response fields.
+ *
+ *	@param	req			The request object
+ *	@param	rc			The return code to set
+ */
+void nm_request_set_ret_code(NMRequest * req, NMERR_T rc);
+
+/** 
+ *	Get the transaction id for this request.
+ *
+ *	@param	req			The request object
+ *
+ * 	@return	The transaction id.
+ */
+int nm_request_get_trans_id(NMRequest * req);
+
+/** 
+ *	Get the command (request type) for this request.
+ *
+ *	@param	req			The request object
+ *
+ * 	@return	The request cmd
+ */
+const char *nm_request_get_cmd(NMRequest * req);
+
+/** 
+ *	Get the response data for this request
+ *
+ *	@param	req			The request object
+ *
+ * 	@return	The response data
+ */
+gpointer nm_request_get_data(NMRequest * req);
+
+/** 
+ *	Get the user defined data for this request
+ *
+ *	@param	req			The request object
+ *
+ * 	@return	The user defined data
+ */
+gpointer nm_request_get_user_define(NMRequest * req);
+
+/** 
+ *	Get the response callback for this request
+ *
+ *	@param	req			The request object
+ *
+ * 	@return	The response callback
+ */
+nm_response_cb nm_request_get_callback(NMRequest * req);
+
+/** 
+ *	Get the return code
+ *
+ *	@param	req			The request object
+ *
+ * 	@return	The return code (from the response fields)
+ */
+NMERR_T nm_request_get_ret_code(NMRequest * req);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/novell/nmuser.c	Sat Apr 17 13:55:28 2004 +0000
@@ -0,0 +1,1777 @@
+/*
+ * nmuser.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 <glib.h>
+#include <string.h>
+#include "nmfield.h"
+#include "nmuser.h"
+#include "nmconn.h"
+#include "nmcontact.h"
+#include "nmuserrecord.h"
+#include "util.h"
+
+/* This is the template that we wrap outgoing messages in, since the other 
+ * GW Messenger clients expect messages to be in RTF.
+ */
+#define RTF_TEMPLATE 	"{\\rtf1\\fbidis\\ansi\\ansicpg1252\\deff0\\deflang1033"\
+                        "{\\fonttbl{\\f0\\fswiss\\fprq2\\fcharset0 "\
+                        "Microsoft Sans Serif;}}\n{\\colortbl ;\\red0"\
+                        "\\green0\\blue0;}\n\\viewkind4\\uc1\\pard\\ltrpar"\
+                        "\\li50\\ri50\\cf1\\f0\\fs20 %s\\par\n}"
+
+static NMERR_T nm_process_response(NMUser * user);
+
+static void _update_contact_list(NMUser * user, NMField * fields);
+
+/**
+ * See header for comments on on "public" functions
+ */
+
+NMUser *
+nm_initialize_user(const char *name, const char *server_addr,
+				   int port, gpointer data, nm_event_cb event_callback)
+{
+	if (name == NULL || server_addr == NULL || event_callback == NULL)
+		return NULL;
+
+	NMUser *user = g_new0(NMUser, 1);
+
+	user->conn = g_new0(NMConn, 1);
+
+	user->contacts =
+		g_hash_table_new_full(g_str_hash, nm_utf8_str_equal,
+							  g_free, (GDestroyNotify) nm_release_contact);
+
+	user->user_records =
+		g_hash_table_new_full(g_str_hash, nm_utf8_str_equal, g_free,
+							  (GDestroyNotify) nm_release_user_record);
+
+	user->display_id_to_dn = g_hash_table_new_full(g_str_hash, nm_utf8_str_equal,
+												   g_free, g_free);
+
+	user->name = g_strdup(name);
+	user->conn->addr = g_strdup(server_addr);
+	user->conn->port = port;
+	user->evt_callback = event_callback;
+	user->client_data = data;
+
+	return user;
+}
+
+
+void
+nm_deinitialize_user(NMUser * user)
+{
+	NMConn *conn = user->conn;
+
+	g_free(conn->addr);
+	g_free(conn);
+
+	if (user->contacts) {
+		g_hash_table_destroy(user->contacts);
+	}
+
+	if (user->user_records) {
+		g_hash_table_destroy(user->user_records);
+	}
+
+	if (user->display_id_to_dn) {
+		g_hash_table_destroy(user->display_id_to_dn);
+	}
+
+	if (user->name) {
+		g_free(user->name);
+	}
+
+	if (user->user_record) {
+		nm_release_user_record(user->user_record);
+	}
+
+	nm_conference_list_free(user);
+	nm_destroy_contact_list(user);
+
+	g_free(user);
+}
+
+NMERR_T
+nm_send_login(NMUser * user, const char *pwd, const char *my_addr,
+			  const char *user_agent, nm_response_cb callback, gpointer data)
+{
+	NMERR_T rc = NM_OK;
+	NMField *fields = NULL;
+	NMRequest *req = NULL;
+
+	if (user == NULL || pwd == NULL || user_agent == NULL) {
+		return NMERR_BAD_PARM;
+	}
+
+	fields = nm_add_field(fields, NM_A_SZ_USERID, 0, NMFIELD_METHOD_VALID, 0,
+						  (guint32) g_strdup(user->name), NMFIELD_TYPE_UTF8);
+	
+	fields = nm_add_field(fields, NM_A_SZ_CREDENTIALS, 0, NMFIELD_METHOD_VALID, 0,
+						  (guint32) g_strdup(pwd), NMFIELD_TYPE_UTF8);
+
+	fields = nm_add_field(fields, NM_A_SZ_USER_AGENT, 0, NMFIELD_METHOD_VALID, 0,
+						  (guint32) g_strdup(user_agent), NMFIELD_TYPE_UTF8);
+
+	fields = nm_add_field(fields, NM_A_UD_BUILD, 0, NMFIELD_METHOD_VALID, 0,
+						  (guint32) NM_PROTOCOL_VERSION,
+						  NMFIELD_TYPE_UDWORD);
+	if (my_addr) {
+		fields = nm_add_field(fields, NM_A_IP_ADDRESS, 0, NMFIELD_METHOD_VALID, 0,
+							  (guint32) g_strdup(my_addr), NMFIELD_TYPE_UTF8);
+	}
+
+	/* Send the login */
+	rc = nm_send_request(user->conn, "login", fields, &req);
+	if (rc == NM_OK && req != NULL) {
+		nm_request_set_callback(req, callback);
+		nm_request_set_user_define(req, data);
+		nm_conn_add_request_item(user->conn, req);
+	}
+
+	if (fields) {
+		nm_free_fields(&fields);
+	}
+
+	if (req) {
+		nm_release_request(req);
+	}
+
+	return rc;
+}
+
+NMERR_T
+nm_send_set_status(NMUser * user, int status, const char *text,
+				   const char *auto_resp, nm_response_cb callback, gpointer data)
+{
+	NMERR_T rc = NM_OK;
+	NMField *fields = NULL;
+	NMRequest *req = NULL;
+
+	if (user == NULL)
+		return NMERR_BAD_PARM;
+
+	/* Add the status */
+	fields = nm_add_field(fields, NM_A_SZ_STATUS, 0, NMFIELD_METHOD_VALID, 0,
+						  (guint32) g_strdup_printf("%d", status),
+						  NMFIELD_TYPE_UTF8);
+
+	/* Add the status text and auto reply text if there is any */
+	if (text) {
+		fields = nm_add_field(fields, NM_A_SZ_STATUS_TEXT,
+							  0, NMFIELD_METHOD_VALID, 0,
+							  (guint32) g_strdup(text), NMFIELD_TYPE_UTF8);
+	}
+
+	if (auto_resp) {
+		fields = nm_add_field(fields, NM_A_SZ_MESSAGE_BODY, 0,
+							  NMFIELD_METHOD_VALID, 0,
+							  (guint32) g_strdup(auto_resp), NMFIELD_TYPE_UTF8);
+	}
+
+	rc = nm_send_request(user->conn, "setstatus", fields, &req);
+	if (rc == NM_OK && req) {
+		nm_request_set_callback(req, callback);
+		nm_request_set_user_define(req, data);
+		nm_conn_add_request_item(user->conn, req);
+	}
+
+	if (fields) {
+		nm_free_fields(&fields);
+	}
+
+	if (req) {
+		nm_release_request(req);
+	}
+
+	return rc;
+}
+
+NMERR_T
+nm_send_get_details(NMUser * user, const char *name,
+					nm_response_cb callback, gpointer data)
+{
+	NMERR_T rc = NM_OK;
+	NMField *fields = NULL;
+	NMRequest *req = NULL;
+
+	if (user == NULL || name == NULL)
+		return NMERR_BAD_PARM;
+
+	/* Add in DN or display id */
+	if (strstr("=", name)) {
+		fields = nm_add_field(fields, NM_A_SZ_DN, 0, NMFIELD_METHOD_VALID, 0,
+							  (guint32) g_strdup(name), NMFIELD_TYPE_DN);
+	} else {
+
+		const char *dn = nm_lookup_dn(user, name);
+
+		if (dn) {
+			fields = nm_add_field(fields, NM_A_SZ_DN, 0, NMFIELD_METHOD_VALID, 0,
+								  (guint32) g_strdup(name), NMFIELD_TYPE_DN);
+		} else {
+			fields =
+				nm_add_field(fields, NM_A_SZ_USERID, 0, NMFIELD_METHOD_VALID, 0,
+							 (guint32) g_strdup(name), NMFIELD_TYPE_UTF8);
+		}
+
+	}
+
+	rc = nm_send_request(user->conn, "getdetails", fields, &req);
+	if (rc == NM_OK) {
+		nm_request_set_callback(req, callback);
+		nm_request_set_user_define(req, data);
+		nm_conn_add_request_item(user->conn, req);
+	}
+
+	if (fields)
+		nm_free_fields(&fields);
+
+	if (req)
+		nm_release_request(req);
+
+	return rc;
+}
+
+NMERR_T
+nm_send_create_conference(NMUser * user, NMConference * conference,
+						  nm_response_cb callback, gpointer message)
+{
+	NMERR_T rc = NM_OK;
+	NMField *fields = NULL;
+	NMField *tmp = NULL;
+	NMField *field = NULL;
+	NMRequest *req = NULL;
+	int count, i;
+
+	if (user == NULL || conference == NULL)
+		return NMERR_BAD_PARM;
+
+	/* Add in a blank guid */
+	tmp = nm_add_field(tmp, NM_A_SZ_OBJECT_ID, 0, NMFIELD_METHOD_VALID, 0,
+					   (guint32) g_strdup(BLANK_GUID), NMFIELD_TYPE_UTF8);
+
+	fields = nm_add_field(fields, NM_A_FA_CONVERSATION, 0,
+						  NMFIELD_METHOD_VALID, 0, (guint32) tmp,
+						  NMFIELD_TYPE_ARRAY);
+	tmp = NULL;
+
+	/* Add participants in */
+	count = nm_conference_get_participant_count(conference);
+	for (i = 0; i < count; i++) {
+		NMUserRecord *user_record = nm_conference_get_participant(conference, i);
+
+		if (user_record) {
+			fields = nm_add_field(fields, NM_A_SZ_DN,
+								  0, NMFIELD_METHOD_VALID, 0,
+								  (guint32)
+								  g_strdup(nm_user_record_get_dn(user_record)),
+								  NMFIELD_TYPE_DN);
+		}
+	}
+
+	/* Add our user in */
+	field = nm_locate_field(NM_A_SZ_DN, user->fields);
+	if (field) {
+		fields = nm_add_field(fields, NM_A_SZ_DN,
+							  0, NMFIELD_METHOD_VALID, 0,
+							  (guint32) g_strdup((char *) field->value),
+							  NMFIELD_TYPE_DN);
+	}
+
+	rc = nm_send_request(user->conn, "createconf", fields, &req);
+	if (rc == NM_OK && req) {
+		nm_request_set_callback(req, callback);
+		nm_request_set_data(req, conference);
+		nm_request_set_user_define(req, message);
+		nm_conn_add_request_item(user->conn, req);
+	}
+
+	if (req)
+		nm_release_request(req);
+
+	if (fields)
+		nm_free_fields(&fields);
+
+	return rc;
+}
+
+NMERR_T
+nm_send_leave_conference(NMUser * user, NMConference * conference,
+						 nm_response_cb callback, gpointer message)
+{
+
+	NMERR_T rc = NM_OK;
+	NMField *fields = NULL;
+	NMField *tmp = NULL;
+	NMRequest *req = NULL;
+
+	if (user == NULL || conference == NULL)
+		return NMERR_BAD_PARM;
+
+	/* Add in the conference guid */
+	tmp = nm_add_field(tmp, NM_A_SZ_OBJECT_ID, 0, NMFIELD_METHOD_VALID, 0,
+					   (guint32) g_strdup(nm_conference_get_guid(conference)),
+					   NMFIELD_TYPE_UTF8);
+
+	fields = nm_add_field(fields, NM_A_FA_CONVERSATION, 0,
+						  NMFIELD_METHOD_VALID, 0, (guint32) tmp,
+						  NMFIELD_TYPE_ARRAY);
+	tmp = NULL;
+	
+	/* Send the request to the server */
+	rc = nm_send_request(user->conn, "leaveconf", fields, &req);
+	if (rc == NM_OK && req) {
+		nm_request_set_callback(req, callback);
+		nm_request_set_data(req, conference);
+		nm_request_set_user_define(req, message);
+		nm_conn_add_request_item(user->conn, req);
+	}
+
+	if (req)
+		nm_release_request(req);
+
+	if (fields)
+		nm_free_fields(&fields);
+
+	return rc;
+}
+
+NMERR_T
+nm_send_join_conference(NMUser * user, NMConference * conference,
+						nm_response_cb callback, gpointer data)
+{
+	NMERR_T rc = NM_OK;
+	NMField *fields = NULL, *tmp = NULL;
+	NMRequest *req = NULL;
+
+	if (user == NULL || conference == NULL)
+		return NMERR_BAD_PARM;
+
+	/* Add in the conference guid */
+	tmp = nm_add_field(tmp, NM_A_SZ_OBJECT_ID, 0, NMFIELD_METHOD_VALID, 0,
+					   (guint32) g_strdup(nm_conference_get_guid(conference)),
+					   NMFIELD_TYPE_UTF8);
+
+	fields = nm_add_field(fields, NM_A_FA_CONVERSATION, 0,
+						  NMFIELD_METHOD_VALID, 0, (guint32) tmp,
+						  NMFIELD_TYPE_ARRAY);
+	tmp = NULL;
+
+	/* Send the request to the server */
+	rc = nm_send_request(user->conn, "joinconf", fields, &req);
+
+	/* Set up the request object so that we know what to do
+	 * when we get a response
+	 */
+	if (rc == NM_OK && req) {
+		nm_request_set_callback(req, callback);
+		nm_request_set_data(req, conference);
+		nm_request_set_user_define(req, data);
+		nm_conn_add_request_item(user->conn, req);
+	}
+
+	if (req)
+		nm_release_request(req);
+
+	if (fields)
+		nm_free_fields(&fields);
+
+	return rc;
+}
+
+NMERR_T
+nm_send_reject_conference(NMUser * user, NMConference * conference,
+						  nm_response_cb callback, gpointer data)
+{
+	NMERR_T rc = NM_OK;
+	NMField *fields = NULL;
+	NMField *tmp = NULL;
+	NMRequest *req = NULL;
+
+	if (user == NULL || conference == NULL)
+		return NMERR_BAD_PARM;
+
+	/* Add in the conference guid */
+	tmp = nm_add_field(tmp, NM_A_SZ_OBJECT_ID, 0, NMFIELD_METHOD_VALID, 0,
+					   (guint32) g_strdup(nm_conference_get_guid(conference)),
+					   NMFIELD_TYPE_UTF8);
+
+	fields = nm_add_field(fields, NM_A_FA_CONVERSATION, 0,
+						  NMFIELD_METHOD_VALID, 0, (guint32) tmp,
+						  NMFIELD_TYPE_ARRAY);
+	tmp = NULL;
+
+	/* Send the request to the server */
+	rc = nm_send_request(user->conn, "rejectconf", fields, &req);
+
+	/* Set up the request object so that we know what to do
+	 * when we get a response
+	 */
+	if (rc == NM_OK && req) {
+		nm_request_set_callback(req, callback);
+		nm_request_set_data(req, conference);
+		nm_request_set_user_define(req, data);
+		nm_conn_add_request_item(user->conn, req);
+	}
+
+	if (req)
+		nm_release_request(req);
+
+	if (fields)
+		nm_free_fields(&fields);
+
+	return rc;
+}
+
+NMERR_T
+nm_send_message(NMUser * user, NMMessage * message, nm_response_cb callback)
+{
+	NMERR_T rc = NM_OK;
+	const char *text;
+	NMField *fields = NULL, *tmp = NULL;
+	NMRequest *req = NULL;
+	NMConference *conf;
+	NMUserRecord *user_record;
+	int count, i;
+
+	if (user == NULL || message == NULL) {
+		return NMERR_BAD_PARM;
+	}
+
+	conf = nm_message_get_conference(message);
+	if (!nm_conference_is_instantiated(conf)) {
+		rc = NMERR_CONFERENCE_NOT_INSTANTIATED;
+	} else {
+
+		tmp = nm_add_field(tmp, NM_A_SZ_OBJECT_ID, 0, NMFIELD_METHOD_VALID, 0,
+						   (guint32) g_strdup(nm_conference_get_guid(conf)),
+						   NMFIELD_TYPE_UTF8);
+
+		fields =
+			nm_add_field(fields, NM_A_FA_CONVERSATION, 0, NMFIELD_METHOD_VALID, 0,
+						 (guint32) tmp, NMFIELD_TYPE_ARRAY);
+		tmp = NULL;
+
+		/* Add RTF and plain text versions of the message */
+		text = nm_message_get_text(message);
+
+		tmp = nm_add_field(tmp, NM_A_SZ_MESSAGE_BODY, 0, NMFIELD_METHOD_VALID, 0,
+						   (guint32) g_strdup_printf(RTF_TEMPLATE, text),
+						   NMFIELD_TYPE_UTF8);
+
+		tmp = nm_add_field(tmp, NM_A_UD_MESSAGE_TYPE, 0, NMFIELD_METHOD_VALID, 0,
+						   (guint32) 0, NMFIELD_TYPE_UDWORD);
+
+		tmp = nm_add_field(tmp, NM_A_SZ_MESSAGE_TEXT, 0, NMFIELD_METHOD_VALID, 0,
+						   (guint32) g_strdup(text), NMFIELD_TYPE_UTF8);
+
+		fields = nm_add_field(fields, NM_A_FA_MESSAGE, 0, NMFIELD_METHOD_VALID, 0,
+							  (guint32) tmp, NMFIELD_TYPE_ARRAY);
+		tmp = NULL;
+
+		/* Add participants */
+		count = nm_conference_get_participant_count(conf);
+		for (i = 0; i < count; i++) {
+			user_record = nm_conference_get_participant(conf, i);
+			if (user_record) {
+				fields =
+					nm_add_field(fields, NM_A_SZ_DN, 0, NMFIELD_METHOD_VALID, 0,
+								 (guint32)
+								 g_strdup(nm_user_record_get_dn(user_record)),
+								 NMFIELD_TYPE_DN);
+			}
+		}
+
+		/* Send the request */
+		rc = nm_send_request(user->conn, "sendmessage", fields, &req);
+		if (rc == NM_OK && req) {
+			nm_request_set_callback(req, callback);
+			nm_conn_add_request_item(user->conn, req);
+		}
+	}
+
+	if (fields) {
+		nm_free_fields(&fields);
+	}
+
+	if (req) {
+		nm_release_request(req);
+	}
+
+	return rc;
+}
+
+NMERR_T
+nm_send_typing(NMUser * user, NMConference * conf,
+			   gboolean typing, nm_response_cb callback)
+{
+	NMERR_T rc = NM_OK;
+	char *str = NULL;
+	NMField *fields = NULL, *tmp = NULL;
+	NMRequest *req = NULL;
+
+	if (user == NULL || conf == NULL) {
+		return NMERR_BAD_PARM;
+	}
+
+	if (!nm_conference_is_instantiated(conf)) {
+		rc = NMERR_CONFERENCE_NOT_INSTANTIATED;
+	} else {
+		/* Add the conference GUID */
+		tmp = nm_add_field(tmp, NM_A_SZ_OBJECT_ID, 0, NMFIELD_METHOD_VALID, 0,
+						   (guint32) g_strdup(nm_conference_get_guid(conf)),
+						   NMFIELD_TYPE_UTF8);
+
+		/* Add typing type */
+		str = g_strdup_printf("%d",
+							  (typing ? NMEVT_USER_TYPING :
+							   NMEVT_USER_NOT_TYPING));
+
+		tmp = nm_add_field(tmp, NM_A_SZ_TYPE, 0, NMFIELD_METHOD_VALID, 0,
+						   (guint32) str, NMFIELD_TYPE_UTF8);
+
+		fields =
+			nm_add_field(fields, NM_A_FA_CONVERSATION, 0, NMFIELD_METHOD_VALID, 0,
+						 (guint32) tmp, NMFIELD_TYPE_ARRAY);
+		tmp = NULL;
+
+		rc = nm_send_request(user->conn, "sendtyping", fields, &req);
+		if (rc == NM_OK && req) {
+			nm_request_set_callback(req, callback);
+			nm_conn_add_request_item(user->conn, req);
+		}
+	}
+
+	if (req)
+		nm_release_request(req);
+
+	if (fields)
+		nm_free_fields(&fields);
+
+	return rc;
+}
+
+NMERR_T
+nm_send_create_contact(NMUser * user, NMFolder * folder,
+					   NMContact * contact, nm_response_cb callback,
+					   gpointer data)
+{
+	NMERR_T rc = NM_OK;
+	NMField *fields = NULL;
+	NMRequest *req = NULL;
+	const char *name = NULL;
+	const char *display_name = NULL;
+
+	if (user == NULL || folder == NULL || contact == NULL) {
+		return NMERR_BAD_PARM;
+	}
+
+	/* Add parent ID */
+	fields = nm_add_field(fields, NM_A_SZ_PARENT_ID, 0, NMFIELD_METHOD_VALID, 0,
+						  (guint32) g_strdup_printf("%d",
+													nm_folder_get_id(folder)),
+						  NMFIELD_TYPE_UTF8);
+
+	/* Check to see if userid is current user and return an error? */
+
+	/* Check to see if contact already exists and return an error? */
+
+	/* Add userid or dn */
+	name = nm_contact_get_dn(contact);
+	if (name == NULL)
+		return NMERR_BAD_PARM;
+
+	if (strstr("=", name)) {
+		fields = nm_add_field(fields, NM_A_SZ_DN, 0, NMFIELD_METHOD_VALID, 0,
+							  (guint32) g_strdup(name), NMFIELD_TYPE_DN);
+	} else {
+		fields = nm_add_field(fields, NM_A_SZ_USERID, 0, NMFIELD_METHOD_VALID, 0,
+							  (guint32) g_strdup(name), NMFIELD_TYPE_UTF8);
+	}
+
+	/* Add display name */
+	display_name = nm_contact_get_display_name(contact);
+	if (display_name)
+		fields = nm_add_field(fields, NM_A_SZ_DISPLAY_NAME, 0, NMFIELD_METHOD_VALID, 0,
+							  (guint32) g_strdup(display_name), NMFIELD_TYPE_UTF8);
+		
+	/* Dispatch the request */
+	rc = nm_send_request(user->conn, "createcontact", fields, &req);
+	if (rc == NM_OK && req) {
+		nm_request_set_callback(req, callback);
+		nm_request_set_data(req, contact);
+		nm_request_set_user_define(req, data);
+		nm_conn_add_request_item(user->conn, req);
+	}
+
+	if (fields)
+		nm_free_fields(&fields);
+
+	if (req)
+		nm_release_request(req);
+
+	return rc;
+}
+
+NMERR_T
+nm_send_remove_contact(NMUser * user, NMFolder * folder,
+					   NMContact * contact, nm_response_cb callback,
+					   gpointer data)
+{
+	NMERR_T rc = NM_OK;
+	NMField *fields = NULL;
+	NMRequest *req = NULL;
+
+	if (user == NULL || folder == NULL || contact == NULL) {
+		return NMERR_BAD_PARM;
+	}
+
+	/* Add parent id */
+	fields = nm_add_field(fields, NM_A_SZ_PARENT_ID, 0, NMFIELD_METHOD_VALID, 0,
+						  (guint32) g_strdup_printf("%d",
+													nm_folder_get_id(folder)),
+						  NMFIELD_TYPE_UTF8);
+
+	/* Add object id */
+	fields = nm_add_field(fields, NM_A_SZ_OBJECT_ID, 0, NMFIELD_METHOD_VALID, 0,
+						  (guint32) g_strdup_printf("%d",
+													nm_contact_get_id(contact)),
+						  NMFIELD_TYPE_UTF8);
+
+	/* Dispatch the request */
+	rc = nm_send_request(user->conn, "deletecontact", fields, &req);
+	if (rc == NM_OK && req) {
+		nm_request_set_callback(req, callback);
+		nm_request_set_data(req, contact);
+		nm_request_set_user_define(req, data);
+		nm_conn_add_request_item(user->conn, req);
+	}
+
+	if (fields)
+		nm_free_fields(&fields);
+
+	if (req)
+		nm_release_request(req);
+
+	return rc;
+}
+
+NMERR_T
+nm_send_create_folder(NMUser * user, const char *name,
+					  nm_response_cb callback, gpointer data)
+{
+	NMERR_T rc = NM_OK;
+	NMField *fields = NULL;
+	NMRequest *req = NULL;
+
+	if (user == NULL || name == NULL) {
+		return NMERR_BAD_PARM;
+	}
+
+	/* Add parent ID */
+	fields = nm_add_field(fields, NM_A_SZ_PARENT_ID, 0, NMFIELD_METHOD_VALID, 0,
+						  (guint32) g_strdup("0"), NMFIELD_TYPE_UTF8);
+
+	/* Add name of the folder to add */
+	fields =
+		nm_add_field(fields, NM_A_SZ_DISPLAY_NAME, 0, NMFIELD_METHOD_VALID, 0,
+					 (guint32) g_strdup(name), NMFIELD_TYPE_UTF8);
+
+	/* Add sequence, for now just put it at the bottom */
+	fields =
+		nm_add_field(fields, NM_A_SZ_SEQUENCE_NUMBER, 0, NMFIELD_METHOD_VALID, 0,
+					 (guint32) g_strdup("-1"), NMFIELD_TYPE_UTF8);
+
+	/* Dispatch the request */
+	rc = nm_send_request(user->conn, "createfolder", fields, &req);
+	if (rc == NM_OK && req) {
+		nm_request_set_callback(req, callback);
+		nm_request_set_data(req, g_strdup(name));
+		nm_request_set_user_define(req, data);
+		nm_conn_add_request_item(user->conn, req);
+	}
+
+	if (fields)
+		nm_free_fields(&fields);
+
+	if (req)
+		nm_release_request(req);
+
+	return rc;
+}
+
+NMERR_T
+nm_send_remove_folder(NMUser * user, NMFolder * folder,
+					  nm_response_cb callback, gpointer data)
+{
+	NMERR_T rc = NM_OK;
+	NMField *fields = NULL;
+	NMRequest *req = NULL;
+
+	if (user == NULL || folder == NULL) {
+		return NMERR_BAD_PARM;
+	}
+
+	/* Add the object id */
+	fields = nm_add_field(fields, NM_A_SZ_OBJECT_ID, 0, NMFIELD_METHOD_VALID, 0,
+						  (guint32) g_strdup_printf("%d", nm_folder_get_id(folder)),
+						  NMFIELD_TYPE_UTF8);
+
+	/* Dispatch the request */
+	rc = nm_send_request(user->conn, "deletecontact", fields, &req);
+	if (rc == NM_OK && req) {
+		nm_request_set_callback(req, callback);
+		nm_request_set_data(req, folder);
+		nm_request_set_user_define(req, data);
+		nm_conn_add_request_item(user->conn, req);
+	}
+
+	if (fields)
+		nm_free_fields(&fields);
+
+	if (req)
+		nm_release_request(req);
+
+	return rc;
+}
+
+NMERR_T
+nm_send_get_status(NMUser * user, NMUserRecord * user_record,
+				   nm_response_cb callback, gpointer data)
+{
+	NMERR_T rc = NM_OK;
+	NMField *fields = NULL;
+	NMRequest *req = NULL;
+	const char *dn;
+
+	if (user == NULL || user_record == NULL)
+		return NMERR_BAD_PARM;
+
+	/* Add DN to field list */
+	dn = nm_user_record_get_dn(user_record);
+	if (dn == NULL)
+		return (NMERR_T) -1;
+
+	fields = nm_add_field(fields, NM_A_SZ_DN, 0, NMFIELD_METHOD_VALID, 0,
+						  (guint32) g_strdup(dn), NMFIELD_TYPE_UTF8);
+
+	/* Dispatch the request */
+	rc = nm_send_request(user->conn, "getstatus", fields, &req);
+	if (rc == NM_OK && req) {
+		nm_request_set_callback(req, callback);
+		nm_request_set_data(req, user_record);
+		nm_request_set_user_define(req, data);
+		nm_conn_add_request_item(user->conn, req);
+	}
+
+	if (fields)
+		nm_free_fields(&fields);
+
+	if (req)
+		nm_release_request(req);
+
+	return rc;
+}
+
+NMERR_T
+nm_send_rename_contact(NMUser * user, NMContact * contact,
+					   const char *new_name, nm_response_cb callback,
+					   gpointer data)
+{
+	NMERR_T rc = NM_OK;
+	NMField *field = NULL, *fields = NULL, *list = NULL;
+	NMRequest *req = NULL;
+
+	if (user == NULL || contact == NULL || new_name == NULL)
+		return NMERR_BAD_PARM;
+
+	/* Create field list for current contact */
+	field = nm_contact_to_fields(contact);
+	if (field) {
+
+		fields =
+			nm_add_field(fields, NM_A_FA_CONTACT, 0, NMFIELD_METHOD_DELETE, 0,
+						 (guint32) field, NMFIELD_TYPE_ARRAY);
+		field = NULL;
+
+		/* Update the contacts display name locally */
+		nm_contact_set_display_name(contact, new_name);
+
+		/* Create field list for updated contact */
+		field = nm_contact_to_fields(contact);
+		if (field) {
+			fields =
+				nm_add_field(fields, NM_A_FA_CONTACT, 0, NMFIELD_METHOD_ADD, 0,
+							 (guint32) field, NMFIELD_TYPE_ARRAY);
+			field = NULL;
+
+			/* Package it up */
+			list =
+				nm_add_field(list, NM_A_FA_CONTACT_LIST, 0, NMFIELD_METHOD_VALID,
+							 0, (guint32) fields, NMFIELD_TYPE_ARRAY);
+			fields = NULL;
+
+			rc = nm_send_request(user->conn, "updateitem", list, &req);
+			if (rc == NM_OK && req) {
+				nm_request_set_callback(req, callback);
+				nm_request_set_data(req, contact);
+				nm_request_set_user_define(req, data);
+				nm_conn_add_request_item(user->conn, req);
+			}
+		}
+	}
+
+	if (list)
+		nm_free_fields(&list);
+
+	return rc;
+}
+
+NMERR_T
+nm_send_rename_folder(NMUser * user, NMFolder * folder, const char *new_name,
+					  nm_response_cb callback, gpointer data)
+{
+	NMERR_T rc = NM_OK;
+	NMField *field = NULL, *fields = NULL, *list = NULL;
+	NMRequest *req = NULL;
+
+	if (user == NULL || folder == NULL || new_name == NULL)
+		return NMERR_BAD_PARM;
+
+	/* Make sure folder does not already exist!? */
+	if (nm_find_folder(user, new_name))
+		return NMERR_FOLDER_EXISTS;
+
+	/* Create field list for current folder */
+	field = nm_folder_to_fields(folder);
+	if (field) {
+
+		fields = nm_add_field(fields, NM_A_FA_FOLDER, 0, NMFIELD_METHOD_DELETE, 0,
+							  (guint32) field, NMFIELD_TYPE_ARRAY);
+		field = NULL;
+
+		/* Update the folders display name locally */
+		nm_folder_set_name(folder, new_name);
+
+		/* Create field list for updated folder */
+		field = nm_folder_to_fields(folder);
+		if (field) {
+			fields =
+				nm_add_field(fields, NM_A_FA_FOLDER, 0, NMFIELD_METHOD_ADD, 0,
+							 (guint32) field, NMFIELD_TYPE_ARRAY);
+			field = NULL;
+
+			/* Package it up */
+			list =
+				nm_add_field(list, NM_A_FA_CONTACT_LIST, 0, NMFIELD_METHOD_VALID,
+							 0, (guint32) fields, NMFIELD_TYPE_ARRAY);
+			fields = NULL;
+
+			rc = nm_send_request(user->conn, "updateitem", list, &req);
+			if (rc == NM_OK && req) {
+				nm_request_set_callback(req, callback);
+				nm_request_set_data(req, folder);
+				nm_request_set_user_define(req, data);
+				nm_conn_add_request_item(user->conn, req);
+			}
+		}
+	}
+
+	if (list)
+		nm_free_fields(&list);
+
+	return rc;
+}
+
+NMERR_T
+nm_send_move_contact(NMUser * user, NMContact * contact, NMFolder * folder,
+					 nm_response_cb callback, gpointer data)
+{
+	NMERR_T rc = NM_OK;
+	NMField *field = NULL, *fields = NULL, *list = NULL;
+	NMRequest *req = NULL;
+
+	if (user == NULL || contact == NULL || folder == NULL)
+		return NMERR_BAD_PARM;
+
+	/* Create field list for the contact */
+	field = nm_contact_to_fields(contact);
+	if (field) {
+
+		fields =
+			nm_add_field(fields, NM_A_FA_CONTACT, 0, NMFIELD_METHOD_DELETE, 0,
+						 (guint32) field, NMFIELD_TYPE_ARRAY);
+		field = NULL;
+
+		/* Wrap the contact up and add it to the request field list */
+		list =
+			nm_add_field(list, NM_A_FA_CONTACT_LIST, 0, NMFIELD_METHOD_VALID, 0,
+						 (guint32) fields, NMFIELD_TYPE_ARRAY);
+		fields = NULL;
+
+		/* Add sequence number */
+		list =
+			nm_add_field(list, NM_A_SZ_SEQUENCE_NUMBER, 0, NMFIELD_METHOD_VALID,
+						 0, (guint32) g_strdup("-1"), NMFIELD_TYPE_UTF8);
+
+		/* Add parent ID */
+		list = nm_add_field(list, NM_A_SZ_PARENT_ID, 0, NMFIELD_METHOD_VALID, 0,
+							(guint32) g_strdup_printf("%d",
+													  nm_folder_get_id(folder)),
+							NMFIELD_TYPE_UTF8);
+
+		/* Dispatch the request */
+		rc = nm_send_request(user->conn, "movecontact", list, &req);
+		if (rc == NM_OK && req) {
+			nm_request_set_callback(req, callback);
+			nm_request_set_data(req, contact);
+			nm_request_set_user_define(req, data);
+			nm_conn_add_request_item(user->conn, req);
+
+		}
+	}
+
+	if (list)
+		nm_free_fields(&list);
+
+	return rc;
+}
+
+
+NMERR_T
+nm_process_new_data(NMUser * user)
+{
+	NMConn *conn;
+	NMERR_T rc = NM_OK;
+	int ret;
+	guint32 val;
+
+	if (user == NULL)
+		return NMERR_BAD_PARM;
+
+	conn = user->conn;
+
+	/* Check to see if this is an event or a response */
+	ret = nm_tcp_read(conn, (char *) &val, sizeof(val));
+	if (ret == sizeof(val)) {
+
+		if (strncmp((char *) &val, "HTTP", strlen("HTTP")) == 0)
+			rc = nm_process_response(user);
+		else
+			rc = nm_process_event(user, val);
+
+	} else {
+		rc = NMERR_PROTOCOL;
+	}
+
+	return rc;
+}
+
+NMConference *
+nm_find_conversation(NMUser * user, const char *who)
+{
+	NMConference *conference = NULL;
+	NMConference *tmp;
+	GSList *cnode;
+
+	if (user && user->conferences) {
+		for (cnode = user->conferences; cnode; cnode = cnode->next) {
+			tmp = cnode->data;
+			if (nm_conference_get_participant_count(tmp) == 1) {
+				NMUserRecord *ur = nm_conference_get_participant(tmp, 0);
+
+				if (ur) {
+					if (nm_utf8_str_equal(nm_user_record_get_dn(ur), who)) {
+						conference = tmp;
+						break;
+					}
+				}
+			}
+		}
+	}
+
+	return conference;
+}
+
+void
+nm_conference_list_add(NMUser * user, NMConference * conf)
+{
+	if (user == NULL || conf == NULL)
+		return;
+
+	nm_conference_add_ref(conf);
+	user->conferences = g_slist_append(user->conferences, conf);
+}
+
+void
+nm_conference_list_remove(NMUser * user, NMConference * conf)
+{
+	if (user == NULL || conf == NULL)
+		return;
+
+	if (g_slist_find(user->conferences, conf)) {
+		user->conferences = g_slist_remove(user->conferences, conf);
+		nm_release_conference(conf);
+	}
+}
+
+void
+nm_conference_list_free(NMUser * user)
+{
+	GSList *cnode;
+	NMConference *conference;
+
+	if (user == NULL)
+		return;
+
+	if (user->conferences) {
+		for (cnode = user->conferences; cnode; cnode = cnode->next) {
+			conference = cnode->data;
+			cnode->data = NULL;
+			nm_release_conference(conference);
+		}
+
+		g_slist_free(user->conferences);
+		user->conferences = NULL;
+	}
+}
+
+NMConference *
+nm_conference_list_find(NMUser * user, const char *guid)
+{
+	GSList *cnode;
+	NMConference *conference = NULL, *tmp;
+
+	if (user == NULL || guid == NULL)
+		return NULL;
+
+	if (user->conferences) {
+		for (cnode = user->conferences; cnode; cnode = cnode->next) {
+			tmp = cnode->data;
+			if (nm_are_guids_equal(nm_conference_get_guid(tmp), guid)) {
+				conference = tmp;
+				break;
+			}
+		}
+	}
+
+	return conference;
+}
+
+gboolean
+nm_are_guids_equal(const char *guid1, const char *guid2)
+{
+	if (guid1 == NULL || guid2 == NULL)
+		return FALSE;
+
+	return (strncmp(guid1, guid2, CONF_GUID_END) == 0);
+}
+
+void
+nm_user_add_contact(NMUser * user, NMContact * contact)
+{
+	if (user == NULL || contact == NULL)
+		return;
+
+	nm_contact_add_ref(contact);
+
+	g_hash_table_insert(user->contacts,
+						g_utf8_strdown(nm_contact_get_dn(contact), -1), contact);
+}
+
+void
+nm_user_add_user_record(NMUser * user, NMUserRecord * user_record)
+{
+	nm_user_record_add_ref(user_record);
+
+	g_hash_table_insert(user->user_records,
+						g_utf8_strdown(nm_user_record_get_dn(user_record), -1),
+						user_record);
+
+	g_hash_table_insert(user->display_id_to_dn,
+						g_utf8_strdown(nm_user_record_get_display_id(user_record),
+									   -1),
+						g_utf8_strdown(nm_user_record_get_dn(user_record), -1));
+
+}
+
+nm_event_cb
+nm_user_get_event_callback(NMUser * user)
+{
+	if (user == NULL)
+		return NULL;
+
+	return user->evt_callback;
+}
+
+NMConn *
+nm_user_get_conn(NMUser * user)
+{
+	if (user == NULL)
+		return NULL;
+
+	return user->conn;
+}
+
+NMERR_T
+nm_create_contact_list(NMUser * user)
+{
+	NMERR_T rc = NM_OK;
+	NMField *locate = NULL;
+
+	if (user == NULL || user->fields == NULL) {
+		return NMERR_BAD_PARM;
+	}
+
+	/* Create the root folder */
+	user->root_folder = nm_create_folder("");
+
+	/* Find the contact list in the login fields */
+	locate = nm_locate_field(NM_A_FA_CONTACT_LIST, user->fields);
+	if (locate != NULL) {
+
+		/* Add the folders and then the contacts */
+		nm_folder_add_contacts_and_folders(user, user->root_folder,
+										   (NMField *) (locate->value));
+
+	}
+
+	return rc;
+}
+
+void
+nm_destroy_contact_list(NMUser * user)
+{
+	if (user == NULL)
+		return;
+
+	if (user->root_folder) {
+		nm_release_folder(user->root_folder);
+		user->root_folder = NULL;
+	}
+}
+
+NMFolder *
+nm_get_root_folder(NMUser * user)
+{
+	if (user == NULL)
+		return NULL;
+
+	if (user->root_folder == NULL)
+		nm_create_contact_list(user);
+
+	return user->root_folder;
+}
+
+NMContact *
+nm_find_contact(NMUser * user, const char *name)
+{
+	char *str;
+	const char *dn = NULL;
+	NMContact *contact = NULL;
+
+	if (user == NULL || name == NULL)
+		return NULL;
+
+	str = g_utf8_strdown(name, -1);
+	if (strstr(str, "=")) {
+		dn = str;
+	} else {
+		/* Assume that we have a display id instead of a dn */
+		dn = (const char *) g_hash_table_lookup(user->display_id_to_dn, str);
+	}
+
+	/* Find contact object in reference table */
+	if (dn) {
+		contact = (NMContact *) g_hash_table_lookup(user->contacts, dn);
+	}
+
+	g_free(str);
+	return contact;
+}
+
+GList *
+nm_find_contacts(NMUser * user, const char *dn)
+{
+	guint32 i, cnt;
+	NMFolder *folder;
+	NMContact *contact;
+	GList *contacts = NULL;
+
+	if (user == NULL || dn == NULL)
+		return NULL;
+
+	/* Check for contact at the root */
+	contact = nm_folder_find_contact(user->root_folder, dn);
+	if (contact) {
+		contacts = g_list_append(contacts, contact);
+		contact = NULL;
+	}
+
+	/* Check for contact in each subfolder */
+	cnt = nm_folder_get_subfolder_count(user->root_folder);
+	for (i = 0; i < cnt; i++) {
+		folder = nm_folder_get_subfolder(user->root_folder, i);
+		contact = nm_folder_find_contact(folder, dn);
+		if (contact) {
+			contacts = g_list_append(contacts, contact);
+			contact = NULL;
+		}
+	}
+
+	return contacts;
+}
+
+NMUserRecord *
+nm_find_user_record(NMUser * user, const char *name)
+{
+	char *str = NULL;
+	const char *dn = NULL;
+	NMUserRecord *user_record = NULL;
+
+	if (user == NULL || name == NULL)
+		return NULL;
+
+	str = g_utf8_strdown(name, -1);
+	if (strstr(str, "=")) {
+		dn = str;
+	} else {
+		/* Assume that we have a display id instead of a dn */
+		dn = (const char *) g_hash_table_lookup(user->display_id_to_dn, str);
+	}
+
+	/* Find user record in reference table */
+	if (dn) {
+		user_record =
+			(NMUserRecord *) g_hash_table_lookup(user->user_records, dn);
+	}
+
+	g_free(str);
+	return user_record;
+}
+
+const char *
+nm_lookup_dn(NMUser * user, const char *display_id)
+{
+	if (user == NULL || display_id == NULL)
+		return NULL;
+
+	return (const char *) g_hash_table_lookup(user->display_id_to_dn,
+											  g_utf8_strdown(display_id, -1));
+}
+
+NMFolder *
+nm_find_folder(NMUser * user, const char *name)
+{
+	NMFolder *folder = NULL, *temp;
+	int i, num_folders;
+	const char *tname = NULL;
+
+	if (user == NULL || name == NULL)
+		return NULL;
+
+	if (*name == '\0')
+		return user->root_folder;
+
+	num_folders = nm_folder_get_subfolder_count(user->root_folder);
+	for (i = 0; i < num_folders; i++) {
+		temp = nm_folder_get_subfolder(user->root_folder, i);
+		tname = nm_folder_get_name(temp);
+
+		if (tname && (strcmp(tname, name) == 0)) {
+			folder = temp;
+			break;
+		}
+	}
+
+	return folder;
+}
+
+NMFolder *
+nm_find_folder_by_id(NMUser * user, int object_id)
+{
+	NMFolder *folder = NULL, *temp;
+	int i, num_folders;
+
+	if (user == NULL)
+		return NULL;
+
+	if (object_id == 0)
+		return user->root_folder;
+
+	num_folders = nm_folder_get_subfolder_count(user->root_folder);
+	for (i = 0; i < num_folders; i++) {
+		temp = nm_folder_get_subfolder(user->root_folder, i);
+		if (nm_folder_get_id(temp) == object_id) {
+			folder = temp;
+			break;
+		}
+	}
+
+	return folder;
+}
+
+static void
+_handle_multiple_get_details_joinconf_cb(NMUser * user, NMERR_T ret_code,
+										 gpointer resp_data, gpointer user_data)
+{
+	NMRequest *request = user_data;
+	NMUserRecord *user_record = resp_data;
+	NMConference *conference;
+	GSList *list, *node;
+
+	if (user == NULL || resp_data == NULL || user_data == NULL)
+		return;
+
+	conference = nm_request_get_data(request);
+	list = nm_request_get_user_define(request);
+
+	if (ret_code == 0 && conference && list) {
+
+		/* Add the user to the conference */
+		nm_conference_add_participant(conference, user_record);
+
+		/* Find the user in the list and remove it */
+		for (node = list; node; node = node->next) {
+			if (nm_utf8_str_equal(nm_user_record_get_dn(user_record),
+								  (const char *) node->data)) {
+				list = g_slist_remove(list, node->data);
+				nm_request_set_user_define(request, list);
+				g_free(node->data);
+				break;
+			}
+		}
+
+		/* Time to callback? */
+		if (g_slist_length(list) == 0) {
+			nm_response_cb cb = nm_request_get_callback(request);
+
+			if (cb) {
+				cb(user, 0, conference, conference);
+			}
+			g_slist_free(list);
+			nm_release_request(request);
+		}
+	}
+}
+
+NMERR_T
+nm_call_handler(NMUser * user, NMRequest * request, NMField * fields)
+{
+	NMERR_T rc = NM_OK, ret_code = NM_OK;
+	NMConference *conf = NULL;
+	NMUserRecord *user_record = NULL;
+	NMField *locate = NULL;
+	NMField *field = NULL;
+	const char *cmd;
+	nm_response_cb cb;
+	gboolean done = TRUE;
+	
+	if (user == NULL || request == NULL || fields == NULL)
+		return NMERR_BAD_PARM;
+
+	/* Get the return code */
+	field = nm_locate_field(NM_A_SZ_RESULT_CODE, fields);
+	if (field) {
+		ret_code = atoi((char *) field->value);
+	} else {
+		ret_code = NMERR_PROTOCOL;
+	}
+	
+	cmd = nm_request_get_cmd(request);
+	if (ret_code == NM_OK && cmd != NULL) {
+
+		if (strcmp("login", cmd) == 0) {
+
+			user->user_record = nm_create_user_record_from_fields(fields);
+
+			/* Save the users fields */
+			user->fields = fields;
+
+		} else if (strcmp("setstatus", cmd) == 0) {
+
+			/* Nothing to do */
+
+		} else if (strcmp("createconf", cmd) == 0) {
+
+			conf = (NMConference *) nm_request_get_data(request);
+
+			/* get the convo guid */
+			locate = nm_locate_field(NM_A_FA_CONVERSATION, fields);
+			if (locate) {
+				field =
+					nm_locate_field(NM_A_SZ_OBJECT_ID, (NMField *) fields->value);
+				if (field) {
+					nm_conference_set_guid(conf, (char *) field->value);
+				}
+			}
+
+			nm_conference_list_add(user, conf);
+
+		} else if (strcmp("leaveconf", cmd) == 0) {
+
+			conf = (NMConference *) nm_request_get_data(request);
+			nm_conference_list_remove(user, conf);
+
+		} else if (strcmp("joinconf", cmd) == 0) {
+			GSList *list = NULL, *node;
+
+			nm_print_fields(fields);
+
+			conf = nm_request_get_data(request);
+
+			locate = nm_locate_field(NM_A_FA_CONTACT_LIST, fields);
+			if (locate && locate->value != 0) {
+
+				field = (NMField *) locate->value;
+				while ((field = nm_locate_field(NM_A_SZ_DN, field))) {
+					if (field && field->value != 0) {
+
+						if (nm_utf8_str_equal
+							(nm_user_record_get_dn(user->user_record),
+							 (const char *) field->value)) {
+							field++;
+							continue;
+						}
+
+						user_record =
+							nm_find_user_record(user,
+												(const char *) field->value);
+						if (user_record == NULL) {
+							list =
+								g_slist_append(list,
+											   g_strdup((char *) field->value));
+						} else {
+							nm_conference_add_participant(conf, user_record);
+						}
+					}
+					field++;
+				}
+
+				if (list != NULL) {
+
+					done = FALSE;
+					nm_request_add_ref(request);
+					nm_request_set_user_define(request, list);
+					for (node = list; node; node = node->next) {
+
+						nm_send_get_details(user, (const char *) node->data,
+											_handle_multiple_get_details_joinconf_cb,
+											request);
+					}
+				}
+			}
+
+		} else if (strcmp("getdetails", cmd) == 0) {
+
+			locate = nm_locate_field(NM_A_FA_RESULTS, fields);
+			if (locate && locate->value != 0) {
+
+				user_record = nm_create_user_record_from_fields(locate);
+				if (user_record) {
+					NMUserRecord *tmp;
+
+					tmp =
+						nm_find_user_record(user,
+											nm_user_record_get_dn(user_record));
+					if (tmp) {
+
+						/* Update the existing user record */
+						nm_user_record_copy(tmp, user_record);
+						nm_release_user_record(user_record);
+						user_record = tmp;
+
+					} else {
+
+						nm_user_add_user_record(user, user_record);
+						nm_release_user_record(user_record);
+
+					}
+
+					/* Response data is new user record */
+					nm_request_set_data(request, (gpointer) user_record);
+				}
+
+			}
+
+		} else if (strcmp("createfolder", cmd) == 0) {
+
+			_update_contact_list(user, fields);
+
+		} else if (strcmp("createcontact", cmd) == 0) {
+
+			_update_contact_list(user, fields);
+
+			locate =
+				nm_locate_field(NM_A_SZ_OBJECT_ID, (NMField *) fields->value);
+			if (locate) {
+
+				NMContact *new_contact =
+					nm_folder_find_item_by_object_id(user->root_folder,
+													 atoi((char *) locate->
+														  value));
+
+				if (new_contact) {
+
+					/* Add the contact to our cache */
+					nm_user_add_contact(user, new_contact);
+
+					/* Set the contact as the response data */
+					nm_request_set_data(request, (gpointer) new_contact);
+
+				}
+
+			}
+
+		} else if (strcmp("deletecontact", cmd) == 0) {
+
+			_update_contact_list(user, fields);
+
+		} else if (strcmp("movecontact", cmd) == 0) {
+
+			_update_contact_list(user, fields);
+
+		} else if (strcmp("getstatus", cmd) == 0) {
+
+			locate = nm_locate_field(NM_A_SZ_STATUS, fields);
+			if (locate) {
+				nm_user_record_set_status((NMUserRecord *)
+										  nm_request_get_data(request),
+										  atoi((char *) locate->value), NULL);
+			}
+
+		} else if (strcmp("updateitem", cmd) == 0) {
+
+			/* Nothing extra to do here */
+
+		} else {
+
+			/* Nothing to do, just print debug message  */
+			gaim_debug(GAIM_DEBUG_INFO, "novell",
+					   "nm_call_handler(): Unknown request command, %s\n", cmd);
+
+		}
+	}
+
+	if (done && (cb = nm_request_get_callback(request))) {
+
+		cb(user, ret_code, nm_request_get_data(request),
+		   nm_request_get_user_define(request));
+
+	}
+
+	return rc;
+}
+
+static NMERR_T
+nm_process_response(NMUser * user)
+{
+	NMERR_T rc = NM_OK;
+	NMField *fields = NULL;
+	NMField *field = NULL;
+	NMConn *conn = user->conn;
+	NMRequest *req = NULL;
+
+	rc = nm_read_header(conn);
+	if (rc == NM_OK) {
+		rc = nm_read_fields(conn, -1, &fields);
+	}
+
+	if (rc == NM_OK) {
+		field = nm_locate_field(NM_A_SZ_TRANSACTION_ID, fields);
+		if (field != NULL && field->value != 0) {
+			req = nm_conn_find_request(conn, atoi((char *) field->value));
+			if (req != NULL) {
+				rc = nm_call_handler(user, req, fields);
+			}
+		}
+	}
+
+	return rc;
+}
+
+/*
+ * Some utility functions...haven't figured out where
+ * they belong yet.
+ */
+gint
+nm_utf8_strcasecmp(gconstpointer str1, gconstpointer str2)
+{
+	gint rv;
+	char *str1_down = g_utf8_strdown(str1, -1);
+	char *str2_down = g_utf8_strdown(str2, -1);
+
+	rv = g_utf8_collate(str1_down, str2_down);
+
+	g_free(str1_down);
+	g_free(str2_down);
+
+	return rv;
+}
+
+gboolean
+nm_utf8_str_equal(gconstpointer str1, gconstpointer str2)
+{
+	return (nm_utf8_strcasecmp(str1, str2) == 0);
+}
+
+char *
+nm_typed_to_dotted(const char *typed)
+{
+	unsigned i = 0, j = 0;
+
+	if (typed == NULL)
+		return NULL;
+
+	char *dotted = g_new0(char, strlen(typed));
+
+	do {
+
+		/* replace comma with a dot */
+		if (j != 0) {
+			dotted[j] = '.';
+			j++;
+		}
+
+		/* skip the type */
+		while (typed[i] != '\0' && typed[i] != '=')
+			i++;
+
+		/* verify that we aren't running off the end */
+		if (typed[i] == '\0') {
+			dotted[j] = '\0';
+			break;
+		}
+
+		i++;
+
+		/* copy the object name to context */
+		while (typed[i] != '\0' && typed[i] != ',') {
+			dotted[j] = typed[i];
+			j++;
+			i++;
+		}
+
+	} while (typed[i] != '\0');
+
+	return dotted;
+}
+
+static void
+_update_contact_list(NMUser * user, NMField * fields)
+{
+	NMField *list, *cursor, *locate;
+	gint objid1;
+	NMContact *contact;
+	NMFolder *folder;
+
+	if (user == NULL || fields == NULL)
+		return;
+
+	/* Is it wrapped in a RESULTS array? */
+	if (strcmp(fields->tag, NM_A_FA_RESULTS) == 0) {
+		list = (NMField *) fields->value;
+	} else {
+		list = fields;
+	}
+
+	/* Update the cached contact list */
+	cursor = (NMField *) list->value;
+	while (cursor->tag != NULL) {
+		if ((g_ascii_strcasecmp(cursor->tag, NM_A_FA_CONTACT) == 0) ||
+			(g_ascii_strcasecmp(cursor->tag, NM_A_FA_FOLDER) == 0)) {
+
+			locate =
+				nm_locate_field(NM_A_SZ_OBJECT_ID, (NMField *) cursor->value);
+			if (locate != NULL && locate->value != 0) {
+				objid1 = atoi((char *) locate->value);
+				gpointer item =
+					nm_folder_find_item_by_object_id(user->root_folder, objid1);
+				if (item != NULL) {
+					if (cursor->method == NMFIELD_METHOD_ADD) {
+						if (g_ascii_strcasecmp(cursor->tag, NM_A_FA_CONTACT) == 0) {
+							contact = (NMContact *) item;
+							nm_contact_update_list_properties(contact, cursor);
+						} else if (g_ascii_strcasecmp(cursor->tag, NM_A_FA_FOLDER)
+								   == 0) {
+							folder = (NMFolder *) item;
+							nm_folder_update_list_properties(folder, cursor);
+						}
+					} else if (cursor->method == NMFIELD_METHOD_DELETE) {
+						if (g_ascii_strcasecmp(cursor->tag, NM_A_FA_CONTACT) == 0) {
+							contact = (NMContact *) item;
+							folder =
+								nm_find_folder_by_id(user,
+													 nm_contact_get_parent_id
+													 (contact));
+							if (folder) {
+								nm_folder_remove_contact(folder, contact);
+							}
+						} else if (g_ascii_strcasecmp(cursor->tag, NM_A_FA_FOLDER)
+								   == 0) {
+							/* TODO: write nm_folder_remove_folder */
+							/* ignoring for now, should not be a big deal */
+/*								folder = (NMFolder *) item;*/
+/*								nm_folder_remove_folder(user->root_folder, folder);*/
+						}
+					}
+				} else {
+
+					if (cursor->method == NMFIELD_METHOD_ADD) {
+
+						/* Not found,  so we need to add it */
+						if (g_ascii_strcasecmp(cursor->tag, NM_A_FA_CONTACT) == 0) {
+
+							const char *dn = NULL;
+
+							locate =
+								nm_locate_field(NM_A_SZ_DN,
+												(NMField *) cursor->value);
+							if (locate != NULL && locate->value != 0) {
+								dn = (const char *) locate->value;
+								if (dn != NULL) {
+									contact =
+										nm_create_contact_from_fields(cursor);
+									if (contact) {
+										nm_folder_add_contact_to_list(user->
+																	  root_folder,
+																	  contact);
+										nm_release_contact(contact);
+									}
+								}
+							}
+						} else if (g_ascii_strcasecmp(cursor->tag, NM_A_FA_FOLDER)
+								   == 0) {
+							folder = nm_create_folder_from_fields(cursor);
+							nm_folder_add_folder_to_list(user->root_folder,
+														 folder);
+							nm_release_folder(folder);
+						}
+					}
+				}
+			}
+		}
+		cursor++;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/novell/nmuser.h	Sat Apr 17 13:55:28 2004 +0000
@@ -0,0 +1,569 @@
+/*
+ * nmuser.h
+ *
+ * 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.
+ *
+ */
+
+#ifndef __NM_USER_H__
+#define __NM_USER_H__
+
+#include <glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+typedef guint32 NMERR_T;
+typedef int NMSTATUS_T;
+
+typedef struct _NMUser NMUser;
+
+typedef enum
+{
+	NMREQUEST_TYPE_LOGIN = 0,
+	NMREQUEST_TYPE_LOGOUT,
+	NMREQUEST_TYPE_SETSTATUS,
+	NMREQUEST_TYPE_GETDETAILS,
+	NMREQUEST_TYPE_CREATECONF,
+	NMREQUEST_TYPE_SENDMESSAGE,
+	NMREQUEST_TYPE_JOINCONF,
+	NMREQUEST_TYPE_LEAVECONF,
+	NMREQUEST_TYPE_REJECTCONF,
+	NMREQUEST_TYPE_SENDTYPING,
+	NMREQUEST_TYPE_CREATECONTACT,
+	NMREQUEST_TYPE_DELETECONTACT
+
+} NMRequestType;
+
+#include "debug.h"
+#include "nmmessage.h"
+#include "nmconference.h"
+#include "nmcontact.h"
+#include "nmuserrecord.h"
+#include "nmfield.h"
+#include "nmevent.h"
+
+/* Callback typedefs */
+typedef void (*nm_response_cb) (NMUser * user, NMERR_T ret_code,
+								gpointer resp_data, gpointer user_data);
+
+typedef void (*nm_event_cb) (NMUser * user, NMEvent * event);
+
+#include "nmrequest.h"
+#include "nmconn.h"
+
+/* This represents user that we are currently logged in as */
+struct _NMUser
+{
+
+	char *name;
+
+	NMSTATUS_T status;
+
+	/* A copy of the login response fields */
+	NMField *fields;
+
+	/* The user record for this user */
+	NMUserRecord *user_record;
+
+	/* Our connection information */
+	NMConn *conn;
+
+	/* Our public IP address */
+	char *address;
+
+	/* This is the contact list */
+	NMFolder *root_folder;
+
+	/* All contacts that we know about hashed by dn */
+	GHashTable *contacts;
+
+	/* All user records hashed by dn */
+	GHashTable *user_records;
+
+	/* DN lookup */
+	GHashTable *display_id_to_dn;
+
+	/* One on one conversations indexed by recipient's dn */
+	GSList *conferences;
+
+	guint32 conference_count;
+
+	/* Called when we receive an event */
+	nm_event_cb evt_callback;
+
+	/* Pending requests. If we need to go to the server to more info
+	 * before processing a request we will queue it up and process when
+	 * we get a response
+	 */
+	GSList *pending_requests;
+
+	/* Pending events. Same as above except for events. */
+	GSList *pending_events;
+
+	/* Generic pointer to data needed by the client
+	 * using the API
+	 */
+	gpointer client_data;
+
+};
+
+
+#define	NM_STATUS_UNKNOWN			0
+#define	NM_STATUS_OFFLINE			1
+#define NM_STATUS_AVAILABLE			2
+#define	NM_STATUS_BUSY				3
+#define	NM_STATUS_AWAY				4
+#define	NM_STATUS_AWAY_IDLE			5
+#define	NM_STATUS_INVALID			6
+
+#define NMERR_BASE							0x2000L
+#define NM_OK								0L
+#define NMERR_BAD_PARM						(NMERR_BASE + 0x0001)
+#define NMERR_TCP_WRITE						(NMERR_BASE + 0x0002)
+#define NMERR_TCP_READ						(NMERR_BASE + 0x0003)
+#define NMERR_PROTOCOL						(NMERR_BASE + 0x0004)
+#define NMERR_SSL_REDIRECT					(NMERR_BASE + 0x0005)
+#define NMERR_CONFERENCE_NOT_FOUND 			(NMERR_BASE + 0x0006)
+#define NMERR_CONFERENCE_NOT_INSTANTIATED 	(NMERR_BASE + 0x0007)
+#define NMERR_FOLDER_EXISTS					(NMERR_BASE + 0x0008)
+
+
+/** 
+ *	Initialize the user that we are going to login to the system as.
+ *
+ *	@param	name			The userid of the user
+ *	@param	server			IP Address of server
+ *	@param	port			Port to connect to on the server
+ *  @param 	data			Client data to associate with the user
+ *	@param	event_callback	Function to call when we recieve an event
+ *
+ *	@return The initialized user object. Must be freed by calling
+ *			nm_deinitialize_user
+ */
+NMUser *nm_initialize_user(const char *name, const char *server, int port,
+						   gpointer data, nm_event_cb event_callback);
+
+
+/** 
+ *	Free up resources associated with the user object.
+ *
+ *  @param	user	The user to deinitialize
+ */
+void nm_deinitialize_user(NMUser * user);
+
+/** 
+ *	Send a login request to the server.
+ *
+ *	The response data sent to the callback will be NULL.
+ *
+ *  @param	user		The User to login -- must be initialized
+ *	@param	pwd			The password of the user
+ *  @param  my_addr		The address of the client machine
+ *  @param	user_agent	String describing the client (eg. "Gaim/0.76 (Linux; 2.4.20)")
+ *	@param	callback	Function to call when we get the response from the server
+ *  @param 	data		User defined data to be passed to the callback function
+ * 
+ *
+ *	@return	NM_OK if login is sent successfully, error otherwise.
+ */
+NMERR_T nm_send_login(NMUser * user, const char *pwd, const char *my_addr,
+					  const char *user_agent, nm_response_cb callback,
+					  gpointer data);
+
+/** 
+ *	Send a set status request to the server.
+ *
+ *	The response data sent to the callback will be NULL.
+ *
+ *  @param	user		The logged in User
+ *	@param	dn			The DN of the user (if known, or NULL if not known)
+ *	@param	address		IP Address of server
+ *	@param	callback	Function to call when we get the response from the server
+ *
+ *
+ *	@return	NM_OK if successfully sent, error otherwise
+ */
+NMERR_T nm_send_set_status(NMUser * user, int status, const char *text,
+						   const char *auto_resp, nm_response_cb callback,
+						   gpointer data);
+
+/** 
+ *	Send a create conference to the server.
+ *
+ *	The response data sent to the callback will be NULL.
+ *
+ *  @param	user		The logged in User
+ *	@param	conference	Conference to create
+ *	@param	callback	Function to call when we get the response from the server
+ *	@param	data		User defined data to be passed to the callback function
+ *
+ *	@return	NM_OK if successfully sent, error otherwise
+ */
+NMERR_T nm_send_create_conference(NMUser * user, NMConference * conference,
+								  nm_response_cb callback, gpointer data);
+
+/** 
+ *	Tell server we have left the conference.
+ * 
+ *	The response data sent to the callback will be NULL.
+ *
+ *  @param	user		The logged in User
+ *	@param	conference	Conference the user is leaving
+ *	@param	callback	Function to call when we get the response from the server
+ *	@param	data		User defined data to be passed to the callback function
+ *
+ *	@return	NM_OK if successfully sent, error otherwise
+ */
+NMERR_T nm_send_leave_conference(NMUser * user, NMConference * conference,
+								 nm_response_cb callback, gpointer data);
+
+/** 
+ *	Send a join conference request to the server.
+ *
+ *	The response data sent to the callback will be NULL.
+ *
+ *  @param	user		The logged in User
+ *	@param	conference	Conference the user is joining
+ *	@param	callback	Function to call when we get the response from the server
+ *	@param	data		User defined data to be passed to the callback function
+ *
+ *
+ *	@return	NM_OK if successfully sent, error otherwise
+ */
+NMERR_T nm_send_join_conference(NMUser * user, NMConference * conference,
+								nm_response_cb callback, gpointer data);
+
+/** 
+ *	Send a conference reject request to the server.
+ *
+ *	The response data sent to the callback will be NULL. 
+ *
+ *  @param	user		The logged in User
+ *	@param	conference	Conference the user is rejecting
+ *	@param	callback	Function to call when we get the response from the server
+ *	@param	data		User defined data to be passed to the callback function
+ *
+ *
+ *	@return	NM_OK if successfully sent, error otherwise
+ */
+NMERR_T nm_send_reject_conference(NMUser * user, NMConference * conference,
+								  nm_response_cb callback, gpointer data);
+
+/** 
+ *	Get details for a user from the server.
+ * 
+ *	The response data sent to the callback will be an NMUserRecord which should be
+ *  freed with nm_release_user_record
+ *
+ *  @param	user		The logged in User
+ *	@param	name		Userid or DN of the user to look up
+ *	@param	callback	Function to call when we get the response from the server
+ *	@param	data		User defined data to be passed to the callback function
+ *
+ *	@return	NM_OK if successfully sent, error otherwise
+ */
+NMERR_T nm_send_get_details(NMUser * user, const char *name,
+							nm_response_cb callback, gpointer data);
+
+/** 
+ *	Get details for multiple users from the server.
+ * 
+ *	The response data to the callback will be a list of NMUserRecord, which should be
+ *	freed (each user record should be released with nm_release_user_record and the 
+ *  list should be freed)
+ *
+ *  @param	user		The logged in User
+ *	@param	name		Userid or DN of the user to look up
+ *	@param	callback	Function to call when we get the response from the server
+ *	@param	data		User defined data to be passed to the callback function
+ *
+ *	@return	NM_OK if successfully sent, error otherwise
+ */
+NMERR_T nm_send_multiple_get_details(NMUser *user, GSList *names,
+									 nm_response_cb callback, gpointer data);
+
+/** 
+ *	Send a message.
+ *
+ *  The response data sent to the callback will be NULL.
+ *
+ *  @param	user		The logged in User
+ *	@param	message		The message to send.
+ *	@param	callback	Function to call when we get the response from the server
+ *
+ *	@return	NM_OK if successfully sent, error otherwise
+ */
+NMERR_T nm_send_message(NMUser * user, NMMessage * message,
+						nm_response_cb callback);
+
+/** 
+ *	Sends a typing event to the server.
+ *
+ *  The response data sent to the callback will be NULL.
+ *
+ *  @param	user		The logged in User
+ *	@param	conf		The conference that corresponds to the typing event
+ *	@param	typing		TRUE if the user is typing
+ *						FALSE if the user has stopped typing
+ *	@param	callback	Function to call when we get the response from the server
+ *
+ *	@return	NM_OK if successfully sent, error otherwise
+ */
+NMERR_T nm_send_typing(NMUser * user, NMConference * conf,
+					   gboolean typing, nm_response_cb callback);
+
+/** 
+ *	Send a create contact request to the server.
+ *
+ *  The given folder should already exist on the server. If not,
+ *  the call will fail.
+ *
+ *  The response data sent to the callback will be a NMContact which should
+ *  be released with nm_release_contact
+ *
+ *  @param	user		The logged in User
+ *	@param	folder		The folder that the contact should be created in
+ *	@param	contact		The contact to add
+ *	@param	callback	Function to call when we get the response from the server
+ *	@param	data		User defined data
+ *
+ *	@return	NM_OK if successfully sent, error otherwise
+ */
+NMERR_T nm_send_create_contact(NMUser * user, NMFolder * folder,
+							   NMContact * contact, nm_response_cb callback,
+							   gpointer data);
+
+/** 
+ *	Send a remove contact request to the server.
+ *
+ *  The response data sent to the callback will be NULL.
+ *
+ *  @param	user		The logged in User
+ *	@param	folder		The folder to remove contact from
+ *	@param	contact		The contact to remove
+ *	@param	callback	Function to call when we get the response from the server
+ *  @param	data		User defined data
+ *
+ *	@return	NM_OK if successfully sent, error otherwise
+ */
+NMERR_T nm_send_remove_contact(NMUser * user, NMFolder * folder,
+							   NMContact * contact, nm_response_cb callback,
+							   gpointer data);
+
+/** 
+ *	Send a create folder request to the server.
+ *
+ *  The response data sent to the callback will be a NMFolder which should be
+ *  released with nm_release_folder
+ *
+ *  @param	user		The logged in User
+ *	@param	name		The name of the folder to create
+ *	@param	callback	Function to call when we get the response from the server
+ *  @param	data		User defined data
+ *
+ *	@return	NM_OK if successfully sent, error otherwise
+ */
+NMERR_T nm_send_create_folder(NMUser * user, const char *name,
+							  nm_response_cb callback, gpointer data);
+
+/** 
+ *	Send a delete folder request to the server.
+ *
+ *  The response data sent to the callback will be NULL.
+ *
+ *  @param	user		The logged in User
+ *	@param	folder		The name of the folder to remove
+ *	@param	callback	Function to call when we get the response from the server
+ *  @param	data		User defined data
+ *
+ *	@return	NM_OK if successfully sent, error otherwise
+ */
+NMERR_T nm_send_remove_folder(NMUser * user, NMFolder * folder,
+							  nm_response_cb callback, gpointer data);
+
+/** 
+ *	Send a rename contact request to the server.
+ *
+ *	The response data sent to the callback will be NULL.
+ *
+ *  @param	user		The logged in User
+ *	@param	contact		The contact to rename
+ *  @param	new_name	The new display name for the contact
+ *	@param	callback	Function to call when we get the response from the server
+ *  @param	data		User defined data
+ *
+ *	@return	NM_OK if successfully sent, error otherwise
+ */
+NMERR_T nm_send_rename_contact(NMUser * user, NMContact * contact,
+							   const char *new_name, nm_response_cb callback,
+							   gpointer data);
+
+/** 
+ *	Send a rename folder request to the server.
+ *
+ *	The response data sent to the callback will be NULL.
+ *
+ *  @param	user		The logged in User
+ *	@param	folder		The folder to rename
+ *  @param	new_name	The new name of the folder
+ *	@param	callback	Function to call when we get the response from the server
+ *  @param	data		User defined data
+ *
+ *	@return	NM_OK if successfully sent, error otherwise
+ */
+NMERR_T nm_send_rename_folder(NMUser * user, NMFolder * folder,
+							  const char *new_name, nm_response_cb callback,
+							  gpointer data);
+
+/** 
+ *	Send a move contact request to the server.
+ *
+ *	The response data sent to the callback will be NULL.
+ *
+ *  @param	user		The logged in User
+ *	@param	contact		The contact to move
+ *  @param	folder	    The folder to move the contact to
+ *	@param	callback	Function to call when we get the response from the server
+ *  @param	data		User defined data
+ *
+ *	@return	NM_OK if successfully sent, error otherwise
+ */
+NMERR_T nm_send_move_contact(NMUser * user, NMContact * contact,
+							 NMFolder * folder, nm_response_cb callback,
+							 gpointer data);
+
+/** 
+ *	Send a get status request to the server.
+ *
+ * 	The response data sent to the callback will be a NMUserRecord.
+ *
+ *  @param	user		The logged in User
+ *	@param	contact		The contact to move
+ *  @param	folder	    The folder to move the contact to
+ *	@param	callback	Function to call when we get the response from the server
+ *  @param	data		User defined data
+ *
+ *	@return	NM_OK if successfully sent, error otherwise
+ */
+NMERR_T nm_send_get_status(NMUser * user, NMUserRecord * user_record,
+						   nm_response_cb callback, gpointer data);
+
+/** 
+ *	Reads a response/event from the server and processes it.
+ *
+ *  @param	user	The logged in User
+ */
+NMERR_T nm_process_new_data(NMUser * user);
+
+/** 
+ *	Return the root folder of the contact list
+ *
+ *  @param	user	The logged in User
+ *
+ *	@return	Root folder
+ */
+NMFolder *nm_get_root_folder(NMUser * user);
+
+/** 
+ *	Create the contact list based on the login fields
+ *
+ *  @param	user	The logged in User
+ *
+ */
+NMERR_T nm_create_contact_list(NMUser * user);
+
+void nm_destroy_contact_list(NMUser * user);
+
+void nm_user_add_contact(NMUser * user, NMContact * contact);
+
+void nm_user_add_user_record(NMUser * user, NMUserRecord * user_record);
+
+NMContact *nm_find_contact(NMUser * user, const char *dn);
+
+GList *nm_find_contacts(NMUser * user, const char *dn);
+
+NMUserRecord *nm_find_user_record(NMUser * user, const char *dn);
+
+NMFolder *nm_find_folder(NMUser * user, const char *name);
+
+NMFolder *nm_find_folder_by_id(NMUser * user, int object_id);
+
+NMConference *nm_find_conversation(NMUser * user, const char *who);
+
+void nm_conference_list_add(NMUser * user, NMConference * conf);
+
+void nm_conference_list_remove(NMUser * user, NMConference * conf);
+
+void nm_conference_list_free(NMUser * user);
+
+NMConference *nm_conference_list_find(NMUser * user, const char *guid);
+
+const char *nm_lookup_dn(NMUser * user, const char *display_id);
+
+nm_event_cb nm_user_get_event_callback(NMUser * user);
+
+NMConn *nm_user_get_conn(NMUser * user);
+
+/** Some utility functions **/
+
+/**
+ * Check to see if the conference GUIDs are equivalent.	
+ *
+ * @param guid1	First guid to compare
+ * @param guid2 Second guid to compare
+ *
+ * @return 		TRUE if conference GUIDs are equivalent, FALSE otherwise.
+ *
+ */
+gboolean nm_are_guids_equal(const char *guid1, const char *guid2);
+
+
+/**
+ * Case insensitive compare for utf8 strings
+ *
+ * @param guid1	First string to compare
+ * @param guid2 Second string to compare
+ *
+ * @return 		-1 if str1 < str2, 0 if str1 = str2, 1 if str1 > str2
+ *
+ */
+gint nm_utf8_strcasecmp(gconstpointer str1, gconstpointer str2);
+
+/**
+ * Compare UTF8 strings for equality only (case insensitive)
+ *
+ * @param guid1	First string to compare
+ * @param guid2 Second string to compare
+ *
+ * @return 		TRUE if strings are equal, FALSE otherwise
+ *
+ */
+gboolean nm_utf8_str_equal(gconstpointer str1, gconstpointer str2);
+
+/**
+ * Convert a fully typed LDAP DN to dotted, untype notation
+ * e.g. cn=mike,o=novell -> mike.novell
+ *
+ * @param typed	Fully typed dn
+ *
+ * @return 		Dotted equivalent of typed (must be freed).
+ *
+ */
+char *nm_typed_to_dotted(const char *typed);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/novell/nmuserrecord.c	Sat Apr 17 13:55:28 2004 +0000
@@ -0,0 +1,563 @@
+/*
+ * nmuserrecord.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 <glib.h>
+#include <string.h>
+#include "nmuserrecord.h"
+#include "nmfield.h"
+#include "nmuser.h"
+
+struct _NMUserRecord
+{
+	NMSTATUS_T status;
+	char *status_text;
+	char *dn;
+	char *cn;
+	char *display_id;
+	char *fname;
+	char *lname;
+	char *full_name;
+	NMField *fields;
+	gboolean auth_attr;
+	gpointer data;
+	int ref_count;
+};
+
+struct _NMProperty
+{
+	char *tag;
+	char *value;
+};
+
+static int count = 0;
+
+/* API functions */
+
+NMUserRecord *
+nm_create_user_record()
+{
+	NMUserRecord *user_record = g_new0(NMUserRecord, 1);
+
+	user_record->ref_count = 1;
+
+	gaim_debug(GAIM_DEBUG_INFO, "novell", "Creating user_record, total=%d\n",
+			   count++);
+
+	return user_record;
+}
+
+static char *
+_get_attribute_value(NMField *field)
+{
+	char *value = NULL;
+
+	if (field->value == 0)
+		return NULL;
+
+	if (field->type == NMFIELD_TYPE_UTF8 || field->type == NMFIELD_TYPE_DN) {
+
+		value = (char *)field->value;
+
+	} else if (field->type == NMFIELD_TYPE_MV) {
+
+		/* Need to handle multi-valued returns, for now
+		 * just pick the first value and return it
+		 */
+		NMField *tmp = (NMField *)field->value;
+		if ((tmp != NULL) && 
+			((tmp->type == NMFIELD_TYPE_UTF8) ||
+			(tmp->type == NMFIELD_TYPE_DN))) {
+
+			value = (char *)tmp->value;
+
+		} else {
+			return NULL;
+		}
+
+	} else {
+		return NULL;
+	}
+	
+	return g_strdup(value);
+}
+/*
+ * This creates a user_record for the reference list the 
+ * field array that is passed in should be a 
+ * NM_A_FA_USER_DETAILS array.
+ */
+NMUserRecord *
+nm_create_user_record_from_fields(NMField * details)
+{
+	NMUserRecord *user_record;
+	NMField *field, *fields = details;
+
+	if (details == NULL) {
+		return NULL;
+	}
+
+	if (details->type == NMFIELD_TYPE_ARRAY) {
+		if (details->value == 0)
+			return NULL;
+		fields = (NMField *) details->value;
+	}
+
+	user_record = nm_create_user_record();
+
+	if ((field = nm_locate_field(NM_A_SZ_AUTH_ATTRIBUTE, fields))) {
+		
+		if (field->value) {
+			user_record->display_id = _get_attribute_value(field);
+			user_record->auth_attr = TRUE;
+		}
+	}
+
+	if ((field = nm_locate_field(NM_A_SZ_DN, fields))) {
+		
+		if (field->value) {
+			user_record->dn = _get_attribute_value(field);
+		}
+	}
+
+	if ((field = nm_locate_field("CN", fields))) {
+
+		if (field->value) {
+			user_record->cn = _get_attribute_value(field);
+		}
+	}
+
+	if ((field = nm_locate_field("Given Name", fields))) {
+
+		if (field->value) {
+			user_record->fname = _get_attribute_value(field);
+		}
+	}
+
+	if ((field = nm_locate_field("Surname", fields))) {
+		
+		if (field->value) {
+			user_record->lname = _get_attribute_value(field);
+		}
+	}
+
+	if ((field = nm_locate_field("Full Name", fields))) {
+
+		if (field->value) {
+			user_record->full_name = _get_attribute_value(field);
+		}
+	}
+
+	if ((field = nm_locate_field(NM_A_SZ_STATUS, fields))) {
+
+		if (field->value)
+			user_record->status = atoi((char *) field->value);
+
+	}
+
+	if ((field = nm_locate_field(NM_A_SZ_MESSAGE_BODY, fields))) {
+
+		if (field->value)
+			user_record->status_text = g_strdup((char *) field->value);
+
+	}
+
+	user_record->fields = nm_copy_field_array(fields);
+
+	return user_record;
+}
+
+void
+nm_user_record_copy(NMUserRecord * dest, NMUserRecord * src)
+{
+	if (dest == NULL || src == NULL)
+		return;
+
+	dest->status = src->status;
+
+	/* Copy status text */
+	if (dest->status_text) {
+		g_free(dest->status_text);
+		dest->status_text = NULL;
+	}
+
+	if (src->status_text)
+		dest->status_text = g_strdup(src->status_text);
+
+	/* Copy DN */
+	if (dest->dn) {
+		g_free(dest->dn);
+		dest->dn = NULL;
+	}
+
+	if (src->dn)
+		dest->dn = g_strdup(src->dn);
+
+	/* Copy CN */
+	if (dest->cn) {
+		g_free(dest->cn);
+		dest->cn = NULL;
+	}
+
+	if (src->cn)
+		dest->cn = g_strdup(src->cn);
+
+	/* Copy display id */
+	if (dest->display_id) {
+		g_free(dest->display_id);
+		dest->display_id = NULL;
+	}
+
+	if (src->display_id)
+		dest->display_id = g_strdup(src->display_id);
+
+	/* Copy first name */
+	if (dest->fname) {
+		g_free(dest->fname);
+		dest->fname = NULL;
+	}
+
+	if (src->fname)
+		dest->fname = g_strdup(src->fname);
+
+	/* Copy last name */
+	if (dest->lname) {
+		g_free(dest->lname);
+		dest->lname = NULL;
+	}
+
+	if (src->lname)
+		dest->lname = g_strdup(src->lname);
+
+	/* Copy full name */
+	if (dest->full_name) {
+		g_free(dest->full_name);
+		dest->full_name = NULL;
+	}
+
+	if (src->full_name)
+		dest->full_name = g_strdup(src->full_name);
+
+	/* Copy fields */
+	if (src->fields) {
+		
+		if (dest->fields) {
+			nm_free_fields(&dest->fields);
+		}
+		
+		dest->fields = nm_copy_field_array(src->fields);
+	}
+
+	/* Copy data */
+	dest->data = src->data;
+}
+
+void
+nm_user_record_add_ref(NMUserRecord * user_record)
+{
+	if (user_record)
+		user_record->ref_count++;
+}
+
+void
+nm_release_user_record(NMUserRecord * user_record)
+{
+	if (--(user_record->ref_count) == 0) {
+
+		gaim_debug(GAIM_DEBUG_INFO, "novell",
+				   "Releasing user_record, total=%d\n", --count);
+
+		if (user_record->dn) {
+			g_free(user_record->dn);
+		}
+
+		if (user_record->cn) {
+			g_free(user_record->cn);
+		}
+
+		if (user_record->display_id) {
+			g_free(user_record->display_id);
+		}
+
+		if (user_record->fname) {
+			g_free(user_record->fname);
+		}
+
+		if (user_record->lname) {
+			g_free(user_record->lname);
+		}
+
+		if (user_record->full_name) {
+			g_free(user_record->full_name);
+		}
+		
+		if (user_record->status_text) {
+			g_free(user_record->status_text);
+		}
+
+		nm_free_fields(&user_record->fields);
+
+		g_free(user_record);
+	}
+}
+
+/* UserRecord API */
+
+NMSTATUS_T
+nm_user_record_get_status(NMUserRecord * user_record)
+{
+	if (user_record == NULL)
+		return (NMSTATUS_T) - 1;
+
+	return user_record->status;
+
+}
+
+const char *
+nm_user_record_get_status_text(NMUserRecord * user_record)
+{
+	if (user_record == NULL)
+		return NULL;
+
+	return user_record->status_text;
+}
+
+void
+nm_user_record_set_dn(NMUserRecord * user_record, const char *dn)
+{
+	if (user_record != NULL && dn != NULL) {
+		if (user_record->dn)
+			g_free(user_record->dn);
+
+		user_record->dn = g_strdup(dn);
+	}
+}
+
+const char *
+nm_user_record_get_dn(NMUserRecord * user_record)
+{
+	if (user_record == NULL)
+		return NULL;
+
+	return user_record->dn;
+}
+
+void
+nm_user_record_set_userid(NMUserRecord * user_record, const char *userid)
+{
+	if (user_record != NULL && userid != NULL) {
+		if (user_record->cn)
+			g_free(user_record->cn);
+
+		user_record->cn = g_strdup(userid);
+	}
+}
+
+const char *
+nm_user_record_get_userid(NMUserRecord * user_record)
+{
+	if (user_record == NULL)
+		return NULL;
+
+	return user_record->cn;
+}
+
+void
+nm_user_record_set_display_id(NMUserRecord * user_record, const char *display_id)
+{
+	if (user_record != NULL && display_id != NULL) {
+		if (user_record->display_id)
+			g_free(user_record->display_id);
+
+		user_record->display_id = g_strdup(display_id);
+	}
+}
+
+const char *
+nm_user_record_get_display_id(NMUserRecord * user_record)
+{
+	if (user_record == NULL)
+		return NULL;
+
+	if (user_record->display_id == NULL) {
+		user_record->display_id = nm_typed_to_dotted(user_record->dn);
+	}
+
+	return user_record->display_id;
+}
+
+const char *
+nm_user_record_get_full_name(NMUserRecord * user_record)
+{
+	if (user_record == NULL)
+		return NULL;
+
+	if (user_record->full_name == NULL) {
+		if (user_record->fname && user_record->lname) {
+			user_record->full_name = g_strdup_printf("%s %s",
+													 user_record->fname,
+													 user_record->lname);
+
+		}
+	}
+
+	return user_record->full_name;
+}
+
+const char *
+nm_user_record_get_first_name(NMUserRecord * user_record)
+{
+	if (user_record == NULL)
+		return NULL;
+
+	return user_record->fname;
+
+}
+
+const char *
+nm_user_record_get_last_name(NMUserRecord * user_record)
+{
+	if (user_record == NULL)
+		return NULL;
+
+	return user_record->lname;
+}
+
+gpointer
+nm_user_record_get_data(NMUserRecord * user_record)
+{
+	if (user_record == NULL)
+		return NULL;
+
+	return user_record->data;
+}
+
+void
+nm_user_record_set_data(NMUserRecord * user_record, gpointer data)
+{
+	if (user_record == NULL)
+		return;
+
+	user_record->data = data;
+}
+
+void
+nm_user_record_set_status(NMUserRecord * user_record,
+						  int status, const char *text)
+{
+	if (user_record == NULL)
+		return;
+
+	user_record->status = status;
+
+	if (user_record->status_text) {
+		g_free(user_record->status_text);
+		user_record->status_text = NULL;
+	}
+	
+	if (text)
+		user_record->status_text = g_strdup(text);
+}
+
+gboolean
+nm_user_record_get_auth_attr(NMUserRecord *user_record)
+{
+	if (user_record == NULL)
+		return FALSE;
+
+	return user_record->auth_attr;
+}
+
+int
+nm_user_record_get_property_count(NMUserRecord * user_record)
+{
+	NMField *locate, *fields;
+
+	int count = 0;
+
+	if (user_record && user_record->fields) {
+		locate = nm_locate_field(NM_A_FA_INFO_DISPLAY_ARRAY,
+								 (NMField *) user_record->fields);
+		if (locate && (fields = (NMField *) (locate->value))) {
+			count = (int) nm_count_fields(fields);
+		}
+	}
+	return count;
+}
+
+NMProperty *
+nm_user_record_get_property(NMUserRecord * user_record, int index)
+{
+	NMProperty *property = NULL;
+	NMField *field = NULL, *fields, *locate;
+
+	if (user_record && user_record->fields) {
+		locate = nm_locate_field(NM_A_FA_INFO_DISPLAY_ARRAY,
+								 (NMField *) user_record->fields);
+		if (locate && (fields = (NMField *) (locate->value))) {
+			int max = nm_count_fields(fields);
+
+			if (index < max) {
+				if (user_record) {
+					field = &fields[index];
+					if (field && field->tag && field->value) {
+						property = g_new0(NMProperty, 1);
+						property->tag = g_strdup(field->tag);
+						property->value = _get_attribute_value(field);
+					}
+				}
+			}
+		}
+	}
+
+	return property;
+}
+
+void
+nm_release_property(NMProperty * property)
+{
+	if (property) {
+		if (property->tag)
+			g_free(property->tag);
+
+		if (property->value)
+			g_free(property->value);
+
+		g_free(property);
+	}
+}
+
+const char *
+nm_property_get_tag(NMProperty * property)
+{
+	if (property)
+		return property->tag;
+	else
+		return NULL;
+}
+
+const char *
+nm_property_get_value(NMProperty * property)
+{
+	if (property)
+		return property->value;
+	else
+		return NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/novell/nmuserrecord.h	Sat Apr 17 13:55:28 2004 +0000
@@ -0,0 +1,275 @@
+/*
+ * nmuserrecord.h
+ *
+ * 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.
+ *
+ */
+
+#ifndef __NM_USER_RECORD_H__
+#define __NM_USER_RECORD_H__
+
+#include <glib.h>
+
+typedef struct _NMUserRecord NMUserRecord;
+typedef struct _NMProperty NMProperty;
+
+#include "nmfield.h"
+#include "nmuser.h"
+
+/**
+ * Creates an NMUserRecord
+ *
+ * The NMUserRecord should be released by calling
+ * nm_release_user_record
+ *
+ * @return 			The new user record
+ *
+ */
+NMUserRecord *nm_create_user_record();
+
+/**
+ * Creates an NMUserRecord
+ *
+ * The NMUserRecord should be released by calling
+ * nm_release_user_record 
+ *
+ * @param	details	Should be a NM_A_FA_USER_DETAILS
+ *
+ *
+ * @return 			The new user record
+ *
+ */
+NMUserRecord *nm_create_user_record_from_fields(NMField * details);
+
+/**
+ * Add a reference to an existing user_record
+ *
+ * The reference should be released by calling
+ * nm_release_user_record 
+ *
+ * @param	user_record	The contact to addref
+ *
+ */
+void nm_user_record_add_ref(NMUserRecord * user_record);
+
+/**
+ * Release a reference to the user record
+ *
+ * @param	user_record		The user record
+ *
+ */
+void nm_release_user_record(NMUserRecord * user_record);
+
+/**
+ * Set the status for the user record
+ *
+ * @param	user_record		The user record
+ * @param 	status			The status for the user
+ * @param	text			The status text for the user
+ *
+ */
+void nm_user_record_set_status(NMUserRecord * user_record, NMSTATUS_T status,
+							   const char *text);
+
+/**
+ * Get the status for the user record
+ *
+ * @param	user_record 	The user record
+ *
+ * @return	The status for the user record
+ */
+NMSTATUS_T nm_user_record_get_status(NMUserRecord * user_record);
+
+/**
+ * Get the status text for the user record
+ *
+ * @param	user_record		The user record
+ *
+ * @return	The status text if there is any, NULL otherwise
+ *
+ */
+const char *nm_user_record_get_status_text(NMUserRecord * user_record);
+
+/**
+ * Set the DN for the user record
+ *
+ * @param	user_record		The user record
+ * @param	dn				The new DN for the user record
+ *
+ */
+void nm_user_record_set_dn(NMUserRecord * user_record, const char *dn);
+
+/**
+ * Get the DN for the user record
+ *
+ * @param	user_record		The user record
+ *
+ * @return	The DN for the user record
+ */
+const char *nm_user_record_get_dn(NMUserRecord * user_record);
+
+/**
+ * Set the user id for the 
+ *
+ * @param	user_record		The user record
+ * @param	userid			The userid (CN) for the user record
+ *
+ */
+void nm_user_record_set_userid(NMUserRecord * user_record, const char *userid);
+
+/**
+ * Get the user id for the user record
+ *
+ * @param	user_record	The user record
+ *
+ * @return	The user id for the user record
+ */
+const char *nm_user_record_get_userid(NMUserRecord * user_record);
+
+/**
+ * Set the display id for the user record
+ *
+ * @param	user_record		The user record
+ * @param	display_id		The new display id for the user
+ *
+ */
+void nm_user_record_set_display_id(NMUserRecord * user_record,
+								   const char *display_id);
+
+/**
+ * Get the display id for the user record
+ *
+ * @param	user_record		The user record
+ *
+ * @return	The display id for the user record
+ */
+const char *nm_user_record_get_display_id(NMUserRecord * user_record);
+
+/**
+ * Return whether or not the display id is an auth attribute or not.
+ *
+ * @param	user_record		The user record
+ *
+ * @return	TRUE if display_id is an auth attribute, FALSE otherwise.
+ */
+gboolean
+nm_user_record_get_auth_attr(NMUserRecord *user_record);
+
+/**
+ * Get the full name for the user record
+ *
+ * @param	user_record		The user record
+ *
+ * @return	The full name for the user
+ */
+const char *nm_user_record_get_full_name(NMUserRecord * user_record);
+
+/**
+ * Get the first name for the user record
+ *
+ * @param	user_record		The user record
+ *
+ * @return	The first name for the user
+ */
+const char *nm_user_record_get_first_name(NMUserRecord * user_record);
+
+/**
+ * Get the last name for the user record
+ *
+ * @param	user_record		The user record
+ * 
+ * @return	The last name for the user
+ */
+const char *nm_user_record_get_last_name(NMUserRecord * user_record);
+
+/**
+ * Set the user defined data for the user record
+ *
+ * @param	user_record		The user record
+ * @param	data			The user defined data for the user record
+ * 
+ */
+void nm_user_record_set_data(NMUserRecord * user_record, gpointer data);
+
+/**
+ * Get the user defined data for the user record
+ *
+ * @param	user_record		The user record
+ *
+ * @return	The user defined data for the user record
+ */
+gpointer nm_user_record_get_data(NMUserRecord * user_record);
+
+/**
+ * Get the property count for the user record
+ *
+ * @param	user_record		The user record
+ *
+ * @return	The number of information properties for the user record
+ *
+ */
+int nm_user_record_get_property_count(NMUserRecord * user_record);
+
+/**
+ * Get an info property for the user record. The property must be released
+ * by calling nm_release_property()
+ *
+ * @param	user_record		The user record
+ * @param	index			The index of the property to get (zero based)
+ *
+ * @return	The property
+ */
+NMProperty *nm_user_record_get_property(NMUserRecord * user_record, int index);
+
+/**
+ * Release a property object
+ *
+ * @param	property	The property
+ *
+ */
+void nm_release_property(NMProperty * property);
+
+/**
+ * Get the tag for the property
+ *
+ * @param 	property	The property
+ *
+ * @return 	The tag of the property (i.e. "Email Address")
+ */
+const char *nm_property_get_tag(NMProperty * property);
+
+/**
+ * Get the value for the property
+ *
+ * @param	property	The property
+ *
+ * @return	The value of the property (i.e. "nobody@nowhere.com")
+ */
+const char *nm_property_get_value(NMProperty * property);
+
+/**
+ * Copy a user record (deep copy). The dest user record must have already been
+ * created (nm_create_user_record)
+ *
+ * @param	dest	The destination of the copy
+ * @param	src		The source of the copy
+ *
+ */
+void nm_user_record_copy(NMUserRecord * dest, NMUserRecord * src);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/novell/novell.c	Sat Apr 17 13:55:28 2004 +0000
@@ -0,0 +1,2436 @@
+/*
+ * 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,
+	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 Messenger",								 /**< 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);
--- a/src/prpl.c	Fri Apr 16 23:32:46 2004 +0000
+++ b/src/prpl.c	Sat Apr 17 13:55:28 2004 +0000
@@ -49,6 +49,7 @@
 		case GAIM_PROTO_MOO:      return "prpl-moo";     break;
 		case GAIM_PROTO_TREPIA:   return "prpl-trepia";  break;
 		case GAIM_PROTO_BLOGGER:  return "prpl-blogger"; break;
+		case GAIM_PROTO_NOVELL:  return "prpl-novell"; break;
 
 		default:
 			break;
@@ -75,6 +76,7 @@
 	else if (!strcmp(id, "prpl-moo"))     return GAIM_PROTO_MOO;
 	else if (!strcmp(id, "prpl-trepia"))  return GAIM_PROTO_TREPIA;
 	else if (!strcmp(id, "prpl-blogger")) return GAIM_PROTO_BLOGGER;
+	else if (!strcmp(id, "prpl-novell"))  return GAIM_PROTO_NOVELL;
 
 	return -1;
 }
--- a/src/prpl.h	Fri Apr 16 23:32:46 2004 +0000
+++ b/src/prpl.h	Sat Apr 17 13:55:28 2004 +0000
@@ -66,6 +66,7 @@
 	GAIM_PROTO_BATTLENET,   /**< Battle.NET protocol       */
 	GAIM_PROTO_SSCP,	/**< SSCP (ConfMgr) protocol   */
 	GAIM_PROTO_BLOGGER,	/**< Blogger xml-rpc protocol  */
+	GAIM_PROTO_NOVELL,	/**< GroupWise Messenger protocol  */
 	GAIM_PROTO_UNTAKEN      /**< Untaken protocol number   */
 
 } GaimProtocol;