changeset 10969:3ef77720e577

[gaim-migrate @ 12790] importing meanwhile library for use in the sametime plugin committer: Tailor Script <tailor@pidgin.im>
author Christopher O'Brien <siege@pidgin.im>
date Sun, 05 Jun 2005 02:50:13 +0000
parents e0d5038fbb7e
children a126bdfd7688
files 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_client.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_dir.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/mw_st_list.h src/protocols/sametime/meanwhile/mw_util.c src/protocols/sametime/meanwhile/mw_util.h src/protocols/sametime/meanwhile/service.c 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_dir.c src/protocols/sametime/meanwhile/srvc_ft.c src/protocols/sametime/meanwhile/srvc_im.c src/protocols/sametime/meanwhile/srvc_resolve.c src/protocols/sametime/meanwhile/srvc_store.c src/protocols/sametime/meanwhile/st_list.c
diffstat 36 files changed, 15256 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/channel.c	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,878 @@
+
+/*
+  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 <string.h>
+
+#include "mw_channel.h"
+#include "mw_cipher.h"
+#include "mw_debug.h"
+#include "mw_error.h"
+#include "mw_message.h"
+#include "mw_service.h"
+#include "mw_session.h"
+#include "mw_util.h"
+
+
+/** @todo reorganize this file, stuff is just strewn about */
+
+
+struct mwChannel {
+
+  /** session this channel belongs to */
+  struct mwSession *session;
+
+  enum mwChannelState state;
+
+  /** creator for incoming channel, target for outgoing channel */
+  struct mwLoginInfo user;
+
+  /* similar to data from the CreateCnl message in 8.4.1.7 */
+  guint32 reserved;    /**< special, unknown meaning */
+  guint32 id;          /**< channel ID */
+  guint32 service;     /**< service ID */
+  guint32 proto_type;  /**< service protocol type */
+  guint32 proto_ver;   /**< service protocol version */
+  guint32 options;     /**< channel options */
+
+  struct mwOpaque addtl_create;
+  struct mwOpaque addtl_accept;
+
+  /** all those supported ciphers */
+  GHashTable *supported;
+
+  /** cipher information determined at channel acceptance */
+  struct mwCipherInstance *cipher;
+
+  /** statistics table */
+  GHashTable *stats;
+
+  GSList *outgoing_queue;     /**< queued outgoing messages */
+  GSList *incoming_queue;     /**< queued incoming messages */
+
+  struct mw_datum srvc_data;  /**< service-specific data */
+};
+
+
+struct mwChannelSet {
+  struct mwSession *session;  /**< owning session */
+  GHashTable *map;            /**< map of all channels, by ID */
+  guint32 counter;            /**< counter for outgoing ID */
+};
+
+
+static void flush_channel(struct mwChannel *);
+
+
+static const char *state_str(enum mwChannelState state) {
+  switch(state) {
+  case mwChannel_NEW:      return "new";
+  case mwChannel_INIT:     return "initializing";
+  case mwChannel_WAIT:     return "waiting";
+  case mwChannel_OPEN:     return "open";
+  case mwChannel_DESTROY:  return "closing";
+  case mwChannel_ERROR:    return "error";
+
+  case mwChannel_UNKNOWN:  /* fall through */
+  default:                 return "UNKNOWN";
+  }
+}
+
+
+static void state(struct mwChannel *chan, enum mwChannelState state) {
+  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));
+}
+
+
+static gpointer get_stat(struct mwChannel *chan,
+			 enum mwChannelStatField field) {
+
+  return g_hash_table_lookup(chan->stats, (gpointer) field);
+}
+
+
+static void set_stat(struct mwChannel *chan, enum mwChannelStatField field,
+		     gpointer val) {
+
+  g_hash_table_insert(chan->stats, (gpointer) field, val);
+}
+
+
+#define incr_stat(chan, field, incr) \
+  set_stat(chan, field, get_stat(chan, field) + incr)
+
+
+#define timestamp_stat(chan, field) \
+  set_stat(chan, field, (gpointer) time(NULL))
+
+
+static void sup_free(gpointer a) {
+  mwCipherInstance_free(a);
+}
+
+
+struct mwCipherInstance *get_supported(struct mwChannel *chan, guint16 id) {
+  guint32 cid = (guint32) id;
+  return g_hash_table_lookup(chan->supported, GUINT_TO_POINTER(cid));
+}
+
+
+void put_supported(struct mwChannel *chan, struct mwCipherInstance *ci) {
+  struct mwCipher *cipher = mwCipherInstance_getCipher(ci);
+  guint32 cid = (guint32) mwCipher_getType(cipher);
+  g_hash_table_insert(chan->supported, GUINT_TO_POINTER(cid), ci);
+}
+
+
+struct mwChannel *mwChannel_newIncoming(struct mwChannelSet *cs, guint32 id) {
+  struct mwChannel *chan;
+
+  g_return_val_if_fail(cs != NULL, NULL);
+  g_return_val_if_fail(cs->session != NULL, NULL);
+
+  chan = g_new0(struct mwChannel, 1);
+  chan->state = mwChannel_NEW;
+  chan->session = cs->session;
+  chan->id = id;
+
+  chan->stats = g_hash_table_new(g_direct_hash, g_direct_equal);
+
+  chan->supported = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+					  NULL, sup_free);
+
+  g_hash_table_insert(cs->map, GUINT_TO_POINTER(id), chan);
+
+  state(chan, mwChannel_WAIT);
+
+  return chan;
+}
+
+
+struct mwChannel *mwChannel_newOutgoing(struct mwChannelSet *cs) {
+  guint32 id;
+  struct mwChannel *chan;
+
+  g_return_val_if_fail(cs != NULL, NULL);
+  g_return_val_if_fail(cs->map != NULL, NULL);
+
+  /* grab the next id, and try to make sure there isn't already a
+     channel using it */
+  do {
+    id = ++cs->counter;
+  } while(g_hash_table_lookup(cs->map, GUINT_TO_POINTER(id)));
+  
+  chan = mwChannel_newIncoming(cs, id);
+  state(chan, mwChannel_INIT);
+
+  return chan;
+}
+
+
+guint32 mwChannel_getId(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, 0);
+  return chan->id;
+}
+
+
+struct mwSession *mwChannel_getSession(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, NULL);
+  return chan->session;
+}
+
+
+guint32 mwChannel_getServiceId(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, 0);
+  return chan->service;
+}
+
+
+struct mwService *mwChannel_getService(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, NULL);
+  return mwSession_getService(chan->session, chan->service);
+}
+
+
+void mwChannel_setService(struct mwChannel *chan, struct mwService *srvc) {
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(srvc != NULL);
+  g_return_if_fail(chan->state == mwChannel_INIT);
+  chan->service = mwService_getType(srvc);
+}
+
+
+gpointer mwChannel_getServiceData(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, NULL);
+  return mw_datum_get(&chan->srvc_data);
+}
+
+
+void mwChannel_setServiceData(struct mwChannel *chan,
+			      gpointer data, GDestroyNotify clean) {
+
+  g_return_if_fail(chan != NULL);
+  mw_datum_set(&chan->srvc_data, data, clean);
+}
+
+
+void mwChannel_removeServiceData(struct mwChannel *chan) {
+  g_return_if_fail(chan != NULL);
+  mw_datum_clear(&chan->srvc_data);
+}
+
+
+guint32 mwChannel_getProtoType(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, 0x00);
+  return chan->proto_type;
+}
+
+
+void mwChannel_setProtoType(struct mwChannel *chan, guint32 proto_type) {
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(chan->state == mwChannel_INIT);
+  chan->proto_type = proto_type;
+}
+
+
+guint32 mwChannel_getProtoVer(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, 0x00);
+  return chan->proto_ver;
+}
+
+
+void mwChannel_setProtoVer(struct mwChannel *chan, guint32 proto_ver) {
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(chan->state == mwChannel_INIT);
+  chan->proto_ver = proto_ver;
+}
+
+
+guint32 mwChannel_getOptions(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, 0x00);
+  return chan->options;
+}
+
+
+void mwChannel_setOptions(struct mwChannel *chan, guint32 options) {
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(chan->state == mwChannel_INIT);
+  chan->options = options;
+}
+
+
+struct mwLoginInfo *mwChannel_getUser(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, NULL);
+  return &chan->user;
+}
+
+
+struct mwOpaque *mwChannel_getAddtlCreate(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, NULL);
+  return &chan->addtl_create;
+}
+
+
+struct mwOpaque *mwChannel_getAddtlAccept(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, NULL);
+  return &chan->addtl_accept;
+}
+
+
+struct mwCipherInstance *mwChannel_getCipherInstance(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, NULL);
+  return chan->cipher;
+}
+
+
+enum mwChannelState mwChannel_getState(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, mwChannel_UNKNOWN);
+  return chan->state;
+}
+
+
+gpointer mwChannel_getStatistic(struct mwChannel *chan,
+				enum mwChannelStatField stat) {
+  
+  g_return_val_if_fail(chan != NULL, 0);
+  g_return_val_if_fail(chan->stats != NULL, 0);
+
+  return get_stat(chan, stat);
+}
+
+
+/* send a channel create message */
+int mwChannel_create(struct mwChannel *chan) {
+  struct mwMsgChannelCreate *msg;
+  GList *list, *l;
+  int ret;
+
+  g_return_val_if_fail(chan != NULL, -1);
+  g_return_val_if_fail(chan->state == mwChannel_INIT, -1);
+  g_return_val_if_fail(mwChannel_isOutgoing(chan), -1);
+
+  msg = (struct mwMsgChannelCreate *)
+    mwMessage_new(mwMessage_CHANNEL_CREATE);
+
+  msg->channel = chan->id;
+  msg->target.user = g_strdup(chan->user.user_id);
+  msg->target.community = g_strdup(chan->user.community);
+  msg->service = chan->service;
+  msg->proto_type = chan->proto_type;
+  msg->proto_ver = chan->proto_ver;
+  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);
+  }
+  if(list) {
+    msg->encrypt.mode = 0x1000;
+    msg->encrypt.extra = 0x1000;
+  }
+  g_list_free(list);
+  
+  ret = mwSession_send(chan->session, MW_MESSAGE(msg));
+  mwMessage_free(MW_MESSAGE(msg));
+
+  state(chan, (ret)? mwChannel_ERROR: mwChannel_WAIT);
+
+  return ret;
+}
+
+
+static void channel_open(struct mwChannel *chan) {
+  state(chan, mwChannel_OPEN);
+  timestamp_stat(chan, mwChannelStat_OPENED_AT);
+  flush_channel(chan);
+}
+
+
+int mwChannel_accept(struct mwChannel *chan) {
+  struct mwSession *session;
+  struct mwMsgChannelAccept *msg;
+  int ret;
+
+  g_return_val_if_fail(chan != NULL, -1);
+  g_return_val_if_fail(mwChannel_isIncoming(chan), -1);
+  g_return_val_if_fail(chan->state == mwChannel_WAIT, -1);
+
+  session = chan->session;
+  g_return_val_if_fail(session != NULL, -1);
+
+  msg = (struct mwMsgChannelAccept *)
+    mwMessage_new(mwMessage_CHANNEL_ACCEPT);
+
+  msg->head.channel = chan->id;
+  msg->service = chan->service;
+  msg->proto_type = chan->proto_type;
+  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 {
+      mwChannel_selectCipherInstance(chan, NULL);
+    }
+  }
+
+  if(chan->cipher) {
+    mwCipherInstance_accept(chan->cipher);
+
+    msg->encrypt.item = mwCipherInstance_newItem(chan->cipher);
+
+    /** @todo figure out encrypt modes */
+    msg->encrypt.mode = 0x1000;
+    msg->encrypt.extra = 0x1000;
+  }
+
+  ret = mwSession_send(session, MW_MESSAGE(msg));
+  mwMessage_free(MW_MESSAGE(msg));
+
+  if(ret) {
+    state(chan, mwChannel_ERROR);
+  } else {
+    channel_open(chan);
+  }
+
+  return ret;
+}
+
+
+static void channel_free(struct mwChannel *chan) {
+  struct mwSession *s;
+  struct mwMessage *msg;
+  GSList *l;
+
+  /* maybe no warning in the future */
+  g_return_if_fail(chan != NULL);
+
+  s = chan->session;
+
+  mwLoginInfo_clear(&chan->user);
+  mwOpaque_clear(&chan->addtl_create);
+  mwOpaque_clear(&chan->addtl_accept);
+
+  if(chan->supported) {
+    g_hash_table_destroy(chan->supported);
+    chan->supported = NULL;
+  }
+
+  if(chan->stats) {
+    g_hash_table_destroy(chan->stats);
+    chan->stats = NULL;
+  }
+  
+  mwCipherInstance_free(chan->cipher);
+
+  /* clean up the outgoing queue */
+  for(l = chan->outgoing_queue; l; l = l->next) {
+    msg = (struct mwMessage *) l->data;
+    l->data = NULL;
+    mwMessage_free(msg);
+  }
+  g_slist_free(chan->outgoing_queue);
+
+  /* clean up the incoming queue */
+  for(l = chan->incoming_queue; l; l = l->next) {
+    msg = (struct mwMessage *) l->data;
+    l->data = NULL;
+    mwMessage_free(msg);
+  }
+  g_slist_free(chan->incoming_queue);
+
+  g_free(chan);
+}
+
+
+int mwChannel_destroy(struct mwChannel *chan,
+		      guint32 reason, struct mwOpaque *info) {
+
+  struct mwMsgChannelDestroy *msg;
+  struct mwSession *session;
+  struct mwChannelSet *cs;
+  int ret;
+
+  /* may make this not a warning in the future */
+  g_return_val_if_fail(chan != NULL, 0);
+
+  state(chan, reason? mwChannel_ERROR: mwChannel_DESTROY);
+
+  session = chan->session;
+  g_return_val_if_fail(session != NULL, -1);
+
+  cs = mwSession_getChannels(session);
+  g_return_val_if_fail(cs != NULL, -1);
+
+  /* compose the message */
+  msg = (struct mwMsgChannelDestroy *)
+    mwMessage_new(mwMessage_CHANNEL_DESTROY);
+  msg->head.channel = chan->id;
+  msg->reason = reason;
+  if(info) mwOpaque_clone(&msg->data, info);
+
+  /* remove the channel from the channel set */
+  g_hash_table_remove(cs->map, GUINT_TO_POINTER(chan->id));
+  
+  /* send the message */
+  ret = mwSession_send(session, (struct mwMessage *) msg);
+  mwMessage_free(MW_MESSAGE(msg));
+
+  return ret;
+}
+
+
+static void queue_outgoing(struct mwChannel *chan,
+			   struct mwMsgChannelSend *msg) {
+
+  g_info("queue_outgoing, channel 0x%08x", chan->id);
+  chan->outgoing_queue = g_slist_append(chan->outgoing_queue, msg);
+}
+
+
+static int channel_send(struct mwChannel *chan,
+			struct mwMsgChannelSend *msg) {
+
+  int ret = 0;
+
+  /* if the channel is open, send and free the message. Otherwise,
+     queue the message to be sent once the channel is finally
+     opened */
+
+  if(chan->state == mwChannel_OPEN) {
+    ret = mwSession_send(chan->session, (struct mwMessage *) msg);
+    mwMessage_free(MW_MESSAGE(msg));
+
+  } else {
+    queue_outgoing(chan, msg);
+  }
+
+  return ret;
+}
+
+
+int mwChannel_sendEncrypted(struct mwChannel *chan,
+			    guint32 type, struct mwOpaque *data,
+			    gboolean encrypt) {
+
+  struct mwMsgChannelSend *msg;
+
+  g_return_val_if_fail(chan != NULL, -1);
+
+  msg = (struct mwMsgChannelSend *) mwMessage_new(mwMessage_CHANNEL_SEND);
+  msg->head.channel = chan->id;
+  msg->type = type;
+
+  mwOpaque_clone(&msg->data, data);
+
+  if(encrypt && chan->cipher) {
+    msg->head.options = mwMessageOption_ENCRYPT;
+    mwCipherInstance_encrypt(chan->cipher, &msg->data);
+  }
+
+  return channel_send(chan, msg);  
+}
+
+
+int mwChannel_send(struct mwChannel *chan, guint32 type,
+		   struct mwOpaque *data) {
+
+  return mwChannel_sendEncrypted(chan, type, data, TRUE);
+}
+
+
+static void queue_incoming(struct mwChannel *chan,
+			   struct mwMsgChannelSend *msg) {
+
+  /* we clone the message, because session_process will clear it once
+     we return */
+
+  struct mwMsgChannelSend *m = g_new0(struct mwMsgChannelSend, 1);
+  m->head.type = msg->head.type;
+  m->head.options = msg->head.options;
+  m->head.channel = msg->head.channel;
+  mwOpaque_clone(&m->head.attribs, &msg->head.attribs);
+
+  m->type = msg->type;
+  mwOpaque_clone(&m->data, &msg->data);
+
+  g_info("queue_incoming, channel 0x%08x", chan->id);
+  chan->incoming_queue = g_slist_append(chan->incoming_queue, m);
+}
+
+
+static void channel_recv(struct mwChannel *chan,
+			 struct mwMsgChannelSend *msg) {
+
+  struct mwService *srvc;
+  srvc = mwChannel_getService(chan);
+
+  incr_stat(chan, mwChannelStat_MSG_RECV, 1);
+
+  if(msg->head.options & mwMessageOption_ENCRYPT) {
+    struct mwOpaque data = { 0, 0 };
+    mwOpaque_clone(&data, &msg->data);
+
+    mwCipherInstance_decrypt(chan->cipher, &data);
+    mwService_recv(srvc, chan, msg->type, &data);
+    mwOpaque_clear(&data);
+    
+  } else {
+    mwService_recv(srvc, chan, msg->type, &msg->data);
+  }
+}
+
+
+static void flush_channel(struct mwChannel *chan) {
+  GSList *l;
+
+  for(l = chan->incoming_queue; l; l = l->next) {
+    struct mwMsgChannelSend *msg = (struct mwMsgChannelSend *) l->data;
+    l->data = NULL;
+
+    channel_recv(chan, msg);
+    mwMessage_free(MW_MESSAGE(msg));
+  }
+  g_slist_free(chan->incoming_queue);
+  chan->incoming_queue = NULL;
+
+  for(l = chan->outgoing_queue; l; l = l->next) {
+    struct mwMessage *msg = (struct mwMessage *) l->data;
+    l->data = NULL;
+
+    mwSession_send(chan->session, msg);
+    mwMessage_free(msg);
+  }
+  g_slist_free(chan->outgoing_queue);
+  chan->outgoing_queue = NULL;
+}
+
+
+void mwChannel_recv(struct mwChannel *chan, struct mwMsgChannelSend *msg) {
+  if(chan->state == mwChannel_OPEN) {
+    channel_recv(chan, msg);
+
+  } else {
+    queue_incoming(chan, msg);
+  }
+}
+
+
+struct mwChannel *mwChannel_find(struct mwChannelSet *cs, guint32 chan) {
+  g_return_val_if_fail(cs != NULL, NULL);
+  g_return_val_if_fail(cs->map != NULL, NULL);
+  return g_hash_table_lookup(cs->map, GUINT_TO_POINTER(chan));
+}
+
+
+void mwChannelSet_free(struct mwChannelSet *cs) {
+  if(! cs) return;
+  if(cs->map) g_hash_table_destroy(cs->map);
+  g_free(cs);
+}
+
+
+struct mwChannelSet *mwChannelSet_new(struct mwSession *s) {
+  struct mwChannelSet *cs = g_new0(struct mwChannelSet, 1);
+  cs->session = s;
+
+  /* for some reason, g_int_hash/g_int_equal cause a SIGSEGV */
+  cs->map = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+				  NULL, (GDestroyNotify) channel_free);
+  return cs;
+}
+
+
+void mwChannel_recvCreate(struct mwChannel *chan,
+			  struct mwMsgChannelCreate *msg) {
+
+  struct mwSession *session;
+  GList *list;
+  struct mwService *srvc;
+  
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(msg != NULL);
+  g_return_if_fail(chan->id == msg->channel);
+
+  session = chan->session;
+  g_return_if_fail(session != NULL);
+
+  if(mwChannel_isOutgoing(chan)) {
+    g_warning("channel 0x%08x not an incoming channel", chan->id);
+    mwChannel_destroy(chan, ERR_REQUEST_INVALID, NULL);
+    return;
+  }
+
+  for(list = msg->encrypt.items; list; list = list->next) {
+    struct mwEncryptItem *ei = list->data;
+    struct mwCipher *cipher;
+    struct mwCipherInstance *ci;
+
+    g_message("channel offered cipher id 0x%04x", ei->id);
+    cipher = mwSession_getCipher(session, ei->id);
+    if(! cipher) {
+      g_message("no such cipher found in session");
+      continue;
+    }
+
+    ci = mwCipher_newInstance(cipher, chan);
+    mwCipherInstance_offered(ci, ei);
+    mwChannel_addSupportedCipherInstance(chan, ci);
+  }
+
+  mwLoginInfo_clone(&chan->user, &msg->creator);
+  chan->service = msg->service;
+  chan->proto_type = msg->proto_type;
+  chan->proto_ver = msg->proto_ver;
+  
+  srvc = mwSession_getService(session, msg->service);
+  if(srvc) {
+    mwService_recvCreate(srvc, chan, msg);
+
+  } else {
+    mwChannel_destroy(chan, ERR_SERVICE_NO_SUPPORT, NULL);
+  }  
+}
+
+
+void mwChannel_recvAccept(struct mwChannel *chan,
+			  struct mwMsgChannelAccept *msg) {
+
+  struct mwService *srvc;
+
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(msg != NULL);
+  g_return_if_fail(chan->id == msg->head.channel);
+
+  if(mwChannel_isIncoming(chan)) {
+    g_warning("channel 0x%08x not an outgoing channel", chan->id);
+    mwChannel_destroy(chan, ERR_REQUEST_INVALID, NULL);
+    return;
+  }
+
+  if(chan->state != mwChannel_WAIT) {
+    g_warning("channel 0x%08x state not WAIT: %s",
+	      chan->id, state_str(chan->state));
+    mwChannel_destroy(chan, ERR_REQUEST_INVALID, NULL);
+    return;
+  }
+
+  mwLoginInfo_clone(&chan->user, &msg->acceptor);
+
+  srvc = mwSession_getService(chan->session, chan->service);
+  if(! srvc) {
+    g_warning("no service: 0x%08x", chan->service);
+    mwChannel_destroy(chan, ERR_SERVICE_NO_SUPPORT, NULL);
+    return;
+  }
+
+  if(! msg->encrypt.mode || ! msg->encrypt.item) {
+    mwChannel_selectCipherInstance(chan, NULL);
+
+  } else {
+    guint16 cid = msg->encrypt.item->id;
+    struct mwCipherInstance *ci = get_supported(chan, cid);
+
+    if(! ci) {
+      g_warning("not an offered cipher: 0x%04x", cid);
+      mwChannel_destroy(chan, ERR_REQUEST_INVALID, NULL);
+      return;
+    }
+
+    mwCipherInstance_accepted(ci, msg->encrypt.item);
+    mwChannel_selectCipherInstance(chan, ci);
+  }
+
+  /* mark it as open for the service */
+  state(chan, mwChannel_OPEN);
+
+  /* let the service know */
+  mwService_recvAccept(srvc, chan, msg);
+
+  /* flush it if the service didn't just immediately close it */
+  if(mwChannel_isState(chan, mwChannel_OPEN)) {
+    channel_open(chan);
+  }
+}
+
+
+void mwChannel_recvDestroy(struct mwChannel *chan,
+			   struct mwMsgChannelDestroy *msg) {
+
+  struct mwChannelSet *cs;
+  struct mwService *srvc;
+
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(msg != NULL);
+  g_return_if_fail(chan->id == msg->head.channel);
+
+  state(chan, msg->reason? mwChannel_ERROR: mwChannel_DESTROY);
+
+  srvc = mwChannel_getService(chan);
+  if(srvc) mwService_recvDestroy(srvc, chan, msg);
+
+  cs = mwSession_getChannels(chan->session);
+  g_return_if_fail(cs != NULL);
+  g_return_if_fail(cs->map != NULL);
+
+  g_hash_table_remove(cs->map, GUINT_TO_POINTER(chan->id));
+}
+
+
+void mwChannel_populateSupportedCipherInstances(struct mwChannel *chan) {
+  struct mwSession *session;
+  GList *list;
+
+  g_return_if_fail(chan != NULL);
+
+  session = chan->session;
+  g_return_if_fail(session != NULL);
+
+  for(list = mwSession_getCiphers(session); list; list = list->next) {
+    struct mwCipherInstance *ci = mwCipher_newInstance(list->data, chan);
+    if(! ci) continue;
+    put_supported(chan, ci);
+  }
+}
+
+
+void mwChannel_addSupportedCipherInstance(struct mwChannel *chan,
+					  struct mwCipherInstance *ci) {
+  g_return_if_fail(chan != NULL);
+  g_message("channel 0x%08x added cipher %s", chan->id,
+	    NSTR(mwCipher_getName(mwCipherInstance_getCipher(ci))));
+  put_supported(chan, ci);
+}
+
+
+static void collect(gpointer a, gpointer b, gpointer c) {
+  GList **list = c;
+  *list = g_list_append(*list, b);
+}
+
+
+GList *mwChannel_getSupportedCipherInstances(struct mwChannel *chan) {
+  GList *list = NULL;
+
+  g_return_val_if_fail(chan != NULL, NULL);
+  g_hash_table_foreach(chan->supported, collect, &list);
+
+  return list;
+}
+
+
+void mwChannel_selectCipherInstance(struct mwChannel *chan,
+				    struct mwCipherInstance *ci) {
+  struct mwCipher *c;
+
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(chan->supported != NULL);
+
+  chan->cipher = ci;
+  if(ci) {
+    guint cid;
+
+    c = mwCipherInstance_getCipher(ci);
+    cid = mwCipher_getType(c);
+
+    g_hash_table_steal(chan->supported, GUINT_TO_POINTER(cid));
+
+    g_message("channel 0x%08x selected cipher %s",
+	      chan->id, NSTR(mwCipher_getName(c)));
+  } else {
+    g_message("channel 0x%08x selected no cipher", chan->id);
+  }
+
+  g_hash_table_destroy(chan->supported);
+  chan->supported = NULL;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/cipher.c	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,598 @@
+
+/*
+  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 <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mw_channel.h"
+#include "mw_cipher.h"
+#include "mw_debug.h"
+#include "mw_session.h"
+
+
+/** From RFC2268 */
+static unsigned char PT[] = {
+  0xD9, 0x78, 0xF9, 0xC4, 0x19, 0xDD, 0xB5, 0xED,
+  0x28, 0xE9, 0xFD, 0x79, 0x4A, 0xA0, 0xD8, 0x9D,
+  0xC6, 0x7E, 0x37, 0x83, 0x2B, 0x76, 0x53, 0x8E,
+  0x62, 0x4C, 0x64, 0x88, 0x44, 0x8B, 0xFB, 0xA2,
+  0x17, 0x9A, 0x59, 0xF5, 0x87, 0xB3, 0x4F, 0x13,
+  0x61, 0x45, 0x6D, 0x8D, 0x09, 0x81, 0x7D, 0x32,
+  0xBD, 0x8F, 0x40, 0xEB, 0x86, 0xB7, 0x7B, 0x0B,
+  0xF0, 0x95, 0x21, 0x22, 0x5C, 0x6B, 0x4E, 0x82,
+  0x54, 0xD6, 0x65, 0x93, 0xCE, 0x60, 0xB2, 0x1C,
+  0x73, 0x56, 0xC0, 0x14, 0xA7, 0x8C, 0xF1, 0xDC,
+  0x12, 0x75, 0xCA, 0x1F, 0x3B, 0xBE, 0xE4, 0xD1,
+  0x42, 0x3D, 0xD4, 0x30, 0xA3, 0x3C, 0xB6, 0x26,
+  0x6F, 0xBF, 0x0E, 0xDA, 0x46, 0x69, 0x07, 0x57,
+  0x27, 0xF2, 0x1D, 0x9B, 0xBC, 0x94, 0x43, 0x03,
+  0xF8, 0x11, 0xC7, 0xF6, 0x90, 0xEF, 0x3E, 0xE7,
+  0x06, 0xC3, 0xD5, 0x2F, 0xC8, 0x66, 0x1E, 0xD7,
+  0x08, 0xE8, 0xEA, 0xDE, 0x80, 0x52, 0xEE, 0xF7,
+  0x84, 0xAA, 0x72, 0xAC, 0x35, 0x4D, 0x6A, 0x2A,
+  0x96, 0x1A, 0xD2, 0x71, 0x5A, 0x15, 0x49, 0x74,
+  0x4B, 0x9F, 0xD0, 0x5E, 0x04, 0x18, 0xA4, 0xEC,
+  0xC2, 0xE0, 0x41, 0x6E, 0x0F, 0x51, 0xCB, 0xCC,
+  0x24, 0x91, 0xAF, 0x50, 0xA1, 0xF4, 0x70, 0x39,
+  0x99, 0x7C, 0x3A, 0x85, 0x23, 0xB8, 0xB4, 0x7A,
+  0xFC, 0x02, 0x36, 0x5B, 0x25, 0x55, 0x97, 0x31,
+  0x2D, 0x5D, 0xFA, 0x98, 0xE3, 0x8A, 0x92, 0xAE,
+  0x05, 0xDF, 0x29, 0x10, 0x67, 0x6C, 0xBA, 0xC9,
+  0xD3, 0x00, 0xE6, 0xCF, 0xE1, 0x9E, 0xA8, 0x2C,
+  0x63, 0x16, 0x01, 0x3F, 0x58, 0xE2, 0x89, 0xA9,
+  0x0D, 0x38, 0x34, 0x1B, 0xAB, 0x33, 0xFF, 0xB0,
+  0xBB, 0x48, 0x0C, 0x5F, 0xB9, 0xB1, 0xCD, 0x2E,
+  0xC5, 0xF3, 0xDB, 0x47, 0xE5, 0xA5, 0x9C, 0x77,
+  0x0A, 0xA6, 0x20, 0x68, 0xFE, 0x7F, 0xC1, 0xAD
+};
+
+
+void rand_key(char *key, gsize keylen) {
+  srand(clock());
+  while(keylen--) key[keylen] = rand() & 0xff;
+}
+
+
+void mwIV_init(char *iv) {
+  static unsigned char normal_iv[] = {
+    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;
+  */
+}
+
+
+/* This does not seem to produce the same results as normal RC2 key
+   expansion would, but it works, so eh. It might be smart to farm
+   this out to mozilla or openssl */
+void mwKeyExpand(int *ekey, const char *key, gsize keylen) {
+  char tmp[128];
+  int i, j;
+
+  /*
+  g_message("expanding key from:");
+  pretty_print(key, keylen);
+  */
+
+  if(keylen > 128) keylen = 128;
+  memcpy(tmp, key, keylen);
+
+  for(i = 0; keylen < 128; i++) {
+    tmp[keylen] = PT[ (tmp[keylen - 1] + tmp[i]) & 0xff ];
+    keylen++;
+  }
+
+  tmp[0] = PT[ tmp[0] & 0xff ];
+
+  for(i = 0, j = 0; i < 64; i++) {
+    ekey[i] = (tmp[j] & 0xff) | (tmp[j+1] << 8);
+    j += 2;
+  }
+}
+
+
+/* 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 i, j;
+
+  for(i = 0; i < 16; i++) {
+    j = i * 4;
+
+    d += ((c & (a ^ 0xffff)) + (b & a) + ekey[j++]);
+    d = (d << 1) | (d >> 15 & 0x0001);
+
+    c += ((b & (d ^ 0xffff)) + (a & d) + ekey[j++]);
+    c = (c << 2) | (c >> 14 & 0x0003);
+
+    b += ((a & (c ^ 0xffff)) + (d & c) + ekey[j++]);
+    b = (b << 3) | (b >> 13 & 0x0007);
+    
+    a += ((d & (b ^ 0xffff)) + (c & b) + ekey[j++]);
+    a = (a << 5) | (a >> 11 & 0x001f);
+
+    if(i == 4 || i == 10) {
+      d += ekey[a & 0x003f];
+      c += ekey[d & 0x003f];
+      b += ekey[c & 0x003f];
+      a += ekey[b & 0x003f];
+    }    
+  }
+
+  *out++ = d & 0xff;
+  *out++ = (d >> 8) & 0xff;
+  *out++ = c & 0xff;
+  *out++ = (c >> 8) & 0xff;
+  *out++ = b & 0xff;
+  *out++ = (b >> 8) & 0xff;
+  *out++ = a & 0xff;
+  *out++ = (a >> 8) & 0xff;
+}
+
+
+void mwEncryptExpanded(const int *ekey, char *iv,
+		       struct mwOpaque *in_data,
+		       struct mwOpaque *out_data) {
+
+  char *i = in_data->data;
+  gsize i_len = in_data->len;
+
+  char *o;
+  gsize o_len;
+
+  int x, y;
+
+  /* pad upwards to a multiple of 8 */
+  o_len = (i_len & -8) + 8;
+  o = g_malloc(o_len);
+
+  out_data->data = o;
+  out_data->len = o_len;
+
+  /* figure out the amount of padding */
+  y = o_len - i_len;
+
+  /* copy in to out, and write padding bytes */
+  memcpy(o, i, i_len);
+  memset(o + i_len, y, y);
+
+  /* encrypt in blocks */
+  for(x = o_len; x > 0; x -= 8) {
+    for(y = 8; y--; o[y] ^= iv[y]);
+    mwEncryptBlock(ekey, o);
+    memcpy(iv, o, 8);
+    o += 8;
+  }
+}
+
+
+void mwEncrypt(const char *key, gsize keylen, char *iv,
+	       struct mwOpaque *in, struct mwOpaque *out) {
+
+  int ekey[64];
+  mwKeyExpand(ekey, key, keylen);
+  mwEncryptExpanded(ekey, iv, in, out);
+}
+
+
+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 i, j;
+
+  for(i = 16; i--; ) {
+    j = i * 4 + 3;
+
+    a = (a << 11) | (a >> 5 & 0x07ff);
+    a -= ((d & (b ^ 0xffff)) + (c & b) + ekey[j--]);
+
+    b = (b << 13) | (b >> 3 & 0x1fff);
+    b -= ((a & (c ^ 0xffff)) + (d & c) + ekey[j--]);
+
+    c = (c << 14) | (c >> 2 & 0x3fff);
+    c -= ((b & (d ^ 0xffff)) + (a & d) + ekey[j--]);
+
+    d = (d << 15) | (d >> 1 & 0x7fff);
+    d -= ((c & (a ^ 0xffff)) + (b & a) + ekey[j--]);
+
+    if(i == 5 || i == 11) {
+      a -= ekey[b & 0x003f];
+      b -= ekey[c & 0x003f];
+      c -= ekey[d & 0x003f];
+      d -= ekey[a & 0x003f];
+    }
+  }
+
+  *out++ = d & 0xff;
+  *out++ = (d >> 8) & 0xff;
+  *out++ = c & 0xff;
+  *out++ = (c >> 8) & 0xff;
+  *out++ = b & 0xff;
+  *out++ = (b >> 8) & 0xff;
+  *out++ = a & 0xff;
+  *out++ = (a >> 8) & 0xff;
+}
+
+
+void mwDecryptExpanded(const int *ekey, char *iv,
+		       struct mwOpaque *in_data,
+		       struct mwOpaque *out_data) {
+
+  char *i = in_data->data;
+  gsize i_len = in_data->len;
+
+  char *o;
+  gsize o_len;
+
+  int x, y;
+
+  /* this doesn't check to ensure that in_data->len is a multiple of
+     8, which is damn well ought to be. */
+
+  o = g_malloc(i_len);
+  o_len = i_len;
+  memcpy(o, i, i_len);
+
+  out_data->data = o;
+  out_data->len = o_len;
+
+  for(x = o_len; x > 0; x -= 8) {
+    /* decrypt a block */
+    mwDecryptBlock(ekey, o);
+
+    /* modify the initialization vector */
+    for(y = 8; y--; o[y] ^= iv[y]);
+    memcpy(iv, i, 8);
+    i += 8;
+    o += 8;
+  }
+
+  /* shorten the length by the value of the filler in the padding
+     bytes */
+  out_data->len -= *(o - 1);
+}
+
+
+void mwDecrypt(const char *key, gsize keylen, char *iv,
+	       struct mwOpaque *in, struct mwOpaque *out) {
+
+  int ekey[64];
+  mwKeyExpand(ekey, key, keylen);
+  mwDecryptExpanded(ekey, iv, in, out);
+}
+
+
+struct mwCipher_RC2_40 {
+  struct mwCipher cipher;
+  int session_key[64];
+  gboolean ready;
+};
+
+
+struct mwCipherInstance_RC2_40 {
+  struct mwCipherInstance instance;
+  int incoming_key[64];
+  char outgoing_iv[8];
+  char incoming_iv[8];
+};
+
+
+static const char *get_name_RC2_40() {
+  return "RC2/40 Cipher";
+}
+
+
+static const char *get_desc_RC2_40() {
+  return "RC2, 40-bit effective key";
+}
+
+
+static int encrypt_RC2_40(struct mwCipherInstance *ci,
+			  struct mwOpaque *data) {
+
+  struct mwCipherInstance_RC2_40 *cir;
+  struct mwCipher_RC2_40 *cr;
+  struct mwOpaque o = { 0, 0 };
+
+  cir = (struct mwCipherInstance_RC2_40 *) ci;
+  cr = (struct mwCipher_RC2_40 *) ci->cipher;
+
+  mwEncryptExpanded(cr->session_key, cir->outgoing_iv, data, &o);
+
+  mwOpaque_clear(data);
+  data->data = o.data;
+  data->len = o.len;
+
+  return 0;
+}
+
+
+static int decrypt_RC2_40(struct mwCipherInstance *ci,
+			  struct mwOpaque *data) {
+  
+  struct mwCipherInstance_RC2_40 *cir;
+  struct mwCipher_RC2_40 *cr;
+  struct mwOpaque o = { 0, 0 };
+
+  cir = (struct mwCipherInstance_RC2_40 *) ci;
+  cr = (struct mwCipher_RC2_40 *) ci->cipher;
+
+  mwDecryptExpanded(cir->incoming_key, cir->incoming_iv, data, &o);
+
+  mwOpaque_clear(data);
+  data->data = o.data;
+  data->len = o.len;
+
+  return 0;
+}
+
+
+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);
+    mwKeyExpand(cr->session_key, info->login_id, 5);
+    cr->ready = TRUE;
+  }
+
+  mwIV_init(cir->incoming_iv);
+  mwIV_init(cir->outgoing_iv);
+
+  return ci;
+}
+
+
+static struct mwEncryptItem *new_item_RC2_40(struct mwCipherInstance *ci) {
+  struct mwEncryptItem *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 void accepted_RC2_40(struct mwCipherInstance *ci,
+			    struct mwEncryptItem *item) {
+  accept_RC2_40(ci);
+}
+
+
+struct mwCipher *mwCipher_new_RC2_40(struct mwSession *s) {
+  struct mwCipher_RC2_40 *cr = g_new0(struct mwCipher_RC2_40, 1);
+  struct mwCipher *c = &cr->cipher;
+
+  c->session = s;
+  c->type = mwCipher_RC2_40;
+  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->accepted = accepted_RC2_40;
+  c->accept = accept_RC2_40;
+
+  c->encrypt = encrypt_RC2_40;
+  c->decrypt = decrypt_RC2_40;
+
+  return c;
+}
+
+
+struct mwSession *mwCipher_getSession(struct mwCipher *cipher) {
+  g_return_val_if_fail(cipher != NULL, NULL);
+  return cipher->session;
+}
+
+
+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);
+  return cipher->type;
+}
+
+
+const char *mwCipher_getName(struct mwCipher *cipher) {
+  g_return_val_if_fail(cipher != NULL, NULL);
+  g_return_val_if_fail(cipher->get_name != NULL, NULL);
+  return cipher->get_name();
+}
+
+
+const char *mwCipher_getDesc(struct mwCipher *cipher) {
+  g_return_val_if_fail(cipher != NULL, NULL);
+  g_return_val_if_fail(cipher->get_desc != NULL, NULL);
+  return cipher->get_desc();
+}
+
+
+void mwCipher_free(struct mwCipher *cipher) {
+  if(! cipher) return;
+
+  if(cipher->clear)
+    cipher->clear(cipher);
+
+  g_free(cipher);
+}
+
+
+struct mwCipherInstance *mwCipher_newInstance(struct mwCipher *cipher,
+					      struct mwChannel *chan) {
+  g_return_val_if_fail(cipher != NULL, NULL);
+  g_return_val_if_fail(chan != NULL, NULL);
+  g_return_val_if_fail(cipher->new_instance != NULL, NULL);
+  return cipher->new_instance(cipher, chan);
+}
+
+
+struct mwCipher *mwCipherInstance_getCipher(struct mwCipherInstance *ci) {
+  g_return_val_if_fail(ci != NULL, NULL);
+  return ci->cipher;
+}
+
+
+struct mwChannel *mwCipherInstance_getChannel(struct mwCipherInstance *ci) {
+  g_return_val_if_fail(ci != NULL, NULL);
+  return ci->channel;
+}
+
+
+struct mwEncryptItem *mwCipherInstance_newItem(struct mwCipherInstance *ci) {
+  struct mwCipher *cipher;
+
+  g_return_val_if_fail(ci != NULL, NULL);
+  cipher = ci->cipher;
+
+  g_return_val_if_fail(cipher != NULL, NULL);
+  g_return_val_if_fail(cipher->new_item != NULL, NULL);
+
+  return cipher->new_item(ci);
+}
+
+
+void mwCipherInstance_offered(struct mwCipherInstance *ci,
+			      struct mwEncryptItem *item) {
+  struct mwCipher *cipher;
+
+  g_return_if_fail(ci != NULL);
+
+  cipher = ci->cipher;
+  g_return_if_fail(cipher != NULL);
+
+  if(cipher->offered) cipher->offered(ci, item);
+}
+
+
+void mwCipherInstance_offer(struct mwCipherInstance *ci) {
+  struct mwCipher *cipher;
+
+  g_return_if_fail(ci != NULL);
+
+  cipher = ci->cipher;
+  g_return_if_fail(cipher != NULL);
+
+  if(cipher->offer) cipher->offer(ci);
+}
+
+
+void mwCipherInstance_accepted(struct mwCipherInstance *ci,
+			       struct mwEncryptItem *item) {
+  struct mwCipher *cipher;
+
+  g_return_if_fail(ci != NULL);
+
+  cipher = ci->cipher;
+  g_return_if_fail(cipher != NULL);
+
+  if(cipher->accepted) cipher->accepted(ci, item);
+}
+
+
+void mwCipherInstance_accept(struct mwCipherInstance *ci) {
+  struct mwCipher *cipher;
+
+  g_return_if_fail(ci != NULL);
+
+  cipher = ci->cipher;
+  g_return_if_fail(cipher != NULL);
+
+  if(cipher->accept) cipher->accept(ci);
+}
+
+
+int mwCipherInstance_encrypt(struct mwCipherInstance *ci,
+			     struct mwOpaque *data) {
+  struct mwCipher *cipher;
+
+  g_return_val_if_fail(data != NULL, 0);
+
+  if(! ci) return 0;
+  cipher = ci->cipher;
+
+  g_return_val_if_fail(cipher != NULL, -1);
+
+  return (cipher->encrypt)?
+    cipher->encrypt(ci, data): 0;
+}
+
+
+int mwCipherInstance_decrypt(struct mwCipherInstance *ci,
+			     struct mwOpaque *data) {
+  struct mwCipher *cipher;
+
+  g_return_val_if_fail(data != NULL, 0);
+
+  if(! ci) return 0;
+  cipher = ci->cipher;
+
+  g_return_val_if_fail(cipher != NULL, -1);
+
+  return (cipher->decrypt)?
+    cipher->decrypt(ci, data): 0;
+}
+
+
+void mwCipherInstance_free(struct mwCipherInstance *ci) {
+  struct mwCipher *cipher;
+
+  if(! ci) return;
+
+  cipher = ci->cipher;
+
+  if(cipher && cipher->clear_instance)
+    cipher->clear_instance(ci);
+
+  g_free(ci);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/common.c	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,982 @@
+
+/*
+  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 <string.h>
+
+#include "mw_common.h"
+
+
+/** @todo the *_get functions should make sure to clear their
+    structures in the event of failure, to prevent memory leaks */
+
+
+#define MW16_PUT(b, val) \
+  *(b)++ = ((val) >> 0x08) & 0xff; \
+  *(b)++ = (val) & 0xff;
+
+
+#define MW16_GET(b, val) \
+  val = (*(b)++ & 0xff) << 8; \
+  val = val | (*(b)++ & 0xff);
+
+
+#define MW32_PUT(b, val) \
+  *(b)++ = ((val) >> 0x18) & 0xff; \
+  *(b)++ = ((val) >> 0x10) & 0xff; \
+  *(b)++ = ((val) >> 0x08) & 0xff; \
+  *(b)++ = (val) & 0xff;
+
+
+#define MW32_GET(b, val) \
+  val = (*(b)++ & 0xff) << 0x18; \
+  val = val | (*(b)++ & 0xff) << 0x10; \
+  val = val | (*(b)++ & 0xff) << 0x08; \
+  val = val | (*(b)++ & 0xff);
+
+
+struct mwPutBuffer {
+  char *buf;  /**< head of buffer */
+  gsize len;  /**< length of buffer */
+
+  char *ptr;  /**< offset to first unused byte */
+  gsize rem;  /**< count of unused bytes remaining */
+};
+
+
+struct mwGetBuffer {
+  char *buf;  /**< head of buffer */
+  gsize len;  /**< length of buffer */
+
+  char *ptr;  /**< offset to first unused byte */
+  gsize rem;  /**< count of unused bytes remaining */
+
+  gboolean wrap;   /**< TRUE to indicate buf shouldn't be freed */
+  gboolean error;  /**< TRUE to indicate an error */
+};
+
+
+#define BUFFER_USED(buffer) \
+  ((buffer)->len - (buffer)->rem)
+
+
+/** ensure that there's at least enough space remaining in the put
+    buffer to fit needed. */
+static void ensure_buffer(struct mwPutBuffer *b, gsize needed) {
+  if(b->rem < needed) {
+    gsize len = b->len, use = BUFFER_USED(b);
+    char *buf;
+
+    /* newly created buffers are empty until written to, and then they
+       have 1024 available */
+    if(! len) len = 1024;
+
+    /* double len until it's large enough to fit needed */
+    while( (len - use) < needed ) len = len << 1;
+
+    /* create the new buffer. if there was anything in the old buffer,
+       copy it into the new buffer and free the old copy */
+    buf = g_malloc(len);
+    if(b->buf) {
+      memcpy(buf, b->buf, use);
+      g_free(b->buf);
+    }
+
+    /* put the new buffer into b */
+    b->buf = buf;
+    b->len = len;
+    b->ptr = buf + use;
+    b->rem = len - use;
+  }
+}
+
+
+/** determine if there are at least needed bytes available in the
+    buffer. sets the error flag if there's not at least needed bytes
+    left in the buffer
+
+    @returns true if there's enough data, false if not */
+static gboolean check_buffer(struct mwGetBuffer *b, gsize needed) {
+  if(! b->error)  b->error = (b->rem < needed);
+  return ! b->error;
+}
+
+
+struct mwPutBuffer *mwPutBuffer_new() {
+  return g_new0(struct mwPutBuffer, 1);
+}
+
+
+void mwPutBuffer_write(struct mwPutBuffer *b, gpointer data, gsize len) {
+  g_return_if_fail(b != NULL);
+  g_return_if_fail(data != NULL);
+
+  if(! len) return;
+
+  ensure_buffer(b, len);
+  memcpy(b->ptr, data, len);
+  b->ptr += len;
+  b->rem -= len;
+}
+
+
+void mwPutBuffer_free(struct mwPutBuffer *b) {
+  if(! b) return;
+  g_free(b->buf);
+  g_free(b);
+}
+
+
+void mwPutBuffer_finalize(struct mwOpaque *to, struct mwPutBuffer *from) {
+  g_return_if_fail(to != NULL);
+  g_return_if_fail(from != NULL);
+
+  to->len = BUFFER_USED(from);
+  to->data = from->buf;
+
+  g_free(from);
+}
+
+
+struct mwGetBuffer *mwGetBuffer_new(struct mwOpaque *o) {
+  struct mwGetBuffer *b = g_new0(struct mwGetBuffer, 1);
+
+  if(o && o->len) {
+    b->buf = b->ptr = g_memdup(o->data, o->len);
+    b->len = b->rem = o->len;
+  }
+
+  return b;
+}
+
+
+struct mwGetBuffer *mwGetBuffer_wrap(const struct mwOpaque *o) {
+  struct mwGetBuffer *b = g_new0(struct mwGetBuffer, 1);
+
+  if(o && o->len) {
+    b->buf = b->ptr = (char *) o->data;
+    b->len = b->rem = o->len;
+  }
+  b->wrap = TRUE;
+
+  return b;
+}
+
+
+gsize mwGetBuffer_read(struct mwGetBuffer *b, gpointer data, gsize len) {
+  g_return_val_if_fail(b != NULL, 0);
+  g_return_val_if_fail(data != NULL, 0);
+
+  if(b->error) return 0;
+  if(! len) return 0;
+
+  if(b->rem < len)
+    len = b->rem;
+
+  memcpy(data, b->ptr, len);
+  b->ptr += len;
+  b->rem -= len;
+
+  return len;
+}
+
+
+gsize mwGetBuffer_advance(struct mwGetBuffer *b, gsize len) {
+  g_return_val_if_fail(b != NULL, 0);
+
+  if(b->error) return 0;
+  if(! len) return 0;
+
+  if(b->rem < len)
+    len = b->rem;
+
+  b->ptr += len;
+  b->rem -= len;
+
+  return len;
+}
+
+
+void mwGetBuffer_reset(struct mwGetBuffer *b) {
+  g_return_if_fail(b != NULL);
+
+  b->rem = b->len;
+  b->ptr = b->buf;
+  b->error = FALSE;
+}
+
+
+gsize mwGetBuffer_remaining(struct mwGetBuffer *b) {
+  g_return_val_if_fail(b != NULL, 0);
+  return b->rem;
+}
+
+
+gboolean mwGetBuffer_error(struct mwGetBuffer *b) {
+  g_return_val_if_fail(b != NULL, TRUE);
+  return b->error;
+}
+
+
+void mwGetBuffer_free(struct mwGetBuffer *b) {
+  if(! b) return;
+  if(! b->wrap) g_free(b->buf);
+  g_free(b);
+}
+
+
+#define guint16_buflen()  2
+
+
+void guint16_put(struct mwPutBuffer *b, guint16 val) {
+  g_return_if_fail(b != NULL);
+
+  ensure_buffer(b, guint16_buflen());
+  MW16_PUT(b->ptr, val);
+  b->rem -= guint16_buflen();
+}
+
+
+void guint16_get(struct mwGetBuffer *b, guint16 *val) {
+  g_return_if_fail(b != NULL);
+
+  if(b->error) return;
+  g_return_if_fail(check_buffer(b, guint16_buflen()));
+
+  MW16_GET(b->ptr, *val);
+  b->rem -= guint16_buflen();
+}
+
+
+guint16 guint16_peek(struct mwGetBuffer *b) {
+  char *buf = b->buf;
+  guint16 r = 0;
+  
+  if(b->rem >= guint16_buflen())
+    MW16_GET(buf, r);
+
+  return r;
+}
+
+
+#define guint32_buflen()  4
+
+
+void guint32_put(struct mwPutBuffer *b, guint32 val) {
+  g_return_if_fail(b != NULL);
+
+  ensure_buffer(b, guint32_buflen());
+  MW32_PUT(b->ptr, val);
+  b->rem -= guint32_buflen();
+}
+
+
+void guint32_get(struct mwGetBuffer *b, guint32 *val) {
+  g_return_if_fail(b != NULL);
+
+  if(b->error) return;
+  g_return_if_fail(check_buffer(b, guint32_buflen()));
+
+  MW32_GET(b->ptr, *val);
+  b->rem -= guint32_buflen();
+}
+
+
+guint32 guint32_peek(struct mwGetBuffer *b) {
+  char *buf = b->buf;
+  guint32 r = 0;
+
+  if(b->rem >= guint32_buflen())
+    MW32_GET(buf, r);
+
+  return r;
+}
+
+
+#define gboolean_buflen()  1
+
+
+void gboolean_put(struct mwPutBuffer *b, gboolean val) {
+  g_return_if_fail(b != NULL);
+
+  ensure_buffer(b, gboolean_buflen());
+  *(b->ptr) = !! val;
+  b->ptr++;
+  b->rem--;
+}
+
+
+void gboolean_get(struct mwGetBuffer *b, gboolean *val) {
+  g_return_if_fail(b != NULL);
+
+  if(b->error) return;
+  g_return_if_fail(check_buffer(b, gboolean_buflen()));
+
+  *val = !! *(b->ptr);
+  b->ptr++;
+  b->rem--;
+}
+
+
+gboolean gboolean_peek(struct mwGetBuffer *b) {
+  gboolean v = FALSE;
+
+  if(b->rem >= gboolean_buflen())
+    v = !! *(b->ptr);
+
+  return v;
+}
+
+
+gboolean mw_streq(const char *a, const char *b) {
+  return (a == b) || (a && b && !strcmp(a, b));
+}
+
+
+void mwString_put(struct mwPutBuffer *b, const char *val) {
+  gsize len = 0;
+
+  g_return_if_fail(b != NULL);
+
+  if(val) len = strlen(val);
+
+  guint16_put(b, (guint16) len);
+
+  if(len) {
+    ensure_buffer(b, len);
+    memcpy(b->ptr, val, len);
+    b->ptr += len;
+    b->rem -= len;
+  }
+}
+
+
+void mwString_get(struct mwGetBuffer *b, char **val) {
+  guint16 len = 0;
+
+  g_return_if_fail(b != NULL);
+  g_return_if_fail(val != NULL);
+
+  *val = NULL;
+
+  if(b->error) return;
+  guint16_get(b, &len);
+
+  g_return_if_fail(check_buffer(b, (gsize) len));
+
+  if(len) {
+    *val = g_malloc0(len + 1);
+    memcpy(*val, b->ptr, len);
+    b->ptr += len;
+    b->rem -= len;
+  }
+}
+
+
+void mwOpaque_put(struct mwPutBuffer *b, const struct mwOpaque *o) {
+  gsize len;
+
+  g_return_if_fail(b != NULL);
+
+  if(! o) {
+    guint32_put(b, 0x00);
+    return;
+  }
+
+  len = o->len;
+  if(len)
+    g_return_if_fail(o->data != NULL);
+  
+  guint32_put(b, (guint32) len);
+
+  if(len) {
+    ensure_buffer(b, len);
+    memcpy(b->ptr, o->data, len);
+    b->ptr += len;
+    b->rem -= len;
+  }
+}
+
+
+void mwOpaque_get(struct mwGetBuffer *b, struct mwOpaque *o) {
+  guint32 tmp = 0;
+
+  g_return_if_fail(b != NULL);
+  g_return_if_fail(o != NULL);
+
+  o->len = 0;
+  o->data = NULL;
+  
+  if(b->error) return;
+  guint32_get(b, &tmp);
+
+  g_return_if_fail(check_buffer(b, (gsize) tmp));
+
+  o->len = (gsize) tmp;
+  if(tmp > 0) {
+    o->data = g_memdup(b->ptr, tmp);
+    b->ptr += tmp;
+    b->rem -= tmp;
+  }
+}
+
+
+void mwOpaque_clear(struct mwOpaque *o) {
+  if(! o) return;
+  g_free(o->data);
+  o->data = NULL;
+  o->len = 0;
+}
+
+
+void mwOpaque_free(struct mwOpaque *o) {
+  if(! o) return;
+  g_free(o->data);
+  g_free(o);
+}
+
+
+void mwOpaque_clone(struct mwOpaque *to, const struct mwOpaque *from) {
+  g_return_if_fail(to != NULL);
+
+  to->len = 0;
+  to->data = NULL;
+
+  if(from) {
+    to->len = from->len;
+    if(to->len)
+      to->data = g_memdup(from->data, to->len);
+  }
+}
+
+
+/* 8.2 Common Structures */
+/* 8.2.1 Login Info block */
+
+
+void mwLoginInfo_put(struct mwPutBuffer *b, const struct mwLoginInfo *login) {
+  g_return_if_fail(b != NULL);
+  g_return_if_fail(login != NULL);
+
+  mwString_put(b, login->login_id);
+  guint16_put(b, login->type);
+  mwString_put(b, login->user_id);
+  mwString_put(b, login->user_name);
+  mwString_put(b, login->community);
+  gboolean_put(b, login->full);
+
+  if(login->full) {
+    mwString_put(b, login->desc);
+    guint32_put(b, login->ip_addr);
+    mwString_put(b, login->server_id);
+  }
+}
+
+
+void mwLoginInfo_get(struct mwGetBuffer *b, struct mwLoginInfo *login) {
+  g_return_if_fail(b != NULL);
+  g_return_if_fail(login != NULL);
+
+  if(b->error) return;
+
+  mwString_get(b, &login->login_id);
+  guint16_get(b, &login->type);
+  mwString_get(b, &login->user_id);
+  mwString_get(b, &login->user_name);
+  mwString_get(b, &login->community);
+  gboolean_get(b, &login->full);
+  
+  if(login->full) {
+    mwString_get(b, &login->desc);
+    guint32_get(b, &login->ip_addr);
+    mwString_get(b, &login->server_id);
+  }
+}
+
+
+void mwLoginInfo_clear(struct mwLoginInfo *login) {
+  if(! login) return;
+
+  g_free(login->login_id);
+  g_free(login->user_id);
+  g_free(login->user_name);
+  g_free(login->community);
+  g_free(login->desc);
+  g_free(login->server_id);
+
+  memset(login, 0x00, sizeof(struct mwLoginInfo));
+}
+
+
+void mwLoginInfo_clone(struct mwLoginInfo *to,
+		       const struct mwLoginInfo *from) {
+
+  g_return_if_fail(to != NULL);
+  g_return_if_fail(from != NULL);
+
+  to->login_id= g_strdup(from->login_id);
+  to->type = from->type;
+  to->user_id = g_strdup(from->user_id);
+  to->user_name = g_strdup(from->user_name);
+  to->community = g_strdup(from->community);
+
+  if( (to->full = from->full) ) {
+    to->desc = g_strdup(from->desc);
+    to->ip_addr = from->ip_addr;
+    to->server_id = g_strdup(from->server_id);
+  }
+}
+
+
+/* 8.2.2 Private Info Block */
+
+
+void mwUserItem_put(struct mwPutBuffer *b, const struct mwUserItem *user) {
+  g_return_if_fail(b != NULL);
+  g_return_if_fail(user != NULL);
+
+  gboolean_put(b, user->full);
+  mwString_put(b, user->id);
+  mwString_put(b, user->community);
+  
+  if(user->full)
+    mwString_put(b, user->name);
+}
+
+
+void mwUserItem_get(struct mwGetBuffer *b, struct mwUserItem *user) {
+  g_return_if_fail(b != NULL);
+  g_return_if_fail(user != NULL);
+
+  if(b->error) return;
+
+  gboolean_get(b, &user->full);
+  mwString_get(b, &user->id);
+  mwString_get(b, &user->community);
+
+  if(user->full)
+    mwString_get(b, &user->name);
+}
+
+
+void mwUserItem_clear(struct mwUserItem *user) {
+  if(! user) return;
+
+  g_free(user->id);
+  g_free(user->community);
+  g_free(user->name);
+
+  memset(user, 0x00, sizeof(struct mwUserItem));
+}
+
+
+void mwUserItem_clone(struct mwUserItem *to,
+		      const struct mwUserItem *from) {
+
+  g_return_if_fail(to != NULL);
+  g_return_if_fail(from != NULL);
+
+  to->full = from->full;
+  to->id = g_strdup(from->id);
+  to->community = g_strdup(from->community);
+  to->name = (to->full)? g_strdup(from->name): NULL;
+}
+
+
+void mwPrivacyInfo_put(struct mwPutBuffer *b,
+		       const struct mwPrivacyInfo *info) {
+  guint32 c;
+
+  g_return_if_fail(b != NULL);
+  g_return_if_fail(info != NULL);
+
+  gboolean_put(b, info->deny);
+  guint32_put(b, info->count);
+
+  for(c = info->count; c--; ) mwUserItem_put(b, info->users + c);
+}
+
+
+void mwPrivacyInfo_get(struct mwGetBuffer *b, struct mwPrivacyInfo *info) {
+  g_return_if_fail(b != NULL);
+  g_return_if_fail(info != NULL);
+
+  if(b->error) return;
+
+  gboolean_get(b, &info->deny);
+  guint32_get(b, &info->count);
+
+  if(info->count) {
+    guint32 c = info->count;
+    info->users = g_new0(struct mwUserItem, c);
+    while(c--) mwUserItem_get(b, info->users + c);
+  }
+}
+
+
+void mwPrivacyInfo_clone(struct mwPrivacyInfo *to,
+			 const struct mwPrivacyInfo *from) {
+
+  guint32 c;
+
+  g_return_if_fail(to != NULL);
+  g_return_if_fail(from != NULL);
+
+  to->deny = from->deny;
+  c = to->count = from->count;
+
+  to->users = g_new0(struct mwUserItem, c);
+  while(c--) mwUserItem_clone(to->users+c, from->users+c);
+}
+
+
+void mwPrivacyInfo_clear(struct mwPrivacyInfo *info) {
+  struct mwUserItem *u;
+  guint32 c;
+
+  g_return_if_fail(info != NULL);
+
+  u = info->users;
+  c = info->count;
+
+  while(c--) mwUserItem_clear(u + c);
+  g_free(u);
+
+  info->count = 0;
+  info->users = NULL;
+}
+
+
+/* 8.2.3 User Status Block */
+
+
+void mwUserStatus_put(struct mwPutBuffer *b,
+		      const struct mwUserStatus *stat) {
+
+  g_return_if_fail(b != NULL);
+  g_return_if_fail(stat != NULL);
+
+  guint16_put(b, stat->status);
+  guint32_put(b, stat->time);
+  mwString_put(b, stat->desc);
+}
+
+
+void mwUserStatus_get(struct mwGetBuffer *b, struct mwUserStatus *stat) {
+  g_return_if_fail(b != NULL);
+  g_return_if_fail(stat != NULL);
+
+  if(b->error) return;
+
+  guint16_get(b, &stat->status);
+  guint32_get(b, &stat->time);
+  mwString_get(b, &stat->desc);
+}
+
+
+void mwUserStatus_clear(struct mwUserStatus *stat) {
+  if(! stat) return;
+  g_free(stat->desc);
+  memset(stat, 0x00, sizeof(struct mwUserStatus));
+}
+
+
+void mwUserStatus_clone(struct mwUserStatus *to,
+			const struct mwUserStatus *from) {
+
+  g_return_if_fail(to != NULL);
+  g_return_if_fail(from != NULL);
+
+  to->status = from->status;
+  to->time = from->time;
+  to->desc = g_strdup(from->desc);
+}
+
+
+/* 8.2.4 ID Block */
+
+
+void mwIdBlock_put(struct mwPutBuffer *b, const struct mwIdBlock *id) {
+  g_return_if_fail(b != NULL);
+  g_return_if_fail(id != NULL);
+
+  mwString_put(b, id->user);
+  mwString_put(b, id->community);
+}
+
+
+void mwIdBlock_get(struct mwGetBuffer *b, struct mwIdBlock *id) {
+  g_return_if_fail(b != NULL);
+  g_return_if_fail(id != NULL);
+
+  if(b->error) return;
+
+  mwString_get(b, &id->user);
+  mwString_get(b, &id->community);
+}
+
+
+void mwIdBlock_clear(struct mwIdBlock *id) {
+  if(! id) return;
+
+  g_free(id->user);
+  id->user = NULL;
+
+  g_free(id->community);
+  id->community = NULL;
+}
+
+
+void mwIdBlock_clone(struct mwIdBlock *to, const struct mwIdBlock *from) {
+  g_return_if_fail(to != NULL);
+  g_return_if_fail(from != NULL);
+
+  to->user = g_strdup(from->user);
+  to->community = g_strdup(from->community);
+}
+
+
+guint mwIdBlock_hash(const struct mwIdBlock *idb) {
+  return (idb)? g_str_hash(idb->user): 0;
+}
+
+
+gboolean mwIdBlock_equal(const struct mwIdBlock *a,
+			 const struct mwIdBlock *b) {
+
+  g_return_val_if_fail(a != NULL, FALSE);
+  g_return_val_if_fail(b != NULL, FALSE);
+
+  return ( mw_streq(a->user, b->user) &&
+	   mw_streq(a->community, b->community) );
+}
+
+
+/* 8.2.5 Encryption Block */
+
+/** @todo I think this can be put into cipher */
+
+void mwEncryptItem_put(struct mwPutBuffer *b,
+		       const struct mwEncryptItem *ei) {
+
+  g_return_if_fail(b != NULL);
+  g_return_if_fail(ei != NULL);
+  
+  guint16_put(b, ei->id);
+  mwOpaque_put(b, &ei->info);
+
+}
+
+
+void mwEncryptItem_get(struct mwGetBuffer *b, struct mwEncryptItem *ei) {
+  g_return_if_fail(b != NULL);
+  g_return_if_fail(ei != NULL);
+
+  if(b->error) return;
+
+  guint16_get(b, &ei->id);
+  mwOpaque_get(b, &ei->info);
+}
+
+
+void mwEncryptItem_clear(struct mwEncryptItem *ei) {
+  if(! ei) return;
+  ei->id = 0x0000;
+  mwOpaque_clear(&ei->info);
+}
+
+
+/* 8.4.2.1 Awareness ID Block */
+
+
+/** @todo move this into srvc_aware */
+
+void mwAwareIdBlock_put(struct mwPutBuffer *b,
+			const struct mwAwareIdBlock *idb) {
+
+  g_return_if_fail(b != NULL);
+  g_return_if_fail(idb != NULL);
+
+  guint16_put(b, idb->type);
+  mwString_put(b, idb->user);
+  mwString_put(b, idb->community);
+}
+
+
+void mwAwareIdBlock_get(struct mwGetBuffer *b, struct mwAwareIdBlock *idb) {
+  g_return_if_fail(b != NULL);
+  g_return_if_fail(idb != NULL);
+
+  if(b->error) return;
+
+  guint16_get(b, &idb->type);
+  mwString_get(b, &idb->user);
+  mwString_get(b, &idb->community);
+}
+
+
+void mwAwareIdBlock_clone(struct mwAwareIdBlock *to,
+			  const struct mwAwareIdBlock *from) {
+
+  g_return_if_fail(to != NULL);
+  g_return_if_fail(from != NULL);
+
+  to->type = from->type;
+  to->user = g_strdup(from->user);
+  to->community = g_strdup(from->community);
+}
+
+
+void mwAwareIdBlock_clear(struct mwAwareIdBlock *idb) {
+  if(! idb) return;
+  g_free(idb->user);
+  g_free(idb->community);
+  memset(idb, 0x00, sizeof(struct mwAwareIdBlock));
+}
+
+
+guint mwAwareIdBlock_hash(const struct mwAwareIdBlock *a) {
+  return (a)? g_str_hash(a->user): 0;
+}
+
+
+gboolean mwAwareIdBlock_equal(const struct mwAwareIdBlock *a,
+			      const struct mwAwareIdBlock *b) {
+
+  g_return_val_if_fail(a != NULL, FALSE);
+  g_return_val_if_fail(b != NULL, FALSE);
+  
+  return ( (a->type == b->type) &&
+	   mw_streq(a->user, b->user) &&
+	   mw_streq(a->community, b->community) );
+}
+
+
+/* 8.4.2.4 Snapshot */
+
+void mwAwareSnapshot_get(struct mwGetBuffer *b, struct mwAwareSnapshot *idb) {
+  guint32 junk;
+  char *empty = NULL;
+
+  g_return_if_fail(b != NULL);
+  g_return_if_fail(idb != NULL);
+
+  guint32_get(b, &junk);
+  mwAwareIdBlock_get(b, &idb->id);
+  mwString_get(b, &idb->group);
+  gboolean_get(b, &idb->online);
+
+  g_free(empty);
+
+  if(idb->online) {
+    mwString_get(b, &idb->alt_id);
+    mwUserStatus_get(b, &idb->status);
+    mwString_get(b, &idb->name);
+  }
+}
+
+
+void mwAwareSnapshot_clone(struct mwAwareSnapshot *to,
+			   const struct mwAwareSnapshot *from) {
+
+  g_return_if_fail(to != NULL);
+  g_return_if_fail(from != NULL);
+
+  mwAwareIdBlock_clone(&to->id, &from->id);
+  if( (to->online = from->online) ) {
+    to->alt_id = g_strdup(from->alt_id);
+    mwUserStatus_clone(&to->status, &from->status);
+    to->name = g_strdup(from->name);
+    to->group = g_strdup(from->group);
+  }
+}
+
+
+void mwAwareSnapshot_clear(struct mwAwareSnapshot *idb) {
+  if(! idb) return;
+  mwAwareIdBlock_clear(&idb->id);
+  mwUserStatus_clear(&idb->status);
+  g_free(idb->alt_id);
+  g_free(idb->name);
+  g_free(idb->group);
+  memset(idb, 0x00, sizeof(struct mwAwareSnapshot));
+}
+
+
+const char *mwLoginType_getName(enum mwLoginType type) {
+  switch(type) {
+  case mwLogin_LIB:
+    return "Lotus Binary Library";
+
+  case mwLogin_JAVA_WEB:
+    return "Lotus Java Client Applet";
+
+  case mwLogin_BINARY:
+    return "Lotus Sametime";
+
+  case mwLogin_JAVA_APP:
+    return "Lotus Java Client Application";
+
+  case mwLogin_NOTES_6_5:
+    return "Lotus Notes Client 6.5.2+";
+
+  case mwLogin_NOTES_7_0:
+    return "Lotus Notes Client 7";
+
+  case mwLogin_ICT:
+    return "IBM Community Tools (ICT)";
+
+  case mwLogin_NOTESBUDDY:
+  case mwLogin_NOTESBUDDY_4_15:
+    return "Alphaworks NotesBuddy";
+
+  case mwLogin_SANITY:
+    return "Sanity";
+
+  case mwLogin_ST_PERL:
+    return "ST-Send-Message";
+
+  case mwLogin_PMR_ALERT:
+    return "PMR Alert";
+
+  case mwLogin_TRILLIAN:
+  case mwLogin_TRILLIAN_IBM:
+    return "Trillian";
+
+  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;
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/error.c	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,96 @@
+
+/*
+  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 <stdio.h>
+#include <string.h>
+
+#include "mw_error.h"
+
+
+static char *err_to_str(guint32 code) {
+  static char b[11]; /* 0x12345678 + NULL terminator */
+  sprintf((char *) b, "0x%08x", code);
+  b[10] = '\0';
+  return b;
+}
+
+
+#define CASE(val, str) \
+case val: \
+  m = str; \
+  break;
+
+
+char* mwError(guint32 code) {
+  const char *m;
+
+  switch(code) {
+
+    /* 8.3.1.1 General error/success codes */
+    CASE(ERR_SUCCESS, "Success");
+    CASE(ERR_FAILURE, "General failure");
+    CASE(ERR_REQUEST_DELAY, "Request delayed");
+    CASE(ERR_REQUEST_INVALID, "Request is invalid");
+    CASE(ERR_NOT_AUTHORIZED, "Not authorized");
+    CASE(ERR_NO_USER, "User is not online");
+    CASE(ERR_CHANNEL_NO_SUPPORT, "Requested channel is not supported");
+    CASE(ERR_CHANNEL_EXISTS, "Requested channel already exists");
+    CASE(ERR_SERVICE_NO_SUPPORT, "Requested service is not supported");
+    CASE(ERR_PROTOCOL_NO_SUPPORT, "Requested protocol is not supported");
+    CASE(ERR_VERSION_NO_SUPPORT, "Version is not supported");
+    CASE(ERR_USER_SKETCHY, "User is invalid or not trusted");
+    CASE(ERR_ALREADY_INITIALIZED, "Already initialized");
+    CASE(ERR_ENCRYPT_NO_SUPPORT, "Encryption method not supported");
+    CASE(ERR_NO_COMMON_ENCRYPT, "No common encryption method");
+    
+    /* 8.3.1.2 Connection/disconnection errors */
+    CASE(VERSION_MISMATCH, "Version mismatch");
+    CASE(FAT_MESSAGE, "Message is too large");
+    CASE(CONNECTION_BROKEN, "Connection broken");
+    CASE(CONNECTION_ABORTED, "Connection aborted");
+    CASE(CONNECTION_REFUSED, "Connection refused");
+    CASE(CONNECTION_RESET, "Connection reset");
+    CASE(CONNECTION_TIMED, "Connection timed out");
+    CASE(CONNECTION_CLOSED, "Connection closed");
+    CASE(INCORRECT_LOGIN, "Incorrect Username/Password");
+    CASE(VERIFICATION_DOWN, "Login verification down or unavailable");
+    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");
+
+    /* 8.3.1.3 Client error codes */
+    CASE(ERR_CLIENT_USER_GONE, "User is not online");
+    CASE(ERR_CLIENT_USER_DND, "User is in Do Not Disturb mode");
+    CASE(ERR_CLIENT_USER_ELSEWHERE, "Already logged in elsewhere");
+
+    /* 8.3.1.4 IM error codes */
+    CASE(ERR_IM_COULDNT_REGISTER, "Cannot register a reserved type");
+    CASE(ERR_IM_ALREADY_REGISTERED, "Requested type is already registered");
+    CASE(ERR_IM_NOT_REGISTERED, "Requested type is not registered");
+
+  default:
+    m = err_to_str(code);
+  }
+
+  return g_strdup(m);
+}
+
+
+#undef CASE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/message.c	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,715 @@
+
+/*
+  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 "mw_debug.h"
+#include "mw_message.h"
+
+
+/* 7.1 Layering and message encapsulation */
+/* 7.1.1 The Sametime Message Header */
+
+
+static void mwMessageHead_put(struct mwPutBuffer *b, struct mwMessage *msg) {
+  guint16_put(b, msg->type);
+  guint16_put(b, msg->options);
+  guint32_put(b, msg->channel);
+  
+  if(msg->options & mwMessageOption_HAS_ATTRIBS)
+    mwOpaque_put(b, &msg->attribs);
+}
+
+
+static void mwMessageHead_get(struct mwGetBuffer *b, struct mwMessage *msg) {
+
+  if(mwGetBuffer_error(b)) return;
+
+  guint16_get(b, &msg->type);
+  guint16_get(b, &msg->options);
+  guint32_get(b, &msg->channel);
+
+  if(msg->options & mwMessageOption_HAS_ATTRIBS)
+    mwOpaque_get(b, &msg->attribs);
+}
+
+
+static void mwMessageHead_clone(struct mwMessage *to,
+				struct mwMessage *from) {
+
+  to->type = from->type;
+  to->options = from->options;
+  to->channel = from->channel;
+  mwOpaque_clone(&to->attribs, &from->attribs);
+}
+
+
+static void mwMessageHead_clear(struct mwMessage *msg) {
+  mwOpaque_clear(&msg->attribs);
+}
+
+
+/* 8.4 Messages */
+/* 8.4.1 Basic Community Messages */
+/* 8.4.1.1 Handshake */
+
+
+static void HANDSHAKE_put(struct mwPutBuffer *b, struct mwMsgHandshake *msg) {
+  guint16_put(b, msg->major);
+  guint16_put(b, msg->minor);
+  guint32_put(b, msg->head.channel);
+  guint32_put(b, msg->srvrcalc_addr);
+  guint16_put(b, msg->login_type);
+  guint32_put(b, msg->loclcalc_addr);
+}
+
+
+static void HANDSHAKE_clear(struct mwMsgHandshake *msg) {
+  ; /* nothing to clean up */
+}
+
+
+/* 8.4.1.2 HandshakeAck */
+
+
+static void HANDSHAKE_ACK_get(struct mwGetBuffer *b,
+			      struct mwMsgHandshakeAck *msg) {
+
+  if(mwGetBuffer_error(b)) return;
+
+  guint16_get(b, &msg->major);
+  guint16_get(b, &msg->minor);
+  guint32_get(b, &msg->srvrcalc_addr);
+
+  /** @todo: get a better handle on what versions support what parts
+      of this message. eg: minor version 0x0018 doesn't send the
+      following */
+  if(msg->major >= 0x1e && msg->minor > 0x18) {
+    guint32_get(b, &msg->unknown);
+    mwOpaque_get(b, &msg->data);
+  }
+}
+
+
+static void HANDSHAKE_ACK_clear(struct mwMsgHandshakeAck *msg) {
+  mwOpaque_clear(&msg->data);
+}
+
+
+/* 8.4.1.3 Login */
+
+
+static void LOGIN_put(struct mwPutBuffer *b, struct mwMsgLogin *msg) {
+  guint16_put(b, msg->login_type);
+  mwString_put(b, msg->name);
+
+  /* ordering reversed from draft?? */
+  mwOpaque_put(b, &msg->auth_data);
+  guint16_put(b, msg->auth_type);
+}
+
+
+static void LOGIN_clear(struct mwMsgLogin *msg) {
+  g_free(msg->name);
+  mwOpaque_clear(&msg->auth_data);
+}
+
+
+/* 8.4.1.4 LoginAck */
+
+
+static void LOGIN_ACK_get(struct mwGetBuffer *b, struct mwMsgLoginAck *msg) {
+  guint16 junk;
+
+  if(mwGetBuffer_error(b)) return;
+
+  mwLoginInfo_get(b, &msg->login);
+  guint16_get(b, &junk);
+  mwPrivacyInfo_get(b, &msg->privacy);
+  mwUserStatus_get(b, &msg->status);
+}
+
+
+static void LOGIN_ACK_clear(struct mwMsgLoginAck *msg) {
+  mwLoginInfo_clear(&msg->login);
+  mwPrivacyInfo_clear(&msg->privacy);
+  mwUserStatus_clear(&msg->status);
+}
+
+
+/* 8.4.1.5 LoginCont */
+
+
+static void LOGIN_CONTINUE_put(struct mwPutBuffer *b,
+			       struct mwMsgLoginContinue *msg) {
+
+  ; /* nothing but a message header */
+}
+
+
+static void LOGIN_CONTINUE_clear(struct mwMsgLoginContinue *msg) {
+  ; /* this is a very simple message */
+}
+
+
+/* 8.4.1.6 AuthPassed */
+
+
+static void LOGIN_REDIRECT_get(struct mwGetBuffer *b,
+			       struct mwMsgLoginRedirect *msg) {
+
+  if(mwGetBuffer_error(b)) return;
+  mwString_get(b, &msg->host);
+  mwString_get(b, &msg->server_id);  
+}
+
+
+static void LOGIN_REDIRECT_clear(struct mwMsgLoginRedirect *msg) {
+  g_free(msg->host);
+  msg->host = NULL;
+
+  g_free(msg->server_id);
+  msg->server_id = NULL;
+}
+
+
+/* 8.4.1.7 CreateCnl */
+
+
+static void enc_offer_put(struct mwPutBuffer *b, struct mwEncryptOffer *enc) {
+  char tail = 0x07;
+
+  guint16_put(b, enc->mode);
+
+  if(enc->items) {
+    guint32 count;
+    struct mwPutBuffer *p;
+    struct mwOpaque o;
+    GList *list;
+
+    /* write the count, items, extra, and flag into a tmp buffer,
+       render that buffer into an opaque, and write it into b */
+
+    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);
+    }
+
+    guint16_put(p, enc->extra);
+    gboolean_put(p, enc->flag);
+
+    mwPutBuffer_finalize(&o, p);
+    mwOpaque_put(b, &o);
+    mwOpaque_clear(&o);
+  }
+
+  guint32_put(b, 0x00);
+  guint32_put(b, 0x00);
+  gboolean_put(b, FALSE);
+  mwPutBuffer_write(b, &tail, 1);
+}
+
+
+static void CHANNEL_CREATE_put(struct mwPutBuffer *b,
+			       struct mwMsgChannelCreate *msg) {
+
+  guint32_put(b, msg->reserved);
+  guint32_put(b, msg->channel);
+  mwIdBlock_put(b, &msg->target);
+  guint32_put(b, msg->service);
+  guint32_put(b, msg->proto_type);
+  guint32_put(b, msg->proto_ver);
+  guint32_put(b, msg->options);
+  mwOpaque_put(b, &msg->addtl);
+  gboolean_put(b, msg->creator_flag);
+
+  if(msg->creator_flag)
+    mwLoginInfo_put(b, &msg->creator);
+
+  enc_offer_put(b, &msg->encrypt);
+}
+
+
+static void enc_offer_get(struct mwGetBuffer *b,
+			  struct mwEncryptOffer *enc) {
+  guint32 skip;
+
+  if(mwGetBuffer_error(b)) return;
+
+  guint16_get(b, &enc->mode);
+  guint32_get(b, &skip);
+
+  if(skip >= 7) {
+    guint32 count;
+
+    guint32_get(b, &count);
+
+    while(count-- && (! mwGetBuffer_error(b))) {
+      struct mwEncryptItem *ei = g_new0(struct mwEncryptItem, 1);
+      mwEncryptItem_get(b, ei);
+      enc->items = g_list_append(enc->items, ei);
+    }
+
+    guint16_get(b, &enc->extra);
+    gboolean_get(b, &enc->flag);
+  }
+}
+
+
+static void CHANNEL_CREATE_get(struct mwGetBuffer *b,
+			       struct mwMsgChannelCreate *msg) {
+
+  if(mwGetBuffer_error(b)) return;
+
+  guint32_get(b, &msg->reserved);
+  guint32_get(b, &msg->channel);
+  mwIdBlock_get(b, &msg->target);
+  guint32_get(b, &msg->service);
+  guint32_get(b, &msg->proto_type);
+  guint32_get(b, &msg->proto_ver);
+  guint32_get(b, &msg->options);
+  mwOpaque_get(b, &msg->addtl);
+  gboolean_get(b, &msg->creator_flag);
+  
+  if(msg->creator_flag)
+    mwLoginInfo_get(b, &msg->creator);
+  
+  enc_offer_get(b, &msg->encrypt);
+}
+
+
+static void CHANNEL_CREATE_clear(struct mwMsgChannelCreate *msg) {
+  GList *list;
+
+  mwIdBlock_clear(&msg->target);
+  mwOpaque_clear(&msg->addtl);
+  mwLoginInfo_clear(&msg->creator);
+  
+  for(list = msg->encrypt.items; list; list = list->next) {
+    mwEncryptItem_clear(list->data);
+    g_free(list->data);
+  }
+  g_list_free(msg->encrypt.items);
+}
+
+
+/* 8.4.1.8 AcceptCnl */
+
+
+static void enc_accept_put(struct mwPutBuffer *b,
+			   struct mwEncryptAccept *enc) {
+
+  char tail = 0x07;
+
+  guint16_put(b, enc->mode);
+
+  if(enc->item) {
+    struct mwPutBuffer *p;
+    struct mwOpaque o;
+
+    p = mwPutBuffer_new();
+
+    mwEncryptItem_put(p, enc->item);
+    guint16_put(p, enc->extra);
+    gboolean_put(p, enc->flag);
+
+    mwPutBuffer_finalize(&o, p);
+    mwOpaque_put(b, &o);
+    mwOpaque_clear(&o);
+  }
+
+  guint32_put(b, 0x00);
+  guint32_put(b, 0x00);
+  gboolean_put(b, FALSE);
+
+  mwPutBuffer_write(b, &tail, 1);
+}
+
+
+static void CHANNEL_ACCEPT_put(struct mwPutBuffer *b,
+			       struct mwMsgChannelAccept *msg) {
+  
+  guint32_put(b, msg->service);
+  guint32_put(b, msg->proto_type);
+  guint32_put(b, msg->proto_ver);
+  mwOpaque_put(b, &msg->addtl);
+  gboolean_put(b, msg->acceptor_flag);
+  
+  if(msg->acceptor_flag)
+    mwLoginInfo_put(b, &msg->acceptor);
+  
+  enc_accept_put(b, &msg->encrypt);
+}
+
+
+static void enc_accept_get(struct mwGetBuffer *b,
+			   struct mwEncryptAccept *enc) {
+  guint32 skip;
+
+  if(mwGetBuffer_error(b)) return;
+
+  guint16_get(b, &enc->mode);
+  guint32_get(b, &skip);
+
+  if(skip >= 6) {
+    enc->item = g_new0(struct mwEncryptItem, 1);
+    mwEncryptItem_get(b, enc->item);
+  }
+
+  if(skip >= 9) {
+    guint16_get(b, &enc->extra);
+    gboolean_get(b, &enc->flag);
+  }
+}
+
+
+static void CHANNEL_ACCEPT_get(struct mwGetBuffer *b,
+			       struct mwMsgChannelAccept *msg) {
+
+  if(mwGetBuffer_error(b)) return;
+
+  guint32_get(b, &msg->service);
+  guint32_get(b, &msg->proto_type);
+  guint32_get(b, &msg->proto_ver);
+  mwOpaque_get(b, &msg->addtl);
+  gboolean_get(b, &msg->acceptor_flag);
+
+  if(msg->acceptor_flag)
+    mwLoginInfo_get(b, &msg->acceptor);
+
+  enc_accept_get(b, &msg->encrypt);
+}
+
+
+static void CHANNEL_ACCEPT_clear(struct mwMsgChannelAccept *msg) {
+  mwOpaque_clear(&msg->addtl);
+  mwLoginInfo_clear(&msg->acceptor);
+
+  if(msg->encrypt.item) {
+    mwEncryptItem_clear(msg->encrypt.item);
+    g_free(msg->encrypt.item);
+  }
+}
+
+
+/* 8.4.1.9 SendOnCnl */
+
+
+static void CHANNEL_SEND_put(struct mwPutBuffer *b,
+			     struct mwMsgChannelSend *msg) {
+
+  guint16_put(b, msg->type);
+  mwOpaque_put(b, &msg->data);
+}
+
+
+static void CHANNEL_SEND_get(struct mwGetBuffer *b,
+			     struct mwMsgChannelSend *msg) {
+
+  if(mwGetBuffer_error(b)) return;
+
+  guint16_get(b, &msg->type);
+  mwOpaque_get(b, &msg->data);
+}
+
+
+static void CHANNEL_SEND_clear(struct mwMsgChannelSend *msg) {
+  mwOpaque_clear(&msg->data);
+}
+
+
+/* 8.4.1.10 DestroyCnl */
+
+
+static void CHANNEL_DESTROY_put(struct mwPutBuffer *b,
+				struct mwMsgChannelDestroy *msg) {
+  guint32_put(b, msg->reason);
+  mwOpaque_put(b, &msg->data);
+}
+
+
+static void CHANNEL_DESTROY_get(struct mwGetBuffer *b,
+				struct mwMsgChannelDestroy *msg) {
+
+  if(mwGetBuffer_error(b)) return;
+
+  guint32_get(b, &msg->reason);
+  mwOpaque_get(b, &msg->data);
+}
+
+
+static void CHANNEL_DESTROY_clear(struct mwMsgChannelDestroy *msg) {
+  mwOpaque_clear(&msg->data);
+}
+
+
+/* 8.4.1.11 SetUserStatus */
+
+
+static void SET_USER_STATUS_put(struct mwPutBuffer *b,
+				struct mwMsgSetUserStatus *msg) {
+  mwUserStatus_put(b, &msg->status);
+}
+
+
+static void SET_USER_STATUS_get(struct mwGetBuffer *b,
+				struct mwMsgSetUserStatus *msg) {
+
+  if(mwGetBuffer_error(b)) return;
+  mwUserStatus_get(b, &msg->status);
+}
+
+
+static void SET_USER_STATUS_clear(struct mwMsgSetUserStatus *msg) {
+  mwUserStatus_clear(&msg->status);
+}
+
+
+/* 8.4.1.12 SetPrivacyList */
+
+
+static void SET_PRIVACY_LIST_put(struct mwPutBuffer *b,
+				 struct mwMsgSetPrivacyList *msg) {
+  mwPrivacyInfo_put(b, &msg->privacy);
+}
+
+
+static void SET_PRIVACY_LIST_get(struct mwGetBuffer *b,
+				 struct mwMsgSetPrivacyList *msg) {
+
+  if(mwGetBuffer_error(b)) return;
+  mwPrivacyInfo_get(b, &msg->privacy);
+}
+
+
+static void SET_PRIVACY_LIST_clear(struct mwMsgSetPrivacyList *msg) {
+  mwPrivacyInfo_clear(&msg->privacy);
+}
+
+
+/* Sense Service messages */
+
+
+static void SENSE_SERVICE_put(struct mwPutBuffer *b,
+			      struct mwMsgSenseService *msg) {
+  guint32_put(b, msg->service);
+}
+
+
+static void SENSE_SERVICE_get(struct mwGetBuffer *b,
+			      struct mwMsgSenseService *msg) {
+
+  if(mwGetBuffer_error(b)) return;
+  guint32_get(b, &msg->service);
+}
+
+
+static void SENSE_SERVICE_clear(struct mwMsgSenseService *msg) {
+  ;
+}
+
+
+/* Admin messages */
+
+
+static void ADMIN_get(struct mwGetBuffer *b, struct mwMsgAdmin *msg) {
+  mwString_get(b, &msg->text);
+}
+
+
+static void ADMIN_clear(struct mwMsgAdmin *msg) {
+  g_free(msg->text);
+  msg->text = NULL;
+}
+
+
+#define CASE(v, t) \
+case mwMessage_ ## v: \
+  msg = (struct mwMessage *) g_new0(struct t, 1); \
+  msg->type = type; \
+  break;
+
+
+struct mwMessage *mwMessage_new(enum mwMessageType type) {
+  struct mwMessage *msg = NULL;
+  
+  switch(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);
+    CASE(CHANNEL_SEND, mwMsgChannelSend);
+    CASE(CHANNEL_ACCEPT, mwMsgChannelAccept);
+    CASE(SET_USER_STATUS, mwMsgSetUserStatus);
+    CASE(SET_PRIVACY_LIST, mwMsgSetPrivacyList);
+    CASE(SENSE_SERVICE, mwMsgSenseService);
+    CASE(ADMIN, mwMsgAdmin);
+    
+  default:
+    g_warning("unknown message type 0x%02x\n", type);
+  }
+  
+  return msg;
+}
+
+
+#undef CASE
+
+
+/* each type needs to be passed to a specially named _get functions,
+   and cast to a specific subclass of mwMessage. */
+#define CASE(v, t) \
+case mwMessage_ ## v: \
+  msg = (struct mwMessage *) g_new0(struct t, 1); \
+  mwMessageHead_clone(msg, &head); \
+  v ## _get(b, (struct t *) msg); \
+  break;
+
+
+struct mwMessage *mwMessage_get(struct mwGetBuffer *b) {
+  struct mwMessage *msg = NULL;
+  struct mwMessage head;
+  
+  g_return_val_if_fail(b != NULL, NULL);
+
+  head.attribs.len = 0;
+  head.attribs.data = NULL;
+
+  /* attempt to read the header first */
+  mwMessageHead_get(b, &head);
+
+  if(mwGetBuffer_error(b)) {
+    mwMessageHead_clear(&head);
+    g_warning("problem parsing message head from buffer");
+    return NULL;
+  }
+
+  /* load the rest of the message depending on the header type */
+  switch(head.type) {
+    CASE(HANDSHAKE_ACK, mwMsgHandshakeAck);
+    CASE(LOGIN_REDIRECT, mwMsgLoginRedirect);
+    CASE(LOGIN_ACK, mwMsgLoginAck);
+    CASE(CHANNEL_CREATE, mwMsgChannelCreate);
+    CASE(CHANNEL_DESTROY, mwMsgChannelDestroy);
+    CASE(CHANNEL_SEND, mwMsgChannelSend);
+    CASE(CHANNEL_ACCEPT, mwMsgChannelAccept);
+    CASE(SET_USER_STATUS, mwMsgSetUserStatus);
+    CASE(SET_PRIVACY_LIST, mwMsgSetPrivacyList);
+    CASE(SENSE_SERVICE, mwMsgSenseService);
+    CASE(ADMIN, mwMsgAdmin);
+
+  default:
+    g_warning("unknown message type 0x%02x, no parse handler", head.type);
+  }
+
+  if(mwGetBuffer_error(b)) {
+    g_warning("problem parsing message type 0x%02x, not enough data",
+	      head.type);
+  }
+
+  mwMessageHead_clear(&head);
+  
+  return msg;
+}
+
+
+#undef CASE
+
+
+#define CASE(v, t) \
+case mwMessage_ ## v: \
+  v ## _put(b, (struct t *) msg); \
+  break;
+
+
+void mwMessage_put(struct mwPutBuffer *b, struct mwMessage *msg) {
+
+  g_return_if_fail(b != NULL);
+  g_return_if_fail(msg != NULL);
+
+  mwMessageHead_put(b, msg);
+
+  switch(msg->type) {
+    CASE(HANDSHAKE, mwMsgHandshake);
+    CASE(LOGIN, mwMsgLogin);
+    CASE(LOGIN_CONTINUE, mwMsgLoginContinue);
+    CASE(CHANNEL_CREATE, mwMsgChannelCreate);
+    CASE(CHANNEL_DESTROY, mwMsgChannelDestroy);
+    CASE(CHANNEL_SEND, mwMsgChannelSend);
+    CASE(CHANNEL_ACCEPT, mwMsgChannelAccept);
+    CASE(SET_USER_STATUS, mwMsgSetUserStatus);
+    CASE(SET_PRIVACY_LIST, mwMsgSetPrivacyList);
+    CASE(SENSE_SERVICE, mwMsgSenseService);
+    
+  default:
+    ; /* hrm. */
+  }
+}
+
+
+#undef CASE
+
+
+#define CASE(v, t) \
+case mwMessage_ ## v: \
+  v ## _clear((struct t *) msg); \
+  break;
+
+
+void mwMessage_free(struct mwMessage *msg) {
+  if(! msg) return;
+
+  mwMessageHead_clear(msg);
+
+  switch(msg->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);
+    CASE(CHANNEL_SEND, mwMsgChannelSend);
+    CASE(CHANNEL_ACCEPT, mwMsgChannelAccept);
+    CASE(SET_USER_STATUS, mwMsgSetUserStatus);
+    CASE(SET_PRIVACY_LIST, mwMsgSetPrivacyList);
+    CASE(SENSE_SERVICE, mwMsgSenseService);
+    CASE(ADMIN, mwMsgAdmin);
+    
+  default:
+    ; /* hrm. */
+  }
+
+  g_free(msg);
+}
+
+
+#undef CASE
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/mw_channel.h	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,341 @@
+
+/*
+  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_CHANNEL_H
+#define _MW_CHANNEL_H
+
+
+#include <time.h>
+#include "mw_common.h"
+
+
+/** @file channel.h
+    
+Life-cycle of an outgoing channel:
+
+1: mwChannel_new is called. If there is a channel in the outgoing
+collection in state NEW, then it is returned. Otherwise, a channel
+is allocated, assigned a unique outgoing id, marked as NEW, and
+returned.
+
+2: channel is set to INIT status (effectively earmarking it as in-
+use).  fields on the channel can then be set as necessary to
+prepare it for creation.
+
+3: mwChannel_create is called. The channel is marked to WAIT status
+and a message is sent to the server. The channel is also marked as
+inactive as of that moment.
+
+4: the channel is accepted (step 5) or rejected (step 7)
+
+5: an accept message is received from the server, and the channel
+is marked as OPEN, and the inactive mark is removed. And messages
+in the in or out queues for that channel are processed. The channel
+is now ready to be used.
+
+6: data is sent and received over the channel
+
+7: the channel is closed either by receipt of a close message or by
+local action. If by local action, then a close message is sent to
+the server.  The channel is cleaned up, its queues dumped, and it
+is set to NEW status to await re-use.
+
+Life-cycle of an incoming channel:
+
+1: a channel create message is received. A channel is allocated and
+given an id matching the message. It is placed in status WAIT, and
+marked as inactive as of that moment. The service matching that
+channel is alerted of the incoming creation request.
+
+2: the service can either accept (step 3) or reject (step 5) the
+channel
+
+3: mwChannel_accept is called. The channel is marked as OPEN, and
+an accept message is sent to the server. And messages in the in or
+out queues for that channel are processed. The channel is now ready
+to be used.
+
+4: data is sent and received over the channel
+
+5: The channel is closed either by receipt of a close message or by
+local action. If by local action, then a close message is sent to
+the server.  The channel is cleaned up, its queues dumped, and it
+is deallocated. */
+
+
+/* place-holders */
+struct mwCipherInstance;
+struct mwMsgChannelAccept;
+struct mwMsgChannelCreate;
+struct mwMsgChannelDestroy;
+struct mwMsgChannelSend;
+struct mwService;
+struct mwSession;
+
+
+
+/** @struct mwChannel
+    Represents a channel to a service */
+struct mwChannel;
+
+
+/** @struct mwChannelSet
+    Collection of channels */
+struct mwChannelSet;
+
+
+/** special ID indicating the master channel */
+#define MW_MASTER_CHANNEL_ID  0x00000000
+
+
+/** non-zero if a channel id appears to be that of an outgoing channel */
+#define mwChannel_idIsOutgoing(id) \
+  (! (0x80000000 & (id)))
+
+/** non-zero if a channel id appears to be that of an incoming channel */
+#define mwChannel_idIsIncoming(id) \
+  (! mwChannel_idIsOutgoing(id))
+
+/** non-zero if a channel appears to be an outgoing channel */
+#define mwChannel_isOutgoing(chan) \
+  mwChannel_idIsOutgoing(mwChannel_getId(chan))
+
+/** non-zero if a channel appears to be an incoming channel */
+#define mwChannel_isIncoming(chan) \
+  mwChannel_idIsIncoming(mwChannel_getId(chan))
+
+
+/** channel status */
+enum mwChannelState {
+  mwChannel_NEW,      /**< channel is newly allocated, in the pool */
+  mwChannel_INIT,     /**< channel is being prepared, out of the pool */
+  mwChannel_WAIT,     /**< channel is waiting for accept */
+  mwChannel_OPEN,     /**< channel is accepted and open */
+  mwChannel_DESTROY,  /**< channel is being destroyed */
+  mwChannel_ERROR,    /**< channel is being destroyed due to error */
+  mwChannel_UNKNOWN,  /**< unknown state, or error determining state */
+};
+
+
+#define mwChannel_isState(chan, state) \
+  (mwChannel_getState(chan) == (state))
+
+
+/** channel statistic fields.
+    @see mwChannel_getStatistic */
+enum mwChannelStatField {
+  mwChannelStat_MSG_SENT,      /**< total send-on-chan messages sent */
+  mwChannelStat_MSG_RECV,      /**< total send-on-chan messages received */
+  mwChannelStat_U_BYTES_SENT,  /**< total bytes sent, pre-encryption */
+  mwChannelStat_U_BYTES_RECV,  /**< total bytes received, post-decryption */
+  mwChannelStat_OPENED_AT,     /**< time when channel was opened */
+  mwChannelStat_CLOSED_AT,     /**< time when channel was closed */
+};
+
+
+/** Allocate and initialize a channel set for a session */
+struct mwChannelSet *mwChannelSet_new(struct mwSession *);
+
+
+/** Clear and deallocate a channel set. Closes, clears, and frees all
+    contained channels. */
+void mwChannelSet_free(struct mwChannelSet *);
+
+
+/** Create an incoming channel with the given channel id. Channel's state
+    will be set to WAIT. Primarily for use in mw_session */
+struct mwChannel *mwChannel_newIncoming(struct mwChannelSet *, guint32 id);
+
+
+/** Create an outgoing channel. Its channel ID will be generated by
+    the owning channel set. Channel's state will be set to INIT */
+struct mwChannel *mwChannel_newOutgoing(struct mwChannelSet *);
+
+
+/** Obtain a reference to a channel by its id.
+    @returns the channel matching chan, or NULL */
+struct mwChannel *mwChannel_find(struct mwChannelSet *cs, guint32 chan);
+
+
+/** get the ID for a channel. 0x00 indicates an error, as that is not
+    a permissible value */
+guint32 mwChannel_getId(struct mwChannel *);
+
+
+/** get the session for a channel. */
+struct mwSession *mwChannel_getSession(struct mwChannel *);
+
+
+/** get the ID of the service for a channel. This may be 0x00 for NEW
+    channels */
+guint32 mwChannel_getServiceId(struct mwChannel *);
+
+
+/** get the service for a channel. This may be NULL for NEW
+    channels */
+struct mwService *mwChannel_getService(struct mwChannel *);
+
+
+/** associate a channel with an owning service */
+void mwChannel_setService(struct mwChannel *chan, struct mwService *srvc);
+
+
+/** get service-specific data. This is for use by service
+    implementations to easily associate information with the
+    channel */
+gpointer mwChannel_getServiceData(struct mwChannel *chan);
+
+
+/** set service-specific data. This is for use by service
+    implementations to easily associate information with the
+    channel */
+void mwChannel_setServiceData(struct mwChannel *chan,
+			      gpointer data, GDestroyNotify clean);
+
+
+void mwChannel_removeServiceData(struct mwChannel *chan);
+
+
+guint32 mwChannel_getProtoType(struct mwChannel *chan);
+
+
+void mwChannel_setProtoType(struct mwChannel *chan, guint32 proto_type);
+
+
+guint32 mwChannel_getProtoVer(struct mwChannel *chan);
+
+
+void mwChannel_setProtoVer(struct mwChannel *chan, guint32 proto_ver);
+
+
+guint32 mwChannel_getOptions(struct mwChannel *chan);
+
+
+void mwChannel_setOptions(struct mwChannel *chan, guint32 options);
+
+
+/** User at the other end of the channel. The target user for outgoing
+    channels, the creator for incoming channels */
+struct mwLoginInfo *mwChannel_getUser(struct mwChannel *chan);
+
+
+/** direct reference to the create addtl information for a channel */
+struct mwOpaque *mwChannel_getAddtlCreate(struct mwChannel *);
+
+
+/** direct reference to the accept addtl information for a channel */
+struct mwOpaque *mwChannel_getAddtlAccept(struct mwChannel *);
+
+
+/** automatically adds instances of all ciphers in the session to the
+    list of supported ciphers for a channel */
+void mwChannel_populateSupportedCipherInstances(struct mwChannel *chan);
+
+
+/** add a cipher instance to a channel's list of supported
+    ciphers. Channel must be NEW. */
+void mwChannel_addSupportedCipherInstance(struct mwChannel *chan,
+					  struct mwCipherInstance *ci);
+
+
+/** the list of supported ciphers for a channel. This list will be
+    empty once a cipher has been selected for the channel */
+GList *mwChannel_getSupportedCipherInstances(struct mwChannel *chan);
+
+
+/** select a cipher instance for a channel. A NULL instance indicates
+    that no encryption should be used. */
+void mwChannel_selectCipherInstance(struct mwChannel *chan,
+				    struct mwCipherInstance *ci);
+
+
+/** get the state of a channel  */
+enum mwChannelState mwChannel_getState(struct mwChannel *);
+
+
+/** obtain the value for a statistic field as a gpointer */
+gpointer mwChannel_getStatistic(struct mwChannel *chan,
+				enum mwChannelStatField stat);
+
+
+/** Formally open a channel.
+
+    For outgoing channels: instruct the session to send a channel
+    create message to the server, and to mark the channel (which must
+    be in INIT status) as being in WAIT status.
+   
+    For incoming channels: configures the channel according to options
+    in the channel create message. Marks the channel as being in WAIT
+    status
+*/
+int mwChannel_create(struct mwChannel *chan);
+
+
+/** Formally accept an incoming channel. Instructs the session to send
+    a channel accept message to the server, and to mark the channel as
+    being OPEN. */
+int mwChannel_accept(struct mwChannel *chan);
+
+
+/** Destroy a channel. Sends a channel-destroy message to the server,
+    and perform cleanup to remove the channel.
+
+    @param chan    the channel to destroy
+    @param reason  the reason code for closing the channel
+    @param data    optional additional information 
+*/
+int mwChannel_destroy(struct mwChannel *chan, guint32 reason,
+		      struct mwOpaque *data);
+
+
+/** Compose a send-on-channel message, encrypt it as per the channel's
+    specification, and send it */
+int mwChannel_send(struct mwChannel *chan, guint32 msg_type,
+		   struct mwOpaque *msg);
+
+
+/** Compose a send-on-channel message, and if encrypt is TRUE, encrypt
+    it as per the channel's specification, and send it */
+int mwChannel_sendEncrypted(struct mwChannel *chan,
+			    guint32 msg_type, struct mwOpaque *msg,
+			    gboolean encrypt);
+
+
+/** pass a create message to a channel for handling */
+void mwChannel_recvCreate(struct mwChannel *chan,
+			  struct mwMsgChannelCreate *msg);
+
+
+/** pass an accept message to a channel for handling */
+void mwChannel_recvAccept(struct mwChannel *chan,
+			  struct mwMsgChannelAccept *msg);
+
+
+/** pass a destroy message to a channel for handling */
+void mwChannel_recvDestroy(struct mwChannel *chan,
+			   struct mwMsgChannelDestroy *msg);
+
+
+/** Feed data into a channel. */
+void mwChannel_recv(struct mwChannel *chan, struct mwMsgChannelSend *msg);
+
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/mw_cipher.h	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,249 @@
+
+/*
+  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_CIPHER_H
+#define _MW_CIPHER_H
+
+
+#include <glib.h>
+#include "mw_common.h"
+
+
+/* place-holders */
+struct mwChannel;
+struct mwSession;
+
+
+
+/** Common cipher types */
+enum mwCipherType {
+  mwCipher_RC2_40   = 0x0000,
+  mwCipher_RC2_128  = 0x0001,
+};
+
+
+struct mwCipher;
+struct mwCipherInstance;
+
+
+/** Obtain an instance of a given cipher, which can be used for the
+    processing of a single channel. */
+typedef struct mwCipherInstance *(*mwCipherInstantiator)
+     (struct mwCipher *cipher, struct mwChannel *chan);
+
+
+/** Generate a descriptor for use in a channel create message to
+    indicate the availability of this cipher */
+typedef struct mwEncryptItem *(*mwCipherDescriptor)
+     (struct mwCipherInstance *instance);
+
+
+/** Process (encrypt or decrypt, depending) the given data. The passed
+    buffer may be freed in processing and be replaced with a freshly
+    allocated buffer. The post-processed buffer must in turn be freed
+    after use */
+typedef int (*mwCipherProcessor)
+     (struct mwCipherInstance *ci, struct mwOpaque *data);
+
+
+/** A cipher. Ciphers are primarily used to provide cipher instances
+    for bi-directional encryption on channels, but some may be used
+    for other activities. Expand upon this structure to create a
+    custom encryption provider.
+    @see mwCipherInstance */
+struct mwCipher {
+
+  /** service this cipher is providing for
+      @see mwCipher_getSession */
+  struct mwSession *session;
+
+  guint16 type;               /**< @see mwCipher_getType */
+  const char *(*get_name)();  /**< @see mwCipher_getName */
+  const char *(*get_desc)();  /**< @see mwCipher_getDesc */
+
+  /** Generate a new Cipher Instance for use on a channel
+      @see mwCipher_newInstance */
+  mwCipherInstantiator new_instance;
+
+  /** @see mwCipher_newItem */
+  mwCipherDescriptor new_item;
+
+  void (*offered)(struct mwCipherInstance *ci, struct mwEncryptItem *item);
+  void (*offer)(struct mwCipherInstance *ci);
+  void (*accepted)(struct mwCipherInstance *ci, struct mwEncryptItem *item);
+  void (*accept)(struct mwCipherInstance *ci);
+
+  mwCipherProcessor encrypt; /**< @see mwCipherInstance_encrypt */
+  mwCipherProcessor decrypt; /**< @see mwCipherInstance_decrypt */
+
+  /** prepare this cipher for being free'd
+      @see mwCipher_free */
+  void (*clear)(struct mwCipher *c);
+
+  /** clean up a cipher instance before being free'd
+      @see mwCipherInstance_free */
+  void (*clear_instance)(struct mwCipherInstance *ci);
+};
+
+
+/** An instance of a cipher. Expand upon this structure to contain
+    necessary state data
+    @see mwCipher */
+struct mwCipherInstance {
+
+  /** the parent cipher.
+      @see mwCipherInstance_getCipher */
+  struct mwCipher *cipher;
+
+  /** the channel this instances processes
+      @see mwCipherInstance_getChannel */
+  struct mwChannel *channel;
+};
+
+
+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 mwSession *mwCipher_getSession(struct mwCipher *cipher);
+
+
+guint16 mwCipher_getType(struct mwCipher *cipher);
+
+
+const char *mwCipher_getName(struct mwCipher *cipher);
+
+
+const char *mwCipher_getDesc(struct mwCipher *cipher);
+
+
+struct mwCipherInstance *mwCipher_newInstance(struct mwCipher *cipher,
+					      struct mwChannel *channel);
+
+
+/** destroy a cipher */
+void mwCipher_free(struct mwCipher* cipher);
+
+
+/** reference the parent cipher of an instance */
+struct mwCipher *mwCipherInstance_getCipher(struct mwCipherInstance *ci);
+
+
+struct mwEncryptItem *mwCipherInstance_newItem(struct mwCipherInstance *ci);
+
+
+/** Indicates a cipher has been offered to our channel */
+void mwCipherInstance_offered(struct mwCipherInstance *ci,
+			      struct mwEncryptItem *item);
+
+
+/** Offer a cipher */
+void mwCipherInstance_offer(struct mwCipherInstance *ci);
+
+
+/** Indicates an offered cipher has been accepted */
+void mwCipherInstance_accepted(struct mwCipherInstance *ci,
+			       struct mwEncryptItem *item);
+
+
+/** Accept a cipher offered to our channel */
+void mwCipherInstance_accept(struct mwCipherInstance *ci);
+
+
+/** encrypt data */
+int mwCipherInstance_encrypt(struct mwCipherInstance *ci,
+			     struct mwOpaque *data);
+
+
+/** decrypt data */
+int mwCipherInstance_decrypt(struct mwCipherInstance *ci,
+			     struct mwOpaque *data);
+
+
+/** destroy a cipher instance */
+void mwCipherInstance_free(struct mwCipherInstance *ci);
+
+
+/**
+  @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...
+*/
+/* @{ */
+
+
+/** generate some pseudo-random bytes
+    @param keylen  count of bytes to write into key
+    @param key     buffer to write keys into
+*/
+void rand_key(char *key, gsize keylen);
+
+
+/** Setup an Initialization Vector */
+void mwIV_init(char *iv);
+
+
+/** Expand a variable-length key into a 128-byte key (represented as
+    an an array of 64 ints) */
+void mwKeyExpand(int *ekey, const char *key, gsize keylen);
+
+
+/** Encrypt data using an already-expanded key */
+void mwEncryptExpanded(const int *ekey, char *iv,
+		       struct mwOpaque *in,
+		       struct mwOpaque *out);
+
+
+/** Encrypt data using an expanded form of the given key */
+void mwEncrypt(const char *key, gsize keylen, char *iv,
+	       struct mwOpaque *in, struct mwOpaque *out);
+
+
+/** Decrypt data using an already expanded key */
+void mwDecryptExpanded(const int *ekey, char *iv,
+		       struct mwOpaque *in,
+		       struct mwOpaque *out);
+
+
+/** Decrypt data using an expanded form of the given key */
+void mwDecrypt(const char *key, gsize keylen, char *iv,
+	       struct mwOpaque *in, struct mwOpaque *out);
+
+
+/* @} */
+
+
+#endif
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/mw_client.h	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,154 @@
+
+#ifndef _MW_CLIENT_H
+#define _MW_CLIENT_H
+
+
+#include <glib.h>
+#include <glib/glist.h>
+
+
+/* place-holders */
+struct mwChannel;
+struct mwClient;
+struct mwClientHandler;
+struct mwConnection;
+struct mwConnectionHandler;
+struct mwMessage;
+struct mwService;
+struct mwSession;
+
+
+
+/* @file mw_client.h
+   
+*/
+
+
+
+/* @section Client
+
+*/
+/*@{*/
+
+
+struct mwClient;
+
+
+typedef (struct mwConnectionHandler *)(*mwClientConnect)
+     (struct mwClient *c, const char *host);
+
+
+struct mwClientHandler {
+  mwClientConnect connect;
+}
+
+
+struct mwClient *
+mwClient_new(struct mwClientHandler *h);
+
+
+struct mwChannel *
+mwClient_newChannel(struct mwClient *client,
+		    struct mwService *srvc);
+
+
+struct mwChannel *
+mwClient_newMasterChannel(struct mwClient *client,
+			  struct mwSession *session);
+
+
+int mwClient_sendKeepAlive(struct mwClient *client);
+
+
+void mwClient_setUsesCountByte(struct mwClient *client,
+			      gboolean use);
+
+
+gboolean mwClient_getUsesCountByte(struct mwClient *client);
+
+
+void mwClient_destroy(struct mwClient *client);
+
+
+/*@}*/
+
+
+
+/* @section Connection
+
+*/
+/*{*/
+
+
+struct mwConnection;
+
+
+typedef (int)(*mwConnectionWrite)
+     (struct mwConnection *c, const char *buf, gsize len);
+
+
+typedef (void)(*mwConnectionClose)
+     (struct mwConnection *c);
+
+
+struct mwConnectionHandler {
+  mwConnectionWrite write;
+  mwConnectionClose close;
+}
+
+
+struct mwConnection *
+mwConnection_new(struct mwConnectionHandler *h);
+
+
+void mwConnection_recv(struct mwConnection *connection,
+		       const char *buf,
+		       gsize len);
+
+
+void mwConnection_destroy(struct mwConnection *connection);
+
+
+/*@}*/
+
+
+
+/* @section Channel
+
+*/
+/*@{*/
+
+
+struct mwChannel;
+
+
+int mwChannel_sendMessage(struct mwChannel *channel,
+			  struct mwMessage *msg);
+
+
+int mwChannel_send(struct mwChannel *channel,
+		   guint32 type,
+		   guint32 options,
+		   struct mwOpaque *data);
+
+
+int mwChannel_destroy(struct mwChannel *channel,
+		      guint32 reason,
+		      struct mwOpaque *info);
+
+
+gboolean mwChannel_isMasterChannel(struct mwChannel *channel);
+
+
+guint32 mwChannel_getId(struct mwChannel *channel);
+
+
+enum mwChannelState mwChannel_getState(struct mwChannel *channel);
+
+
+
+/*@}*/
+
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/mw_common.h	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,433 @@
+
+/*
+  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_COMMON_H
+#define _MW_COMMON_H
+
+
+#include <glib.h>
+
+
+/** @file common.h
+
+    Common data types and functions for handling those types.
+
+    Functions in this file all fit into similar naming conventions of
+    <code>TYPE_ACTION</code> as per the activity they perform. The
+    following actions are available:
+
+    <code>void TYPE_put(struct mwPutBuffer *b, TYPE *val)</code>
+    - marshalls val onto the buffer b. The buffer will grow as necessary
+    to fit all the data put into it. For guint16, guint32, and
+    gboolean, <code>TYPE val</code> is used instead of <code>TYPE
+    \*val</code>.
+
+    <code>void TYPE_get(struct mwGetBuffer *b, TYPE *val)</code>
+    - unmarshals val from the buffer b. Failure (due to lack of
+    insufficient remaining buffer) is indicated in the buffer's error
+    field. A call to a _get function with a buffer in an error state
+    has to effect.
+
+    <code>void TYPE_clear(TYPE *val)</code>
+    - zeros and frees internal members of val, but does not free val
+    itself. Needs to be called before free-ing any complex types which
+    have been unmarshalled from a TYPE_get or populated from a
+    TYPE_clone call to prevent memory leaks.
+
+    <code>void TYPE_clone(TYPE *to, TYPE *from)</code>
+    - copies/clones members of from into to. May result in memory
+    allocation for some types. Note that to is not cleared
+    before-hand, it must already be in a pristine condition.
+
+    <code>gboolean TYPE_equal(TYPE *y, TYPE *z)</code>
+    - simple equality test.
+*/
+
+
+/** @struct mwPutBuffer
+    buffer to be written to */
+struct mwPutBuffer;
+
+/** @struct mwGetBuffer
+    buffer to be read from */
+struct mwGetBuffer;
+
+
+/** A length of binary data, not null-terminated. */
+struct mwOpaque {
+  gsize len;   /**< length of data. */
+  char *data;  /**< data, normally with no NULL termination */
+};
+
+
+/* 8.3.6 Login Types */
+
+/** The type of login. Normally meaning the type of client code being
+    used to login with.
+
+    If you know of any additional client identifiers, please add them
+    below.
+
+    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.
+*/
+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 */
+
+  /* now we're getting crazy */
+  mwLogin_NOTES_6_5        = 0x1200,
+  mwLogin_NOTES_7_0        = 0x1210,
+  mwLogin_ICT              = 0x1300,
+  mwLogin_NOTESBUDDY       = 0x1400,  /**< 0xff00 mask? */
+  mwLogin_NOTESBUDDY_4_15  = 0x1405,
+  mwLogin_SANITY           = 0x1600,
+  mwLogin_ST_PERL          = 0x1625,
+  mwLogin_PMR_ALERT        = 0x1650,
+  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 */
+};
+
+
+/* 8.2 Common Structures */
+/* 8.2.1 Login Info block */
+
+struct mwLoginInfo {
+  char *login_id;   /**< community-unique ID of the login */
+  guint16 type;     /**< @see mwLoginType */
+  char *user_id;    /**< community-unique ID of the user */
+  char *user_name;  /**< name of user (nick name, full name, etc) */
+  char *community;  /**< community name (usually domain name) */
+  gboolean full;    /**< if FALSE, following fields non-existant */
+  char *desc;       /**< implementation defined description */
+  guint32 ip_addr;  /**< ip addr of the login */
+  char *server_id;  /**< unique ID of login's server */
+};
+
+
+/* 8.2.2 Private Info Block */
+
+struct mwUserItem {
+  gboolean full;    /**< if FALSE, don't include name */
+  char *id;         /**< user id */
+  char *community;  /**< community */
+  char *name;       /**< user name */
+};
+
+
+struct mwPrivacyInfo {
+  gboolean deny;             /**< deny (true) or allow (false) users */
+  guint32 count;             /**< count of users */
+  struct mwUserItem *users;  /**< the users list */
+};
+
+
+/* 8.3.5 User Status Types */
+
+enum mwStatusType {
+  mwStatus_ACTIVE  = 0x0020,
+  mwStatus_IDLE    = 0x0040,
+  mwStatus_AWAY    = 0x0060,
+  mwStatus_BUSY    = 0x0080,
+};
+
+
+/* 8.2.3 User Status Block */
+
+struct mwUserStatus {
+  guint16 status;  /**< @see mwStatusType */
+  guint32 time;    /**< last status change time in seconds */
+  char *desc;      /**< status description */
+};
+
+
+/* 8.2.4 ID Block */
+
+struct mwIdBlock {
+  char *user;       /**< user id (login id or empty for some services) */
+  char *community;  /**< community id (NULL for same community) */
+};
+
+
+/* 8.3.8.2 Awareness Presence Types */
+
+/* @todo move mwAwareType, mwAwareIdBlock and mwAwareSnapshot into the
+   aware service and out of common */
+
+/** type codes for mwAwareIdBlock */
+enum mwAwareType {
+  mwAware_USER    = 0x0002,  /**< a single user */
+  mwAware_GROUP   = 0x0003,  /**< a group */
+  mwAware_SERVER  = 0x0008,  /**< a server */
+};
+
+
+/* 8.4.2 Awareness Messages */
+/* 8.4.2.1 Awareness ID Block */
+
+struct mwAwareIdBlock {
+  guint16 type;     /**< @see mwAwareType */
+  char *user;       /**< user id */
+  char *community;  /**< community id (NULL for same community) */
+};
+
+
+/* 8.4.2.4 Snapshot */
+
+struct mwAwareSnapshot {
+  struct mwAwareIdBlock id;
+  char *group;                 /**< group this id belongs to */
+  gboolean online;             /**< is this user online? */
+  char *alt_id;                /**< alternate ID, often same as id.user */
+  struct mwUserStatus status;  /**< status of this user */
+  char *name;                  /**< Formatted version of ID */
+};
+
+
+/** encryption blocks */
+struct mwEncryptItem {
+  guint16 id;            /**< cipher identifier */
+  struct mwOpaque info;  /**< cipher information */
+};
+
+
+/** @name buffer utility functions */
+/*@{*/
+
+
+/** allocate a new empty buffer */
+struct mwPutBuffer *mwPutBuffer_new();
+
+
+/** write raw data to the put buffer */
+void mwPutBuffer_write(struct mwPutBuffer *b, gpointer data, gsize len);
+
+
+/** destroy the buffer */
+void mwPutBuffer_free(struct mwPutBuffer *b);
+
+
+/** move the buffer's data into an opaque, destroy the buffer */
+void mwPutBuffer_finalize(struct mwOpaque *to, struct mwPutBuffer *from);
+
+
+/** allocate a new buffer with a copy of the given data */
+struct mwGetBuffer *mwGetBuffer_new(struct mwOpaque *data);
+
+
+/** read len bytes of raw data from the get buffer into mem. If len is
+    greater than the count of bytes remaining in the buffer, the
+    buffer's error flag will NOT be set.
+
+    @returns count of bytes successfully copied to mem */
+gsize mwGetBuffer_read(struct mwGetBuffer *b, gpointer mem, gsize len);
+
+
+/** skip len bytes in the get buffer. If len is greater than the count
+    of bytes remaining in the buffer, the buffer's error flag will NOT
+    be set.
+
+    @returns count of bytes successfully skipped */
+gsize mwGetBuffer_advance(struct mwGetBuffer *b, gsize len);
+
+
+/** allocate a new buffer backed by the given data. Calling
+    mwGetBuffer_free will not result in the underlying data being
+    freed */
+struct mwGetBuffer *mwGetBuffer_wrap(const struct mwOpaque *data);
+
+
+/** destroy the buffer */
+void mwGetBuffer_free(struct mwGetBuffer *b);
+
+
+/** reset the buffer to the very beginning. Also clears the buffer's
+    error flag. */
+void mwGetBuffer_reset(struct mwGetBuffer *b);
+
+
+/** count of remaining available bytes */
+gsize mwGetBuffer_remaining(struct mwGetBuffer *b);
+
+
+/** TRUE if an error occurred while reading a basic type from this
+    buffer */
+gboolean mwGetBuffer_error(struct mwGetBuffer *b);
+
+
+/*@}*/
+
+
+/** provides a textual name for a given login type. If the type is not
+    known by name, returns NULL */
+const char *mwLoginType_getName(enum mwLoginType type);
+
+
+/** @name Basic Data Type Marshalling
+    The basic types are combined to construct the complex types.
+ */
+/*@{*/
+
+
+void guint16_put(struct mwPutBuffer *b, guint16 val);
+
+void guint16_get(struct mwGetBuffer *b, guint16 *val);
+
+guint16 guint16_peek(struct mwGetBuffer *b);
+
+
+void guint32_put(struct mwPutBuffer *b, guint32 val);
+
+void guint32_get(struct mwGetBuffer *b, guint32 *val);
+
+guint32 guint32_peek(struct mwGetBuffer *b);
+
+
+void gboolean_put(struct mwPutBuffer *b, gboolean val);
+
+void gboolean_get(struct mwGetBuffer *b, gboolean *val);
+
+gboolean gboolean_peek(struct mwGetBuffer *b);
+
+
+void mwString_put(struct mwPutBuffer *b, const char *str);
+
+void mwString_get(struct mwGetBuffer *b, char **str);
+
+
+void mwOpaque_put(struct mwPutBuffer *b, const struct mwOpaque *o);
+
+void mwOpaque_get(struct mwGetBuffer *b, struct mwOpaque *o);
+
+void mwOpaque_clear(struct mwOpaque *o);
+
+void mwOpaque_free(struct mwOpaque *o);
+
+void mwOpaque_clone(struct mwOpaque *to, const struct mwOpaque *from);
+
+
+/*@}*/
+
+
+/** @name Complex Data Type Marshalling */
+/*@{*/
+
+
+void mwLoginInfo_put(struct mwPutBuffer *b, const struct mwLoginInfo *info);
+
+void mwLoginInfo_get(struct mwGetBuffer *b, struct mwLoginInfo *info);
+
+void mwLoginInfo_clear(struct mwLoginInfo *info);
+
+void mwLoginInfo_clone(struct mwLoginInfo *to, const struct mwLoginInfo *from);
+
+
+void mwUserItem_put(struct mwPutBuffer *b, const struct mwUserItem *user);
+
+void mwUserItem_get(struct mwGetBuffer *b, struct mwUserItem *user);
+
+void mwUserItem_clear(struct mwUserItem *user);
+
+void mwUserItem_clone(struct mwUserItem *to, const struct mwUserItem *from);
+
+
+void mwPrivacyInfo_put(struct mwPutBuffer *b,
+		       const struct mwPrivacyInfo *info);
+
+void mwPrivacyInfo_get(struct mwGetBuffer *b, struct mwPrivacyInfo *info);
+
+void mwPrivacyInfo_clear(struct mwPrivacyInfo *info);
+
+
+void mwUserStatus_put(struct mwPutBuffer *b,
+		      const struct mwUserStatus *stat);
+
+void mwUserStatus_get(struct mwGetBuffer *b, struct mwUserStatus *stat);
+
+void mwUserStatus_clear(struct mwUserStatus *stat);
+
+void mwUserStatus_clone(struct mwUserStatus *to,
+			const struct mwUserStatus *from);
+
+
+void mwIdBlock_put(struct mwPutBuffer *b, const struct mwIdBlock *id);
+
+void mwIdBlock_get(struct mwGetBuffer *b, struct mwIdBlock *id);
+
+void mwIdBlock_clear(struct mwIdBlock *id);
+
+void mwIdBlock_clone(struct mwIdBlock *to,
+		     const struct mwIdBlock *from);
+
+guint mwIdBlock_hash(const struct mwIdBlock *idb);
+
+gboolean mwIdBlock_equal(const struct mwIdBlock *a,
+			 const struct mwIdBlock *b);
+
+
+void mwAwareIdBlock_put(struct mwPutBuffer *b,
+			const struct mwAwareIdBlock *idb);
+
+void mwAwareIdBlock_get(struct mwGetBuffer *b, struct mwAwareIdBlock *idb);
+
+void mwAwareIdBlock_clear(struct mwAwareIdBlock *idb);
+
+void mwAwareIdBlock_clone(struct mwAwareIdBlock *to,
+			  const struct mwAwareIdBlock *from);
+
+guint mwAwareIdBlock_hash(const struct mwAwareIdBlock *a);
+
+gboolean mwAwareIdBlock_equal(const struct mwAwareIdBlock *a,
+			      const struct mwAwareIdBlock *b);
+
+
+void mwAwareSnapshot_get(struct mwGetBuffer *b,
+			 struct mwAwareSnapshot *idb);
+
+void mwAwareSnapshot_clear(struct mwAwareSnapshot *idb);
+
+void mwAwareSnapshot_clone(struct mwAwareSnapshot *to,
+			   const struct mwAwareSnapshot *from);
+
+
+void mwEncryptItem_put(struct mwPutBuffer *b,
+		       const struct mwEncryptItem *item);
+
+void mwEncryptItem_get(struct mwGetBuffer *b, struct mwEncryptItem *item);
+
+void mwEncryptItem_clear(struct mwEncryptItem *item);
+
+
+/*@}*/
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/mw_debug.c	Sun Jun 05 02:50:13 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
+*/
+
+#include <glib/gstring.h>
+
+#include "mw_debug.h"
+
+
+#define FRM               "%02x"
+#define FRMT              "%02x%02x "
+#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) {
+  while(len) {
+    if(len >= 16) {
+      g_string_append_printf(str,
+			     FRMT FRMT FRMT FRMT FRMT FRMT FRMT FRMT "\n",
+			     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 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
+
+
+void pretty_print(const char *buf, gsize len) {
+#ifdef DEBUG
+  GString *str;
+
+  if(! len) return;
+
+  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
+  ;
+}
+
+
+void pretty_print_opaque(struct mwOpaque *o) {
+  if(! o) return;
+  pretty_print(o->data, o->len);
+}
+
+
+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
+  */
+
+#ifdef DEBUG
+  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");
+
+  txt = g_strdup_vprintf(info, args);
+  g_string_append(str, txt);
+  g_free(txt);
+
+  g_string_append(str, "\n");
+
+  if(block) {
+    t_pretty_print(str, block->data, block->len);
+  }
+
+  g_string_append(str, MW_MAILME_CUT_STOP);
+
+  g_debug(str->str);
+  g_string_free(str, TRUE);
+#endif
+  ;
+}
+
+
+void mw_debug_mailme(struct mwOpaque *block,
+		     const char *info, ...) {
+  va_list args;
+  va_start(args, info);
+  mw_debug_mailme_v(block, info, args);
+  va_end(args);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/mw_debug.h	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,112 @@
+
+/*
+  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_DEBUG_H
+#define _MW_DEBUG_H
+
+
+#include <stdarg.h>
+#include <glib.h>
+
+#include "mw_common.h"
+
+
+/** replaces NULL strings with "(null)". useful for printf where
+    you're unsure that the %s will be non-NULL. Note that while the
+    linux printf will do this automatically, not all will. The others
+    will instead segfault */
+#define NSTR(str) ((str)? (str): "(null)")
+
+
+#ifndef g_debug
+#define g_debug(format...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, format)
+#endif
+
+
+#ifndef g_info
+#define g_info(format...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, format)
+#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"
+#endif
+
+
+#ifndef MW_MAILME_CUT_START
+#define MW_MAILME_CUT_START  "-------- begin copy --------"
+#endif
+
+
+#ifndef MW_MAILME_CUT_STOP
+#define MW_MAILME_CUT_STOP   "--------- end copy ---------"
+#endif
+
+
+#ifndef MW_MAILME_MESSAGE
+/** message used in mw_debug_mailme instructing user on what to do
+    with the debugging output produced from that function */
+#define MW_MAILME_MESSAGE "\n" \
+ "  Greetings! It seems that you've run across protocol data that the\n" \
+ "Meanwhile library does not yet know about. As such, there may be\n"    \
+ "some unexpected behaviour in this session. If you'd like to help\n"    \
+ "resolve this issue, please copy and paste the following block into\n"  \
+ "an email to the address listed below with a brief explanation of\n"    \
+ "what you were doing at the time of this message. Thanks a lot!"
+#endif
+
+
+/** Outputs a hex dump of a mwOpaque with debugging info and a
+    pre-defined message. Identical to mw_debug_mailme, but taking a
+    va_list argument */
+void mw_debug_mailme_v(struct mwOpaque *block,
+		       const char *info, va_list args);
+
+
+/** Outputs a hex dump of a mwOpaque with debugging info and a
+    pre-defined message.
+
+    @arg block  data to be printed in a hex block
+    @arg info   a printf-style format string
+
+    The resulting message is in the following format:
+    @code
+    MW_MAILME_MESSAGE
+    " Please send mail to: " MW_MAILME_ADDRESS
+    MW_MAILME_CUT_START
+    info
+    block
+    MW_MAILME_CUT_STOP
+    @endcode
+ */
+void mw_debug_mailme(struct mwOpaque *block, const char *info, ...);
+
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/mw_error.h	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,163 @@
+
+/*
+  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_ERROR_H
+#define _MW_ERROR_H
+
+#include <glib.h>
+
+
+/** @file error.h
+
+    Common error code constants used by Meanwhile.
+
+    Not all of these error codes (or even many, really) will ever
+    actually appear from Meanwhile. These are taken directly from the
+    houri draft, along with the minimal explanation for each.
+*/
+
+
+/** reference to a new string appropriate for the given error code.*/
+char* mwError(guint32 code);
+
+
+/* 8.3 Constants */
+/* 8.3.1 Error Codes */
+/* 8.3.1.1 General error/success codes */
+
+/** @enum ERR_GENERAL
+    general error codes */
+enum ERR_GENERAL {
+  ERR_SUCCESS                = 0x00000000,
+  ERR_FAILURE                = 0x80000000,
+  ERR_REQUEST_DELAY          = 0x00000001,
+  ERR_REQUEST_INVALID        = 0x80000001,
+  ERR_NOT_LOGGED_IN          = 0x80000002,
+  ERR_NOT_AUTHORIZED         = 0x80000003,
+  ERR_ABORT                  = 0x80000004,
+  ERR_NO_ELEMENT             = 0x80000005,
+  ERR_NO_USER                = 0x80000006,
+  ERR_BAD_DATA               = 0x80000007,
+  ERR_NOT_IMPLEMENTED        = 0x80000008,
+  ERR_UNKNOWN_ERROR          = 0x80000009, /* what is this? */
+  ERR_STARVING               = 0x8000000a,
+  ERR_CHANNEL_NO_SUPPORT     = 0x8000000b,
+  ERR_CHANNEL_EXISTS         = 0x8000000c,
+  ERR_SERVICE_NO_SUPPORT     = 0x8000000d,
+  ERR_PROTOCOL_NO_SUPPORT    = 0x8000000e,
+  ERR_PROTOCOL_NO_SUPPORT2   = 0x8000000f, /* duplicate? */
+  ERR_VERSION_NO_SUPPORT     = 0x80000010,
+  ERR_USER_SKETCHY           = 0x80000011,
+  ERR_ALREADY_INITIALIZED    = 0x80000013,
+  ERR_NOT_OWNER              = 0x80000014,
+  ERR_TOKEN_INVALID          = 0x80000015,
+  ERR_TOKEN_EXPIRED          = 0x80000016,
+  ERR_TOKEN_IP_MISMATCH      = 0x80000017,
+  ERR_PORT_IN_USE            = 0x80000018,
+  ERR_NETWORK_DEAD           = 0x80000019,
+  ERR_NO_MASTER_CHANNEL      = 0x8000001a,
+  ERR_ALREADY_SUBSCRIBED     = 0x8000001b,
+  ERR_NOT_SUBSCRIBED         = 0x8000001c,
+  ERR_ENCRYPT_NO_SUPPORT     = 0x8000001d,
+  ERR_ENCRYPT_UNINITIALIZED  = 0x8000001e,
+  ERR_ENCRYPT_UNACCEPTABLE   = 0x8000001f,
+  ERR_ENCRYPT_INVALID        = 0x80000020,
+  ERR_NO_COMMON_ENCRYPT      = 0x80000021,
+  ERR_CHANNEL_DESTROYED      = 0x80000022,
+  ERR_CHANNEL_REDIRECTED     = 0x80000023
+};
+
+
+/* 8.3.1.2 Connection/disconnection errors */
+
+#define VERSION_MISMATCH     0x80000200
+#define INSUF_BUFFER         0x80000201
+#define NOT_IN_USE           0x80000202
+#define INSUF_SOCKET         0x80000203
+#define HARDWARE_ERROR       0x80000204
+#define NETWORK_DOWN         0x80000205
+#define HOST_DOWN            0x80000206
+#define HOST_UNREACHABLE     0x80000207
+#define TCPIP_ERROR          0x80000208
+#define FAT_MESSAGE          0x80000209
+#define PROXY_ERROR          0x8000020A
+#define SERVER_FULL          0x8000020B
+#define SERVER_NORESPOND     0x8000020C
+#define CANT_CONNECT         0x8000020D
+#define USER_REMOVED         0x8000020E
+#define PROTOCOL_ERROR       0x8000020F
+#define USER_RESTRICTED      0x80000210
+#define INCORRECT_LOGIN      0x80000211
+#define ENCRYPT_MISMATCH     0x80000212
+#define USER_UNREGISTERED    0x80000213
+#define VERIFICATION_DOWN    0x80000214
+#define USER_TOO_IDLE        0x80000216
+#define GUEST_IN_USE         0x80000217
+#define USER_EXISTS          0x80000218
+#define USER_RE_LOGIN        0x80000219
+#define BAD_NAME             0x8000021A
+#define REG_MODE_NS          0x8000021B
+#define WRONG_USER_PRIV      0x8000021C
+#define NEED_EMAIL           0x8000021D
+#define DNS_ERROR            0x8000021E
+#define DNS_FATAL_ERROR      0x8000021F
+#define DNS_NOT_FOUND        0x80000220
+#define CONNECTION_BROKEN    0x80000221
+#define CONNECTION_ABORTED   0x80000222
+#define CONNECTION_REFUSED   0x80000223
+#define CONNECTION_RESET     0x80000224
+#define CONNECTION_TIMED     0x80000225
+#define CONNECTION_CLOSED    0x80000226
+#define MULTI_SERVER_LOGIN   0x80000227
+#define MULTI_SERVER_LOGIN2  0x80000228
+#define MULTI_LOGIN_COMP     0x80000229
+#define MUTLI_LOGIN_ALREADY  0x8000022A
+#define SERVER_BROKEN        0x8000022B
+#define SERVER_PATH_OLD      0x8000022C
+#define APPLET_LOGOUT        0x8000022D
+
+
+/* 8.3.1.3 Client error codes */
+
+/** @enum ERR_CLIENT
+    Client error codes */
+enum ERR_CLIENT {
+  ERR_CLIENT_USER_GONE       = 0x80002000, /* user isn't here */
+  ERR_CLIENT_USER_DND        = 0x80002001, /* user is DND */
+  ERR_CLIENT_USER_ELSEWHERE  = 0x80002002, /* already logged in elsewhere */
+};
+
+
+/* 8.3.1.4 IM error codes */
+
+/** @enum ERR_IM
+    IM error codes */
+enum ERR_IM {
+  ERR_IM_COULDNT_REGISTER    = 0x80002003,
+  ERR_IM_ALREADY_REGISTERED  = 0x80002004,
+
+  /** apparently, this is used to mean that the requested feature (per
+      the channel create addtl data) is not supported by the client on
+      the other end of the IM channel */
+  ERR_IM_NOT_REGISTERED      = 0x80002005,
+};
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/mw_message.h	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,271 @@
+
+/*
+  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_MESSAGE_H
+#define _MW_MESSAGE_H
+
+
+#include <glib/glist.h>
+#include "mw_common.h"
+
+
+/** Cast a pointer to a message subtype (eg, mwMsgHandshake,
+    mwMsgAdmin) into a pointer to a mwMessage */
+#define MW_MESSAGE(msg) (&msg->head)
+
+
+/** Indicates the type of a message. */
+enum mwMessageType {
+  mwMessage_HANDSHAKE         = 0x0000,  /**< mwMsgHandshake */
+  mwMessage_HANDSHAKE_ACK     = 0x8000,  /**< mwMsgHandshakeAck */
+  mwMessage_LOGIN             = 0x0001,  /**< mwMsgLogin */
+  mwMessage_LOGIN_ACK         = 0x8001,  /**< mwMsgLoginAck */
+  mwMessage_LOGIN_REDIRECT    = 0x0018,  /**< mwMsgLoginRedirect */
+  mwMessage_LOGIN_CONTINUE    = 0x0016,  /**< mwMsgLoginContinue */
+
+  mwMessage_CHANNEL_CREATE    = 0x0002,  /**< mwMsgChannelCreate */
+  mwMessage_CHANNEL_DESTROY   = 0x0003,  /**< mwMsgChannelDestroy */
+  mwMessage_CHANNEL_SEND      = 0x0004,  /**< mwMsgChannelSend */
+  mwMessage_CHANNEL_ACCEPT    = 0x0006,  /**< mwMsgChannelAccept */
+
+  mwMessage_SET_USER_STATUS   = 0x0009,  /**< mwMsgSetUserStatus */
+  mwMessage_SET_PRIVACY_LIST  = 0x000b,  /**< mwMsgSetPrivacyList */
+  mwMessage_SENSE_SERVICE     = 0x0011,  /**< mwMsgSenseService */
+  mwMessage_ADMIN             = 0x0019,  /**< mwMsgAdmin */
+};
+
+
+enum mwMessageOption {
+  mwMessageOption_ENCRYPT      = 0x4000,  /**< message data is encrypted */
+  mwMessageOption_HAS_ATTRIBS  = 0x8000,  /**< message has attributes */
+};
+
+
+/** @see mwMessageOption */
+#define MW_MESSAGE_HAS_OPTION(msg, opt) \
+  ((msg)->options & (opt))
+
+
+struct mwMessage {
+  guint16 type;     /**< @see mwMessageType */
+  guint16 options;  /**< @see mwMessageOption */
+  guint32 channel;  /**< ID of channel message is intended for */
+  struct mwOpaque attribs;  /**< optional message attributes */
+};
+
+
+
+/** Allocate and initialize a new message of the specified type */
+struct mwMessage *mwMessage_new(enum mwMessageType type);
+
+
+/** build a message from its representation */
+struct mwMessage *mwMessage_get(struct mwGetBuffer *b);
+
+
+void mwMessage_put(struct mwPutBuffer *b, struct mwMessage *msg);
+
+
+void mwMessage_free(struct mwMessage *msg);
+
+
+/* 8.4 Messages */
+/* 8.4.1 Basic Community Messages */
+/* 8.4.1.1 Handshake */
+
+struct mwMsgHandshake {
+  struct mwMessage head;
+  guint16 major;          /**< client's major version number */
+  guint16 minor;          /**< client's minor version number */
+  guint32 srvrcalc_addr;  /**<  */
+  guint16 login_type;     /**< @see mwLoginType */
+  guint32 loclcalc_addr;  /**<  */
+};
+
+
+/* 8.4.1.2 HandshakeAck */
+
+struct mwMsgHandshakeAck {
+  struct mwMessage head;
+  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 */
+};
+
+
+/* 8.3.7 Authentication Types */
+
+enum mwAuthType {
+  mwAuthType_PLAIN    = 0x0000,
+  mwAuthType_TOKEN    = 0x0001,
+  mwAuthType_ENCRYPT  = 0x0002,
+};
+
+
+/* 8.4.1.3 Login */
+
+struct mwMsgLogin {
+  struct mwMessage head;
+  guint16 login_type;         /**< @see mwLoginType */
+  char *name;                 /**< user identification */
+  guint16 auth_type;          /**< @see mwAuthType */
+  struct mwOpaque auth_data;  /**< authentication data */
+};
+
+
+/* 8.4.1.4 LoginAck */
+
+struct mwMsgLoginAck {
+  struct mwMessage head;
+  struct mwLoginInfo login;
+  struct mwPrivacyInfo privacy;
+  struct mwUserStatus status;
+};
+
+
+/* 8.4.1.5 LoginCont */
+
+struct mwMsgLoginContinue {
+  struct mwMessage head;
+};
+
+
+/* 8.4.1.6 AuthPassed */
+
+struct mwMsgLoginRedirect {
+  struct mwMessage head;
+  char *host;
+  char *server_id;
+};
+
+
+/* 8.4.1.7 CreateCnl */
+
+/** an offer of encryption items */
+struct mwEncryptOffer {
+  guint16 mode;   /**< encryption mode */
+  GList *items;   /**< list of mwEncryptItem offered */
+  guint16 extra;  /**< encryption mode again? */
+  gboolean flag;  /**< unknown flag */
+};
+
+
+struct mwMsgChannelCreate {
+  struct mwMessage head;
+  guint32 reserved;         /**< unknown reserved data */
+  guint32 channel;          /**< intended ID for new channel */
+  struct mwIdBlock target;  /**< User ID. for service use */
+  guint32 service;          /**< ID for the target service */
+  guint32 proto_type;       /**< protocol type for the service */
+  guint32 proto_ver;        /**< protocol version for the service */
+  guint32 options;          /**< options */
+  struct mwOpaque addtl;    /**< service-specific additional data */
+  gboolean creator_flag;    /**< indicate presence of creator information */
+  struct mwLoginInfo creator;
+  struct mwEncryptOffer encrypt;
+};
+
+
+/* 8.4.1.8 AcceptCnl */
+
+/** a selected encryption item from those offered */
+struct mwEncryptAccept {
+  guint16 mode;                /**< encryption mode */
+  struct mwEncryptItem *item;  /**< chosen mwEncryptItem (optional) */
+  guint16 extra;               /**< encryption mode again? */
+  gboolean flag;               /**< unknown flag */
+};
+
+
+struct mwMsgChannelAccept {
+  struct mwMessage head;
+  guint32 service;         /**< ID for the channel's service */
+  guint32 proto_type;      /**< protocol type for the service */
+  guint32 proto_ver;       /**< protocol version for the service */
+  struct mwOpaque addtl;   /**< service-specific additional data */
+  gboolean acceptor_flag;  /**< indicate presence of acceptor information */
+  struct mwLoginInfo acceptor;
+  struct mwEncryptAccept encrypt;
+};
+
+
+/* 8.4.1.9 SendOnCnl */
+
+struct mwMsgChannelSend {
+  struct mwMessage head;
+
+  /** message type. each service defines its own send types. Type IDs
+      are only necessarily unique within a given service. */
+  guint16 type;
+
+  /** protocol data to be interpreted by the handling service */
+  struct mwOpaque data;
+};
+
+
+/* 8.4.1.10 DestroyCnl */
+
+struct mwMsgChannelDestroy {
+  struct mwMessage head;
+  guint32 reason;        /**< reason for closing the channel. */
+  struct mwOpaque data;  /**< additional information */
+};
+
+
+/* 8.4.1.11 SetUserStatus */
+
+struct mwMsgSetUserStatus {
+  struct mwMessage head;
+  struct mwUserStatus status;
+};
+
+
+/* 8.4.1.12 SetPrivacyList */
+
+struct mwMsgSetPrivacyList {
+  struct mwMessage head;
+  struct mwPrivacyInfo privacy;
+};
+
+
+/* Sense Service */
+
+/** Sent to the server to request the presense of a service by its
+    ID. Sent to the client to indicate the presense of such a
+    service */
+struct mwMsgSenseService {
+  struct mwMessage head;
+  guint32 service;
+};
+
+
+/* Admin */
+
+/** An administrative broadcast message */
+struct mwMsgAdmin {
+  struct mwMessage head;
+  char *text;
+};
+
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/mw_service.h	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,366 @@
+
+/*
+  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_SERVICE_H
+#define _MW_SERVICE_H
+
+
+#include "mw_common.h"
+
+
+/* place-holders */
+struct mwChannel;
+struct mwService;
+struct mwSession;
+struct mwMsgChannelCreate;
+struct mwMsgChannelAccept;
+struct mwMsgChannelDestroy;
+
+
+/** State-tracking for a service */
+enum mwServiceState {
+  mwServiceState_STOPPED,   /**< the service is not active */
+  mwServiceState_STOPPING,  /**< the service is shutting down */
+  mwServiceState_STARTED,   /**< the service is active */
+  mwServiceState_STARTING,  /**< the service is starting up */
+  mwServiceState_ERROR,     /**< error in service, shutting down */
+  mwServiceState_UNKNOWN,   /**< error determining state */
+};
+
+
+/** Casts a concrete service (such as mwServiceAware) into a mwService */
+#define MW_SERVICE(srv) ((struct mwService *) srv)
+
+
+#define MW_SERVICE_IS_STATE(srvc, state) \
+  (mwService_getState(MW_SERVICE(srvc)) == (state))
+
+#define MW_SERVICE_IS_STOPPED(srvc)  \
+  MW_SERVICE_IS_STATE(srvc, mwServiceState_STOPPED)
+
+#define MW_SERVICE_IS_STOPPING(srvc) \
+  MW_SERVICE_IS_STATE(srvc, mwServiceState_STOPPING)
+
+#define MW_SERVICE_IS_STARTED(srvc)  \
+  MW_SERVICE_IS_STATE(srvc, mwServiceState_STARTED)
+
+#define MW_SERVICE_IS_STARTING(srvc) \
+  MW_SERVICE_IS_STATE(srvc, mwServiceState_STARTING)
+
+
+/** If a service is STARTING or STARTED, it's considered LIVE */
+#define MW_SERVICE_IS_LIVE(srvc) \
+  (MW_SERVICE_IS_STARTING(srvc) || MW_SERVICE_IS_STARTED(srvc))
+
+/** If a service is STOPPING or STOPPED, it's considered DEAD */
+#define MW_SERVICE_IS_DEAD(srvc) \
+  (MW_SERVICE_IS_STOPPING(srvc) || MW_SERVICE_IS_STOPPED(srvc))
+
+
+typedef void (*mwService_funcStart)(struct mwService *service);
+
+typedef void (*mwService_funcStop)(struct mwService *service);
+
+typedef void (*mwService_funcClear)(struct mwService *service);
+
+typedef const char *(*mwService_funcGetName)(struct mwService *service);
+
+typedef const char *(*mwService_funcGetDesc)(struct mwService *service);
+
+/** @todo remove msg and replace with appropriate additional parameters */
+typedef void (*mwService_funcRecvCreate)
+     (struct mwService *service,
+      struct mwChannel *channel,
+      struct mwMsgChannelCreate *msg);
+
+/** @todo remove msg and replace with appropriate additional parameters */
+typedef void (*mwService_funcRecvAccept)
+     (struct mwService *service,
+      struct mwChannel *channel,
+      struct mwMsgChannelAccept *msg);
+
+/** @todo remove msg and replace with appropriate additional parameters */
+typedef void (*mwService_funcRecvDestroy)
+     (struct mwService *service,
+      struct mwChannel *channel,
+      struct mwMsgChannelDestroy *msg);
+
+typedef void (*mwService_funcRecv)
+     (struct mwService *service,
+      struct mwChannel *channel,
+      guint16 msg_type,
+      struct mwOpaque *data);
+
+
+/** A service is the recipient of sendOnCnl messages sent over
+    channels marked with the corresponding service id. Services
+    provide functionality such as IM relaying, Awareness tracking and
+    notification, and Conference handling.  It is a service's
+    responsibility to accept or destroy channels, and to process data
+    sent over those channels */
+struct mwService {
+
+  /** the unique identifier by which this service is registered. The
+      type value also relates to those channels which will be directed
+      to this service */
+  guint32 type;
+
+  /** the state of this service. Determines whether or not the session
+      should call the start function upon receipt of a service
+      available message. Should not be set or checked by hand.
+
+      @relates mwService_getState */
+  enum mwServiceState state;
+
+  /** session this service is attached to.
+      @relates mwService_getSession */
+  struct mwSession *session;
+
+  /** @return string short name of the service
+      @relates mwService_getName */
+  mwService_funcGetName get_name;
+
+  /** @return string short description of the service
+      @relates mwService_getDesc */
+  mwService_funcGetDesc get_desc;
+
+  /** The service's channel create handler. Called when the session
+      receives a channel create message with a service matching this
+      service's type.
+
+      @relates mwService_recvCreate */
+  mwService_funcRecvCreate recv_create;
+
+  /** The service's channel accept handler. Called when the session
+      receives a channel accept message for a channel with a service
+      matching this service's type.
+
+      @relates mwService_recvAccept */
+  mwService_funcRecvAccept recv_accept;
+
+  /** The service's channel destroy handler. Called when the session
+      receives a channel destroy message for a channel with a service
+      matching this service's type.
+
+      @relates mwService_recvDestroy */
+  mwService_funcRecvDestroy recv_destroy;
+
+  /** The service's input handler. Called when the session receives
+      data on a channel belonging to this service
+
+      @relates mwService_recv */
+  mwService_funcRecv recv;
+
+  /** The service's start handler. Called upon the receipt of a
+      service available message.
+
+      @relates mwService_start */
+  mwService_funcStart start;
+
+  /** The service's stop handler. Called when the session is shutting
+      down, or when the service is free'd.
+
+      @relates mwService_stop */
+  mwService_funcStop stop;
+  
+  /** The service's cleanup handler. Service implementations should
+      presume that mwService::stop will be called first. The clear
+      handler is not for shutting down channels or generating
+      non-cleanup side-effects, it is only for handling tear-down of
+      the service, and will only be called once for any instance.
+
+      @relates mwService_free */
+  mwService_funcClear clear;
+
+  /** Optional client data, not for use by service implementations
+
+      @relates mwService_getClientData
+      @relates mwService_setClientData */
+  gpointer client_data;
+
+  /** Optional client data cleanup function. Called with client_data
+      from mwService_free
+
+      @relates mwService_getClientData
+      @relates mwService_setClientData */
+  GDestroyNotify client_cleanup;
+};
+
+
+/** @name Service Extension API
+
+    These functions are for use by service implementations */
+/*@{*/
+
+
+/** Prepares a newly allocated service for use.
+    
+    Intended for use by service implementations, rather than by code
+    utilizing a service.
+    
+    The service state will be initialized to STOPPED.
+    
+    @param service       the service to initialize
+    @param session       the service's owning session
+    @param service_type  the service ID number */
+void mwService_init(struct mwService *service,
+		    struct mwSession *session,
+		    guint32 service_type);
+
+
+/** Indicate that a service is started. To be used by service
+    implementations when the service is fully started. */
+void mwService_started(struct mwService *service);
+
+
+/** Indicate that a service is stopped. To be used by service
+    implementations when the service is fully stopped. */
+void mwService_stopped(struct mwService *service);
+
+
+/*@}*/
+
+
+/** @name General Services API
+
+    These functions provide unified access to the general functions of
+    a client service, with some simple sanity-checking. */
+/*@{*/
+
+
+/** Triggers the recv_create handler on the service.
+
+    @param service  the service to handle the message
+    @param channel  the channel being created
+    @param msg      the channel create message */
+void mwService_recvCreate(struct mwService *service,
+			  struct mwChannel *channel,
+			  struct mwMsgChannelCreate *msg);
+
+
+/** Triggers the recv_accept handler on the service.
+
+    @param service  the service to handle the message
+    @param channel  the channel being accepted
+    @param msg      the channel accept message */
+void mwService_recvAccept(struct mwService *service,
+			  struct mwChannel *channel,
+			  struct mwMsgChannelAccept *msg);
+
+
+/** Triggers the recv_destroy handler on the service.
+
+    @param service  the service to handle the message
+    @param channel  the channel being destroyed
+    @param msg      the channel destroy message */
+void mwService_recvDestroy(struct mwService *service,
+			   struct mwChannel *channel,
+			   struct mwMsgChannelDestroy *msg);
+
+
+/** Triggers the input handler on the service
+
+    @param service   the service to receive the input
+    @param channel   the channel the input was received from
+    @param msg_type  the service-dependant message type
+    @param data      the message data */
+void mwService_recv(struct mwService *service,
+		    struct mwChannel *channel,
+		    guint16 msg_type,
+		    struct mwOpaque *data);
+
+
+/** @return the appropriate type id for the service */
+guint32 mwService_getType(struct mwService *);
+
+
+/** @return string short name of the service */
+const char *mwService_getName(struct mwService *);
+
+
+/** @return string short description of the service */
+const char *mwService_getDesc(struct mwService *);
+
+
+/** @return the service's session */
+struct mwSession *mwService_getSession(struct mwService *service);
+
+
+/** @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);
+
+
+/** Triggers the start handler for the service. Normally called from
+    the session upon receipt of a service available message. Service
+    implementations should use this handler to open any necessary
+    channels, etc. Checks that the service is STOPPED, or returns.
+    
+    @param service The service to start
+*/
+void mwService_start(struct mwService *service);
+
+
+/** Triggers the stop handler for the service. Normally called from
+    the session before closing down the connection. Checks that the
+    service is STARTED or STARTING, or returns
+
+    @param service The service to stop
+*/
+void mwService_stop(struct mwService *service);
+
+
+/** Frees memory used by a service. Will trigger the stop handler if
+    the service is STARTED or STARTING. Triggers clear handler to allow
+    cleanup.
+
+    @param service The service to clear and free
+*/
+void mwService_free(struct mwService *service);
+
+
+/** Associates client data with service. If there is existing data, it
+    will not have its cleanup function called. Client data is not for
+    use by service implementations. Rather, it is for use by client
+    code which may later make use of those service implementations. */
+void mwService_setClientData(struct mwService *service,
+			     gpointer data, GDestroyNotify cleanup);
+
+
+/** Reference associated client data */
+gpointer mwService_getClientData(struct mwService *service);
+
+
+/** Removes client data from service. If there is a cleanup function,
+    it will be called. */
+void mwService_removeClientData(struct mwService *service);
+
+
+/*@}*/
+
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/mw_session.h	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,357 @@
+
+/*
+  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_SESSION_H
+#define _MW_SESSION_H
+
+
+#include "mw_common.h"
+
+
+/** @file session.h
+
+    A client session with a Sametime server is encapsulated in the
+    mwSession structure. The session controls channels, provides
+    encryption ciphers, and manages services using messages over the
+    Master channel.
+
+    A session does not directly communicate with a socket or stream,
+    instead the session is initialized from client code with an
+    instance of a mwSessionHandler structure. This session handler
+    provides functions as call-backs for common session events, and
+    provides functions for writing-to and closing the connection to
+    the server.
+
+    A session does not perform reads on a socket directly. Instead, it
+    must be fed from an outside source via the mwSession_recv
+    function. The session will buffer and merge data passed to this
+    function to build complete protocol messages, and will act upon
+    each complete message accordingly.
+*/
+
+
+struct mwCipher;
+struct mwMessage;
+
+
+/** default protocol major version */
+#define MW_PROTOCOL_VERSION_MAJOR  0x001e
+
+
+/** default protocol minor version */
+#define MW_PROTOCOL_VERSION_MINOR  0x001d
+
+
+/** @section Session Properties
+    ...
+*/
+/*@{*/
+
+/** char *, session user ID */
+#define mwSession_AUTH_USER_ID      "session.auth.user"
+
+/** char *, plaintext password */
+#define mwSession_AUTH_PASSWORD     "session.auth.password"
+
+/** struct mwOpaque *, authentication token */
+#define mwSession_AUTH_TOKEN        "session.auth.token"
+
+/** guint16, major version of client protocol */
+#define mwSession_CLIENT_VER_MAJOR  "client.version.major"
+
+/** guint16, minor version of client protocol */
+#define mwSession_CLIENT_VER_MINOR  "client.version.minor"
+
+/** guint16, client type identifier */
+#define mwSession_CLIENT_TYPE_ID    "client.id"
+
+/** guint16, major version of server protocol */
+#define mwSession_SERVER_VER_MAJOR  "server.version.major"
+
+/** guint16, minor version of server protocol */
+#define mwSession_SERVER_VER_MINOR  "server.version.minor"
+
+/*@}*/
+
+
+enum mwSessionState {
+  mwSession_STARTING,      /**< session is starting */
+  mwSession_HANDSHAKE,     /**< session has sent handshake */
+  mwSession_HANDSHAKE_ACK, /**< session has received handshake ack */
+  mwSession_LOGIN,         /**< session has sent login */
+  mwSession_LOGIN_REDIR,   /**< session has been redirected */
+  mwSession_LOGIN_ACK,     /**< session has received login ack */
+  mwSession_STARTED,       /**< session is active */
+  mwSession_STOPPING,      /**< session is shutting down */
+  mwSession_STOPPED,       /**< session is stopped */
+  mwSession_UNKNOWN,       /**< indicates an error determining state */
+  mwSession_LOGIN_CONT,    /**< session has sent a login continue */
+};
+
+
+#define mwSession_isState(session, state) \
+  (mwSession_getState((session)) == (state))
+
+#define mwSession_isStarting(s) \
+  (mwSession_isState((s), mwSession_STARTING)  || \
+   mwSession_isState((s), mwSession_HANDSHAKE) || \
+   mwSession_isState((s), mwSession_HANDSHAKE_ACK) || \
+   mwSession_isState((s), mwSession_LOGIN) || \
+   mwSession_isState((s), mwSession_LOGIN_ACK) || \
+   mwSession_isState((s), mwSession_LOGIN_REDIR) || \
+   mwSession_isState((s), mwSession_LOGIN_CONT))
+
+#define mwSession_isStarted(s) \
+  (mwSession_isState((s), mwSession_STARTED))
+
+#define mwSession_isStopping(s) \
+  (mwSession_isState((s), mwSession_STOPPING))
+
+#define mwSession_isStopped(s) \
+  (mwSession_isState((s), mwSession_STOPPED))
+
+
+/** @struct mwSession
+
+    Represents a Sametime client session */
+struct mwSession;
+
+
+/** @struct mwSessionHandler
+
+    session handler. Structure which interfaces a session with client
+    code to provide I/O and event handling */
+struct mwSessionHandler {
+  
+  /** write data to the server connection. Required. Should return
+      zero for success, non-zero for error */
+  int (*io_write)(struct mwSession *, const char *buf, gsize len);
+  
+  /** close the server connection. Required */
+  void (*io_close)(struct mwSession *);
+
+  /** triggered by mwSession_free. Optional. Put cleanup code here */
+  void (*clear)(struct mwSession *);
+
+  /** 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
+
+      @param s      the session
+      @param state  the session's state
+      @param info   additional state info. */
+  void (*on_stateChange)(struct mwSession *s,
+			 enum mwSessionState state, guint32 info);
+
+  /** called when privacy information has been sent or received
+
+      @see mwSession_getPrivacyInfo
+  */
+  void (*on_setPrivacyInfo)(struct mwSession *);
+
+  /** called when user status has changed
+
+      @see mwSession_getUserStatus */
+  void (*on_setUserStatus)(struct mwSession *);
+
+  /** called when an admin messages has been received */
+  void (*on_admin)(struct mwSession *, const char *text);
+
+  /** called when a login redirect message is received
+
+      @todo remove in favour of on_stateChange, passing host as a
+      gpointer in info */
+  void (*on_loginRedirect)(struct mwSession *, const char *host);
+};
+
+
+/** allocate a new session */
+struct mwSession *mwSession_new(struct mwSessionHandler *);
+
+
+/** stop, clear, free a session. Does not free contained ciphers or
+    services, these must be taken care of explicitly. */
+void mwSession_free(struct mwSession *);
+
+
+/** obtain a reference to the session's handler */
+struct mwSessionHandler *mwSession_getHandler(struct mwSession *);
+
+
+/** instruct the session to begin. This will result in the initial
+    handshake message being sent. */
+void mwSession_start(struct mwSession *);
+
+
+/** instruct the session to shut down with the following reason
+    code. */
+void mwSession_stop(struct mwSession *, guint32 reason);
+
+
+/** Data is buffered, unpacked, and parsed into a message, then
+    processed accordingly. */
+void mwSession_recv(struct mwSession *, const char *, gsize);
+
+
+/** primarily used by services to have messages serialized and sent
+    @param s    session to send message over
+    @param msg  message to serialize and send
+    @returns    0 for success */
+int mwSession_send(struct mwSession *s, struct mwMessage *msg);
+
+
+/** sends the keepalive byte */
+int mwSession_sendKeepalive(struct mwSession *s);
+
+
+/** respond to a login redirect message by forcing the login sequence
+    to continue through the immediate server. */
+int mwSession_forceLogin(struct mwSession *s);
+
+
+/** set the internal privacy information, and inform the server as
+    necessary. Triggers the on_setPrivacyInfo call-back. */
+int mwSession_setPrivacyInfo(struct mwSession *, struct mwPrivacyInfo *);
+
+
+struct mwPrivacyInfo *mwSession_getPrivacyInfo(struct mwSession *);
+
+
+/** reference the login information for the session */
+struct mwLoginInfo *mwSession_getLoginInfo(struct mwSession *);
+
+
+/** set the internal user status state, and inform the server as
+    necessary. Triggers the on_setUserStatus call-back */
+int mwSession_setUserStatus(struct mwSession *, struct mwUserStatus *);
+
+
+struct mwUserStatus *mwSession_getUserStatus(struct mwSession *);
+
+
+/** current status of the session */
+enum mwSessionState mwSession_getState(struct mwSession *);
+
+
+/** additional status-specific information */
+guint32 mwSession_getStateInfo(struct mwSession *);
+
+
+struct mwChannelSet *mwSession_getChannels(struct mwSession *);
+
+
+/** adds a service to the session. If the session is started (or when
+    the session is successfully started) and the service has a start
+    function, the session will request service availability from the
+    server. On receipt of the service availability notification, the
+    session will call the service's start function.
+
+    @return TRUE if the session was added correctly */
+gboolean mwSession_addService(struct mwSession *, struct mwService *);
+
+
+/** find a service by its type identifier */
+struct mwService *mwSession_getService(struct mwSession *, guint32 type);
+
+
+/** removes a service from the session. If the session is started and
+    the service has a stop function, it will be called. Returns the
+    removed service */
+struct mwService *mwSession_removeService(struct mwSession *, guint32 type);
+
+
+/** a GList of services in this session. The GList needs to be freed
+    after use */
+GList *mwSession_getServices(struct mwSession *);
+
+
+/** instruct a STARTED session to check the server for the presense of
+    a given service. The service will be automatically started upon
+    receipt of an affirmative reply from the server. This function is
+    automatically called upon all services in a session when the
+    session is fully STARTED.
+
+    Services which terminate due to an error may call this on
+    themselves to re-initialize when their server-side counterpart is
+    made available again.
+
+    @param s     owning session
+    @param type  service type ID */
+void mwSession_senseService(struct mwSession *s, guint32 type);
+
+
+/** adds a cipher to the session. */
+gboolean mwSession_addCipher(struct mwSession *, struct mwCipher *);
+
+
+/** find a cipher by its type identifier */
+struct mwCipher *mwSession_getCipher(struct mwSession *, guint16 type);
+
+
+/** remove a cipher from the session */
+struct mwCipher *mwSession_removeCipher(struct mwSession *, guint16 type);
+
+
+/** a GList of ciphers in this session. The GList needs to be freed
+    after use */
+GList *mwSession_getCiphers(struct mwSession *);
+
+
+/** associate a key:value pair with the session. If an existing value is
+    associated with the same key, it will have its clear function called
+    and will be replaced with the new value */
+void mwSession_setProperty(struct mwSession *, const char *key,
+			   gpointer val, GDestroyNotify clear);
+
+
+/** obtain the value of a previously set property, or NULL */
+gpointer mwSession_getProperty(struct mwSession *, const char *key);
+
+
+/** remove a property, calling the optional GDestroyNotify function
+    indicated in mwSession_setProperty if applicable */
+void mwSession_removeProperty(struct mwSession *, const char *key);
+
+
+/** associate arbitrary data with the session for use by the client
+    code. Only client applications should use this, never services.
+
+    @param session  the session to associate the data with
+    @param data     arbitrary client data
+    @param clear    optional cleanup function called on data from
+                    mwSession_removeClientData and mwSession_free
+*/
+void mwSession_setClientData(struct mwSession *session,
+			     gpointer data, GDestroyNotify clear);
+
+
+gpointer mwSession_getClientData(struct mwSession *session);
+
+
+/** remove client data, calling the optional GDestroyNotify function
+    indicated in mwSession_setClientData if applicable */
+void mwSession_removeClientData(struct mwSession *session);
+
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/mw_srvc_aware.h	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,261 @@
+
+/*
+  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_AWARE_H
+#define _MW_SRVC_AWARE_H
+
+
+#include "mw_common.h"
+
+
+/** @file mw_srvc_aware.h
+
+    The aware service...
+*/
+
+
+/** Type identifier for the aware service */
+#define SERVICE_AWARE  0x00000011
+
+
+/** @struct mwServiceAware
+
+    Instance of an Aware Service. The members of this structure are
+    not made available. Accessing the parts of an aware service should
+    be performed through the appropriate functions. Note that
+    instances of this structure can be safely cast to a mwService.
+*/
+struct mwServiceAware;
+
+
+/** @struct mwAwareList
+
+    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.
+*/
+struct mwAwareList;
+
+
+/** @struct mwAwareAttribute
+
+    Key/Opaque pair indicating an identity's attribute.
+ */
+struct mwAwareAttribute;
+
+
+/** Predefined keys appropriate for a mwAwareAttribute
+ */
+enum mwAwareAttributeKeys {
+  mwAttribute_AV_PREFS_SET   = 0x01, /**< A/V prefs specified, gboolean */
+  mwAttribute_MICROPHONE     = 0x02, /**< has a microphone, gboolean */
+  mwAttribute_SPEAKERS       = 0x03, /**< has speakers, gboolean */
+  mwAttribute_VIDEO_CAMERA   = 0x04, /**< has a video camera, gboolean */
+  mwAttribute_FILE_TRANSFER  = 0x06, /**< supports file transfers, gboolean */
+};
+
+
+typedef void (*mwAwareAttributeHandler)
+     (struct mwServiceAware *srvc,
+      struct mwAwareAttribute *attrib);
+
+
+struct mwAwareHandler {
+  mwAwareAttributeHandler on_attrib;
+  void (*clear)(struct mwServiceAware *srvc);
+};
+
+
+/** Appropriate function type for the on-aware signal
+
+    @param list  mwAwareList emiting the signal
+    @param id    awareness status information
+    @param data  user-specified data
+*/
+typedef void (*mwAwareSnapshotHandler)
+     (struct mwAwareList *list,
+      struct mwAwareSnapshot *id);
+
+
+/** Appropriate function type for the on-option signal. The option's
+    value may need to be explicitly loaded in some instances,
+    resulting in this handler being triggered again.
+
+    @param list    mwAwareList emiting the signal
+    @param id      awareness the attribute belongs to
+    @param attrib  attribute
+*/
+typedef void (*mwAwareIdAttributeHandler)
+     (struct mwAwareList *list,
+      struct mwAwareIdBlock *id,
+      struct mwAwareAttribute *attrib);
+
+
+struct mwAwareListHandler {
+  /** handle aware updates */
+  mwAwareSnapshotHandler on_aware;
+
+  /** handle attribute updates */
+  mwAwareIdAttributeHandler on_attrib;
+
+  /** optional. Called from mwAwareList_free */
+  void (*clear)(struct mwAwareList *list);
+};
+
+
+struct mwServiceAware *
+mwServiceAware_new(struct mwSession *session,
+		   struct mwAwareHandler *handler);
+
+
+/** Set an attribute value for this session */
+int mwServiceAware_setAttribute(struct mwServiceAware *srvc,
+				guint32 key, struct mwOpaque *opaque);
+
+
+int mwServiceAware_setAttributeBoolean(struct mwServiceAware *srvc,
+				       guint32 key, gboolean val);
+
+
+int mwServiceAware_setAttributeInteger(struct mwServiceAware *srvc,
+				       guint32 key, guint32 val);
+
+
+int mwServiceAware_setAttributeString(struct mwServiceAware *srvc,
+				      guint32 key, const char *str);
+
+
+/** Unset an attribute for this session */
+int mwServiceAware_unsetAttribute(struct mwServiceAware *srvc,
+				  guint32 key);
+
+
+guint32 mwAwareAttribute_getKey(const struct mwAwareAttribute *attrib);
+
+
+gboolean mwAwareAttribute_asBoolean(const struct mwAwareAttribute *attrib);
+
+
+guint32 mwAwareAttribute_asInteger(const struct mwAwareAttribute *attrib);
+
+
+/** Copy of attribute string, must be g_free'd. If the attribute's
+    content cannot be loaded as a string, returns NULL */
+char *mwAwareAttribute_asString(const struct mwAwareAttribute *attrib);
+
+
+/** Direct access to an attribute's underlying opaque */
+const struct mwOpaque *
+mwAwareAttribute_asOpaque(const struct mwAwareAttribute *attrib);
+
+
+/** Allocate and initialize an aware list */
+struct mwAwareList *
+mwAwareList_new(struct mwServiceAware *srvc,
+		struct mwAwareListHandler *handler);
+
+
+/** Clean and free an aware list */
+void mwAwareList_free(struct mwAwareList *list);
+
+
+struct mwAwareListHandler *mwAwareList_getHandler(struct mwAwareList *list);
+
+
+/** Add a collection of user IDs to an aware list.
+    @param list     mwAwareList to add user ID to
+    @param id_list  mwAwareIdBlock list of user IDs to add
+    @return         0 for success, non-zero to indicate an error.
+*/
+int mwAwareList_addAware(struct mwAwareList *list, GList *id_list);
+
+
+/** Remove a collection of user IDs from an aware list.
+    @param list     mwAwareList to remove user ID from
+    @param id_list  mwAwareIdBlock list of user IDs to remove
+    @return  0      for success, non-zero to indicate an error.
+*/
+int mwAwareList_removeAware(struct mwAwareList *list, GList *id_list);
+
+
+int mwAwareList_removeAllAware(struct mwAwareList *list);
+
+
+/** watch an NULL terminated array of keys */
+int mwAwareList_watchAttributeArray(struct mwAwareList *list,
+				    guint32 *keys);
+
+
+/** watch a NULL terminated list of keys */
+int mwAwareList_watchAttributes(struct mwAwareList *list,
+				guint32 key, ...);
+
+
+/** stop watching a NULL terminated array of keys */
+int mwAwareList_unwatchAttributeArray(struct mwAwareList *list,
+				      guint32 *keys);
+
+
+/** stop watching a NULL terminated list of keys */
+int mwAwareList_unwatchAttributes(struct mwAwareList *list,
+				  guint32 key, ...);
+
+
+/** remove all watched attributes */
+int mwAwareList_unwatchAllAttributes(struct mwAwareList *list);
+
+
+guint32 *mwAwareList_getWatchedAttributes(struct mwAwareList *list);
+
+
+void mwAwareList_setClientData(struct mwAwareList *list,
+			       gpointer data, GDestroyNotify cleanup);
+
+
+void mwAwareList_removeClientData(struct mwAwareList *list);
+
+
+gpointer mwAwareList_getClientData(struct mwAwareList *list);
+
+
+/** trigger a got_aware event constructed from the passed user and
+    status information. Useful for adding false users and having the
+    getText function work for them */
+void mwServiceAware_setStatus(struct mwServiceAware *srvc,
+			      struct mwAwareIdBlock *user,
+			      struct mwUserStatus *stat);
+
+
+/** look up the status description for a user */
+const char *mwServiceAware_getText(struct mwServiceAware *srvc,
+				   struct mwAwareIdBlock *user);
+
+
+/** look up the last known copy of an attribute for a user by the
+    attribute's key */
+const struct mwAwareAttribute *
+mwServiceAware_getAttribute(struct mwServiceAware *srvc,
+			    struct mwAwareIdBlock *user,
+			    guint32 key);
+
+
+#endif
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/mw_srvc_conf.h	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,198 @@
+
+/*
+  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_CONF_H
+#define _MW_SRVC_CONF_H
+
+
+#include <glib/glist.h>
+#include "mw_common.h"
+
+
+/** Type identifier for the conference service */
+#define SERVICE_CONFERENCE  0x80000010
+
+
+enum mwConferenceState {
+  mwConference_NEW,      /**< new outgoing conference */
+  mwConference_PENDING,  /**< outgoing conference pending creation */
+  mwConference_INVITED,  /**< invited to incoming conference */
+  mwConference_OPEN,     /**< conference open and active */
+  mwConference_CLOSING,  /**< conference is closing */
+  mwConference_ERROR,    /**< conference is closing due to error */
+  mwConference_UNKNOWN,  /**< unable to determine conference state */
+};
+
+
+/** @struct mwServiceConference
+    Instance of the multi-user conference service */
+struct mwServiceConference;
+
+
+/** @struct mwConference
+    A multi-user chat */
+struct mwConference;
+
+
+/** Handler structure used to provide callbacks for an instance of the
+    conferencing service */
+struct mwConferenceHandler {
+
+  /** triggered when we receive a conference invitation. Call
+      mwConference_accept to accept the invitation and join the
+      conference, or mwConference_close to reject the invitation.
+
+      @param conf     the newly created conference
+      @param inviter  the indentity of the user who sent the invitation
+      @param invite   the invitation text
+   */
+  void (*on_invited)(struct mwConference *conf,
+		     struct mwLoginInfo *inviter, const char *invite);
+
+  /** triggered when we enter the conference. Provides the initial
+      conference membership list as a GList of mwLoginInfo structures
+
+      @param conf     the conference just joined
+      @param members  mwLoginInfo list of existing conference members
+  */
+  void (*conf_opened)(struct mwConference *conf, GList *members);
+
+  /** triggered when a conference is closed. This is typically when
+      we've left it */
+  void (*conf_closed)(struct mwConference *, guint32 reason);
+
+  /** triggered when someone joins the conference */
+  void (*on_peer_joined)(struct mwConference *, struct mwLoginInfo *);
+
+  /** triggered when someone leaves the conference */
+  void (*on_peer_parted)(struct mwConference *, struct mwLoginInfo *);
+
+  /** triggered when someone says something */
+  void (*on_text)(struct mwConference *conf,
+		  struct mwLoginInfo *who, const char *what);
+
+  /** typing notification */
+  void (*on_typing)(struct mwConference *conf,
+		    struct mwLoginInfo *who, gboolean typing);
+
+  /** optional. called from mwService_free */
+  void (*clear)(struct mwServiceConference *srvc);
+};
+
+
+/** Allocate a new conferencing service, attaching the given handler
+    @param sess     owning session
+    @param handler  handler providing call-back functions for the service
+ */
+struct mwServiceConference *
+mwServiceConference_new(struct mwSession *sess,
+			struct mwConferenceHandler *handler);
+
+
+/** @returns the conference handler for the service */
+struct mwConferenceHandler *
+mwServiceConference_getHandler(struct mwServiceConference *srvc);
+
+
+/** a mwConference list of the conferences in this service. The GList
+    will need to be destroyed with g_list_free after use */
+GList *mwServiceConference_getConferences(struct mwServiceConference *srvc);
+
+
+/** Allocate a new conference, in state NEW with the given title.
+    @see mwConference_create */
+struct mwConference *mwConference_new(struct mwServiceConference *srvc,
+				      const char *title);
+
+
+/** @returns the owning service of a conference */
+struct mwServiceConference *mwConference_getService(struct mwConference *conf);
+
+
+/** @returns unique conference name */
+const char *mwConference_getName(struct mwConference *conf);
+
+
+/** @returns conference title */
+const char *mwConference_getTitle(struct mwConference *conf);
+
+
+/** a mwIdBlock list of the members of the conference. The GList will
+    need to be free'd after use */
+GList *mwConference_getMembers(struct mwConference *conf);
+
+
+/** Initiate a conference. Conference must be in state NEW. If no name
+    or title for the conference has been set, they will be
+    generated. Conference will be placed into state PENDING. */
+int mwConference_open(struct mwConference *conf);
+
+
+/** Leave and close an existing conference, or reject an invitation.
+    Triggers mwServiceConfHandler::conf_closed and free's the
+    conference.
+ */
+int mwConference_destroy(struct mwConference *conf,
+			 guint32 reason, const char *text);
+
+
+#define mwConference_reject(c,r,t) \
+  mwConference_destroy((c),(r),(t))
+
+
+/** accept a conference invitation. Conference must be in the state
+    INVITED. */
+int mwConference_accept(struct mwConference *conf);
+
+
+/** invite another user to an ACTIVE conference
+    @param who   user to invite
+    @param text  invitation message
+ */
+int mwConference_invite(struct mwConference *conf,
+			struct mwIdBlock *who, const char *text);
+
+
+/** send a text message over an open conference */
+int mwConference_sendText(struct mwConference *conf, const char *text);
+
+
+/** send typing notification over an open conference */
+int mwConference_sendTyping(struct mwConference *conf, gboolean typing);
+
+
+/** associate arbitrary client data and an optional cleanup function
+    with a conference. If there is already client data with a clear
+    function, it will not be called. */
+void mwConference_setClientData(struct mwConference *conf,
+				gpointer data, GDestroyNotify clear);
+
+
+/** reference associated client data */
+gpointer mwConference_getClientData(struct mwConference *conf);
+
+
+/** remove associated client data if any, and call the cleanup
+    function on the data as necessary */
+void mwConference_removeClientData(struct mwConference *conf);
+				    
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/mw_srvc_dir.h	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,202 @@
+/*
+  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_DIR_H
+#define _MW_SERV_DIR_H
+
+
+#include <glib.h>
+#include <glib/glist.h>
+
+
+struct mwSession;
+
+
+#define SERVICE_DIRECTORY  0x0000001a
+
+
+/** @struct mwServiceDirectory
+
+    the directory service. */
+struct mwServiceDirectory;
+
+
+/** @struct mwAddressBook
+
+    server-side collection of users and groups. Open a directory
+    based on an address book to search or list its contents */
+struct mwAddressBook;
+
+
+/** @struct mwDirectory
+
+    searchable directory, based off of an address book */
+struct mwDirectory;
+
+
+enum mwDirectoryState {
+  mwDirectory_NEW,      /**< directory is created, but not open */
+  mwDirectory_PENDING,  /**< directory has in the process of opening */
+  mwDirectory_OPEN,     /**< directory is open */
+  mwDirectory_ERROR,    /**< error opening or using directory */
+  mwDirectory_UNKNOWN,  /**< error determining directory state */
+};
+
+
+/** return value of directory searches that fail */
+#define DIR_SEARCH_ERROR  0x00000000
+
+
+#define MW_DIRECTORY_IS_STATE(dir, state) \
+  (mwDirectory_getState(dir) == (state))
+
+#define MW_DIRECTORY_IS_NEW(dir) \
+  MW_DIRECTORY_IS_STATE((dir), mwDirectory_NEW)
+
+#define MW_DIRECTORY_IS_PENDING(dir) \
+  MW_DIRECTORY_IS_STATE((dir), mwDirectory_PENDING)
+
+#define MW_DIRECTORY_IS_OPEN(dir) \
+  MW_DIRECTORY_IS_STATE((dir), mwDirectory_OPEN)
+
+
+enum mwDirectoryMemberType {
+  mwDirectoryMember_USER   = 0x0000,
+  mwDirectoryMember_GROUP  = 0x0001,
+};
+
+
+struct mwDirectoryMember {
+  guint16 type;      /**< @see mwDirectoryMemberType */
+  char *id;          /**< proper ID for member */
+  char *long_name;   /**< full name of member (USER type only) */
+  char *short_name;  /**< short name of member */
+  guint16 foo;       /**< unknown */
+};
+
+
+/** Appropriate function signature for handling directory search results */
+typedef void (*mwSearchHandler)
+     (struct mwDirectory *dir,
+      guint32 code, guint32 offset, GList *members);
+
+
+/** handles asynchronous events for a directory service instance */
+struct mwDirectoryHandler {
+
+  /** handle receipt of the address book list from the service.
+      Initially triggered by mwServiceDirectory_refreshAddressBooks
+      and at service startup */
+  void (*on_book_list)(struct mwServiceDirectory *srvc, GList *books);
+
+  /** triggered when a directory has been successfully opened */
+  void (*dir_opened)(struct mwDirectory *dir);
+
+  /** triggered when a directory has been closed */
+  void (*dir_closed)(struct mwDirectory *dir, guint32 reason);
+
+  /** optional. called from mwService_free */
+  void (*clear)(struct mwServiceDirectory *srvc);
+};
+
+
+/** Allocate a new directory service instance for use with session */
+struct mwServiceDirectory *
+mwServiceDirectory_new(struct mwSession *session,
+		       struct mwDirectoryHandler *handler);
+
+
+/** the handler associated with the service at its creation */
+struct mwDirectoryHandler *
+mwServiceDirectory_getHandler(struct mwServiceDirectory *srvc);
+
+
+/** most recent list of address books available in service */
+GList *mwServiceDirectory_getAddressBooks(struct mwServiceDirectory *srvc);
+
+
+/** submit a request to obtain an updated list of address books from
+    service */
+int mwServiceDirectory_refreshAddressBooks(struct mwServiceDirectory *srvc);
+
+
+/** list of directories in the service */
+GList *mwServiceDirectory_getDirectories(struct mwServiceDirectory *srvc);
+
+
+/** list of directories associated with address book. Note that the
+    returned GList will need to be free'd after use */
+GList *mwAddressBook_getDirectories(struct mwAddressBook *book);
+
+
+/** the name of the address book */
+const char *mwAddressBook_getName(struct mwAddressBook *book);
+
+
+/** allocate a new directory based off the given address book */
+struct mwDirectory *mwDirectory_new(struct mwAddressBook *book);
+
+
+enum mwDirectoryState mwDirectory_getState(struct mwDirectory *dir);
+
+
+/** set client data. If there is an existing clear function, it will
+    not be called */
+void mwDirectory_setClientData(struct mwDirectory *dir,
+			       gpointer data, GDestroyNotify clear);
+
+
+/** reference associated client data */
+gpointer mwDirectory_getClientData(struct mwDirectory *dir);
+
+
+/** remove and cleanup user data */
+void mwDirectory_removeClientData(struct mwDirectory *dir);
+
+
+/** reference owning service */
+struct mwServiceDirectory *mwDirectory_getService(struct mwDirectory *dir);
+
+
+/** reference owning address book */
+struct mwAddressBook *mwDirectory_getAddressBook(struct mwDirectory *dir);
+
+
+/** initialize a directory. */
+int mwDirectory_open(struct mwDirectory *dir, mwSearchHandler cb);
+
+
+/** continue a search into its next results */
+int mwDirectory_next(struct mwDirectory *dir);
+
+
+/** continue a search into its previous results */
+int mwDirectory_previous(struct mwDirectory *dir);
+
+
+/** initiate a search on an open directory */
+int mwDirectory_search(struct mwDirectory *dir, const char *query);
+
+
+/** close and free the directory, and unassociate it with its owning
+    address book and service */
+int mwDirectory_destroy(struct mwDirectory *dir);
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/mw_srvc_ft.h	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,235 @@
+
+/*
+  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_FT_H
+#define _MW_SRVC_FT_H
+
+
+#include "mw_common.h"
+
+
+/** @file mw_srvc_ft.h
+
+    A file transfer is a simple way to get large chunks of binary data
+    from one client to another.
+*/
+
+
+/** @struct mwServiceFileTransfer
+    File transfer service
+*/
+struct mwServiceFileTransfer;
+
+
+/** @struct mwFileTransfer
+    A single file trasfer session
+ */
+struct mwFileTransfer;
+
+
+#define mwService_FILE_TRANSFER  0x00000038
+
+
+enum mwFileTransferState {
+  mwFileTransfer_NEW,   /**< file transfer is not open */
+  mwFileTransfer_PENDING,  /**< file transfer is opening */
+  mwFileTransfer_OPEN,     /**< file transfer is open */
+  mwFileTransfer_CANCEL_LOCAL,
+  mwFileTransfer_CANCEL_REMOTE,
+  mwFileTransfer_DONE,
+  mwFileTransfer_ERROR,    /**< error in file transfer */
+  mwFileTransfer_UNKNOWN,  /**< unknown state */
+};
+
+
+#define mwFileTransfer_isState(ft, state) \
+  (mwFileTransfer_getState(ft) == (state))
+
+#define mwFileTransfer_isNew(ft) \
+  mwFileTransfer_isState((ft), mwFileTransfer_NEW)
+
+#define mwFileTransfer_isPending(ft) \
+  mwFileTransfer_isState((ft), mwFileTransfer_PENDING)
+
+#define mwFileTransfer_isOpen(ft) \
+  mwFileTransfer_isState((ft), mwFileTransfer_OPEN)
+
+#define mwFileTransfer_isDone(ft) \
+  mwFileTransfer_isState((ft), mwFileTransfer_DONE)
+
+#define mwFileTransfer_isCancelLocal(ft) \
+  mwFileTransfer_isState((ft), mwFileTransfer_CANCEL_LOCAL)
+
+#define mwFileTransfer_isCancelRemote(ft) \
+  mwFileTransfer_isState((ft), mwFileTransfer_CANCEL_REMOTE)
+
+
+enum mwFileTranferCode {
+  mwFileTransfer_SUCCESS   = 0x00000000,
+  mwFileTransfer_REJECTED  = 0x08000606,
+};
+
+
+struct mwFileTransferHandler {
+
+  /** an incoming file transfer has been offered */
+  void (*ft_offered)(struct mwFileTransfer *ft);
+
+  /** a file transfer has been fully initiated */
+  void (*ft_opened)(struct mwFileTransfer *ft);
+
+  /** a file transfer has been closed. Check the status of the file
+      transfer to determine if the transfer was complete or if it had
+      been interrupted */
+  void (*ft_closed)(struct mwFileTransfer *ft, guint32 code);
+
+  /** receive a chunk of a file from an inbound file transfer. */
+  void (*ft_recv)(struct mwFileTransfer *ft, struct mwOpaque *data);
+
+  /** received an ack for a sent chunk on an outbound file transfer.
+      this indicates that a previous call to mwFileTransfer_send has
+      reached the target and that the target has responded. */
+  void (*ft_ack)(struct mwFileTransfer *ft);
+
+  /** optional. called from mwService_free */
+  void (*clear)(struct mwServiceFileTransfer *srvc);
+};
+
+
+struct mwServiceFileTransfer *
+mwServiceFileTransfer_new(struct mwSession *session,
+			  struct mwFileTransferHandler *handler);
+
+
+struct mwFileTransferHandler *
+mwServiceFileTransfer_getHandler(struct mwServiceFileTransfer *srvc);
+
+
+struct mwFileTransfer *
+mwFileTransfer_new(struct mwServiceFileTransfer *srvc,
+		   const struct mwIdBlock *who, const char *msg,
+		   const char *filename, guint32 filesize);
+
+
+/** deallocate a file transfer. will call mwFileTransfer_close if
+    necessary */
+void
+mwFileTransfer_free(struct mwFileTransfer *ft);
+
+
+/** the status of this file transfer */
+enum mwFileTransferState
+mwFileTransfer_getState(struct mwFileTransfer *ft);
+
+
+struct mwServiceFileTransfer *
+mwFileTransfer_getService(struct mwFileTransfer *ft);
+
+
+/** the user on the other end of the file transfer */
+const struct mwIdBlock *
+mwFileTransfer_getUser(struct mwFileTransfer *ft);
+
+
+/** the message sent along with an offered file transfer */
+const char *
+mwFileTransfer_getMessage(struct mwFileTransfer *ft);
+
+
+/** the publicized file name. Not necessarily related to any actual
+    file on either system */
+const char *
+mwFileTransfer_getFileName(struct mwFileTransfer *ft);
+
+
+/** total bytes intended to be sent/received */
+guint32 mwFileTransfer_getFileSize(struct mwFileTransfer *ft);
+
+
+/** bytes remaining to be received/send */
+guint32 mwFileTransfer_getRemaining(struct mwFileTransfer *ft);
+
+
+/** count of bytes sent/received over this file transfer so far */
+#define mwFileTransfer_getSent(ft) \
+  (mwFileTransfer_getFileSize(ft) - mwFileTransfer_getRemaining(ft))
+
+
+/** initiate an outgoing file transfer */
+int mwFileTransfer_offer(struct mwFileTransfer *ft);
+
+
+/** accept an incoming file transfer */
+int mwFileTransfer_accept(struct mwFileTransfer *ft);
+
+
+/** reject an incoming file transfer */
+#define mwFileTransfer_reject(ft) \
+  mwFileTransfer_close((ft), mwFileTransfer_REJECTED)
+
+
+/** cancel an open file transfer */
+#define mwFileTransfer_cancel(ft) \
+  mwFileTransfer_close((ft), mwFileTransfer_SUCCESS);
+
+
+/** Close a file transfer. This will trigger the ft_close function of the
+    session's handler.
+
+    @relates mwFileTransfer_reject
+    @relates mwFileTransfer_cancel
+*/
+int mwFileTransfer_close(struct mwFileTransfer *ft, guint32 code);
+
+
+/** send a chunk of data over an outbound file transfer. The client at
+    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
+*/
+int mwFileTransfer_send(struct mwFileTransfer *ft,
+			struct mwOpaque *data);
+
+
+/** acknowledge the receipt of a chunk of data from an inbound file
+    transfer.  This should be done after every received chunk, or the
+    transfer will stall. However, not all clients will wait for an ack
+    after sending a chunk before sending the next chunk, so it is
+    possible to have the handler's ft_recv function triggered again
+    even if no ack was sent.
+
+    @relates mwFileTransferHandler::ft_recv
+*/
+int mwFileTransfer_ack(struct mwFileTransfer *ft);
+
+
+void mwFileTransfer_setClientData(struct mwFileTransfer *ft,
+				  gpointer data, GDestroyNotify clean);
+
+
+gpointer mwFileTransfer_getClientData(struct mwFileTransfer *ft);
+
+
+void mwFileTransfer_removeClientData(struct mwFileTransfer *ft);
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/mw_srvc_im.h	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,266 @@
+
+/*
+  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_IM_H
+#define _MW_SRVC_IM_H
+
+
+#include <glib.h>
+#include "mw_common.h"
+
+
+/** @file srvc_im.h
+
+    The IM service provides one-on-one communication between
+    users. Messages sent over conversations may relay different types
+    of information, in a variety of formats. The basic feature-set
+    provides plain-text chat with typing notification. More complex
+    features may be negotiated transparently by setting the IM Client
+    Type for a conversation, or for the service as a whole.
+ */
+
+
+/** Type identifier for the IM service */
+#define SERVICE_IM  0x00001000
+
+
+/** @struct mwServiceIm
+
+    An instance of the IM service. This service provides simple
+    instant messaging functionality */
+struct mwServiceIm;
+
+
+/** @struct mwConversation
+
+    A conversation between the local service and a single other user */
+struct mwConversation;
+
+
+enum mwImClientType {
+  mwImClient_PLAIN       = 0x00000001,  /**< text, typing */
+  mwImClient_NOTESBUDDY  = 0x00033453,  /**< adds html, subject, mime */
+  mwImClient_PRECONF     = 0x00000019,  /**< pre-conference, legacy */
+  mwImClient_UNKNOWN     = 0xffffffff,  /**< trouble determining type */
+};
+
+
+/**
+   Types of supported messages. When a conversation is created, the
+   least common denominator of features between either side of the
+   conversation (based on what features are available in the IM
+   service itself) becomes the set of supported features for that
+   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
+ */
+enum mwImSendType {
+  mwImSend_PLAIN,   /**< char *, plain-text message */
+  mwImSend_TYPING,  /**< gboolean, typing status */
+  mwImSend_HTML,    /**< char *, HTML formatted message (NOTESBUDDY) */
+  mwImSend_SUBJECT, /**< char *, conversation subject (NOTESBUDDY) */
+  mwImSend_MIME,    /**< char *, MIME-encoded message (NOTESBUDDY) */
+};
+
+
+
+/** @relates mwConversation_getState */
+enum mwConversationState {
+  mwConversation_CLOSED,   /**< conversation is not open */
+  mwConversation_PENDING,  /**< conversation is opening */
+  mwConversation_OPEN,     /**< conversation is open */
+  mwConversation_UNKNOWN,  /**< unknown state */
+};
+
+
+#define mwConversation_isState(conv, state) \
+  (mwConversation_getState(conv) == (state))
+
+#define mwConversation_isClosed(conv) \
+  mwConversation_isState((conv), mwConversation_CLOSED)
+
+#define mwConversation_isPending(conv) \
+  mwConversation_isState((conv), mwConversation_PENDING)
+
+#define mwConversation_isOpen(conv) \
+  mwConversation_isState((conv), mwConversation_OPEN)
+
+
+
+/** IM Service Handler. Provides functions for events triggered from an
+    IM service instance. */
+struct mwImHandler {
+
+  /** A conversation has been successfully opened */
+  void (*conversation_opened)(struct mwConversation *conv);
+
+  /** A conversation has been closed */
+  void (*conversation_closed)(struct mwConversation *conv, guint32 err);
+  
+  /** A message has been received on a conversation */
+  void (*conversation_recv)(struct mwConversation *conv,
+			    enum mwImSendType type, gconstpointer msg);
+
+  /** Handle a Place invitation. Set this to NULL and we should end up
+      receiving a conference invitation instead. */
+  void (*place_invite)(struct mwConversation *conv,
+		       const char *message,
+		       const char *title, const char *name);
+
+  /** optional. called from mwService_free */
+  void (*clear)(struct mwServiceIm *srvc);
+};
+
+
+struct mwServiceIm *mwServiceIm_new(struct mwSession *session,
+				    struct mwImHandler *handler);
+
+
+struct mwImHandler *mwServiceIm_getHandler(struct mwServiceIm *srvc);
+
+
+/** reference an existing conversation to target, or create a new
+    conversation to target if one does not already exist */
+struct mwConversation *mwServiceIm_getConversation(struct mwServiceIm *srvc,
+						   struct mwIdBlock *target);
+
+
+/** reference an existing conversation to target */
+struct mwConversation *mwServiceIm_findConversation(struct mwServiceIm *srvc,
+						    struct mwIdBlock *target);
+
+
+/** determine if the conversations created from this service will
+    support a given send type */
+gboolean mwServiceIm_supports(struct mwServiceIm *srvc,
+			      enum mwImSendType type);
+
+
+/** Set the default client type for the service. Newly created
+    conversations will attempt to meet this level of functionality
+    first.
+
+    @param srvc       the IM service
+    @param type       the send type to enable/disable
+*/
+void mwServiceIm_setClientType(struct mwServiceIm *srvc,
+			       enum mwImClientType type);
+
+
+enum mwImClientType mwServiceIm_getClientType(struct mwServiceIm *srvc);
+
+
+/** attempt to open a conversation. If the conversation was not
+    already open and it is accepted,
+    mwServiceImHandler::conversation_opened will be triggered. Upon
+    failure, mwServiceImHandler::conversation_closed will be
+    triggered */
+void mwConversation_open(struct mwConversation *conv);
+
+
+/** close a conversation. If the conversation was not already closed,
+    mwServiceImHandler::conversation_closed will be triggered */
+void mwConversation_close(struct mwConversation *conv, guint32 err);
+
+
+/** determine whether a conversation supports the given message type */
+gboolean mwConversation_supports(struct mwConversation *conv,
+				 enum mwImSendType type);
+
+
+enum mwImClientType mwConversation_getClientType(struct mwConversation *conv);
+
+
+/** get the state of a conversation
+
+    @relates mwConversation_isOpen
+    @relates mwConversation_isClosed
+    @relates mwConversation_isPending
+*/
+enum mwConversationState mwConversation_getState(struct mwConversation *conv);
+
+
+/** send a message over an open conversation */
+int mwConversation_send(struct mwConversation *conv,
+			enum mwImSendType type, gconstpointer send);
+
+
+/** @returns owning service for a conversation */
+struct mwServiceIm *mwConversation_getService(struct mwConversation *conv);
+
+
+/** login information for conversation partner. returns NULL if conversation 
+    is not OPEN */
+struct mwLoginInfo *mwConversation_getTargetInfo(struct mwConversation *conv);
+
+
+/** ID for conversation partner */
+struct mwIdBlock *mwConversation_getTarget(struct mwConversation *conv);
+
+
+/** set whether outgoing messages should be encrypted using the
+    negotiated cipher, if any */
+void mwConversation_setEncrypted(struct mwConversation *conv,
+				 gboolean useCipher);
+
+
+/** determine whether outgoing messages are being encrypted */
+gboolean mwConversation_isEncrypted(struct mwConversation *conv);
+
+
+/** 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
+*/
+void mwConversation_setClientData(struct mwConversation *conv,
+				  gpointer data, GDestroyNotify clean);
+
+
+/** Reference associated client data
+
+    @relates mwConversation_setClientData
+    @relates mwConversation_removeClientData
+ */
+gpointer mwConversation_getClientData(struct mwConversation *conv);
+
+
+/** Remove any associated client data, calling the optional cleanup
+    function if one was provided
+
+    @relates mwConversation_setClientData
+    @relates mwConversation_getClientData
+*/
+void mwConversation_removeClientData(struct mwConversation *conv);
+
+
+/** close and destroy the conversation and its backing channel, and
+    call the optional client data cleanup function */
+void mwConversation_free(struct mwConversation *conv);
+
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/mw_srvc_place.h	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,71 @@
+
+/*
+  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 SERVICE_PLACE  0x80000022
+
+
+/** @struct mwServicePlace */
+struct mwServicePlace;
+
+
+/** @struct mwPlace */
+struct mwPlace;
+
+
+/** @struct mwPlaceSection */
+struct mwPlaceSection;
+
+
+/** @struct mwPlaceAction */
+struct mwPlaceAction;
+
+
+struct mwPlaceHandler {
+
+};
+
+
+struct mwServicePlace *mwServicePlace_new(struct mwSession *session,
+					  struct mwPlaceHandler *handler);
+
+
+struct mwPlace *mwPlace_new(struct mwServicePlace *place,
+			    const char *title, const char *name);
+
+
+int mwPlace_open(struct mwPlace *place);
+
+
+int mwPlace_close(struct mwPlace *place);
+
+
+void mwPlace_free(struct mwPlace *place);
+
+
+const GList *mwPlace_getSections(struct mwPlace *place);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/mw_srvc_resolve.h	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,139 @@
+
+/*
+  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_RESOLVE_H
+#define _MW_SRVC_RESOLVE_H
+
+
+#include <glib.h>
+#include <glib/glist.h>
+
+
+/** Type identifier for the conference service */
+#define SERVICE_RESOLVE  0x00000015
+
+
+/** Return value of mwServiceResolve_search indicating an error */
+#define SEARCH_ERROR  0x00
+
+
+/** @struct mwServiceResolve
+    User lookup service */
+struct mwServiceResolve;
+
+
+enum mwResolveFlag {
+  /** return unique results or none at all */
+  mwResolveFlag_UNIQUE    = 0x00000001,
+
+  /** return only the first result */
+  mwResolveFlag_FIRST     = 0x00000002,
+
+  /** search all directories, not just the first with a match */
+  mwResolveFlag_ALL_DIRS  = 0x00000004,
+
+  /** search for users */
+  mwResolveFlag_USERS     = 0x00000008,
+
+  /** search for groups */
+  mwResolveFlag_GROUPS    = 0x00000010,
+};
+
+
+/** @see mwResolveResult */
+enum mwResolveCode {
+  /** successful search */
+  mwResolveCode_SUCCESS     = 0x00000000,
+
+  /** only some of the nested searches were successful */
+  mwResolveCode_PARTIAL     = 0x00010000,
+
+  /** more than one result (occurs when mwResolveFlag_UNIQUE is used
+      and more than one result would have been otherwise returned) */
+  mwResolveCode_MULTIPLE    = 0x80020000,
+
+  /** the name is not resolvable due to its format */
+  mwResolveCode_BAD_FORMAT  = 0x80030000,
+};
+
+
+enum mwResolveMatchType {
+  mwResolveMatch_USER   = 0x00000001,
+  mwResolveMatch_GROUP  = 0x00000002,
+};
+
+
+struct mwResolveMatch {
+  char *id;      /**< user id */
+  char *name;    /**< user name */
+  char *desc;    /**< description */
+  guint32 type;  /**< @see mwResolveMatchType */
+};
+
+
+struct mwResolveResult {
+  guint32 code;    /**< @see mwResolveCode */
+  char *name;      /**< name of the result */
+  GList *matches;  /**< list of mwResolveMatch */
+};
+
+
+/** Handle the results of a resolve request. If there was a cleanup
+    function specified to mwServiceResolve_search, it will be called
+    upon the user data after this callback returns.
+
+    @param srvc     the resolve service
+    @param id       the resolve request ID
+    @param code     return code
+    @param results  list of mwResolveResult
+    @param data     optional user data attached to the request
+*/
+typedef void (*mwResolveHandler)
+     (struct mwServiceResolve *srvc,
+      guint32 id, guint32 code, GList *results,
+      gpointer data);
+
+
+/** Allocate a new resolve service */
+struct mwServiceResolve *mwServiceResolve_new(struct mwSession *);
+
+
+/** Inisitate a resolve request.
+
+    @param queries  list query strings
+    @param flags    search flags
+    @param handler  result handling function
+    @param data     optional user data attached to the request
+    @param cleanup  optional function to clean up user data
+    @return         generated ID for the search request, or SEARCH_ERROR
+*/
+guint32 mwServiceResolve_resolve(struct mwServiceResolve *srvc,
+				 GList *queries, enum mwResolveFlag flags,
+				 mwResolveHandler handler,
+				 gpointer data, GDestroyNotify cleanup);
+
+
+/** Cancel a resolve request by its generated ID. The handler function
+    will not be called, and the optional cleanup function will be
+    called upon the optional user data for the request */
+void mwServiceResolve_cancelResolve(struct mwServiceResolve *, guint32);
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/mw_srvc_store.h	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,182 @@
+
+/*
+  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_STORE_H
+#define _MW_SRVC_STORE_H
+
+
+#include <glib.h>
+#include "mw_common.h"
+
+
+/** Type identifier for the storage service */
+#define SERVICE_STORAGE  0x00000018
+
+
+/** @struct mwServiceStorage
+    @see mwServiceStorage_new
+
+    Instance of the storage service */
+struct mwServiceStorage;
+
+
+/** @struct mwStorage
+
+    Unit Represents information intended for loading from or saving to
+    the storage service */
+struct mwStorageUnit;
+
+
+/** The upper limit of reserved Lotus keys */
+#define LOTUS_RESERVED_LIMIT  0x186a0
+
+
+/** Check if a key is in the range of Lotus reserved keys */
+#define KEY_IS_LOTUS_RESERVED(key) \
+  (((guint32) key) <= (LOTUS_RESERVED_LIMIT))
+
+
+/** Some common keys storage keys. Anything in the range 0x00 to
+    0x186a0 (100000) is reserved for use by the Lotus
+    clients. */
+enum mwStorageKey {
+
+  /** The buddy list, in the Sametime .dat file format. String */
+  mwStore_AWARE_LIST      = 0x00000000,
+
+  /** Default text for chat invitations. String */
+  mwStore_INVITE_CHAT     = 0x00000006,
+
+  /** Default text for meeting invitations. String */
+  mwStore_INVITE_MEETING  = 0x0000000e,
+
+  /** Last five Away messages, separated by semicolon. String */
+  mwStore_AWAY_MESSAGES   = 0x00000050,
+
+  /** Last five Busy (DND) messages, separated by semicolon. String */
+  mwStore_BUSY_MESSAGES   = 0x0000005a,
+
+  /** Last five Active messages, separated by semicolon. String */
+  mwStore_ACTIVE_MESSAGES = 0x00000064,
+};
+
+
+/** Appropriate function type for load and store callbacks.
+    @param srvc       the storage service
+    @param result     the result value of the load or store call
+    @param item       the storage unit loaded or saved
+    @param data       optional user data
+*/
+typedef void (*mwStorageCallback)
+     (struct mwServiceStorage *srvc,
+      guint32 result, struct mwStorageUnit *item,
+      gpointer data);
+
+
+/** Allocates and initializes a storage service instance for use on
+    the passed session. */
+struct mwServiceStorage *mwServiceStorage_new(struct mwSession *);
+
+
+/** create an empty storage unit */
+struct mwStorageUnit *mwStorageUnit_new(guint32 key);
+
+
+/** creates a storage unit with the passed key, and a copy of data. */
+struct mwStorageUnit *mwStorageUnit_newOpaque(guint32 key,
+					      struct mwOpaque *data);
+
+
+/** creates a storage unit with the passed key, and an encapsulated
+    boolean value */
+struct mwStorageUnit *mwStorageUnit_newBoolean(guint32 key,
+					       gboolean val);
+
+
+struct mwStorageUnit *mwStorageUnit_newInteger(guint32 key,
+					       guint32 val);
+
+
+/** creates a storage unit with the passed key, and an encapsulated
+    string value. */
+struct mwStorageUnit *mwStorageUnit_newString(guint32 key,
+					      const char *str);
+
+
+/** get the key for the given storage unit */
+guint32 mwStorageUnit_getKey(struct mwStorageUnit *);
+
+
+/** attempts to obtain a boolean value from a storage unit. If the
+    unit is empty, or does not contain the type in a recongnizable
+    format, val is returned instead */
+gboolean mwStorageUnit_asBoolean(struct mwStorageUnit *, gboolean val);
+
+
+/** attempts to obtain a guint32 value from a storage unit. If the
+    unit is empty, or does not contain the type in a recognizable
+    format, val is returned instead */
+guint32 mwStorageUnit_asInteger(struct mwStorageUnit *, guint32 val);
+
+
+/** attempts to obtain a string value from a storage unit. If the unit
+    is empty, or does not contain the type in a recognizable format,
+    NULL is returned instead. Note that the string returned is a copy,
+    and will need to be deallocated at some point. */
+char *mwStorageUnit_asString(struct mwStorageUnit *);
+
+
+/** direct access to the opaque data backing the storage unit */
+struct mwOpaque *mwStorageUnit_asOpaque(struct mwStorageUnit *);
+
+
+/** clears and frees a storage unit */
+void mwStorageUnit_free(struct mwStorageUnit *);
+
+      
+/** 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 item       storage unit to load
+    @param cb         callback function when the load call completes
+    @param data       user data for callback
+    @param data_free  optional cleanup function for user data
+*/
+void mwServiceStorage_load(struct mwServiceStorage *srvc,
+			   struct mwStorageUnit *item,
+			   mwStorageCallback cb,
+			   gpointer data, GDestroyNotify data_free);
+
+
+/** 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 item       storage unit to save
+    @param cb         callback function when the load call completes
+    @param data       optional user data for callback
+    @param data_free  optional cleanup function for user data
+ */
+void mwServiceStorage_save(struct mwServiceStorage *srvc,
+			   struct mwStorageUnit *item,
+			   mwStorageCallback cb,
+			   gpointer data, GDestroyNotify data_free);
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/mw_st_list.h	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,200 @@
+
+/*
+  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_ST_LIST_H
+#define _MW_ST_LIST_H
+
+
+#include <glib.h>
+#include <glib/glist.h>
+#include "mw_common.h"
+
+
+#define ST_LIST_MAJOR  3
+#define ST_LIST_MINOR  1
+#define ST_LIST_MICRO  3
+
+
+enum mwSametimeGroupType {
+  mwSametimeGroup_NORMAL  = 1,  /**< a normal group of users */
+  mwSametimeGroup_DYNAMIC = 2,  /**< a server-side group */
+  mwSametimeGroup_UNKNOWN = 0,  /**< error determining group type */
+};
+
+
+enum mwSametimeUserType {
+  mwSametimeUser_NORMAL   = 1,  /**< user on same community */
+  mwSametimeUser_EXTERNAL = 2,  /**< external user */
+  mwSametimeUser_UNKNOWN  = 0,  /**< error determining user type */
+};
+
+
+/** @struct mwSametimeList
+
+    Represents a group-based buddy list. */
+struct mwSametimeList;
+
+
+/** @struct mwSametimeGroup
+
+    Represents a group in a buddy list */
+struct mwSametimeGroup;
+
+
+/** @struct mwSametimeUser
+
+    Represents a user in a group in a buddy list */
+struct mwSametimeUser;
+
+
+/** Create a new list */
+struct mwSametimeList *mwSametimeList_new();
+
+
+/** Free the list, all of its groups, and all of the groups' members */
+void mwSametimeList_free(struct mwSametimeList *l);
+
+
+/** Load a sametime list from a buffer. The list must be encapsulated
+    as a string (eg, the first two bytes in the buffer should be the
+    length of the string) */
+void mwSametimeList_get(struct mwGetBuffer *b, struct mwSametimeList *l);
+
+
+/** Write a sametime list onto a buffer. The list will be encapsulated
+    in a string (the first two bytes written will be the length of the
+    rest of the written list data) */
+void mwSametimeList_put(struct mwPutBuffer *b, struct mwSametimeList *l);
+
+
+/** convert a plain string into a sametime list */
+struct mwSametimeList *mwSametimeList_load(const char *str);
+
+
+/** convert a sametime list into a string */
+char *mwSametimeList_store(struct mwSametimeList *l);
+
+
+void mwSametimeList_setMajor(struct mwSametimeList *l, guint v);
+
+
+guint mwSametimeList_getMajor(struct mwSametimeList *l);
+
+
+void mwSametimeList_setMinor(struct mwSametimeList *l, guint v);
+
+
+guint mwSametimeList_getMinor(struct mwSametimeList *l);
+
+
+void mwSametimeList_setMicro(struct mwSametimeList *l, guint v);
+
+
+guint mwSametimeList_getMicro(struct mwSametimeList *l);
+
+
+/** Get a GList snapshot of the groups in a list */
+GList *mwSametimeList_getGroups(struct mwSametimeList *l);
+
+
+struct mwSametimeGroup *
+mwSametimeList_findGroup(struct mwSametimeList *l,
+			 const char *name);
+
+
+/** Create a new group in a list */
+struct mwSametimeGroup *
+mwSametimeGroup_new(struct mwSametimeList *l,
+		    enum mwSametimeGroupType type,
+		    const char *name);
+
+
+/** Remove a group from its list, and free it. Also frees all users
+    contained in the group */
+void mwSametimeGroup_free(struct mwSametimeGroup *g);
+
+
+enum mwSametimeGroupType mwSametimeGroup_getType(struct mwSametimeGroup *g);
+
+
+const char *mwSametimeGroup_getName(struct mwSametimeGroup *g);
+
+
+void mwSametimeGroup_setAlias(struct mwSametimeGroup *g,
+			      const char *alias);
+
+
+const char *mwSametimeGroup_getAlias(struct mwSametimeGroup *g);
+
+
+void mwSametimeGroup_setOpen(struct mwSametimeGroup *g, gboolean open);
+
+
+gboolean mwSametimeGroup_isOpen(struct mwSametimeGroup *g);
+
+
+struct mwSametimeList *mwSametimeGroup_getList(struct mwSametimeGroup *g);
+
+
+/** Get a GList snapshot of the users in a list */
+GList *mwSametimeGroup_getUsers(struct mwSametimeGroup *g);
+
+
+struct mwSametimeUser *
+mwSametimeGroup_findUser(struct mwSametimeGroup *g,
+			 struct mwIdBlock *user);
+
+
+/** Create a user in a group */
+struct mwSametimeUser *
+mwSametimeUser_new(struct mwSametimeGroup *g,
+		   enum mwSametimeUserType type,
+		   struct mwIdBlock *user);
+
+
+/** Remove user from its group, and free it */
+void mwSametimeUser_free(struct mwSametimeUser *u);
+
+
+struct mwSametimeGroup *mwSametimeUser_getGroup(struct mwSametimeUser *u);
+
+
+enum mwSametimeUserType mwSametimeUser_getType(struct mwSametimeUser *u);
+
+
+const char *mwSametimeUser_getUser(struct mwSametimeUser *u);
+
+
+const char *mwSametimeUser_getCommunity(struct mwSametimeUser *u);
+
+
+void mwSametimeUser_setShortName(struct mwSametimeUser *u, const char *name);
+
+
+const char *mwSametimeUser_getShortName(struct mwSametimeUser *u);
+
+
+void mwSametimeUser_setAlias(struct mwSametimeUser *u, const char *alias);
+
+
+const char *mwSametimeUser_getAlias(struct mwSametimeUser *u);
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/mw_util.c	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,80 @@
+
+/*
+  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 "mw_util.h"
+
+
+static void collect_keys(gpointer key, gpointer val, gpointer data) {
+  GList **list = data;
+  *list = g_list_append(*list, key);
+}
+
+
+GList *map_collect_keys(GHashTable *ht) {
+  GList *ret = NULL;
+  g_hash_table_foreach(ht, collect_keys, &ret);
+  return ret;
+}
+
+
+static void collect_values(gpointer key, gpointer val, gpointer data) {
+  GList **list = data;
+  *list = g_list_append(*list, val);
+}
+
+
+GList *map_collect_values(GHashTable *ht) {
+  GList *ret = NULL;
+  g_hash_table_foreach(ht, collect_values, &ret);
+  return ret;
+}
+
+
+struct mw_datum *mw_datum_new(gpointer data, GDestroyNotify clear) {
+  struct mw_datum *d = g_new(struct mw_datum, 1);
+  mw_datum_set(d, data, clear);
+  return d;
+}
+
+
+void mw_datum_set(struct mw_datum *d, gpointer data, GDestroyNotify clear) {
+  d->data = data;
+  d->clear = clear;
+}
+
+
+gpointer mw_datum_get(struct mw_datum *d) {
+  return d->data;
+}
+
+
+void mw_datum_clear(struct mw_datum *d) {
+  if(d->clear) {
+    d->clear(d->data);
+    d->clear = NULL;
+  }
+  d->data = NULL;
+}
+
+
+void mw_datum_free(struct mw_datum *d) {
+  mw_datum_clear(d);
+  g_free(d);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/mw_util.h	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,85 @@
+
+/*
+  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_UTIL_H
+#define _MW_UTIL_H
+
+
+#include <glib.h>
+#include <glib/ghash.h>
+#include <glib/glist.h>
+
+
+#define map_guint_new() \
+  g_hash_table_new(g_direct_hash, g_direct_equal)
+
+
+#define map_guint_new_full(valfree) \
+  g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (valfree))
+
+
+#define map_guint_insert(ht, key, val) \
+  g_hash_table_insert((ht), GUINT_TO_POINTER((guint)(key)), (val))
+
+
+#define map_guint_replace(ht, key, val) \
+  g_hash_table_replace((ht), GUINT_TO_POINTER((guint)(key)), (val))
+
+
+#define map_guint_lookup(ht, key) \
+  g_hash_table_lookup((ht), GUINT_TO_POINTER((guint)(key)))
+
+
+#define map_guint_remove(ht, key) \
+  g_hash_table_remove((ht), GUINT_TO_POINTER((guint)(key)))
+
+
+#define map_guint_steal(ht, key) \
+  g_hash_table_steal((ht), GUINT_TO_POINTER((guint)(key)))
+
+
+GList *map_collect_keys(GHashTable *ht);
+
+
+GList *map_collect_values(GHashTable *ht);
+
+
+struct mw_datum {
+  gpointer data;
+  GDestroyNotify clear;
+};
+
+
+struct mw_datum *mw_datum_new(gpointer data, GDestroyNotify clear);
+
+
+void mw_datum_set(struct mw_datum *d, gpointer data, GDestroyNotify clear);
+
+
+gpointer mw_datum_get(struct mw_datum *d);
+
+
+void mw_datum_clear(struct mw_datum *d);
+
+
+void mw_datum_free(struct mw_datum *d);
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/service.c	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,267 @@
+
+/*
+  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 "mw_channel.h"
+#include "mw_debug.h"
+#include "mw_error.h"
+#include "mw_message.h"
+#include "mw_service.h"
+
+
+/* I tried to be explicit with the g_return_* calls, to make the debug
+   logging a bit more sensible. Hence all the explicit "foo != NULL"
+   checks. */
+
+
+void mwService_recvCreate(struct mwService *s, struct mwChannel *chan,
+			  struct mwMsgChannelCreate *msg) {
+
+  /* ensure none are null, ensure that the service and channel belong
+     to the same session, and ensure that the message belongs on the
+     channel */
+  g_return_if_fail(s != NULL);
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(msg != NULL);
+  g_return_if_fail(s->session == mwChannel_getSession(chan));
+  g_return_if_fail(mwChannel_getId(chan) == msg->channel);
+
+  if(s->recv_create) {
+    s->recv_create(s, chan, msg);
+  } else {
+    mwChannel_destroy(chan, ERR_FAILURE, NULL);
+  }
+}
+
+
+void mwService_recvAccept(struct mwService *s, struct mwChannel *chan,
+			  struct mwMsgChannelAccept *msg) {
+
+  /* ensure none are null, ensure that the service and channel belong
+     to the same session, and ensure that the message belongs on the
+     channel */
+  g_return_if_fail(s != NULL);
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(msg != NULL);
+  g_return_if_fail(s->session == mwChannel_getSession(chan));
+  g_return_if_fail(mwChannel_getId(chan) == msg->head.channel);
+
+  if(s->recv_accept)
+    s->recv_accept(s, chan, msg);
+}
+
+
+void mwService_recvDestroy(struct mwService *s, struct mwChannel *chan,
+			   struct mwMsgChannelDestroy *msg) {
+
+  /* ensure none are null, ensure that the service and channel belong
+     to the same session, and ensure that the message belongs on the
+     channel */
+  g_return_if_fail(s != NULL);
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(msg != NULL);
+  g_return_if_fail(s->session == mwChannel_getSession(chan));
+  g_return_if_fail(mwChannel_getId(chan) == msg->head.channel);
+
+  if(s->recv_destroy)
+    s->recv_destroy(s, chan, msg);
+}
+
+
+void mwService_recv(struct mwService *s, struct mwChannel *chan,
+		    guint16 msg_type, struct mwOpaque *data) {
+
+  /* ensure that none are null. zero-length messages are acceptable */
+  g_return_if_fail(s != NULL);
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(data != NULL);
+  g_return_if_fail(s->session == mwChannel_getSession(chan));
+
+  /*
+  g_message("mwService_recv: session = %p, service = %p, b = %p, n = %u",
+	    mwService_getSession(s), s, data->data, data->len);
+  */
+
+  if(s->recv)
+    s->recv(s, chan, msg_type, data);
+}
+
+
+guint32 mwService_getType(struct mwService *s) {
+  g_return_val_if_fail(s != NULL, 0x00);
+  return s->type;
+}
+
+
+const char *mwService_getName(struct mwService *s) {
+  g_return_val_if_fail(s != NULL, NULL);
+  g_return_val_if_fail(s->get_name != NULL, NULL);
+
+  return s->get_name(s);
+}
+
+
+const char *mwService_getDesc(struct mwService *s) {
+  g_return_val_if_fail(s != NULL, NULL);
+  g_return_val_if_fail(s->get_desc != NULL, NULL);
+
+  return s->get_desc(s);
+}
+
+
+struct mwSession *mwService_getSession(struct mwService *s) {
+  g_return_val_if_fail(s != NULL, NULL);
+  return s->session;
+}
+
+
+void mwService_init(struct mwService *srvc, struct mwSession *sess,
+		    guint32 type) {
+
+  /* ensure nothing is null, and there's no such thing as a zero
+     service type */
+  g_return_if_fail(srvc != NULL);
+  g_return_if_fail(sess != NULL);
+  g_return_if_fail(type != 0x00);
+
+  srvc->session = sess;
+  srvc->type = type;
+  srvc->state = mwServiceState_STOPPED;
+}
+
+
+enum mwServiceState mwService_getState(struct mwService *srvc) {
+  g_return_val_if_fail(srvc != NULL, mwServiceState_STOPPED);
+  return srvc->state;
+}
+
+
+void mwService_start(struct mwService *srvc) {
+  g_return_if_fail(srvc != NULL);
+
+  if(! MW_SERVICE_IS_STOPPED(srvc))
+    return;
+
+  srvc->state = mwServiceState_STARTING;
+  g_message("starting service %s", NSTR(mwService_getName(srvc)));
+
+  if(srvc->start) {
+    srvc->start(srvc);
+  } else {
+    mwService_started(srvc);
+  }
+}
+
+
+void mwService_started(struct mwService *srvc) {
+  g_return_if_fail(srvc != NULL);
+
+  srvc->state = mwServiceState_STARTED;
+  g_message("started service %s", NSTR(mwService_getName(srvc)));
+}
+
+
+void mwService_error(struct mwService *srvc) {
+  g_return_if_fail(srvc != NULL);
+
+  if(MW_SERVICE_IS_DEAD(srvc))
+    return;
+
+  srvc->state = mwServiceState_ERROR;
+  g_message("error in service %s", NSTR(mwService_getName(srvc)));
+
+  if(srvc->stop) {
+    srvc->stop(srvc);
+  } else {
+    mwService_stopped(srvc);
+  }
+}
+
+
+void mwService_stop(struct mwService *srvc) {
+  g_return_if_fail(srvc != NULL);
+
+  if(MW_SERVICE_IS_DEAD(srvc))
+    return;
+
+  srvc->state = mwServiceState_STOPPING;
+  g_message("stopping service %s", NSTR(mwService_getName(srvc)));
+
+  if(srvc->stop) {
+    srvc->stop(srvc);
+  } else {
+    mwService_stopped(srvc);
+  }
+}
+
+
+void mwService_stopped(struct mwService *srvc) {
+  g_return_if_fail(srvc != NULL);
+
+  if(srvc->state != mwServiceState_STOPPED) {
+    srvc->state = mwServiceState_STOPPED;
+    g_message("stopped service %s", NSTR(mwService_getName(srvc)));
+  }
+}
+
+
+void mwService_free(struct mwService *srvc) {
+  g_return_if_fail(srvc != NULL);
+
+  mwService_stop(srvc);
+
+  if(srvc->clear)
+    srvc->clear(srvc);
+
+  if(srvc->client_cleanup)
+    srvc->client_cleanup(srvc->client_data);
+
+  g_free(srvc);
+}
+
+
+/** @todo switch the following to using mw_datum */
+
+void mwService_setClientData(struct mwService *srvc,
+			     gpointer data, GDestroyNotify cleanup) {
+
+  g_return_if_fail(srvc != NULL);
+
+  srvc->client_data = data;
+  srvc->client_cleanup = cleanup;
+}
+
+
+gpointer mwService_getClientData(struct mwService *srvc) {
+  g_return_val_if_fail(srvc != NULL, NULL);
+  return srvc->client_data;
+}
+
+
+void mwService_removeClientData(struct mwService *srvc) {
+  g_return_if_fail(srvc != NULL);
+
+  if(srvc->client_cleanup) {
+    srvc->client_cleanup(srvc->client_data);
+    srvc->client_cleanup = NULL;
+  }
+
+  srvc->client_data = NULL;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/session.c	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,1087 @@
+
+/*
+  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 <string.h>
+
+#include "mw_channel.h"
+#include "mw_cipher.h"
+#include "mw_debug.h"
+#include "mw_error.h"
+#include "mw_message.h"
+#include "mw_service.h"
+#include "mw_session.h"
+#include "mw_util.h"
+
+
+/** the hash table key for a service, for mwSession::services */
+#define SERVICE_KEY(srvc) mwService_getType(srvc)
+
+/** the hash table key for a cipher, for mwSession::ciphers */
+#define CIPHER_KEY(ciph)  mwCipher_getType(ciph)
+
+
+#define GPOINTER(val)  (GUINT_TO_POINTER((guint) (val)))
+#define GUINT(val)     (GPOINTER_TO_UINT((val)))
+
+
+struct mwSession {
+
+  /** provides I/O and callback functions */
+  struct mwSessionHandler *handler;
+
+  enum mwSessionState state;  /**< session state */
+  guint32 state_info;         /**< additional state info */
+
+  /* input buffering for an incoming message */
+  char *buf;       /**< buffer for incoming message data */
+  gsize buf_len;   /**< length of buf */
+  gsize buf_used;  /**< offset to last-used byte of buf */
+  
+  struct mwLoginInfo login;      /**< login information */
+  struct mwUserStatus status;    /**< user status */
+  struct mwPrivacyInfo privacy;  /**< privacy list */
+
+  /** the collection of channels */
+  struct mwChannelSet *channels;
+
+  /** the collection of services, keyed to guint32 service id */
+  GHashTable *services;
+
+  /** the collection of ciphers, keyed to guint16 cipher type */
+  GHashTable *ciphers;
+
+  /** arbitrary key:value pairs */
+  GHashTable *attributes;
+
+  /** optional user data */
+  struct mw_datum client_data;
+};
+
+
+static void property_set(struct mwSession *s, const char *key,
+			 gpointer val, GDestroyNotify clean) {
+
+  g_hash_table_insert(s->attributes, g_strdup(key),
+		      mw_datum_new(val, clean));
+}
+
+
+static gpointer property_get(struct mwSession *s, const char *key) {
+  struct mw_datum *p = g_hash_table_lookup(s->attributes, key);
+  return p? p->data: NULL;
+}
+
+
+static void property_del(struct mwSession *s, const char *key) {
+  g_hash_table_remove(s->attributes, key);
+}
+
+
+/**
+   set up the default properties for a newly created session
+*/
+static void session_defaults(struct mwSession *s) {
+  property_set(s, mwSession_CLIENT_VER_MAJOR,
+	       GPOINTER(MW_PROTOCOL_VERSION_MAJOR), NULL);
+
+  property_set(s, mwSession_CLIENT_VER_MINOR, 
+	       GPOINTER(MW_PROTOCOL_VERSION_MINOR), NULL);
+
+  property_set(s, mwSession_CLIENT_TYPE_ID,
+	       GPOINTER(mwLogin_MEANWHILE), NULL);
+}
+
+
+struct mwSession *mwSession_new(struct mwSessionHandler *handler) {
+  struct mwSession *s;
+
+  g_return_val_if_fail(handler != NULL, NULL);
+
+  /* consider io_write and io_close to be absolute necessities */
+  g_return_val_if_fail(handler->io_write != NULL, NULL);
+  g_return_val_if_fail(handler->io_close != NULL, NULL);
+
+  s = g_new0(struct mwSession, 1);
+
+  s->state = mwSession_STOPPED;
+
+  s->handler = handler;
+
+  s->channels = mwChannelSet_new(s);
+  s->services = map_guint_new();
+  s->ciphers = map_guint_new();
+
+  s->attributes = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+					(GDestroyNotify) mw_datum_free);
+
+  session_defaults(s);
+
+  return s;
+}
+
+
+/** free and reset the session buffer */
+static void session_buf_free(struct mwSession *s) {
+  g_return_if_fail(s != NULL);
+
+  g_free(s->buf);
+  s->buf = NULL;
+  s->buf_len = 0;
+  s->buf_used = 0;
+}
+
+
+/** a polite string version of the session state enum */
+static const char *state_str(enum mwSessionState state) {
+  switch(state) {
+  case mwSession_STARTING:      return "starting";
+  case mwSession_HANDSHAKE:     return "handshake sent";
+  case mwSession_HANDSHAKE_ACK: return "handshake acknowledged";
+  case mwSession_LOGIN:         return "login sent";
+  case mwSession_LOGIN_REDIR:   return "login redirected";
+  case mwSession_LOGIN_CONT:    return "forcing login";
+  case mwSession_LOGIN_ACK:     return "login acknowledged";
+  case mwSession_STARTED:       return "started";
+  case mwSession_STOPPING:      return "stopping";
+  case mwSession_STOPPED:       return "stopped";
+
+  case mwSession_UNKNOWN:       /* fall-through */
+  default:                      return "UNKNOWN";
+  }
+}
+
+
+void mwSession_free(struct mwSession *s) {
+  struct mwSessionHandler *h;
+
+  g_return_if_fail(s != NULL);
+
+  if(! mwSession_isStopped(s)) {
+    g_debug("session is not stopped (state: %s), proceeding with free",
+	    state_str(s->state));
+  }
+
+  h = s->handler;
+  if(h) h->clear(s);
+  s->handler = NULL;
+
+  session_buf_free(s);
+
+  mwChannelSet_free(s->channels);
+  g_hash_table_destroy(s->services);
+  g_hash_table_destroy(s->ciphers);
+  g_hash_table_destroy(s->attributes);
+
+  mwLoginInfo_clear(&s->login);
+  mwUserStatus_clear(&s->status);
+  mwPrivacyInfo_clear(&s->privacy);
+
+  g_free(s);
+}
+
+
+/** write data to the session handler */
+static int io_write(struct mwSession *s, const char *buf, gsize len) {
+  g_return_val_if_fail(s != NULL, -1);
+  g_return_val_if_fail(s->handler != NULL, -1);
+  g_return_val_if_fail(s->handler->io_write != NULL, -1);
+
+  return s->handler->io_write(s, buf, len);
+}
+
+
+/** close the session handler */
+static void io_close(struct mwSession *s) {
+  g_return_if_fail(s != NULL);
+  g_return_if_fail(s->handler != NULL);
+  g_return_if_fail(s->handler->io_close != NULL);
+
+  s->handler->io_close(s);
+}
+
+
+/** 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) {
+
+  struct mwSessionHandler *sh;
+
+  g_return_if_fail(s != NULL);
+  g_return_if_fail(s->handler != NULL);
+
+  if(mwSession_isState(s, state)) return;
+
+  s->state = state;
+  s->state_info = info;
+
+  if(info) {
+    g_message("session state: %s (0x%08x)", state_str(state), info);
+  } else {
+    g_message("session state: %s", state_str(state));
+  }
+
+  sh = s->handler;
+  if(sh->on_stateChange)
+    sh->on_stateChange(s, state, info);
+}
+
+
+void mwSession_start(struct mwSession *s) {
+  struct mwMsgHandshake *msg;
+  int ret;
+
+  g_return_if_fail(s != NULL);
+  g_return_if_fail(mwSession_isStopped(s));
+
+  state(s, mwSession_STARTING, 0);
+
+  msg = (struct mwMsgHandshake *) mwMessage_new(mwMessage_HANDSHAKE);
+  msg->major = GUINT(property_get(s, mwSession_CLIENT_VER_MAJOR));
+  msg->minor = GUINT(property_get(s, mwSession_CLIENT_VER_MINOR));
+  msg->login_type = GUINT(property_get(s, mwSession_CLIENT_TYPE_ID));
+
+  ret = mwSession_send(s, MW_MESSAGE(msg));
+  mwMessage_free(MW_MESSAGE(msg));
+
+  if(ret) {
+    mwSession_stop(s, CONNECTION_BROKEN);
+  } else {
+    state(s, mwSession_HANDSHAKE, 0);
+  }
+}
+
+
+void mwSession_stop(struct mwSession *s, guint32 reason) {
+  GList *list, *l = NULL;
+  struct mwMsgChannelDestroy *msg;
+
+  g_return_if_fail(s != NULL);
+  g_return_if_fail(! mwSession_isStopping(s));
+  g_return_if_fail(! mwSession_isStopped(s));
+
+  state(s, mwSession_STOPPING, reason);
+
+  for(list = l = mwSession_getServices(s); l; l = l->next)
+    mwService_stop(MW_SERVICE(l->data));
+  g_list_free(list);
+
+  msg = (struct mwMsgChannelDestroy *)
+    mwMessage_new(mwMessage_CHANNEL_DESTROY);
+
+  msg->head.channel = MW_MASTER_CHANNEL_ID;
+  msg->reason = reason;
+
+  /* don't care if this fails, we're closing the connection anyway */
+  mwSession_send(s, MW_MESSAGE(msg));
+  mwMessage_free(MW_MESSAGE(msg));
+
+  session_buf_free(s);
+
+  /* close the connection */
+  io_close(s);
+
+  state(s, mwSession_STOPPED, reason);
+}
+
+
+/** compose authentication information into an opaque based on the
+    password */
+static void compose_auth(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);
+
+  /* the opaque with the key */
+  a.len = 5;
+  a.data = key;
+
+  /* the opaque to receive the encrypted pass */
+  b.len = 0;
+  b.data = NULL;
+
+  /* the plain-text pass dressed up as an opaque */
+  z.len = strlen(pass);
+  z.data = (char *) pass;
+
+  /* the opaque with the encrypted pass */
+  mwEncrypt(a.data, a.len, iv, &z, &b);
+
+  /* an opaque containing the other two opaques */
+  p = mwPutBuffer_new();
+  mwOpaque_put(p, &a);
+  mwOpaque_put(p, &b);
+  mwPutBuffer_finalize(auth, p);
+
+  /* this is the only one to clear, as the other uses a static buffer */
+  mwOpaque_clear(&b);
+}
+
+
+/** handle the receipt of a handshake_ack message by sending the login
+    message */
+static void HANDSHAKE_ACK_recv(struct mwSession *s,
+			       struct mwMsgHandshakeAck *msg) {
+  struct mwMsgLogin *log;
+  int ret;
+			       
+  g_return_if_fail(s != NULL);
+  g_return_if_fail(msg != NULL);
+  g_return_if_fail(mwSession_isState(s, mwSession_HANDSHAKE) ||
+		   mwSession_isState(s, mwSession_LOGIN_CONT));
+
+  if(mwSession_isState(s, mwSession_LOGIN_CONT)) {
+    /* this is a login continuation, don't re-send the login. We
+       should receive a login ack in a moment */
+
+    state(s, mwSession_HANDSHAKE_ACK, 0);
+    state(s, mwSession_LOGIN, 0);
+    return;
+
+  } else {
+    state(s, mwSession_HANDSHAKE_ACK, 0);
+  }
+
+  /* record the major/minor versions from the server */
+  property_set(s, mwSession_SERVER_VER_MAJOR, GPOINTER(msg->major), NULL);
+  property_set(s, mwSession_SERVER_VER_MINOR, GPOINTER(msg->minor), NULL);
+
+  /* compose the login message */
+  log = (struct mwMsgLogin *) mwMessage_new(mwMessage_LOGIN);
+  log->login_type = GUINT(property_get(s, mwSession_CLIENT_TYPE_ID));
+  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));
+  
+  /* send the login message */
+  ret = mwSession_send(s, MW_MESSAGE(log));
+  mwMessage_free(MW_MESSAGE(log));
+
+  if(! ret) {
+    /* sent login OK, set state appropriately */
+    state(s, mwSession_LOGIN, 0);
+  }
+}
+
+
+/** handle the receipt of a login_ack message. This completes the
+    startup sequence for the session */
+static void LOGIN_ACK_recv(struct mwSession *s,
+			   struct mwMsgLoginAck *msg) {
+  GList *ll, *l;
+
+  g_return_if_fail(s != NULL);
+  g_return_if_fail(msg != NULL);
+  g_return_if_fail(mwSession_isState(s, mwSession_LOGIN));
+
+  /* store the login information in the session */
+  mwLoginInfo_clear(&s->login);
+  mwLoginInfo_clone(&s->login, &msg->login);
+
+  state(s, mwSession_LOGIN_ACK, 0);
+
+  /* start up our services */
+  for(ll = l = mwSession_getServices(s); l; l = l->next) {
+    mwService_start(l->data);
+  }
+  g_list_free(ll);
+
+  /* @todo any further startup stuff? */
+
+  state(s, mwSession_STARTED, 0);
+}
+
+
+static void CHANNEL_CREATE_recv(struct mwSession *s,
+				struct mwMsgChannelCreate *msg) {
+  struct mwChannel *chan;
+  chan = mwChannel_newIncoming(s->channels, msg->channel);
+
+  /* hand off to channel */
+  mwChannel_recvCreate(chan, msg);
+}
+
+
+static void CHANNEL_ACCEPT_recv(struct mwSession *s,
+				struct mwMsgChannelAccept *msg) {
+  struct mwChannel *chan;
+  chan = mwChannel_find(s->channels, msg->head.channel);
+
+  g_return_if_fail(chan != NULL);
+
+  /* hand off to channel */
+  mwChannel_recvAccept(chan, msg);
+}
+
+
+static void CHANNEL_DESTROY_recv(struct mwSession *s,
+				 struct mwMsgChannelDestroy *msg) {
+
+  /* the server can indicate that we should close the session by
+     destroying the zero channel */
+  if(msg->head.channel == MW_MASTER_CHANNEL_ID) {
+    mwSession_stop(s, msg->reason);
+
+  } else {
+    struct mwChannel *chan;
+    chan = mwChannel_find(s->channels, msg->head.channel);
+
+    /* we don't have any such channel... so I guess we destroyed it.
+       This is to remove a warning from timing errors when two clients
+       both try to close a channel at about the same time. */
+    if(! chan) return;
+    
+    /* hand off to channel */
+    mwChannel_recvDestroy(chan, msg);
+  }
+}
+
+
+static void CHANNEL_SEND_recv(struct mwSession *s,
+			      struct mwMsgChannelSend *msg) {
+  struct mwChannel *chan;
+  chan = mwChannel_find(s->channels, msg->head.channel);
+
+  /* if we don't have any such channel, we're certainly not going to
+     accept data from it */
+  if(! chan) return;
+
+  /* hand off to channel */
+  mwChannel_recv(chan, msg);
+}
+
+
+static void SET_PRIVACY_LIST_recv(struct mwSession *s,
+				  struct mwMsgSetPrivacyList *msg) {
+  struct mwSessionHandler *sh = s->handler;
+
+  g_info("SET_PRIVACY_LIST");
+
+  mwPrivacyInfo_clear(&s->privacy);
+  mwPrivacyInfo_clone(&s->privacy, &msg->privacy);
+
+  if(sh && sh->on_setPrivacyInfo)
+    sh->on_setPrivacyInfo(s);
+}
+
+
+static void SET_USER_STATUS_recv(struct mwSession *s,
+				 struct mwMsgSetUserStatus *msg) {
+  struct mwSessionHandler *sh = s->handler;
+
+  mwUserStatus_clear(&s->status);
+  mwUserStatus_clone(&s->status, &msg->status);
+
+  if(sh && sh->on_setUserStatus)
+    sh->on_setUserStatus(s);
+}
+
+
+static void SENSE_SERVICE_recv(struct mwSession *s,
+			       struct mwMsgSenseService *msg) {
+  struct mwService *srvc;
+
+  srvc = mwSession_getService(s, msg->service);
+  if(srvc) mwService_start(srvc);
+}
+
+
+static void ADMIN_recv(struct mwSession *s, struct mwMsgAdmin *msg) {
+  struct mwSessionHandler *sh = s->handler;
+
+  if(sh && sh->on_admin)
+    sh->on_admin(s, msg->text);
+}
+
+
+static void LOGIN_REDIRECT_recv(struct mwSession *s,
+				struct mwMsgLoginRedirect *msg) {
+  struct mwSessionHandler *sh = s->handler;
+
+  state(s, mwSession_LOGIN_REDIR, 0);
+
+  if(sh && sh->on_loginRedirect)
+    sh->on_loginRedirect(s, msg->host);
+}
+
+
+#define CASE(var, type) \
+case mwMessage_ ## var: \
+  var ## _recv(s, (struct type *) msg); \
+  break;
+
+
+static void session_process(struct mwSession *s,
+			    const char *buf, gsize len) {
+
+  struct mwOpaque o = { len, (char *) buf };
+  struct mwGetBuffer *b;
+  struct mwMessage *msg;
+
+  g_assert(s != NULL);
+  g_return_if_fail(buf != NULL);
+
+  /* ignore zero-length messages */
+  if(len == 0) return;
+
+  /* wrap up buf */
+  b = mwGetBuffer_wrap(&o);
+
+  /* attempt to parse the message. */
+  msg = mwMessage_get(b);
+  g_return_if_fail(msg != NULL);
+
+  /* handle each of the appropriate incoming types of mwMessage */
+  switch(msg->type) {
+    CASE(HANDSHAKE_ACK, mwMsgHandshakeAck);
+    CASE(LOGIN_REDIRECT, mwMsgLoginRedirect);
+    CASE(LOGIN_ACK, mwMsgLoginAck);
+    CASE(CHANNEL_CREATE, mwMsgChannelCreate);
+    CASE(CHANNEL_DESTROY, mwMsgChannelDestroy);
+    CASE(CHANNEL_SEND, mwMsgChannelSend);
+    CASE(CHANNEL_ACCEPT, mwMsgChannelAccept);
+    CASE(SET_PRIVACY_LIST, mwMsgSetPrivacyList);
+    CASE(SET_USER_STATUS, mwMsgSetUserStatus);
+    CASE(SENSE_SERVICE, mwMsgSenseService);
+    CASE(ADMIN, mwMsgAdmin);
+    
+  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);
+}
+
+
+#undef CASE
+
+
+#define ADVANCE(b, n, count) (b += count, n -= count)
+
+
+/* handle input to complete an existing buffer */
+static gsize session_recv_cont(struct mwSession *s, const char *b, gsize n) {
+
+  /* determine how many bytes still required */
+  gsize x = s->buf_len - s->buf_used;
+
+  /* g_message(" session_recv_cont: session = %p, b = %p, n = %u",
+	    s, b, n); */
+  
+  if(n < x) {
+    /* not quite enough; still need some more */
+    memcpy(s->buf+s->buf_used, b, n);
+    s->buf_used += n;
+    return 0;
+    
+  } else {
+    /* enough to finish the buffer, at least */
+    memcpy(s->buf+s->buf_used, b, x);
+    ADVANCE(b, n, x);
+    
+    if(s->buf_len == 4) {
+      /* if only the length bytes were being buffered, we'll now try
+       to complete an actual message */
+
+      struct mwOpaque o = { 4, s->buf };
+      struct mwGetBuffer *gb = mwGetBuffer_wrap(&o);
+      x = guint32_peek(gb);
+      mwGetBuffer_free(gb);
+
+      if(n < x) {
+	/* there isn't enough to meet the demands of the length, so
+	   we'll buffer it for next time */
+
+	char *t;
+	x += 4;
+	t = (char *) g_malloc(x);
+	memcpy(t, s->buf, 4);
+	memcpy(t+4, b, n);
+	
+	session_buf_free(s);
+	
+	s->buf = t;
+	s->buf_len = x;
+	s->buf_used = n + 4;
+	return 0;
+	
+      } else {
+	/* there's enough (maybe more) for a full message. don't need
+	   the old session buffer (which recall, was only the length
+	   bytes) any more */
+	
+	session_buf_free(s);
+	session_process(s, b, x);
+	ADVANCE(b, n, x);
+      }
+      
+    } else {
+      /* process the now-complete buffer. remember to skip the first
+	 four bytes, since they're just the size count */
+      session_process(s, s->buf+4, s->buf_len-4);
+      session_buf_free(s);
+    }
+  }
+
+  return n;
+}
+
+
+/* handle input when there's nothing previously buffered */
+static gsize session_recv_empty(struct mwSession *s, const char *b, gsize n) {
+  struct mwOpaque o = { n, (char *) b };
+  struct mwGetBuffer *gb;
+  gsize x;
+
+  if(n < 4) {
+    /* uh oh. less than four bytes means we've got an incomplete
+       length indicator. Have to buffer to get the rest of it. */
+    s->buf = (char *) g_malloc0(4);
+    memcpy(s->buf, b, n);
+    s->buf_len = 4;
+    s->buf_used = n;
+    return 0;
+  }
+  
+  /* peek at the length indicator. if it's a zero length message,
+     don't process, just skip it */
+  gb = mwGetBuffer_wrap(&o);
+  x = guint32_peek(gb);
+  mwGetBuffer_free(gb);
+  if(! x) return n - 4;
+
+  if(n < (x + 4)) {
+    /* if the total amount of data isn't enough to cover the length
+       bytes and the length indicated by those bytes, then we'll need
+       to buffer. This is where the DOS mentioned below in
+       session_recv takes place */
+
+    x += 4;
+    s->buf = (char *) g_malloc(x);
+    memcpy(s->buf, b, n);
+    s->buf_len = x;
+    s->buf_used = n;
+    return 0;
+    
+  } else {
+    /* advance past length bytes */
+    ADVANCE(b, n, 4);
+    
+    /* process and advance */
+    session_process(s, b, x);
+    ADVANCE(b, n, x);
+
+    /* return left-over count */
+    return n;
+  }
+}
+
+
+static gsize session_recv(struct mwSession *s, const char *b, gsize n) {
+  /* This is messy and kind of confusing. I'd like to simplify it at
+     some point, but the constraints are as follows:
+
+      - buffer up to a single full message on the session buffer
+      - buffer must contain the four length bytes
+      - the four length bytes indicate how much we'll need to buffer
+      - the four length bytes might not arrive all at once, so it's
+        possible that we'll need to buffer to get them.
+      - since our buffering includes the length bytes, we know we
+        still have an incomplete length if the buffer length is only
+        four. */
+  
+  /** @todo we should allow a compiled-in upper limit to message
+     sizes, and just drop messages over that size. However, to do that
+     we'd need to keep track of the size of a message and keep
+     dropping bytes until we'd fulfilled the entire length. eg: if we
+     receive a message size of 10MB, we need to pass up exactly 10MB
+     before it's safe to start processing the rest as a new
+     message. As it stands, a malicious packet from the server can run
+     us out of memory by indicating it's going to send us some
+     obscenely long message (even if it never actually sends it) */
+  
+  /* 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(n == 0) {
+    return 0;
+
+  } else if(s->buf_len > 0) {
+    return session_recv_cont(s, b, n);
+
+  } else {
+    return session_recv_empty(s, b, n);
+  }
+}
+
+
+#undef ADVANCE
+
+
+void mwSession_recv(struct mwSession *s, const char *buf, gsize n) {
+  char *b = (char *) buf;
+  gsize remain = 0;
+
+  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);
+    n = remain;
+  }
+}
+
+
+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);
+
+  /* writing nothing is easy */
+  if(! msg) return 0;
+
+  /* first we render the message into an opaque */
+  b = mwPutBuffer_new();
+  mwMessage_put(b, msg);
+  mwPutBuffer_finalize(&o, b);
+
+  /* then we render the opaque into... another opaque! */
+  b = mwPutBuffer_new();
+  mwOpaque_put(b, &o);
+  mwOpaque_clear(&o);
+  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);
+
+  /* ensure we could actually write the message */
+  if(! ret) {
+
+    /* special case, as the server doesn't always respond to user
+       status messages. Thus, we trigger the event when we send the
+       messages as well as when we receive them */
+    if(msg->type == mwMessage_SET_USER_STATUS) {
+      SET_USER_STATUS_recv(s, (struct mwMsgSetUserStatus *) msg);
+    }
+  }
+
+  return ret;
+}
+
+
+int mwSession_sendKeepalive(struct mwSession *s) {
+  const char b = 0x80;
+
+  g_return_val_if_fail(s != NULL, -1);
+  return io_write(s, &b, 1);
+}
+
+
+int mwSession_forceLogin(struct mwSession *s) {
+  struct mwMsgLoginContinue *msg;
+  int ret;
+
+  g_return_val_if_fail(s != NULL, -1);
+  g_return_val_if_fail(mwSession_isState(s, mwSession_LOGIN_REDIR), -1);
+  
+  state(s, mwSession_LOGIN_CONT, 0x00);
+
+  msg = (struct mwMsgLoginContinue *)
+    mwMessage_new(mwMessage_LOGIN_CONTINUE);
+
+  ret = mwSession_send(s, MW_MESSAGE(msg));
+  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;
+}
+
+
+struct mwLoginInfo *mwSession_getLoginInfo(struct mwSession *s) {
+  g_return_val_if_fail(s != NULL, NULL);
+  return &s->login;
+}
+
+
+int mwSession_setPrivacyInfo(struct mwSession *s,
+			     struct mwPrivacyInfo *privacy) {
+
+  struct mwMsgSetPrivacyList *msg;
+  int ret;
+
+  g_return_val_if_fail(s != NULL, -1);
+  g_return_val_if_fail(privacy != NULL, -1);
+
+  msg = (struct mwMsgSetPrivacyList *)
+    mwMessage_new(mwMessage_SET_PRIVACY_LIST);
+
+  mwPrivacyInfo_clone(&msg->privacy, privacy);
+
+  ret = mwSession_send(s, MW_MESSAGE(msg));
+  mwMessage_free(MW_MESSAGE(msg));
+
+  return ret;
+}
+
+
+struct mwPrivacyInfo *mwSession_getPrivacyInfo(struct mwSession *s) {
+  g_return_val_if_fail(s != NULL, NULL);
+  return &s->privacy;
+}
+
+
+int mwSession_setUserStatus(struct mwSession *s,
+			    struct mwUserStatus *stat) {
+
+  struct mwMsgSetUserStatus *msg;
+  int ret;
+
+  g_return_val_if_fail(s != NULL, -1);
+  g_return_val_if_fail(stat != NULL, -1);
+
+  msg = (struct mwMsgSetUserStatus *)
+    mwMessage_new(mwMessage_SET_USER_STATUS);
+
+  mwUserStatus_clone(&msg->status, stat);
+
+  ret = mwSession_send(s, MW_MESSAGE(msg));
+  mwMessage_free(MW_MESSAGE(msg));
+
+  return ret;
+}
+
+
+struct mwUserStatus *mwSession_getUserStatus(struct mwSession *s) {
+  g_return_val_if_fail(s != NULL, NULL);
+  return &s->status;
+}
+
+
+enum mwSessionState mwSession_getState(struct mwSession *s) {
+  g_return_val_if_fail(s != NULL, mwSession_UNKNOWN);
+  return s->state;
+}
+
+
+guint32 mwSession_getStateInfo(struct mwSession *s) {
+  g_return_val_if_fail(s != NULL, 0);
+  return s->state_info;
+}
+
+
+struct mwChannelSet *mwSession_getChannels(struct mwSession *session) {
+  g_return_val_if_fail(session != NULL, NULL);
+  return session->channels;
+}
+
+
+gboolean mwSession_addService(struct mwSession *s, struct mwService *srv) {
+  g_return_val_if_fail(s != NULL, FALSE);
+  g_return_val_if_fail(srv != NULL, FALSE);
+  g_return_val_if_fail(s->services != NULL, FALSE);
+
+  if(map_guint_lookup(s->services, SERVICE_KEY(srv))) {
+    return FALSE;
+
+  } else {
+    map_guint_insert(s->services, SERVICE_KEY(srv), srv);
+    if(mwSession_isState(s, mwSession_STARTED))
+      mwSession_senseService(s, mwService_getType(srv));
+    return TRUE;
+  }
+}
+
+
+struct mwService *mwSession_getService(struct mwSession *s, guint32 srv) {
+  g_return_val_if_fail(s != NULL, NULL);
+  g_return_val_if_fail(s->services != NULL, NULL);
+
+  return map_guint_lookup(s->services, srv);
+}
+
+
+struct mwService *mwSession_removeService(struct mwSession *s, guint32 srv) {
+  struct mwService *svc;
+
+  g_return_val_if_fail(s != NULL, NULL);
+  g_return_val_if_fail(s->services != NULL, NULL);
+
+  svc = map_guint_lookup(s->services, srv);
+  if(svc) map_guint_remove(s->services, srv);
+  return svc;
+}
+
+
+GList *mwSession_getServices(struct mwSession *s) {
+  g_return_val_if_fail(s != NULL, NULL);
+  g_return_val_if_fail(s->services != NULL, NULL);
+
+  return map_collect_values(s->services);
+}
+
+
+void mwSession_senseService(struct mwSession *s, guint32 srvc) {
+  struct mwMsgSenseService *msg;
+
+  g_return_if_fail(s != NULL);
+  g_return_if_fail(srvc != 0x00);
+  g_return_if_fail(mwSession_isStarted(s));
+
+  msg = (struct mwMsgSenseService *)
+    mwMessage_new(mwMessage_SENSE_SERVICE);
+  msg->service = srvc;
+
+  mwSession_send(s, MW_MESSAGE(msg));
+  mwMessage_free(MW_MESSAGE(msg));
+}
+
+
+gboolean mwSession_addCipher(struct mwSession *s, struct mwCipher *c) {
+  g_return_val_if_fail(s != NULL, FALSE);
+  g_return_val_if_fail(c != NULL, FALSE);
+  g_return_val_if_fail(s->ciphers != NULL, FALSE);
+
+  if(map_guint_lookup(s->ciphers, mwCipher_getType(c))) {
+    g_message("cipher %s is already added, apparently",
+	      NSTR(mwCipher_getName(c)));
+    return FALSE;
+
+  } else {
+    g_message("adding cipher %s", NSTR(mwCipher_getName(c)));
+    map_guint_insert(s->ciphers, mwCipher_getType(c), c);
+    return TRUE;
+  }
+}
+
+
+struct mwCipher *mwSession_getCipher(struct mwSession *s, guint16 c) {
+  g_return_val_if_fail(s != NULL, NULL);
+  g_return_val_if_fail(s->ciphers != NULL, NULL);
+
+  return map_guint_lookup(s->ciphers, c);
+}
+
+
+struct mwCipher *mwSession_removeCipher(struct mwSession *s, guint16 c) {
+  struct mwCipher *ciph;
+
+  g_return_val_if_fail(s != NULL, NULL);
+  g_return_val_if_fail(s->ciphers != NULL, NULL);
+
+  ciph = map_guint_lookup(s->ciphers, c);
+  if(ciph) map_guint_remove(s->ciphers, c);
+  return ciph;
+}
+
+
+GList *mwSession_getCiphers(struct mwSession *s) {
+  g_return_val_if_fail(s != NULL, NULL);
+  g_return_val_if_fail(s->ciphers != NULL, NULL);
+
+  return map_collect_values(s->ciphers);
+}
+
+
+void mwSession_setProperty(struct mwSession *s, const char *key,
+			   gpointer val, GDestroyNotify clean) {
+
+  g_return_if_fail(s != NULL);
+  g_return_if_fail(s->attributes != NULL);
+  g_return_if_fail(key != NULL);
+
+  property_set(s, key, val, clean);
+}
+
+
+gpointer mwSession_getProperty(struct mwSession *s, const char *key) {
+ 
+  g_return_val_if_fail(s != NULL, NULL);
+  g_return_val_if_fail(s->attributes != NULL, NULL);
+  g_return_val_if_fail(key != NULL, NULL);
+
+  return property_get(s, key);
+}
+
+
+void mwSession_removeProperty(struct mwSession *s, const char *key) {
+  g_return_if_fail(s != NULL);
+  g_return_if_fail(s->attributes != NULL);
+  g_return_if_fail(key != NULL);
+
+  property_del(s, key);
+}
+
+
+void mwSession_setClientData(struct mwSession *session,
+			     gpointer data, GDestroyNotify clear) {
+
+  g_return_if_fail(session != NULL);
+  mw_datum_set(&session->client_data, data, clear);
+}
+
+
+gpointer mwSession_getClientData(struct mwSession *session) {
+  g_return_val_if_fail(session != NULL, NULL);
+  return mw_datum_get(&session->client_data);
+}
+
+
+void mwSession_removeClientData(struct mwSession *session) {
+  g_return_if_fail(session != NULL);
+  mw_datum_clear(&session->client_data);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/srvc_aware.c	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,1283 @@
+
+/*
+  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 <string.h>
+
+#include "mw_channel.h"
+#include "mw_debug.h"
+#include "mw_error.h"
+#include "mw_message.h"
+#include "mw_service.h"
+#include "mw_session.h"
+#include "mw_srvc_aware.h"
+#include "mw_util.h"
+
+
+struct mwServiceAware {
+  struct mwService service;
+
+  struct mwAwareHandler *handler;
+
+  /** map of ENTRY_KEY(aware_entry):aware_entry */
+  GHashTable *entries;
+
+  /** set of guint32:attrib_watch_entry attribute keys */
+  GHashTable *attribs;
+
+  /** collection of lists of awareness for this service. Each item is
+      a mwAwareList */
+  GList *lists;
+
+  /** the buddy list channel */
+  struct mwChannel *channel;
+};
+
+
+struct mwAwareList {
+
+  /** the owning service */
+  struct mwServiceAware *service;
+
+  /** map of ENTRY_KEY(aware_entry):aware_entry */
+  GHashTable *entries;
+
+  /** set of guint32:attrib_watch_entry attribute keys */
+  GHashTable *attribs;
+
+  struct mwAwareListHandler *handler;
+  struct mw_datum client_data;
+};
+
+
+struct mwAwareAttribute {
+  guint32 key;
+  struct mwOpaque data;
+};
+
+
+struct attrib_entry {
+  guint32 key;
+  GList *membership;
+};
+
+
+/** an actual awareness entry, belonging to any number of aware lists */
+struct aware_entry {
+  struct mwAwareSnapshot aware;
+
+  /** list of mwAwareList containing this entry */
+  GList *membership;
+
+  /** collection of attribute values for this entry.
+      map of ATTRIB_KEY(mwAwareAttribute):mwAwareAttribute */
+  GHashTable *attribs;
+};
+
+
+#define ENTRY_KEY(entry) &entry->aware.id
+
+
+/** the channel send types used by this service */
+enum msg_types {
+  msg_AWARE_ADD       = 0x0068,  /**< remove an aware */
+  msg_AWARE_REMOVE    = 0x0069,  /**< add an aware */
+
+  msg_OPT_DO_SET      = 0x00c9,  /**< set an attribute */
+  msg_OPT_DO_UNSET    = 0x00ca,  /**< unset an attribute */
+  msg_OPT_WATCH       = 0x00cb,  /**< set the attribute watch list */
+
+  msg_AWARE_SNAPSHOT  = 0x01f4,  /**< recv aware snapshot */
+  msg_AWARE_UPDATE    = 0x01f5,  /**< recv aware update */
+  msg_AWARE_GROUP     = 0x01f6,  /**< recv group aware */
+
+  msg_OPT_GOT_SET     = 0x0259,  /**< recv attribute set update */
+  msg_OPT_GOT_UNSET   = 0x025a,  /**< recv attribute unset update */
+
+  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 */
+};
+
+
+static void aware_entry_free(struct aware_entry *ae) {
+  mwAwareSnapshot_clear(&ae->aware);
+  g_list_free(ae->membership);
+  g_hash_table_destroy(ae->attribs);
+  g_free(ae);
+}
+
+
+static void attrib_entry_free(struct attrib_entry *ae) {
+  g_list_free(ae->membership);
+  g_free(ae);
+}
+
+
+static void attrib_free(struct mwAwareAttribute *attrib) {
+  mwOpaque_clear(&attrib->data);
+  g_free(attrib);
+}
+
+
+static struct aware_entry *aware_find(struct mwServiceAware *srvc,
+				      struct mwAwareIdBlock *srch) {
+  g_assert(srvc != NULL);
+  g_assert(srvc->entries != NULL);
+  g_return_val_if_fail(srch != NULL, NULL);
+  
+  return g_hash_table_lookup(srvc->entries, srch);
+}
+
+
+static struct aware_entry *list_aware_find(struct mwAwareList *list,
+					   struct mwAwareIdBlock *srch) {
+  g_return_val_if_fail(list != NULL, NULL);
+  g_return_val_if_fail(list->entries != NULL, NULL);
+  g_return_val_if_fail(srch != NULL, NULL);
+
+  return g_hash_table_lookup(list->entries, srch);
+}
+
+
+static void compose_list(struct mwPutBuffer *b, GList *id_list) {
+  guint32_put(b, g_list_length(id_list));
+  for(; id_list; id_list = id_list->next)
+    mwAwareIdBlock_put(b, id_list->data);
+}
+
+
+static int send_add(struct mwChannel *chan, GList *id_list) {
+  struct mwPutBuffer *b = mwPutBuffer_new();
+  struct mwOpaque o;
+  int ret;
+
+  compose_list(b, id_list);
+
+  mwPutBuffer_finalize(&o, b);
+
+  ret = mwChannel_send(chan, msg_AWARE_ADD, &o);
+  mwOpaque_clear(&o);
+
+  return ret;  
+}
+
+
+static int send_rem(struct mwChannel *chan, GList *id_list) {
+  struct mwPutBuffer *b = mwPutBuffer_new();
+  struct mwOpaque o;
+  int ret;
+
+  compose_list(b, id_list);
+  mwPutBuffer_finalize(&o, b);
+
+  ret = mwChannel_send(chan, msg_AWARE_REMOVE, &o);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+static gboolean collect_dead(gpointer key, gpointer val, gpointer data) {
+  struct aware_entry *aware = val;
+  GList **dead = data;
+
+  if(aware->membership == NULL) {
+    g_info(" removing %s, %s",
+	   NSTR(aware->aware.id.user), NSTR(aware->aware.id.community));
+    *dead = g_list_append(*dead, aware);
+    return TRUE;
+
+  } else {
+    return FALSE;
+  }
+}
+
+
+static int remove_unused(struct mwServiceAware *srvc) {
+  /* - create a GList of all the unused aware entries
+     - remove each unused aware from the service
+     - if the service is alive, send a removal message for the collected
+     unused.
+  */
+
+  int ret = 0;
+  GList *dead = NULL, *l;
+
+  if(srvc->entries) {
+    g_info("bring out your dead *clang*");
+    g_hash_table_foreach_steal(srvc->entries, collect_dead, &dead);
+  }
+ 
+  if(dead) {
+    if(MW_SERVICE_IS_LIVE(srvc))
+      ret = send_rem(srvc->channel, dead) || ret;
+    
+    for(l = dead; l; l = l->next)
+      aware_entry_free(l->data);
+
+    g_list_free(dead);
+  }
+
+  return ret;
+}
+
+
+static int send_attrib_list(struct mwServiceAware *srvc) {
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+
+  int tmp;
+  GList *l;
+
+  l = map_collect_keys(srvc->attribs);
+  tmp = g_list_length(l);
+
+  b = mwPutBuffer_new();
+  guint32_put(b, 0x00);
+  guint32_put(b, tmp);
+  
+  for(; l; l = g_list_delete_link(l, l)) {
+    guint32_put(b, GPOINTER_TO_UINT(l->data));
+  }
+
+  mwPutBuffer_finalize(&o, b);
+  tmp = mwChannel_send(srvc->channel, msg_OPT_WATCH, &o);
+  mwOpaque_clear(&o);
+
+  return tmp;
+}
+
+
+static gboolean collect_attrib_dead(gpointer key, gpointer val,
+				    gpointer data) {
+
+  struct attrib_entry *attrib = val;
+  GList **dead = data;
+
+  if(attrib->membership == NULL) {
+    g_info(" removing 0x%08x", GPOINTER_TO_UINT(key));
+    *dead = g_list_append(*dead, attrib);
+    return TRUE;
+
+  } else {
+    return FALSE;
+  }
+}
+
+
+static int remove_unused_attrib(struct mwServiceAware *srvc) {
+  GList *dead = NULL;
+
+  if(srvc->entries) {
+    g_info("collecting dead attributes");
+    g_hash_table_foreach_steal(srvc->attribs, collect_attrib_dead, &dead);
+  }
+ 
+  /* since we stole them, we'll have to clean 'em up manually */
+  for(; dead; dead = g_list_delete_link(dead, dead)) {
+    attrib_entry_free(dead->data);
+  }
+
+  return send_attrib_list(srvc);
+}
+
+
+static void recv_accept(struct mwServiceAware *srvc,
+			struct mwChannel *chan,
+			struct mwMsgChannelAccept *msg) {
+
+  g_return_if_fail(srvc->channel != NULL);
+  g_return_if_fail(srvc->channel == chan);
+
+  if(MW_SERVICE_IS_STARTING(MW_SERVICE(srvc))) {
+    GList *list = NULL;
+
+    list = map_collect_values(srvc->entries);
+    send_add(chan, list);
+    g_list_free(list);
+
+    send_attrib_list(srvc);
+
+    mwService_started(MW_SERVICE(srvc));
+
+  } else {
+    mwChannel_destroy(chan, ERR_FAILURE, NULL);
+  }
+}
+
+
+static void recv_destroy(struct mwServiceAware *srvc,
+			 struct mwChannel *chan,
+			 struct mwMsgChannelDestroy *msg) {
+
+  srvc->channel = NULL;
+  mwService_stop(MW_SERVICE(srvc));
+
+  /** @todo session sense service and mwService_start */
+}
+
+
+/** called from SNAPSHOT_recv, UPDATE_recv, and
+    mwServiceAware_setStatus */
+static void status_recv(struct mwServiceAware *srvc,
+			struct mwAwareSnapshot *idb) {
+
+  struct aware_entry *aware;
+  GList *l;
+
+  aware = aware_find(srvc, &idb->id);
+
+  if(! aware) {
+    /* we don't deal with receiving status for something we're not
+       monitoring, but it will happen sometimes, eg from manually set
+       status */
+    return;
+  }
+  
+  /* clear the existing status, then clone in the new status */
+  mwAwareSnapshot_clear(&aware->aware);
+  mwAwareSnapshot_clone(&aware->aware, idb);
+  
+  /* trigger each of the entry's lists */
+  for(l = aware->membership; l; l = l->next) {
+    struct mwAwareList *alist = l->data;
+    struct mwAwareListHandler *handler = alist->handler;
+
+    if(handler && handler->on_aware)
+      handler->on_aware(alist, idb);
+  }
+}
+
+
+static void attrib_recv(struct mwServiceAware *srvc,
+			struct mwAwareIdBlock *idb,
+			struct mwAwareAttribute *attrib) {
+
+  struct aware_entry *aware;
+  struct mwAwareAttribute *old_attrib;
+  GList *l;
+  guint32 key;
+  gpointer k;
+
+  aware = aware_find(srvc, idb);
+  g_return_if_fail(aware != NULL);
+
+  key = attrib->key;
+  k = GUINT_TO_POINTER(key);
+
+  old_attrib = g_hash_table_lookup(aware->attribs, k);
+  if(! old_attrib) {
+    old_attrib = g_new0(struct mwAwareAttribute, 1);
+    old_attrib->key = key;
+    g_hash_table_insert(aware->attribs, k, old_attrib);
+  }
+  
+  mwOpaque_clear(&old_attrib->data);
+  mwOpaque_clone(&old_attrib->data, &attrib->data);
+  
+  for(l = aware->membership; l; l = l->next) {
+    struct mwAwareList *list = l->data;
+    struct mwAwareListHandler *h = list->handler;
+
+    if(h && h->on_attrib && g_hash_table_lookup(list->attribs, k))
+      h->on_attrib(list, idb, old_attrib);
+  }
+}
+
+
+gboolean list_add(struct mwAwareList *list, struct mwAwareIdBlock *id) {
+
+  struct mwServiceAware *srvc = list->service;
+  struct aware_entry *aware;
+
+  g_return_val_if_fail(id->user != NULL, FALSE);
+  g_return_val_if_fail(strlen(id->user) > 0, FALSE);
+
+  aware = list_aware_find(list, id);
+  if(aware) return FALSE;
+
+  aware = aware_find(srvc, id);
+  if(! aware) {
+    aware = g_new0(struct aware_entry, 1);
+    aware->attribs = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
+					   (GDestroyNotify) attrib_free);
+    mwAwareIdBlock_clone(ENTRY_KEY(aware), id);
+
+    g_hash_table_insert(srvc->entries, ENTRY_KEY(aware), aware);
+  }
+
+  aware->membership = g_list_append(aware->membership, list);
+  g_hash_table_insert(list->entries, ENTRY_KEY(aware), aware);
+  return TRUE;
+}
+
+
+static void group_member_recv(struct mwServiceAware *srvc,
+			      struct mwAwareSnapshot *idb) {
+  /* @todo
+     - look up group by id
+     - find each list group belongs to
+     - add user to lists
+  */
+
+  struct mwAwareIdBlock gsrch = { mwAware_GROUP, idb->group, NULL };
+  struct aware_entry *grp;
+  GList *l, *m;
+
+  grp = aware_find(srvc, &gsrch);
+  g_return_if_fail(grp != NULL); /* this could happen, with timing. */
+
+  l = g_list_prepend(NULL, &idb->id);
+
+  for(m = grp->membership; m; m = m->next) {
+
+    /* if we just list_add, we won't receive updates for attributes,
+       so annoyingly we have to turn around and send out an add aware
+       message for each incoming group member */
+
+    /* list_add(m->data, &idb->id); */
+    mwAwareList_addAware(m->data, l);
+  }
+
+  g_list_free(l);
+}
+
+
+static void recv_SNAPSHOT(struct mwServiceAware *srvc,
+			  struct mwGetBuffer *b) {
+
+  guint32 count;
+
+  struct mwAwareSnapshot *snap;
+  snap = g_new0(struct mwAwareSnapshot, 1);
+
+  guint32_get(b, &count);
+
+  while(count--) {
+    mwAwareSnapshot_get(b, snap);
+
+    if(mwGetBuffer_error(b)) {
+      mwAwareSnapshot_clear(snap);
+      break;
+    }
+
+    if(snap->group)
+      group_member_recv(srvc, snap);
+
+    status_recv(srvc, snap);
+    mwAwareSnapshot_clear(snap);
+  }
+
+  g_free(snap);
+}
+
+
+static void recv_UPDATE(struct mwServiceAware *srvc,
+			struct mwGetBuffer *b) {
+
+  struct mwAwareSnapshot *snap;
+
+  snap = g_new0(struct mwAwareSnapshot, 1);
+  mwAwareSnapshot_get(b, snap);
+
+  if(snap->group)
+    group_member_recv(srvc, snap);
+
+  if(! mwGetBuffer_error(b))
+    status_recv(srvc, snap);
+
+  mwAwareSnapshot_clear(snap);
+  g_free(snap);
+}
+
+
+static void recv_GROUP(struct mwServiceAware *srvc,
+		       struct mwGetBuffer *b) {
+
+  struct mwAwareIdBlock idb = { 0, 0, 0 };
+
+  /* really nothing to be done with this. The group should have
+     already been added to the list and service, and is now simply
+     awaiting a snapshot/update with users listed as belonging in said
+     group. */
+
+  mwAwareIdBlock_get(b, &idb);
+  mwAwareIdBlock_clear(&idb);
+}
+
+
+static void recv_OPT_GOT_SET(struct mwServiceAware *srvc,
+			     struct mwGetBuffer *b) {
+
+  struct mwAwareAttribute attrib;
+  struct mwAwareIdBlock idb;
+  guint32 junk, check;
+
+  guint32_get(b, &junk);
+  mwAwareIdBlock_get(b, &idb);
+  guint32_get(b, &junk);
+  guint32_get(b, &check);
+  guint32_get(b, &junk);
+  guint32_get(b, &attrib.key);
+
+  if(check) {
+    mwOpaque_get(b, &attrib.data);
+  } else {
+    attrib.data.len = 0;
+    attrib.data.data = NULL;
+  }
+
+  attrib_recv(srvc, &idb, &attrib);
+
+  mwAwareIdBlock_clear(&idb);
+  mwOpaque_clear(&attrib.data);
+}
+
+
+static void recv_OPT_GOT_UNSET(struct mwServiceAware *srvc,
+			       struct mwGetBuffer *b) {
+
+  struct mwAwareAttribute attrib;
+  struct mwAwareIdBlock idb;
+  guint32 junk;
+
+  attrib.key = 0;
+  attrib.data.len = 0;
+  attrib.data.data = NULL;
+
+  guint32_get(b, &junk);
+  mwAwareIdBlock_get(b, &idb);
+  guint32_get(b, &attrib.key);
+
+  attrib_recv(srvc, &idb, &attrib);
+
+  mwAwareIdBlock_clear(&idb);
+}
+
+
+static void recv(struct mwService *srvc, struct mwChannel *chan,
+		 guint16 type, struct mwOpaque *data) {
+
+  struct mwServiceAware *srvc_aware = (struct mwServiceAware *) srvc;
+  struct mwGetBuffer *b;
+
+  g_return_if_fail(srvc_aware->channel == chan);
+  g_return_if_fail(srvc->session == mwChannel_getSession(chan));
+  g_return_if_fail(data != NULL);
+
+  b = mwGetBuffer_wrap(data);
+
+  switch(type) {
+  case msg_AWARE_SNAPSHOT:
+    recv_SNAPSHOT(srvc_aware, b);
+    break;
+
+  case msg_AWARE_UPDATE:
+    recv_UPDATE(srvc_aware, b);
+    break;
+
+  case msg_AWARE_GROUP:
+    recv_GROUP(srvc_aware, b);
+    break;
+
+  case msg_OPT_GOT_SET:
+    recv_OPT_GOT_SET(srvc_aware, b);
+    break;
+
+  case msg_OPT_GOT_UNSET:
+    recv_OPT_GOT_UNSET(srvc_aware, b);
+    break;
+
+  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);   
+  }
+
+  mwGetBuffer_free(b);  
+}
+
+
+static void clear(struct mwService *srvc) {
+  struct mwServiceAware *srvc_aware = (struct mwServiceAware *) srvc;
+
+  g_return_if_fail(srvc != NULL);
+
+  while(srvc_aware->lists)
+    mwAwareList_free( (struct mwAwareList *) srvc_aware->lists->data );
+
+  g_hash_table_destroy(srvc_aware->entries);
+  srvc_aware->entries = NULL;
+}
+
+
+static const char *name(struct mwService *srvc) {
+  return "Presence Awareness";
+}
+
+
+static const char *desc(struct mwService *srvc) {
+  return "Buddy list service with support for server-side groups";
+}
+
+
+static struct mwChannel *make_blist(struct mwServiceAware *srvc,
+				    struct mwChannelSet *cs) {
+
+  struct mwChannel *chan = mwChannel_newOutgoing(cs);
+
+  mwChannel_setService(chan, MW_SERVICE(srvc));
+  mwChannel_setProtoType(chan, 0x00000011);
+  mwChannel_setProtoVer(chan, 0x00030005);
+
+  return mwChannel_create(chan)? NULL: chan;
+}
+
+
+static void start(struct mwService *srvc) {
+  struct mwServiceAware *srvc_aware;
+  struct mwChannel *chan;
+
+  srvc_aware = (struct mwServiceAware *) srvc;
+  chan = make_blist(srvc_aware, mwSession_getChannels(srvc->session));
+
+  if(chan != NULL) {
+    srvc_aware->channel = chan;
+  } else {
+    mwService_stopped(srvc);
+  }
+}
+
+
+static void stop(struct mwService *srvc) {
+  struct mwServiceAware *srvc_aware;
+
+  srvc_aware = (struct mwServiceAware *) srvc;
+  mwChannel_destroy(srvc_aware->channel, ERR_SUCCESS, NULL);
+  mwService_stopped(srvc);
+}
+
+
+struct mwServiceAware *
+mwServiceAware_new(struct mwSession *session,
+		   struct mwAwareHandler *handler) {
+
+  struct mwService *service;
+  struct mwServiceAware *srvc;
+
+  g_return_val_if_fail(session != NULL, NULL);
+  g_return_val_if_fail(handler != NULL, NULL);
+
+  srvc = g_new0(struct mwServiceAware, 1);
+  srvc->handler = handler;
+  srvc->entries = g_hash_table_new_full((GHashFunc) mwAwareIdBlock_hash,
+					(GEqualFunc) mwAwareIdBlock_equal,
+					NULL,
+					(GDestroyNotify) aware_entry_free);
+
+  srvc->attribs = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
+					(GDestroyNotify) attrib_entry_free);
+
+  service = MW_SERVICE(srvc);
+  mwService_init(service, session, SERVICE_AWARE);
+
+  service->recv_accept = (mwService_funcRecvAccept) recv_accept;
+  service->recv_destroy = (mwService_funcRecvDestroy) recv_destroy;
+  service->recv = recv;
+  service->start = start;
+  service->stop = stop;
+  service->clear = clear;
+  service->get_name = name;
+  service->get_desc = desc;
+
+  return srvc;
+}
+
+
+int mwServiceAware_setAttribute(struct mwServiceAware *srvc,
+				guint32 key, struct mwOpaque *data) {
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+  int ret;
+
+  b = mwPutBuffer_new();
+
+  guint32_put(b, 0x00);
+  guint32_put(b, data->len);
+  guint32_put(b, 0x00);
+  guint32_put(b, key);
+  mwOpaque_put(b, data);
+
+  mwPutBuffer_finalize(&o, b);
+  ret = mwChannel_send(srvc->channel, msg_OPT_DO_SET, &o);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+int mwServiceAware_setAttributeBoolean(struct mwServiceAware *srvc,
+				       guint32 key, gboolean val) {
+  int ret;
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+ 
+  b = mwPutBuffer_new();
+
+  gboolean_put(b, FALSE);
+  gboolean_put(b, val);
+
+  mwPutBuffer_finalize(&o, b);
+
+  ret = mwServiceAware_setAttribute(srvc, key, &o);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+int mwServiceAware_setAttributeInteger(struct mwServiceAware *srvc,
+				       guint32 key, guint32 val) {
+  int ret;
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+  
+  b = mwPutBuffer_new();
+  guint32_put(b, val);
+
+  mwPutBuffer_finalize(&o, b);
+
+  ret = mwServiceAware_setAttribute(srvc, key, &o);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+int mwServiceAware_setAttributeString(struct mwServiceAware *srvc,
+				      guint32 key, const char *str) {
+  int ret;
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+
+  b = mwPutBuffer_new();
+  mwString_put(b, str);
+
+  mwPutBuffer_finalize(&o, b);
+
+  ret = mwServiceAware_setAttribute(srvc, key, &o);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+int mwServiceAware_unsetAttribute(struct mwServiceAware *srvc,
+				  guint32 key) {
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+  int ret;
+
+  b = mwPutBuffer_new();
+
+  guint32_put(b, 0x00);
+  guint32_put(b, key);
+  
+  mwPutBuffer_finalize(&o, b);
+  ret = mwChannel_send(srvc->channel, msg_OPT_DO_UNSET, &o);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+guint32 mwAwareAttribute_getKey(const struct mwAwareAttribute *attrib) {
+  g_return_val_if_fail(attrib != NULL, 0x00);
+  return attrib->key;
+}
+
+
+gboolean mwAwareAttribute_asBoolean(const struct mwAwareAttribute *attrib) {
+  struct mwGetBuffer *b;
+  gboolean ret;
+  
+  if(! attrib) return FALSE;
+
+  b = mwGetBuffer_wrap(&attrib->data);
+  if(attrib->data.len >= 4) {
+    guint32 r32 = 0x00;
+    guint32_get(b, &r32);
+    ret = !! r32;
+
+  } else if(attrib->data.len >= 2) {
+    guint16 r16 = 0x00;
+    guint16_get(b, &r16);
+    ret = !! r16;
+
+  } else if(attrib->data.len) {
+    gboolean_get(b, &ret);
+  }
+
+  mwGetBuffer_free(b);
+
+  return ret;
+}
+
+
+guint32 mwAwareAttribute_asInteger(const struct mwAwareAttribute *attrib) {
+  struct mwGetBuffer *b;
+  guint32 r32 = 0x00;
+  
+  if(! attrib) return 0x00;
+
+  b = mwGetBuffer_wrap(&attrib->data);
+  if(attrib->data.len >= 4) {
+    guint32_get(b, &r32);
+
+  } else if(attrib->data.len == 3) {
+    gboolean rb = FALSE;
+    guint16 r16 = 0x00;
+    gboolean_get(b, &rb);
+    guint16_get(b, &r16);
+    r32 = (guint32) r16;
+
+  } else if(attrib->data.len == 2) {
+    guint16 r16 = 0x00;
+    guint16_get(b, &r16);
+    r32 = (guint32) r16;
+
+  } else if(attrib->data.len) {
+    gboolean rb = FALSE;
+    gboolean_get(b, &rb);
+    r32 = (guint32) rb;
+  }
+
+  mwGetBuffer_free(b);
+
+  return r32;
+}
+
+
+char *mwAwareAttribute_asString(const struct mwAwareAttribute *attrib) {
+  struct mwGetBuffer *b;
+  char *ret = NULL;
+
+  if(! attrib) return NULL;
+
+  b = mwGetBuffer_wrap(&attrib->data);
+  mwString_get(b, &ret);
+  mwGetBuffer_free(b);
+
+  return ret;
+}
+
+
+const struct mwOpaque *
+mwAwareAttribute_asOpaque(const struct mwAwareAttribute *attrib) {
+  g_return_val_if_fail(attrib != NULL, NULL);
+  return &attrib->data;
+}
+			  
+
+struct mwAwareList *
+mwAwareList_new(struct mwServiceAware *srvc,
+		struct mwAwareListHandler *handler) {
+
+  struct mwAwareList *al;
+
+  g_return_val_if_fail(srvc != NULL, NULL);
+  g_return_val_if_fail(handler != NULL, NULL);
+
+  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;
+
+  return al;
+}
+
+
+void mwAwareList_free(struct mwAwareList *list) {
+  struct mwServiceAware *srvc;
+  struct mwAwareListHandler *handler;
+
+  g_return_if_fail(list != NULL);
+  g_return_if_fail(list->entries != NULL);
+  g_return_if_fail(list->service != NULL);
+
+  handler = list->handler;
+  if(handler && handler->clear) {
+    handler->clear(list);
+    list->handler = NULL;
+  }
+
+  mw_datum_clear(&list->client_data);
+
+  srvc = list->service;
+  srvc->lists = g_list_remove(srvc->lists, list);
+
+  mwAwareList_unwatchAllAttributes(list);
+  mwAwareList_removeAllAware(list);
+}
+
+
+struct mwAwareListHandler *mwAwareList_getHandler(struct mwAwareList *list) {
+  g_return_val_if_fail(list != NULL, NULL);
+  return list->handler;
+}
+
+
+static void watch_add(struct mwAwareList *list, guint32 key) {
+  struct mwServiceAware *srvc;
+  struct attrib_entry *watch;
+  gpointer k = GUINT_TO_POINTER(key);
+
+  if(g_hash_table_lookup(list->attribs, k))
+    return;
+
+  srvc = list->service;
+
+  watch = g_hash_table_lookup(srvc->attribs, k);
+  if(! watch) {
+    watch = g_new0(struct attrib_entry, 1);
+    watch->key = key;
+    g_hash_table_insert(srvc->attribs, k, watch);
+  }
+
+  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;
+  gpointer k = GUINT_TO_POINTER(key);
+
+  watch = g_hash_table_lookup(list->attribs, k);
+  g_return_if_fail(watch != NULL);
+
+  g_hash_table_remove(list->attribs, k);
+  watch->membership = g_list_remove(watch->membership, list);
+}
+
+
+int mwAwareList_watchAttributeArray(struct mwAwareList *list,
+				    guint32 *keys) {
+  guint32 k;
+
+  g_return_val_if_fail(list != NULL, -1);
+  g_return_val_if_fail(list->service != NULL, -1);
+
+  if(! keys) return 0;
+
+  for(k = *keys; k; keys++)
+    watch_add(list, k);
+
+  return send_attrib_list(list->service);
+}
+
+
+int mwAwareList_watchAttributes(struct mwAwareList *list,
+				guint32 key, ...) {
+  guint32 k;
+  va_list args;
+
+  g_return_val_if_fail(list != NULL, -1);
+  g_return_val_if_fail(list->service != NULL, -1);
+
+  va_start(args, key);
+  for(k = key; k; k = va_arg(args, guint32))
+    watch_add(list, k);
+  va_end(args);
+
+  return send_attrib_list(list->service);
+}
+
+
+int mwAwareList_unwatchAttributeArray(struct mwAwareList *list,
+				      guint32 *keys) {
+  guint32 k;
+
+  g_return_val_if_fail(list != NULL, -1);
+  g_return_val_if_fail(list->service != NULL, -1);
+
+  if(! keys) return 0;
+
+  for(k = *keys; k; keys++)
+    watch_add(list, k);
+
+  remove_unused_attrib(list->service);
+  return send_attrib_list(list->service);
+}
+
+
+int mwAwareList_unwatchAttributes(struct mwAwareList *list,
+				  guint32 key, ...) {
+  guint32 k;
+  va_list args;
+
+  g_return_val_if_fail(list != NULL, -1);
+  g_return_val_if_fail(list->service != NULL, -1);
+
+  va_start(args, key);
+  for(k = key; k; k = va_arg(args, guint32))
+    watch_remove(list, k);
+  va_end(args);
+
+  remove_unused_attrib(list->service);
+  return send_attrib_list(list->service);
+}
+
+
+static void dismember_attrib(gpointer k, struct attrib_entry *watch,
+			    struct mwAwareList *list) {
+
+  watch->membership = g_list_remove(watch->membership, list);
+}
+
+
+int mwAwareList_unwatchAllAttributes(struct mwAwareList *list) {
+  
+  struct mwServiceAware *srvc;
+
+  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);
+
+  remove_unused_attrib(srvc);
+  return send_attrib_list(list->service);
+}
+
+
+static void collect_attrib_keys(gpointer key, struct attrib_entry *attrib,
+				guint32 **ck) {
+  guint32 *keys = (*ck)++;
+  *keys = GPOINTER_TO_UINT(key);
+}
+
+
+guint32 *mwAwareList_getWatchedAttributes(struct mwAwareList *list) {
+  guint32 *keys, **ck;
+  guint count;
+
+  g_return_val_if_fail(list != NULL, NULL);
+  g_return_val_if_fail(list->attribs != NULL, NULL);
+  
+  count = g_hash_table_size(list->attribs);
+  keys = g_new0(guint32, count + 1);
+
+  ck = &keys;
+  g_hash_table_foreach(list->attribs, (GHFunc) collect_attrib_keys, ck);
+
+  return keys;
+}
+
+
+int mwAwareList_addAware(struct mwAwareList *list, GList *id_list) {
+
+  /* for each awareness id:
+     - if it's already in the list, continue
+     - if it's not in the service list:
+       - create an awareness
+       - add it to the service list
+     - add this list to the membership
+     - add to the list
+  */
+
+  struct mwServiceAware *srvc;
+  GList *additions = NULL;
+  int ret = 0;
+
+  g_return_val_if_fail(list != NULL, -1);
+
+  srvc = list->service;
+  g_return_val_if_fail(srvc != NULL, -1);
+
+  for(; id_list; id_list = id_list->next) {
+    if(list_add(list, id_list->data))
+      additions = g_list_prepend(additions, id_list->data);
+  }
+
+  /* if the service is alive-- or getting there-- we'll need to send
+     these additions upstream */
+  if(MW_SERVICE_IS_LIVE(srvc) && additions)
+    ret = send_add(srvc->channel, additions);
+
+  g_list_free(additions);
+  return ret;
+}
+
+
+int mwAwareList_removeAware(struct mwAwareList *list, GList *id_list) {
+
+  /* for each awareness id:
+     - if it's not in the list, forget it
+     - remove from the list
+     - remove list from the membership
+
+     - call remove round
+  */
+
+  struct mwServiceAware *srvc;
+  struct mwAwareIdBlock *id;
+  struct aware_entry *aware;
+
+  g_return_val_if_fail(list != NULL, -1);
+
+  srvc = list->service;
+  g_return_val_if_fail(srvc != NULL, -1);
+
+  for(; id_list; id_list = id_list->next) {
+    id = id_list->data;
+    aware = list_aware_find(list, id);
+
+    if(! aware) {
+      g_warning("buddy %s, %s not in list",
+		NSTR(id->user),
+		NSTR(id->community));
+      continue;
+    }
+
+    aware->membership = g_list_remove(aware->membership, list);
+    g_hash_table_remove(list->entries, id);
+  }
+
+  return remove_unused(srvc);
+}
+
+
+static void dismember_aware(gpointer k, struct aware_entry *aware,
+			    struct mwAwareList *list) {
+
+  aware->membership = g_list_remove(aware->membership, list);
+}
+
+
+int mwAwareList_removeAllAware(struct mwAwareList *list) {
+  struct mwServiceAware *srvc;
+
+  g_return_val_if_fail(list != NULL, -1);
+  srvc = list->service;
+
+  g_return_val_if_fail(srvc != NULL, -1);
+
+  /* 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);
+
+  return remove_unused(srvc);
+}
+
+
+void mwAwareList_setClientData(struct mwAwareList *list,
+			       gpointer data, GDestroyNotify clear) {
+
+  g_return_if_fail(list != NULL);
+  mw_datum_set(&list->client_data, data, clear);
+}
+
+
+gpointer mwAwareList_getClientData(struct mwAwareList *list) {
+  g_return_val_if_fail(list != NULL, NULL);
+  return mw_datum_get(&list->client_data);
+}
+
+
+void mwAwareList_removeClientData(struct mwAwareList *list) {
+  g_return_if_fail(list != NULL);
+  mw_datum_clear(&list->client_data);
+}
+
+
+void mwServiceAware_setStatus(struct mwServiceAware *srvc,
+			      struct mwAwareIdBlock *user,
+			      struct mwUserStatus *stat) {
+
+  struct mwAwareSnapshot idb;
+
+  g_return_if_fail(srvc != NULL);
+  g_return_if_fail(user != NULL);
+  g_return_if_fail(stat != NULL);
+
+  /* just reference the strings. then we don't need to free them */
+  idb.id.type = user->type;
+  idb.id.user = user->user;
+  idb.id.community = user->community;
+
+  idb.group = NULL;
+  idb.online = TRUE;
+  idb.alt_id = NULL;
+
+  idb.status.status = stat->status;
+  idb.status.time = stat->time;
+  idb.status.desc = stat->desc;
+
+  idb.name = NULL;
+
+  status_recv(srvc, &idb);
+}
+
+
+const struct mwAwareAttribute *
+mwServiceAware_getAttribute(struct mwServiceAware *srvc,
+			    struct mwAwareIdBlock *user,
+			    guint32 key) {
+
+  struct aware_entry *aware;
+
+  g_return_val_if_fail(srvc != NULL, NULL);
+  g_return_val_if_fail(user != NULL, NULL);
+  g_return_val_if_fail(key != 0x00, NULL);
+
+  aware = aware_find(srvc, user);
+  g_return_val_if_fail(aware != NULL, NULL);
+
+  return g_hash_table_lookup(aware->attribs, GUINT_TO_POINTER(key));
+}
+
+
+const char *mwServiceAware_getText(struct mwServiceAware *srvc,
+				   struct mwAwareIdBlock *user) {
+
+  struct aware_entry *aware;
+
+  g_return_val_if_fail(srvc != NULL, NULL);
+  g_return_val_if_fail(user != NULL, NULL);
+
+  aware = aware_find(srvc, user);
+  g_return_val_if_fail(aware != NULL, NULL);
+
+  return aware->aware.status.desc;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/srvc_conf.c	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,865 @@
+
+/*
+  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 <string.h>
+#include <time.h>
+
+#include "mw_channel.h"
+#include "mw_debug.h"
+#include "mw_error.h"
+#include "mw_message.h"
+#include "mw_service.h"
+#include "mw_session.h"
+#include "mw_srvc_conf.h"
+#include "mw_util.h"
+
+
+/* This thing needs a re-write. More than anything else, I need to
+   re-examine the conferencing service protocol from more modern
+   clients */
+
+
+#define PROTOCOL_TYPE   0x00000010
+#define PROTOCOL_VER    0x00000002
+
+
+/** @see mwMsgChannelSend::type
+    @see recv */
+enum msg_type {
+  msg_WELCOME  = 0x0000,  /**< welcome message */
+  msg_INVITE   = 0x0001,  /**< outgoing invitation */
+  msg_JOIN     = 0x0002,  /**< someone joined */
+  msg_PART     = 0x0003,  /**< someone left */
+  msg_MESSAGE  = 0x0004,  /**< conference message */
+};
+
+
+/** the conferencing service */
+struct mwServiceConference {
+  struct mwService service;
+
+  /** call-back handler for this service */
+  struct mwConferenceHandler *handler;
+
+  /** collection of conferences in this service */
+  GList *confs;
+};
+
+
+/** a conference and its members */
+struct mwConference {
+  enum mwConferenceState state;   /**< state of the conference */
+  struct mwServiceConference *service;  /**< owning service */
+  struct mwChannel *channel;      /**< conference's channel */
+
+  char *name;   /**< server identifier for the conference */
+  char *title;  /**< topic for the conference */
+
+  struct mwLoginInfo owner;  /**< person who created this conference */
+  GHashTable *members;       /**< mapping guint16:mwLoginInfo */
+  struct mw_datum client_data;
+};
+
+
+#define MEMBER_FIND(conf, id) \
+  g_hash_table_lookup(conf->members, GUINT_TO_POINTER((guint) id))
+
+
+#define MEMBER_ADD(conf, id, member) \
+  g_hash_table_insert(conf->members, GUINT_TO_POINTER((guint) id), member)
+
+
+#define MEMBER_REM(conf, id) \
+  g_hash_table_remove(conf->members, GUINT_TO_POINTER((guint) id));
+
+
+/** clear and free a login info block */
+static void login_free(struct mwLoginInfo *li) {
+  mwLoginInfo_clear(li);
+  g_free(li);
+}
+
+
+/** 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());
+  a = ((rand() & 0xff) << 8) | (rand() & 0xff);
+  b = time(NULL);
+  sprintf(c, "(%08x,%04x)", b, a);
+
+  ret = g_strconcat(user, c, NULL);
+
+  g_debug("generated random conference name: '%s'", ret);
+  return ret;
+}
+
+
+static struct mwConference *conf_new(struct mwServiceConference *srvc) {
+
+  struct mwConference *conf;
+  struct mwSession *session;
+  const char *user;
+
+  conf = g_new0(struct mwConference, 1);
+  conf->state = mwConference_NEW;
+  conf->service = srvc;
+  conf->members = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
+					(GDestroyNotify) login_free);
+
+  session = mwService_getSession(MW_SERVICE(srvc));
+  user = mwSession_getProperty(session, mwSession_AUTH_USER_ID);
+
+  srvc->confs = g_list_prepend(srvc->confs, conf);
+
+  return conf;
+}
+
+
+/** clean and free a conference structure */
+static void conf_free(struct mwConference *conf) {
+  struct mwServiceConference *srvc;
+
+  /* this shouldn't ever happen, but just to be sure */
+  g_return_if_fail(conf != NULL);
+  
+  srvc = conf->service;
+
+  if(conf->members)
+    g_hash_table_destroy(conf->members);
+
+  g_list_remove_all(srvc->confs, conf);
+
+  mw_datum_clear(&conf->client_data);
+  
+  g_free(conf->name);
+  g_free(conf->title);
+  g_free(conf);
+}
+
+
+static struct mwConference *conf_find(struct mwServiceConference *srvc,
+				      struct mwChannel *chan) {
+  GList *l;
+
+  g_return_val_if_fail(srvc != NULL, NULL);
+  g_return_val_if_fail(chan != NULL, NULL);
+  
+  for(l = srvc->confs; l; l = l->next) {
+    struct mwConference *conf = l->data;
+    if(conf->channel == chan) return conf;
+  }
+
+  return NULL;
+}
+
+
+static const char *conf_state_str(enum mwConferenceState state) {
+  switch(state) {
+  case mwConference_NEW:      return "new";
+  case mwConference_PENDING:  return "pending";
+  case mwConference_INVITED:  return "invited";
+  case mwConference_OPEN:     return "open";
+  case mwConference_CLOSING:  return "closing";
+  case mwConference_ERROR:    return "error";
+
+  case mwConference_UNKNOWN:  /* fall through */
+  default:                    return "UNKNOWN";
+  }
+}
+
+
+static void conf_state(struct mwConference *conf,
+		       enum mwConferenceState state) {
+  g_return_if_fail(conf != NULL);
+
+  if(conf->state == state) return;
+
+  conf->state = state;
+  g_message("conference %s state: %s",
+	    NSTR(conf->name), conf_state_str(state));
+}
+
+
+static void recv_channelCreate(struct mwService *srvc,
+			       struct mwChannel *chan,
+			       struct mwMsgChannelCreate *msg) {
+
+  /* - this is how we really receive invitations
+     - create a conference and associate it with the channel
+     - obtain the invite data from the msg addtl info
+     - mark the conference as INVITED
+     - trigger the got_invite event
+  */
+
+  struct mwServiceConference *srvc_conf = (struct mwServiceConference *) srvc;
+  struct mwConference *conf;
+
+  struct mwGetBuffer *b;
+
+  char *invite = NULL;
+  guint tmp;
+
+  conf = conf_new(srvc_conf);
+  conf->channel = chan;
+
+  b = mwGetBuffer_wrap(&msg->addtl);
+
+  guint32_get(b, &tmp);
+  mwString_get(b, &conf->name);
+  mwString_get(b, &conf->title);
+  guint32_get(b, &tmp);
+  mwLoginInfo_get(b, &conf->owner);
+  guint32_get(b, &tmp);
+  mwString_get(b, &invite);
+
+  if(mwGetBuffer_error(b)) {
+    g_warning("failure parsing addtl for conference invite");
+    mwConference_destroy(conf, ERR_FAILURE, NULL);
+
+  } else {
+    struct mwConferenceHandler *h = srvc_conf->handler;
+    conf_state(conf, mwConference_INVITED);
+    if(h->on_invited)
+      h->on_invited(conf, &conf->owner, invite);
+  }
+
+  mwGetBuffer_free(b);
+  g_free(invite);
+}
+
+
+static void recv_channelAccept(struct mwService *srvc,
+			       struct mwChannel *chan,
+			       struct mwMsgChannelAccept *msg) {
+
+  ;
+}
+
+
+static void recv_channelDestroy(struct mwService *srvc,
+				struct mwChannel *chan,
+				struct mwMsgChannelDestroy *msg) {
+
+  /* - find conference from channel
+     - trigger got_closed
+     - remove conference, dealloc
+  */
+
+  struct mwServiceConference *srvc_conf = (struct mwServiceConference *) srvc;
+  struct mwConference *conf = conf_find(srvc_conf, chan);
+  struct mwConferenceHandler *h = srvc_conf->handler;
+
+  /* if there's no such conference, then I guess there's nothing to worry
+     about. Except of course for the fact that we should never receive a
+     channel destroy for a conference that doesn't exist. */
+  if(! conf) return;
+
+  conf->channel = NULL;
+
+  conf_state(conf, msg->reason? mwConference_ERROR: mwConference_CLOSING);
+
+  if(h->conf_closed)
+    h->conf_closed(conf, msg->reason);
+
+  mwConference_destroy(conf, ERR_SUCCESS, NULL);
+}
+
+
+static void WELCOME_recv(struct mwServiceConference *srvc,
+			 struct mwConference *conf,
+			 struct mwGetBuffer *b) {
+
+  struct mwConferenceHandler *h;
+  guint16 tmp16;
+  guint32 tmp32;
+  guint32 count;
+  GList *l = NULL;
+
+  /* re-read name and title */
+  g_free(conf->name);
+  g_free(conf->title);
+  conf->name = NULL;
+  conf->title = NULL;
+  mwString_get(b, &conf->name);
+  mwString_get(b, &conf->title);
+
+  /* some numbers we don't care about, then a count of members */
+  guint16_get(b, &tmp16);
+  guint32_get(b, &tmp32);
+  guint32_get(b, &count);
+
+  if(mwGetBuffer_error(b)) {
+    g_warning("error parsing welcome message for conference");
+    mwConference_destroy(conf, ERR_FAILURE, NULL);
+    return;
+  }
+  
+  while(count--) {
+    guint16 member_id;
+    struct mwLoginInfo *member = g_new0(struct mwLoginInfo, 1);
+
+    guint16_get(b, &member_id);
+    mwLoginInfo_get(b, member);
+
+    if(mwGetBuffer_error(b)) {
+      login_free(member);
+      break;
+    }
+
+    MEMBER_ADD(conf, member_id, member);
+    l = g_list_append(l, member);
+  }
+
+  conf_state(conf, mwConference_OPEN);
+
+  h = srvc->handler;
+  if(h->conf_opened)
+    h->conf_opened(conf, l);
+
+  /* get rid of the GList, but not its contents */
+  g_list_free(l);
+}
+
+
+static void JOIN_recv(struct mwServiceConference *srvc,
+		      struct mwConference *conf,
+		      struct mwGetBuffer *b) {
+
+  struct mwConferenceHandler *h;
+  guint16 m_id;
+  struct mwLoginInfo *m;
+  
+  /* for some inane reason, conferences we create will send a join
+     message for ourselves before the welcome message. Since the
+     welcome message will list our ID among those in the channel,
+     we're going to just pretend that these join messages don't
+     exist */
+  if(conf->state == mwConference_PENDING)
+    return;
+
+  m = g_new0(struct mwLoginInfo, 1);
+
+  guint16_get(b, &m_id);
+  mwLoginInfo_get(b, m);
+
+  if(mwGetBuffer_error(b)) {
+    g_warning("failed parsing JOIN message in conference");
+    login_free(m);
+    return;
+  }
+
+  MEMBER_ADD(conf, m_id, m);
+
+  h = srvc->handler;
+  if(h->on_peer_joined)
+    h->on_peer_joined(conf, m);
+}
+
+
+static void PART_recv(struct mwServiceConference *srvc,
+		      struct mwConference *conf,
+		      struct mwGetBuffer *b) {
+
+  /* - parse who left
+     - look up their membership
+     - remove them from the members list
+     - trigger the event
+  */
+
+  struct mwConferenceHandler *h;
+  guint16 id = 0;
+  struct mwLoginInfo *m;
+
+  guint16_get(b, &id);
+
+  if(mwGetBuffer_error(b)) return;
+
+  m = MEMBER_FIND(conf, id);
+  if(! m) return;
+
+  h = srvc->handler;
+  if(h->on_peer_parted)
+    h->on_peer_parted(conf, m);
+
+  MEMBER_REM(conf, id);
+}
+
+
+static void text_recv(struct mwServiceConference *srvc,
+		      struct mwConference *conf,
+		      struct mwLoginInfo *m,
+		      struct mwGetBuffer *b) {
+
+  /* this function acts a lot like receiving an IM Text message. The text
+     message contains only a string */
+
+  char *text = NULL;
+  struct mwConferenceHandler *h;
+  
+  mwString_get(b, &text);
+
+  if(mwGetBuffer_error(b)) {
+    g_warning("failed to parse text message in conference");
+    g_free(text);
+    return;
+  }
+
+  h = srvc->handler;
+  if(text && h->on_text) {
+    h->on_text(conf, m, text);
+  }
+
+  g_free(text);
+}
+
+
+static void data_recv(struct mwServiceConference *srvc,
+		      struct mwConference *conf,
+		      struct mwLoginInfo *m,
+		      struct mwGetBuffer *b) {
+
+  /* this function acts a lot like receiving an IM Data message. The
+     data message has a type, a subtype, and an opaque. We only
+     support typing notification though. */
+
+  /** @todo it's possible that some clients send text in a data
+      message, as we've seen rarely in the IM service. Have to add
+      support for that here */
+
+  guint32 type, subtype;
+  struct mwConferenceHandler *h;
+
+  guint32_get(b, &type);
+  guint32_get(b, &subtype);
+
+  if(mwGetBuffer_error(b)) return;
+
+  /* don't know how to deal with any others yet */
+  if(type != 0x01) {
+    g_message("unknown data message type (0x%08x, 0x%08x)", type, subtype);
+    return;
+  }
+
+  h = srvc->handler;
+  if(h->on_typing) {
+    h->on_typing(conf, m, !subtype);
+  }
+}
+
+
+static void MESSAGE_recv(struct mwServiceConference *srvc,
+			 struct mwConference *conf,
+			 struct mwGetBuffer *b) {
+
+  /* - look up who send the message by their id
+     - trigger the event
+  */
+
+  guint16 id;
+  guint32 type;
+  struct mwLoginInfo *m;
+
+  /* an empty buffer isn't an error, just ignored */
+  if(! mwGetBuffer_remaining(b)) return;
+
+  guint16_get(b, &id);
+  guint32_get(b, &type); /* reuse type variable */
+  guint32_get(b, &type);
+
+  if(mwGetBuffer_error(b)) return;
+
+  m = MEMBER_FIND(conf, id);
+  if(! m) {
+    g_warning("received message type 0x%04x from"
+	      " unknown conference member %u", type, id);
+    return;
+  }
+  
+  switch(type) {
+  case 0x01:  /* type is text */
+    text_recv(srvc, conf, m, b);
+    break;
+
+  case 0x02:  /* type is data */
+    data_recv(srvc, conf, m, b);
+    break;
+
+  default:
+    g_warning("unknown message type 0x%4x received in conference", type);
+  }
+}
+
+
+static void recv(struct mwService *srvc, struct mwChannel *chan,
+		 guint16 type, struct mwOpaque *data) {
+
+  struct mwServiceConference *srvc_conf = (struct mwServiceConference *) srvc;
+  struct mwConference *conf = conf_find(srvc_conf, chan);
+  struct mwGetBuffer *b;
+
+  g_return_if_fail(conf != NULL);
+
+  b = mwGetBuffer_wrap(data);
+
+  switch(type) {
+  case msg_WELCOME:
+    WELCOME_recv(srvc_conf, conf, b);
+    break;
+
+  case msg_JOIN:
+    JOIN_recv(srvc_conf, conf, b);
+    break;
+
+  case msg_PART:
+    PART_recv(srvc_conf, conf, b);
+    break;
+
+  case msg_MESSAGE:
+    MESSAGE_recv(srvc_conf, conf, b);
+    break;
+
+  default:
+    ; /* hrm. should log this. TODO */
+  }
+}
+
+
+static void clear(struct mwServiceConference *srvc) {
+  struct mwConferenceHandler *h;
+
+  while(srvc->confs)
+    conf_free(srvc->confs->data);
+
+  h = srvc->handler;
+  if(h && h->clear)
+    h->clear(srvc);
+  srvc->handler = NULL;
+}
+
+
+static const char *name(struct mwService *srvc) {
+  return "Basic Conferencing";
+}
+
+
+static const char *desc(struct mwService *srvc) {
+  return "Multi-user plain-text conferencing";
+}
+
+
+static void start(struct mwService *srvc) {
+  mwService_started(srvc);
+}
+
+
+static void stop(struct mwServiceConference *srvc) {
+  while(srvc->confs)
+    mwConference_destroy(srvc->confs->data, ERR_SUCCESS, NULL);
+
+  mwService_stopped(MW_SERVICE(srvc));
+}
+
+
+struct mwServiceConference *
+mwServiceConference_new(struct mwSession *session,
+			struct mwConferenceHandler *handler) {
+
+  struct mwServiceConference *srvc_conf;
+  struct mwService *srvc;
+
+  g_return_val_if_fail(session != NULL, NULL);
+  g_return_val_if_fail(handler != NULL, NULL);
+
+  srvc_conf = g_new0(struct mwServiceConference, 1);
+  srvc = &srvc_conf->service;
+
+  mwService_init(srvc, session, SERVICE_CONFERENCE);
+  srvc->start = start;
+  srvc->stop = (mwService_funcStop) stop;
+  srvc->recv_create = recv_channelCreate;
+  srvc->recv_accept = recv_channelAccept;
+  srvc->recv_destroy = recv_channelDestroy;
+  srvc->recv = recv;
+  srvc->clear = (mwService_funcClear) clear;
+  srvc->get_name = name;
+  srvc->get_desc = desc;
+
+  srvc_conf->handler = handler;
+
+  return srvc_conf;
+}
+
+
+struct mwConference *mwConference_new(struct mwServiceConference *srvc,
+				      const char *title) {
+  struct mwConference *conf;
+
+  g_return_val_if_fail(srvc != NULL, NULL);
+
+  conf = conf_new(srvc);
+  conf->title = g_strdup(title);
+
+  return conf;
+}
+
+
+struct mwServiceConference *
+mwConference_getService(struct mwConference *conf) {
+  g_return_val_if_fail(conf != NULL, NULL);
+  return conf->service;
+}
+
+
+const char *mwConference_getName(struct mwConference *conf) {
+  g_return_val_if_fail(conf != NULL, NULL);
+  return conf->name;
+}
+
+
+const char *mwConference_getTitle(struct mwConference *conf) {
+  g_return_val_if_fail(conf != NULL, NULL);
+  return conf->title;
+}
+
+
+GList *mwConference_memebers(struct mwConference *conf) {
+  g_return_val_if_fail(conf != NULL, NULL);
+  g_return_val_if_fail(conf->members != NULL, NULL);
+
+  return map_collect_values(conf->members);
+}
+
+
+int mwConference_open(struct mwConference *conf) {
+  struct mwSession *session;
+  struct mwChannel *chan;
+  struct mwPutBuffer *b;
+  int ret;
+  
+  g_return_val_if_fail(conf != NULL, -1);
+  g_return_val_if_fail(conf->service != NULL, -1);
+  g_return_val_if_fail(conf->state == mwConference_NEW, -1);
+  g_return_val_if_fail(conf->channel == NULL, -1);
+
+  session = mwService_getSession(MW_SERVICE(conf->service));
+  g_assert(session != NULL);
+
+  if(! conf->name) {
+    char *user = mwSession_getProperty(session, mwSession_AUTH_USER_ID);
+    conf->name = conf_generate_name(user? user: "meanwhile");
+  }
+
+  chan = mwChannel_newOutgoing(mwSession_getChannels(session));
+  mwChannel_setService(chan, MW_SERVICE(conf->service));
+  mwChannel_setProtoType(chan, PROTOCOL_TYPE);
+  mwChannel_setProtoVer(chan, PROTOCOL_VER);
+  
+  /* offer all known ciphers */
+  mwChannel_populateSupportedCipherInstances(chan);
+
+  b = mwPutBuffer_new();
+  mwString_put(b, conf->name);
+  mwString_put(b, conf->title);
+  guint32_put(b, 0x00);
+  mwPutBuffer_finalize(mwChannel_getAddtlCreate(chan), b);
+
+  ret = mwChannel_create(chan);
+  if(ret) {
+    conf_state(conf, mwConference_ERROR);
+  } else {
+    conf_state(conf, mwConference_PENDING);
+    conf->channel = chan;
+  }
+
+  return ret;
+}
+
+
+int mwConference_destroy(struct mwConference *conf,
+			 guint32 reason, const char *text) {
+
+  struct mwServiceConference *srvc;
+  struct mwOpaque info = { 0, 0 };
+  int ret = 0;
+
+  g_return_val_if_fail(conf != NULL, -1);
+
+  srvc = conf->service;
+  g_return_val_if_fail(srvc != NULL, -1);
+
+  /* remove conference from the service */
+  srvc->confs = g_list_remove_all(srvc->confs, conf);
+
+  /* close the channel if applicable */
+  if(conf->channel) {
+    if(text && *text) {
+      info.len = strlen(text);
+      info.data = (char *) text;
+    }
+
+    ret = mwChannel_destroy(conf->channel, reason, &info);
+  }
+  
+  /* free the conference */
+  conf_free(conf);
+
+  return ret;
+}
+
+
+int mwConference_accept(struct mwConference *conf) {
+  /* - if conference is not INVITED, return -1
+     - accept the conference channel
+     - send an empty JOIN message
+  */
+
+  struct mwChannel *chan;
+  int ret;
+
+  g_return_val_if_fail(conf != NULL, -1);
+  g_return_val_if_fail(conf->state == mwConference_INVITED, -1);
+
+  chan = conf->channel;
+  ret = mwChannel_accept(chan);
+
+  if(! ret)
+    ret = mwChannel_sendEncrypted(chan, msg_JOIN, NULL, FALSE);
+
+  return ret;
+}
+
+
+int mwConference_invite(struct mwConference *conf,
+			struct mwIdBlock *who,
+			const char *text) {
+
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+  int ret;
+
+  g_return_val_if_fail(conf != NULL, -1);
+  g_return_val_if_fail(conf->channel != NULL, -1);
+  g_return_val_if_fail(who != NULL, -1);
+
+  b = mwPutBuffer_new();
+
+  mwIdBlock_put(b, who);
+  guint16_put(b, 0x00);
+  guint32_put(b, 0x00);
+  mwString_put(b, text);
+  mwString_put(b, who->user);
+
+  mwPutBuffer_finalize(&o, b);
+  ret = mwChannel_sendEncrypted(conf->channel, msg_INVITE, &o, FALSE);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+int mwConference_sendText(struct mwConference *conf, const char *text) {
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+  int ret;
+
+  g_return_val_if_fail(conf != NULL, -1);
+  g_return_val_if_fail(conf->channel != NULL, -1);
+
+  b = mwPutBuffer_new();
+
+  guint32_put(b, 0x01);
+  mwString_put(b, text);
+
+  mwPutBuffer_finalize(&o, b);
+  ret = mwChannel_sendEncrypted(conf->channel, msg_MESSAGE, &o, FALSE);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+int mwConference_sendTyping(struct mwConference *conf, gboolean typing) {
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+  int ret;
+
+  g_return_val_if_fail(conf != NULL, -1);
+  g_return_val_if_fail(conf->channel != NULL, -1);
+  g_return_val_if_fail(conf->state == mwConference_OPEN, -1);
+
+  b = mwPutBuffer_new();
+
+  guint32_put(b, 0x02);
+  guint32_put(b, 0x01);
+  guint32_put(b, !typing);
+  mwOpaque_put(b, NULL);
+
+  mwPutBuffer_finalize(&o, b);
+  ret = mwChannel_sendEncrypted(conf->channel, msg_MESSAGE, &o, FALSE);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+void mwConference_setClientData(struct mwConference *conference,
+			     gpointer data, GDestroyNotify clear) {
+
+  g_return_if_fail(conference != NULL);
+  mw_datum_set(&conference->client_data, data, clear);
+}
+
+
+gpointer mwConference_getClientData(struct mwConference *conference) {
+  g_return_val_if_fail(conference != NULL, NULL);
+  return mw_datum_get(&conference->client_data);
+}
+
+
+void mwConference_removeClientData(struct mwConference *conference) {
+  g_return_if_fail(conference != NULL);
+  mw_datum_clear(&conference->client_data);
+}
+
+
+struct mwConferenceHandler *
+mwServiceConference_getHandler(struct mwServiceConference *srvc) {
+  g_return_val_if_fail(srvc != NULL, NULL);
+  return srvc->handler;
+}
+
+
+GList *mwServiceConference_getConferences(struct mwServiceConference *srvc) {
+  g_return_val_if_fail(srvc != NULL, NULL);
+  return g_list_copy(srvc->confs);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/srvc_dir.c	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,664 @@
+
+/*
+  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/ghash.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_dir.h"
+#include "mw_util.h"
+
+
+#define PROTOCOL_TYPE  0x0000001c
+#define PROTOCOL_VER   0x00000005
+
+
+enum dir_action {
+  action_list    = 0x0000,  /**< list address books */
+  action_open    = 0x0001,  /**< open an addressbook as a directory */
+  action_close   = 0x0002,  /**< close a directory */
+  action_search  = 0x0003,  /**< search an open directory */
+};
+
+
+struct mwServiceDirectory {
+  struct mwService service;
+
+  struct mwDirectoryHandler *handler;
+
+  struct mwChannel *channel;
+ 
+  guint32 counter;       /**< counter of request IDs */
+  GHashTable *requests;  /**< map of request ID:directory */
+  GHashTable *books;     /**< book->name:mwAddressBook */
+};
+
+
+struct mwAddressBook {
+  struct mwServiceDirectory *service;
+
+  guint32 id;        /**< id or type or something */
+  char *name;        /**< name of address book */
+  GHashTable *dirs;  /**< dir->id:mwDirectory */
+};
+
+
+struct mwDirectory {
+  struct mwServiceDirectory *service;
+  struct mwAddressBook *book;
+
+  enum mwDirectoryState state;
+
+  guint32 id;         /**< id of directory, assigned by server */
+  guint32 search_id;  /**< id of current search, from srvc->counter++ */
+
+  mwSearchHandler handler;
+  struct mw_datum client_data;
+};
+
+
+#define next_request_id(srvc) ( ++((srvc)->counter) )
+
+
+static guint32 map_request(struct mwDirectory *dir) {
+  struct mwServiceDirectory *srvc = dir->service;
+  guint32 id = next_request_id(srvc);
+
+  dir->search_id = id;
+  map_guint_insert(srvc->requests, id, dir);
+
+  return id;
+}
+
+
+/** called when directory is removed from the service directory map */
+static void dir_free(struct mwDirectory *dir) {
+  map_guint_remove(dir->service->requests, dir->search_id);
+  g_free(dir);
+}
+
+
+/** remove the directory from the service list and its owning address
+    book, then frees the directory */
+static void dir_remove(struct mwDirectory *dir) {
+  struct mwAddressBook *book = dir->book;
+  map_guint_remove(book->dirs, dir->id);
+}
+
+
+__attribute__((used))
+static struct mwDirectory *dir_new(struct mwAddressBook *book, guint32 id) {
+  struct mwDirectory *dir = g_new0(struct mwDirectory, 1);
+  dir->service = book->service;
+  dir->book = book;
+  dir->id = id;
+  map_guint_insert(book->dirs, id, dir);
+  return dir;
+}
+
+
+/** called when book is removed from the service book map. Removed all
+    directories as well */
+static void book_free(struct mwAddressBook *book) {
+  g_hash_table_destroy(book->dirs);
+  g_free(book->name);
+}
+
+
+__attribute__((used))
+static void book_remove(struct mwAddressBook *book) {
+  struct mwServiceDirectory *srvc = book->service;
+  g_hash_table_remove(srvc->books, book->name);
+}
+
+
+static struct mwAddressBook *book_new(struct mwServiceDirectory *srvc,
+				      const char *name, guint32 id) {
+  struct mwAddressBook *book = g_new0(struct mwAddressBook, 1);
+  book->service = srvc;
+  book->id = id;
+  book->name = g_strdup(name);
+  book->dirs = map_guint_new_full((GDestroyNotify) dir_free);
+  g_hash_table_insert(srvc->books, book->name, book);
+  return book;
+}
+
+
+static const char *getName(struct mwService *srvc) {
+  return "Address Book and Directory";
+}
+
+
+static const char *getDesc(struct mwService *srvc) {
+  return "Address book directory service for user and group lookups";
+}
+
+
+static struct mwChannel *make_channel(struct mwServiceDirectory *srvc) {
+  struct mwSession *session;
+  struct mwChannelSet *cs;
+  struct mwChannel *chan;
+
+  session = mwService_getSession(MW_SERVICE(srvc));
+  cs = mwSession_getChannels(session);
+  chan = mwChannel_newOutgoing(cs);
+
+  mwChannel_setService(chan, MW_SERVICE(srvc));
+  mwChannel_setProtoType(chan, PROTOCOL_TYPE);
+  mwChannel_setProtoVer(chan, PROTOCOL_VER);
+
+  return mwChannel_create(chan)? NULL: chan;
+}
+
+
+static void start(struct mwServiceDirectory *srvc) {
+  struct mwChannel *chan;
+
+  chan = make_channel(srvc);
+  if(chan) {
+    srvc->channel = chan;
+  } else {
+    mwService_stopped(MW_SERVICE(srvc));
+    return;
+  }
+}
+
+
+static void stop(struct mwServiceDirectory *srvc) {
+  /* XXX */
+
+  if(srvc->channel) {
+    mwChannel_destroy(srvc->channel, ERR_SUCCESS, NULL);
+    srvc->channel = NULL;
+  }
+}
+
+
+static void clear(struct mwServiceDirectory *srvc) {
+  struct mwDirectoryHandler *handler;
+
+  if(srvc->books) {
+    g_hash_table_destroy(srvc->books);
+    srvc->books = NULL;
+  }
+  
+  /* clear the handler */
+  handler = srvc->handler;
+  if(handler && handler->clear)
+    handler->clear(srvc);
+  srvc->handler = NULL;
+}
+
+
+static void recv_create(struct mwServiceDirectory *srvc,
+			struct mwChannel *chan,
+			struct mwMsgChannelCreate *msg) {
+
+  /* no way man, we call the shots around here */
+  mwChannel_destroy(chan, ERR_FAILURE, NULL);
+}
+
+
+static void recv_accept(struct mwServiceDirectory *srvc,
+			struct mwChannel *chan,
+			struct mwMsgChannelAccept *msg) {
+
+  g_return_if_fail(srvc->channel != NULL);
+  g_return_if_fail(srvc->channel == chan);
+
+  if(MW_SERVICE_IS_STARTING(srvc)) {
+    mwService_started(MW_SERVICE(srvc));
+      
+  } else {
+    mwChannel_destroy(chan, ERR_FAILURE, NULL);
+  }
+}
+
+
+static void recv_destroy(struct mwServiceDirectory *srvc,
+			 struct mwChannel *chan,
+			 struct mwMsgChannelDestroy *msg) {
+
+  srvc->channel = NULL;
+  mwService_stop(MW_SERVICE(srvc));
+  /** @todo session sense service */
+}
+
+
+static void recv_list(struct mwServiceDirectory *srvc,
+		      struct mwOpaque *data) {
+
+  struct mwGetBuffer *b;
+  guint32 request, code, count;
+  gboolean foo_1;
+  guint16 foo_2;
+  
+  b = mwGetBuffer_wrap(data);
+  
+  guint32_get(b, &request);
+  guint32_get(b, &code);
+  guint32_get(b, &count);
+
+  gboolean_get(b, &foo_1);
+  guint16_get(b, &foo_2);
+
+  if(foo_1 || foo_2) {
+    mw_debug_mailme(data, "received strange address book list");
+    mwGetBuffer_free(b);
+    return;
+  }
+
+  while(!mwGetBuffer_error(b) && count--) {
+    guint32 id;
+    char *name = NULL;
+
+    guint32_get(b, &id);
+    mwString_get(b, &name);
+
+    book_new(srvc, name, id);
+    g_free(name);
+  }
+}
+
+
+static void recv_open(struct mwServiceDirectory *srvc,
+		      struct mwOpaque *data) {
+
+  /* look up the directory associated with this request id, 
+     mark it as open, and trigger the event */
+}
+
+
+static void recv_search(struct mwServiceDirectory *srvc,
+			struct mwOpaque *data) {
+
+  /* look up the directory associated with this request id,
+     trigger the event */
+}
+
+
+static void recv(struct mwServiceDirectory *srvc,
+		 struct mwChannel *chan,
+		 guint16 msg_type, struct mwOpaque *data) {
+  
+  g_return_if_fail(srvc != NULL);
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(chan == srvc->channel);
+  g_return_if_fail(data != NULL);
+
+  switch(msg_type) {
+  case action_list:
+    recv_list(srvc, data);
+    break;
+
+  case action_open:
+    recv_open(srvc, data);
+    break;
+
+  case action_close:
+    ; /* I don't think we should receive these */
+    break;
+
+  case action_search:
+    recv_search(srvc, data);
+    break;
+
+  default:
+    mw_debug_mailme(data, "msg type 0x%04x in directory service", msg_type);
+  }
+}
+
+
+struct mwServiceDirectory *
+mwServiceDirectory_new(struct mwSession *session,
+		       struct mwDirectoryHandler *handler) {
+
+  struct mwServiceDirectory *srvc;
+  struct mwService *service;
+
+  g_return_val_if_fail(session != NULL, NULL);
+  g_return_val_if_fail(handler != NULL, NULL);
+
+  srvc = g_new0(struct mwServiceDirectory, 1);
+  service = MW_SERVICE(srvc);
+
+  mwService_init(service, session, SERVICE_DIRECTORY);
+  service->get_name = getName;
+  service->get_desc = getDesc;
+  service->start = (mwService_funcStart) start;
+  service->stop = (mwService_funcStop) stop;
+  service->clear = (mwService_funcClear) clear;
+  service->recv_create = (mwService_funcRecvCreate) recv_create;
+  service->recv_accept = (mwService_funcRecvAccept) recv_accept;
+  service->recv_destroy = (mwService_funcRecvDestroy) recv_destroy;
+  service->recv = (mwService_funcRecv) recv;
+
+  srvc->handler = handler;
+  srvc->requests = map_guint_new();
+  srvc->books = g_hash_table_new_full(g_str_hash, g_str_equal,
+				      NULL, (GDestroyNotify) book_free);
+  return srvc;
+}
+
+
+struct mwDirectoryHandler *
+mwServiceDirectory_getHandler(struct mwServiceDirectory *srvc) {
+  g_return_val_if_fail(srvc != NULL, NULL);
+  return srvc->handler;
+}
+
+
+int mwServiceDirectory_refreshAddressBooks(struct mwServiceDirectory *srvc) {
+  struct mwChannel *chan;
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+  int ret;
+
+  g_return_val_if_fail(srvc != NULL, -1);
+
+  chan = srvc->channel;
+  g_return_val_if_fail(chan != NULL, -1);
+
+  b = mwPutBuffer_new();
+  guint32_put(b, next_request_id(srvc));
+
+  mwPutBuffer_finalize(&o, b);
+  ret = mwChannel_send(chan, action_list, &o);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+GList *mwServiceDirectory_getAddressBooks(struct mwServiceDirectory *srvc) {
+  g_return_val_if_fail(srvc != NULL, NULL);
+  g_return_val_if_fail(srvc->books != NULL, NULL);
+
+  return map_collect_values(srvc->books);
+}
+
+
+GList *mwServiceDirectory_getDirectories(struct mwServiceDirectory *srvc) {
+  GList *bl, *ret = NULL;
+  
+  g_return_val_if_fail(srvc != NULL, NULL);
+  g_return_val_if_fail(srvc->books != NULL, NULL);
+
+  bl = map_collect_values(srvc->books);
+  for( ; bl; bl = g_list_delete_link(bl, bl)) {
+    struct mwAddressBook *book = bl->data;
+    ret = g_list_concat(ret, map_collect_values(book->dirs));
+  }
+
+  return ret;
+}
+
+
+GList *mwAddressBook_getDirectories(struct mwAddressBook *book) {
+  g_return_val_if_fail(book != NULL, NULL);
+  g_return_val_if_fail(book->dirs != NULL, NULL);
+
+  return map_collect_values(book->dirs);
+}
+
+
+const char *mwAddressBook_getName(struct mwAddressBook *book) {
+  g_return_val_if_fail(book != NULL, NULL);
+  return book->name;
+}
+
+
+struct mwDirectory *mwDirectory_new(struct mwAddressBook *book) {
+  struct mwDirectory *dir;
+
+  g_return_val_if_fail(book != NULL, NULL);
+  g_return_val_if_fail(book->service != NULL, NULL);
+
+  dir = g_new0(struct mwDirectory, 1);
+  dir->service = book->service;
+  dir->book = book;
+  dir->state = mwDirectory_NEW;
+
+  return dir;
+}
+
+
+enum mwDirectoryState mwDirectory_getState(struct mwDirectory *dir) {
+  g_return_val_if_fail(dir != NULL, mwDirectory_UNKNOWN);
+  return dir->state;
+}
+
+
+void mwDirectory_setClientData(struct mwDirectory *dir,
+			       gpointer data, GDestroyNotify clear) {
+
+  g_return_if_fail(dir != NULL);
+  mw_datum_set(&dir->client_data, data, clear);
+}
+
+
+gpointer mwDirectory_getClientData(struct mwDirectory *dir) {
+  g_return_val_if_fail(dir != NULL, NULL);
+  return mw_datum_get(&dir->client_data);
+}
+
+
+void mwDirectory_removeClientData(struct mwDirectory *dir) {
+  g_return_if_fail(dir != NULL);
+  mw_datum_clear(&dir->client_data);
+}
+
+
+struct mwServiceDirectory *mwDirectory_getService(struct mwDirectory *dir) {
+  g_return_val_if_fail(dir != NULL, NULL);
+  g_return_val_if_fail(dir->book != NULL, NULL);
+  return dir->book->service;
+}
+
+
+struct mwAddressBook *mwDirectory_getAddressBook(struct mwDirectory *dir) {
+  g_return_val_if_fail(dir != NULL, NULL);
+  return dir->book;
+}
+
+
+static int dir_open(struct mwDirectory *dir) {
+  struct mwServiceDirectory *srvc;
+  struct mwChannel *chan;
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+  int ret;
+
+  g_return_val_if_fail(dir != NULL, -1);
+
+  srvc = dir->service;
+  g_return_val_if_fail(srvc != NULL, -1);
+
+  chan = srvc->channel;
+  g_return_val_if_fail(chan != NULL, -1);
+
+  b = mwPutBuffer_new();
+  guint32_put(b, map_request(dir));
+
+  /* unsure about these three bytes */
+  gboolean_put(b, FALSE);
+  guint16_put(b, 0x0000);
+
+  guint32_put(b, dir->book->id);
+  mwString_put(b, dir->book->name);
+
+  mwPutBuffer_finalize(&o, b);
+  ret = mwChannel_send(chan, action_open, &o);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+int mwDirectory_open(struct mwDirectory *dir, mwSearchHandler cb) {
+  g_return_val_if_fail(dir != NULL, -1);
+  g_return_val_if_fail(cb != NULL, -1);
+  g_return_val_if_fail(MW_DIRECTORY_IS_NEW(dir), -1);
+
+  dir->state = mwDirectory_PENDING;
+  dir->handler = cb;
+
+  return dir_open(dir);
+}
+
+
+int mwDirectory_next(struct mwDirectory *dir) {  
+  struct mwServiceDirectory *srvc;
+  struct mwChannel *chan;
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+  int ret;
+
+  g_return_val_if_fail(dir != NULL, -1);
+  g_return_val_if_fail(MW_DIRECTORY_IS_OPEN(dir), -1);
+
+  srvc = dir->service;
+  g_return_val_if_fail(srvc != NULL, -1);
+
+  chan = srvc->channel;
+  g_return_val_if_fail(chan != NULL, -1);
+
+  b = mwPutBuffer_new();
+  guint32_put(b, map_request(dir));
+  guint32_put(b, dir->id);
+  guint16_put(b, 0xffff);      /* some magic? */
+  guint32_put(b, 0x00000000);  /* next results */
+
+  mwPutBuffer_finalize(&o, b);
+  ret = mwChannel_send(chan, action_search, &o);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+int mwDirectory_previous(struct mwDirectory *dir) {
+  struct mwServiceDirectory *srvc;
+  struct mwChannel *chan;
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+  int ret;
+
+  g_return_val_if_fail(dir != NULL, -1);
+  g_return_val_if_fail(MW_DIRECTORY_IS_OPEN(dir), -1);
+
+  srvc = dir->service;
+  g_return_val_if_fail(srvc != NULL, -1);
+
+  chan = srvc->channel;
+  g_return_val_if_fail(chan != NULL, -1);
+
+  b = mwPutBuffer_new();
+  guint32_put(b, map_request(dir));
+  guint32_put(b, dir->id);
+  guint16_put(b, 0x0061);      /* some magic? */
+  guint32_put(b, 0x00000001);  /* prev results */
+
+  mwPutBuffer_finalize(&o, b);
+  ret = mwChannel_send(chan, action_search, &o);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+int mwDirectory_search(struct mwDirectory *dir, const char *query) {
+  struct mwServiceDirectory *srvc;
+  struct mwChannel *chan;
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+  int ret;
+
+  g_return_val_if_fail(dir != NULL, -1);
+  g_return_val_if_fail(MW_DIRECTORY_IS_OPEN(dir), -1);
+  g_return_val_if_fail(query != NULL, -1);
+  g_return_val_if_fail(*query != '\0', -1);
+
+  srvc = dir->service;
+  g_return_val_if_fail(srvc != NULL, -1);
+
+  chan = srvc->channel;
+  g_return_val_if_fail(chan != NULL, -1);
+
+  b = mwPutBuffer_new();
+  guint32_put(b, map_request(dir));
+  guint32_put(b, dir->id);
+  guint16_put(b, 0x0061);      /* some magic? */
+  guint32_put(b, 0x00000008);  /* seek results */
+  mwString_put(b, query);
+
+  mwPutBuffer_finalize(&o, b);
+  ret = mwChannel_send(chan, action_search, &o);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+static int dir_close(struct mwDirectory *dir) {
+  struct mwServiceDirectory *srvc;
+  struct mwChannel *chan;
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+  int ret;
+
+  g_return_val_if_fail(dir != NULL, -1);
+
+  srvc = dir->service;
+  g_return_val_if_fail(srvc != NULL, -1);
+
+  chan = srvc->channel;
+  g_return_val_if_fail(chan != NULL, -1);
+
+  b = mwPutBuffer_new();
+  guint32_put(b, next_request_id(dir->service));
+  guint32_put(b, dir->id);
+
+  mwPutBuffer_finalize(&o, b);
+  ret = mwChannel_send(chan, action_close, &o);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+int mwDirectory_destroy(struct mwDirectory *dir) {
+  int ret = 0;
+
+  g_return_val_if_fail(dir != NULL, -1);
+
+  if(MW_DIRECTORY_IS_OPEN(dir) || MW_DIRECTORY_IS_PENDING(dir)) {
+    ret = dir_close(dir);
+  }
+  dir_remove(dir);
+
+  return ret;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/srvc_ft.c	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,647 @@
+
+/*
+  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/glist.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_ft.h"
+#include "mw_util.h"
+
+
+#define PROTOCOL_TYPE  0x00000000
+#define PROTOCOL_VER   0x00000001
+
+
+/** send-on-channel type: FT transfer data */
+#define msg_TRANSFER  0x0001
+
+
+/** ack received transfer data */
+#define msg_RECEIVED  0x0002
+
+
+struct mwServiceFileTransfer {
+  struct mwService service;
+
+  struct mwFileTransferHandler *handler;
+  GList *transfers;
+};
+
+
+struct mwFileTransfer {
+  struct mwServiceFileTransfer *service;
+  
+  struct mwChannel *channel;
+  struct mwIdBlock who;
+
+  enum mwFileTransferState state;
+
+  char *filename;
+  char *message;
+
+  guint32 size;
+  guint32 remaining;
+
+  struct mw_datum client_data;
+};
+
+
+/** momentarily places a mwLoginInfo into a mwIdBlock */
+static void login_into_id(struct mwIdBlock *to, struct mwLoginInfo *from) {
+  to->user = from->user_id;
+  to->community = from->community;
+}
+
+
+static const char *ft_state_str(enum mwFileTransferState state) {
+  switch(state) {
+  case mwFileTransfer_NEW:
+    return "new";
+
+  case mwFileTransfer_PENDING:
+    return "pending";
+
+  case mwFileTransfer_OPEN:
+    return "open";
+
+  case mwFileTransfer_CANCEL_LOCAL:
+    return "cancelled locally";
+
+  case mwFileTransfer_CANCEL_REMOTE:
+    return "cancelled remotely";
+
+  case mwFileTransfer_DONE:
+    return "done";
+
+  case mwFileTransfer_ERROR:
+    return "error";
+
+  case mwFileTransfer_UNKNOWN:
+  default:
+    return "UNKNOWN";
+  }
+}
+
+
+static void ft_state(struct mwFileTransfer *ft,
+		     enum mwFileTransferState state) {
+
+  g_return_if_fail(ft != NULL);
+
+  if(ft->state == state) return;
+
+  g_info("setting ft (%s, %s) state: %s",
+	 NSTR(ft->who.user), NSTR(ft->who.community),
+	 ft_state_str(state));
+
+  ft->state = state;
+}
+
+
+static void recv_channelCreate(struct mwServiceFileTransfer *srvc,
+			       struct mwChannel *chan,
+			       struct mwMsgChannelCreate *msg) {
+
+  struct mwFileTransferHandler *handler;
+  struct mwGetBuffer *b;
+
+  char *fnm, *txt;
+  guint32 size, junk;
+  gboolean b_err;
+
+  g_return_if_fail(srvc->handler != NULL);
+  handler = srvc->handler;
+  
+  b = mwGetBuffer_wrap(&msg->addtl);
+
+  guint32_get(b, &junk); /* unknown */
+  mwString_get(b, &fnm); /* offered filename */
+  mwString_get(b, &txt); /* offering message */
+  guint32_get(b, &size); /* size of offered file */
+  guint32_get(b, &junk); /* unknown */
+  /* and we just skip an unknown guint16 at the end */
+
+  b_err = mwGetBuffer_error(b);
+  mwGetBuffer_free(b);
+
+  if(b_err) {
+    g_warning("bad/malformed addtl in File Transfer service");
+    mwChannel_destroy(chan, ERR_FAILURE, NULL);
+
+  } else {
+    struct mwIdBlock idb;
+    struct mwFileTransfer *ft;
+
+    login_into_id(&idb, mwChannel_getUser(chan));
+    ft = mwFileTransfer_new(srvc, &idb, txt, fnm, size);
+    ft->channel = chan;
+    ft_state(ft, mwFileTransfer_PENDING);
+
+    mwChannel_setServiceData(chan, ft, NULL);
+
+    if(handler->ft_offered)
+      handler->ft_offered(ft);
+  }
+
+  g_free(fnm);
+  g_free(txt);
+}
+
+
+static void recv_channelAccept(struct mwServiceFileTransfer *srvc,
+			       struct mwChannel *chan,
+			       struct mwMsgChannelAccept *msg) {
+
+  struct mwFileTransferHandler *handler;
+  struct mwFileTransfer *ft;
+
+  g_return_if_fail(srvc->handler != NULL);
+  handler = srvc->handler;
+
+  ft = mwChannel_getServiceData(chan);
+  g_return_if_fail(ft != NULL);
+
+  ft_state(ft, mwFileTransfer_OPEN);
+
+  if(handler->ft_opened)
+    handler->ft_opened(ft);
+}
+
+
+static void recv_channelDestroy(struct mwServiceFileTransfer *srvc,
+				struct mwChannel *chan,
+				struct mwMsgChannelDestroy *msg) {
+
+  struct mwFileTransferHandler *handler;
+  struct mwFileTransfer *ft;
+  guint32 code;
+
+  code = msg->reason;
+
+  g_return_if_fail(srvc->handler != NULL);
+  handler = srvc->handler;
+
+  ft = mwChannel_getServiceData(chan);
+  g_return_if_fail(ft != NULL);
+
+  ft->channel = NULL;
+
+  if(! mwFileTransfer_isDone(ft))
+    ft_state(ft, mwFileTransfer_CANCEL_REMOTE);
+
+  mwFileTransfer_close(ft, code);
+}
+
+
+static void recv_TRANSFER(struct mwFileTransfer *ft,
+			  struct mwOpaque *data) {
+
+  struct mwServiceFileTransfer *srvc;
+  struct mwFileTransferHandler *handler;
+  
+  srvc = ft->service;
+  handler = srvc->handler;
+
+  g_return_if_fail(mwFileTransfer_isOpen(ft));
+
+  if(data->len > ft->remaining) {
+    /* @todo handle error */
+
+  } else {
+    ft->remaining -= data->len;
+
+    if(! ft->remaining)
+      ft_state(ft, mwFileTransfer_DONE);
+    
+    if(handler->ft_recv)
+      handler->ft_recv(ft, data);
+  }
+}
+
+
+static void recv_RECEIVED(struct mwFileTransfer *ft,
+			  struct mwOpaque *data) {
+
+  struct mwServiceFileTransfer *srvc;
+  struct mwFileTransferHandler *handler;
+  
+  srvc = ft->service;
+  handler = srvc->handler;
+
+  if(! ft->remaining)
+    ft_state(ft, mwFileTransfer_DONE);
+
+  if(handler->ft_ack)
+    handler->ft_ack(ft);
+
+  if(! ft->remaining)
+    mwFileTransfer_close(ft, mwFileTransfer_SUCCESS);
+}
+
+
+static void recv(struct mwService *srvc, struct mwChannel *chan,
+		 guint16 type, struct mwOpaque *data) {
+
+  struct mwFileTransfer *ft;
+  
+  ft = mwChannel_getServiceData(chan);
+  g_return_if_fail(ft != NULL);
+
+  switch(type) {
+  case msg_TRANSFER:
+    recv_TRANSFER(ft, data);
+    break;
+
+  case msg_RECEIVED:
+    recv_RECEIVED(ft, data);
+    break;
+
+  default:
+    mw_debug_mailme(data, "unknown message in ft service: 0x%04x", type);
+  }
+}
+
+
+static void clear(struct mwServiceFileTransfer *srvc) {
+  struct mwFileTransferHandler *h;
+  
+  h = srvc->handler;
+  if(h && h->clear)
+    h->clear(srvc);
+  srvc->handler = NULL;
+}
+
+
+static const char *name(struct mwService *srvc) {
+  return "File Transfer";
+}
+
+
+static const char *desc(struct mwService *srvc) {
+  return "Provides file transfer capabilities through the community server";
+}
+
+
+static void start(struct mwService *srvc) {
+  mwService_started(srvc);
+}
+
+
+static void stop(struct mwServiceFileTransfer *srvc) {
+  while(srvc->transfers) {
+    mwFileTransfer_free(srvc->transfers->data);
+  }
+
+  mwService_stopped(MW_SERVICE(srvc));
+}
+
+
+struct mwServiceFileTransfer *
+mwServiceFileTransfer_new(struct mwSession *session,
+			  struct mwFileTransferHandler *handler) {
+
+  struct mwServiceFileTransfer *srvc_ft;
+  struct mwService *srvc;
+
+  g_return_val_if_fail(session != NULL, NULL);
+  g_return_val_if_fail(handler != NULL, NULL);
+
+  srvc_ft = g_new0(struct mwServiceFileTransfer, 1);
+  srvc = MW_SERVICE(srvc_ft);
+
+  mwService_init(srvc, session, mwService_FILE_TRANSFER);
+  srvc->recv_create = (mwService_funcRecvCreate) recv_channelCreate;
+  srvc->recv_accept = (mwService_funcRecvAccept) recv_channelAccept;
+  srvc->recv_destroy = (mwService_funcRecvDestroy) recv_channelDestroy;
+  srvc->recv = recv;
+  srvc->clear = (mwService_funcClear) clear;
+  srvc->get_name = name;
+  srvc->get_desc = desc;
+  srvc->start = start;
+  srvc->stop = (mwService_funcStop) stop;
+
+  srvc_ft->handler = handler;
+
+  return srvc_ft;
+}
+
+
+struct mwFileTransferHandler *
+mwServiceFileTransfer_getHandler(struct mwServiceFileTransfer *srvc) {
+  g_return_val_if_fail(srvc != NULL, NULL);
+  return srvc->handler;
+}
+
+
+struct mwFileTransfer *
+mwFileTransfer_new(struct mwServiceFileTransfer *srvc,
+		   const struct mwIdBlock *who, const char *msg,
+		   const char *filename, guint32 filesize) {
+  
+  struct mwFileTransfer *ft;
+
+  g_return_val_if_fail(srvc != NULL, NULL);
+  g_return_val_if_fail(who != NULL, NULL);
+  
+  ft = g_new0(struct mwFileTransfer, 1);
+  ft->service = srvc;
+  mwIdBlock_clone(&ft->who, who);
+  ft->filename = g_strdup(filename);
+  ft->message = g_strdup(msg);
+  ft->size = ft->remaining = filesize;
+
+  ft_state(ft, mwFileTransfer_NEW);
+
+  /* stick a reference in the service */
+  srvc->transfers = g_list_prepend(srvc->transfers, ft);
+
+  return ft;
+}
+
+
+struct mwServiceFileTransfer *
+mwFileTransfer_getService(struct mwFileTransfer *ft) {
+  g_return_val_if_fail(ft != NULL, NULL);
+  return ft->service;
+}
+
+
+enum mwFileTransferState
+mwFileTransfer_getState(struct mwFileTransfer *ft) {
+  g_return_val_if_fail(ft != NULL, mwFileTransfer_UNKNOWN);
+  return ft->state;
+}
+
+
+const struct mwIdBlock *
+mwFileTransfer_getUser(struct mwFileTransfer *ft) {
+  g_return_val_if_fail(ft != NULL, NULL);
+  return &ft->who;
+}
+
+
+const char *
+mwFileTransfer_getMessage(struct mwFileTransfer *ft) {
+  g_return_val_if_fail(ft != NULL, NULL);
+  return ft->message;
+}
+
+
+const char *
+mwFileTransfer_getFileName(struct mwFileTransfer *ft) {
+  g_return_val_if_fail(ft != NULL, NULL);
+  return ft->filename;
+}
+
+
+guint32 mwFileTransfer_getFileSize(struct mwFileTransfer *ft) {
+  g_return_val_if_fail(ft != NULL, 0);
+  return ft->size;
+}
+
+
+guint32 mwFileTransfer_getRemaining(struct mwFileTransfer *ft) {
+  g_return_val_if_fail(ft != NULL, 0);
+  return ft->remaining;
+}
+
+
+int mwFileTransfer_accept(struct mwFileTransfer *ft) {
+  struct mwServiceFileTransfer *srvc;
+  struct mwFileTransferHandler *handler;
+  int ret;
+
+  g_return_val_if_fail(ft != NULL, -1);
+  g_return_val_if_fail(ft->channel != NULL, -1);
+  g_return_val_if_fail(mwFileTransfer_isPending(ft), -1);
+  g_return_val_if_fail(mwChannel_isIncoming(ft->channel), -1);
+  g_return_val_if_fail(mwChannel_isState(ft->channel, mwChannel_WAIT), -1);
+
+  g_return_val_if_fail(ft->service != NULL, -1);
+  srvc = ft->service;
+
+  g_return_val_if_fail(srvc->handler != NULL, -1);
+  handler = srvc->handler;
+
+  ret = mwChannel_accept(ft->channel);
+
+  if(ret) {
+    mwFileTransfer_close(ft, ERR_FAILURE);
+
+  } else {
+    ft_state(ft, mwFileTransfer_OPEN);
+    if(handler->ft_opened)
+      handler->ft_opened(ft);
+  }
+
+  return ret;
+}
+
+
+static void ft_create_chan(struct mwFileTransfer *ft) {
+  struct mwSession *s;
+  struct mwChannelSet *cs;
+  struct mwChannel *chan;
+  struct mwLoginInfo *login;
+  struct mwPutBuffer *b;
+  
+  /* we only should be calling this if there isn't a channel already
+     associated with the conversation */
+  g_return_if_fail(ft != NULL);
+  g_return_if_fail(mwFileTransfer_isNew(ft));
+  g_return_if_fail(ft->channel == NULL);
+		   
+  s = mwService_getSession(MW_SERVICE(ft->service));
+  cs = mwSession_getChannels(s);
+
+  chan = mwChannel_newOutgoing(cs);
+  mwChannel_setService(chan, MW_SERVICE(ft->service));
+  mwChannel_setProtoType(chan, PROTOCOL_TYPE);
+  mwChannel_setProtoVer(chan, PROTOCOL_VER);
+
+  /* offer all known ciphers */
+  mwChannel_populateSupportedCipherInstances(chan);
+
+  /* set the target */
+  login = mwChannel_getUser(chan);
+  login->user_id = g_strdup(ft->who.user);
+  login->community = g_strdup(ft->who.community);
+
+  /* compose the addtl create */
+  b = mwPutBuffer_new();
+  guint32_put(b, 0x00);
+  mwString_put(b, ft->filename);
+  mwString_put(b, ft->message);
+  guint32_put(b, ft->size);
+  guint32_put(b, 0x00);
+  guint16_put(b, 0x00);
+
+  mwPutBuffer_finalize(mwChannel_getAddtlCreate(chan), b);
+
+  ft->channel = mwChannel_create(chan)? NULL: chan;
+  if(ft->channel) {
+    mwChannel_setServiceData(ft->channel, ft, NULL);
+  }
+}
+
+
+int mwFileTransfer_offer(struct mwFileTransfer *ft) {
+  struct mwServiceFileTransfer *srvc;
+  struct mwFileTransferHandler *handler;
+
+  g_return_val_if_fail(ft != NULL, -1);
+  g_return_val_if_fail(ft->channel == NULL, -1);
+  g_return_val_if_fail(mwFileTransfer_isNew(ft), -1);
+
+  g_return_val_if_fail(ft->service != NULL, -1);
+  srvc = ft->service;
+
+  g_return_val_if_fail(srvc->handler != NULL, -1);
+  handler = srvc->handler;
+
+  ft_create_chan(ft);
+  if(ft->channel) {
+    ft_state(ft, mwFileTransfer_PENDING);
+  } else {
+    ft_state(ft, mwFileTransfer_ERROR);
+    mwFileTransfer_close(ft, ERR_FAILURE);
+  }
+
+  return 0;
+}
+
+
+int mwFileTransfer_close(struct mwFileTransfer *ft, guint32 code) {
+  struct mwServiceFileTransfer *srvc;
+  struct mwFileTransferHandler *handler;
+  int ret;
+
+  g_return_val_if_fail(ft != NULL, -1);
+
+  if(mwFileTransfer_isOpen(ft))
+    ft_state(ft, mwFileTransfer_CANCEL_LOCAL);
+
+  if(ft->channel) {
+    ret = mwChannel_destroy(ft->channel, code, NULL);
+    ft->channel = NULL;
+  }
+
+  srvc = ft->service;
+  g_return_val_if_fail(srvc != NULL, ret);
+
+  handler = srvc->handler;
+  g_return_val_if_fail(handler != NULL, ret);
+
+  if(handler->ft_closed)
+    handler->ft_closed(ft, code);
+
+  return ret;
+}
+
+
+void mwFileTransfer_free(struct mwFileTransfer *ft) {
+  struct mwServiceFileTransfer *srvc;
+
+  if(! ft) return;
+
+  srvc = ft->service;
+  if(srvc)
+    srvc->transfers = g_list_remove(srvc->transfers, ft);
+
+  if(ft->channel) {
+    mwChannel_destroy(ft->channel, mwFileTransfer_SUCCESS, NULL);
+    ft->channel = NULL;
+  }
+
+  mwFileTransfer_removeClientData(ft);
+
+  mwIdBlock_clear(&ft->who);
+  g_free(ft->filename);
+  g_free(ft->message);
+  g_free(ft);
+}
+
+
+int mwFileTransfer_send(struct mwFileTransfer *ft,
+			struct mwOpaque *data) {
+
+  struct mwChannel *chan;
+  int ret;
+
+  g_return_val_if_fail(ft != NULL, -1);
+  g_return_val_if_fail(mwFileTransfer_isOpen(ft), -1);
+  g_return_val_if_fail(ft->channel != NULL, -1);
+  chan = ft->channel;
+
+  g_return_val_if_fail(mwChannel_isOutgoing(chan), -1);
+
+  if(data->len > ft->remaining) {
+    /* @todo handle error */
+    return -1;
+  }
+
+  ret = mwChannel_send(chan, msg_TRANSFER, data);
+  if(! ret) ft->remaining -= data->len;
+  
+  /* we're not done until we receive an ACK for the last piece of
+     outgoing data */
+
+  return ret;
+}
+
+
+int mwFileTransfer_ack(struct mwFileTransfer *ft) {
+  struct mwChannel *chan;
+
+  g_return_val_if_fail(ft != NULL, -1);
+
+  chan = ft->channel;
+  g_return_val_if_fail(chan != NULL, -1);
+  g_return_val_if_fail(mwChannel_isIncoming(chan), -1);
+
+  return mwChannel_sendEncrypted(chan, msg_RECEIVED, NULL, FALSE);
+}
+
+
+void mwFileTransfer_setClientData(struct mwFileTransfer *ft,
+				  gpointer data, GDestroyNotify clean) {
+  g_return_if_fail(ft != NULL);
+  mw_datum_set(&ft->client_data, data, clean);
+}
+
+
+gpointer mwFileTransfer_getClientData(struct mwFileTransfer *ft) {
+  g_return_val_if_fail(ft != NULL, NULL);
+  return mw_datum_get(&ft->client_data);
+}
+
+
+void mwFileTransfer_removeClientData(struct mwFileTransfer *ft) {
+  g_return_if_fail(ft != NULL);
+  mw_datum_clear(&ft->client_data);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/srvc_im.c	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,1015 @@
+
+/*
+  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/glist.h>
+#include <string.h>
+
+#include "mw_channel.h"
+#include "mw_debug.h"
+#include "mw_error.h"
+#include "mw_message.h"
+#include "mw_service.h"
+#include "mw_session.h"
+#include "mw_srvc_im.h"
+#include "mw_util.h"
+
+
+#define PROTOCOL_TYPE  0x00001000
+#define PROTOCOL_VER   0x00000003
+
+
+/* data for the addtl blocks of channel creation */
+#define mwImAddtlA_NORMAL  0x00000001
+
+#define mwImAddtlB_NORMAL      0x00000001  /**< standard */
+#define mwImAddtlB_PRECONF     0x00000019  /**< pre-conference chat */
+#define mwImAddtlB_NOTESBUDDY  0x00033453  /**< notesbuddy */
+
+#define mwImAddtlC_NORMAL  0x00000002
+
+
+/* send-on-channel message type */
+#define msg_MESSAGE  0x0064  /**< IM message */
+
+
+/* which type of im? */
+enum mwImType {
+  mwIm_TEXT  = 0x00000001,  /**< text message */
+  mwIm_DATA  = 0x00000002,  /**< status message (usually) */
+};
+
+
+/* which type of data im? */
+enum mwImDataType {
+  mwImData_TYPING   = 0x00000001,  /**< common use typing indicator */
+  mwImData_SUBJECT  = 0x00000003,  /**< notesbuddy IM topic */
+  mwImData_HTML     = 0x00000004,  /**< notesbuddy HTML message */
+  mwImData_MIME     = 0x00000005,  /**< notesbuddy MIME message, w/image */
+
+  mwImData_INVITE   = 0x0000000a,  /**< Places invitation */
+
+  mwImData_MULTI_START  = 0x00001388,
+  mwImData_MULTI_STOP   = 0x00001389,
+};
+
+
+/** @todo might be appropriate to make a couple of hashtables to
+    reference conversations by channel and target */
+struct mwServiceIm {
+  struct mwService service;
+
+  enum mwImClientType features;
+
+  struct mwImHandler *handler;
+  GList *convs;  /**< list of struct im_convo */
+};
+
+
+struct mwConversation {
+  struct mwServiceIm *service;  /**< owning service */
+  struct mwChannel *channel;    /**< channel */
+  struct mwIdBlock target;      /**< conversation target */
+
+  /** state of the conversation, based loosely on the state of its
+      underlying channel */
+  enum mwConversationState state;
+
+  enum mwImClientType features;
+
+  GString *multi;               /**< buffer for multi-chunk message */
+  enum mwImSendType multi_type; /**< type of incoming multi-chunk message */
+
+  struct mw_datum client_data;
+};
+
+
+/** momentarily places a mwLoginInfo into a mwIdBlock */
+static void login_into_id(struct mwIdBlock *to, struct mwLoginInfo *from) {
+  to->user = from->user_id;
+  to->community = from->community;
+}
+
+
+static struct mwConversation *convo_find_by_user(struct mwServiceIm *srvc,
+						 struct mwIdBlock *to) {
+  GList *l;
+
+  for(l = srvc->convs; l; l = l->next) {
+    struct mwConversation *c = l->data;
+    if(mwIdBlock_equal(&c->target, to))
+       return c;
+  }
+
+  return NULL;
+}
+
+
+static const char *conv_state_str(enum mwConversationState state) {
+  switch(state) {
+  case mwConversation_CLOSED:
+    return "closed";
+
+  case mwConversation_OPEN:
+    return "open";
+
+  case mwConversation_PENDING:
+    return "pending";
+
+  case mwConversation_UNKNOWN:
+  default:
+    return "UNKNOWN";
+  }
+}
+
+
+static void convo_set_state(struct mwConversation *conv,
+			    enum mwConversationState state) {
+
+  g_return_if_fail(conv != NULL);
+
+  if(conv->state != state) {
+    g_info("setting conversation (%s, %s) state: %s",
+	   NSTR(conv->target.user), NSTR(conv->target.community),
+	   conv_state_str(state));
+    conv->state = state;
+  }
+}
+
+
+struct mwConversation *mwServiceIm_findConversation(struct mwServiceIm *srvc,
+						    struct mwIdBlock *to) {
+  g_return_val_if_fail(srvc != NULL, NULL);
+  g_return_val_if_fail(to != NULL, NULL);
+
+  return convo_find_by_user(srvc, to);
+}
+
+
+struct mwConversation *mwServiceIm_getConversation(struct mwServiceIm *srvc,
+						   struct mwIdBlock *to) {
+  struct mwConversation *c;
+
+  g_return_val_if_fail(srvc != NULL, NULL);
+  g_return_val_if_fail(to != NULL, NULL);
+
+  c = convo_find_by_user(srvc, to);
+  if(! c) {
+    c = g_new0(struct mwConversation, 1);
+    c->service = srvc;
+    mwIdBlock_clone(&c->target, to);
+    c->state = mwConversation_CLOSED;
+    c->features = srvc->features;
+
+    srvc->convs = g_list_prepend(srvc->convs, c);
+  }
+
+  return c;
+}
+
+
+static void convo_create_chan(struct mwConversation *c) {
+  struct mwSession *s;
+  struct mwChannelSet *cs;
+  struct mwChannel *chan;
+  struct mwLoginInfo *login;
+  struct mwPutBuffer *b;
+
+  /* we only should be calling this if there isn't a channel already
+     associated with the conversation */
+  g_return_if_fail(c != NULL);
+  g_return_if_fail(mwConversation_isPending(c));
+  g_return_if_fail(c->channel == NULL);
+
+  s = mwService_getSession(MW_SERVICE(c->service));
+  cs = mwSession_getChannels(s);
+
+  chan = mwChannel_newOutgoing(cs);
+  mwChannel_setService(chan, MW_SERVICE(c->service));
+  mwChannel_setProtoType(chan, PROTOCOL_TYPE);
+  mwChannel_setProtoVer(chan, PROTOCOL_VER);
+
+  /* offer all known ciphers */
+  mwChannel_populateSupportedCipherInstances(chan);
+
+  /* set the target */
+  login = mwChannel_getUser(chan);
+  login->user_id = g_strdup(c->target.user);
+  login->community = g_strdup(c->target.community);
+
+  /* compose the addtl create, with optional FANCY HTML! */
+  b = mwPutBuffer_new();
+  guint32_put(b, mwImAddtlA_NORMAL);
+  guint32_put(b, c->features);
+  mwPutBuffer_finalize(mwChannel_getAddtlCreate(chan), b);
+
+  c->channel = mwChannel_create(chan)? NULL: chan;
+  if(c->channel) {
+    mwChannel_setServiceData(c->channel, c, NULL);
+  }
+}
+
+
+void mwConversation_open(struct mwConversation *conv) {
+  g_return_if_fail(conv != NULL);
+  g_return_if_fail(mwConversation_isClosed(conv));
+
+  convo_set_state(conv, mwConversation_PENDING);
+  convo_create_chan(conv);
+}
+
+
+static void convo_opened(struct mwConversation *conv) {
+  struct mwImHandler *h = NULL;
+
+  g_return_if_fail(conv != NULL);
+  g_return_if_fail(conv->service != NULL);
+
+  convo_set_state(conv, mwConversation_OPEN);
+  h = conv->service->handler;
+
+  g_return_if_fail(h != NULL);
+
+  if(h->conversation_opened)
+    h->conversation_opened(conv);
+}
+
+
+static void convo_free(struct mwConversation *conv) {
+  struct mwServiceIm *srvc;
+
+  mwConversation_removeClientData(conv);
+
+  srvc = conv->service;
+  srvc->convs = g_list_remove(srvc->convs, conv);
+
+  mwIdBlock_clear(&conv->target);
+  g_free(conv);
+}
+
+
+static int send_accept(struct mwConversation *c) {
+
+  struct mwChannel *chan = c->channel;
+  struct mwSession *s = mwChannel_getSession(chan);
+  struct mwUserStatus *stat = mwSession_getUserStatus(s);
+
+  struct mwPutBuffer *b;
+  struct mwOpaque *o;
+
+  b = mwPutBuffer_new();
+  guint32_put(b, mwImAddtlA_NORMAL);
+  guint32_put(b, c->features);
+  guint32_put(b, mwImAddtlC_NORMAL);
+  mwUserStatus_put(b, stat);
+
+  o = mwChannel_getAddtlAccept(chan);
+  mwOpaque_clear(o);
+  mwPutBuffer_finalize(o, b);
+
+  return mwChannel_accept(chan);
+}
+
+
+static void recv_channelCreate(struct mwService *srvc,
+			       struct mwChannel *chan,
+			       struct mwMsgChannelCreate *msg) {
+
+  /* - ensure it's the right service,proto,and proto ver
+     - check the opaque for the right opaque junk
+     - if not, close channel
+     - compose & send a channel accept
+  */
+
+  struct mwServiceIm *srvc_im = (struct mwServiceIm *) srvc;
+  struct mwImHandler *handler;
+  struct mwSession *s;
+  struct mwUserStatus *stat;
+  guint32 x, y, z;
+  struct mwGetBuffer *b;
+  struct mwConversation *c = NULL;
+  struct mwIdBlock idb;
+
+  handler = srvc_im->handler;
+  s = mwChannel_getSession(chan);
+  stat = mwSession_getUserStatus(s);
+
+  /* ensure the appropriate service/proto/ver */
+  x = mwChannel_getServiceId(chan);
+  y = mwChannel_getProtoType(chan);
+  z = mwChannel_getProtoVer(chan);
+
+  if( (x != SERVICE_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);
+    return;
+  }
+
+  /* act upon the values in the addtl block */
+  b = mwGetBuffer_wrap(&msg->addtl);
+  guint32_get(b, &x);
+  guint32_get(b, &y);
+  z = (guint) mwGetBuffer_error(b);
+  mwGetBuffer_free(b);
+
+  if(z /* buffer error, BOOM! */ ) {
+    g_warning("bad/malformed addtl in IM service");
+    mwChannel_destroy(chan, ERR_FAILURE, NULL);
+    return;
+
+  } else if(x != mwImAddtlA_NORMAL) {
+    g_message("unknown params: 0x%08x, 0x%08x", x, y);
+    mwChannel_destroy(chan, ERR_IM_NOT_REGISTERED, NULL);
+    return;
+    
+  } else if(y == mwImAddtlB_PRECONF) {
+    if(! handler->place_invite) {
+      g_info("rejecting place-invite channel");
+      mwChannel_destroy(chan, ERR_IM_NOT_REGISTERED, NULL);
+      return;
+
+    } else {
+      g_info("accepting place-invite channel");
+    }
+
+  } else if(y != mwImClient_PLAIN && y != srvc_im->features) {
+    /** reject what we do not understand */
+    mwChannel_destroy(chan, ERR_IM_NOT_REGISTERED, NULL);
+    return;
+
+  } else if(stat->status == mwStatus_BUSY) {
+    g_info("rejecting IM channel due to DND status");
+    mwChannel_destroy(chan, ERR_CLIENT_USER_DND, NULL);
+    return;
+  }
+
+  login_into_id(&idb, mwChannel_getUser(chan));
+
+#if 0
+  c = convo_find_by_user(srvc_im, &idb);
+#endif
+
+  if(! c) {
+    c = g_new0(struct mwConversation, 1);
+    c->service = srvc_im;
+    srvc_im->convs = g_list_prepend(srvc_im->convs, c);
+  }
+
+#if 0
+  /* we're going to re-associate any existing conversations with this
+     new channel. That means closing any existing ones */
+  if(c->channel) {
+    g_info("closing existing IM channel 0x%08x", mwChannel_getId(c->channel));
+    mwConversation_close(c, ERR_SUCCESS);
+  }
+#endif
+
+  /* set up the conversation with this channel, target, and be fancy
+     if the other side requested it */
+  c->channel = chan;
+  mwIdBlock_clone(&c->target, &idb);
+  c->features = y;
+  convo_set_state(c, mwConversation_PENDING);
+  mwChannel_setServiceData(c->channel, c, NULL);
+
+  if(send_accept(c)) {
+    g_warning("sending IM channel accept failed");
+    mwConversation_free(c);
+
+  } else {
+    convo_opened(c);
+  }
+}
+
+
+static void recv_channelAccept(struct mwService *srvc, struct mwChannel *chan,
+			       struct mwMsgChannelAccept *msg) {
+
+  struct mwConversation *conv;
+
+  conv = mwChannel_getServiceData(chan);
+  if(! conv) {
+    g_warning("received channel accept for non-existant conversation");
+    mwChannel_destroy(chan, ERR_FAILURE, NULL);
+    return;
+  }
+
+  convo_opened(conv);
+}
+
+
+static void recv_channelDestroy(struct mwService *srvc, struct mwChannel *chan,
+				struct mwMsgChannelDestroy *msg) {
+
+  struct mwConversation *c;
+
+  c = mwChannel_getServiceData(chan);
+  g_return_if_fail(c != NULL);
+
+  c->channel = NULL;
+
+  if(mwChannel_isState(chan, mwChannel_ERROR)) {
+
+    /* checking for failure on the receiving end to accept html
+       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) {
+
+      g_debug("falling back on a plaintext conversation");
+      c->features = mwImClient_PLAIN;
+      convo_create_chan(c);
+      return;
+    }
+  }
+
+  mwConversation_close(c, msg->reason);
+}
+
+
+static void convo_recv(struct mwConversation *conv, enum mwImSendType type,
+		       gconstpointer msg) {
+
+  struct mwServiceIm *srvc;
+  struct mwImHandler *handler;
+
+  g_return_if_fail(conv != NULL);
+
+  srvc = conv->service;
+  g_return_if_fail(srvc != NULL);
+
+  handler = srvc->handler;
+  if(handler && handler->conversation_recv)
+    handler->conversation_recv(conv, type, msg);
+}
+
+
+static void convo_multi_start(struct mwConversation *conv) {
+  g_return_if_fail(conv->multi == NULL);
+  conv->multi = g_string_new(NULL);
+}
+
+
+static void convo_multi_stop(struct mwConversation *conv) {
+
+  g_return_if_fail(conv->multi != NULL);
+
+  /* actually send it */
+  convo_recv(conv, conv->multi_type, conv->multi->str);
+
+  /* clear up the multi buffer */
+  g_string_free(conv->multi, TRUE);
+  conv->multi = NULL;
+}
+
+
+static void recv_text(struct mwServiceIm *srvc, struct mwChannel *chan,
+		      struct mwGetBuffer *b) {
+
+  struct mwConversation *c;
+  char *text = NULL;
+
+  mwString_get(b, &text);
+
+  if(! text) return;
+
+  c = mwChannel_getServiceData(chan);
+  if(c) {
+    if(c->multi) {
+      g_string_append(c->multi, text);
+
+    } else {
+      convo_recv(c, mwImSend_PLAIN, text); 
+    }
+  }
+
+  g_free(text);
+}
+
+
+static void convo_invite(struct mwConversation *conv,
+			 struct mwOpaque *o) {
+
+  struct mwServiceIm *srvc;
+  struct mwImHandler *handler;
+
+  struct mwGetBuffer *b;
+  char *title, *name, *msg;
+
+  g_info("convo_invite");
+
+  srvc = conv->service;
+  handler = srvc->handler;
+
+  g_return_if_fail(handler != NULL);
+  g_return_if_fail(handler->place_invite != NULL);
+
+  b = mwGetBuffer_wrap(o);
+  mwGetBuffer_advance(b, 4);
+  mwString_get(b, &title);
+  mwString_get(b, &msg);
+  mwGetBuffer_advance(b, 19);
+  mwString_get(b, &name);
+
+  if(! mwGetBuffer_error(b)) {
+    handler->place_invite(conv, msg, title, name);
+  }
+
+  mwGetBuffer_free(b);
+  g_free(msg);
+  g_free(title);
+  g_free(name);
+}
+
+
+static void recv_data(struct mwServiceIm *srvc, struct mwChannel *chan,
+		      struct mwGetBuffer *b) {
+
+  struct mwConversation *conv;
+  guint32 type, subtype;
+  struct mwOpaque o = { 0, 0 };
+  char *x;
+
+  guint32_get(b, &type);
+  guint32_get(b, &subtype);
+  mwOpaque_get(b, &o);
+
+  if(mwGetBuffer_error(b)) {
+    mwOpaque_clear(&o);
+    return;
+  }
+
+  conv = mwChannel_getServiceData(chan);
+  if(! conv) return;
+
+  switch(type) {
+  case mwImData_TYPING:
+    convo_recv(conv, mwImSend_TYPING, GINT_TO_POINTER(! subtype));
+    break;
+
+  case mwImData_HTML:
+    if(o.len) {
+      if(conv->multi) {
+	g_string_append_len(conv->multi, o.data, o.len);
+	conv->multi_type = mwImSend_HTML;
+
+      } else {
+	x = g_strndup(o.data, o.len);
+	convo_recv(conv, mwImSend_HTML, x);
+	g_free(x);
+      }
+    }
+    break;
+
+  case mwImData_SUBJECT:
+    x = g_strndup(o.data, o.len);
+    convo_recv(conv, mwImSend_SUBJECT, x);
+    g_free(x);
+    break;
+
+  case mwImData_MIME:
+    if(conv->multi) {
+      g_string_append_len(conv->multi, o.data, o.len);
+      conv->multi_type = mwImSend_MIME;
+
+    } else {
+      x = g_strndup(o.data, o.len);
+      convo_recv(conv, mwImSend_MIME, x);
+      g_free(x);
+    }
+    break;
+
+  case mwImData_INVITE:
+    convo_invite(conv, &o);
+    break;
+
+  case mwImData_MULTI_START:
+    convo_multi_start(conv);
+    break;
+
+  case mwImData_MULTI_STOP:
+    convo_multi_stop(conv);
+    break;
+
+  default:
+    
+    mw_debug_mailme(&o, "unknown data message type in IM service:"
+		    " (0x%08x, 0x%08x)", type, subtype);
+  }
+
+  mwOpaque_clear(&o);
+}
+
+
+static void recv(struct mwService *srvc, struct mwChannel *chan,
+		 guint16 type, struct mwOpaque *data) {
+
+  /* - ensure message type is something we want
+     - parse message type into either mwIMText or mwIMData
+     - handle
+  */
+
+  struct mwGetBuffer *b;
+  guint32 mt;
+
+  g_return_if_fail(type == msg_MESSAGE);
+
+  b = mwGetBuffer_wrap(data);
+  guint32_get(b, &mt);
+
+  if(mwGetBuffer_error(b)) {
+    g_warning("failed to parse message for IM service");
+    mwGetBuffer_free(b);
+    return;
+  }
+
+  switch(mt) {
+  case mwIm_TEXT:
+    recv_text((struct mwServiceIm *) srvc, chan, b);
+    break;
+
+  case mwIm_DATA:
+    recv_data((struct mwServiceIm *) srvc, chan, b);
+    break;
+
+  default:
+    g_warning("unknown message type 0x%08x for IM service", mt);
+  }
+
+  if(mwGetBuffer_error(b))
+    g_warning("failed to parse message type 0x%08x for IM service", mt);
+
+  mwGetBuffer_free(b);
+}
+
+
+static void clear(struct mwServiceIm *srvc) {
+  struct mwImHandler *h;
+
+  while(srvc->convs)
+    convo_free(srvc->convs->data);
+
+  h = srvc->handler;
+  if(h && h->clear)
+    h->clear(srvc);
+  srvc->handler = NULL;
+}
+
+
+static const char *name(struct mwService *srvc) {
+  return "Instant Messaging";
+}
+
+
+static const char *desc(struct mwService *srvc) {
+  return "IM service with Standard and NotesBuddy features";
+}
+
+
+static void start(struct mwService *srvc) {
+  mwService_started(srvc);
+}
+
+
+static void stop(struct mwServiceIm *srvc) {
+
+  while(srvc->convs)
+    mwConversation_free(srvc->convs->data);
+
+  mwService_stopped(MW_SERVICE(srvc));
+}
+
+
+struct mwServiceIm *mwServiceIm_new(struct mwSession *session,
+				    struct mwImHandler *hndl) {
+
+  struct mwServiceIm *srvc_im;
+  struct mwService *srvc;
+
+  g_return_val_if_fail(session != NULL, NULL);
+  g_return_val_if_fail(hndl != NULL, NULL);
+
+  srvc_im = g_new0(struct mwServiceIm, 1);
+  srvc = &srvc_im->service;
+
+  mwService_init(srvc, session, SERVICE_IM);
+  srvc->recv_create = recv_channelCreate;
+  srvc->recv_accept = recv_channelAccept;
+  srvc->recv_destroy = recv_channelDestroy;
+  srvc->recv = recv;
+  srvc->clear = (mwService_funcClear) clear;
+  srvc->get_name = name;
+  srvc->get_desc = desc;
+  srvc->start = start;
+  srvc->stop = (mwService_funcStop) stop;
+
+  srvc_im->features = mwImClient_PLAIN;
+  srvc_im->handler = hndl;
+
+  return srvc_im;
+}
+
+
+struct mwImHandler *mwServiceIm_getHandler(struct mwServiceIm *srvc) {
+  g_return_val_if_fail(srvc != NULL, NULL);
+  return srvc->handler;
+}
+
+
+gboolean mwServiceIm_supports(struct mwServiceIm *srvc,
+			      enum mwImSendType type) {
+
+  g_return_val_if_fail(srvc != NULL, FALSE);
+
+  switch(type) {
+  case mwImSend_PLAIN:
+  case mwImSend_TYPING:
+    return TRUE;
+
+  case mwImSend_SUBJECT:
+  case mwImSend_HTML:
+  case mwImSend_MIME:
+    return srvc->features == mwImClient_NOTESBUDDY;
+
+  default:
+    return FALSE;
+  }
+}
+
+
+void mwServiceIm_setClientType(struct mwServiceIm *srvc,
+			       enum mwImClientType type) {
+
+  g_return_if_fail(srvc != NULL);
+  srvc->features = type;
+}
+
+
+enum mwImClientType mwServiceIm_getClientType(struct mwServiceIm *srvc) {
+  g_return_val_if_fail(srvc != NULL, mwImClient_UNKNOWN);
+  return srvc->features;
+}
+
+
+static int convo_sendText(struct mwConversation *conv, const char *text) {
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+  int ret;
+  
+  b = mwPutBuffer_new();
+
+  guint32_put(b, mwIm_TEXT);
+  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);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+static int convo_sendTyping(struct mwConversation *conv, gboolean typing) {
+  struct mwPutBuffer *b;
+  struct mwOpaque o = { 0, NULL };
+  int ret;
+
+  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);
+
+  mwPutBuffer_finalize(&o, b);
+  ret = mwChannel_send(conv->channel, msg_MESSAGE, &o);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+static int convo_sendMime(struct mwConversation *conv,
+			  const char *mime) {
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+  int ret;
+
+  b = mwPutBuffer_new();
+
+  guint32_put(b, mwIm_DATA);
+  guint32_put(b, mwImData_MIME);
+  guint32_put(b, 0x00);
+
+  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;
+}
+
+
+int mwConversation_send(struct mwConversation *conv, enum mwImSendType type,
+			gconstpointer msg) {
+
+  g_return_val_if_fail(conv != NULL, -1);
+  g_return_val_if_fail(mwConversation_isOpen(conv), -1);
+  g_return_val_if_fail(conv->channel != NULL, -1);
+
+  switch(type) {
+  case mwImSend_PLAIN:
+    return convo_sendText(conv, msg);
+  case mwImSend_TYPING:
+    return convo_sendTyping(conv, GPOINTER_TO_INT(msg));
+  case mwImSend_SUBJECT:
+    return convo_sendSubject(conv, msg);
+  case mwImSend_HTML:
+    return convo_sendHtml(conv, msg);
+  case mwImSend_MIME:
+    return convo_sendMime(conv, msg);
+
+  default:
+    g_warning("unsupported IM Send Type, 0x%x", type);
+    return -1;
+  }
+}
+
+
+enum mwConversationState mwConversation_getState(struct mwConversation *conv) {
+  g_return_val_if_fail(conv != NULL, mwConversation_UNKNOWN);
+  return conv->state;
+}
+
+
+struct mwServiceIm *mwConversation_getService(struct mwConversation *conv) {
+  g_return_val_if_fail(conv != NULL, NULL);
+  return conv->service;
+}
+
+
+gboolean mwConversation_supports(struct mwConversation *conv,
+				 enum mwImSendType type) {
+  g_return_val_if_fail(conv != NULL, FALSE);
+
+  switch(type) {
+  case mwImSend_PLAIN:
+  case mwImSend_TYPING:
+    return TRUE;
+
+  case mwImSend_SUBJECT:
+  case mwImSend_HTML:
+  case mwImSend_MIME:
+    return conv->features == mwImClient_NOTESBUDDY;
+
+  default:
+    return FALSE;
+  }
+}
+
+
+enum mwImClientType
+mwConversation_getClientType(struct mwConversation *conv) {
+  g_return_val_if_fail(conv != NULL, mwImClient_UNKNOWN);
+  return conv->features;
+}
+
+
+struct mwIdBlock *mwConversation_getTarget(struct mwConversation *conv) {
+  g_return_val_if_fail(conv != NULL, NULL);
+  return &conv->target;
+}
+
+
+struct mwLoginInfo *mwConversation_getTargetInfo(struct mwConversation *conv) {
+  g_return_val_if_fail(conv != NULL, NULL);
+  g_return_val_if_fail(conv->channel != NULL, NULL);
+  return mwChannel_getUser(conv->channel);
+}
+
+
+void mwConversation_setClientData(struct mwConversation *conv,
+				  gpointer data, GDestroyNotify clean) {
+  g_return_if_fail(conv != NULL);
+  mw_datum_set(&conv->client_data, data, clean);
+}
+
+
+gpointer mwConversation_getClientData(struct mwConversation *conv) {
+  g_return_val_if_fail(conv != NULL, NULL);
+  return mw_datum_get(&conv->client_data);
+}
+
+
+void mwConversation_removeClientData(struct mwConversation *conv) {
+  g_return_if_fail(conv != NULL);
+  mw_datum_clear(&conv->client_data);
+}
+
+
+void mwConversation_close(struct mwConversation *conv, guint32 reason) {
+  struct mwServiceIm *srvc;
+  struct mwImHandler *h;
+
+  g_return_if_fail(conv != NULL);
+
+  convo_set_state(conv, mwConversation_CLOSED);
+
+  srvc = conv->service;
+  g_return_if_fail(srvc != NULL);
+
+  h = srvc->handler;
+  if(h && h->conversation_closed)
+    h->conversation_closed(conv, reason);
+
+  if(conv->channel) {
+    mwChannel_destroy(conv->channel, reason, NULL);
+    conv->channel = NULL;
+  }
+}
+
+
+void mwConversation_free(struct mwConversation *conv) {
+  g_return_if_fail(conv != NULL);
+
+  if(! mwConversation_isClosed(conv))
+    mwConversation_close(conv, ERR_SUCCESS);
+
+  convo_free(conv);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/srvc_resolve.c	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,389 @@
+
+/*
+  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/ghash.h>
+
+#include "mw_channel.h"
+#include "mw_common.h"
+#include "mw_debug.h"
+#include "mw_error.h"
+#include "mw_service.h"
+#include "mw_session.h"
+#include "mw_srvc_resolve.h"
+
+
+#define PROTOCOL_TYPE  0x00000015
+#define PROTOCOL_VER   0x00000000
+
+
+/** oddly, there is only one message type in this service */
+#define RESOLVE_ACTION  0x02
+
+
+struct mwServiceResolve {
+  struct mwService service;
+
+  struct mwChannel *channel;  /**< channel for this service */
+  GHashTable *searches;       /**< guint32:struct mw_search */
+  guint32 counter;            /**< incremented to provide searche IDs */
+};
+
+
+/** structure representing an active search. keeps track of the ID,
+    the handler, and the optional user data and cleanup */
+struct mw_search {
+  struct mwServiceResolve *service;
+  guint32 id;
+  mwResolveHandler handler;
+  gpointer data;
+  GDestroyNotify cleanup;
+};
+
+
+static struct mw_search *search_new(struct mwServiceResolve *srvc,
+				    mwResolveHandler handler,
+				    gpointer data, GDestroyNotify cleanup) {
+
+  struct mw_search *search = g_new0(struct mw_search, 1);
+
+  search->service = srvc;
+  search->handler = handler;
+
+  /* we want search IDs that aren't SEARCH_ERROR */
+  do {
+    search->id = srvc->counter++;
+  } while(search->id == SEARCH_ERROR);
+
+  search->data = data;
+  search->cleanup = cleanup;
+
+  return search;
+}
+
+
+/** called whenever a mw_search is removed from the searches table of
+    the service */
+static void search_free(struct mw_search *search) {
+  g_return_if_fail(search != NULL);
+
+  if(search->cleanup)
+    search->cleanup(search->data);
+  
+  g_free(search);
+}
+
+
+static const char *get_name(struct mwService *srvc) {
+  return "Identity Resolution";
+}
+
+
+static const char *get_desc(struct mwService *srvc) {
+  return "Resolves short IDs to full IDs";
+}
+
+
+static struct mwChannel *make_channel(struct mwServiceResolve *srvc) {
+  struct mwSession *session;
+  struct mwChannelSet *cs;
+  struct mwChannel *chan;
+
+  session = mwService_getSession(MW_SERVICE(srvc));
+  cs = mwSession_getChannels(session);
+  chan = mwChannel_newOutgoing(cs);
+ 
+  mwChannel_setService(chan, MW_SERVICE(srvc));
+  mwChannel_setProtoType(chan, PROTOCOL_TYPE);
+  mwChannel_setProtoVer(chan, PROTOCOL_VER);
+
+  return mwChannel_create(chan)? NULL: chan;
+}
+
+
+static void start(struct mwServiceResolve *srvc) {
+  struct mwChannel *chan;
+
+  g_return_if_fail(srvc != NULL);
+
+  chan = make_channel(srvc);
+  if(chan) {
+    srvc->channel = chan;
+  } else {
+    mwService_stopped(MW_SERVICE(srvc));
+    return;
+  }
+
+  /* semi-lazily create the searches table */
+  srvc->searches = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+					 NULL, (GDestroyNotify) search_free);
+}
+
+
+static void stop(struct mwServiceResolve *srvc) {
+  g_return_if_fail(srvc != NULL);
+
+  if(srvc->channel) {
+    mwChannel_destroy(srvc->channel, ERR_SUCCESS, NULL);
+    srvc->channel = NULL;
+  }
+
+  /* destroy all the pending requests. */
+  g_hash_table_destroy(srvc->searches);
+  srvc->searches = NULL;
+  
+  mwService_stopped(MW_SERVICE(srvc));
+}
+
+
+static void clear(struct mwServiceResolve *srvc) {
+  if(srvc->searches) {
+    g_hash_table_destroy(srvc->searches);
+    srvc->searches = NULL;
+  }
+}
+
+
+static void recv_create(struct mwServiceResolve *srvc,
+			struct mwChannel *chan,
+			struct mwMsgChannelCreate *msg) {
+
+  /* you serve me, not the other way around */
+  mwChannel_destroy(chan, ERR_FAILURE, NULL);
+}
+
+
+static void recv_accept(struct mwServiceResolve *srvc,
+			struct mwChannel *chan,
+			struct mwMsgChannelAccept *msg) {
+  
+  g_return_if_fail(srvc != NULL);
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(chan == srvc->channel);
+
+  mwService_started(MW_SERVICE(srvc));
+}
+
+
+static void recv_destroy(struct mwServiceResolve *srvc,
+			 struct mwChannel *chan,
+			 struct mwMsgChannelDestroy *msg) {
+
+  struct mwSession *session;
+
+  g_return_if_fail(srvc != NULL);
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(chan == srvc->channel);
+
+  srvc->channel = NULL;
+  mwService_stop(MW_SERVICE(srvc));
+
+  session = mwService_getSession(MW_SERVICE(srvc));
+  g_return_if_fail(session != NULL);
+
+  mwSession_senseService(session, mwService_getType(MW_SERVICE(srvc)));
+}
+
+
+static GList *load_matches(struct mwGetBuffer *b, guint32 count) {
+  GList *matches = NULL;
+
+  while(count--) {
+    struct mwResolveMatch *m = g_new0(struct mwResolveMatch, 1);
+
+    mwString_get(b, &m->id);
+    mwString_get(b, &m->name);
+    mwString_get(b, &m->desc);
+    guint32_get(b, &m->type);
+ 
+    matches = g_list_append(matches, m);
+  }
+
+  return matches;
+}
+
+
+static GList *load_results(struct mwGetBuffer *b, guint32 count) {
+  GList *results = NULL;
+
+  while(count--) {
+    struct mwResolveResult *r = g_new0(struct mwResolveResult, 1);
+    guint32 junk, matches;
+
+    guint32_get(b, &junk);
+    guint32_get(b, &r->code);
+    mwString_get(b, &r->name);
+
+    guint32_get(b, &matches);
+    r->matches = load_matches(b, matches);
+
+    results = g_list_append(results, r);
+  }
+
+  return results;
+}
+
+
+static void free_matches(GList *matches) {
+  for(; matches; matches = g_list_delete_link(matches, matches)) {
+    struct mwResolveMatch *m = matches->data;
+    g_free(m->id);
+    g_free(m->name);
+    g_free(m->desc);
+    g_free(m);
+  }
+}
+
+
+static void free_results(GList *results) {
+  for(; results; results = g_list_delete_link(results, results)) {
+    struct mwResolveResult *r = results->data;
+    g_free(r->name);
+    free_matches(r->matches);
+    g_free(r);
+  }
+}
+
+
+static void recv(struct mwServiceResolve *srvc,
+		 struct mwChannel *chan,
+		 guint16 type, struct mwOpaque *data) {
+
+  struct mwGetBuffer *b;
+  guint32 junk, id, code, count;
+  struct mw_search *search;
+
+  g_return_if_fail(srvc != NULL);
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(chan == srvc->channel);
+  g_return_if_fail(data != NULL);
+
+  if(type != RESOLVE_ACTION) {
+    mw_debug_mailme(data, "unknown message in resolve service: 0x%04x", type);
+    return;
+  }
+
+  b = mwGetBuffer_wrap(data);
+  guint32_get(b, &junk);
+  guint32_get(b, &id);
+  guint32_get(b, &code);
+  guint32_get(b, &count);
+
+  if(mwGetBuffer_error(b)) {
+    g_warning("error parsing search result");
+    mwGetBuffer_free(b);
+    return;
+  }
+  
+  search = g_hash_table_lookup(srvc->searches, GUINT_TO_POINTER(id));
+
+  if(search) {
+    GList *results = load_results(b, count);
+    if(mwGetBuffer_error(b)) {
+      g_warning("error parsing search results");
+    } else {
+      g_debug("triggering handler");
+      search->handler(srvc, id, code, results, search->data);
+    }
+    free_results(results);
+    g_hash_table_remove(srvc->searches, GUINT_TO_POINTER(id));
+
+  } else {
+    g_debug("no search found: 0x%x", id);
+  }
+
+  mwGetBuffer_free(b);
+}
+
+
+struct mwServiceResolve *mwServiceResolve_new(struct mwSession *session) {
+  struct mwServiceResolve *srvc_resolve;
+  struct mwService *srvc;
+
+  g_return_val_if_fail(session != NULL, NULL);
+
+  srvc_resolve = g_new0(struct mwServiceResolve, 1);
+
+  srvc = MW_SERVICE(srvc_resolve);
+
+  mwService_init(srvc, session, SERVICE_RESOLVE);
+  srvc->get_name = get_name;
+  srvc->get_desc = get_desc;
+  srvc->recv_create = (mwService_funcRecvCreate) recv_create;
+  srvc->recv_accept = (mwService_funcRecvAccept) recv_accept;
+  srvc->recv_destroy = (mwService_funcRecvDestroy) recv_destroy;
+  srvc->recv = (mwService_funcRecv) recv;
+  srvc->start = (mwService_funcStart) start;
+  srvc->stop = (mwService_funcStop) stop;
+  srvc->clear = (mwService_funcClear) clear;
+
+  return srvc_resolve;
+}
+
+
+guint32 mwServiceResolve_resolve(struct mwServiceResolve *srvc,
+				 GList *queries, enum mwResolveFlag flags,
+				 mwResolveHandler handler,
+				 gpointer data, GDestroyNotify cleanup) {
+
+  struct mw_search *search;
+  struct mwPutBuffer *b;
+  struct mwOpaque o = { 0, 0 };
+  int ret, count = 0;
+
+  g_return_val_if_fail(srvc != NULL, SEARCH_ERROR);
+  g_return_val_if_fail(handler != NULL, SEARCH_ERROR);
+
+  count = g_list_length(queries);
+  g_return_val_if_fail(count > 0, SEARCH_ERROR);
+
+  search = search_new(srvc, handler, data, cleanup);
+
+  b = mwPutBuffer_new();
+  guint32_put(b, 0x00); /* to be overwritten */
+  guint32_put(b, search->id);
+  guint32_put(b, count);
+  for(; queries; queries = queries->next)
+    mwString_put(b, queries->data);
+  guint32_put(b, flags);
+
+  mwPutBuffer_finalize(&o, b);
+  
+  ret = mwChannel_send(srvc->channel, RESOLVE_ACTION, &o);
+  if(ret) {
+    search_free(search);
+    return SEARCH_ERROR;
+
+  } else {
+    g_hash_table_insert(srvc->searches,
+			GUINT_TO_POINTER(search->id), search);
+    return search->id;
+  }
+}
+
+
+void mwServiceResolve_cancelResolve(struct mwServiceResolve *srvc,
+				    guint32 id) {
+
+  g_return_if_fail(srvc != NULL);
+  g_return_if_fail(srvc->searches != NULL);
+
+  g_hash_table_remove(srvc->searches, GUINT_TO_POINTER(id));
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/srvc_store.c	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,608 @@
+
+/*
+  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/glist.h>
+
+#include "mw_channel.h"
+#include "mw_debug.h"
+#include "mw_error.h"
+#include "mw_message.h"
+#include "mw_service.h"
+#include "mw_session.h"
+#include "mw_srvc_store.h"
+
+
+#define PROTOCOL_TYPE  0x00000025
+#define PROTOCOL_VER   0x00000001
+
+
+enum storage_action {
+  action_load    = 0x0004,
+  action_loaded  = 0x0005,
+  action_save    = 0x0006,
+  action_saved   = 0x0007,
+};
+
+
+struct mwStorageUnit {
+  /** key by which data is referenced in service
+      @see mwStorageKey */
+  guint32 key;
+
+  /** Data associated with key in service */
+  struct mwOpaque data;
+};
+
+
+struct mwStorageReq {
+  guint32 id;                  /**< unique id for this request */
+  guint32 result_code;         /**< result code for completed request */
+  enum storage_action action;  /**< load or save */
+  struct mwStorageUnit *item;  /**< the key/data pair */ 
+  mwStorageCallback cb;        /**< callback to notify upon completion */
+  gpointer data;               /**< user data to pass with callback */
+  GDestroyNotify data_free;    /**< optionally frees user data */
+};
+
+
+struct mwServiceStorage {
+  struct mwService service;
+
+  /** collection of mwStorageReq */
+  GList *pending;
+
+  /** current service channel */
+  struct mwChannel *channel;
+
+  /** keep track of the counter */
+  guint32 id_counter;
+};
+
+
+static void request_get(struct mwGetBuffer *b, struct mwStorageReq *req) {
+  guint32 id, count, junk;
+
+  if(mwGetBuffer_error(b)) return;
+
+  guint32_get(b, &id);
+  guint32_get(b, &req->result_code);
+
+  if(req->action == action_loaded) {
+    guint32_get(b, &count);
+
+    if(count > 0) {
+      guint32_get(b, &junk);
+      guint32_get(b, &req->item->key);
+
+      mwOpaque_clear(&req->item->data);
+      mwOpaque_get(b, &req->item->data);
+    }
+  }
+}
+
+
+static void request_put(struct mwPutBuffer *b, struct mwStorageReq *req) {
+
+  guint32_put(b, req->id);
+  guint32_put(b, 1);
+
+  if(req->action == action_save) {
+    guint32_put(b, 20 + req->item->data.len); /* ugh, offset garbage */
+    guint32_put(b, req->item->key);
+    mwOpaque_put(b, &req->item->data);
+
+  } else {
+    guint32_put(b, req->item->key);
+  }
+}
+
+
+static int request_send(struct mwChannel *chan, struct mwStorageReq *req) {
+  struct mwPutBuffer *b;
+  struct mwOpaque o = { 0, 0 };
+  int ret;
+
+  b = mwPutBuffer_new();
+  request_put(b, req);
+
+  mwPutBuffer_finalize(&o, b);
+  ret = mwChannel_send(chan, req->action, &o);
+  mwOpaque_clear(&o);
+
+  if(! ret) {
+    if(req->action == action_save) {
+      req->action = action_saved;
+    } else if(req->action == action_load) {
+      req->action = action_loaded;
+    }
+  }
+
+  return ret;
+}
+
+
+static struct mwStorageReq *request_find(struct mwServiceStorage *srvc,
+					 guint32 id) {
+  GList *l;
+
+  for(l = srvc->pending; l; l = l->next) {
+    struct mwStorageReq *r = l->data;
+    if(r->id == id) return r;
+  }
+
+  return NULL;
+}
+
+
+static const char *action_str(enum storage_action act) {
+  switch(act) {
+  case action_load:    return "load";
+  case action_loaded:  return "loaded";
+  case action_save:    return "save";
+  case action_saved:   return "saved";
+  default:             return "UNKNOWN";
+  }
+}
+
+
+static void request_trigger(struct mwServiceStorage *srvc,
+			    struct mwStorageReq *req) {
+
+  struct mwStorageUnit *item = req->item;
+
+  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);
+  
+  if(req->cb)
+    req->cb(srvc, req->result_code, item, req->data);
+}
+
+
+static void request_free(struct mwStorageReq *req) {
+  if(req->data_free) {
+    req->data_free(req->data);
+    req->data = NULL;
+    req->data_free = NULL;
+  }
+
+  mwStorageUnit_free(req->item);
+  g_free(req);
+}
+
+
+static void request_remove(struct mwServiceStorage *srvc,
+			   struct mwStorageReq *req) {
+
+  srvc->pending = g_list_remove_all(srvc->pending, req);
+  request_free(req);
+}
+
+
+static const char *get_name(struct mwService *srvc) {
+  return "User Storage";
+}
+
+
+static const char *get_desc(struct mwService *srvc) {
+  return "Stores user data and settings on the server";
+}
+
+
+static struct mwChannel *make_channel(struct mwServiceStorage *srvc) {
+  struct mwSession *session;
+  struct mwChannelSet *cs;
+  struct mwChannel *chan;
+
+  session = mwService_getSession(MW_SERVICE(srvc));
+  cs = mwSession_getChannels(session);
+  chan = mwChannel_newOutgoing(cs);
+ 
+  mwChannel_setService(chan, MW_SERVICE(srvc));
+  mwChannel_setProtoType(chan, PROTOCOL_TYPE);
+  mwChannel_setProtoVer(chan, PROTOCOL_VER);
+
+  return mwChannel_create(chan)? NULL: chan;
+}
+
+
+static void start(struct mwService *srvc) {
+  struct mwServiceStorage *srvc_store;
+  struct mwChannel *chan;
+
+  g_return_if_fail(srvc != NULL);
+  srvc_store = (struct mwServiceStorage *) srvc;
+
+  chan = make_channel(srvc_store);
+  if(chan) {
+    srvc_store->channel = chan;
+  } else {
+    mwService_stopped(srvc);
+  }
+}
+
+
+static void stop(struct mwService *srvc) {
+
+  struct mwServiceStorage *srvc_store;
+  GList *l;
+
+  g_return_if_fail(srvc != NULL);
+  srvc_store = (struct mwServiceStorage *) srvc;
+
+  if(srvc_store->channel) {
+    mwChannel_destroy(srvc_store->channel, ERR_SUCCESS, NULL);
+    srvc_store->channel = NULL;
+  }
+
+#if 1
+  /* the new way */
+  /* remove pending requests. Sometimes we can crash the storage
+     service, and when that happens, we end up resending the killer
+     request over and over again, and the service never stays up */
+  for(l = srvc_store->pending; l; l = l->next)
+    request_free(l->data);
+
+  g_list_free(srvc_store->pending);
+  srvc_store->pending = NULL;
+
+  srvc_store->id_counter = 0;
+
+#else
+  /* the old way */
+  /* reset all of the started requests to their unstarted states */
+  for(l = srvc_store->pending; l; l = l->next) {
+    struct mwStorageReq *req = l->data;
+
+    if(req->action == action_loaded) {
+      req->action = action_load;
+    } else if(req->action == action_saved) {
+      req->action = action_save;
+    }
+  }
+#endif
+
+  mwService_stopped(srvc);
+}
+
+
+static void recv_channelAccept(struct mwService *srvc,
+			       struct mwChannel *chan,
+			       struct mwMsgChannelAccept *msg) {
+ 
+  struct mwServiceStorage *srvc_stor;
+  GList *l;
+
+  g_return_if_fail(srvc != NULL);
+  srvc_stor = (struct mwServiceStorage *) srvc;
+
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(chan == srvc_stor->channel);
+
+  /* send all pending requests */
+  for(l = srvc_stor->pending; l; l = l->next) {
+    struct mwStorageReq *req = l->data;
+
+    if(req->action == action_save || req->action == action_load) {
+      request_send(chan, req);
+    }
+  }
+
+  mwService_started(srvc);
+}
+
+
+static void recv_channelDestroy(struct mwService *srvc,
+				struct mwChannel *chan,
+				struct mwMsgChannelDestroy *msg) {
+
+  struct mwSession *session;
+  struct mwServiceStorage *srvc_stor;
+
+  g_return_if_fail(srvc != NULL);
+  g_return_if_fail(chan != NULL);
+
+  session = mwService_getSession(srvc);
+  g_return_if_fail(session != NULL);
+
+  srvc_stor = (struct mwServiceStorage *) srvc;
+  srvc_stor->channel = NULL;
+
+  mwService_stop(srvc);
+  mwSession_senseService(session, mwService_getType(srvc));
+}
+
+
+static void recv(struct mwService *srvc, struct mwChannel *chan,
+		 guint16 type, struct mwOpaque *data) {
+
+  /* process into results, trigger callbacks */
+
+  struct mwGetBuffer *b;
+  struct mwServiceStorage *srvc_stor;
+  struct mwStorageReq *req;
+  guint32 id;
+
+  g_return_if_fail(srvc != NULL);
+  srvc_stor = (struct mwServiceStorage *) srvc;
+
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(chan == srvc_stor->channel);
+  g_return_if_fail(data != NULL);
+
+  b = mwGetBuffer_wrap(data);
+
+  id = guint32_peek(b);
+  req = request_find(srvc_stor, id);
+
+  if(! req) {
+    g_warning("couldn't find request 0x%x in storage service", id);
+    mwGetBuffer_free(b);
+    return;
+  }
+
+  g_return_if_fail(req->action == type);
+  request_get(b, req);
+
+  if(mwGetBuffer_error(b)) {
+    mw_debug_mailme(data, "storage request 0x%x, type: 0x%x", id, type);
+
+  } else {
+    request_trigger(srvc_stor, req);
+  }
+
+  mwGetBuffer_free(b);
+  request_remove(srvc_stor, req);
+}
+
+
+static void clear(struct mwService *srvc) {
+  struct mwServiceStorage *srvc_stor;
+  GList *l;
+
+  srvc_stor = (struct mwServiceStorage *) srvc;
+
+  for(l = srvc_stor->pending; l; l = l->next)
+    request_free(l->data);
+
+  g_list_free(srvc_stor->pending);
+  srvc_stor->pending = NULL;
+
+  srvc_stor->id_counter = 0;
+}
+
+
+struct mwServiceStorage *mwServiceStorage_new(struct mwSession *session) {
+  struct mwServiceStorage *srvc_store;
+  struct mwService *srvc;
+
+  srvc_store = g_new0(struct mwServiceStorage, 1);
+  srvc = MW_SERVICE(srvc_store);
+
+  mwService_init(srvc, session, SERVICE_STORAGE);
+  srvc->get_name = get_name;
+  srvc->get_desc = get_desc;
+  srvc->recv_accept = recv_channelAccept;
+  srvc->recv_destroy = recv_channelDestroy;
+  srvc->recv = recv;
+  srvc->start = start;
+  srvc->stop = stop;
+  srvc->clear = clear;
+
+  return srvc_store;
+}
+
+
+struct mwStorageUnit *mwStorageUnit_new(guint32 key) {
+  struct mwStorageUnit *u;
+
+  u = g_new0(struct mwStorageUnit, 1);
+  u->key = key;
+
+  return u;
+}
+
+
+struct mwStorageUnit *mwStorageUnit_newOpaque(guint32 key,
+					      struct mwOpaque *data) {
+  struct mwStorageUnit *u;
+
+  u = g_new0(struct mwStorageUnit, 1);
+  u->key = key;
+
+  if(data)
+    mwOpaque_clone(&u->data, data);
+
+  return u;
+}
+
+
+struct mwStorageUnit *mwStorageUnit_newBoolean(guint32 key,
+					       gboolean val) {
+
+  return mwStorageUnit_newInteger(key, (guint32) val);
+}
+
+
+struct mwStorageUnit *mwStorageUnit_newInteger(guint32 key,
+					       guint32 val) {
+  struct mwStorageUnit *u;
+  struct mwPutBuffer *b;
+
+  u = g_new0(struct mwStorageUnit, 1);
+  u->key = key;
+  
+  b = mwPutBuffer_new();
+  guint32_put(b, val);
+  mwPutBuffer_finalize(&u->data, b);
+
+  return u;
+}
+
+
+struct mwStorageUnit *mwStorageUnit_newString(guint32 key,
+					      const char *str) {
+  struct mwStorageUnit *u;
+  struct mwPutBuffer *b;
+
+  u = g_new0(struct mwStorageUnit, 1);
+  u->key = key;
+
+  b = mwPutBuffer_new();
+  mwString_put(b, str);
+  mwPutBuffer_finalize(&u->data, b);
+
+  return u;
+}
+
+
+guint32 mwStorageUnit_getKey(struct mwStorageUnit *item) {
+  g_return_val_if_fail(item != NULL, 0x00); /* feh, unsafe */
+  return item->key;
+}
+
+
+gboolean mwStorageUnit_asBoolean(struct mwStorageUnit *item,
+				 gboolean val) {
+
+  return !! mwStorageUnit_asInteger(item, (guint32) val);
+}
+
+
+guint32 mwStorageUnit_asInteger(struct mwStorageUnit *item,
+				guint32 val) {
+  struct mwGetBuffer *b;
+  guint32 v;
+
+  g_return_val_if_fail(item != NULL, val);
+
+  b = mwGetBuffer_wrap(&item->data);
+
+  guint32_get(b, &v);
+  if(! mwGetBuffer_error(b)) val = v;
+  mwGetBuffer_free(b);
+
+  return val;
+}
+
+
+char *mwStorageUnit_asString(struct mwStorageUnit *item) {
+  struct mwGetBuffer *b;
+  char *c = NULL;
+
+  g_return_val_if_fail(item != NULL, NULL);
+
+  b = mwGetBuffer_wrap(&item->data);
+
+  mwString_get(b, &c);
+
+  if(mwGetBuffer_error(b))
+    g_debug("error obtaining string value from opaque");
+
+  mwGetBuffer_free(b);
+
+  return c;
+}
+
+
+struct mwOpaque *mwStorageUnit_asOpaque(struct mwStorageUnit *item) {
+  g_return_val_if_fail(item != NULL, NULL);
+  return &item->data;
+}
+
+
+void mwStorageUnit_free(struct mwStorageUnit *item) {
+  if(! item) return;
+
+  mwOpaque_clear(&item->data);
+  g_free(item);
+}
+
+
+static struct mwStorageReq *request_new(struct mwServiceStorage *srvc,
+					struct mwStorageUnit *item,
+					mwStorageCallback cb,
+					gpointer data, GDestroyNotify df) {
+
+  struct mwStorageReq *req = g_new0(struct mwStorageReq, 1);
+
+  req->id = ++srvc->id_counter;
+  req->item = item;
+  req->cb = cb;
+  req->data = data;
+  req->data_free = df;
+
+  return req;
+}
+
+
+void mwServiceStorage_load(struct mwServiceStorage *srvc,
+			   struct mwStorageUnit *item,
+			   mwStorageCallback cb,
+			   gpointer data, GDestroyNotify d_free) {
+
+  /* - construct a request
+     - put request at end of pending
+     - if channel is open and connected
+       - compose the load message
+       - send message
+       - set request to sent
+     - else
+       - start service
+  */ 
+
+  struct mwStorageReq *req;
+
+  req = request_new(srvc, item, cb, data, d_free);
+  req->action = action_load;
+
+  srvc->pending = g_list_append(srvc->pending, req);
+
+  if(MW_SERVICE_IS_STARTED(MW_SERVICE(srvc)))
+    request_send(srvc->channel, req);
+}
+
+
+void mwServiceStorage_save(struct mwServiceStorage *srvc,
+			   struct mwStorageUnit *item,
+			   mwStorageCallback cb,
+			   gpointer data, GDestroyNotify d_free) {
+
+  /* - construct a request
+     - put request at end of pending
+     - if channel is open and connected
+       - compose the save message
+       - send message
+       - set request to sent
+     - else
+       - start service
+  */
+
+  struct mwStorageReq *req;
+
+  req = request_new(srvc, item, cb, data, d_free);
+  req->action = action_save;
+
+  srvc->pending = g_list_append(srvc->pending, req);
+
+  if(MW_SERVICE_IS_STARTED(MW_SERVICE(srvc)))
+    request_send(srvc->channel, req);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/st_list.c	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,668 @@
+
+/*
+  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 <stdio.h>
+#include <string.h>
+#include <glib/gstring.h>
+
+#include "mw_debug.h"
+#include "mw_util.h"
+#include "mw_st_list.h"
+
+
+struct mwSametimeList {
+  guint ver_major;
+  guint ver_minor;
+  guint ver_micro;
+
+  GList *groups;
+};
+
+
+struct mwSametimeGroup {
+  struct mwSametimeList *list;
+
+  enum mwSametimeGroupType type;
+  char *name;
+  char *alias;
+  gboolean open;
+
+  GList *users;
+};
+
+
+struct mwSametimeUser {
+  struct mwSametimeGroup *group;
+
+  enum mwSametimeUserType type;
+  struct mwIdBlock id;
+  char *name;
+  char *alias;
+};
+
+
+static void user_free(struct mwSametimeUser *u) {
+  struct mwSametimeGroup *g;
+
+  g = u->group;
+  g->users = g_list_remove(g->users, u);
+
+  mwIdBlock_clear(&u->id);
+  g_free(u->name);
+  g_free(u->alias);
+  g_free(u);
+}
+
+
+static void group_free(struct mwSametimeGroup *g) {
+  struct mwSametimeList *l;
+
+  l = g->list;
+  l->groups = g_list_remove(l->groups, g);
+
+  while(g->users)
+    mwSametimeUser_free(g->users->data);
+
+  g_free(g->name);
+  g_free(g->alias);
+  g_free(g);
+}
+
+
+static void list_free(struct mwSametimeList *l) {
+  while(l->groups)
+    mwSametimeGroup_free(l->groups->data);
+
+  g_free(l);
+}
+
+
+struct mwSametimeList *
+mwSametimeList_new() {
+
+  struct mwSametimeList *stl;
+
+  stl = g_new0(struct mwSametimeList, 1);
+  stl->ver_major = ST_LIST_MAJOR;
+  stl->ver_minor = ST_LIST_MINOR;
+  stl->ver_micro = ST_LIST_MICRO;
+
+  return stl;
+}
+
+
+void mwSametimeList_setMajor(struct mwSametimeList *l, guint v) {
+  g_return_if_fail(l != NULL);
+  l->ver_major = v;
+}
+
+
+guint mwSametimeList_getMajor(struct mwSametimeList *l) {
+  g_return_val_if_fail(l != NULL, 0);
+  return l->ver_major;
+}
+
+
+void mwSametimeList_setMinor(struct mwSametimeList *l, guint v) {
+  g_return_if_fail(l != NULL);
+  l->ver_minor = v;
+}
+
+
+guint mwSametimeList_getMinor(struct mwSametimeList *l) {
+  g_return_val_if_fail(l != NULL, 0);
+  return l->ver_minor;
+}
+
+
+void mwSametimeList_setMicro(struct mwSametimeList *l, guint v) {
+  g_return_if_fail(l != NULL);
+  l->ver_micro = v;
+}
+
+
+guint mwSametimeList_getMicro(struct mwSametimeList *l) {
+  g_return_val_if_fail(l != NULL, 0);
+  return l->ver_micro;
+}
+
+
+GList *mwSametimeList_getGroups(struct mwSametimeList *l) {
+  g_return_val_if_fail(l != NULL, NULL);
+  return g_list_copy(l->groups);
+}
+
+
+struct mwSametimeGroup *
+mwSametimeList_findGroup(struct mwSametimeList *l,
+			 const char *name) {
+  GList *s;
+
+  g_return_val_if_fail(l != NULL, NULL);
+  g_return_val_if_fail(name != NULL, NULL);
+  g_return_val_if_fail(*name != '\0', NULL);
+
+  for(s = l->groups; s; s = s->next) {
+    struct mwSametimeGroup *g = s->data;
+    if(! strcmp(g->name, name)) return g;
+  }
+
+  return NULL;
+}
+
+
+void mwSametimeList_free(struct mwSametimeList *l) {
+  g_return_if_fail(l != NULL);
+  list_free(l);
+}
+
+
+struct mwSametimeGroup *
+mwSametimeGroup_new(struct mwSametimeList *list,
+		    enum mwSametimeGroupType type,
+		    const char *name) {
+  
+  struct mwSametimeGroup *stg;
+
+  g_return_val_if_fail(list != NULL, NULL);
+  g_return_val_if_fail(name != NULL, NULL);
+  g_return_val_if_fail(*name != '\0', NULL);
+
+  stg = g_new0(struct mwSametimeGroup, 1);
+  stg->list = list;
+  stg->type = type;
+  stg->name = g_strdup(name);
+
+  list->groups = g_list_append(list->groups, stg);
+
+  return stg;
+}
+
+
+enum mwSametimeGroupType mwSametimeGroup_getType(struct mwSametimeGroup *g) {
+  g_return_val_if_fail(g != NULL, mwSametimeGroup_UNKNOWN);
+  return g->type;
+}
+
+
+const char *mwSametimeGroup_getName(struct mwSametimeGroup *g) {
+  g_return_val_if_fail(g != NULL, NULL);
+  return g->name;
+}
+
+
+void mwSametimeGroup_setAlias(struct mwSametimeGroup *g,
+			      const char *alias) {
+  g_return_if_fail(g != NULL);
+
+  g_free(g->alias);
+  g->alias = g_strdup(alias);
+}
+
+
+const char *mwSametimeGroup_getAlias(struct mwSametimeGroup *g) {
+  g_return_val_if_fail(g != NULL, NULL);
+  return g->alias;
+}
+
+
+void mwSametimeGroup_setOpen(struct mwSametimeGroup *g, gboolean open) {
+  g_return_if_fail(g != NULL);
+  g->open = open;
+}
+
+
+gboolean mwSametimeGroup_isOpen(struct mwSametimeGroup *g) {
+  g_return_val_if_fail(g != NULL, FALSE);
+  return g->open;
+}
+
+
+struct mwSametimeList *mwSametimeGroup_getList(struct mwSametimeGroup *g) {
+  g_return_val_if_fail(g != NULL, NULL);
+  return g->list;
+}
+
+
+GList *mwSametimeGroup_getUsers(struct mwSametimeGroup *g) {
+  g_return_val_if_fail(g != NULL, NULL);
+  return g_list_copy(g->users);
+}
+
+
+struct mwSametimeUser *
+mwSametimeGroup_findUser(struct mwSametimeGroup *g,
+			 struct mwIdBlock *user) {
+  GList *s;
+
+  g_return_val_if_fail(g != NULL, NULL);
+  g_return_val_if_fail(user != NULL, NULL);
+
+  for(s = g->users; s; s = s->next) {
+    struct mwSametimeUser *u = s->data;
+    if(mwIdBlock_equal(user, &u->id)) return u;
+  }
+
+  return NULL;
+}
+
+
+void mwSametimeGroup_free(struct mwSametimeGroup *g) {
+  g_return_if_fail(g != NULL);
+  g_return_if_fail(g->list != NULL);
+  group_free(g);
+}
+
+
+struct mwSametimeUser *
+mwSametimeUser_new(struct mwSametimeGroup *group,
+		   enum mwSametimeUserType type,
+		   struct mwIdBlock *id) {
+
+  struct mwSametimeUser *stu;
+
+  g_return_val_if_fail(group != NULL, NULL);
+  g_return_val_if_fail(id != NULL, NULL);
+  
+  stu = g_new0(struct mwSametimeUser, 1);
+  stu->group = group;
+  stu->type = type;
+  mwIdBlock_clone(&stu->id, id);
+
+  group->users = g_list_append(group->users, stu);
+  
+  return stu;
+}
+
+
+struct mwSametimeGroup *mwSametimeUser_getGroup(struct mwSametimeUser *u) {
+  g_return_val_if_fail(u != NULL, NULL);
+  return u->group;
+}
+
+
+enum mwSametimeUserType mwSametimeUser_getType(struct mwSametimeUser *u) {
+  g_return_val_if_fail(u != NULL, mwSametimeUser_UNKNOWN);
+  return u->type;
+}
+
+
+const char *mwSametimeUser_getUser(struct mwSametimeUser *u) {
+  g_return_val_if_fail(u != NULL, NULL);
+  return u->id.user;
+}
+
+
+const char *mwSametimeUser_getCommunity(struct mwSametimeUser *u) {
+  g_return_val_if_fail(u != NULL, NULL);
+  return u->id.community;
+}
+
+
+void mwSametimeUser_setShortName(struct mwSametimeUser *u, const char *name) {
+  g_return_if_fail(u != NULL);
+  g_free(u->name);
+  u->name = g_strdup(name);
+}
+
+
+const char *mwSametimeUser_getShortName(struct mwSametimeUser *u) {
+  g_return_val_if_fail(u != NULL, NULL);
+  return u->name;
+}
+
+
+void mwSametimeUser_setAlias(struct mwSametimeUser *u, const char *alias) {
+  g_return_if_fail(u != NULL);
+  g_free(u->alias);
+  u->alias = g_strdup(alias);
+}
+
+
+const char *mwSametimeUser_getAlias(struct mwSametimeUser *u) {
+  g_return_val_if_fail(u != NULL, NULL);
+  return u->alias;
+}
+
+
+void mwSametimeUser_free(struct mwSametimeUser *u) {
+  g_return_if_fail(u != NULL);
+  g_return_if_fail(u->group != NULL);
+  user_free(u);
+}
+
+
+static void str_replace(char *str, char from, char to) {
+  if(! str) return;
+  for(; *str; str++) if(*str == from) *str = to;
+}
+
+
+static char user_type_to_char(enum mwSametimeUserType type) {
+  switch(type) {
+  case mwSametimeUser_NORMAL:    return '1';
+  case mwSametimeUser_EXTERNAL:  return '2';
+  case mwSametimeUser_UNKNOWN:
+  default:                       return '9';
+  }
+}
+
+
+static enum mwSametimeUserType user_char_to_type(char type) {
+  switch(type) {
+  case '1':  return mwSametimeUser_NORMAL;
+  case '2':  return mwSametimeUser_EXTERNAL;
+  default:   return mwSametimeUser_UNKNOWN;
+  }
+}
+
+
+static void user_put(GString *str, struct mwSametimeUser *u) {
+  char *id, *name, *alias;
+  char type;
+  
+  id = g_strdup(u->id.user);
+  name = g_strdup(u->name);
+  alias = g_strdup(u->alias);
+  type = user_type_to_char(u->type);
+
+  if(id) str_replace(id, ' ', ';');
+  if(name) str_replace(name, ' ', ';');
+  if(alias) str_replace(alias, ' ', ';');
+
+  if(!name && alias) {
+    name = alias;
+    alias = NULL;
+  }
+
+  g_string_append_printf(str, "U %s%c:: %s,%s\r\n",
+			 id, type, (name? name: ""), (alias? alias: ""));
+
+  g_free(id);
+  g_free(name);
+  g_free(alias);  
+}
+
+
+static char group_type_to_char(enum mwSametimeGroupType type) {
+  switch(type) {
+  case mwSametimeGroup_NORMAL:   return '2';
+  case mwSametimeGroup_DYNAMIC:  return '3';
+  case mwSametimeGroup_UNKNOWN:
+  default:                       return '9';
+  }
+}
+
+
+static enum mwSametimeGroupType group_char_to_type(char type) {
+  switch(type) {
+  case '2':  return mwSametimeGroup_NORMAL;
+  case '3':  return mwSametimeGroup_DYNAMIC;
+  default:   return mwSametimeGroup_UNKNOWN;
+  }
+}
+
+
+static void group_put(GString *str, struct mwSametimeGroup *g) {
+  char *name, *alias;
+  char type;
+  GList *gl;
+
+  name = g_strdup(g->name);
+  alias = g_strdup((g->alias)? g->alias: name);
+  type = group_type_to_char(g->type);
+
+  str_replace(name, ' ', ';');
+  str_replace(alias, ' ', ';');
+
+  g_string_append_printf(str, "G %s%c %s %c\r\n",
+			 name, type, alias, (g->open? 'O':'C'));
+
+  for(gl = g->users; gl; gl = gl->next) {
+    user_put(str, gl->data);
+  }
+
+  g_free(name);
+  g_free(alias);
+}
+
+
+/** composes a GString with the written contents of a sametime list */
+static GString *list_store(struct mwSametimeList *l) {
+  GString *str;
+  GList *gl;
+
+  g_return_val_if_fail(l != NULL, NULL);
+
+  str = g_string_new(NULL);
+  g_string_append_printf(str, "Version=%u.%u.%u\r\n",
+			 l->ver_major, l->ver_minor, l->ver_micro);
+
+  for(gl = l->groups; gl; gl = gl->next) {
+    group_put(str, gl->data);
+  }
+
+  return str;
+}
+
+
+char *mwSametimeList_store(struct mwSametimeList *l) {
+  GString *str;
+  char *s;
+
+  g_return_val_if_fail(l != NULL, NULL);
+
+  str = list_store(l);
+  s = str->str;
+  g_string_free(str, FALSE);
+  return s;
+}
+
+
+void mwSametimeList_put(struct mwPutBuffer *b, struct mwSametimeList *l) {
+  GString *str;
+  guint16 len;
+
+  g_return_if_fail(l != NULL);
+  g_return_if_fail(b != NULL);
+
+  str = list_store(l);
+  len = (guint16) str->len;
+  guint16_put(b, len);
+  mwPutBuffer_write(b, str->str, len);
+
+  g_string_free(str, TRUE);
+}
+
+
+static void get_version(const char *line, struct mwSametimeList *l) {
+  guint major = 0, minor = 0, micro = 0;
+  int ret;
+
+  ret = sscanf(line, "Version=%u.%u.%u\n", &major, &minor, &micro);
+  if(ret != 3) {
+    g_warning("strange sametime list version line:\n%s", line);
+  }
+
+  l->ver_major = major;
+  l->ver_minor = minor;
+  l->ver_micro = micro;
+}
+
+
+static struct mwSametimeGroup *get_group(const char *line,
+					 struct mwSametimeList *l) {
+  struct mwSametimeGroup *group;
+  char *name, *alias;
+  char type = '2', open = 'O';
+  int ret;
+
+  ret = strlen(line);
+  name = g_malloc0(ret);
+  alias = g_malloc0(ret);
+
+  ret = sscanf(line, "G %s %s %c\n",
+	       name, alias, &open);
+
+  if(ret < 3) {
+    g_warning("strange sametime list group line:\n%s", line);
+  }
+  
+  str_replace(name, ';', ' ');
+  str_replace(alias, ';', ' ');
+
+  if(name && *name) {
+    int l = strlen(name)-1;
+    type = name[l];
+    name[l] = '\0';
+  }
+
+  group = g_new0(struct mwSametimeGroup, 1);
+  group->list = l;
+  group->name = name;
+  group->type = group_char_to_type(type);
+  group->alias = alias;
+  group->open = (open == 'O');
+
+  l->groups = g_list_append(l->groups, group);
+
+  return group;
+}
+
+
+static void get_user(const char *line, struct mwSametimeGroup *g) {
+  struct mwSametimeUser *user;
+  struct mwIdBlock idb = { 0, 0 };
+  char *name, *alias = NULL;
+  char type = '1';
+  int ret;
+  
+  ret = strlen(line);
+  idb.user = g_malloc0(ret);
+  name = g_malloc0(ret);
+
+  ret = sscanf(line, "U %s %s",
+	       idb.user, name);
+
+  if(ret < 2) {
+    g_warning("strange sametime list user line:\n%s", line);
+  }
+
+  str_replace(idb.user, ';', ' ');
+  str_replace(name, ';', ' ');
+
+  if(idb.user && *idb.user) {
+    int l = strlen(idb.user) - 3;
+    type = idb.user[l];
+    idb.user[l] = '\0';
+  }
+
+  if(name && *name) {
+    char *tmp;
+
+    tmp = strrchr(name, ',');
+    if(tmp) {
+      *tmp++ = '\0';
+      if(*tmp) alias = tmp;
+    }
+  }
+
+  user = g_new0(struct mwSametimeUser, 1);
+  user->group = g;
+  user->id.user = idb.user;
+  user->type = user_char_to_type(type);
+  user->name = name;
+  user->alias = g_strdup(alias);
+  
+  g->users = g_list_append(g->users, user);
+}
+
+
+/** returns a line from str, and advances str */
+static char *fetch_line(char **str) {
+  char *start = *str;
+  char *end;
+
+  /* move to first non-whitespace character */
+  while(*start && g_ascii_isspace(*start)) start++;
+  if(! *start) return NULL;
+
+  for(end = start + 1; *end; end++) {
+    if(*end == '\n' || *end == '\r') {
+      *(end++) = '\0';
+      break;
+    }
+  }
+
+  *str = end;
+  return start;
+}
+
+
+void list_get(const char *lines, struct mwSametimeList *l) {
+  char *s = (char *) lines;
+  char *line;
+
+  struct mwSametimeGroup *g = NULL;
+  
+  while( (line = fetch_line(&s)) ) {
+    switch(*line) {
+    case 'V':
+      get_version(line, l);
+      break;
+
+    case 'G':
+      g = get_group(line, l);
+      break;
+
+    case 'U':
+      get_user(line, g);
+      break;
+
+    default:
+      g_warning("unknown sametime list data line:\n%s", line);
+    }
+  }  
+}
+
+
+struct mwSametimeList *mwSametimeList_load(const char *data) {
+  struct mwSametimeList *l;
+
+  g_return_val_if_fail(data != NULL, NULL);
+  
+  l = mwSametimeList_new();
+  list_get(data, l);
+
+  return l;
+}
+
+
+void mwSametimeList_get(struct mwGetBuffer *b, struct mwSametimeList *l) {
+  char *str = NULL;
+
+  g_return_if_fail(l != NULL);
+  g_return_if_fail(b != NULL);
+
+  mwString_get(b, &str);
+  list_get(str, l);
+  g_free(str);
+}
+