changeset 11943:0110fc7c6a8a

[gaim-migrate @ 14234] Bringing things up to date with the last Meanwhile release, 0.5.0 and the last gaim-meanwhile plugin release, 1.2.5 (which should be the last plugin release against oldstatus, if all goes well with HEAD and no major bugs crop up) It builds, so that's a start. The status bits that have been empty since the first import of the sametime stuff are still empty, but I'm going to try and fill those in tomorrow. I've decided to try and start using HEAD actively, to encourage me to get this freaking prpl fully functional. committer: Tailor Script <tailor@pidgin.im>
author Christopher O'Brien <siege@pidgin.im>
date Wed, 02 Nov 2005 03:39:03 +0000
parents a24cfe32961a
children 7470ae0d0e63
files src/protocols/sametime/meanwhile/Makefile.am src/protocols/sametime/meanwhile/channel.c src/protocols/sametime/meanwhile/cipher.c src/protocols/sametime/meanwhile/common.c src/protocols/sametime/meanwhile/error.c src/protocols/sametime/meanwhile/message.c src/protocols/sametime/meanwhile/mw_channel.h src/protocols/sametime/meanwhile/mw_cipher.h src/protocols/sametime/meanwhile/mw_common.h src/protocols/sametime/meanwhile/mw_debug.c src/protocols/sametime/meanwhile/mw_debug.h src/protocols/sametime/meanwhile/mw_error.h src/protocols/sametime/meanwhile/mw_message.h src/protocols/sametime/meanwhile/mw_service.h src/protocols/sametime/meanwhile/mw_session.h src/protocols/sametime/meanwhile/mw_srvc_aware.h src/protocols/sametime/meanwhile/mw_srvc_conf.h src/protocols/sametime/meanwhile/mw_srvc_ft.h src/protocols/sametime/meanwhile/mw_srvc_im.h src/protocols/sametime/meanwhile/mw_srvc_place.h src/protocols/sametime/meanwhile/mw_srvc_resolve.h src/protocols/sametime/meanwhile/mw_srvc_store.h src/protocols/sametime/meanwhile/session.c src/protocols/sametime/meanwhile/srvc_aware.c src/protocols/sametime/meanwhile/srvc_conf.c src/protocols/sametime/meanwhile/srvc_ft.c src/protocols/sametime/meanwhile/srvc_im.c src/protocols/sametime/meanwhile/srvc_place.c src/protocols/sametime/meanwhile/srvc_resolve.c src/protocols/sametime/meanwhile/srvc_store.c src/protocols/sametime/meanwhile/st_list.c src/protocols/sametime/sametime.c src/protocols/sametime/sametime.h
diffstat 33 files changed, 3592 insertions(+), 892 deletions(-) [+]
line wrap: on
line diff
--- a/src/protocols/sametime/meanwhile/Makefile.am	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/Makefile.am	Wed Nov 02 03:39:03 2005 +0000
@@ -11,6 +11,7 @@
 	mw_srvc_conf.h \
 	mw_srvc_ft.h \
 	mw_srvc_im.h \
+	mw_srvc_place.h \
 	mw_srvc_resolve.h \
 	mw_srvc_store.h \
 	mw_st_list.h \
@@ -31,6 +32,7 @@
 	srvc_conf.c \
 	srvc_ft.c \
 	srvc_im.c \
+	srvc_place.c \
 	srvc_store.c \
 	srvc_resolve.c \
 	st_list.c
--- a/src/protocols/sametime/meanwhile/channel.c	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/channel.c	Wed Nov 02 03:39:03 2005 +0000
@@ -59,6 +59,8 @@
 
   /** all those supported ciphers */
   GHashTable *supported;
+  guint16 offered_policy;  /**< @see enum mwEncryptPolicy */
+  guint16 policy;          /**< @see enum mwEncryptPolicy */
 
   /** cipher information determined at channel acceptance */
   struct mwCipherInstance *cipher;
@@ -98,13 +100,21 @@
 }
 
 
-static void state(struct mwChannel *chan, enum mwChannelState state) {
+static void state(struct mwChannel *chan, enum mwChannelState state,
+		  guint32 err_code) {
+
   g_return_if_fail(chan != NULL);
 
   if(chan->state == state) return;
 
   chan->state = state;
-  g_message("channel 0x%08x state: %s", chan->id, state_str(state));
+
+  if(err_code) {
+    g_message("channel 0x%08x state: %s (0x%08x)",
+	      chan->id, state_str(state), err_code);
+  } else {
+    g_message("channel 0x%08x state: %s", chan->id, state_str(state));
+  }
 }
 
 
@@ -166,7 +176,7 @@
 
   g_hash_table_insert(cs->map, GUINT_TO_POINTER(id), chan);
 
-  state(chan, mwChannel_WAIT);
+  state(chan, mwChannel_WAIT, 0);
 
   return chan;
 }
@@ -186,7 +196,7 @@
   } while(g_hash_table_lookup(cs->map, GUINT_TO_POINTER(id)));
   
   chan = mwChannel_newIncoming(cs, id);
-  state(chan, mwChannel_INIT);
+  state(chan, mwChannel_INIT, 0);
 
   return chan;
 }
@@ -270,6 +280,12 @@
 }
 
 
+guint16 mwChannel_getEncryptPolicy(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, 0x00);
+  return chan->policy;
+}
+
+
 guint32 mwChannel_getOptions(struct mwChannel *chan) {
   g_return_val_if_fail(chan != NULL, 0x00);
   return chan->options;
@@ -345,27 +361,37 @@
   msg->options = chan->options;
   mwOpaque_clone(&msg->addtl, &chan->addtl_create);
 
-  for(list = l = mwChannel_getSupportedCipherInstances(chan); l; l = l->next) {
-    struct mwEncryptItem *ei = mwCipherInstance_newItem(l->data);
-    msg->encrypt.items = g_list_append(msg->encrypt.items, ei);
-  }
+  list = mwChannel_getSupportedCipherInstances(chan);
   if(list) {
-    msg->encrypt.mode = 0x1000;
-    msg->encrypt.extra = 0x1000;
+    /* offer what we have */
+    for(l = list; l; l = l->next) {
+      struct mwEncryptItem *ei = mwCipherInstance_offer(l->data);
+      msg->encrypt.items = g_list_append(msg->encrypt.items, ei);
+    }
+
+    /* we're easy to get along with */
+    chan->offered_policy = mwEncrypt_WHATEVER;
+    g_list_free(list);
+
+  } else {
+    /* we apparently don't support anything */
+    chan->offered_policy = mwEncrypt_NONE;
   }
-  g_list_free(list);
+
+  msg->encrypt.mode = chan->offered_policy;
+  msg->encrypt.extra = chan->offered_policy;
   
   ret = mwSession_send(chan->session, MW_MESSAGE(msg));
   mwMessage_free(MW_MESSAGE(msg));
 
-  state(chan, (ret)? mwChannel_ERROR: mwChannel_WAIT);
+  state(chan, (ret)? mwChannel_ERROR: mwChannel_WAIT, ret);
 
   return ret;
 }
 
 
 static void channel_open(struct mwChannel *chan) {
-  state(chan, mwChannel_OPEN);
+  state(chan, mwChannel_OPEN, 0);
   timestamp_stat(chan, mwChannelStat_OPENED_AT);
   flush_channel(chan);
 }
@@ -374,6 +400,8 @@
 int mwChannel_accept(struct mwChannel *chan) {
   struct mwSession *session;
   struct mwMsgChannelAccept *msg;
+  struct mwCipherInstance *ci;
+
   int ret;
 
   g_return_val_if_fail(chan != NULL, -1);
@@ -392,33 +420,64 @@
   msg->proto_ver = chan->proto_ver;
   mwOpaque_clone(&msg->addtl, &chan->addtl_accept);
 
-  /* if nobody selected a cipher, we'll just pick the first that comes
-     handy */
-  if(chan->supported) {
-    GList *l = mwChannel_getSupportedCipherInstances(chan);
-    if(l) {
-      mwChannel_selectCipherInstance(chan, l->data);
-      g_list_free(l);
-    } else {
+  ci = chan->cipher;
+
+  if(! ci) {
+    /* automatically select a cipher if one hasn't been already */
+
+    switch(chan->offered_policy) {
+    case mwEncrypt_NONE:
       mwChannel_selectCipherInstance(chan, NULL);
+      break;
+      
+    case mwEncrypt_RC2_40:
+      ci = get_supported(chan, mwCipher_RC2_40);
+      mwChannel_selectCipherInstance(chan, ci);
+      break;
+
+    case mwEncrypt_RC2_128:
+      ci = get_supported(chan, mwCipher_RC2_128);
+      mwChannel_selectCipherInstance(chan, ci);
+      break;
+      
+    case mwEncrypt_WHATEVER:
+    case mwEncrypt_ALL:
+    default:
+      {
+	GList *l, *ll;
+
+	l = mwChannel_getSupportedCipherInstances(chan);
+	if(l) {
+	  /* nobody selected a cipher, so we'll just pick the last in
+	     the list of available ones */
+	  for(ll = l; ll->next; ll = ll->next);
+	  ci = ll->data;
+	  g_list_free(l);
+	  
+	  mwChannel_selectCipherInstance(chan, ci);
+	  
+	} else {
+	  /* this may cause breakage, but there's really nothing else
+	     we can do. They want something we can't provide. If they
+	     don't like it, then they'll error the channel out */
+	  mwChannel_selectCipherInstance(chan, NULL);
+	}
+      }
     }
   }
 
-  if(chan->cipher) {
-    mwCipherInstance_accept(chan->cipher);
+  msg->encrypt.mode = chan->policy; /* set in selectCipherInstance */
+  msg->encrypt.extra = chan->offered_policy;
 
-    msg->encrypt.item = mwCipherInstance_newItem(chan->cipher);
-
-    /** @todo figure out encrypt modes */
-    msg->encrypt.mode = 0x1000;
-    msg->encrypt.extra = 0x1000;
+  if(chan->cipher) {
+    msg->encrypt.item = mwCipherInstance_accept(chan->cipher);
   }
 
   ret = mwSession_send(session, MW_MESSAGE(msg));
   mwMessage_free(MW_MESSAGE(msg));
 
   if(ret) {
-    state(chan, mwChannel_ERROR);
+    state(chan, mwChannel_ERROR, ret);
   } else {
     channel_open(chan);
   }
@@ -484,7 +543,7 @@
   /* may make this not a warning in the future */
   g_return_val_if_fail(chan != NULL, 0);
 
-  state(chan, reason? mwChannel_ERROR: mwChannel_DESTROY);
+  state(chan, reason? mwChannel_ERROR: mwChannel_DESTROY, reason);
 
   session = chan->session;
   g_return_val_if_fail(session != NULL, -1);
@@ -691,6 +750,9 @@
     return;
   }
 
+  chan->offered_policy = msg->encrypt.mode;
+  g_message("channel offered with encrypt policy 0x%04x", chan->policy);
+
   for(list = msg->encrypt.items; list; list = list->next) {
     struct mwEncryptItem *ei = list->data;
     struct mwCipher *cipher;
@@ -754,7 +816,11 @@
     return;
   }
 
+  chan->policy = msg->encrypt.mode;
+  g_message("channel accepted with encrypt policy 0x%04x", chan->policy);
+
   if(! msg->encrypt.mode || ! msg->encrypt.item) {
+    /* no mode or no item means no encryption */
     mwChannel_selectCipherInstance(chan, NULL);
 
   } else {
@@ -772,7 +838,7 @@
   }
 
   /* mark it as open for the service */
-  state(chan, mwChannel_OPEN);
+  state(chan, mwChannel_OPEN, 0);
 
   /* let the service know */
   mwService_recvAccept(srvc, chan, msg);
@@ -794,7 +860,7 @@
   g_return_if_fail(msg != NULL);
   g_return_if_fail(chan->id == msg->head.channel);
 
-  state(chan, msg->reason? mwChannel_ERROR: mwChannel_DESTROY);
+  state(chan, msg->reason? mwChannel_ERROR: mwChannel_DESTROY, msg->reason);
 
   srvc = mwChannel_getService(chan);
   if(srvc) mwService_recvDestroy(srvc, chan, msg);
@@ -865,9 +931,26 @@
 
     g_hash_table_steal(chan->supported, GUINT_TO_POINTER(cid));
 
+    switch(mwCipher_getType(c)) {
+    case mwCipher_RC2_40:
+      chan->policy = mwEncrypt_RC2_40;
+      break;
+
+    case mwCipher_RC2_128:
+      chan->policy = mwEncrypt_RC2_128;
+      break;
+
+    default:
+      /* unsure if this is bad */
+      chan->policy = mwEncrypt_WHATEVER;
+    }
+
     g_message("channel 0x%08x selected cipher %s",
 	      chan->id, NSTR(mwCipher_getName(c)));
+
   } else {
+
+    chan->policy = mwEncrypt_NONE;
     g_message("channel 0x%08x selected no cipher", chan->id);
   }
 
--- a/src/protocols/sametime/meanwhile/cipher.c	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/cipher.c	Wed Nov 02 03:39:03 2005 +0000
@@ -22,6 +22,9 @@
 #include <string.h>
 #include <time.h>
 
+#include <gmp.h>
+
+
 #include "mw_channel.h"
 #include "mw_cipher.h"
 #include "mw_debug.h"
@@ -65,7 +68,82 @@
 };
 
 
-void rand_key(char *key, gsize keylen) {
+/** prime number used in DH exchange */
+static unsigned char dh_prime[] = {
+  0xCF, 0x84, 0xAF, 0xCE, 0x86, 0xDD, 0xFA, 0x52,
+  0x7F, 0x13, 0x6D, 0x10, 0x35, 0x75, 0x28, 0xEE,
+  0xFB, 0xA0, 0xAF, 0xEF, 0x80, 0x8F, 0x29, 0x17,
+  0x4E, 0x3B, 0x6A, 0x9E, 0x97, 0x00, 0x01, 0x71,
+  0x7C, 0x8F, 0x10, 0x6C, 0x41, 0xC1, 0x61, 0xA6,
+  0xCE, 0x91, 0x05, 0x7B, 0x34, 0xDA, 0x62, 0xCB,
+  0xB8, 0x7B, 0xFD, 0xC1, 0xB3, 0x5C, 0x1B, 0x91,
+  0x0F, 0xEA, 0x72, 0x24, 0x9D, 0x56, 0x6B, 0x9F
+};
+
+
+/** base used in DH exchange */
+#define DH_BASE  3
+
+
+void mwInitDHPrime(mpz_t z) {
+  mpz_init(z);
+  mpz_import(z, 64, 1, 1, 0, 0, dh_prime);
+}
+
+
+void mwInitDHBase(mpz_t z) {
+  mpz_init_set_ui(z, DH_BASE);
+}
+
+
+void mwDHRandKeypair(mpz_t private, mpz_t public) {
+  gmp_randstate_t rstate;
+  mpz_t prime, base;
+ 
+  mwInitDHPrime(prime);
+  mwInitDHBase(base);
+
+  gmp_randinit_default(rstate);
+  mpz_urandomb(private, rstate, 512);
+  mpz_powm(public, base, private, prime);
+
+  mpz_clear(prime);
+  mpz_clear(base);
+  gmp_randclear(rstate);
+}
+
+
+void mwDHCalculateShared(mpz_t shared, mpz_t remote, mpz_t private) {
+  mpz_t prime;
+ 
+  mwInitDHPrime(prime);
+  mpz_powm(shared, remote, private, prime);
+  mpz_clear(prime);
+}
+
+
+void mwDHImportKey(mpz_t key, struct mwOpaque *o) {
+  g_return_if_fail(o != NULL);
+  mpz_import(key, o->len, 1, 1, 1, 0, o->data);
+}
+
+
+void mwDHExportKey(mpz_t key, struct mwOpaque *o) {
+  gsize needed;
+
+  g_return_if_fail(o != NULL);
+
+  needed = (mpz_sizeinbase(key,2) + 7) / 8;
+  o->len = 65;
+  o->data = g_malloc0(o->len);
+
+  mpz_export(o->data+(o->len-needed), NULL, 1, 1, 1, 0, key);
+}
+
+
+void mwKeyRandom(char *key, gsize keylen) {
+  g_return_if_fail(key != NULL);
+
   srand(clock());
   while(keylen--) key[keylen] = rand() & 0xff;
 }
@@ -76,13 +154,6 @@
     0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef
   };
   memcpy(iv, normal_iv, 8);
-
-  /*
-  *iv++ = 0x01; *iv++ = 0x23;
-  *iv++ = 0x45; *iv++ = 0x67;
-  *iv++ = 0x89; *iv++ = 0xab;
-  *iv++ = 0xcd; *iv   = 0xef;
-  */
 }
 
 
@@ -93,14 +164,15 @@
   char tmp[128];
   int i, j;
 
-  /*
-  g_message("expanding key from:");
-  pretty_print(key, keylen);
-  */
+  g_return_if_fail(keylen > 0);
+  g_return_if_fail(key != NULL);
 
   if(keylen > 128) keylen = 128;
+
+  /* fill the first chunk with what key bytes we have */
   memcpy(tmp, key, keylen);
 
+  /* build the remaining key from the given data */
   for(i = 0; keylen < 128; i++) {
     tmp[keylen] = PT[ (tmp[keylen - 1] + tmp[i]) & 0xff ];
     keylen++;
@@ -117,12 +189,14 @@
 
 /* normal RC2 encryption given a full 128-byte (as 64 ints) key */
 static void mwEncryptBlock(const int *ekey, char *out) {
-  int a = (out[7] << 8) | (out[6] & 0xff);
-  int b = (out[5] << 8) | (out[4] & 0xff);
-  int c = (out[3] << 8) | (out[2] & 0xff);
-  int d = (out[1] << 8) | (out[0] & 0xff);
+
+  int a, b, c, d;
+  int i, j;
 
-  int i, j;
+  a = (out[7] << 8) | (out[6] & 0xff);
+  b = (out[5] << 8) | (out[4] & 0xff);
+  c = (out[3] << 8) | (out[2] & 0xff);
+  d = (out[1] << 8) | (out[0] & 0xff);
 
   for(i = 0; i < 16; i++) {
     j = i * 4;
@@ -204,12 +278,14 @@
 
 
 static void mwDecryptBlock(const int *ekey, char *out) {
-  int a = (out[7] << 8) | (out[6] & 0xff);
-  int b = (out[5] << 8) | (out[4] & 0xff);
-  int c = (out[3] << 8) | (out[2] & 0xff);
-  int d = (out[1] << 8) | (out[0] & 0xff);
+
+  int a, b, c, d;
+  int i, j;
 
-  int i, j;
+  a = (out[7] << 8) | (out[6] & 0xff);
+  b = (out[5] << 8) | (out[4] & 0xff);
+  c = (out[3] << 8) | (out[2] & 0xff);
+  d = (out[1] << 8) | (out[0] & 0xff);
 
   for(i = 16; i--; ) {
     j = i * 4 + 3;
@@ -293,6 +369,7 @@
 }
 
 
+
 struct mwCipher_RC2_40 {
   struct mwCipher cipher;
   int session_key[64];
@@ -358,20 +435,16 @@
 }
 
 
-static struct mwCipherInstance *new_instance_RC2_40(struct mwCipher *cipher,
-						    struct mwChannel *chan) {
+static struct mwCipherInstance *
+new_instance_RC2_40(struct mwCipher *cipher,
+		    struct mwChannel *chan) {
+
   struct mwCipher_RC2_40 *cr;
   struct mwCipherInstance_RC2_40 *cir;
   struct mwCipherInstance *ci;
 
   cr = (struct mwCipher_RC2_40 *) cipher;
 
-  cir = g_new0(struct mwCipherInstance_RC2_40, 1);
-  ci = &cir->instance;
-
-  ci->cipher = cipher;
-  ci->channel = chan;
-
   /* a bit of lazy initialization here */
   if(! cr->ready) {
     struct mwLoginInfo *info = mwSession_getLoginInfo(cipher->session);
@@ -379,6 +452,12 @@
     cr->ready = TRUE;
   }
 
+  cir = g_new0(struct mwCipherInstance_RC2_40, 1);
+  ci = &cir->instance;
+
+  ci->cipher = cipher;
+  ci->channel = chan;
+
   mwIV_init(cir->incoming_iv);
   mwIV_init(cir->outgoing_iv);
 
@@ -387,24 +466,40 @@
 
 
 static struct mwEncryptItem *new_item_RC2_40(struct mwCipherInstance *ci) {
-  struct mwEncryptItem *e = g_new0(struct mwEncryptItem, 1);
+  struct mwEncryptItem *e;
+
+  e = g_new0(struct mwEncryptItem, 1);
   e->id = mwCipher_RC2_40;
   return e;
 }
 
 
-static void accept_RC2_40(struct mwCipherInstance *ci) {
-  struct mwCipherInstance_RC2_40 *cir;
-  struct mwLoginInfo *info = mwChannel_getUser(ci->channel);
-
-  cir = (struct mwCipherInstance_RC2_40 *) ci;
-  mwKeyExpand(cir->incoming_key, info->login_id, 5);
+static struct mwEncryptItem *
+offer_RC2_40(struct mwCipherInstance *ci) {
+  return new_item_RC2_40(ci);
 }
 
 
 static void accepted_RC2_40(struct mwCipherInstance *ci,
 			    struct mwEncryptItem *item) {
-  accept_RC2_40(ci);
+
+  struct mwCipherInstance_RC2_40 *cir;
+  struct mwLoginInfo *info;
+
+  cir = (struct mwCipherInstance_RC2_40 *) ci;
+  info = mwChannel_getUser(ci->channel);
+
+  if(info->login_id) {
+    mwKeyExpand(cir->incoming_key, info->login_id, 5);
+  }
+}
+
+
+static struct mwEncryptItem *
+accept_RC2_40(struct mwCipherInstance *ci) {
+
+  accepted_RC2_40(ci, NULL);
+  return new_item_RC2_40(ci);
 }
 
 
@@ -417,7 +512,8 @@
   c->get_name = get_name_RC2_40;
   c->get_desc = get_desc_RC2_40;
   c->new_instance = new_instance_RC2_40;
-  c->new_item = new_item_RC2_40;
+
+  c->offer = offer_RC2_40;
 
   c->accepted = accepted_RC2_40;
   c->accept = accept_RC2_40;
@@ -429,6 +525,202 @@
 }
 
 
+struct mwCipher_RC2_128 {
+  struct mwCipher cipher;
+  mpz_t private_key;
+  struct mwOpaque public_key;
+};
+
+
+struct mwCipherInstance_RC2_128 {
+  struct mwCipherInstance instance;
+  int shared[64];      /* shared secret determined via DH exchange */
+  char outgoing_iv[8];
+  char incoming_iv[8];
+};
+
+
+static const char *get_name_RC2_128() {
+  return "RC2/128 Cipher";
+}
+
+
+static const char *get_desc_RC2_128() {
+  return "RC2, DH shared secret key";
+}
+
+
+static struct mwCipherInstance *
+new_instance_RC2_128(struct mwCipher *cipher,
+		     struct mwChannel *chan) {
+
+  struct mwCipher_RC2_128 *cr;
+  struct mwCipherInstance_RC2_128 *cir;
+  struct mwCipherInstance *ci;
+
+  cr = (struct mwCipher_RC2_128 *) cipher;
+
+  cir = g_new0(struct mwCipherInstance_RC2_128, 1);
+  ci = &cir->instance;
+  
+  ci->cipher = cipher;
+  ci->channel = chan;
+
+  mwIV_init(cir->incoming_iv);
+  mwIV_init(cir->outgoing_iv);
+
+  return ci;
+}
+
+
+static void offered_RC2_128(struct mwCipherInstance *ci,
+			    struct mwEncryptItem *item) {
+  
+  mpz_t remote_key;
+  mpz_t shared;
+  struct mwOpaque sho = { 0, 0 };
+
+  struct mwCipher *c;
+  struct mwCipher_RC2_128 *cr;
+  struct mwCipherInstance_RC2_128 *cir;
+
+  c = ci->cipher;
+  cr = (struct mwCipher_RC2_128 *) c;
+  cir = (struct mwCipherInstance_RC2_128 *) ci;
+
+  mpz_init(remote_key);
+  mpz_init(shared);
+
+  mwDHImportKey(remote_key, &item->info);
+  mwDHCalculateShared(shared, remote_key, cr->private_key);
+  mwDHExportKey(shared, &sho);
+
+  /* key expanded from the last 16 bytes of the DH shared secret. This
+     took me forever to figure out. 16 bytes is 128 bit. */
+  /* the sh_len-16 is important, because the key len could
+     hypothetically start with 8bits or more unset, meaning the
+     exported key might be less than 64 bytes in length */
+  mwKeyExpand(cir->shared, sho.data+(sho.len-16), 16);
+  
+  mpz_clear(remote_key);
+  mpz_clear(shared);
+  mwOpaque_clear(&sho);
+}
+
+
+static struct mwEncryptItem *
+offer_RC2_128(struct mwCipherInstance *ci) {
+
+  struct mwCipher *c;
+  struct mwCipher_RC2_128 *cr;
+  struct mwEncryptItem *ei;
+
+  c = ci->cipher;
+  cr = (struct mwCipher_RC2_128 *) c;
+
+  ei = g_new0(struct mwEncryptItem, 1);
+  ei->id = mwCipher_RC2_128;
+  mwOpaque_clone(&ei->info, &cr->public_key);
+
+  return ei;
+}			  
+
+
+static void accepted_RC2_128(struct mwCipherInstance *ci,
+			     struct mwEncryptItem *item) {
+
+  return offered_RC2_128(ci, item);
+}
+
+
+static struct mwEncryptItem *
+accept_RC2_128(struct mwCipherInstance *ci) {
+
+  return offer_RC2_128(ci);
+}
+
+
+static int encrypt_RC2_128(struct mwCipherInstance *ci,
+			   struct mwOpaque *data) {
+
+  struct mwCipherInstance_RC2_128 *cir;
+  struct mwOpaque o = { 0, 0 };
+
+  cir = (struct mwCipherInstance_RC2_128 *) ci;
+
+  mwEncryptExpanded(cir->shared, cir->outgoing_iv, data, &o);
+
+  mwOpaque_clear(data);
+  data->data = o.data;
+  data->len = o.len;
+
+  return 0;
+}
+
+
+static int decrypt_RC2_128(struct mwCipherInstance *ci,
+			   struct mwOpaque *data) {
+
+  struct mwCipherInstance_RC2_128 *cir;
+  struct mwOpaque o = { 0, 0 };
+
+  cir = (struct mwCipherInstance_RC2_128 *) ci;
+
+  mwDecryptExpanded(cir->shared, cir->incoming_iv, data, &o);
+
+  mwOpaque_clear(data);
+  data->data = o.data;
+  data->len = o.len;
+
+  return 0;
+}
+
+
+static void clear_RC2_128(struct mwCipher *c) {
+  struct mwCipher_RC2_128 *cr;
+  cr = (struct mwCipher_RC2_128 *) c;
+
+  mpz_clear(cr->private_key);
+  mwOpaque_clear(&cr->public_key);
+}
+
+
+struct mwCipher *mwCipher_new_RC2_128(struct mwSession *s) {
+  struct mwCipher_RC2_128 *cr;
+  struct mwCipher *c;
+
+  mpz_t pubkey;
+
+  cr = g_new0(struct mwCipher_RC2_128, 1);
+  c = &cr->cipher;
+
+  c->session = s;
+  c->type = mwCipher_RC2_128;
+  c->get_name = get_name_RC2_128;
+  c->get_desc = get_desc_RC2_128;
+  c->new_instance = new_instance_RC2_128;
+
+  c->offered = offered_RC2_128;
+  c->offer = offer_RC2_128;
+
+  c->accepted = accepted_RC2_128;
+  c->accept = accept_RC2_128;
+
+  c->encrypt = encrypt_RC2_128;
+  c->decrypt = decrypt_RC2_128;
+
+  c->clear = clear_RC2_128;
+  
+  mpz_init(cr->private_key);
+  mpz_init(pubkey);
+  mwDHRandKeypair(cr->private_key, pubkey);
+  mwDHExportKey(pubkey, &cr->public_key);
+  mpz_clear(pubkey);
+
+  return c;
+}
+
+
 struct mwSession *mwCipher_getSession(struct mwCipher *cipher) {
   g_return_val_if_fail(cipher != NULL, NULL);
   return cipher->session;
@@ -437,8 +729,8 @@
 
 guint16 mwCipher_getType(struct mwCipher *cipher) {
   /* oh man, this is a bad failover... who the hell decided to make
-     zero a real cipher id?? */
-  g_return_val_if_fail(cipher != NULL, 0x00);
+     zero a real cipher id? */
+  g_return_val_if_fail(cipher != NULL, 0xffff);
   return cipher->type;
 }
 
@@ -514,15 +806,16 @@
 }
 
 
-void mwCipherInstance_offer(struct mwCipherInstance *ci) {
+struct mwEncryptItem *
+mwCipherInstance_offer(struct mwCipherInstance *ci) {
   struct mwCipher *cipher;
 
-  g_return_if_fail(ci != NULL);
+  g_return_val_if_fail(ci != NULL, NULL);
 
   cipher = ci->cipher;
-  g_return_if_fail(cipher != NULL);
+  g_return_val_if_fail(cipher != NULL, NULL);
 
-  if(cipher->offer) cipher->offer(ci);
+  return cipher->offer(ci);
 }
 
 
@@ -539,15 +832,16 @@
 }
 
 
-void mwCipherInstance_accept(struct mwCipherInstance *ci) {
+struct mwEncryptItem *
+mwCipherInstance_accept(struct mwCipherInstance *ci) {
   struct mwCipher *cipher;
 
-  g_return_if_fail(ci != NULL);
+  g_return_val_if_fail(ci != NULL, NULL);
 
   cipher = ci->cipher;
-  g_return_if_fail(cipher != NULL);
+  g_return_val_if_fail(cipher != NULL, NULL);
 
-  if(cipher->accept) cipher->accept(ci);
+  return cipher->accept(ci);
 }
 
 
--- a/src/protocols/sametime/meanwhile/common.c	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/common.c	Wed Nov 02 03:39:03 2005 +0000
@@ -803,6 +803,12 @@
 }
 
 
+void mwEncryptItem_free(struct mwEncryptItem *ei) {
+  mwEncryptItem_clear(ei);
+  g_free(ei);
+}
+
+
 /* 8.4.2.1 Awareness ID Block */
 
 
@@ -929,19 +935,21 @@
     return "Lotus Java Client Applet";
 
   case mwLogin_BINARY:
-    return "Lotus Sametime";
+    return "Lotus Sametime Connect";
 
   case mwLogin_JAVA_APP:
     return "Lotus Java Client Application";
 
-  case mwLogin_NOTES_6_5:
-    return "Lotus Notes Client 6.5.2+";
+  case mwLogin_LINKS:
+    return "Lotus Sametime Links";
 
+  case mwLogin_NOTES_6_5:
   case mwLogin_NOTES_7_0:
-    return "Lotus Notes Client 7";
+    return "Lotus Notes Client";
 
   case mwLogin_ICT:
-    return "IBM Community Tools (ICT)";
+  case mwLogin_ICT_1_7_8_2:
+    return "IBM Community Tools";
 
   case mwLogin_NOTESBUDDY:
   case mwLogin_NOTESBUDDY_4_15:
@@ -963,18 +971,6 @@
   case mwLogin_MEANWHILE:
     return "Meanwhile";
 
-  case mwLogin_MW_PYTHON:
-    return "Meanwhile Python";
-
-  case mwLogin_MW_GAIM:
-    return "Meanwhile Gaim";
-
-  case mwLogin_MW_ADIUM:
-    return "Meanwhile Adium";
-
-  case mwLogin_MW_KOPETE:
-    return "Meanwhile Kopete";
-
   default:
     return NULL;
   }
--- a/src/protocols/sametime/meanwhile/error.c	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/error.c	Wed Nov 02 03:39:03 2005 +0000
@@ -74,6 +74,7 @@
     CASE(GUEST_IN_USE, "The guest name is currently being used");
     CASE(MULTI_SERVER_LOGIN, "Login to two different servers concurrently");
     CASE(MULTI_SERVER_LOGIN2, "Login to two different servers concurrently");
+    CASE(SERVER_BROKEN, "Server misconfiguration");
 
     /* 8.3.1.3 Client error codes */
     CASE(ERR_CLIENT_USER_GONE, "User is not online");
--- a/src/protocols/sametime/meanwhile/message.c	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/message.c	Wed Nov 02 03:39:03 2005 +0000
@@ -78,6 +78,30 @@
   guint32_put(b, msg->srvrcalc_addr);
   guint16_put(b, msg->login_type);
   guint32_put(b, msg->loclcalc_addr);
+  
+  if(msg->major >= 0x001e && msg->minor >= 0x001d) {
+    guint16_put(b, msg->unknown_a);
+    guint32_put(b, msg->unknown_b);
+    mwString_put(b, msg->local_host);
+  }
+}
+
+
+static void HANDSHAKE_get(struct mwGetBuffer *b, struct mwMsgHandshake *msg) {
+  if(mwGetBuffer_error(b)) return;
+
+  guint16_get(b, &msg->major);
+  guint16_get(b, &msg->minor);
+  guint32_get(b, &msg->head.channel);
+  guint32_get(b, &msg->srvrcalc_addr);
+  guint16_get(b, &msg->login_type);
+  guint32_get(b, &msg->loclcalc_addr);
+
+  if(msg->major >= 0x001e && msg->minor >= 0x001d) {
+    guint16_get(b, &msg->unknown_a);
+    guint32_get(b, &msg->unknown_b);
+    mwString_get(b, &msg->local_host);
+  }
 }
 
 
@@ -102,12 +126,26 @@
       of this message. eg: minor version 0x0018 doesn't send the
       following */
   if(msg->major >= 0x1e && msg->minor > 0x18) {
-    guint32_get(b, &msg->unknown);
+    guint32_get(b, &msg->magic);
     mwOpaque_get(b, &msg->data);
   }
 }
 
 
+static void HANDSHAKE_ACK_put(struct mwPutBuffer *b,
+			      struct mwMsgHandshakeAck *msg) {
+
+  guint16_put(b, msg->major);
+  guint16_put(b, msg->minor);
+  guint32_put(b, msg->srvrcalc_addr);
+
+  if(msg->major >= 0x1e && msg->minor > 0x18) {
+    guint32_put(b, msg->magic);
+    mwOpaque_put(b, &msg->data);
+  }
+}
+
+
 static void HANDSHAKE_ACK_clear(struct mwMsgHandshakeAck *msg) {
   mwOpaque_clear(&msg->data);
 }
@@ -120,14 +158,26 @@
   guint16_put(b, msg->login_type);
   mwString_put(b, msg->name);
 
-  /* ordering reversed from draft?? */
+  /* ordering reversed from houri draft?? */
   mwOpaque_put(b, &msg->auth_data);
   guint16_put(b, msg->auth_type);
+
+  guint16_put(b, 0x0000); /* unknown */
+}
+
+
+static void LOGIN_get(struct mwGetBuffer *b, struct mwMsgLogin *msg) {
+  if(mwGetBuffer_error(b)) return;
+
+  guint16_get(b, &msg->login_type);
+  mwString_get(b, &msg->name);
+  mwOpaque_get(b, &msg->auth_data);
+  guint16_get(b, &msg->auth_type);
 }
 
 
 static void LOGIN_clear(struct mwMsgLogin *msg) {
-  g_free(msg->name);
+  g_free(msg->name);  msg->name = NULL;
   mwOpaque_clear(&msg->auth_data);
 }
 
@@ -164,6 +214,13 @@
 }
 
 
+static void LOGIN_CONTINUE_get(struct mwGetBuffer *b,
+			       struct mwMsgLoginContinue *msg) {
+
+  ; /* nothing but a message header */
+}
+
+
 static void LOGIN_CONTINUE_clear(struct mwMsgLoginContinue *msg) {
   ; /* this is a very simple message */
 }
@@ -181,6 +238,13 @@
 }
 
 
