1 /**
2  * @file irc.c
3  *
4  * purple
5  *
6  * Copyright (C) 2003, Robbert Haarman <purple@inglorion.net>
7  * Copyright (C) 2003, 2012 Ethan Blanton <elb@pidgin.im>
8  * Copyright (C) 2000-2003, Rob Flynn <rob@tgflinux.com>
9  * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
24  */
25 
26 #include "internal.h"
27 
28 #include "accountopt.h"
29 #include "blist.h"
30 #include "conversation.h"
31 #include "debug.h"
32 #include "notify.h"
33 #include "prpl.h"
34 #include "plugin.h"
35 #include "util.h"
36 #include "version.h"
37 
38 #include "irc.h"
39 
40 #define PING_TIMEOUT 60
41 
42 static void irc_ison_buddy_init(char *name, struct irc_buddy *ib, GList **list);
43 
44 static const char *irc_blist_icon(PurpleAccount *a, PurpleBuddy *b);
45 static GList *irc_status_types(PurpleAccount *account);
46 static GList *irc_actions(PurplePlugin *plugin, gpointer context);
47 /* static GList *irc_chat_info(PurpleConnection *gc); */
48 static void irc_login(PurpleAccount *account);
49 static void irc_login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
50 static void irc_login_cb(gpointer data, gint source, const gchar *error_message);
51 static void irc_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error, gpointer data);
52 static void irc_close(PurpleConnection *gc);
53 static int irc_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags);
54 static int irc_chat_send(PurpleConnection *gc, int id, const char *what, PurpleMessageFlags flags);
55 static void irc_chat_join (PurpleConnection *gc, GHashTable *data);
56 static void irc_input_cb(gpointer data, gint source, PurpleInputCondition cond);
57 static void irc_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
58 
59 static guint irc_nick_hash(const char *nick);
60 static gboolean irc_nick_equal(const char *nick1, const char *nick2);
61 static void irc_buddy_free(struct irc_buddy *ib);
62 
63 PurplePlugin *_irc_plugin = NULL;
64 
irc_view_motd(PurplePluginAction * action)65 static void irc_view_motd(PurplePluginAction *action)
66 {
67 	PurpleConnection *gc = (PurpleConnection *) action->context;
68 	struct irc_conn *irc;
69 	char *title, *body;
70 
71 	if (gc == NULL || gc->proto_data == NULL) {
72 		purple_debug(PURPLE_DEBUG_ERROR, "irc", "got MOTD request for NULL gc\n");
73 		return;
74 	}
75 	irc = gc->proto_data;
76 	if (irc->motd == NULL) {
77 		purple_notify_error(gc, _("Error displaying MOTD"), _("No MOTD available"),
78 				  _("There is no MOTD associated with this connection."));
79 		return;
80 	}
81 	title = g_strdup_printf(_("MOTD for %s"), irc->server);
82 	body = g_strdup_printf("<span style=\"font-family: monospace;\">%s</span>", irc->motd->str);
83 	purple_notify_formatted(gc, title, title, NULL, body, NULL, NULL);
84 	g_free(title);
85 	g_free(body);
86 }
87 
do_send(struct irc_conn * irc,const char * buf,gsize len)88 static int do_send(struct irc_conn *irc, const char *buf, gsize len)
89 {
90 	int ret;
91 
92 	if (irc->gsc) {
93 		ret = purple_ssl_write(irc->gsc, buf, len);
94 	} else {
95 		ret = write(irc->fd, buf, len);
96 	}
97 
98 	return ret;
99 }
100 
irc_send_raw(PurpleConnection * gc,const char * buf,int len)101 static int irc_send_raw(PurpleConnection *gc, const char *buf, int len)
102 {
103 	struct irc_conn *irc = (struct irc_conn*)gc->proto_data;
104 	if (len == -1) {
105 		len = strlen(buf);
106 	}
107 	irc_send_len(irc, buf, len);
108 	return len;
109 }
110 
111 static void
irc_send_cb(gpointer data,gint source,PurpleInputCondition cond)112 irc_send_cb(gpointer data, gint source, PurpleInputCondition cond)
113 {
114 	struct irc_conn *irc = data;
115 	int ret, writelen;
116 
117 	writelen = purple_circ_buffer_get_max_read(irc->outbuf);
118 
119 	if (writelen == 0) {
120 		purple_input_remove(irc->writeh);
121 		irc->writeh = 0;
122 		return;
123 	}
124 
125 	ret = do_send(irc, irc->outbuf->outptr, writelen);
126 
127 	if (ret < 0 && errno == EAGAIN)
128 		return;
129 	else if (ret <= 0) {
130 		PurpleConnection *gc = purple_account_get_connection(irc->account);
131 		gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
132 			g_strerror(errno));
133 		purple_connection_error_reason (gc,
134 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
135 		g_free(tmp);
136 		return;
137 	}
138 
139 	purple_circ_buffer_mark_read(irc->outbuf, ret);
140 
141 #if 0
142 	/* We *could* try to write more if we wrote it all */
143 	if (ret == write_len) {
144 		irc_send_cb(data, source, cond);
145 	}
146 #endif
147 }
148 
irc_send(struct irc_conn * irc,const char * buf)149 int irc_send(struct irc_conn *irc, const char *buf)
150 {
151     return irc_send_len(irc, buf, strlen(buf));
152 }
153 
irc_send_len(struct irc_conn * irc,const char * buf,int buflen)154 int irc_send_len(struct irc_conn *irc, const char *buf, int buflen)
155 {
156 	int ret;
157  	char *tosend = g_strdup(buf);
158 
159 	purple_signal_emit(_irc_plugin, "irc-sending-text", purple_account_get_connection(irc->account), &tosend);
160 
161 	if (tosend == NULL)
162 		return 0;
163 
164 	if (!purple_strequal(tosend, buf)) {
165 		buflen = strlen(tosend);
166 	}
167 
168 	if (purple_debug_is_verbose()) {
169 		char *clean = purple_utf8_salvage(tosend);
170 		clean = g_strstrip(clean);
171 		purple_debug_misc("irc", "<< %s\n", clean);
172 		g_free(clean);
173 	}
174 
175 	/* If we're not buffering writes, try to send immediately */
176 	if (!irc->writeh)
177 		ret = do_send(irc, tosend, buflen);
178 	else {
179 		ret = -1;
180 		errno = EAGAIN;
181 	}
182 
183 	/* purple_debug(PURPLE_DEBUG_MISC, "irc", "sent%s: %s",
184 		irc->gsc ? " (ssl)" : "", tosend); */
185 	if (ret <= 0 && errno != EAGAIN) {
186 		PurpleConnection *gc = purple_account_get_connection(irc->account);
187 		gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
188 			g_strerror(errno));
189 		purple_connection_error_reason (gc,
190 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
191 		g_free(tmp);
192 	} else if (ret < buflen) {
193 		if (ret < 0)
194 			ret = 0;
195 		if (!irc->writeh)
196 			irc->writeh = purple_input_add(
197 				irc->gsc ? irc->gsc->fd : irc->fd,
198 				PURPLE_INPUT_WRITE, irc_send_cb, irc);
199 		purple_circ_buffer_append(irc->outbuf, tosend + ret,
200 			buflen - ret);
201 	}
202 	g_free(tosend);
203 	return ret;
204 }
205 
206 /* XXX I don't like messing directly with these buddies */
irc_blist_timeout(struct irc_conn * irc)207 gboolean irc_blist_timeout(struct irc_conn *irc)
208 {
209 	if (irc->ison_outstanding) {
210 		return TRUE;
211 	}
212 
213 	g_hash_table_foreach(irc->buddies, (GHFunc)irc_ison_buddy_init,
214 	                     (gpointer *)&irc->buddies_outstanding);
215 
216 	irc_buddy_query(irc);
217 
218 	return TRUE;
219 }
220 
irc_buddy_query(struct irc_conn * irc)221 void irc_buddy_query(struct irc_conn *irc)
222 {
223 	GList *lp;
224 	GString *string;
225 	struct irc_buddy *ib;
226 	char *buf;
227 
228 	string = g_string_sized_new(512);
229 
230 	while ((lp = g_list_first(irc->buddies_outstanding))) {
231 		ib = (struct irc_buddy *)lp->data;
232 		if (string->len + strlen(ib->name) + 1 > 450)
233 			break;
234 		g_string_append_printf(string, "%s ", ib->name);
235 		ib->new_online_status = FALSE;
236 		irc->buddies_outstanding = g_list_remove_link(irc->buddies_outstanding, lp);
237 	}
238 
239 	if (string->len) {
240 		buf = irc_format(irc, "vn", "ISON", string->str);
241 		irc_send(irc, buf);
242 		g_free(buf);
243 		irc->ison_outstanding = TRUE;
244 	} else
245 		irc->ison_outstanding = FALSE;
246 
247 	g_string_free(string, TRUE);
248 }
249 
irc_ison_buddy_init(char * name,struct irc_buddy * ib,GList ** list)250 static void irc_ison_buddy_init(char *name, struct irc_buddy *ib, GList **list)
251 {
252 	*list = g_list_append(*list, ib);
253 }
254 
255 
irc_ison_one(struct irc_conn * irc,struct irc_buddy * ib)256 static void irc_ison_one(struct irc_conn *irc, struct irc_buddy *ib)
257 {
258 	char *buf;
259 
260 	if (irc->buddies_outstanding != NULL) {
261 		irc->buddies_outstanding = g_list_append(irc->buddies_outstanding, ib);
262 		return;
263 	}
264 
265 	ib->new_online_status = FALSE;
266 	buf = irc_format(irc, "vn", "ISON", ib->name);
267 	irc_send(irc, buf);
268 	g_free(buf);
269 }
270 
271 
irc_blist_icon(PurpleAccount * a,PurpleBuddy * b)272 static const char *irc_blist_icon(PurpleAccount *a, PurpleBuddy *b)
273 {
274 	return "irc";
275 }
276 
irc_status_types(PurpleAccount * account)277 static GList *irc_status_types(PurpleAccount *account)
278 {
279 	PurpleStatusType *type;
280 	GList *types = NULL;
281 
282 	type = purple_status_type_new(PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE);
283 	types = g_list_append(types, type);
284 
285 	type = purple_status_type_new_with_attrs(
286 		PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
287 		"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
288 		NULL);
289 	types = g_list_append(types, type);
290 
291 	type = purple_status_type_new(PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE);
292 	types = g_list_append(types, type);
293 
294 	return types;
295 }
296 
irc_actions(PurplePlugin * plugin,gpointer context)297 static GList *irc_actions(PurplePlugin *plugin, gpointer context)
298 {
299 	GList *list = NULL;
300 	PurplePluginAction *act = NULL;
301 
302 	act = purple_plugin_action_new(_("View MOTD"), irc_view_motd);
303 	list = g_list_append(list, act);
304 
305 	return list;
306 }
307 
irc_chat_join_info(PurpleConnection * gc)308 static GList *irc_chat_join_info(PurpleConnection *gc)
309 {
310 	GList *m = NULL;
311 	struct proto_chat_entry *pce;
312 
313 	pce = g_new0(struct proto_chat_entry, 1);
314 	pce->label = _("_Channel:");
315 	pce->identifier = "channel";
316 	pce->required = TRUE;
317 	m = g_list_append(m, pce);
318 
319 	pce = g_new0(struct proto_chat_entry, 1);
320 	pce->label = _("_Password:");
321 	pce->identifier = "password";
322 	pce->secret = TRUE;
323 	m = g_list_append(m, pce);
324 
325 	return m;
326 }
327 
irc_chat_info_defaults(PurpleConnection * gc,const char * chat_name)328 static GHashTable *irc_chat_info_defaults(PurpleConnection *gc, const char *chat_name)
329 {
330 	GHashTable *defaults;
331 
332 	defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
333 
334 	if (chat_name != NULL)
335 		g_hash_table_insert(defaults, "channel", g_strdup(chat_name));
336 
337 	return defaults;
338 }
339 
irc_login(PurpleAccount * account)340 static void irc_login(PurpleAccount *account)
341 {
342 	PurpleConnection *gc;
343 	struct irc_conn *irc;
344 	char **userparts;
345 	const char *username = purple_account_get_username(account);
346 
347 	gc = purple_account_get_connection(account);
348 	gc->flags |= PURPLE_CONNECTION_NO_NEWLINES;
349 
350 	if (strpbrk(username, " \t\v\r\n") != NULL) {
351 		purple_connection_error_reason (gc,
352 			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
353 			_("IRC nick and server may not contain whitespace"));
354 		return;
355 	}
356 
357 	gc->proto_data = irc = g_new0(struct irc_conn, 1);
358 	irc->fd = -1;
359 	irc->account = account;
360 	irc->outbuf = purple_circ_buffer_new(512);
361 
362 	userparts = g_strsplit(username, "@", 2);
363 	purple_connection_set_display_name(gc, userparts[0]);
364 	irc->server = g_strdup(userparts[1]);
365 	g_strfreev(userparts);
366 
367 	irc->buddies = g_hash_table_new_full((GHashFunc)irc_nick_hash, (GEqualFunc)irc_nick_equal,
368 					     NULL, (GDestroyNotify)irc_buddy_free);
369 	irc->cmds = g_hash_table_new(g_str_hash, g_str_equal);
370 	irc_cmd_table_build(irc);
371 	irc->msgs = g_hash_table_new(g_str_hash, g_str_equal);
372 	irc_msg_table_build(irc);
373 
374 	purple_connection_update_progress(gc, _("Connecting"), 1, 2);
375 
376 	if (purple_account_get_bool(account, "ssl", FALSE)) {
377 		if (purple_ssl_is_supported()) {
378 			irc->gsc = purple_ssl_connect(account, irc->server,
379 					purple_account_get_int(account, "port", IRC_DEFAULT_SSL_PORT),
380 					irc_login_cb_ssl, irc_ssl_connect_failure, gc);
381 		} else {
382 			purple_connection_error_reason (gc,
383 				PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
384 				_("SSL support unavailable"));
385 			return;
386 		}
387 	}
388 
389 	if (!irc->gsc) {
390 
391 		if (purple_proxy_connect(gc, account, irc->server,
392 				 purple_account_get_int(account, "port", IRC_DEFAULT_PORT),
393 				 irc_login_cb, gc) == NULL)
394 		{
395 			purple_connection_error_reason (gc,
396 				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
397 				_("Unable to connect"));
398 			return;
399 		}
400 	}
401 }
402 
do_login(PurpleConnection * gc)403 static gboolean do_login(PurpleConnection *gc) {
404 	char *buf, *tmp = NULL;
405 	char *server;
406 	const char *nickname, *identname, *realname;
407 	struct irc_conn *irc = gc->proto_data;
408 	const char *pass = purple_connection_get_password(gc);
409 #ifdef HAVE_CYRUS_SASL
410 	const gboolean use_sasl = purple_account_get_bool(irc->account, "sasl", FALSE);
411 #endif
412 
413 	if (pass && *pass) {
414 #ifdef HAVE_CYRUS_SASL
415 		if (use_sasl)
416 			buf = irc_format(irc, "vv:", "CAP", "REQ", "sasl");
417 		else /* intended to fall through */
418 #endif
419 			buf = irc_format(irc, "v:", "PASS", pass);
420 		if (irc_send(irc, buf) < 0) {
421 			g_free(buf);
422 			return FALSE;
423 		}
424 		g_free(buf);
425 	}
426 
427 	realname = purple_account_get_string(irc->account, "realname", "");
428 	identname = purple_account_get_string(irc->account, "username", "");
429 
430 	if (identname == NULL || *identname == '\0') {
431 		identname = g_get_user_name();
432 	}
433 
434 	if (identname != NULL && strchr(identname, ' ') != NULL) {
435 		tmp = g_strdup(identname);
436 		while ((buf = strchr(tmp, ' ')) != NULL) {
437 			*buf = '_';
438 		}
439 	}
440 
441 	if (*irc->server == ':') {
442 		/* Same as hostname, above. */
443 		server = g_strdup_printf("0%s", irc->server);
444 	} else {
445 		server = g_strdup(irc->server);
446 	}
447 
448 	buf = irc_format(irc, "vvvv:", "USER", tmp ? tmp : identname, "*", server,
449 	                 strlen(realname) ? realname : IRC_DEFAULT_ALIAS);
450 	g_free(tmp);
451 	g_free(server);
452 	if (irc_send(irc, buf) < 0) {
453 		g_free(buf);
454 		return FALSE;
455 	}
456 	g_free(buf);
457 	nickname = purple_connection_get_display_name(gc);
458 	buf = irc_format(irc, "vn", "NICK", nickname);
459 	irc->reqnick = g_strdup(nickname);
460 	irc->nickused = FALSE;
461 	if (irc_send(irc, buf) < 0) {
462 		g_free(buf);
463 		return FALSE;
464 	}
465 	g_free(buf);
466 
467 	irc->recv_time = time(NULL);
468 
469 	return TRUE;
470 }
471 
irc_login_cb_ssl(gpointer data,PurpleSslConnection * gsc,PurpleInputCondition cond)472 static void irc_login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
473 	PurpleInputCondition cond)
474 {
475 	PurpleConnection *gc = data;
476 
477 	if (do_login(gc)) {
478 		purple_ssl_input_add(gsc, irc_input_cb_ssl, gc);
479 	}
480 }
481 
irc_login_cb(gpointer data,gint source,const gchar * error_message)482 static void irc_login_cb(gpointer data, gint source, const gchar *error_message)
483 {
484 	PurpleConnection *gc = data;
485 	struct irc_conn *irc = gc->proto_data;
486 
487 	if (source < 0) {
488 		gchar *tmp = g_strdup_printf(_("Unable to connect: %s"),
489 			error_message);
490 		purple_connection_error_reason (gc,
491 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
492 		g_free(tmp);
493 		return;
494 	}
495 
496 	irc->fd = source;
497 
498 	if (do_login(gc)) {
499 		gc->inpa = purple_input_add(irc->fd, PURPLE_INPUT_READ, irc_input_cb, gc);
500 	}
501 }
502 
503 static void
irc_ssl_connect_failure(PurpleSslConnection * gsc,PurpleSslErrorType error,gpointer data)504 irc_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
505 		gpointer data)
506 {
507 	PurpleConnection *gc = data;
508 	struct irc_conn *irc = gc->proto_data;
509 
510 	irc->gsc = NULL;
511 
512 	purple_connection_ssl_error (gc, error);
513 }
514 
irc_close(PurpleConnection * gc)515 static void irc_close(PurpleConnection *gc)
516 {
517 	struct irc_conn *irc = gc->proto_data;
518 
519 	if (irc == NULL)
520 		return;
521 
522 	if (irc->gsc || (irc->fd >= 0))
523 		irc_cmd_quit(irc, "quit", NULL, NULL);
524 
525 	if (gc->inpa)
526 		purple_input_remove(gc->inpa);
527 
528 	g_free(irc->inbuf);
529 	if (irc->gsc) {
530 		purple_ssl_close(irc->gsc);
531 	} else if (irc->fd >= 0) {
532 		close(irc->fd);
533 	}
534 	if (irc->timer)
535 		purple_timeout_remove(irc->timer);
536 	g_hash_table_destroy(irc->cmds);
537 	g_hash_table_destroy(irc->msgs);
538 	g_hash_table_destroy(irc->buddies);
539 	if (irc->motd)
540 		g_string_free(irc->motd, TRUE);
541 	g_free(irc->server);
542 
543 	if (irc->writeh)
544 		purple_input_remove(irc->writeh);
545 
546 	purple_circ_buffer_destroy(irc->outbuf);
547 
548 	g_free(irc->mode_chars);
549 	g_free(irc->reqnick);
550 
551 #ifdef HAVE_CYRUS_SASL
552 	if (irc->sasl_conn) {
553 		sasl_dispose(&irc->sasl_conn);
554 		irc->sasl_conn = NULL;
555 	}
556 	g_free(irc->sasl_cb);
557 	if(irc->sasl_mechs)
558 		g_string_free(irc->sasl_mechs, TRUE);
559 #endif
560 
561 
562 	g_free(irc);
563 }
564 
irc_im_send(PurpleConnection * gc,const char * who,const char * what,PurpleMessageFlags flags)565 static int irc_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
566 {
567 	struct irc_conn *irc = gc->proto_data;
568 	char *plain;
569 	const char *args[2];
570 
571 	args[0] = irc_nick_skip_mode(irc, who);
572 
573 	purple_markup_html_to_xhtml(what, NULL, &plain);
574 	args[1] = plain;
575 
576 	irc_cmd_privmsg(irc, "msg", NULL, args);
577 	g_free(plain);
578 	return 1;
579 }
580 
irc_get_info(PurpleConnection * gc,const char * who)581 static void irc_get_info(PurpleConnection *gc, const char *who)
582 {
583 	struct irc_conn *irc = gc->proto_data;
584 	const char *args[2];
585 	args[0] = who;
586 	args[1] = NULL;
587 	irc_cmd_whois(irc, "whois", NULL, args);
588 }
589 
irc_set_status(PurpleAccount * account,PurpleStatus * status)590 static void irc_set_status(PurpleAccount *account, PurpleStatus *status)
591 {
592 	PurpleConnection *gc = purple_account_get_connection(account);
593 	struct irc_conn *irc;
594 	const char *args[1];
595 	const char *status_id = purple_status_get_id(status);
596 
597 	g_return_if_fail(gc != NULL);
598 	irc = gc->proto_data;
599 
600 	if (!purple_status_is_active(status))
601 		return;
602 
603 	args[0] = NULL;
604 
605 	if (purple_strequal(status_id, "away")) {
606 		args[0] = purple_status_get_attr_string(status, "message");
607 		if ((args[0] == NULL) || (*args[0] == '\0'))
608 			args[0] = _("Away");
609 		irc_cmd_away(irc, "away", NULL, args);
610 	} else if (purple_strequal(status_id, "available")) {
611 		irc_cmd_away(irc, "back", NULL, args);
612 	}
613 }
614 
irc_add_buddy(PurpleConnection * gc,PurpleBuddy * buddy,PurpleGroup * group)615 static void irc_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
616 {
617 	struct irc_conn *irc = (struct irc_conn *)gc->proto_data;
618 	struct irc_buddy *ib;
619 	const char *bname = purple_buddy_get_name(buddy);
620 
621 	ib = g_hash_table_lookup(irc->buddies, bname);
622 	if (ib != NULL) {
623 		ib->ref++;
624 		purple_prpl_got_user_status(irc->account, bname,
625 				ib->online ? "available" : "offline", NULL);
626 	} else {
627 		ib = g_new0(struct irc_buddy, 1);
628 		ib->name = g_strdup(bname);
629 		ib->ref = 1;
630 		g_hash_table_replace(irc->buddies, ib->name, ib);
631 	}
632 
633 	/* if the timer isn't set, this is during signon, so we don't want to flood
634 	 * ourself off with ISON's, so we don't, but after that we want to know when
635 	 * someone's online asap */
636 	if (irc->timer)
637 		irc_ison_one(irc, ib);
638 }
639 
irc_remove_buddy(PurpleConnection * gc,PurpleBuddy * buddy,PurpleGroup * group)640 static void irc_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
641 {
642 	struct irc_conn *irc = (struct irc_conn *)gc->proto_data;
643 	struct irc_buddy *ib;
644 
645 	ib = g_hash_table_lookup(irc->buddies, purple_buddy_get_name(buddy));
646 	if (ib && --ib->ref == 0) {
647 		g_hash_table_remove(irc->buddies, purple_buddy_get_name(buddy));
648 	}
649 }
650 
read_input(struct irc_conn * irc,int len)651 static void read_input(struct irc_conn *irc, int len)
652 {
653 	char *cur, *end;
654 
655 	irc->account->gc->last_received = time(NULL);
656 	irc->inbufused += len;
657 	irc->inbuf[irc->inbufused] = '\0';
658 
659 	cur = irc->inbuf;
660 
661 	/* This is a hack to work around the fact that marv gets messages
662 	 * with null bytes in them while using some weird irc server at work
663 	 */
664 	while ((cur < (irc->inbuf + irc->inbufused)) && !*cur)
665 		cur++;
666 
667 	while (cur < irc->inbuf + irc->inbufused &&
668 	       ((end = strstr(cur, "\r\n")) || (end = strstr(cur, "\n")))) {
669 		int step = (*end == '\r' ? 2 : 1);
670 		*end = '\0';
671 		irc_parse_msg(irc, cur);
672 		cur = end + step;
673 	}
674 	if (cur != irc->inbuf + irc->inbufused) { /* leftover */
675 		irc->inbufused -= (cur - irc->inbuf);
676 		memmove(irc->inbuf, cur, irc->inbufused);
677 	} else {
678 		irc->inbufused = 0;
679 	}
680 }
681 
irc_input_cb_ssl(gpointer data,PurpleSslConnection * gsc,PurpleInputCondition cond)682 static void irc_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
683 		PurpleInputCondition cond)
684 {
685 
686 	PurpleConnection *gc = data;
687 	struct irc_conn *irc = gc->proto_data;
688 	int len;
689 
690 	if(!g_list_find(purple_connections_get_all(), gc)) {
691 		purple_ssl_close(gsc);
692 		return;
693 	}
694 
695 	do {
696 		// resize buffer upwards so we have at least IRC_BUFSIZE_INCREMENT
697 		// bytes free in inbuf
698 		if (irc->inbuflen < irc->inbufused + IRC_BUFSIZE_INCREMENT) {
699 			if (irc->inbuflen + IRC_BUFSIZE_INCREMENT <= IRC_MAX_BUFSIZE) {
700 				irc->inbuflen += IRC_BUFSIZE_INCREMENT;
701 				irc->inbuf = g_realloc(irc->inbuf, irc->inbuflen);
702 			} else {
703 				// discard unparseable data from the buffer
704 				irc->inbufused = 0;
705 			}
706 		}
707 
708 		len = purple_ssl_read(gsc, irc->inbuf + irc->inbufused, irc->inbuflen - irc->inbufused - 1);
709 		if (len > 0) {
710 			read_input(irc, len);
711 		}
712 	} while (len > 0);
713 
714 	if (len < 0 && errno != EAGAIN) {
715 		gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
716 				g_strerror(errno));
717 		purple_connection_error_reason (gc,
718 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
719 		g_free(tmp);
720 	} else if (len == 0) {
721 		purple_connection_error_reason (gc,
722 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
723 			_("Server closed the connection"));
724 	}
725 
726 	/* else: len < 0 && errno == EAGAIN; this is fine, try again later */
727 }
728 
irc_input_cb(gpointer data,gint source,PurpleInputCondition cond)729 static void irc_input_cb(gpointer data, gint source, PurpleInputCondition cond)
730 {
731 	PurpleConnection *gc = data;
732 	struct irc_conn *irc = gc->proto_data;
733 	int len;
734 
735 	/* see irc_input_cb_ssl */
736 	if (irc->inbuflen < irc->inbufused + IRC_BUFSIZE_INCREMENT) {
737 		if (irc->inbuflen + IRC_BUFSIZE_INCREMENT <= IRC_MAX_BUFSIZE) {
738 			irc->inbuflen += IRC_BUFSIZE_INCREMENT;
739 			irc->inbuf = g_realloc(irc->inbuf, irc->inbuflen);
740 		} else {
741 			irc->inbufused = 0;
742 		}
743 	}
744 
745 	len = read(irc->fd, irc->inbuf + irc->inbufused, irc->inbuflen - irc->inbufused - 1);
746 
747 	if (len < 0 && errno == EAGAIN) {
748 		return;
749 	} else if (len < 0) {
750 		gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
751 				g_strerror(errno));
752 		purple_connection_error_reason (gc,
753 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
754 		g_free(tmp);
755 		return;
756 	} else if (len == 0) {
757 		purple_connection_error_reason (gc,
758 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
759 			_("Server closed the connection"));
760 		return;
761 	}
762 
763 	read_input(irc, len);
764 }
765 
irc_chat_join(PurpleConnection * gc,GHashTable * data)766 static void irc_chat_join (PurpleConnection *gc, GHashTable *data)
767 {
768 	struct irc_conn *irc = gc->proto_data;
769 	const char *args[2];
770 
771 	args[0] = g_hash_table_lookup(data, "channel");
772 	args[1] = g_hash_table_lookup(data, "password");
773 	irc_cmd_join(irc, "join", NULL, args);
774 }
775 
irc_get_chat_name(GHashTable * data)776 static char *irc_get_chat_name(GHashTable *data) {
777 	return g_strdup(g_hash_table_lookup(data, "channel"));
778 }
779 
irc_chat_invite(PurpleConnection * gc,int id,const char * message,const char * name)780 static void irc_chat_invite(PurpleConnection *gc, int id, const char *message, const char *name)
781 {
782 	struct irc_conn *irc = gc->proto_data;
783 	PurpleConversation *convo = purple_find_chat(gc, id);
784 	const char *args[2];
785 
786 	if (!convo) {
787 		purple_debug(PURPLE_DEBUG_ERROR, "irc", "Got chat invite request for bogus chat\n");
788 		return;
789 	}
790 	args[0] = name;
791 	args[1] = purple_conversation_get_name(convo);
792 	irc_cmd_invite(irc, "invite", purple_conversation_get_name(convo), args);
793 }
794 
795 
irc_chat_leave(PurpleConnection * gc,int id)796 static void irc_chat_leave (PurpleConnection *gc, int id)
797 {
798 	struct irc_conn *irc = gc->proto_data;
799 	PurpleConversation *convo = purple_find_chat(gc, id);
800 	const char *args[2];
801 
802 	if (!convo)
803 		return;
804 
805 	args[0] = purple_conversation_get_name(convo);
806 	args[1] = NULL;
807 	irc_cmd_part(irc, "part", purple_conversation_get_name(convo), args);
808 	serv_got_chat_left(gc, id);
809 }
810 
irc_chat_send(PurpleConnection * gc,int id,const char * what,PurpleMessageFlags flags)811 static int irc_chat_send(PurpleConnection *gc, int id, const char *what, PurpleMessageFlags flags)
812 {
813 	struct irc_conn *irc = gc->proto_data;
814 	PurpleConversation *convo = purple_find_chat(gc, id);
815 	const char *args[2];
816 	char *tmp;
817 
818 	if (!convo) {
819 		purple_debug(PURPLE_DEBUG_ERROR, "irc", "chat send on nonexistent chat\n");
820 		return -EINVAL;
821 	}
822 #if 0
823 	if (*what == '/') {
824 		return irc_parse_cmd(irc, convo->name, what + 1);
825 	}
826 #endif
827 	purple_markup_html_to_xhtml(what, NULL, &tmp);
828 	args[0] = convo->name;
829 	args[1] = tmp;
830 
831 	irc_cmd_privmsg(irc, "msg", NULL, args);
832 
833 	serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), flags, what, time(NULL));
834 	g_free(tmp);
835 	return 0;
836 }
837 
irc_nick_hash(const char * nick)838 static guint irc_nick_hash(const char *nick)
839 {
840 	char *lc;
841 	guint bucket;
842 
843 	lc = g_utf8_strdown(nick, -1);
844 	bucket = g_str_hash(lc);
845 	g_free(lc);
846 
847 	return bucket;
848 }
849 
irc_nick_equal(const char * nick1,const char * nick2)850 static gboolean irc_nick_equal(const char *nick1, const char *nick2)
851 {
852 	return (purple_utf8_strcasecmp(nick1, nick2) == 0);
853 }
854 
irc_buddy_free(struct irc_buddy * ib)855 static void irc_buddy_free(struct irc_buddy *ib)
856 {
857 	g_free(ib->name);
858 	g_free(ib);
859 }
860 
irc_chat_set_topic(PurpleConnection * gc,int id,const char * topic)861 static void irc_chat_set_topic(PurpleConnection *gc, int id, const char *topic)
862 {
863 	char *buf;
864 	const char *name = NULL;
865 	struct irc_conn *irc;
866 
867 	irc = gc->proto_data;
868 	name = purple_conversation_get_name(purple_find_chat(gc, id));
869 
870 	if (name == NULL)
871 		return;
872 
873 	buf = irc_format(irc, "vt:", "TOPIC", name, topic);
874 	irc_send(irc, buf);
875 	g_free(buf);
876 }
877 
irc_roomlist_get_list(PurpleConnection * gc)878 static PurpleRoomlist *irc_roomlist_get_list(PurpleConnection *gc)
879 {
880 	struct irc_conn *irc;
881 	GList *fields = NULL;
882 	PurpleRoomlistField *f;
883 	char *buf;
884 
885 	irc = gc->proto_data;
886 
887 	if (irc->roomlist)
888 		purple_roomlist_unref(irc->roomlist);
889 
890 	irc->roomlist = purple_roomlist_new(purple_connection_get_account(gc));
891 
892 	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "", "channel", TRUE);
893 	fields = g_list_append(fields, f);
894 
895 	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_INT, _("Users"), "users", FALSE);
896 	fields = g_list_append(fields, f);
897 
898 	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Topic"), "topic", FALSE);
899 	fields = g_list_append(fields, f);
900 
901 	purple_roomlist_set_fields(irc->roomlist, fields);
902 
903 	buf = irc_format(irc, "v", "LIST");
904 	irc_send(irc, buf);
905 	g_free(buf);
906 
907 	return irc->roomlist;
908 }
909 
irc_roomlist_cancel(PurpleRoomlist * list)910 static void irc_roomlist_cancel(PurpleRoomlist *list)
911 {
912 	PurpleConnection *gc = purple_account_get_connection(list->account);
913 	struct irc_conn *irc;
914 
915 	if (gc == NULL)
916 		return;
917 
918 	irc = gc->proto_data;
919 
920 	purple_roomlist_set_in_progress(list, FALSE);
921 
922 	if (irc->roomlist == list) {
923 		irc->roomlist = NULL;
924 		purple_roomlist_unref(list);
925 	}
926 }
927 
irc_keepalive(PurpleConnection * gc)928 static void irc_keepalive(PurpleConnection *gc)
929 {
930 	struct irc_conn *irc = gc->proto_data;
931 	if ((time(NULL) - irc->recv_time) > PING_TIMEOUT)
932 		irc_cmd_ping(irc, NULL, NULL, NULL);
933 }
934 
935 static PurplePluginProtocolInfo prpl_info =
936 {
937 	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_PASSWORD_OPTIONAL |
938 	OPT_PROTO_SLASH_COMMANDS_NATIVE,
939 	NULL,					/* user_splits */
940 	NULL,					/* protocol_options */
941 	NO_BUDDY_ICONS,		/* icon_spec */
942 	irc_blist_icon,		/* list_icon */
943 	NULL,			/* list_emblems */
944 	NULL,					/* status_text */
945 	NULL,					/* tooltip_text */
946 	irc_status_types,	/* away_states */
947 	NULL,					/* blist_node_menu */
948 	irc_chat_join_info,	/* chat_info */
949 	irc_chat_info_defaults,	/* chat_info_defaults */
950 	irc_login,		/* login */
951 	irc_close,		/* close */
952 	irc_im_send,		/* send_im */
953 	NULL,					/* set_info */
954 	NULL,					/* send_typing */
955 	irc_get_info,		/* get_info */
956 	irc_set_status,		/* set_status */
957 	NULL,					/* set_idle */
958 	NULL,					/* change_passwd */
959 	irc_add_buddy,		/* add_buddy */
960 	NULL,					/* add_buddies */
961 	irc_remove_buddy,	/* remove_buddy */
962 	NULL,					/* remove_buddies */
963 	NULL,					/* add_permit */
964 	NULL,					/* add_deny */
965 	NULL,					/* rem_permit */
966 	NULL,					/* rem_deny */
967 	NULL,					/* set_permit_deny */
968 	irc_chat_join,		/* join_chat */
969 	NULL,					/* reject_chat */
970 	irc_get_chat_name,	/* get_chat_name */
971 	irc_chat_invite,	/* chat_invite */
972 	irc_chat_leave,		/* chat_leave */
973 	NULL,					/* chat_whisper */
974 	irc_chat_send,		/* chat_send */
975 	irc_keepalive,		/* keepalive */
976 	NULL,					/* register_user */
977 	NULL,					/* get_cb_info */
978 	NULL,					/* get_cb_away */
979 	NULL,					/* alias_buddy */
980 	NULL,					/* group_buddy */
981 	NULL,					/* rename_group */
982 	NULL,					/* buddy_free */
983 	NULL,					/* convo_closed */
984 	purple_normalize_nocase,	/* normalize */
985 	NULL,					/* set_buddy_icon */
986 	NULL,					/* remove_group */
987 	NULL,					/* get_cb_real_name */
988 	irc_chat_set_topic,	/* set_chat_topic */
989 	NULL,					/* find_blist_chat */
990 	irc_roomlist_get_list,	/* roomlist_get_list */
991 	irc_roomlist_cancel,	/* roomlist_cancel */
992 	NULL,					/* roomlist_expand_category */
993 	NULL,					/* can_receive_file */
994 	irc_dccsend_send_file,	/* send_file */
995 	irc_dccsend_new_xfer,	/* new_xfer */
996 	NULL,					/* offline_message */
997 	NULL,					/* whiteboard_prpl_ops */
998 	irc_send_raw,			/* send_raw */
999 	NULL,					/* roomlist_room_serialize */
1000 	NULL,                   /* unregister_user */
1001 	NULL,                   /* send_attention */
1002 	NULL,                   /* get_attention_types */
1003 	sizeof(PurplePluginProtocolInfo),    /* struct_size */
1004 	NULL,                    /* get_account_text_table */
1005 	NULL,                    /* initiate_media */
1006 	NULL,					 /* get_media_caps */
1007 	NULL,					 /* get_moods */
1008 	NULL,					 /* set_public_alias */
1009 	NULL,					 /* get_public_alias */
1010 	NULL,					 /* add_buddy_with_invite */
1011 	NULL,					 /* add_buddies_with_invite */
1012 	NULL,					 /* get_cb_alias */
1013 	NULL,					 /* chat_can_receive_file */
1014 	NULL,					 /* chat_send_file */
1015 };
1016 
load_plugin(PurplePlugin * plugin)1017 static gboolean load_plugin (PurplePlugin *plugin) {
1018 
1019 	purple_signal_register(plugin, "irc-sending-text",
1020 			     purple_marshal_VOID__POINTER_POINTER, NULL, 2,
1021 			     purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
1022 			     purple_value_new_outgoing(PURPLE_TYPE_STRING));
1023 	purple_signal_register(plugin, "irc-receiving-text",
1024 			     purple_marshal_VOID__POINTER_POINTER, NULL, 2,
1025 			     purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
1026 			     purple_value_new_outgoing(PURPLE_TYPE_STRING));
1027 	return TRUE;
1028 }
1029 
1030 
1031 static PurplePluginInfo info =
1032 {
1033 	PURPLE_PLUGIN_MAGIC,
1034 	PURPLE_MAJOR_VERSION,
1035 	PURPLE_MINOR_VERSION,
1036 	PURPLE_PLUGIN_PROTOCOL,                             /**< type           */
1037 	NULL,                                             /**< ui_requirement */
1038 	0,                                                /**< flags          */
1039 	NULL,                                             /**< dependencies   */
1040 	PURPLE_PRIORITY_DEFAULT,                            /**< priority       */
1041 
1042 	"prpl-irc",                                       /**< id             */
1043 	"IRC",                                            /**< name           */
1044 	DISPLAY_VERSION,                                  /**< version        */
1045 	N_("IRC Protocol Plugin"),                        /**  summary        */
1046 	N_("The IRC Protocol Plugin that Sucks Less"),    /**  description    */
1047 	NULL,                                             /**< author         */
1048 	PURPLE_WEBSITE,                                     /**< homepage       */
1049 
1050 	load_plugin,                                      /**< load           */
1051 	NULL,                                             /**< unload         */
1052 	NULL,                                             /**< destroy        */
1053 
1054 	NULL,                                             /**< ui_info        */
1055 	&prpl_info,                                       /**< extra_info     */
1056 	NULL,                                             /**< prefs_info     */
1057 	irc_actions,
1058 
1059 	/* padding */
1060 	NULL,
1061 	NULL,
1062 	NULL,
1063 	NULL
1064 };
1065 
_init_plugin(PurplePlugin * plugin)1066 static void _init_plugin(PurplePlugin *plugin)
1067 {
1068 	PurpleAccountUserSplit *split;
1069 	PurpleAccountOption *option;
1070 
1071 	split = purple_account_user_split_new(_("Server"), IRC_DEFAULT_SERVER, '@');
1072 	prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
1073 
1074 	option = purple_account_option_int_new(_("Port"), "port", IRC_DEFAULT_PORT);
1075 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
1076 
1077 	option = purple_account_option_string_new(_("Encodings"), "encoding", IRC_DEFAULT_CHARSET);
1078 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
1079 
1080 	option = purple_account_option_bool_new(_("Auto-detect incoming UTF-8"), "autodetect_utf8", IRC_DEFAULT_AUTODETECT);
1081 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
1082 
1083 	option = purple_account_option_string_new(_("Ident name"), "username", "");
1084 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
1085 
1086 	option = purple_account_option_string_new(_("Real name"), "realname", "");
1087 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
1088 
1089 	/*
1090 	option = purple_account_option_string_new(_("Quit message"), "quitmsg", IRC_DEFAULT_QUIT);
1091 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
1092 	*/
1093 
1094 	option = purple_account_option_bool_new(_("Use SSL"), "ssl", FALSE);
1095 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
1096 
1097 #ifdef HAVE_CYRUS_SASL
1098 	option = purple_account_option_bool_new(_("Authenticate with SASL"), "sasl", FALSE);
1099 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
1100 
1101 	option = purple_account_option_bool_new(
1102 						_("Allow plaintext SASL auth over unencrypted connection"),
1103 						"auth_plain_in_clear", FALSE);
1104 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
1105 #endif
1106 
1107 	_irc_plugin = plugin;
1108 
1109 	purple_prefs_remove("/plugins/prpl/irc/quitmsg");
1110 	purple_prefs_remove("/plugins/prpl/irc");
1111 
1112 	irc_register_commands();
1113 }
1114 
1115 PURPLE_INIT_PLUGIN(irc, _init_plugin, info);
1116