diff --git a/samples/logging_proxy.c b/samples/logging_proxy.c index 26a7d96..dc22740 100644 --- a/samples/logging_proxy.c +++ b/samples/logging_proxy.c @@ -25,7 +25,6 @@ #include #include -#include #include #include diff --git a/samples/logging_proxy.c.fix-glib-headers b/samples/logging_proxy.c.fix-glib-headers new file mode 100644 index 0000000..26a7d96 --- /dev/null +++ b/samples/logging_proxy.c.fix-glib-headers @@ -0,0 +1,1004 @@ + +/* + Logging Sametime Proxy Utility + The Meanwhile Project + + This is a tool which can act as a proxy between a client and a + sametime server, which will log all messages to stdout. It will also + munge channel creation messages in order to be able to decrypt any + encrypted data sent over a channel, and will log decrypted chunks to + stdout as well. This makes reverse-engineering of services much, + much easier. + + The idea is simple, but the implementation made my head hurt. + + Christopher O'Brien +*/ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + + +/** one side of the proxy (either the client side or the server + side). The forward method for one should push data into the socket + of the other. */ +struct proxy_side { + int sock; + GIOChannel *chan; + gint chan_io; + + guchar *buf; + gsize buf_size; + gsize buf_recv; + + void (*forward)(const guchar *buf, gsize len); +}; + + +static struct proxy_side client; /**< side facing the client */ +static struct proxy_side server; /**< side facing the server */ + + +static char *host = NULL; +static int client_port = 0; +static int server_port = 0; + + +static int counter = 0; +static int listen_sock = 0; +static GIOChannel *listen_chan = NULL; +static gint listen_io = 0; + + + + + +/** given one side, get the other */ +#define OTHER_SIDE(side) \ + ((side == &client)? &server: &client) + + +/** encryption state information used in the RC2/40 cipher */ +struct rc2_40enc { + guchar outgoing_iv[8]; + int outgoing_key[64]; + guchar incoming_iv[8]; + int incoming_key[64]; +}; + + +/* re-usable rc2 40 stuff */ +static int session_key[64] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + + +/** encryption state information used in the RC2/128 cipher */ +struct rc2_128enc { + guchar outgoing_iv[8]; + guchar incoming_iv[8]; + int shared_key[64]; +}; + + +/* re-usable rc2 128 stuff */ +static struct mwMpi *private_key; +static struct mwOpaque public_key; + + +/** represents a channel. The channel has a left side and a right + side. The left side is the creator of the channel. The right side + is the target of the channel. Each side has its own encryption + state information, so an incoming message from either side can + be decrypted, then re-encrypted for the other side. */ +struct channel { + guint32 id; + + /* login id of creator or NULL if created by client side */ + char *creator; + + /* the offer from the left side */ + struct mwEncryptOffer offer; + + /** the mode of encryption */ + enum { + enc_none = 0, /**< nothing encrypted */ + enc_easy, /**< easy (rc2/40) encryption */ + enc_hard, /**< hard (rc2/128) encryption */ + } enc_mode; + + /** encryption data for the left side */ + union { + struct rc2_40enc easy; + struct rc2_128enc hard; + } left_enc; + + /** encryption data for the right side */ + union { + struct rc2_40enc easy; + struct rc2_128enc hard; + } right_enc; + + struct proxy_side *left; /**< proxy side acting as the left side */ + struct proxy_side *right; /**< proxy side acting as the right side */ +}; + + +/* collection of channels */ +static GHashTable *channels; + + +#define PUT_CHANNEL(chan) \ + g_hash_table_insert(channels, GUINT_TO_POINTER((chan)->id), (chan)) + +#define GET_CHANNEL(id) \ + g_hash_table_lookup(channels, GUINT_TO_POINTER(id)) + +#define REMOVE_CHANNEL(id) \ + g_hash_table_remove(channels, GUINT_TO_POINTER(id)) + + +/** print a message to stdout and use hexdump to print a data chunk */ +static void hexdump_vprintf(const guchar *buf, gsize len, + const char *txt, va_list args) { + FILE *fp; + + if(txt) { + fputc('\n', stdout); + vfprintf(stdout, txt, args); + fputc('\n', stdout); + } + fflush(stdout); + + fp = popen("hexdump -C", "w"); + fwrite(buf, len, 1, fp); + fflush(fp); + pclose(fp); +} + + +/** print a message to stdout and use hexdump to print a data chunk */ +static void hexdump_printf(const guchar *buf, gsize len, + const char *txt, ...) { + va_list args; + va_start(args, txt); + hexdump_vprintf(buf, len, txt, args); + va_end(args); +} + + +/** serialize a message for sending */ +static void put_msg(struct mwMessage *msg, struct mwOpaque *o) { + struct mwPutBuffer *b; + + b = mwPutBuffer_new(); + mwMessage_put(b, msg); + mwPutBuffer_finalize(o, b); + + b = mwPutBuffer_new(); + mwOpaque_put(b, o); + mwOpaque_clear(o); + mwPutBuffer_finalize(o, b); +} + + +static void key_copy(int to[64], int from[64]) { + int i = 64; + while(i--) to[i] = from[i]; +} + + +/* we don't want to be redirected away from the proxy, so eat any + redirect messages from the server and respond with a login cont */ +static void munge_redir() { + struct mwMessage *msg; + struct mwOpaque o = { 0, 0 }; + + msg = mwMessage_new(mwMessage_LOGIN_CONTINUE); + put_msg(msg, &o); + mwMessage_free(msg); + + server.forward(o.data, o.len); + + mwOpaque_clear(&o); +} + + +/* handle receipt of channel create messages from either side, + recording the offered ciphers, and munging it to instead include + our own key as applicable, then sending it on */ +static void munge_create(struct proxy_side *side, + struct mwMsgChannelCreate *msg) { + + struct mwOpaque o = { 0, 0 }; + GList *l; + struct channel *c; + + /* create a new channel on the side */ + c = g_new0(struct channel, 1); + c->id = msg->channel; + c->left = side; + c->right = OTHER_SIDE(side); + + if(msg->creator_flag) { + c->creator = g_strdup(msg->creator.login_id); + } + + /* record the mode and encryption items */ + c->offer.mode = msg->encrypt.mode; + c->offer.items = msg->encrypt.items; + c->offer.extra = msg->encrypt.extra; + c->offer.flag = msg->encrypt.flag; + + PUT_CHANNEL(c); + + /* replace the encryption items with our own as applicable */ + if(msg->encrypt.items) { + l = msg->encrypt.items; + msg->encrypt.items = NULL; /* steal them */ + + for(; l; l = l->next) { + struct mwEncryptItem *i1, *i2; + + /* the original we've stolen */ + i1 = l->data; + + /* the munged replacement */ + i2 = g_new0(struct mwEncryptItem, 1); + i2->id = i1->id; + + switch(i1->id) { + case mwCipher_RC2_128: + printf("munging an offered RC2/128\n"); + mwOpaque_clone(&i2->info, &public_key); + break; + case mwCipher_RC2_40: + printf("munging an offered RC2/40\n"); + default: + ; + } + + msg->encrypt.items = g_list_append(msg->encrypt.items, i2); + } + } + + put_msg(MW_MESSAGE(msg), &o); + side->forward(o.data, o.len); + mwOpaque_clear(&o); +} + + +/* find an enc item by id in a list of items */ +struct mwEncryptItem *find_item(GList *items, guint16 id) { + GList *ltmp; + for(ltmp = items; ltmp; ltmp = ltmp->next) { + struct mwEncryptItem *i = ltmp->data; + if(i->id == id) return i; + } + return NULL; +} + + +/* handle acceptance of a channel */ +static void munge_accept(struct proxy_side *side, + struct mwMsgChannelAccept *msg) { + + struct mwOpaque o = {0,0}; + struct channel *chan; + struct mwEncryptItem *item; + + chan = GET_CHANNEL(msg->head.channel); + item = msg->encrypt.item; + + if(! item) { + /* cut to the chase */ + put_msg(MW_MESSAGE(msg), &o); + side->forward(o.data, o.len); + mwOpaque_clear(&o); + return; + } + + /* init right-side encryption with our enc and accepted enc */ + switch(item->id) { + case mwCipher_RC2_128: { + struct mwMpi *remote, *shared; + struct mwOpaque k; + + remote = mwMpi_new(); + shared = mwMpi_new(); + + printf("right side accepted RC2/128\n"); + + mwMpi_import(remote, &item->info); + mwMpi_calculateDHShared(shared, remote, private_key); + mwMpi_export(shared, &k); + + chan->enc_mode = enc_hard; + + mwIV_init(chan->right_enc.hard.outgoing_iv); + mwIV_init(chan->right_enc.hard.incoming_iv); + mwKeyExpand(chan->right_enc.hard.shared_key, k.data+(k.len-16), 16); + + mwMpi_free(remote); + mwMpi_free(shared); + mwOpaque_clear(&k); + break; + } + case mwCipher_RC2_40: { + char *who; + + printf("right side accepted RC2/40\n"); + + chan->enc_mode = enc_easy; + + mwIV_init(chan->right_enc.easy.outgoing_iv); + mwIV_init(chan->right_enc.easy.incoming_iv); + + if(msg->acceptor_flag) { + who = msg->acceptor.login_id; + printf("right side is the server\n"); + printf("server is %s\n", who); + mwKeyExpand(chan->right_enc.easy.incoming_key, (guchar *) who, 5); + key_copy(chan->right_enc.easy.outgoing_key, session_key); + + } else { + who = chan->creator; + printf("right side is the client\n"); + printf("server is %s\n", who); + key_copy(chan->right_enc.easy.incoming_key, session_key); + mwKeyExpand(chan->right_enc.easy.outgoing_key, (guchar *) who, 5); + } + + break; + } + default: + chan->enc_mode = enc_none; + break; + } + + /* init left-side encryption with offered enc and our enc, munge accept */ + switch(item->id) { + case mwCipher_RC2_128: { + struct mwMpi *remote, *shared; + struct mwOpaque k; + struct mwEncryptItem *offered; + + remote = mwMpi_new(); + shared = mwMpi_new(); + + printf("accepting left side with RC2/128\n"); + + offered = find_item(chan->offer.items, mwCipher_RC2_128); + mwMpi_import(remote, &offered->info); + mwMpi_calculateDHShared(shared, remote, private_key); + mwMpi_export(shared, &k); + + mwIV_init(chan->left_enc.hard.outgoing_iv); + mwIV_init(chan->left_enc.hard.incoming_iv); + mwKeyExpand(chan->left_enc.hard.shared_key, k.data+(k.len-16), 16); + + mwMpi_free(remote); + mwMpi_free(shared); + mwOpaque_clear(&k); + + /* munge accept with out public key */ + mwOpaque_clear(&item->info); + mwOpaque_clone(&item->info, &public_key); + break; + } + case mwCipher_RC2_40: + printf("accepting left side with RC2/40\n"); + + mwIV_init(chan->left_enc.easy.outgoing_iv); + mwIV_init(chan->left_enc.easy.incoming_iv); + + key_copy(chan->left_enc.easy.outgoing_key, + chan->right_enc.easy.incoming_key); + + key_copy(chan->left_enc.easy.incoming_key, + chan->right_enc.easy.outgoing_key); + break; + + default: + ; + } + + put_msg(MW_MESSAGE(msg), &o); + side->forward(o.data, o.len); + mwOpaque_clear(&o); +} + + +static void dec(struct channel *chan, struct proxy_side *side, + struct mwOpaque *to, struct mwOpaque *from) { + + switch(chan->enc_mode) { + case enc_easy: { + if(chan->left == side) { + /* left side decrypt */ + mwDecryptExpanded(chan->left_enc.easy.incoming_key, + chan->left_enc.easy.incoming_iv, + from, to); + } else { + /* right side decrypt */ + mwDecryptExpanded(chan->right_enc.easy.incoming_key, + chan->right_enc.easy.incoming_iv, + from, to); + } + break; + } + case enc_hard: { + if(chan->left == side) { + /* left side decrypt */ + mwDecryptExpanded(chan->left_enc.hard.shared_key, + chan->left_enc.hard.incoming_iv, + from, to); + } else { + /* right side decrypt */ + mwDecryptExpanded(chan->right_enc.hard.shared_key, + chan->right_enc.hard.incoming_iv, + from, to); + } + break; + } + } +} + + +static void enc(struct channel *chan, struct proxy_side *side, + struct mwOpaque *to, struct mwOpaque *from) { + + switch(chan->enc_mode) { + case enc_easy: { + if(chan->left == side) { + /* left side encrypt */ + mwEncryptExpanded(chan->left_enc.easy.outgoing_key, + chan->left_enc.easy.outgoing_iv, + from, to); + } else { + /* right side encrypt */ + mwEncryptExpanded(chan->right_enc.easy.outgoing_key, + chan->right_enc.easy.outgoing_iv, + from, to); + } + break; + } + case enc_hard: { + if(chan->left == side) { + /* left side encrypt */ + mwEncryptExpanded(chan->left_enc.hard.shared_key, + chan->left_enc.hard.outgoing_iv, + from, to); + } else { + /* right side encrypt */ + mwEncryptExpanded(chan->right_enc.hard.shared_key, + chan->right_enc.hard.outgoing_iv, + from, to); + } + break; + } + } +} + + +static void munge_channel(struct proxy_side *side, + struct mwMsgChannelSend *msg) { + + struct mwOpaque o = {0,0}; + + if(msg->head.options & mwMessageOption_ENCRYPT) { + struct mwOpaque d = {0,0}; + struct channel *chan; + + chan = GET_CHANNEL(msg->head.channel); + + /* decrypt from side */ + dec(chan, side, &d, &msg->data); + + /* display */ + hexdump_printf(d.data, d.len, "decrypted channel message data:", + msg->type); + + /* encrypt to other side */ + mwOpaque_clear(&msg->data); + enc(chan, OTHER_SIDE(side), &msg->data, &d); + mwOpaque_clear(&d); + } + + /* send to other side */ + put_msg(MW_MESSAGE(msg), &o); + side->forward(o.data, o.len); + mwOpaque_clear(&o); +} + + +/* handle destruction of a channel */ +static void handle_destroy(struct proxy_side *side, + struct mwMsgChannelDestroy *msg) { + + struct channel *chan; + GList *l; + + chan = GET_CHANNEL(msg->head.channel); + REMOVE_CHANNEL(msg->head.channel); + + if(! chan) return; + + for(l = chan->offer.items; l; l = l->next) { + mwEncryptItem_free(l->data); + } + g_list_free(chan->offer.items); + chan->offer.items = NULL; + + g_free(chan->creator); + chan->creator = NULL; + + g_free(chan); +} + + +static void forward(struct proxy_side *to, + struct mwOpaque *data) { + + struct mwPutBuffer *pb = mwPutBuffer_new(); + struct mwOpaque po = { 0, 0 }; + + mwOpaque_put(pb, data); + mwPutBuffer_finalize(&po, pb); + to->forward(po.data, po.len); + mwOpaque_clear(&po); +} + + +/* handle messages from either side */ +static void side_process(struct proxy_side *s, const guchar *buf, gsize len) { + struct mwOpaque o = { .len = len, .data = (guchar *) buf }; + struct mwGetBuffer *b; + guint16 type; + + if(! len) return; + + if(s == &server) { + hexdump_printf(buf, len, "server -> client"); + } else { + hexdump_printf(buf, len, "client -> server"); + } + + b = mwGetBuffer_wrap(&o); + type = guint16_peek(b); + + switch(type) { + case mwMessage_LOGIN_ACK: { + struct mwMsgLoginAck *msg = (struct mwMsgLoginAck *) mwMessage_get(b); + printf("client is %s\n", msg->login.login_id); + mwKeyExpand(session_key, (guchar *) msg->login.login_id, 5); + mwMessage_free(MW_MESSAGE(msg)); + forward(s, &o); + break; + } + + case mwMessage_LOGIN_REDIRECT: { + munge_redir(); + break; + } + + case mwMessage_CHANNEL_CREATE: { + struct mwMessage *msg = mwMessage_get(b); + munge_create(s, (struct mwMsgChannelCreate *) msg); + mwMessage_free(msg); + break; + } + + case mwMessage_CHANNEL_ACCEPT: { + struct mwMessage *msg = mwMessage_get(b); + munge_accept(s, (struct mwMsgChannelAccept *) msg); + mwMessage_free(msg); + break; + } + + case mwMessage_CHANNEL_DESTROY: { + struct mwMessage *msg = mwMessage_get(b); + handle_destroy(s, (struct mwMsgChannelDestroy *) msg); + mwMessage_free(msg); + forward(s, &o); + break; + } + + case mwMessage_CHANNEL_SEND: { + struct mwMessage *msg = mwMessage_get(b); + munge_channel(s, (struct mwMsgChannelSend *) msg); + mwMessage_free(msg); + break; + } + + default: + forward(s, &o); + } + + mwGetBuffer_free(b); +} + + +/** clean up a proxy side's inner buffer */ +static void side_buf_free(struct proxy_side *s) { + g_free(s->buf); + s->buf = NULL; + s->buf_size = 0; + s->buf_recv = 0; +} + + +#define ADVANCE(b, n, count) { b += count; n -= count; } + + +/** handle input to complete an existing buffer */ +static gsize side_recv_cont(struct proxy_side *s, const guchar *b, gsize n) { + + gsize x = s->buf_size - s->buf_recv; + + if(n < x) { + memcpy(s->buf+s->buf_recv, b, n); + s->buf_recv += n; + return 0; + + } else { + memcpy(s->buf+s->buf_recv, b, x); + ADVANCE(b, n, x); + + if(s->buf_size == 4) { + struct mwOpaque o = { .len = 4, .data = s->buf }; + struct mwGetBuffer *gb = mwGetBuffer_wrap(&o); + x = guint32_peek(gb); + mwGetBuffer_free(gb); + + if(n < x) { + guchar *t; + x += 4; + t = (guchar *) g_malloc(x); + memcpy(t, s->buf, 4); + memcpy(t+4, b, n); + + side_buf_free(s); + + s->buf = t; + s->buf_size = x; + s->buf_recv = n + 4; + return 0; + + } else { + side_buf_free(s); + side_process(s, b, x); + ADVANCE(b, n, x); + } + + } else { + side_process(s, s->buf+4, s->buf_size-4); + side_buf_free(s); + } + } + + return n; +} + + +/** handle input when there's nothing previously buffered */ +static gsize side_recv_empty(struct proxy_side *s, const guchar *b, gsize n) { + struct mwOpaque o = { .len = n, .data = (guchar *) b }; + struct mwGetBuffer *gb; + gsize x; + + if(n < 4) { + s->buf = (guchar *) g_malloc0(4); + memcpy(s->buf, b, n); + s->buf_size = 4; + s->buf_recv = n; + return 0; + } + + gb = mwGetBuffer_wrap(&o); + x = guint32_peek(gb); + mwGetBuffer_free(gb); + if(! x) return n - 4; + + if(n < (x + 4)) { + + x += 4; + s->buf = (guchar *) g_malloc(x); + memcpy(s->buf, b, n); + s->buf_size = x; + s->buf_recv = n; + return 0; + + } else { + ADVANCE(b, n, 4); + side_process(s, b, x); + ADVANCE(b, n, x); + + return n; + } +} + + +/** handle input in chunks */ +static gsize side_recv(struct proxy_side *s, const guchar *b, gsize n) { + + if(n && (s->buf_size == 0) && (*b & 0x80)) { + ADVANCE(b, n, 1); + } + + if(n == 0) { + return 0; + + } else if(s->buf_size > 0) { + return side_recv_cont(s, b, n); + + } else { + return side_recv_empty(s, b, n); + } +} + + +/** handle input */ +static void feed_buf(struct proxy_side *side, const guchar *buf, gsize n) { + guchar *b = (guchar *) buf; + gsize remain = 0; + + g_return_if_fail(side != NULL); + + while(n > 0) { + remain = side_recv(side, b, n); + b += (n - remain); + n = remain; + } +} + + +static int read_recv(struct proxy_side *side) { + guchar buf[2048]; + int len; + + len = read(side->sock, buf, 2048); + if(len > 0) feed_buf(side, buf, (gsize) len); + + return len; +} + + +static void init_listen(); + + +static void side_done(struct proxy_side *side) { + if(side->sock) { + g_source_remove(side->chan_io); + close(side->sock); + side->sock = 0; + side->chan = NULL; + side->chan_io = 0; + } +} + + +static void done() { + printf("closing connection\n"); + + side_done(&client); + side_done(&server); + + if(counter--) { + init_listen(); + } else { + exit(0); + } +} + + +static gboolean read_cb(GIOChannel *chan, + GIOCondition cond, + gpointer data) { + + struct proxy_side *side = data; + int ret = 0; + + if(cond & G_IO_IN) { + ret = read_recv(side); + if(ret > 0) return TRUE; + } + + done(); + + return FALSE; +} + + +static void client_cb(const guchar *buf, gsize len) { + if(server.sock) write(server.sock, buf, len); +} + + +/** setup the client */ +static void init_client(int sock) { + + client.forward = client_cb; + client.sock = sock; + client.chan = g_io_channel_unix_new(sock); + client.chan_io = g_io_add_watch(client.chan, G_IO_IN | G_IO_ERR | G_IO_HUP, + read_cb, &client); +} + + +static void server_cb(const guchar *buf, gsize len) { + if(client.sock) write(client.sock, buf, len); +} + + +/** generate a private/public DH keypair for internal (re)use */ +static void init_rc2_128() { + struct mwMpi *public; + + private_key = mwMpi_new(); + public = mwMpi_new(); + + mwMpi_randDHKeypair(private_key, public); + mwMpi_export(public, &public_key); + + mwMpi_free(public); +} + + +/** address lookup used by init_sock */ +static void init_sockaddr(struct sockaddr_in *addr, + const char *host, int port) { + + struct hostent *hostinfo; + + addr->sin_family = AF_INET; + addr->sin_port = htons (port); + hostinfo = gethostbyname(host); + if(hostinfo == NULL) { + fprintf(stderr, "Unknown host %s.\n", host); + exit(1); + } + addr->sin_addr = *(struct in_addr *) hostinfo->h_addr; +} + + +/** connect to server on host:port */ +static void init_server() { + struct sockaddr_in srvrname; + int sock; + + printf("connecting to %s:%i\n", host, server_port); + + sock = socket(PF_INET, SOCK_STREAM, 0); + if(sock < 0) { + fprintf(stderr, "socket failure"); + exit(1); + } + + init_sockaddr(&srvrname, host, server_port); + connect(sock, (struct sockaddr *)&srvrname, sizeof(srvrname)); + + server.forward = server_cb; + server.sock = sock; + server.chan = g_io_channel_unix_new(sock); + server.chan_io = g_io_add_watch(server.chan, G_IO_IN | G_IO_ERR | G_IO_HUP, + read_cb, &server); + + printf("connected to %s:%i\n", host, server_port); +} + + + +static gboolean listen_cb(GIOChannel *chan, + GIOCondition cond, + gpointer data) { + + struct sockaddr_in rem; + guint len = sizeof(rem); + int sock; + + printf("got connection\n"); + + sock = accept(listen_sock, (struct sockaddr *) &rem, &len); + /* g_assert(sock > 0); */ + + init_server(); + init_client(sock); + + listen_io = 0; + + return FALSE; +} + + +static void init_listen() { + if(! listen_sock) { + struct sockaddr_in sin; + int sock; + + sock = socket(PF_INET, SOCK_STREAM, 0); + g_assert(sock >= 0); + + memset(&sin, 0, sizeof(struct sockaddr_in)); + sin.sin_family = PF_INET; + sin.sin_port = htons(client_port); + sin.sin_addr.s_addr = htonl(INADDR_ANY); + + if(bind(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) + g_assert_not_reached(); + + if(listen(sock, 1) < 0) + g_assert_not_reached(); + + listen_sock = sock; + listen_chan = g_io_channel_unix_new(sock); + } + + if(! listen_io) { + listen_io = g_io_add_watch(listen_chan, G_IO_IN | G_IO_ERR | G_IO_HUP, + listen_cb, NULL); + printf("listening on port %i\n", client_port); + } +} + + +int main(int argc, char *argv[]) { + + memset(&client, 0, sizeof(struct proxy_side)); + memset(&server, 0, sizeof(struct proxy_side)); + + if(argc > 1) { + char *z; + + host = argv[1]; + z = host; + + host = strchr(z, ':'); + if(host) *host++ = '\0'; + client_port = atoi(z); + + z = strchr(host, ':'); + if(z) *z++ = '\0'; + server_port = atoi(z); + } + + if(argc > 2) { + counter = atoi(argv[2]); + } + + if(!host || !*host || !client_port || !server_port) { + fprintf(stderr, + ( " Usage: %s local_port:remote_host:remote_port [n]\n" + " Creates a locally-running sametime proxy which enforces" + " unencrypted channels. Will drop the first n connections\n" ), + argv[0]); + exit(1); + } + + /* @todo create signal handlers to cleanup sockets */ + + channels = g_hash_table_new(g_direct_hash, g_direct_equal); + + init_rc2_128(); + init_listen(); + + g_main_loop_run(g_main_loop_new(NULL, FALSE)); + return 0; +} + diff --git a/samples/login_server.c b/samples/login_server.c index 3264467..43380d3 100644 --- a/samples/login_server.c +++ b/samples/login_server.c @@ -19,7 +19,6 @@ #include #include -#include #include #include diff --git a/samples/login_server.c.fix-glib-headers b/samples/login_server.c.fix-glib-headers new file mode 100644 index 0000000..3264467 --- /dev/null +++ b/samples/login_server.c.fix-glib-headers @@ -0,0 +1,421 @@ + +/* + Login-parsing Faux Server + The Meanwhile Project + + This is a tool to aide in reverse engineering different types of + authentication schemes. + + Christopher O'Brien +*/ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + + +/** the server socket or the connected socket */ +static int sock; + +/** the io channel */ +static GIOChannel *chan; + +/** the listening event on the io channel */ +static int chan_io; + + +static guchar *sbuf; +static gsize sbuf_size; +static gsize sbuf_recv; + + +struct mwMpi *private, *public; + + +static void hexout(const char *txt, const guchar *buf, gsize len) { + FILE *fp; + + if(txt) fprintf(stdout, "\n%s\n", txt); + fflush(stdout); + + fp = popen("hexdump -C", "w"); + fwrite(buf, len, 1, fp); + fflush(fp); + pclose(fp); +} + + +static void send_msg(struct mwMessage *msg) { + struct mwPutBuffer *b; + struct mwOpaque o = { 0, 0 }; + + b = mwPutBuffer_new(); + mwMessage_put(b, msg); + mwPutBuffer_finalize(&o, b); + + b = mwPutBuffer_new(); + mwOpaque_put(b, &o); + mwOpaque_clear(&o); + mwPutBuffer_finalize(&o, b); + + if(sock) write(sock, o.data, o.len); + + hexout("sent:", o.data, o.len); + + mwOpaque_clear(&o); +} + + +static void handshake_ack() { + struct mwMsgHandshakeAck *msg; + + msg = (struct mwMsgHandshakeAck *) + mwMessage_new(mwMessage_HANDSHAKE_ACK); + + msg->major = 0x1e; + msg->minor = 0x1d; + + mwMpi_randDHKeypair(private, public); + mwMpi_export(public, &msg->data); + + msg->magic = 0x01234567; + hexout("sending pubkey:", msg->data.data, msg->data.len); + + send_msg(MW_MESSAGE(msg)); + mwMessage_free(MW_MESSAGE(msg)); +} + + +static void handle_login(struct mwMsgLogin *msg) { + struct mwGetBuffer *gb; + struct mwOpaque a, b, c; + guint16 z; + struct mwMpi *remote, *shared; + guchar iv[8]; + + remote = mwMpi_new(); + shared = mwMpi_new(); + + mwIV_init(iv); + + gb = mwGetBuffer_wrap(&msg->auth_data); + guint16_get(gb, &z); + mwOpaque_get(gb, &a); + mwOpaque_get(gb, &b); + mwGetBuffer_free(gb); + + mwMpi_import(remote, &a); + mwOpaque_clear(&a); + + mwMpi_calculateDHShared(shared, remote, private); + mwMpi_export(shared, &a); + hexout("shared key:", a.data, a.len); + + mwDecrypt(a.data+(a.len-16), 16, iv, &b, &c); + hexout("decrypted to:", c.data, c.len); + + mwOpaque_clear(&a); + mwOpaque_clear(&b); + mwOpaque_clear(&c); + + mwMpi_free(remote); + mwMpi_free(shared); +} + + +static void done() { + close(sock); + exit(0); +} + + +static void side_process(const guchar *buf, gsize len) { + struct mwOpaque o = { .len = len, .data = (guchar *) buf }; + struct mwGetBuffer *b; + guint16 type; + + if(! len) return; + + b = mwGetBuffer_wrap(&o); + type = guint16_peek(b); + + hexout("received:", buf, len); + + switch(type) { + case mwMessage_HANDSHAKE: + printf("got handshake, sending handshake_ack\n"); + handshake_ack(); + break; + + case mwMessage_LOGIN: + printf("got login, attempting to decipher\n"); + { + struct mwMsgLogin *msg = (struct mwMsgLogin *) mwMessage_get(b); + handle_login(msg); + mwMessage_free(MW_MESSAGE(msg)); + done(); + } + break; + + case mwMessage_CHANNEL_DESTROY: + printf("channel destroy\n"); + done(); + break; + + default: + ; + } + + mwGetBuffer_free(b); +} + + +static void sbuf_free() { + g_free(sbuf); + sbuf = NULL; + sbuf_size = 0; + sbuf_recv = 0; +} + + +#define ADVANCE(b, n, count) { b += count; n -= count; } + + +/* handle input to complete an existing buffer */ +static gsize side_recv_cont(const guchar *b, gsize n) { + + gsize x = sbuf_size - sbuf_recv; + + if(n < x) { + memcpy(sbuf + sbuf_recv, b, n); + sbuf_recv += n; + return 0; + + } else { + memcpy(sbuf + sbuf_recv, b, x); + ADVANCE(b, n, x); + + if(sbuf_size == 4) { + struct mwOpaque o = { .len = 4, .data = sbuf }; + struct mwGetBuffer *gb = mwGetBuffer_wrap(&o); + x = guint32_peek(gb); + mwGetBuffer_free(gb); + + if(n < x) { + guchar *t; + x += 4; + t = (guchar *) g_malloc(x); + memcpy(t, sbuf, 4); + memcpy(t+4, b, n); + + sbuf_free(); + + sbuf = t; + sbuf_size = x; + sbuf_recv = n + 4; + return 0; + + } else { + sbuf_free(); + side_process(b, x); + ADVANCE(b, n, x); + } + + } else { + side_process(sbuf+4, sbuf_size-4); + sbuf_free(); + } + } + + return n; +} + + +/* handle input when there's nothing previously buffered */ +static gsize side_recv_empty(const guchar *b, gsize n) { + struct mwOpaque o = { .len = n, .data = (guchar *) b }; + struct mwGetBuffer *gb; + gsize x; + + if(n < 4) { + sbuf = (guchar *) g_malloc0(4); + memcpy(sbuf, b, n); + sbuf_size = 4; + sbuf_recv = n; + return 0; + } + + gb = mwGetBuffer_wrap(&o); + x = guint32_peek(gb); + mwGetBuffer_free(gb); + if(! x) return n - 4; + + if(n < (x + 4)) { + + x += 4; + sbuf = (guchar *) g_malloc(x); + memcpy(sbuf, b, n); + sbuf_size = x; + sbuf_recv = n; + return 0; + + } else { + ADVANCE(b, n, 4); + side_process(b, x); + ADVANCE(b, n, x); + + return n; + } +} + + +static gsize side_recv(const guchar *b, gsize n) { + + if(n && (sbuf_size == 0) && (*b & 0x80)) { + ADVANCE(b, n, 1); + } + + if(n == 0) { + return 0; + + } else if(sbuf_size > 0) { + return side_recv_cont(b, n); + + } else { + return side_recv_empty(b, n); + } +} + + +static void feed_buf(const guchar *buf, gsize n) { + guchar *b = (guchar *) buf; + gsize remain = 0; + + while(n > 0) { + remain = side_recv(b, n); + b += (n - remain); + n = remain; + } +} + + +static int read_recv() { + guchar buf[2048]; + int len; + + len = read(sock, buf, 2048); + if(len > 0) feed_buf(buf, (gsize) len); + + return len; +} + + +static gboolean read_cb(GIOChannel *chan, + GIOCondition cond, + gpointer data) { + int ret = 0; + + if(cond & G_IO_IN) { + ret = read_recv(); + if(ret > 0) return TRUE; + } + + if(sock) { + g_source_remove(chan_io); + close(sock); + sock = 0; + chan = NULL; + chan_io = 0; + } + + done(); + + return FALSE; +} + + +static gboolean listen_cb(GIOChannel *chan, + GIOCondition cond, + gpointer data) { + + struct sockaddr_in rem; + guint len = sizeof(rem); + + printf("accepting connection\n"); + + sock = accept(sock, (struct sockaddr *) &rem, &len); + g_assert(sock > 0); + + g_source_remove(chan_io); + chan = g_io_channel_unix_new(sock); + chan_io = g_io_add_watch(chan, G_IO_IN | G_IO_ERR | G_IO_HUP, + read_cb, NULL); + + return FALSE; +} + + +static void init_socket(int port) { + /* start listening on the local port specifier */ + + struct sockaddr_in sin; + + sock = socket(PF_INET, SOCK_STREAM, 0); + g_assert(sock >= 0); + + memset(&sin, 0, sizeof(struct sockaddr_in)); + sin.sin_family = PF_INET; + sin.sin_port = htons(port); + sin.sin_addr.s_addr = htonl(INADDR_ANY); + + if(bind(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) + g_assert_not_reached(); + + if(listen(sock, 1) < 0) + g_assert_not_reached(); + + chan = g_io_channel_unix_new(sock); + chan_io = g_io_add_watch(chan, G_IO_IN | G_IO_ERR | G_IO_HUP, + listen_cb, NULL); +} + + +int main(int argc, char *argv[]) { + int port = 0; + + private = mwMpi_new(); + public = mwMpi_new(); + + if(argc > 1) { + port = atoi(argv[1]); + } + + if(!port) { + fprintf(stderr, + ( " Usage: %s local_port\n" + " Creates a locally-running sametime server which prints" + " login information to stdout\n" ), + argv[0]); + exit(1); + } + + /* @todo create signal handlers to cleanup socket */ + + init_socket(port); + + g_main_loop_run(g_main_loop_new(NULL, FALSE)); + return 0; +} + diff --git a/samples/nocipher_proxy.c b/samples/nocipher_proxy.c index dde6485..3851913 100644 --- a/samples/nocipher_proxy.c +++ b/samples/nocipher_proxy.c @@ -29,7 +29,6 @@ #include #include -#include #include #include diff --git a/samples/nocipher_proxy.c.fix-glib-headers b/samples/nocipher_proxy.c.fix-glib-headers new file mode 100644 index 0000000..dde6485 --- /dev/null +++ b/samples/nocipher_proxy.c.fix-glib-headers @@ -0,0 +1,476 @@ + +/* + Clear Channel Sametime Proxy Utility + The Meanwhile Project + + This is a tool which can act as a proxy between a client and a + sametime server, which will force all channels to be created without + any encryption method. This makes reverse-engineering much, much + easier. + + It also outputs the messages sent to and from the client to stdout + as hex pairs. If compiled with USE_HEXDUMP, output will be printed + via `hexdump -C` + + All it really does is nab all Channel Create messages, strip the + offered ciphers portion from the message and replace it with an + empty ciphers list. + + Christopher O'Brien +*/ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + + +struct proxy_side { + int sock; + GIOChannel *chan; + gint chan_io; + + guchar *buf; + gsize buf_size; + gsize buf_recv; + + void (*forward)(const guchar *buf, gsize len); +}; + + +static struct proxy_side client; +static struct proxy_side server; + + +static void hexdump(const char *txt, const guchar *buf, gsize len) { + FILE *fp; + + if(txt) fprintf(stdout, "\n%s\n", txt); + fflush(stdout); + + fp = popen("hexdump -C", "w"); + fwrite(buf, len, 1, fp); + fflush(fp); + pclose(fp); +} + + +static void put_msg(struct mwMessage *msg, struct mwOpaque *o) { + struct mwPutBuffer *b; + + b = mwPutBuffer_new(); + mwMessage_put(b, msg); + mwPutBuffer_finalize(o, b); + + b = mwPutBuffer_new(); + mwOpaque_put(b, o); + mwOpaque_clear(o); + mwPutBuffer_finalize(o, b); +} + + +static void side_buf_free(struct proxy_side *s) { + g_free(s->buf); + s->buf = NULL; + s->buf_size = 0; + s->buf_recv = 0; +} + + +static void munge_redir() { + struct mwMessage *msg; + struct mwOpaque o = { 0, 0 }; + + msg = mwMessage_new(mwMessage_LOGIN_CONTINUE); + put_msg(msg, &o); + mwMessage_free(msg); + + server.forward(o.data, o.len); + + mwOpaque_clear(&o); +} + + +static void munge_create(struct proxy_side *side, + struct mwMsgChannelCreate *msg) { + + struct mwOpaque o = { 0, 0 }; + GList *l; + + for(l = msg->encrypt.items; l; l = l->next) { + mwEncryptItem_clear(l->data); + g_free(l->data); + } + g_list_free(msg->encrypt.items); + msg->encrypt.items = NULL; + + msg->encrypt.mode = 0x00; + msg->encrypt.extra = 0x00; + msg->encrypt.flag = FALSE; + + put_msg(MW_MESSAGE(msg), &o); + side->forward(o.data, o.len); + mwOpaque_clear(&o); +} + + +static void side_process(struct proxy_side *s, const guchar *buf, gsize len) { + struct mwOpaque o = { .len = len, .data = (guchar *) buf }; + struct mwGetBuffer *b; + guint16 type; + + if(! len) return; + + b = mwGetBuffer_wrap(&o); + type = guint16_peek(b); + + switch(type) { + case mwMessage_LOGIN_REDIRECT: + munge_redir(); + break; + + case mwMessage_CHANNEL_CREATE: + { + struct mwMessage *msg = mwMessage_get(b); + munge_create(s, (struct mwMsgChannelCreate *) msg); + mwMessage_free(msg); + break; + } + + default: + { + struct mwPutBuffer *pb = mwPutBuffer_new(); + struct mwOpaque po = { 0, 0 }; + mwOpaque_put(pb, &o); + mwPutBuffer_finalize(&po, pb); + s->forward(po.data, po.len); + mwOpaque_clear(&po); + } + } + + mwGetBuffer_free(b); +} + + +#define ADVANCE(b, n, count) { b += count; n -= count; } + + +/* handle input to complete an existing buffer */ +static gsize side_recv_cont(struct proxy_side *s, const guchar *b, gsize n) { + + gsize x = s->buf_size - s->buf_recv; + + if(n < x) { + memcpy(s->buf+s->buf_recv, b, n); + s->buf_recv += n; + return 0; + + } else { + memcpy(s->buf+s->buf_recv, b, x); + ADVANCE(b, n, x); + + if(s->buf_size == 4) { + struct mwOpaque o = { .len = 4, .data = s->buf }; + struct mwGetBuffer *gb = mwGetBuffer_wrap(&o); + x = guint32_peek(gb); + mwGetBuffer_free(gb); + + if(n < x) { + guchar *t; + x += 4; + t = (guchar *) g_malloc(x); + memcpy(t, s->buf, 4); + memcpy(t+4, b, n); + + side_buf_free(s); + + s->buf = t; + s->buf_size = x; + s->buf_recv = n + 4; + return 0; + + } else { + side_buf_free(s); + side_process(s, b, x); + ADVANCE(b, n, x); + } + + } else { + side_process(s, s->buf+4, s->buf_size-4); + side_buf_free(s); + } + } + + return n; +} + + +/* handle input when there's nothing previously buffered */ +static gsize side_recv_empty(struct proxy_side *s, const guchar *b, gsize n) { + struct mwOpaque o = { .len = n, .data = (guchar *) b }; + struct mwGetBuffer *gb; + gsize x; + + if(n < 4) { + s->buf = (guchar *) g_malloc0(4); + memcpy(s->buf, b, n); + s->buf_size = 4; + s->buf_recv = n; + return 0; + } + + gb = mwGetBuffer_wrap(&o); + x = guint32_peek(gb); + mwGetBuffer_free(gb); + if(! x) return n - 4; + + if(n < (x + 4)) { + + x += 4; + s->buf = (guchar *) g_malloc(x); + memcpy(s->buf, b, n); + s->buf_size = x; + s->buf_recv = n; + return 0; + + } else { + ADVANCE(b, n, 4); + side_process(s, b, x); + ADVANCE(b, n, x); + + return n; + } +} + + +static gsize side_recv(struct proxy_side *s, const guchar *b, gsize n) { + + if(n && (s->buf_size == 0) && (*b & 0x80)) { + ADVANCE(b, n, 1); + } + + if(n == 0) { + return 0; + + } else if(s->buf_size > 0) { + return side_recv_cont(s, b, n); + + } else { + return side_recv_empty(s, b, n); + } +} + + +static void feed_buf(struct proxy_side *side, const guchar *buf, gsize n) { + guchar *b = (guchar *) buf; + gsize remain = 0; + + g_return_if_fail(side != NULL); + + while(n > 0) { + remain = side_recv(side, b, n); + b += (n - remain); + n = remain; + } +} + + +static int read_recv(struct proxy_side *side) { + guchar buf[2048]; + int len; + + len = read(side->sock, buf, 2048); + if(len > 0) feed_buf(side, buf, (gsize) len); + + return len; +} + + +static void done() { + close(client.sock); + close(server.sock); + exit(0); +} + + +static gboolean read_cb(GIOChannel *chan, + GIOCondition cond, + gpointer data) { + + struct proxy_side *side = data; + int ret = 0; + + if(cond & G_IO_IN) { + ret = read_recv(side); + if(ret > 0) return TRUE; + } + + if(side->sock) { + g_source_remove(side->chan_io); + close(side->sock); + side->sock = 0; + side->chan = NULL; + side->chan_io = 0; + } + + done(); + + return FALSE; +} + + +static void client_cb(const guchar *buf, gsize len) { + if(server.sock) { + hexdump("client -> server", buf, len); + write(server.sock, buf, len); + } +} + + +static gboolean listen_cb(GIOChannel *chan, + GIOCondition cond, + gpointer data) { + + struct sockaddr_in rem; + guint len = sizeof(rem); + struct proxy_side *side = data; + int sock; + + sock = accept(side->sock, (struct sockaddr *) &rem, &len); + g_assert(sock > 0); + + g_source_remove(side->chan_io); + side->sock = sock; + side->chan = g_io_channel_unix_new(sock); + side->chan_io = g_io_add_watch(side->chan, G_IO_IN | G_IO_ERR | G_IO_HUP, + read_cb, side); + + return FALSE; +} + + +static void init_client(int port) { + /* start listening on the local port specifier */ + + struct sockaddr_in sin; + int sock; + + sock = socket(PF_INET, SOCK_STREAM, 0); + g_assert(sock >= 0); + + memset(&sin, 0, sizeof(struct sockaddr_in)); + sin.sin_family = PF_INET; + sin.sin_port = htons(port); + sin.sin_addr.s_addr = htonl(INADDR_ANY); + + if(bind(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) + g_assert_not_reached(); + + if(listen(sock, 1) < 0) + g_assert_not_reached(); + + client.forward = client_cb; + client.sock = sock; + client.chan = g_io_channel_unix_new(sock); + client.chan_io = g_io_add_watch(client.chan, G_IO_IN | G_IO_ERR | G_IO_HUP, + listen_cb, &client); +} + + +static void server_cb(const guchar *buf, gsize len) { + if(client.sock) { + hexdump("server -> client", buf, len); + write(client.sock, buf, len); + } +} + + +/* address lookup used by init_sock */ +static void init_sockaddr(struct sockaddr_in *addr, + const char *host, int port) { + + struct hostent *hostinfo; + + addr->sin_family = AF_INET; + addr->sin_port = htons (port); + hostinfo = gethostbyname(host); + if(hostinfo == NULL) { + fprintf(stderr, "Unknown host %s.\n", host); + exit(1); + } + addr->sin_addr = *(struct in_addr *) hostinfo->h_addr; +} + + +static void init_server(const char *host, int port) { + /* connect to server on host/port */ + struct sockaddr_in srvrname; + int sock; + + sock = socket(PF_INET, SOCK_STREAM, 0); + if(sock < 0) { + fprintf(stderr, "socket failure"); + exit(1); + } + + init_sockaddr(&srvrname, host, port); + connect(sock, (struct sockaddr *)&srvrname, sizeof(srvrname)); + + server.forward = server_cb; + server.sock = sock; + server.chan = g_io_channel_unix_new(sock); + server.chan_io = g_io_add_watch(server.chan, G_IO_IN | G_IO_ERR | G_IO_HUP, + read_cb, &server); +} + + +int main(int argc, char *argv[]) { + char *host = NULL; + int client_port = 0, server_port = 0; + + memset(&client, 0, sizeof(struct proxy_side)); + memset(&server, 0, sizeof(struct proxy_side)); + + if(argc > 1) { + char *z; + + host = argv[1]; + z = host; + + host = strchr(z, ':'); + if(host) *host++ = '\0'; + client_port = atoi(z); + + z = strchr(host, ':'); + if(z) *z++ = '\0'; + server_port = atoi(z); + } + + if(!host || !*host || !client_port || !server_port) { + fprintf(stderr, + ( " Usage: %s local_port:remote_host:remote_port\n" + " Creates a locally-running sametime proxy which enforces" + " unencrypted channels\n" ), + argv[0]); + exit(1); + } + + /* @todo create signal handlers to cleanup sockets */ + + init_client(client_port); + init_server(host, server_port); + + g_main_loop_run(g_main_loop_new(NULL, FALSE)); + return 0; +} + diff --git a/samples/redirect_server.c b/samples/redirect_server.c index 91f43f7..c0ed353 100644 --- a/samples/redirect_server.c +++ b/samples/redirect_server.c @@ -22,7 +22,6 @@ #include #include -#include #include #include diff --git a/samples/redirect_server.c.fix-glib-headers b/samples/redirect_server.c.fix-glib-headers new file mode 100644 index 0000000..91f43f7 --- /dev/null +++ b/samples/redirect_server.c.fix-glib-headers @@ -0,0 +1,365 @@ + +/* + Redirecting Sametime Faux-Server + The Meanwhile Project + + This is a tool which helps to test handling of login-redirects when + you don't have a server which will issue them. It will listen for a + single incoming connection, will accept a handshake message and send + a handshake ack, and will respond to a login message with a redirect + message to a server specified on the command line. + + Christopher O'Brien +*/ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + + +/** the server socket or the connected socket */ +static int sock; + +/** the io channel */ +static GIOChannel *chan; + +/** the listening event on the io channel */ +static int chan_io; + +static char *host; + +static guchar *sbuf; +static gsize sbuf_size; +static gsize sbuf_recv; + + +static void send_msg(struct mwMessage *msg) { + struct mwPutBuffer *b; + struct mwOpaque o = { 0, 0 }; + + b = mwPutBuffer_new(); + mwMessage_put(b, msg); + mwPutBuffer_finalize(&o, b); + + b = mwPutBuffer_new(); + mwOpaque_put(b, &o); + mwOpaque_clear(&o); + mwPutBuffer_finalize(&o, b); + + if(sock) write(sock, o.data, o.len); + + mwOpaque_clear(&o); +} + + +static void handshake_ack() { + struct mwMsgHandshakeAck *msg; + + msg = (struct mwMsgHandshakeAck *) + mwMessage_new(mwMessage_HANDSHAKE_ACK); + + send_msg(MW_MESSAGE(msg)); + mwMessage_free(MW_MESSAGE(msg)); +} + + +static void login_redir() { + struct mwMsgLoginRedirect *msg; + + msg = (struct mwMsgLoginRedirect *) + mwMessage_new(mwMessage_LOGIN_REDIRECT); + msg->host = g_strdup(host); + msg->server_id = NULL; + + send_msg(MW_MESSAGE(msg)); + mwMessage_free(MW_MESSAGE(msg)); +} + + +static void done() { + close(sock); + exit(0); +} + + +static void side_process(const guchar *buf, gsize len) { + struct mwOpaque o = { .len = len, .data = (guchar *) buf }; + struct mwGetBuffer *b; + guint16 type; + + if(! len) return; + + b = mwGetBuffer_wrap(&o); + type = guint16_peek(b); + + switch(type) { + case mwMessage_HANDSHAKE: + handshake_ack(); + break; + + case mwMessage_LOGIN: + login_redir(); + break; + + case mwMessage_CHANNEL_DESTROY: + done(); + break; + + default: + ; + } + + mwGetBuffer_free(b); +} + + +static void sbuf_free() { + g_free(sbuf); + sbuf = NULL; + sbuf_size = 0; + sbuf_recv = 0; +} + + +#define ADVANCE(b, n, count) { b += count; n -= count; } + + +/* handle input to complete an existing buffer */ +static gsize side_recv_cont(const guchar *b, gsize n) { + + gsize x = sbuf_size - sbuf_recv; + + if(n < x) { + memcpy(sbuf + sbuf_recv, b, n); + sbuf_recv += n; + return 0; + + } else { + memcpy(sbuf + sbuf_recv, b, x); + ADVANCE(b, n, x); + + if(sbuf_size == 4) { + struct mwOpaque o = { .len = 4, .data = sbuf }; + struct mwGetBuffer *gb = mwGetBuffer_wrap(&o); + x = guint32_peek(gb); + mwGetBuffer_free(gb); + + if(n < x) { + guchar *t; + x += 4; + t = (guchar *) g_malloc(x); + memcpy(t, sbuf, 4); + memcpy(t+4, b, n); + + sbuf_free(); + + sbuf = t; + sbuf_size = x; + sbuf_recv = n + 4; + return 0; + + } else { + sbuf_free(); + side_process(b, x); + ADVANCE(b, n, x); + } + + } else { + side_process(sbuf+4, sbuf_size-4); + sbuf_free(); + } + } + + return n; +} + + +/* handle input when there's nothing previously buffered */ +static gsize side_recv_empty(const guchar *b, gsize n) { + struct mwOpaque o = { .len = n, .data = (guchar *) b }; + struct mwGetBuffer *gb; + gsize x; + + if(n < 4) { + sbuf = (guchar *) g_malloc0(4); + memcpy(sbuf, b, n); + sbuf_size = 4; + sbuf_recv = n; + return 0; + } + + gb = mwGetBuffer_wrap(&o); + x = guint32_peek(gb); + mwGetBuffer_free(gb); + if(! x) return n - 4; + + if(n < (x + 4)) { + + x += 4; + sbuf = (guchar *) g_malloc(x); + memcpy(sbuf, b, n); + sbuf_size = x; + sbuf_recv = n; + return 0; + + } else { + ADVANCE(b, n, 4); + side_process(b, x); + ADVANCE(b, n, x); + + return n; + } +} + + +static gsize side_recv(const guchar *b, gsize n) { + + if(n && (sbuf_size == 0) && (*b & 0x80)) { + ADVANCE(b, n, 1); + } + + if(n == 0) { + return 0; + + } else if(sbuf_size > 0) { + return side_recv_cont(b, n); + + } else { + return side_recv_empty(b, n); + } +} + + +static void feed_buf(const guchar *buf, gsize n) { + guchar *b = (guchar *) buf; + gsize remain = 0; + + while(n > 0) { + remain = side_recv(b, n); + b += (n - remain); + n = remain; + } +} + + +static int read_recv() { + guchar buf[2048]; + int len; + + len = read(sock, buf, 2048); + if(len > 0) feed_buf(buf, (gsize) len); + + return len; +} + + +static gboolean read_cb(GIOChannel *chan, + GIOCondition cond, + gpointer data) { + int ret = 0; + + if(cond & G_IO_IN) { + ret = read_recv(); + if(ret > 0) return TRUE; + } + + if(sock) { + g_source_remove(chan_io); + close(sock); + sock = 0; + chan = NULL; + chan_io = 0; + } + + done(); + + return FALSE; +} + + +static gboolean listen_cb(GIOChannel *chan, + GIOCondition cond, + gpointer data) { + + struct sockaddr_in rem; + guint len = sizeof(rem); + + sock = accept(sock, (struct sockaddr *) &rem, &len); + g_assert(sock > 0); + + g_source_remove(chan_io); + chan = g_io_channel_unix_new(sock); + chan_io = g_io_add_watch(chan, G_IO_IN | G_IO_ERR | G_IO_HUP, + read_cb, NULL); + + return FALSE; +} + + +static void init_socket(int port) { + /* start listening on the local port specifier */ + + struct sockaddr_in sin; + + sock = socket(PF_INET, SOCK_STREAM, 0); + g_assert(sock >= 0); + + memset(&sin, 0, sizeof(struct sockaddr_in)); + sin.sin_family = PF_INET; + sin.sin_port = htons(port); + sin.sin_addr.s_addr = htonl(INADDR_ANY); + + if(bind(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) + g_assert_not_reached(); + + if(listen(sock, 1) < 0) + g_assert_not_reached(); + + chan = g_io_channel_unix_new(sock); + chan_io = g_io_add_watch(chan, G_IO_IN | G_IO_ERR | G_IO_HUP, + listen_cb, NULL); +} + + +int main(int argc, char *argv[]) { + int port = 0; + + if(argc > 1) { + char *z; + + host = argv[1]; + z = host; + + host = strchr(z, ':'); + if(host) *host++ = '\0'; + port = atoi(z); + } + + if(!host || !*host || !port) { + fprintf(stderr, + ( " Usage: %s local_port:redirect_host\n" + " Creates a locally-running sametime proxy which redirects" + " logins to the\n specified host\n" ), + argv[0]); + exit(1); + } + + /* @todo create signal handlers to cleanup socket */ + + init_socket(port); + + g_main_loop_run(g_main_loop_new(NULL, FALSE)); + return 0; +} + diff --git a/src/channel.c b/src/channel.c index d7215a9..932522d 100644 --- a/src/channel.c +++ b/src/channel.c @@ -19,8 +19,6 @@ */ #include -#include -#include #include #include "mw_channel.h" diff --git a/src/channel.c.fix-glib-headers b/src/channel.c.fix-glib-headers new file mode 100644 index 0000000..d7215a9 --- /dev/null +++ b/src/channel.c.fix-glib-headers @@ -0,0 +1,967 @@ + +/* + 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 +#include +#include +#include + +#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); +} + + +static struct mwCipherInstance * +get_supported(struct mwChannel *chan, guint16 id) { + + guint32 cid = (guint32) id; + return g_hash_table_lookup(chan->supported, GUINT_TO_POINTER(cid)); +} + + +static 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; +} + + diff --git a/src/mw_debug.c b/src/mw_debug.c index cf47a38..d22d287 100644 --- a/src/mw_debug.c +++ b/src/mw_debug.c @@ -19,7 +19,7 @@ */ -#include +#include #include "mw_debug.h" diff --git a/src/mw_debug.c.fix-glib-headers b/src/mw_debug.c.fix-glib-headers new file mode 100644 index 0000000..cf47a38 --- /dev/null +++ b/src/mw_debug.c.fix-glib-headers @@ -0,0 +1,184 @@ + +/* + 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 + +#include "mw_debug.h" + + + +#define FRMT1 "%02x" +#define FRMT2 FRMT1 FRMT1 " " +#define FRMT4 FRMT2 FRMT2 +#define FRMT8 FRMT4 FRMT4 +#define FRMT16 FRMT8 FRMT8 + +#define ADVANCE(b, n, c) {b += c; n -= c;} + + + +/** writes hex pairs of buf to str */ +static void pretty_print(GString *str, const guchar *buf, gsize len) { + while(len >= 16) { + /* write a complete line */ + g_string_append_printf(str, FRMT16, + buf[0], buf[1], buf[2], buf[3], + buf[4], buf[5], buf[6], buf[7], + buf[8], buf[9], buf[10], buf[11], + buf[12], buf[13], buf[14], buf[15]); + ADVANCE(buf, len, 16); + + /* append \n to each line but the last */ + if(len) g_string_append(str, "\n"); + } + + /* write an incomplete line */ + if(len >= 8) { + g_string_append_printf(str, FRMT8, + buf[0], buf[1], buf[2], buf[3], + buf[4], buf[5], buf[6], buf[7]); + ADVANCE(buf, len, 8); + } + + if(len >= 4) { + g_string_append_printf(str, FRMT4, + buf[0], buf[1], buf[2], buf[3]); + ADVANCE(buf, len, 4); + } + + if(len >= 2) { + g_string_append_printf(str, FRMT2, buf[0], buf[1]); + ADVANCE(buf, len, 2); + } + + if(len >= 1) { + g_string_append_printf(str, FRMT1, buf[0]); + ADVANCE(buf, len, 1); + } +} + + + +void mw_debug_datav(const guchar *buf, gsize len, + const char *msg, va_list args) { + GString *str; + + g_return_if_fail(buf != NULL || len == 0); + + str = g_string_new(NULL); + + if(msg) { + char *txt = g_strdup_vprintf(msg, args); + g_string_append_printf(str, "%s\n", txt); + g_free(txt); + } + pretty_print(str, buf, len); + + g_debug(str->str); + g_string_free(str, TRUE); +} + + + +void mw_debug_data(const guchar *buf, gsize len, + const char *msg, ...) { + va_list args; + + g_return_if_fail(buf != NULL || len == 0); + + va_start(args, msg); + mw_debug_datav(buf, len, msg, args); + va_end(args); +} + + + +void mw_debug_opaquev(struct mwOpaque *o, const char *txt, va_list args) { + g_return_if_fail(o != NULL); + mw_debug_datav(o->data, o->len, txt, args); +} + + + +void mw_debug_opaque(struct mwOpaque *o, const char *txt, ...) { + va_list args; + + g_return_if_fail(o != NULL); + + va_start(args, txt); + mw_debug_opaquev(o, txt, args); + va_end(args); +} + + +void mw_mailme_datav(const guchar *buf, gsize len, + const char *info, va_list args) { + +#if MW_MAILME + GString *str; + char *txt; + + str = g_string_new(MW_MAILME_MESSAGE "\n" + " Please send mail to: " MW_MAILME_ADDRESS "\n" + MW_MAILME_CUT_START "\n"); + str = g_string_new(NULL); + + txt = g_strdup_vprintf(info, args); + g_string_append_printf(str, "%s\n", txt); + g_free(txt); + + if(buf && len) pretty_print(str, buf, len); + + g_string_append(str, MW_MAILME_CUT_STOP); + + g_debug(str->str); + g_string_free(str, TRUE); + +#else + mw_debug_datav(buf, len, info, args); + +#endif +} + + + +void mw_mailme_data(const guchar *buf, gsize len, + const char *info, ...) { + va_list args; + va_start(args, info); + mw_mailme_datav(buf, len, info, args); + va_end(args); +} + + + +void mw_mailme_opaquev(struct mwOpaque *o, const char *info, va_list args) { + mw_mailme_datav(o->data, o->len, info, args); +} + + + +void mw_mailme_opaque(struct mwOpaque *o, const char *info, ...) { + va_list args; + va_start(args, info); + mw_mailme_opaquev(o, info, args); + va_end(args); +} diff --git a/src/mw_message.h b/src/mw_message.h index 8402b8b..a825ec2 100644 --- a/src/mw_message.h +++ b/src/mw_message.h @@ -22,7 +22,7 @@ #define _MW_MESSAGE_H -#include +#include #include "mw_common.h" diff --git a/src/mw_message.h.fix-glib-headers b/src/mw_message.h.fix-glib-headers new file mode 100644 index 0000000..8402b8b --- /dev/null +++ b/src/mw_message.h.fix-glib-headers @@ -0,0 +1,305 @@ + +/* + 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 +#include "mw_common.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** 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 */ + mwMessage_ANNOUNCE = 0x0022, /**< mwMsgAnnounce */ +}; + + +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; /**< 0.0.0.0 */ + guint16 login_type; /**< @see mwLoginType */ + guint32 loclcalc_addr; /**< local public IP */ + guint16 unknown_a; /**< normally 0x0100 */ + guint32 unknown_b; /**< normally 0x00000000 */ + char *local_host; /**< name of client host */ +}; + + +/* 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 magic; /**< four bytes of something */ + struct mwOpaque data; /**< server's DH public key for auth */ +}; + + +/* 8.3.7 Authentication Types */ + +enum mwAuthType { + mwAuthType_PLAIN = 0x0000, + mwAuthType_TOKEN = 0x0001, + mwAuthType_ENCRYPT = 0x0002, /**< @todo remove for 1.0 */ + mwAuthType_RC2_40 = 0x0002, + mwAuthType_RC2_128 = 0x0004, +}; + + +/* 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; +}; + + +/* Announce */ + +/** An announcement between users */ +struct mwMsgAnnounce { + struct mwMessage head; + gboolean sender_present; /**< indicates presence of sender data */ + struct mwLoginInfo sender; /**< who sent the announcement */ + guint16 unknown_a; /**< unknown A. Usually 0x00 */ + gboolean may_reply; /**< replies allowed */ + char *text; /**< text of message */ + + /** list of (char *) indicating recipients. Recipient users are in + the format "@U username" and recipient NAB groups are in the + format "@G groupname" */ + GList *recipients; +}; + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MW_MESSAGE_H */ + diff --git a/src/mw_srvc_conf.h b/src/mw_srvc_conf.h index 156c639..66d885b 100644 --- a/src/mw_srvc_conf.h +++ b/src/mw_srvc_conf.h @@ -22,7 +22,7 @@ #define _MW_SRVC_CONF_H -#include +#include #include "mw_common.h" diff --git a/src/mw_srvc_conf.h.fix-glib-headers b/src/mw_srvc_conf.h.fix-glib-headers new file mode 100644 index 0000000..156c639 --- /dev/null +++ b/src/mw_srvc_conf.h.fix-glib-headers @@ -0,0 +1,209 @@ + +/* + 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 +#include "mw_common.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** Type identifier for the conference service */ +#define mwService_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 conf 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); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MW_SRVC_CONF_H */ + diff --git a/src/mw_srvc_dir.h b/src/mw_srvc_dir.h index b9230bb..c45e850 100644 --- a/src/mw_srvc_dir.h +++ b/src/mw_srvc_dir.h @@ -22,7 +22,6 @@ #include -#include #ifdef __cplusplus diff --git a/src/mw_srvc_dir.h.fix-glib-headers b/src/mw_srvc_dir.h.fix-glib-headers new file mode 100644 index 0000000..b9230bb --- /dev/null +++ b/src/mw_srvc_dir.h.fix-glib-headers @@ -0,0 +1,212 @@ +/* + 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 +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +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); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MW_SRVC_DIR_H */ diff --git a/src/mw_srvc_place.h b/src/mw_srvc_place.h index e17aa71..bba97b6 100644 --- a/src/mw_srvc_place.h +++ b/src/mw_srvc_place.h @@ -22,7 +22,7 @@ #define _MW_SRVC_PLACE_H -#include +#include #include "mw_common.h" diff --git a/src/mw_srvc_place.h.fix-glib-headers b/src/mw_srvc_place.h.fix-glib-headers new file mode 100644 index 0000000..e17aa71 --- /dev/null +++ b/src/mw_srvc_place.h.fix-glib-headers @@ -0,0 +1,148 @@ + +/* + 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 +#include "mw_common.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** Type identifier for the place service */ +#define mwService_PLACE 0x80000022 + + +/** @struct mwServicePlace */ +struct mwServicePlace; + + +/** @struct mwPlace */ +struct mwPlace; + + +struct mwPlaceHandler { + void (*opened)(struct mwPlace *place); + void (*closed)(struct mwPlace *place, guint32 code); + + void (*peerJoined)(struct mwPlace *place, + const struct mwIdBlock *peer); + + void (*peerParted)(struct mwPlace *place, + const struct mwIdBlock *peer); + + void (*peerSetAttribute)(struct mwPlace *place, + const struct mwIdBlock *peer, + guint32 attr, struct mwOpaque *o); + + void (*peerUnsetAttribute)(struct mwPlace *place, + const struct mwIdBlock *peer, + guint32 attr); + + void (*message)(struct mwPlace *place, + const struct mwIdBlock *who, + const char *msg); + + void (*clear)(struct mwServicePlace *srvc); +}; + + +enum mwPlacePeerAttribute { + mwPlacePeer_TYPING = 0x00000008, +}; + + +struct mwServicePlace * +mwServicePlace_new(struct mwSession *session, + struct mwPlaceHandler *handler); + + +struct mwPlaceHandler * +mwServicePlace_getHandler(struct mwServicePlace *srvc); + + +const GList *mwServicePlace_getPlaces(struct mwServicePlace *srvc); + + +struct mwPlace *mwPlace_new(struct mwServicePlace *srvc, + const char *name, const char *title); + + +struct mwServicePlace *mwPlace_getService(struct mwPlace *place); + + +const char *mwPlace_getName(struct mwPlace *place); + + +const char *mwPlace_getTitle(struct mwPlace *place); + + +int mwPlace_open(struct mwPlace *place); + + +int mwPlace_destroy(struct mwPlace *place, guint32 code); + + +/** returns a GList* of struct mwIdBlock*. The GList will need to be + freed after use, the mwIdBlock structures should not be modified + or freed */ +GList *mwPlace_getMembers(struct mwPlace *place); + + +int mwPlace_sendText(struct mwPlace *place, const char *msg); + + +/** send a legacy invitation for this place to a user. The user will + receive an apparent invitation from a Conference (rather than a + Place) */ +int mwPlace_legacyInvite(struct mwPlace *place, + struct mwIdBlock *idb, + const char *message); + + +int mwPlace_setAttribute(struct mwPlace *place, guint32 attrib, + struct mwOpaque *data); + + +int mwPlace_unsetAttribute(struct mwPlace *place, guint32 attrib); + + +void mwPlace_setClientData(struct mwPlace *place, + gpointer data, GDestroyNotify clean); + + +gpointer mwPlace_getClientData(struct mwPlace *place); + + +void mwPlace_removeClientData(struct mwPlace *place); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MW_SRVC_PLACE_H */ + diff --git a/src/mw_srvc_resolve.h b/src/mw_srvc_resolve.h index 3b06b75..dd033e1 100644 --- a/src/mw_srvc_resolve.h +++ b/src/mw_srvc_resolve.h @@ -23,7 +23,6 @@ #include -#include #ifdef __cplusplus diff --git a/src/mw_srvc_resolve.h.fix-glib-headers b/src/mw_srvc_resolve.h.fix-glib-headers new file mode 100644 index 0000000..3b06b75 --- /dev/null +++ b/src/mw_srvc_resolve.h.fix-glib-headers @@ -0,0 +1,150 @@ + +/* + 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 +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** Type identifier for the conference service */ +#define mwService_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 srvc the resolve service + @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); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MW_SRVC_RESOLVE_H */ diff --git a/src/mw_st_list.h b/src/mw_st_list.h index db0d0e1..60635ee 100644 --- a/src/mw_st_list.h +++ b/src/mw_st_list.h @@ -30,7 +30,6 @@ #include -#include #include "mw_common.h" diff --git a/src/mw_st_list.h.fix-glib-headers b/src/mw_st_list.h.fix-glib-headers new file mode 100644 index 0000000..db0d0e1 --- /dev/null +++ b/src/mw_st_list.h.fix-glib-headers @@ -0,0 +1,218 @@ + +/* + 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 + + +/** @file mw_st_list.h + + Parse and compose buddy lists in the format commonly used by Sametime + Connect clients. +*/ + + +#include +#include +#include "mw_common.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#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(void); + + +/** 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); + + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MW_ST_LIST_H */ diff --git a/src/mw_util.h b/src/mw_util.h index b8e0b7e..10dcd69 100644 --- a/src/mw_util.h +++ b/src/mw_util.h @@ -23,9 +23,6 @@ #include -#include -#include - #define map_guint_new() \ g_hash_table_new(g_direct_hash, g_direct_equal) diff --git a/src/mw_util.h.fix-glib-headers b/src/mw_util.h.fix-glib-headers new file mode 100644 index 0000000..b8e0b7e --- /dev/null +++ b/src/mw_util.h.fix-glib-headers @@ -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 +#include +#include + + +#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 diff --git a/src/srvc_aware.c b/src/srvc_aware.c index 869f973..dc2c12f 100644 --- a/src/srvc_aware.c +++ b/src/srvc_aware.c @@ -19,8 +19,6 @@ */ #include -#include -#include #include #include "mw_channel.h" diff --git a/src/srvc_aware.c.fix-glib-headers b/src/srvc_aware.c.fix-glib-headers new file mode 100644 index 0000000..869f973 --- /dev/null +++ b/src/srvc_aware.c.fix-glib-headers @@ -0,0 +1,1319 @@ + +/* + 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 +#include +#include +#include + +#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_GOT_UNKNOWN = 0x025b, /**< UNKNOWN */ + + msg_OPT_DID_SET = 0x025d, /**< attribute set response */ + msg_OPT_DID_UNSET = 0x025e, /**< attribute unset response */ + msg_OPT_DID_ERROR = 0x025f, /**< attribute set/unset error */ +}; + + +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_return_val_if_fail(srvc != NULL, NULL); + g_return_val_if_fail(srvc->entries != NULL, 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; + + g_return_val_if_fail(chan != NULL, 0); + + 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; + + g_return_val_if_fail(chan != NULL, 0); + + 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; + + g_return_val_if_fail(srvc != NULL, -1); + g_return_val_if_fail(srvc->channel != NULL, 0); + + l = map_collect_keys(srvc->attribs); + tmp = g_list_length(l); + + 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->attribs) { + 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 MW_SERVICE_IS_LIVE(srvc)? send_attrib_list(srvc): 0; +} + + +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 = NULL; + 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); + + if(aware->attribs) + old_attrib = g_hash_table_lookup(aware->attribs, k); + + if(! old_attrib) { + old_attrib = g_new0(struct mwAwareAttribute, 1); + old_attrib->key = key; + 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 && + list->attribs && g_hash_table_lookup(list->attribs, k)) + + h->on_attrib(list, idb, old_attrib); + } +} + + +static 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); + + if(! list->entries) + list->entries = g_hash_table_new((GHashFunc) mwAwareIdBlock_hash, + (GEqualFunc) mwAwareIdBlock_equal); + + aware = list_aware_find(list, id); + if(aware) return FALSE; + + 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_GOT_UNKNOWN: + case msg_OPT_DID_SET: + case msg_OPT_DID_UNSET: + case msg_OPT_DID_ERROR: + break; + + default: + mw_mailme_opaque(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; + + g_hash_table_destroy(srvc_aware->attribs); + srvc_aware->attribs = 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 = NULL; + + 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; + + if(srvc_aware->channel) { + mwChannel_destroy(srvc_aware->channel, ERR_SUCCESS, NULL); + srvc_aware->channel = 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, mwService_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->handler = handler; + + srvc->lists = g_list_prepend(srvc->lists, al); + + 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->service != NULL); + + srvc = list->service; + srvc->lists = g_list_remove_all(srvc->lists, list); + + handler = list->handler; + if(handler && handler->clear) { + handler->clear(list); + list->handler = NULL; + } + + mw_datum_clear(&list->client_data); + + mwAwareList_unwatchAllAttributes(list); + mwAwareList_removeAllAware(list); + + list->service = NULL; + + g_free(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(! list->attribs) + list->attribs = g_hash_table_new(g_direct_hash, g_direct_equal); + + 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 = NULL; + gpointer k = GUINT_TO_POINTER(key); + + if(list->attribs) + 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); + + return remove_unused_attrib(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); + + return remove_unused_attrib(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; + + if(list->attribs) { + g_hash_table_foreach(list->attribs, (GHFunc) dismember_attrib, list); + g_hash_table_destroy(list->attribs); + } + + return remove_unused_attrib(srvc); +} + + +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 */ + if(list->entries) { + g_hash_table_foreach(list->entries, (GHFunc) dismember_aware, list); + g_hash_table_destroy(list->entries); + } + + 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); + if(! aware) return NULL; + + return aware->aware.status.desc; +} + + diff --git a/src/srvc_conf.c b/src/srvc_conf.c index 7a10d22..65e1e72 100644 --- a/src/srvc_conf.c +++ b/src/srvc_conf.c @@ -19,8 +19,6 @@ */ #include -#include -#include #include #include diff --git a/src/srvc_conf.c.fix-glib-headers b/src/srvc_conf.c.fix-glib-headers new file mode 100644 index 0000000..7a10d22 --- /dev/null +++ b/src/srvc_conf.c.fix-glib-headers @@ -0,0 +1,867 @@ + +/* + 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 +#include +#include + +#include +#include +#include +#include + +#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 *ret; + + user = user? user: ""; + + srand(clock() + rand()); + a = ((rand() & 0xff) << 8) | (rand() & 0xff); + b = time(NULL); + + ret = g_strdup_printf("%s(%08x,%04x)", user, b, a); + g_debug("generated random conference name: '%s'", ret); + return ret; +} + + + + + +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); + + srvc->confs = 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, mwService_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_getMembers(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_return_val_if_fail(session != NULL, -1); + + 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); + +#if 0 + /* offer all known ciphers */ + mwChannel_populateSupportedCipherInstances(chan); +#endif + + 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 = (guchar *) 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); +} + diff --git a/src/srvc_dir.c b/src/srvc_dir.c index cf49152..ed5997e 100644 --- a/src/srvc_dir.c +++ b/src/srvc_dir.c @@ -18,7 +18,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include +#include #include "mw_channel.h" #include "mw_common.h" diff --git a/src/srvc_dir.c.fix-glib-headers b/src/srvc_dir.c.fix-glib-headers new file mode 100644 index 0000000..cf49152 --- /dev/null +++ b/src/srvc_dir.c.fix-glib-headers @@ -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 + +#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_mailme_opaque(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_mailme_opaque(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; +} + diff --git a/src/srvc_ft.c b/src/srvc_ft.c index 0564732..ff9f010 100644 --- a/src/srvc_ft.c +++ b/src/srvc_ft.c @@ -19,7 +19,7 @@ */ -#include +#include #include "mw_channel.h" #include "mw_common.h" diff --git a/src/srvc_ft.c.fix-glib-headers b/src/srvc_ft.c.fix-glib-headers new file mode 100644 index 0000000..0564732 --- /dev/null +++ b/src/srvc_ft.c.fix-glib-headers @@ -0,0 +1,654 @@ + +/* + 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 + +#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_mailme_opaque(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; +} + + +const GList * +mwServiceFileTransfer_getTransfers(struct mwServiceFileTransfer *srvc) { + g_return_val_if_fail(srvc != NULL, NULL); + return srvc->transfers; +} + + +struct mwFileTransfer * +mwFileTransfer_new(struct mwServiceFileTransfer *srvc, + const struct mwIdBlock *who, const char *msg, + 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 = 0; + + 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); +} + diff --git a/src/srvc_im.c b/src/srvc_im.c index 5930687..5c13ca3 100644 --- a/src/srvc_im.c +++ b/src/srvc_im.c @@ -19,7 +19,6 @@ */ #include -#include #include #include "mw_channel.h" diff --git a/src/srvc_im.c.fix-glib-headers b/src/srvc_im.c.fix-glib-headers new file mode 100644 index 0000000..5930687 --- /dev/null +++ b/src/srvc_im.c.fix-glib-headers @@ -0,0 +1,1075 @@ + +/* + 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 +#include +#include + +#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 */ + + +#define BREAKUP 2048 + + +/* 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_TIMESTAMP = 0x00000006, /**< notesbuddy timestamp */ + + 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 */ + + gboolean ext_id; /**< special treatment, external ID */ + + /** 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; + + /* mark external users */ + /* c->ext_id = g_str_has_prefix(to->user, "@E "); */ + + 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_all(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 != mwService_IM) || (y != PROTOCOL_TYPE) || (z != PROTOCOL_VER) ) { + g_warning("unacceptable service, proto, ver:" + " 0x%08x, 0x%08x, 0x%08x", x, y, z); + mwChannel_destroy(chan, ERR_SERVICE_NO_SUPPORT, NULL); + 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 || + msg->reason == ERR_SERVICE_NO_SUPPORT)) { + + 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; + char *unkn, *host; + guint16 with_who; + + 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); + + /* todo: add a mwString_skip */ + mwString_get(b, &unkn); + mwString_get(b, &host); + g_free(unkn); + g_free(host); + + /* hack. Sometimes incoming convo invitation channels won't have the + owner id block filled in */ + guint16_get(b, &with_who); + if(with_who && !conv->target.user) { + char *login; + mwString_get(b, &conv->target.user); + mwString_get(b, &login); g_free(login); + mwString_get(b, &conv->target.community); + } + + if(mwGetBuffer_error(b)) { + mw_mailme_opaque(o, "problem with place invite over IM service"); + } else { + 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, (char *) o.data, o.len); + conv->multi_type = mwImSend_HTML; + + } else { + x = g_strndup((char *) o.data, o.len); + convo_recv(conv, mwImSend_HTML, x); + g_free(x); + } + } + break; + + case mwImData_SUBJECT: + x = g_strndup((char *) 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, (char *) o.data, o.len); + conv->multi_type = mwImSend_MIME; + + } else { + x = g_strndup((char *) o.data, o.len); + convo_recv(conv, mwImSend_MIME, x); + g_free(x); + } + break; + + case mwImData_TIMESTAMP: + /* todo */ + 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_mailme_opaque(&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 = MW_SERVICE(srvc_im); + + mwService_init(srvc, session, mwService_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: + case mwImSend_TIMESTAMP: + 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_send_data(struct mwConversation *conv, + guint32 type, guint32 subtype, + struct mwOpaque *data) { + struct mwPutBuffer *b; + struct mwOpaque o; + struct mwChannel *chan; + int ret; + + chan = conv->channel; + g_return_val_if_fail(chan != NULL, -1); + + b = mwPutBuffer_new(); + + guint32_put(b, mwIm_DATA); + guint32_put(b, type); + guint32_put(b, subtype); + mwOpaque_put(b, data); + + mwPutBuffer_finalize(&o, b); + + ret = mwChannel_sendEncrypted(chan, msg_MESSAGE, &o, !conv->ext_id); + mwOpaque_clear(&o); + + return ret; +} + + +static int convo_send_multi_start(struct mwConversation *conv) { + return convo_send_data(conv, mwImData_MULTI_START, 0x00, NULL); +} + + +static int convo_send_multi_stop(struct mwConversation *conv) { + return convo_send_data(conv, mwImData_MULTI_STOP, 0x00, NULL); +} + + +/* breaks up a large message into segments, sends a start_segment + message, then sends each segment in turn, then sends a stop_segment + message */ +static int +convo_sendSegmented(struct mwConversation *conv, const char *message, + int (*send)(struct mwConversation *conv, + const char *msg)) { + char *buf = (char *) message; + gsize len; + int ret = 0; + + len = strlen(buf); + ret = convo_send_multi_start(conv); + + while(len && !ret) { + char tail; + gsize seg; + + seg = BREAKUP; + if(len < BREAKUP) + seg = len; + + /* temporarily NUL-terminate this segment */ + tail = buf[seg]; + buf[seg] = 0x00; + + ret = send(conv, buf); + + /* restore this segment */ + buf[seg] = tail; + + buf += seg; + len -= seg; + } + + if(! ret) + ret = convo_send_multi_stop(conv); + + return ret; +} + + +static int convo_sendText(struct mwConversation *conv, const char *text) { + struct mwPutBuffer *b; + struct mwOpaque o; + int ret; + + b = mwPutBuffer_new(); + + guint32_put(b, mwIm_TEXT); + mwString_put(b, text); + + mwPutBuffer_finalize(&o, b); + ret = mwChannel_sendEncrypted(conv->channel, msg_MESSAGE, &o, !conv->ext_id); + mwOpaque_clear(&o); + + return ret; +} + + +static int convo_sendTyping(struct mwConversation *conv, gboolean typing) { + return convo_send_data(conv, mwImData_TYPING, !typing, NULL); +} + + +static int convo_sendSubject(struct mwConversation *conv, + const char *subject) { + struct mwOpaque o; + + o.len = strlen(subject); + o.data = (guchar *) subject; + + return convo_send_data(conv, mwImData_SUBJECT, 0x00, &o); +} + + +static int convo_sendHtml(struct mwConversation *conv, const char *html) { + struct mwOpaque o; + + o.len = strlen(html); + o.data = (guchar *) html; + + if(o.len > BREAKUP) { + return convo_sendSegmented(conv, html, convo_sendHtml); + } else { + return convo_send_data(conv, mwImData_HTML, 0x00, &o); + } +} + + +static int convo_sendMime(struct mwConversation *conv, const char *mime) { + struct mwOpaque o; + + o.len = strlen(mime); + o.data = (guchar *) mime; + + if(o.len > BREAKUP) { + return convo_sendSegmented(conv, mime, convo_sendMime); + } else { + return convo_send_data(conv, mwImData_MIME, 0x00, &o); + } +} + + +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); +} + diff --git a/src/srvc_place.c b/src/srvc_place.c index fc979ff..58ba574 100644 --- a/src/srvc_place.c +++ b/src/srvc_place.c @@ -19,8 +19,6 @@ */ #include -#include -#include #include #include diff --git a/src/srvc_place.c.fix-glib-headers b/src/srvc_place.c.fix-glib-headers new file mode 100644 index 0000000..fc979ff --- /dev/null +++ b/src/srvc_place.c.fix-glib-headers @@ -0,0 +1,1075 @@ + +/* + 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 +#include +#include + +#include +#include + +#include "mw_channel.h" +#include "mw_common.h" +#include "mw_debug.h" +#include "mw_error.h" +#include "mw_message.h" +#include "mw_service.h" +#include "mw_session.h" +#include "mw_srvc_place.h" +#include "mw_util.h" + + +#define PROTOCOL_TYPE 0x00 +#define PROTOCOL_VER 0x05 + + +enum incoming_msg { + msg_in_JOIN_RESPONSE = 0x0000, /* ? */ + msg_in_INFO = 0x0002, + msg_in_MESSAGE = 0x0004, + msg_in_SECTION = 0x0014, /* see in_section_subtype */ + msg_in_UNKNOWNa = 0x0015, +}; + + +enum in_section_subtype { + msg_in_SECTION_LIST = 0x0000, /* list of section members */ + msg_in_SECTION_PEER = 0x0001, /* see in_section_peer_subtye */ + msg_in_SECTION_PART = 0x0003, +}; + + +enum in_section_peer_subtype { + msg_in_SECTION_PEER_JOIN = 0x0000, + msg_in_SECTION_PEER_PART = 0x0001, /* after msg_in_SECTION_PART */ + msg_in_SECTION_PEER_CLEAR_ATTR = 0x0003, + msg_in_SECTION_PEER_SET_ATTR = 0x0004, +}; + + +enum outgoing_msg { + msg_out_JOIN_PLACE = 0x0000, /* ? */ + msg_out_PEER_INFO = 0x0002, /* ? */ + msg_out_MESSAGE = 0x0003, + msg_out_OLD_INVITE = 0x0005, /* old-style conf. invitation */ + msg_out_SET_ATTR = 0x000a, + msg_out_CLEAR_ATTR = 0x000b, + msg_out_SECTION = 0x0014, /* see out_section_subtype */ + msg_out_UNKNOWNb = 0x001e, /* ? maybe enter stage ? */ +}; + + +enum out_section_subtype { + msg_out_SECTION_LIST = 0x0002, /* req list of members */ + msg_out_SECTION_PART = 0x0003, +}; + + +/* + : allocate section + : state = NEW + + : create channel + : state = PENDING + + : channel accepted + : msg_out_JOIN_PLACE (maybe create?) + : state = JOINING + + : msg_in_JOIN_RESPONSE (contains our place member ID and section ID) + : msg_in_INFO (for place, not peer) + : state = JOINED + + : msg_out_SECTION_LIST (asking for all sections) (optional) + : msg_in_SECTION_LIST (listing all sections, as requested above) + + : msg_out_PEER_INFO (with our place member ID) (optional) + : msg_in_INFO (peer info as requested above) + + : msg_out_SECTION_LIST (with our section ID) (sorta optional) + : msg_in_SECTION_LIST (section listing as requested above) + + : msg_out_UNKNOWNb + : msg_in_SECTION_PEER_JOINED (empty, with our place member ID) + : state = OPEN + + : stuff... (invites, joins, parts, messages, attr) + + : state = CLOSING + : msg_out_SECTION_PART + : destroy channel + : deallocate section +*/ + + +struct mwServicePlace { + struct mwService service; + struct mwPlaceHandler *handler; + GList *places; +}; + + +enum mwPlaceState { + mwPlace_NEW, + mwPlace_PENDING, + mwPlace_JOINING, + mwPlace_JOINED, + mwPlace_OPEN, + mwPlace_CLOSING, + mwPlace_ERROR, + mwPlace_UNKNOWN, +}; + + +struct mwPlace { + struct mwServicePlace *service; + + enum mwPlaceState state; + struct mwChannel *channel; + + char *name; + char *title; + GHashTable *members; /* mapping of member ID: place_member */ + guint32 our_id; /* our member ID */ + guint32 section; /* the section we're using */ + + guint32 requests; /* counter for requests */ + + struct mw_datum client_data; +}; + + +struct place_member { + guint32 place_id; + guint16 member_type; + struct mwIdBlock idb; + char *login_id; + char *name; + guint16 login_type; + guint32 unknown_a; + guint32 unknown_b; +}; + + +#define GET_MEMBER(place, id) \ + (g_hash_table_lookup(place->members, GUINT_TO_POINTER(id))) + + +#define PUT_MEMBER(place, member) \ + (g_hash_table_insert(place->members, \ + GUINT_TO_POINTER(member->place_id), member)) + + +#define REMOVE_MEMBER_ID(place, id) \ + (g_hash_table_remove(place->members, GUINT_TO_POINTER(id))) + + +#define REMOVE_MEMBER(place, member) \ + REMOVE_MEMBER_ID(place, member->place_id) + + +static void member_free(struct place_member *p) { + mwIdBlock_clear(&p->idb); + g_free(p->login_id); + g_free(p->name); + g_free(p); +} + + +__attribute__((used)) +static const struct mwLoginInfo * +member_as_login_info(struct place_member *p) { + static struct mwLoginInfo li; + + li.login_id = p->login_id; + li.type = p->login_type; + li.user_id = p->idb.user; + li.user_name = p->name; + li.community = p->idb.community; + li.full = FALSE; + + return &li; +} + + +static const char *place_state_str(enum mwPlaceState s) { + switch(s) { + case mwPlace_NEW: return "new"; + case mwPlace_PENDING: return "pending"; + case mwPlace_JOINING: return "joining"; + case mwPlace_JOINED: return "joined"; + case mwPlace_OPEN: return "open"; + case mwPlace_CLOSING: return "closing"; + case mwPlace_ERROR: return "error"; + + case mwPlace_UNKNOWN: /* fall-through */ + default: return "UNKNOWN"; + } +} + + +static void place_state(struct mwPlace *place, enum mwPlaceState s) { + g_return_if_fail(place != NULL); + + if(place->state == s) return; + + place->state = s; + g_message("place %s state: %s", NSTR(place->name), place_state_str(s)); +} + + +static void place_free(struct mwPlace *place) { + struct mwServicePlace *srvc; + + if(! place) return; + + srvc = place->service; + g_return_if_fail(srvc != NULL); + + srvc->places = g_list_remove_all(srvc->places, place); + + mw_datum_clear(&place->client_data); + + g_hash_table_destroy(place->members); + + g_free(place->name); + g_free(place->title); + g_free(place); +} + + +static int recv_JOIN_RESPONSE(struct mwPlace *place, + struct mwGetBuffer *b) { + + int ret = 0; + guint32 our_id, section; + + guint32_get(b, &our_id); + guint32_get(b, §ion); + + place->our_id = our_id; + place->section = section; + + return ret; +} + + +static int send_SECTION_LIST(struct mwPlace *place, guint32 section) { + int ret = 0; + struct mwOpaque o = {0, 0}; + struct mwPutBuffer *b; + + b = mwPutBuffer_new(); + guint16_put(b, msg_out_SECTION_LIST); + guint32_put(b, section); + gboolean_put(b, FALSE); + guint32_put(b, ++place->requests); + mwPutBuffer_finalize(&o, b); + + ret = mwChannel_send(place->channel, msg_out_SECTION, &o); + mwOpaque_clear(&o); + + return ret; +} + + +static int recv_INFO(struct mwPlace *place, + struct mwGetBuffer *b) { + + int ret = 0; + guint32 skip = 0; + guint32 section = 0; + + guint32_get(b, &skip); + guint32_get(b, §ion); + mwGetBuffer_advance(b, skip); + + if(! section) { + /* this is a place info rather than member info */ + if(place->title) g_free(place->title); + mwGetBuffer_advance(b, 2); + mwString_get(b, &place->title); + + place_state(place, mwPlace_JOINED); + ret = send_SECTION_LIST(place, place->section); + } + + return ret; +} + + +static int recv_MESSAGE(struct mwPlace *place, + struct mwGetBuffer *b) { + + struct mwServicePlace *srvc; + guint32 pm_id; + guint32 unkn_a, unkn_b, ign; + struct place_member *pm; + char *msg = NULL; + int ret = 0; + + srvc = place->service; + + /* no messages before becoming fully open, please */ + g_return_val_if_fail(place->state == mwPlace_OPEN, -1); + + /* regarding unkn_a and unkn_b: + + they're probably a section indicator and a message count, I'm + just not sure which is which. Until this implementation supports + place sections in the API, it really doesn't matter. */ + + guint32_get(b, &pm_id); + pm = GET_MEMBER(place, pm_id); + g_return_val_if_fail(pm != NULL, -1); + + guint32_get(b, &unkn_a); + guint32_get(b, &ign); /* actually an opaque length */ + + if(! ign) return ret; + + guint32_get(b, &unkn_b); + mwString_get(b, &msg); + + if(srvc->handler && srvc->handler->message) + srvc->handler->message(place, &pm->idb, msg); + + g_free(msg); + + return ret; +} + + +static void place_opened(struct mwPlace *place) { + struct mwServicePlace *srvc; + + place_state(place, mwPlace_OPEN); + + srvc = place->service; + if(srvc->handler && srvc->handler->opened) + srvc->handler->opened(place); +} + + +static int recv_SECTION_PEER_JOIN(struct mwPlace *place, + struct mwGetBuffer *b) { + struct mwServicePlace *srvc; + struct place_member *pm; + guint32 section; + int ret = 0; + + srvc = place->service; + + guint32_get(b, §ion); + if(! section) { + g_info("SECTION_PEER_JOIN with section 0x00"); + return 0; + } + + mwGetBuffer_advance(b, 4); + + pm = g_new0(struct place_member, 1); + guint32_get(b, &pm->place_id); + guint16_get(b, &pm->member_type); + mwIdBlock_get(b, &pm->idb); + mwString_get(b, &pm->login_id); + mwString_get(b, &pm->name); + guint16_get(b, &pm->login_type); + guint32_get(b, &pm->unknown_a); + guint32_get(b, &pm->unknown_b); + + PUT_MEMBER(place, pm); + if(srvc->handler && srvc->handler->peerJoined) + srvc->handler->peerJoined(place, &pm->idb); + + if(pm->place_id == place->our_id) + place_opened(place); + + return ret; +} + + +static int recv_SECTION_PEER_PART(struct mwPlace *place, + struct mwGetBuffer *b) { + struct mwServicePlace *srvc; + int ret = 0; + guint32 section, id; + struct place_member *pm; + + srvc = place->service; + + guint32_get(b, §ion); + g_return_val_if_fail(section == place->section, 0); + + guint32_get(b, &id); + pm = GET_MEMBER(place, id); + + /* SECTION_PART may have been called already */ + if(! pm) return 0; + + if(srvc->handler && srvc->handler->peerParted) + srvc->handler->peerParted(place, &pm->idb); + + REMOVE_MEMBER(place, pm); + + return ret; +} + + +static int recv_SECTION_PEER_CLEAR_ATTR(struct mwPlace *place, + struct mwGetBuffer *b) { + struct mwServicePlace *srvc; + int ret = 0; + guint32 id, attr; + struct place_member *pm; + + srvc = place->service; + + guint32_get(b, &id); + guint32_get(b, &attr); + + pm = GET_MEMBER(place, id); + g_return_val_if_fail(pm != NULL, -1); + + if(srvc->handler && srvc->handler->peerUnsetAttribute) + srvc->handler->peerUnsetAttribute(place, &pm->idb, attr); + + return ret; +} + + +static int recv_SECTION_PEER_SET_ATTR(struct mwPlace *place, + struct mwGetBuffer *b) { + struct mwServicePlace *srvc; + int ret = 0; + guint32 id, attr; + struct mwOpaque o = {0,0}; + struct place_member *pm; + + srvc = place->service; + + guint32_get(b, &id); + mwGetBuffer_advance(b, 4); + mwOpaque_get(b, &o); + mwGetBuffer_advance(b, 4); + guint32_get(b, &attr); + + pm = GET_MEMBER(place, id); + g_return_val_if_fail(pm != NULL, -1); + + if(srvc->handler && srvc->handler->peerSetAttribute) + srvc->handler->peerSetAttribute(place, &pm->idb, attr, &o); + + mwOpaque_clear(&o); + + return ret; +} + + +static int recv_SECTION_PEER(struct mwPlace *place, + struct mwGetBuffer *b) { + guint16 subtype; + int res; + + guint16_get(b, &subtype); + + g_return_val_if_fail(! mwGetBuffer_error(b), -1); + + switch(subtype) { + case msg_in_SECTION_PEER_JOIN: + res = recv_SECTION_PEER_JOIN(place, b); + break; + + case msg_in_SECTION_PEER_PART: + res = recv_SECTION_PEER_PART(place, b); + break; + + case msg_in_SECTION_PEER_CLEAR_ATTR: + res = recv_SECTION_PEER_CLEAR_ATTR(place, b); + break; + + case msg_in_SECTION_PEER_SET_ATTR: + res = recv_SECTION_PEER_SET_ATTR(place, b); + break; + + default: + res = -1; + } + + return res; +} + + +static int recv_SECTION_LIST(struct mwPlace *place, + struct mwGetBuffer *b) { + int ret = 0; + guint32 sec, count; + + mwGetBuffer_advance(b, 4); + guint32_get(b, &sec); + + g_return_val_if_fail(sec == place->section, -1); + + mwGetBuffer_advance(b, 8); + guint32_get(b, &count); + mwGetBuffer_advance(b, 8); + + while(count--) { + struct place_member *m; + + m = g_new0(struct place_member, 1); + mwGetBuffer_advance(b, 4); + guint32_get(b, &m->place_id); + guint16_get(b, &m->member_type); + mwIdBlock_get(b, &m->idb); + mwString_get(b, &m->login_id); + mwString_get(b, &m->name); + guint16_get(b, &m->login_type); + guint32_get(b, &m->unknown_a); + guint32_get(b, &m->unknown_b); + + PUT_MEMBER(place, m); + } + + if(place->state != mwPlace_OPEN) + place_opened(place); + + return ret; +} + + +static int recv_SECTION_PART(struct mwPlace *place, + struct mwGetBuffer *b) { + /* look up user in place + remove user from place + trigger event */ + + struct mwServicePlace *srvc; + guint32 pm_id; + struct place_member *pm; + + srvc = place->service; + + guint32_get(b, &pm_id); + pm = GET_MEMBER(place, pm_id); + + /* SECTION_PEER_PART may have been called already */ + if(! pm) return 0; + + if(srvc->handler && srvc->handler->peerParted) + srvc->handler->peerParted(place, &pm->idb); + + REMOVE_MEMBER(place, pm); + + return 0; +} + + +static int recv_SECTION(struct mwPlace *place, struct mwGetBuffer *b) { + guint16 subtype; + int res; + + guint16_get(b, &subtype); + + g_return_val_if_fail(! mwGetBuffer_error(b), -1); + + switch(subtype) { + case msg_in_SECTION_LIST: + res = recv_SECTION_LIST(place, b); + break; + + case msg_in_SECTION_PEER: + res = recv_SECTION_PEER(place, b); + break; + + case msg_in_SECTION_PART: + res = recv_SECTION_PART(place, b); + break; + + default: + res = -1; + } + + return res; +} + + +static int recv_UNKNOWNa(struct mwPlace *place, struct mwGetBuffer *b) { + int res = 0; + + if(place->state == mwPlace_JOINING) { + ; + /* place_state(place, mwPlace_JOINED); + res = send_SECTION_LIST(place, place->section); */ + + } else if(place->state == mwPlace_JOINED) { + ; + /* if(GET_MEMBER(place, place->our_id)) + place_opened(place); */ + } + + return res; +} + + +static void recv(struct mwService *service, struct mwChannel *chan, + guint16 type, struct mwOpaque *data) { + + struct mwPlace *place; + struct mwGetBuffer *b; + int res = 0; + + place = mwChannel_getServiceData(chan); + g_return_if_fail(place != NULL); + + b = mwGetBuffer_wrap(data); + switch(type) { + case msg_in_JOIN_RESPONSE: + res = recv_JOIN_RESPONSE(place, b); + break; + + case msg_in_INFO: + res = recv_INFO(place, b); + break; + + case msg_in_MESSAGE: + res = recv_MESSAGE(place, b); + break; + + case msg_in_SECTION: + res = recv_SECTION(place, b); + break; + + case msg_in_UNKNOWNa: + res = recv_UNKNOWNa(place, b); + break; + + default: + mw_mailme_opaque(data, "Received unknown message type 0x%x on place %s", + type, NSTR(place->name)); + } + + if(res) { + mw_mailme_opaque(data, "Troubling parsing message type 0x0%x on place %s", + type, NSTR(place->name)); + } + + mwGetBuffer_free(b); +} + + +static void stop(struct mwServicePlace *srvc) { + while(srvc->places) + mwPlace_destroy(srvc->places->data, ERR_SUCCESS); + + mwService_stopped(MW_SERVICE(srvc)); +} + + +static int send_JOIN_PLACE(struct mwPlace *place) { + struct mwOpaque o = {0, 0}; + struct mwPutBuffer *b; + int ret; + + b = mwPutBuffer_new(); + gboolean_put(b, FALSE); + guint16_put(b, 0x01); + guint16_put(b, 0x02); /* 0x01 */ + guint16_put(b, 0x01); /* 0x00 */ + + mwPutBuffer_finalize(&o, b); + + ret = mwChannel_send(place->channel, msg_out_JOIN_PLACE, &o); + + mwOpaque_clear(&o); + + if(ret) { + place_state(place, mwPlace_ERROR); + } else { + place_state(place, mwPlace_JOINING); + } + + return ret; +} + + +static void recv_channelAccept(struct mwService *service, + struct mwChannel *chan, + struct mwMsgChannelAccept *msg) { + struct mwServicePlace *srvc; + struct mwPlace *place; + int res; + + srvc = (struct mwServicePlace *) service; + g_return_if_fail(srvc != NULL); + + place = mwChannel_getServiceData(chan); + g_return_if_fail(place != NULL); + + res = send_JOIN_PLACE(place); +} + + +static void recv_channelDestroy(struct mwService *service, + struct mwChannel *chan, + struct mwMsgChannelDestroy *msg) { + struct mwServicePlace *srvc; + struct mwPlace *place; + + srvc = (struct mwServicePlace *) service; + g_return_if_fail(srvc != NULL); + + place = mwChannel_getServiceData(chan); + g_return_if_fail(place != NULL); + + place_state(place, mwPlace_ERROR); + + place->channel = NULL; + + if(srvc->handler && srvc->handler->closed) + srvc->handler->closed(place, msg->reason); + + mwPlace_destroy(place, msg->reason); +} + + +static void clear(struct mwServicePlace *srvc) { + + if(srvc->handler && srvc->handler->clear) + srvc->handler->clear(srvc); + + while(srvc->places) + place_free(srvc->places->data); +} + + +static const char *get_name(struct mwService *srvc) { + return "Places Conferencing"; +} + + +static const char *get_desc(struct mwService *srvc) { + return "Barebones conferencing via Places"; +} + + +struct mwServicePlace * +mwServicePlace_new(struct mwSession *session, + struct mwPlaceHandler *handler) { + + struct mwServicePlace *srvc_place; + struct mwService *srvc; + + g_return_val_if_fail(session != NULL, NULL); + g_return_val_if_fail(handler != NULL, NULL); + + srvc_place = g_new0(struct mwServicePlace, 1); + srvc_place->handler = handler; + + srvc = MW_SERVICE(srvc_place); + mwService_init(srvc, session, mwService_PLACE); + srvc->start = NULL; + srvc->stop = (mwService_funcStop) stop; + srvc->recv_create = NULL; + srvc->recv_accept = recv_channelAccept; + srvc->recv_destroy = recv_channelDestroy; + srvc->recv = recv; + srvc->clear = (mwService_funcClear) clear; + srvc->get_name = get_name; + srvc->get_desc = get_desc; + + return srvc_place; +} + + +struct mwPlaceHandler * +mwServicePlace_getHandler(struct mwServicePlace *srvc) { + g_return_val_if_fail(srvc != NULL, NULL); + return srvc->handler; +} + + +const GList *mwServicePlace_getPlaces(struct mwServicePlace *srvc) { + g_return_val_if_fail(srvc != NULL, NULL); + return srvc->places; +} + + +struct mwPlace *mwPlace_new(struct mwServicePlace *srvc, + const char *name, const char *title) { + struct mwPlace *place; + + g_return_val_if_fail(srvc != NULL, NULL); + + place = g_new0(struct mwPlace, 1); + place->service = srvc; + place->name = g_strdup(name); + place->title = g_strdup(title); + place->state = mwPlace_NEW; + + place->members = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify) member_free); + + srvc->places = g_list_prepend(srvc->places, place); + + return place; +} + + +struct mwServicePlace *mwPlace_getService(struct mwPlace *place) { + g_return_val_if_fail(place != NULL, NULL); + return place->service; +} + + +static char *place_generate_name(const char *user) { + guint a, b; + char *ret; + + user = user? user: "meanwhile"; + + srand(clock() + rand()); + a = ((rand() & 0xff) << 8) | (rand() & 0xff); + b = time(NULL); + + ret = g_strdup_printf("%s(%08x,%04x)", user, b, a); + g_debug("generated random conference name: '%s'", ret); + return ret; +} + + +const char *mwPlace_getName(struct mwPlace *place) { + g_return_val_if_fail(place != NULL, NULL); + + if(! place->name) { + struct mwSession *session; + struct mwLoginInfo *li; + + session = mwService_getSession(MW_SERVICE(place->service)); + li = mwSession_getLoginInfo(session); + + place->name = place_generate_name(li? li->user_id: NULL); + } + + return place->name; +} + + +static char *place_generate_title(const char *user) { + char *ret; + + user = user? user: "Meanwhile"; + ret = g_strdup_printf("%s's Conference", user); + g_debug("generated conference title: %s", ret); + + return ret; +} + + +const char *mwPlace_getTitle(struct mwPlace *place) { + g_return_val_if_fail(place != NULL, NULL); + + if(! place->title) { + struct mwSession *session; + struct mwLoginInfo *li; + + session = mwService_getSession(MW_SERVICE(place->service)); + li = mwSession_getLoginInfo(session); + + place->title = place_generate_title(li? li->user_name: NULL); + } + + return place->title; +} + + +int mwPlace_open(struct mwPlace *p) { + struct mwSession *session; + struct mwChannelSet *cs; + struct mwChannel *chan; + struct mwPutBuffer *b; + int ret; + + g_return_val_if_fail(p != NULL, -1); + g_return_val_if_fail(p->service != NULL, -1); + + session = mwService_getSession(MW_SERVICE(p->service)); + g_return_val_if_fail(session != NULL, -1); + + cs = mwSession_getChannels(session); + g_return_val_if_fail(cs != NULL, -1); + + chan = mwChannel_newOutgoing(cs); + mwChannel_setService(chan, MW_SERVICE(p->service)); + mwChannel_setProtoType(chan, PROTOCOL_TYPE); + mwChannel_setProtoVer(chan, PROTOCOL_VER); + + mwChannel_populateSupportedCipherInstances(chan); + + b = mwPutBuffer_new(); + mwString_put(b, mwPlace_getName(p)); + mwString_put(b, mwPlace_getTitle(p)); + guint32_put(b, 0x00); /* ? */ + + mwPutBuffer_finalize(mwChannel_getAddtlCreate(chan), b); + + ret = mwChannel_create(chan); + if(ret) { + place_state(p, mwPlace_ERROR); + } else { + place_state(p, mwPlace_PENDING); + p->channel = chan; + mwChannel_setServiceData(chan, p, NULL); + } + + return ret; +} + + +int mwPlace_destroy(struct mwPlace *p, guint32 code) { + int ret = 0; + + place_state(p, mwPlace_CLOSING); + + if(p->channel) { + ret = mwChannel_destroy(p->channel, code, NULL); + p->channel = NULL; + } + + place_free(p); + + return ret; +} + + +GList *mwPlace_getMembers(struct mwPlace *place) { + GList *l, *ll; + + g_return_val_if_fail(place != NULL, NULL); + g_return_val_if_fail(place->members != NULL, NULL); + + ll = map_collect_values(place->members); + for(l = ll; l; l = l->next) { + struct place_member *pm = l->data; + l->data = &pm->idb; + g_info("collected member %u: %s, %s", pm->place_id, + NSTR(pm->idb.user), NSTR(pm->idb.community)); + } + + return ll; +} + + +int mwPlace_sendText(struct mwPlace *place, const char *msg) { + struct mwOpaque o = {0,0}; + struct mwPutBuffer *b; + int ret; + + b = mwPutBuffer_new(); + guint32_put(b, 0x01); /* probably a message type */ + mwString_put(b, msg); + mwPutBuffer_finalize(&o, b); + + b = mwPutBuffer_new(); + guint32_put(b, place->section); + mwOpaque_put(b, &o); + mwOpaque_clear(&o); + mwPutBuffer_finalize(&o, b); + + ret = mwChannel_send(place->channel, msg_out_MESSAGE, &o); + mwOpaque_clear(&o); + return ret; +} + + +int mwPlace_legacyInvite(struct mwPlace *place, + struct mwIdBlock *idb, + const char *message) { + + struct mwOpaque o = {0,0}; + struct mwPutBuffer *b; + int ret; + + b = mwPutBuffer_new(); + mwIdBlock_put(b, idb); + mwString_put(b, idb->user); + mwString_put(b, idb->user); + mwString_put(b, message); + gboolean_put(b, FALSE); + mwPutBuffer_finalize(&o, b); + + ret = mwChannel_send(place->channel, msg_out_OLD_INVITE, &o); + mwOpaque_clear(&o); + return ret; +} + + +int mwPlace_setAttribute(struct mwPlace *place, guint32 attrib, + struct mwOpaque *data) { + + struct mwOpaque o = {0,0}; + struct mwPutBuffer *b; + int ret; + + b = mwPutBuffer_new(); + guint32_put(b, place->our_id); + guint32_put(b, 0x00); + guint32_put(b, attrib); + mwOpaque_put(b, data); + + ret = mwChannel_send(place->channel, msg_out_SET_ATTR, &o); + mwOpaque_clear(&o); + return ret; +} + + +int mwPlace_unsetAttribute(struct mwPlace *place, guint32 attrib) { + struct mwOpaque o = {0,0}; + struct mwPutBuffer *b; + int ret; + + b = mwPutBuffer_new(); + guint32_put(b, place->our_id); + guint32_put(b, attrib); + + ret = mwChannel_send(place->channel, msg_out_SET_ATTR, &o); + mwOpaque_clear(&o); + return ret; +} + + +void mwPlace_setClientData(struct mwPlace *place, + gpointer data, GDestroyNotify clear) { + + g_return_if_fail(place != NULL); + mw_datum_set(&place->client_data, data, clear); +} + + +gpointer mwPlace_getClientData(struct mwPlace *place) { + g_return_val_if_fail(place != NULL, NULL); + return mw_datum_get(&place->client_data); +} + + +void mwPlace_removeClientData(struct mwPlace *place) { + g_return_if_fail(place != NULL); + mw_datum_clear(&place->client_data); +} diff --git a/src/srvc_resolve.c b/src/srvc_resolve.c index aac0d49..c71d7b3 100644 --- a/src/srvc_resolve.c +++ b/src/srvc_resolve.c @@ -18,7 +18,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include +#include #include "mw_channel.h" #include "mw_common.h" diff --git a/src/srvc_resolve.c.fix-glib-headers b/src/srvc_resolve.c.fix-glib-headers new file mode 100644 index 0000000..aac0d49 --- /dev/null +++ b/src/srvc_resolve.c.fix-glib-headers @@ -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 + +#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_mailme_opaque(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, mwService_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)); +} + diff --git a/src/srvc_store.c b/src/srvc_store.c index 90f2efc..afbd2b6 100644 --- a/src/srvc_store.c +++ b/src/srvc_store.c @@ -18,7 +18,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include +#include #include "mw_channel.h" #include "mw_debug.h" diff --git a/src/srvc_store.c.fix-glib-headers b/src/srvc_store.c.fix-glib-headers new file mode 100644 index 0000000..90f2efc --- /dev/null +++ b/src/srvc_store.c.fix-glib-headers @@ -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 + +#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, (guint) 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_mailme_opaque(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, mwService_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); +} + diff --git a/src/st_list.c b/src/st_list.c index c2809be..186d5a3 100644 --- a/src/st_list.c +++ b/src/st_list.c @@ -20,7 +20,7 @@ #include #include -#include +#include #include "mw_debug.h" #include "mw_util.h" diff --git a/src/st_list.c.fix-glib-headers b/src/st_list.c.fix-glib-headers new file mode 100644 index 0000000..c2809be --- /dev/null +++ b/src/st_list.c.fix-glib-headers @@ -0,0 +1,670 @@ + +/* + 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 +#include +#include + +#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, µ); + 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) { + char *tmp = strstr(idb.user, "::"); + if(tmp--) { + type = *(tmp); + *tmp = '\0'; + } + } + + if(name && *name) { + char *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; +} + + +static 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); + if (str) { + list_get(str, l); + g_free(str); + } +} +