+static void LOGIN_REDIRECT_put(struct mwPutBuffer *b,
+			       struct mwMsgLoginRedirect *msg) {
+  mwString_put(b, msg->host);
+  mwString_put(b, msg->server_id);
+}
+
+
 static void LOGIN_REDIRECT_clear(struct mwMsgLoginRedirect *msg) {
   g_free(msg->host);
   msg->host = NULL;
@@ -194,8 +258,6 @@
 
 
 static void enc_offer_put(struct mwPutBuffer *b, struct mwEncryptOffer *enc) {
-  char tail = 0x07;
-
   guint16_put(b, enc->mode);
 
   if(enc->items) {
@@ -210,7 +272,6 @@
     count = g_list_length(enc->items);
     p = mwPutBuffer_new();
 
-  
     guint32_put(p, count);
     for(list = enc->items; list; list = list->next) {
       mwEncryptItem_put(p, list->data);
@@ -223,11 +284,6 @@
     mwOpaque_put(b, &o);
     mwOpaque_clear(&o);
   }
-
-  guint32_put(b, 0x00);
-  guint32_put(b, 0x00);
-  gboolean_put(b, FALSE);
-  mwPutBuffer_write(b, &tail, 1);
 }
 
 
@@ -248,6 +304,10 @@
     mwLoginInfo_put(b, &msg->creator);
 
   enc_offer_put(b, &msg->encrypt);
+
+  guint32_put(b, 0x00);
+  guint32_put(b, 0x00);
+  guint16_put(b, 0x07);
 }
 
 
@@ -320,8 +380,6 @@
 static void enc_accept_put(struct mwPutBuffer *b,
 			   struct mwEncryptAccept *enc) {
 
-  char tail = 0x07;
-
   guint16_put(b, enc->mode);
 
   if(enc->item) {
@@ -338,12 +396,6 @@
     mwOpaque_put(b, &o);
     mwOpaque_clear(&o);
   }
-
-  guint32_put(b, 0x00);
-  guint32_put(b, 0x00);
-  gboolean_put(b, FALSE);
-
-  mwPutBuffer_write(b, &tail, 1);
 }
 
 
@@ -360,6 +412,10 @@
     mwLoginInfo_put(b, &msg->acceptor);
   
   enc_accept_put(b, &msg->encrypt);
+
+  guint32_put(b, 0x00);
+  guint32_put(b, 0x00);
+  guint16_put(b, 0x07);
 }
 
 
@@ -544,6 +600,79 @@
 }
 
 
+/* Announcement messages */
+
+
+static void ANNOUNCE_get(struct mwGetBuffer *b, struct mwMsgAnnounce *msg) {
+  struct mwOpaque o = { 0, 0 };
+  struct mwGetBuffer *gb;
+  guint32 count;
+
+  gboolean_get(b, &msg->sender_present);
+  if(msg->sender_present)
+    mwLoginInfo_get(b, &msg->sender);
+  guint16_get(b, &msg->unknown_a);
+  
+  mwOpaque_get(b, &o);
+  gb = mwGetBuffer_wrap(&o);
+
+  gboolean_get(gb, &msg->may_reply);
+  mwString_get(gb, &msg->text);
+
+  mwGetBuffer_free(gb);
+  mwOpaque_clear(&o);
+
+  guint32_get(b, &count);
+  while(count--) {
+    char *r = NULL;
+    mwString_get(b, &r);
+    msg->recipients = g_list_prepend(msg->recipients, r);
+  }
+}
+
+
+static void ANNOUNCE_put(struct mwPutBuffer *b, struct mwMsgAnnounce *msg) {
+  struct mwOpaque o = { 0, 0 };
+  struct mwPutBuffer *pb;
+  GList *l;
+  
+  gboolean_put(b, msg->sender_present);
+  if(msg->sender_present)
+    mwLoginInfo_put(b, &msg->sender);
+  guint16_put(b, msg->unknown_a);
+
+  pb = mwPutBuffer_new();
+  
+  gboolean_put(pb, msg->may_reply);
+  mwString_put(pb, msg->text);
+
+  mwPutBuffer_finalize(&o, pb);
+  mwOpaque_put(b, &o);
+  mwOpaque_clear(&o);
+
+  guint32_put(b, g_list_length(msg->recipients));
+  for(l = msg->recipients; l; l = l->next) {
+    mwString_put(b, l->data);
+  }
+}
+
+
+static void ANNOUNCE_clear(struct mwMsgAnnounce *msg) {
+  mwLoginInfo_clear(&msg->sender);
+
+  g_free(msg->text);
+  msg->text = NULL;
+  
+  while(msg->recipients) {
+    g_free(msg->recipients->data);
+    msg->recipients = g_list_delete_link(msg->recipients, msg->recipients);
+  }
+}
+
+
+/* general functions */
+
+
 #define CASE(v, t) \
 case mwMessage_ ## v: \
   msg = (struct mwMessage *) g_new0(struct t, 1); \
@@ -569,6 +698,7 @@
     CASE(SET_PRIVACY_LIST, mwMsgSetPrivacyList);
     CASE(SENSE_SERVICE, mwMsgSenseService);
     CASE(ADMIN, mwMsgAdmin);
+    CASE(ANNOUNCE, mwMsgAnnounce);
     
   default:
     g_warning("unknown message type 0x%02x\n", type);
@@ -611,8 +741,11 @@
 
   /* load the rest of the message depending on the header type */
   switch(head.type) {
+    CASE(HANDSHAKE, mwMsgHandshake);
     CASE(HANDSHAKE_ACK, mwMsgHandshakeAck);
+    CASE(LOGIN, mwMsgLogin);
     CASE(LOGIN_REDIRECT, mwMsgLoginRedirect);
+    CASE(LOGIN_CONTINUE, mwMsgLoginContinue);
     CASE(LOGIN_ACK, mwMsgLoginAck);
     CASE(CHANNEL_CREATE, mwMsgChannelCreate);
     CASE(CHANNEL_DESTROY, mwMsgChannelDestroy);
@@ -622,6 +755,7 @@
     CASE(SET_PRIVACY_LIST, mwMsgSetPrivacyList);
     CASE(SENSE_SERVICE, mwMsgSenseService);
     CASE(ADMIN, mwMsgAdmin);
+    CASE(ANNOUNCE, mwMsgAnnounce);
 
   default:
     g_warning("unknown message type 0x%02x, no parse handler", head.type);
@@ -656,7 +790,9 @@
 
   switch(msg->type) {
     CASE(HANDSHAKE, mwMsgHandshake);
+    CASE(HANDSHAKE_ACK, mwMsgHandshakeAck);
     CASE(LOGIN, mwMsgLogin);
+    CASE(LOGIN_REDIRECT, mwMsgLoginRedirect);
     CASE(LOGIN_CONTINUE, mwMsgLoginContinue);
     CASE(CHANNEL_CREATE, mwMsgChannelCreate);
     CASE(CHANNEL_DESTROY, mwMsgChannelDestroy);
@@ -665,6 +801,7 @@
     CASE(SET_USER_STATUS, mwMsgSetUserStatus);
     CASE(SET_PRIVACY_LIST, mwMsgSetPrivacyList);
     CASE(SENSE_SERVICE, mwMsgSenseService);
+    CASE(ANNOUNCE, mwMsgAnnounce);
     
   default:
     ; /* hrm. */
@@ -701,6 +838,7 @@
     CASE(SET_PRIVACY_LIST, mwMsgSetPrivacyList);
     CASE(SENSE_SERVICE, mwMsgSenseService);
     CASE(ADMIN, mwMsgAdmin);
+    CASE(ANNOUNCE, mwMsgAnnounce);
     
   default:
     ; /* hrm. */
--- a/src/protocols/sametime/meanwhile/mw_channel.h	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/mw_channel.h	Wed Nov 02 03:39:03 2005 +0000
@@ -26,7 +26,7 @@
 #include "mw_common.h"
 
 
-/** @file channel.h
+/** @file mw_channel.h
     
 Life-cycle of an outgoing channel:
 
@@ -150,6 +150,20 @@
 };
 
 
+/** @enum mwEncryptPolicy
+
+    Policy for a channel, dictating what sort of encryption should be
+    used, if any, and when.
+*/
+enum mwEncryptPolicy {
+  mwEncrypt_NONE      = 0x0000, /**< encrypt none */
+  mwEncrypt_WHATEVER  = 0x0001, /**< encrypt whatever you want */
+  mwEncrypt_ALL       = 0x0002, /**< encrypt all, any cipher */
+  mwEncrypt_RC2_40    = 0x1000, /**< encrypt all, RC2/40 cipher */
+  mwEncrypt_RC2_128   = 0x2000, /**< encrypt all, RC2/128 cipher */
+};
+
+
 /** Allocate and initialize a channel set for a session */
 struct mwChannelSet *mwChannelSet_new(struct mwSession *);
 
@@ -225,6 +239,17 @@
 void mwChannel_setProtoVer(struct mwChannel *chan, guint32 proto_ver);
 
 
+/** Channel encryption policy.
+
+    Cannot currently be set, used internally to automatically
+    negotiate ciphers. Future revisions may allow this to be specified
+    in a new channel to dictate channel encryption.
+
+    @see enum mwEncryptPolicy
+*/
+guint16 mwChannel_getEncryptPolicy(struct mwChannel *chan);
+
+
 guint32 mwChannel_getOptions(struct mwChannel *chan);
 
 
--- a/src/protocols/sametime/meanwhile/mw_cipher.h	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/mw_cipher.h	Wed Nov 02 03:39:03 2005 +0000
@@ -31,8 +31,8 @@
 struct mwSession;
 
 
-
-/** Common cipher types */
+/** @enum mwCipherType
+    Common cipher types */
 enum mwCipherType {
   mwCipher_RC2_40   = 0x0000,
   mwCipher_RC2_128  = 0x0001,
@@ -50,7 +50,10 @@
 
 
 /** Generate a descriptor for use in a channel create message to
-    indicate the availability of this cipher */
+    indicate the availability of this cipher
+
+    @todo remove for 1.0
+*/
 typedef struct mwEncryptItem *(*mwCipherDescriptor)
      (struct mwCipherInstance *instance);
 
@@ -82,13 +85,15 @@
       @see mwCipher_newInstance */
   mwCipherInstantiator new_instance;
 
-  /** @see mwCipher_newItem */
+  /** @see mwCipher_newItem
+      @todo remove for 1.0
+   */
   mwCipherDescriptor new_item;
 
   void (*offered)(struct mwCipherInstance *ci, struct mwEncryptItem *item);
-  void (*offer)(struct mwCipherInstance *ci);
+  struct mwEncryptItem *(*offer)(struct mwCipherInstance *ci);
   void (*accepted)(struct mwCipherInstance *ci, struct mwEncryptItem *item);
-  void (*accept)(struct mwCipherInstance *ci);
+  struct mwEncryptItem *(*accept)(struct mwCipherInstance *ci);
 
   mwCipherProcessor encrypt; /**< @see mwCipherInstance_encrypt */
   mwCipherProcessor decrypt; /**< @see mwCipherInstance_decrypt */
@@ -121,10 +126,7 @@
 struct mwCipher *mwCipher_new_RC2_40(struct mwSession *s);
 
 
-#if 0
-/* @todo write this */
-struct mwCipher *mwCipher_new_DH_RC2_128(struct mwSession *s);
-#endif
+struct mwCipher *mwCipher_new_RC2_128(struct mwSession *s);
 
 
 struct mwSession *mwCipher_getSession(struct mwCipher *cipher);
@@ -151,6 +153,10 @@
 struct mwCipher *mwCipherInstance_getCipher(struct mwCipherInstance *ci);
 
 
+/**
+   Deprecated in favor of the methods mwCipherInstance_offer and
+   mwCipherInstance_accept
+*/
 struct mwEncryptItem *mwCipherInstance_newItem(struct mwCipherInstance *ci);
 
 
@@ -160,7 +166,8 @@
 
 
 /** Offer a cipher */
-void mwCipherInstance_offer(struct mwCipherInstance *ci);
+struct mwEncryptItem *
+mwCipherInstance_offer(struct mwCipherInstance *ci);
 
 
 /** Indicates an offered cipher has been accepted */
@@ -169,7 +176,8 @@
 
 
 /** Accept a cipher offered to our channel */
-void mwCipherInstance_accept(struct mwCipherInstance *ci);
+struct mwEncryptItem *
+mwCipherInstance_accept(struct mwCipherInstance *ci);
 
 
 /** encrypt data */
@@ -189,16 +197,8 @@
 /**
   @section General Cipher Functions
 
-  This set of functions is a broken sort of RC2 implementation. But it
-  works with sametime, so we're all happy, right? Primary change to
-  functionality appears in the mwKeyExpand function. Hypothetically,
-  using a key expanded here (but breaking it into a 128-char array
-  rather than 64 ints), one could pass it at that length to openssl
-  and no further key expansion would occur.
-
-  I'm not certain if replacing this with a wrapper for calls to some
-  other crypto library is a good idea or not. Proven software versus
-  added dependencies...
+  These functions are reused where encryption is necessary outside of
+  a channel (eg. session authentication)
 */
 /* @{ */
 
@@ -207,10 +207,10 @@
     @param keylen  count of bytes to write into key
     @param key     buffer to write keys into
 */
-void rand_key(char *key, gsize keylen);
+void mwKeyRandom(char *key, gsize keylen);
 
 
-/** Setup an Initialization Vector */
+/** Setup an Initialization Vector. IV must be at least 8 bytes */
 void mwIV_init(char *iv);
 
 
@@ -244,6 +244,47 @@
 /* @} */
 
 
+/**
+  @section Diffie-Hellman Functions
+
+  These functions are reused where DH Key negotiation is necessary
+  outside of a channel (eg. session authentication). You'll need to
+  include <gmp.h> in order to use these functions.
+*/
+/* @{ */
+#ifdef __GMP_H__
+
+
+/** initialize and set a big integer to the Sametime Prime value */
+void mwInitDHPrime(mpz_t z);
+
+
+/** initialize and set a big integer to the Sametime Base value */
+void mwInitDHBase(mpz_t z);
+
+
+/** sets private to a randomly generated value, and calculates public
+    using the Sametime Prime and Base */
+void mwDHRandKeypair(mpz_t private, mpz_t public);
+
+
+/** sets the shared key value based on the remote and private keys,
+    using the Sametime Prime and Base */
+void mwDHCalculateShared(mpz_t shared, mpz_t remote, mpz_t private);
+
+
+/** Import a DH key from an opaque */
+void mwDHImportKey(mpz_t key, struct mwOpaque *o);
+
+
+/** Export a DH key into an opaque */
+void mwDHExportKey(mpz_t key, struct mwOpaque *o);
+
+
+#endif
+/* @} */
+
+
 #endif
 
 
--- a/src/protocols/sametime/meanwhile/mw_common.h	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/mw_common.h	Wed Nov 02 03:39:03 2005 +0000
@@ -25,7 +25,7 @@
 #include <glib.h>
 
 
-/** @file common.h
+/** @file mw_common.h
 
     Common data types and functions for handling those types.
 
@@ -87,19 +87,23 @@
 
     If you are using Meanwhile in your client code and would like to
     differentiate yourself, please email siege at preoccupied dot net
-    with all the relevant information you can think of. I intend to be
-    pretty liberal with 'em.
+    with all the relevant information you can think of and I'll add it
+    to the text mapping as well
+
+    @see mwLoginType_getName
 */
 enum mwLoginType {
   mwLogin_LIB           = 0x1000,  /**< official Lotus binary library */
   mwLogin_JAVA_WEB      = 0x1001,  /**< official Lotus Java applet */
   mwLogin_BINARY        = 0x1002,  /**< official Lotus binary application */
   mwLogin_JAVA_APP      = 0x1003,  /**< official Lotus Java application */
+  mwLogin_LINKS         = 0x100a,  /**< official Sametime Links toolkit */
 
   /* now we're getting crazy */
   mwLogin_NOTES_6_5        = 0x1200,
   mwLogin_NOTES_7_0        = 0x1210,
   mwLogin_ICT              = 0x1300,
+  mwLogin_ICT_1_7_8_2      = 0x1302,
   mwLogin_NOTESBUDDY       = 0x1400,  /**< 0xff00 mask? */
   mwLogin_NOTESBUDDY_4_15  = 0x1405,
   mwLogin_SANITY           = 0x1600,
@@ -108,13 +112,6 @@
   mwLogin_TRILLIAN         = 0x16aa,  /**< http://sf.net/st-plugin/ */
   mwLogin_TRILLIAN_IBM     = 0x16bb,
   mwLogin_MEANWHILE        = 0x1700,  /**< Meanwhile library */
-
-  /* these aren't ready for use yet, DO NOT USE WHILE THIS COMMENT
-     EXISTS HERE, it will only cause you trouble */
-  mwLogin_MW_PYTHON     = 0x1701,  /**< Meanwhile Python */
-  mwLogin_MW_GAIM       = 0x1702,  /**< gaim-meanwhile */
-  mwLogin_MW_ADIUM      = 0x1703,  /**< adium-meanwhile */
-  mwLogin_MW_KOPETE     = 0x1704,  /**< kopete-meanwhile */
 };
 
 
@@ -429,6 +426,8 @@
 
 void mwEncryptItem_clear(struct mwEncryptItem *item);
 
+void mwEncryptItem_free(struct mwEncryptItem *item);
+
 
 /*@}*/
 
--- a/src/protocols/sametime/meanwhile/mw_debug.c	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/mw_debug.c	Wed Nov 02 03:39:03 2005 +0000
@@ -18,112 +18,171 @@
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
 
+
 #include <glib/gstring.h>
 
 #include "mw_debug.h"
 
 
-#define FRM               "%02x"
-#define FRMT              "%02x%02x "
+
+#define FRMT1            "%02x"
+#define FRMT2            FRMT1 FRMT1 " "
+#define FRMT4            FRMT2 FRMT2
+#define FRMT8            FRMT4 FRMT4
+#define FRMT16           FRMT8 FRMT8
+
 #define BUF(n)            ((unsigned char) buf[n])
 #define ADVANCE(b, n, c)  {b += c; n -= c;}
 
 
-#ifdef DEBUG
+
 /** writes hex pairs of buf to str */
-static void t_pretty_print(GString *str, const char *buf, gsize len) {
+static void pretty_print(GString *str, const char *buf, gsize len) {
   while(len) {
     if(len >= 16) {
-      g_string_append_printf(str,
-			     FRMT FRMT FRMT FRMT FRMT FRMT FRMT FRMT "\n",
+      /* write a complete line */
+      g_string_append_printf(str, FRMT16,
 			     BUF(0),  BUF(1),  BUF(2),  BUF(3),
 			     BUF(4),  BUF(5),  BUF(6),  BUF(7),
 			     BUF(8),  BUF(9),  BUF(10), BUF(11),
 			     BUF(12), BUF(13), BUF(14), BUF(15));
       ADVANCE(buf, len, 16);
 
-    } else if(len == 2) {
-      g_string_append_printf(str, FRMT "\n", BUF(0), BUF(1));
-      ADVANCE(buf, len, 2);
+    } else {
+      /* write an incomplete line */
+      if(len >= 8) {
+	g_string_append_printf(str, FRMT8,
+			       BUF(0), BUF(1), BUF(2), BUF(3),
+			       BUF(4), BUF(5), BUF(6), BUF(7));
+	ADVANCE(buf, len, 8);
+      }
       
-    } else if(len > 1) {
-      g_string_append_printf(str, FRMT, BUF(0), BUF(1));
-      ADVANCE(buf, len, 2);
-
-    } else {
-      g_string_append_printf(str, FRM "\n", BUF(0));
-      ADVANCE(buf, len, 1);
-    }
-  }
-}
-#endif
+      if(len >= 4) {
+	g_string_append_printf(str, FRMT4,
+			       BUF(0), BUF(1), BUF(2), BUF(3));
+	ADVANCE(buf, len, 4);
+      }
 
-
-void pretty_print(const char *buf, gsize len) {
-#ifdef DEBUG
-  GString *str;
-
-  if(! len) return;
+      if(len >= 2) {
+	g_string_append_printf(str, FRMT2, BUF(0), BUF(1));
+	ADVANCE(buf, len, 2);
+      }
 
-  g_return_if_fail(buf != NULL);
-
-  str = g_string_new(NULL);
-  t_pretty_print(str, buf, len);
-  g_debug(str->str);
-  g_string_free(str, TRUE);
-#endif
-  ;
+      if(len >= 1) {
+	g_string_append_printf(str, FRMT1, BUF(0));
+	ADVANCE(buf, len, 1);
+      }
+    }
+    
+    /* append \n to each line but the last */
+    if(len) g_string_append(str, "\n");
+  }
 }
 
 
-void pretty_print_opaque(struct mwOpaque *o) {
-  if(! o) return;
-  pretty_print(o->data, o->len);
+
+void mw_debug_datav(const char *buf, gsize len,
+		    const char *msg, va_list args) {
+  GString *str;
+
+  g_return_if_fail(buf != NULL || len == 0);
+
+  str = g_string_new(NULL);
+
+  if(msg) {
+    char *txt = g_strdup_vprintf(msg, args);
+    g_string_append_printf(str, "%s\n", txt);
+    g_free(txt);
+  }
+  pretty_print(str, buf, len);
+
+  g_debug(str->str);
+  g_string_free(str, TRUE);
 }
 
 
-void mw_debug_mailme_v(struct mwOpaque *block,
-		       const char *info, va_list args) {
-  /*
-    MW_MAILME_MESSAGE
-    begin here
-    info % args
-    pretty_print
-    end here
-  */
+
+void mw_debug_data(const char *buf, gsize len,
+		   const char *msg, ...) {
+  va_list args;
+  
+  g_return_if_fail(buf != NULL || len == 0);
+
+  va_start(args, msg);
+  mw_debug_datav(buf, len, msg, args);
+  va_end(args);
+}
+
+
 
-#ifdef DEBUG
+void mw_debug_opaquev(struct mwOpaque *o, const char *txt, va_list args) {
+  g_return_if_fail(o != NULL);
+  mw_debug_datav(o->data, o->len, txt, args);
+}
+
+
+
+void mw_debug_opaque(struct mwOpaque *o, const char *txt, ...) {
+  va_list args;
+
+  g_return_if_fail(o != NULL);
+
+  va_start(args, txt);
+  mw_debug_opaquev(o, txt, args);
+  va_end(args);
+}
+
+
+void mw_mailme_datav(const char *buf, gsize len,
+		     const char *info, va_list args) {
+
+#if MW_MAILME
   GString *str;
   char *txt;
 
   str = g_string_new(MW_MAILME_MESSAGE "\n"
 		     "  Please send mail to: " MW_MAILME_ADDRESS "\n"
 		     MW_MAILME_CUT_START "\n");
+  str = g_string_new(NULL);
 
   txt = g_strdup_vprintf(info, args);
-  g_string_append(str, txt);
+  g_string_append_printf(str, "%s\n", txt);
   g_free(txt);
 
-  g_string_append(str, "\n");
-
-  if(block) {
-    t_pretty_print(str, block->data, block->len);
-  }
+  if(buf && len) pretty_print(str, buf, len);
 
   g_string_append(str, MW_MAILME_CUT_STOP);
 
   g_debug(str->str);
   g_string_free(str, TRUE);
+
+#else
+  mw_debug_datav(buf, len, info, args);
+
 #endif
-  ;
 }
 
 
-void mw_debug_mailme(struct mwOpaque *block,
-		     const char *info, ...) {
+
+void mw_mailme_data(const char *buf, gsize len,
+		    const char *info, ...) {
   va_list args;
   va_start(args, info);
-  mw_debug_mailme_v(block, info, args);
+  mw_mailme_datav(buf, len, info, args);
   va_end(args);
 }
 
+
+
+void mw_mailme_opaquev(struct mwOpaque *o, const char *info, va_list args) {
+  mw_mailme_datav(o->data, o->len, info, args);
+}
+
+
+
+void mw_mailme_opaque(struct mwOpaque *o, const char *info, ...) {
+  va_list args;
+  va_start(args, info);
+  mw_mailme_opaquev(o, info, args);
+  va_end(args);
+}
--- a/src/protocols/sametime/meanwhile/mw_debug.h	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/mw_debug.h	Wed Nov 02 03:39:03 2005 +0000
@@ -45,14 +45,6 @@
 #endif
 
 
-/** logs buf as hex pairs. Requires DEBUG enabled during build */
-void pretty_print(const char *buf, gsize len);
-
-
-/** logs block as hex pairs. Requires DEBUG enabled during build */
-void pretty_print_opaque(struct mwOpaque *block);
-
-
 #ifndef MW_MAILME_ADDRESS
 /** email address used in mw_debug_mailme. */
 #define MW_MAILME_ADDRESS  "meanwhile-devel@lists.sourceforge.net"
@@ -82,16 +74,40 @@
 #endif
 
 
+void mw_debug_datav(const char *buf, gsize len,
+		    const char *info, va_list args);
+
+
+void mw_debug_data(const char *buf, gsize len,
+		   const char *info, ...);
+
+
+void mw_debug_opaquev(struct mwOpaque *o, const char *info, va_list args);
+
+
+void mw_debug_opaque(struct mwOpaque *o, const char *info, ...);
+
+
+void mw_mailme_datav(const char *buf, gsize len,
+		     const char *info, va_list args);
+
+void mw_mailme_data(const char *buf, gsize len,
+		    const char *info, ...);
+
+
 /** Outputs a hex dump of a mwOpaque with debugging info and a
-    pre-defined message. Identical to mw_debug_mailme, but taking a
+    pre-defined message. Identical to mw_mailme_opaque, but taking a
     va_list argument */
-void mw_debug_mailme_v(struct mwOpaque *block,
-		       const char *info, va_list args);
+void mw_mailme_opaquev(struct mwOpaque *o, const char *info, va_list args);
+
 
 
 /** Outputs a hex dump of a mwOpaque with debugging info and a
     pre-defined message.
 
+    if MW_MAILME is undefined or false, this function acts the same as
+    mw_mailme_opaque.
+
     @arg block  data to be printed in a hex block
     @arg info   a printf-style format string
 
@@ -105,7 +121,7 @@
     MW_MAILME_CUT_STOP
     @endcode
  */
-void mw_debug_mailme(struct mwOpaque *block, const char *info, ...);
+void mw_mailme_opaque(struct mwOpaque *o, const char *info, ...);
 
 
 #endif
--- a/src/protocols/sametime/meanwhile/mw_error.h	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/mw_error.h	Wed Nov 02 03:39:03 2005 +0000
@@ -24,7 +24,7 @@
 #include <glib.h>
 
 
-/** @file error.h
+/** @file mw_error.h
 
     Common error code constants used by Meanwhile.
 
--- a/src/protocols/sametime/meanwhile/mw_message.h	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/mw_message.h	Wed Nov 02 03:39:03 2005 +0000
@@ -49,6 +49,7 @@
   mwMessage_SET_PRIVACY_LIST  = 0x000b,  /**< mwMsgSetPrivacyList */
   mwMessage_SENSE_SERVICE     = 0x0011,  /**< mwMsgSenseService */
   mwMessage_ADMIN             = 0x0019,  /**< mwMsgAdmin */
+  mwMessage_ANNOUNCE          = 0x0022,  /**< mwMsgAnnounce */
 };
 
 
@@ -94,9 +95,12 @@
   struct mwMessage head;
   guint16 major;          /**< client's major version number */
   guint16 minor;          /**< client's minor version number */
-  guint32 srvrcalc_addr;  /**<  */
+  guint32 srvrcalc_addr;  /**< 0.0.0.0 */
   guint16 login_type;     /**< @see mwLoginType */
-  guint32 loclcalc_addr;  /**<  */
+  guint32 loclcalc_addr;  /**< local public IP */
+  guint16 unknown_a;      /**< normally 0x0100 */
+  guint32 unknown_b;      /**< normally 0x00000000 */
+  char *local_host;       /**< name of client host */
 };
 
 
@@ -107,8 +111,8 @@
   guint16 major;          /**< server's major version number */
   guint16 minor;          /**< server's minor version number */
   guint32 srvrcalc_addr;  /**< server-calculated address */
-  guint32 unknown;        /**< four bytes of something */
-  struct mwOpaque data;   /**< some stuff */
+  guint32 magic;          /**< four bytes of something */
+  struct mwOpaque data;   /**< server's DH public key for auth */
 };
 
 
@@ -117,7 +121,9 @@
 enum mwAuthType {
   mwAuthType_PLAIN    = 0x0000,
   mwAuthType_TOKEN    = 0x0001,
-  mwAuthType_ENCRYPT  = 0x0002,
+  mwAuthType_ENCRYPT  = 0x0002, /**< @todo remove for 1.0 */
+  mwAuthType_RC2_40   = 0x0002,
+  mwAuthType_RC2_128  = 0x0004,
 };
 
 
@@ -267,5 +273,23 @@
 };
 
 
+/* Announce */
+
+/** An announcement between users */
+struct mwMsgAnnounce {
+  struct mwMessage head;
+  gboolean sender_present;    /**< indicates presence of sender data */
+  struct mwLoginInfo sender;  /**< who sent the announcement */
+  guint16 unknown_a;          /**< unknown A. Usually 0x00 */
+  gboolean may_reply;         /**< replies allowed */
+  char *text;                 /**< text of message */
+
+  /** list of (char *) indicating recipients. Recipient users are in
+      the format "@U username" and recipient NAB groups are in the
+      format "@G groupname" */
+  GList *recipients;
+};
+
+
 #endif
 
--- a/src/protocols/sametime/meanwhile/mw_service.h	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/mw_service.h	Wed Nov 02 03:39:03 2005 +0000
@@ -304,12 +304,6 @@
 
 
 /** @returns the service's state
-    @relates MW_SERVICE_IS_STARTING
-    @relates MW_SERVICE_IS_STARTED
-    @relates MW_SERVICE_IS_STOPPING
-    @relates MW_SERVICE_IS_STOPPED
-    @relates MW_SERVICE_IS_LIVE
-    @relates MW_SERVICE_IS_DEAD
 */
 enum mwServiceState mwService_getState(struct mwService *service);
 
--- a/src/protocols/sametime/meanwhile/mw_session.h	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/mw_session.h	Wed Nov 02 03:39:03 2005 +0000
@@ -22,10 +22,7 @@
 #define _MW_SESSION_H
 
 
-#include "mw_common.h"
-
-
-/** @file session.h
+/** @file mw_session.h
 
     A client session with a Sametime server is encapsulated in the
     mwSession structure. The session controls channels, provides
@@ -47,8 +44,13 @@
 */
 
 
+#include "mw_common.h"
+
+
+struct mwChannelSet;
 struct mwCipher;
 struct mwMessage;
+struct mwService;
 
 
 /** default protocol major version */
@@ -60,7 +62,7 @@
 
 
 /** @section Session Properties
-    ...
+    for use with mwSession_setProperty, et al.
 */
 /*@{*/
 
@@ -73,6 +75,12 @@
 /** struct mwOpaque *, authentication token */
 #define mwSession_AUTH_TOKEN        "session.auth.token"
 
+/** char *, hostname of client */
+#define mwSession_CLIENT_HOST       "client.host"
+
+/** guint32, local IP of client */
+#define mwSession_CLIENT_IP         "client.ip"
+
 /** guint16, major version of client protocol */
 #define mwSession_CLIENT_VER_MAJOR  "client.version.major"
 
@@ -152,16 +160,13 @@
 
   /** Called when the session has changed status.
 
-      Uses of the info param:
-      - <code>STOPPING</code> error code causing the session to shut down
-
-      @todo change info to a gpointer
+      @see mwSession_getStateInfo for uses of info field
 
       @param s      the session
       @param state  the session's state
-      @param info   additional state info. */
+      @param info   additional state information */
   void (*on_stateChange)(struct mwSession *s,
-			 enum mwSessionState state, guint32 info);
+			 enum mwSessionState state, gpointer info);
 
   /** called when privacy information has been sent or received
 
@@ -177,11 +182,10 @@
   /** called when an admin messages has been received */
   void (*on_admin)(struct mwSession *, const char *text);
 
-  /** called when a login redirect message is received
+  /** called when an announcement arrives */
+  void (*on_announce)(struct mwSession *, struct mwLoginInfo *from,
+		      gboolean may_reply, const char *text);
 
-      @todo remove in favour of on_stateChange, passing host as a
-      gpointer in info */
-  void (*on_loginRedirect)(struct mwSession *, const char *host);
 };
 
 
