rename accountopt.[ch] to purpleaccountoption.[ch]
[pidgin-git.git] / libpurple / protocols / irc / msgs.c
blobf50509a5612ffaac62993854410f29bc0481fa4e
1 /**
2 * purple
4 * Copyright (C) 2003, 2012 Ethan Blanton <elb@pidgin.im>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
22 * Note: If you change any of these functions to use additional args you
23 * MUST ensure the arg count is correct in parse.c. Otherwise it may be
24 * possible for a malicious server or man-in-the-middle to trigger a crash.
27 #include "internal.h"
28 #include <purple.h>
30 #include "irc.h"
32 #include <stdio.h>
33 #include <stdlib.h>
35 #ifdef HAVE_CYRUS_SASL
36 #include <sasl/sasl.h>
37 #endif
39 static char *irc_mask_nick(const char *mask);
40 static char *irc_mask_userhost(const char *mask);
41 static void irc_chat_remove_buddy(PurpleChatConversation *chat, char *data[2]);
42 static void irc_buddy_status(char *name, struct irc_buddy *ib, struct irc_conn *irc);
43 static void irc_connected(struct irc_conn *irc, const char *nick);
45 static void irc_msg_handle_privmsg(struct irc_conn *irc, const char *name,
46 const char *from, const char *to,
47 const char *rawmsg, gboolean notice);
49 #ifdef HAVE_CYRUS_SASL
50 static void irc_sasl_finish(struct irc_conn *irc);
51 #endif
53 static char *irc_mask_nick(const char *mask)
55 char *end, *buf;
57 end = strchr(mask, '!');
58 if (!end)
59 buf = g_strdup(mask);
60 else
61 buf = g_strndup(mask, end - mask);
63 return buf;
66 static char *irc_mask_userhost(const char *mask)
68 return g_strdup(strchr(mask, '!') + 1);
71 static void irc_chat_remove_buddy(PurpleChatConversation *chat, char *data[2])
73 char *message, *stripped;
75 stripped = data[1] ? irc_mirc2txt(data[1]) : NULL;
76 message = g_strdup_printf("quit: %s", stripped);
77 g_free(stripped);
79 if (purple_chat_conversation_has_user(chat, data[0]))
80 purple_chat_conversation_remove_user(chat, data[0], message);
82 g_free(message);
85 static void irc_connected(struct irc_conn *irc, const char *nick)
87 PurpleConnection *gc;
88 PurpleStatus *status;
89 GSList *buddies;
90 PurpleAccount *account;
92 if ((gc = purple_account_get_connection(irc->account)) == NULL
93 || PURPLE_CONNECTION_IS_CONNECTED(gc))
94 return;
96 purple_connection_set_display_name(gc, nick);
97 purple_connection_set_state(gc, PURPLE_CONNECTION_CONNECTED);
98 account = purple_connection_get_account(gc);
100 /* If we're away then set our away message */
101 status = purple_account_get_active_status(irc->account);
102 if (purple_status_type_get_primitive(purple_status_get_status_type(status)) != PURPLE_STATUS_AVAILABLE) {
103 PurpleProtocol *protocol = purple_connection_get_protocol(gc);
104 purple_protocol_server_iface_set_status(protocol, irc->account, status);
107 /* this used to be in the core, but it's not now */
108 for (buddies = purple_blist_find_buddies(account, NULL); buddies;
109 buddies = g_slist_delete_link(buddies, buddies))
111 PurpleBuddy *b = buddies->data;
112 struct irc_buddy *ib = g_new0(struct irc_buddy, 1);
113 ib->name = g_strdup(purple_buddy_get_name(b));
114 ib->ref = 1;
115 g_hash_table_replace(irc->buddies, ib->name, ib);
118 irc_blist_timeout(irc);
119 if (!irc->timer)
120 irc->timer = g_timeout_add_seconds(45, (GSourceFunc)irc_blist_timeout, (gpointer)irc);
123 /* This function is ugly, but it's really an error handler. */
124 void irc_msg_default(struct irc_conn *irc, const char *name, const char *from, char **args)
126 int i;
127 const char *end, *cur, *numeric = NULL;
128 char *clean, *tmp, *convname;
129 PurpleConversation *convo;
131 for (cur = args[0], i = 0; i < 4; i++) {
132 end = strchr(cur, ' ');
133 if (end == NULL) {
134 goto undirected;
136 /* Check for 3-digit numeric in second position */
137 if (i == 1) {
138 if (end - cur != 3
139 || !isdigit(cur[0]) || !isdigit(cur[1])
140 || !isdigit(cur[2])) {
141 goto undirected;
143 /* Save the numeric for printing to the channel */
144 numeric = cur;
146 /* Don't advance cur if we're on the final iteration. */
147 if (i != 3) {
148 cur = end + 1;
152 /* At this point, cur is the beginning of the fourth position,
153 * end is the following space, and there are remaining
154 * arguments. We'll check to see if this argument is a
155 * currently active conversation (private message or channel,
156 * either one), and print the numeric to that conversation if it
157 * is. */
159 tmp = g_strndup(cur, end - cur);
160 convname = purple_utf8_salvage(tmp);
161 g_free(tmp);
163 /* Check for an existing conversation */
164 convo = purple_conversations_find_with_account(convname, irc->account);
165 g_free(convname);
167 if (convo == NULL) {
168 goto undirected;
171 /* end + 1 is the first argument past the target. The initial
172 * arguments we've skipped are routing info, numeric, recipient
173 * (this account's nick, most likely), and target (this
174 * channel). If end + 1 is an ASCII :, skip it, because it's
175 * meaningless in this context. This won't catch all
176 * :-arguments, but it'll catch the easy case. */
177 if (*++end == ':') {
178 end++;
181 /* We then print "numeric: remainder". */
182 clean = purple_utf8_salvage(end);
183 tmp = g_strdup_printf("%.3s: %s", numeric, clean);
184 g_free(clean);
185 purple_conversation_write_system_message(convo, tmp,
186 PURPLE_MESSAGE_NO_LOG | PURPLE_MESSAGE_RAW |
187 PURPLE_MESSAGE_NO_LINKIFY);
188 g_free(tmp);
189 return;
191 undirected:
192 /* This, too, should be escaped somehow (smarter) */
193 clean = purple_utf8_salvage(args[0]);
194 purple_debug(PURPLE_DEBUG_INFO, "irc", "Unrecognized message: %s\n", clean);
195 g_free(clean);
198 void irc_msg_features(struct irc_conn *irc, const char *name, const char *from, char **args)
200 gchar **features;
201 int i;
203 features = g_strsplit(args[1], " ", -1);
204 for (i = 0; features[i]; i++) {
205 char *val;
206 if (!strncmp(features[i], "PREFIX=", 7)) {
207 if ((val = strchr(features[i] + 7, ')')) != NULL)
208 irc->mode_chars = g_strdup(val + 1);
212 g_strfreev(features);
215 void irc_msg_luser(struct irc_conn *irc, const char *name, const char *from, char **args)
217 if (purple_strequal(name, "251")) {
218 /* 251 is required, so we pluck our nick from here and
219 * finalize connection */
220 irc_connected(irc, args[0]);
221 /* Some IRC servers seem to not send a 255 numeric, so
222 * I guess we can't require it; 251 will do. */
223 /* } else if (purple_strequal(name, "255")) { */
227 void irc_msg_away(struct irc_conn *irc, const char *name, const char *from, char **args)
229 PurpleConnection *gc;
230 char *msg;
232 if (irc->whois.nick && !purple_utf8_strcasecmp(irc->whois.nick, args[1])) {
233 /* We're doing a whois, show this in the whois dialog */
234 irc_msg_whois(irc, name, from, args);
235 return;
238 gc = purple_account_get_connection(irc->account);
239 if (gc) {
240 msg = g_markup_escape_text(args[2], -1);
241 purple_serv_got_im(gc, args[1], msg, PURPLE_MESSAGE_AUTO_RESP, time(NULL));
242 g_free(msg);
246 void irc_msg_badmode(struct irc_conn *irc, const char *name, const char *from, char **args)
248 PurpleConnection *gc = purple_account_get_connection(irc->account);
250 g_return_if_fail(gc);
252 purple_notify_error(gc, NULL, _("Bad mode"), args[1],
253 purple_request_cpar_from_connection(gc));
256 void irc_msg_ban(struct irc_conn *irc, const char *name, const char *from, char **args)
258 PurpleChatConversation *chat;
260 chat = purple_conversations_find_chat_with_account(args[1], irc->account);
262 if (purple_strequal(name, "367")) {
263 char *msg = NULL;
264 /* Ban list entry */
265 if (args[3] && args[4]) {
266 /* This is an extended syntax, not in RFC 1459 */
267 int t1 = atoi(args[4]);
268 time_t t2 = time(NULL);
269 char *time = purple_str_seconds_to_string(t2 - t1);
270 msg = g_strdup_printf(_("Ban on %s by %s, set %s ago"),
271 args[2], args[3], time);
272 g_free(time);
273 } else {
274 msg = g_strdup_printf(_("Ban on %s"), args[2]);
276 if (chat) {
277 purple_conversation_write_system_message(
278 PURPLE_CONVERSATION(chat), msg, PURPLE_MESSAGE_NO_LOG);
279 } else {
280 purple_debug_info("irc", "%s\n", msg);
282 g_free(msg);
283 } else if (purple_strequal(name, "368")) {
284 if (!chat)
285 return;
286 /* End of ban list */
287 purple_conversation_write_system_message(PURPLE_CONVERSATION(chat),
288 _("End of ban list"), PURPLE_MESSAGE_NO_LOG);
292 void irc_msg_banned(struct irc_conn *irc, const char *name, const char *from, char **args)
294 PurpleConnection *gc = purple_account_get_connection(irc->account);
295 char *buf;
297 g_return_if_fail(gc);
299 buf = g_strdup_printf(_("You are banned from %s."), args[1]);
300 purple_notify_error(gc, _("Banned"), _("Banned"), buf,
301 purple_request_cpar_from_connection(gc));
302 g_free(buf);
305 void irc_msg_banfull(struct irc_conn *irc, const char *name, const char *from, char **args)
307 PurpleChatConversation *chat;
308 char *buf, *nick;
310 chat = purple_conversations_find_chat_with_account(args[1], irc->account);
311 if (!chat)
312 return;
314 nick = g_markup_escape_text(args[2], -1);
315 buf = g_strdup_printf(_("Cannot ban %s: banlist is full"), nick);
316 g_free(nick);
317 purple_conversation_write_system_message(PURPLE_CONVERSATION(chat),
318 buf, PURPLE_MESSAGE_NO_LOG);
319 g_free(buf);
322 void irc_msg_chanmode(struct irc_conn *irc, const char *name, const char *from, char **args)
324 PurpleChatConversation *chat;
325 char *buf, *escaped;
327 chat = purple_conversations_find_chat_with_account(args[1], irc->account);
328 if (!chat) /* XXX punt on channels we are not in for now */
329 return;
331 escaped = (args[3] != NULL) ? g_markup_escape_text(args[3], -1) : NULL;
332 buf = g_strdup_printf("mode for %s: %s %s", args[1], args[2], escaped ? escaped : "");
333 purple_conversation_write_system_message(PURPLE_CONVERSATION(chat), buf, 0);
334 g_free(escaped);
335 g_free(buf);
337 return;
340 void irc_msg_whois(struct irc_conn *irc, const char *name, const char *from, char **args)
342 if (!irc->whois.nick) {
343 purple_debug(PURPLE_DEBUG_WARNING, "irc", "Unexpected %s reply for %s\n", purple_strequal(name, "314") ? "WHOWAS" : "WHOIS"
344 , args[1]);
345 return;
348 if (purple_utf8_strcasecmp(irc->whois.nick, args[1])) {
349 purple_debug(PURPLE_DEBUG_WARNING, "irc", "Got %s reply for %s while waiting for %s\n", purple_strequal(name, "314") ? "WHOWAS" : "WHOIS"
350 , args[1], irc->whois.nick);
351 return;
354 if (purple_strequal(name, "301")) {
355 irc->whois.away = g_strdup(args[2]);
356 } else if (purple_strequal(name, "311") || purple_strequal(name, "314")) {
357 irc->whois.ident = g_strdup(args[2]);
358 irc->whois.host = g_strdup(args[3]);
359 irc->whois.real = g_strdup(args[5]);
360 } else if (purple_strequal(name, "312")) {
361 irc->whois.server = g_strdup(args[2]);
362 irc->whois.serverinfo = g_strdup(args[3]);
363 } else if (purple_strequal(name, "313")) {
364 irc->whois.ircop = 1;
365 } else if (purple_strequal(name, "317")) {
366 irc->whois.idle = atoi(args[2]);
367 if (args[3])
368 irc->whois.signon = (time_t)atoi(args[3]);
369 } else if (purple_strequal(name, "319")) {
370 if (irc->whois.channels == NULL) {
371 irc->whois.channels = g_string_new(args[2]);
372 } else {
373 irc->whois.channels = g_string_append(irc->whois.channels, args[2]);
375 } else if (purple_strequal(name, "320")) {
376 irc->whois.identified = 1;
377 } else if (purple_strequal(name, "330")) {
378 purple_debug(PURPLE_DEBUG_INFO, "irc", "330 %s: 1=[%s] 2=[%s] 3=[%s]",
379 name, args[1], args[2], args[3]);
380 if (purple_strequal(args[3], "is logged in as"))
381 irc->whois.login = g_strdup(args[2]);
385 void irc_msg_endwhois(struct irc_conn *irc, const char *name, const char *from, char **args)
387 PurpleConnection *gc;
388 char *tmp, *tmp2;
389 PurpleNotifyUserInfo *user_info;
391 if (!irc->whois.nick) {
392 purple_debug(PURPLE_DEBUG_WARNING, "irc", "Unexpected End of %s for %s\n", purple_strequal(name, "369") ? "WHOWAS" : "WHOIS"
393 , args[1]);
394 return;
396 if (purple_utf8_strcasecmp(irc->whois.nick, args[1])) {
397 purple_debug(PURPLE_DEBUG_WARNING, "irc", "Received end of %s for %s, expecting %s\n", purple_strequal(name, "369") ? "WHOWAS" : "WHOIS"
398 , args[1], irc->whois.nick);
399 return;
402 user_info = purple_notify_user_info_new();
404 tmp2 = g_markup_escape_text(args[1], -1);
405 tmp = g_strdup_printf("%s%s%s", tmp2,
406 (irc->whois.ircop ? _(" <i>(ircop)</i>") : ""),
407 (irc->whois.identified ? _(" <i>(identified)</i>") : ""));
408 purple_notify_user_info_add_pair_html(user_info, _("Nick"), tmp);
409 g_free(tmp2);
410 g_free(tmp);
412 if (irc->whois.away) {
413 purple_notify_user_info_add_pair_plaintext(user_info, _("Away"), irc->whois.away);
414 g_free(irc->whois.away);
416 if (irc->whois.real) {
417 purple_notify_user_info_add_pair_plaintext(user_info, _("Real name"), irc->whois.real);
418 g_free(irc->whois.real);
420 if (irc->whois.login) {
421 purple_notify_user_info_add_pair_plaintext(user_info, _("Login name"), irc->whois.login);
422 g_free(irc->whois.login);
424 if (irc->whois.ident) {
425 purple_notify_user_info_add_pair_plaintext(user_info, _("Ident name"), irc->whois.ident);
426 g_free(irc->whois.ident);
428 if (irc->whois.host) {
429 purple_notify_user_info_add_pair_plaintext(user_info, _("Host name"), irc->whois.host);
430 g_free(irc->whois.host);
432 if (irc->whois.server) {
433 tmp = g_strdup_printf("%s (%s)", irc->whois.server, irc->whois.serverinfo);
434 purple_notify_user_info_add_pair_plaintext(user_info, _("Server"), tmp);
435 g_free(tmp);
436 g_free(irc->whois.server);
437 g_free(irc->whois.serverinfo);
439 if (irc->whois.channels) {
440 purple_notify_user_info_add_pair_plaintext(user_info, _("Currently on"), irc->whois.channels->str);
441 g_string_free(irc->whois.channels, TRUE);
443 if (irc->whois.idle) {
444 gchar *timex = purple_str_seconds_to_string(irc->whois.idle);
445 purple_notify_user_info_add_pair_plaintext(user_info, _("Idle for"), timex);
446 g_free(timex);
447 purple_notify_user_info_add_pair_plaintext(user_info,
448 _("Online since"), purple_date_format_full(localtime(&irc->whois.signon)));
450 if (purple_strequal(irc->whois.nick, "elb")) {
451 purple_notify_user_info_add_pair_plaintext(user_info,
452 _("<b>Defining adjective:</b>"), _("Glorious"));
455 gc = purple_account_get_connection(irc->account);
457 purple_notify_userinfo(gc, irc->whois.nick, user_info, NULL, NULL);
458 purple_notify_user_info_destroy(user_info);
460 g_free(irc->whois.nick);
461 memset(&irc->whois, 0, sizeof(irc->whois));
464 void irc_msg_who(struct irc_conn *irc, const char *name, const char *from, char **args)
466 if (purple_strequal(name, "352")) {
467 PurpleChatConversation *chat;
468 PurpleChatUser *cb;
470 char *cur, *userhost, *realname;
472 PurpleChatUserFlags flags;
474 chat = purple_conversations_find_chat_with_account(args[1], irc->account);
475 if (!chat) {
476 purple_debug(PURPLE_DEBUG_ERROR, "irc","Got a WHO response for %s, which doesn't exist\n", args[1]);
477 return;
480 cb = purple_chat_conversation_find_user(chat, args[5]);
481 if (!cb) {
482 purple_debug(PURPLE_DEBUG_ERROR, "irc", "Got a WHO response for %s who isn't a buddy.\n", args[5]);
483 return;
486 userhost = g_strdup_printf("%s@%s", args[2], args[3]);
488 /* The final argument is a :-argument, but annoyingly
489 * contains two "words", the hop count and real name. */
490 for (cur = args[7]; *cur; cur++) {
491 if (*cur == ' ') {
492 cur++;
493 break;
496 realname = g_strdup(cur);
498 g_object_set_data_full(G_OBJECT(cb), "userhost", userhost, (GDestroyNotify)g_free);
499 g_object_set_data_full(G_OBJECT(cb), "realname", realname, (GDestroyNotify)g_free);
501 flags = purple_chat_user_get_flags(cb);
503 /* FIXME: I'm not sure this is really a good idea, now
504 * that we no longer do periodic WHO. It seems to me
505 * like it's more likely to be confusing than not.
506 * Comments? */
507 if (args[6][0] == 'G' && !(flags & PURPLE_CHAT_USER_AWAY)) {
508 purple_chat_user_set_flags(cb, flags | PURPLE_CHAT_USER_AWAY);
509 } else if(args[6][0] == 'H' && (flags & PURPLE_CHAT_USER_AWAY)) {
510 purple_chat_user_set_flags(cb, flags & ~PURPLE_CHAT_USER_AWAY);
515 void irc_msg_list(struct irc_conn *irc, const char *name, const char *from, char **args)
517 if (!irc->roomlist)
518 return;
520 if (purple_strequal(name, "321")) {
521 purple_roomlist_set_in_progress(irc->roomlist, TRUE);
522 return;
525 if (purple_strequal(name, "323")) {
526 purple_roomlist_set_in_progress(irc->roomlist, FALSE);
527 g_object_unref(irc->roomlist);
528 irc->roomlist = NULL;
529 return;
532 if (purple_strequal(name, "322")) {
533 PurpleRoomlistRoom *room;
534 char *topic;
536 if (!purple_roomlist_get_in_progress(irc->roomlist)) {
537 purple_debug_warning("irc", "Buggy server didn't send RPL_LISTSTART.\n");
538 purple_roomlist_set_in_progress(irc->roomlist, TRUE);
541 room = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM, args[1], NULL);
542 purple_roomlist_room_add_field(irc->roomlist, room, args[1]);
543 purple_roomlist_room_add_field(irc->roomlist, room, GINT_TO_POINTER(strtol(args[2], NULL, 10)));
544 topic = irc_mirc2txt(args[3]);
545 purple_roomlist_room_add_field(irc->roomlist, room, topic);
546 g_free(topic);
547 purple_roomlist_room_add(irc->roomlist, room);
551 void irc_msg_topic(struct irc_conn *irc, const char *name, const char *from, char **args)
553 char *chan, *topic, *msg, *nick, *tmp, *tmp2;
554 PurpleChatConversation *chat;
556 if (purple_strequal(name, "topic")) {
557 chan = args[0];
558 topic = irc_mirc2txt (args[1]);
559 } else {
560 chan = args[1];
561 topic = irc_mirc2txt (args[2]);
564 chat = purple_conversations_find_chat_with_account(chan, irc->account);
565 if (!chat) {
566 purple_debug(PURPLE_DEBUG_ERROR, "irc", "Got a topic for %s, which doesn't exist\n", chan);
567 g_free(topic);
568 return;
571 /* If this is an interactive update, print it out */
572 tmp = g_markup_escape_text(topic, -1);
573 tmp2 = purple_markup_linkify(tmp);
574 g_free(tmp);
575 if (purple_strequal(name, "topic")) {
576 const char *current_topic = purple_chat_conversation_get_topic(chat);
577 if (!(current_topic != NULL && purple_strequal(tmp2, current_topic)))
579 char *nick_esc;
580 nick = irc_mask_nick(from);
581 nick_esc = g_markup_escape_text(nick, -1);
582 purple_chat_conversation_set_topic(chat, nick, topic);
583 if (*tmp2)
584 msg = g_strdup_printf(_("%s has changed the topic to: %s"), nick_esc, tmp2);
585 else
586 msg = g_strdup_printf(_("%s has cleared the topic."), nick_esc);
587 g_free(nick_esc);
588 g_free(nick);
589 purple_conversation_write_system_message(
590 PURPLE_CONVERSATION(chat), msg, 0);
591 g_free(msg);
593 } else {
594 char *chan_esc = g_markup_escape_text(chan, -1);
595 msg = g_strdup_printf(_("The topic for %s is: %s"), chan_esc, tmp2);
596 g_free(chan_esc);
597 purple_chat_conversation_set_topic(chat, NULL, topic);
598 purple_conversation_write_system_message(PURPLE_CONVERSATION(chat), msg, 0);
599 g_free(msg);
601 g_free(tmp2);
602 g_free(topic);
605 void irc_msg_topicinfo(struct irc_conn *irc, const char *name, const char *from, char **args)
607 PurpleChatConversation *chat;
608 struct tm *tm;
609 time_t t;
610 char *msg, *timestamp, *datestamp;
612 chat = purple_conversations_find_chat_with_account(args[1], irc->account);
613 if (!chat) {
614 purple_debug(PURPLE_DEBUG_ERROR, "irc", "Got topic info for %s, which doesn't exist\n", args[1]);
615 return;
618 t = (time_t)atol(args[3]);
619 if (t == 0) {
620 purple_debug(PURPLE_DEBUG_ERROR, "irc", "Got apparently nonsensical topic timestamp %s\n", args[3]);
621 return;
623 tm = localtime(&t);
625 timestamp = g_strdup(purple_time_format(tm));
626 datestamp = g_strdup(purple_date_format_short(tm));
627 msg = g_strdup_printf(_("Topic for %s set by %s at %s on %s"), args[1], args[2], timestamp, datestamp);
628 purple_conversation_write_system_message(PURPLE_CONVERSATION(chat),
629 msg, PURPLE_MESSAGE_NO_LINKIFY);
630 g_free(timestamp);
631 g_free(datestamp);
632 g_free(msg);
635 void irc_msg_unknown(struct irc_conn *irc, const char *name, const char *from, char **args)
637 PurpleConnection *gc = purple_account_get_connection(irc->account);
638 char *buf;
640 g_return_if_fail(gc);
642 buf = g_strdup_printf(_("Unknown message '%s'"), args[1]);
643 purple_notify_error(gc, _("Unknown message"), buf, _("The IRC server "
644 "received a message it did not understand."),
645 purple_request_cpar_from_connection(gc));
646 g_free(buf);
649 void irc_msg_names(struct irc_conn *irc, const char *name, const char *from, char **args)
651 char *names, *cur, *end, *tmp, *msg;
652 PurpleConversation *convo;
654 if (purple_strequal(name, "366")) {
655 convo = purple_conversations_find_with_account(args[1], irc->account);
656 if (!convo) {
657 purple_debug(PURPLE_DEBUG_ERROR, "irc", "Got a NAMES list for %s, which doesn't exist\n", args[1]);
658 g_string_free(irc->names, TRUE);
659 irc->names = NULL;
660 return;
663 names = cur = g_string_free(irc->names, FALSE);
664 irc->names = NULL;
665 if (g_object_get_data(G_OBJECT(convo), IRC_NAMES_FLAG)) {
666 msg = g_strdup_printf(_("Users on %s: %s"), args[1], names ? names : "");
667 purple_conversation_write_system_message(convo, msg, PURPLE_MESSAGE_NO_LOG);
668 g_free(msg);
669 } else if (cur != NULL) {
670 GList *users = NULL;
671 GList *flags = NULL;
673 while (*cur) {
674 PurpleChatUserFlags f = PURPLE_CHAT_USER_NONE;
675 end = strchr(cur, ' ');
676 if (!end)
677 end = cur + strlen(cur);
678 if (*cur == '@') {
679 f = PURPLE_CHAT_USER_OP;
680 cur++;
681 } else if (*cur == '%') {
682 f = PURPLE_CHAT_USER_HALFOP;
683 cur++;
684 } else if(*cur == '+') {
685 f = PURPLE_CHAT_USER_VOICE;
686 cur++;
687 } else if(irc->mode_chars
688 && strchr(irc->mode_chars, *cur)) {
689 if (*cur == '~')
690 f = PURPLE_CHAT_USER_FOUNDER;
691 cur++;
693 tmp = g_strndup(cur, end - cur);
694 users = g_list_prepend(users, tmp);
695 flags = g_list_prepend(flags, GINT_TO_POINTER(f));
696 cur = end;
697 if (*cur)
698 cur++;
701 if (users != NULL) {
702 GList *l;
704 purple_chat_conversation_add_users(PURPLE_CHAT_CONVERSATION(convo), users, NULL, flags, FALSE);
706 for (l = users; l != NULL; l = l->next)
707 g_free(l->data);
709 g_list_free(users);
710 g_list_free(flags);
713 g_object_set_data(G_OBJECT(convo), IRC_NAMES_FLAG,
714 GINT_TO_POINTER(TRUE));
716 g_free(names);
717 } else {
718 if (!irc->names)
719 irc->names = g_string_new("");
721 if (irc->names->len && irc->names->str[irc->names->len - 1] != ' ')
722 irc->names = g_string_append_c(irc->names, ' ');
723 irc->names = g_string_append(irc->names, args[3]);
727 void irc_msg_motd(struct irc_conn *irc, const char *name, const char *from, char **args)
729 char *escaped;
731 if (purple_strequal(name, "375")) {
732 if (irc->motd) {
733 g_string_free(irc->motd, TRUE);
734 irc->motd = NULL;
736 irc->motd = g_string_new("");
737 return;
738 } else if (purple_strequal(name, "376")) {
739 /* dircproxy 1.0.5 does not send 251 on reconnection, so
740 * finalize the connection here if it is not already done. */
741 irc_connected(irc, args[0]);
742 return;
743 } else if (purple_strequal(name, "422")) {
744 /* in case there is no 251, and no MOTD set, finalize the connection.
745 * (and clear the motd for good measure). */
747 if (irc->motd) {
748 g_string_free(irc->motd, TRUE);
749 irc->motd = NULL;
752 irc_connected(irc, args[0]);
753 return;
756 if (!irc->motd) {
757 purple_debug_error("irc", "IRC server sent MOTD without STARTMOTD\n");
758 return;
761 if (!args[1])
762 return;
764 escaped = g_markup_escape_text(args[1], -1);
765 g_string_append_printf(irc->motd, "%s<br>", escaped);
766 g_free(escaped);
769 void irc_msg_time(struct irc_conn *irc, const char *name, const char *from, char **args)
771 PurpleConnection *gc;
773 gc = purple_account_get_connection(irc->account);
775 g_return_if_fail(gc);
777 purple_notify_message(gc, PURPLE_NOTIFY_MSG_INFO, _("Time Response"),
778 _("The IRC server's local time is:"), args[2], NULL, NULL,
779 purple_request_cpar_from_connection(gc));
782 void irc_msg_nochan(struct irc_conn *irc, const char *name, const char *from, char **args)
784 PurpleConnection *gc = purple_account_get_connection(irc->account);
786 g_return_if_fail(gc);
788 purple_notify_error(gc, NULL, _("No such channel"), args[1],
789 purple_request_cpar_from_connection(gc));
792 void irc_msg_nonick(struct irc_conn *irc, const char *name, const char *from, char **args)
794 PurpleConnection *gc;
795 PurpleConversation *convo;
797 convo = purple_conversations_find_with_account(args[1], irc->account);
798 if (convo) {
799 purple_conversation_write_system_message(convo,
800 PURPLE_IS_IM_CONVERSATION(convo) ? _("User is not logged in") : _("no such channel"),
801 PURPLE_MESSAGE_NO_LOG);
803 } else {
804 if ((gc = purple_account_get_connection(irc->account)) == NULL)
805 return;
806 purple_notify_error(gc, NULL, _("No such nick or channel"),
807 args[1], purple_request_cpar_from_connection(gc));
810 if (irc->whois.nick && !purple_utf8_strcasecmp(irc->whois.nick, args[1])) {
811 g_free(irc->whois.nick);
812 irc->whois.nick = NULL;
816 void irc_msg_nosend(struct irc_conn *irc, const char *name, const char *from, char **args)
818 PurpleConnection *gc;
819 PurpleChatConversation *chat;
821 chat = purple_conversations_find_chat_with_account(args[1], irc->account);
822 if (chat) {
823 purple_conversation_write_system_message(PURPLE_CONVERSATION(chat), args[2],
824 PURPLE_MESSAGE_NO_LOG);
825 } else {
826 if ((gc = purple_account_get_connection(irc->account)) == NULL)
827 return;
828 purple_notify_error(gc, NULL, _("Could not send"), args[2],
829 purple_request_cpar_from_connection(gc));
833 void irc_msg_notinchan(struct irc_conn *irc, const char *name, const char *from, char **args)
835 PurpleChatConversation *chat = purple_conversations_find_chat_with_account(args[1], irc->account);
837 purple_debug(PURPLE_DEBUG_INFO, "irc", "We're apparently not in %s, but tried to use it\n", args[1]);
838 if (chat) {
839 /*g_slist_remove(irc->gc->buddy_chats, chat);
840 purple_conversation_set_account(chat, NULL);*/
841 purple_conversation_write_system_message(PURPLE_CONVERSATION(chat),
842 args[2], PURPLE_MESSAGE_NO_LOG);
846 void irc_msg_notop(struct irc_conn *irc, const char *name, const char *from, char **args)
848 PurpleChatConversation *chat;
850 chat = purple_conversations_find_chat_with_account(args[1], irc->account);
851 if (!chat)
852 return;
854 purple_conversation_write_system_message(PURPLE_CONVERSATION(chat), args[2], 0);
857 void irc_msg_invite(struct irc_conn *irc, const char *name, const char *from, char **args)
859 PurpleConnection *gc = purple_account_get_connection(irc->account);
860 GHashTable *components;
861 gchar *nick;
863 g_return_if_fail(gc);
865 components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
866 nick = irc_mask_nick(from);
868 g_hash_table_insert(components, g_strdup("channel"), g_strdup(args[1]));
870 purple_serv_got_chat_invite(gc, args[1], nick, NULL, components);
871 g_free(nick);
874 void irc_msg_inviteonly(struct irc_conn *irc, const char *name, const char *from, char **args)
876 PurpleConnection *gc = purple_account_get_connection(irc->account);
877 char *buf;
879 g_return_if_fail(gc);
881 buf = g_strdup_printf(_("Joining %s requires an invitation."), args[1]);
882 purple_notify_error(gc, _("Invitation only"), _("Invitation only"), buf,
883 purple_request_cpar_from_connection(gc));
884 g_free(buf);
887 void irc_msg_ison(struct irc_conn *irc, const char *name, const char *from, char **args)
889 char **nicks;
890 struct irc_buddy *ib;
891 int i;
893 nicks = g_strsplit(args[1], " ", -1);
894 for (i = 0; nicks[i]; i++) {
895 if ((ib = g_hash_table_lookup(irc->buddies, (gconstpointer)nicks[i])) == NULL) {
896 continue;
898 ib->new_online_status = TRUE;
900 g_strfreev(nicks);
902 if (irc->ison_outstanding)
903 irc_buddy_query(irc);
905 if (!irc->ison_outstanding)
906 g_hash_table_foreach(irc->buddies, (GHFunc)irc_buddy_status, (gpointer)irc);
909 static void irc_buddy_status(char *name, struct irc_buddy *ib, struct irc_conn *irc)
911 PurpleConnection *gc = purple_account_get_connection(irc->account);
912 PurpleBuddy *buddy = purple_blist_find_buddy(irc->account, name);
914 if (!gc || !buddy)
915 return;
917 if (ib->online && !ib->new_online_status) {
918 purple_protocol_got_user_status(irc->account, name, "offline", NULL);
919 ib->online = FALSE;
920 } else if (!ib->online && ib->new_online_status) {
921 purple_protocol_got_user_status(irc->account, name, "available", NULL);
922 ib->online = TRUE;
926 void irc_msg_join(struct irc_conn *irc, const char *name, const char *from, char **args)
928 PurpleConnection *gc = purple_account_get_connection(irc->account);
929 PurpleChatConversation *chat;
930 PurpleChatUser *cb;
932 char *nick, *userhost, *buf;
933 struct irc_buddy *ib;
934 static int id = 1;
936 g_return_if_fail(gc);
938 nick = irc_mask_nick(from);
940 if (!purple_utf8_strcasecmp(nick, purple_connection_get_display_name(gc))) {
941 /* We are joining a channel for the first time */
942 purple_serv_got_joined_chat(gc, id++, args[0]);
943 g_free(nick);
944 chat = purple_conversations_find_chat_with_account(args[0], irc->account);
946 if (chat == NULL) {
947 purple_debug_error("irc", "tried to join %s but couldn't\n", args[0]);
948 return;
950 g_object_set_data(G_OBJECT(chat), IRC_NAMES_FLAG,
951 GINT_TO_POINTER(FALSE));
953 // Get the real name and user host for all participants.
954 buf = irc_format(irc, "vc", "WHO", args[0]);
955 irc_send(irc, buf);
956 g_free(buf);
958 /* Until purple_conversation_present does something that
959 * one would expect in Pidgin, this call produces buggy
960 * behavior both for the /join and auto-join cases. */
961 /* purple_conversation_present(chat); */
962 return;
965 chat = purple_conversations_find_chat_with_account(args[0], irc->account);
966 if (chat == NULL) {
967 purple_debug(PURPLE_DEBUG_ERROR, "irc", "JOIN for %s failed\n", args[0]);
968 g_free(nick);
969 return;
972 userhost = irc_mask_userhost(from);
974 purple_chat_conversation_add_user(chat, nick, userhost, PURPLE_CHAT_USER_NONE, TRUE);
976 cb = purple_chat_conversation_find_user(chat, nick);
978 if (cb) {
979 g_object_set_data_full(G_OBJECT(cb), "userhost", userhost, (GDestroyNotify)g_free);
982 if ((ib = g_hash_table_lookup(irc->buddies, nick)) != NULL) {
983 ib->new_online_status = TRUE;
984 irc_buddy_status(nick, ib, irc);
987 g_free(nick);
990 void irc_msg_kick(struct irc_conn *irc, const char *name, const char *from, char **args)
992 PurpleConnection *gc = purple_account_get_connection(irc->account);
993 PurpleChatConversation *chat = purple_conversations_find_chat_with_account(args[0], irc->account);
994 char *nick, *buf;
996 g_return_if_fail(gc);
998 nick = irc_mask_nick(from);
1000 if (!chat) {
1001 purple_debug(PURPLE_DEBUG_ERROR, "irc", "Received a KICK for unknown channel %s\n", args[0]);
1002 g_free(nick);
1003 return;
1006 if (!purple_utf8_strcasecmp(purple_connection_get_display_name(gc), args[1])) {
1007 buf = g_strdup_printf(_("You have been kicked by %s: (%s)"), nick, args[2]);
1008 purple_conversation_write_system_message(PURPLE_CONVERSATION(chat), buf, 0);
1009 g_free(buf);
1010 purple_serv_got_chat_left(gc, purple_chat_conversation_get_id(chat));
1011 } else {
1012 buf = g_strdup_printf(_("Kicked by %s (%s)"), nick, args[2]);
1013 purple_chat_conversation_remove_user(chat, args[1], buf);
1014 g_free(buf);
1017 g_free(nick);
1018 return;
1021 void irc_msg_mode(struct irc_conn *irc, const char *name, const char *from, char **args)
1023 PurpleChatConversation *chat;
1024 char *nick = irc_mask_nick(from), *buf;
1026 if (*args[0] == '#' || *args[0] == '&') { /* Channel */
1027 char *escaped;
1028 chat = purple_conversations_find_chat_with_account(args[0], irc->account);
1029 if (!chat) {
1030 purple_debug(PURPLE_DEBUG_ERROR, "irc", "MODE received for %s, which we are not in\n", args[0]);
1031 g_free(nick);
1032 return;
1034 escaped = (args[2] != NULL) ? g_markup_escape_text(args[2], -1) : NULL;
1035 buf = g_strdup_printf(_("mode (%s %s) by %s"), args[1], escaped ? escaped : "", nick);
1036 purple_conversation_write_system_message(PURPLE_CONVERSATION(chat), buf, 0);
1037 g_free(escaped);
1038 g_free(buf);
1039 if(args[2]) {
1040 PurpleChatUser *cb;
1041 PurpleChatUserFlags newflag, flags;
1042 char *mcur, *cur, *end, *user;
1043 gboolean add = FALSE;
1044 mcur = args[1];
1045 cur = args[2];
1046 while (*cur && *mcur) {
1047 if ((*mcur == '+') || (*mcur == '-')) {
1048 add = (*mcur == '+') ? TRUE : FALSE;
1049 mcur++;
1050 continue;
1052 end = strchr(cur, ' ');
1053 if (!end)
1054 end = cur + strlen(cur);
1055 user = g_strndup(cur, end - cur);
1056 cb = purple_chat_conversation_find_user(chat, user);
1057 flags = purple_chat_user_get_flags(cb);
1058 newflag = PURPLE_CHAT_USER_NONE;
1059 if (*mcur == 'o')
1060 newflag = PURPLE_CHAT_USER_OP;
1061 else if (*mcur =='h')
1062 newflag = PURPLE_CHAT_USER_HALFOP;
1063 else if (*mcur == 'v')
1064 newflag = PURPLE_CHAT_USER_VOICE;
1065 else if(irc->mode_chars
1066 && strchr(irc->mode_chars, '~') && (*mcur == 'q'))
1067 newflag = PURPLE_CHAT_USER_FOUNDER;
1068 if (newflag) {
1069 if (add)
1070 flags |= newflag;
1071 else
1072 flags &= ~newflag;
1073 purple_chat_user_set_flags(cb, flags);
1075 g_free(user);
1076 cur = end;
1077 if (*cur)
1078 cur++;
1079 if (*mcur)
1080 mcur++;
1083 } else { /* User */
1085 g_free(nick);
1088 void irc_msg_nick(struct irc_conn *irc, const char *name, const char *from, char **args)
1090 PurpleConnection *gc = purple_account_get_connection(irc->account);
1091 PurpleIMConversation *im;
1092 GSList *chats;
1093 char *nick = irc_mask_nick(from);
1095 irc->nickused = FALSE;
1097 if (!gc) {
1098 g_free(nick);
1099 return;
1101 chats = purple_connection_get_active_chats(gc);
1103 if (!purple_utf8_strcasecmp(nick, purple_connection_get_display_name(gc))) {
1104 purple_connection_set_display_name(gc, args[0]);
1107 while (chats) {
1108 PurpleChatConversation *chat = PURPLE_CHAT_CONVERSATION(chats->data);
1109 /* This is ugly ... */
1110 if (purple_chat_conversation_has_user(chat, nick))
1111 purple_chat_conversation_rename_user(chat, nick, args[0]);
1112 chats = chats->next;
1115 im = purple_conversations_find_im_with_account(nick,
1116 irc->account);
1117 if (im != NULL)
1118 purple_conversation_set_name(PURPLE_CONVERSATION(im), args[0]);
1120 g_free(nick);
1123 void irc_msg_badnick(struct irc_conn *irc, const char *name, const char *from, char **args)
1125 PurpleConnection *gc = purple_account_get_connection(irc->account);
1126 if (purple_connection_get_state(gc) == PURPLE_CONNECTION_CONNECTED) {
1127 purple_notify_error(gc, _("Invalid nickname"), _("Invalid "
1128 "nickname"), _("Your selected nickname was rejected by "
1129 "the server. It probably contains invalid characters."),
1130 purple_request_cpar_from_connection(gc));
1132 } else {
1133 purple_connection_take_error(gc, g_error_new_literal(
1134 PURPLE_CONNECTION_ERROR,
1135 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
1136 _("Your selected account name was rejected by the server. It probably contains invalid characters.")));
1140 void irc_msg_nickused(struct irc_conn *irc, const char *name, const char *from, char **args)
1142 char *newnick, *buf, *end;
1143 PurpleConnection *gc = purple_account_get_connection(irc->account);
1145 if (gc && purple_connection_get_state(gc) == PURPLE_CONNECTION_CONNECTED) {
1146 /* We only want to do the following dance if the connection
1147 has not been successfully completed. If it has, just
1148 notify the user that their /nick command didn't go. */
1149 buf = g_strdup_printf(_("The nickname \"%s\" is already being used."),
1150 irc->reqnick);
1151 purple_notify_error(gc, _("Nickname in use"), _("Nickname in "
1152 "use"), buf, purple_request_cpar_from_connection(gc));
1153 g_free(buf);
1154 g_free(irc->reqnick);
1155 irc->reqnick = NULL;
1156 return;
1159 if (strlen(args[1]) < strlen(irc->reqnick) || irc->nickused)
1160 newnick = g_strdup(args[1]);
1161 else
1162 newnick = g_strdup_printf("%s0", args[1]);
1163 end = newnick + strlen(newnick) - 1;
1164 /* try fallbacks */
1165 if((*end < '9') && (*end >= '1')) {
1166 *end = *end + 1;
1167 } else *end = '1';
1169 g_free(irc->reqnick);
1170 irc->reqnick = newnick;
1171 irc->nickused = TRUE;
1173 purple_connection_set_display_name(
1174 purple_account_get_connection(irc->account), newnick);
1176 buf = irc_format(irc, "vn", "NICK", newnick);
1177 irc_send(irc, buf);
1178 g_free(buf);
1181 void irc_msg_notice(struct irc_conn *irc, const char *name, const char *from, char **args)
1183 irc_msg_handle_privmsg(irc, name, from, args[0], args[1], TRUE);
1186 void irc_msg_nochangenick(struct irc_conn *irc, const char *name, const char *from, char **args)
1188 PurpleConnection *gc = purple_account_get_connection(irc->account);
1190 g_return_if_fail(gc);
1192 purple_notify_error(gc, _("Cannot change nick"),
1193 _("Could not change nick"), args[2],
1194 purple_request_cpar_from_connection(gc));
1197 void irc_msg_part(struct irc_conn *irc, const char *name, const char *from, char **args)
1199 PurpleConnection *gc = purple_account_get_connection(irc->account);
1200 PurpleChatConversation *chat;
1201 char *nick, *msg, *channel;
1203 g_return_if_fail(gc);
1205 /* Undernet likes to :-quote the channel name, for no good reason
1206 * that I can see. This catches that. */
1207 channel = (args[0][0] == ':') ? &args[0][1] : args[0];
1209 chat = purple_conversations_find_chat_with_account(channel, irc->account);
1210 if (!chat) {
1211 purple_debug(PURPLE_DEBUG_INFO, "irc", "Got a PART on %s, which doesn't exist -- probably closed\n", channel);
1212 return;
1215 nick = irc_mask_nick(from);
1216 if (!purple_utf8_strcasecmp(nick, purple_connection_get_display_name(gc))) {
1217 char *escaped = args[1] ? g_markup_escape_text(args[1], -1) : NULL;
1218 msg = g_strdup_printf(_("You have parted the channel%s%s"),
1219 (args[1] && *args[1]) ? ": " : "",
1220 (escaped && *escaped) ? escaped : "");
1221 g_free(escaped);
1222 purple_conversation_write_system_message(PURPLE_CONVERSATION(chat), msg, 0);
1223 g_free(msg);
1224 purple_serv_got_chat_left(gc, purple_chat_conversation_get_id(chat));
1225 } else {
1226 msg = args[1] ? irc_mirc2txt(args[1]) : NULL;
1227 purple_chat_conversation_remove_user(chat, nick, msg);
1228 g_free(msg);
1230 g_free(nick);
1233 void irc_msg_ping(struct irc_conn *irc, const char *name, const char *from, char **args)
1235 char *buf;
1237 buf = irc_format(irc, "v:", "PONG", args[0]);
1238 irc_send(irc, buf);
1239 g_free(buf);
1242 void irc_msg_pong(struct irc_conn *irc, const char *name, const char *from, char **args)
1244 PurpleConversation *convo;
1245 PurpleConnection *gc;
1246 char **parts, *msg;
1247 time_t oldstamp;
1249 parts = g_strsplit(args[1], " ", 2);
1251 if (!parts[0] || !parts[1]) {
1252 g_strfreev(parts);
1253 return;
1256 if (sscanf(parts[1], "%lu", &oldstamp) != 1) {
1257 msg = g_strdup(_("Error: invalid PONG from server"));
1258 } else {
1259 msg = g_strdup_printf(_("PING reply -- Lag: %lu seconds"), time(NULL) - oldstamp);
1262 convo = purple_conversations_find_with_account(parts[0], irc->account);
1263 g_strfreev(parts);
1264 if (convo) {
1265 purple_conversation_write_system_message(convo, msg, PURPLE_MESSAGE_NO_LOG);
1266 } else {
1267 gc = purple_account_get_connection(irc->account);
1268 if (!gc) {
1269 g_free(msg);
1270 return;
1272 purple_notify_info(gc, NULL, "PONG", msg,
1273 purple_request_cpar_from_connection(gc));
1275 g_free(msg);
1278 void irc_msg_privmsg(struct irc_conn *irc, const char *name, const char *from, char **args)
1280 irc_msg_handle_privmsg(irc, name, from, args[0], args[1], FALSE);
1283 static void irc_msg_handle_privmsg(struct irc_conn *irc, const char *name, const char *from, const char *to, const char *rawmsg, gboolean notice)
1285 PurpleConnection *gc = purple_account_get_connection(irc->account);
1286 PurpleChatConversation *chat;
1287 char *tmp;
1288 char *msg;
1289 char *nick;
1291 if (!gc)
1292 return;
1294 nick = irc_mask_nick(from);
1295 tmp = irc_parse_ctcp(irc, nick, to, rawmsg, notice);
1296 if (!tmp) {
1297 g_free(nick);
1298 return;
1301 msg = irc_escape_privmsg(tmp, -1);
1302 g_free(tmp);
1304 tmp = irc_mirc2html(msg);
1305 g_free(msg);
1306 msg = tmp;
1307 if (notice) {
1308 tmp = g_strdup_printf("(notice) %s", msg);
1309 g_free(msg);
1310 msg = tmp;
1313 if (!purple_utf8_strcasecmp(to, purple_connection_get_display_name(gc))) {
1314 purple_serv_got_im(gc, nick, msg, 0, time(NULL));
1315 } else {
1316 chat = purple_conversations_find_chat_with_account(irc_nick_skip_mode(irc, to), irc->account);
1317 if (chat) {
1318 purple_serv_got_chat_in(gc, purple_chat_conversation_get_id(chat),
1319 nick, PURPLE_MESSAGE_RECV, msg, time(NULL));
1320 } else
1321 purple_debug_error("irc", "Got a %s on %s, which does not exist\n",
1322 notice ? "NOTICE" : "PRIVMSG", to);
1324 g_free(msg);
1325 g_free(nick);
1328 void irc_msg_regonly(struct irc_conn *irc, const char *name, const char *from, char **args)
1330 PurpleConnection *gc = purple_account_get_connection(irc->account);
1331 char *msg;
1333 g_return_if_fail(gc);
1335 if (purple_conversations_find_chat_with_account(args[1], irc->account)) {
1336 /* This is a channel we're already in; for some reason,
1337 * freenode feels the need to notify us that in some
1338 * hypothetical other situation this might not have
1339 * succeeded. Suppress that. */
1340 return;
1343 msg = g_strdup_printf(_("Cannot join %s: Registration is required."), args[1]);
1344 purple_notify_error(gc, _("Cannot join channel"), msg, args[2],
1345 purple_request_cpar_from_connection(gc));
1346 g_free(msg);
1349 void irc_msg_quit(struct irc_conn *irc, const char *name, const char *from, char **args)
1351 PurpleConnection *gc = purple_account_get_connection(irc->account);
1352 struct irc_buddy *ib;
1353 char *data[2];
1355 g_return_if_fail(gc);
1357 data[0] = irc_mask_nick(from);
1358 data[1] = args[0];
1359 /* XXX this should have an API, I shouldn't grab this directly */
1360 g_slist_foreach(purple_connection_get_active_chats(gc),
1361 (GFunc)irc_chat_remove_buddy, data);
1363 if ((ib = g_hash_table_lookup(irc->buddies, data[0])) != NULL) {
1364 ib->new_online_status = FALSE;
1365 irc_buddy_status(data[0], ib, irc);
1367 g_free(data[0]);
1369 return;
1372 void irc_msg_unavailable(struct irc_conn *irc, const char *name, const char *from, char **args)
1374 PurpleConnection *gc = purple_account_get_connection(irc->account);
1376 purple_notify_error(gc, NULL, _("Nick or channel is temporarily "
1377 "unavailable."), args[1],
1378 purple_request_cpar_from_connection(gc));
1381 void irc_msg_wallops(struct irc_conn *irc, const char *name, const char *from, char **args)
1383 PurpleConnection *gc = purple_account_get_connection(irc->account);
1384 char *nick, *msg;
1386 g_return_if_fail(gc);
1388 nick = irc_mask_nick(from);
1389 msg = g_strdup_printf (_("Wallops from %s"), nick);
1390 g_free(nick);
1391 purple_notify_info(gc, NULL, msg, args[0],
1392 purple_request_cpar_from_connection(gc));
1393 g_free(msg);
1396 #ifdef HAVE_CYRUS_SASL
1397 static int
1398 irc_sasl_cb_secret(sasl_conn_t *conn, void *ctx, int id, sasl_secret_t **secret)
1400 struct irc_conn *irc = ctx;
1401 sasl_secret_t *sasl_secret;
1402 const char *pw;
1403 size_t len;
1405 pw = purple_connection_get_password(purple_account_get_connection(
1406 irc->account));
1408 if (!conn || !secret || id != SASL_CB_PASS)
1409 return SASL_BADPARAM;
1411 len = strlen(pw);
1412 /* Not an off-by-one because sasl_secret_t defines char data[1] */
1413 /* TODO: This can probably be moved to glib's allocator */
1414 sasl_secret = malloc(sizeof(sasl_secret_t) + len);
1415 if (!sasl_secret)
1416 return SASL_NOMEM;
1418 sasl_secret->len = len;
1419 strcpy((char*)sasl_secret->data, pw);
1421 *secret = sasl_secret;
1422 return SASL_OK;
1425 static int
1426 irc_sasl_cb_log(void *context, int level, const char *message)
1428 if(level <= SASL_LOG_TRACE)
1429 purple_debug_info("sasl", "%s\n", message);
1431 return SASL_OK;
1434 static int
1435 irc_sasl_cb_simple(void *ctx, int id, const char **res, unsigned *len)
1437 struct irc_conn *irc = ctx;
1438 PurpleConnection *gc = purple_account_get_connection(irc->account);
1440 switch(id) {
1441 case SASL_CB_AUTHNAME:
1442 *res = purple_connection_get_display_name(gc);
1443 break;
1444 case SASL_CB_USER:
1445 *res = "";
1446 break;
1447 default:
1448 return SASL_BADPARAM;
1450 if (len) *len = strlen((char *)*res);
1451 return SASL_OK;
1454 static void
1455 irc_auth_start_cyrus(struct irc_conn *irc)
1457 int ret = 0;
1458 char *buf;
1459 sasl_security_properties_t secprops;
1460 PurpleAccount *account = irc->account;
1461 PurpleConnection *gc = purple_account_get_connection(account);
1463 gboolean again = FALSE;
1465 /* Set up security properties and options */
1466 secprops.min_ssf = 0;
1467 secprops.security_flags = SASL_SEC_NOANONYMOUS;
1469 if (!G_IS_TLS_CONNECTION(irc->conn)) {
1470 gboolean plaintext;
1472 secprops.max_ssf = -1;
1473 secprops.maxbufsize = 4096;
1474 plaintext = purple_account_get_bool(account, "auth_plain_in_clear", FALSE);
1475 if (!plaintext)
1476 secprops.security_flags |= SASL_SEC_NOPLAINTEXT;
1477 } else {
1478 secprops.max_ssf = 0;
1479 secprops.maxbufsize = 0;
1482 secprops.property_names = 0;
1483 secprops.property_values = 0;
1485 do {
1486 again = FALSE;
1488 ret = sasl_client_new("irc", irc->server, NULL, NULL, irc->sasl_cb, 0, &irc->sasl_conn);
1490 if (ret != SASL_OK) {
1491 purple_debug_error("irc", "sasl_client_new failed: %d\n", ret);
1492 purple_connection_take_error(gc, g_error_new(
1493 PURPLE_CONNECTION_ERROR,
1494 PURPLE_CONNECTION_ERROR_OTHER_ERROR,
1495 ("Failed to initialize SASL authentication: %s"),
1496 sasl_errdetail(irc->sasl_conn)));
1497 return;
1500 sasl_setprop(irc->sasl_conn, SASL_AUTH_EXTERNAL, purple_account_get_username(irc->account));
1501 sasl_setprop(irc->sasl_conn, SASL_SEC_PROPS, &secprops);
1503 ret = sasl_client_start(irc->sasl_conn, irc->sasl_mechs->str, NULL, NULL, NULL, &irc->current_mech);
1505 switch (ret) {
1506 case SASL_OK:
1507 case SASL_CONTINUE:
1508 irc->mech_works = FALSE;
1509 break;
1510 case SASL_NOMECH:
1511 purple_connection_take_error(gc,
1512 g_error_new_literal(
1513 PURPLE_CONNECTION_ERROR,
1514 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
1515 _("SASL authentication failed: No worthy authentication mechanisms found.")));
1517 irc_sasl_finish(irc);
1518 return;
1519 case SASL_BADPARAM:
1520 case SASL_NOMEM:
1521 purple_connection_take_error(gc, g_error_new(
1522 PURPLE_CONNECTION_ERROR,
1523 PURPLE_CONNECTION_ERROR_OTHER_ERROR,
1524 _("SASL authentication failed: %s"),
1525 sasl_errdetail(irc->sasl_conn)));
1527 irc_sasl_finish(irc);
1528 return;
1529 default:
1530 purple_debug_error("irc", "sasl_client_start failed: %s\n", sasl_errdetail(irc->sasl_conn));
1532 if (irc->current_mech && *irc->current_mech) {
1533 char *pos;
1534 if ((pos = strstr(irc->sasl_mechs->str, irc->current_mech))) {
1535 size_t index = pos - irc->sasl_mechs->str;
1536 g_string_erase(irc->sasl_mechs, index, strlen(irc->current_mech));
1538 /* Remove space which separated this mech from the next */
1539 if ((irc->sasl_mechs->str)[index] == ' ') {
1540 g_string_erase(irc->sasl_mechs, index, 1);
1544 again = TRUE;
1546 irc_sasl_finish(irc);
1548 } while (again);
1550 purple_debug_info("irc", "Using SASL: %s\n", irc->current_mech);
1552 buf = irc_format(irc, "vv", "AUTHENTICATE", irc->current_mech);
1553 irc_send(irc, buf);
1554 g_free(buf);
1557 /* SASL authentication */
1558 void
1559 irc_msg_cap(struct irc_conn *irc, const char *name, const char *from, char **args)
1561 int ret = 0;
1562 int id = 0;
1563 PurpleConnection *gc = purple_account_get_connection(irc->account);
1564 const char *mech_list = NULL;
1565 char *pos;
1566 size_t index;
1568 if (strncmp(g_strstrip(args[2]), "sasl", 5))
1569 return;
1570 if (strncmp(args[1], "ACK", 4)) {
1571 purple_connection_take_error(gc, g_error_new_literal(
1572 PURPLE_CONNECTION_ERROR,
1573 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
1574 _("SASL authentication failed: Server does not support SASL authentication.")));
1576 irc_sasl_finish(irc);
1577 return;
1580 if ((ret = sasl_client_init(NULL)) != SASL_OK) {
1581 purple_connection_take_error(gc, g_error_new_literal(
1582 PURPLE_CONNECTION_ERROR,
1583 PURPLE_CONNECTION_ERROR_OTHER_ERROR,
1584 _("SASL authentication failed: Initializing SASL failed.")));
1585 return;
1588 irc->sasl_cb = g_new0(sasl_callback_t, 5);
1590 irc->sasl_cb[id].id = SASL_CB_AUTHNAME;
1591 irc->sasl_cb[id].proc = (int (*)(void))irc_sasl_cb_simple; /* sasl_getsimple_t */
1592 irc->sasl_cb[id].context = (void *)irc;
1593 id++;
1595 irc->sasl_cb[id].id = SASL_CB_USER;
1596 irc->sasl_cb[id].proc = (int (*)(void))irc_sasl_cb_simple; /* sasl_getsimple_t */
1597 irc->sasl_cb[id].context = (void *)irc;
1598 id++;
1600 irc->sasl_cb[id].id = SASL_CB_PASS;
1601 irc->sasl_cb[id].proc = (int (*)(void))irc_sasl_cb_secret; /* sasl_getsecret_t */
1602 irc->sasl_cb[id].context = (void *)irc;
1603 id++;
1605 irc->sasl_cb[id].id = SASL_CB_LOG;
1606 irc->sasl_cb[id].proc = (int (*)(void))irc_sasl_cb_log; /* sasl_log_t */
1607 irc->sasl_cb[id].context = (void *)irc;
1608 id++;
1610 irc->sasl_cb[id].id = SASL_CB_LIST_END;
1612 /* We need to do this to be able to list the mechanisms. */
1613 ret = sasl_client_new("irc", irc->server, NULL, NULL, irc->sasl_cb, 0, &irc->sasl_conn);
1615 sasl_listmech(irc->sasl_conn, NULL, "", " ", "", &mech_list, NULL, NULL);
1616 purple_debug_info("irc", "SASL: we have available: %s\n", mech_list);
1618 if (ret != SASL_OK) {
1619 purple_debug_error("irc", "sasl_client_new failed: %d\n", ret);
1621 purple_connection_take_error(gc, g_error_new(
1622 PURPLE_CONNECTION_ERROR,
1623 PURPLE_CONNECTION_ERROR_OTHER_ERROR,
1624 _("Failed to initialize SASL authentication: %s"),
1625 sasl_errdetail(irc->sasl_conn)));
1627 return;
1630 irc->sasl_mechs = g_string_new(mech_list);
1631 /* Drop EXTERNAL mechanism since we don't support it */
1632 if ((pos = strstr(irc->sasl_mechs->str, "EXTERNAL"))) {
1633 index = pos - irc->sasl_mechs->str;
1634 g_string_erase(irc->sasl_mechs, index, strlen("EXTERNAL"));
1635 /* Remove space which separated this mech from the next */
1636 if ((irc->sasl_mechs->str)[index] == ' ') {
1637 g_string_erase(irc->sasl_mechs, index, 1);
1641 irc_auth_start_cyrus(irc);
1644 void
1645 irc_msg_auth(struct irc_conn *irc, char *arg)
1647 PurpleConnection *gc = purple_account_get_connection(irc->account);
1648 char *buf, *authinfo;
1649 char *serverin = NULL;
1650 gsize serverinlen = 0;
1651 const gchar *c_out;
1652 unsigned int clen;
1653 int ret;
1655 irc->mech_works = TRUE;
1657 if (!arg)
1658 return;
1660 if (arg[0] != '+')
1661 serverin = (char *)g_base64_decode(arg, &serverinlen);
1663 ret = sasl_client_step(irc->sasl_conn, serverin, serverinlen,
1664 NULL, &c_out, &clen);
1666 if (ret != SASL_OK && ret != SASL_CONTINUE) {
1667 purple_connection_take_error(gc, g_error_new(
1668 PURPLE_CONNECTION_ERROR,
1669 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
1670 _("SASL authentication failed: %s"),
1671 sasl_errdetail(irc->sasl_conn)));
1673 irc_sasl_finish(irc);
1674 g_free(serverin);
1675 return;
1678 if (clen > 0)
1679 authinfo = g_base64_encode((const guchar*)c_out, clen);
1680 else
1681 authinfo = g_strdup("+");
1683 buf = irc_format(irc, "vv", "AUTHENTICATE", authinfo);
1684 irc_send(irc, buf);
1685 g_free(buf);
1686 g_free(authinfo);
1687 g_free(serverin);
1690 void
1691 irc_msg_authenticate(struct irc_conn *irc, const char *name, const char *from, char **args)
1693 irc_msg_auth(irc, args[0]);
1696 void
1697 irc_msg_authok(struct irc_conn *irc, const char *name, const char *from, char **args)
1699 char *buf;
1701 sasl_dispose(&irc->sasl_conn);
1702 irc->sasl_conn = NULL;
1703 purple_debug_info("irc", "Succesfully authenticated using SASL.\n");
1705 /* Finish auth session */
1706 buf = irc_format(irc, "vv", "CAP", "END");
1707 irc_send(irc, buf);
1708 g_free(buf);
1711 void
1712 irc_msg_authtryagain(struct irc_conn *irc, const char *name, const char *from, char **args)
1714 PurpleConnection *gc = purple_account_get_connection(irc->account);
1716 /* We already received at least one AUTHENTICATE reply from the
1717 * server. This suggests it supports this mechanism, but the
1718 * password was incorrect. It would be better to abort and inform
1719 * the user than to try again with a different mechanism, so they
1720 * aren't told the server supports no worthy mechanisms.
1722 if (irc->mech_works) {
1723 purple_connection_take_error(gc, g_error_new_literal(
1724 PURPLE_CONNECTION_ERROR,
1725 PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
1726 _("Incorrect Password")));
1728 irc_sasl_finish(irc);
1730 return;
1733 if (irc->current_mech) {
1734 char *pos;
1735 if ((pos = strstr(irc->sasl_mechs->str, irc->current_mech))) {
1736 size_t index = pos - irc->sasl_mechs->str;
1737 g_string_erase(irc->sasl_mechs, index, strlen(irc->current_mech));
1739 /* Remove space which separated this mech from the next */
1740 if ((irc->sasl_mechs->str)[index] == ' ') {
1741 g_string_erase(irc->sasl_mechs, index, 1);
1745 if (*irc->sasl_mechs->str) {
1746 sasl_dispose(&irc->sasl_conn);
1748 purple_debug_info("irc", "Now trying with %s\n", irc->sasl_mechs->str);
1749 irc_auth_start_cyrus(irc);
1750 } else {
1751 purple_connection_take_error(gc, g_error_new_literal(
1752 PURPLE_CONNECTION_ERROR,
1753 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
1754 _("SASL authentication failed: No worthy mechanisms found")));
1756 irc_sasl_finish(irc);
1760 void
1761 irc_msg_authfail(struct irc_conn *irc, const char *name, const char *from, char **args)
1763 PurpleConnection *gc = purple_account_get_connection(irc->account);
1765 /* Only show an error if we did not abort ourselves. */
1766 if (irc->sasl_conn) {
1767 purple_debug_info("irc", "SASL authentication failed: %s", sasl_errdetail(irc->sasl_conn));
1769 purple_connection_take_error(gc, g_error_new_literal(
1770 PURPLE_CONNECTION_ERROR,
1771 PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
1772 _("Incorrect Password")));
1775 irc_sasl_finish(irc);
1778 static void
1779 irc_sasl_finish(struct irc_conn *irc)
1781 char *buf;
1783 sasl_dispose(&irc->sasl_conn);
1784 irc->sasl_conn = NULL;
1786 g_free(irc->sasl_cb);
1787 irc->sasl_cb = NULL;
1789 /* Auth failed, abort */
1790 buf = irc_format(irc, "vv", "CAP", "END");
1791 irc_send(irc, buf);
1792 g_free(buf);
1794 #endif