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