@@ -229,11 +233,26 @@
 int mwSession_forceLogin(struct mwSession *s);
 
 
+/** send an announcement to a list of users/groups. Targets of
+    announcement must be in the same community as the session.
+
+    @param s          session to send announcement from
+    @param may_reply  permit clients to reply. Not all clients honor this.
+    @param text       text of announcement
+    @param recipients list of recipients. Each recipient is specified
+                      by a single string, prefix with "@U " for users
+                      and "@G " for Notes Address Book groups.
+*/
+int mwSession_sendAnnounce(struct mwSession *s, gboolean may_reply,
+			   const char *text, const GList *recipients);
+
+
 /** set the internal privacy information, and inform the server as
     necessary. Triggers the on_setPrivacyInfo call-back. */
 int mwSession_setPrivacyInfo(struct mwSession *, struct mwPrivacyInfo *);
 
 
+/** direct reference to the session's internal privacy structure */
 struct mwPrivacyInfo *mwSession_getPrivacyInfo(struct mwSession *);
 
 
@@ -253,8 +272,19 @@
 enum mwSessionState mwSession_getState(struct mwSession *);
 
 
-/** additional status-specific information */
-guint32 mwSession_getStateInfo(struct mwSession *);
+/** additional status-specific information. Depending on the state of
+    the session, this value has different meaning.
+
+    @li @c mwSession_STOPPING guint32 error code causing
+    the session to shut down
+
+    @li @c mwSession_STOPPED guint32 error code causing
+    the session to shut down
+
+    @li @c mwSession_LOGIN_REDIR (char *) host to redirect
+    to
+*/
+gpointer mwSession_getStateInfo(struct mwSession *);
 
 
 struct mwChannelSet *mwSession_getChannels(struct mwSession *);
--- a/src/protocols/sametime/meanwhile/mw_srvc_aware.h	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/mw_srvc_aware.h	Wed Nov 02 03:39:03 2005 +0000
@@ -28,11 +28,17 @@
 /** @file mw_srvc_aware.h
 
     The aware service...
+
+    @todo remove the whole idea of an instantiated mwAwareList and
+    instead use arbitrary pointers (including NULL) as keys to
+    internally stored lists. This removes the problem of the service
+    free'ing its lists and invalidating mwAwareList references from
+    client code.
 */
 
 
 /** Type identifier for the aware service */
-#define SERVICE_AWARE  0x00000011
+#define mwService_AWARE  0x00000011
 
 
 /** @struct mwServiceAware
@@ -50,6 +56,9 @@
     Instance of an Aware List. The members of this structure are not
     made available. Access to the parts of an aware list should be
     handled through the appropriate functions.
+
+    Any references to an aware list are rendered invalid when the
+    parent service is free'd
 */
 struct mwAwareList;
 
--- a/src/protocols/sametime/meanwhile/mw_srvc_conf.h	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/mw_srvc_conf.h	Wed Nov 02 03:39:03 2005 +0000
@@ -27,7 +27,7 @@
 
 
 /** Type identifier for the conference service */
-#define SERVICE_CONFERENCE  0x80000010
+#define mwService_CONFERENCE  0x80000010
 
 
 enum mwConferenceState {
@@ -163,6 +163,7 @@
 
 
 /** invite another user to an ACTIVE conference
+    @param conf  conference
     @param who   user to invite
     @param text  invitation message
  */
--- a/src/protocols/sametime/meanwhile/mw_srvc_ft.h	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/mw_srvc_ft.h	Wed Nov 02 03:39:03 2005 +0000
@@ -123,6 +123,10 @@
 mwServiceFileTransfer_getHandler(struct mwServiceFileTransfer *srvc);
 
 
+const GList *
+mwServiceFileTransfer_getTransfers(struct mwServiceFileTransfer *srvc);
+
+
 struct mwFileTransfer *
 mwFileTransfer_new(struct mwServiceFileTransfer *srvc,
 		   const struct mwIdBlock *who, const char *msg,
@@ -194,8 +198,8 @@
 /** Close a file transfer. This will trigger the ft_close function of the
     session's handler.
 
-    @relates mwFileTransfer_reject
-    @relates mwFileTransfer_cancel
+    @see mwFileTransfer_reject
+    @see mwFileTransfer_cancel
 */
 int mwFileTransfer_close(struct mwFileTransfer *ft, guint32 code);
 
@@ -204,7 +208,7 @@
     the other end of the transfer should respond with an acknowledgement
     message, which can be caught in the service's handler.
 
-    @relates mwFileTransferHandler::ft_ack
+    @see mwFileTransferHandler::ft_ack
 */
 int mwFileTransfer_send(struct mwFileTransfer *ft,
 			struct mwOpaque *data);
@@ -217,7 +221,7 @@
     possible to have the handler's ft_recv function triggered again
     even if no ack was sent.
 
-    @relates mwFileTransferHandler::ft_recv
+    @see mwFileTransferHandler::ft_recv
 */
 int mwFileTransfer_ack(struct mwFileTransfer *ft);
 
--- a/src/protocols/sametime/meanwhile/mw_srvc_im.h	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/mw_srvc_im.h	Wed Nov 02 03:39:03 2005 +0000
@@ -26,7 +26,7 @@
 #include "mw_common.h"
 
 
-/** @file srvc_im.h
+/** @file mw_srvc_im.h
 
     The IM service provides one-on-one communication between
     users. Messages sent over conversations may relay different types
@@ -38,7 +38,7 @@
 
 
 /** Type identifier for the IM service */
-#define SERVICE_IM  0x00001000
+#define mwService_IM  0x00001000
 
 
 /** @struct mwServiceIm
@@ -70,11 +70,11 @@
    conversation. At any point, the feature set for the service may
    change, without affecting any existing conversations.
 
-   @relates mwServiceIm_supports
-   @relates mwServiceIm_setSupported
-   @relates mwConversation_supports
-   @relates mwConversation_send
-   @relates mwServiceImHandler::conversation_recv
+   @see mwServiceIm_supports
+   @see mwServiceIm_setSupported
+   @see mwConversation_supports
+   @see mwConversation_send
+   @see mwServiceImHandler::conversation_recv
  */
 enum mwImSendType {
   mwImSend_PLAIN,   /**< char *, plain-text message */
@@ -86,7 +86,7 @@
 
 
 
-/** @relates mwConversation_getState */
+/** @see mwConversation_getState */
 enum mwConversationState {
   mwConversation_CLOSED,   /**< conversation is not open */
   mwConversation_PENDING,  /**< conversation is opening */
@@ -195,9 +195,9 @@
 
 /** get the state of a conversation
 
-    @relates mwConversation_isOpen
-    @relates mwConversation_isClosed
-    @relates mwConversation_isPending
+    @see mwConversation_isOpen
+    @see mwConversation_isClosed
+    @see mwConversation_isPending
 */
 enum mwConversationState mwConversation_getState(struct mwConversation *conv);
 
@@ -233,8 +233,8 @@
 /** Associates client data with a conversation. If there is existing data,
     it will not have its cleanup function called.
 
-    @relates mwConversation_getClientData
-    @relates mwConversation_removeClientData
+    @see mwConversation_getClientData
+    @see mwConversation_removeClientData
 */
 void mwConversation_setClientData(struct mwConversation *conv,
 				  gpointer data, GDestroyNotify clean);
@@ -242,8 +242,8 @@
 
 /** Reference associated client data
 
-    @relates mwConversation_setClientData
-    @relates mwConversation_removeClientData
+    @see mwConversation_setClientData
+    @see mwConversation_removeClientData
  */
 gpointer mwConversation_getClientData(struct mwConversation *conv);
 
@@ -251,8 +251,8 @@
 /** Remove any associated client data, calling the optional cleanup
     function if one was provided
 
-    @relates mwConversation_setClientData
-    @relates mwConversation_getClientData
+    @see mwConversation_setClientData
+    @see mwConversation_getClientData
 */
 void mwConversation_removeClientData(struct mwConversation *conv);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/mw_srvc_place.h	Wed Nov 02 03:39:03 2005 +0000
@@ -0,0 +1,129 @@
+
+/*
+  Meanwhile - Unofficial Lotus Sametime Community Client Library
+  Copyright (C) 2004  Christopher (siege) O'Brien
+  
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Library General Public
+  License as published by the Free Software Foundation; either
+  version 2 of the License, or (at your option) any later version.
+  
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Library General Public License for more details.
+  
+  You should have received a copy of the GNU Library General Public
+  License along with this library; if not, write to the Free
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#ifndef _MW_SRVC_PLACE_H
+#define _MW_SRVC_PLACE_H
+
+
+#include <glib/glist.h>
+#include "mw_common.h"
+
+
+/** Type identifier for the place service */
+#define mwService_PLACE  0x80000022
+
+
+/** @struct mwServicePlace */
+struct mwServicePlace;
+
+
+/** @struct mwPlace */
+struct mwPlace;
+
+
+struct mwPlaceHandler {
+  void (*opened)(struct mwPlace *place);
+  void (*closed)(struct mwPlace *place, guint32 code);
+
+  void (*peerJoined)(struct mwPlace *place,
+		     const struct mwIdBlock *peer);
+
+  void (*peerParted)(struct mwPlace *place,
+		     const struct mwIdBlock *peer);
+
+  void (*peerSetAttribute)(struct mwPlace *place,
+			   const struct mwIdBlock *peer,
+			   guint32 attr, struct mwOpaque *o);
+
+  void (*peerUnsetAttribute)(struct mwPlace *place,
+			     const struct mwIdBlock *peer,
+			     guint32 attr);
+
+  void (*message)(struct mwPlace *place,
+		  const struct mwIdBlock *who,
+		  const char *msg);
+
+  void (*clear)(struct mwServicePlace *srvc);
+};
+
+
+enum mwPlacePeerAttribute {
+  mwPlacePeer_TYPING = 0x00000008,
+};
+
+
+struct mwServicePlace *
+mwServicePlace_new(struct mwSession *session,
+		   struct mwPlaceHandler *handler);
+
+
+struct mwPlaceHandler *
+mwServicePlace_getHandler(struct mwServicePlace *srvc);
+
+
+const GList *mwServicePlace_getPlaces(struct mwServicePlace *srvc);
+
+
+struct mwPlace *mwPlace_new(struct mwServicePlace *srvc,
+			    const char *name, const char *title);
+
+
+struct mwServicePlace *mwPlace_getService(struct mwPlace *place);
+
+
+const char *mwPlace_getName(struct mwPlace *place);
+
+
+const char *mwPlace_getTitle(struct mwPlace *place);
+
+
+int mwPlace_open(struct mwPlace *place);
+
+
+int mwPlace_destroy(struct mwPlace *place, guint32 code);
+
+
+/** returns a GList* of struct mwIdBlock*. The GList will need to be
+    freed after use, the mwIdBlock structures should not be modified
+    or freed */
+GList *mwPlace_getMembers(struct mwPlace *place);
+
+
+int mwPlace_sendText(struct mwPlace *place, const char *msg);
+
+
+int mwPlace_setAttribute(struct mwPlace *place, guint32 attrib,
+			 struct mwOpaque *data);
+
+
+int mwPlace_unsetAttribute(struct mwPlace *place, guint32 attrib);
+
+
+void mwPlace_setClientData(struct mwPlace *place,
+			   gpointer data, GDestroyNotify clean);
+
+
+gpointer mwPlace_getClientData(struct mwPlace *place);
+
+
+void mwPlace_removeClientData(struct mwPlace *place);
+
+
+#endif
--- a/src/protocols/sametime/meanwhile/mw_srvc_resolve.h	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/mw_srvc_resolve.h	Wed Nov 02 03:39:03 2005 +0000
@@ -27,7 +27,7 @@
 
 
 /** Type identifier for the conference service */
-#define SERVICE_RESOLVE  0x00000015
+#define mwService_RESOLVE  0x00000015
 
 
 /** Return value of mwServiceResolve_search indicating an error */
@@ -117,6 +117,7 @@
 
 /** Inisitate a resolve request.
 
+    @param srvc     the resolve service
     @param queries  list query strings
     @param flags    search flags
     @param handler  result handling function
--- a/src/protocols/sametime/meanwhile/mw_srvc_store.h	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/mw_srvc_store.h	Wed Nov 02 03:39:03 2005 +0000
@@ -27,7 +27,7 @@
 
 
 /** Type identifier for the storage service */
-#define SERVICE_STORAGE  0x00000018
+#define mwService_STORAGE  0x00000018
 
 
 /** @struct mwServiceStorage
@@ -154,6 +154,8 @@
 /** Initiates a load call to the storage service. If the service is
     not currently available, the call will be cached and processed
     when the service is started.
+
+    @param srvc       the storage service
     @param item       storage unit to load
     @param cb         callback function when the load call completes
     @param data       user data for callback
@@ -168,6 +170,8 @@
 /** Initiates a store call to the storage service. If the service is
     not currently available, the call will be cached and processed
     when the service is started.
+
+    @param srvc       the storage service
     @param item       storage unit to save
     @param cb         callback function when the load call completes
     @param data       optional user data for callback
--- a/src/protocols/sametime/meanwhile/session.c	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/session.c	Wed Nov 02 03:39:03 2005 +0000
@@ -19,6 +19,7 @@
 */
 
 #include <glib.h>
+#include <gmp.h>
 #include <string.h>
 
 #include "mw_channel.h"
@@ -48,7 +49,7 @@
   struct mwSessionHandler *handler;
 
   enum mwSessionState state;  /**< session state */
-  guint32 state_info;         /**< additional state info */
+  gpointer state_info;        /**< additional state info */
 
   /* input buffering for an incoming message */
   char *buf;       /**< buffer for incoming message data */
@@ -218,16 +219,8 @@
 }
 
 
-/** set the state of the session, and trigger the session handler's
-    on_stateChange function. Has no effect if the session is already
-    in the specified state (ignores additional state info)
-
-    @param s      the session
-    @param state  the state to set
-    @param info   additional state info
-*/
 static void state(struct mwSession *s, enum mwSessionState state,
-		  guint32 info) {
+		  gpointer info) {
 
   struct mwSessionHandler *sh;
 
@@ -239,14 +232,24 @@
   s->state = state;
   s->state_info = info;
 
-  if(info) {
-    g_message("session state: %s (0x%08x)", state_str(state), info);
-  } else {
+  switch(state) {
+  case mwSession_STOPPING:
+  case mwSession_STOPPED:
+    g_message("session state: %s (0x%08x)", state_str(state),
+	      GPOINTER_TO_UINT(info));
+    break;
+
+  case mwSession_LOGIN_REDIR:
+    g_message("session state: %s (%s)", state_str(state),
+	      (char *)info);
+    break;
+
+  default:
     g_message("session state: %s", state_str(state));
   }
 
   sh = s->handler;
-  if(sh->on_stateChange)
+  if(sh && sh->on_stateChange)
     sh->on_stateChange(s, state, info);
 }
 
@@ -258,6 +261,11 @@
   g_return_if_fail(s != NULL);
   g_return_if_fail(mwSession_isStopped(s));
 
+  if(mwSession_isStarted(s) || mwSession_isStarting(s)) {
+    g_debug("attempted to start session that is already started/starting");
+    return;
+  }
+  
   state(s, mwSession_STARTING, 0);
 
   msg = (struct mwMsgHandshake *) mwMessage_new(mwMessage_HANDSHAKE);
@@ -265,6 +273,13 @@
   msg->minor = GUINT(property_get(s, mwSession_CLIENT_VER_MINOR));
   msg->login_type = GUINT(property_get(s, mwSession_CLIENT_TYPE_ID));
 
+  msg->loclcalc_addr = GUINT(property_get(s, mwSession_CLIENT_IP));
+
+  if(msg->major >= 0x001e && msg->minor >= 0x001d) {
+    msg->unknown_a = 0x0100;
+    msg->local_host = (char *) property_get(s, mwSession_CLIENT_HOST);
+  }
+
   ret = mwSession_send(s, MW_MESSAGE(msg));
   mwMessage_free(MW_MESSAGE(msg));
 
@@ -281,10 +296,13 @@
   struct mwMsgChannelDestroy *msg;
 
   g_return_if_fail(s != NULL);
-  g_return_if_fail(! mwSession_isStopping(s));
-  g_return_if_fail(! mwSession_isStopped(s));
+  
+  if(mwSession_isStopped(s) || mwSession_isStopping(s)) {
+    g_debug("attempted to stop session that is already stopped/stopping");
+    return;
+  }
 
-  state(s, mwSession_STOPPING, reason);
+  state(s, mwSession_STOPPING, GUINT_TO_POINTER(reason));
 
   for(list = l = mwSession_getServices(s); l; l = l->next)
     mwService_stop(MW_SERVICE(l->data));
@@ -305,20 +323,20 @@
   /* close the connection */
   io_close(s);
 
-  state(s, mwSession_STOPPED, reason);
+  state(s, mwSession_STOPPED, GUINT_TO_POINTER(reason));
 }
 
 
 /** compose authentication information into an opaque based on the
-    password */
-static void compose_auth(struct mwOpaque *auth, const char *pass) {
+    password, encrypted via RC2/40 */
+static void compose_auth_rc2_40(struct mwOpaque *auth, const char *pass) {
   char iv[8], key[5];
   struct mwOpaque a, b, z;
   struct mwPutBuffer *p;
 
   /* get an IV and a random five-byte key */
   mwIV_init((char *) iv);
-  rand_key((char *) key, 5);
+  mwKeyRandom((char *) key, 5);
 
   /* the opaque with the key */
   a.len = 5;
@@ -346,6 +364,63 @@
 }
 
 
+static void compose_auth_rc2_128(struct mwOpaque *auth, const char *pass,
+				 guint32 magic, struct mwOpaque *rkey) {
+
+  char iv[8];
+  struct mwOpaque a, b, c;
+  struct mwPutBuffer *p;
+
+  mpz_t private, public;
+  mpz_t remote;
+  mpz_t shared;
+
+  mpz_init(private);
+  mpz_init(public);
+  mpz_init(remote);
+  mpz_init(shared);
+
+  mwIV_init(iv);
+
+  mwDHRandKeypair(private, public);
+  mwDHImportKey(remote, rkey);
+  mwDHCalculateShared(shared, remote, private);
+
+  /* put the password in opaque a */
+  p = mwPutBuffer_new();
+  guint32_put(p, magic);
+  mwString_put(p, pass);
+  mwPutBuffer_finalize(&a, p);
+
+  /* put the shared key in opaque b */
+  mwDHExportKey(shared, &b);
+
+  /* encrypt the password (a) using the shared key (b), put the result
+     in opaque c */
+  mwEncrypt(b.data+(b.len-16), 16, iv, &a, &c);
+
+  /* don't need the shared key anymore, re-use opaque (b) as the
+     export of the public key */
+  mwOpaque_clear(&b);
+  mwDHExportKey(public, &b);
+
+  p = mwPutBuffer_new();
+  guint16_put(p, 0x0001);  /* XXX: unknown */
+  mwOpaque_put(p, &b);
+  mwOpaque_put(p, &c);
+  mwPutBuffer_finalize(auth, p);
+
+  mwOpaque_clear(&a);
+  mwOpaque_clear(&b);
+  mwOpaque_clear(&c);
+
+  mpz_clear(private);
+  mpz_clear(public);
+  mpz_clear(remote);
+  mpz_clear(shared);
+}
+
+
 /** handle the receipt of a handshake_ack message by sending the login
     message */
 static void HANDSHAKE_ACK_recv(struct mwSession *s,
@@ -380,8 +455,21 @@
   log->name = g_strdup(property_get(s, mwSession_AUTH_USER_ID));
 
   /** @todo default to password for now. later use token optionally */
-  log->auth_type = mwAuthType_ENCRYPT;
-  compose_auth(&log->auth_data, property_get(s, mwSession_AUTH_PASSWORD));
+  {
+    const char *pw;
+    pw = (const char *) property_get(s, mwSession_AUTH_PASSWORD);
+   
+    if(msg->data.len >= 64) {
+      /* good login encryption */
+      log->auth_type = mwAuthType_RC2_128;
+      compose_auth_rc2_128(&log->auth_data, pw, msg->magic, &msg->data);
+
+    } else {
+      /* BAD login encryption */
+      log->auth_type = mwAuthType_RC2_40;
+      compose_auth_rc2_40(&log->auth_data, pw);
+    }
+  }
   
   /* send the login message */
   ret = mwSession_send(s, MW_MESSAGE(log));
@@ -524,14 +612,18 @@
 }
 
 
-static void LOGIN_REDIRECT_recv(struct mwSession *s,
-				struct mwMsgLoginRedirect *msg) {
+static void ANNOUNCE_recv(struct mwSession *s, struct mwMsgAnnounce *msg) {
   struct mwSessionHandler *sh = s->handler;
 
-  state(s, mwSession_LOGIN_REDIR, 0);
+  if(sh && sh->on_announce)
+    sh->on_announce(s, &msg->sender, msg->may_reply, msg->text);
+}
+
 
-  if(sh && sh->on_loginRedirect)
-    sh->on_loginRedirect(s, msg->host);
+static void LOGIN_REDIRECT_recv(struct mwSession *s,
+				struct mwMsgLoginRedirect *msg) {
+
+  state(s, mwSession_LOGIN_REDIR, msg->host);
 }
 
 
@@ -544,7 +636,7 @@
 static void session_process(struct mwSession *s,
 			    const char *buf, gsize len) {
 
-  struct mwOpaque o = { len, (char *) buf };
+  struct mwOpaque o = { .len = len, .data = (char *) buf };
   struct mwGetBuffer *b;
   struct mwMessage *msg;
 
@@ -559,6 +651,13 @@
 
   /* attempt to parse the message. */
   msg = mwMessage_get(b);
+
+  if(mwGetBuffer_error(b)) {
+    mw_mailme_opaque(&o, "parsing of message failed");
+  }
+
+  mwGetBuffer_free(b);
+
   g_return_if_fail(msg != NULL);
 
   /* handle each of the appropriate incoming types of mwMessage */
@@ -574,17 +673,12 @@
     CASE(SET_USER_STATUS, mwMsgSetUserStatus);
     CASE(SENSE_SERVICE, mwMsgSenseService);
     CASE(ADMIN, mwMsgAdmin);
+    CASE(ANNOUNCE, mwMsgAnnounce);
     
   default:
     g_warning("unknown message type 0x%04x, no handler", msg->type);
   }
 
-  if(mwGetBuffer_error(b)) {
-    struct mwOpaque o = { .data = (char *) buf, .len = len };
-    mw_debug_mailme(&o, "parsing of message type 0x%04x failed", msg->type);
-  }
-
-  mwGetBuffer_free(b);
   mwMessage_free(msg);
 }
 
@@ -592,7 +686,7 @@
 #undef CASE
 
 
-#define ADVANCE(b, n, count) (b += count, n -= count)
+#define ADVANCE(b, n, count) { b += count; n -= count; }
 
 
 /* handle input to complete an existing buffer */
@@ -739,9 +833,11 @@
   /* g_message(" session_recv: session = %p, b = %p, n = %u",
 	    s, b, n); */
   
-  if(n && (s->buf_len == 0) && (*b & 0x80)) {
-    /* keep-alive and series bytes are ignored */
-    ADVANCE(b, n, 1);
+  if(s->buf_len == 0) {
+    while(n && (*b & 0x80)) {
+      /* keep-alive and series bytes are ignored */
+      ADVANCE(b, n, 1);
+    }
   }
 
   if(n == 0) {
@@ -765,9 +861,6 @@
 
   g_return_if_fail(s != NULL);
 
-  /* g_message(" mwSession_recv: session = %p, b = %p, n = %u",
-	    s, b, n); */
-
   while(n > 0) {
     remain = session_recv(s, b, n);
     b += (n - remain);
@@ -779,7 +872,6 @@
 int mwSession_send(struct mwSession *s, struct mwMessage *msg) {
   struct mwPutBuffer *b;
   struct mwOpaque o;
-  gsize len;
   int ret = 0;
 
   g_return_val_if_fail(s != NULL, -1);
@@ -799,7 +891,6 @@
   mwPutBuffer_finalize(&o, b);
 
   /* then we use that opaque's data and length to write to the socket */
-  len = o.len;
   ret = io_write(s, o.data, o.len);
   mwOpaque_clear(&o);
 
@@ -845,6 +936,30 @@
 }
 
 
+int mwSession_sendAnnounce(struct mwSession *s, gboolean may_reply,
+			   const char *text, const GList *recipients) {
+
+  struct mwMsgAnnounce *msg;
+  int ret;
+
+  g_return_val_if_fail(s != NULL, -1);
+  g_return_val_if_fail(mwSession_isStarted(s), -1);
+  
+  msg = (struct mwMsgAnnounce *) mwMessage_new(mwMessage_ANNOUNCE);
+
+  msg->recipients = (GList *) recipients;
+  msg->may_reply = may_reply;
+  msg->text = g_strdup(text);
+
+  ret = mwSession_send(s, MW_MESSAGE(msg));
+
+  msg->recipients = NULL;  /* don't kill our recipients param */
+  mwMessage_free(MW_MESSAGE(msg));
+
+  return ret;
+}
+
+
 struct mwSessionHandler *mwSession_getHandler(struct mwSession *s) {
   g_return_val_if_fail(s != NULL, NULL);
   return s->handler;
@@ -917,7 +1032,7 @@
 }
 
 
-guint32 mwSession_getStateInfo(struct mwSession *s) {
+gpointer mwSession_getStateInfo(struct mwSession *s) {
   g_return_val_if_fail(s != NULL, 0);
   return s->state_info;
 }
--- a/src/protocols/sametime/meanwhile/srvc_aware.c	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/srvc_aware.c	Wed Nov 02 03:39:03 2005 +0000
@@ -113,6 +113,8 @@
   msg_OPT_GOT_SET     = 0x0259,  /**< recv attribute set update */
   msg_OPT_GOT_UNSET   = 0x025a,  /**< recv attribute unset update */
 
+  msg_OPT_GOT_UNKNOWN = 0x025b,  /**< UNKNOWN */
+  
   msg_OPT_DID_SET     = 0x025d,  /**< attribute set response */
   msg_OPT_DID_UNSET   = 0x025e,  /**< attribute unset response */
   msg_OPT_DID_ERROR   = 0x025f,  /**< attribute set/unset error */
@@ -171,6 +173,8 @@
   struct mwOpaque o;
   int ret;
 
+  g_return_val_if_fail(chan != NULL, 0);
+
   compose_list(b, id_list);
 
   mwPutBuffer_finalize(&o, b);
@@ -187,6 +191,8 @@
   struct mwOpaque o;
   int ret;
 
+  g_return_val_if_fail(chan != NULL, 0);
+
   compose_list(b, id_list);
   mwPutBuffer_finalize(&o, b);
 
@@ -249,6 +255,9 @@
   int tmp;
   GList *l;
 
+  g_return_val_if_fail(srvc != NULL, -1);
+  g_return_val_if_fail(srvc->channel != NULL, 0);
+
   l = map_collect_keys(srvc->attribs);
   tmp = g_list_length(l);
 
@@ -288,7 +297,7 @@
 static int remove_unused_attrib(struct mwServiceAware *srvc) {
   GList *dead = NULL;
 
-  if(srvc->entries) {
+  if(srvc->attribs) {
     g_info("collecting dead attributes");
     g_hash_table_foreach_steal(srvc->attribs, collect_attrib_dead, &dead);
   }
@@ -298,7 +307,7 @@
     attrib_entry_free(dead->data);
   }
 
-  return send_attrib_list(srvc);
+  return MW_SERVICE_IS_LIVE(srvc)? send_attrib_list(srvc): 0;
 }
 
 
@@ -374,7 +383,7 @@
 			struct mwAwareAttribute *attrib) {
 
   struct aware_entry *aware;
-  struct mwAwareAttribute *old_attrib;
+  struct mwAwareAttribute *old_attrib = NULL;
   GList *l;
   guint32 key;
   gpointer k;
@@ -385,7 +394,9 @@
   key = attrib->key;
   k = GUINT_TO_POINTER(key);
 
-  old_attrib = g_hash_table_lookup(aware->attribs, k);
+  if(aware->attribs)
+    old_attrib = g_hash_table_lookup(aware->attribs, k);
+
   if(! old_attrib) {
     old_attrib = g_new0(struct mwAwareAttribute, 1);
     old_attrib->key = key;
@@ -399,7 +410,9 @@
     struct mwAwareList *list = l->data;
     struct mwAwareListHandler *h = list->handler;
 
-    if(h && h->on_attrib && g_hash_table_lookup(list->attribs, k))
+    if(h && h->on_attrib &&
+       list->attribs && g_hash_table_lookup(list->attribs, k))
+
       h->on_attrib(list, idb, old_attrib);
   }
 }
@@ -413,6 +426,10 @@
   g_return_val_if_fail(id->user != NULL, FALSE);
   g_return_val_if_fail(strlen(id->user) > 0, FALSE);
 
+  if(! list->entries)
+    list->entries = g_hash_table_new((GHashFunc) mwAwareIdBlock_hash,
+				     (GEqualFunc) mwAwareIdBlock_equal);
+
   aware = list_aware_find(list, id);
   if(aware) return FALSE;
 
@@ -427,7 +444,9 @@
   }
 
   aware->membership = g_list_append(aware->membership, list);
+
   g_hash_table_insert(list->entries, ENTRY_KEY(aware), aware);
+
   return TRUE;
 }
 
@@ -608,13 +627,14 @@
     recv_OPT_GOT_UNSET(srvc_aware, b);
     break;
 
+  case msg_OPT_GOT_UNKNOWN:
   case msg_OPT_DID_SET:
   case msg_OPT_DID_UNSET:
   case msg_OPT_DID_ERROR:
     break;
 
   default:
-    mw_debug_mailme(data, "unknown message in aware service: 0x%04x", type);   
+    mw_mailme_opaque(data, "unknown message in aware service: 0x%04x", type);
   }
 
   mwGetBuffer_free(b);  
@@ -631,6 +651,9 @@
 
   g_hash_table_destroy(srvc_aware->entries);
   srvc_aware->entries = NULL;
+
+  g_hash_table_destroy(srvc_aware->attribs);
+  srvc_aware->attribs = NULL;
 }
 
 
@@ -659,7 +682,7 @@
 
 static void start(struct mwService *srvc) {
   struct mwServiceAware *srvc_aware;
-  struct mwChannel *chan;
+  struct mwChannel *chan = NULL;
 
   srvc_aware = (struct mwServiceAware *) srvc;
   chan = make_blist(srvc_aware, mwSession_getChannels(srvc->session));
@@ -676,7 +699,12 @@
   struct mwServiceAware *srvc_aware;
 
   srvc_aware = (struct mwServiceAware *) srvc;
-  mwChannel_destroy(srvc_aware->channel, ERR_SUCCESS, NULL);
+
+  if(srvc_aware->channel) {
+    mwChannel_destroy(srvc_aware->channel, ERR_SUCCESS, NULL);
+    srvc_aware->channel = NULL;
+  }
+
   mwService_stopped(srvc);
 }
 
@@ -702,7 +730,7 @@
 					(GDestroyNotify) attrib_entry_free);
 
   service = MW_SERVICE(srvc);
-  mwService_init(service, session, SERVICE_AWARE);
+  mwService_init(service, session, mwService_AWARE);
 
   service->recv_accept = (mwService_funcRecvAccept) recv_accept;
   service->recv_destroy = (mwService_funcRecvDestroy) recv_destroy;
@@ -913,11 +941,10 @@
 
   al = g_new0(struct mwAwareList, 1);
   al->service = srvc;
-  al->entries = g_hash_table_new((GHashFunc) mwAwareIdBlock_hash,
-				 (GEqualFunc) mwAwareIdBlock_equal);
-  al->attribs = g_hash_table_new(g_direct_hash, g_direct_equal);
   al->handler = handler;
 
+  srvc->lists = g_list_prepend(srvc->lists, al);
+
   return al;
 }
 
@@ -927,9 +954,11 @@
   struct mwAwareListHandler *handler;
 
   g_return_if_fail(list != NULL);
-  g_return_if_fail(list->entries != NULL);
   g_return_if_fail(list->service != NULL);
 
+  srvc = list->service;
+  srvc->lists = g_list_remove_all(srvc->lists, list);
+
   handler = list->handler;
   if(handler && handler->clear) {
     handler->clear(list);
@@ -938,11 +967,12 @@
 
   mw_datum_clear(&list->client_data);
 
-  srvc = list->service;
-  srvc->lists = g_list_remove(srvc->lists, list);
-
   mwAwareList_unwatchAllAttributes(list);
   mwAwareList_removeAllAware(list);
+
+  list->service = NULL;
+
+  g_free(list);
 }
 
 
@@ -957,6 +987,9 @@
   struct attrib_entry *watch;
   gpointer k = GUINT_TO_POINTER(key);
 
+  if(! list->attribs)
+    list->attribs = g_hash_table_new(g_direct_hash, g_direct_equal);
+
   if(g_hash_table_lookup(list->attribs, k))
     return;
 
@@ -970,15 +1003,18 @@
   }
 
   g_hash_table_insert(list->attribs, k, watch);
