changeset 9268:54fb1f466953

[gaim-migrate @ 10069] " - Added keepalive support - Improved handling of character sets in RTF (improves intl interoperability between Gaim and the Windows/XPLAT Messenger clients)." --Mike Stoddard of Novell committer: Tailor Script <tailor@pidgin.im>
author Luke Schierer <lschiere@pidgin.im>
date Sat, 12 Jun 2004 15:13:29 +0000
parents 3e8619644a8a
children 37aa1a2fe9df
files ChangeLog src/protocols/novell/Makefile.am src/protocols/novell/Makefile.mingw src/protocols/novell/nmconn.c src/protocols/novell/nmevent.c src/protocols/novell/nmrtf.c src/protocols/novell/nmrtf.h src/protocols/novell/nmuser.c src/protocols/novell/nmuser.h src/protocols/novell/novell.c
diffstat 10 files changed, 1042 insertions(+), 104 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sat Jun 12 00:00:01 2004 +0000
+++ b/ChangeLog	Sat Jun 12 15:13:29 2004 +0000
@@ -23,6 +23,8 @@
 	  defaults for the next pounce.
 
 	Bug Fixes:
+	* Better handling of character sets in RTF for Novell (Mike Stoddard of
+	  Novell)
 	* Non-looping animated icons no longer cause Gaim to freeze
 	* Fixed a crash in SILC that sometimes happened when resolving
 	  the buddy list (Pekka Riikonen)
--- a/src/protocols/novell/Makefile.am	Sat Jun 12 00:00:01 2004 +0000
+++ b/src/protocols/novell/Makefile.am	Sat Jun 12 15:13:29 2004 +0000
@@ -18,6 +18,8 @@
 		nmmessage.c \
 		nmrequest.h \
 		nmrequest.c \
+		nmrtf.h \
+		nmrtf.c \
 		nmuser.h \
 		nmuser.c \
 		nmuserrecord.h \
--- a/src/protocols/novell/Makefile.mingw	Sat Jun 12 00:00:01 2004 +0000
+++ b/src/protocols/novell/Makefile.mingw	Sat Jun 12 15:13:29 2004 +0000
@@ -75,6 +75,7 @@
 		nmevent.c \
 		nmmessage.c \
 		nmrequest.c \
+		nmrtf.c \
 		nmuser.c \
 		nmuserrecord.c \
 		novell.c
--- a/src/protocols/novell/nmconn.c	Sat Jun 12 00:00:01 2004 +0000
+++ b/src/protocols/novell/nmconn.c	Sat Jun 12 15:13:29 2004 +0000
@@ -383,6 +383,7 @@
 	int bytes_to_send;
 	int ret;
 	NMField *request = NULL;
+	char *str = NULL;
 
 	if (conn == NULL || cmd == NULL)
 		return NMERR_BAD_PARM;
@@ -415,14 +416,13 @@
 
 	/* 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));
+		if (fields)
+			request = nm_copy_field_array(fields);
 
-			request = nm_field_add_pointer(request, NM_A_SZ_TRANSACTION_ID, 0,
-										   NMFIELD_METHOD_VALID, 0,
-										   str, NMFIELD_TYPE_UTF8);
-		}
+		str = g_strdup_printf("%d", ++(conn->trans_id));
+		request = nm_field_add_pointer(request, NM_A_SZ_TRANSACTION_ID, 0,
+									   NMFIELD_METHOD_VALID, 0,
+									   str, NMFIELD_TYPE_UTF8);
 	}
 
 	/* Send the request to the server */
--- a/src/protocols/novell/nmevent.c	Sat Jun 12 00:00:01 2004 +0000
+++ b/src/protocols/novell/nmevent.c	Sat Jun 12 15:13:29 2004 +0000
@@ -25,6 +25,7 @@
 #include "nmfield.h"
 #include "nmconn.h"
 #include "nmuserrecord.h"
+#include "nmrtf.h"
 
 struct _NMEvent
 {
@@ -52,66 +53,6 @@
 
 };
 
