source: protocols/purple/purple.c @ d6e2aa8

Last change on this file since d6e2aa8 was e4c3041, checked in by dequis <dx@…>, at 2015-10-25T05:16:52Z

purple: Fix certificate cache dir after changing user dir

This fixes the issue with getting asked to accept certificates that are
perfectly valid, every time.

The directory is normally created by x509_tls_peers_init(), a few calls
below purple_core_init(), which is at module initialization time, way
before we have an irc username to fix the user directory. So it creates
the wrong directory first, and now we have to fix it manually.

And apparently not being able to save cached certificates somehow means
they aren't trusted. For some reason.

< krisfremen> "for some reason"
< dx> idklol

  • Property mode set to 100644
File size: 42.2 KB
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  libpurple module - Main file                                             *
5*                                                                           *
6*  Copyright 2009-2012 Wilmer van der Gaast <wilmer@gaast.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        *
11*  (at your option) any later version.                                      *
12*                                                                           *
13*  This program is distributed in the hope that it will be useful,          *
14*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
15*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
16*  GNU General Public License for more details.                             *
17*                                                                           *
18*  You should have received a copy of the GNU General Public License along  *
19*  with this program; if not, write to the Free Software Foundation, Inc.,  *
20*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
21*                                                                           *
22\***************************************************************************/
23
24#include "bitlbee.h"
25#include "bpurple.h"
26#include "help.h"
27
28#include <stdarg.h>
29
30#include <glib.h>
31#include <purple.h>
32
33GSList *purple_connections;
34
35/* This makes me VERY sad... :-( But some libpurple callbacks come in without
36   any context so this is the only way to get that. Don't want to support
37   libpurple in daemon mode anyway. */
38static bee_t *local_bee;
39
40static char *set_eval_display_name(set_t *set, char *value);
41
42void purple_request_input_callback(guint id, struct im_connection *ic,
43                                   const char *message, const char *who);
44
45/* purple_request_input specific stuff */
46typedef void (*ri_callback_t)(gpointer, const gchar *);
47
48struct request_input_data {
49        ri_callback_t data_callback;
50        void *user_data;
51        struct im_connection *ic;
52        char *buddy;
53        guint id;
54};
55
56struct im_connection *purple_ic_by_pa(PurpleAccount *pa)
57{
58        GSList *i;
59        struct purple_data *pd;
60
61        for (i = purple_connections; i; i = i->next) {
62                pd = ((struct im_connection *) i->data)->proto_data;
63                if (pd->account == pa) {
64                        return i->data;
65                }
66        }
67
68        return NULL;
69}
70
71static struct im_connection *purple_ic_by_gc(PurpleConnection *gc)
72{
73        return purple_ic_by_pa(purple_connection_get_account(gc));
74}
75
76static gboolean purple_menu_cmp(const char *a, const char *b)
77{
78        while (*a && *b) {
79                while (*a == '_') {
80                        a++;
81                }
82                while (*b == '_') {
83                        b++;
84                }
85                if (g_ascii_tolower(*a) != g_ascii_tolower(*b)) {
86                        return FALSE;
87                }
88
89                a++;
90                b++;
91        }
92
93        return (*a == '\0' && *b == '\0');
94}
95
96static void purple_init(account_t *acc)
97{
98        PurplePlugin *prpl = purple_plugins_find_with_id((char *) acc->prpl->data);
99        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
100        PurpleAccount *pa;
101        GList *i, *st;
102        set_t *s;
103        char help_title[64];
104        GString *help;
105        static gboolean dir_fixed = FALSE;
106
107        /* Layer violation coming up: Making an exception for libpurple here.
108           Dig in the IRC state a bit to get a username. Ideally we should
109           check if s/he identified but this info doesn't seem *that* important.
110           It's just that fecking libpurple can't *not* store this shit.
111
112           Remember that libpurple is not really meant to be used on public
113           servers anyway! */
114        if (!dir_fixed) {
115                PurpleCertificatePool *pool;
116                irc_t *irc = acc->bee->ui_data;
117                char *dir;
118
119                dir = g_strdup_printf("%s/purple/%s", global.conf->configdir, irc->user->nick);
120                purple_util_set_user_dir(dir);
121                g_free(dir);
122
123                purple_blist_load();
124                purple_prefs_load();
125
126                if (proxytype == PROXY_SOCKS4A) {
127                        /* do this here after loading prefs. yes, i know, it sucks */
128                        purple_prefs_set_bool("/purple/proxy/socks4_remotedns", TRUE);
129                }
130
131                /* re-create the certificate cache directory */
132                pool = purple_certificate_find_pool("x509", "tls_peers");
133                dir = purple_certificate_pool_mkpath(pool, NULL);
134                purple_build_dir(dir, 0700);
135
136                dir_fixed = TRUE;
137        }
138
139        help = g_string_new("");
140        g_string_printf(help, "BitlBee libpurple module %s (%s).\n\nSupported settings:",
141                        (char *) acc->prpl->name, prpl->info->name);
142
143        if (pi->user_splits) {
144                GList *l;
145                g_string_append_printf(help, "\n* username: Username");
146                for (l = pi->user_splits; l; l = l->next) {
147                        g_string_append_printf(help, "%c%s",
148                                               purple_account_user_split_get_separator(l->data),
149                                               purple_account_user_split_get_text(l->data));
150                }
151        }
152
153        /* Convert all protocol_options into per-account setting variables. */
154        for (i = pi->protocol_options; i; i = i->next) {
155                PurpleAccountOption *o = i->data;
156                const char *name;
157                char *def = NULL;
158                set_eval eval = NULL;
159                void *eval_data = NULL;
160                GList *io = NULL;
161                GSList *opts = NULL;
162
163                name = purple_account_option_get_setting(o);
164
165                switch (purple_account_option_get_type(o)) {
166                case PURPLE_PREF_STRING:
167                        def = g_strdup(purple_account_option_get_default_string(o));
168
169                        g_string_append_printf(help, "\n* %s (%s), %s, default: %s",
170                                               name, purple_account_option_get_text(o),
171                                               "string", def);
172
173                        break;
174
175                case PURPLE_PREF_INT:
176                        def = g_strdup_printf("%d", purple_account_option_get_default_int(o));
177                        eval = set_eval_int;
178
179                        g_string_append_printf(help, "\n* %s (%s), %s, default: %s",
180                                               name, purple_account_option_get_text(o),
181                                               "integer", def);
182
183                        break;
184
185                case PURPLE_PREF_BOOLEAN:
186                        if (purple_account_option_get_default_bool(o)) {
187                                def = g_strdup("true");
188                        } else {
189                                def = g_strdup("false");
190                        }
191                        eval = set_eval_bool;
192
193                        g_string_append_printf(help, "\n* %s (%s), %s, default: %s",
194                                               name, purple_account_option_get_text(o),
195                                               "boolean", def);
196
197                        break;
198
199                case PURPLE_PREF_STRING_LIST:
200                        def = g_strdup(purple_account_option_get_default_list_value(o));
201
202                        g_string_append_printf(help, "\n* %s (%s), %s, default: %s",
203                                               name, purple_account_option_get_text(o),
204                                               "list", def);
205                        g_string_append(help, "\n  Possible values: ");
206
207                        for (io = purple_account_option_get_list(o); io; io = io->next) {
208                                PurpleKeyValuePair *kv = io->data;
209                                opts = g_slist_append(opts, kv->value);
210                                /* TODO: kv->value is not a char*, WTF? */
211                                if (strcmp(kv->value, kv->key) != 0) {
212                                        g_string_append_printf(help, "%s (%s), ", (char *) kv->value, kv->key);
213                                } else {
214                                        g_string_append_printf(help, "%s, ", (char *) kv->value);
215                                }
216                        }
217                        g_string_truncate(help, help->len - 2);
218                        eval = set_eval_list;
219                        eval_data = opts;
220
221                        break;
222
223                default:
224                        /** No way to talk to the user right now, invent one when
225                        this becomes important.
226                        irc_rootmsg( acc->irc, "Setting with unknown type: %s (%d) Expect stuff to break..\n",
227                                     name, purple_account_option_get_type( o ) );
228                        */
229                        g_string_append_printf(help, "\n* [%s] UNSUPPORTED (type %d)",
230                                               name, purple_account_option_get_type(o));
231                        name = NULL;
232                }
233
234                if (name != NULL) {
235                        s = set_add(&acc->set, name, def, eval, acc);
236                        s->flags |= ACC_SET_OFFLINE_ONLY;
237                        s->eval_data = eval_data;
238                        g_free(def);
239                }
240        }
241
242        g_snprintf(help_title, sizeof(help_title), "purple %s", (char *) acc->prpl->name);
243        help_add_mem(&global.help, help_title, help->str);
244        g_string_free(help, TRUE);
245
246        s = set_add(&acc->set, "display_name", NULL, set_eval_display_name, acc);
247        s->flags |= ACC_SET_ONLINE_ONLY;
248
249        if (pi->options & OPT_PROTO_MAIL_CHECK) {
250                s = set_add(&acc->set, "mail_notifications", "false", set_eval_bool, acc);
251                s->flags |= ACC_SET_OFFLINE_ONLY;
252
253                s = set_add(&acc->set, "mail_notifications_handle", NULL, NULL, acc);
254                s->flags |= ACC_SET_OFFLINE_ONLY | SET_NULL_OK;
255        }
256
257        if (strcmp(prpl->info->name, "Gadu-Gadu") == 0) {
258                s = set_add(&acc->set, "gg_sync_contacts", "true", set_eval_bool, acc);
259        }
260
261        /* Go through all away states to figure out if away/status messages
262           are possible. */
263        pa = purple_account_new(acc->user, (char *) acc->prpl->data);
264        for (st = purple_account_get_status_types(pa); st; st = st->next) {
265                PurpleStatusPrimitive prim = purple_status_type_get_primitive(st->data);
266
267                if (prim == PURPLE_STATUS_AVAILABLE) {
268                        if (purple_status_type_get_attr(st->data, "message")) {
269                                acc->flags |= ACC_FLAG_STATUS_MESSAGE;
270                        }
271                } else if (prim != PURPLE_STATUS_OFFLINE) {
272                        if (purple_status_type_get_attr(st->data, "message")) {
273                                acc->flags |= ACC_FLAG_AWAY_MESSAGE;
274                        }
275                }
276        }
277        purple_accounts_remove(pa);
278}
279
280static void purple_sync_settings(account_t *acc, PurpleAccount *pa)
281{
282        PurplePlugin *prpl = purple_plugins_find_with_id(pa->protocol_id);
283        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
284        GList *i;
285
286        for (i = pi->protocol_options; i; i = i->next) {
287                PurpleAccountOption *o = i->data;
288                const char *name;
289                set_t *s;
290
291                name = purple_account_option_get_setting(o);
292                s = set_find(&acc->set, name);
293                if (s->value == NULL) {
294                        continue;
295                }
296
297                switch (purple_account_option_get_type(o)) {
298                case PURPLE_PREF_STRING:
299                case PURPLE_PREF_STRING_LIST:
300                        purple_account_set_string(pa, name, set_getstr(&acc->set, name));
301                        break;
302
303                case PURPLE_PREF_INT:
304                        purple_account_set_int(pa, name, set_getint(&acc->set, name));
305                        break;
306
307                case PURPLE_PREF_BOOLEAN:
308                        purple_account_set_bool(pa, name, set_getbool(&acc->set, name));
309                        break;
310
311                default:
312                        break;
313                }
314        }
315
316        if (pi->options & OPT_PROTO_MAIL_CHECK) {
317                purple_account_set_check_mail(pa, set_getbool(&acc->set, "mail_notifications"));
318        }
319}
320
321static void purple_login(account_t *acc)
322{
323        struct im_connection *ic = imcb_new(acc);
324        struct purple_data *pd;
325
326        if ((local_bee != NULL && local_bee != acc->bee) ||
327            (global.conf->runmode == RUNMODE_DAEMON && !getenv("BITLBEE_DEBUG"))) {
328                imcb_error(ic,  "Daemon mode detected. Do *not* try to use libpurple in daemon mode! "
329                           "Please use inetd or ForkDaemon mode instead.");
330                imc_logout(ic, FALSE);
331                return;
332        }
333        local_bee = acc->bee;
334
335        /* For now this is needed in the _connected() handlers if using
336           GLib event handling, to make sure we're not handling events
337           on dead connections. */
338        purple_connections = g_slist_prepend(purple_connections, ic);
339
340        ic->proto_data = pd = g_new0(struct purple_data, 1);
341        pd->account = purple_account_new(acc->user, (char *) acc->prpl->data);
342        pd->input_requests = g_hash_table_new_full(g_direct_hash, g_direct_equal,
343                                                   NULL, g_free);
344        pd->next_request_id = 0;
345        purple_account_set_password(pd->account, acc->pass);
346        purple_sync_settings(acc, pd->account);
347
348        purple_account_set_enabled(pd->account, "BitlBee", TRUE);
349
350        if (set_getbool(&acc->set, "mail_notifications") && set_getstr(&acc->set, "mail_notifications_handle")) {
351                imcb_add_buddy(ic, set_getstr(&acc->set, "mail_notifications_handle"), NULL);
352        }
353}
354
355static void purple_logout(struct im_connection *ic)
356{
357        struct purple_data *pd = ic->proto_data;
358
359        if (!pd) {
360                return;
361        }
362
363        purple_account_set_enabled(pd->account, "BitlBee", FALSE);
364        purple_connections = g_slist_remove(purple_connections, ic);
365        purple_accounts_remove(pd->account);
366        g_hash_table_destroy(pd->input_requests);
367        g_free(pd);
368}
369
370static int purple_buddy_msg(struct im_connection *ic, char *who, char *message, int flags)
371{
372        PurpleConversation *conv;
373        struct purple_data *pd = ic->proto_data;
374
375        if (!strncmp(who, PURPLE_REQUEST_HANDLE, sizeof(PURPLE_REQUEST_HANDLE) - 1)) {
376                guint request_id = atoi(who + sizeof(PURPLE_REQUEST_HANDLE));
377                purple_request_input_callback(request_id, ic, message, who);
378                return 1;
379        }
380
381        if ((conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
382                                                          who, pd->account)) == NULL) {
383                conv = purple_conversation_new(PURPLE_CONV_TYPE_IM,
384                                               pd->account, who);
385        }
386
387        purple_conv_im_send(purple_conversation_get_im_data(conv), message);
388
389        return 1;
390}
391
392static GList *purple_away_states(struct im_connection *ic)
393{
394        struct purple_data *pd = ic->proto_data;
395        GList *st, *ret = NULL;
396
397        for (st = purple_account_get_status_types(pd->account); st; st = st->next) {
398                PurpleStatusPrimitive prim = purple_status_type_get_primitive(st->data);
399                if (prim != PURPLE_STATUS_AVAILABLE && prim != PURPLE_STATUS_OFFLINE) {
400                        ret = g_list_append(ret, (void *) purple_status_type_get_name(st->data));
401                }
402        }
403
404        return ret;
405}
406
407static void purple_set_away(struct im_connection *ic, char *state_txt, char *message)
408{
409        struct purple_data *pd = ic->proto_data;
410        GList *status_types = purple_account_get_status_types(pd->account), *st;
411        PurpleStatusType *pst = NULL;
412        GList *args = NULL;
413
414        for (st = status_types; st; st = st->next) {
415                pst = st->data;
416
417                if (state_txt == NULL &&
418                    purple_status_type_get_primitive(pst) == PURPLE_STATUS_AVAILABLE) {
419                        break;
420                }
421
422                if (state_txt != NULL &&
423                    g_strcasecmp(state_txt, purple_status_type_get_name(pst)) == 0) {
424                        break;
425                }
426        }
427
428        if (message && purple_status_type_get_attr(pst, "message")) {
429                args = g_list_append(args, "message");
430                args = g_list_append(args, message);
431        }
432
433        purple_account_set_status_list(pd->account,
434                                       st ? purple_status_type_get_id(pst) : "away",
435                                       TRUE, args);
436
437        g_list_free(args);
438}
439
440static char *set_eval_display_name(set_t *set, char *value)
441{
442        account_t *acc = set->data;
443        struct im_connection *ic = acc->ic;
444
445        if (ic) {
446                imcb_log(ic, "Changing display_name not currently supported with libpurple!");
447        }
448
449        return NULL;
450}
451
452/* Bad bad gadu-gadu, not saving buddy list by itself */
453static void purple_gg_buddylist_export(PurpleConnection *gc)
454{
455        struct im_connection *ic = purple_ic_by_gc(gc);
456
457        if (set_getstr(&ic->acc->set, "gg_sync_contacts")) {
458                GList *actions = gc->prpl->info->actions(gc->prpl, gc);
459                GList *p;
460                for (p = g_list_first(actions); p; p = p->next) {
461                        if (((PurplePluginAction *) p->data) &&
462                            purple_menu_cmp(((PurplePluginAction *) p->data)->label,
463                                            "Upload buddylist to Server") == 0) {
464                                PurplePluginAction action;
465                                action.plugin = gc->prpl;
466                                action.context = gc;
467                                action.user_data = NULL;
468                                ((PurplePluginAction *) p->data)->callback(&action);
469                                break;
470                        }
471                }
472                g_list_free(actions);
473        }
474}
475
476static void purple_gg_buddylist_import(PurpleConnection *gc)
477{
478        struct im_connection *ic = purple_ic_by_gc(gc);
479
480        if (set_getstr(&ic->acc->set, "gg_sync_contacts")) {
481                GList *actions = gc->prpl->info->actions(gc->prpl, gc);
482                GList *p;
483                for (p = g_list_first(actions); p; p = p->next) {
484                        if (((PurplePluginAction *) p->data) &&
485                            purple_menu_cmp(((PurplePluginAction *) p->data)->label,
486                                            "Download buddylist from Server") == 0) {
487                                PurplePluginAction action;
488                                action.plugin = gc->prpl;
489                                action.context = gc;
490                                action.user_data = NULL;
491                                ((PurplePluginAction *) p->data)->callback(&action);
492                                break;
493                        }
494                }
495                g_list_free(actions);
496        }
497}
498
499static void purple_add_buddy(struct im_connection *ic, char *who, char *group)
500{
501        PurpleBuddy *pb;
502        PurpleGroup *pg = NULL;
503        struct purple_data *pd = ic->proto_data;
504
505        if (group && !(pg = purple_find_group(group))) {
506                pg = purple_group_new(group);
507                purple_blist_add_group(pg, NULL);
508        }
509
510        pb = purple_buddy_new(pd->account, who, NULL);
511        purple_blist_add_buddy(pb, NULL, pg, NULL);
512        purple_account_add_buddy(pd->account, pb);
513
514        purple_gg_buddylist_export(pd->account->gc);
515}
516
517static void purple_remove_buddy(struct im_connection *ic, char *who, char *group)
518{
519        PurpleBuddy *pb;
520        struct purple_data *pd = ic->proto_data;
521
522        pb = purple_find_buddy(pd->account, who);
523        if (pb != NULL) {
524                PurpleGroup *group;
525
526                group = purple_buddy_get_group(pb);
527                purple_account_remove_buddy(pd->account, pb, group);
528
529                purple_blist_remove_buddy(pb);
530        }
531
532        purple_gg_buddylist_export(pd->account->gc);
533}
534
535static void purple_add_permit(struct im_connection *ic, char *who)
536{
537        struct purple_data *pd = ic->proto_data;
538
539        purple_privacy_permit_add(pd->account, who, FALSE);
540}
541
542static void purple_add_deny(struct im_connection *ic, char *who)
543{
544        struct purple_data *pd = ic->proto_data;
545
546        purple_privacy_deny_add(pd->account, who, FALSE);
547}
548
549static void purple_rem_permit(struct im_connection *ic, char *who)
550{
551        struct purple_data *pd = ic->proto_data;
552
553        purple_privacy_permit_remove(pd->account, who, FALSE);
554}
555
556static void purple_rem_deny(struct im_connection *ic, char *who)
557{
558        struct purple_data *pd = ic->proto_data;
559
560        purple_privacy_deny_remove(pd->account, who, FALSE);
561}
562
563static void purple_get_info(struct im_connection *ic, char *who)
564{
565        struct purple_data *pd = ic->proto_data;
566
567        serv_get_info(purple_account_get_connection(pd->account), who);
568}
569
570static void purple_keepalive(struct im_connection *ic)
571{
572}
573
574static int purple_send_typing(struct im_connection *ic, char *who, int flags)
575{
576        PurpleTypingState state = PURPLE_NOT_TYPING;
577        struct purple_data *pd = ic->proto_data;
578
579        if (flags & OPT_TYPING) {
580                state = PURPLE_TYPING;
581        } else if (flags & OPT_THINKING) {
582                state = PURPLE_TYPED;
583        }
584
585        serv_send_typing(purple_account_get_connection(pd->account), who, state);
586
587        return 1;
588}
589
590static void purple_chat_msg(struct groupchat *gc, char *message, int flags)
591{
592        PurpleConversation *pc = gc->data;
593
594        purple_conv_chat_send(purple_conversation_get_chat_data(pc), message);
595}
596
597struct groupchat *purple_chat_with(struct im_connection *ic, char *who)
598{
599        /* No, "of course" this won't work this way. Or in fact, it almost
600           does, but it only lets you send msgs to it, you won't receive
601           any. Instead, we have to click the virtual menu item.
602        PurpleAccount *pa = ic->proto_data;
603        PurpleConversation *pc;
604        PurpleConvChat *pcc;
605        struct groupchat *gc;
606
607        gc = imcb_chat_new( ic, "BitlBee-libpurple groupchat" );
608        gc->data = pc = purple_conversation_new( PURPLE_CONV_TYPE_CHAT, pa, "BitlBee-libpurple groupchat" );
609        pc->ui_data = gc;
610
611        pcc = PURPLE_CONV_CHAT( pc );
612        purple_conv_chat_add_user( pcc, ic->acc->user, "", 0, TRUE );
613        purple_conv_chat_invite_user( pcc, who, "Please join my chat", FALSE );
614        //purple_conv_chat_add_user( pcc, who, "", 0, TRUE );
615        */
616
617        /* There went my nice afternoon. :-( */
618
619        struct purple_data *pd = ic->proto_data;
620        PurplePlugin *prpl = purple_plugins_find_with_id(pd->account->protocol_id);
621        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
622        PurpleBuddy *pb = purple_find_buddy(pd->account, who);
623        PurpleMenuAction *mi;
624        GList *menu;
625
626        void (*callback)(PurpleBlistNode *, gpointer); /* FFFFFFFFFFFFFUUUUUUUUUUUUUU */
627
628        if (!pb || !pi || !pi->blist_node_menu) {
629                return NULL;
630        }
631
632        menu = pi->blist_node_menu(&pb->node);
633        while (menu) {
634                mi = menu->data;
635                if (purple_menu_cmp(mi->label, "initiate chat") ||
636                    purple_menu_cmp(mi->label, "initiate conference")) {
637                        break;
638                }
639                menu = menu->next;
640        }
641
642        if (menu == NULL) {
643                return NULL;
644        }
645
646        /* Call the fucker. */
647        callback = (void *) mi->callback;
648        callback(&pb->node, menu->data);
649
650        return NULL;
651}
652
653void purple_chat_invite(struct groupchat *gc, char *who, char *message)
654{
655        PurpleConversation *pc = gc->data;
656        PurpleConvChat *pcc = PURPLE_CONV_CHAT(pc);
657        struct purple_data *pd = gc->ic->proto_data;
658
659        serv_chat_invite(purple_account_get_connection(pd->account),
660                         purple_conv_chat_get_id(pcc),
661                         message && *message ? message : "Please join my chat",
662                         who);
663}
664
665void purple_chat_kick(struct groupchat *gc, char *who, const char *message)
666{
667        PurpleConversation *pc = gc->data;
668        char *str = g_strdup_printf("kick %s %s", who, message);
669
670        purple_conversation_do_command(pc, str, NULL, NULL);
671        g_free(str);
672}
673
674void purple_chat_leave(struct groupchat *gc)
675{
676        PurpleConversation *pc = gc->data;
677
678        purple_conversation_destroy(pc);
679}
680
681struct groupchat *purple_chat_join(struct im_connection *ic, const char *room, const char *nick, const char *password,
682                                   set_t **sets)
683{
684        struct purple_data *pd = ic->proto_data;
685        PurplePlugin *prpl = purple_plugins_find_with_id(pd->account->protocol_id);
686        PurplePluginProtocolInfo *pi = prpl->info->extra_info;
687        GHashTable *chat_hash;
688        PurpleConversation *conv;
689        GList *info, *l;
690
691        if (!pi->chat_info || !pi->chat_info_defaults ||
692            !(info = pi->chat_info(purple_account_get_connection(pd->account)))) {
693                imcb_error(ic, "Joining chatrooms not supported by this protocol");
694                return NULL;
695        }
696
697        if ((conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
698                                                          room, pd->account))) {
699                purple_conversation_destroy(conv);
700        }
701
702        chat_hash = pi->chat_info_defaults(
703                purple_account_get_connection(pd->account), room
704        );
705
706        for (l = info; l; l = l->next) {
707                struct proto_chat_entry *pce = l->data;
708
709                if (strcmp(pce->identifier, "handle") == 0) {
710                        g_hash_table_replace(chat_hash, "handle", g_strdup(nick));
711                } else if (strcmp(pce->identifier, "password") == 0) {
712                        g_hash_table_replace(chat_hash, "password", g_strdup(password));
713                } else if (strcmp(pce->identifier, "passwd") == 0) {
714                        g_hash_table_replace(chat_hash, "passwd", g_strdup(password));
715                }
716        }
717
718        serv_join_chat(purple_account_get_connection(pd->account), chat_hash);
719
720        return NULL;
721}
722
723void purple_transfer_request(struct im_connection *ic, file_transfer_t *ft, char *handle);
724
725static void purple_ui_init();
726
727GHashTable *prplcb_ui_info()
728{
729        static GHashTable *ret;
730
731        if (ret == NULL) {
732                ret = g_hash_table_new(g_str_hash, g_str_equal);
733                g_hash_table_insert(ret, "name", "BitlBee");
734                g_hash_table_insert(ret, "version", BITLBEE_VERSION);
735        }
736
737        return ret;
738}
739
740static PurpleCoreUiOps bee_core_uiops =
741{
742        NULL,
743        NULL,
744        purple_ui_init,
745        NULL,
746        prplcb_ui_info,
747};
748
749static void prplcb_conn_progress(PurpleConnection *gc, const char *text, size_t step, size_t step_count)
750{
751        struct im_connection *ic = purple_ic_by_gc(gc);
752
753        imcb_log(ic, "%s", text);
754}
755
756static void prplcb_conn_connected(PurpleConnection *gc)
757{
758        struct im_connection *ic = purple_ic_by_gc(gc);
759        const char *dn;
760        set_t *s;
761
762        imcb_connected(ic);
763
764        if ((dn = purple_connection_get_display_name(gc)) &&
765            (s = set_find(&ic->acc->set, "display_name"))) {
766                g_free(s->value);
767                s->value = g_strdup(dn);
768        }
769
770        // user list needs to be requested for Gadu-Gadu
771        purple_gg_buddylist_import(gc);
772
773        if (gc->flags & PURPLE_CONNECTION_HTML) {
774                ic->flags |= OPT_DOES_HTML;
775        }
776}
777
778static void prplcb_conn_disconnected(PurpleConnection *gc)
779{
780        struct im_connection *ic = purple_ic_by_gc(gc);
781
782        if (ic != NULL) {
783                imc_logout(ic, !gc->wants_to_die);
784        }
785}
786
787static void prplcb_conn_notice(PurpleConnection *gc, const char *text)
788{
789        struct im_connection *ic = purple_ic_by_gc(gc);
790
791        if (ic != NULL) {
792                imcb_log(ic, "%s", text);
793        }
794}
795
796static void prplcb_conn_report_disconnect_reason(PurpleConnection *gc, PurpleConnectionError reason, const char *text)
797{
798        struct im_connection *ic = purple_ic_by_gc(gc);
799
800        /* PURPLE_CONNECTION_ERROR_NAME_IN_USE means concurrent login,
801           should probably handle that. */
802        if (ic != NULL) {
803                imcb_error(ic, "%s", text);
804        }
805}
806
807static PurpleConnectionUiOps bee_conn_uiops =
808{
809        prplcb_conn_progress,
810        prplcb_conn_connected,
811        prplcb_conn_disconnected,
812        prplcb_conn_notice,
813        NULL,
814        NULL,
815        NULL,
816        prplcb_conn_report_disconnect_reason,
817};
818
819static void prplcb_blist_update(PurpleBuddyList *list, PurpleBlistNode *node)
820{
821        if (node->type == PURPLE_BLIST_BUDDY_NODE) {
822                PurpleBuddy *bud = (PurpleBuddy *) node;
823                PurpleGroup *group = purple_buddy_get_group(bud);
824                struct im_connection *ic = purple_ic_by_pa(bud->account);
825                PurpleStatus *as;
826                int flags = 0;
827
828                if (ic == NULL) {
829                        return;
830                }
831
832                if (bud->server_alias) {
833                        imcb_rename_buddy(ic, bud->name, bud->server_alias);
834                } else if (bud->alias) {
835                        imcb_rename_buddy(ic, bud->name, bud->alias);
836                }
837
838                if (group) {
839                        imcb_add_buddy(ic, bud->name, purple_group_get_name(group));
840                }
841
842                flags |= purple_presence_is_online(bud->presence) ? OPT_LOGGED_IN : 0;
843                flags |= purple_presence_is_available(bud->presence) ? 0 : OPT_AWAY;
844
845                as = purple_presence_get_active_status(bud->presence);
846
847                imcb_buddy_status(ic, bud->name, flags, purple_status_get_name(as),
848                                  purple_status_get_attr_string(as, "message"));
849
850                imcb_buddy_times(ic, bud->name,
851                                 purple_presence_get_login_time(bud->presence),
852                                 purple_presence_get_idle_time(bud->presence));
853        }
854}
855
856static void prplcb_blist_new(PurpleBlistNode *node)
857{
858        if (node->type == PURPLE_BLIST_BUDDY_NODE) {
859                PurpleBuddy *bud = (PurpleBuddy *) node;
860                struct im_connection *ic = purple_ic_by_pa(bud->account);
861
862                if (ic == NULL) {
863                        return;
864                }
865
866                imcb_add_buddy(ic, bud->name, NULL);
867
868                prplcb_blist_update(NULL, node);
869        }
870}
871
872static void prplcb_blist_remove(PurpleBuddyList *list, PurpleBlistNode *node)
873{
874/*
875        PurpleBuddy *bud = (PurpleBuddy*) node;
876
877        if( node->type == PURPLE_BLIST_BUDDY_NODE )
878        {
879                struct im_connection *ic = purple_ic_by_pa( bud->account );
880
881                if( ic == NULL )
882                        return;
883
884                imcb_remove_buddy( ic, bud->name, NULL );
885        }
886*/
887}
888
889static PurpleBlistUiOps bee_blist_uiops =
890{
891        NULL,
892        prplcb_blist_new,
893        NULL,
894        prplcb_blist_update,
895        prplcb_blist_remove,
896};
897
898void prplcb_conv_new(PurpleConversation *conv)
899{
900        if (conv->type == PURPLE_CONV_TYPE_CHAT) {
901                struct im_connection *ic = purple_ic_by_pa(conv->account);
902                struct groupchat *gc;
903
904                gc = imcb_chat_new(ic, conv->name);
905                if (conv->title != NULL) {
906                        imcb_chat_name_hint(gc, conv->title);
907                        imcb_chat_topic(gc, NULL, conv->title, 0);
908                }
909
910                conv->ui_data = gc;
911                gc->data = conv;
912
913                /* libpurple brokenness: Whatever. Show that we join right away,
914                   there's no clear "This is you!" signaling in _add_users so
915                   don't even try. */
916                imcb_chat_add_buddy(gc, gc->ic->acc->user);
917        }
918}
919
920void prplcb_conv_free(PurpleConversation *conv)
921{
922        struct groupchat *gc = conv->ui_data;
923
924        imcb_chat_free(gc);
925}
926
927void prplcb_conv_add_users(PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals)
928{
929        struct groupchat *gc = conv->ui_data;
930        GList *b;
931
932        for (b = cbuddies; b; b = b->next) {
933                PurpleConvChatBuddy *pcb = b->data;
934
935                imcb_chat_add_buddy(gc, pcb->name);
936        }
937}
938
939void prplcb_conv_del_users(PurpleConversation *conv, GList *cbuddies)
940{
941        struct groupchat *gc = conv->ui_data;
942        GList *b;
943
944        for (b = cbuddies; b; b = b->next) {
945                imcb_chat_remove_buddy(gc, b->data, "");
946        }
947}
948
949void prplcb_conv_chat_msg(PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags,
950                          time_t mtime)
951{
952        struct groupchat *gc = conv->ui_data;
953        PurpleBuddy *buddy;
954
955        /* ..._SEND means it's an outgoing message, no need to echo those. */
956        if (flags & PURPLE_MESSAGE_SEND) {
957                return;
958        }
959
960        buddy = purple_find_buddy(conv->account, who);
961        if (buddy != NULL) {
962                who = purple_buddy_get_name(buddy);
963        }
964
965        imcb_chat_msg(gc, who, (char *) message, 0, mtime);
966}
967
968static void prplcb_conv_im(PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags,
969                           time_t mtime)
970{
971        struct im_connection *ic = purple_ic_by_pa(conv->account);
972        PurpleBuddy *buddy;
973
974        /* ..._SEND means it's an outgoing message, no need to echo those. */
975        if (flags & PURPLE_MESSAGE_SEND) {
976                return;
977        }
978
979        buddy = purple_find_buddy(conv->account, who);
980        if (buddy != NULL) {
981                who = purple_buddy_get_name(buddy);
982        }
983
984        imcb_buddy_msg(ic, (char *) who, (char *) message, 0, mtime);
985}
986
987/* No, this is not a ui_op but a signal. */
988static void prplcb_buddy_typing(PurpleAccount *account, const char *who, gpointer null)
989{
990        PurpleConversation *conv;
991        PurpleConvIm *im;
992        int state;
993
994        if ((conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, account)) == NULL) {
995                return;
996        }
997
998        im = PURPLE_CONV_IM(conv);
999        switch (purple_conv_im_get_typing_state(im)) {
1000        case PURPLE_TYPING:
1001                state = OPT_TYPING;
1002                break;
1003        case PURPLE_TYPED:
1004                state = OPT_THINKING;
1005                break;
1006        default:
1007                state = 0;
1008        }
1009
1010        imcb_buddy_typing(purple_ic_by_pa(account), who, state);
1011}
1012
1013static PurpleConversationUiOps bee_conv_uiops =
1014{
1015        prplcb_conv_new,           /* create_conversation  */
1016        prplcb_conv_free,          /* destroy_conversation */
1017        prplcb_conv_chat_msg,      /* write_chat           */
1018        prplcb_conv_im,            /* write_im             */
1019        NULL,                      /* write_conv           */
1020        prplcb_conv_add_users,     /* chat_add_users       */
1021        NULL,                      /* chat_rename_user     */
1022        prplcb_conv_del_users,     /* chat_remove_users    */
1023        NULL,                      /* chat_update_user     */
1024        NULL,                      /* present              */
1025        NULL,                      /* has_focus            */
1026        NULL,                      /* custom_smiley_add    */
1027        NULL,                      /* custom_smiley_write  */
1028        NULL,                      /* custom_smiley_close  */
1029        NULL,                      /* send_confirm         */
1030};
1031
1032struct prplcb_request_action_data {
1033        void *user_data, *bee_data;
1034        PurpleRequestActionCb yes, no;
1035        int yes_i, no_i;
1036};
1037
1038static void prplcb_request_action_yes(void *data)
1039{
1040        struct prplcb_request_action_data *pqad = data;
1041
1042        if (pqad->yes) {
1043                pqad->yes(pqad->user_data, pqad->yes_i);
1044        }
1045}
1046
1047static void prplcb_request_action_no(void *data)
1048{
1049        struct prplcb_request_action_data *pqad = data;
1050
1051        if (pqad->no) {
1052                pqad->no(pqad->user_data, pqad->no_i);
1053        }
1054}
1055
1056/* q->free() callback from query_del()*/
1057static void prplcb_request_action_free(void *data)
1058{
1059        struct prplcb_request_action_data *pqad = data;
1060
1061        pqad->bee_data = NULL;
1062        purple_request_close(PURPLE_REQUEST_ACTION, pqad);
1063}
1064
1065static void *prplcb_request_action(const char *title, const char *primary, const char *secondary,
1066                                   int default_action, PurpleAccount *account, const char *who,
1067                                   PurpleConversation *conv, void *user_data, size_t action_count,
1068                                   va_list actions)
1069{
1070        struct prplcb_request_action_data *pqad;
1071        int i;
1072        char *q;
1073
1074        pqad = g_new0(struct prplcb_request_action_data, 1);
1075
1076        for (i = 0; i < action_count; i++) {
1077                char *caption;
1078                void *fn;
1079
1080                caption = va_arg(actions, char*);
1081                fn = va_arg(actions, void*);
1082
1083                if (strstr(caption, "Accept") || strstr(caption, "OK")) {
1084                        pqad->yes = fn;
1085                        pqad->yes_i = i;
1086                } else if (strstr(caption, "Reject") || strstr(caption, "Cancel")) {
1087                        pqad->no = fn;
1088                        pqad->no_i = i;
1089                }
1090        }
1091
1092        pqad->user_data = user_data;
1093
1094        /* TODO: IRC stuff here :-( */
1095        q = g_strdup_printf("Request: %s\n\n%s\n\n%s", title, primary, secondary);
1096        pqad->bee_data = query_add(local_bee->ui_data, purple_ic_by_pa(account), q,
1097                                   prplcb_request_action_yes, prplcb_request_action_no,
1098                                   prplcb_request_action_free, pqad);
1099
1100        g_free(q);
1101
1102        return pqad;
1103}
1104
1105/* So it turns out some requests have no account context at all, because
1106 * libpurple hates us. This means that query_del_by_conn() won't remove those
1107 * on logout, and will segfault if the user replies. That's why this exists.
1108 */
1109static void prplcb_close_request(PurpleRequestType type, void *data)
1110{
1111        struct prplcb_request_action_data *pqad;
1112        struct request_input_data *ri;
1113        struct purple_data *pd;
1114
1115        if (!data) {
1116                return;
1117        }
1118
1119        switch (type) {
1120        case PURPLE_REQUEST_ACTION:
1121                pqad = data;
1122                /* if this is null, it's because query_del was run already */
1123                if (pqad->bee_data) {
1124                        query_del(local_bee->ui_data, pqad->bee_data);
1125                }
1126                g_free(pqad);
1127                break;
1128        case PURPLE_REQUEST_INPUT:
1129                ri = data;
1130                pd = ri->ic->proto_data;
1131                imcb_remove_buddy(ri->ic, ri->buddy, NULL);
1132                g_free(ri->buddy);
1133                g_hash_table_remove(pd->input_requests, GUINT_TO_POINTER(ri->id));
1134                break;
1135        default:
1136                g_free(data);
1137                break;
1138        }
1139
1140}
1141
1142void* prplcb_request_input(const char *title, const char *primary,
1143        const char *secondary, const char *default_value, gboolean multiline,
1144        gboolean masked, gchar *hint, const char *ok_text, GCallback ok_cb,
1145        const char *cancel_text, GCallback cancel_cb, PurpleAccount *account,
1146        const char *who, PurpleConversation *conv, void *user_data)
1147{
1148        struct im_connection *ic = purple_ic_by_pa(account);
1149        struct purple_data *pd = ic->proto_data;
1150        struct request_input_data *ri = g_new0(struct request_input_data, 1);
1151        guint id = pd->next_request_id++;
1152
1153        ri->id = id;
1154        ri->ic = ic;
1155        ri->buddy = g_strdup_printf("%s_%u", PURPLE_REQUEST_HANDLE, id);
1156        ri->data_callback = (ri_callback_t) ok_cb;
1157        ri->user_data = user_data;
1158        g_hash_table_insert(pd->input_requests, GUINT_TO_POINTER(id), ri);
1159
1160        imcb_add_buddy(ic, ri->buddy, NULL);
1161        imcb_buddy_msg(ic, ri->buddy, secondary, 0, 0);
1162
1163        return ri;
1164}
1165
1166void purple_request_input_callback(guint id, struct im_connection *ic,
1167                                   const char *message, const char *who)
1168{
1169        struct purple_data *pd = ic->proto_data;
1170        struct request_input_data *ri;
1171
1172        if (!(ri = g_hash_table_lookup(pd->input_requests, GUINT_TO_POINTER(id)))) {
1173                return;
1174        }
1175
1176        ri->data_callback(ri->user_data, message);
1177
1178        purple_request_close(PURPLE_REQUEST_INPUT, ri);
1179}
1180
1181
1182static PurpleRequestUiOps bee_request_uiops =
1183{
1184        prplcb_request_input,
1185        NULL,
1186        prplcb_request_action,
1187        NULL,
1188        NULL,
1189        prplcb_close_request,
1190        NULL,
1191};
1192
1193static void prplcb_privacy_permit_added(PurpleAccount *account, const char *name)
1194{
1195        struct im_connection *ic = purple_ic_by_pa(account);
1196
1197        if (!g_slist_find_custom(ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp)) {
1198                ic->permit = g_slist_prepend(ic->permit, g_strdup(name));
1199        }
1200}
1201
1202static void prplcb_privacy_permit_removed(PurpleAccount *account, const char *name)
1203{
1204        struct im_connection *ic = purple_ic_by_pa(account);
1205        void *n;
1206
1207        n = g_slist_find_custom(ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp);
1208        ic->permit = g_slist_remove(ic->permit, n);
1209}
1210
1211static void prplcb_privacy_deny_added(PurpleAccount *account, const char *name)
1212{
1213        struct im_connection *ic = purple_ic_by_pa(account);
1214
1215        if (!g_slist_find_custom(ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp)) {
1216                ic->deny = g_slist_prepend(ic->deny, g_strdup(name));
1217        }
1218}
1219
1220static void prplcb_privacy_deny_removed(PurpleAccount *account, const char *name)
1221{
1222        struct im_connection *ic = purple_ic_by_pa(account);
1223        void *n;
1224
1225        n = g_slist_find_custom(ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp);
1226        ic->deny = g_slist_remove(ic->deny, n);
1227}
1228
1229static PurplePrivacyUiOps bee_privacy_uiops =
1230{
1231        prplcb_privacy_permit_added,
1232        prplcb_privacy_permit_removed,
1233        prplcb_privacy_deny_added,
1234        prplcb_privacy_deny_removed,
1235};
1236
1237static void prplcb_debug_print(PurpleDebugLevel level, const char *category, const char *arg_s)
1238{
1239        fprintf(stderr, "DEBUG %s: %s", category, arg_s);
1240}
1241
1242static PurpleDebugUiOps bee_debug_uiops =
1243{
1244        prplcb_debug_print,
1245};
1246
1247static guint prplcb_ev_timeout_add(guint interval, GSourceFunc func, gpointer udata)
1248{
1249        return b_timeout_add(interval, (b_event_handler) func, udata);
1250}
1251
1252static guint prplcb_ev_input_add(int fd, PurpleInputCondition cond, PurpleInputFunction func, gpointer udata)
1253{
1254        return b_input_add(fd, cond | B_EV_FLAG_FORCE_REPEAT, (b_event_handler) func, udata);
1255}
1256
1257static gboolean prplcb_ev_remove(guint id)
1258{
1259        b_event_remove((gint) id);
1260        return TRUE;
1261}
1262
1263static PurpleEventLoopUiOps glib_eventloops =
1264{
1265        prplcb_ev_timeout_add,
1266        prplcb_ev_remove,
1267        prplcb_ev_input_add,
1268        prplcb_ev_remove,
1269};
1270
1271static void *prplcb_notify_email(PurpleConnection *gc, const char *subject, const char *from,
1272                                 const char *to, const char *url)
1273{
1274        struct im_connection *ic = purple_ic_by_gc(gc);
1275
1276        imcb_notify_email(ic, "Received e-mail from %s for %s: %s <%s>", from, to, subject, url);
1277
1278        return NULL;
1279}
1280
1281static void *prplcb_notify_userinfo(PurpleConnection *gc, const char *who, PurpleNotifyUserInfo *user_info)
1282{
1283        struct im_connection *ic = purple_ic_by_gc(gc);
1284        GString *info = g_string_new("");
1285        GList *l = purple_notify_user_info_get_entries(user_info);
1286        char *key;
1287        const char *value;
1288        int n;
1289
1290        while (l) {
1291                PurpleNotifyUserInfoEntry *e = l->data;
1292
1293                switch (purple_notify_user_info_entry_get_type(e)) {
1294                case PURPLE_NOTIFY_USER_INFO_ENTRY_PAIR:
1295                case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_HEADER:
1296                        key = g_strdup(purple_notify_user_info_entry_get_label(e));
1297                        value = purple_notify_user_info_entry_get_value(e);
1298
1299                        if (key) {
1300                                strip_html(key);
1301                                g_string_append_printf(info, "%s: ", key);
1302
1303                                if (value) {
1304                                        n = strlen(value) - 1;
1305                                        while (g_ascii_isspace(value[n])) {
1306                                                n--;
1307                                        }
1308                                        g_string_append_len(info, value, n + 1);
1309                                }
1310                                g_string_append_c(info, '\n');
1311                                g_free(key);
1312                        }
1313
1314                        break;
1315                case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_BREAK:
1316                        g_string_append(info, "------------------------\n");
1317                        break;
1318                }
1319
1320                l = l->next;
1321        }
1322
1323        imcb_log(ic, "User %s info:\n%s", who, info->str);
1324        g_string_free(info, TRUE);
1325
1326        return NULL;
1327}
1328
1329static PurpleNotifyUiOps bee_notify_uiops =
1330{
1331        NULL,
1332        prplcb_notify_email,
1333        NULL,
1334        NULL,
1335        NULL,
1336        NULL,
1337        prplcb_notify_userinfo,
1338};
1339
1340static void *prplcb_account_request_authorize(PurpleAccount *account, const char *remote_user,
1341                                              const char *id, const char *alias, const char *message, gboolean on_list,
1342                                              PurpleAccountRequestAuthorizationCb authorize_cb,
1343                                              PurpleAccountRequestAuthorizationCb deny_cb, void *user_data)
1344{
1345        struct im_connection *ic = purple_ic_by_pa(account);
1346        char *q;
1347
1348        if (alias) {
1349                q = g_strdup_printf("%s (%s) wants to add you to his/her contact "
1350                                    "list. (%s)", alias, remote_user, message);
1351        } else {
1352                q = g_strdup_printf("%s wants to add you to his/her contact "
1353                                    "list. (%s)", remote_user, message);
1354        }
1355
1356        imcb_ask_with_free(ic, q, user_data, authorize_cb, deny_cb, NULL);
1357        g_free(q);
1358
1359        return NULL;
1360}
1361
1362static PurpleAccountUiOps bee_account_uiops =
1363{
1364        NULL,
1365        NULL,
1366        NULL,
1367        prplcb_account_request_authorize,
1368        NULL,
1369};
1370
1371extern PurpleXferUiOps bee_xfer_uiops;
1372
1373static void purple_ui_init()
1374{
1375        purple_connections_set_ui_ops(&bee_conn_uiops);
1376        purple_blist_set_ui_ops(&bee_blist_uiops);
1377        purple_conversations_set_ui_ops(&bee_conv_uiops);
1378        purple_request_set_ui_ops(&bee_request_uiops);
1379        purple_privacy_set_ui_ops(&bee_privacy_uiops);
1380        purple_notify_set_ui_ops(&bee_notify_uiops);
1381        purple_accounts_set_ui_ops(&bee_account_uiops);
1382        purple_xfers_set_ui_ops(&bee_xfer_uiops);
1383
1384        if (getenv("BITLBEE_DEBUG")) {
1385                purple_debug_set_ui_ops(&bee_debug_uiops);
1386        }
1387}
1388
1389void purple_initmodule()
1390{
1391        struct prpl funcs;
1392        GList *prots;
1393        GString *help;
1394        char *dir;
1395
1396        if (B_EV_IO_READ != PURPLE_INPUT_READ ||
1397            B_EV_IO_WRITE != PURPLE_INPUT_WRITE) {
1398                /* FIXME FIXME FIXME FIXME FIXME :-) */
1399                exit(1);
1400        }
1401
1402        dir = g_strdup_printf("%s/purple", global.conf->configdir);
1403        purple_util_set_user_dir(dir);
1404        g_free(dir);
1405
1406        purple_debug_set_enabled(FALSE);
1407        purple_core_set_ui_ops(&bee_core_uiops);
1408        purple_eventloop_set_ui_ops(&glib_eventloops);
1409        if (!purple_core_init("BitlBee")) {
1410                /* Initializing the core failed. Terminate. */
1411                fprintf(stderr, "libpurple initialization failed.\n");
1412                abort();
1413        }
1414
1415        if (proxytype != PROXY_NONE) {
1416                PurpleProxyInfo *pi = purple_global_proxy_get_info();
1417                switch (proxytype) {
1418                case PROXY_SOCKS4A:
1419                case PROXY_SOCKS4:
1420                        purple_proxy_info_set_type(pi, PURPLE_PROXY_SOCKS4);
1421                        break;
1422                case PROXY_SOCKS5:
1423                        purple_proxy_info_set_type(pi, PURPLE_PROXY_SOCKS5);
1424                        break;
1425                case PROXY_HTTP:
1426                        purple_proxy_info_set_type(pi, PURPLE_PROXY_HTTP);
1427                        break;
1428                }
1429                purple_proxy_info_set_host(pi, proxyhost);
1430                purple_proxy_info_set_port(pi, proxyport);
1431                purple_proxy_info_set_username(pi, proxyuser);
1432                purple_proxy_info_set_password(pi, proxypass);
1433        }
1434
1435        purple_set_blist(purple_blist_new());
1436
1437        /* No, really. So far there were ui_ops for everything, but now suddenly
1438           one needs to use signals for typing notification stuff. :-( */
1439        purple_signal_connect(purple_conversations_get_handle(), "buddy-typing",
1440                              &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL);
1441        purple_signal_connect(purple_conversations_get_handle(), "buddy-typed",
1442                              &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL);
1443        purple_signal_connect(purple_conversations_get_handle(), "buddy-typing-stopped",
1444                              &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL);
1445
1446        memset(&funcs, 0, sizeof(funcs));
1447        funcs.login = purple_login;
1448        funcs.init = purple_init;
1449        funcs.logout = purple_logout;
1450        funcs.buddy_msg = purple_buddy_msg;
1451        funcs.away_states = purple_away_states;
1452        funcs.set_away = purple_set_away;
1453        funcs.add_buddy = purple_add_buddy;
1454        funcs.remove_buddy = purple_remove_buddy;
1455        funcs.add_permit = purple_add_permit;
1456        funcs.add_deny = purple_add_deny;
1457        funcs.rem_permit = purple_rem_permit;
1458        funcs.rem_deny = purple_rem_deny;
1459        funcs.get_info = purple_get_info;
1460        funcs.keepalive = purple_keepalive;
1461        funcs.send_typing = purple_send_typing;
1462        funcs.handle_cmp = g_strcasecmp;
1463        /* TODO(wilmer): Set these only for protocols that support them? */
1464        funcs.chat_msg = purple_chat_msg;
1465        funcs.chat_with = purple_chat_with;
1466        funcs.chat_invite = purple_chat_invite;
1467        funcs.chat_kick = purple_chat_kick;
1468        funcs.chat_leave = purple_chat_leave;
1469        funcs.chat_join = purple_chat_join;
1470        funcs.transfer_request = purple_transfer_request;
1471
1472        help = g_string_new("BitlBee libpurple module supports the following IM protocols:\n");
1473
1474        /* Add a protocol entry to BitlBee's structures for every protocol
1475           supported by this libpurple instance. */
1476        for (prots = purple_plugins_get_protocols(); prots; prots = prots->next) {
1477                PurplePlugin *prot = prots->data;
1478                struct prpl *ret;
1479
1480                /* If we already have this one (as a native module), don't
1481                   add a libpurple duplicate. */
1482                if (find_protocol(prot->info->id)) {
1483                        continue;
1484                }
1485
1486                ret = g_memdup(&funcs, sizeof(funcs));
1487                ret->name = ret->data = prot->info->id;
1488                if (strncmp(ret->name, "prpl-", 5) == 0) {
1489                        ret->name += 5;
1490                }
1491                register_protocol(ret);
1492
1493                g_string_append_printf(help, "\n* %s (%s)", ret->name, prot->info->name);
1494
1495                /* libpurple doesn't define a protocol called OSCAR, but we
1496                   need it to be compatible with normal BitlBee. */
1497                if (g_strcasecmp(prot->info->id, "prpl-aim") == 0) {
1498                        ret = g_memdup(&funcs, sizeof(funcs));
1499                        ret->name = "oscar";
1500                        ret->data = prot->info->id;
1501                        register_protocol(ret);
1502                }
1503        }
1504
1505        g_string_append(help, "\n\nFor used protocols, more information about available "
1506                        "settings can be found using \x02help purple <protocol name>\x02 "
1507                        "(create an account using that protocol first!)");
1508
1509        /* Add a simple dynamically-generated help item listing all
1510           the supported protocols. */
1511        help_add_mem(&global.help, "purple", help->str);
1512        g_string_free(help, TRUE);
1513}
Note: See TracBrowser for help on using the repository browser.