--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/sametime/meanwhile/channel.c Fri Jan 20 00:19:53 2006 +0000 @@ -0,0 +1,961 @@ + +/* + 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; + guint16 offered_policy; /**< @see enum mwEncryptPolicy */ + guint16 policy; /**< @see enum mwEncryptPolicy */ + + /** 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, + guint32 err_code) { + + g_return_if_fail(chan != NULL); + + if(chan->state == state) return; + + chan->state = state; + + if(err_code) { + g_message("channel 0x%08x state: %s (0x%08x)", + chan->id, state_str(state), err_code); + } else { + g_message("channel 0x%08x state: %s", chan->id, state_str(state)); + } +} + + +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, 0); + + 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, 0); + + 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; +} + + +guint16 mwChannel_getEncryptPolicy(struct mwChannel *chan) { + g_return_val_if_fail(chan != NULL, 0x00); + return chan->policy; +} + + +guint32 mwChannel_getOptions(struct mwChannel *chan) { + g_return_val_if_fail(chan != NULL, 0x00); + return chan->options; +} + + +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); + + list = mwChannel_getSupportedCipherInstances(chan); + if(list) { + /* offer what we have */ + for(l = list; l; l = l->next) { + struct mwEncryptItem *ei = mwCipherInstance_offer(l->data); + msg->encrypt.items = g_list_append(msg->encrypt.items, ei); + } + + /* we're easy to get along with */ + chan->offered_policy = mwEncrypt_WHATEVER; + g_list_free(list); + + } else { + /* we apparently don't support anything */ + chan->offered_policy = mwEncrypt_NONE; + } + + msg->encrypt.mode = chan->offered_policy; + msg->encrypt.extra = chan->offered_policy; + + ret = mwSession_send(chan->session, MW_MESSAGE(msg)); + mwMessage_free(MW_MESSAGE(msg)); + + state(chan, (ret)? mwChannel_ERROR: mwChannel_WAIT, ret); + + return ret; +} + + +static void channel_open(struct mwChannel *chan) { + state(chan, mwChannel_OPEN, 0); + timestamp_stat(chan, mwChannelStat_OPENED_AT); + flush_channel(chan); +} + + +int mwChannel_accept(struct mwChannel *chan) { + struct mwSession *session; + struct mwMsgChannelAccept *msg; + struct mwCipherInstance *ci; + + int ret; + + g_return_val_if_fail(chan != NULL, -1); + 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); + + ci = chan->cipher; + + if(! ci) { + /* automatically select a cipher if one hasn't been already */ + + switch(chan->offered_policy) { + case mwEncrypt_NONE: + mwChannel_selectCipherInstance(chan, NULL); + break; + + case mwEncrypt_RC2_40: + ci = get_supported(chan, mwCipher_RC2_40); + mwChannel_selectCipherInstance(chan, ci); + break; + + case mwEncrypt_RC2_128: + ci = get_supported(chan, mwCipher_RC2_128); + mwChannel_selectCipherInstance(chan, ci); + break; + + case mwEncrypt_WHATEVER: + case mwEncrypt_ALL: + default: + { + GList *l, *ll; + + l = mwChannel_getSupportedCipherInstances(chan); + if(l) { + /* nobody selected a cipher, so we'll just pick the last in + the list of available ones */ + for(ll = l; ll->next; ll = ll->next); + ci = ll->data; + g_list_free(l); + + mwChannel_selectCipherInstance(chan, ci); + + } else { + /* this may cause breakage, but there's really nothing else + we can do. They want something we can't provide. If they + don't like it, then they'll error the channel out */ + mwChannel_selectCipherInstance(chan, NULL); + } + } + } + } + + msg->encrypt.mode = chan->policy; /* set in selectCipherInstance */ + msg->encrypt.extra = chan->offered_policy; + + if(chan->cipher) { + msg->encrypt.item = mwCipherInstance_accept(chan->cipher); + } + + ret = mwSession_send(session, MW_MESSAGE(msg)); + mwMessage_free(MW_MESSAGE(msg)); + + if(ret) { + state(chan, mwChannel_ERROR, ret); + } 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, reason); + + 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; + } + + chan->offered_policy = msg->encrypt.mode; + g_message("channel offered with encrypt policy 0x%04x", chan->policy); + + for(list = msg->encrypt.items; list; list = list->next) { + struct mwEncryptItem *ei = list->data; + struct mwCipher *cipher; + 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; + } + + chan->policy = msg->encrypt.mode; + g_message("channel accepted with encrypt policy 0x%04x", chan->policy); + + if(! msg->encrypt.mode || ! msg->encrypt.item) { + /* no mode or no item means no encryption */ + mwChannel_selectCipherInstance(chan, NULL); + + } else { + 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, 0); + + /* 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, msg->reason); + + 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)); + + switch(mwCipher_getType(c)) { + case mwCipher_RC2_40: + chan->policy = mwEncrypt_RC2_40; + break; + + case mwCipher_RC2_128: + chan->policy = mwEncrypt_RC2_128; + break; + + default: + /* unsure if this is bad */ + chan->policy = mwEncrypt_WHATEVER; + } + + g_message("channel 0x%08x selected cipher %s", + chan->id, NSTR(mwCipher_getName(c))); + + } else { + + chan->policy = mwEncrypt_NONE; + g_message("channel 0x%08x selected no cipher", chan->id); + } + + g_hash_table_destroy(chan->supported); + chan->supported = NULL; +} + +