-/* 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,
@@ -228,8 +169,11 @@
 
 			/* Auto replies are not in RTF format! */
 			if (!autoreply) {
+				NMRtfContext *ctx;
 
-				nortf = _strip_rtf((const char *) msg, size);
+				ctx = nm_rtf_init();
+				nortf = nm_rtf_strip_formatting(ctx, msg);
+				nm_rtf_deinit(ctx);
 
 				gaim_debug(GAIM_DEBUG_INFO, "novell",
 						   "Message without RTF is %s\n", nortf);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/novell/nmrtf.c	Sat Jun 12 15:13:29 2004 +0000
@@ -0,0 +1,829 @@
+/*
+ * nmrtf.c
+ *
+ * Copyright (c) 2004 Novell, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA	02111-1307	USA
+ *
+ */
+
+/* This code was adapted from the sample RTF reader found here:
+ * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnrtfspec/html/rtfspec.asp
+ */
+
+#include <glib.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <ctype.h>
+#include <string.h>
+#include "nmrtf.h"
+#include "debug.h"
+
+/* Internal RTF parser error codes */
+#define NMRTF_OK 0                      /* Everything's fine! */
+#define NMRTF_STACK_UNDERFLOW    1       /* Unmatched '}' */
+#define NMRTF_STACK_OVERFLOW     2       /* Too many '{' -- memory exhausted */
+#define NMRTF_UNMATCHED_BRACE    3       /* RTF ended during an open group. */
+#define NMRTF_INVALID_HEX        4       /* invalid hex character found in data */
+#define NMRTF_BAD_TABLE          5       /* RTF table (sym or prop) invalid */
+#define NMRTF_ASSERTION		     6       /* Assertion failure */
+#define NMRTF_EOF		         7       /* End of file reached while reading RTF */
+#define NMRTF_CONVERT_ERROR		 8		 /* Error converting text  */
+
+#define NMRTF_MAX_DEPTH 256
+
+typedef enum
+{
+	NMRTF_STATE_NORMAL,
+	NMRTF_STATE_SKIP,
+	NMRTF_STATE_FONTTABLE,
+	NMRTF_STATE_BIN,
+	NMRTF_STATE_HEX
+} NMRtfState;  /* Rtf State */
+
+/* Property types that we care about */
+typedef enum
+{
+	NMRTF_PROP_FONT_IDX,
+	NMRTF_PROP_FONT_CHARSET,
+	NMRTF_PROP_MAX
+} NMRtfProperty;
+
+typedef enum
+{
+	NMRTF_SPECIAL_BIN,
+	NMRTF_SPECIAL_HEX,
+	NMRTF_SPECIAL_UNICODE,
+	NMRTF_SPECIAL_SKIP
+} NMRtfSpecialKwd;
+
+typedef enum
+{
+	NMRTF_DEST_FONTTABLE,
+	NMRTF_DEST_SKIP
+} NMRtfDestinationType;
+
+typedef enum
+{
+	NMRTF_KWD_CHAR,
+	NMRTF_KWD_DEST,
+	NMRTF_KWD_PROP,
+	NMRTF_KWD_SPEC
+} NMRtfKeywordType;
+
+typedef struct _NMRTFCharProp
+{
+	/* All we care about for now is the font.
+	 * bold, italic, underline, etc. should be
+	 * added here
+	 */
+	int font_idx;
+	int	font_charset;
+} NMRtfCharProp;
+
+typedef struct _NMRtfStateSave
+{
+    NMRtfCharProp chp;
+    NMRtfState rds;
+    NMRtfState ris;
+} NMRtfStateSave;
+
+typedef struct _NMRtfSymbol
+{
+    char *keyword;          	/* RTF keyword */
+    int  default_val;          	/* default value to use */
+    gboolean pass_default;  	/* true to use default value from this table */
+    NMRtfKeywordType kwd_type;  /* the type of the keyword */
+    int  action;               	/* property type if the keyword represents a property */
+                            	/* destination type if the keyword represents a destination */
+                            	/* character to print if the keyword represents a character */
+} NMRtfSymbol;
+
+
+typedef struct _NMRtfFont
+{
+	int number;
+	char *name;
+	int charset;
+} NMRtfFont;
+
+/* RTF Context */
+struct _NMRtfContext
+{
+	NMRtfState rds; 		/* destination state */
+	NMRtfState ris; 		/* internal state */
+	NMRtfCharProp chp; 		/* current character properties (ie. font, bold, italic, etc.) */
+	GSList *font_table;		/* the font table */
+	GSList *saved;			/* saved state stack */
+	int param;				/* numeric parameter for the current keyword */
+	long bytes_to_skip; 	/* number of bytes to skip (after encountering \bin) */
+	int depth;				/* how many groups deep are we */
+	gboolean skip_unknown;	/* if true, skip any unknown destinations (this is set after encountering '\*') */
+	char *input;			/* input string */
+	char nextch;			/* next char in input */
+	GString *ansi;   		/* Temporary ansi text, will be convert/flushed to the output string */
+	GString *output; 		/* The plain text UTF8 string */
+};
+
+static int rtf_parse(NMRtfContext *ctx);
+static int rtf_push_state(NMRtfContext *ctx);
+static int rtf_pop_state(NMRtfContext *ctx);
+static NMRtfFont *rtf_get_font(NMRtfContext *ctx, int index);
+static int rtf_get_char(NMRtfContext *ctx, guchar *ch);
+static int rtf_unget_char(NMRtfContext *ctx, guchar ch);
+static int rtf_flush_data(NMRtfContext *ctx);
+static int rtf_parse_keyword(NMRtfContext *ctx);
+static int rtf_dispatch_control(NMRtfContext *ctx, char *keyword, int param, gboolean param_set);
+static int rtf_dispatch_char(NMRtfContext *ctx, guchar ch);
+static int rtf_dispatch_unicode_char(NMRtfContext *ctx, gunichar ch);
+static int rtf_print_char(NMRtfContext *ctx, guchar ch);
+static int rtf_print_unicode_char(NMRtfContext *ctx, gunichar ch);
+static int rtf_change_destination(NMRtfContext *ctx, NMRtfDestinationType dest);
+static int rtf_dispatch_special(NMRtfContext *ctx, NMRtfSpecialKwd special);
+static int rtf_apply_property(NMRtfContext *ctx, NMRtfProperty prop, int val);
+
+/* RTF parser tables */
+
+/* Keyword descriptions */
+NMRtfSymbol rtf_symbols[] = {
+	/* keyword, default, pass_default, keyword_type, action */
+    {"fonttbl",  0,   	FALSE,    	NMRTF_KWD_DEST, NMRTF_DEST_FONTTABLE},
+	{"f",		 0,		FALSE,		NMRTF_KWD_PROP, NMRTF_PROP_FONT_IDX},
+	{"fcharset",  0,  	FALSE,		NMRTF_KWD_PROP,	NMRTF_PROP_FONT_CHARSET},
+    {"par",      0,     FALSE,		NMRTF_KWD_CHAR, 0x0a},
+    {"line",      0,    FALSE,     	NMRTF_KWD_CHAR, 0x0a},
+    {"\0x0a",    0,     FALSE,     	NMRTF_KWD_CHAR, 0x0a},
+    {"\0x0d",    0,     FALSE,     	NMRTF_KWD_CHAR, 0x0a},
+    {"tab",      0,     FALSE,     	NMRTF_KWD_CHAR, 0x09},
+	{"\r",		0,		FALSE,		NMRTF_KWD_CHAR,	'\r'},
+	{"\n",		0,		FALSE,		NMRTF_KWD_CHAR,	'\n'},
+    {"ldblquote",0,     FALSE,     	NMRTF_KWD_CHAR, '"'},
+    {"rdblquote",0,     FALSE,     	NMRTF_KWD_CHAR, '"'},
+    {"{",        0,     FALSE,     	NMRTF_KWD_CHAR, '{'},
+    {"}",        0,     FALSE,     	NMRTF_KWD_CHAR, '}'},
+    {"\\",       0,     FALSE,     	NMRTF_KWD_CHAR,  '\\'},
+    {"bin",      0,     FALSE,     	NMRTF_KWD_SPEC, NMRTF_SPECIAL_BIN},
+    {"*",        0,     FALSE,     	NMRTF_KWD_SPEC, NMRTF_SPECIAL_SKIP},
+    {"'",        0,     FALSE,     	NMRTF_KWD_SPEC, NMRTF_SPECIAL_HEX},
+	{"u",		0,		FALSE,		NMRTF_KWD_SPEC,	NMRTF_SPECIAL_UNICODE},
+    {"colortbl", 0,     FALSE,     NMRTF_KWD_DEST,	NMRTF_DEST_SKIP},
+    {"author",   0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"buptim",   0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"comment",  0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"creatim",  0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"doccomm",  0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"footer",   0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"footerf",  0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"footerl",  0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"footerr",  0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"footnote", 0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"ftncn",    0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"ftnsep",   0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"ftnsepc",  0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"header",   0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"headerf",  0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"headerl",  0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"headerr",  0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"info",     0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"keywords", 0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"operator", 0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"pict",     0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"printim",  0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"private1", 0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"revtim",   0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"rxe",      0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"stylesheet",   0, FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"subject",  0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"tc",       0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"title",    0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"txe",      0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP},
+    {"xe",       0,     FALSE,     NMRTF_KWD_DEST,  NMRTF_DEST_SKIP}
+};
+int table_size = sizeof(rtf_symbols) / sizeof(NMRtfSymbol);
+
+NMRtfContext *
+nm_rtf_init()
+{
+	NMRtfContext *ctx = g_new0(NMRtfContext, 1);
+	ctx->nextch = -1;
+	ctx->ansi = g_string_new("");
+	ctx->output = g_string_new("");
+	return ctx;
+}
+
+char *
+nm_rtf_strip_formatting(NMRtfContext *ctx, const char *input)
+{
+	int status;
+
+	ctx->input = (char *)input;
+	status = rtf_parse(ctx);
+	if (status == NMRTF_OK)
+		return g_strdup(ctx->output->str);
+
+	gaim_debug_info("novell", "RTF parser failed with error code %d", status);
+	return NULL;
+}
+
+void
+nm_rtf_deinit(NMRtfContext *ctx)
+{
+	GSList *node;
+	NMRtfFont *font;
+	NMRtfStateSave *save;
+
+	if (ctx) {
+		for (node = ctx->font_table; node; node = node->next) {
+			font = node->data;
+			g_free(font->name);
+			g_free(font);
+			node->data = NULL;
+		}
+		g_slist_free(ctx->font_table);
+		for (node = ctx->saved; node; node = node->next) {
+			save = node->data;
+			g_free(save);
+			node->data = NULL;
+		}
+		g_slist_free(ctx->saved);
+		g_string_free(ctx->ansi, TRUE);
+		g_string_free(ctx->output, TRUE);
+		g_free(ctx);
+	}
+}
+
+static const char *
+get_current_encoding(NMRtfContext *ctx)
+{
+	NMRtfFont *font;
+
+	font = rtf_get_font(ctx, ctx->chp.font_idx);
+
+	switch (font->charset) {
+		case 0:
+			return "CP1252";
+		case 77:
+			return "MACINTOSH";
+		case 78:
+			return "SJIS";
+		case 128:
+			return "CP932";
+		case 129:
+			return "CP949";
+		case 130:
+			return "CP1361";
+		case 134:
+			return "CP936";
+		case 136:
+			return "CP950";
+		case 161:
+			return "CP1253";
+		case 162:
+			return "CP1254";
+		case 163:
+			return "CP1258";
+		case 181:
+		case 177:
+			return "CP1255";
+		case 178:
+		case 179:
+		case 180:
+			return "CP1256";
+		case 186:
+			return "CP1257";
+		case 204:
+			return "CP1251";
+		case 222:
+			return "CP874";
+		case 238:
+			return "CP1250";
+		case 254:
+			return "CP437";
+		default:
+			gaim_debug_info("novell", "Unhandled font charset %d\n", font->charset);
+			return "CP1252";
+	}
+	return "CP1252";
+}
+
+
+/*
+ * Add an entry to the font table
+ */
+static int
+rtf_add_font_entry(NMRtfContext *ctx, int number, const char *name, int charset)
+{
+    NMRtfFont *font = g_new0(NMRtfFont, 1);
+
+	font->number = number;
+    font->name = g_strdup(name);
+	font->charset = charset;
+
+	gaim_debug_info("novell", "Adding font to table: #%d\t%s\t%d\n",
+					font->number, font->name, font->charset);
+
+	ctx->font_table = g_slist_append(ctx->font_table, font);
+
+    return NMRTF_OK;
+}
+
+/*
+ * Return the nth entry in the font table
+ */
+static NMRtfFont *
+rtf_get_font(NMRtfContext *ctx, int nth)
+{
+	NMRtfFont *font;
+
+	font = g_slist_nth_data(ctx->font_table, nth);
+
+	return font;
+}
+
+/*
+ * Step 1:
+ * Isolate RTF keywords and send them to rtf_parse_keyword;
+ * Push and pop state at the start and end of RTF groups;
+ * Send text to rtf_dispatch_char for further processing.
+ */
+static int
+rtf_parse(NMRtfContext *ctx)
+{
+    int status;
+    guchar ch;
+    guchar hex_byte = 0;
+    int hex_count = 2;
+	int len;
+
+	if (ctx->input == NULL)
+		return NMRTF_OK;
+
+    while (rtf_get_char(ctx, &ch) == NMRTF_OK) {
+        if (ctx->depth < 0)
+            return NMRTF_STACK_UNDERFLOW;
+
+		/* if we're parsing binary data, handle it directly */
+        if (ctx->ris == NMRTF_STATE_BIN) {
+            if ((status = rtf_dispatch_char(ctx, ch)) != NMRTF_OK)
+                return status;
+        } else {
+            switch (ch) {
+				case '{':
+					if (ctx->depth > NMRTF_MAX_DEPTH)
+						return NMRTF_STACK_OVERFLOW;
+                    rtf_flush_data(ctx);
+					if ((status = rtf_push_state(ctx)) != NMRTF_OK)
+						return status;
+					break;
+				case '}':
+					rtf_flush_data(ctx);
+
+					/* for some reason there is always an unwanted '\par' at the end */
+					if (ctx->rds == NMRTF_STATE_NORMAL) {
+						len = ctx->output->len;
+						if (ctx->output->str[len-1] == '\n')
+							ctx->output = g_string_truncate(ctx->output, len-1);
+					}
+
+					if ((status = rtf_pop_state(ctx)) != NMRTF_OK)
+						return status;
+
+					if (ctx->depth < 0)
+						return NMRTF_STACK_OVERFLOW;
+					break;
+				case '\\':
+					if ((status = rtf_parse_keyword(ctx)) != NMRTF_OK)
+						return status;
+					break;
+				case 0x0d:
+				case 0x0a:          /*  cr and lf are noise characters... */
+					break;
+				default:
+					if (ctx->ris == NMRTF_STATE_NORMAL) {
+						if ((status = rtf_dispatch_char(ctx, ch)) != NMRTF_OK)
+							return status;
+					} else {               /* parsing a hex encoded character */
+						if (ctx->ris != NMRTF_STATE_HEX)
+							return NMRTF_ASSERTION;
+
+						hex_byte = hex_byte << 4;
+						if (isdigit(ch))
+							hex_byte += (char) ch - '0';
+						else {
+							if (islower(ch)) {
+								if (ch < 'a' || ch > 'f')
+									return NMRTF_INVALID_HEX;
+								hex_byte += (char) ch - 'a' + 10;
+							} else {
+								if (ch < 'A' || ch > 'F')
+									return NMRTF_INVALID_HEX;
+								hex_byte += (char) ch - 'A' + 10;
+							}
+						}
+						hex_count--;
+						if (hex_count == 0) {
+							if ((status = rtf_dispatch_char(ctx, hex_byte)) != NMRTF_OK)
+								return status;
+							hex_count = 2;
+							hex_byte = 0;
+							ctx->ris = NMRTF_STATE_NORMAL;
+						}
+					}
+					break;
+            }
+        }
+    }
+    if (ctx->depth < 0)
+        return NMRTF_STACK_OVERFLOW;
+    if (ctx->depth > 0)
+        return NMRTF_UNMATCHED_BRACE;
+    return NMRTF_OK;
+}
+
+/*
+ * Push the current state onto stack
+ */
+static int
+rtf_push_state(NMRtfContext *ctx)
+{
+    NMRtfStateSave *save = g_new0(NMRtfStateSave, 1);
+    save->chp = ctx->chp;
+    save->rds = ctx->rds;
+    save->ris = ctx->ris;
+	ctx->saved = g_slist_prepend(ctx->saved, save);
+    ctx->ris = NMRTF_STATE_NORMAL;
+    (ctx->depth)++;
+    return NMRTF_OK;
+}
+
+/*
+ * Restore the state at the top of the stack
+ */
+static int
+rtf_pop_state(NMRtfContext *ctx)
+{
+    NMRtfStateSave *save_old;
+	GSList *link_old;
+
+    if (ctx->saved == NULL)
+        return NMRTF_STACK_UNDERFLOW;
+
+	save_old = ctx->saved->data;
+    ctx->chp = save_old->chp;
+    ctx->rds = save_old->rds;
+    ctx->ris = save_old->ris;
+    (ctx->depth)--;
+
+    g_free(save_old);
+	link_old = ctx->saved;
+	ctx->saved = g_slist_remove_link(ctx->saved, link_old);
+	g_slist_free_1(link_old);
+    return NMRTF_OK;
+}
+
+/*
+ * Step 2:
+ * Get a control word (and its associated value) and
+ * dispatch the control.
+ */
+static int
+rtf_parse_keyword(NMRtfContext *ctx)
+{
+	int status = NMRTF_OK;
+    guchar ch;
+    gboolean param_set = FALSE;
+    gboolean is_neg = FALSE;
+    int param = 0;
+    char *pch;
+    char keyword[30];
+    char parameter[20];
+
+    keyword[0] = '\0';
+    parameter[0] = '\0';
+    if ((status = rtf_get_char(ctx, &ch)) != NMRTF_OK)
+        return status;
+
+    if (!isalpha(ch)) {
+		/* a control symbol; no delimiter. */
+        keyword[0] = (char) ch;
+        keyword[1] = '\0';
+        return rtf_dispatch_control(ctx, keyword, 0, param_set);
+    }
+
+	/* parse keyword */
+    for (pch = keyword; isalpha(ch); rtf_get_char(ctx, &ch)) {
+        *pch = (char) ch;
+		pch++;
+	}
+    *pch = '\0';
+
+	/* check for '-' indicated a negative parameter value  */
+    if (ch == '-') {
+        is_neg = TRUE;
+        if ((status = rtf_get_char(ctx, &ch)) != NMRTF_OK)
+            return status;
+    }
+
+	/* check for numerical param */
+    if (isdigit(ch)) {
+
+        param_set = TRUE;
+        for (pch = parameter; isdigit(ch); rtf_get_char(ctx, &ch)) {
+            *pch = (char) ch;
+			pch++;
+		}
+        *pch = '\0';
+
+        ctx->param = param = atoi(parameter);
+        if (is_neg)
+            ctx->param = param = -param;
+    }
+
+	/* space after control is optional, put character back if it is not a space */
+    if (ch != ' ')
+        rtf_unget_char(ctx, ch);
+
+    return rtf_dispatch_control(ctx, keyword, param, param_set);
+}
+
+/*
+ * Route the character to the appropriate destination
+ */
+static int
+rtf_dispatch_char(NMRtfContext *ctx, guchar ch)
+{
+    if (ctx->ris == NMRTF_STATE_BIN && --(ctx->bytes_to_skip) <= 0)
+        ctx->ris = NMRTF_STATE_NORMAL;
+
+    switch (ctx->rds) {
+		case NMRTF_STATE_SKIP:
+			return NMRTF_OK;
+		case NMRTF_STATE_NORMAL:
+			return rtf_print_char(ctx, ch);
+        case NMRTF_STATE_FONTTABLE:
+            if (ch == ';')  {
+				rtf_add_font_entry(ctx, ctx->chp.font_idx,
+								   ctx->ansi->str, ctx->chp.font_charset);
+				g_string_truncate(ctx->ansi, 0);
+            }
+            else {
+                return rtf_print_char(ctx, ch);
+            }
+            return NMRTF_OK;
+		default:
+			return NMRTF_OK;
+    }
+}
+
+/* Handle a unicode character */
+static int
+rtf_dispatch_unicode_char(NMRtfContext *ctx, gunichar ch)
+{
+    switch (ctx->rds) {
+		case NMRTF_STATE_SKIP:
+			return NMRTF_OK;
+		case NMRTF_STATE_NORMAL:
+        case NMRTF_STATE_FONTTABLE:
+			return rtf_print_unicode_char(ctx, ch);
+		default:
+			return NMRTF_OK;
+    }
+}
+
+/*
+ * Output a character
+ */
+static int
+rtf_print_char(NMRtfContext *ctx, guchar ch)
+{
+
+	ctx->ansi = g_string_append_c(ctx->ansi, ch);
+
+    return NMRTF_OK;
+}
+
+/*
+ * Output a unicode character
+ */
+static int
+rtf_print_unicode_char(NMRtfContext *ctx, gunichar ch)
+{
+	char buf[7];
+	int num;
+
+	/* convert and flush the ansi buffer to the utf8 buffer */
+	rtf_flush_data(ctx);
+
+	/* convert the unicode character to utf8 and add directly to the output buffer */
+	num = g_unichar_to_utf8((gunichar) ch, buf);
+	buf[num] = 0;
+	gaim_debug_info("novell", "converted unichar 0x%X to utf8 char %s\n", ch, buf);
+
+	ctx->output = g_string_append(ctx->output, buf);
+	return NMRTF_OK;
+}
+
+/*
+ * Flush the output text
+ */
+static int
+rtf_flush_data(NMRtfContext *ctx)
+{
+    int status = NMRTF_OK;
+	char *conv_data = NULL;
+	const char *enc = NULL;
+	GError *gerror = NULL;
+
+    if (ctx->rds == NMRTF_STATE_NORMAL && ctx->ansi->len > 0) {
+		enc = get_current_encoding(ctx);
+		conv_data = g_convert(ctx->ansi->str, ctx->ansi->len, "UTF-8", enc,
+							  NULL, NULL, &gerror);
+		if (conv_data) {
+			ctx->output = g_string_append(ctx->output, conv_data);
+			g_free(conv_data);
+			ctx->ansi = g_string_truncate(ctx->ansi, 0);
+		} else {
+			status = NMRTF_CONVERT_ERROR;
+			gaim_debug_info("novell", "failed to convert data! error code = %d msg = %s\n",
+							gerror->code, gerror->message);
+			g_free(gerror);
+		}
+	}
+
+    return status;
+}
+
+/*
+ * Handle a property change
+ */
+static int
+rtf_apply_property(NMRtfContext *ctx, NMRtfProperty prop, int val)
+{
+    if (ctx->rds == NMRTF_STATE_SKIP)  /* If we're skipping text, */
+        return NMRTF_OK;          /* don't do anything. */
+
+	/* Need to flush any temporary data before a property change*/
+	rtf_flush_data(ctx);
+
+    switch (prop) {
+		case NMRTF_PROP_FONT_IDX:
+			ctx->chp.font_idx = val;
+			break;
+		case NMRTF_PROP_FONT_CHARSET:
+			ctx->chp.font_charset = val;
+			break;
+		default:
+			return NMRTF_BAD_TABLE;
+    }
+
+    return NMRTF_OK;
+}
+
+/*
+ * Step 3.
+ * Search the table for keyword and evaluate it appropriately.
+ *
+ * Inputs:
+ * keyword:   The RTF control to evaluate.
+ * param:     The parameter of the RTF control.
+ * param_set: TRUE if the control had a parameter; (that is, if param is valid)
+ *            FALSE if it did not.
+ */
+static int
+rtf_dispatch_control(NMRtfContext *ctx, char *keyword, int param, gboolean param_set)
+{
+    int idx;
+
+    for (idx = 0; idx < table_size; idx++) {
+        if (strcmp(keyword, rtf_symbols[idx].keyword) == 0)
+            break;
+	}
+
+    if (idx == table_size)  {
+        if (ctx->skip_unknown)
+            ctx->rds = NMRTF_STATE_SKIP;
+        ctx->skip_unknown = FALSE;
+        return NMRTF_OK;
+    }
+
+    /* found it! use kwd_type and action to determine what to do with it. */
+    ctx->skip_unknown = FALSE;
+    switch (rtf_symbols[idx].kwd_type) {
+		case NMRTF_KWD_PROP:
+			if (rtf_symbols[idx].pass_default || !param_set)
+				param = rtf_symbols[idx].default_val;
+			return rtf_apply_property(ctx, rtf_symbols[idx].action, param);
+		case NMRTF_KWD_CHAR:
+			return rtf_dispatch_char(ctx, rtf_symbols[idx].action);
+		case NMRTF_KWD_DEST:
+			return rtf_change_destination(ctx, rtf_symbols[idx].action);
+		case NMRTF_KWD_SPEC:
+			return rtf_dispatch_special(ctx, rtf_symbols[idx].action);
+		default:
+			return NMRTF_BAD_TABLE;
+    }
+    return NMRTF_BAD_TABLE;
+}
+
+/*
+ * Change to the destination specified.
+ */
+static int
+rtf_change_destination(NMRtfContext *ctx, NMRtfDestinationType type)
+{
+	/* if we're skipping text, don't do anything */
+    if (ctx->rds == NMRTF_STATE_SKIP)
+        return NMRTF_OK;
+
+    switch (type) {
+		case NMRTF_DEST_FONTTABLE:
+            ctx->rds = NMRTF_STATE_FONTTABLE;
+			g_string_truncate(ctx->ansi, 0);
+            break;
+		default:
+			ctx->rds = NMRTF_STATE_SKIP;       /* when in doubt, skip it... */
+			break;
+    }
+    return NMRTF_OK;
+}
+
+/*
+ * Dispatch an RTF control that needs special processing
+ */
+static int
+rtf_dispatch_special(NMRtfContext *ctx, NMRtfSpecialKwd type)
+{
+	int status = NMRTF_OK;
+	guchar ch;
+
+    if (ctx->rds == NMRTF_STATE_SKIP && type != NMRTF_SPECIAL_BIN)  /* if we're skipping, and it's not */
+        return NMRTF_OK;                        /* the \bin keyword, ignore it. */
+
+    switch (type) {
+		case NMRTF_SPECIAL_BIN:
+			ctx->ris = NMRTF_STATE_BIN;
+			ctx->bytes_to_skip = ctx->param;
+			break;
+		case NMRTF_SPECIAL_SKIP:
+			ctx->skip_unknown = TRUE;
+			break;
+		case NMRTF_SPECIAL_HEX:
+			ctx->ris = NMRTF_STATE_HEX;
+			break;
+		case NMRTF_SPECIAL_UNICODE:
+			gaim_debug_info("novell", "parsing unichar\n");
+			status = rtf_dispatch_unicode_char(ctx, ctx->param);
+ 			/* Skip next char */
+			if (status == NMRTF_OK)
+				status = rtf_get_char(ctx, &ch);
+			break;
+		default:
+			status = NMRTF_BAD_TABLE;
+			break;
+    }
+
+    return status;
+}
+
+/*
+ * Get the next character from the input stream
+ */
+static int
+rtf_get_char(NMRtfContext *ctx, guchar *ch)
+{
+    if (ctx->nextch >= 0) {
+        *ch = ctx->nextch;
+        ctx->nextch = -1;
+    }
+    else {
+		*ch = *(ctx->input);
+		ctx->input++;
+    }
+
+	if (*ch)
+		return NMRTF_OK;
+	else
+		return NMRTF_EOF;
+}
+
+/*
+ * Move a character back into the input stream
+ */
+static int
+rtf_unget_char(NMRtfContext *ctx, guchar ch)
+{
+    ctx->nextch = ch;
+    return NMRTF_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/novell/nmrtf.h	Sat Jun 12 15:13:29 2004 +0000
@@ -0,0 +1,30 @@
+/*
+ * nmrtf.h
+ *
+ * Copyright (c) 2004 Novell, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA	02111-1307	USA
+ *
+ */
+
+#ifndef __NMRTF_H__
+#define __NMRTF_H__
+
+typedef struct _NMRtfContext NMRtfContext;
+
+NMRtfContext *nm_rtf_init();
+char *nm_rtf_strip_formatting(NMRtfContext *ctx, const char *input);
+void nm_rtf_deinit(NMRtfContext *ctx);
+
+#endif
--- a/src/protocols/novell/nmuser.c	Sat Jun 12 00:00:01 2004 +0000
+++ b/src/protocols/novell/nmuser.c	Sat Jun 12 15:13:29 2004 +0000
@@ -31,20 +31,17 @@
 /* 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}"
+#define RTF_TEMPLATE 	"{\\rtf1\\ansi\n"\
+                        "{\\fonttbl{\\f0\\fnil Unknown;}}\n"\
+						"{\\colortbl ;\\red0\\green0\\blue0;}\n"\
+						"\\uc1\\cf1\\f0\\fs24 %s\\par\n}"
 #define NM_MAX_MESSAGE_SIZE 2048
 
 static NMERR_T nm_process_response(NMUser * user);
-
 static void _update_contact_list(NMUser * user, NMField * fields);
-
-static void
-_handle_multiple_get_details_login_cb(NMUser * user, NMERR_T ret_code,
-									  gpointer resp_data, gpointer user_data);
+static void _handle_multiple_get_details_login_cb(NMUser * user, NMERR_T ret_code,
+												  gpointer resp_data, gpointer user_data);
+static char * nm_rtfize_text(char *text);
 
 /**
  * See header for comments on on "public" functions
@@ -540,7 +537,7 @@
 nm_send_message(NMUser * user, NMMessage * message, nm_response_cb callback)
 {
 	NMERR_T rc = NM_OK;
-	char *text;
+	char *text, *rtfized;
 	NMField *fields = NULL, *tmp = NULL;
 	NMRequest *req = NULL;
 	NMConference *conf;
@@ -572,21 +569,24 @@
 		if (strlen(text) > NM_MAX_MESSAGE_SIZE)
 			text[NM_MAX_MESSAGE_SIZE] = 0;
 
+		rtfized = nm_rtfize_text(text);
+
+		gaim_debug_info("novell", "message text is: %s\n", text);
+		gaim_debug_info("novell", "message rtf is: %s\n", rtfized);
+
 		tmp = nm_field_add_pointer(tmp, NM_A_SZ_MESSAGE_BODY, 0, NMFIELD_METHOD_VALID, 0,
-								   g_strdup_printf(RTF_TEMPLATE, text), NMFIELD_TYPE_UTF8);
+								   rtfized, NMFIELD_TYPE_UTF8);
 
 		tmp = nm_field_add_number(tmp, NM_A_UD_MESSAGE_TYPE, 0, NMFIELD_METHOD_VALID, 0,
 								  0, NMFIELD_TYPE_UDWORD);
 
 		tmp = nm_field_add_pointer(tmp, NM_A_SZ_MESSAGE_TEXT, 0, NMFIELD_METHOD_VALID, 0,
-								   g_strdup(text), NMFIELD_TYPE_UTF8);
+								   text, NMFIELD_TYPE_UTF8);
 
 		fields = nm_field_add_pointer(fields, NM_A_FA_MESSAGE, 0, NMFIELD_METHOD_VALID, 0,
 									  tmp, NMFIELD_TYPE_ARRAY);
 		tmp = NULL;
 
-		g_free(text);
-
 		/* Add participants */
 		count = nm_conference_get_participant_count(conf);
 		for (i = 0; i < count; i++) {
@@ -1116,9 +1116,6 @@
 		nm_conn_add_request_item(user->conn, req);
 	}
 
-	if (rc == NM_OK) {
-	}
-
 	if (fields)
 		nm_free_fields(&fields);
 
@@ -1155,6 +1152,25 @@
 }
 
 NMERR_T
+nm_send_keepalive(NMUser *user, nm_response_cb callback, gpointer data)
+{
+	NMERR_T rc = NM_OK;
+	NMRequest *req = NULL;
+
+	if (user == NULL)
+		return NMERR_BAD_PARM;
+
+	rc = nm_send_request(user->conn, "ping", NULL, &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);
+	}
+
+	return rc;
+}
+
+NMERR_T
 nm_process_new_data(NMUser * user)
 {
 	NMConn *conn;
@@ -1449,6 +1465,7 @@
 
 	if (need_details) {
 
+		nm_request_add_ref(request);
 		nm_send_multiple_get_details(user, need_details,
 									 _handle_multiple_get_details_login_cb, request);
 
@@ -1648,6 +1665,7 @@
 	if ((cb = nm_request_get_callback(request))) {
 		cb(user, ret_code, nm_request_get_data(request),
 		   nm_request_get_user_define(request));
+		nm_release_request(request);
 	}
 }
 
@@ -1793,8 +1811,8 @@
 				if (list != NULL) {
 
 					done = FALSE;
+					nm_request_set_user_define(request, list);
 					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,
@@ -1909,7 +1927,6 @@
 
 		cb(user, ret_code, nm_request_get_data(request),
 		   nm_request_get_user_define(request));
-
 	}
 
 	return rc;
@@ -1935,7 +1952,9 @@
 			req = nm_conn_find_request(conn, atoi((char *) field->ptr_value));
 			if (req != NULL) {
 				rc = nm_call_handler(user, req, fields);
+				nm_conn_remove_request_item(conn, req);
 			}
+
 		}
 	}
 
