libpurple/protocols/irc/irc.c

changeset 37667
4523af5dc59c
parent 37523
02346b6472b3
child 37668
a658571d344d
equal deleted inserted replaced
37666:08ec2e6e0174 37667:4523af5dc59c
30 #include "conversation.h" 30 #include "conversation.h"
31 #include "debug.h" 31 #include "debug.h"
32 #include "notify.h" 32 #include "notify.h"
33 #include "protocol.h" 33 #include "protocol.h"
34 #include "plugins.h" 34 #include "plugins.h"
35 #include "tls-certificate.h"
35 #include "util.h" 36 #include "util.h"
36 #include "version.h" 37 #include "version.h"
37 38
38 #include "irc.h" 39 #include "irc.h"
39 40
44 static const char *irc_blist_icon(PurpleAccount *a, PurpleBuddy *b); 45 static const char *irc_blist_icon(PurpleAccount *a, PurpleBuddy *b);
45 static GList *irc_status_types(PurpleAccount *account); 46 static GList *irc_status_types(PurpleAccount *account);
46 static GList *irc_get_actions(PurpleConnection *gc); 47 static GList *irc_get_actions(PurpleConnection *gc);
47 /* static GList *irc_chat_info(PurpleConnection *gc); */ 48 /* static GList *irc_chat_info(PurpleConnection *gc); */
48 static void irc_login(PurpleAccount *account); 49 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(GObject *source, GAsyncResult *res, gpointer user_data);
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); 51 static void irc_close(PurpleConnection *gc);
53 static int irc_im_send(PurpleConnection *gc, PurpleMessage *msg); 52 static int irc_im_send(PurpleConnection *gc, PurpleMessage *msg);
54 static int irc_chat_send(PurpleConnection *gc, int id, PurpleMessage *msg); 53 static int irc_chat_send(PurpleConnection *gc, int id, PurpleMessage *msg);
55 static void irc_chat_join (PurpleConnection *gc, GHashTable *data); 54 static void irc_chat_join (PurpleConnection *gc, GHashTable *data);
56 static void irc_input_cb(gpointer data, gint source, PurpleInputCondition cond); 55 static void irc_read_input(struct irc_conn *irc);
57 static void irc_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
58 56
59 static guint irc_nick_hash(const char *nick); 57 static guint irc_nick_hash(const char *nick);
60 static gboolean irc_nick_equal(const char *nick1, const char *nick2); 58 static gboolean irc_nick_equal(const char *nick1, const char *nick2);
61 static void irc_buddy_free(struct irc_buddy *ib); 59 static void irc_buddy_free(struct irc_buddy *ib);
62 60
85 purple_notify_formatted(gc, title, title, NULL, body, NULL, NULL); 83 purple_notify_formatted(gc, title, title, NULL, body, NULL, NULL);
86 g_free(title); 84 g_free(title);
87 g_free(body); 85 g_free(body);
88 } 86 }
89 87
90 static int do_send(struct irc_conn *irc, const char *buf, gsize len)
91 {
92 int ret;
93
94 if (irc->gsc) {
95 ret = purple_ssl_write(irc->gsc, buf, len);
96 } else {
97 ret = write(irc->fd, buf, len);
98 }
99
100 return ret;
101 }
102
103 static int irc_send_raw(PurpleConnection *gc, const char *buf, int len) 88 static int irc_send_raw(PurpleConnection *gc, const char *buf, int len)
104 { 89 {
105 struct irc_conn *irc = purple_connection_get_protocol_data(gc); 90 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
106 if (len == -1) { 91 if (len == -1) {
107 len = strlen(buf); 92 len = strlen(buf);
109 irc_send_len(irc, buf, len); 94 irc_send_len(irc, buf, len);
110 return len; 95 return len;
111 } 96 }
112 97
113 static void 98 static void
114 irc_send_cb(gpointer data, gint source, PurpleInputCondition cond) 99 irc_flush_cb(GObject *source, GAsyncResult *res, gpointer data)
115 { 100 {
116 struct irc_conn *irc = data; 101 PurpleConnection *gc = data;
117 int ret, writelen; 102 gboolean result;
118 const gchar *buffer = NULL; 103 GError *error = NULL;
119 104
120 writelen = purple_circular_buffer_get_max_read(irc->outbuf); 105 result = g_output_stream_flush_finish(G_OUTPUT_STREAM(source),
121 106 res, &error);
122 if (writelen == 0) { 107
123 purple_input_remove(irc->writeh); 108 if (!result) {
124 irc->writeh = 0; 109 purple_connection_g_error(gc, error,
125 return; 110 _("Lost connection with server: %s"));
126 } 111 g_clear_error(&error);
127 112 return;
128 buffer = purple_circular_buffer_get_output(irc->outbuf); 113 }
129
130 ret = do_send(irc, buffer, writelen);
131
132 if (ret < 0 && errno == EAGAIN)
133 return;
134 else if (ret <= 0) {
135 PurpleConnection *gc = purple_account_get_connection(irc->account);
136 gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
137 g_strerror(errno));
138 purple_connection_error (gc,
139 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
140 g_free(tmp);
141 return;
142 }
143
144 purple_circular_buffer_mark_read(irc->outbuf, ret);
145
146 #if 0
147 /* We *could* try to write more if we wrote it all */
148 if (ret == write_len) {
149 irc_send_cb(data, source, cond);
150 }
151 #endif
152 } 114 }
153 115
154 int irc_send(struct irc_conn *irc, const char *buf) 116 int irc_send(struct irc_conn *irc, const char *buf)
155 { 117 {
156 return irc_send_len(irc, buf, strlen(buf)); 118 return irc_send_len(irc, buf, strlen(buf));
157 } 119 }
158 120
159 int irc_send_len(struct irc_conn *irc, const char *buf, int buflen) 121 int irc_send_len(struct irc_conn *irc, const char *buf, int buflen)
160 { 122 {
161 int ret;
162 char *tosend= g_strdup(buf); 123 char *tosend= g_strdup(buf);
124 GBytes *data;
163 125
164 purple_signal_emit(_irc_protocol, "irc-sending-text", purple_account_get_connection(irc->account), &tosend); 126 purple_signal_emit(_irc_protocol, "irc-sending-text", purple_account_get_connection(irc->account), &tosend);
165 127
166 if (tosend == NULL) 128 if (tosend == NULL)
167 return 0; 129 return 0;
168 130
169 /* If we're not buffering writes, try to send immediately */ 131 data = g_bytes_new_take(tosend, strlen(tosend));
170 if (!irc->writeh) 132 purple_queued_output_stream_push_bytes(irc->output, data);
171 ret = do_send(irc, tosend, buflen); 133 g_bytes_unref(data);
172 else { 134
173 ret = -1; 135 if (!g_output_stream_has_pending(G_OUTPUT_STREAM(irc->output))) {
174 errno = EAGAIN; 136 /* Connection idle. Flush data. */
175 } 137 g_output_stream_flush_async(G_OUTPUT_STREAM(irc->output),
176 138 G_PRIORITY_DEFAULT, irc->cancellable,
177 /* purple_debug(PURPLE_DEBUG_MISC, "irc", "sent%s: %s", 139 irc_flush_cb,
178 irc->gsc ? " (ssl)" : "", tosend); */ 140 purple_account_get_connection(irc->account));
179 if (ret <= 0 && errno != EAGAIN) { 141 }
180 PurpleConnection *gc = purple_account_get_connection(irc->account); 142
181 gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"), 143 return strlen(tosend);
182 g_strerror(errno));
183 purple_connection_error (gc,
184 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
185 g_free(tmp);
186 } else if (ret < buflen) {
187 if (ret < 0)
188 ret = 0;
189 if (!irc->writeh)
190 irc->writeh = purple_input_add(
191 irc->gsc ? irc->gsc->fd : irc->fd,
192 PURPLE_INPUT_WRITE, irc_send_cb, irc);
193 purple_circular_buffer_append(irc->outbuf, tosend + ret,
194 buflen - ret);
195 }
196 g_free(tosend);
197 return ret;
198 } 144 }
199 145
200 /* XXX I don't like messing directly with these buddies */ 146 /* XXX I don't like messing directly with these buddies */
201 gboolean irc_blist_timeout(struct irc_conn *irc) 147 gboolean irc_blist_timeout(struct irc_conn *irc)
202 { 148 {
335 { 281 {
336 PurpleConnection *gc; 282 PurpleConnection *gc;
337 struct irc_conn *irc; 283 struct irc_conn *irc;
338 char **userparts; 284 char **userparts;
339 const char *username = purple_account_get_username(account); 285 const char *username = purple_account_get_username(account);
286 GSocketClient *client;
287 GProxyResolver *resolver;
340 288
341 gc = purple_account_get_connection(account); 289 gc = purple_account_get_connection(account);
342 purple_connection_set_flags(gc, PURPLE_CONNECTION_FLAG_NO_NEWLINES | 290 purple_connection_set_flags(gc, PURPLE_CONNECTION_FLAG_NO_NEWLINES |
343 PURPLE_CONNECTION_FLAG_NO_IMAGES); 291 PURPLE_CONNECTION_FLAG_NO_IMAGES);
344 292
349 return; 297 return;
350 } 298 }
351 299
352 irc = g_new0(struct irc_conn, 1); 300 irc = g_new0(struct irc_conn, 1);
353 purple_connection_set_protocol_data(gc, irc); 301 purple_connection_set_protocol_data(gc, irc);
354 irc->fd = -1;
355 irc->account = account; 302 irc->account = account;
356 irc->outbuf = purple_circular_buffer_new(512); 303 irc->cancellable = g_cancellable_new();
357 304
358 userparts = g_strsplit(username, "@", 2); 305 userparts = g_strsplit(username, "@", 2);
359 purple_connection_set_display_name(gc, userparts[0]); 306 purple_connection_set_display_name(gc, userparts[0]);
360 irc->server = g_strdup(userparts[1]); 307 irc->server = g_strdup(userparts[1]);
361 g_strfreev(userparts); 308 g_strfreev(userparts);
367 irc->msgs = g_hash_table_new(g_str_hash, g_str_equal); 314 irc->msgs = g_hash_table_new(g_str_hash, g_str_equal);
368 irc_msg_table_build(irc); 315 irc_msg_table_build(irc);
369 316
370 purple_connection_update_progress(gc, _("Connecting"), 1, 2); 317 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
371 318
319 if ((resolver = purple_proxy_get_proxy_resolver(account)) == NULL) {
320 /* Invalid proxy settings */
321 purple_connection_error (gc,
322 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
323 _("Unable to connect"));
324 return;
325 }
326
327 client = g_socket_client_new();
328 g_socket_client_set_proxy_resolver(client, resolver);
329 g_object_unref(resolver);
330
331 /* Optionally use TLS if it's set in the account settings */
372 if (purple_account_get_bool(account, "ssl", FALSE)) { 332 if (purple_account_get_bool(account, "ssl", FALSE)) {
373 irc->gsc = purple_ssl_connect(account, irc->server, 333 g_socket_client_set_tls(client, TRUE);
374 purple_account_get_int(account, "port", IRC_DEFAULT_SSL_PORT), 334 purple_tls_certificate_attach_to_socket_client(client);
375 irc_login_cb_ssl, irc_ssl_connect_failure, gc); 335 }
376 } 336
377 337 g_socket_client_connect_to_host_async(client, irc->server,
378 if (!irc->gsc) { 338 purple_account_get_int(account, "port",
379 339 g_socket_client_get_tls(client) ?
380 if (purple_proxy_connect(gc, account, irc->server, 340 IRC_DEFAULT_SSL_PORT :
381 purple_account_get_int(account, "port", IRC_DEFAULT_PORT), 341 IRC_DEFAULT_PORT),
382 irc_login_cb, gc) == NULL) 342 irc->cancellable, irc_login_cb, gc);
383 { 343 g_object_unref(client);
384 purple_connection_error (gc,
385 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
386 _("Unable to connect"));
387 return;
388 }
389 }
390 } 344 }
391 345
392 static gboolean do_login(PurpleConnection *gc) { 346 static gboolean do_login(PurpleConnection *gc) {
393 char *buf, *tmp = NULL; 347 char *buf, *tmp = NULL;
394 char *server; 348 char *server;
456 irc->recv_time = time(NULL); 410 irc->recv_time = time(NULL);
457 411
458 return TRUE; 412 return TRUE;
459 } 413 }
460 414
461 static void irc_login_cb_ssl(gpointer data, PurpleSslConnection *gsc, 415 static void
462 PurpleInputCondition cond) 416 irc_login_cb(GObject *source, GAsyncResult *res, gpointer user_data)
463 { 417 {
464 PurpleConnection *gc = data; 418 PurpleConnection *gc = user_data;
419 GSocketConnection *conn;
420 GError *error = NULL;
421 struct irc_conn *irc;
422
423 conn = g_socket_client_connect_to_host_finish(G_SOCKET_CLIENT(source),
424 res, &error);
425
426 if (conn == NULL) {
427 purple_connection_g_error(gc, error,
428 _("Unable to connect: %s"));
429 g_clear_error(&error);
430 return;
431 }
432
433 irc = purple_connection_get_protocol_data(gc);
434 irc->conn = conn;
435 irc->output = purple_queued_output_stream_new(
436 g_io_stream_get_output_stream(G_IO_STREAM(irc->conn)));
465 437
466 if (do_login(gc)) { 438 if (do_login(gc)) {
467 purple_ssl_input_add(gsc, irc_input_cb_ssl, gc); 439 irc->input = g_data_input_stream_new(
468 } 440 g_io_stream_get_input_stream(
469 } 441 G_IO_STREAM(irc->conn)));
470 442 irc_read_input(irc);
471 static void irc_login_cb(gpointer data, gint source, const gchar *error_message) 443 }
472 {
473 PurpleConnection *gc = data;
474 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
475
476 if (source < 0) {
477 gchar *tmp = g_strdup_printf(_("Unable to connect: %s"),
478 error_message);
479 purple_connection_error (gc,
480 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
481 g_free(tmp);
482 return;
483 }
484
485 irc->fd = source;
486
487 if (do_login(gc)) {
488 irc->inpa = purple_input_add(irc->fd, PURPLE_INPUT_READ, irc_input_cb, gc);
489 }
490 }
491
492 static void
493 irc_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
494 gpointer data)
495 {
496 PurpleConnection *gc = data;
497 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
498
499 irc->gsc = NULL;
500
501 purple_connection_ssl_error (gc, error);
502 } 444 }
503 445
504 static void irc_close(PurpleConnection *gc) 446 static void irc_close(PurpleConnection *gc)
505 { 447 {
506 struct irc_conn *irc = purple_connection_get_protocol_data(gc); 448 struct irc_conn *irc = purple_connection_get_protocol_data(gc);
507 449
508 if (irc == NULL) 450 if (irc == NULL)
509 return; 451 return;
510 452
511 if (irc->gsc || (irc->fd >= 0)) 453 if (irc->conn != NULL)
512 irc_cmd_quit(irc, "quit", NULL, NULL); 454 irc_cmd_quit(irc, "quit", NULL, NULL);
513 455
514 if (irc->inpa) { 456 if (irc->cancellable != NULL) {
515 purple_input_remove(irc->inpa); 457 g_cancellable_cancel(irc->cancellable);
516 irc->inpa = 0; 458 g_clear_object(&irc->cancellable);
517 } 459 }
518 460
519 g_free(irc->inbuf); 461 g_clear_object(&irc->input);
520 if (irc->gsc) { 462 g_clear_object(&irc->output);
521 purple_ssl_close(irc->gsc); 463
522 } else if (irc->fd >= 0) { 464 if (irc->conn != NULL) {
523 close(irc->fd); 465 GError *error = NULL;
466
467 if (!g_io_stream_close(G_IO_STREAM(irc->conn), NULL, &error)) {
468 purple_debug_warning("irc",
469 "Error closing connection: %s",
470 error->message);
471 g_clear_error(&error);
472 }
473
474 g_clear_object(&irc->conn);
524 } 475 }
525 if (irc->timer) 476 if (irc->timer)
526 purple_timeout_remove(irc->timer); 477 purple_timeout_remove(irc->timer);
527 g_hash_table_destroy(irc->cmds); 478 g_hash_table_destroy(irc->cmds);
528 g_hash_table_destroy(irc->msgs); 479 g_hash_table_destroy(irc->msgs);
529 g_hash_table_destroy(irc->buddies); 480 g_hash_table_destroy(irc->buddies);
530 if (irc->motd) 481 if (irc->motd)
531 g_string_free(irc->motd, TRUE); 482 g_string_free(irc->motd, TRUE);
532 g_free(irc->server); 483 g_free(irc->server);
533
534 if (irc->writeh)
535 purple_input_remove(irc->writeh);
536
537 g_object_unref(G_OBJECT(irc->outbuf));
538 484
539 g_free(irc->mode_chars); 485 g_free(irc->mode_chars);
540 g_free(irc->reqnick); 486 g_free(irc->reqnick);
541 487
542 #ifdef HAVE_CYRUS_SASL 488 #ifdef HAVE_CYRUS_SASL
638 if (ib && --ib->ref == 0) { 584 if (ib && --ib->ref == 0) {
639 g_hash_table_remove(irc->buddies, purple_buddy_get_name(buddy)); 585 g_hash_table_remove(irc->buddies, purple_buddy_get_name(buddy));
640 } 586 }
641 } 587 }
642 588
643 static void read_input(struct irc_conn *irc, int len) 589 static void
644 { 590 irc_read_input_cb(GObject *source, GAsyncResult *res, gpointer data)
645 PurpleConnection *connection = purple_account_get_connection(irc->account); 591 {
646 char *cur, *end;
647
648 purple_connection_update_last_received(connection);
649 irc->inbufused += len;
650 irc->inbuf[irc->inbufused] = '\0';
651
652 cur = irc->inbuf;
653
654 /* This is a hack to work around the fact that marv gets messages
655 * with null bytes in them while using some weird irc server at work
656 */
657 while ((cur < (irc->inbuf + irc->inbufused)) && !*cur)
658 cur++;
659
660 while (cur < irc->inbuf + irc->inbufused &&
661 ((end = strstr(cur, "\r\n")) || (end = strstr(cur, "\n")))) {
662 int step = (*end == '\r' ? 2 : 1);
663 *end = '\0';
664 irc_parse_msg(irc, cur);
665 cur = end + step;
666 }
667 if (cur != irc->inbuf + irc->inbufused) { /* leftover */
668 irc->inbufused -= (cur - irc->inbuf);
669 memmove(irc->inbuf, cur, irc->inbufused);
670 } else {
671 irc->inbufused = 0;
672 }
673 }
674
675 static void irc_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
676 PurpleInputCondition cond)
677 {
678
679 PurpleConnection *gc = data; 592 PurpleConnection *gc = data;
680 struct irc_conn *irc = purple_connection_get_protocol_data(gc); 593 struct irc_conn *irc;
681 int len; 594 gchar *line;
682 595 gsize len;
683 if(!g_list_find(purple_connections_get_all(), gc)) { 596 gsize start = 0;
684 purple_ssl_close(gsc); 597 GError *error = NULL;
685 return; 598
686 } 599 line = g_data_input_stream_read_line_finish(
687 600 G_DATA_INPUT_STREAM(source), res, &len, &error);
688 if (irc->inbuflen < irc->inbufused + IRC_INITIAL_BUFSIZE) { 601
689 irc->inbuflen += IRC_INITIAL_BUFSIZE; 602 if (line == NULL && error != NULL) {
690 irc->inbuf = g_realloc(irc->inbuf, irc->inbuflen); 603 purple_connection_g_error(gc, error,
691 } 604 _("Lost connection with server: %s"));
692 605 g_clear_error(&error);
693 len = purple_ssl_read(gsc, irc->inbuf + irc->inbufused, IRC_INITIAL_BUFSIZE - 1); 606 return;
694 607 } else if (line == NULL) {
695 if (len < 0 && errno == EAGAIN) {
696 /* Try again later */
697 return;
698 } else if (len < 0) {
699 gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
700 g_strerror(errno));
701 purple_connection_error (gc,
702 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
703 g_free(tmp);
704 return;
705 } else if (len == 0) {
706 purple_connection_error (gc, 608 purple_connection_error (gc,
707 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, 609 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
708 _("Server closed the connection")); 610 _("Server closed the connection"));
709 return; 611 return;
710 } 612 }
711 613
712 read_input(irc, len); 614 irc = purple_connection_get_protocol_data(gc);
713 } 615
714 616 purple_connection_update_last_received(gc);
715 static void irc_input_cb(gpointer data, gint source, PurpleInputCondition cond) 617
716 { 618 if (len > 0 && line[len - 1] == '\r')
717 PurpleConnection *gc = data; 619 line[len - 1] = '\0';
718 struct irc_conn *irc = purple_connection_get_protocol_data(gc); 620
719 int len; 621 /* This is a hack to work around the fact that marv gets messages
720 622 * with null bytes in them while using some weird irc server at work
721 if (irc->inbuflen < irc->inbufused + IRC_INITIAL_BUFSIZE) { 623 */
722 irc->inbuflen += IRC_INITIAL_BUFSIZE; 624 while (start < len && line[start] == '\0')
723 irc->inbuf = g_realloc(irc->inbuf, irc->inbuflen); 625 ++start;
724 } 626
725 627 if (len - start > 0)
726 len = read(irc->fd, irc->inbuf + irc->inbufused, IRC_INITIAL_BUFSIZE - 1); 628 irc_parse_msg(irc, line + start);
727 if (len < 0 && errno == EAGAIN) { 629
728 return; 630 g_free(line);
729 } else if (len < 0) { 631
730 gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"), 632 irc_read_input(irc);
731 g_strerror(errno)); 633 }
732 purple_connection_error (gc, 634
733 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); 635 static void
734 g_free(tmp); 636 irc_read_input(struct irc_conn *irc)
735 return; 637 {
736 } else if (len == 0) { 638 PurpleConnection *gc = purple_account_get_connection(irc->account);
737 purple_connection_error (gc, 639
738 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, 640 g_data_input_stream_read_line_async(irc->input,
739 _("Server closed the connection")); 641 G_PRIORITY_DEFAULT, irc->cancellable,
740 return; 642 irc_read_input_cb, gc);
741 }
742
743 read_input(irc, len);
744 } 643 }
745 644
746 static void irc_chat_join (PurpleConnection *gc, GHashTable *data) 645 static void irc_chat_join (PurpleConnection *gc, GHashTable *data)
747 { 646 {
748 struct irc_conn *irc = purple_connection_get_protocol_data(gc); 647 struct irc_conn *irc = purple_connection_get_protocol_data(gc);

mercurial