1 /********************************************************************\
2 * BitlBee -- An IRC to other IM-networks gateway *
3 * *
4 * Copyright 2002-2012 Wilmer van der Gaast and others *
5 \********************************************************************/
6
7 /* Some glue to put the IRC and the IM stuff together. */
8
9 /*
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License with
21 the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
22 if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
23 Fifth Floor, Boston, MA 02110-1301 USA
24 */
25
26 #include "bitlbee.h"
27 #include "dcc.h"
28
29 /* IM->IRC callbacks: Simple IM/buddy-related stuff. */
30
31 static const struct irc_user_funcs irc_user_im_funcs;
32
bee_irc_imc_connected(struct im_connection * ic)33 static void bee_irc_imc_connected(struct im_connection *ic)
34 {
35 irc_t *irc = (irc_t *) ic->bee->ui_data;
36
37 irc_channel_auto_joins(irc, ic->acc);
38 }
39
bee_irc_imc_disconnected(struct im_connection * ic)40 static void bee_irc_imc_disconnected(struct im_connection *ic)
41 {
42 /* Maybe try to send /QUITs here instead of later on. */
43 }
44
bee_irc_user_new(bee_t * bee,bee_user_t * bu)45 static gboolean bee_irc_user_new(bee_t *bee, bee_user_t *bu)
46 {
47 irc_user_t *iu;
48 irc_t *irc = (irc_t *) bee->ui_data;
49 char nick[MAX_NICK_LENGTH + 1], *s;
50
51 memset(nick, 0, MAX_NICK_LENGTH + 1);
52 strncpy(nick, nick_get(bu), MAX_NICK_LENGTH);
53
54 bu->ui_data = iu = irc_user_new(irc, nick);
55 iu->bu = bu;
56
57 if (set_getbool(&irc->b->set, "private")) {
58 iu->last_channel = NULL;
59 } else {
60 iu->last_channel = irc_channel_with_user(irc, iu);
61 }
62
63 if ((s = strchr(bu->handle, '@'))) {
64 iu->host = g_strdup(s + 1);
65 iu->user = g_strndup(bu->handle, s - bu->handle);
66 } else {
67 iu->user = g_strdup(bu->handle);
68 if (bu->ic->acc->server) {
69 iu->host = g_strdup(bu->ic->acc->server);
70 } else {
71 char *s;
72 for (s = bu->ic->acc->tag; g_ascii_isalnum(*s); s++) {
73 ;
74 }
75 /* Only use the tag if we got to the end of the string.
76 (So allow alphanumerics only. Hopefully not too
77 restrictive.) */
78 if (*s) {
79 iu->host = g_strdup(bu->ic->acc->prpl->name);
80 } else {
81 iu->host = g_strdup(bu->ic->acc->tag);
82 }
83 }
84 }
85
86 /* Sanitize */
87 str_reject_chars(iu->user, " ", '_');
88 str_reject_chars(iu->host, " ", '_');
89
90 if (bu->flags & BEE_USER_LOCAL) {
91 char *s = set_getstr(&bu->ic->acc->set, "handle_unknown") ? :
92 set_getstr(&bee->set, "handle_unknown");
93
94 if (g_strcasecmp(s, "add_private") == 0) {
95 iu->last_channel = NULL;
96 } else if (g_strcasecmp(s, "add_channel") == 0) {
97 iu->last_channel = irc->default_channel;
98 }
99 }
100
101 iu->f = &irc_user_im_funcs;
102
103 return TRUE;
104 }
105
bee_irc_user_free(bee_t * bee,bee_user_t * bu)106 static gboolean bee_irc_user_free(bee_t *bee, bee_user_t *bu)
107 {
108 return irc_user_free(bee->ui_data, (irc_user_t *) bu->ui_data);
109 }
110
bee_irc_user_status(bee_t * bee,bee_user_t * bu,bee_user_t * old)111 static gboolean bee_irc_user_status(bee_t *bee, bee_user_t *bu, bee_user_t *old)
112 {
113 irc_t *irc = bee->ui_data;
114 irc_user_t *iu = bu->ui_data;
115
116 /* Do this outside the if below since away state can change without
117 the online state changing. */
118 iu->flags &= ~IRC_USER_AWAY;
119 if (bu->flags & BEE_USER_AWAY || !(bu->flags & BEE_USER_ONLINE)) {
120 iu->flags |= IRC_USER_AWAY;
121 }
122
123 /* return early if nothing changed */
124 if ((bu->flags == old->flags) &&
125 (g_strcmp0(bu->status, old->status) == 0) &&
126 (g_strcmp0(bu->status_msg, old->status_msg) == 0)) {
127 return TRUE;
128 }
129
130 if ((bu->flags & BEE_USER_ONLINE) != (old->flags & BEE_USER_ONLINE)) {
131 if (bu->flags & BEE_USER_ONLINE) {
132 if (g_hash_table_lookup(irc->watches, iu->key)) {
133 irc_send_num(irc, 600, "%s %s %s %d :%s", iu->nick, iu->user,
134 iu->host, (int) time(NULL), "logged online");
135 }
136 } else {
137 if (g_hash_table_lookup(irc->watches, iu->key)) {
138 irc_send_num(irc, 601, "%s %s %s %d :%s", iu->nick, iu->user,
139 iu->host, (int) time(NULL), "logged offline");
140 }
141
142 /* Send a QUIT since those will also show up in any
143 query windows the user may have, plus it's only
144 one QUIT instead of possibly many (in case of
145 multiple control chans). If there's a channel that
146 shows offline people, a JOIN will follow. */
147 if (set_getbool(&bee->set, "offline_user_quits")) {
148 irc_user_quit(iu, "Leaving...");
149 }
150 }
151 }
152
153 iu->away_reply_timeout = 0;
154
155 bee_irc_channel_update(irc, NULL, iu);
156
157 if (irc->caps & CAP_AWAY_NOTIFY) {
158 irc_send_away_notify(iu);
159 }
160
161 return TRUE;
162 }
163
bee_irc_channel_update(irc_t * irc,irc_channel_t * ic,irc_user_t * iu)164 void bee_irc_channel_update(irc_t *irc, irc_channel_t *ic, irc_user_t *iu)
165 {
166 GSList *l;
167
168 if (ic == NULL) {
169 for (l = irc->channels; l; l = l->next) {
170 ic = l->data;
171 /* TODO: Just add a type flag or so.. */
172 if (ic->f == irc->default_channel->f &&
173 (ic->flags & IRC_CHANNEL_JOINED)) {
174 bee_irc_channel_update(irc, ic, iu);
175 }
176 }
177 return;
178 }
179 if (iu == NULL) {
180 GHashTableIter iter;
181 gpointer itervalue;
182 g_hash_table_iter_init(&iter, irc->nick_user_hash);
183
184 while (g_hash_table_iter_next(&iter, NULL, &itervalue)) {
185 iu = itervalue;
186 if (iu->bu) {
187 bee_irc_channel_update(irc, ic, iu);
188 }
189 }
190 return;
191 }
192
193 if (!irc_channel_wants_user(ic, iu)) {
194 irc_channel_del_user(ic, iu, IRC_CDU_PART, NULL);
195 } else {
196 struct irc_control_channel *icc = ic->data;
197 int mode = 0;
198
199 if (!(iu->bu->flags & BEE_USER_ONLINE)) {
200 mode = icc->modes[0];
201 } else if (iu->bu->flags & BEE_USER_AWAY) {
202 mode = icc->modes[1];
203 } else if (iu->bu->flags & BEE_USER_SPECIAL) {
204 mode = icc->modes[2];
205 } else {
206 mode = icc->modes[3];
207 }
208
209 if (!mode) {
210 irc_channel_del_user(ic, iu, IRC_CDU_PART, NULL);
211 } else {
212 irc_channel_add_user(ic, iu);
213 irc_channel_user_set_mode(ic, iu, mode);
214 }
215 }
216 }
217
bee_irc_user_msg(bee_t * bee,bee_user_t * bu,const char * msg_,guint32 flags,time_t sent_at)218 static gboolean bee_irc_user_msg(bee_t *bee, bee_user_t *bu, const char *msg_, guint32 flags, time_t sent_at)
219 {
220 irc_t *irc = bee->ui_data;
221 irc_user_t *iu = (irc_user_t *) bu->ui_data;
222 irc_user_t *src_iu = iu;
223 irc_user_t *dst_iu = irc->user;
224 const char *dst;
225 char *prefix = NULL;
226 char *wrapped, *ts = NULL;
227 char *msg = g_strdup(msg_);
228 char *message_type = "PRIVMSG";
229 GSList *l;
230
231 if (sent_at > 0 &&
232 !(irc->caps & CAP_SERVER_TIME) &&
233 set_getbool(&irc->b->set, "display_timestamps")) {
234 ts = irc_format_timestamp(irc, sent_at);
235 }
236
237 dst = irc_user_msgdest(iu);
238
239 if (flags & OPT_SELFMESSAGE) {
240 char *setting = set_getstr(&irc->b->set, "self_messages");
241
242 if (is_bool(setting)) {
243 if (bool2int(setting)) {
244 /* set to true, send it with src/dst flipped */
245
246 dst_iu = iu;
247 src_iu = irc->user;
248
249 if (dst == irc->user->nick) {
250 dst = dst_iu->nick;
251 }
252 } else {
253 /* set to false, skip the message completely */
254 goto cleanup;
255 }
256 } else if (g_strncasecmp(setting, "prefix", 6) == 0) {
257 /* third state, prefix, loosely imitates the znc privmsg_prefix module */
258
259 g_free(msg);
260 if (g_strncasecmp(msg_, "/me ", 4) == 0) {
261 msg = g_strdup_printf("/me -> %s", msg_ + 4);
262 } else {
263 msg = g_strdup_printf("-> %s", msg_);
264 }
265
266 if (g_strcasecmp(setting, "prefix_notice") == 0) {
267 message_type = "NOTICE";
268 }
269 }
270
271 }
272
273 if (dst != dst_iu->nick) {
274 /* if not messaging directly (control channel), call user by name */
275 prefix = g_strdup_printf("%s%s%s", dst_iu->nick, set_getstr(&bee->set, "to_char"), ts ? : "");
276 } else {
277 prefix = ts;
278 ts = NULL; /* don't double-free */
279 }
280
281 for (l = irc_plugins; l; l = l->next) {
282 irc_plugin_t *p = l->data;
283 if (p->filter_msg_in) {
284 char *s = p->filter_msg_in(iu, msg, 0);
285 if (s) {
286 if (s != msg) {
287 g_free(msg);
288 }
289 msg = s;
290 } else {
291 /* Modules can swallow messages. */
292 goto cleanup;
293 }
294 }
295 }
296
297 if ((g_strcasecmp(set_getstr(&bee->set, "strip_html"), "always") == 0) ||
298 ((bu->ic->flags & OPT_DOES_HTML) && set_getbool(&bee->set, "strip_html"))) {
299 char *s = g_strdup(msg);
300 strip_html(s);
301 g_free(msg);
302 msg = s;
303 }
304
305 wrapped = word_wrap(msg, IRC_WORD_WRAP);
306 irc_send_msg_ts(src_iu, message_type, dst, wrapped, prefix, sent_at);
307 g_free(wrapped);
308
309 cleanup:
310 g_free(prefix);
311 g_free(msg);
312 g_free(ts);
313
314 return TRUE;
315 }
316
bee_irc_user_typing(bee_t * bee,bee_user_t * bu,guint32 flags)317 static gboolean bee_irc_user_typing(bee_t *bee, bee_user_t *bu, guint32 flags)
318 {
319 irc_t *irc = (irc_t *) bee->ui_data;
320
321 if (set_getbool(&bee->set, "typing_notice")) {
322 irc_send_msg_f((irc_user_t *) bu->ui_data, "PRIVMSG", irc->user->nick,
323 "\001TYPING %d\001", (flags >> 8) & 3);
324 } else {
325 return FALSE;
326 }
327
328 return TRUE;
329 }
330
bee_irc_user_action_response(bee_t * bee,bee_user_t * bu,const char * action,char * const args[],void * data)331 static gboolean bee_irc_user_action_response(bee_t *bee, bee_user_t *bu, const char *action, char * const args[],
332 void *data)
333 {
334 irc_t *irc = (irc_t *) bee->ui_data;
335 GString *msg = g_string_new("\001");
336
337 g_string_append(msg, action);
338 while (*args) {
339 if (strchr(*args, ' ')) {
340 g_string_append_printf(msg, " \"%s\"", *args);
341 } else {
342 g_string_append_printf(msg, " %s", *args);
343 }
344 args++;
345 }
346 g_string_append_c(msg, '\001');
347
348 irc_send_msg((irc_user_t *) bu->ui_data, "NOTICE", irc->user->nick, msg->str, NULL);
349
350 g_string_free(msg, TRUE);
351
352 return TRUE;
353 }
354
355 static gboolean bee_irc_user_nick_update(irc_user_t *iu, gboolean offline_only);
356
bee_irc_user_fullname(bee_t * bee,bee_user_t * bu)357 static gboolean bee_irc_user_fullname(bee_t *bee, bee_user_t *bu)
358 {
359 irc_user_t *iu = (irc_user_t *) bu->ui_data;
360 char *s;
361
362 if (iu->fullname != iu->nick) {
363 g_free(iu->fullname);
364 }
365 iu->fullname = g_strdup(bu->fullname);
366
367 /* Strip newlines (unlikely, but IRC-unfriendly so they must go)
368 TODO(wilmer): Do the same with away msgs again! */
369 for (s = iu->fullname; *s; s++) {
370 if (g_ascii_isspace(*s)) {
371 *s = ' ';
372 }
373 }
374
375 if ((bu->ic->flags & OPT_LOGGED_IN) && set_getbool(&bee->set, "display_namechanges")) {
376 /* People don't like this /NOTICE. Meh, let's go back to the old one.
377 char *msg = g_strdup_printf( "<< \002BitlBee\002 - Changed name to `%s' >>", iu->fullname );
378 irc_send_msg( iu, "NOTICE", irc->user->nick, msg, NULL );
379 */
380 imcb_log(bu->ic, "User `%s' changed name to `%s'", iu->nick, iu->fullname);
381 }
382
383 bee_irc_user_nick_update(iu, TRUE);
384
385 return TRUE;
386 }
387
bee_irc_user_nick_hint(bee_t * bee,bee_user_t * bu,const char * hint)388 static gboolean bee_irc_user_nick_hint(bee_t *bee, bee_user_t *bu, const char *hint)
389 {
390 bee_irc_user_nick_update((irc_user_t *) bu->ui_data, TRUE);
391
392 return TRUE;
393 }
394
bee_irc_user_nick_change(bee_t * bee,bee_user_t * bu,const char * nick)395 static gboolean bee_irc_user_nick_change(bee_t *bee, bee_user_t *bu, const char *nick)
396 {
397 bee_irc_user_nick_update((irc_user_t *) bu->ui_data, FALSE);
398
399 return TRUE;
400 }
401
bee_irc_user_group(bee_t * bee,bee_user_t * bu)402 static gboolean bee_irc_user_group(bee_t *bee, bee_user_t *bu)
403 {
404 irc_user_t *iu = (irc_user_t *) bu->ui_data;
405 irc_t *irc = (irc_t *) bee->ui_data;
406
407 bee_irc_channel_update(irc, NULL, iu);
408 bee_irc_user_nick_update(iu, FALSE);
409
410 return TRUE;
411 }
412
bee_irc_user_nick_update(irc_user_t * iu,gboolean offline_only)413 static gboolean bee_irc_user_nick_update(irc_user_t *iu, gboolean offline_only)
414 {
415 bee_user_t *bu = iu->bu;
416 char *newnick;
417
418 if (offline_only && bu->flags & BEE_USER_ONLINE) {
419 /* Ignore if the user is visible already. */
420 return TRUE;
421 }
422
423 if (nick_saved(bu)) {
424 /* The user already assigned a nickname to this person. */
425 return TRUE;
426 }
427
428 newnick = nick_get(bu);
429
430 if (strcmp(iu->nick, newnick) != 0) {
431 nick_dedupe(bu, newnick);
432 irc_user_set_nick(iu, newnick);
433 }
434
435 return TRUE;
436 }
437
bee_irc_user_nick_reset(irc_user_t * iu)438 void bee_irc_user_nick_reset(irc_user_t *iu)
439 {
440 bee_user_t *bu = iu->bu;
441
442 if (bu == FALSE) {
443 return;
444 }
445
446 nick_del(bu);
447 bee_irc_user_nick_update(iu, FALSE);
448
449 }
450
451 #define PASTEBUF_LONG_SPACELESS_LINE_LENGTH 350
452
453 /* Returns FALSE if the last line of the message is near the typical irc length
454 * limit and the message has no spaces, indicating that it's probably desirable
455 * to join messages without the newline.
456 *
457 * The main use case for this is pasting long URLs and not breaking them */
bee_irc_pastebuf_should_start_with_newline(const char * msg)458 static gboolean bee_irc_pastebuf_should_start_with_newline(const char *msg)
459 {
460 int i;
461 const char *last_line = strrchr(msg, '\n') ? : msg;
462
463 if (*last_line == '\n') {
464 last_line++;
465 }
466
467 for (i = 0; last_line[i]; i++) {
468 if (g_ascii_isspace(last_line[i])) {
469 return TRUE;
470 }
471 }
472
473 if (i < PASTEBUF_LONG_SPACELESS_LINE_LENGTH) {
474 return TRUE;
475 }
476
477 return FALSE;
478 }
479
480 /* IRC->IM calls */
481
482 static gboolean bee_irc_user_privmsg_cb(gpointer data, gint fd, b_input_condition cond);
483
bee_irc_user_privmsg(irc_user_t * iu,const char * msg)484 static gboolean bee_irc_user_privmsg(irc_user_t *iu, const char *msg)
485 {
486 const char *away;
487
488 if (iu->bu == NULL) {
489 return FALSE;
490 }
491
492 if (iu->last_channel == NULL &&
493 (away = irc_user_get_away(iu)) &&
494 time(NULL) >= iu->away_reply_timeout) {
495 irc_send_num(iu->irc, 301, "%s :%s", iu->nick, away);
496 iu->away_reply_timeout = time(NULL) +
497 set_getint(&iu->irc->b->set, "away_reply_timeout");
498 }
499
500 if (iu->pastebuf == NULL) {
501 iu->pastebuf = g_string_new(msg);
502 } else {
503 b_event_remove(iu->pastebuf_timer);
504 if (bee_irc_pastebuf_should_start_with_newline(iu->pastebuf->str)) {
505 g_string_append_c(iu->pastebuf, '\n');
506 }
507 g_string_append(iu->pastebuf, msg);
508 }
509
510 if (set_getbool(&iu->irc->b->set, "paste_buffer")) {
511 int delay;
512
513 if ((delay = set_getint(&iu->irc->b->set, "paste_buffer_delay")) <= 5) {
514 delay *= 1000;
515 }
516
517 iu->pastebuf_timer = b_timeout_add(delay, bee_irc_user_privmsg_cb, iu);
518
519 return TRUE;
520 } else {
521 bee_irc_user_privmsg_cb(iu, 0, 0);
522
523 return TRUE;
524 }
525 }
526
bee_irc_user_privmsg_cb(gpointer data,gint fd,b_input_condition cond)527 static gboolean bee_irc_user_privmsg_cb(gpointer data, gint fd, b_input_condition cond)
528 {
529 irc_user_t *iu = data;
530 char *msg;
531 GSList *l;
532
533 msg = g_string_free(iu->pastebuf, FALSE);
534 iu->pastebuf = NULL;
535 iu->pastebuf_timer = 0;
536
537 for (l = irc_plugins; l; l = l->next) {
538 irc_plugin_t *p = l->data;
539 if (p->filter_msg_out) {
540 char *s = p->filter_msg_out(iu, msg, 0);
541 if (s) {
542 if (s != msg) {
543 g_free(msg);
544 }
545 msg = s;
546 } else {
547 /* Modules can swallow messages. */
548 iu->pastebuf = NULL;
549 g_free(msg);
550 return FALSE;
551 }
552 }
553 }
554
555 bee_user_msg(iu->irc->b, iu->bu, msg, 0);
556
557 g_free(msg);
558
559 return FALSE;
560 }
561
bee_irc_user_ctcp(irc_user_t * iu,char * const * ctcp)562 static gboolean bee_irc_user_ctcp(irc_user_t *iu, char *const *ctcp)
563 {
564 if (ctcp[1] && g_strcasecmp(ctcp[0], "DCC") == 0
565 && g_strcasecmp(ctcp[1], "SEND") == 0) {
566 if (iu->bu && iu->bu->ic && iu->bu->ic->acc->prpl->transfer_request) {
567 file_transfer_t *ft = dcc_request(iu->bu->ic, ctcp);
568 if (ft) {
569 iu->bu->ic->acc->prpl->transfer_request(iu->bu->ic, ft, iu->bu->handle);
570 }
571
572 return TRUE;
573 }
574 } else if (g_strcasecmp(ctcp[0], "TYPING") == 0) {
575 if (iu->bu && iu->bu->ic && iu->bu->ic->acc->prpl->send_typing && ctcp[1]) {
576 int st = ctcp[1][0];
577 if (st >= '0' && st <= '2') {
578 st <<= 8;
579 iu->bu->ic->acc->prpl->send_typing(iu->bu->ic, iu->bu->handle, st);
580 }
581
582 return TRUE;
583 }
584 } else if (g_strcasecmp(ctcp[0], "HELP") == 0 && iu->bu) {
585 GString *supp = g_string_new("Supported CTCPs:");
586 GList *l;
587
588 if (iu->bu->ic && iu->bu->ic->acc->prpl->transfer_request) {
589 g_string_append(supp, " DCC SEND,");
590 }
591 if (iu->bu->ic && iu->bu->ic->acc->prpl->send_typing) {
592 g_string_append(supp, " TYPING,");
593 }
594 if (iu->bu->ic->acc->prpl->buddy_action_list) {
595 for (l = iu->bu->ic->acc->prpl->buddy_action_list(iu->bu); l; l = l->next) {
596 struct buddy_action *ba = l->data;
597 g_string_append_printf(supp, " %s (%s),",
598 ba->name, ba->description);
599 }
600 }
601 g_string_truncate(supp, supp->len - 1);
602 irc_send_msg_f(iu, "NOTICE", iu->irc->user->nick, "\001HELP %s\001", supp->str);
603 g_string_free(supp, TRUE);
604 } else if (iu->bu && iu->bu->ic && iu->bu->ic->acc->prpl->buddy_action) {
605 iu->bu->ic->acc->prpl->buddy_action(iu->bu, ctcp[0], ctcp + 1, NULL);
606 }
607
608 return FALSE;
609 }
610
611 static const struct irc_user_funcs irc_user_im_funcs = {
612 bee_irc_user_privmsg,
613 bee_irc_user_ctcp,
614 };
615
616
617 /* IM->IRC: Groupchats */
618 const struct irc_channel_funcs irc_channel_im_chat_funcs;
619
bee_irc_chat_new(bee_t * bee,struct groupchat * c)620 static gboolean bee_irc_chat_new(bee_t *bee, struct groupchat *c)
621 {
622 irc_t *irc = bee->ui_data;
623 irc_channel_t *ic;
624 char *topic;
625 GSList *l;
626 int i;
627
628 /* Try to find a channel that expects to receive a groupchat.
629 This flag is set earlier in our current call trace. */
630 for (l = irc->channels; l; l = l->next) {
631 ic = l->data;
632 if (ic->flags & IRC_CHANNEL_CHAT_PICKME) {
633 break;
634 }
635 }
636
637 /* If we found none, just generate some stupid name. */
638 if (l == NULL) {
639 for (i = 0; i <= 999; i++) {
640 char name[16];
641 sprintf(name, "#chat_%03d", i);
642 if ((ic = irc_channel_new(irc, name))) {
643 break;
644 }
645 }
646 }
647
648 if (ic == NULL) {
649 return FALSE;
650 }
651
652 c->ui_data = ic;
653 ic->data = c;
654
655 topic = g_strdup_printf(
656 "BitlBee groupchat: \"%s\". Please keep in mind that root-commands won't work here. Have fun!",
657 c->title);
658 irc_channel_set_topic(ic, topic, irc->root);
659 g_free(topic);
660
661 return TRUE;
662 }
663
bee_irc_chat_free(bee_t * bee,struct groupchat * c)664 static gboolean bee_irc_chat_free(bee_t *bee, struct groupchat *c)
665 {
666 irc_channel_t *ic = c->ui_data;
667
668 if (ic == NULL) {
669 return FALSE;
670 }
671
672 ic->data = NULL;
673 c->ui_data = NULL;
674 irc_channel_del_user(ic, ic->irc->user, IRC_CDU_KICK, "Chatroom closed by server");
675
676 return TRUE;
677 }
678
bee_irc_chat_log(bee_t * bee,struct groupchat * c,const char * text)679 static gboolean bee_irc_chat_log(bee_t *bee, struct groupchat *c, const char *text)
680 {
681 irc_channel_t *ic = c->ui_data;
682
683 if (ic == NULL) {
684 return FALSE;
685 }
686
687 irc_channel_printf(ic, "%s", text);
688
689 return TRUE;
690 }
691
bee_irc_chat_msg(bee_t * bee,struct groupchat * c,bee_user_t * bu,const char * msg,guint32 flags,time_t sent_at)692 static gboolean bee_irc_chat_msg(bee_t *bee, struct groupchat *c, bee_user_t *bu, const char *msg, guint32 flags, time_t sent_at)
693 {
694 irc_t *irc = bee->ui_data;
695 irc_user_t *iu = flags & OPT_SELFMESSAGE ? irc->user : bu->ui_data;
696 irc_channel_t *ic = c->ui_data;
697 char *wrapped, *ts = NULL;
698
699 if (ic == NULL) {
700 return FALSE;
701 }
702
703 if (sent_at > 0 &&
704 !(irc->caps & CAP_SERVER_TIME) &&
705 set_getbool(&bee->set, "display_timestamps")) {
706 ts = irc_format_timestamp(irc, sent_at);
707 }
708
709 wrapped = word_wrap(msg, IRC_WORD_WRAP);
710 irc_send_msg_ts(iu, "PRIVMSG", ic->name, wrapped, ts, sent_at);
711 g_free(ts);
712 g_free(wrapped);
713
714 return TRUE;
715 }
716
bee_irc_chat_add_user(bee_t * bee,struct groupchat * c,bee_user_t * bu)717 static gboolean bee_irc_chat_add_user(bee_t *bee, struct groupchat *c, bee_user_t *bu)
718 {
719 irc_t *irc = bee->ui_data;
720 irc_channel_t *ic = c->ui_data;
721
722 if (ic == NULL) {
723 return FALSE;
724 }
725
726 irc_channel_add_user(ic, bu == bee->user ? irc->user : bu->ui_data);
727
728 return TRUE;
729 }
730
bee_irc_chat_remove_user(bee_t * bee,struct groupchat * c,bee_user_t * bu,const char * reason)731 static gboolean bee_irc_chat_remove_user(bee_t *bee, struct groupchat *c, bee_user_t *bu, const char *reason)
732 {
733 irc_t *irc = bee->ui_data;
734 irc_channel_t *ic = c->ui_data;
735
736 if (ic == NULL || bu == NULL) {
737 return FALSE;
738 }
739
740 /* TODO: Possible bug here: If a module removes $user here instead of just
741 using imcb_chat_free() and the channel was IRC_CHANNEL_TEMP, we get into
742 a broken state around here. */
743 irc_channel_del_user(ic, bu == bee->user ? irc->user : bu->ui_data, IRC_CDU_PART, reason);
744
745 return TRUE;
746 }
747
bee_irc_chat_topic(bee_t * bee,struct groupchat * c,const char * new,bee_user_t * bu)748 static gboolean bee_irc_chat_topic(bee_t *bee, struct groupchat *c, const char *new, bee_user_t *bu)
749 {
750 irc_channel_t *ic = c->ui_data;
751 irc_t *irc = bee->ui_data;
752 irc_user_t *iu;
753
754 if (ic == NULL) {
755 return FALSE;
756 }
757
758 if (bu == NULL) {
759 iu = irc->root;
760 } else if (bu == bee->user) {
761 iu = irc->user;
762 } else {
763 iu = bu->ui_data;
764 }
765
766 irc_channel_set_topic(ic, new, iu);
767
768 return TRUE;
769 }
770
bee_irc_chat_name_hint(bee_t * bee,struct groupchat * c,const char * name)771 static gboolean bee_irc_chat_name_hint(bee_t *bee, struct groupchat *c, const char *name)
772 {
773 return irc_channel_name_hint(c->ui_data, name);
774 }
775
bee_irc_chat_invite(bee_t * bee,bee_user_t * bu,const char * name,const char * msg)776 static gboolean bee_irc_chat_invite(bee_t *bee, bee_user_t *bu, const char *name, const char *msg)
777 {
778 char *channel, *s;
779 irc_t *irc = bee->ui_data;
780 irc_user_t *iu = bu->ui_data;
781 irc_channel_t *chan;
782
783 if (strchr(CTYPES, name[0])) {
784 channel = g_strdup(name);
785 } else {
786 channel = g_strdup_printf("#%s", name);
787 }
788
789 if ((s = strchr(channel, '@'))) {
790 *s = '\0';
791 }
792
793 if (strlen(channel) > MAX_NICK_LENGTH) {
794 /* If the channel name is very long (like those insane GTalk
795 UUID names), try if we can use the inviter's nick. */
796 s = g_strdup_printf("#%s", iu->nick);
797 if (irc_channel_by_name(irc, s) == NULL) {
798 g_free(channel);
799 channel = s;
800 } else {
801 g_free(s);
802 }
803 }
804
805 if ((chan = irc_channel_new(irc, channel)) &&
806 set_setstr(&chan->set, "type", "chat") &&
807 set_setstr(&chan->set, "chat_type", "room") &&
808 set_setstr(&chan->set, "account", bu->ic->acc->tag) &&
809 set_setstr(&chan->set, "room", (char *) name)) {
810 /* I'm assuming that if the user didn't "chat add" the room
811 himself but got invited, it's temporary, so make this a
812 temporary mapping that is removed as soon as we /PART. */
813 chan->flags |= IRC_CHANNEL_TEMP;
814 } else {
815 irc_channel_free(chan);
816 chan = NULL;
817 }
818 g_free(channel);
819
820 irc_send_msg_f(iu, "PRIVMSG", irc->user->nick, "<< \002BitlBee\002 - Invitation to chatroom %s >>", name);
821 if (msg) {
822 irc_send_msg(iu, "PRIVMSG", irc->user->nick, msg, NULL);
823 }
824 if (chan) {
825 irc_send_msg_f(iu, "PRIVMSG", irc->user->nick, "To join the room, just /join %s", chan->name);
826 irc_send_invite(iu, chan);
827 }
828
829 return TRUE;
830 }
831
832 /* IRC->IM */
833 static gboolean bee_irc_channel_chat_privmsg_cb(gpointer data, gint fd, b_input_condition cond);
834
bee_irc_channel_chat_privmsg(irc_channel_t * ic,const char * msg)835 static gboolean bee_irc_channel_chat_privmsg(irc_channel_t *ic, const char *msg)
836 {
837 struct groupchat *c = ic->data;
838 char *trans = NULL, *s;
839
840 if (c == NULL) {
841 return FALSE;
842 }
843
844 if (set_getbool(&ic->set, "translate_to_nicks")) {
845 char nick[MAX_NICK_LENGTH + 1];
846 irc_user_t *iu;
847
848 strncpy(nick, msg, MAX_NICK_LENGTH);
849 nick[MAX_NICK_LENGTH] = '\0';
850 if ((s = strchr(nick, ':')) || (s = strchr(nick, ','))) {
851 *s = '\0';
852 if ((iu = irc_user_by_name(ic->irc, nick)) && iu->bu &&
853 iu->bu->nick && irc_channel_has_user(ic, iu)) {
854 trans = g_strconcat(iu->bu->nick, msg + (s - nick), NULL);
855 msg = trans;
856 }
857 }
858 }
859
860 if (set_getbool(&ic->irc->b->set, "paste_buffer")) {
861 int delay;
862
863 if (ic->pastebuf == NULL) {
864 ic->pastebuf = g_string_new(msg);
865 } else {
866 b_event_remove(ic->pastebuf_timer);
867 g_string_append_printf(ic->pastebuf, "\n%s", msg);
868 }
869
870 if ((delay = set_getint(&ic->irc->b->set, "paste_buffer_delay")) <= 5) {
871 delay *= 1000;
872 }
873
874 ic->pastebuf_timer = b_timeout_add(delay, bee_irc_channel_chat_privmsg_cb, ic);
875
876 g_free(trans);
877 return TRUE;
878 } else {
879 bee_chat_msg(ic->irc->b, c, msg, 0);
880 }
881
882 g_free(trans);
883 return TRUE;
884 }
885
bee_irc_channel_chat_privmsg_cb(gpointer data,gint fd,b_input_condition cond)886 static gboolean bee_irc_channel_chat_privmsg_cb(gpointer data, gint fd, b_input_condition cond)
887 {
888 irc_channel_t *ic = data;
889
890 if (ic->data) {
891 bee_chat_msg(ic->irc->b, ic->data, ic->pastebuf->str, 0);
892 }
893
894 g_string_free(ic->pastebuf, TRUE);
895 ic->pastebuf = 0;
896 ic->pastebuf_timer = 0;
897
898 return FALSE;
899 }
900
bee_irc_channel_chat_join(irc_channel_t * ic)901 static gboolean bee_irc_channel_chat_join(irc_channel_t *ic)
902 {
903 char *acc_s, *room;
904 account_t *acc;
905
906 if (strcmp(set_getstr(&ic->set, "chat_type"), "room") != 0) {
907 return TRUE;
908 }
909
910 if ((acc_s = set_getstr(&ic->set, "account")) &&
911 (room = set_getstr(&ic->set, "room")) &&
912 (acc = account_get(ic->irc->b, acc_s)) &&
913 acc->ic && (acc->ic->flags & OPT_LOGGED_IN) &&
914 acc->prpl->chat_join) {
915 char *nick;
916 struct groupchat *gc;
917
918 if (!(nick = set_getstr(&ic->set, "nick"))) {
919 nick = ic->irc->user->nick;
920 }
921
922 ic->flags |= IRC_CHANNEL_CHAT_PICKME;
923 gc = acc->prpl->chat_join(acc->ic, room, nick, NULL, &ic->set);
924 ic->flags &= ~IRC_CHANNEL_CHAT_PICKME;
925
926 if (!gc) {
927 irc_send_num(ic->irc, 403, "%s :Error joining channel (check control channel?)", ic->name);
928 }
929
930 return FALSE;
931 } else {
932 irc_send_num(ic->irc, 403, "%s :Can't join channel, account offline?", ic->name);
933 return FALSE;
934 }
935 }
936
bee_irc_channel_chat_part(irc_channel_t * ic,const char * msg)937 static gboolean bee_irc_channel_chat_part(irc_channel_t *ic, const char *msg)
938 {
939 struct groupchat *c = ic->data;
940
941 if (c && c->ic->acc->prpl->chat_leave) {
942 c->ic->acc->prpl->chat_leave(c);
943 }
944
945 if (!(ic->flags & IRC_CHANNEL_TEMP)) {
946 /* Remove the reference.
947 * We only need it for temp channels that are being freed */
948 ic->data = NULL;
949 }
950
951 return TRUE;
952 }
953
bee_irc_channel_chat_topic(irc_channel_t * ic,const char * new)954 static gboolean bee_irc_channel_chat_topic(irc_channel_t *ic, const char *new)
955 {
956 struct groupchat *c = ic->data;
957
958 if (c == NULL) {
959 return FALSE;
960 }
961
962 if (c->ic->acc->prpl->chat_topic == NULL) {
963 irc_send_num(ic->irc, 482, "%s :IM network does not support channel topics", ic->name);
964 } else {
965 /* TODO: Need more const goodness here, sigh */
966 char *topic = g_strdup(new);
967 c->ic->acc->prpl->chat_topic(c, topic);
968 g_free(topic);
969 }
970
971 /* Whatever happened, the IM module should ack the topic change. */
972 return FALSE;
973 }
974
bee_irc_channel_chat_invite(irc_channel_t * ic,irc_user_t * iu)975 static gboolean bee_irc_channel_chat_invite(irc_channel_t *ic, irc_user_t *iu)
976 {
977 struct groupchat *c = ic->data;
978 bee_user_t *bu = iu->bu;
979
980 if (bu == NULL) {
981 return FALSE;
982 }
983
984 if (c) {
985 if (iu->bu->ic != c->ic) {
986 irc_send_num(ic->irc, 482, "%s :Can't mix different IM networks in one groupchat", ic->name);
987 } else if (c->ic->acc->prpl->chat_invite) {
988 c->ic->acc->prpl->chat_invite(c, iu->bu->handle, NULL);
989 } else {
990 irc_send_num(ic->irc, 482, "%s :IM protocol does not support room invitations", ic->name);
991 }
992 } else if (bu->ic->acc->prpl->chat_with &&
993 strcmp(set_getstr(&ic->set, "chat_type"), "groupchat") == 0) {
994 ic->flags |= IRC_CHANNEL_CHAT_PICKME;
995 iu->bu->ic->acc->prpl->chat_with(bu->ic, bu->handle);
996 ic->flags &= ~IRC_CHANNEL_CHAT_PICKME;
997 } else {
998 irc_send_num(ic->irc, 482, "%s :IM protocol does not support room invitations", ic->name);
999 }
1000
1001 return TRUE;
1002 }
1003
bee_irc_channel_chat_kick(irc_channel_t * ic,irc_user_t * iu,const char * msg)1004 static void bee_irc_channel_chat_kick(irc_channel_t *ic, irc_user_t *iu, const char *msg)
1005 {
1006 struct groupchat *c = ic->data;
1007 bee_user_t *bu = iu->bu;
1008
1009 if ((c == NULL) || (bu == NULL)) {
1010 return;
1011 }
1012
1013 if (!c->ic->acc->prpl->chat_kick) {
1014 irc_send_num(ic->irc, 482, "%s :IM protocol does not support room kicking", ic->name);
1015 return;
1016 }
1017
1018 c->ic->acc->prpl->chat_kick(c, iu->bu->handle, msg);
1019 }
1020
1021 static char *set_eval_room_account(set_t *set, char *value);
1022 static char *set_eval_chat_type(set_t *set, char *value);
1023
bee_irc_channel_init(irc_channel_t * ic)1024 static gboolean bee_irc_channel_init(irc_channel_t *ic)
1025 {
1026 set_t *s;
1027
1028 set_add(&ic->set, "account", NULL, set_eval_room_account, ic);
1029 set_add(&ic->set, "chat_type", "groupchat", set_eval_chat_type, ic);
1030
1031 s = set_add(&ic->set, "nick", NULL, NULL, ic);
1032 s->flags |= SET_NULL_OK;
1033
1034 set_add(&ic->set, "room", NULL, NULL, ic);
1035 set_add(&ic->set, "translate_to_nicks", "true", set_eval_bool, ic);
1036
1037 /* chat_type == groupchat */
1038 ic->flags |= IRC_CHANNEL_TEMP;
1039
1040 return TRUE;
1041 }
1042
set_eval_room_account(set_t * set,char * value)1043 static char *set_eval_room_account(set_t *set, char *value)
1044 {
1045 struct irc_channel *ic = set->data;
1046 account_t *acc, *oa;
1047
1048 if (!(acc = account_get(ic->irc->b, value))) {
1049 return SET_INVALID;
1050 } else if (!acc->prpl->chat_join && acc->prpl != &protocol_missing) {
1051 irc_rootmsg(ic->irc, "Named chatrooms not supported on that account.");
1052 return SET_INVALID;
1053 }
1054
1055 if (set->value && (oa = account_get(ic->irc->b, set->value)) &&
1056 oa->prpl->chat_free_settings) {
1057 oa->prpl->chat_free_settings(oa, &ic->set);
1058 }
1059
1060 if (acc->prpl->chat_add_settings) {
1061 acc->prpl->chat_add_settings(acc, &ic->set);
1062 }
1063
1064 return g_strdup(acc->tag);
1065 }
1066
set_eval_chat_type(set_t * set,char * value)1067 static char *set_eval_chat_type(set_t *set, char *value)
1068 {
1069 struct irc_channel *ic = set->data;
1070
1071 if (strcmp(value, "groupchat") == 0) {
1072 ic->flags |= IRC_CHANNEL_TEMP;
1073 } else if (strcmp(value, "room") == 0) {
1074 ic->flags &= ~IRC_CHANNEL_TEMP;
1075 } else {
1076 return NULL;
1077 }
1078
1079 return value;
1080 }
1081
bee_irc_channel_free(irc_channel_t * ic)1082 static gboolean bee_irc_channel_free(irc_channel_t *ic)
1083 {
1084 struct groupchat *c = ic->data;
1085
1086 set_del(&ic->set, "account");
1087 set_del(&ic->set, "chat_type");
1088 set_del(&ic->set, "nick");
1089 set_del(&ic->set, "room");
1090 set_del(&ic->set, "translate_to_nicks");
1091
1092 ic->flags &= ~IRC_CHANNEL_TEMP;
1093
1094 /* That one still points at this channel. Don't. */
1095 if (c) {
1096 c->ui_data = NULL;
1097 }
1098
1099 return TRUE;
1100 }
1101
1102 const struct irc_channel_funcs irc_channel_im_chat_funcs = {
1103 bee_irc_channel_chat_privmsg,
1104 bee_irc_channel_chat_join,
1105 bee_irc_channel_chat_part,
1106 bee_irc_channel_chat_topic,
1107 bee_irc_channel_chat_invite,
1108 bee_irc_channel_chat_kick,
1109
1110 bee_irc_channel_init,
1111 bee_irc_channel_free,
1112 };
1113
1114
1115 /* IM->IRC: File transfers */
bee_irc_ft_in_start(bee_t * bee,bee_user_t * bu,const char * file_name,size_t file_size)1116 static file_transfer_t *bee_irc_ft_in_start(bee_t *bee, bee_user_t *bu, const char *file_name, size_t file_size)
1117 {
1118 return dccs_send_start(bu->ic, (irc_user_t *) bu->ui_data, file_name, file_size);
1119 }
1120
bee_irc_ft_out_start(struct im_connection * ic,file_transfer_t * ft)1121 static gboolean bee_irc_ft_out_start(struct im_connection *ic, file_transfer_t *ft)
1122 {
1123 return dccs_recv_start(ft);
1124 }
1125
bee_irc_ft_close(struct im_connection * ic,file_transfer_t * ft)1126 static void bee_irc_ft_close(struct im_connection *ic, file_transfer_t *ft)
1127 {
1128 return dcc_close(ft);
1129 }
1130
bee_irc_ft_finished(struct im_connection * ic,file_transfer_t * file)1131 static void bee_irc_ft_finished(struct im_connection *ic, file_transfer_t *file)
1132 {
1133 dcc_file_transfer_t *df = file->priv;
1134
1135 if (file->bytes_transferred >= file->file_size) {
1136 dcc_finish(file);
1137 } else {
1138 df->proto_finished = TRUE;
1139 }
1140 }
1141
bee_irc_log(bee_t * bee,const char * tag,const char * msg)1142 static void bee_irc_log(bee_t *bee, const char *tag, const char *msg)
1143 {
1144 irc_t *irc = (irc_t *) bee->ui_data;
1145
1146 irc_rootmsg(irc, "%s - %s", tag, msg);
1147 }
1148
1149 const struct bee_ui_funcs irc_ui_funcs = {
1150 bee_irc_imc_connected,
1151 bee_irc_imc_disconnected,
1152
1153 bee_irc_user_new,
1154 bee_irc_user_free,
1155 bee_irc_user_fullname,
1156 bee_irc_user_nick_hint,
1157 bee_irc_user_group,
1158 bee_irc_user_status,
1159 bee_irc_user_msg,
1160 bee_irc_user_typing,
1161 bee_irc_user_action_response,
1162
1163 bee_irc_chat_new,
1164 bee_irc_chat_free,
1165 bee_irc_chat_log,
1166 bee_irc_chat_msg,
1167 bee_irc_chat_add_user,
1168 bee_irc_chat_remove_user,
1169 bee_irc_chat_topic,
1170 bee_irc_chat_name_hint,
1171 bee_irc_chat_invite,
1172
1173 bee_irc_ft_in_start,
1174 bee_irc_ft_out_start,
1175 bee_irc_ft_close,
1176 bee_irc_ft_finished,
1177
1178 bee_irc_log,
1179 bee_irc_user_nick_change,
1180 };
1181