@@ -2220,3 +2239,82 @@
 		cursor++;
 	}
 }
+
+static char *
+nm_rtfize_text(char *text)
+{
+	GString *gstr = NULL;
+	unsigned char *pch;
+	char *uni_str = NULL, *rtf = NULL;
+	int bytes;
+	gunichar uc;
+
+	gstr = g_string_sized_new(strlen(text)*2);
+	pch = text;
+	while (*pch) {
+		if ((*pch) <= 0x7F) {
+			switch (*pch) {
+				case '{':
+				case '}':
+				case '\\':
+					gstr = g_string_append_c(gstr, '\\');
+					gstr = g_string_append_c(gstr, *pch);
+					break;
+				case '\n':
+					gstr = g_string_append(gstr, "\\par ");
+					break;
+				default:
+					gstr = g_string_append_c(gstr, *pch);
+					break;
+			}
+			pch++;
+		} else {
+			/* convert the utf-8 character to ucs-4 for rtf encoding */
+			if(*pch <= 0xDF) {
+				uc = ((((gunichar)pch[0]) & 0x001F) << 6) |
+					(((gunichar)pch[1]) & 0x003F);
+				bytes = 2;
+			} else if(*pch <= 0xEF) {
+				uc = ((((gunichar)pch[0]) & 0x000F) << 12) |
+					((((gunichar)pch[1]) & 0x003F) << 6) |
+					(((gunichar)pch[2]) & 0x003F);
+				bytes = 3;
+			} else if (*pch <= 0xF7) {
+				uc = ((((gunichar)pch[0]) & 0x0007) << 18) |
+					((((gunichar)pch[1]) & 0x003F) << 12) |
+					((((gunichar)pch[2]) & 0x003F) << 6) |
+					(((gunichar)pch[3]) & 0x003F);
+				bytes = 4;
+			} else if (*pch <= 0xFB) {
+				uc = ((((gunichar)pch[0]) & 0x0003) << 24) |
+					((((gunichar)pch[1]) & 0x003F) << 18) |
+					((((gunichar)pch[2]) & 0x003F) << 12) |
+					((((gunichar)pch[3]) & 0x003F) << 6) |
+					(((gunichar)pch[4]) & 0x003F);
+				bytes = 5;
+			} else if (*pch <= 0xFD) {
+				uc = ((((gunichar)pch[0]) & 0x0001) << 30) |
+					((((gunichar)pch[1]) & 0x003F) << 24) |
+					((((gunichar)pch[2]) & 0x003F) << 18) |
+					((((gunichar)pch[3]) & 0x003F) << 12) |
+					((((gunichar)pch[4]) & 0x003F) << 6) |
+					(((gunichar)pch[5]) & 0x003F);
+				bytes = 6;
+			} else {
+				/* should never happen ... bogus utf-8! */
+				gaim_debug_info("novell", "bogus utf-8 lead byte: 0x%X\n", pch[0]);
+				uc = 0x003F;
+				bytes = 1;
+			}
+			uni_str = g_strdup_printf("\\u%d?", uc);
+			gaim_debug_info("novell", "unicode escaped char %s\n", uni_str);
+			gstr = g_string_append(gstr, uni_str);
+			pch += bytes;
+			g_free(uni_str);
+		}
+	}
+
+	rtf = g_strdup_printf(RTF_TEMPLATE, gstr->str);
+	g_string_free(gstr, TRUE);
+	return rtf;
+}
--- a/src/protocols/novell/nmuser.h	Sat Jun 12 00:00:01 2004 +0000
+++ b/src/protocols/novell/nmuser.h	Sat Jun 12 15:13:29 2004 +0000
@@ -124,6 +124,9 @@
 	 */
 	gpointer client_data;
 