+
   watch->membership = g_list_prepend(watch->membership, list);
 }
 
 
 static void watch_remove(struct mwAwareList *list, guint32 key) {
-  struct attrib_entry *watch;
+  struct attrib_entry *watch = NULL;
   gpointer k = GUINT_TO_POINTER(key);
 
-  watch = g_hash_table_lookup(list->attribs, k);
+  if(list->attribs)
+    watch = g_hash_table_lookup(list->attribs, k);
+
   g_return_if_fail(watch != NULL);
 
   g_hash_table_remove(list->attribs, k);
@@ -1031,8 +1067,7 @@
   for(k = *keys; k; keys++)
     watch_add(list, k);
 
-  remove_unused_attrib(list->service);
-  return send_attrib_list(list->service);
+  return remove_unused_attrib(list->service);
 }
 
 
@@ -1049,8 +1084,7 @@
     watch_remove(list, k);
   va_end(args);
 
-  remove_unused_attrib(list->service);
-  return send_attrib_list(list->service);
+  return remove_unused_attrib(list->service);
 }
 
 
@@ -1068,12 +1102,12 @@
   g_return_val_if_fail(list != NULL, -1);
   srvc = list->service;
 
-  g_hash_table_foreach(list->attribs, (GHFunc) dismember_attrib, list);
-  g_hash_table_destroy(list->attribs);
-  list->attribs = g_hash_table_new(g_direct_hash, g_direct_equal);
+  if(list->attribs) {
+    g_hash_table_foreach(list->attribs, (GHFunc) dismember_attrib, list);
+    g_hash_table_destroy(list->attribs);
+  }
 
-  remove_unused_attrib(srvc);
-  return send_attrib_list(list->service);
+  return remove_unused_attrib(srvc);
 }
 
 
@@ -1191,9 +1225,10 @@
 
   /* for each entry, remove the aware list from the service entry's
      membership collection */
-  g_hash_table_foreach(list->entries, (GHFunc) dismember_aware, list);
-  g_hash_table_destroy(list->entries);
-  g_free(list);
+  if(list->entries) {
+    g_hash_table_foreach(list->entries, (GHFunc) dismember_aware, list);
+    g_hash_table_destroy(list->entries);
+  }
 
   return remove_unused(srvc);
 }
@@ -1275,7 +1310,7 @@
   g_return_val_if_fail(user != NULL, NULL);
 
   aware = aware_find(srvc, user);
-  g_return_val_if_fail(aware != NULL, NULL);
+  if(! aware) return NULL;
 
   return aware->aware.status.desc;
 }
--- a/src/protocols/sametime/meanwhile/srvc_conf.c	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/srvc_conf.c	Wed Nov 02 03:39:03 2005 +0000
@@ -106,23 +106,23 @@
 /** generates a random conference name built around a user name */
 static char *conf_generate_name(const char *user) {
   guint a, b;
-  char c[16]; /* limited space. Used only to hold sprintf output */
   char *ret;
   
   user = user? user: "";
 
-  srand(clock());
+  srand(clock() + rand());
   a = ((rand() & 0xff) << 8) | (rand() & 0xff);
   b = time(NULL);
-  sprintf(c, "(%08x,%04x)", b, a);
 
-  ret = g_strconcat(user, c, NULL);
-
+  ret = g_strdup_printf("%s(%08x,%04x)", user, b, a);
   g_debug("generated random conference name: '%s'", ret);
   return ret;
 }
 
 
+
+
+
 static struct mwConference *conf_new(struct mwServiceConference *srvc) {
 
   struct mwConference *conf;
@@ -602,7 +602,7 @@
   srvc_conf = g_new0(struct mwServiceConference, 1);
   srvc = &srvc_conf->service;
 
-  mwService_init(srvc, session, SERVICE_CONFERENCE);
+  mwService_init(srvc, session, mwService_CONFERENCE);
   srvc->start = start;
   srvc->stop = (mwService_funcStop) stop;
   srvc->recv_create = recv_channelCreate;
--- a/src/protocols/sametime/meanwhile/srvc_ft.c	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/srvc_ft.c	Wed Nov 02 03:39:03 2005 +0000
@@ -281,7 +281,7 @@
     break;
 
   default:
-    mw_debug_mailme(data, "unknown message in ft service: 0x%04x", type);
+    mw_mailme_opaque(data, "unknown message in ft service: 0x%04x", type);
   }
 }
 
@@ -357,6 +357,13 @@
 }
 
 
