1 
2 /*
3   Meanwhile Protocol Plugin for Purple
4   Adds Lotus Sametime support to Purple using the Meanwhile library
5 
6   Copyright (C) 2004 Christopher (siege) O'Brien <siege@preoccupied.net>
7 
8   This program is free software; you can redistribute it and/or modify
9   it under the terms of the GNU General Public License as published by
10   the Free Software Foundation; either version 2 of the License, or (at
11   your option) any later version.
12 
13   This program is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   General Public License for more details.
17 
18   You should have received a copy of the GNU General Public License
19   along with this program; if not, write to the Free Software
20   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301,
21   USA.
22 */
23 
24 #include "internal.h"
25 
26 /* system includes */
27 #include <stdlib.h>
28 #include <time.h>
29 
30 /* glib includes */
31 #include <glib.h>
32 
33 /* purple includes */
34 #include "account.h"
35 #include "accountopt.h"
36 #include "circbuffer.h"
37 #include "conversation.h"
38 #include "debug.h"
39 #include "ft.h"
40 #include "imgstore.h"
41 #include "mime.h"
42 #include "notify.h"
43 #include "plugin.h"
44 #include "privacy.h"
45 #include "prpl.h"
46 #include "request.h"
47 #include "util.h"
48 #include "version.h"
49 
50 /* meanwhile includes */
51 #include <mw_cipher.h>
52 #include <mw_common.h>
53 #include <mw_error.h>
54 #include <mw_service.h>
55 #include <mw_session.h>
56 #include <mw_srvc_aware.h>
57 #include <mw_srvc_conf.h>
58 #include <mw_srvc_ft.h>
59 #include <mw_srvc_im.h>
60 #include <mw_srvc_place.h>
61 #include <mw_srvc_resolve.h>
62 #include <mw_srvc_store.h>
63 #include <mw_st_list.h>
64 
65 /* plugin includes */
66 #include "sametime.h"
67 
68 
69 /* considering that there's no display of this information for prpls,
70    I don't know why I even bother providing these. Oh valiant reader,
71    I do it all for you. */
72 /* scratch that, I just added it to the prpl options panel */
73 #define PLUGIN_ID        "prpl-meanwhile"
74 #define PLUGIN_NAME      "Sametime"
75 #define PLUGIN_SUMMARY   "Sametime Protocol Plugin"
76 #define PLUGIN_DESC      "Open implementation of a Lotus Sametime client"
77 #define PLUGIN_AUTHOR    "Christopher (siege) O'Brien <siege@preoccupied.net>"
78 #define PLUGIN_HOMEPAGE  "http://meanwhile.sourceforge.net/"
79 
80 
81 /* plugin preference names */
82 #define MW_PRPL_OPT_BASE          "/plugins/prpl/meanwhile"
83 #define MW_PRPL_OPT_BLIST_ACTION  MW_PRPL_OPT_BASE "/blist_action"
84 #define MW_PRPL_OPT_PSYCHIC       MW_PRPL_OPT_BASE "/psychic"
85 #define MW_PRPL_OPT_FORCE_LOGIN   MW_PRPL_OPT_BASE "/force_login"
86 #define MW_PRPL_OPT_SAVE_DYNAMIC  MW_PRPL_OPT_BASE "/save_dynamic"
87 
88 
89 /* stages of connecting-ness */
90 #define MW_CONNECT_STEPS  11
91 
92 
93 /* stages of conciousness */
94 #define MW_STATE_OFFLINE      "offline"
95 #define MW_STATE_ACTIVE       "active"
96 #define MW_STATE_AWAY         "away"
97 #define MW_STATE_BUSY         "dnd"
98 #define MW_STATE_MESSAGE      "message"
99 #define MW_STATE_ENLIGHTENED  "buddha"
100 
101 
102 /* keys to get/set chat information */
103 #define CHAT_KEY_CREATOR   "chat.creator"
104 #define CHAT_KEY_NAME      "chat.name"
105 #define CHAT_KEY_TOPIC     "chat.topic"
106 #define CHAT_KEY_INVITE    "chat.invite"
107 #define CHAT_KEY_IS_PLACE  "chat.is_place"
108 
109 
110 /* key for associating a mwLoginType with a buddy */
111 #define BUDDY_KEY_CLIENT  "meanwhile.client"
112 
113 /* store the remote alias so that we can re-create it easily */
114 #define BUDDY_KEY_NAME    "meanwhile.shortname"
115 
116 /* enum mwSametimeUserType */
117 #define BUDDY_KEY_TYPE    "meanwhile.type"
118 
119 
120 /* key for the real group name for a meanwhile group */
121 #define GROUP_KEY_NAME    "meanwhile.group"
122 
123 /* enum mwSametimeGroupType */
124 #define GROUP_KEY_TYPE    "meanwhile.type"
125 
126 /* NAB group owning account */
127 #define GROUP_KEY_OWNER   "meanwhile.account"
128 
129 /* key gtk blist uses to indicate a collapsed group */
130 #define GROUP_KEY_COLLAPSED  "collapsed"
131 
132 
133 /* verification replacement */
134 #define mwSession_NO_SECRET  "meanwhile.no_secret"
135 
136 
137 /* keys to get/set purple plugin information */
138 #define MW_KEY_HOST        "server"
139 #define MW_KEY_PORT        "port"
140 #define MW_KEY_FORCE       "force_login"
141 #define MW_KEY_FAKE_IT     "fake_client_id"
142 #define MW_KEY_CLIENT      "client_id_val"
143 #define MW_KEY_MAJOR       "client_major"
144 #define MW_KEY_MINOR       "client_minor"
145 
146 
147 /** number of seconds from the first blist change before a save to the
148     storage service occurs. */
149 #define BLIST_SAVE_SECONDS  15
150 
151 
152 /** the possible buddy list storage settings */
153 enum blist_choice {
154   blist_choice_LOCAL = 1, /**< local only */
155   blist_choice_MERGE = 2, /**< merge from server */
156   blist_choice_STORE = 3, /**< merge from and save to server */
157   blist_choice_SYNCH = 4  /**< sync with server */
158 };
159 
160 
161 /** the default blist storage option */
162 #define BLIST_CHOICE_DEFAULT  blist_choice_SYNCH
163 
164 
165 /* testing for the above */
166 #define BLIST_PREF_IS(n) (purple_prefs_get_int(MW_PRPL_OPT_BLIST_ACTION)==(n))
167 #define BLIST_PREF_IS_LOCAL()  BLIST_PREF_IS(blist_choice_LOCAL)
168 #define BLIST_PREF_IS_MERGE()  BLIST_PREF_IS(blist_choice_MERGE)
169 #define BLIST_PREF_IS_STORE()  BLIST_PREF_IS(blist_choice_STORE)
170 #define BLIST_PREF_IS_SYNCH()  BLIST_PREF_IS(blist_choice_SYNCH)
171 
172 
173 /* debugging output */
174 #define DEBUG_ERROR(...)  purple_debug_error(G_LOG_DOMAIN, __VA_ARGS__)
175 #define DEBUG_INFO(...)   purple_debug_info(G_LOG_DOMAIN, __VA_ARGS__)
176 #define DEBUG_MISC(...)   purple_debug_misc(G_LOG_DOMAIN, __VA_ARGS__)
177 #define DEBUG_WARN(...)   purple_debug_warning(G_LOG_DOMAIN, __VA_ARGS__)
178 
179 
180 /** ensure non-null strings */
181 #ifndef NSTR
182 # define NSTR(str) ((str)? (str): "(null)")
183 #endif
184 
185 
186 /** calibrates distinct secure channel nomenclature */
187 static const unsigned char no_secret[] = {
188   0x2d, 0x2d, 0x20, 0x73, 0x69, 0x65, 0x67, 0x65,
189   0x20, 0x6c, 0x6f, 0x76, 0x65, 0x73, 0x20, 0x6a,
190   0x65, 0x6e, 0x6e, 0x69, 0x20, 0x61, 0x6e, 0x64,
191   0x20, 0x7a, 0x6f, 0x65, 0x20, 0x2d, 0x2d, 0x00,
192 };
193 
194 
195 /** handler IDs from g_log_set_handler in mw_plugin_init */
196 static guint log_handler[2] = { 0, 0 };
197 
198 
199 /** the purple plugin data.
200     available as gc->proto_data and mwSession_getClientData */
201 struct mwPurplePluginData {
202   struct mwSession *session;
203 
204   struct mwServiceAware *srvc_aware;
205   struct mwServiceConference *srvc_conf;
206   struct mwServiceFileTransfer *srvc_ft;
207   struct mwServiceIm *srvc_im;
208   struct mwServicePlace *srvc_place;
209   struct mwServiceResolve *srvc_resolve;
210   struct mwServiceStorage *srvc_store;
211 
212   /** map of PurpleGroup:mwAwareList and mwAwareList:PurpleGroup */
213   GHashTable *group_list_map;
214 
215   /** event id for the buddy list save callback */
216   guint save_event;
217 
218   /** socket fd */
219   int socket;
220   gint outpa;  /* like inpa, but the other way */
221 
222   /** circular buffer for outgoing data */
223   PurpleCircBuffer *sock_buf;
224 
225   PurpleConnection *gc;
226 };
227 
228 
229 typedef struct {
230   PurpleBuddy *buddy;
231   PurpleGroup *group;
232 } BuddyAddData;
233 
234 
235 /* blist and aware functions */
236 
237 static void blist_export(PurpleConnection *gc, struct mwSametimeList *stlist);
238 
239 static void blist_store(struct mwPurplePluginData *pd);
240 
241 static void blist_schedule(struct mwPurplePluginData *pd);
242 
243 static void blist_merge(PurpleConnection *gc, struct mwSametimeList *stlist);
244 
245 static void blist_sync(PurpleConnection *gc, struct mwSametimeList *stlist);
246 
247 static gboolean buddy_is_external(PurpleBuddy *b);
248 
249 static void buddy_add(struct mwPurplePluginData *pd, PurpleBuddy *buddy);
250 
251 static PurpleBuddy *
252 buddy_ensure(PurpleConnection *gc, PurpleGroup *group,
253 	     struct mwSametimeUser *stuser);
254 
255 static void group_add(struct mwPurplePluginData *pd, PurpleGroup *group);
256 
257 static PurpleGroup *
258 group_ensure(PurpleConnection *gc, struct mwSametimeGroup *stgroup);
259 
260 static struct mwAwareList *
261 list_ensure(struct mwPurplePluginData *pd, PurpleGroup *group);
262 
263 
264 /* session functions */
265 
266 static struct mwSession *
267 gc_to_session(PurpleConnection *gc);
268 
269 static PurpleConnection *session_to_gc(struct mwSession *session);
270 
271 
272 /* conference functions */
273 
274 static struct mwConference *
275 conf_find_by_id(struct mwPurplePluginData *pd, int id);
276 
277 
278 /* conversation functions */
279 
280 struct convo_msg {
281   enum mwImSendType type;
282   gpointer data;
283   GDestroyNotify clear;
284 };
285 
286 
287 struct convo_data {
288   struct mwConversation *conv;
289   GList *queue;   /**< outgoing message queue, list of convo_msg */
290 };
291 
292 static void convo_data_new(struct mwConversation *conv);
293 
294 static void convo_data_free(struct convo_data *conv);
295 
296 static void convo_features(struct mwConversation *conv);
297 
298 static PurpleConversation *convo_get_gconv(struct mwConversation *conv);
299 
300 
301 /* name and id */
302 
303 struct named_id {
304   char *id;
305   char *name;
306 };
307 
308 
309 /* connection functions */
310 
311 static void connect_cb(gpointer data, gint source, const gchar *error_message);
312 
313 
314 /* ----- session ------ */
315 
316 
317 /** resolves a mwSession from a PurpleConnection */
gc_to_session(PurpleConnection * gc)318 static struct mwSession *gc_to_session(PurpleConnection *gc) {
319   struct mwPurplePluginData *pd;
320 
321   g_return_val_if_fail(gc != NULL, NULL);
322 
323   pd = gc->proto_data;
324   g_return_val_if_fail(pd != NULL, NULL);
325 
326   return pd->session;
327 }
328 
329 
330 /** resolves a PurpleConnection from a mwSession */
session_to_gc(struct mwSession * session)331 static PurpleConnection *session_to_gc(struct mwSession *session) {
332   struct mwPurplePluginData *pd;
333 
334   g_return_val_if_fail(session != NULL, NULL);
335 
336   pd = mwSession_getClientData(session);
337   g_return_val_if_fail(pd != NULL, NULL);
338 
339   return pd->gc;
340 }
341 
342 
write_cb(gpointer data,gint source,PurpleInputCondition cond)343 static void write_cb(gpointer data, gint source, PurpleInputCondition cond) {
344   struct mwPurplePluginData *pd = data;
345   PurpleCircBuffer *circ = pd->sock_buf;
346   gsize avail;
347   int ret;
348 
349   DEBUG_INFO("write_cb\n");
350 
351   g_return_if_fail(circ != NULL);
352 
353   avail = purple_circ_buffer_get_max_read(circ);
354   if(BUF_LONG < avail) avail = BUF_LONG;
355 
356   while(avail) {
357     ret = write(pd->socket, circ->outptr, avail);
358 
359     if(ret <= 0)
360       break;
361 
362     purple_circ_buffer_mark_read(circ, ret);
363     avail = purple_circ_buffer_get_max_read(circ);
364     if(BUF_LONG < avail) avail = BUF_LONG;
365   }
366 
367   if(! avail) {
368     purple_input_remove(pd->outpa);
369     pd->outpa = 0;
370   }
371 }
372 
373 
mw_session_io_write(struct mwSession * session,const guchar * buf,gsize len)374 static int mw_session_io_write(struct mwSession *session,
375 			       const guchar *buf, gsize len) {
376   struct mwPurplePluginData *pd;
377   gssize ret = 0;
378   int err = 0;
379 
380   pd = mwSession_getClientData(session);
381 
382   /* socket was already closed. */
383   if(pd->socket == 0)
384     return 1;
385 
386   if(pd->outpa) {
387     DEBUG_INFO("already pending INPUT_WRITE, buffering\n");
388     purple_circ_buffer_append(pd->sock_buf, buf, len);
389     return 0;
390   }
391 
392   while(len) {
393     ret = write(pd->socket, buf, (len > BUF_LEN)? BUF_LEN: len);
394 
395     if(ret <= 0)
396       break;
397 
398     len -= ret;
399     buf += ret;
400   }
401 
402   if(ret <= 0)
403     err = errno;
404 
405   if(err == EAGAIN) {
406     /* append remainder to circular buffer */
407     DEBUG_INFO("EAGAIN\n");
408     purple_circ_buffer_append(pd->sock_buf, buf, len);
409     pd->outpa = purple_input_add(pd->socket, PURPLE_INPUT_WRITE, write_cb, pd);
410 
411   } else if(len > 0) {
412 	gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
413 			g_strerror(errno));
414     DEBUG_ERROR("write returned %" G_GSSIZE_FORMAT ", %" G_GSIZE_FORMAT
415 			" bytes left unwritten\n", ret, len);
416     purple_connection_error_reason(pd->gc,
417                                    PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
418                                    tmp);
419 	g_free(tmp);
420 
421 #if 0
422     close(pd->socket);
423     pd->socket = 0;
424 #endif
425 
426     return -1;
427   }
428 
429   return 0;
430 }
431 
432 
mw_session_io_close(struct mwSession * session)433 static void mw_session_io_close(struct mwSession *session) {
434   struct mwPurplePluginData *pd;
435   PurpleConnection *gc;
436 
437   pd = mwSession_getClientData(session);
438   g_return_if_fail(pd != NULL);
439 
440   gc = pd->gc;
441 
442   if(pd->outpa) {
443     purple_input_remove(pd->outpa);
444     pd->outpa = 0;
445   }
446 
447   if(pd->socket) {
448     close(pd->socket);
449     pd->socket = 0;
450   }
451 
452   if(gc->inpa) {
453     purple_input_remove(gc->inpa);
454     gc->inpa = 0;
455   }
456 }
457 
458 
mw_session_clear(struct mwSession * session)459 static void mw_session_clear(struct mwSession *session) {
460   ; /* nothing for now */
461 }
462 
463 
464 /* ----- aware list ----- */
465 
466 
blist_resolve_alias_cb(struct mwServiceResolve * srvc,guint32 id,guint32 code,GList * results,gpointer data)467 static void blist_resolve_alias_cb(struct mwServiceResolve *srvc,
468 				   guint32 id, guint32 code, GList *results,
469 				   gpointer data) {
470   struct mwResolveResult *result;
471   struct mwResolveMatch *match;
472 
473   g_return_if_fail(results != NULL);
474 
475   result = results->data;
476   g_return_if_fail(result != NULL);
477   g_return_if_fail(result->matches != NULL);
478 
479   match = result->matches->data;
480   g_return_if_fail(match != NULL);
481 
482   purple_blist_server_alias_buddy(data, match->name);
483   purple_blist_node_set_string(data, BUDDY_KEY_NAME, match->name);
484 }
485 
486 
mw_aware_list_on_aware(struct mwAwareList * list,struct mwAwareSnapshot * aware)487 static void mw_aware_list_on_aware(struct mwAwareList *list,
488 				   struct mwAwareSnapshot *aware) {
489 
490   PurpleConnection *gc;
491   PurpleAccount *acct;
492 
493   struct mwPurplePluginData *pd;
494   guint32 idle;
495   guint stat;
496   const char *id;
497   const char *status = MW_STATE_ACTIVE;
498 
499   gc = mwAwareList_getClientData(list);
500   acct = purple_connection_get_account(gc);
501 
502   pd = gc->proto_data;
503   idle = aware->status.time;
504   stat = aware->status.status;
505   id = aware->id.user;
506 
507   if(idle) {
508     guint32 idle_len;       /*< how long a client has been idle */
509     guint32 ugly_idle_len;  /*< how long a broken client has been idle */
510 
511     DEBUG_INFO("%s has idle value 0x%x\n", NSTR(id), idle);
512 
513     idle_len = time(NULL) - idle;
514     ugly_idle_len = ((time(NULL) * 1000) - idle) / 1000;
515 
516 	if(idle > ugly_idle_len)
517 		ugly_idle_len = 0;
518 	else
519 		ugly_idle_len = (ugly_idle_len - idle) / 1000;
520 
521     /*
522        what's the deal here? Well, good clients are smart enough to
523        publish their idle time by using an attribute to indicate that
524        they went idle at some time UTC, in seconds since epoch. Bad
525        clients use milliseconds since epoch. So we're going to compute
526        the idle time for either method, then figure out the lower of
527        the two and use that. Blame the ST 7.5 development team for
528        this.
529      */
530 
531     DEBUG_INFO("idle time: %u, ugly idle time: %u\n", idle_len, ugly_idle_len);
532 
533 #if 1
534     if(idle_len <= ugly_idle_len) {
535       ; /* DEBUG_INFO("sane idle value, let's use it\n"); */
536     } else {
537       idle = time(NULL) - ugly_idle_len;
538     }
539 
540 #else
541     if(idle < 0 || idle > time(NULL)) {
542       DEBUG_INFO("hiding a messy idle value 0x%x\n", NSTR(id), idle);
543       idle = -1;
544     }
545 #endif
546   }
547 
548   switch(stat) {
549   case mwStatus_ACTIVE:
550     status = MW_STATE_ACTIVE;
551     idle = 0;
552     break;
553 
554   case mwStatus_IDLE:
555     if(! idle) idle = -1;
556     break;
557 
558   case mwStatus_AWAY:
559     status = MW_STATE_AWAY;
560     break;
561 
562   case mwStatus_BUSY:
563     status = MW_STATE_BUSY;
564     break;
565   }
566 
567   /* NAB group members */
568   if(aware->group) {
569     PurpleGroup *group;
570     PurpleBuddy *buddy;
571     PurpleBlistNode *bnode;
572 
573     group = g_hash_table_lookup(pd->group_list_map, list);
574     buddy = purple_find_buddy_in_group(acct, id, group);
575     bnode = (PurpleBlistNode *) buddy;
576 
577     if(! buddy) {
578       struct mwServiceResolve *srvc;
579       GList *query;
580 
581       buddy = purple_buddy_new(acct, id, NULL);
582       purple_blist_add_buddy(buddy, NULL, group, NULL);
583 
584       bnode = (PurpleBlistNode *) buddy;
585 
586       srvc = pd->srvc_resolve;
587       query = g_list_append(NULL, (char *) id);
588 
589       mwServiceResolve_resolve(srvc, query, mwResolveFlag_USERS,
590 			       blist_resolve_alias_cb, buddy, NULL);
591       g_list_free(query);
592     }
593 
594     purple_blist_node_set_int(bnode, BUDDY_KEY_TYPE, mwSametimeUser_NORMAL);
595   }
596 
597   if(aware->online) {
598     purple_prpl_got_user_status(acct, id, status, NULL);
599     purple_prpl_got_user_idle(acct, id, !!idle, (time_t) idle);
600 
601   } else {
602     purple_prpl_got_user_status(acct, id, MW_STATE_OFFLINE, NULL);
603   }
604 }
605 
606 
mw_aware_list_on_attrib(struct mwAwareList * list,struct mwAwareIdBlock * id,struct mwAwareAttribute * attrib)607 static void mw_aware_list_on_attrib(struct mwAwareList *list,
608 				    struct mwAwareIdBlock *id,
609 				    struct mwAwareAttribute *attrib) {
610 
611   ; /* nothing. We'll get attribute data as we need it */
612 }
613 
614 
mw_aware_list_clear(struct mwAwareList * list)615 static void mw_aware_list_clear(struct mwAwareList *list) {
616   ; /* nothing for now */
617 }
618 
619 
620 static struct mwAwareListHandler mw_aware_list_handler = {
621   mw_aware_list_on_aware,
622   mw_aware_list_on_attrib,
623   mw_aware_list_clear,
624 };
625 
626 
627 /** Ensures that an Aware List is associated with the given group, and
628     returns that list. */
629 static struct mwAwareList *
list_ensure(struct mwPurplePluginData * pd,PurpleGroup * group)630 list_ensure(struct mwPurplePluginData *pd, PurpleGroup *group) {
631 
632   struct mwAwareList *list;
633 
634   g_return_val_if_fail(pd != NULL, NULL);
635   g_return_val_if_fail(group != NULL, NULL);
636 
637   list = g_hash_table_lookup(pd->group_list_map, group);
638   if(! list) {
639     list = mwAwareList_new(pd->srvc_aware, &mw_aware_list_handler);
640     mwAwareList_setClientData(list, pd->gc, NULL);
641 
642     mwAwareList_watchAttributes(list,
643 				mwAttribute_AV_PREFS_SET,
644 				mwAttribute_MICROPHONE,
645 				mwAttribute_SPEAKERS,
646 				mwAttribute_VIDEO_CAMERA,
647 				mwAttribute_FILE_TRANSFER,
648 				NULL);
649 
650     g_hash_table_replace(pd->group_list_map, group, list);
651     g_hash_table_insert(pd->group_list_map, list, group);
652   }
653 
654   return list;
655 }
656 
657 
blist_export(PurpleConnection * gc,struct mwSametimeList * stlist)658 static void blist_export(PurpleConnection *gc, struct mwSametimeList *stlist) {
659   /* - find the account for this connection
660      - iterate through the buddy list
661      - add each buddy matching this account to the stlist
662   */
663 
664   PurpleAccount *acct;
665   PurpleBlistNode *gn, *cn, *bn;
666   PurpleGroup *grp;
667   PurpleBuddy *bdy;
668 
669   struct mwSametimeGroup *stg = NULL;
670   struct mwIdBlock idb = { NULL, NULL };
671 
672   acct = purple_connection_get_account(gc);
673   g_return_if_fail(acct != NULL);
674 
675   for(gn = purple_blist_get_root(); gn;
676 		  gn = purple_blist_node_get_sibling_next(gn)) {
677     const char *owner;
678     const char *gname;
679     enum mwSametimeGroupType gtype;
680     gboolean gopen;
681 
682     if(! PURPLE_BLIST_NODE_IS_GROUP(gn)) continue;
683     grp = (PurpleGroup *) gn;
684 
685     /* the group's type (normal or dynamic) */
686     gtype = purple_blist_node_get_int(gn, GROUP_KEY_TYPE);
687     if(! gtype) gtype = mwSametimeGroup_NORMAL;
688 
689     /* if it's a normal group with none of our people in it, skip it */
690     if(gtype == mwSametimeGroup_NORMAL && !purple_group_on_account(grp, acct))
691       continue;
692 
693     /* if the group has an owner and we're not it, skip it */
694     owner = purple_blist_node_get_string(gn, GROUP_KEY_OWNER);
695     if(owner && !purple_strequal(owner, purple_account_get_username(acct)))
696       continue;
697 
698     /* the group's actual name may be different from the purple group's
699        name. Find whichever is there */
700     gname = purple_blist_node_get_string(gn, GROUP_KEY_NAME);
701     if(! gname) gname = purple_group_get_name(grp);
702 
703     /* we save this, but never actually honor it */
704     gopen = ! purple_blist_node_get_bool(gn, GROUP_KEY_COLLAPSED);
705 
706     stg = mwSametimeGroup_new(stlist, gtype, gname);
707     mwSametimeGroup_setAlias(stg, purple_group_get_name(grp));
708     mwSametimeGroup_setOpen(stg, gopen);
709 
710     /* don't attempt to put buddies in a dynamic group, it breaks
711        other clients */
712     if(gtype == mwSametimeGroup_DYNAMIC)
713       continue;
714 
715     for(cn = purple_blist_node_get_first_child(gn);
716 			cn;
717 			cn = purple_blist_node_get_sibling_next(cn)) {
718       if(! PURPLE_BLIST_NODE_IS_CONTACT(cn)) continue;
719 
720       for(bn = purple_blist_node_get_first_child(cn);
721 			  bn;
722 			  bn = purple_blist_node_get_sibling_next(bn)) {
723 	if(! PURPLE_BLIST_NODE_IS_BUDDY(bn)) continue;
724 	if(! PURPLE_BLIST_NODE_SHOULD_SAVE(bn)) continue;
725 
726 	bdy = (PurpleBuddy *) bn;
727 
728 	if(purple_buddy_get_account(bdy) == acct) {
729 	  struct mwSametimeUser *stu;
730 	  enum mwSametimeUserType utype;
731 
732 	  idb.user = (char *)purple_buddy_get_name(bdy);
733 
734 	  utype = purple_blist_node_get_int(bn, BUDDY_KEY_TYPE);
735 	  if(! utype) utype = mwSametimeUser_NORMAL;
736 
737 	  stu = mwSametimeUser_new(stg, utype, &idb);
738 	  mwSametimeUser_setShortName(stu, purple_buddy_get_server_alias(bdy));
739 	  mwSametimeUser_setAlias(stu, purple_buddy_get_local_buddy_alias(bdy));
740 	}
741       }
742     }
743   }
744 }
745 
746 
blist_store(struct mwPurplePluginData * pd)747 static void blist_store(struct mwPurplePluginData *pd) {
748 
749   struct mwSametimeList *stlist;
750   struct mwServiceStorage *srvc;
751   struct mwStorageUnit *unit;
752 
753   PurpleConnection *gc;
754 
755   struct mwPutBuffer *b;
756   struct mwOpaque *o;
757 
758   g_return_if_fail(pd != NULL);
759 
760   srvc = pd->srvc_store;
761   g_return_if_fail(srvc != NULL);
762 
763   gc = pd->gc;
764 
765   if(BLIST_PREF_IS_LOCAL() || BLIST_PREF_IS_MERGE()) {
766     DEBUG_INFO("preferences indicate not to save remote blist\n");
767     return;
768 
769   } else if(MW_SERVICE_IS_DEAD(srvc)) {
770     DEBUG_INFO("aborting save of blist: storage service is not alive\n");
771     return;
772 
773   } else if(BLIST_PREF_IS_STORE() || BLIST_PREF_IS_SYNCH()) {
774     DEBUG_INFO("saving remote blist\n");
775 
776   } else {
777     g_return_if_reached();
778   }
779 
780   /* create and export to a list object */
781   stlist = mwSametimeList_new();
782   blist_export(gc, stlist);
783 
784   /* write it to a buffer */
785   b = mwPutBuffer_new();
786   mwSametimeList_put(b, stlist);
787   mwSametimeList_free(stlist);
788 
789   /* put the buffer contents into a storage unit */
790   unit = mwStorageUnit_new(mwStore_AWARE_LIST);
791   o = mwStorageUnit_asOpaque(unit);
792   mwPutBuffer_finalize(o, b);
793 
794   /* save the storage unit to the service */
795   mwServiceStorage_save(srvc, unit, NULL, NULL, NULL);
796 }
797 
798 
blist_save_cb(gpointer data)799 static gboolean blist_save_cb(gpointer data) {
800   struct mwPurplePluginData *pd = data;
801 
802   blist_store(pd);
803   pd->save_event = 0;
804   return FALSE;
805 }
806 
807 
808 /** schedules the buddy list to be saved to the server */
blist_schedule(struct mwPurplePluginData * pd)809 static void blist_schedule(struct mwPurplePluginData *pd) {
810   if(pd->save_event) return;
811 
812   pd->save_event = purple_timeout_add_seconds(BLIST_SAVE_SECONDS,
813 				    blist_save_cb, pd);
814 }
815 
816 
buddy_is_external(PurpleBuddy * b)817 static gboolean buddy_is_external(PurpleBuddy *b) {
818   g_return_val_if_fail(b != NULL, FALSE);
819   return purple_str_has_prefix(purple_buddy_get_name(b), "@E ");
820 }
821 
822 
823 /** Actually add a buddy to the aware service, and schedule the buddy
824     list to be saved to the server */
buddy_add(struct mwPurplePluginData * pd,PurpleBuddy * buddy)825 static void buddy_add(struct mwPurplePluginData *pd,
826 		      PurpleBuddy *buddy) {
827 
828   struct mwAwareIdBlock idb = { mwAware_USER, (char *) purple_buddy_get_name(buddy), NULL };
829   struct mwAwareList *list;
830 
831   PurpleGroup *group;
832   GList *add;
833 
834   add = g_list_prepend(NULL, &idb);
835 
836   group = purple_buddy_get_group(buddy);
837   list = list_ensure(pd, group);
838 
839   if(mwAwareList_addAware(list, add)) {
840     purple_blist_remove_buddy(buddy);
841   }
842 
843   blist_schedule(pd);
844 
845   g_list_free(add);
846 }
847 
848 
849 /** ensure that a PurpleBuddy exists in the group with data
850     appropriately matching the st user entry from the st list */
buddy_ensure(PurpleConnection * gc,PurpleGroup * group,struct mwSametimeUser * stuser)851 static PurpleBuddy *buddy_ensure(PurpleConnection *gc, PurpleGroup *group,
852 			       struct mwSametimeUser *stuser) {
853 
854   struct mwPurplePluginData *pd = gc->proto_data;
855   PurpleBuddy *buddy;
856   PurpleAccount *acct = purple_connection_get_account(gc);
857 
858   const char *id = mwSametimeUser_getUser(stuser);
859   const char *name = mwSametimeUser_getShortName(stuser);
860   const char *alias = mwSametimeUser_getAlias(stuser);
861   enum mwSametimeUserType type = mwSametimeUser_getType(stuser);
862 
863   g_return_val_if_fail(id != NULL, NULL);
864   g_return_val_if_fail(strlen(id) > 0, NULL);
865 
866   buddy = purple_find_buddy_in_group(acct, id, group);
867   if(! buddy) {
868     buddy = purple_buddy_new(acct, id, alias);
869 
870     purple_blist_add_buddy(buddy, NULL, group, NULL);
871     buddy_add(pd, buddy);
872   }
873 
874   purple_blist_alias_buddy(buddy, alias);
875   purple_blist_server_alias_buddy(buddy, name);
876   purple_blist_node_set_string((PurpleBlistNode *) buddy, BUDDY_KEY_NAME, name);
877   purple_blist_node_set_int((PurpleBlistNode *) buddy, BUDDY_KEY_TYPE, type);
878 
879   return buddy;
880 }
881 
882 
883 /** add aware watch for a dynamic group */
group_add(struct mwPurplePluginData * pd,PurpleGroup * group)884 static void group_add(struct mwPurplePluginData *pd,
885 		      PurpleGroup *group) {
886 
887   struct mwAwareIdBlock idb = { mwAware_GROUP, NULL, NULL };
888   struct mwAwareList *list;
889   const char *n;
890   GList *add;
891 
892   n = purple_blist_node_get_string((PurpleBlistNode *) group, GROUP_KEY_NAME);
893   if(! n) n = purple_group_get_name(group);
894 
895   idb.user = (char *) n;
896   add = g_list_prepend(NULL, &idb);
897 
898   list = list_ensure(pd, group);
899   mwAwareList_addAware(list, add);
900   g_list_free(add);
901 }
902 
903 
904 /** ensure that a PurpleGroup exists in the blist with data
905     appropriately matching the st group entry from the st list */
group_ensure(PurpleConnection * gc,struct mwSametimeGroup * stgroup)906 static PurpleGroup *group_ensure(PurpleConnection *gc,
907 			       struct mwSametimeGroup *stgroup) {
908   PurpleAccount *acct;
909   PurpleGroup *group = NULL;
910   PurpleBuddyList *blist;
911   PurpleBlistNode *gn;
912   const char *name, *alias, *owner;
913   enum mwSametimeGroupType type;
914 
915   acct = purple_connection_get_account(gc);
916   owner = purple_account_get_username(acct);
917 
918   blist = purple_get_blist();
919   g_return_val_if_fail(blist != NULL, NULL);
920 
921   name = mwSametimeGroup_getName(stgroup);
922   alias = mwSametimeGroup_getAlias(stgroup);
923   type = mwSametimeGroup_getType(stgroup);
924 
925   if (!name) {
926     DEBUG_WARN("Can't ensure a null group\n");
927     return NULL;
928   }
929 
930   if (!name) {
931     DEBUG_WARN("Can't ensure a null group\n");
932     return NULL;
933   }
934 
935   DEBUG_INFO("attempting to ensure group %s, called %s\n",
936 	     NSTR(name), NSTR(alias));
937 
938   /* first attempt at finding the group, by the name key */
939   for(gn = purple_blist_get_root(); gn;
940 		  gn = purple_blist_node_get_sibling_next(gn)) {
941     const char *n, *o;
942     if(! PURPLE_BLIST_NODE_IS_GROUP(gn)) continue;
943     n = purple_blist_node_get_string(gn, GROUP_KEY_NAME);
944     o = purple_blist_node_get_string(gn, GROUP_KEY_OWNER);
945 
946     DEBUG_INFO("found group named %s, owned by %s\n", NSTR(n), NSTR(o));
947 
948     if(n && purple_strequal(n, name)) {
949       if(!o || purple_strequal(o, owner)) {
950 	DEBUG_INFO("that'll work\n");
951 	group = (PurpleGroup *) gn;
952 	break;
953       }
954     }
955   }
956 
957   /* try again, by alias */
958   if(! group) {
959     DEBUG_INFO("searching for group by alias %s\n", NSTR(alias));
960     group = purple_find_group(alias);
961   }
962 
963   /* oh well, no such group. Let's create it! */
964   if(! group) {
965     DEBUG_INFO("creating group\n");
966     group = purple_group_new(alias);
967     purple_blist_add_group(group, NULL);
968   }
969 
970   gn = (PurpleBlistNode *) group;
971   purple_blist_node_set_string(gn, GROUP_KEY_NAME, name);
972   purple_blist_node_set_int(gn, GROUP_KEY_TYPE, type);
973 
974   if(type == mwSametimeGroup_DYNAMIC) {
975     purple_blist_node_set_string(gn, GROUP_KEY_OWNER, owner);
976     group_add(gc->proto_data, group);
977   }
978 
979   return group;
980 }
981 
982 
983 /** merge the entries from a st list into the purple blist */
blist_merge(PurpleConnection * gc,struct mwSametimeList * stlist)984 static void blist_merge(PurpleConnection *gc, struct mwSametimeList *stlist) {
985   struct mwSametimeGroup *stgroup;
986   struct mwSametimeUser *stuser;
987 
988   PurpleGroup *group;
989 
990   GList *gl, *gtl, *ul, *utl;
991 
992   gl = gtl = mwSametimeList_getGroups(stlist);
993   for(; gl; gl = gl->next) {
994 
995     stgroup = (struct mwSametimeGroup *) gl->data;
996     group = group_ensure(gc, stgroup);
997 
998     ul = utl = mwSametimeGroup_getUsers(stgroup);
999     for(; ul; ul = ul->next) {
1000 
1001       stuser = (struct mwSametimeUser *) ul->data;
1002       buddy_ensure(gc, group, stuser);
1003     }
1004     g_list_free(utl);
1005   }
1006   g_list_free(gtl);
1007 }
1008 
1009 
1010 /** remove all buddies on account from group. If del is TRUE and group
1011     is left empty, remove group as well */
group_clear(PurpleGroup * group,PurpleAccount * acct,gboolean del)1012 static void group_clear(PurpleGroup *group, PurpleAccount *acct, gboolean del) {
1013   PurpleConnection *gc;
1014   GList *prune = NULL;
1015   PurpleBlistNode *gn, *cn, *bn;
1016 
1017   g_return_if_fail(group != NULL);
1018 
1019   DEBUG_INFO("clearing members from pruned group %s\n", NSTR(purple_group_get_name(group)));
1020 
1021   gc = purple_account_get_connection(acct);
1022   g_return_if_fail(gc != NULL);
1023 
1024   gn = (PurpleBlistNode *) group;
1025 
1026   for(cn = purple_blist_node_get_first_child(gn);
1027 		  cn;
1028 		  cn = purple_blist_node_get_sibling_next(cn)) {
1029     if(! PURPLE_BLIST_NODE_IS_CONTACT(cn)) continue;
1030 
1031     for(bn = purple_blist_node_get_first_child(cn);
1032 			bn;
1033 			bn = purple_blist_node_get_sibling_next(bn)) {
1034       PurpleBuddy *gb = (PurpleBuddy *) bn;
1035 
1036       if(! PURPLE_BLIST_NODE_IS_BUDDY(bn)) continue;
1037 
1038       if(purple_buddy_get_account(gb) == acct) {
1039 	DEBUG_INFO("clearing %s from group\n", NSTR(purple_buddy_get_name(gb)));
1040 	prune = g_list_prepend(prune, gb);
1041       }
1042     }
1043   }
1044 
1045   /* quickly unsubscribe from presence for the entire group */
1046   purple_account_remove_group(acct, group);
1047 
1048   /* remove blist entries that need to go */
1049   while(prune) {
1050     purple_blist_remove_buddy(prune->data);
1051     prune = g_list_delete_link(prune, prune);
1052   }
1053   DEBUG_INFO("cleared buddies\n");
1054 
1055   /* optionally remove group from blist */
1056   if(del && !purple_blist_get_group_size(group, TRUE)) {
1057     DEBUG_INFO("removing empty group\n");
1058     purple_blist_remove_group(group);
1059   }
1060 }
1061 
1062 
1063 /** prune out group members that shouldn't be there */
group_prune(PurpleConnection * gc,PurpleGroup * group,struct mwSametimeGroup * stgroup)1064 static void group_prune(PurpleConnection *gc, PurpleGroup *group,
1065 			struct mwSametimeGroup *stgroup) {
1066 
1067   PurpleAccount *acct;
1068   PurpleBlistNode *gn, *cn, *bn;
1069 
1070   GHashTable *stusers;
1071   GList *prune = NULL;
1072   GList *ul, *utl;
1073 
1074   g_return_if_fail(group != NULL);
1075 
1076   DEBUG_INFO("pruning membership of group %s\n", NSTR(purple_group_get_name(group)));
1077 
1078   acct = purple_connection_get_account(gc);
1079   g_return_if_fail(acct != NULL);
1080 
1081   stusers = g_hash_table_new(g_str_hash, g_str_equal);
1082 
1083   /* build a hash table for quick lookup while pruning the group
1084      contents */
1085   utl = mwSametimeGroup_getUsers(stgroup);
1086   for(ul = utl; ul; ul = ul->next) {
1087     const char *id = mwSametimeUser_getUser(ul->data);
1088     g_hash_table_insert(stusers, (char *) id, ul->data);
1089     DEBUG_INFO("server copy has %s\n", NSTR(id));
1090   }
1091   g_list_free(utl);
1092 
1093   gn = (PurpleBlistNode *) group;
1094 
1095   for(cn = purple_blist_node_get_first_child(gn);
1096 		  cn;
1097 		  cn = purple_blist_node_get_sibling_next(cn)) {
1098     if(! PURPLE_BLIST_NODE_IS_CONTACT(cn)) continue;
1099 
1100     for(bn = purple_blist_node_get_first_child(cn);
1101 			bn;
1102 			bn = purple_blist_node_get_sibling_next(bn)) {
1103       PurpleBuddy *gb = (PurpleBuddy *) bn;
1104 
1105       if(! PURPLE_BLIST_NODE_IS_BUDDY(bn)) continue;
1106 
1107       /* if the account is correct and they're not in our table, mark
1108 	 them for pruning */
1109       if(purple_buddy_get_account(gb) == acct && !g_hash_table_lookup(stusers, purple_buddy_get_name(gb))) {
1110 	DEBUG_INFO("marking %s for pruning\n", NSTR(purple_buddy_get_name(gb)));
1111 	prune = g_list_prepend(prune, gb);
1112       }
1113     }
1114   }
1115   DEBUG_INFO("done marking\n");
1116 
1117   g_hash_table_destroy(stusers);
1118 
1119   if(prune) {
1120     purple_account_remove_buddies(acct, prune, NULL);
1121     while(prune) {
1122       purple_blist_remove_buddy(prune->data);
1123       prune = g_list_delete_link(prune, prune);
1124     }
1125   }
1126 }
1127 
1128 
1129 /** synch the entries from a st list into the purple blist, removing any
1130     existing buddies that aren't in the st list */
blist_sync(PurpleConnection * gc,struct mwSametimeList * stlist)1131 static void blist_sync(PurpleConnection *gc, struct mwSametimeList *stlist) {
1132 
1133   PurpleAccount *acct;
1134   PurpleBuddyList *blist;
1135   PurpleBlistNode *gn;
1136 
1137   GHashTable *stgroups;
1138   GList *g_prune = NULL;
1139 
1140   GList *gl, *gtl;
1141 
1142   const char *acct_n;
1143 
1144   DEBUG_INFO("synchronizing local buddy list from server list\n");
1145 
1146   acct = purple_connection_get_account(gc);
1147   g_return_if_fail(acct != NULL);
1148 
1149   acct_n = purple_account_get_username(acct);
1150 
1151   blist = purple_get_blist();
1152   g_return_if_fail(blist != NULL);
1153 
1154   /* build a hash table for quick lookup while pruning the local
1155      list, mapping group name to group structure */
1156   stgroups = g_hash_table_new(g_str_hash, g_str_equal);
1157 
1158   gtl = mwSametimeList_getGroups(stlist);
1159   for(gl = gtl; gl; gl = gl->next) {
1160     const char *name = mwSametimeGroup_getName(gl->data);
1161     g_hash_table_insert(stgroups, (char *) name, gl->data);
1162   }
1163   g_list_free(gtl);
1164 
1165   /* find all groups which should be pruned from the local list */
1166   for(gn = purple_blist_get_root(); gn;
1167 		  gn = purple_blist_node_get_sibling_next(gn)) {
1168     PurpleGroup *grp = (PurpleGroup *) gn;
1169     const char *gname, *owner;
1170     struct mwSametimeGroup *stgrp;
1171 
1172     if(! PURPLE_BLIST_NODE_IS_GROUP(gn)) continue;
1173 
1174     /* group not belonging to this account */
1175     if(! purple_group_on_account(grp, acct))
1176       continue;
1177 
1178     /* dynamic group belonging to this account. don't prune contents */
1179     owner = purple_blist_node_get_string(gn, GROUP_KEY_OWNER);
1180     if(owner && purple_strequal(owner, acct_n))
1181        continue;
1182 
1183     /* we actually are synching by this key as opposed to the group
1184        title, which can be different things in the st list */
1185     gname = purple_blist_node_get_string(gn, GROUP_KEY_NAME);
1186     if(! gname) gname = purple_group_get_name(grp);
1187 
1188     stgrp = g_hash_table_lookup(stgroups, gname);
1189     if(! stgrp) {
1190       /* remove the whole group */
1191       DEBUG_INFO("marking group %s for pruning\n", purple_group_get_name(grp));
1192       g_prune = g_list_prepend(g_prune, grp);
1193 
1194     } else {
1195       /* synch the group contents */
1196       group_prune(gc, grp, stgrp);
1197     }
1198   }
1199   DEBUG_INFO("done marking groups\n");
1200 
1201   /* don't need this anymore */
1202   g_hash_table_destroy(stgroups);
1203 
1204   /* prune all marked groups */
1205   while(g_prune) {
1206     PurpleGroup *grp = g_prune->data;
1207     PurpleBlistNode *gn = (PurpleBlistNode *) grp;
1208     const char *owner;
1209     gboolean del = TRUE;
1210 
1211     owner = purple_blist_node_get_string(gn, GROUP_KEY_OWNER);
1212     if(owner && !purple_strequal(owner, acct_n)) {
1213       /* it's a specialty group belonging to another account with some
1214 	 of our members in it, so don't fully delete it */
1215       del = FALSE;
1216     }
1217 
1218     group_clear(g_prune->data, acct, del);
1219     g_prune = g_list_delete_link(g_prune, g_prune);
1220   }
1221 
1222   /* done with the pruning, let's merge in the additions */
1223   blist_merge(gc, stlist);
1224 }
1225 
1226 
1227 /** callback passed to the storage service when it's told to load the
1228     st list */
fetch_blist_cb(struct mwServiceStorage * srvc,guint32 result,struct mwStorageUnit * item,gpointer data)1229 static void fetch_blist_cb(struct mwServiceStorage *srvc,
1230 			   guint32 result, struct mwStorageUnit *item,
1231 			   gpointer data) {
1232 
1233   struct mwPurplePluginData *pd = data;
1234   struct mwSametimeList *stlist;
1235 
1236   struct mwGetBuffer *b;
1237 
1238   g_return_if_fail(result == ERR_SUCCESS);
1239 
1240   /* check our preferences for loading */
1241   if(BLIST_PREF_IS_LOCAL()) {
1242     DEBUG_INFO("preferences indicate not to load remote buddy list\n");
1243     return;
1244   }
1245 
1246   b = mwGetBuffer_wrap(mwStorageUnit_asOpaque(item));
1247 
1248   stlist = mwSametimeList_new();
1249   mwSametimeList_get(b, stlist);
1250 
1251   /* merge or synch depending on preferences */
1252   if(BLIST_PREF_IS_MERGE() || BLIST_PREF_IS_STORE()) {
1253     blist_merge(pd->gc, stlist);
1254 
1255   } else if(BLIST_PREF_IS_SYNCH()) {
1256     blist_sync(pd->gc, stlist);
1257   }
1258 
1259   mwSametimeList_free(stlist);
1260   mwGetBuffer_free(b);
1261 }
1262 
1263 
1264 /** signal triggered when a conversation is opened in Purple */
conversation_created_cb(PurpleConversation * g_conv,struct mwPurplePluginData * pd)1265 static void conversation_created_cb(PurpleConversation *g_conv,
1266 				    struct mwPurplePluginData *pd) {
1267 
1268   /* we need to tell the IM service to negotiate features for the
1269      conversation right away, otherwise it'll wait until the first
1270      message is sent before offering NotesBuddy features. Therefore
1271      whenever Purple creates a conversation, we'll immediately open the
1272      channel to the other side and figure out what the target can
1273      handle. Unfortunately, this makes us vulnerable to Psychic Mode,
1274      whereas a more lazy negotiation based on the first message
1275      would not */
1276 
1277   PurpleConnection *gc;
1278   struct mwIdBlock who = { 0, 0 };
1279   struct mwConversation *conv;
1280 
1281   gc = purple_conversation_get_gc(g_conv);
1282   if(pd->gc != gc)
1283     return; /* not ours */
1284 
1285   if(purple_conversation_get_type(g_conv) != PURPLE_CONV_TYPE_IM)
1286     return; /* wrong type */
1287 
1288   who.user = (char *) purple_conversation_get_name(g_conv);
1289   conv = mwServiceIm_getConversation(pd->srvc_im, &who);
1290 
1291   convo_features(conv);
1292 
1293   if(mwConversation_isClosed(conv))
1294     mwConversation_open(conv);
1295 }
1296 
1297 
blist_menu_nab(PurpleBlistNode * node,gpointer data)1298 static void blist_menu_nab(PurpleBlistNode *node, gpointer data) {
1299   struct mwPurplePluginData *pd = data;
1300   PurpleConnection *gc;
1301 
1302   PurpleGroup *group = (PurpleGroup *) node;
1303 
1304   GString *str;
1305   char *tmp;
1306   const char *gname;
1307 
1308   g_return_if_fail(pd != NULL);
1309 
1310   gc = pd->gc;
1311   g_return_if_fail(gc != NULL);
1312 
1313   g_return_if_fail(PURPLE_BLIST_NODE_IS_GROUP(node));
1314 
1315   str = g_string_new(NULL);
1316 
1317   tmp = (char *) purple_blist_node_get_string(node, GROUP_KEY_NAME);
1318   gname = purple_group_get_name(group);
1319 
1320   g_string_append_printf(str, _("<b>Group Title:</b> %s<br>"), gname);
1321   g_string_append_printf(str, _("<b>Notes Group ID:</b> %s<br>"), tmp);
1322 
1323   tmp = g_strdup_printf(_("Info for Group %s"), gname);
1324 
1325   purple_notify_formatted(gc, tmp, _("Notes Address Book Information"),
1326 			NULL, str->str, NULL, NULL);
1327 
1328   g_free(tmp);
1329   g_string_free(str, TRUE);
1330 }
1331 
1332 
1333 /** The normal blist menu prpl function doesn't get called for groups,
1334     so we use the blist-node-extended-menu signal to trigger this
1335     handler */
blist_node_menu_cb(PurpleBlistNode * node,GList ** menu,struct mwPurplePluginData * pd)1336 static void blist_node_menu_cb(PurpleBlistNode *node,
1337                                GList **menu, struct mwPurplePluginData *pd) {
1338   const char *owner;
1339   PurpleAccount *acct;
1340   PurpleMenuAction *act;
1341 
1342   /* we only want groups */
1343   if(! PURPLE_BLIST_NODE_IS_GROUP(node)) return;
1344 
1345   acct = purple_connection_get_account(pd->gc);
1346   g_return_if_fail(acct != NULL);
1347 
1348   /* better make sure we're connected */
1349   if(! purple_account_is_connected(acct)) return;
1350 
1351 #if 0
1352   /* if there's anyone in the group for this acct, offer to invite
1353      them all to a conference */
1354   if(purple_group_on_account(group, acct)) {
1355     act = purple_menu_action_new(_("Invite Group to Conference..."),
1356                                PURPLE_CALLBACK(blist_menu_group_invite),
1357                                pd, NULL);
1358     *menu = g_list_append(*menu, NULL);
1359   }
1360 #endif
1361 
1362   /* check if it's a NAB group for this account */
1363   owner = purple_blist_node_get_string(node, GROUP_KEY_OWNER);
1364   if(owner && purple_strequal(owner, purple_account_get_username(acct))) {
1365     act = purple_menu_action_new(_("Get Notes Address Book Info"),
1366                                PURPLE_CALLBACK(blist_menu_nab), pd, NULL);
1367     *menu = g_list_append(*menu, act);
1368   }
1369 }
1370 
1371 
1372 /* lifted this from oldstatus, since HEAD doesn't do this at login
1373    anymore. */
blist_init(PurpleAccount * acct)1374 static void blist_init(PurpleAccount *acct) {
1375   PurpleBlistNode *gnode, *cnode, *bnode;
1376   GList *add_buds = NULL;
1377 
1378   for(gnode = purple_blist_get_root(); gnode;
1379 		  gnode = purple_blist_node_get_sibling_next(gnode)) {
1380     if(! PURPLE_BLIST_NODE_IS_GROUP(gnode)) continue;
1381 
1382     for(cnode = purple_blist_node_get_first_child(gnode);
1383 			cnode;
1384 			cnode = purple_blist_node_get_sibling_next(cnode)) {
1385       if(! PURPLE_BLIST_NODE_IS_CONTACT(cnode))
1386 	continue;
1387       for(bnode = purple_blist_node_get_first_child(cnode);
1388 			  bnode;
1389 			  bnode = purple_blist_node_get_sibling_next(bnode)) {
1390 	PurpleBuddy *b;
1391 	if(!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
1392 	  continue;
1393 
1394 	b = (PurpleBuddy *)bnode;
1395 	if(purple_buddy_get_account(b) == acct) {
1396 	  add_buds = g_list_append(add_buds, b);
1397 	}
1398       }
1399     }
1400   }
1401 
1402   if(add_buds) {
1403     purple_account_add_buddies(acct, add_buds);
1404     g_list_free(add_buds);
1405   }
1406 }
1407 
1408 
1409 /** Last thing to happen from a started session */
services_starting(struct mwPurplePluginData * pd)1410 static void services_starting(struct mwPurplePluginData *pd) {
1411 
1412   PurpleConnection *gc;
1413   PurpleAccount *acct;
1414   struct mwStorageUnit *unit;
1415   PurpleBlistNode *l;
1416 
1417   gc = pd->gc;
1418   acct = purple_connection_get_account(gc);
1419 
1420   /* grab the buddy list from the server */
1421   unit = mwStorageUnit_new(mwStore_AWARE_LIST);
1422   mwServiceStorage_load(pd->srvc_store, unit, fetch_blist_cb, pd, NULL);
1423 
1424   /* find all the NAB groups and subscribe to them */
1425   for(l = purple_blist_get_root(); l;
1426 		  l = purple_blist_node_get_sibling_next(l)) {
1427     PurpleGroup *group = (PurpleGroup *) l;
1428     enum mwSametimeGroupType gt;
1429     const char *owner;
1430 
1431     if(! PURPLE_BLIST_NODE_IS_GROUP(l)) continue;
1432 
1433     /* if the group is ownerless, or has an owner and we're not it,
1434        skip it */
1435     owner = purple_blist_node_get_string(l, GROUP_KEY_OWNER);
1436     if(!owner || !purple_strequal(owner, purple_account_get_username(acct)))
1437       continue;
1438 
1439     gt = purple_blist_node_get_int(l, GROUP_KEY_TYPE);
1440     if(gt == mwSametimeGroup_DYNAMIC)
1441       group_add(pd, group);
1442   }
1443 
1444   /* set the aware attributes */
1445   /* indicate we understand what AV prefs are, but don't support any */
1446   mwServiceAware_setAttributeBoolean(pd->srvc_aware,
1447 				     mwAttribute_AV_PREFS_SET, TRUE);
1448   mwServiceAware_unsetAttribute(pd->srvc_aware, mwAttribute_MICROPHONE);
1449   mwServiceAware_unsetAttribute(pd->srvc_aware, mwAttribute_SPEAKERS);
1450   mwServiceAware_unsetAttribute(pd->srvc_aware, mwAttribute_VIDEO_CAMERA);
1451 
1452   /* ... but we can do file transfers! */
1453   mwServiceAware_setAttributeBoolean(pd->srvc_aware,
1454 				     mwAttribute_FILE_TRANSFER, TRUE);
1455 
1456   blist_init(acct);
1457 }
1458 
1459 
session_loginRedirect(struct mwSession * session,const char * host)1460 static void session_loginRedirect(struct mwSession *session,
1461 				  const char *host) {
1462   struct mwPurplePluginData *pd;
1463   PurpleConnection *gc;
1464   PurpleAccount *account;
1465   guint port;
1466   const char *current_host;
1467 
1468   pd = mwSession_getClientData(session);
1469   gc = pd->gc;
1470   account = purple_connection_get_account(gc);
1471   port = purple_account_get_int(account, MW_KEY_PORT, MW_PLUGIN_DEFAULT_PORT);
1472   current_host = purple_account_get_string(account, MW_KEY_HOST,
1473 					 MW_PLUGIN_DEFAULT_HOST);
1474 
1475   if(purple_account_get_bool(account, MW_KEY_FORCE, FALSE) ||
1476      !host || purple_strequal(current_host, host) ||
1477      (purple_proxy_connect(gc, account, host, port, connect_cb, pd) == NULL)) {
1478 
1479     /* if we're configured to force logins, or if we're being
1480        redirected to the already configured host, or if we couldn't
1481        connect to the new host, we'll force the login instead */
1482 
1483     mwSession_forceLogin(session);
1484   }
1485 }
1486 
1487 
1488 static void mw_prpl_set_status(PurpleAccount *acct, PurpleStatus *status);
1489 
1490 
1491 /** called from mw_session_stateChange when the session's state is
1492     mwSession_STARTED. Any finalizing of start-up stuff should go
1493     here */
session_started(struct mwPurplePluginData * pd)1494 static void session_started(struct mwPurplePluginData *pd) {
1495   PurpleStatus *status;
1496   PurpleAccount *acct;
1497 
1498   /* set out initial status */
1499   acct = purple_connection_get_account(pd->gc);
1500   status = purple_account_get_active_status(acct);
1501   mw_prpl_set_status(acct, status);
1502 
1503   /* start watching for new conversations */
1504   purple_signal_connect(purple_conversations_get_handle(),
1505 		      "conversation-created", pd,
1506 		      PURPLE_CALLBACK(conversation_created_cb), pd);
1507 
1508   /* watch for group extended menu items */
1509   purple_signal_connect(purple_blist_get_handle(),
1510 		      "blist-node-extended-menu", pd,
1511 		      PURPLE_CALLBACK(blist_node_menu_cb), pd);
1512 
1513   /* use our services to do neat things */
1514   services_starting(pd);
1515 }
1516 
1517 
session_stopping(struct mwPurplePluginData * pd)1518 static void session_stopping(struct mwPurplePluginData *pd) {
1519   /* stop watching the signals from session_started */
1520   purple_signals_disconnect_by_handle(pd);
1521 }
1522 
1523 
mw_session_stateChange(struct mwSession * session,enum mwSessionState state,gpointer info)1524 static void mw_session_stateChange(struct mwSession *session,
1525 				   enum mwSessionState state,
1526 				   gpointer info) {
1527   struct mwPurplePluginData *pd;
1528   PurpleConnection *gc;
1529   const char *msg = NULL;
1530 
1531   pd = mwSession_getClientData(session);
1532   gc = pd->gc;
1533 
1534   switch(state) {
1535   case mwSession_STARTING:
1536     msg = _("Sending Handshake");
1537     purple_connection_update_progress(gc, msg, 2, MW_CONNECT_STEPS);
1538     break;
1539 
1540   case mwSession_HANDSHAKE:
1541     msg = _("Waiting for Handshake Acknowledgement");
1542     purple_connection_update_progress(gc, msg, 3, MW_CONNECT_STEPS);
1543     break;
1544 
1545   case mwSession_HANDSHAKE_ACK:
1546     msg = _("Handshake Acknowledged, Sending Login");
1547     purple_connection_update_progress(gc, msg, 4, MW_CONNECT_STEPS);
1548     break;
1549 
1550   case mwSession_LOGIN:
1551     msg = _("Waiting for Login Acknowledgement");
1552     purple_connection_update_progress(gc, msg, 5, MW_CONNECT_STEPS);
1553     break;
1554 
1555   case mwSession_LOGIN_REDIR:
1556     msg = _("Login Redirected");
1557     purple_connection_update_progress(gc, msg, 6, MW_CONNECT_STEPS);
1558     session_loginRedirect(session, info);
1559     break;
1560 
1561   case mwSession_LOGIN_CONT:
1562     msg = _("Forcing Login");
1563     purple_connection_update_progress(gc, msg, 7, MW_CONNECT_STEPS);
1564     break;
1565 
1566   case mwSession_LOGIN_ACK:
1567     msg = _("Login Acknowledged");
1568     purple_connection_update_progress(gc, msg, 8, MW_CONNECT_STEPS);
1569     break;
1570 
1571   case mwSession_STARTED:
1572     msg = _("Starting Services");
1573     purple_connection_update_progress(gc, msg, 9, MW_CONNECT_STEPS);
1574 
1575     session_started(pd);
1576 
1577     msg = _("Connected");
1578     purple_connection_update_progress(gc, msg, 10, MW_CONNECT_STEPS);
1579     purple_connection_set_state(gc, PURPLE_CONNECTED);
1580     break;
1581 
1582   case mwSession_STOPPING:
1583 
1584     session_stopping(pd);
1585 
1586     if(GPOINTER_TO_UINT(info) & ERR_FAILURE) {
1587       char *err = mwError(GPOINTER_TO_UINT(info));
1588       PurpleConnectionError reason;
1589       switch (GPOINTER_TO_UINT(info)) {
1590       case VERSION_MISMATCH:
1591         reason = PURPLE_CONNECTION_ERROR_OTHER_ERROR;
1592         break;
1593 
1594       case USER_RESTRICTED:
1595       case INCORRECT_LOGIN:
1596       case USER_UNREGISTERED:
1597       case GUEST_IN_USE:
1598         reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
1599         break;
1600 
1601       case ENCRYPT_MISMATCH:
1602       case ERR_ENCRYPT_NO_SUPPORT:
1603       case ERR_NO_COMMON_ENCRYPT:
1604         reason = PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR;
1605         break;
1606 
1607       case VERIFICATION_DOWN:
1608         reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE;
1609         break;
1610 
1611       case MULTI_SERVER_LOGIN:
1612       case MULTI_SERVER_LOGIN2:
1613         reason = PURPLE_CONNECTION_ERROR_NAME_IN_USE;
1614         break;
1615 
1616       default:
1617         reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
1618       }
1619       purple_connection_error_reason(gc, reason, err);
1620       g_free(err);
1621     }
1622     break;
1623 
1624   case mwSession_STOPPED:
1625     break;
1626 
1627   case mwSession_UNKNOWN:
1628   default:
1629     DEBUG_WARN("session in unknown state\n");
1630   }
1631 }
1632 
1633 
mw_session_setPrivacyInfo(struct mwSession * session)1634 static void mw_session_setPrivacyInfo(struct mwSession *session) {
1635   struct mwPurplePluginData *pd;
1636   PurpleConnection *gc;
1637   PurpleAccount *acct;
1638   struct mwPrivacyInfo *privacy;
1639   GSList *l, **ll;
1640   guint count;
1641 
1642   DEBUG_INFO("privacy information set from server\n");
1643 
1644   g_return_if_fail(session != NULL);
1645 
1646   pd = mwSession_getClientData(session);
1647   g_return_if_fail(pd != NULL);
1648 
1649   gc = pd->gc;
1650   g_return_if_fail(gc != NULL);
1651 
1652   acct = purple_connection_get_account(gc);
1653   g_return_if_fail(acct != NULL);
1654 
1655   privacy = mwSession_getPrivacyInfo(session);
1656   count = privacy->count;
1657 
1658   ll = (privacy->deny)? &acct->deny: &acct->permit;
1659   for(l = *ll; l; l = l->next) g_free(l->data);
1660   g_slist_free(*ll);
1661   l = *ll = NULL;
1662 
1663   while(count--) {
1664     struct mwUserItem *u = privacy->users + count;
1665     l = g_slist_prepend(l, g_strdup(u->id));
1666   }
1667   *ll = l;
1668 }
1669 
1670 
mw_session_setUserStatus(struct mwSession * session)1671 static void mw_session_setUserStatus(struct mwSession *session) {
1672   struct mwPurplePluginData *pd;
1673   PurpleConnection *gc;
1674   struct mwAwareIdBlock idb = { mwAware_USER, NULL, NULL };
1675   struct mwUserStatus *stat;
1676 
1677   g_return_if_fail(session != NULL);
1678 
1679   pd = mwSession_getClientData(session);
1680   g_return_if_fail(pd != NULL);
1681 
1682   gc = pd->gc;
1683   g_return_if_fail(gc != NULL);
1684 
1685   idb.user = mwSession_getProperty(session, mwSession_AUTH_USER_ID);
1686   stat = mwSession_getUserStatus(session);
1687 
1688   /* trigger an update of our own status if we're in the buddy list */
1689   mwServiceAware_setStatus(pd->srvc_aware, &idb, stat);
1690 }
1691 
1692 
mw_session_admin(struct mwSession * session,const char * text)1693 static void mw_session_admin(struct mwSession *session,
1694 			     const char *text) {
1695   PurpleConnection *gc;
1696   PurpleAccount *acct;
1697   const char *host;
1698   const char *msg;
1699   char *prim;
1700 
1701   gc = session_to_gc(session);
1702   g_return_if_fail(gc != NULL);
1703 
1704   acct = purple_connection_get_account(gc);
1705   g_return_if_fail(acct != NULL);
1706 
1707   host = purple_account_get_string(acct, MW_KEY_HOST, NULL);
1708 
1709   msg = _("A Sametime administrator has issued the following announcement"
1710 	   " on server %s");
1711   prim = g_strdup_printf(msg, NSTR(host));
1712 
1713   purple_notify_message(gc, PURPLE_NOTIFY_MSG_INFO,
1714 		      _("Sametime Administrator Announcement"),
1715 		      prim, text, NULL, NULL);
1716 
1717   g_free(prim);
1718 }
1719 
1720 
1721 /** called from read_cb, attempts to read available data from sock and
1722     pass it to the session, passing back the return code from the read
1723     call for handling in read_cb */
read_recv(struct mwSession * session,int sock)1724 static int read_recv(struct mwSession *session, int sock) {
1725   guchar buf[BUF_LEN];
1726   int len;
1727 
1728   len = read(sock, buf, BUF_LEN);
1729   if(len > 0) {
1730     mwSession_recv(session, buf, len);
1731   }
1732 
1733   return len;
1734 }
1735 
1736 
1737 /** callback triggered from purple_input_add, watches the socked for
1738     available data to be processed by the session */
read_cb(gpointer data,gint source,PurpleInputCondition cond)1739 static void read_cb(gpointer data, gint source, PurpleInputCondition cond) {
1740   struct mwPurplePluginData *pd = data;
1741   int ret = 0, err = 0;
1742 
1743   g_return_if_fail(pd != NULL);
1744 
1745   ret = read_recv(pd->session, pd->socket);
1746 
1747   /* normal operation ends here */
1748   if(ret > 0) return;
1749 
1750   /* fetch the global error value */
1751   err = errno;
1752 
1753   /* read problem occurred if we're here, so we'll need to take care of
1754      it and clean up internal state */
1755 
1756   if(pd->socket) {
1757     close(pd->socket);
1758     pd->socket = 0;
1759   }
1760 
1761   if(pd->gc->inpa) {
1762     purple_input_remove(pd->gc->inpa);
1763     pd->gc->inpa = 0;
1764   }
1765 
1766   if(! ret) {
1767     DEBUG_INFO("connection reset\n");
1768     purple_connection_error_reason(pd->gc,
1769                                    PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1770                                    _("Server closed the connection"));
1771 
1772   } else if(ret < 0) {
1773     const gchar *err_str = g_strerror(err);
1774     char *msg = NULL;
1775 
1776     DEBUG_INFO("error in read callback: %s\n", err_str);
1777 
1778     msg = g_strdup_printf(_("Lost connection with server: %s"), err_str);
1779     purple_connection_error_reason(pd->gc,
1780                                    PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1781                                    msg);
1782     g_free(msg);
1783   }
1784 }
1785 
1786 
1787 /** Callback passed to purple_proxy_connect when an account is logged
1788     in, and if the session logging in receives a redirect message */
connect_cb(gpointer data,gint source,const gchar * error_message)1789 static void connect_cb(gpointer data, gint source, const gchar *error_message) {
1790 
1791   struct mwPurplePluginData *pd = data;
1792   PurpleConnection *gc = pd->gc;
1793 
1794   if(source < 0) {
1795     /* connection failed */
1796 
1797     if(pd->socket) {
1798       /* this is a redirect connect, force login on existing socket */
1799       mwSession_forceLogin(pd->session);
1800 
1801     } else {
1802       /* this is a regular connect, error out */
1803       gchar *tmp = g_strdup_printf(_("Unable to connect: %s"),
1804           error_message);
1805       purple_connection_error_reason(pd->gc,
1806                                      PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1807                                      tmp);
1808       g_free(tmp);
1809     }
1810 
1811     return;
1812   }
1813 
1814   if(pd->socket) {
1815     /* stop any existing login attempt */
1816     mwSession_stop(pd->session, ERR_SUCCESS);
1817   }
1818 
1819   pd->socket = source;
1820   gc->inpa = purple_input_add(source, PURPLE_INPUT_READ,
1821 			    read_cb, pd);
1822 
1823   mwSession_start(pd->session);
1824 }
1825 
1826 
mw_session_announce(struct mwSession * s,struct mwLoginInfo * from,gboolean may_reply,const char * text)1827 static void mw_session_announce(struct mwSession *s,
1828 				struct mwLoginInfo *from,
1829 				gboolean may_reply,
1830 				const char *text) {
1831   struct mwPurplePluginData *pd;
1832   PurpleAccount *acct;
1833   PurpleConversation *conv;
1834   PurpleBuddy *buddy;
1835   char *who = from->user_id;
1836   char *msg;
1837 
1838   pd = mwSession_getClientData(s);
1839   acct = purple_connection_get_account(pd->gc);
1840   conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, acct);
1841   if(! conv) conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, who);
1842 
1843   buddy = purple_find_buddy(acct, who);
1844   if(buddy) who = (char *) purple_buddy_get_contact_alias(buddy);
1845 
1846   who = g_strdup_printf(_("Announcement from %s"), who);
1847   msg = purple_markup_linkify(text);
1848 
1849   purple_conversation_write(conv, who, msg ? msg : "", PURPLE_MESSAGE_RECV, time(NULL));
1850   g_free(who);
1851   g_free(msg);
1852 }
1853 
1854 
1855 static struct mwSessionHandler mw_session_handler = {
1856   mw_session_io_write,
1857   mw_session_io_close,
1858   mw_session_clear,
1859   mw_session_stateChange,
1860   mw_session_setPrivacyInfo,
1861   mw_session_setUserStatus,
1862   mw_session_admin,
1863   mw_session_announce,
1864 };
1865 
1866 
mw_aware_on_attrib(struct mwServiceAware * srvc,struct mwAwareAttribute * attrib)1867 static void mw_aware_on_attrib(struct mwServiceAware *srvc,
1868 			       struct mwAwareAttribute *attrib) {
1869 
1870   ; /** @todo handle server attributes.  There may be some stuff we
1871 	actually want to look for, but I'm not aware of anything right
1872 	now.*/
1873 }
1874 
1875 
mw_aware_clear(struct mwServiceAware * srvc)1876 static void mw_aware_clear(struct mwServiceAware *srvc) {
1877   ; /* nothing for now */
1878 }
1879 
1880 
1881 static struct mwAwareHandler mw_aware_handler = {
1882   mw_aware_on_attrib,
1883   mw_aware_clear,
1884 };
1885 
1886 
mw_srvc_aware_new(struct mwSession * s)1887 static struct mwServiceAware *mw_srvc_aware_new(struct mwSession *s) {
1888   struct mwServiceAware *srvc;
1889   srvc = mwServiceAware_new(s, &mw_aware_handler);
1890   return srvc;
1891 };
1892 
1893 
mw_conf_invited(struct mwConference * conf,struct mwLoginInfo * inviter,const char * invitation)1894 static void mw_conf_invited(struct mwConference *conf,
1895 			    struct mwLoginInfo *inviter,
1896 			    const char *invitation) {
1897 
1898   struct mwServiceConference *srvc;
1899   struct mwSession *session;
1900   struct mwPurplePluginData *pd;
1901   PurpleConnection *gc;
1902 
1903   char *c_inviter, *c_name, *c_topic, *c_invitation;
1904   GHashTable *ht;
1905 
1906   srvc = mwConference_getService(conf);
1907   session = mwService_getSession(MW_SERVICE(srvc));
1908   pd = mwSession_getClientData(session);
1909   gc = pd->gc;
1910 
1911   ht = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
1912 
1913   c_inviter = g_strdup(inviter->user_id);
1914   g_hash_table_insert(ht, CHAT_KEY_CREATOR, c_inviter);
1915 
1916   c_name = g_strdup(mwConference_getName(conf));
1917   g_hash_table_insert(ht, CHAT_KEY_NAME, c_name);
1918 
1919   c_topic = g_strdup(mwConference_getTitle(conf));
1920   g_hash_table_insert(ht, CHAT_KEY_TOPIC, c_topic);
1921 
1922   c_invitation = g_strdup(invitation);
1923   g_hash_table_insert(ht, CHAT_KEY_INVITE, c_invitation);
1924 
1925   DEBUG_INFO("received invitation from '%s' to join ('%s','%s'): '%s'\n",
1926 	     NSTR(c_inviter), NSTR(c_name),
1927 	     NSTR(c_topic), NSTR(c_invitation));
1928 
1929   if(! c_topic) c_topic = "(no title)";
1930   if(! c_invitation) c_invitation = "(no message)";
1931   serv_got_chat_invite(gc, c_topic, c_inviter, c_invitation, ht);
1932 }
1933 
1934 
1935 /* The following mess helps us relate a mwConference to a PurpleConvChat
1936    in the various forms by which either may be indicated */
1937 
1938 #define CONF_TO_ID(conf)   (GPOINTER_TO_INT(conf))
1939 #define ID_TO_CONF(pd, id) (conf_find_by_id((pd), (id)))
1940 
1941 #define CHAT_TO_ID(chat)   (purple_conv_chat_get_id(chat))
1942 #define ID_TO_CHAT(id)     (purple_find_chat(id))
1943 
1944 #define CHAT_TO_CONF(pd, chat)  (ID_TO_CONF((pd), CHAT_TO_ID(chat)))
1945 #define CONF_TO_CHAT(conf)      (ID_TO_CHAT(CONF_TO_ID(conf)))
1946 
1947 
1948 static struct mwConference *
conf_find_by_id(struct mwPurplePluginData * pd,int id)1949 conf_find_by_id(struct mwPurplePluginData *pd, int id) {
1950 
1951   struct mwServiceConference *srvc = pd->srvc_conf;
1952   struct mwConference *conf = NULL;
1953   GList *l, *ll;
1954 
1955   ll = mwServiceConference_getConferences(srvc);
1956   for(l = ll; l; l = l->next) {
1957     struct mwConference *c = l->data;
1958     PurpleConvChat *h = mwConference_getClientData(c);
1959 
1960     if(CHAT_TO_ID(h) == id) {
1961       conf = c;
1962       break;
1963     }
1964   }
1965   g_list_free(ll);
1966 
1967   return conf;
1968 }
1969 
1970 
mw_conf_opened(struct mwConference * conf,GList * members)1971 static void mw_conf_opened(struct mwConference *conf, GList *members) {
1972   struct mwServiceConference *srvc;
1973   struct mwSession *session;
1974   struct mwPurplePluginData *pd;
1975   PurpleConnection *gc;
1976   PurpleConversation *g_conf;
1977 
1978   const char *n = mwConference_getName(conf);
1979   const char *t = mwConference_getTitle(conf);
1980 
1981   DEBUG_INFO("conf %s opened, %u initial members\n",
1982 	     NSTR(n), g_list_length(members));
1983 
1984   srvc = mwConference_getService(conf);
1985   session = mwService_getSession(MW_SERVICE(srvc));
1986   pd = mwSession_getClientData(session);
1987   gc = pd->gc;
1988 
1989   if(! t) t = "(no title)";
1990   g_conf = serv_got_joined_chat(gc, CONF_TO_ID(conf), t);
1991 
1992   mwConference_setClientData(conf, PURPLE_CONV_CHAT(g_conf), NULL);
1993 
1994   for(; members; members = members->next) {
1995     struct mwLoginInfo *peer = members->data;
1996     purple_conv_chat_add_user(PURPLE_CONV_CHAT(g_conf), peer->user_id,
1997 			    NULL, PURPLE_CBFLAGS_NONE, FALSE);
1998   }
1999 }
2000 
2001 
mw_conf_closed(struct mwConference * conf,guint32 reason)2002 static void mw_conf_closed(struct mwConference *conf, guint32 reason) {
2003   struct mwServiceConference *srvc;
2004   struct mwSession *session;
2005   struct mwPurplePluginData *pd;
2006   PurpleConnection *gc;
2007 
2008   const char *n = mwConference_getName(conf);
2009   char *msg = mwError(reason);
2010 
2011   DEBUG_INFO("conf %s closed, 0x%08x\n", NSTR(n), reason);
2012 
2013   srvc = mwConference_getService(conf);
2014   session = mwService_getSession(MW_SERVICE(srvc));
2015   pd = mwSession_getClientData(session);
2016   gc = pd->gc;
2017 
2018   serv_got_chat_left(gc, CONF_TO_ID(conf));
2019 
2020   purple_notify_error(gc, _("Conference Closed"), NULL, msg);
2021   g_free(msg);
2022 }
2023 
2024 
mw_conf_peer_joined(struct mwConference * conf,struct mwLoginInfo * peer)2025 static void mw_conf_peer_joined(struct mwConference *conf,
2026 				struct mwLoginInfo *peer) {
2027 
2028   PurpleConvChat *g_conf;
2029 
2030   const char *n = mwConference_getName(conf);
2031 
2032   DEBUG_INFO("%s joined conf %s\n", NSTR(peer->user_id), NSTR(n));
2033 
2034   g_conf = mwConference_getClientData(conf);
2035   g_return_if_fail(g_conf != NULL);
2036 
2037   purple_conv_chat_add_user(g_conf, peer->user_id,
2038 			  NULL, PURPLE_CBFLAGS_NONE, TRUE);
2039 }
2040 
2041 
mw_conf_peer_parted(struct mwConference * conf,struct mwLoginInfo * peer)2042 static void mw_conf_peer_parted(struct mwConference *conf,
2043 				struct mwLoginInfo *peer) {
2044 
2045   PurpleConvChat *g_conf;
2046 
2047   const char *n = mwConference_getName(conf);
2048 
2049   DEBUG_INFO("%s left conf %s\n", NSTR(peer->user_id), NSTR(n));
2050 
2051   g_conf = mwConference_getClientData(conf);
2052   g_return_if_fail(g_conf != NULL);
2053 
2054   purple_conv_chat_remove_user(g_conf, peer->user_id, NULL);
2055 }
2056 
2057 
mw_conf_text(struct mwConference * conf,struct mwLoginInfo * who,const char * text)2058 static void mw_conf_text(struct mwConference *conf,
2059 			 struct mwLoginInfo *who, const char *text) {
2060 
2061   struct mwServiceConference *srvc;
2062   struct mwSession *session;
2063   struct mwPurplePluginData *pd;
2064   PurpleConnection *gc;
2065   char *esc;
2066 
2067   if(! text) return;
2068 
2069   srvc = mwConference_getService(conf);
2070   session = mwService_getSession(MW_SERVICE(srvc));
2071   pd = mwSession_getClientData(session);
2072   gc = pd->gc;
2073 
2074   esc = g_markup_escape_text(text, -1);
2075   serv_got_chat_in(gc, CONF_TO_ID(conf), who->user_id, 0, esc, time(NULL));
2076   g_free(esc);
2077 }
2078 
2079 
mw_conf_typing(struct mwConference * conf,struct mwLoginInfo * who,gboolean typing)2080 static void mw_conf_typing(struct mwConference *conf,
2081 			   struct mwLoginInfo *who, gboolean typing) {
2082 
2083   /* purple really has no good way to expose this to the user. */
2084 
2085   const char *n = mwConference_getName(conf);
2086   const char *w = who->user_id;
2087 
2088   if(typing) {
2089     DEBUG_INFO("%s in conf %s: <typing>\n", NSTR(w), NSTR(n));
2090 
2091   } else {
2092     DEBUG_INFO("%s in conf %s: <stopped typing>\n", NSTR(w), NSTR(n));
2093   }
2094 }
2095 
2096 
mw_conf_clear(struct mwServiceConference * srvc)2097 static void mw_conf_clear(struct mwServiceConference *srvc) {
2098   ;
2099 }
2100 
2101 
2102 static struct mwConferenceHandler mw_conference_handler = {
2103   mw_conf_invited,
2104   mw_conf_opened,
2105   mw_conf_closed,
2106   mw_conf_peer_joined,
2107   mw_conf_peer_parted,
2108   mw_conf_text,
2109   mw_conf_typing,
2110   mw_conf_clear,
2111 };
2112 
2113 
mw_srvc_conf_new(struct mwSession * s)2114 static struct mwServiceConference *mw_srvc_conf_new(struct mwSession *s) {
2115   struct mwServiceConference *srvc;
2116   srvc = mwServiceConference_new(s, &mw_conference_handler);
2117   return srvc;
2118 }
2119 
2120 
2121 /** size of an outgoing file transfer chunk */
2122 #define MW_FT_LEN  (BUF_LONG * 2)
2123 
2124 
ft_incoming_cancel(PurpleXfer * xfer)2125 static void ft_incoming_cancel(PurpleXfer *xfer) {
2126   /* incoming transfer rejected or cancelled in-progress */
2127   struct mwFileTransfer *ft = xfer->data;
2128   if(ft) mwFileTransfer_reject(ft);
2129 }
2130 
2131 
ft_incoming_init(PurpleXfer * xfer)2132 static void ft_incoming_init(PurpleXfer *xfer) {
2133   /* incoming transfer accepted */
2134 
2135   /* - accept the mwFileTransfer
2136      - open/create the local FILE "wb"
2137      - stick the FILE's fp in xfer->dest_fp
2138   */
2139 
2140   struct mwFileTransfer *ft;
2141   FILE *fp;
2142 
2143   ft = xfer->data;
2144 
2145   fp = g_fopen(xfer->local_filename, "wb");
2146   if(! fp) {
2147     mwFileTransfer_cancel(ft);
2148     return;
2149   }
2150 
2151   xfer->dest_fp = fp;
2152   mwFileTransfer_accept(ft);
2153 }
2154 
2155 
mw_ft_offered(struct mwFileTransfer * ft)2156 static void mw_ft_offered(struct mwFileTransfer *ft) {
2157   /*
2158     - create a purple ft object
2159     - offer it
2160   */
2161 
2162   struct mwServiceFileTransfer *srvc;
2163   struct mwSession *session;
2164   struct mwPurplePluginData *pd;
2165   PurpleConnection *gc;
2166   PurpleAccount *acct;
2167   const char *who;
2168   PurpleXfer *xfer;
2169 
2170   /* @todo add some safety checks */
2171   srvc = mwFileTransfer_getService(ft);
2172   session = mwService_getSession(MW_SERVICE(srvc));
2173   pd = mwSession_getClientData(session);
2174   gc = pd->gc;
2175   acct = purple_connection_get_account(gc);
2176 
2177   who = mwFileTransfer_getUser(ft)->user;
2178 
2179   DEBUG_INFO("file transfer %p offered\n", ft);
2180   DEBUG_INFO(" from: %s\n", NSTR(who));
2181   DEBUG_INFO(" file: %s\n", NSTR(mwFileTransfer_getFileName(ft)));
2182   DEBUG_INFO(" size: %u\n", mwFileTransfer_getFileSize(ft));
2183   DEBUG_INFO(" text: %s\n", NSTR(mwFileTransfer_getMessage(ft)));
2184 
2185   xfer = purple_xfer_new(acct, PURPLE_XFER_RECEIVE, who);
2186   if (xfer)
2187   {
2188 	purple_xfer_ref(xfer);
2189 	mwFileTransfer_setClientData(ft, xfer, (GDestroyNotify) purple_xfer_unref);
2190 	xfer->data = ft;
2191 
2192 	purple_xfer_set_init_fnc(xfer, ft_incoming_init);
2193 	purple_xfer_set_cancel_recv_fnc(xfer, ft_incoming_cancel);
2194 	purple_xfer_set_request_denied_fnc(xfer, ft_incoming_cancel);
2195 
2196 	purple_xfer_set_filename(xfer, mwFileTransfer_getFileName(ft));
2197 	purple_xfer_set_size(xfer, mwFileTransfer_getFileSize(ft));
2198 	purple_xfer_set_message(xfer, mwFileTransfer_getMessage(ft));
2199 
2200 	purple_xfer_request(xfer);
2201   }
2202 }
2203 
2204 
ft_send(struct mwFileTransfer * ft,FILE * fp)2205 static void ft_send(struct mwFileTransfer *ft, FILE *fp) {
2206   guchar buf[MW_FT_LEN];
2207   struct mwOpaque o = { MW_FT_LEN, buf };
2208   guint32 rem;
2209   PurpleXfer *xfer;
2210 
2211   xfer = mwFileTransfer_getClientData(ft);
2212 
2213   rem = mwFileTransfer_getRemaining(ft);
2214   if(rem < MW_FT_LEN) o.len = rem;
2215 
2216   if (fread(buf, (size_t)o.len, 1, fp) == 1) {
2217 
2218     /* calculate progress and display it */
2219     xfer->bytes_sent += o.len;
2220     xfer->bytes_remaining -= o.len;
2221     purple_xfer_update_progress(xfer);
2222 
2223     mwFileTransfer_send(ft, &o);
2224 
2225   } else {
2226     int err = errno;
2227     DEBUG_WARN("problem reading from file %s: %s\n",
2228 	       NSTR(mwFileTransfer_getFileName(ft)), g_strerror(err));
2229 
2230     mwFileTransfer_cancel(ft);
2231   }
2232 }
2233 
2234 
mw_ft_opened(struct mwFileTransfer * ft)2235 static void mw_ft_opened(struct mwFileTransfer *ft) {
2236   /*
2237     - get purple ft from client data in ft
2238     - set the state to active
2239   */
2240 
2241   PurpleXfer *xfer;
2242 
2243   xfer = mwFileTransfer_getClientData(ft);
2244 
2245   if(! xfer) {
2246     mwFileTransfer_cancel(ft);
2247     mwFileTransfer_free(ft);
2248     g_return_if_reached();
2249   }
2250 
2251   if(purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) {
2252     xfer->dest_fp = g_fopen(xfer->local_filename, "rb");
2253     if (xfer->dest_fp)
2254       ft_send(ft, xfer->dest_fp);
2255   }
2256 }
2257 
2258 
mw_ft_closed(struct mwFileTransfer * ft,guint32 code)2259 static void mw_ft_closed(struct mwFileTransfer *ft, guint32 code) {
2260   /*
2261     - get purple ft from client data in ft
2262     - indicate rejection/cancelation/completion
2263     - free the file transfer itself
2264   */
2265 
2266   PurpleXfer *xfer;
2267 
2268   xfer = mwFileTransfer_getClientData(ft);
2269   if(xfer) {
2270     xfer->data = NULL;
2271 
2272     if(! mwFileTransfer_getRemaining(ft)) {
2273       purple_xfer_set_completed(xfer, TRUE);
2274       purple_xfer_end(xfer);
2275 
2276     } else if(mwFileTransfer_isCancelLocal(ft)) {
2277       /* calling purple_xfer_cancel_local is redundant, since that's
2278 	 probably what triggered this function to be called */
2279       ;
2280 
2281     } else if(mwFileTransfer_isCancelRemote(ft)) {
2282       /* steal the reference for the xfer */
2283       mwFileTransfer_setClientData(ft, NULL, NULL);
2284       purple_xfer_cancel_remote(xfer);
2285 
2286       /* drop the stolen reference */
2287       purple_xfer_unref(xfer);
2288       return;
2289     }
2290   }
2291 
2292   mwFileTransfer_free(ft);
2293 }
2294 
2295 
mw_ft_recv(struct mwFileTransfer * ft,struct mwOpaque * data)2296 static void mw_ft_recv(struct mwFileTransfer *ft,
2297 		       struct mwOpaque *data) {
2298   /*
2299     - get purple ft from client data in ft
2300     - update transfered percentage
2301     - if done, destroy the ft, disassociate from purple ft
2302   */
2303 
2304   PurpleXfer *xfer;
2305   FILE *fp;
2306   size_t wc;
2307 
2308   xfer = mwFileTransfer_getClientData(ft);
2309   g_return_if_fail(xfer != NULL);
2310 
2311   fp = xfer->dest_fp;
2312   g_return_if_fail(fp != NULL);
2313 
2314   /* we must collect and save our precious data */
2315   wc = fwrite(data->data, 1, data->len, fp);
2316   if (wc != data->len) {
2317     DEBUG_ERROR("failed to write data\n");
2318     purple_xfer_cancel_local(xfer);
2319     return;
2320   }
2321 
2322   /* update the progress */
2323   xfer->bytes_sent += data->len;
2324   xfer->bytes_remaining -= data->len;
2325   purple_xfer_update_progress(xfer);
2326 
2327   /* let the other side know we got it, and to send some more */
2328   mwFileTransfer_ack(ft);
2329 }
2330 
2331 
mw_ft_ack(struct mwFileTransfer * ft)2332 static void mw_ft_ack(struct mwFileTransfer *ft) {
2333   PurpleXfer *xfer;
2334 
2335   xfer = mwFileTransfer_getClientData(ft);
2336   g_return_if_fail(xfer != NULL);
2337   g_return_if_fail(xfer->watcher == 0);
2338 
2339   if(! mwFileTransfer_getRemaining(ft)) {
2340     purple_xfer_set_completed(xfer, TRUE);
2341     purple_xfer_end(xfer);
2342 
2343   } else if(mwFileTransfer_isOpen(ft)) {
2344     ft_send(ft, xfer->dest_fp);
2345   }
2346 }
2347 
2348 
mw_ft_clear(struct mwServiceFileTransfer * srvc)2349 static void mw_ft_clear(struct mwServiceFileTransfer *srvc) {
2350   ;
2351 }
2352 
2353 
2354 static struct mwFileTransferHandler mw_ft_handler = {
2355   mw_ft_offered,
2356   mw_ft_opened,
2357   mw_ft_closed,
2358   mw_ft_recv,
2359   mw_ft_ack,
2360   mw_ft_clear,
2361 };
2362 
2363 
mw_srvc_ft_new(struct mwSession * s)2364 static struct mwServiceFileTransfer *mw_srvc_ft_new(struct mwSession *s) {
2365   struct mwServiceFileTransfer *srvc;
2366   GHashTable *ft_map;
2367 
2368   ft_map = g_hash_table_new(g_direct_hash, g_direct_equal);
2369 
2370   srvc = mwServiceFileTransfer_new(s, &mw_ft_handler);
2371   mwService_setClientData(MW_SERVICE(srvc), ft_map,
2372 			  (GDestroyNotify) g_hash_table_destroy);
2373 
2374   return srvc;
2375 }
2376 
2377 
convo_data_free(struct convo_data * cd)2378 static void convo_data_free(struct convo_data *cd) {
2379   GList *l;
2380 
2381   /* clean the queue */
2382   for(l = cd->queue; l; l = g_list_delete_link(l, l)) {
2383     struct convo_msg *m = l->data;
2384     if(m->clear) m->clear(m->data);
2385     g_free(m);
2386   }
2387 
2388   g_free(cd);
2389 }
2390 
2391 
2392 /** allocates a convo_data structure and associates it with the
2393     conversation in the client data slot */
convo_data_new(struct mwConversation * conv)2394 static void convo_data_new(struct mwConversation *conv) {
2395   struct convo_data *cd;
2396 
2397   g_return_if_fail(conv != NULL);
2398 
2399   if(mwConversation_getClientData(conv))
2400     return;
2401 
2402   cd = g_new0(struct convo_data, 1);
2403   cd->conv = conv;
2404 
2405   mwConversation_setClientData(conv, cd, (GDestroyNotify) convo_data_free);
2406 }
2407 
2408 
convo_get_gconv(struct mwConversation * conv)2409 static PurpleConversation *convo_get_gconv(struct mwConversation *conv) {
2410   struct mwServiceIm *srvc;
2411   struct mwSession *session;
2412   struct mwPurplePluginData *pd;
2413   PurpleConnection *gc;
2414   PurpleAccount *acct;
2415 
2416   struct mwIdBlock *idb;
2417 
2418   srvc = mwConversation_getService(conv);
2419   session = mwService_getSession(MW_SERVICE(srvc));
2420   pd = mwSession_getClientData(session);
2421   gc = pd->gc;
2422   acct = purple_connection_get_account(gc);
2423 
2424   idb = mwConversation_getTarget(conv);
2425 
2426   return purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
2427 					     idb->user, acct);
2428 }
2429 
2430 
convo_queue(struct mwConversation * conv,enum mwImSendType type,gconstpointer data)2431 static void convo_queue(struct mwConversation *conv,
2432 			enum mwImSendType type, gconstpointer data) {
2433 
2434   struct convo_data *cd;
2435   struct convo_msg *m;
2436 
2437   convo_data_new(conv);
2438   cd = mwConversation_getClientData(conv);
2439 
2440   m = g_new0(struct convo_msg, 1);
2441   m->type = type;
2442 
2443   switch(type) {
2444   case mwImSend_PLAIN:
2445     m->data = g_strdup(data);
2446     m->clear = g_free;
2447     break;
2448 
2449   case mwImSend_TYPING:
2450   default:
2451     m->data = (gpointer) data;
2452     m->clear = NULL;
2453   }
2454 
2455   cd->queue = g_list_append(cd->queue, m);
2456 }
2457 
2458 
2459 /* Does what it takes to get an error displayed for a conversation */
convo_error(struct mwConversation * conv,guint32 err)2460 static void convo_error(struct mwConversation *conv, guint32 err) {
2461   PurpleConversation *gconv;
2462   char *tmp, *text;
2463   struct mwIdBlock *idb;
2464 
2465   idb = mwConversation_getTarget(conv);
2466 
2467   tmp = mwError(err);
2468   text = g_strconcat(_("Unable to send message: "), tmp, NULL);
2469 
2470   gconv = convo_get_gconv(conv);
2471   if(gconv && !purple_conv_present_error(idb->user, gconv->account, text)) {
2472 
2473     g_free(text);
2474     text = g_strdup_printf(_("Unable to send message to %s:"),
2475 			   (idb->user)? idb->user: "(unknown)");
2476     purple_notify_error(purple_account_get_connection(gconv->account),
2477 		      NULL, text, tmp);
2478   }
2479 
2480   g_free(tmp);
2481   g_free(text);
2482 }
2483 
2484 
convo_queue_send(struct mwConversation * conv)2485 static void convo_queue_send(struct mwConversation *conv) {
2486   struct convo_data *cd;
2487   GList *l;
2488 
2489   cd = mwConversation_getClientData(conv);
2490 
2491   for(l = cd->queue; l; l = g_list_delete_link(l, l)) {
2492     struct convo_msg *m = l->data;
2493 
2494     mwConversation_send(conv, m->type, m->data);
2495 
2496     if(m->clear) m->clear(m->data);
2497     g_free(m);
2498   }
2499 
2500   cd->queue = NULL;
2501 }
2502 
2503 
2504 /**  called when a mw conversation leaves a purple conversation to
2505      inform the purple conversation that it's unsafe to offer any *cool*
2506      features. */
convo_nofeatures(struct mwConversation * conv)2507 static void convo_nofeatures(struct mwConversation *conv) {
2508   PurpleConversation *gconv;
2509   PurpleConnection *gc;
2510 
2511   gconv = convo_get_gconv(conv);
2512   if(! gconv) return;
2513 
2514   gc = purple_conversation_get_gc(gconv);
2515   if(! gc) return;
2516 
2517   purple_conversation_set_features(gconv, gc->flags);
2518 }
2519 
2520 
2521 /** called when a mw conversation and purple conversation come together,
2522     to inform the purple conversation of what features to offer the
2523     user */
convo_features(struct mwConversation * conv)2524 static void convo_features(struct mwConversation *conv) {
2525   PurpleConversation *gconv;
2526   PurpleConnectionFlags feat;
2527 
2528   gconv = convo_get_gconv(conv);
2529   if(! gconv) return;
2530 
2531   feat = purple_conversation_get_features(gconv);
2532 
2533   if(mwConversation_isOpen(conv)) {
2534     if(mwConversation_supports(conv, mwImSend_HTML)) {
2535       feat |= PURPLE_CONNECTION_HTML;
2536     } else {
2537       feat &= ~PURPLE_CONNECTION_HTML;
2538     }
2539 
2540     if(mwConversation_supports(conv, mwImSend_MIME)) {
2541       feat &= ~PURPLE_CONNECTION_NO_IMAGES;
2542     } else {
2543       feat |= PURPLE_CONNECTION_NO_IMAGES;
2544     }
2545 
2546     DEBUG_INFO("conversation features set to 0x%04x\n", feat);
2547     purple_conversation_set_features(gconv, feat);
2548 
2549   } else {
2550     convo_nofeatures(conv);
2551   }
2552 }
2553 
2554 
mw_conversation_opened(struct mwConversation * conv)2555 static void mw_conversation_opened(struct mwConversation *conv) {
2556   struct mwServiceIm *srvc;
2557   struct mwSession *session;
2558   struct mwPurplePluginData *pd;
2559   PurpleConnection *gc;
2560   PurpleAccount *acct;
2561 
2562   struct convo_dat *cd;
2563 
2564   srvc = mwConversation_getService(conv);
2565   session = mwService_getSession(MW_SERVICE(srvc));
2566   pd = mwSession_getClientData(session);
2567   gc = pd->gc;
2568   acct = purple_connection_get_account(gc);
2569 
2570   /* set up the queue */
2571   cd = mwConversation_getClientData(conv);
2572   if(cd) {
2573     convo_queue_send(conv);
2574 
2575     if(! convo_get_gconv(conv)) {
2576       mwConversation_free(conv);
2577       return;
2578     }
2579 
2580   } else {
2581     convo_data_new(conv);
2582   }
2583 
2584   { /* record the client key for the buddy */
2585     PurpleBuddy *buddy;
2586     struct mwLoginInfo *info;
2587     info = mwConversation_getTargetInfo(conv);
2588 
2589     buddy = purple_find_buddy(acct, info->user_id);
2590     if(buddy) {
2591       purple_blist_node_set_int((PurpleBlistNode *) buddy,
2592 			      BUDDY_KEY_CLIENT, info->type);
2593     }
2594   }
2595 
2596   convo_features(conv);
2597 }
2598 
2599 
mw_conversation_closed(struct mwConversation * conv,guint32 reason)2600 static void mw_conversation_closed(struct mwConversation *conv,
2601 				   guint32 reason) {
2602 
2603   struct convo_data *cd;
2604 
2605   g_return_if_fail(conv != NULL);
2606 
2607   /* if there's an error code and a non-typing message in the queue,
2608      print an error message to the conversation */
2609   cd = mwConversation_getClientData(conv);
2610   if(reason && cd && cd->queue) {
2611     GList *l;
2612     for(l = cd->queue; l; l = l->next) {
2613       struct convo_msg *m = l->data;
2614       if(m->type != mwImSend_TYPING) {
2615 	convo_error(conv, reason);
2616 	break;
2617       }
2618     }
2619   }
2620 
2621 #if 0
2622   /* don't do this, to prevent the occasional weird sending of
2623      formatted messages as plaintext when the other end closes the
2624      conversation after we've begun composing the message */
2625   convo_nofeatures(conv);
2626 #endif
2627 
2628   mwConversation_removeClientData(conv);
2629 }
2630 
2631 
im_recv_text(struct mwConversation * conv,struct mwPurplePluginData * pd,const char * msg)2632 static void im_recv_text(struct mwConversation *conv,
2633 			 struct mwPurplePluginData *pd,
2634 			 const char *msg) {
2635 
2636   struct mwIdBlock *idb;
2637   char *txt, *esc;
2638   const char *t;
2639 
2640   idb = mwConversation_getTarget(conv);
2641 
2642   txt = purple_utf8_try_convert(msg);
2643   t = txt? txt: msg;
2644 
2645   esc = g_markup_escape_text(t, -1);
2646   serv_got_im(pd->gc, idb->user, esc, 0, time(NULL));
2647   g_free(esc);
2648 
2649   g_free(txt);
2650 }
2651 
2652 
im_recv_typing(struct mwConversation * conv,struct mwPurplePluginData * pd,gboolean typing)2653 static void im_recv_typing(struct mwConversation *conv,
2654 			   struct mwPurplePluginData *pd,
2655 			   gboolean typing) {
2656 
2657   struct mwIdBlock *idb;
2658   idb = mwConversation_getTarget(conv);
2659 
2660   serv_got_typing(pd->gc, idb->user, 0,
2661 		  typing? PURPLE_TYPING: PURPLE_NOT_TYPING);
2662 }
2663 
2664 
im_recv_html(struct mwConversation * conv,struct mwPurplePluginData * pd,const char * msg)2665 static void im_recv_html(struct mwConversation *conv,
2666 			 struct mwPurplePluginData *pd,
2667 			 const char *msg) {
2668   struct mwIdBlock *idb;
2669   char *t1, *t2;
2670   const char *t;
2671 
2672   idb = mwConversation_getTarget(conv);
2673 
2674   /* ensure we're receiving UTF8 */
2675   t1 = purple_utf8_try_convert(msg);
2676   t = t1? t1: msg;
2677 
2678   /* convert entities to UTF8 so they'll log correctly */
2679   t2 = purple_utf8_ncr_decode(t);
2680   t = t2? t2: t;
2681 
2682   serv_got_im(pd->gc, idb->user, t, 0, time(NULL));
2683 
2684   g_free(t1);
2685   g_free(t2);
2686 }
2687 
2688 
im_recv_subj(struct mwConversation * conv,struct mwPurplePluginData * pd,const char * subj)2689 static void im_recv_subj(struct mwConversation *conv,
2690 			 struct mwPurplePluginData *pd,
2691 			 const char *subj) {
2692 
2693   /** @todo somehow indicate receipt of a conversation subject. It
2694       would also be nice if we added a /topic command for the
2695       protocol */
2696   ;
2697 }
2698 
2699 
2700 /** generate "cid:908@20582notesbuddy" from "<908@20582notesbuddy>" */
make_cid(const char * cid)2701 static char *make_cid(const char *cid) {
2702   gsize n;
2703   char *c, *d;
2704 
2705   g_return_val_if_fail(cid != NULL, NULL);
2706 
2707   n = strlen(cid);
2708   g_return_val_if_fail(n > 2, NULL);
2709 
2710   c = g_strndup(cid+1, n-2);
2711   d = g_strdup_printf("cid:%s", c);
2712 
2713   g_free(c);
2714   return d;
2715 }
2716 
2717 
im_recv_mime(struct mwConversation * conv,struct mwPurplePluginData * pd,const char * data)2718 static void im_recv_mime(struct mwConversation *conv,
2719 			 struct mwPurplePluginData *pd,
2720 			 const char *data) {
2721 
2722   GHashTable *img_by_cid;
2723   GList *images;
2724 
2725   GString *str;
2726 
2727   PurpleMimeDocument *doc;
2728   GList *parts;
2729 
2730   img_by_cid = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
2731   images = NULL;
2732 
2733   /* don't want the contained string to ever be NULL */
2734   str = g_string_new("");
2735 
2736   doc = purple_mime_document_parse(data);
2737 
2738   /* handle all the MIME parts */
2739   parts = purple_mime_document_get_parts(doc);
2740   for(; parts; parts = parts->next) {
2741     PurpleMimePart *part = parts->data;
2742     const char *type;
2743 
2744     type = purple_mime_part_get_field(part, "content-type");
2745     DEBUG_INFO("MIME part Content-Type: %s\n", NSTR(type));
2746 
2747     if(! type) {
2748       ; /* feh */
2749 
2750     } else if(purple_str_has_prefix(type, "image")) {
2751       /* put images into the image store */
2752 
2753       guchar *d_dat;
2754       gsize d_len;
2755       char *cid;
2756       int img;
2757 
2758       /* obtain and unencode the data */
2759       purple_mime_part_get_data_decoded(part, &d_dat, &d_len);
2760 
2761       /* look up the content id */
2762       cid = (char *) purple_mime_part_get_field(part, "Content-ID");
2763       cid = make_cid(cid);
2764 
2765       /* add image to the purple image store */
2766       img = purple_imgstore_add_with_id(d_dat, d_len, cid);
2767 
2768       /* map the cid to the image store identifier */
2769       g_hash_table_insert(img_by_cid, cid, GINT_TO_POINTER(img));
2770 
2771       /* recall the image for dereferencing later */
2772       images = g_list_append(images, GINT_TO_POINTER(img));
2773 
2774     } else if(purple_str_has_prefix(type, "text")) {
2775 
2776       /* concatenate all the text parts together */
2777       guchar *data;
2778       gsize len;
2779 
2780       purple_mime_part_get_data_decoded(part, &data, &len);
2781       g_string_append(str, (const char *)data);
2782       g_free(data);
2783     }
2784   }
2785 
2786   purple_mime_document_free(doc);
2787 
2788   /* @todo should put this in its own function */
2789   { /* replace each IMG tag's SRC attribute with an ID attribute. This
2790        actually modifies the contents of str */
2791     GData *attribs;
2792     char *start, *end;
2793     char *tmp = str->str;
2794 
2795     while(*tmp && purple_markup_find_tag("img", tmp, (const char **) &start,
2796 				       (const char **) &end, &attribs)) {
2797 
2798       char *alt, *align, *border, *src;
2799       int img = 0;
2800 
2801       alt = g_datalist_get_data(&attribs, "alt");
2802       align = g_datalist_get_data(&attribs, "align");
2803       border = g_datalist_get_data(&attribs, "border");
2804       src = g_datalist_get_data(&attribs, "src");
2805 
2806       if(src)
2807 	img = GPOINTER_TO_INT(g_hash_table_lookup(img_by_cid, src));
2808 
2809       if(img) {
2810 	GString *atstr;
2811 	gsize len = (end - start);
2812 	gsize mov;
2813 
2814 	atstr = g_string_new("");
2815 	if(alt) g_string_append_printf(atstr, " alt=\"%s\"", alt);
2816 	if(align) g_string_append_printf(atstr, " align=\"%s\"", align);
2817 	if(border) g_string_append_printf(atstr, " border=\"%s\"", border);
2818 
2819 	mov = g_snprintf(start, len, "<img%s id=\"%i\"", atstr->str, img);
2820 	while(mov < len) start[mov++] = ' ';
2821 
2822 	g_string_free(atstr, TRUE);
2823       }
2824 
2825       g_datalist_clear(&attribs);
2826       tmp = end + 1;
2827     }
2828   }
2829 
2830   im_recv_html(conv, pd, str->str);
2831 
2832   g_string_free(str, TRUE);
2833 
2834   /* clean up the cid table */
2835   g_hash_table_destroy(img_by_cid);
2836 
2837   /* dereference all the imgages */
2838   while(images) {
2839     purple_imgstore_unref_by_id(GPOINTER_TO_INT(images->data));
2840     images = g_list_delete_link(images, images);
2841   }
2842 }
2843 
2844 
mw_conversation_recv(struct mwConversation * conv,enum mwImSendType type,gconstpointer msg)2845 static void mw_conversation_recv(struct mwConversation *conv,
2846 				 enum mwImSendType type,
2847 				 gconstpointer msg) {
2848   struct mwServiceIm *srvc;
2849   struct mwSession *session;
2850   struct mwPurplePluginData *pd;
2851 
2852   srvc = mwConversation_getService(conv);
2853   session = mwService_getSession(MW_SERVICE(srvc));
2854   pd = mwSession_getClientData(session);
2855 
2856   switch(type) {
2857   case mwImSend_PLAIN:
2858     im_recv_text(conv, pd, msg);
2859     break;
2860 
2861   case mwImSend_TYPING:
2862     im_recv_typing(conv, pd, !! msg);
2863     break;
2864 
2865   case mwImSend_HTML:
2866     im_recv_html(conv, pd, msg);
2867     break;
2868 
2869   case mwImSend_SUBJECT:
2870     im_recv_subj(conv, pd, msg);
2871     break;
2872 
2873   case mwImSend_MIME:
2874     im_recv_mime(conv, pd, msg);
2875     break;
2876 
2877   default:
2878     DEBUG_INFO("conversation received strange type, 0x%04x\n", type);
2879     ; /* erm... */
2880   }
2881 }
2882 
2883 
mw_place_invite(struct mwConversation * conv,const char * message,const char * title,const char * name)2884 static void mw_place_invite(struct mwConversation *conv,
2885 			    const char *message,
2886 			    const char *title, const char *name) {
2887   struct mwServiceIm *srvc;
2888   struct mwSession *session;
2889   struct mwPurplePluginData *pd;
2890 
2891   struct mwIdBlock *idb;
2892   GHashTable *ht;
2893 
2894   srvc = mwConversation_getService(conv);
2895   session = mwService_getSession(MW_SERVICE(srvc));
2896   pd = mwSession_getClientData(session);
2897 
2898   idb = mwConversation_getTarget(conv);
2899 
2900   ht = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
2901   g_hash_table_insert(ht, CHAT_KEY_CREATOR, g_strdup(idb->user));
2902   g_hash_table_insert(ht, CHAT_KEY_NAME, g_strdup(name));
2903   g_hash_table_insert(ht, CHAT_KEY_TOPIC, g_strdup(title));
2904   g_hash_table_insert(ht, CHAT_KEY_INVITE, g_strdup(message));
2905   g_hash_table_insert(ht, CHAT_KEY_IS_PLACE, g_strdup("")); /* ugh */
2906 
2907   if(! title) title = "(no title)";
2908   if(! message) message = "(no message)";
2909   serv_got_chat_invite(pd->gc, title, idb->user, message, ht);
2910 
2911   mwConversation_close(conv, ERR_SUCCESS);
2912   mwConversation_free(conv);
2913 }
2914 
2915 
mw_im_clear(struct mwServiceIm * srvc)2916 static void mw_im_clear(struct mwServiceIm *srvc) {
2917   ;
2918 }
2919 
2920 
2921 static struct mwImHandler mw_im_handler = {
2922   mw_conversation_opened,
2923   mw_conversation_closed,
2924   mw_conversation_recv,
2925   mw_place_invite,
2926   mw_im_clear,
2927 };
2928 
2929 
mw_srvc_im_new(struct mwSession * s)2930 static struct mwServiceIm *mw_srvc_im_new(struct mwSession *s) {
2931   struct mwServiceIm *srvc;
2932   srvc = mwServiceIm_new(s, &mw_im_handler);
2933   mwServiceIm_setClientType(srvc, mwImClient_NOTESBUDDY);
2934   return srvc;
2935 }
2936 
2937 
2938 /* The following helps us relate a mwPlace to a PurpleConvChat in the
2939    various forms by which either may be indicated. Uses some of
2940    the similar macros from the conference service above */
2941 
2942 #define PLACE_TO_ID(place)   (GPOINTER_TO_INT(place))
2943 #define ID_TO_PLACE(pd, id)  (place_find_by_id((pd), (id)))
2944 
2945 #define CHAT_TO_PLACE(pd, chat)  (ID_TO_PLACE((pd), CHAT_TO_ID(chat)))
2946 #define PLACE_TO_CHAT(place)     (ID_TO_CHAT(PLACE_TO_ID(place)))
2947 
2948 
2949 static struct mwPlace *
place_find_by_id(struct mwPurplePluginData * pd,int id)2950 place_find_by_id(struct mwPurplePluginData *pd, int id) {
2951   struct mwServicePlace *srvc = pd->srvc_place;
2952   struct mwPlace *place = NULL;
2953   GList *l;
2954 
2955   l = (GList *) mwServicePlace_getPlaces(srvc);
2956   for(; l; l = l->next) {
2957     struct mwPlace *p = l->data;
2958     PurpleConvChat *h = PURPLE_CONV_CHAT(mwPlace_getClientData(p));
2959 
2960     if(CHAT_TO_ID(h) == id) {
2961       place = p;
2962       break;
2963     }
2964   }
2965 
2966   return place;
2967 }
2968 
2969 
mw_place_opened(struct mwPlace * place)2970 static void mw_place_opened(struct mwPlace *place) {
2971   struct mwServicePlace *srvc;
2972   struct mwSession *session;
2973   struct mwPurplePluginData *pd;
2974   PurpleConnection *gc;
2975   PurpleConversation *gconf;
2976 
2977   GList *members, *l;
2978 
2979   const char *n = mwPlace_getName(place);
2980   const char *t = mwPlace_getTitle(place);
2981 
2982   srvc = mwPlace_getService(place);
2983   session = mwService_getSession(MW_SERVICE(srvc));
2984   pd = mwSession_getClientData(session);
2985   gc = pd->gc;
2986 
2987   members = mwPlace_getMembers(place);
2988 
2989   DEBUG_INFO("place %s opened, %u initial members\n",
2990 	     NSTR(n), g_list_length(members));
2991 
2992   if(! t) t = "(no title)";
2993   gconf = serv_got_joined_chat(gc, PLACE_TO_ID(place), t);
2994 
2995   mwPlace_setClientData(place, gconf, NULL);
2996 
2997   for(l = members; l; l = l->next) {
2998     struct mwIdBlock *idb = l->data;
2999     purple_conv_chat_add_user(PURPLE_CONV_CHAT(gconf), idb->user,
3000 			    NULL, PURPLE_CBFLAGS_NONE, FALSE);
3001   }
3002   g_list_free(members);
3003 }
3004 
3005 
mw_place_closed(struct mwPlace * place,guint32 code)3006 static void mw_place_closed(struct mwPlace *place, guint32 code) {
3007   struct mwServicePlace *srvc;
3008   struct mwSession *session;
3009   struct mwPurplePluginData *pd;
3010   PurpleConnection *gc;
3011 
3012   const char *n = mwPlace_getName(place);
3013   char *msg = mwError(code);
3014 
3015   DEBUG_INFO("place %s closed, 0x%08x\n", NSTR(n), code);
3016 
3017   srvc = mwPlace_getService(place);
3018   session = mwService_getSession(MW_SERVICE(srvc));
3019   pd = mwSession_getClientData(session);
3020   gc = pd->gc;
3021 
3022   serv_got_chat_left(gc, PLACE_TO_ID(place));
3023 
3024   purple_notify_error(gc, _("Place Closed"), NULL, msg);
3025   g_free(msg);
3026 }
3027 
3028 
mw_place_peerJoined(struct mwPlace * place,const struct mwIdBlock * peer)3029 static void mw_place_peerJoined(struct mwPlace *place,
3030 				const struct mwIdBlock *peer) {
3031   PurpleConversation *gconf;
3032 
3033   const char *n = mwPlace_getName(place);
3034 
3035   DEBUG_INFO("%s joined place %s\n", NSTR(peer->user), NSTR(n));
3036 
3037   gconf = mwPlace_getClientData(place);
3038   g_return_if_fail(gconf != NULL);
3039 
3040   purple_conv_chat_add_user(PURPLE_CONV_CHAT(gconf), peer->user,
3041 			  NULL, PURPLE_CBFLAGS_NONE, TRUE);
3042 }
3043 
3044 
mw_place_peerParted(struct mwPlace * place,const struct mwIdBlock * peer)3045 static void mw_place_peerParted(struct mwPlace *place,
3046 				const struct mwIdBlock *peer) {
3047   PurpleConversation *gconf;
3048 
3049   const char *n = mwPlace_getName(place);
3050 
3051   DEBUG_INFO("%s left place %s\n", NSTR(peer->user), NSTR(n));
3052 
3053   gconf = mwPlace_getClientData(place);
3054   g_return_if_fail(gconf != NULL);
3055 
3056   purple_conv_chat_remove_user(PURPLE_CONV_CHAT(gconf), peer->user, NULL);
3057 }
3058 
3059 
mw_place_peerSetAttribute(struct mwPlace * place,const struct mwIdBlock * peer,guint32 attr,struct mwOpaque * o)3060 static void mw_place_peerSetAttribute(struct mwPlace *place,
3061 				      const struct mwIdBlock *peer,
3062 				      guint32 attr, struct mwOpaque *o) {
3063   ;
3064 }
3065 
3066 
mw_place_peerUnsetAttribute(struct mwPlace * place,const struct mwIdBlock * peer,guint32 attr)3067 static void mw_place_peerUnsetAttribute(struct mwPlace *place,
3068 					const struct mwIdBlock *peer,
3069 					guint32 attr) {
3070   ;
3071 }
3072 
3073 
mw_place_message(struct mwPlace * place,const struct mwIdBlock * who,const char * msg)3074 static void mw_place_message(struct mwPlace *place,
3075 			     const struct mwIdBlock *who,
3076 			     const char *msg) {
3077   struct mwServicePlace *srvc;
3078   struct mwSession *session;
3079   struct mwPurplePluginData *pd;
3080   PurpleConnection *gc;
3081   char *esc;
3082 
3083   if(! msg) return;
3084 
3085   srvc = mwPlace_getService(place);
3086   session = mwService_getSession(MW_SERVICE(srvc));
3087   pd = mwSession_getClientData(session);
3088   gc = pd->gc;
3089 
3090   esc = g_markup_escape_text(msg, -1);
3091   serv_got_chat_in(gc, PLACE_TO_ID(place), who->user, 0, esc, time(NULL));
3092   g_free(esc);
3093 }
3094 
3095 
mw_place_clear(struct mwServicePlace * srvc)3096 static void mw_place_clear(struct mwServicePlace *srvc) {
3097   ;
3098 }
3099 
3100 
3101 static struct mwPlaceHandler mw_place_handler = {
3102   mw_place_opened,
3103   mw_place_closed,
3104   mw_place_peerJoined,
3105   mw_place_peerParted,
3106   mw_place_peerSetAttribute,
3107   mw_place_peerUnsetAttribute,
3108   mw_place_message,
3109   mw_place_clear,
3110 };
3111 
3112 
mw_srvc_place_new(struct mwSession * s)3113 static struct mwServicePlace *mw_srvc_place_new(struct mwSession *s) {
3114   struct mwServicePlace *srvc;
3115   srvc = mwServicePlace_new(s, &mw_place_handler);
3116   return srvc;
3117 }
3118 
3119 
mw_srvc_resolve_new(struct mwSession * s)3120 static struct mwServiceResolve *mw_srvc_resolve_new(struct mwSession *s) {
3121   struct mwServiceResolve *srvc;
3122   srvc = mwServiceResolve_new(s);
3123   return srvc;
3124 }
3125 
3126 
mw_srvc_store_new(struct mwSession * s)3127 static struct mwServiceStorage *mw_srvc_store_new(struct mwSession *s) {
3128   struct mwServiceStorage *srvc;
3129   srvc = mwServiceStorage_new(s);
3130   return srvc;
3131 }
3132 
3133 
3134 /** allocate and associate a mwPurplePluginData with a PurpleConnection */
mwPurplePluginData_new(PurpleConnection * gc)3135 static struct mwPurplePluginData *mwPurplePluginData_new(PurpleConnection *gc) {
3136   struct mwPurplePluginData *pd;
3137 
3138   g_return_val_if_fail(gc != NULL, NULL);
3139 
3140   pd = g_new0(struct mwPurplePluginData, 1);
3141   pd->gc = gc;
3142   pd->session = mwSession_new(&mw_session_handler);
3143   pd->srvc_aware = mw_srvc_aware_new(pd->session);
3144   pd->srvc_conf = mw_srvc_conf_new(pd->session);
3145   pd->srvc_ft = mw_srvc_ft_new(pd->session);
3146   pd->srvc_im = mw_srvc_im_new(pd->session);
3147   pd->srvc_place = mw_srvc_place_new(pd->session);
3148   pd->srvc_resolve = mw_srvc_resolve_new(pd->session);
3149   pd->srvc_store = mw_srvc_store_new(pd->session);
3150   pd->group_list_map = g_hash_table_new(g_direct_hash, g_direct_equal);
3151   pd->sock_buf = purple_circ_buffer_new(0);
3152 
3153   mwSession_addService(pd->session, MW_SERVICE(pd->srvc_aware));
3154   mwSession_addService(pd->session, MW_SERVICE(pd->srvc_conf));
3155   mwSession_addService(pd->session, MW_SERVICE(pd->srvc_ft));
3156   mwSession_addService(pd->session, MW_SERVICE(pd->srvc_im));
3157   mwSession_addService(pd->session, MW_SERVICE(pd->srvc_place));
3158   mwSession_addService(pd->session, MW_SERVICE(pd->srvc_resolve));
3159   mwSession_addService(pd->session, MW_SERVICE(pd->srvc_store));
3160 
3161   mwSession_addCipher(pd->session, mwCipher_new_RC2_40(pd->session));
3162   mwSession_addCipher(pd->session, mwCipher_new_RC2_128(pd->session));
3163 
3164   mwSession_setClientData(pd->session, pd, NULL);
3165   gc->proto_data = pd;
3166 
3167   return pd;
3168 }
3169 
3170 
mwPurplePluginData_free(struct mwPurplePluginData * pd)3171 static void mwPurplePluginData_free(struct mwPurplePluginData *pd) {
3172   g_return_if_fail(pd != NULL);
3173 
3174   pd->gc->proto_data = NULL;
3175 
3176   mwSession_removeService(pd->session, mwService_AWARE);
3177   mwSession_removeService(pd->session, mwService_CONFERENCE);
3178   mwSession_removeService(pd->session, mwService_FILE_TRANSFER);
3179   mwSession_removeService(pd->session, mwService_IM);
3180   mwSession_removeService(pd->session, mwService_PLACE);
3181   mwSession_removeService(pd->session, mwService_RESOLVE);
3182   mwSession_removeService(pd->session, mwService_STORAGE);
3183 
3184   mwService_free(MW_SERVICE(pd->srvc_aware));
3185   mwService_free(MW_SERVICE(pd->srvc_conf));
3186   mwService_free(MW_SERVICE(pd->srvc_ft));
3187   mwService_free(MW_SERVICE(pd->srvc_im));
3188   mwService_free(MW_SERVICE(pd->srvc_place));
3189   mwService_free(MW_SERVICE(pd->srvc_resolve));
3190   mwService_free(MW_SERVICE(pd->srvc_store));
3191 
3192   mwCipher_free(mwSession_getCipher(pd->session, mwCipher_RC2_40));
3193   mwCipher_free(mwSession_getCipher(pd->session, mwCipher_RC2_128));
3194 
3195   mwSession_free(pd->session);
3196 
3197   g_hash_table_destroy(pd->group_list_map);
3198   purple_circ_buffer_destroy(pd->sock_buf);
3199 
3200   g_free(pd);
3201 }
3202 
3203 
mw_prpl_list_icon(PurpleAccount * a,PurpleBuddy * b)3204 static const char *mw_prpl_list_icon(PurpleAccount *a, PurpleBuddy *b) {
3205   /* my little green dude is a chopped up version of the aim running
3206      guy.  First, cut off the head and store someplace safe. Then,
3207      take the left-half side of the body and throw it away. Make a
3208      copy of the remaining body, and flip it horizontally. Now attach
3209      the two pieces into an X shape, and drop the head back on the
3210      top, being careful to center it. Then, just change the color
3211      saturation to bring the red down a bit, and voila! */
3212 
3213   /* then, throw all of that away and use sodipodi to make a new
3214      icon. You know, LIKE A REAL MAN. */
3215 
3216   return "meanwhile";
3217 }
3218 
3219 
mw_prpl_list_emblem(PurpleBuddy * b)3220 static const char* mw_prpl_list_emblem(PurpleBuddy *b)
3221 {
3222   if(buddy_is_external(b))
3223     return "external";
3224 
3225   return NULL;
3226 }
3227 
3228 
mw_prpl_status_text(PurpleBuddy * b)3229 static char *mw_prpl_status_text(PurpleBuddy *b) {
3230   PurpleConnection *gc;
3231   struct mwPurplePluginData *pd;
3232   struct mwAwareIdBlock t = { mwAware_USER, (char *)purple_buddy_get_name(b), NULL };
3233   const char *ret = NULL;
3234 
3235   if ((gc = purple_account_get_connection(purple_buddy_get_account(b)))
3236       && (pd = gc->proto_data))
3237     ret = mwServiceAware_getText(pd->srvc_aware, &t);
3238 
3239   return (ret && g_utf8_validate(ret, -1, NULL)) ? g_markup_escape_text(ret, -1): NULL;
3240 }
3241 
3242 
status_text(PurpleBuddy * b)3243 static const char *status_text(PurpleBuddy *b) {
3244   PurplePresence *presence;
3245   PurpleStatus *status;
3246 
3247   presence = purple_buddy_get_presence(b);
3248   status = purple_presence_get_active_status(presence);
3249 
3250   return purple_status_get_name(status);
3251 }
3252 
3253 
user_supports(struct mwServiceAware * srvc,const char * who,guint32 feature)3254 static gboolean user_supports(struct mwServiceAware *srvc,
3255 			      const char *who, guint32 feature) {
3256 
3257   const struct mwAwareAttribute *attr;
3258   struct mwAwareIdBlock idb = { mwAware_USER, (char *) who, NULL };
3259 
3260   attr = mwServiceAware_getAttribute(srvc, &idb, feature);
3261   return (attr != NULL) && mwAwareAttribute_asBoolean(attr);
3262 }
3263 
3264 
user_supports_text(struct mwServiceAware * srvc,const char * who)3265 static char *user_supports_text(struct mwServiceAware *srvc, const char *who) {
3266   const char *feat[] = {NULL, NULL, NULL, NULL, NULL};
3267   const char **f = feat;
3268 
3269   if(user_supports(srvc, who, mwAttribute_AV_PREFS_SET)) {
3270     gboolean mic, speak, video;
3271 
3272     mic = user_supports(srvc, who, mwAttribute_MICROPHONE);
3273     speak = user_supports(srvc, who, mwAttribute_SPEAKERS);
3274     video = user_supports(srvc, who, mwAttribute_VIDEO_CAMERA);
3275 
3276     if(mic) *f++ = _("Microphone");
3277     if(speak) *f++ = _("Speakers");
3278     if(video) *f++ = _("Video Camera");
3279   }
3280 
3281   if(user_supports(srvc, who, mwAttribute_FILE_TRANSFER))
3282     *f++ = _("File Transfer");
3283 
3284   return (*feat)? g_strjoinv(", ", (char **)feat): NULL;
3285   /* jenni loves siege */
3286 }
3287 
3288 
mw_prpl_tooltip_text(PurpleBuddy * b,PurpleNotifyUserInfo * user_info,gboolean full)3289 static void mw_prpl_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full) {
3290   PurpleConnection *gc;
3291   struct mwPurplePluginData *pd = NULL;
3292   struct mwAwareIdBlock idb = { mwAware_USER, (char *)purple_buddy_get_name(b), NULL };
3293 
3294   const char *message = NULL;
3295   const char *status;
3296   char *tmp;
3297 
3298   if ((gc = purple_account_get_connection(purple_buddy_get_account(b)))
3299       && (pd = gc->proto_data))
3300      message = mwServiceAware_getText(pd->srvc_aware, &idb);
3301 
3302   status = status_text(b);
3303 
3304   if(message != NULL && g_utf8_validate(message, -1, NULL) && purple_utf8_strcasecmp(status, message)) {
3305     tmp = g_markup_escape_text(message, -1);
3306 	purple_notify_user_info_add_pair(user_info, status, tmp);
3307     g_free(tmp);
3308 
3309   } else {
3310 	purple_notify_user_info_add_pair(user_info, _("Status"), status);
3311   }
3312 
3313   if(full && pd != NULL) {
3314     tmp = user_supports_text(pd->srvc_aware, purple_buddy_get_name(b));
3315     if(tmp) {
3316 	  purple_notify_user_info_add_pair(user_info, _("Supports"), tmp);
3317       g_free(tmp);
3318     }
3319 
3320     if(buddy_is_external(b)) {
3321 	  purple_notify_user_info_add_pair(user_info, NULL, _("External User"));
3322     }
3323   }
3324 }
3325 
mw_prpl_status_types(PurpleAccount * acct)3326 static GList *mw_prpl_status_types(PurpleAccount *acct)
3327 {
3328 	GList *types = NULL;
3329 	PurpleStatusType *type;
3330 
3331 	type = purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE,
3332 			MW_STATE_ACTIVE, NULL, TRUE, TRUE, FALSE,
3333 			MW_STATE_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING),
3334 			NULL);
3335 	types = g_list_append(types, type);
3336 
3337 	type = purple_status_type_new_with_attrs(PURPLE_STATUS_AWAY,
3338 			MW_STATE_AWAY, NULL, TRUE, TRUE, FALSE,
3339 			MW_STATE_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING),
3340 			NULL);
3341 	types = g_list_append(types, type);
3342 
3343 	type = purple_status_type_new_with_attrs(PURPLE_STATUS_UNAVAILABLE,
3344 			MW_STATE_BUSY, _("Do Not Disturb"), TRUE, TRUE, FALSE,
3345 			MW_STATE_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING),
3346 			NULL);
3347 	types = g_list_append(types, type);
3348 
3349 	type = purple_status_type_new_full(PURPLE_STATUS_OFFLINE,
3350 			MW_STATE_OFFLINE, NULL, TRUE, TRUE, FALSE);
3351 	types = g_list_append(types, type);
3352 
3353 	return types;
3354 }
3355 
3356 
conf_create_prompt_cancel(PurpleBuddy * buddy,PurpleRequestFields * fields)3357 static void conf_create_prompt_cancel(PurpleBuddy *buddy,
3358 				      PurpleRequestFields *fields) {
3359   ; /* nothing to do */
3360 }
3361 
3362 
conf_create_prompt_join(PurpleBuddy * buddy,PurpleRequestFields * fields)3363 static void conf_create_prompt_join(PurpleBuddy *buddy,
3364 				    PurpleRequestFields *fields) {
3365   PurpleAccount *acct;
3366   PurpleConnection *gc;
3367   struct mwPurplePluginData *pd;
3368   struct mwServiceConference *srvc;
3369 
3370   PurpleRequestField *f;
3371 
3372   const char *topic, *invite;
3373   struct mwConference *conf;
3374   struct mwIdBlock idb = { NULL, NULL };
3375 
3376   acct = purple_buddy_get_account(buddy);
3377   gc = purple_account_get_connection(acct);
3378   pd = gc->proto_data;
3379   srvc = pd->srvc_conf;
3380 
3381   f = purple_request_fields_get_field(fields, CHAT_KEY_TOPIC);
3382   topic = purple_request_field_string_get_value(f);
3383 
3384   f = purple_request_fields_get_field(fields, CHAT_KEY_INVITE);
3385   invite = purple_request_field_string_get_value(f);
3386 
3387   conf = mwConference_new(srvc, topic);
3388   mwConference_open(conf);
3389 
3390   idb.user = (char *)purple_buddy_get_name(buddy);
3391   mwConference_invite(conf, &idb, invite);
3392 }
3393 
3394 
blist_menu_conf_create(PurpleBuddy * buddy,const char * msg)3395 static void blist_menu_conf_create(PurpleBuddy *buddy, const char *msg) {
3396 
3397   PurpleRequestFields *fields;
3398   PurpleRequestFieldGroup *g;
3399   PurpleRequestField *f;
3400 
3401   PurpleAccount *acct;
3402   PurpleConnection *gc;
3403 
3404   const char *msgA;
3405   const char *msgB;
3406   char *msg1;
3407 
3408   g_return_if_fail(buddy != NULL);
3409 
3410   acct = purple_buddy_get_account(buddy);
3411   g_return_if_fail(acct != NULL);
3412 
3413   gc = purple_account_get_connection(acct);
3414   g_return_if_fail(gc != NULL);
3415 
3416   fields = purple_request_fields_new();
3417 
3418   g = purple_request_field_group_new(NULL);
3419   purple_request_fields_add_group(fields, g);
3420 
3421   f = purple_request_field_string_new(CHAT_KEY_TOPIC, _("Topic"), NULL, FALSE);
3422   purple_request_field_group_add_field(g, f);
3423 
3424   f = purple_request_field_string_new(CHAT_KEY_INVITE, _("Message"), msg, FALSE);
3425   purple_request_field_group_add_field(g, f);
3426 
3427   msgA = _("Create conference with user");
3428   msgB = _("Please enter a topic for the new conference, and an invitation"
3429 	   " message to be sent to %s");
3430   msg1 = g_strdup_printf(msgB, purple_buddy_get_name(buddy));
3431 
3432   purple_request_fields(gc, _("New Conference"),
3433 		      msgA, msg1, fields,
3434 		      _("Create"), G_CALLBACK(conf_create_prompt_join),
3435 		      _("Cancel"), G_CALLBACK(conf_create_prompt_cancel),
3436 			  acct, purple_buddy_get_name(buddy), NULL,
3437 		      buddy);
3438   g_free(msg1);
3439 }
3440 
3441 
conf_select_prompt_cancel(PurpleBuddy * buddy,PurpleRequestFields * fields)3442 static void conf_select_prompt_cancel(PurpleBuddy *buddy,
3443 				      PurpleRequestFields *fields) {
3444   ;
3445 }
3446 
3447 
conf_select_prompt_invite(PurpleBuddy * buddy,PurpleRequestFields * fields)3448 static void conf_select_prompt_invite(PurpleBuddy *buddy,
3449 				      PurpleRequestFields *fields) {
3450   PurpleRequestField *f;
3451   GList *l;
3452   const char *msg;
3453 
3454   f = purple_request_fields_get_field(fields, CHAT_KEY_INVITE);
3455   msg = purple_request_field_string_get_value(f);
3456 
3457   f = purple_request_fields_get_field(fields, "conf");
3458   l = purple_request_field_list_get_selected(f);
3459 
3460   if(l) {
3461     gpointer d = purple_request_field_list_get_data(f, l->data);
3462 
3463     if(GPOINTER_TO_INT(d) == 0x01) {
3464       blist_menu_conf_create(buddy, msg);
3465 
3466     } else {
3467       struct mwIdBlock idb = { (char *)purple_buddy_get_name(buddy), NULL };
3468       mwConference_invite(d, &idb, msg);
3469     }
3470   }
3471 }
3472 
3473 
blist_menu_conf_list(PurpleBuddy * buddy,GList * confs)3474 static void blist_menu_conf_list(PurpleBuddy *buddy,
3475 				 GList *confs) {
3476 
3477   PurpleRequestFields *fields;
3478   PurpleRequestFieldGroup *g;
3479   PurpleRequestField *f;
3480 
3481   PurpleAccount *acct;
3482   PurpleConnection *gc;
3483 
3484   const char *msgA;
3485   const char *msgB;
3486   char *msg;
3487 
3488   acct = purple_buddy_get_account(buddy);
3489   g_return_if_fail(acct != NULL);
3490 
3491   gc = purple_account_get_connection(acct);
3492   g_return_if_fail(gc != NULL);
3493 
3494   fields = purple_request_fields_new();
3495 
3496   g = purple_request_field_group_new(NULL);
3497   purple_request_fields_add_group(fields, g);
3498 
3499   f = purple_request_field_list_new("conf", _("Available Conferences"));
3500   purple_request_field_list_set_multi_select(f, FALSE);
3501   for(; confs; confs = confs->next) {
3502     struct mwConference *c = confs->data;
3503     purple_request_field_list_add_icon(f, mwConference_getTitle(c), NULL, c);
3504   }
3505   purple_request_field_list_add_icon(f, _("Create New Conference..."),
3506 			      NULL, GINT_TO_POINTER(0x01));
3507   purple_request_field_group_add_field(g, f);
3508 
3509   f = purple_request_field_string_new(CHAT_KEY_INVITE, "Message", NULL, FALSE);
3510   purple_request_field_group_add_field(g, f);
3511 
3512   msgA = _("Invite user to a conference");
3513   msgB = _("Select a conference from the list below to send an invite to"
3514 	   " user %s. Select \"Create New Conference\" if you'd like to"
3515 	   " create a new conference to invite this user to.");
3516   msg = g_strdup_printf(msgB, purple_buddy_get_name(buddy));
3517 
3518   purple_request_fields(gc, _("Invite to Conference"),
3519 		      msgA, msg, fields,
3520 		      _("Invite"), G_CALLBACK(conf_select_prompt_invite),
3521 		      _("Cancel"), G_CALLBACK(conf_select_prompt_cancel),
3522 			  acct, purple_buddy_get_name(buddy), NULL,
3523 		      buddy);
3524   g_free(msg);
3525 }
3526 
3527 
blist_menu_conf(PurpleBlistNode * node,gpointer data)3528 static void blist_menu_conf(PurpleBlistNode *node, gpointer data) {
3529   PurpleBuddy *buddy = (PurpleBuddy *) node;
3530   PurpleAccount *acct;
3531   PurpleConnection *gc;
3532   struct mwPurplePluginData *pd;
3533   GList *l;
3534 
3535   g_return_if_fail(node != NULL);
3536   g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
3537 
3538   acct = purple_buddy_get_account(buddy);
3539   g_return_if_fail(acct != NULL);
3540 
3541   gc = purple_account_get_connection(acct);
3542   g_return_if_fail(gc != NULL);
3543 
3544   pd = gc->proto_data;
3545   g_return_if_fail(pd != NULL);
3546 
3547   /*
3548     - get a list of all conferences on this session
3549     - if none, prompt to create one, and invite buddy to it
3550     - else, prompt to select a conference or create one
3551   */
3552 
3553   l = mwServiceConference_getConferences(pd->srvc_conf);
3554   if(l) {
3555     blist_menu_conf_list(buddy, l);
3556     g_list_free(l);
3557 
3558   } else {
3559     blist_menu_conf_create(buddy, NULL);
3560   }
3561 }
3562 
3563 
3564 #if 0
3565 static void blist_menu_announce(PurpleBlistNode *node, gpointer data) {
3566   PurpleBuddy *buddy = (PurpleBuddy *) node;
3567   PurpleAccount *acct;
3568   PurpleConnection *gc;
3569   struct mwPurplePluginData *pd;
3570   struct mwSession *session;
3571   char *rcpt_name;
3572   GList *rcpt;
3573 
3574   g_return_if_fail(node != NULL);
3575   g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
3576 
3577   acct = buddy->account;
3578   g_return_if_fail(acct != NULL);
3579 
3580   gc = purple_account_get_connection(acct);
3581   g_return_if_fail(gc != NULL);
3582 
3583   pd = gc->proto_data;
3584   g_return_if_fail(pd != NULL);
3585 
3586   rcpt_name = g_strdup_printf("@U %s", buddy->name);
3587   rcpt = g_list_prepend(NULL, rcpt_name);
3588 
3589   session = pd->session;
3590   mwSession_sendAnnounce(session, FALSE,
3591 			 "This is a TEST announcement. Please ignore.",
3592 			 rcpt);
3593 
3594   g_list_free(rcpt);
3595   g_free(rcpt_name);
3596 }
3597 #endif
3598 
3599 
mw_prpl_blist_node_menu(PurpleBlistNode * node)3600 static GList *mw_prpl_blist_node_menu(PurpleBlistNode *node) {
3601   GList *l = NULL;
3602   PurpleMenuAction *act;
3603 
3604   if(! PURPLE_BLIST_NODE_IS_BUDDY(node))
3605     return l;
3606 
3607   l = g_list_append(l, NULL);
3608 
3609   act = purple_menu_action_new(_("Invite to Conference..."),
3610                              PURPLE_CALLBACK(blist_menu_conf), NULL, NULL);
3611   l = g_list_append(l, act);
3612 
3613 #if 0
3614   act = purple_menu_action_new(_("Send TEST Announcement"),
3615 			     PURPLE_CALLBACK(blist_menu_announce), NULL, NULL);
3616   l = g_list_append(l, act);
3617 #endif
3618 
3619   /** note: this never gets called for a PurpleGroup, have to use the
3620       blist-node-extended-menu signal for that. The function
3621       blist_node_menu_cb is assigned to this signal in the function
3622       services_starting */
3623 
3624   return l;
3625 }
3626 
3627 
mw_prpl_chat_info(PurpleConnection * gc)3628 static GList *mw_prpl_chat_info(PurpleConnection *gc) {
3629   GList *l = NULL;
3630   struct proto_chat_entry *pce;
3631 
3632   pce = g_new0(struct proto_chat_entry, 1);
3633   pce->label = _("Topic:");
3634   pce->identifier = CHAT_KEY_TOPIC;
3635   l = g_list_append(l, pce);
3636 
3637   return l;
3638 }
3639 
3640 
mw_prpl_chat_info_defaults(PurpleConnection * gc,const char * name)3641 static GHashTable *mw_prpl_chat_info_defaults(PurpleConnection *gc,
3642 					      const char *name) {
3643   GHashTable *table;
3644 
3645   g_return_val_if_fail(gc != NULL, NULL);
3646 
3647   table = g_hash_table_new_full(g_str_hash, g_str_equal,
3648 				NULL, g_free);
3649 
3650   g_hash_table_insert(table, CHAT_KEY_NAME, g_strdup(name));
3651   g_hash_table_insert(table, CHAT_KEY_INVITE, NULL);
3652 
3653   return table;
3654 }
3655 
3656 
3657 static void mw_prpl_login(PurpleAccount *acct);
3658 
3659 
mw_prpl_login(PurpleAccount * account)3660 static void mw_prpl_login(PurpleAccount *account) {
3661   PurpleConnection *gc;
3662   struct mwPurplePluginData *pd;
3663 
3664   char *user, *pass, *host;
3665   guint port;
3666 
3667   gc = purple_account_get_connection(account);
3668   pd = mwPurplePluginData_new(gc);
3669 
3670   /* while we do support images, the default is to not offer it */
3671   gc->flags |= PURPLE_CONNECTION_NO_IMAGES;
3672 
3673   user = g_strdup(purple_account_get_username(account));
3674 
3675   host = strrchr(user, ':');
3676   if(host) {
3677     /* annoying user split from 1.2.0, need to undo it */
3678     *host++ = '\0';
3679     purple_account_set_string(account, MW_KEY_HOST, host);
3680     purple_account_set_username(account, user);
3681 
3682   } else {
3683     host = (char *) purple_account_get_string(account, MW_KEY_HOST,
3684 					    MW_PLUGIN_DEFAULT_HOST);
3685   }
3686 
3687   if(! host || ! *host) {
3688     /* somehow, we don't have a host to connect to. Well, we need one
3689        to actually continue, so let's ask the user directly. */
3690     g_free(user);
3691     purple_connection_error_reason(gc,
3692             PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
3693             _("A server is required to connect this account"));
3694     return;
3695   }
3696 
3697   pass = g_strdup(purple_account_get_password(account));
3698   port = purple_account_get_int(account, MW_KEY_PORT, MW_PLUGIN_DEFAULT_PORT);
3699 
3700   DEBUG_INFO("user: '%s'\n", user);
3701   DEBUG_INFO("host: '%s'\n", host);
3702   DEBUG_INFO("port: %u\n", port);
3703 
3704   mwSession_setProperty(pd->session, mwSession_NO_SECRET,
3705 			(char *) no_secret, NULL);
3706   mwSession_setProperty(pd->session, mwSession_AUTH_USER_ID, user, g_free);
3707   mwSession_setProperty(pd->session, mwSession_AUTH_PASSWORD, pass, g_free);
3708 
3709   if(purple_account_get_bool(account, MW_KEY_FAKE_IT, FALSE)) {
3710     guint client, major, minor;
3711 
3712     /* if we're faking the login, let's also fake the version we're
3713        reporting. Let's also allow the actual values to be specified */
3714 
3715     client = purple_account_get_int(account, MW_KEY_CLIENT, mwLogin_BINARY);
3716     major = purple_account_get_int(account, MW_KEY_MAJOR, 0x001e);
3717     minor = purple_account_get_int(account, MW_KEY_MINOR, 0x196f);
3718 
3719     DEBUG_INFO("client id: 0x%04x\n", client);
3720     DEBUG_INFO("client major: 0x%04x\n", major);
3721     DEBUG_INFO("client minor: 0x%04x\n", minor);
3722 
3723     mwSession_setProperty(pd->session, mwSession_CLIENT_TYPE_ID,
3724 			  GUINT_TO_POINTER(client), NULL);
3725 
3726     mwSession_setProperty(pd->session, mwSession_CLIENT_VER_MAJOR,
3727 			  GUINT_TO_POINTER(major), NULL);
3728 
3729     mwSession_setProperty(pd->session, mwSession_CLIENT_VER_MINOR,
3730 			  GUINT_TO_POINTER(minor), NULL);
3731   }
3732 
3733   purple_connection_update_progress(gc, _("Connecting"), 1, MW_CONNECT_STEPS);
3734 
3735   if (purple_proxy_connect(gc, account, host, port, connect_cb, pd) == NULL) {
3736     purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
3737                                    _("Unable to connect"));
3738   }
3739 }
3740 
3741 
mw_prpl_close(PurpleConnection * gc)3742 static void mw_prpl_close(PurpleConnection *gc) {
3743   struct mwPurplePluginData *pd;
3744 
3745   g_return_if_fail(gc != NULL);
3746 
3747   pd = gc->proto_data;
3748   g_return_if_fail(pd != NULL);
3749 
3750   /* get rid of the blist save timeout */
3751   if(pd->save_event) {
3752     purple_timeout_remove(pd->save_event);
3753     pd->save_event = 0;
3754     blist_store(pd);
3755   }
3756 
3757   /* stop the session */
3758   mwSession_stop(pd->session, 0x00);
3759 
3760   /* no longer necessary */
3761   gc->proto_data = NULL;
3762 
3763   /* stop watching the socket */
3764   if(gc->inpa) {
3765     purple_input_remove(gc->inpa);
3766     gc->inpa = 0;
3767   }
3768 
3769   /* clean up the rest */
3770   mwPurplePluginData_free(pd);
3771 }
3772 
3773 
mw_rand(void)3774 static int mw_rand(void) {
3775   static int seed = 0;
3776 
3777   /* for diversity, not security. don't touch */
3778   srand(time(NULL) ^ seed);
3779   seed = rand();
3780 
3781   return seed;
3782 }
3783 
3784 
3785 /** generates a random-ish content id string */
im_mime_content_id(void)3786 static char *im_mime_content_id(void) {
3787   return g_strdup_printf("%03x@%05xmeanwhile",
3788 			 mw_rand() & 0xfff, mw_rand() & 0xfffff);
3789 }
3790 
3791 
3792 /** generates a multipart/related content type with a random-ish
3793     boundary value */
im_mime_content_type(void)3794 static char *im_mime_content_type(void) {
3795   return g_strdup_printf("multipart/related; boundary=related_MW%03x_%04x",
3796                          mw_rand() & 0xfff, mw_rand() & 0xffff);
3797 }
3798 
3799 
3800 /** determine content type from extension. Not so happy about this,
3801     but I don't want to actually write image type detection */
im_mime_img_content_type(PurpleStoredImage * img)3802 static char *im_mime_img_content_type(PurpleStoredImage *img) {
3803   const char *fn = purple_imgstore_get_filename(img);
3804   const char *ct = NULL;
3805 
3806   ct = strrchr(fn, '.');
3807   if(! ct) {
3808     ct = "image";
3809 
3810   } else if(purple_strequal(".png", ct)) {
3811     ct = "image/png";
3812 
3813   } else if(purple_strequal(".jpg", ct)) {
3814     ct = "image/jpeg";
3815 
3816   } else if(purple_strequal(".jpeg", ct)) {
3817     ct = "image/jpeg";
3818 
3819   } else if(purple_strequal(".gif", ct)) {
3820     ct = "image/gif";
3821 
3822   } else {
3823     ct = "image";
3824   }
3825 
3826   return g_strdup_printf("%s; name=\"%s\"", ct, fn);
3827 }
3828 
3829 
im_mime_img_content_disp(PurpleStoredImage * img)3830 static char *im_mime_img_content_disp(PurpleStoredImage *img) {
3831   const char *fn = purple_imgstore_get_filename(img);
3832   return g_strdup_printf("attachment; filename=\"%s\"", fn);
3833 }
3834 
3835 
3836 /** turn an IM with embedded images into a multi-part mime document */
im_mime_convert(PurpleConnection * gc,struct mwConversation * conv,const char * message)3837 static char *im_mime_convert(PurpleConnection *gc,
3838 			     struct mwConversation *conv,
3839 			     const char *message) {
3840   GString *str;
3841   PurpleMimeDocument *doc;
3842   PurpleMimePart *part;
3843 
3844   GData *attr;
3845   char *tmp, *start, *end;
3846 
3847   str = g_string_new(NULL);
3848 
3849   doc = purple_mime_document_new();
3850 
3851   purple_mime_document_set_field(doc, "Mime-Version", "1.0");
3852   purple_mime_document_set_field(doc, "Content-Disposition", "inline");
3853 
3854   tmp = im_mime_content_type();
3855   purple_mime_document_set_field(doc, "Content-Type", tmp);
3856   g_free(tmp);
3857 
3858   tmp = (char *) message;
3859   while(*tmp && purple_markup_find_tag("img", tmp, (const char **) &start,
3860 				     (const char **) &end, &attr)) {
3861     char *id;
3862     PurpleStoredImage *img = NULL;
3863 
3864     gsize len = (start - tmp);
3865 
3866     /* append the in-between-tags text */
3867     if(len) g_string_append_len(str, tmp, len);
3868 
3869     /* find the imgstore data by the id tag */
3870     id = g_datalist_get_data(&attr, "id");
3871     if(id && *id)
3872       img = purple_imgstore_find_by_id(atoi(id));
3873 
3874     if(img) {
3875       char *cid;
3876       gpointer data;
3877       size_t size;
3878 
3879       part = purple_mime_part_new(doc);
3880 
3881       data = im_mime_img_content_disp(img);
3882       purple_mime_part_set_field(part, "Content-Disposition", data);
3883       g_free(data);
3884 
3885       data = im_mime_img_content_type(img);
3886       purple_mime_part_set_field(part, "Content-Type", data);
3887       g_free(data);
3888 
3889       cid = im_mime_content_id();
3890       data = g_strdup_printf("<%s>", cid);
3891       purple_mime_part_set_field(part, "Content-ID", data);
3892       g_free(data);
3893 
3894       purple_mime_part_set_field(part, "Content-transfer-encoding", "base64");
3895 
3896       /* obtain and base64 encode the image data, and put it in the
3897 	 mime part */
3898       size = purple_imgstore_get_size(img);
3899       data = purple_base64_encode(purple_imgstore_get_data(img), (gsize) size);
3900       purple_mime_part_set_data(part, data);
3901       g_free(data);
3902 
3903       /* append the modified tag */
3904       g_string_append_printf(str, "<img src=\"cid:%s\">", cid);
3905       g_free(cid);
3906 
3907     } else {
3908       /* append the literal image tag, since we couldn't find a
3909 	 relative imgstore object */
3910       gsize len = (end - start) + 1;
3911       g_string_append_len(str, start, len);
3912     }
3913 
3914     g_datalist_clear(&attr);
3915     tmp = end + 1;
3916   }
3917 
3918   /* append left-overs */
3919   g_string_append(str, tmp);
3920 
3921   /* add the text/html part */
3922   part = purple_mime_part_new(doc);
3923   purple_mime_part_set_field(part, "Content-Disposition", "inline");
3924 
3925   tmp = purple_utf8_ncr_encode(str->str);
3926   purple_mime_part_set_field(part, "Content-Type", "text/html");
3927   purple_mime_part_set_field(part, "Content-Transfer-Encoding", "7bit");
3928   purple_mime_part_set_data(part, tmp);
3929   g_free(tmp);
3930 
3931   g_string_free(str, TRUE);
3932 
3933   str = g_string_new(NULL);
3934   purple_mime_document_write(doc, str);
3935   tmp = str->str;
3936   g_string_free(str, FALSE);
3937 
3938   return tmp;
3939 }
3940 
3941 
mw_prpl_send_im(PurpleConnection * gc,const char * name,const char * message,PurpleMessageFlags flags)3942 static int mw_prpl_send_im(PurpleConnection *gc,
3943 			   const char *name,
3944 			   const char *message,
3945 			   PurpleMessageFlags flags) {
3946 
3947   struct mwPurplePluginData *pd;
3948   struct mwIdBlock who = { (char *) name, NULL };
3949   struct mwConversation *conv;
3950 
3951   g_return_val_if_fail(gc != NULL, 0);
3952   pd = gc->proto_data;
3953 
3954   g_return_val_if_fail(pd != NULL, 0);
3955 
3956   conv = mwServiceIm_getConversation(pd->srvc_im, &who);
3957 
3958   /* this detection of features to determine how to send the message
3959      (plain, html, or mime) is flawed because the other end of the
3960      conversation could close their channel at any time, rendering any
3961      existing formatting in an outgoing message innapropriate. The end
3962      result is that it may be possible that the other side of the
3963      conversation will receive a plaintext message with html contents,
3964      which is bad. I'm not sure how to fix this correctly. */
3965 
3966   if(strstr(message, "<img ") || strstr(message, "<IMG "))
3967     flags |= PURPLE_MESSAGE_IMAGES;
3968 
3969   if(mwConversation_isOpen(conv)) {
3970     char *tmp;
3971     int ret;
3972 
3973     if((flags & PURPLE_MESSAGE_IMAGES) &&
3974        mwConversation_supports(conv, mwImSend_MIME)) {
3975       /* send a MIME message */
3976 
3977       tmp = im_mime_convert(gc, conv, message);
3978       ret = mwConversation_send(conv, mwImSend_MIME, tmp);
3979       g_free(tmp);
3980 
3981     } else if(mwConversation_supports(conv, mwImSend_HTML)) {
3982       /* send an HTML message */
3983 
3984       char *ncr;
3985       ncr = purple_utf8_ncr_encode(message);
3986       tmp = purple_strdup_withhtml(ncr);
3987       g_free(ncr);
3988 
3989       ret = mwConversation_send(conv, mwImSend_HTML, tmp);
3990       g_free(tmp);
3991 
3992     } else {
3993       /* default to text */
3994       tmp = purple_markup_strip_html(message);
3995       ret = mwConversation_send(conv, mwImSend_PLAIN, tmp);
3996       g_free(tmp);
3997     }
3998 
3999     return !ret;
4000 
4001   } else {
4002 
4003     /* queue up the message safely as plain text */
4004     char *tmp = purple_markup_strip_html(message);
4005     convo_queue(conv, mwImSend_PLAIN, tmp);
4006     g_free(tmp);
4007 
4008     if(! mwConversation_isPending(conv))
4009       mwConversation_open(conv);
4010 
4011     return 1;
4012   }
4013 }
4014 
4015 
mw_prpl_send_typing(PurpleConnection * gc,const char * name,PurpleTypingState state)4016 static unsigned int mw_prpl_send_typing(PurpleConnection *gc,
4017 					const char *name,
4018 					PurpleTypingState state) {
4019 
4020   struct mwPurplePluginData *pd;
4021   struct mwIdBlock who = { (char *) name, NULL };
4022   struct mwConversation *conv;
4023 
4024   gpointer t = GINT_TO_POINTER(!! state);
4025 
4026   g_return_val_if_fail(gc != NULL, 0);
4027   pd = gc->proto_data;
4028 
4029   g_return_val_if_fail(pd != NULL, 0);
4030 
4031   conv = mwServiceIm_getConversation(pd->srvc_im, &who);
4032 
4033   if(mwConversation_isOpen(conv)) {
4034     mwConversation_send(conv, mwImSend_TYPING, t);
4035 
4036   } else if((state == PURPLE_TYPING) || (state == PURPLE_TYPED)) {
4037     /* only open a channel for sending typing notification, not for
4038        when typing has stopped. There's no point in re-opening a
4039        channel just to tell someone that this side isn't typing. */
4040 
4041     convo_queue(conv, mwImSend_TYPING, t);
4042 
4043     if(! mwConversation_isPending(conv)) {
4044       mwConversation_open(conv);
4045     }
4046   }
4047 
4048   return 0;
4049 }
4050 
4051 
mw_client_name(guint16 type)4052 static const char *mw_client_name(guint16 type) {
4053   switch(type) {
4054   case mwLogin_LIB:
4055     return "Lotus Binary Library";
4056 
4057   case mwLogin_JAVA_WEB:
4058     return "Lotus Java Client Applet";
4059 
4060   case mwLogin_BINARY:
4061     return "Lotus Sametime Connect";
4062 
4063   case mwLogin_JAVA_APP:
4064     return "Lotus Java Client Application";
4065 
4066   case mwLogin_LINKS:
4067     return "Lotus Sametime Links";
4068 
4069   case mwLogin_NOTES_6_5:
4070   case mwLogin_NOTES_6_5_3:
4071   case mwLogin_NOTES_7_0_beta:
4072   case mwLogin_NOTES_7_0:
4073     return "Lotus Notes Client";
4074 
4075   case mwLogin_ICT:
4076   case mwLogin_ICT_1_7_8_2:
4077   case mwLogin_ICT_SIP:
4078     return "IBM Community Tools";
4079 
4080   case mwLogin_NOTESBUDDY_4_14:
4081   case mwLogin_NOTESBUDDY_4_15:
4082   case mwLogin_NOTESBUDDY_4_16:
4083     return "Alphaworks NotesBuddy";
4084 
4085   case 0x1305:
4086   case 0x1306:
4087   case 0x1307:
4088     return "Lotus Sametime Connect 7.5";
4089 
4090   case mwLogin_SANITY:
4091     return "Sanity";
4092 
4093   case mwLogin_ST_PERL:
4094     return "ST-Send-Message";
4095 
4096   case mwLogin_TRILLIAN:
4097   case mwLogin_TRILLIAN_IBM:
4098     return "Trillian";
4099 
4100   case mwLogin_MEANWHILE:
4101     return "Meanwhile";
4102 
4103   default:
4104     return NULL;
4105   }
4106 }
4107 
4108 
mw_prpl_get_info(PurpleConnection * gc,const char * who)4109 static void mw_prpl_get_info(PurpleConnection *gc, const char *who) {
4110 
4111   struct mwAwareIdBlock idb = { mwAware_USER, (char *) who, NULL };
4112 
4113   struct mwPurplePluginData *pd;
4114   PurpleAccount *acct;
4115   PurpleBuddy *b;
4116   PurpleNotifyUserInfo *user_info;
4117   char *tmp;
4118   const char *tmp2;
4119 
4120   g_return_if_fail(who != NULL);
4121   g_return_if_fail(*who != '\0');
4122 
4123   pd = gc->proto_data;
4124 
4125   acct = purple_connection_get_account(gc);
4126   b = purple_find_buddy(acct, who);
4127   user_info = purple_notify_user_info_new();
4128 
4129   if(purple_str_has_prefix(who, "@E ")) {
4130 	purple_notify_user_info_add_pair(user_info, _("External User"), NULL);
4131   }
4132 
4133   purple_notify_user_info_add_pair(user_info, _("User ID"), who);
4134 
4135   if(b) {
4136     guint32 type;
4137 
4138     if(purple_buddy_get_server_alias(b)) {
4139 		purple_notify_user_info_add_pair(user_info, _("Full Name"), purple_buddy_get_server_alias(b));
4140     }
4141 
4142     type = purple_blist_node_get_int((PurpleBlistNode *) b, BUDDY_KEY_CLIENT);
4143     if(type) {
4144 	  tmp = g_strdup(mw_client_name(type));
4145 	  if (!tmp)
4146 		tmp = g_strdup_printf(_("Unknown (0x%04x)<br>"), type);
4147 
4148 	  purple_notify_user_info_add_pair(user_info, _("Last Known Client"), tmp);
4149 
4150 	  g_free(tmp);
4151     }
4152   }
4153 
4154   tmp = user_supports_text(pd->srvc_aware, who);
4155   if(tmp) {
4156 	purple_notify_user_info_add_pair(user_info, _("Supports"), tmp);
4157 	g_free(tmp);
4158   }
4159 
4160   if(b) {
4161 	purple_notify_user_info_add_pair(user_info, _("Status"), status_text(b));
4162 
4163 	/* XXX Is this adding a status message in its own section rather than with the "Status" label? */
4164     tmp2 = mwServiceAware_getText(pd->srvc_aware, &idb);
4165     if(tmp2 && g_utf8_validate(tmp2, -1, NULL)) {
4166       tmp = g_markup_escape_text(tmp2, -1);
4167 	  purple_notify_user_info_add_section_break(user_info);
4168 	  purple_notify_user_info_add_pair(user_info, NULL, tmp);
4169       g_free(tmp);
4170     }
4171   }
4172 
4173   /* @todo emit a signal to allow a plugin to override the display of
4174      this notification, so that it can create its own */
4175 
4176   purple_notify_userinfo(gc, who, user_info, NULL, NULL);
4177   purple_notify_user_info_destroy(user_info);
4178 }
4179 
4180 
mw_prpl_set_status(PurpleAccount * acct,PurpleStatus * status)4181 static void mw_prpl_set_status(PurpleAccount *acct, PurpleStatus *status) {
4182   PurpleConnection *gc;
4183   const char *state;
4184   char *message = NULL;
4185   struct mwSession *session;
4186   struct mwUserStatus stat;
4187 
4188   g_return_if_fail(acct != NULL);
4189   gc = purple_account_get_connection(acct);
4190 
4191   state = purple_status_get_id(status);
4192 
4193   DEBUG_INFO("Set status to %s\n", purple_status_get_name(status));
4194 
4195   g_return_if_fail(gc != NULL);
4196 
4197   session = gc_to_session(gc);
4198   g_return_if_fail(session != NULL);
4199 
4200   /* get a working copy of the current status */
4201   mwUserStatus_clone(&stat, mwSession_getUserStatus(session));
4202 
4203   /* determine the state */
4204   if(purple_strequal(state, MW_STATE_ACTIVE)) {
4205     stat.status = mwStatus_ACTIVE;
4206 
4207   } else if(purple_strequal(state, MW_STATE_AWAY)) {
4208     stat.status = mwStatus_AWAY;
4209 
4210   } else if(purple_strequal(state, MW_STATE_BUSY)) {
4211     stat.status = mwStatus_BUSY;
4212   }
4213 
4214   /* determine the message */
4215   message = (char *) purple_status_get_attr_string(status, MW_STATE_MESSAGE);
4216 
4217   if(message) {
4218     /* all the possible non-NULL values of message up to this point
4219        are const, so we don't need to free them */
4220     message = purple_markup_strip_html(message);
4221   }
4222 
4223   /* out with the old */
4224   g_free(stat.desc);
4225 
4226   /* in with the new */
4227   stat.desc = (char *) message;
4228 
4229   mwSession_setUserStatus(session, &stat);
4230   mwUserStatus_clear(&stat);
4231 }
4232 
4233 
mw_prpl_set_idle(PurpleConnection * gc,int t)4234 static void mw_prpl_set_idle(PurpleConnection *gc, int t) {
4235   struct mwSession *session;
4236   struct mwUserStatus stat;
4237 
4238 
4239   session = gc_to_session(gc);
4240   g_return_if_fail(session != NULL);
4241 
4242   mwUserStatus_clone(&stat, mwSession_getUserStatus(session));
4243 
4244   if(t) {
4245     time_t now = time(NULL);
4246     stat.time = now - t;
4247 
4248   } else {
4249     stat.time = 0;
4250   }
4251 
4252   if(t > 0 && stat.status == mwStatus_ACTIVE) {
4253     /* we were active and went idle, so change the status to IDLE. */
4254     stat.status = mwStatus_IDLE;
4255 
4256   } else if(t == 0 && stat.status == mwStatus_IDLE) {
4257     /* we only become idle automatically, so change back to ACTIVE */
4258     stat.status = mwStatus_ACTIVE;
4259   }
4260 
4261   mwSession_setUserStatus(session, &stat);
4262   mwUserStatus_clear(&stat);
4263 }
4264 
4265 
notify_im(PurpleConnection * gc,GList * row,void * user_data)4266 static void notify_im(PurpleConnection *gc, GList *row, void *user_data) {
4267   PurpleAccount *acct;
4268   PurpleConversation *conv;
4269   char *id;
4270 
4271   acct = purple_connection_get_account(gc);
4272   id = g_list_nth_data(row, 1);
4273   conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
4274   if(! conv) conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
4275   purple_conversation_present(conv);
4276 }
4277 
4278 
notify_add(PurpleConnection * gc,GList * row,void * user_data)4279 static void notify_add(PurpleConnection *gc, GList *row, void *user_data) {
4280   BuddyAddData *data = user_data;
4281   const char *group_name = NULL;
4282 
4283   if (data && data->group) {
4284     group_name = purple_group_get_name(data->group);
4285   }
4286 
4287   purple_blist_request_add_buddy(purple_connection_get_account(gc),
4288 			       g_list_nth_data(row, 1), group_name,
4289 			       g_list_nth_data(row, 0));
4290 }
4291 
4292 
notify_close(gpointer data)4293 static void notify_close(gpointer data) {
4294   if (data) {
4295     g_free(data);
4296   }
4297 }
4298 
4299 
multi_resolved_query(struct mwResolveResult * result,PurpleConnection * gc,gpointer data)4300 static void multi_resolved_query(struct mwResolveResult *result,
4301 				 PurpleConnection *gc, gpointer data) {
4302   GList *l;
4303   const char *msgA;
4304   const char *msgB;
4305   char *msg;
4306 
4307   PurpleNotifySearchResults *sres;
4308   PurpleNotifySearchColumn *scol;
4309 
4310   sres = purple_notify_searchresults_new();
4311 
4312   scol = purple_notify_searchresults_column_new(_("User Name"));
4313   purple_notify_searchresults_column_add(sres, scol);
4314 
4315   scol = purple_notify_searchresults_column_new(_("Sametime ID"));
4316   purple_notify_searchresults_column_add(sres, scol);
4317 
4318   purple_notify_searchresults_button_add(sres, PURPLE_NOTIFY_BUTTON_IM,
4319 				       notify_im);
4320 
4321   purple_notify_searchresults_button_add(sres, PURPLE_NOTIFY_BUTTON_ADD,
4322 				       notify_add);
4323 
4324   for(l = result->matches; l; l = l->next) {
4325     struct mwResolveMatch *match = l->data;
4326     GList *row = NULL;
4327 
4328     DEBUG_INFO("multi resolve: %s, %s\n",
4329 	       NSTR(match->id), NSTR(match->name));
4330 
4331     if(!match->id || !match->name)
4332       continue;
4333 
4334     row = g_list_append(row, g_strdup(match->name));
4335     row = g_list_append(row, g_strdup(match->id));
4336     purple_notify_searchresults_row_add(sres, row);
4337   }
4338 
4339   msgA = _("An ambiguous user ID was entered");
4340   msgB = _("The identifier '%s' may possibly refer to any of the following"
4341 	   " users. Please select the correct user from the list below to"
4342 	   " add them to your buddy list.");
4343   msg = g_strdup_printf(msgB, result->name);
4344 
4345   purple_notify_searchresults(gc, _("Select User"),
4346 			    msgA, msg, sres, notify_close, data);
4347 
4348   g_free(msg);
4349 }
4350 
4351 
add_buddy_resolved(struct mwServiceResolve * srvc,guint32 id,guint32 code,GList * results,gpointer b)4352 static void add_buddy_resolved(struct mwServiceResolve *srvc,
4353 			       guint32 id, guint32 code, GList *results,
4354 			       gpointer b) {
4355 
4356   struct mwResolveResult *res = NULL;
4357   BuddyAddData *data = b;
4358   PurpleBuddy *buddy = NULL;
4359   PurpleConnection *gc;
4360   struct mwPurplePluginData *pd;
4361 
4362   g_return_if_fail(data != NULL);
4363 
4364   buddy = data->buddy;
4365 
4366   gc = purple_account_get_connection(purple_buddy_get_account(buddy));
4367   pd = gc->proto_data;
4368 
4369   if(results)
4370     res = results->data;
4371 
4372   if(!code && res && res->matches) {
4373     if(!res->matches->next) {
4374       struct mwResolveMatch *match = res->matches->data;
4375 
4376       /* only one? that might be the right one! */
4377       if(!purple_strequal(res->name, match->id)) {
4378 	/* uh oh, the single result isn't identical to the search
4379 	   term, better safe then sorry, so let's make sure it's who
4380 	   the user meant to add */
4381 	purple_blist_remove_buddy(buddy);
4382 	multi_resolved_query(res, gc, data);
4383 
4384       } else {
4385 
4386 	/* same person, set the server alias */
4387 	purple_blist_server_alias_buddy(buddy, match->name);
4388 	purple_blist_node_set_string((PurpleBlistNode *) buddy,
4389 				   BUDDY_KEY_NAME, match->name);
4390 
4391 	/* subscribe to awareness */
4392 	buddy_add(pd, buddy);
4393 
4394 	blist_schedule(pd);
4395 
4396         g_free(data);
4397       }
4398 
4399     } else {
4400       /* prompt user if more than one match was returned */
4401       purple_blist_remove_buddy(buddy);
4402       multi_resolved_query(res, gc, data);
4403     }
4404 
4405     return;
4406   }
4407 
4408 #if 0
4409   /* fall-through indicates that we couldn't find a matching user in
4410      the resolve service (ether error or zero results), so we remove
4411      this buddy */
4412 
4413   /* note: I can't really think of a good reason to alter the buddy
4414      list in any way. There has been at least one report where the
4415      resolve service isn't returning correct results anyway, so let's
4416      just leave them in the list. I'm just going to if0 this section
4417      out unless I can think of a very good reason to do this. -siege */
4418 
4419   DEBUG_INFO("no such buddy in community\n");
4420   purple_blist_remove_buddy(buddy);
4421   blist_schedule(pd);
4422 
4423   if(res && res->name) {
4424     /* compose and display an error message */
4425     const char *msgA;
4426     const char *msgB;
4427     char *msg;
4428 
4429     msgA = _("Unable to add user: user not found");
4430 
4431     msgB = _("The identifier '%s' did not match any users in your"
4432 	     " Sametime community. This entry has been removed from"
4433 	     " your buddy list.");
4434     msg = g_strdup_printf(msgB, NSTR(res->name));
4435 
4436     purple_notify_error(gc, _("Unable to add user"), msgA, msg);
4437 
4438     g_free(msg);
4439   }
4440 #endif
4441 }
4442 
4443 
mw_prpl_add_buddy(PurpleConnection * gc,PurpleBuddy * buddy,PurpleGroup * group)4444 static void mw_prpl_add_buddy(PurpleConnection *gc,
4445 			      PurpleBuddy *buddy,
4446 			      PurpleGroup *group) {
4447 
4448   struct mwPurplePluginData *pd = gc->proto_data;
4449   struct mwServiceResolve *srvc;
4450   GList *query;
4451   enum mwResolveFlag flags;
4452   guint32 req;
4453   BuddyAddData *data;
4454 
4455   /* catch external buddies. They won't be in the resolve service */
4456   if(buddy_is_external(buddy)) {
4457     buddy_add(pd, buddy);
4458     return;
4459   }
4460 
4461   data = g_new0(BuddyAddData, 1);
4462   data->buddy = buddy;
4463   data->group = group;
4464 
4465   srvc = pd->srvc_resolve;
4466 
4467   query = g_list_prepend(NULL, (char *)purple_buddy_get_name(buddy));
4468   flags = mwResolveFlag_FIRST | mwResolveFlag_USERS;
4469 
4470   req = mwServiceResolve_resolve(srvc, query, flags, add_buddy_resolved,
4471 				 data, NULL);
4472   g_list_free(query);
4473 
4474   if(req == SEARCH_ERROR) {
4475     purple_blist_remove_buddy(buddy);
4476     blist_schedule(pd);
4477   }
4478 }
4479 
4480 
foreach_add_buddies(PurpleGroup * group,GList * buddies,struct mwPurplePluginData * pd)4481 static void foreach_add_buddies(PurpleGroup *group, GList *buddies,
4482 				struct mwPurplePluginData *pd) {
4483   struct mwAwareList *list;
4484 
4485   list = list_ensure(pd, group);
4486   mwAwareList_addAware(list, buddies);
4487   g_list_free(buddies);
4488 }
4489 
4490 
mw_prpl_add_buddies(PurpleConnection * gc,GList * buddies,GList * groups)4491 static void mw_prpl_add_buddies(PurpleConnection *gc,
4492 				GList *buddies,
4493 				GList *groups) {
4494 
4495   struct mwPurplePluginData *pd;
4496   GHashTable *group_sets;
4497   struct mwAwareIdBlock *idbs, *idb;
4498 
4499   pd = gc->proto_data;
4500 
4501   /* map PurpleGroup:GList of mwAwareIdBlock */
4502   group_sets = g_hash_table_new(g_direct_hash, g_direct_equal);
4503 
4504   /* bunch of mwAwareIdBlock allocated at once, free'd at once */
4505   idb = idbs = g_new(struct mwAwareIdBlock, g_list_length(buddies));
4506 
4507   /* first pass collects mwAwareIdBlock lists for each group */
4508   for(; buddies; buddies = buddies->next) {
4509     PurpleBuddy *b = buddies->data;
4510     PurpleGroup *g;
4511     const char *fn;
4512     GList *l;
4513 
4514     /* nab the saved server alias and stick it on the buddy */
4515     fn = purple_blist_node_get_string((PurpleBlistNode *) b, BUDDY_KEY_NAME);
4516     purple_blist_server_alias_buddy(b, fn);
4517 
4518     /* convert PurpleBuddy into a mwAwareIdBlock */
4519     idb->type = mwAware_USER;
4520     idb->user = (char *) purple_buddy_get_name(b);
4521     idb->community = NULL;
4522 
4523     /* put idb into the list associated with the buddy's group */
4524     g = purple_buddy_get_group(b);
4525     l = g_hash_table_lookup(group_sets, g);
4526     l = g_list_prepend(l, idb++);
4527     g_hash_table_insert(group_sets, g, l);
4528   }
4529 
4530   /* each group's buddies get added in one shot, and schedule the blist
4531      for saving */
4532   g_hash_table_foreach(group_sets, (GHFunc) foreach_add_buddies, pd);
4533   blist_schedule(pd);
4534 
4535   /* cleanup */
4536   g_hash_table_destroy(group_sets);
4537   g_free(idbs);
4538 }
4539 
4540 
mw_prpl_remove_buddy(PurpleConnection * gc,PurpleBuddy * buddy,PurpleGroup * group)4541 static void mw_prpl_remove_buddy(PurpleConnection *gc,
4542 				 PurpleBuddy *buddy, PurpleGroup *group) {
4543 
4544   struct mwPurplePluginData *pd;
4545   struct mwAwareIdBlock idb = { mwAware_USER, (char *)purple_buddy_get_name(buddy), NULL };
4546   struct mwAwareList *list;
4547 
4548   GList *rem = g_list_prepend(NULL, &idb);
4549 
4550   pd = gc->proto_data;
4551   group = purple_buddy_get_group(buddy);
4552   list = list_ensure(pd, group);
4553 
4554   mwAwareList_removeAware(list, rem);
4555   blist_schedule(pd);
4556 
4557   g_list_free(rem);
4558 }
4559 
4560 
privacy_fill(struct mwPrivacyInfo * priv,GSList * members)4561 static void privacy_fill(struct mwPrivacyInfo *priv,
4562 			 GSList *members) {
4563 
4564   struct mwUserItem *u;
4565   guint count;
4566 
4567   count = g_slist_length(members);
4568   DEBUG_INFO("privacy_fill: %u members\n", count);
4569 
4570   priv->count = count;
4571   priv->users = g_new0(struct mwUserItem, count);
4572 
4573   while(count--) {
4574     u = priv->users + count;
4575     u->id = members->data;
4576     members = members->next;
4577   }
4578 }
4579 
4580 
mw_prpl_set_permit_deny(PurpleConnection * gc)4581 static void mw_prpl_set_permit_deny(PurpleConnection *gc) {
4582   PurpleAccount *acct;
4583   struct mwPurplePluginData *pd;
4584   struct mwSession *session;
4585 
4586   struct mwPrivacyInfo privacy = {
4587     FALSE, /* deny  */
4588     0,     /* count */
4589     NULL,  /* users */
4590   };
4591 
4592   g_return_if_fail(gc != NULL);
4593 
4594   acct = purple_connection_get_account(gc);
4595   g_return_if_fail(acct != NULL);
4596 
4597   pd = gc->proto_data;
4598   g_return_if_fail(pd != NULL);
4599 
4600   session = pd->session;
4601   g_return_if_fail(session != NULL);
4602 
4603   switch(acct->perm_deny) {
4604   case PURPLE_PRIVACY_DENY_USERS:
4605     DEBUG_INFO("PURPLE_PRIVACY_DENY_USERS\n");
4606     privacy_fill(&privacy, acct->deny);
4607     privacy.deny = TRUE;
4608     break;
4609 
4610   case PURPLE_PRIVACY_ALLOW_ALL:
4611     DEBUG_INFO("PURPLE_PRIVACY_ALLOW_ALL\n");
4612     privacy.deny = TRUE;
4613     break;
4614 
4615   case PURPLE_PRIVACY_ALLOW_USERS:
4616     DEBUG_INFO("PURPLE_PRIVACY_ALLOW_USERS\n");
4617     privacy_fill(&privacy, acct->permit);
4618     privacy.deny = FALSE;
4619     break;
4620 
4621   case PURPLE_PRIVACY_DENY_ALL:
4622     DEBUG_INFO("PURPLE_PRIVACY_DENY_ALL\n");
4623     privacy.deny = FALSE;
4624     break;
4625 
4626   default:
4627     DEBUG_INFO("acct->perm_deny is 0x%x\n", acct->perm_deny);
4628     return;
4629   }
4630 
4631   mwSession_setPrivacyInfo(session, &privacy);
4632   g_free(privacy.users);
4633 }
4634 
4635 
mw_prpl_add_permit(PurpleConnection * gc,const char * name)4636 static void mw_prpl_add_permit(PurpleConnection *gc, const char *name) {
4637   mw_prpl_set_permit_deny(gc);
4638 }
4639 
4640 
mw_prpl_add_deny(PurpleConnection * gc,const char * name)4641 static void mw_prpl_add_deny(PurpleConnection *gc, const char *name) {
4642   mw_prpl_set_permit_deny(gc);
4643 }
4644 
4645 
mw_prpl_rem_permit(PurpleConnection * gc,const char * name)4646 static void mw_prpl_rem_permit(PurpleConnection *gc, const char *name) {
4647   mw_prpl_set_permit_deny(gc);
4648 }
4649 
4650 
mw_prpl_rem_deny(PurpleConnection * gc,const char * name)4651 static void mw_prpl_rem_deny(PurpleConnection *gc, const char *name) {
4652   mw_prpl_set_permit_deny(gc);
4653 }
4654 
4655 
conf_find(struct mwServiceConference * srvc,const char * name)4656 static struct mwConference *conf_find(struct mwServiceConference *srvc,
4657 				      const char *name) {
4658   GList *l, *ll;
4659   struct mwConference *conf = NULL;
4660 
4661   ll = mwServiceConference_getConferences(srvc);
4662   for(l = ll; l; l = l->next) {
4663     struct mwConference *c = l->data;
4664     if(purple_strequal(name, mwConference_getName(c))) {
4665       conf = c;
4666       break;
4667     }
4668   }
4669   g_list_free(ll);
4670 
4671   return conf;
4672 }
4673 
4674 
mw_prpl_join_chat(PurpleConnection * gc,GHashTable * components)4675 static void mw_prpl_join_chat(PurpleConnection *gc,
4676 			      GHashTable *components) {
4677 
4678   struct mwPurplePluginData *pd;
4679   char *c, *t;
4680 
4681   pd = gc->proto_data;
4682 
4683   c = g_hash_table_lookup(components, CHAT_KEY_NAME);
4684   t = g_hash_table_lookup(components, CHAT_KEY_TOPIC);
4685 
4686   if(g_hash_table_lookup(components, CHAT_KEY_IS_PLACE)) {
4687     /* use place service */
4688     struct mwServicePlace *srvc;
4689     struct mwPlace *place = NULL;
4690 
4691     srvc = pd->srvc_place;
4692     place = mwPlace_new(srvc, c, t);
4693     mwPlace_open(place);
4694 
4695   } else {
4696     /* use conference service */
4697     struct mwServiceConference *srvc;
4698     struct mwConference *conf = NULL;
4699 
4700     srvc = pd->srvc_conf;
4701     if(c) conf = conf_find(srvc, c);
4702 
4703     if(conf) {
4704       DEBUG_INFO("accepting conference invitation\n");
4705       mwConference_accept(conf);
4706 
4707     } else {
4708       DEBUG_INFO("creating new conference\n");
4709       conf = mwConference_new(srvc, t);
4710       mwConference_open(conf);
4711     }
4712   }
4713 }
4714 
4715 
mw_prpl_reject_chat(PurpleConnection * gc,GHashTable * components)4716 static void mw_prpl_reject_chat(PurpleConnection *gc,
4717 				GHashTable *components) {
4718 
4719   struct mwPurplePluginData *pd;
4720   struct mwServiceConference *srvc;
4721   char *c;
4722 
4723   pd = gc->proto_data;
4724   srvc = pd->srvc_conf;
4725 
4726   if(g_hash_table_lookup(components, CHAT_KEY_IS_PLACE)) {
4727     ; /* nothing needs doing */
4728 
4729   } else {
4730     /* reject conference */
4731     c = g_hash_table_lookup(components, CHAT_KEY_NAME);
4732     if(c) {
4733       struct mwConference *conf = conf_find(srvc, c);
4734       if(conf) mwConference_reject(conf, ERR_SUCCESS, "Declined");
4735     }
4736   }
4737 }
4738 
4739 
mw_prpl_get_chat_name(GHashTable * components)4740 static char *mw_prpl_get_chat_name(GHashTable *components) {
4741   return g_hash_table_lookup(components, CHAT_KEY_NAME);
4742 }
4743 
4744 
mw_prpl_chat_invite(PurpleConnection * gc,int id,const char * invitation,const char * who)4745 static void mw_prpl_chat_invite(PurpleConnection *gc,
4746 				int id,
4747 				const char *invitation,
4748 				const char *who) {
4749 
4750   struct mwPurplePluginData *pd;
4751   struct mwConference *conf;
4752   struct mwPlace *place;
4753   struct mwIdBlock idb = { (char *) who, NULL };
4754 
4755   pd = gc->proto_data;
4756   g_return_if_fail(pd != NULL);
4757 
4758   conf = ID_TO_CONF(pd, id);
4759 
4760   if(conf) {
4761     mwConference_invite(conf, &idb, invitation);
4762     return;
4763   }
4764 
4765   place = ID_TO_PLACE(pd, id);
4766   g_return_if_fail(place != NULL);
4767 
4768   /* @todo: use the IM service for invitation */
4769   mwPlace_legacyInvite(place, &idb, invitation);
4770 }
4771 
4772 
mw_prpl_chat_leave(PurpleConnection * gc,int id)4773 static void mw_prpl_chat_leave(PurpleConnection *gc,
4774 			       int id) {
4775 
4776   struct mwPurplePluginData *pd;
4777   struct mwConference *conf;
4778 
4779   pd = gc->proto_data;
4780 
4781   g_return_if_fail(pd != NULL);
4782   conf = ID_TO_CONF(pd, id);
4783 
4784   if(conf) {
4785     mwConference_destroy(conf, ERR_SUCCESS, "Leaving");
4786 
4787   } else {
4788     struct mwPlace *place = ID_TO_PLACE(pd, id);
4789     g_return_if_fail(place != NULL);
4790 
4791     mwPlace_destroy(place, ERR_SUCCESS);
4792   }
4793 }
4794 
4795 
mw_prpl_chat_whisper(PurpleConnection * gc,int id,const char * who,const char * message)4796 static void mw_prpl_chat_whisper(PurpleConnection *gc,
4797 				 int id,
4798 				 const char *who,
4799 				 const char *message) {
4800 
4801   mw_prpl_send_im(gc, who, message, 0);
4802 }
4803 
4804 
mw_prpl_chat_send(PurpleConnection * gc,int id,const char * message,PurpleMessageFlags flags)4805 static int mw_prpl_chat_send(PurpleConnection *gc,
4806 			     int id,
4807 			     const char *message,
4808 			     PurpleMessageFlags flags) {
4809 
4810   struct mwPurplePluginData *pd;
4811   struct mwConference *conf;
4812   char *msg;
4813   int ret;
4814 
4815   pd = gc->proto_data;
4816 
4817   g_return_val_if_fail(pd != NULL, 0);
4818   conf = ID_TO_CONF(pd, id);
4819 
4820   msg = purple_markup_strip_html(message);
4821 
4822   if(conf) {
4823     ret = ! mwConference_sendText(conf, msg);
4824 
4825   } else {
4826     struct mwPlace *place = ID_TO_PLACE(pd, id);
4827     g_return_val_if_fail(place != NULL, 0);
4828 
4829     ret = ! mwPlace_sendText(place, msg);
4830   }
4831 
4832   g_free(msg);
4833   return ret;
4834 }
4835 
4836 
mw_prpl_keepalive(PurpleConnection * gc)4837 static void mw_prpl_keepalive(PurpleConnection *gc) {
4838   struct mwSession *session;
4839 
4840   g_return_if_fail(gc != NULL);
4841 
4842   session = gc_to_session(gc);
4843   g_return_if_fail(session != NULL);
4844 
4845   mwSession_sendKeepalive(session);
4846 }
4847 
4848 
mw_prpl_alias_buddy(PurpleConnection * gc,const char * who,const char * alias)4849 static void mw_prpl_alias_buddy(PurpleConnection *gc,
4850 				const char *who,
4851 				const char *alias) {
4852 
4853   struct mwPurplePluginData *pd = gc->proto_data;
4854   g_return_if_fail(pd != NULL);
4855 
4856   /* it's a change to the buddy list, so we've gotta reflect that in
4857      the server copy */
4858 
4859   blist_schedule(pd);
4860 }
4861 
4862 
mw_prpl_group_buddy(PurpleConnection * gc,const char * who,const char * old_group,const char * new_group)4863 static void mw_prpl_group_buddy(PurpleConnection *gc,
4864 				const char *who,
4865 				const char *old_group,
4866 				const char *new_group) {
4867 
4868   struct mwAwareIdBlock idb = { mwAware_USER, (char *) who, NULL };
4869   GList *gl = g_list_prepend(NULL, &idb);
4870 
4871   struct mwPurplePluginData *pd = gc->proto_data;
4872   PurpleGroup *group;
4873   struct mwAwareList *list;
4874 
4875   /* add who to new_group's aware list */
4876   group = purple_find_group(new_group);
4877   list = list_ensure(pd, group);
4878   mwAwareList_addAware(list, gl);
4879 
4880   /* remove who from old_group's aware list */
4881   group = purple_find_group(old_group);
4882   list = list_ensure(pd, group);
4883   mwAwareList_removeAware(list, gl);
4884 
4885   g_list_free(gl);
4886 
4887   /* schedule the changes to be saved */
4888   blist_schedule(pd);
4889 }
4890 
4891 
mw_prpl_rename_group(PurpleConnection * gc,const char * old,PurpleGroup * group,GList * buddies)4892 static void mw_prpl_rename_group(PurpleConnection *gc,
4893 				 const char *old,
4894 				 PurpleGroup *group,
4895 				 GList *buddies) {
4896 
4897   struct mwPurplePluginData *pd = gc->proto_data;
4898   g_return_if_fail(pd != NULL);
4899 
4900   /* it's a change in the buddy list, so we've gotta reflect that in
4901      the server copy. Also, having this function should prevent all
4902      those buddies from being removed and re-added. We don't really
4903      give a crap what the group is named in Purple other than to record
4904      that as the group name/alias */
4905 
4906   blist_schedule(pd);
4907 }
4908 
4909 
mw_prpl_buddy_free(PurpleBuddy * buddy)4910 static void mw_prpl_buddy_free(PurpleBuddy *buddy) {
4911   /* I don't think we have any cleanup for buddies yet */
4912   ;
4913 }
4914 
4915 
mw_prpl_convo_closed(PurpleConnection * gc,const char * who)4916 static void mw_prpl_convo_closed(PurpleConnection *gc, const char *who) {
4917   struct mwPurplePluginData *pd = gc->proto_data;
4918   struct mwServiceIm *srvc;
4919   struct mwConversation *conv;
4920   struct mwIdBlock idb = { (char *) who, NULL };
4921 
4922   g_return_if_fail(pd != NULL);
4923 
4924   srvc = pd->srvc_im;
4925   g_return_if_fail(srvc != NULL);
4926 
4927   conv = mwServiceIm_findConversation(srvc, &idb);
4928   if(! conv) return;
4929 
4930   if(mwConversation_isOpen(conv))
4931     mwConversation_free(conv);
4932 }
4933 
4934 
mw_prpl_normalize(const PurpleAccount * account,const char * id)4935 static const char *mw_prpl_normalize(const PurpleAccount *account,
4936 				     const char *id) {
4937 
4938   /* code elsewhere assumes that the return value points to different
4939      memory than the passed value, but it won't free the normalized
4940      data. wtf? */
4941 
4942   static char buf[BUF_LEN];
4943   g_strlcpy(buf, id, sizeof(buf));
4944   return buf;
4945 }
4946 
4947 
mw_prpl_remove_group(PurpleConnection * gc,PurpleGroup * group)4948 static void mw_prpl_remove_group(PurpleConnection *gc, PurpleGroup *group) {
4949   struct mwPurplePluginData *pd;
4950   struct mwAwareList *list;
4951 
4952   pd = gc->proto_data;
4953   g_return_if_fail(pd != NULL);
4954   g_return_if_fail(pd->group_list_map != NULL);
4955 
4956   list = g_hash_table_lookup(pd->group_list_map, group);
4957 
4958   if(list) {
4959     g_hash_table_remove(pd->group_list_map, list);
4960     g_hash_table_remove(pd->group_list_map, group);
4961     mwAwareList_free(list);
4962 
4963     blist_schedule(pd);
4964   }
4965 }
4966 
4967 
mw_prpl_can_receive_file(PurpleConnection * gc,const char * who)4968 static gboolean mw_prpl_can_receive_file(PurpleConnection *gc,
4969 					 const char *who) {
4970   struct mwPurplePluginData *pd;
4971   struct mwServiceAware *srvc;
4972   PurpleAccount *acct;
4973 
4974   g_return_val_if_fail(gc != NULL, FALSE);
4975 
4976   pd = gc->proto_data;
4977   g_return_val_if_fail(pd != NULL, FALSE);
4978 
4979   srvc = pd->srvc_aware;
4980   g_return_val_if_fail(srvc != NULL, FALSE);
4981 
4982   acct = purple_connection_get_account(gc);
4983   g_return_val_if_fail(acct != NULL, FALSE);
4984 
4985   return purple_find_buddy(acct, who) &&
4986     user_supports(srvc, who, mwAttribute_FILE_TRANSFER);
4987 }
4988 
4989 
ft_outgoing_init(PurpleXfer * xfer)4990 static void ft_outgoing_init(PurpleXfer *xfer) {
4991   PurpleAccount *acct;
4992   PurpleConnection *gc;
4993 
4994   struct mwPurplePluginData *pd;
4995   struct mwServiceFileTransfer *srvc;
4996   struct mwFileTransfer *ft;
4997 
4998   const char *filename;
4999   gsize filesize;
5000   FILE *fp;
5001 
5002   struct mwIdBlock idb = { NULL, NULL };
5003 
5004   DEBUG_INFO("ft_outgoing_init\n");
5005 
5006   acct = purple_xfer_get_account(xfer);
5007   gc = purple_account_get_connection(acct);
5008   pd = gc->proto_data;
5009   srvc = pd->srvc_ft;
5010 
5011   filename = purple_xfer_get_local_filename(xfer);
5012   filesize = purple_xfer_get_size(xfer);
5013   idb.user = xfer->who;
5014 
5015   purple_xfer_update_progress(xfer);
5016 
5017   /* test that we can actually send the file */
5018   fp = g_fopen(filename, "rb");
5019   if(! fp) {
5020     char *msg = g_strdup_printf(_("Error reading file %s: \n%s\n"),
5021 				filename, g_strerror(errno));
5022     purple_xfer_error(purple_xfer_get_type(xfer), acct, xfer->who, msg);
5023     g_free(msg);
5024     return;
5025   }
5026   fclose(fp);
5027 
5028   {
5029     char *tmp = strrchr(filename, G_DIR_SEPARATOR);
5030     if(tmp++) filename = tmp;
5031   }
5032 
5033   ft = mwFileTransfer_new(srvc, &idb, NULL, filename, filesize);
5034 
5035   purple_xfer_ref(xfer);
5036   mwFileTransfer_setClientData(ft, xfer, (GDestroyNotify) purple_xfer_unref);
5037   xfer->data = ft;
5038 
5039   mwFileTransfer_offer(ft);
5040 }
5041 
5042 
ft_outgoing_cancel(PurpleXfer * xfer)5043 static void ft_outgoing_cancel(PurpleXfer *xfer) {
5044   struct mwFileTransfer *ft = xfer->data;
5045 
5046   DEBUG_INFO("ft_outgoing_cancel called\n");
5047 
5048   if(ft) mwFileTransfer_cancel(ft);
5049 }
5050 
5051 
mw_prpl_new_xfer(PurpleConnection * gc,const char * who)5052 static PurpleXfer *mw_prpl_new_xfer(PurpleConnection *gc, const char *who) {
5053   PurpleAccount *acct;
5054   PurpleXfer *xfer;
5055 
5056   acct = purple_connection_get_account(gc);
5057 
5058   xfer = purple_xfer_new(acct, PURPLE_XFER_SEND, who);
5059   if (xfer)
5060   {
5061     purple_xfer_set_init_fnc(xfer, ft_outgoing_init);
5062     purple_xfer_set_cancel_send_fnc(xfer, ft_outgoing_cancel);
5063   }
5064 
5065   return xfer;
5066 }
5067 
mw_prpl_send_file(PurpleConnection * gc,const char * who,const char * file)5068 static void mw_prpl_send_file(PurpleConnection *gc,
5069 			      const char *who, const char *file) {
5070 
5071   PurpleXfer *xfer = mw_prpl_new_xfer(gc, who);
5072 
5073   if(file) {
5074     DEBUG_INFO("file != NULL\n");
5075     purple_xfer_request_accepted(xfer, file);
5076 
5077   } else {
5078     DEBUG_INFO("file == NULL\n");
5079     purple_xfer_request(xfer);
5080   }
5081 }
5082 
5083 
5084 static PurplePluginProtocolInfo mw_prpl_info = {
5085   OPT_PROTO_IM_IMAGE,
5086   NULL, /*< set in mw_plugin_init */
5087   NULL, /*< set in mw_plugin_init */
5088   NO_BUDDY_ICONS,
5089   mw_prpl_list_icon,
5090   mw_prpl_list_emblem,
5091   mw_prpl_status_text,
5092   mw_prpl_tooltip_text,
5093   mw_prpl_status_types,
5094   mw_prpl_blist_node_menu,
5095   mw_prpl_chat_info,
5096   mw_prpl_chat_info_defaults,
5097   mw_prpl_login,
5098   mw_prpl_close,
5099   mw_prpl_send_im,
5100   NULL,
5101   mw_prpl_send_typing,
5102   mw_prpl_get_info,
5103   mw_prpl_set_status,
5104   mw_prpl_set_idle,
5105   NULL,
5106   mw_prpl_add_buddy,
5107   mw_prpl_add_buddies,
5108   mw_prpl_remove_buddy,
5109   NULL,
5110   mw_prpl_add_permit,
5111   mw_prpl_add_deny,
5112   mw_prpl_rem_permit,
5113   mw_prpl_rem_deny,
5114   mw_prpl_set_permit_deny,
5115   mw_prpl_join_chat,
5116   mw_prpl_reject_chat,
5117   mw_prpl_get_chat_name,
5118   mw_prpl_chat_invite,
5119   mw_prpl_chat_leave,
5120   mw_prpl_chat_whisper,
5121   mw_prpl_chat_send,
5122   mw_prpl_keepalive,
5123   NULL,
5124   NULL,
5125   NULL,
5126   mw_prpl_alias_buddy,
5127   mw_prpl_group_buddy,
5128   mw_prpl_rename_group,
5129   mw_prpl_buddy_free,
5130   mw_prpl_convo_closed,
5131   mw_prpl_normalize,
5132   NULL,
5133   mw_prpl_remove_group,
5134   NULL,
5135   NULL,
5136   NULL,
5137   NULL,
5138   NULL,
5139   NULL,
5140   mw_prpl_can_receive_file,
5141   mw_prpl_send_file,
5142   mw_prpl_new_xfer,
5143   NULL,
5144   NULL,
5145   NULL,
5146   NULL,
5147   NULL,
5148   NULL,
5149   NULL,
5150   sizeof(PurplePluginProtocolInfo),
5151   NULL,
5152   NULL,
5153   NULL,
5154   NULL,
5155   NULL,
5156   NULL,
5157   NULL,
5158   NULL,
5159   NULL, /* get_cb_alias */
5160   NULL, /* chat_can_receive_file */
5161   NULL, /* chat_send_file */
5162 };
5163 
5164 
5165 static PurplePluginPrefFrame *
mw_plugin_get_plugin_pref_frame(PurplePlugin * plugin)5166 mw_plugin_get_plugin_pref_frame(PurplePlugin *plugin) {
5167   PurplePluginPrefFrame *frame;
5168   PurplePluginPref *pref;
5169 
5170   frame = purple_plugin_pref_frame_new();
5171 
5172   pref = purple_plugin_pref_new_with_label(_("Remotely Stored Buddy List"));
5173   purple_plugin_pref_frame_add(frame, pref);
5174 
5175 
5176   pref = purple_plugin_pref_new_with_name(MW_PRPL_OPT_BLIST_ACTION);
5177   purple_plugin_pref_set_label(pref, _("Buddy List Storage Mode"));
5178 
5179   purple_plugin_pref_set_type(pref, PURPLE_PLUGIN_PREF_CHOICE);
5180   purple_plugin_pref_add_choice(pref, _("Local Buddy List Only"),
5181 			      GINT_TO_POINTER(blist_choice_LOCAL));
5182   purple_plugin_pref_add_choice(pref, _("Merge List from Server"),
5183 			      GINT_TO_POINTER(blist_choice_MERGE));
5184   purple_plugin_pref_add_choice(pref, _("Merge and Save List to Server"),
5185 			      GINT_TO_POINTER(blist_choice_STORE));
5186   purple_plugin_pref_add_choice(pref, _("Synchronize List with Server"),
5187 			      GINT_TO_POINTER(blist_choice_SYNCH));
5188 
5189   purple_plugin_pref_frame_add(frame, pref);
5190 
5191   return frame;
5192 }
5193 
5194 
5195 static PurplePluginUiInfo mw_plugin_ui_info = {
5196   mw_plugin_get_plugin_pref_frame,
5197   0,    /* page_num */
5198   NULL, /* frame */
5199   NULL,
5200   NULL,
5201   NULL,
5202   NULL
5203 };
5204 
5205 
st_import_action_cb(PurpleConnection * gc,char * filename)5206 static void st_import_action_cb(PurpleConnection *gc, char *filename) {
5207   struct mwSametimeList *l;
5208 
5209   FILE *file;
5210   char buf[BUF_LEN];
5211   size_t len;
5212 
5213   GString *str;
5214 
5215   file = g_fopen(filename, "r");
5216   g_return_if_fail(file != NULL);
5217 
5218   str = g_string_new(NULL);
5219   while( (len = fread(buf, 1, BUF_LEN, file)) ) {
5220     g_string_append_len(str, buf, len);
5221   }
5222 
5223   fclose(file);
5224 
5225   l = mwSametimeList_load(str->str);
5226   g_string_free(str, TRUE);
5227 
5228   blist_merge(gc, l);
5229   mwSametimeList_free(l);
5230 }
5231 
5232 
5233 /** prompts for a file to import blist from */
st_import_action(PurplePluginAction * act)5234 static void st_import_action(PurplePluginAction *act) {
5235   PurpleConnection *gc;
5236   PurpleAccount *account;
5237   char *title;
5238 
5239   gc = act->context;
5240   account = purple_connection_get_account(gc);
5241   title = g_strdup_printf(_("Import Sametime List for Account %s"),
5242 			  purple_account_get_username(account));
5243 
5244   purple_request_file(gc, title, NULL, FALSE,
5245 		    G_CALLBACK(st_import_action_cb), NULL,
5246 		    account, NULL, NULL,
5247 		    gc);
5248 
5249   g_free(title);
5250 }
5251 
5252 
st_export_action_cb(PurpleConnection * gc,char * filename)5253 static void st_export_action_cb(PurpleConnection *gc, char *filename) {
5254   struct mwSametimeList *l;
5255   char *str;
5256   FILE *file;
5257 
5258   file = g_fopen(filename, "w");
5259   g_return_if_fail(file != NULL);
5260 
5261   l = mwSametimeList_new();
5262   blist_export(gc, l);
5263   str = mwSametimeList_store(l);
5264   mwSametimeList_free(l);
5265 
5266   fprintf(file, "%s", str);
5267   fclose(file);
5268 
5269   g_free(str);
5270 }
5271 
5272 
5273 /** prompts for a file to export blist to */
st_export_action(PurplePluginAction * act)5274 static void st_export_action(PurplePluginAction *act) {
5275   PurpleConnection *gc;
5276   PurpleAccount *account;
5277   char *title;
5278 
5279   gc = act->context;
5280   account = purple_connection_get_account(gc);
5281   title = g_strdup_printf(_("Export Sametime List for Account %s"),
5282 			  purple_account_get_username(account));
5283 
5284   purple_request_file(gc, title, NULL, TRUE,
5285 		    G_CALLBACK(st_export_action_cb), NULL,
5286 			account, NULL, NULL,
5287 		    gc);
5288 
5289   g_free(title);
5290 }
5291 
5292 
remote_group_multi_cleanup(gpointer ignore,PurpleRequestFields * fields)5293 static void remote_group_multi_cleanup(gpointer ignore,
5294 				       PurpleRequestFields *fields) {
5295 
5296   PurpleRequestField *f;
5297   GList *l;
5298 
5299   f = purple_request_fields_get_field(fields, "group");
5300   l = purple_request_field_list_get_items(f);
5301 
5302   for(; l; l = l->next) {
5303     const char *i = l->data;
5304     struct named_id *res;
5305 
5306     res = purple_request_field_list_get_data(f, i);
5307 
5308     g_free(res->id);
5309     g_free(res->name);
5310     g_free(res);
5311   }
5312 }
5313 
5314 
remote_group_done(struct mwPurplePluginData * pd,const char * id,const char * name)5315 static void remote_group_done(struct mwPurplePluginData *pd,
5316 			      const char *id, const char *name) {
5317   PurpleConnection *gc;
5318   PurpleAccount *acct;
5319   PurpleGroup *group;
5320   PurpleBlistNode *gn;
5321   const char *owner;
5322 
5323   g_return_if_fail(pd != NULL);
5324 
5325   gc = pd->gc;
5326   acct = purple_connection_get_account(gc);
5327 
5328   /* collision checking */
5329   group = purple_find_group(name);
5330   if(group) {
5331     const char *msgA;
5332     const char *msgB;
5333     char *msg;
5334 
5335     msgA = _("Unable to add group: group exists");
5336     msgB = _("A group named '%s' already exists in your buddy list.");
5337     msg = g_strdup_printf(msgB, name);
5338 
5339     purple_notify_error(gc, _("Unable to add group"), msgA, msg);
5340 
5341     g_free(msg);
5342     return;
5343   }
5344 
5345   group = purple_group_new(name);
5346   gn = (PurpleBlistNode *) group;
5347 
5348   owner = purple_account_get_username(acct);
5349 
5350   purple_blist_node_set_string(gn, GROUP_KEY_NAME, id);
5351   purple_blist_node_set_int(gn, GROUP_KEY_TYPE, mwSametimeGroup_DYNAMIC);
5352   purple_blist_node_set_string(gn, GROUP_KEY_OWNER, owner);
5353   purple_blist_add_group(group, NULL);
5354 
5355   group_add(pd, group);
5356   blist_schedule(pd);
5357 }
5358 
5359 
remote_group_multi_cb(struct mwPurplePluginData * pd,PurpleRequestFields * fields)5360 static void remote_group_multi_cb(struct mwPurplePluginData *pd,
5361 				  PurpleRequestFields *fields) {
5362   PurpleRequestField *f;
5363   GList *l;
5364 
5365   f = purple_request_fields_get_field(fields, "group");
5366   l = purple_request_field_list_get_selected(f);
5367 
5368   if(l) {
5369     const char *i = l->data;
5370     struct named_id *res;
5371 
5372     res = purple_request_field_list_get_data(f, i);
5373     remote_group_done(pd, res->id, res->name);
5374   }
5375 
5376   remote_group_multi_cleanup(NULL, fields);
5377 }
5378 
5379 
remote_group_multi(struct mwResolveResult * result,struct mwPurplePluginData * pd)5380 static void remote_group_multi(struct mwResolveResult *result,
5381 			       struct mwPurplePluginData *pd) {
5382 
5383   PurpleRequestFields *fields;
5384   PurpleRequestFieldGroup *g;
5385   PurpleRequestField *f;
5386   GList *l;
5387   const char *msgA;
5388   const char *msgB;
5389   char *msg;
5390 
5391   PurpleConnection *gc = pd->gc;
5392 
5393   fields = purple_request_fields_new();
5394 
5395   g = purple_request_field_group_new(NULL);
5396   purple_request_fields_add_group(fields, g);
5397 
5398   f = purple_request_field_list_new("group", _("Possible Matches"));
5399   purple_request_field_list_set_multi_select(f, FALSE);
5400   purple_request_field_set_required(f, TRUE);
5401 
5402   for(l = result->matches; l; l = l->next) {
5403     struct mwResolveMatch *match = l->data;
5404     struct named_id *res = g_new0(struct named_id, 1);
5405 
5406     res->id = g_strdup(match->id);
5407     res->name = g_strdup(match->name);
5408 
5409     purple_request_field_list_add_icon(f, res->name, NULL, res);
5410   }
5411 
5412   purple_request_field_group_add_field(g, f);
5413 
5414   msgA = _("Notes Address Book group results");
5415   msgB = _("The identifier '%s' may possibly refer to any of the following"
5416 	  " Notes Address Book groups. Please select the correct group from"
5417 	  " the list below to add it to your buddy list.");
5418   msg = g_strdup_printf(msgB, result->name);
5419 
5420   purple_request_fields(gc, _("Select Notes Address Book"),
5421 		      msgA, msg, fields,
5422 		      _("Add Group"), G_CALLBACK(remote_group_multi_cb),
5423 		      _("Cancel"), G_CALLBACK(remote_group_multi_cleanup),
5424 			  purple_connection_get_account(gc), result->name, NULL,
5425 		      pd);
5426 
5427   g_free(msg);
5428 }
5429 
5430 
remote_group_resolved(struct mwServiceResolve * srvc,guint32 id,guint32 code,GList * results,gpointer b)5431 static void remote_group_resolved(struct mwServiceResolve *srvc,
5432 				  guint32 id, guint32 code, GList *results,
5433 				  gpointer b) {
5434 
5435   struct mwResolveResult *res = NULL;
5436   struct mwSession *session;
5437   struct mwPurplePluginData *pd;
5438   PurpleConnection *gc;
5439 
5440   session = mwService_getSession(MW_SERVICE(srvc));
5441   g_return_if_fail(session != NULL);
5442 
5443   pd = mwSession_getClientData(session);
5444   g_return_if_fail(pd != NULL);
5445 
5446   gc = pd->gc;
5447   g_return_if_fail(gc != NULL);
5448 
5449   if(!code && results) {
5450     res = results->data;
5451 
5452     if(res->matches) {
5453       remote_group_multi(res, pd);
5454       return;
5455     }
5456   }
5457 
5458   if(res && res->name) {
5459     const char *msgA;
5460     const char *msgB;
5461     char *msg;
5462 
5463     msgA = _("Unable to add group: group not found");
5464 
5465     msgB = _("The identifier '%s' did not match any Notes Address Book"
5466 	    " groups in your Sametime community.");
5467     msg = g_strdup_printf(msgB, res->name);
5468 
5469     purple_notify_error(gc, _("Unable to add group"), msgA, msg);
5470 
5471     g_free(msg);
5472   }
5473 }
5474 
5475 
remote_group_action_cb(PurpleConnection * gc,const char * name)5476 static void remote_group_action_cb(PurpleConnection *gc, const char *name) {
5477   struct mwPurplePluginData *pd;
5478   struct mwServiceResolve *srvc;
5479   GList *query;
5480   enum mwResolveFlag flags;
5481   guint32 req;
5482 
5483   pd = gc->proto_data;
5484   srvc = pd->srvc_resolve;
5485 
5486   query = g_list_prepend(NULL, (char *) name);
5487   flags = mwResolveFlag_FIRST | mwResolveFlag_GROUPS;
5488 
5489   req = mwServiceResolve_resolve(srvc, query, flags, remote_group_resolved,
5490 				 NULL, NULL);
5491   g_list_free(query);
5492 
5493   if(req == SEARCH_ERROR) {
5494     /** @todo display error */
5495   }
5496 }
5497 
5498 
remote_group_action(PurplePluginAction * act)5499 static void remote_group_action(PurplePluginAction *act) {
5500   PurpleConnection *gc;
5501   const char *msgA;
5502   const char *msgB;
5503 
5504   gc = act->context;
5505 
5506   msgA = _("Notes Address Book Group");
5507   msgB = _("Enter the name of a Notes Address Book group in the field below"
5508 	  " to add the group and its members to your buddy list.");
5509 
5510   purple_request_input(gc, _("Add Group"), msgA, msgB, NULL,
5511 		     FALSE, FALSE, NULL,
5512 		     _("Add"), G_CALLBACK(remote_group_action_cb),
5513 		     _("Cancel"), NULL,
5514 			 purple_connection_get_account(gc), NULL, NULL,
5515 		     gc);
5516 }
5517 
5518 
search_notify(struct mwResolveResult * result,PurpleConnection * gc)5519 static void search_notify(struct mwResolveResult *result,
5520 			  PurpleConnection *gc) {
5521   GList *l;
5522   const char *msgA;
5523   const char *msgB;
5524   char *msg1;
5525   char *msg2;
5526 
5527   PurpleNotifySearchResults *sres;
5528   PurpleNotifySearchColumn *scol;
5529 
5530   sres = purple_notify_searchresults_new();
5531 
5532   scol = purple_notify_searchresults_column_new(_("User Name"));
5533   purple_notify_searchresults_column_add(sres, scol);
5534 
5535   scol = purple_notify_searchresults_column_new(_("Sametime ID"));
5536   purple_notify_searchresults_column_add(sres, scol);
5537 
5538   purple_notify_searchresults_button_add(sres, PURPLE_NOTIFY_BUTTON_IM,
5539 				       notify_im);
5540 
5541   purple_notify_searchresults_button_add(sres, PURPLE_NOTIFY_BUTTON_ADD,
5542 				       notify_add);
5543 
5544   for(l = result->matches; l; l = l->next) {
5545     struct mwResolveMatch *match = l->data;
5546     GList *row = NULL;
5547 
5548     if(!match->id || !match->name)
5549       continue;
5550 
5551     row = g_list_append(row, g_strdup(match->name));
5552     row = g_list_append(row, g_strdup(match->id));
5553     purple_notify_searchresults_row_add(sres, row);
5554   }
5555 
5556   msgA = _("Search results for '%s'");
5557   msgB = _("The identifier '%s' may possibly refer to any of the following"
5558 	   " users. You may add these users to your buddy list or send them"
5559 	   " messages with the action buttons below.");
5560 
5561   msg1 = g_strdup_printf(msgA, result->name);
5562   msg2 = g_strdup_printf(msgB, result->name);
5563 
5564   purple_notify_searchresults(gc, _("Search Results"),
5565 			    msg1, msg2, sres, notify_close, NULL);
5566 
5567   g_free(msg1);
5568   g_free(msg2);
5569 }
5570 
5571 
search_resolved(struct mwServiceResolve * srvc,guint32 id,guint32 code,GList * results,gpointer b)5572 static void search_resolved(struct mwServiceResolve *srvc,
5573 			    guint32 id, guint32 code, GList *results,
5574 			    gpointer b) {
5575 
5576   PurpleConnection *gc = b;
5577   struct mwResolveResult *res = NULL;
5578 
5579   if(results) res = results->data;
5580 
5581   if(!code && res && res->matches) {
5582     search_notify(res, gc);
5583 
5584   } else {
5585     const char *msgA;
5586     const char *msgB;
5587     char *msg;
5588 
5589     msgA = _("No matches");
5590     msgB = _("The identifier '%s' did not match any users in your"
5591 	     " Sametime community.");
5592     msg = g_strdup_printf(msgB, (res && res->name) ? NSTR(res->name) : "");
5593 
5594     purple_notify_error(gc, _("No Matches"), msgA, msg);
5595 
5596     g_free(msg);
5597   }
5598 }
5599 
5600 
search_action_cb(PurpleConnection * gc,const char * name)5601 static void search_action_cb(PurpleConnection *gc, const char *name) {
5602   struct mwPurplePluginData *pd;
5603   struct mwServiceResolve *srvc;
5604   GList *query;
5605   enum mwResolveFlag flags;
5606   guint32 req;
5607 
5608   pd = gc->proto_data;
5609   srvc = pd->srvc_resolve;
5610 
5611   query = g_list_prepend(NULL, (char *) name);
5612   flags = mwResolveFlag_FIRST | mwResolveFlag_USERS;
5613 
5614   req = mwServiceResolve_resolve(srvc, query, flags, search_resolved,
5615 				 gc, NULL);
5616   g_list_free(query);
5617 
5618   if(req == SEARCH_ERROR) {
5619     /** @todo display error */
5620   }
5621 }
5622 
5623 
search_action(PurplePluginAction * act)5624 static void search_action(PurplePluginAction *act) {
5625   PurpleConnection *gc;
5626   const char *msgA;
5627   const char *msgB;
5628 
5629   gc = act->context;
5630 
5631   msgA = _("Search for a user");
5632   msgB = _("Enter a name or partial ID in the field below to search"
5633 	   " for matching users in your Sametime community.");
5634 
5635   purple_request_input(gc, _("User Search"), msgA, msgB, NULL,
5636 		     FALSE, FALSE, NULL,
5637 		     _("Search"), G_CALLBACK(search_action_cb),
5638 		     _("Cancel"), NULL,
5639 			 purple_connection_get_account(gc), NULL, NULL,
5640 			 gc);
5641 }
5642 
5643 
mw_plugin_actions(PurplePlugin * plugin,gpointer context)5644 static GList *mw_plugin_actions(PurplePlugin *plugin, gpointer context) {
5645   PurplePluginAction *act;
5646   GList *l = NULL;
5647 
5648   act = purple_plugin_action_new(_("Import Sametime List..."),
5649 			       st_import_action);
5650   l = g_list_append(l, act);
5651 
5652   act = purple_plugin_action_new(_("Export Sametime List..."),
5653 			       st_export_action);
5654   l = g_list_append(l, act);
5655 
5656   act = purple_plugin_action_new(_("Add Notes Address Book Group..."),
5657 			       remote_group_action);
5658   l = g_list_append(l, act);
5659 
5660   act = purple_plugin_action_new(_("User Search..."),
5661 			       search_action);
5662   l = g_list_append(l, act);
5663 
5664   return l;
5665 }
5666 
5667 
mw_plugin_load(PurplePlugin * plugin)5668 static gboolean mw_plugin_load(PurplePlugin *plugin) {
5669   return TRUE;
5670 }
5671 
5672 
mw_plugin_unload(PurplePlugin * plugin)5673 static gboolean mw_plugin_unload(PurplePlugin *plugin) {
5674   return TRUE;
5675 }
5676 
5677 
mw_plugin_destroy(PurplePlugin * plugin)5678 static void mw_plugin_destroy(PurplePlugin *plugin) {
5679   g_log_remove_handler(G_LOG_DOMAIN, log_handler[0]);
5680   g_log_remove_handler("meanwhile", log_handler[1]);
5681 }
5682 
5683 static PurplePluginInfo mw_plugin_info =
5684 {
5685 	PURPLE_PLUGIN_MAGIC,
5686 	PURPLE_MAJOR_VERSION,
5687 	PURPLE_MINOR_VERSION,
5688 	PURPLE_PLUGIN_PROTOCOL,                           /**< type           */
5689 	NULL,                                             /**< ui_requirement */
5690 	0,                                                /**< flags          */
5691 	NULL,                                             /**< dependencies   */
5692 	PURPLE_PRIORITY_DEFAULT,                          /**< priority       */
5693 
5694 	PLUGIN_ID,                                        /**< id             */
5695 	PLUGIN_NAME,                                      /**< name           */
5696 	DISPLAY_VERSION,                                  /**< version        */
5697 	PLUGIN_SUMMARY,                                   /**< summary        */
5698 	PLUGIN_DESC,                                      /**<  description    */
5699 	PLUGIN_AUTHOR,                                    /**< author         */
5700 	PLUGIN_HOMEPAGE,                                  /**< homepage       */
5701 
5702 	mw_plugin_load,                                   /**< load           */
5703 	mw_plugin_unload,                                 /**< unload         */
5704 	mw_plugin_destroy,                                /**< destroy        */
5705 
5706 	NULL,                                             /**< ui_info        */
5707 	&mw_prpl_info,                                    /**< extra_info     */
5708 	&mw_plugin_ui_info,                               /**< prefs_info     */
5709 	mw_plugin_actions,
5710 
5711 	/* padding */
5712 	NULL,
5713 	NULL,
5714 	NULL,
5715 	NULL
5716 };
5717 
5718 
mw_log_handler(const gchar * domain,GLogLevelFlags flags,const gchar * msg,gpointer data)5719 static void mw_log_handler(const gchar *domain, GLogLevelFlags flags,
5720 			   const gchar *msg, gpointer data) {
5721 
5722   if(! (msg && *msg)) return;
5723 
5724   /* handle g_log requests via purple's built-in debug logging */
5725   if(flags & G_LOG_LEVEL_ERROR) {
5726     purple_debug_error(domain, "%s\n", msg);
5727 
5728   } else if(flags & G_LOG_LEVEL_WARNING) {
5729     purple_debug_warning(domain, "%s\n", msg);
5730 
5731   } else {
5732     purple_debug_info(domain, "%s\n", msg);
5733   }
5734 }
5735 
5736 
mw_plugin_init(PurplePlugin * plugin)5737 static void mw_plugin_init(PurplePlugin *plugin) {
5738   PurpleAccountUserSplit *split;
5739   PurpleAccountOption *opt;
5740   GList *l = NULL;
5741 
5742   GLogLevelFlags logflags =
5743     G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION;
5744 
5745   /* set up the preferences */
5746   purple_prefs_add_none(MW_PRPL_OPT_BASE);
5747   purple_prefs_add_int(MW_PRPL_OPT_BLIST_ACTION, BLIST_CHOICE_DEFAULT);
5748 
5749   /* set up account ID as user:server */
5750   split = purple_account_user_split_new(_("Server"),
5751                                         MW_PLUGIN_DEFAULT_HOST, ':');
5752   mw_prpl_info.user_splits = g_list_append(mw_prpl_info.user_splits, split);
5753 
5754   /* remove dead preferences */
5755   purple_prefs_remove(MW_PRPL_OPT_PSYCHIC);
5756   purple_prefs_remove(MW_PRPL_OPT_SAVE_DYNAMIC);
5757 
5758   /* port to connect to */
5759   opt = purple_account_option_int_new(_("Port"), MW_KEY_PORT,
5760 				    MW_PLUGIN_DEFAULT_PORT);
5761   l = g_list_append(l, opt);
5762 
5763   { /* copy the old force login setting from prefs if it's
5764        there. Don't delete the preference, since there may be more
5765        than one account that wants to check for it. */
5766     gboolean b = FALSE;
5767     const char *label = _("Force login (ignore server redirects)");
5768 
5769     if(purple_prefs_exists(MW_PRPL_OPT_FORCE_LOGIN))
5770       b = purple_prefs_get_bool(MW_PRPL_OPT_FORCE_LOGIN);
5771 
5772     opt = purple_account_option_bool_new(label, MW_KEY_FORCE, b);
5773     l = g_list_append(l, opt);
5774   }
5775 
5776   /* pretend to be Sametime Connect */
5777   opt = purple_account_option_bool_new(_("Hide client identity"),
5778 				     MW_KEY_FAKE_IT, FALSE);
5779   l = g_list_append(l, opt);
5780 
5781   mw_prpl_info.protocol_options = l;
5782   l = NULL;
5783 
5784   /* forward all our g_log messages to purple. Generally all the logging
5785      calls are using purple_log directly, but the g_return macros will
5786      get caught here */
5787   log_handler[0] = g_log_set_handler(G_LOG_DOMAIN, logflags,
5788 				     mw_log_handler, NULL);
5789 
5790   /* redirect meanwhile's logging to purple's */
5791   log_handler[1] = g_log_set_handler("meanwhile", logflags,
5792 				     mw_log_handler, NULL);
5793 }
5794 
5795 
5796 PURPLE_INIT_PLUGIN(sametime, mw_plugin_init, mw_plugin_info);
5797 /* The End. */
5798 
5799