+	/* Have the privacy lists been synched yet */
+	gboolean privacy_synched;
+
 };
 
 #define	NM_STATUS_UNKNOWN			0
@@ -559,6 +562,18 @@
 							nm_response_cb callback, gpointer data);
 
 /**
+ *	Send a ping to the server
+ *
+ *  @param	user			The logged in User
+ *	@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_keepalive(NMUser *user, nm_response_cb callback, gpointer data);
+
+/**
  *	Reads a response/event from the server and processes it.
  *
  *  @param	user	The logged in User
--- a/src/protocols/novell/novell.c	Sat Jun 12 00:00:01 2004 +0000
+++ b/src/protocols/novell/novell.c	Sat Jun 12 15:13:29 2004 +0000
@@ -37,8 +37,6 @@
 
 static GaimPlugin *my_protocol = NULL;
 
-static gboolean set_permit = FALSE;
-
 static gboolean
 _is_disconnect_error(NMERR_T err);
 
@@ -104,7 +102,6 @@
 		}
 
 		_sync_contact_list(user);
-		_sync_privacy_lists(user);
 
 		/* Tell Gaim that we are connected */
 		gaim_connection_set_state(gc, GAIM_CONNECTED);
@@ -1685,6 +1682,9 @@
 	GaimConversation *gconv;
 	NMConference *conference;
 	GaimConvImFlags imflags;