+const GList *
+mwServiceFileTransfer_getTransfers(struct mwServiceFileTransfer *srvc) {
+  g_return_val_if_fail(srvc != NULL, NULL);
+  return srvc->transfers;
+}
+
+
 struct mwFileTransfer *
 mwFileTransfer_new(struct mwServiceFileTransfer *srvc,
 		   const struct mwIdBlock *who, const char *msg,
--- a/src/protocols/sametime/meanwhile/srvc_im.c	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/srvc_im.c	Wed Nov 02 03:39:03 2005 +0000
@@ -50,6 +50,9 @@
 #define msg_MESSAGE  0x0064  /**< IM message */
 
 
+#define BREAKUP  2048
+
+
 /* which type of im? */
 enum mwImType {
   mwIm_TEXT  = 0x00000001,  /**< text message */
@@ -88,6 +91,8 @@
   struct mwChannel *channel;    /**< channel */
   struct mwIdBlock target;      /**< conversation target */
 
+  gboolean ext_id;              /**< special treatment, external ID */
+
   /** state of the conversation, based loosely on the state of its
       underlying channel */
   enum mwConversationState state;
@@ -178,6 +183,9 @@
     c->state = mwConversation_CLOSED;
     c->features = srvc->features;
 
+    /* mark external users */
+    c->ext_id = g_str_has_prefix(to->user, "@E ");
+
     srvc->convs = g_list_prepend(srvc->convs, c);
   }
 
@@ -258,7 +266,7 @@
   mwConversation_removeClientData(conv);
 
   srvc = conv->service;
-  srvc->convs = g_list_remove(srvc->convs, conv);
+  srvc->convs = g_list_remove_all(srvc->convs, conv);
 
   mwIdBlock_clear(&conv->target);
   g_free(conv);
@@ -316,7 +324,7 @@
   y = mwChannel_getProtoType(chan);
   z = mwChannel_getProtoVer(chan);
 
-  if( (x != SERVICE_IM) || (y != PROTOCOL_TYPE) || (z != PROTOCOL_VER) ) {
+  if( (x != mwService_IM) || (y != PROTOCOL_TYPE) || (z != PROTOCOL_VER) ) {
     g_warning("unacceptable service, proto, ver:"
 	      " 0x%08x, 0x%08x, 0x%08x", x, y, z);
     mwChannel_destroy(chan, ERR_SERVICE_NO_SUPPORT, NULL);
@@ -432,7 +440,8 @@
        messages. Fail-over to a non-html format on a new channel for
        the convo */
     if(c->features != mwImClient_PLAIN
-       && msg->reason == ERR_IM_NOT_REGISTERED) {
+       && (msg->reason == ERR_IM_NOT_REGISTERED ||
+	   msg->reason == ERR_SERVICE_NO_SUPPORT)) {
 
       g_debug("falling back on a plaintext conversation");
       c->features = mwImClient_PLAIN;
@@ -611,8 +620,8 @@
 
   default:
     
-    mw_debug_mailme(&o, "unknown data message type in IM service:"
-		    " (0x%08x, 0x%08x)", type, subtype);
+    mw_mailme_opaque(&o, "unknown data message type in IM service:"
+		     " (0x%08x, 0x%08x)", type, subtype);
   }
 
   mwOpaque_clear(&o);
@@ -708,9 +717,9 @@
   g_return_val_if_fail(hndl != NULL, NULL);
 
   srvc_im = g_new0(struct mwServiceIm, 1);
-  srvc = &srvc_im->service;
+  srvc = MW_SERVICE(srvc_im);
 
-  mwService_init(srvc, session, SERVICE_IM);
+  mwService_init(srvc, session, mwService_IM);
   srvc->recv_create = recv_channelCreate;
   srvc->recv_accept = recv_channelAccept;
   srvc->recv_destroy = recv_channelDestroy;
@@ -769,6 +778,85 @@
 }
 
 
+static int convo_send_data(struct mwConversation *conv,
+			   guint32 type, guint32 subtype,
+			   struct mwOpaque *data) {
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+  struct mwChannel *chan;
+  int ret;
+
+  chan = conv->channel;
+  g_return_val_if_fail(chan != NULL, -1);
+
+  b = mwPutBuffer_new();
+
+  guint32_put(b, mwIm_DATA);
+  guint32_put(b, type);
+  guint32_put(b, subtype);
+  mwOpaque_put(b, data);
+
+  mwPutBuffer_finalize(&o, b);
+
+  ret = mwChannel_sendEncrypted(chan, msg_MESSAGE, &o, !conv->ext_id);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+static int convo_send_multi_start(struct mwConversation *conv) {
+  return convo_send_data(conv, mwImData_MULTI_START, 0x00, NULL);
+}
+
+
+static int convo_send_multi_stop(struct mwConversation *conv) {
+  return convo_send_data(conv, mwImData_MULTI_STOP, 0x00, NULL);
+}
+
+
+/* breaks up a large message into segments, sends a start_segment
+   message, then sends each segment in turn, then sends a stop_segment
+   message */
+static int
+convo_sendSegmented(struct mwConversation *conv, const char *message,
+		    int (*send)(struct mwConversation *conv,
+				const char *msg)) {
+  char *buf = (char *) message;
+  gsize len;
+  int ret = 0;
+
+  len = strlen(buf);
+  ret = convo_send_multi_start(conv);
+
+  while(len && !ret) {
+    char tail;
+    gsize seg;
+
+    seg = BREAKUP;
+    if(len < BREAKUP)
+      seg = len;
+
+    /* temporarily NUL-terminate this segment */
+    tail = buf[seg];
+    buf[seg] = 0x00;
+
+    ret = send(conv, buf);
+
+    /* restore this segment */
+    buf[seg] = tail;
+    
+    buf += seg;
+    len -= seg;
+  }
+
+  if(! ret)
+    ret = convo_send_multi_stop(conv);
+
+  return ret;
+}
+
+
 static int convo_sendText(struct mwConversation *conv, const char *text) {
   struct mwPutBuffer *b;
   struct mwOpaque o;
@@ -780,58 +868,7 @@
   mwString_put(b, text);
 
   mwPutBuffer_finalize(&o, b);
-  ret = mwChannel_send(conv->channel, msg_MESSAGE, &o);
-  mwOpaque_clear(&o);
-
-  return ret;
-}
-
-
-static int convo_sendHtml(struct mwConversation *conv, const char *html) {
-  struct mwPutBuffer *b;
-  struct mwOpaque o;
-  int ret;
-  
-  b = mwPutBuffer_new();
-
-  guint32_put(b, mwIm_DATA);
-  guint32_put(b, mwImData_HTML);
-  guint32_put(b, 0x00);
-
-  /* use o first as a shell of an opaque for the text */
-  o.len = strlen(html);
-  o.data = (char *) html;
-  mwOpaque_put(b, &o);
-
-  /* use o again as the holder of the buffer's finalized data */
-  mwPutBuffer_finalize(&o, b);
-  ret = mwChannel_send(conv->channel, msg_MESSAGE, &o);
-  mwOpaque_clear(&o);
-
-  return ret;
-}
-
-
-static int convo_sendSubject(struct mwConversation *conv,
-			     const char *subject) {
-  struct mwPutBuffer *b;
-  struct mwOpaque o;
-  int ret;
-
-  b = mwPutBuffer_new();
-
-  guint32_put(b, mwIm_DATA);
-  guint32_put(b, mwImData_SUBJECT);
-  guint32_put(b, 0x00);
-
-  /* use o first as a shell of an opaque for the text */
-  o.len = strlen(subject);
-  o.data = (char *) subject;
-  mwOpaque_put(b, &o);
-
-  /* use o again as the holder of the buffer's finalized data */
-  mwPutBuffer_finalize(&o, b);
-  ret = mwChannel_send(conv->channel, msg_MESSAGE, &o);
+  ret = mwChannel_sendEncrypted(conv->channel, msg_MESSAGE, &o, !conv->ext_id);
   mwOpaque_clear(&o);
 
   return ret;
@@ -839,49 +876,46 @@
 
 
 static int convo_sendTyping(struct mwConversation *conv, gboolean typing) {
-  struct mwPutBuffer *b;
-  struct mwOpaque o = { 0, NULL };
-  int ret;
+  return convo_send_data(conv, mwImData_TYPING, !typing, NULL);
+}
 
-  b = mwPutBuffer_new();
-
-  guint32_put(b, mwIm_DATA);
-  guint32_put(b, mwImData_TYPING);
-  guint32_put(b, !typing);
 
-  /* not to be confusing, but we're re-using o first as an empty
-     opaque, and later as the contents of the finalized buffer */
-  mwOpaque_put(b, &o);
+static int convo_sendSubject(struct mwConversation *conv,
+			     const char *subject) {
+  struct mwOpaque o;
 
-  mwPutBuffer_finalize(&o, b);
-  ret = mwChannel_send(conv->channel, msg_MESSAGE, &o);
-  mwOpaque_clear(&o);
+  o.len = strlen(subject);
+  o.data = (char *) subject;
 
-  return ret;
+  return convo_send_data(conv, mwImData_SUBJECT, 0x00, &o);
 }
 
 
-static int convo_sendMime(struct mwConversation *conv,
-			  const char *mime) {
-  struct mwPutBuffer *b;
+static int convo_sendHtml(struct mwConversation *conv, const char *html) {
   struct mwOpaque o;
-  int ret;
+
+  o.len = strlen(html);
+  o.data = (char *) html;
 
-  b = mwPutBuffer_new();
+  if(o.len > BREAKUP) {
+    return convo_sendSegmented(conv, html, convo_sendHtml);
+  } else {
+    return convo_send_data(conv, mwImData_HTML, 0x00, &o);
+  }
+}
 
-  guint32_put(b, mwIm_DATA);
-  guint32_put(b, mwImData_MIME);
-  guint32_put(b, 0x00);
+
+static int convo_sendMime(struct mwConversation *conv, const char *mime) {
+  struct mwOpaque o;
 
   o.len = strlen(mime);
   o.data = (char *) mime;
-  mwOpaque_put(b, &o);
 
-  mwPutBuffer_finalize(&o, b);
-  ret = mwChannel_send(conv->channel, msg_MESSAGE, &o);
-  mwOpaque_clear(&o);
-
-  return ret;
+  if(o.len > BREAKUP) {
+    return convo_sendSegmented(conv, mime, convo_sendMime);
+  } else {
+    return convo_send_data(conv, mwImData_MIME, 0x00, &o);
+  }
 }
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/srvc_place.c	Wed Nov 02 03:39:03 2005 +0000
@@ -0,0 +1,1045 @@
+
+/*
+  Meanwhile - Unofficial Lotus Sametime Community Client Library
+  Copyright (C) 2004  Christopher (siege) O'Brien
+  
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Library General Public
+  License as published by the Free Software Foundation; either
+  version 2 of the License, or (at your option) any later version.
+  
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Library General Public License for more details.
+  
+  You should have received a copy of the GNU Library General Public
+  License along with this library; if not, write to the Free
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#include <glib.h>
+#include <glib/ghash.h>
+#include <glib/glist.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "mw_channel.h"
+#include "mw_common.h"
+#include "mw_debug.h"
+#include "mw_error.h"
+#include "mw_message.h"
+#include "mw_service.h"
+#include "mw_session.h"
+#include "mw_srvc_place.h"
+#include "mw_util.h"
+
+
+#define PROTOCOL_TYPE  0x00
+#define PROTOCOL_VER   0x05
+
+
+enum incoming_msg {
+  msg_in_JOIN_RESPONSE  = 0x0000,  /* ? */
+  msg_in_INFO           = 0x0002,
+  msg_in_MESSAGE        = 0x0004,
+  msg_in_SECTION        = 0x0014,  /* see in_section_subtype */
+  msg_in_UNKNOWNa       = 0x0015,
+};
+
+
+enum in_section_subtype {
+  msg_in_SECTION_LIST  = 0x0000,  /* list of section members */
+  msg_in_SECTION_PEER  = 0x0001,  /* see in_section_peer_subtye */
+  msg_in_SECTION_PART  = 0x0003,
+};
+
+
+enum in_section_peer_subtype {
+  msg_in_SECTION_PEER_JOIN        = 0x0000,
+  msg_in_SECTION_PEER_PART        = 0x0001,  /* after msg_in_SECTION_PART */
+  msg_in_SECTION_PEER_CLEAR_ATTR  = 0x0003,
+  msg_in_SECTION_PEER_SET_ATTR    = 0x0004,
+};
+
+
+enum outgoing_msg {
+  msg_out_JOIN_PLACE  = 0x0000,  /* ? */
+  msg_out_PEER_INFO   = 0x0002,  /* ? */
+  msg_out_MESSAGE     = 0x0003,
+  msg_out_OLD_INVITE  = 0x0005,  /* old-style conf. invitation */
+  msg_out_SET_ATTR    = 0x000a,
+  msg_out_CLEAR_ATTR  = 0x000b,
+  msg_out_SECTION     = 0x0014,  /* see out_section_subtype */
+  msg_out_UNKNOWNb    = 0x001e,  /* ? maybe enter stage ? */
+};
+
+
+enum out_section_subtype {
+  msg_out_SECTION_LIST  = 0x0002,  /* req list of members */
+  msg_out_SECTION_PART  = 0x0003,
+};
+
+
+/*
+  : allocate section
+  : state = NEW
+
+  : create channel
+  : state = PENDING
+
+  : channel accepted
+  : msg_out_JOIN_PLACE  (maybe create?)
+  : state = JOINING
+
+  : msg_in_JOIN_RESPONSE (contains our place member ID and section ID)
+  : msg_in_INFO (for place, not peer)
+  : msg_in_UNKNOWNa
+  : state = JOINED
+
+  : msg_out_SECTION_LIST (asking for all sections) (optional)
+  : msg_in_SECTION_LIST (listing all sections, as requested above)
+
+  : msg_out_PEER_INFO (with our place member ID) (optional)
+  : msg_in_INFO (peer info as requested above)
+
+  : msg_out_SECTION_LIST (with our section ID) (sorta optional)
+  : msg_in_SECTION_LIST (section listing as requested above)
+
+  : msg_out_UNKNOWNb
+  : msg_in_SECTION_PEER_JOINED (empty, with our place member ID)
+  : msg_in_UNKNOWNa
+  : state = OPEN
+
+  : stuff... (invites, joins, parts, messages, attr)
+
+  : state = CLOSING
+  : msg_out_SECTION_PART
+  : destroy channel
+  : deallocate section
+*/
+
+
+struct mwServicePlace {
+  struct mwService service;
+  struct mwPlaceHandler *handler;
+  GList *places;
+};
+
+
+enum mwPlaceState {
+  mwPlace_NEW,
+  mwPlace_PENDING,
+  mwPlace_JOINING,
+  mwPlace_JOINED,
+  mwPlace_OPEN,
+  mwPlace_CLOSING,
+  mwPlace_ERROR,
+  mwPlace_UNKNOWN,
+};
+
+
+struct mwPlace {
+  struct mwServicePlace *service;
+
+  enum mwPlaceState state;
+  struct mwChannel *channel;
+
+  char *name;
+  char *title;
+  GHashTable *members;  /* mapping of member ID: place_member */
+  guint32 our_id;       /* our member ID */
+  guint32 section;      /* the section we're using */
+
+  guint32 requests;     /* counter for requests */
+
+  struct mw_datum client_data;
+};
+
+
+struct place_member {
+  guint32 place_id;
+  guint16 member_type;
+  struct mwIdBlock idb;
+  char *login_id;
+  char *name;
+  guint16 login_type;
+  guint32 unknown_a;
+  guint32 unknown_b;
+};
+
+
+#define GET_MEMBER(place, id) \
+  (g_hash_table_lookup(place->members, GUINT_TO_POINTER(id)))
+
+
+#define PUT_MEMBER(place, member) \
+  (g_hash_table_insert(place->members, \
+                       GUINT_TO_POINTER(member->place_id), member))
+
+
+#define REMOVE_MEMBER_ID(place, id) \
+  (g_hash_table_remove(place->members, GUINT_TO_POINTER(id)))
+
+
+#define REMOVE_MEMBER(place, member) \
+  REMOVE_MEMBER_ID(place, member->place_id)
+
+
+static void member_free(struct place_member *p) {
+  mwIdBlock_clear(&p->idb);
+  g_free(p->login_id);
+  g_free(p->name);
+  g_free(p);
+}
+
+
+__attribute__((used))
+static const struct mwLoginInfo *
+member_as_login_info(struct place_member *p) {
+  static struct mwLoginInfo li;
+  
+  li.login_id = p->login_id;
+  li.type = p->login_type;
+  li.user_id = p->idb.user;
+  li.user_name = p->name;
+  li.community = p->idb.community;
+  li.full = FALSE;
+
+  return &li;
+}
+
+
+static const char *place_state_str(enum mwPlaceState s) {
+  switch(s) {
+  case mwPlace_NEW:      return "new";
+  case mwPlace_PENDING:  return "pending";
+  case mwPlace_JOINING:  return "joining";
+  case mwPlace_JOINED:   return "joined";
+  case mwPlace_OPEN:     return "open";
+  case mwPlace_CLOSING:  return "closing";
+  case mwPlace_ERROR:    return "error";
+
+  case mwPlace_UNKNOWN:  /* fall-through */
+  default:               return "UNKNOWN";
+  }
+}
+
+
+static void place_state(struct mwPlace *place, enum mwPlaceState s) {
+  g_return_if_fail(place != NULL);
+  
+  if(place->state == s) return;
+
+  place->state = s;
+  g_message("place %s state: %s", NSTR(place->name), place_state_str(s));
+}
+
+
+static void place_free(struct mwPlace *place) {
+  struct mwServicePlace *srvc;
+
+  if(! place) return;
+  
+  srvc = place->service;
+  g_return_if_fail(srvc != NULL);
+
+  srvc->places = g_list_remove_all(srvc->places, place);
+
+  mw_datum_clear(&place->client_data);
+
+  g_hash_table_destroy(place->members);
+
+  g_free(place->name);
+  g_free(place->title);
+  g_free(place);
+}
+
+
+static int recv_JOIN_RESPONSE(struct mwPlace *place,
+			      struct mwGetBuffer *b) {
+  
+  int ret = 0;
+  guint32 our_id, section;
+
+  guint32_get(b, &our_id);
+  guint32_get(b, &section);
+
+  place->our_id = our_id;
+  place->section = section;
+
+  return ret;
+}
+
+
+static int recv_INFO(struct mwPlace *place,
+		     struct mwGetBuffer *b) {
+
+  int ret = 0;
+  guint32 skip = 0;
+  guint32 section = 0;
+
+  guint32_get(b, &skip);
+  guint32_get(b, &section);
+  mwGetBuffer_advance(b, skip);
+
+  if(! section) {
+    /* this is a place info rather than member info */
+    if(place->title) g_free(place->title);
+    mwGetBuffer_advance(b, 2);
+    mwString_get(b, &place->title);
+  }
+
+  return ret;
+}
+
+
+static int recv_MESSAGE(struct mwPlace *place,
+			struct mwGetBuffer *b) {
+
+  struct mwServicePlace *srvc;
+  guint32 pm_id;
+  guint32 unkn_a, unkn_b, ign;
+  struct place_member *pm;
+  char *msg = NULL;
+  int ret = 0;
+
+  srvc = place->service;
+
+  /* regarding unkn_a and unkn_b:
+
+     they're probably a section indicator and a message count, I'm
+     just not sure which is which. Until this implementation supports
+     place sections in the API, it really doesn't matter. */
+  
+  guint32_get(b, &pm_id);
+  pm = GET_MEMBER(place, pm_id);
+  g_return_val_if_fail(pm != NULL, -1);
+
+  guint32_get(b, &unkn_a);
+  guint32_get(b, &ign);     /* actually an opaque length */
+  
+  if(! ign) return ret;
+
+  guint32_get(b, &unkn_b);
+  mwString_get(b, &msg);
+
+  if(srvc->handler && srvc->handler->message)
+    srvc->handler->message(place, &pm->idb, msg);
+
+  g_free(msg);
+
+  return ret;
+}
+
+
+static int recv_SECTION_PEER_JOIN(struct mwPlace *place,
+				  struct mwGetBuffer *b) {
+  struct mwServicePlace *srvc;
+  struct place_member *pm;
+  guint32 section;
+  int ret = 0;
+
+  srvc = place->service;
+
+  guint32_get(b, &section);
+  if(! section) {
+    g_info("SECTION_PEER_JOIN with section 0x00");
+    return 0;
+  }
+
+  mwGetBuffer_advance(b, 4);
+
+  pm = g_new0(struct place_member, 1);
+  guint32_get(b, &pm->place_id);
+  guint16_get(b, &pm->member_type);
+  mwIdBlock_get(b, &pm->idb);
+  mwString_get(b, &pm->login_id);
+  mwString_get(b, &pm->name);
+  guint16_get(b, &pm->login_type);
+  guint32_get(b, &pm->unknown_a);
+  guint32_get(b, &pm->unknown_b);
+
+  PUT_MEMBER(place, pm);
+  if(srvc->handler && srvc->handler->peerJoined)
+    srvc->handler->peerJoined(place, &pm->idb);
+
+  return ret;
+}
+
+
+static int recv_SECTION_PEER_PART(struct mwPlace *place,
+				  struct mwGetBuffer *b) {
+  struct mwServicePlace *srvc;
+  int ret = 0;
+  guint32 section, id;
+  struct place_member *pm;
+
+  srvc = place->service;
+
+  guint32_get(b, &section);
+  g_return_val_if_fail(section == place->section, 0);
+
+  guint32_get(b, &id);
+  pm = GET_MEMBER(place, id);
+
+  /* SECTION_PART may have been called already */
+  if(! pm) return 0;
+
+  if(srvc->handler && srvc->handler->peerParted)
+    srvc->handler->peerParted(place, &pm->idb);
+
+  REMOVE_MEMBER(place, pm);
+
+  return ret;
+}
+
+
+static int recv_SECTION_PEER_CLEAR_ATTR(struct mwPlace *place,
+					struct mwGetBuffer *b) {
+  struct mwServicePlace *srvc;
+  int ret = 0;
+  guint32 id, attr;
+  struct place_member *pm;
+  
+  srvc = place->service;
+
+  guint32_get(b, &id);
+  guint32_get(b, &attr);
+
+  pm = GET_MEMBER(place, id);
+  g_return_val_if_fail(pm != NULL, -1);
+
+  if(srvc->handler && srvc->handler->peerUnsetAttribute)
+    srvc->handler->peerUnsetAttribute(place, &pm->idb, attr);
+
+  return ret;
+}
+
+
+static int recv_SECTION_PEER_SET_ATTR(struct mwPlace *place,
+				      struct mwGetBuffer *b) {
+  struct mwServicePlace *srvc;
+  int ret = 0;
+  guint32 id, attr;
+  struct mwOpaque o = {0,0};
+  struct place_member *pm;
+  
+  srvc = place->service;
+
+  guint32_get(b, &id);
+  mwGetBuffer_advance(b, 4);
+  mwOpaque_get(b, &o);
+  mwGetBuffer_advance(b, 4);
+  guint32_get(b, &attr);
+
+  pm = GET_MEMBER(place, id);
+  g_return_val_if_fail(pm != NULL, -1);
+
+  if(srvc->handler && srvc->handler->peerSetAttribute)
+    srvc->handler->peerSetAttribute(place, &pm->idb, attr, &o);
+
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+static int recv_SECTION_PEER(struct mwPlace *place,
+			      struct mwGetBuffer *b) {
+  guint16 subtype;
+  int res;
+
+  guint16_get(b, &subtype);
+
+  g_return_val_if_fail(! mwGetBuffer_error(b), -1);
+
+  switch(subtype) {
+  case msg_in_SECTION_PEER_JOIN:
+    res = recv_SECTION_PEER_JOIN(place, b);
+    break;
+
+  case msg_in_SECTION_PEER_PART:
+    res = recv_SECTION_PEER_PART(place, b);
+    break;
+
+  case msg_in_SECTION_PEER_CLEAR_ATTR:
+    res = recv_SECTION_PEER_CLEAR_ATTR(place, b);
+    break;
+
+  case msg_in_SECTION_PEER_SET_ATTR:
+    res = recv_SECTION_PEER_SET_ATTR(place, b);
+    break;
+
+  default:
+    res = -1;
+  }
+
+  return res;
+}
+
+
+static void place_opened(struct mwPlace *place) {
+    struct mwServicePlace *srvc;
+
+    place_state(place, mwPlace_OPEN);
+
+    srvc = place->service;
+    if(srvc->handler && srvc->handler->opened)
+      srvc->handler->opened(place);
+}
+
+
+
+static int recv_SECTION_LIST(struct mwPlace *place,
+			     struct mwGetBuffer *b) {
+  int ret = 0;
+  guint32 sec, count;
+
+  mwGetBuffer_advance(b, 4);
+  guint32_get(b, &sec);
+
+  g_return_val_if_fail(sec == place->section, -1);
+
+  mwGetBuffer_advance(b, 8);
+  guint32_get(b, &count);
+  mwGetBuffer_advance(b, 8);
+
+  while(count--) {
+    struct place_member *m;
+
+    m = g_new0(struct place_member, 1);
+    mwGetBuffer_advance(b, 4);
+    guint32_get(b, &m->place_id);
+    guint16_get(b, &m->member_type);
+    mwIdBlock_get(b, &m->idb);
+    mwString_get(b, &m->login_id);
+    mwString_get(b, &m->name);
+    guint16_get(b, &m->login_type);
+    guint32_get(b, &m->unknown_a);
+    guint32_get(b, &m->unknown_b);
+
+    PUT_MEMBER(place, m);
+  }
+
+  if(place->state != mwPlace_OPEN)
+    place_opened(place);
+
+  return ret;
+}
+
+
+static int recv_SECTION_PART(struct mwPlace *place,
+			     struct mwGetBuffer *b) {
+  /* look up user in place
+     remove user from place
+     trigger event */
+
+  struct mwServicePlace *srvc;
+  guint32 pm_id;
+  struct place_member *pm;
+
+  srvc = place->service;
+
+  guint32_get(b, &pm_id);
+  pm = GET_MEMBER(place, pm_id);
+
+  /* SECTION_PEER_PART may have been called already */
+  if(! pm) return 0;
+
+  if(srvc->handler && srvc->handler->peerParted)
+    srvc->handler->peerParted(place, &pm->idb);
+
+  REMOVE_MEMBER(place, pm);
+
+  return 0;
+}
+
+
+static int recv_SECTION(struct mwPlace *place, struct mwGetBuffer *b) {
+  guint16 subtype;
+  int res;
+
+  guint16_get(b, &subtype);
+
+  g_return_val_if_fail(! mwGetBuffer_error(b), -1);
+
+  switch(subtype) {
+  case msg_in_SECTION_LIST:
+    res = recv_SECTION_LIST(place, b);
+    break;
+
+  case msg_in_SECTION_PEER:
+    res = recv_SECTION_PEER(place, b);
+    break;
+
+  case msg_in_SECTION_PART:
+    res = recv_SECTION_PART(place, b);
+    break;
+
+  default:
+    res = -1;
+  }
+
+  return res;
+}
+
+
+static int send_SECTION_LIST(struct mwPlace *place, guint32 section) {
+  int ret = 0;
+  struct mwOpaque o = {0, 0};
+  struct mwPutBuffer *b;
+
+  b = mwPutBuffer_new();
+  guint16_put(b, msg_out_SECTION_LIST);
+  guint32_put(b, section);
+  gboolean_put(b, FALSE);
+  guint32_put(b, ++place->requests);
+  mwPutBuffer_finalize(&o, b);
+
+  ret = mwChannel_send(place->channel, msg_out_SECTION, &o);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+static int recv_UNKNOWNa(struct mwPlace *place, struct mwGetBuffer *b) {
+  int res = 0;
+
+  if(place->state == mwPlace_JOINING) {
+    place_state(place, mwPlace_JOINED);
+    res = send_SECTION_LIST(place, place->section);
+  
+  } else if(place->state == mwPlace_JOINED) {
+    if(GET_MEMBER(place, place->our_id))
+      place_opened(place);
+  }
+
+  return res;
+}
+
+
+static void recv(struct mwService *service, struct mwChannel *chan,
+		 guint16 type, struct mwOpaque *data) {
+
+  struct mwPlace *place;
+  struct mwGetBuffer *b;
+  int res = 0;
+
+  place = mwChannel_getServiceData(chan);
+  g_return_if_fail(place != NULL);
+
+  b = mwGetBuffer_wrap(data);
+  switch(type) {
+  case msg_in_JOIN_RESPONSE:
+    res = recv_JOIN_RESPONSE(place, b);
+    break;
+
+  case msg_in_INFO:
+    res = recv_INFO(place, b);
+    break;
+
+  case msg_in_MESSAGE:
+    res = recv_MESSAGE(place, b);
+    break;
+
+  case msg_in_SECTION:
+    res = recv_SECTION(place, b);
+    break;
+
+  case msg_in_UNKNOWNa:
+    res = recv_UNKNOWNa(place, b);
+    break;
+
+  default:
+    mw_mailme_opaque(data, "Received unknown message type 0x%x on place %s",
+		     type, NSTR(place->name));
+  }
+
+  if(res) {
+    mw_mailme_opaque(data, "Troubling parsing message type 0x0x on place %s",
+		     type, NSTR(place->name));
+  }
+
+  mwGetBuffer_free(b);
+}
+
+
+static void stop(struct mwServicePlace *srvc) {
+  while(srvc->places)
+    mwPlace_destroy(srvc->places->data, ERR_SUCCESS);
+
+  mwService_stopped(MW_SERVICE(srvc));
+}
+
+
+static int send_JOIN_PLACE(struct mwPlace *place) {
+  struct mwOpaque o = {0, 0};
+  struct mwPutBuffer *b;
+  int ret;
+
+  b = mwPutBuffer_new();
+  gboolean_put(b, FALSE);
+  guint16_put(b, 0x01);
+  guint16_put(b, 0x02); /* 0x01 */
+  guint16_put(b, 0x01); /* 0x00 */
+
+  mwPutBuffer_finalize(&o, b);
+
+  ret = mwChannel_send(place->channel, msg_out_JOIN_PLACE, &o);
+
+  mwOpaque_clear(&o);
+
+  if(ret) {
+    place_state(place, mwPlace_ERROR);
+  } else {
+    place_state(place, mwPlace_JOINING);
+  }
+
+  return ret;
+}
+
+
+static void recv_channelAccept(struct mwService *service,
+			       struct mwChannel *chan,
+			       struct mwMsgChannelAccept *msg) {
+  struct mwServicePlace *srvc;
+  struct mwPlace *place;
+  int res;
+
+  srvc = (struct mwServicePlace *) service;
+  g_return_if_fail(srvc != NULL);
+
+  place = mwChannel_getServiceData(chan);
+  g_return_if_fail(place != NULL);
+
+  res = send_JOIN_PLACE(place);
+}
+
+
+static void recv_channelDestroy(struct mwService *service,
+				struct mwChannel *chan,
+				struct mwMsgChannelDestroy *msg) {
+  struct mwServicePlace *srvc;
+  struct mwPlace *place;
+
+  srvc = (struct mwServicePlace *) service;
+  g_return_if_fail(srvc != NULL);
+
+  place = mwChannel_getServiceData(chan);
+  g_return_if_fail(place != NULL);
+
+  place_state(place, mwPlace_ERROR);
+
+  place->channel = NULL;
+
+  if(srvc->handler && srvc->handler->closed)
+    srvc->handler->closed(place, msg->reason);  
+
+  mwPlace_destroy(place, msg->reason);
+}
+
+
+static void clear(struct mwServicePlace *srvc) {
+
+  if(srvc->handler && srvc->handler->clear)
+    srvc->handler->clear(srvc);
+
+  while(srvc->places)
+    place_free(srvc->places->data);
+}
+
+
+static const char *get_name(struct mwService *srvc) {
+  return "Places Conferencing";
+}
+
+
+static const char *get_desc(struct mwService *srvc) {
+  return "Barebones conferencing via Places";
+}
+
+
+struct mwServicePlace *
+mwServicePlace_new(struct mwSession *session,
+		   struct mwPlaceHandler *handler) {
+
+  struct mwServicePlace *srvc_place;
+  struct mwService *srvc;
+
+  g_return_val_if_fail(session != NULL, NULL);
+  g_return_val_if_fail(handler != NULL, NULL);
+
+  srvc_place = g_new0(struct mwServicePlace, 1);
+  srvc_place->handler = handler;
+
+  srvc = MW_SERVICE(srvc_place);
+  mwService_init(srvc, session, mwService_PLACE);
+  srvc->start = NULL;
+  srvc->stop = (mwService_funcStop) stop;
+  srvc->recv_create = NULL;
+  srvc->recv_accept = recv_channelAccept;
+  srvc->recv_destroy = recv_channelDestroy;
+  srvc->recv = recv;
+  srvc->clear = (mwService_funcClear) clear;
+  srvc->get_name = get_name;
+  srvc->get_desc = get_desc;
+
+  return srvc_place;
+}
+
+
+struct mwPlaceHandler *
+mwServicePlace_getHandler(struct mwServicePlace *srvc) {
+  g_return_val_if_fail(srvc != NULL, NULL);
+  return srvc->handler;
+}
+
+
+const GList *mwServicePlace_getPlaces(struct mwServicePlace *srvc) {
+  g_return_val_if_fail(srvc != NULL, NULL);
+  return srvc->places;
+}
+
+
+struct mwPlace *mwPlace_new(struct mwServicePlace *srvc,
+			    const char *name, const char *title) {
+  struct mwPlace *place;
+
+  g_return_val_if_fail(srvc != NULL, NULL);
+  
+  place = g_new0(struct mwPlace, 1);
+  place->service = srvc;
+  place->name = g_strdup(name);
+  place->title = g_strdup(title);
+  place->state = mwPlace_NEW;
+
+  place->members = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+					 NULL, (GDestroyNotify) member_free);
+
+  srvc->places = g_list_prepend(srvc->places, place);
+  
+  return place;
+}
+
+
+struct mwServicePlace *mwPlace_getService(struct mwPlace *place) {
+  g_return_val_if_fail(place != NULL, NULL);
+  return place->service;
+}
+
+
+static char *place_generate_name(const char *user) {
+  guint a, b;
+  char *ret;
+  
+  user = user? user: "meanwhile";
+
+  srand(clock() + rand());
+  a = ((rand() & 0xff) << 8) | (rand() & 0xff);
+  b = time(NULL);
+
+  ret = g_strdup_printf("%s(%08x,%04x)", user, b, a);
+  g_debug("generated random conference name: '%s'", ret);
+  return ret;
+}
+
+
+const char *mwPlace_getName(struct mwPlace *place) {
+  g_return_val_if_fail(place != NULL, NULL);
+
+  if(! place->name) {
+    struct mwSession *session;
+    struct mwLoginInfo *li;
+
+    session = mwService_getSession(MW_SERVICE(place->service));
+    li = mwSession_getLoginInfo(session);
+
+    place->name = place_generate_name(li? li->user_id: NULL);
+  }
+
+  return place->name;
+}
+
+
+static char *place_generate_title(const char *user) {
+  char *ret;
+  
+  user = user? user: "Meanwhile";
+  ret = g_strdup_printf("%s's Conference", user);
+  g_debug("generated conference title: %s", ret);
+
+  return ret;
+}
+
+
+const char *mwPlace_getTitle(struct mwPlace *place) {
+  g_return_val_if_fail(place != NULL, NULL);
+
+  if(! place->title) {
+    struct mwSession *session;
+    struct mwLoginInfo *li;
+
+    session = mwService_getSession(MW_SERVICE(place->service));
+    li = mwSession_getLoginInfo(session);
+
+    place->title = place_generate_title(li? li->user_name: NULL);
+  }
+
+  return place->title;
+}
+
+
+int mwPlace_open(struct mwPlace *p) {
+  struct mwSession *session;
+  struct mwChannelSet *cs;
+  struct mwChannel *chan;
+  struct mwPutBuffer *b;
+  int ret;
+
+  g_return_val_if_fail(p != NULL, -1);
+  g_return_val_if_fail(p->service != NULL, -1);
+
+  session = mwService_getSession(MW_SERVICE(p->service));
+  g_return_val_if_fail(session != NULL, -1);
+
+  cs = mwSession_getChannels(session);
+  g_return_val_if_fail(cs != NULL, -1);
+
+  chan = mwChannel_newOutgoing(cs);
+  mwChannel_setService(chan, MW_SERVICE(p->service));
+  mwChannel_setProtoType(chan, PROTOCOL_TYPE);
+  mwChannel_setProtoVer(chan, PROTOCOL_VER);
+
+  mwChannel_populateSupportedCipherInstances(chan);
+
+  b = mwPutBuffer_new();
+  mwString_put(b, mwPlace_getName(p));
+  mwString_put(b, mwPlace_getTitle(p));
+  guint32_put(b, 0x00); /* ? */
+
+  mwPutBuffer_finalize(mwChannel_getAddtlCreate(chan), b);
+
+  ret = mwChannel_create(chan);
+  if(ret) {
+    place_state(p, mwPlace_ERROR);
+  } else {
+    place_state(p, mwPlace_PENDING);
+    p->channel = chan;
+    mwChannel_setServiceData(chan, p, NULL);
+  }
+
+  return ret;
+}
+
+
+int mwPlace_destroy(struct mwPlace *p, guint32 code) {
+  int ret = 0;
+
+  place_state(p, mwPlace_CLOSING);
+
+  if(p->channel) {
+    ret = mwChannel_destroy(p->channel, code, NULL);
+    p->channel = NULL;
+  }
+
+  place_free(p);
+
+  return ret;
+}
+
+
+GList *mwPlace_getMembers(struct mwPlace *place) {
+  GList *l, *ll;
+
+  g_return_val_if_fail(place != NULL, NULL);
+  g_return_val_if_fail(place->members != NULL, NULL);
+
+  ll = map_collect_values(place->members);
+  for(l = ll; l; l = l->next) {
+    struct place_member *pm = l->data;
+    l->data = &pm->idb;
+    g_info("collected member %u: %s, %s", pm->place_id,
+	   NSTR(pm->idb.user), NSTR(pm->idb.community));
+  }
+
+  return ll;
+}
+
+
+int mwPlace_sendText(struct mwPlace *place, const char *msg) {
+  struct mwOpaque o = {0,0};
+  struct mwPutBuffer *b;
+  int ret;
+
+  b = mwPutBuffer_new();
+  guint32_put(b, 0x01);  /* probably a message type */
+  mwString_put(b, msg);
+  mwPutBuffer_finalize(&o, b);
+
+  b = mwPutBuffer_new();
+  guint32_put(b, place->section);
+  mwOpaque_put(b, &o);
+  mwOpaque_clear(&o);
+  mwPutBuffer_finalize(&o, b);
+
+  ret = mwChannel_send(place->channel, msg_out_MESSAGE, &o);
+  mwOpaque_clear(&o);
+  return ret;
+}
+
+
+int mwPlace_setAttribute(struct mwPlace *place, guint32 attrib,
+			 struct mwOpaque *data) {
+
+  struct mwOpaque o = {0,0};
+  struct mwPutBuffer *b;
+  int ret;
+
+  b = mwPutBuffer_new();
+  guint32_put(b, place->our_id);
+  guint32_put(b, 0x00);
+  guint32_put(b, attrib);
+  mwOpaque_put(b, data);
+  
+  ret = mwChannel_send(place->channel, msg_out_SET_ATTR, &o);
+  mwOpaque_clear(&o);
+  return ret;
+}
+
+
+int mwPlace_unsetAttribute(struct mwPlace *place, guint32 attrib) {
+  struct mwOpaque o = {0,0};
+  struct mwPutBuffer *b;
+  int ret;
+
+  b = mwPutBuffer_new();
+  guint32_put(b, place->our_id);
+  guint32_put(b, attrib);
+  
+  ret = mwChannel_send(place->channel, msg_out_SET_ATTR, &o);
+  mwOpaque_clear(&o);
+  return ret;
+}
+
+
+void mwPlace_setClientData(struct mwPlace *place,
+			   gpointer data, GDestroyNotify clear) {
+
+  g_return_if_fail(place != NULL);
+  mw_datum_set(&place->client_data, data, clear);
+}
+
+
+gpointer mwPlace_getClientData(struct mwPlace *place) {
+  g_return_val_if_fail(place != NULL, NULL);
+  return mw_datum_get(&place->client_data);
+}
+
+
+void mwPlace_removeClientData(struct mwPlace *place) {
+  g_return_if_fail(place != NULL);
+  mw_datum_clear(&place->client_data);
+}
--- a/src/protocols/sametime/meanwhile/srvc_resolve.c	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/srvc_resolve.c	Wed Nov 02 03:39:03 2005 +0000
@@ -275,7 +275,7 @@
   g_return_if_fail(data != NULL);
 
   if(type != RESOLVE_ACTION) {
-    mw_debug_mailme(data, "unknown message in resolve service: 0x%04x", type);
+    mw_mailme_opaque(data, "unknown message in resolve service: 0x%04x", type);
     return;
   }
 
@@ -322,7 +322,7 @@
 
   srvc = MW_SERVICE(srvc_resolve);
 
-  mwService_init(srvc, session, SERVICE_RESOLVE);
+  mwService_init(srvc, session, mwService_RESOLVE);
   srvc->get_name = get_name;
   srvc->get_desc = get_desc;
   srvc->recv_create = (mwService_funcRecvCreate) recv_create;
--- a/src/protocols/sametime/meanwhile/srvc_store.c	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/srvc_store.c	Wed Nov 02 03:39:03 2005 +0000
@@ -19,7 +19,6 @@
 */
 
 #include <glib/glist.h>
-#include <internal.h>
 
 #include "mw_channel.h"
 #include "mw_debug.h"
@@ -168,9 +167,9 @@
 
   struct mwStorageUnit *item = req->item;
 
-  g_message("storage request %s: key = 0x%x, result = 0x%x, length = %" G_GSIZE_FORMAT,
+  g_message("storage request %s: key = 0x%x, result = 0x%x, length = %u",
 	    action_str(req->action),
-	    item->key, req->result_code, item->data.len);
+	    item->key, req->result_code, (guint) item->data.len);
   
   if(req->cb)
     req->cb(srvc, req->result_code, item, req->data);
@@ -331,7 +330,7 @@
 }
 
 
-static void mwservice_recv(struct mwService *srvc, struct mwChannel *chan,
+static void recv(struct mwService *srvc, struct mwChannel *chan,
 		 guint16 type, struct mwOpaque *data) {
 
   /* process into results, trigger callbacks */
@@ -363,7 +362,7 @@
   request_get(b, req);
 
   if(mwGetBuffer_error(b)) {
-    mw_debug_mailme(data, "storage request 0x%x, type: 0x%x", id, type);
+    mw_mailme_opaque(data, "storage request 0x%x, type: 0x%x", id, type);
 
   } else {
     request_trigger(srvc_stor, req);
@@ -397,12 +396,12 @@
   srvc_store = g_new0(struct mwServiceStorage, 1);
   srvc = MW_SERVICE(srvc_store);
 
-  mwService_init(srvc, session, SERVICE_STORAGE);
+  mwService_init(srvc, session, mwService_STORAGE);
   srvc->get_name = get_name;
   srvc->get_desc = get_desc;
   srvc->recv_accept = recv_channelAccept;
   srvc->recv_destroy = recv_channelDestroy;
-  srvc->recv = mwservice_recv;
+  srvc->recv = recv;
   srvc->start = start;
   srvc->stop = stop;
   srvc->clear = clear;
--- a/src/protocols/sametime/meanwhile/st_list.c	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/meanwhile/st_list.c	Wed Nov 02 03:39:03 2005 +0000
@@ -569,15 +569,15 @@
   str_replace(name, ';', ' ');
 
   if(idb.user && *idb.user) {
-    int l = strlen(idb.user) - 3;
-    type = idb.user[l];
-    idb.user[l] = '\0';
+    char *tmp = strstr(idb.user, "::");
+    if(tmp--) {
+      type = *(tmp);
+      *tmp = '\0';
+    }
   }
 
   if(name && *name) {
-    char *tmp;
-
-    tmp = strrchr(name, ',');
+    char *tmp = strrchr(name, ',');
     if(tmp) {
       *tmp++ = '\0';
       if(*tmp) alias = tmp;
--- a/src/protocols/sametime/sametime.c	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/sametime.c	Wed Nov 02 03:39:03 2005 +0000
@@ -21,8 +21,17 @@
   USA.
 */
 
+
+/* system includes */
 #include <stdlib.h>
-
+#include <time.h>
+
+/* glib includes */
+#include <glib.h>
+#include <glib/ghash.h>
+#include <glib/glist.h>
+
+/* gaim includes */
 #include <internal.h>
 #include <gaim.h>
 #include <config.h>
@@ -42,10 +51,7 @@
 #include <util.h>
 #include <version.h>
 
-#include <glib.h>
-#include <glib/ghash.h>
-#include <glib/glist.h>
-
+/* meanwhile includes */
 #include <mw_cipher.h>
 #include <mw_common.h>
 #include <mw_error.h>
@@ -55,10 +61,12 @@
 #include <mw_srvc_conf.h>
 #include <mw_srvc_ft.h>
 #include <mw_srvc_im.h>
+#include <mw_srvc_place.h>
 #include <mw_srvc_resolve.h>
 #include <mw_srvc_store.h>
 #include <mw_st_list.h>
 
+/* project includes */
 #include "sametime.h"
 
 
@@ -83,26 +91,24 @@
 
 
 /* stages of connecting-ness */
-#define MW_CONNECT_STEPS  10
+#define MW_CONNECT_STEPS  9
 
 
 /* stages of conciousness */
-#define MW_STATE_OFFLINE  "offline"
-#define MW_STATE_ACTIVE   "available"
-#define MW_STATE_AWAY     "away"
-#define MW_STATE_BUSY     "busy"
-#define MW_STATE_IDLE     "idle"
-#define MW_STATE_UNKNOWN  "unknown"
-#define MW_STATE_BUDHA    "enlightened"
-
-#define MW_STATE_MESSAGE  "message"
+#define MW_STATE_OFFLINE      "offline"
+#define MW_STATE_ACTIVE       "active"
+#define MW_STATE_AWAY         "away"
+#define MW_STATE_BUSY         "dnd"
+#define MW_STATE_MESSAGE      "message"
+#define MW_STATE_ENLIGHTENED  "buddha"
 
 
 /* keys to get/set chat information */
-#define CHAT_KEY_CREATOR  "chat.creator"
-#define CHAT_KEY_NAME     "chat.name"
-#define CHAT_KEY_TOPIC    "chat.topic"
-#define CHAT_KEY_INVITE   "chat.invite"
+#define CHAT_KEY_CREATOR   "chat.creator"
+#define CHAT_KEY_NAME      "chat.name"
+#define CHAT_KEY_TOPIC     "chat.topic"
+#define CHAT_KEY_INVITE    "chat.invite"
+#define CHAT_KEY_IS_PLACE  "chat.is_place"
 
 
 /* key for associating a mwLoginType with a buddy */
@@ -140,6 +146,8 @@
 #define MW_KEY_BUSY_MSG    "busy_msg"
 #define MW_KEY_MSG_PROMPT  "msg_prompt"
 #define MW_KEY_INVITE      "conf_invite"
+#define MW_KEY_ENCODING    "encoding"
+#define MW_KEY_FORCE       "force_login"
 
 
 /** number of seconds from the first blist change before a save to the
@@ -147,28 +155,25 @@
 #define BLIST_SAVE_SECONDS  15
 
 
-/** blist storage option, local only */
-#define BLIST_CHOICE_NONE    1
-
-/** blist storage option, load from server */
-#define BLIST_CHOICE_LOAD    2
-
-/** blist storage option, load and save to server */
-#define BLIST_CHOICE_SAVE    3
-
-/** blist storage option, server only */
-#define BLIST_CHOICE_SERVER  4
+/** the possible buddy list storage settings */
+enum blist_choice {
+  blist_choice_LOCAL = 1, /**< local only */
+  blist_choice_MERGE = 2, /**< merge from server */
+  blist_choice_STORE = 3, /**< merge from and save to server */
+  blist_choice_SYNCH = 4, /**< sync with server */
+};
 
 
 /** the default blist storage option */
-#define BLIST_CHOICE_DEFAULT BLIST_CHOICE_SAVE
+#define BLIST_CHOICE_DEFAULT  blist_choice_SYNCH
 
 
 /* testing for the above */
-#define BLIST_CHOICE_IS(n) (gaim_prefs_get_int(MW_PRPL_OPT_BLIST_ACTION)==(n))
-#define BLIST_CHOICE_IS_NONE() BLIST_CHOICE_IS(BLIST_CHOICE_NONE)
-#define BLIST_CHOICE_IS_LOAD() BLIST_CHOICE_IS(BLIST_CHOICE_LOAD)
-#define BLIST_CHOICE_IS_SAVE() BLIST_CHOICE_IS(BLIST_CHOICE_SAVE)
+#define BLIST_PREF_IS(n) (gaim_prefs_get_int(MW_PRPL_OPT_BLIST_ACTION)==(n))
+#define BLIST_PREF_IS_LOCAL()  BLIST_PREF_IS(blist_choice_LOCAL)
+#define BLIST_PREF_IS_MERGE()  BLIST_PREF_IS(blist_choice_MERGE)
+#define BLIST_PREF_IS_STORE()  BLIST_PREF_IS(blist_choice_STORE)
+#define BLIST_PREF_IS_SYNCH()  BLIST_PREF_IS(blist_choice_SYNCH)
 
 
 /* debugging output */
@@ -206,6 +211,7 @@
   struct mwServiceConference *srvc_conf;
   struct mwServiceFileTransfer *srvc_ft;
   struct mwServiceIm *srvc_im;
+  struct mwServicePlace *srvc_place;
   struct mwServiceResolve *srvc_resolve;
   struct mwServiceStorage *srvc_store;
 
@@ -230,7 +236,11 @@
 
 static void blist_schedule(struct mwGaimPluginData *pd);
 
-static void blist_import(GaimConnection *gc, struct mwSametimeList *stlist);
+static void blist_merge(GaimConnection *gc, struct mwSametimeList *stlist);
+
+static void blist_sync(GaimConnection *gc, struct mwSametimeList *stlist);
+
+static gboolean buddy_is_external(GaimBuddy *b);
 
 static void buddy_add(struct mwGaimPluginData *pd, GaimBuddy *buddy);
 
@@ -291,6 +301,15 @@
   char *name;
 };
 
+static struct resolved_id *resolved_id_new(const char *id, const char *name);
+
+static void resolved_id_free(struct resolved_id *rid);
+
+
+/* connection functions */
+
+static void connect_cb(gpointer data, gint source, GaimInputCondition cond);
+
 
 /* ----- session ------ */
 
@@ -340,7 +359,7 @@
 
   if(len > 0) {
     DEBUG_ERROR("write returned %i, %i bytes left unwritten\n", ret, len);
-    gaim_connection_error(pd->gc, "Connection closed (writing)");
+    gaim_connection_error(pd->gc, _("Connection closed (writing)"));
 
 #if 0
     close(pd->socket);
@@ -408,31 +427,51 @@
 
   GaimConnection *gc;
   GaimAccount *acct;
+    
   struct mwGaimPluginData *pd;
-  const char *status_id = MW_STATE_ACTIVE;
-  gboolean idle = FALSE;
-
-  guint stat = aware->status.status;
-
-  const char *id = aware->id.user;
+  time_t idle;
+  guint stat;
+  const char *id;
+  const char *status = MW_STATE_ACTIVE;
 
   gc = mwAwareList_getClientData(list);
+  acct = gaim_connection_get_account(gc);
+
   pd = gc->proto_data;
-  acct = gaim_connection_get_account(gc);
-  
+  idle = aware->status.time;
+  stat = aware->status.status;
+  id = aware->id.user;
+
+  /* not sure which client sends this yet */
+  if(idle == 0xdeadbeef) {
+    /* knock knock!
+       who's there?
+       rude interrupting cow.
+       rude interr...
+       MOO! */
+    idle = -1;
+  }
+
   switch(stat) {
+  case mwStatus_ACTIVE:
+    status = MW_STATE_ACTIVE;
+    idle = 0;
+    break;
+
   case mwStatus_IDLE:
-    idle = TRUE;
+    if(! idle) idle = -1;
     break;
     
   case mwStatus_AWAY:
-  	status_id = MW_STATE_AWAY;
-	break;
+    status = MW_STATE_AWAY;
+    break;
+    
   case mwStatus_BUSY:
-    status_id = MW_STATE_BUSY;
+    status = MW_STATE_BUSY;
     break;
   }
   
+  /* NAB group members */
   if(aware->group) {
     GaimGroup *group;
     GaimBuddy *buddy;
@@ -451,28 +490,19 @@
 
       bnode = (GaimBlistNode *) buddy;
 
-      /* mark buddy as transient if preferences do not indicate that
-	 we should save the buddy between gaim sessions */
-      if(! gaim_prefs_get_bool(MW_PRPL_OPT_SAVE_DYNAMIC))
-	bnode->flags |= GAIM_BLIST_NODE_FLAG_NO_SAVE;
-
       srvc = pd->srvc_resolve;
       query = g_list_append(NULL, (char *) id);
 
       mwServiceResolve_resolve(srvc, query, mwResolveFlag_USERS,
 			       blist_resolve_alias_cb, buddy, NULL);
+      g_list_free(query);
     }
 
     gaim_blist_node_set_int(bnode, BUDDY_KEY_TYPE, mwSametimeUser_NORMAL);
   }
   
-  gaim_prpl_got_user_status(acct, id, status_id, NULL);
-  gaim_prpl_got_user_login_time(acct, id, aware->online - time(NULL));
-
-  if (idle)
-    gaim_prpl_got_user_idle(acct, id, TRUE, -1);
-  else
-    gaim_prpl_got_user_idle(acct, id, FALSE, 0);
+  gaim_prpl_got_user_status(acct, id, status, NULL);
+  gaim_prpl_got_user_idle(acct, id, !!idle, idle);
 }
 
 
@@ -633,17 +663,19 @@
 
   gc = pd->gc;
 
-  /* check if we should do this, according to user prefs */
-  if(! BLIST_CHOICE_IS_SAVE()) {
+  if(BLIST_PREF_IS_LOCAL() || BLIST_PREF_IS_MERGE()) {
     DEBUG_INFO("preferences indicate not to save remote blist\n");
     return;
 
   } else if(MW_SERVICE_IS_DEAD(srvc)) {
     DEBUG_INFO("aborting save of blist: storage service is not alive\n");
     return;
-  
+
+  } else if(BLIST_PREF_IS_STORE() || BLIST_PREF_IS_SYNCH()) {
+    DEBUG_INFO("saving remote blist\n");
+
   } else {
-    DEBUG_INFO("saving remote blist\n");
+    g_return_if_reached();
   }
 
   /* create and export to a list object */
@@ -683,6 +715,12 @@
 }
 
 
+static gboolean buddy_is_external(GaimBuddy *b) {
+  g_return_val_if_fail(b != NULL, FALSE);
+  return g_str_has_prefix(b->name, "@E ");
+}
+
+
 /** Actually add a buddy to the aware service, and schedule the buddy
     list to be saved to the server */
 static void buddy_add(struct mwGaimPluginData *pd,
@@ -802,7 +840,7 @@
 
 
 /** merge the entries from a st list into the gaim blist */
-static void blist_import(GaimConnection *gc, struct mwSametimeList *stlist) {
+static void blist_merge(GaimConnection *gc, struct mwSametimeList *stlist) {
   struct mwSametimeGroup *stgroup;
   struct mwSametimeUser *stuser;
 
@@ -829,6 +867,214 @@
 }
 
 
+/** remove all buddies on account from group. If del is TRUE and group
+    is left empty, remove group as well */
+static void group_clear(GaimGroup *group, GaimAccount *acct, gboolean del) {
+  GaimConnection *gc;
+  GList *prune = NULL;
+  GaimBlistNode *gn, *cn, *bn;
+
+  g_return_if_fail(group != NULL);
+
+  DEBUG_INFO("clearing members from pruned group %s\n", NSTR(group->name));
+
+  gc = gaim_account_get_connection(acct);
+  g_return_if_fail(gc != NULL);
+
+  gn = (GaimBlistNode *) group;
+
+  for(cn = gn->child; cn; cn = cn->next) {
+    if(! GAIM_BLIST_NODE_IS_CONTACT(cn)) continue;
+
+    for(bn = cn->child; bn; bn = bn->next) {
+      GaimBuddy *gb = (GaimBuddy *) bn;
+
+      if(! GAIM_BLIST_NODE_IS_BUDDY(bn)) continue;
+      
+      if(gb->account == acct) {
+	DEBUG_INFO("clearing %s from group\n", NSTR(gb->name));
+	prune = g_list_prepend(prune, gb);
+      }
+    }
+  }
+
+  /* quickly unsubscribe from presence for the entire group */
+  gaim_account_remove_group(acct, group);
+
+  /* remove blist entries that need to go */
+  while(prune) {
+    gaim_blist_remove_buddy(prune->data);
+    prune = g_list_delete_link(prune, prune);
+  }
+  DEBUG_INFO("cleared buddies\n");
+
+  /* optionally remove group from blist */
+  if(del && !gaim_blist_get_group_size(group, TRUE)) {
+    DEBUG_INFO("removing empty group\n");
+    gaim_blist_remove_group(group);
+  }
+}
+
+
+/** prune out group members that shouldn't be there */
+static void group_prune(GaimConnection *gc, GaimGroup *group,
+			struct mwSametimeGroup *stgroup) {
+
+  GaimAccount *acct;
+  GaimBlistNode *gn, *cn, *bn;
+  
+  GHashTable *stusers;
+  GList *prune = NULL;
+  GList *ul, *utl;
+
+  g_return_if_fail(group != NULL);
+
+  DEBUG_INFO("pruning membership of group %s\n", NSTR(group->name));
+
+  acct = gaim_connection_get_account(gc);
+  g_return_if_fail(acct != NULL);
+
+  stusers = g_hash_table_new(g_str_hash, g_str_equal);
+  
+  /* build a hash table for quick lookup while pruning the group
+     contents */
+  utl = mwSametimeGroup_getUsers(stgroup);
+  for(ul = utl; ul; ul = ul->next) {
+    const char *id = mwSametimeUser_getUser(ul->data);
+    g_hash_table_insert(stusers, (char *) id, ul->data);
+    DEBUG_INFO("server copy has %s\n", NSTR(id));
+  }
+  g_list_free(utl);
+
+  gn = (GaimBlistNode *) group;
+
+  for(cn = gn->child; cn; cn = cn->next) {
+    if(! GAIM_BLIST_NODE_IS_CONTACT(cn)) continue;
+
+    for(bn = cn->child; bn; bn = bn->next) {
+      GaimBuddy *gb = (GaimBuddy *) bn;
+
+      if(! GAIM_BLIST_NODE_IS_BUDDY(bn)) continue;
+
+      /* if the account is correct and they're not in our table, mark
+	 them for pruning */
+      if(gb->account == acct && !g_hash_table_lookup(stusers, gb->name)) {
+	DEBUG_INFO("marking %s for pruning\n", NSTR(gb->name));
+	prune = g_list_prepend(prune, gb);
+      }
+    }
+  }
+  DEBUG_INFO("done marking\n");
+
+  g_hash_table_destroy(stusers);
+
+  if(prune) {
+    gaim_account_remove_buddies(acct, prune, NULL);
+    while(prune) {
+      gaim_blist_remove_buddy(prune->data);
+      prune = g_list_delete_link(prune, prune);
+    }
+  }
+}
+
+
+/** synch the entries from a st list into the gaim blist, removing any
+    existing buddies that aren't in the st list */
+static void blist_sync(GaimConnection *gc, struct mwSametimeList *stlist) {
+
+  GaimAccount *acct;
+  GaimBuddyList *blist;
+  GaimBlistNode *gn;
+
+  GHashTable *stgroups;
+  GList *g_prune = NULL;
+
+  GList *gl, *gtl;
+
+  const char *acct_n;
+
+  DEBUG_INFO("synchronizing local buddy list from server list\n");
+
+  acct = gaim_connection_get_account(gc);
+  g_return_if_fail(acct != NULL);
+
+  acct_n = gaim_account_get_username(acct);
+
+  blist = gaim_get_blist();
+  g_return_if_fail(blist != NULL);
+
+  /* build a hash table for quick lookup while pruning the local
+     list, mapping group name to group structure */
+  stgroups = g_hash_table_new(g_str_hash, g_str_equal);
+
+  gtl = mwSametimeList_getGroups(stlist);
+  for(gl = gtl; gl; gl = gl->next) {
+    const char *name = mwSametimeGroup_getName(gl->data);
+    g_hash_table_insert(stgroups, (char *) name, gl->data);
+  }
+  g_list_free(gtl);
+
+  /* find all groups which should be pruned from the local list */
+  for(gn = blist->root; gn; gn = gn->next) {
+    GaimGroup *grp = (GaimGroup *) gn;
+    const char *gname, *owner;
+    struct mwSametimeGroup *stgrp;
+
+    if(! GAIM_BLIST_NODE_IS_GROUP(gn)) continue;
+
+    /* group not belonging to this account */
+    if(! gaim_group_on_account(grp, acct))
+      continue;
+
+    /* dynamic group belonging to this account. don't prune contents */
+    owner = gaim_blist_node_get_string(gn, GROUP_KEY_OWNER);
+    if(owner && !strcmp(owner, acct_n))
+       continue;
+
+    /* we actually are synching by this key as opposed to the group
+       title, which can be different things in the st list */
+    gname = gaim_blist_node_get_string(gn, GROUP_KEY_NAME);
+    if(! gname) gname = grp->name;
+
+    stgrp = g_hash_table_lookup(stgroups, gname);
+    if(! stgrp) {
+      /* remove the whole group */
+      DEBUG_INFO("marking group %s for pruning\n", grp->name);
+      g_prune = g_list_prepend(g_prune, grp);
+
+    } else {
+      /* synch the group contents */
+      group_prune(gc, grp, stgrp);
+    }
+  }
+  DEBUG_INFO("done marking groups\n");
+
+  /* don't need this anymore */
+  g_hash_table_destroy(stgroups);
+
+  /* prune all marked groups */
+  while(g_prune) {
+    GaimGroup *grp = g_prune->data;
+    GaimBlistNode *gn = (GaimBlistNode *) grp;
+    const char *owner;
+    gboolean del = TRUE;
+
+    owner = gaim_blist_node_get_string(gn, GROUP_KEY_OWNER);
+    if(owner && strcmp(owner, acct_n)) {
+      /* it's a specialty group belonging to another account with some
+	 of our members in it, so don't fully delete it */
+      del = FALSE;
+    }
+    
+    group_clear(g_prune->data, acct, del);
+    g_prune = g_list_delete_link(g_prune, g_prune);
+  }
+
+  /* done with the pruning, let's merge in the additions */
+  blist_merge(gc, stlist);
+}
+
+
 /** callback passed to the storage service when it's told to load the
     st list */
 static void fetch_blist_cb(struct mwServiceStorage *srvc,
@@ -844,7 +1090,7 @@
   g_return_if_fail(result == ERR_SUCCESS);
 
   /* check our preferences for loading */
-  if(BLIST_CHOICE_IS_NONE()) {
+  if(BLIST_PREF_IS_LOCAL()) {
     DEBUG_INFO("preferences indicate not to load remote buddy list\n");
     return;
   }
@@ -855,7 +1101,14 @@
   mwSametimeList_get(b, stlist);
 
   s = mwService_getSession(MW_SERVICE(srvc));
-  blist_import(pd->gc, stlist);
+
+  /* merge or synch depending on preferences */
+  if(BLIST_PREF_IS_MERGE() || BLIST_PREF_IS_STORE()) {
+    blist_merge(pd->gc, stlist);
+
+  } else if(BLIST_PREF_IS_SYNCH()) {
+    blist_sync(pd->gc, stlist);
+  }
 
   mwSametimeList_free(stlist);
 }
@@ -928,7 +1181,7 @@
   msg = NULL;
 
 #if 0
-  /* XXX */
+  /* XXX resets the status, thus updating the message */
   if(!gc->away_state || !strcmp(gc->away_state, MW_STATE_ACTIVE)) {
     msg = MW_STATE_ACTIVE;
   } else if(gc->away_state && !strcmp(gc->away_state, MW_STATE_AWAY)) {
@@ -937,8 +1190,7 @@
     msg = MW_STATE_BUSY;
   }
 
-  if(msg)
-    serv_set_away(gc, msg, NULL);
+  if(msg) serv_set_away(gc, msg, NULL);
 #endif
 }
 
@@ -954,7 +1206,7 @@
      channel to the other side and figure out what the target can
      handle. Unfortunately, this makes us vulnerable to Psychic Mode,
      whereas a more lazy negotiation based on the first message
-     isn't */
+     would not */
 
   GaimConnection *gc;
   struct mwIdBlock who = { 0, 0 };
@@ -997,12 +1249,12 @@
 
   tmp = (char *) gaim_blist_node_get_string(node, GROUP_KEY_NAME);
 
-  g_string_append_printf(str, "<b>Group Title:</b> %s<br>", group->name);
-  g_string_append_printf(str, "<b>Notes Group ID:</b> %s<br>", tmp);
-
-  tmp = g_strdup_printf("Info for Group %s", group->name);
-
-  gaim_notify_formatted(gc, tmp, "Notes Address Book Information",
+  g_string_append_printf(str, _("<b>Group Title:</b> %s<br>"), group->name);
+  g_string_append_printf(str, _("<b>Notes Group ID:</b> %s<br>"), tmp);
+
+  tmp = g_strdup_printf(_("Info for Group %s"), group->name);
+
+  gaim_notify_formatted(gc, tmp, _("Notes Address Book Information"),
 			NULL, str->str, NULL, NULL);
 
   g_free(tmp);
@@ -1029,7 +1281,7 @@
     if(! gaim_account_is_connected(acct)) return;
     if(acct != gaim_connection_get_account(pd->gc)) return;
 
-    act = gaim_blist_node_action_new("Get Notes Address Book Info",
+    act = gaim_blist_node_action_new(_("Get Notes Address Book Info"),
 				     blist_menu_nab, pd, NULL);
 
     *menu = g_list_append(*menu, NULL);
@@ -1108,6 +1360,26 @@
 }
 
 
+static void session_loginRedirect(struct mwSession *session,
+				  const char *host) {
+  struct mwGaimPluginData *pd;
+  GaimConnection *gc;
+  GaimAccount *account;
+  guint port;
+
+  pd = mwSession_getClientData(session);
+  gc = pd->gc;
+  account = gaim_connection_get_account(gc);
+  port = gaim_account_get_int(account, MW_KEY_PORT, MW_PLUGIN_DEFAULT_PORT);
+
+  if(gaim_account_get_bool(account, MW_KEY_FORCE, FALSE) ||
+     gaim_proxy_connect(account, host, port, connect_cb, pd)) {
+
+    mwSession_forceLogin(session);
+  }
+}
+
+
 /** called from mw_session_stateChange when the session's state is
     mwSession_STARTED. Any finalizing of start-up stuff should go
     here */
@@ -1121,7 +1393,8 @@
 
 
 static void mw_session_stateChange(struct mwSession *session,
-				   enum mwSessionState state, guint32 info) {
+				   enum mwSessionState state,
+				   gpointer info) {
   struct mwGaimPluginData *pd;
   GaimConnection *gc;
   char *msg = NULL;
@@ -1153,6 +1426,7 @@
   case mwSession_LOGIN_REDIR:
     msg = _("Login Redirected");
     gaim_connection_update_progress(gc, msg, 6, MW_CONNECT_STEPS);
+    session_loginRedirect(session, info);
     break;
 
   case mwSession_LOGIN_CONT:
@@ -1168,14 +1442,13 @@
     msg = _("Connected to Sametime Community Server");
     gaim_connection_update_progress(gc, msg, 9, MW_CONNECT_STEPS);
     gaim_connection_set_state(gc, GAIM_CONNECTED);
-    /* XXX serv_finish_login(gc); */
 
     session_started(pd);
     break;
 
   case mwSession_STOPPING:
-    if(info & ERR_FAILURE) {
-      msg = mwError(info);
+    if(GPOINTER_TO_UINT(info) & ERR_FAILURE) {
+      msg = mwError(GPOINTER_TO_UINT(info));
       gaim_connection_error(gc, msg);
       g_free(msg);
     }
@@ -1287,6 +1560,8 @@
   struct mwGaimPluginData *pd = data;
   int ret = 0, err = 0;
 
+  /* How the heck can this happen? Fix submitted to Gaim so that it
+     won't happen anymore. */
   if(! cond) return;
 
   g_return_if_fail(pd != NULL);
@@ -1315,14 +1590,14 @@
 
   if(! ret) {
     DEBUG_INFO("connection reset\n");
-    gaim_connection_error(pd->gc, "Connection reset");
+    gaim_connection_error(pd->gc, _("Connection reset"));
 
   } else if(ret < 0) {
     char *msg = strerror(err);
 
     DEBUG_INFO("error in read callback: %s\n", msg);
 
-    msg = g_strdup_printf("Error reading from socket: %s", msg);
+    msg = g_strdup_printf(_("Error reading from socket: %s"), msg);
     gaim_connection_error(pd->gc, msg);
     g_free(msg);
   }
@@ -1351,7 +1626,7 @@
 
     } else {
       /* this is a regular connect, error out */
-      gaim_connection_error(pd->gc, "Unable to connect to host");
+      gaim_connection_error(pd->gc, _("Unable to connect to host"));
     }
 
     return;
@@ -1369,24 +1644,34 @@
 }
 
 
-static void mw_session_loginRedirect(struct mwSession *session,
-				     const char *host) {
-
+static void mw_session_announce(struct mwSession *s,
+				struct mwLoginInfo *from,
+				gboolean may_reply,
+				const char *text) {
   struct mwGaimPluginData *pd;
-  GaimConnection *gc;
-  GaimAccount *account;
-  guint port;
-
-  pd = mwSession_getClientData(session);
-  gc = pd->gc;
-  account = gaim_connection_get_account(gc);
-  port = gaim_account_get_int(account, "port", MW_PLUGIN_DEFAULT_PORT);
-
-  if(gaim_prefs_get_bool(MW_PRPL_OPT_FORCE_LOGIN) ||
-     gaim_proxy_connect(account, host, port, connect_cb, pd)) {
-
-    mwSession_forceLogin(session);
+  GaimAccount *acct;
+  GaimConversation *conv;
+  GSList *buddies;
+  char *who = from->user_id;
+  char *msg;
+  
+  pd = mwSession_getClientData(s);
+  acct = gaim_connection_get_account(pd->gc);
+  conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, who, acct);
+  if(! conv) conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, acct, who);
+
+  buddies = gaim_find_buddies(acct, who);
+  if(buddies) {
+    who = (char *) gaim_buddy_get_contact_alias(buddies->data);
+    g_slist_free(buddies);
   }
+
+  who = g_strdup_printf(_("Announcement from %s"), who);
+  msg = gaim_markup_linkify(text);
+
+  gaim_conversation_write(conv, who, msg, GAIM_MESSAGE_RECV, time(NULL));
+  g_free(who);
+  g_free(msg);
 }
 
 
@@ -1398,7 +1683,7 @@
   .on_setPrivacyInfo = mw_session_setPrivacyInfo,
   .on_setUserStatus = mw_session_setUserStatus,
   .on_admin = mw_session_admin,
-  .on_loginRedirect = mw_session_loginRedirect,
+  .on_announce = mw_session_announce,
 };
 
 
@@ -1552,7 +1837,7 @@
 
   serv_got_chat_left(gc, CONF_TO_ID(conf));
 
-  gaim_notify_error(gc, "Conference Closed", NULL, msg);
+  gaim_notify_error(gc, _("Conference Closed"), NULL, msg);
   g_free(msg);
 }
 
@@ -1971,7 +2256,8 @@
 
   idb = mwConversation_getTarget(conv);
 
-  return gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM,idb->user, acct);
+  return gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM,
+					     idb->user, acct);
 }
 
 
@@ -2012,13 +2298,13 @@
   idb = mwConversation_getTarget(conv);
   
   tmp = mwError(err);
-  text = g_strconcat("Unable to send message: ", tmp, NULL);
+  text = g_strconcat(_("Unable to send message: "), tmp, NULL);
   
   gconv = convo_get_gconv(conv);
   if(gconv && !gaim_conv_present_error(idb->user, gconv->account, text)) {
     
     g_free(text);
-    text = g_strdup_printf("Unable to send message to %s:",
+    text = g_strdup_printf(_("Unable to send message to %s:"),
 			   (idb->user)? idb->user: "(unknown)");
     gaim_notify_error(gaim_account_get_connection(gconv->account),
 		      NULL, text, tmp);
@@ -2099,6 +2385,7 @@
 }
 
 
+#if 0
 /** triggered from mw_conversation_opened if the appropriate plugin
     preference is set. This will open a window for the conversation
     before the first message is sent. */
@@ -2112,6 +2399,7 @@
   struct mwIdBlock *idb;
 
   GaimConversation *gconv;
+  GaimConvWindow *win;
 
   srvc = mwConversation_getService(conv);
   session = mwService_getSession(MW_SERVICE(srvc));
@@ -2121,22 +2409,20 @@
 
   idb = mwConversation_getTarget(conv);
 
-  gconv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, idb->user, acct);
+  gconv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM,
+					      idb->user, acct);
   if(! gconv) {
     gconv = gaim_conversation_new(GAIM_CONV_TYPE_IM, acct, idb->user);
   }
 
   g_return_if_fail(gconv != NULL);
 
-
-#if 0
-  GaimConvWindow *win;
   win = gaim_conversation_get_window(gconv);
   g_return_if_fail(win != NULL);
 
   gaim_conv_window_show(win);
+}
 #endif
-}
 
 
 static void mw_conversation_opened(struct mwConversation *conv) {
@@ -2167,9 +2453,11 @@
   } else {
     convo_data_new(conv);
 
+#if 0
     if(gaim_prefs_get_bool(MW_PRPL_OPT_PSYCHIC)) {
       convo_do_psychic(conv);
     }
+#endif
   }
 
   { /* record the client key for the buddy */
@@ -2195,7 +2483,7 @@
 
   g_return_if_fail(conv != NULL);
 
-  /* if there's a error code and a non-typing message in the queue,
+  /* if there's an error code and a non-typing message in the queue,
      print an error message to the conversation */
   cd = mwConversation_getClientData(conv);
   if(reason && cd && cd->queue) {
@@ -2220,21 +2508,29 @@
 }
 
 
+
+static char *im_decode(GaimConnection *gc, const char *msg) {
+  return gaim_utf8_try_convert(msg);
+}
+
+
 static void im_recv_text(struct mwConversation *conv,
 			 struct mwGaimPluginData *pd,
 			 const char *msg) {
 
   struct mwIdBlock *idb;
-  char *txt, *esc;
+  char *txt, *esc, *t;
 
   idb = mwConversation_getTarget(conv);
-  txt = gaim_utf8_try_convert(msg);
-  esc = g_markup_escape_text(txt, -1);
-
+  txt = im_decode(pd->gc, msg);
+
+  t = txt? txt: (char *) msg;
+
+  esc = g_markup_escape_text(t, -1);
   serv_got_im(pd->gc, idb->user, esc, 0, time(NULL));
+  g_free(esc);
 
   g_free(txt);
-  g_free(esc);
 }
 
 
@@ -2255,12 +2551,14 @@
 			 const char *msg) {
 
   struct mwIdBlock *idb;
-  char *txt;
+  char *txt, *t;
 
   idb = mwConversation_getTarget(conv);
-  txt = gaim_utf8_try_convert(msg);
+  txt = im_decode(pd->gc, msg);
   
-  serv_got_im(pd->gc, idb->user, txt, 0, time(NULL));
+  t = txt? txt: (char *) msg;
+
+  serv_got_im(pd->gc, idb->user, t, 0, time(NULL));
 
   g_free(txt);
 }
@@ -2365,16 +2663,17 @@
 
       gaim_mime_part_get_data_decoded(part, &data, &len);
 
-      txt = gaim_utf8_try_convert((const char *)data);
+      txt = im_decode(pd->gc, data);
+      g_string_append(str, txt?txt:(char *)data);
+
       g_free(data);
-
-      g_string_append(str, txt);
       g_free(txt);
     }
-  }
+  }  
 
   gaim_mime_document_free(doc);
 
