source: protocols/purple/purple.c @ 3bb333c

Last change on this file since 3bb333c was 3bb333c, checked in by dequis <dx@…>, at 2015-03-11T21:24:15Z

purple: Implement 'close_request' to prevent segfaults after logout

Fixes trac bug 1190 ("Accepting SSL certs too late resets bitlbee-libpurple")

To reproduce:

  1. Connect to server with self-signed ssl certificate (downgrading to libpurple 2.10.9 might be required to actually get the request)
  2. Disconnect the account
  3. Type "yes"
  4. Acquire segfault.

Normally, query_del_by_conn() would handle this, but some requests have
no account context at all. Yeah, it sucks. This is how pidgin handles it.

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