+	char *text = NULL;
+
+	text = g_markup_escape_text(nm_event_get_text(event), -1);
 
 	conference = nm_event_get_conference(event);
 	if (conference) {
@@ -1703,7 +1703,7 @@
 
 				serv_got_im(gaim_account_get_connection(user->client_data),
 							nm_user_record_get_display_id(user_record),
-							nm_event_get_text(event), imflags,
+							text, imflags,
 							nm_event_get_gmt(event));
 
 				gconv =	gaim_find_conversation_with_account(
@@ -1715,8 +1715,7 @@
 					if (contact) {
 
 						gaim_conversation_set_title(
-							gconv,
-							nm_contact_get_display_name(contact));
+							gconv, nm_contact_get_display_name(contact));
 
 
 					} else {
@@ -1759,12 +1758,12 @@
 
 				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));
+								 name, 0, text, nm_event_get_gmt(event));
 			}
 		}
 	}
+
+	g_free(text);
 }
 
 static void
@@ -2161,7 +2160,7 @@
 		return 0;
 
 	/* Create a new message */
-	message = nm_create_message(gaim_markup_strip_html(message_body));
+	message = nm_create_message(message_body);
 
 	/* Need to get the DN for the buddy so we can look up the convo */
 	dn = nm_lookup_dn(user, name);