+  /* @todo should put this in its own function */
   { /* replace each IMG tag's SRC attribute with an ID attribute. This
        actually modifies the contents of str */
     GData *attribs;
@@ -2469,8 +2768,6 @@
 }
 
 
-#if 0
-/* this will be appropriate when meanwhile supports the Place service */
 static void mw_place_invite(struct mwConversation *conv,
 			    const char *message,
 			    const char *title, const char *name) {
@@ -2492,10 +2789,13 @@
   g_hash_table_insert(ht, CHAT_KEY_NAME, g_strdup(name));
   g_hash_table_insert(ht, CHAT_KEY_TOPIC, g_strdup(title));
   g_hash_table_insert(ht, CHAT_KEY_INVITE, g_strdup(message));
+  g_hash_table_insert(ht, CHAT_KEY_IS_PLACE, g_strdup("")); /* ugh */
 
   serv_got_chat_invite(pd->gc, title, idb->user, message, ht);
-}
-#endif
+
+  mwConversation_close(conv, ERR_SUCCESS);
+  mwConversation_free(conv);
+}
 
 
 static void mw_im_clear(struct mwServiceIm *srvc) {
@@ -2507,7 +2807,7 @@
   .conversation_opened = mw_conversation_opened,
   .conversation_closed = mw_conversation_closed,
   .conversation_recv = mw_conversation_recv,
-  .place_invite = NULL, /* = mw_place_invite, */
+  .place_invite = mw_place_invite,
   .clear = mw_im_clear,
 };
 
