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