@@ -2371,7 +2370,7 @@
 	if (user == NULL)
 		return -1;
 
-	message = nm_create_message(gaim_markup_strip_html(text));
+	message = nm_create_message(text);
 
 	for (cnode = user->conferences; cnode != NULL; cnode = cnode->next) {
 		conference = cnode->data;
@@ -3083,8 +3082,9 @@
 	if (user == NULL)
 		return;
 
-	if (set_permit == FALSE) {
-		set_permit = TRUE;
+	if (user->privacy_synched == FALSE) {
+		_sync_privacy_lists(user);
+		user->privacy_synched = TRUE;
 		return;
 	}
 
@@ -3285,7 +3285,7 @@
 novell_blist_node_menu(GaimBlistNode *node)
 {
 	GList *list = NULL;
-	GaimBlistNodeAction *act;	
+	GaimBlistNodeAction *act;
 
 	if(GAIM_BLIST_NODE_IS_BUDDY(node)) {
 		act = gaim_blist_node_action_new(_("Initiate _Chat"),
@@ -3296,6 +3296,23 @@
 	return list;
 }
 
+static void
+novell_keepalive(GaimConnection *gc)
+{
+	NMUser *user;
+	NMERR_T rc = NM_OK;
+
+	if (gc == NULL)
+		return;
+
+	user = gc->proto_data;
+	if (user == NULL)
+		return;
+
+	rc = nm_send_keepalive(user, NULL, NULL);
+	_check_for_disconnect(user, rc);
+}
+
 static GaimPluginProtocolInfo prpl_info = {
 	GAIM_PRPL_API_VERSION,
 	0,
@@ -3328,12 +3345,12 @@
 	novell_set_permit_deny,
 	NULL,						/* warn */
 	NULL,						/* join_chat */
-	NULL,						/* reject_chat ?? */
+	NULL,						/* reject_chat */
 	novell_chat_invite,			/* chat_invite */
 	novell_chat_leave,
 	NULL,						/* chat_whisper */
 	novell_chat_send,
-	NULL,						/* keepalive */
+	novell_keepalive,
 	NULL,						/* register_user */
 	NULL,						/* get_cb_info */
 	NULL,						/* get_cb_away_msg */