@@ -2520,6 +2820,203 @@
 }
 
 
+/* The following helps us relate a mwPlace to a GaimConvChat in the
+   various forms by which either may be indicated. Uses some of
+   the similar macros from the conference service above */
+
+#define PLACE_TO_ID(place)   (GPOINTER_TO_INT(place))
+#define ID_TO_PLACE(pd, id)  (place_find_by_id((pd), (id)))
+
+#define CHAT_TO_PLACE(pd, chat)  (ID_TO_PLACE((pd), CHAT_TO_ID(chat)))
+#define PLACE_TO_CHAT(place)     (ID_TO_CHAT(PLACE_TO_ID(place)))
+
+
+static struct mwPlace *
+place_find_by_id(struct mwGaimPluginData *pd, int id) {
+  struct mwServicePlace *srvc = pd->srvc_place;
+  struct mwPlace *place = NULL;
+  GList *l;
+
+  l = (GList *) mwServicePlace_getPlaces(srvc);
+  for(; l; l = l->next) {
+    struct mwPlace *p = l->data;
+    GaimConvChat *h = GAIM_CONV_CHAT(mwPlace_getClientData(p));
+
+    if(CHAT_TO_ID(h) == id) {
+      place = p;
+      break;
+    }
+  }
+
+  return place;
+}
+
+
+static void mw_place_opened(struct mwPlace *place) {
+  struct mwServicePlace *srvc;
+  struct mwSession *session;
+  struct mwGaimPluginData *pd;
+  GaimConnection *gc;
+  GaimConversation *gconf;
+
+  GList *members, *l;
+
+  const char *n = mwPlace_getName(place);
+
+  srvc = mwPlace_getService(place);
+  session = mwService_getSession(MW_SERVICE(srvc));
+  pd = mwSession_getClientData(session);
+  gc = pd->gc;
+
+  members = mwPlace_getMembers(place);
+
+  DEBUG_INFO("place %s opened, %u initial members\n",
+	     NSTR(n), g_list_length(members));
+
+  gconf = serv_got_joined_chat(gc, PLACE_TO_ID(place),
+			       mwPlace_getTitle(place));
+
+  mwPlace_setClientData(place, gconf, NULL);
+
+  for(l = members; l; l = l->next) {
+    struct mwIdBlock *idb = l->data;
+    gaim_conv_chat_add_user(GAIM_CONV_CHAT(gconf), idb->user,
+			    NULL, GAIM_CBFLAGS_NONE, FALSE);
+  }
+  g_list_free(members);
+}
+
+
+static void mw_place_closed(struct mwPlace *place, guint32 code) {
+  struct mwServicePlace *srvc;
+  struct mwSession *session;
+  struct mwGaimPluginData *pd;
+  GaimConnection *gc;
+
+  const char *n = mwPlace_getName(place);
+  char *msg = mwError(code);
+
+  DEBUG_INFO("place %s closed, 0x%08x\n", NSTR(n), code);
+
+  srvc = mwPlace_getService(place);
+  session = mwService_getSession(MW_SERVICE(srvc));
+  pd = mwSession_getClientData(session);
+  gc = pd->gc;
+
+  serv_got_chat_left(gc, PLACE_TO_ID(place));
+
+  gaim_notify_error(gc, _("Place Closed"), NULL, msg);
+  g_free(msg);
+}
+
+
+static void mw_place_peerJoined(struct mwPlace *place,
+				const struct mwIdBlock *peer) {
+  struct mwServicePlace *srvc;
+  struct mwSession *session;
+  struct mwGaimPluginData *pd;
+  GaimConnection *gc;
+  GaimConversation *gconf;
+
+  const char *n = mwPlace_getName(place);
+
+  DEBUG_INFO("%s joined place %s\n", NSTR(peer->user), NSTR(n));
+
+  srvc = mwPlace_getService(place);
+  session = mwService_getSession(MW_SERVICE(srvc));
+  pd = mwSession_getClientData(session);
+  gc = pd->gc;
+
+  gconf = mwPlace_getClientData(place);
+  g_return_if_fail(gconf != NULL);
+
+  gaim_conv_chat_add_user(GAIM_CONV_CHAT(gconf), peer->user,
+			  NULL, GAIM_CBFLAGS_NONE, TRUE);
+}
+
+
+static void mw_place_peerParted(struct mwPlace *place,
+				const struct mwIdBlock *peer) {
+  struct mwServicePlace *srvc;
+  struct mwSession *session;
+  struct mwGaimPluginData *pd;
+  GaimConnection *gc;
+  GaimConversation *gconf;
+
+  const char *n = mwPlace_getName(place);
+
+  DEBUG_INFO("%s left place %s\n", NSTR(peer->user), NSTR(n));
+
+  srvc = mwPlace_getService(place);
+  session = mwService_getSession(MW_SERVICE(srvc));
+  pd = mwSession_getClientData(session);
+  gc = pd->gc;
+
+  gconf = mwPlace_getClientData(place);
+  g_return_if_fail(gconf != NULL);
+
+  gaim_conv_chat_remove_user(GAIM_CONV_CHAT(gconf), peer->user, NULL);
+}
+
+
+static void mw_place_peerSetAttribute(struct mwPlace *place,
+				      const struct mwIdBlock *peer,
+				      guint32 attr, struct mwOpaque *o) {
+  ;
+}
+
+
+static void mw_place_peerUnsetAttribute(struct mwPlace *place,
+					const struct mwIdBlock *peer,
+					guint32 attr) {
+  ;
+}
+
+
+static void mw_place_message(struct mwPlace *place,
+			     const struct mwIdBlock *who,
+			     const char *msg) {
+  struct mwServicePlace *srvc;
+  struct mwSession *session;
+  struct mwGaimPluginData *pd;
+  GaimConnection *gc;
+  char *esc;
+
+  srvc = mwPlace_getService(place);
+  session = mwService_getSession(MW_SERVICE(srvc));
+  pd = mwSession_getClientData(session);
+  gc = pd->gc;
+
+  esc = g_markup_escape_text(msg, -1);
+  serv_got_chat_in(gc, PLACE_TO_ID(place), who->user, 0, esc, time(NULL));
+  g_free(esc);
+}
+
+
+static void mw_place_clear(struct mwServicePlace *srvc) {
+  ;
+}
+
+
+static struct mwPlaceHandler mw_place_handler = {
+  .opened = mw_place_opened,
+  .closed = mw_place_closed,
+  .peerJoined = mw_place_peerJoined,
+  .peerParted = mw_place_peerParted,
+  .peerSetAttribute = mw_place_peerSetAttribute,
+  .peerUnsetAttribute = mw_place_peerUnsetAttribute,
+  .message = mw_place_message,
+  .clear = mw_place_clear,
+};
+
+
+static struct mwServicePlace *mw_srvc_place_new(struct mwSession *s) {
+  struct mwServicePlace *srvc;
+  srvc = mwServicePlace_new(s, &mw_place_handler);
+  return srvc;
+}
+
+
 static struct mwServiceResolve *mw_srvc_resolve_new(struct mwSession *s) {
   struct mwServiceResolve *srvc;
   srvc = mwServiceResolve_new(s);
@@ -2547,6 +3044,7 @@
   pd->srvc_conf = mw_srvc_conf_new(pd->session);
   pd->srvc_ft = mw_srvc_ft_new(pd->session);
   pd->srvc_im = mw_srvc_im_new(pd->session);
+  pd->srvc_place = mw_srvc_place_new(pd->session);
   pd->srvc_resolve = mw_srvc_resolve_new(pd->session);
   pd->srvc_store = mw_srvc_store_new(pd->session);
   pd->group_list_map = g_hash_table_new(g_direct_hash, g_direct_equal);
@@ -2555,10 +3053,12 @@
   mwSession_addService(pd->session, MW_SERVICE(pd->srvc_conf));
   mwSession_addService(pd->session, MW_SERVICE(pd->srvc_ft));
   mwSession_addService(pd->session, MW_SERVICE(pd->srvc_im));
+  mwSession_addService(pd->session, MW_SERVICE(pd->srvc_place));
   mwSession_addService(pd->session, MW_SERVICE(pd->srvc_resolve));
   mwSession_addService(pd->session, MW_SERVICE(pd->srvc_store));
 
   mwSession_addCipher(pd->session, mwCipher_new_RC2_40(pd->session));
+  mwSession_addCipher(pd->session, mwCipher_new_RC2_128(pd->session));
 
   mwSession_setClientData(pd->session, pd, NULL);
   gc->proto_data = pd;
@@ -2572,19 +3072,24 @@
 
   pd->gc->proto_data = NULL;
 
-  mwSession_removeService(pd->session, SERVICE_AWARE);
-  mwSession_removeService(pd->session, SERVICE_CONFERENCE);
-  mwSession_removeService(pd->session, SERVICE_IM);
-  mwSession_removeService(pd->session, SERVICE_RESOLVE);
-  mwSession_removeService(pd->session, SERVICE_STORAGE);
+  mwSession_removeService(pd->session, mwService_AWARE);
+  mwSession_removeService(pd->session, mwService_CONFERENCE);
+  mwSession_removeService(pd->session, mwService_FILE_TRANSFER);
+  mwSession_removeService(pd->session, mwService_IM);
+  mwSession_removeService(pd->session, mwService_PLACE);
+  mwSession_removeService(pd->session, mwService_RESOLVE);
+  mwSession_removeService(pd->session, mwService_STORAGE);
 
   mwService_free(MW_SERVICE(pd->srvc_aware));
   mwService_free(MW_SERVICE(pd->srvc_conf));
+  mwService_free(MW_SERVICE(pd->srvc_ft));
   mwService_free(MW_SERVICE(pd->srvc_im));
+  mwService_free(MW_SERVICE(pd->srvc_place));
   mwService_free(MW_SERVICE(pd->srvc_resolve));
   mwService_free(MW_SERVICE(pd->srvc_store));
 
   mwCipher_free(mwSession_getCipher(pd->session, mwCipher_RC2_40));
+  mwCipher_free(mwSession_getCipher(pd->session, mwCipher_RC2_128));
 
   mwSession_free(pd->session);
 
@@ -2613,17 +3118,29 @@
 static void mw_prpl_list_emblems(GaimBuddy *b,
 				 const char **se, const char **sw,
 				 const char **nw, const char **ne) {
-  GaimPresence *presence = gaim_buddy_get_presence(b);
-  GaimStatus *status = gaim_presence_get_active_status(presence);
+
+  /* speaking of custom icons, the external icon here is an ugly
+     little example of what happens when I use Gimp */
+
+  GaimPresence *presence;
+  GaimStatus *status;
+  const char *status_id;
+
+  presence = gaim_buddy_get_presence(b);
+  status = gaim_presence_get_active_status(presence);
+  status_id = gaim_status_get_id(status);
 
   if(! GAIM_BUDDY_IS_ONLINE(b)) {
     *se = "offline";
-  } else if(!gaim_presence_is_available(presence) &&
-            !strcmp(gaim_status_get_id(status), MW_STATE_AWAY)) {
+  } else if(!strcmp(status_id, MW_STATE_AWAY)) {
     *se = "away";
-  } else if(!gaim_presence_is_available(presence) &&
-            !strcmp(gaim_status_get_id(status), MW_STATE_BUSY)) {
+  } else if(!strcmp(status_id, MW_STATE_BUSY)) {
     *se = "dnd";
+  }  
+
+  if(buddy_is_external(b)) {
+    /* best assignment ever */
+    *(*se?sw:se) = "external";
   }
 }
 
@@ -2638,41 +3155,18 @@
   pd = gc->proto_data;
 
   ret = mwServiceAware_getText(pd->srvc_aware, &t);
-  return (ret)? g_strdup(ret): NULL;
+  return g_strdup(ret);
 }
 
 
 static const char *status_text(GaimBuddy *b) {
-  GaimPresence *presence = gaim_buddy_get_presence(b);
-  GaimStatus *status = gaim_presence_get_active_status(presence);
+  GaimPresence *presence;
+  GaimStatus *status;
+
+  presence = gaim_buddy_get_presence(b);
+  status = gaim_presence_get_active_status(presence);
 
   return gaim_status_get_name(status);
-
-  /* I left this here in case it's more accurate than the status name.
-   * Stu. */
-#if 0
-
-  guint status = b->uc;
-
-  if(! GAIM_BUDDY_IS_ONLINE(b) ) {
-    return MW_STATE_OFFLINE;
-
-  } else if(status == (mwStatus_AWAY /* XXX | UC_UNAVAILABLE */)) {
-    return MW_STATE_AWAY;
-
-  } else if(status == (mwStatus_BUSY /* XXX | UC_UNAVAILABLE */)) {
-    return MW_STATE_BUSY;
-
-  } else if(status == mwStatus_IDLE) {
-    return MW_STATE_IDLE;
-
-  } else if(status == mwStatus_ACTIVE) {
-    return MW_STATE_ACTIVE;
-
-  } else {
-    return MW_STATE_UNKNOWN;
-  }
-#endif
 }
 
 
@@ -2698,13 +3192,13 @@
       speak = user_supports(srvc, who, mwAttribute_SPEAKERS);
       video = user_supports(srvc, who, mwAttribute_VIDEO_CAMERA);
 
-      if(mic) *f++ = "Microphone";
-      if(speak) *f++ = "Speakers";
-      if(video) *f++ = "Video Camera";
+      if(mic) *f++ = _("Microphone");
+      if(speak) *f++ = _("Speakers");
+      if(video) *f++ = _("Video Camera");
     }
 
     if(user_supports(srvc, who, mwAttribute_FILE_TRANSFER))
-      *f++ = "File Transfer";
+      *f++ = _("File Transfer");
 
     return (*feat)? g_strjoinv(", ", feat): NULL;
     /* jenni loves siege */
@@ -2725,21 +3219,25 @@
   str = g_string_new(NULL);
 
   tmp = status_text(b);
-  g_string_append_printf(str, "\n<b>Status</b>: %s", tmp);
+  g_string_append_printf(str, _("\n<b>Status</b>: %s"), tmp);
 
   tmp = mwServiceAware_getText(pd->srvc_aware, &idb);
   if(tmp) {
     tmp = g_markup_escape_text(tmp, -1);
-    g_string_append_printf(str, "\n<b>Message</b>: %s", tmp);
+    g_string_append_printf(str, _("\n<b>Message</b>: %s"), tmp);
     g_free((char *) tmp);
   }
 
   tmp = user_supports_text(pd->srvc_aware, b->name);
   if(tmp) {
-    g_string_append_printf(str, "\n<b>Supports</b>: %s", tmp);
+    g_string_append_printf(str, _("\n<b>Supports</b>: %s"), tmp);
     g_free((char *) tmp);
   }
 
+  if(buddy_is_external(b)) {
+    g_string_append(str, _("\n<b>External User</b>"));
+  }
+
   tmp = str->str;
   g_string_free(str, FALSE);
   return (char *) tmp;
@@ -2765,20 +3263,20 @@
   gaim_status_type_add_attr(type, MW_STATE_MESSAGE, _("Message"),
 			    gaim_value_new(GAIM_TYPE_STRING));
   types = g_list_append(types, type);
-
+  
   type = gaim_status_type_new(GAIM_STATUS_UNAVAILABLE, MW_STATE_BUSY,
 			      _("Do Not Disturb"), TRUE);
   gaim_status_type_add_attr(type, MW_STATE_MESSAGE, _("Message"),
 			    gaim_value_new(GAIM_TYPE_STRING));
   types = g_list_append(types, type);
-
+  
   return types;
 }
 
 
 static void conf_create_prompt_cancel(GaimBuddy *buddy,
 				      GaimRequestFields *fields) {
-  ;
+  ; /* nothing to do */
 }
 
 
@@ -2838,21 +3336,21 @@
   g = gaim_request_field_group_new(NULL);
   gaim_request_fields_add_group(fields, g);
   
-  f = gaim_request_field_string_new(CHAT_KEY_TOPIC, "Topic", NULL, FALSE);
+  f = gaim_request_field_string_new(CHAT_KEY_TOPIC, _("Topic"), NULL, FALSE);
   gaim_request_field_group_add_field(g, f);
 
-  f = gaim_request_field_string_new(CHAT_KEY_INVITE, "Message", msg, FALSE);
+  f = gaim_request_field_string_new(CHAT_KEY_INVITE, _("Message"), msg, FALSE);
   gaim_request_field_group_add_field(g, f);
   
-  msgA = ("Create conference with user");
-  msgB = ("Please enter a topic for the new conference, and an invitation"
-	  " message to be sent to %s");
+  msgA = _("Create conference with user");
+  msgB = _("Please enter a topic for the new conference, and an invitation"
+	   " message to be sent to %s");
   msgB = g_strdup_printf(msgB, buddy->name);
 
-  gaim_request_fields(gc, "New Conference",
+  gaim_request_fields(gc, _("New Conference"),
 		      msgA, msgB, fields,
-		      "Create", G_CALLBACK(conf_create_prompt_join),
-		      "Cancel", G_CALLBACK(conf_create_prompt_cancel),
+		      _("Create"), G_CALLBACK(conf_create_prompt_join),
+		      _("Cancel"), G_CALLBACK(conf_create_prompt_cancel),
 		      buddy);
   g_free(msgB);
 }
@@ -2913,29 +3411,29 @@
   g = gaim_request_field_group_new(NULL);
   gaim_request_fields_add_group(fields, g);
 
-  f = gaim_request_field_list_new("conf", "Available Conferences");
+  f = gaim_request_field_list_new("conf", _("Available Conferences"));
   gaim_request_field_list_set_multi_select(f, FALSE);
   for(; confs; confs = confs->next) {
     struct mwConference *c = confs->data;
     gaim_request_field_list_add(f, mwConference_getTitle(c), c);
   }
-  gaim_request_field_list_add(f, "Create New Conference...",
+  gaim_request_field_list_add(f, _("Create New Conference..."),
 			      GINT_TO_POINTER(0x01));
   gaim_request_field_group_add_field(g, f);
   
   f = gaim_request_field_string_new(CHAT_KEY_INVITE, "Message", NULL, FALSE);
   gaim_request_field_group_add_field(g, f);
   
-  msgA = "Invite user to a conference";
-  msgB = ("Select a conference from the list below to send an invite to"
-	  " user %s. Select \"Create New Conference\" if you'd like to"
-	  " create a new conference to invite this user to.");
+  msgA = _("Invite user to a conference");
+  msgB = _("Select a conference from the list below to send an invite to"
+	   " user %s. Select \"Create New Conference\" if you'd like to"
+	   " create a new conference to invite this user to.");
   msgB = g_strdup_printf(msgB, buddy->name);
 
-  gaim_request_fields(gc, "Invite to Conference",
+  gaim_request_fields(gc, _("Invite to Conference"),
 		      msgA, msgB, fields,
-		      "Invite", G_CALLBACK(conf_select_prompt_invite),
-		      "Cancel", G_CALLBACK(conf_select_prompt_cancel),
+		      _("Invite"), G_CALLBACK(conf_select_prompt_invite),
+		      _("Cancel"), G_CALLBACK(conf_select_prompt_cancel),
 		      buddy);
   g_free(msgB);
 }
@@ -2986,7 +3484,7 @@
 
   l = g_list_append(l, NULL);
 
-  act = gaim_blist_node_action_new("Invite to Conference...",
+  act = gaim_blist_node_action_new(_("Invite to Conference..."),
 				   blist_menu_conf, NULL, NULL);
   l = g_list_append(l, act);
 
@@ -3004,7 +3502,7 @@
   struct proto_chat_entry *pce;
   
   pce = g_new0(struct proto_chat_entry, 1);
-  pce->label = "Topic:";
+  pce->label = _("Topic:");
   pce->identifier = CHAT_KEY_TOPIC;
   l = g_list_append(l, pce);
   
@@ -3032,17 +3530,14 @@
 
 
 static void prompt_host_cancel_cb(GaimConnection *gc) {
-  gaim_connection_error(gc, "No Sametime Community Server specified");
+  gaim_connection_error(gc, _("No Sametime Community Server specified"));
 }
 
 
 static void prompt_host_ok_cb(GaimConnection *gc, const char *host) {
   if(host && *host) {
-    GaimAccount *acct;
-
-    acct = gaim_connection_get_account(gc);
+    GaimAccount *acct = gaim_connection_get_account(gc);
     gaim_account_set_string(acct, MW_KEY_HOST, host);
-
     mw_prpl_login(acct);
 
   } else {
@@ -3056,16 +3551,16 @@
   char *msg;
   
   acct = gaim_connection_get_account(gc);
-  msg = ("No host or IP address has been configured for the"
-	 " Meanwhile account %s. Please enter one below to"
-	 " continue logging in.");
+  msg = _("No host or IP address has been configured for the"
+	  " Meanwhile account %s. Please enter one below to"
+	  " continue logging in.");
   msg = g_strdup_printf(msg, NSTR(gaim_account_get_username(acct)));
   
-  gaim_request_input(gc, "Meanwhile Connection Setup",
-		     "No Sametime Community Server Specified", msg,
+  gaim_request_input(gc, _("Meanwhile Connection Setup"),
+		     _("No Sametime Community Server Specified"), msg,
 		     MW_PLUGIN_DEFAULT_HOST, FALSE, FALSE, NULL,
-		     "Connect", G_CALLBACK(prompt_host_ok_cb),
-		     "Cancel", G_CALLBACK(prompt_host_cancel_cb),
+		     _("Connect"), G_CALLBACK(prompt_host_ok_cb),
+		     _("Cancel"), G_CALLBACK(prompt_host_cancel_cb),
 		     gc);
 
   g_free(msg);
@@ -3120,10 +3615,10 @@
   mwSession_setProperty(pd->session, mwSession_CLIENT_TYPE_ID,
 			GUINT_TO_POINTER(MW_CLIENT_TYPE_ID), NULL);
 
-  gaim_connection_update_progress(gc, "Connecting", 1, MW_CONNECT_STEPS);
+  gaim_connection_update_progress(gc, _("Connecting"), 1, MW_CONNECT_STEPS);
 
   if(gaim_proxy_connect(account, host, port, connect_cb, pd)) {
-    gaim_connection_error(gc, "Unable to connect to host");
+    gaim_connection_error(gc, _("Unable to connect to host"));
   }
 }
 
@@ -3177,6 +3672,8 @@
 }
 
 
+/** determine content type from extension. Not so happy about this,
+    but I don't want to actually write image type detection */
 static const char *im_mime_img_content_type(GaimStoredImage *img) {
   const char *fn = gaim_imgstore_get_filename(img);
 
@@ -3208,6 +3705,7 @@
 }
 
 
+/** turn an IM with embedded images into a multi-part mime document */
 static char *im_mime_convert(const char *message) {
   GString *str;
   GaimMimeDocument *doc;
@@ -3307,6 +3805,38 @@
 }
 
 
+static char *im_try_convert(const char *msg,
+			    const char *enc_to,
+			    const char *enc_from) {
+  char *ret;
+  GError *error = NULL;
+
+  ret = g_convert(msg, -1, enc_to, enc_from, NULL, NULL, &error);
+  if(error) {
+    /* if there's something that just won't convert, leave it as UTF-8 */
+    DEBUG_INFO("problem converting to %s, preserving %s: %s\n",
+	       NSTR(enc_to), NSTR(enc_from), NSTR(error->message));
+    g_error_free(error);
+  }
+
+  return ret;
+}
+
+
+static char *im_encode(GaimConnection *gc, const char *msg) {
+  GaimAccount *acct;
+  const char *enc;
+  
+  acct = gaim_connection_get_account(gc);
+  g_return_val_if_fail(acct != NULL, NULL);
+
+  enc = gaim_account_get_string(acct, MW_KEY_ENCODING,
+				MW_PLUGIN_DEFAULT_ENCODING);
+
+  return im_try_convert(msg, enc, "UTF-8");
+}
+
+
 static int mw_prpl_send_im(GaimConnection *gc,
 			   const char *name,
 			   const char *message,
@@ -3315,12 +3845,16 @@
   struct mwGaimPluginData *pd;
   struct mwIdBlock who = { (char *) name, NULL };
   struct mwConversation *conv;
+  char *msg = NULL;
 
   g_return_val_if_fail(gc != NULL, 0);
   pd = gc->proto_data;
 
   g_return_val_if_fail(pd != NULL, 0);
 
+  msg = im_encode(gc, message);
+  if(!msg) msg = g_strdup(message);
+
   conv = mwServiceIm_getConversation(pd->srvc_im, &who);
 
   /* this detection of features to determine how to send the message
@@ -3331,40 +3865,50 @@
      conversation will receive a plaintext message with html contents,
      which is bad. I'm not sure how to fix this correctly. */
 
-  if(strstr(message, "<img ") || strstr(message, "<IMG "))
+  if(strstr(msg, "<img ") || strstr(msg, "<IMG "))
     flags |= GAIM_CONV_IM_IMAGES;
 
   if(mwConversation_isOpen(conv)) {
-    char *msg = NULL;
+    char *tmp;
     int ret;
 
     if((flags & GAIM_CONV_IM_IMAGES) &&
        mwConversation_supports(conv, mwImSend_MIME)) {
-
-      msg = im_mime_convert(message);
-      ret = mwConversation_send(conv, mwImSend_MIME, msg);
+      /* send a MIME message */
+
+      tmp = im_mime_convert(msg);
+      g_free(msg);
+
+      ret = mwConversation_send(conv, mwImSend_MIME, tmp);
+      g_free(tmp);
       
     } else if(mwConversation_supports(conv, mwImSend_HTML)) {
+      /* send an HTML message */
 
       /* need to do this to get the \n to <br> conversion */
-      msg = gaim_strdup_withhtml(message);
-      ret = mwConversation_send(conv, mwImSend_HTML, msg);
+      tmp = gaim_strdup_withhtml(msg);
+      g_free(msg);
+
+      ret = mwConversation_send(conv, mwImSend_HTML, tmp);
+      g_free(tmp);
 
     } else {
-      ret = mwConversation_send(conv, mwImSend_PLAIN, message);
+      /* default to text */
+      ret = mwConversation_send(conv, mwImSend_PLAIN, msg);
+      g_free(msg);
     }
     
-    g_free(msg);
     return !ret;
 
   } else {
-    char *msg;
 
     /* queue up the message safely as plain text */
-    msg = gaim_markup_strip_html(message);
-    convo_queue(conv, mwImSend_PLAIN, msg);
+    char *tmp = gaim_markup_strip_html(msg);
     g_free(msg);
 
+    convo_queue(conv, mwImSend_PLAIN, tmp);
+    g_free(tmp);
+
     if(! mwConversation_isPending(conv))
       mwConversation_open(conv);
 
@@ -3410,59 +3954,69 @@
 
 static void mw_prpl_get_info(GaimConnection *gc, const char *who) {
 
+  struct mwAwareIdBlock idb = { mwAware_USER, (char *) who, NULL };
+
   struct mwGaimPluginData *pd;
-  struct mwAwareIdBlock idb = { mwAware_USER, (char *) who, NULL };
-
   GaimAccount *acct;
   GaimBuddy *b;
   
   GString *str;
   const char *tmp;
-  guint32 type;
+
+  g_return_if_fail(who != NULL);
+  g_return_if_fail(*who != '\0');
 
   pd = gc->proto_data;
 
   acct = gaim_connection_get_account(gc);
   b = gaim_find_buddy(acct, who);
 
-  g_return_if_fail(b != NULL);
-
   str = g_string_new(NULL);
 
-  g_string_append_printf(str, "<b>User ID:</b> %s<br>", b->name);
-
-  if(b->server_alias) {
-    g_string_append_printf(str, "<b>Full Name:</b> %s<br>",
-			   b->server_alias);
+  if(g_str_has_prefix(who, "@E ")) {
+    g_string_append(str, _("<b>External User</b><br>"));
   }
 
-  type = gaim_blist_node_get_int((GaimBlistNode *) b, BUDDY_KEY_CLIENT);
-  if(type) {
-    g_string_append(str, "<b>Last Known Client:</b> ");
-
-    tmp = mwLoginType_getName(type);
-    if(tmp) {
-      g_string_append(str, tmp);
-      g_string_append(str, "<br>");
-
-    } else {
-      g_string_append_printf(str, "Unknown (0x%04x)<br>", type);
+  g_string_append_printf(str, _("<b>User ID:</b> %s<br>"), who);
+
+  if(b) {
+    guint32 type;
+
+    if(b->server_alias) {
+      g_string_append_printf(str, _("<b>Full Name:</b> %s<br>"),
+			     b->server_alias);
+    }
+
+    type = gaim_blist_node_get_int((GaimBlistNode *) b, BUDDY_KEY_CLIENT);
+    if(type) {
+      g_string_append(str, _("<b>Last Known Client:</b> "));
+
+      tmp = mwLoginType_getName(type);
+      if(tmp) {
+	g_string_append(str, tmp);
+	g_string_append(str, "<br>");
+	
+      } else {
+	g_string_append_printf(str, _("Unknown (0x%04x)<br>"), type);
+      }
     }
   }
 
   tmp = user_supports_text(pd->srvc_aware, who);
   if(tmp) {
-    g_string_append_printf(str, "<b>Supports:</b> %s<br>", tmp);
+    g_string_append_printf(str, _("<b>Supports:</b> %s<br>"), tmp);
     g_free((char *) tmp);
   }
 
-  tmp = status_text(b);
-  g_string_append_printf(str, "<b>Status:</b> %s", tmp);
-
-  g_string_append(str, "<hr>");
-
-  tmp = mwServiceAware_getText(pd->srvc_aware, &idb);
-  g_string_append(str, tmp);
+  if(b) {
+    tmp = status_text(b);
+    g_string_append_printf(str, _("<b>Status:</b> %s"), tmp);
+
+    g_string_append(str, "<hr>");
+    
+    tmp = mwServiceAware_getText(pd->srvc_aware, &idb);
+    if(tmp) g_string_append(str, tmp);
+  }
 
   /* @todo emit a signal to allow a plugin to override the display of
      this notification, so that it can create its own */
@@ -3471,41 +4025,42 @@
 
   g_string_free(str, TRUE);
 }
-
-
+ 
+ 
 static void mw_prpl_set_status(GaimAccount *acct, GaimStatus *status) {
   GaimConnection *gc;
   const char *state;
   char *message = NULL;
   struct mwSession *session;
   struct mwUserStatus stat;
-
+  
   g_return_if_fail(acct != NULL);
   gc = gaim_account_get_connection(acct);
-
+  
   state = gaim_status_get_id(status);
-
-  gaim_debug_info("meanwhile", "Set status to %s\n", gaim_status_get_name(status));
-
+  
+  gaim_debug_info("meanwhile", "Set status to %s\n",
+		  gaim_status_get_name(status));
+  
   g_return_if_fail(gc != NULL);
-
+  
   session = gc_to_session(gc);
   g_return_if_fail(session != NULL);
-
+  
   /* get a working copy of the current status */
   mwUserStatus_clone(&stat, mwSession_getUserStatus(session));
-
+  
   /* determine the state */
   if(! strcmp(state, MW_STATE_ACTIVE)) {
     stat.status = mwStatus_ACTIVE;
-
+    
   } else if(! strcmp(state, MW_STATE_AWAY)) {
     stat.status = mwStatus_AWAY;
-
+    
   } else if(! strcmp(state, MW_STATE_BUSY)) {
     stat.status = mwStatus_BUSY;
   }
-
+  
   /* determine the message */
   switch(stat.status) {
   case mwStatus_ACTIVE:
@@ -3515,44 +4070,64 @@
     message = (char *)gaim_status_get_attr_string(status, MW_STATE_MESSAGE);
     break;
   }
-
+  
   if(message) {
     /* all the possible non-NULL values of message up to this point
        are const, so we don't need to free them */
     message = gaim_markup_strip_html(message);
   }
-
+  
   /* out with the old */
   g_free(stat.desc);
-
+  
   /* in with the new */
   stat.desc = (char *) message;
-
+  
   mwSession_setUserStatus(session, &stat);
   mwUserStatus_clear(&stat);
 }
 
-
-static void mw_prpl_set_idle(GaimConnection *gc, int time) {
+ 
+static void mw_prpl_set_idle(GaimConnection *gc, int t) {
   struct mwSession *session;
   struct mwUserStatus stat;
-
+  
   session = gc_to_session(gc);
   g_return_if_fail(session != NULL);
-
+  
   mwUserStatus_clone(&stat, mwSession_getUserStatus(session));
 
-  if(time > 0 && stat.status == mwStatus_ACTIVE) {
+  if(t > 0 && stat.status == mwStatus_ACTIVE) {
+    time_t now = time(NULL);
+    stat.time = now - t;
     stat.status = mwStatus_IDLE;
-
-  } else if(time == 0 && stat.status == mwStatus_IDLE) {
+    
+  } else if(t == 0 && stat.status == mwStatus_IDLE) {
+    stat.time = 0;
     stat.status = mwStatus_ACTIVE;
   }
-
-  /** @todo actually put the idle time in the user status */
-
+  
   mwSession_setUserStatus(session, &stat);
   mwUserStatus_clear(&stat);
+ }
+
+
+static struct resolved_id *resolved_id_new(const char *id,
+					   const char *name) {
+
+  struct resolved_id *rid = g_new0(struct resolved_id, 1);
+  rid->id = g_strdup(id);
+  rid->name = g_strdup(name);
+  return rid;
+}
+
+
+static void resolved_id_free(struct resolved_id *rid) {
+  if(rid) {
+    g_free(rid->id);
+    g_free(rid->name);
+    g_free(rid);
+  }
 }
 
 
@@ -3583,7 +4158,6 @@
 
 
 static void multi_resolved_cleanup(GaimRequestFields *fields) {
-
   GaimRequestField *f;
   const GList *l;
 
@@ -3595,10 +4169,7 @@
     struct resolved_id *res;
 
     res = gaim_request_field_list_get_data(f, i);
-
-    g_free(res->id);
-    g_free(res->name);
-    g_free(res);
+    resolved_id_free(res);
   }
 }
 
@@ -3641,11 +4212,23 @@
 }
 
 
+static void foreach_resolved_id(char *key, char *val, GList **l) {
+  struct resolved_id *res = resolved_id_new(key, val);
+  *l = g_list_prepend(*l, res);
+}
+
+
+static gint resolved_id_comp(struct resolved_id *a, struct resolved_id *b) {
+  return g_ascii_strcasecmp(a->name, b->name);
+}
+
+
 static void multi_resolved_query(struct mwResolveResult *result,
 				 GaimBuddy *buddy) {
   GaimRequestFields *fields;
   GaimRequestFieldGroup *g;
   GaimRequestField *f;
+  GHashTable *hash;
   GList *l;
   char *msgA, *msgB;
 
@@ -3668,18 +4251,31 @@
      before you add a required field to the group. Feh. */
   gaim_request_fields_add_group(fields, g);
 
-  f = gaim_request_field_list_new("user", "Possible Matches");
+  f = gaim_request_field_list_new("user", _("Possible Matches"));
   gaim_request_field_list_set_multi_select(f, FALSE);
   gaim_request_field_set_required(f, TRUE);
 
+  /* collect results into a set of identities */
+  hash = g_hash_table_new(g_str_hash, g_str_equal);
   for(l = result->matches; l; l = l->next) {
     struct mwResolveMatch *match = l->data;
-    struct resolved_id *res = g_new0(struct resolved_id, 1);
+    
+    if(!match->id || !match->name)
+      continue;
+    
+    g_hash_table_insert(hash, match->id, match->name);
+  }
+  
+  /* collect set into a list of structures */
+  l = NULL;
+  g_hash_table_foreach(hash, (GHFunc) foreach_resolved_id, &l);
+  g_list_sort(l, (GCompareFunc) resolved_id_comp);
+
+  /* populate choices in request field */
+  for(; l; l = l->next) {
+    struct resolved_id *res = l->data;
     char *label;
-
-    res->id = g_strdup(match->id);
-    res->name = g_strdup(match->name);
-
+    
     /* fixes bug 1178603 by making the selection label a combination
        of the full name and the user id. Problems arrise when multiple
        entries have identical labels */
@@ -3688,18 +4284,20 @@
     g_free(label);
   }
 
+  g_list_free(l);
+
   gaim_request_field_group_add_field(g, f);
 
-  msgA = ("An ambiguous user ID was entered");
-  msgB = ("The identifier '%s' may possibly refer to any of the following"
-	  " users. Please select the correct user from the list below to"
-	  " add them to your buddy list.");
+  msgA = _("An ambiguous user ID was entered");
+  msgB = _("The identifier '%s' may possibly refer to any of the following"
+	   " users. Please select the correct user from the list below to"
+	   " add them to your buddy list.");
   msgB = g_strdup_printf(msgB, result->name);
 
-  gaim_request_fields(gc, "Select User to Add",
+  gaim_request_fields(gc, _("Select User to Add"),
 		      msgA, msgB, fields,
-		      "Add User", G_CALLBACK(multi_resolved_cb),
-		      "Cancel", G_CALLBACK(multi_resolved_cancel),
+		      _("Add User"), G_CALLBACK(multi_resolved_cb),
+		      _("Cancel"), G_CALLBACK(multi_resolved_cancel),
 		      buddy);
   g_free(msgB);
 }
@@ -3759,14 +4357,14 @@
     /* compose and display an error message */
     char *msgA, *msgB;
 
-    msgA = "Unable to add user: user not found";
-
-    msgB = ("The identifier '%s' did not match any users in your"
-	   " Sametime community. This entry has been removed from"
-	   " your buddy list.");
+    msgA = _("Unable to add user: user not found");
+
+    msgB = _("The identifier '%s' did not match any users in your"
+	     " Sametime community. This entry has been removed from"
+	     " your buddy list.");
     msgB = g_strdup_printf(msgB, NSTR(res->name));
 
-    gaim_notify_error(gc, "Unable to add user", msgA, msgB);
+    gaim_notify_error(gc, _("Unable to add user"), msgA, msgB);
 
     g_free(msgB);
   }
@@ -3786,6 +4384,12 @@
   pd = gc->proto_data;
   srvc = pd->srvc_resolve;
 
+  /* catch external buddies. They won't be in the resolve service */
+  if(g_str_has_prefix(buddy->name, "@E ")) {
+    buddy_add(pd, buddy);
+    return;
+  }
+
   query = g_list_prepend(NULL, buddy->name);
   flags = mwResolveFlag_FIRST | mwResolveFlag_USERS;
 
@@ -3802,7 +4406,6 @@
 
 static void foreach_add_buddies(GaimGroup *group, GList *buddies,
 				struct mwGaimPluginData *pd) {
-
   struct mwAwareList *list;
 
   list = list_ensure(pd, group);
@@ -3999,26 +4602,39 @@
 			      GHashTable *components) {
 
   struct mwGaimPluginData *pd;
-  struct mwServiceConference *srvc;
-  struct mwConference *conf = NULL;
   char *c, *t;
-
+  
   pd = gc->proto_data;
-  srvc = pd->srvc_conf;
 
   c = g_hash_table_lookup(components, CHAT_KEY_NAME);
   t = g_hash_table_lookup(components, CHAT_KEY_TOPIC);
-
-  if(c) conf = conf_find(srvc, c);
-
-  if(conf) {
-    DEBUG_INFO("accepting conference invitation\n");
-    mwConference_accept(conf);
-
+  
+  if(g_hash_table_lookup(components, CHAT_KEY_IS_PLACE)) {
+    /* use place service */
+    struct mwServicePlace *srvc;
+    struct mwPlace *place = NULL;
+
+    srvc = pd->srvc_place;
+    place = mwPlace_new(srvc, c, t);
+    mwPlace_open(place);
+     
   } else {
-    DEBUG_INFO("creating new conference\n");
-    conf = mwConference_new(srvc, t);
-    mwConference_open(conf);
+    /* use conference service */
+    struct mwServiceConference *srvc;
+    struct mwConference *conf = NULL;
+
+    srvc = pd->srvc_conf;
+    if(c) conf = conf_find(srvc, c);
+
+    if(conf) {
+      DEBUG_INFO("accepting conference invitation\n");
+      mwConference_accept(conf);
+      
+    } else {
+      DEBUG_INFO("creating new conference\n");
+      conf = mwConference_new(srvc, t);
+      mwConference_open(conf);
+    }
   }
 }
 
@@ -4033,10 +4649,16 @@
   pd = gc->proto_data;
   srvc = pd->srvc_conf;
 
-  c = g_hash_table_lookup(components, CHAT_KEY_NAME);
-  if(c) {
-    struct mwConference *conf = conf_find(srvc, c);
-    if(conf) mwConference_reject(conf, ERR_SUCCESS, "Declined");
+  if(g_hash_table_lookup(components, CHAT_KEY_IS_PLACE)) {
+    ; /* nothing needs doing */
+
+  } else {
+    /* reject conference */
+    c = g_hash_table_lookup(components, CHAT_KEY_NAME);
+    if(c) {
+      struct mwConference *conf = conf_find(srvc, c);
+      if(conf) mwConference_reject(conf, ERR_SUCCESS, "Declined");
+    }
   }
 }
 
@@ -4063,6 +4685,8 @@
   g_return_if_fail(conf != NULL);
   
   mwConference_invite(conf, &idb, invitation);
+
+  /* @todo: use Place by default instead */
 }
 
 
@@ -4077,9 +4701,15 @@
   g_return_if_fail(pd != NULL);
   conf = ID_TO_CONF(pd, id);
 
-  g_return_if_fail(conf != NULL);
-  
-  mwConference_destroy(conf, ERR_SUCCESS, "Leaving");
+  if(conf) {
+    mwConference_destroy(conf, ERR_SUCCESS, "Leaving");
+
+  } else {
+    struct mwPlace *place = ID_TO_PLACE(pd, id);
+    g_return_if_fail(place != NULL);
+
+    mwPlace_destroy(place, ERR_SUCCESS);
+  }
 }
 
 
@@ -4104,9 +4734,15 @@
   g_return_val_if_fail(pd != NULL, 0);
   conf = ID_TO_CONF(pd, id);
 
-  g_return_val_if_fail(conf != NULL, 0);
-  
-  return ! mwConference_sendText(conf, message);
+  if(conf) {
+    return ! mwConference_sendText(conf, message);
+
+  } else {
+    struct mwPlace *place = ID_TO_PLACE(pd, id);
+    g_return_val_if_fail(place != NULL, 0);
+
+    return ! mwPlace_sendText(place, message);
+  }
 }
 
 
@@ -4291,7 +4927,7 @@
   /* test that we can actually send the file */
   fp = g_fopen(filename, "rb");
   if(! fp) {
-    char *msg = g_strdup_printf("Error reading %s: \n%s\n",
+    char *msg = g_strdup_printf(_("Error reading file %s: \n%s\n"),
 				filename, strerror(errno));
     gaim_xfer_error(gaim_xfer_get_type(xfer), acct, xfer->who, msg);
     g_free(msg);
@@ -4326,8 +4962,6 @@
   GaimAccount *acct;
   GaimXfer *xfer;
 
-  DEBUG_INFO("mw_prpl_send_file\n");
-
   acct = gaim_connection_get_account(gc);
 
   xfer = gaim_xfer_new(acct, GAIM_XFER_SEND, who);
@@ -4409,51 +5043,26 @@
 mw_plugin_get_plugin_pref_frame(GaimPlugin *plugin) {
   GaimPluginPrefFrame *frame;
   GaimPluginPref *pref;
-  
+
   frame = gaim_plugin_pref_frame_new();
   
-  pref = gaim_plugin_pref_new_with_label("Remotely Stored Buddy List");
+  pref = gaim_plugin_pref_new_with_label(_("Remotely Stored Buddy List"));
   gaim_plugin_pref_frame_add(frame, pref);
   
 
   pref = gaim_plugin_pref_new_with_name(MW_PRPL_OPT_BLIST_ACTION);
-  gaim_plugin_pref_set_label(pref, "Buddy List Storage Mode");
+  gaim_plugin_pref_set_label(pref, _("Buddy List Storage Mode"));
 
   gaim_plugin_pref_set_type(pref, GAIM_PLUGIN_PREF_CHOICE);
-  gaim_plugin_pref_add_choice(pref, "Local Buddy List Only",
-			      GINT_TO_POINTER(BLIST_CHOICE_NONE));
-  gaim_plugin_pref_add_choice(pref, "Merge List from Server",
-			      GINT_TO_POINTER(BLIST_CHOICE_LOAD));
-  gaim_plugin_pref_add_choice(pref, "Merge and Save List to Server",
-			      GINT_TO_POINTER(BLIST_CHOICE_SAVE));
-
-#if 0
-  /* possible ways to handle:
-     - mark all buddies as NO_SAVE
-     - load server list, delete all local buddies not in server list
-  */
-  gaim_plugin_pref_add_choice(pref, "Server Buddy List Only",
-			      GINT_TO_POINTER(BLIST_CHOISE_SERVER));
-#endif
-
-  gaim_plugin_pref_frame_add(frame, pref);
-
-  pref = gaim_plugin_pref_new_with_label("General Options");
-  gaim_plugin_pref_frame_add(frame, pref);
-
-  pref = gaim_plugin_pref_new_with_name(MW_PRPL_OPT_FORCE_LOGIN);
-  gaim_plugin_pref_set_type(pref, GAIM_PLUGIN_PREF_NONE);
-  gaim_plugin_pref_set_label(pref, "Force Login (Ignore Login Redirects)");
-  gaim_plugin_pref_frame_add(frame, pref);
-
-  pref = gaim_plugin_pref_new_with_name(MW_PRPL_OPT_PSYCHIC);
-  gaim_plugin_pref_set_type(pref, GAIM_PLUGIN_PREF_NONE);
-  gaim_plugin_pref_set_label(pref, "Enable Psychic Mode");
-  gaim_plugin_pref_frame_add(frame, pref);
-
-  pref = gaim_plugin_pref_new_with_name(MW_PRPL_OPT_SAVE_DYNAMIC);
-  gaim_plugin_pref_set_type(pref, GAIM_PLUGIN_PREF_NONE);
-  gaim_plugin_pref_set_label(pref, "Save NAB group members locally");
+  gaim_plugin_pref_add_choice(pref, _("Local Buddy List Only"),
+			      GINT_TO_POINTER(blist_choice_LOCAL));
+  gaim_plugin_pref_add_choice(pref, _("Merge List from Server"),
+			      GINT_TO_POINTER(blist_choice_MERGE));
+  gaim_plugin_pref_add_choice(pref, _("Merge and Save List to Server"),
+			      GINT_TO_POINTER(blist_choice_STORE));
+  gaim_plugin_pref_add_choice(pref, _("Synchronize List with Server"),
+			      GINT_TO_POINTER(blist_choice_SYNCH));
+
   gaim_plugin_pref_frame_add(frame, pref);
 
   return frame;
@@ -4470,7 +5079,6 @@
   GaimAccount *acct;
   GaimRequestField *f;
   const char *msg;
-  /* gboolean prompt; */
 
   struct mwGaimPluginData *pd;
   struct mwServiceStorage *srvc;
@@ -4500,15 +5108,6 @@
   mwServiceStorage_save(srvc, unit, NULL, NULL, NULL);
 
 #if 0
-  /** @todo not yet used. It should be possible to prompt the user for
-      a message (ala the Sametime Connect client) when changing to one
-      of the default states, and that preference is here */
-  f = gaim_request_fields_get_field(fields, "prompt");
-  prompt = gaim_request_field_bool_get_value(f);
-  gaim_account_set_bool(acct, MW_KEY_MSG_PROMPT, prompt);
-#endif
-
-#if 0
   /* XXX */
   /* need to propagate the message change if we're in any of those
      default states */
@@ -4538,7 +5137,6 @@
   
   char *msgA, *msgB;
   const char *val;
-  /* gboolean prompt; */
 
   gc = act->context;
   acct = gaim_connection_get_account(gc);
@@ -4550,37 +5148,26 @@
 
   val = gaim_account_get_string(acct, MW_KEY_ACTIVE_MSG,
 				MW_PLUGIN_DEFAULT_ACTIVE_MSG);
-  f = gaim_request_field_string_new("active", "Active Message", val, FALSE);
+  f = gaim_request_field_string_new("active", _("Active Message"), val, FALSE);
   gaim_request_field_set_required(f, FALSE);
   gaim_request_field_group_add_field(g, f);
   
   val = gaim_account_get_string(acct, MW_KEY_AWAY_MSG,
 				MW_PLUGIN_DEFAULT_AWAY_MSG);
-  f = gaim_request_field_string_new("away", "Away Message", val, FALSE);
+  f = gaim_request_field_string_new("away", _("Away Message"), val, FALSE);
   gaim_request_field_set_required(f, FALSE);
   gaim_request_field_group_add_field(g, f);
 
   val = gaim_account_get_string(acct, MW_KEY_BUSY_MSG,
 				MW_PLUGIN_DEFAULT_BUSY_MSG);
-  f = gaim_request_field_string_new("busy", "Busy Message", val, FALSE);
+  f = gaim_request_field_string_new("busy", _("Busy Message"), val, FALSE);
   gaim_request_field_set_required(f, FALSE);
   gaim_request_field_group_add_field(g, f);
 
-#if 0
-  /** @todo not yet used. It should be possible to prompt the user for
-      a message (ala the Sametime Connect client) when changing to one
-      of the default states, and that preference is here */
-  prompt = gaim_account_get_bool(acct, MW_KEY_MSG_PROMPT, FALSE);
-  f = gaim_request_field_bool_new("prompt",
-				  ("Prompt for message when changing"
-				   " to one of these states?"), FALSE);
-  gaim_request_field_group_add_field(g, f);
-#endif
-
-  msgA = ("Default status messages");
+  msgA = _("Default status messages");
   msgB = ("");
 
-  gaim_request_fields(gc, "Edit Status Messages",
+  gaim_request_fields(gc, _("Edit Status Messages"),
 		      msgA, msgB, fields,
 		      _("OK"), G_CALLBACK(status_msg_action_cb),
 		      _("Cancel"), NULL,
@@ -4610,7 +5197,7 @@
   l = mwSametimeList_load(str->str);
   g_string_free(str, TRUE);
 
-  blist_import(gc, l);
+  blist_merge(gc, l);
   mwSametimeList_free(l);
 }
 
@@ -4623,7 +5210,7 @@
 
   gc = act->context;
   account = gaim_connection_get_account(gc);
-  title = g_strdup_printf("Import Sametime List for Account %s",
+  title = g_strdup_printf(_("Import Sametime List for Account %s"),
 			  gaim_account_get_username(account));
 
   gaim_request_file(gc, title, NULL, FALSE,
@@ -4662,7 +5249,7 @@
 
   gc = act->context;
   account = gaim_connection_get_account(gc);
-  title = g_strdup_printf("Export Sametime List for Account %s",
+  title = g_strdup_printf(_("Export Sametime List for Account %s"),
 			  gaim_account_get_username(account));
 
   gaim_request_file(gc, title, NULL, TRUE,
@@ -4713,11 +5300,11 @@
   if(group) {
     char *msgA, *msgB;
 
-    msgA = "Unable to add group: group exists";
-    msgB = "A group named '%s' already exists in your buddy list.";
+    msgA = _("Unable to add group: group exists");
+    msgB = _("A group named '%s' already exists in your buddy list.");
     msgB = g_strdup_printf(msgB, name);
 
-    gaim_notify_error(gc, "Unable to add group", msgA, msgB);
+    gaim_notify_error(gc, _("Unable to add group"), msgA, msgB);
 
     g_free(msgB);
     return;
@@ -4774,7 +5361,7 @@
   g = gaim_request_field_group_new(NULL);
   gaim_request_fields_add_group(fields, g);
 
-  f = gaim_request_field_list_new("group", "Possible Matches");
+  f = gaim_request_field_list_new("group", _("Possible Matches"));
   gaim_request_field_list_set_multi_select(f, FALSE);
   gaim_request_field_set_required(f, TRUE);
 
@@ -4790,16 +5377,16 @@
 
   gaim_request_field_group_add_field(g, f);
 
-  msgA = ("Notes Address Book group results");
-  msgB = ("The identifier '%s' may possibly refer to any of the following"
+  msgA = _("Notes Address Book group results");
+  msgB = _("The identifier '%s' may possibly refer to any of the following"
 	  " Notes Address Book groups. Please select the correct group from"
 	  " the list below to add it to your buddy list.");
   msgB = g_strdup_printf(msgB, result->name);
 
-  gaim_request_fields(gc, "Select Notes Address Book",
+  gaim_request_fields(gc, _("Select Notes Address Book"),
 		      msgA, msgB, fields,
-		      "Add Group", G_CALLBACK(remote_group_multi_cb),
-		      "Cancel", G_CALLBACK(remote_group_multi_cleanup),
+		      _("Add Group"), G_CALLBACK(remote_group_multi_cb),
+		      _("Cancel"), G_CALLBACK(remote_group_multi_cleanup),
 		      pd);
 
   g_free(msgB);
@@ -4836,13 +5423,13 @@
   if(res && res->name) {
     char *msgA, *msgB;
 
-    msgA = "Unable to add group: group not found";
-
-    msgB = ("The identifier '%s' did not match any Notes Address Book"
+    msgA = _("Unable to add group: group not found");
+
+    msgB = _("The identifier '%s' did not match any Notes Address Book"
 	    " groups in your Sametime community.");
     msgB = g_strdup_printf(msgB, res->name);
 
-    gaim_notify_error(gc, "Unable to add group", msgA, msgB);
+    gaim_notify_error(gc, _("Unable to add group"), msgA, msgB);
 
     g_free(msgB);
   }
@@ -4878,14 +5465,14 @@
 
   gc = act->context;
 
-  msgA = "Notes Address Book Group";
-  msgB = ("Enter the name of a Notes Address Book group in the field below"
+  msgA = _("Notes Address Book Group");
+  msgB = _("Enter the name of a Notes Address Book group in the field below"
 	  " to add the group and its members to your buddy list.");
 
-  gaim_request_input(gc, "Add Group", msgA, msgB, NULL,
+  gaim_request_input(gc, _("Add Group"), msgA, msgB, NULL,
 		     FALSE, FALSE, NULL,
-		     "Add", G_CALLBACK(remote_group_action_cb),
-		     "Cancel", NULL,
+		     _("Add"), G_CALLBACK(remote_group_action_cb),
+		     _("Cancel"), NULL,
 		     gc);
 }
 
@@ -4894,16 +5481,19 @@
   GaimPluginAction *act;
   GList *l = NULL;
 
-  act = gaim_plugin_action_new("Set Status Messages...", status_msg_action);
+  act = gaim_plugin_action_new(_("Set Status Messages..."),
+			       status_msg_action);
   l = g_list_append(l, act);
 
-  act = gaim_plugin_action_new("Import Sametime List...", st_import_action);
+  act = gaim_plugin_action_new(_("Import Sametime List..."),
+			       st_import_action);
   l = g_list_append(l, act);
 
-  act = gaim_plugin_action_new("Export Sametime List...", st_export_action);
+  act = gaim_plugin_action_new(_("Export Sametime List..."),
+			       st_export_action);
   l = g_list_append(l, act);
 
-  act = gaim_plugin_action_new("Add Notes Address Book Group...",
+  act = gaim_plugin_action_new(_("Add Notes Address Book Group..."),
 			       remote_group_action);
   l = g_list_append(l, act);
 
@@ -4984,26 +5574,45 @@
   GLogLevelFlags logflags =
     G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION;
 
+  /* set up the preferences */
+  gaim_prefs_add_none(MW_PRPL_OPT_BASE);
+  gaim_prefs_add_int(MW_PRPL_OPT_BLIST_ACTION, BLIST_CHOICE_DEFAULT);
+
+  /* remove dead preferences */
+  gaim_prefs_remove(MW_PRPL_OPT_PSYCHIC);
+  gaim_prefs_remove(MW_PRPL_OPT_SAVE_DYNAMIC);
+
   /* host to connect to */
-  opt = gaim_account_option_string_new("Server", MW_KEY_HOST,
+  opt = gaim_account_option_string_new(_("Server"), MW_KEY_HOST,
 				       MW_PLUGIN_DEFAULT_HOST);
   l = g_list_append(l, opt);
 
   /* port to connect to */
-  opt = gaim_account_option_int_new("Port", MW_KEY_PORT,
+  opt = gaim_account_option_int_new(_("Port"), MW_KEY_PORT,
 				    MW_PLUGIN_DEFAULT_PORT);
   l = g_list_append(l, opt);
 
+  /* default attempted encoding */
+  opt = gaim_account_option_string_new(_("Encoding"), MW_KEY_ENCODING,
+				       MW_PLUGIN_DEFAULT_ENCODING);
+  l = g_list_append(l, opt);
+
+  { /* copy the old force login setting from prefs if it's
+       there. Don't delete the preference, since there may be more
+       than one account that wants to check for it. */
+    gboolean b = FALSE;
+    const char *label = _("Force Login (Ignore Server Redirects)");
+
+    if(gaim_prefs_exists(MW_PRPL_OPT_FORCE_LOGIN))
+      b = gaim_prefs_get_bool(MW_PRPL_OPT_FORCE_LOGIN);
+
+    opt = gaim_account_option_bool_new(label, MW_KEY_FORCE, b);
+    l = g_list_append(l, opt);
+  }
+
   mw_prpl_info.protocol_options = l;
   l = NULL;
 
-  /* set up the prefs for blist options */
-  gaim_prefs_add_none(MW_PRPL_OPT_BASE);
-  gaim_prefs_add_int(MW_PRPL_OPT_BLIST_ACTION, BLIST_CHOICE_DEFAULT);
-  gaim_prefs_add_bool(MW_PRPL_OPT_PSYCHIC, FALSE);
-  gaim_prefs_add_bool(MW_PRPL_OPT_FORCE_LOGIN, FALSE);
-  gaim_prefs_add_bool(MW_PRPL_OPT_SAVE_DYNAMIC, TRUE);
-
   /* forward all our g_log messages to gaim. Generally all the logging
      calls are using gaim_log directly, but the g_return macros will
      get caught here */
--- a/src/protocols/sametime/sametime.h	Wed Nov 02 03:31:38 2005 +0000
+++ b/src/protocols/sametime/sametime.h	Wed Nov 02 03:39:03 2005 +0000
@@ -48,3 +48,9 @@
 /*  mwLogin_MEANWHILE */
 
 
+/** default encoding for the gaim plugin.*/
+#ifndef MW_PLUGIN_DEFAULT_ENCODING
+#define MW_PLUGIN_DEFAULT_ENCODING "ISO-8859-1"
+#endif
+/* ISO-8859-1 */
+