14192
|
1 /**
|
|
2 * @file irc.c
|
|
3 *
|
|
4 * gaim
|
|
5 *
|
|
6 * Copyright (C) 2003, Robbert Haarman <gaim@inglorion.net>
|
|
7 * Copyright (C) 2003, Ethan Blanton <eblanton@cs.purdue.edu>
|
|
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 static void irc_buddy_append(char *name, struct irc_buddy *ib, GString *string);
|
|
41
|
|
42 static const char *irc_blist_icon(GaimAccount *a, GaimBuddy *b);
|
|
43 static void irc_blist_emblems(GaimBuddy *b, const char **se, const char **sw, const char **nw, const char **ne);
|
|
44 static GList *irc_status_types(GaimAccount *account);
|
|
45 static GList *irc_actions(GaimPlugin *plugin, gpointer context);
|
|
46 /* static GList *irc_chat_info(GaimConnection *gc); */
|
|
47 static void irc_login(GaimAccount *account);
|
|
48 static void irc_login_cb_ssl(gpointer data, GaimSslConnection *gsc, GaimInputCondition cond);
|
|
49 static void irc_login_cb(gpointer data, gint source, const gchar *error_message);
|
|
50 static void irc_ssl_connect_failure(GaimSslConnection *gsc, GaimSslErrorType error, gpointer data);
|
|
51 static void irc_close(GaimConnection *gc);
|
|
52 static int irc_im_send(GaimConnection *gc, const char *who, const char *what, GaimMessageFlags flags);
|
|
53 static int irc_chat_send(GaimConnection *gc, int id, const char *what, GaimMessageFlags flags);
|
|
54 static void irc_chat_join (GaimConnection *gc, GHashTable *data);
|
|
55 static void irc_input_cb(gpointer data, gint source, GaimInputCondition cond);
|
|
56 static void irc_input_cb_ssl(gpointer data, GaimSslConnection *gsc, GaimInputCondition cond);
|
|
57
|
|
58 static guint irc_nick_hash(const char *nick);
|
|
59 static gboolean irc_nick_equal(const char *nick1, const char *nick2);
|
|
60 static void irc_buddy_free(struct irc_buddy *ib);
|
|
61
|
|
62 static GaimPlugin *_irc_plugin = NULL;
|
|
63
|
|
64 static const char *status_chars = "@+%&";
|
|
65
|
|
66 static void irc_view_motd(GaimPluginAction *action)
|
|
67 {
|
|
68 GaimConnection *gc = (GaimConnection *) action->context;
|
|
69 struct irc_conn *irc;
|
|
70 char *title;
|
|
71
|
|
72 if (gc == NULL || gc->proto_data == NULL) {
|
|
73 gaim_debug(GAIM_DEBUG_ERROR, "irc", "got MOTD request for NULL gc\n");
|
|
74 return;
|
|
75 }
|
|
76 irc = gc->proto_data;
|
|
77 if (irc->motd == NULL) {
|
|
78 gaim_notify_error(gc, _("Error displaying MOTD"), _("No MOTD available"),
|
|
79 _("There is no MOTD associated with this connection."));
|
|
80 return;
|
|
81 }
|
|
82 title = g_strdup_printf(_("MOTD for %s"), irc->server);
|
|
83 gaim_notify_formatted(gc, title, title, NULL, irc->motd->str, NULL, NULL);
|
|
84 }
|
|
85
|
|
86 static int do_send(struct irc_conn *irc, const char *buf, gsize len)
|
|
87 {
|
|
88 int ret;
|
|
89
|
|
90 if (irc->gsc) {
|
|
91 ret = gaim_ssl_write(irc->gsc, buf, len);
|
|
92 } else {
|
|
93 ret = write(irc->fd, buf, len);
|
|
94 }
|
|
95
|
|
96 return ret;
|
|
97 }
|
|
98
|
|
99 static void
|
|
100 irc_send_cb(gpointer data, gint source, GaimInputCondition cond)
|
|
101 {
|
|
102 struct irc_conn *irc = data;
|
|
103 int ret, writelen;
|
|
104
|
|
105 writelen = gaim_circ_buffer_get_max_read(irc->outbuf);
|
|
106
|
|
107 if (writelen == 0) {
|
|
108 gaim_input_remove(irc->writeh);
|
|
109 irc->writeh = 0;
|
|
110 return;
|
|
111 }
|
|
112
|
|
113 ret = do_send(irc, irc->outbuf->outptr, writelen);
|
|
114
|
|
115 if (ret < 0 && errno == EAGAIN)
|
|
116 return;
|
|
117 else if (ret <= 0) {
|
|
118 gaim_connection_error(gaim_account_get_connection(irc->account),
|
|
119 _("Server has disconnected"));
|
|
120 return;
|
|
121 }
|
|
122
|
|
123 gaim_circ_buffer_mark_read(irc->outbuf, ret);
|
|
124
|
|
125 #if 0
|
|
126 /* We *could* try to write more if we wrote it all */
|
|
127 if (ret == write_len) {
|
|
128 irc_send_cb(data, source, cond);
|
|
129 }
|
|
130 #endif
|
|
131 }
|
|
132
|
|
133 int irc_send(struct irc_conn *irc, const char *buf)
|
|
134 {
|
|
135 int ret, buflen = strlen(buf);
|
|
136
|
|
137 /* If we're not buffering writes, try to send immediately */
|
|
138 if (!irc->writeh)
|
|
139 ret = do_send(irc, buf, buflen);
|
|
140 else {
|
|
141 ret = -1;
|
|
142 errno = EAGAIN;
|
|
143 }
|
|
144
|
|
145 /* gaim_debug(GAIM_DEBUG_MISC, "irc", "sent%s: %s",
|
|
146 irc->gsc ? " (ssl)" : "", buf); */
|
|
147 if (ret <= 0 && errno != EAGAIN) {
|
|
148 gaim_connection_error(gaim_account_get_connection(irc->account),
|
|
149 _("Server has disconnected"));
|
|
150 } else if (ret < buflen) {
|
|
151 if (ret < 0)
|
|
152 ret = 0;
|
|
153 if (!irc->writeh)
|
|
154 irc->writeh = gaim_input_add(
|
|
155 irc->gsc ? irc->gsc->fd : irc->fd,
|
|
156 GAIM_INPUT_WRITE, irc_send_cb, irc);
|
|
157 gaim_circ_buffer_append(irc->outbuf, buf + ret,
|
|
158 buflen - ret);
|
|
159 }
|
|
160
|
|
161 return ret;
|
|
162 }
|
|
163
|
|
164 /* XXX I don't like messing directly with these buddies */
|
|
165 gboolean irc_blist_timeout(struct irc_conn *irc)
|
|
166 {
|
|
167 GString *string = g_string_sized_new(512);
|
|
168 char *list, *buf;
|
|
169
|
|
170 g_hash_table_foreach(irc->buddies, (GHFunc)irc_buddy_append, (gpointer)string);
|
|
171
|
|
172 list = g_string_free(string, FALSE);
|
|
173 if (!list || !strlen(list)) {
|
|
174 g_free(list);
|
|
175 return TRUE;
|
|
176 }
|
|
177
|
|
178 buf = irc_format(irc, "vn", "ISON", list);
|
|
179 g_free(list);
|
|
180 irc_send(irc, buf);
|
|
181 g_free(buf);
|
|
182
|
|
183 return TRUE;
|
|
184 }
|
|
185
|
|
186 static void irc_buddy_append(char *name, struct irc_buddy *ib, GString *string)
|
|
187 {
|
|
188 ib->flag = FALSE;
|
|
189 g_string_append_printf(string, "%s ", name);
|
|
190 }
|
|
191
|
|
192 static void irc_ison_one(struct irc_conn *irc, struct irc_buddy *ib)
|
|
193 {
|
|
194 char *buf;
|
|
195
|
|
196 ib->flag = FALSE;
|
|
197 buf = irc_format(irc, "vn", "ISON", ib->name);
|
|
198 irc_send(irc, buf);
|
|
199 g_free(buf);
|
|
200 }
|
|
201
|
|
202
|
|
203 static const char *irc_blist_icon(GaimAccount *a, GaimBuddy *b)
|
|
204 {
|
|
205 return "irc";
|
|
206 }
|
|
207
|
|
208 static void irc_blist_emblems(GaimBuddy *b, const char **se, const char **sw, const char **nw, const char **ne)
|
|
209 {
|
|
210 GaimPresence *presence = gaim_buddy_get_presence(b);
|
|
211
|
|
212 if (gaim_presence_is_online(presence) == FALSE) {
|
|
213 *se = "offline";
|
|
214 }
|
|
215 }
|
|
216
|
|
217 static GList *irc_status_types(GaimAccount *account)
|
|
218 {
|
|
219 GaimStatusType *type;
|
|
220 GList *types = NULL;
|
|
221
|
|
222 type = gaim_status_type_new(GAIM_STATUS_AVAILABLE, NULL, NULL, TRUE);
|
|
223 types = g_list_append(types, type);
|
|
224
|
|
225 type = gaim_status_type_new_with_attrs(
|
|
226 GAIM_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
|
|
227 "message", _("Message"), gaim_value_new(GAIM_TYPE_STRING),
|
|
228 NULL);
|
|
229 types = g_list_append(types, type);
|
|
230
|
|
231 type = gaim_status_type_new(GAIM_STATUS_OFFLINE, NULL, NULL, TRUE);
|
|
232 types = g_list_append(types, type);
|
|
233
|
|
234 return types;
|
|
235 }
|
|
236
|
|
237 static GList *irc_actions(GaimPlugin *plugin, gpointer context)
|
|
238 {
|
|
239 GList *list = NULL;
|
|
240 GaimPluginAction *act = NULL;
|
|
241
|
|
242 act = gaim_plugin_action_new(_("View MOTD"), irc_view_motd);
|
|
243 list = g_list_append(list, act);
|
|
244
|
|
245 return list;
|
|
246 }
|
|
247
|
|
248 static GList *irc_chat_join_info(GaimConnection *gc)
|
|
249 {
|
|
250 GList *m = NULL;
|
|
251 struct proto_chat_entry *pce;
|
|
252
|
|
253 pce = g_new0(struct proto_chat_entry, 1);
|
|
254 pce->label = _("_Channel:");
|
|
255 pce->identifier = "channel";
|
|
256 pce->required = TRUE;
|
|
257 m = g_list_append(m, pce);
|
|
258
|
|
259 pce = g_new0(struct proto_chat_entry, 1);
|
|
260 pce->label = _("_Password:");
|
|
261 pce->identifier = "password";
|
|
262 pce->secret = TRUE;
|
|
263 m = g_list_append(m, pce);
|
|
264
|
|
265 return m;
|
|
266 }
|
|
267
|
|
268 static GHashTable *irc_chat_info_defaults(GaimConnection *gc, const char *chat_name)
|
|
269 {
|
|
270 GHashTable *defaults;
|
|
271
|
|
272 defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
|
|
273
|
|
274 if (chat_name != NULL)
|
|
275 g_hash_table_insert(defaults, "channel", g_strdup(chat_name));
|
|
276
|
|
277 return defaults;
|
|
278 }
|
|
279
|
|
280 static void irc_login(GaimAccount *account)
|
|
281 {
|
|
282 GaimConnection *gc;
|
|
283 struct irc_conn *irc;
|
|
284 char **userparts;
|
|
285 const char *username = gaim_account_get_username(account);
|
|
286
|
|
287 gc = gaim_account_get_connection(account);
|
|
288 gc->flags |= GAIM_CONNECTION_NO_NEWLINES;
|
|
289
|
|
290 if (strpbrk(username, " \t\v\r\n") != NULL) {
|
|
291 gaim_connection_error(gc, _("IRC nicks may not contain whitespace"));
|
|
292 return;
|
|
293 }
|
|
294
|
|
295 gc->proto_data = irc = g_new0(struct irc_conn, 1);
|
|
296 irc->fd = -1;
|
|
297 irc->account = account;
|
|
298 irc->outbuf = gaim_circ_buffer_new(512);
|
|
299
|
|
300 userparts = g_strsplit(username, "@", 2);
|
|
301 gaim_connection_set_display_name(gc, userparts[0]);
|
|
302 irc->server = g_strdup(userparts[1]);
|
|
303 g_strfreev(userparts);
|
|
304
|
|
305 irc->buddies = g_hash_table_new_full((GHashFunc)irc_nick_hash, (GEqualFunc)irc_nick_equal,
|
|
306 NULL, (GDestroyNotify)irc_buddy_free);
|
|
307 irc->cmds = g_hash_table_new(g_str_hash, g_str_equal);
|
|
308 irc_cmd_table_build(irc);
|
|
309 irc->msgs = g_hash_table_new(g_str_hash, g_str_equal);
|
|
310 irc_msg_table_build(irc);
|
|
311
|
|
312 gaim_connection_update_progress(gc, _("Connecting"), 1, 2);
|
|
313
|
|
314 if (gaim_account_get_bool(account, "ssl", FALSE)) {
|
|
315 if (gaim_ssl_is_supported()) {
|
|
316 irc->gsc = gaim_ssl_connect(account, irc->server,
|
|
317 gaim_account_get_int(account, "port", IRC_DEFAULT_SSL_PORT),
|
|
318 irc_login_cb_ssl, irc_ssl_connect_failure, gc);
|
|
319 } else {
|
|
320 gaim_connection_error(gc, _("SSL support unavailable"));
|
|
321 return;
|
|
322 }
|
|
323 }
|
|
324
|
|
325 if (!irc->gsc) {
|
|
326
|
|
327 irc->connect_info = gaim_proxy_connect(account, irc->server,
|
|
328 gaim_account_get_int(account, "port", IRC_DEFAULT_PORT),
|
|
329 irc_login_cb, gc);
|
|
330
|
|
331 if (!irc->connect_info || !gaim_account_get_connection(account)) {
|
|
332 gaim_connection_error(gc, _("Couldn't create socket"));
|
|
333 return;
|
|
334 }
|
|
335 }
|
|
336 }
|
|
337
|
|
338 static gboolean do_login(GaimConnection *gc) {
|
|
339 char *buf;
|
|
340 char hostname[256];
|
|
341 const char *username, *realname;
|
|
342 struct irc_conn *irc = gc->proto_data;
|
|
343 const char *pass = gaim_connection_get_password(gc);
|
|
344
|
|
345 if (pass && *pass) {
|
|
346 buf = irc_format(irc, "vv", "PASS", pass);
|
|
347 if (irc_send(irc, buf) < 0) {
|
|
348 /* gaim_connection_error(gc, "Error sending password"); */
|
|
349 g_free(buf);
|
|
350 return FALSE;
|
|
351 }
|
|
352 g_free(buf);
|
|
353 }
|
|
354
|
|
355 gethostname(hostname, sizeof(hostname));
|
|
356 hostname[sizeof(hostname) - 1] = '\0';
|
|
357 username = gaim_account_get_string(irc->account, "username", "");
|
|
358 realname = gaim_account_get_string(irc->account, "realname", "");
|
|
359 buf = irc_format(irc, "vvvv:", "USER", strlen(username) ? username : g_get_user_name(), hostname, irc->server,
|
|
360 strlen(realname) ? realname : IRC_DEFAULT_ALIAS);
|
|
361 if (irc_send(irc, buf) < 0) {
|
|
362 /* gaim_connection_error(gc, "Error registering with server");*/
|
|
363 g_free(buf);
|
|
364 return FALSE;
|
|
365 }
|
|
366 g_free(buf);
|
|
367 buf = irc_format(irc, "vn", "NICK", gaim_connection_get_display_name(gc));
|
|
368 if (irc_send(irc, buf) < 0) {
|
|
369 /* gaim_connection_error(gc, "Error sending nickname");*/
|
|
370 g_free(buf);
|
|
371 return FALSE;
|
|
372 }
|
|
373 g_free(buf);
|
|
374
|
|
375 return TRUE;
|
|
376 }
|
|
377
|
|
378 static void irc_login_cb_ssl(gpointer data, GaimSslConnection *gsc,
|
|
379 GaimInputCondition cond)
|
|
380 {
|
|
381 GaimConnection *gc = data;
|
|
382
|
|
383 if (do_login(gc)) {
|
|
384 gaim_ssl_input_add(gsc, irc_input_cb_ssl, gc);
|
|
385 }
|
|
386 }
|
|
387
|
|
388 static void irc_login_cb(gpointer data, gint source, const gchar *error_message)
|
|
389 {
|
|
390 GaimConnection *gc = data;
|
|
391 struct irc_conn *irc = gc->proto_data;
|
|
392
|
|
393 irc->connect_info = NULL;
|
|
394
|
|
395 if (source < 0) {
|
|
396 gaim_connection_error(gc, _("Couldn't connect to host"));
|
|
397 return;
|
|
398 }
|
|
399
|
|
400 irc->fd = source;
|
|
401
|
|
402 if (do_login(gc)) {
|
|
403 gc->inpa = gaim_input_add(irc->fd, GAIM_INPUT_READ, irc_input_cb, gc);
|
|
404 }
|
|
405 }
|
|
406
|
|
407 static void
|
|
408 irc_ssl_connect_failure(GaimSslConnection *gsc, GaimSslErrorType error,
|
|
409 gpointer data)
|
|
410 {
|
|
411 GaimConnection *gc = data;
|
|
412 struct irc_conn *irc = gc->proto_data;
|
|
413
|
|
414 switch(error) {
|
|
415 case GAIM_SSL_CONNECT_FAILED:
|
|
416 gaim_connection_error(gc, _("Connection Failed"));
|
|
417 break;
|
|
418 case GAIM_SSL_HANDSHAKE_FAILED:
|
|
419 gaim_connection_error(gc, _("SSL Handshake Failed"));
|
|
420 break;
|
|
421 }
|
|
422
|
|
423 irc->gsc = NULL;
|
|
424 }
|
|
425
|
|
426 static void irc_close(GaimConnection *gc)
|
|
427 {
|
|
428 struct irc_conn *irc = gc->proto_data;
|
|
429
|
|
430 if (irc == NULL)
|
|
431 return;
|
|
432
|
|
433 if (irc->gsc || (irc->fd >= 0))
|
|
434 irc_cmd_quit(irc, "quit", NULL, NULL);
|
|
435
|
|
436 if (irc->connect_info)
|
|
437 gaim_proxy_connect_cancel(irc->connect_info);
|
|
438
|
|
439 if (gc->inpa)
|
|
440 gaim_input_remove(gc->inpa);
|
|
441
|
|
442 g_free(irc->inbuf);
|
|
443 if (irc->gsc) {
|
|
444 gaim_ssl_close(irc->gsc);
|
|
445 } else if (irc->fd >= 0) {
|
|
446 close(irc->fd);
|
|
447 }
|
|
448 if (irc->timer)
|
|
449 gaim_timeout_remove(irc->timer);
|
|
450 g_hash_table_destroy(irc->cmds);
|
|
451 g_hash_table_destroy(irc->msgs);
|
|
452 g_hash_table_destroy(irc->buddies);
|
|
453 if (irc->motd)
|
|
454 g_string_free(irc->motd, TRUE);
|
|
455 g_free(irc->server);
|
|
456
|
|
457 if (irc->writeh)
|
|
458 gaim_input_remove(irc->writeh);
|
|
459
|
|
460 gaim_circ_buffer_destroy(irc->outbuf);
|
|
461
|
|
462 g_free(irc);
|
|
463 }
|
|
464
|
|
465 static int irc_im_send(GaimConnection *gc, const char *who, const char *what, GaimMessageFlags flags)
|
|
466 {
|
|
467 struct irc_conn *irc = gc->proto_data;
|
|
468 char *plain;
|
|
469 const char *args[2];
|
|
470
|
|
471 if (strchr(status_chars, *who) != NULL)
|
|
472 args[0] = who + 1;
|
|
473 else
|
|
474 args[0] = who;
|
|
475
|
|
476 plain = gaim_unescape_html(what);
|
|
477 args[1] = plain;
|
|
478
|
|
479 irc_cmd_privmsg(irc, "msg", NULL, args);
|
|
480 g_free(plain);
|
|
481 return 1;
|
|
482 }
|
|
483
|
|
484 static void irc_get_info(GaimConnection *gc, const char *who)
|
|
485 {
|
|
486 struct irc_conn *irc = gc->proto_data;
|
|
487 const char *args[2];
|
|
488 args[0] = who;
|
|
489 args[1] = NULL;
|
|
490 irc_cmd_whois(irc, "whois", NULL, args);
|
|
491 }
|
|
492
|
|
493 static void irc_set_status(GaimAccount *account, GaimStatus *status)
|
|
494 {
|
|
495 GaimConnection *gc = gaim_account_get_connection(account);
|
|
496 struct irc_conn *irc;
|
|
497 const char *args[1];
|
|
498 const char *status_id = gaim_status_get_id(status);
|
|
499
|
|
500 g_return_if_fail(gc != NULL);
|
|
501 irc = gc->proto_data;
|
|
502
|
|
503 if (!gaim_status_is_active(status))
|
|
504 return;
|
|
505
|
|
506 args[0] = NULL;
|
|
507
|
|
508 if (!strcmp(status_id, "away")) {
|
|
509 args[0] = gaim_status_get_attr_string(status, "message");
|
|
510 if ((args[0] == NULL) || (*args[0] == '\0'))
|
|
511 args[0] = _("Away");
|
|
512 irc_cmd_away(irc, "away", NULL, args);
|
|
513 } else if (!strcmp(status_id, "available")) {
|
|
514 irc_cmd_away(irc, "back", NULL, args);
|
|
515 }
|
|
516 }
|
|
517
|
|
518 static void irc_add_buddy(GaimConnection *gc, GaimBuddy *buddy, GaimGroup *group)
|
|
519 {
|
|
520 struct irc_conn *irc = (struct irc_conn *)gc->proto_data;
|
|
521 struct irc_buddy *ib = g_new0(struct irc_buddy, 1);
|
|
522 ib->name = g_strdup(buddy->name);
|
|
523 g_hash_table_insert(irc->buddies, ib->name, ib);
|
|
524
|
|
525 /* if the timer isn't set, this is during signon, so we don't want to flood
|
|
526 * ourself off with ISON's, so we don't, but after that we want to know when
|
|
527 * someone's online asap */
|
|
528 if (irc->timer)
|
|
529 irc_ison_one(irc, ib);
|
|
530 }
|
|
531
|
|
532 static void irc_remove_buddy(GaimConnection *gc, GaimBuddy *buddy, GaimGroup *group)
|
|
533 {
|
|
534 struct irc_conn *irc = (struct irc_conn *)gc->proto_data;
|
|
535 g_hash_table_remove(irc->buddies, buddy->name);
|
|
536 }
|
|
537
|
|
538 static void read_input(struct irc_conn *irc, int len)
|
|
539 {
|
|
540 char *cur, *end;
|
|
541
|
|
542 irc->inbufused += len;
|
|
543 irc->inbuf[irc->inbufused] = '\0';
|
|
544
|
|
545 cur = irc->inbuf;
|
|
546
|
|
547 /* This is a hack to work around the fact that marv gets messages
|
|
548 * with null bytes in them while using some weird irc server at work
|
|
549 */
|
|
550 while ((cur < (irc->inbuf + irc->inbufused)) && !*cur)
|
|
551 cur++;
|
|
552
|
|
553 while (cur < irc->inbuf + irc->inbufused &&
|
|
554 ((end = strstr(cur, "\r\n")) || (end = strstr(cur, "\n")))) {
|
|
555 int step = (*end == '\r' ? 2 : 1);
|
|
556 *end = '\0';
|
|
557 irc_parse_msg(irc, cur);
|
|
558 cur = end + step;
|
|
559 }
|
|
560 if (cur != irc->inbuf + irc->inbufused) { /* leftover */
|
|
561 irc->inbufused -= (cur - irc->inbuf);
|
|
562 memmove(irc->inbuf, cur, irc->inbufused);
|
|
563 } else {
|
|
564 irc->inbufused = 0;
|
|
565 }
|
|
566 }
|
|
567
|
|
568 static void irc_input_cb_ssl(gpointer data, GaimSslConnection *gsc,
|
|
569 GaimInputCondition cond)
|
|
570 {
|
|
571
|
|
572 GaimConnection *gc = data;
|
|
573 struct irc_conn *irc = gc->proto_data;
|
|
574 int len;
|
|
575
|
|
576 if(!g_list_find(gaim_connections_get_all(), gc)) {
|
|
577 gaim_ssl_close(gsc);
|
|
578 return;
|
|
579 }
|
|
580
|
|
581 if (irc->inbuflen < irc->inbufused + IRC_INITIAL_BUFSIZE) {
|
|
582 irc->inbuflen += IRC_INITIAL_BUFSIZE;
|
|
583 irc->inbuf = g_realloc(irc->inbuf, irc->inbuflen);
|
|
584 }
|
|
585
|
|
586 len = gaim_ssl_read(gsc, irc->inbuf + irc->inbufused, IRC_INITIAL_BUFSIZE - 1);
|
|
587
|
|
588 if (len < 0 && errno == EAGAIN) {
|
|
589 /* Try again later */
|
|
590 return;
|
|
591 } else if (len < 0) {
|
|
592 gaim_connection_error(gc, _("Read error"));
|
|
593 return;
|
|
594 } else if (len == 0) {
|
|
595 gaim_connection_error(gc, _("Server has disconnected"));
|
|
596 return;
|
|
597 }
|
|
598
|
|
599 read_input(irc, len);
|
|
600 }
|
|
601
|
|
602 static void irc_input_cb(gpointer data, gint source, GaimInputCondition cond)
|
|
603 {
|
|
604 GaimConnection *gc = data;
|
|
605 struct irc_conn *irc = gc->proto_data;
|
|
606 int len;
|
|
607
|
|
608 if (irc->inbuflen < irc->inbufused + IRC_INITIAL_BUFSIZE) {
|
|
609 irc->inbuflen += IRC_INITIAL_BUFSIZE;
|
|
610 irc->inbuf = g_realloc(irc->inbuf, irc->inbuflen);
|
|
611 }
|
|
612
|
|
613 len = read(irc->fd, irc->inbuf + irc->inbufused, IRC_INITIAL_BUFSIZE - 1);
|
|
614 if (len < 0 && errno == EAGAIN) {
|
|
615 return;
|
|
616 } else if (len < 0) {
|
|
617 gaim_connection_error(gc, _("Read error"));
|
|
618 return;
|
|
619 } else if (len == 0) {
|
|
620 gaim_connection_error(gc, _("Server has disconnected"));
|
|
621 return;
|
|
622 }
|
|
623
|
|
624 read_input(irc, len);
|
|
625 }
|
|
626
|
|
627 static void irc_chat_join (GaimConnection *gc, GHashTable *data)
|
|
628 {
|
|
629 struct irc_conn *irc = gc->proto_data;
|
|
630 const char *args[2];
|
|
631
|
|
632 args[0] = g_hash_table_lookup(data, "channel");
|
|
633 args[1] = g_hash_table_lookup(data, "password");
|
|
634 irc_cmd_join(irc, "join", NULL, args);
|
|
635 }
|
|
636
|
|
637 static char *irc_get_chat_name(GHashTable *data) {
|
|
638 return g_strdup(g_hash_table_lookup(data, "channel"));
|
|
639 }
|
|
640
|
|
641 static void irc_chat_invite(GaimConnection *gc, int id, const char *message, const char *name)
|
|
642 {
|
|
643 struct irc_conn *irc = gc->proto_data;
|
|
644 GaimConversation *convo = gaim_find_chat(gc, id);
|
|
645 const char *args[2];
|
|
646
|
|
647 if (!convo) {
|
|
648 gaim_debug(GAIM_DEBUG_ERROR, "irc", "Got chat invite request for bogus chat\n");
|
|
649 return;
|
|
650 }
|
|
651 args[0] = name;
|
|
652 args[1] = gaim_conversation_get_name(convo);
|
|
653 irc_cmd_invite(irc, "invite", gaim_conversation_get_name(convo), args);
|
|
654 }
|
|
655
|
|
656
|
|
657 static void irc_chat_leave (GaimConnection *gc, int id)
|
|
658 {
|
|
659 struct irc_conn *irc = gc->proto_data;
|
|
660 GaimConversation *convo = gaim_find_chat(gc, id);
|
|
661 const char *args[2];
|
|
662
|
|
663 if (!convo)
|
|
664 return;
|
|
665
|
|
666 args[0] = gaim_conversation_get_name(convo);
|
|
667 args[1] = NULL;
|
|
668 irc_cmd_part(irc, "part", gaim_conversation_get_name(convo), args);
|
|
669 serv_got_chat_left(gc, id);
|
|
670 }
|
|
671
|
|
672 static int irc_chat_send(GaimConnection *gc, int id, const char *what, GaimMessageFlags flags)
|
|
673 {
|
|
674 struct irc_conn *irc = gc->proto_data;
|
|
675 GaimConversation *convo = gaim_find_chat(gc, id);
|
|
676 const char *args[2];
|
|
677 char *tmp;
|
|
678
|
|
679 if (!convo) {
|
|
680 gaim_debug(GAIM_DEBUG_ERROR, "irc", "chat send on nonexistent chat\n");
|
|
681 return -EINVAL;
|
|
682 }
|
|
683 #if 0
|
|
684 if (*what == '/') {
|
|
685 return irc_parse_cmd(irc, convo->name, what + 1);
|
|
686 }
|
|
687 #endif
|
|
688 tmp = gaim_unescape_html(what);
|
|
689 args[0] = convo->name;
|
|
690 args[1] = tmp;
|
|
691
|
|
692 irc_cmd_privmsg(irc, "msg", NULL, args);
|
|
693
|
|
694 serv_got_chat_in(gc, id, gaim_connection_get_display_name(gc), 0, what, time(NULL));
|
|
695 g_free(tmp);
|
|
696 return 0;
|
|
697 }
|
|
698
|
|
699 static guint irc_nick_hash(const char *nick)
|
|
700 {
|
|
701 char *lc;
|
|
702 guint bucket;
|
|
703
|
|
704 lc = g_utf8_strdown(nick, -1);
|
|
705 bucket = g_str_hash(lc);
|
|
706 g_free(lc);
|
|
707
|
|
708 return bucket;
|
|
709 }
|
|
710
|
|
711 static gboolean irc_nick_equal(const char *nick1, const char *nick2)
|
|
712 {
|
|
713 return (gaim_utf8_strcasecmp(nick1, nick2) == 0);
|
|
714 }
|
|
715
|
|
716 static void irc_buddy_free(struct irc_buddy *ib)
|
|
717 {
|
|
718 g_free(ib->name);
|
|
719 g_free(ib);
|
|
720 }
|
|
721
|
|
722 static void irc_chat_set_topic(GaimConnection *gc, int id, const char *topic)
|
|
723 {
|
|
724 char *buf;
|
|
725 const char *name = NULL;
|
|
726 struct irc_conn *irc;
|
|
727
|
|
728 irc = gc->proto_data;
|
|
729 name = gaim_conversation_get_name(gaim_find_chat(gc, id));
|
|
730
|
|
731 if (name == NULL)
|
|
732 return;
|
|
733
|
|
734 buf = irc_format(irc, "vt:", "TOPIC", name, topic);
|
|
735 irc_send(irc, buf);
|
|
736 g_free(buf);
|
|
737 }
|
|
738
|
|
739 static GaimRoomlist *irc_roomlist_get_list(GaimConnection *gc)
|
|
740 {
|
|
741 struct irc_conn *irc;
|
|
742 GList *fields = NULL;
|
|
743 GaimRoomlistField *f;
|
|
744 char *buf;
|
|
745
|
|
746 irc = gc->proto_data;
|
|
747
|
|
748 if (irc->roomlist)
|
|
749 gaim_roomlist_unref(irc->roomlist);
|
|
750
|
|
751 irc->roomlist = gaim_roomlist_new(gaim_connection_get_account(gc));
|
|
752
|
|
753 f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, "", "channel", TRUE);
|
|
754 fields = g_list_append(fields, f);
|
|
755
|
|
756 f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_INT, _("Users"), "users", FALSE);
|
|
757 fields = g_list_append(fields, f);
|
|
758
|
|
759 f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, _("Topic"), "topic", FALSE);
|
|
760 fields = g_list_append(fields, f);
|
|
761
|
|
762 gaim_roomlist_set_fields(irc->roomlist, fields);
|
|
763
|
|
764 buf = irc_format(irc, "v", "LIST");
|
|
765 irc_send(irc, buf);
|
|
766 g_free(buf);
|
|
767
|
|
768 return irc->roomlist;
|
|
769 }
|
|
770
|
|
771 static void irc_roomlist_cancel(GaimRoomlist *list)
|
|
772 {
|
|
773 GaimConnection *gc = gaim_account_get_connection(list->account);
|
|
774 struct irc_conn *irc;
|
|
775
|
|
776 if (gc == NULL)
|
|
777 return;
|
|
778
|
|
779 irc = gc->proto_data;
|
|
780
|
|
781 gaim_roomlist_set_in_progress(list, FALSE);
|
|
782
|
|
783 if (irc->roomlist == list) {
|
|
784 irc->roomlist = NULL;
|
|
785 gaim_roomlist_unref(list);
|
|
786 }
|
|
787 }
|
|
788
|
|
789 static GaimPluginProtocolInfo prpl_info =
|
|
790 {
|
|
791 OPT_PROTO_CHAT_TOPIC | OPT_PROTO_PASSWORD_OPTIONAL,
|
|
792 NULL, /* user_splits */
|
|
793 NULL, /* protocol_options */
|
|
794 NO_BUDDY_ICONS, /* icon_spec */
|
|
795 irc_blist_icon, /* list_icon */
|
|
796 irc_blist_emblems, /* list_emblems */
|
|
797 NULL, /* status_text */
|
|
798 NULL, /* tooltip_text */
|
|
799 irc_status_types, /* away_states */
|
|
800 NULL, /* blist_node_menu */
|
|
801 irc_chat_join_info, /* chat_info */
|
|
802 irc_chat_info_defaults, /* chat_info_defaults */
|
|
803 irc_login, /* login */
|
|
804 irc_close, /* close */
|
|
805 irc_im_send, /* send_im */
|
|
806 NULL, /* set_info */
|
|
807 NULL, /* send_typing */
|
|
808 irc_get_info, /* get_info */
|
|
809 irc_set_status, /* set_status */
|
|
810 NULL, /* set_idle */
|
|
811 NULL, /* change_passwd */
|
|
812 irc_add_buddy, /* add_buddy */
|
|
813 NULL, /* add_buddies */
|
|
814 irc_remove_buddy, /* remove_buddy */
|
|
815 NULL, /* remove_buddies */
|
|
816 NULL, /* add_permit */
|
|
817 NULL, /* add_deny */
|
|
818 NULL, /* rem_permit */
|
|
819 NULL, /* rem_deny */
|
|
820 NULL, /* set_permit_deny */
|
|
821 irc_chat_join, /* join_chat */
|
|
822 NULL, /* reject_chat */
|
|
823 irc_get_chat_name, /* get_chat_name */
|
|
824 irc_chat_invite, /* chat_invite */
|
|
825 irc_chat_leave, /* chat_leave */
|
|
826 NULL, /* chat_whisper */
|
|
827 irc_chat_send, /* chat_send */
|
|
828 NULL, /* keepalive */
|
|
829 NULL, /* register_user */
|
|
830 NULL, /* get_cb_info */
|
|
831 NULL, /* get_cb_away */
|
|
832 NULL, /* alias_buddy */
|
|
833 NULL, /* group_buddy */
|
|
834 NULL, /* rename_group */
|
|
835 NULL, /* buddy_free */
|
|
836 NULL, /* convo_closed */
|
|
837 gaim_normalize_nocase, /* normalize */
|
|
838 NULL, /* set_buddy_icon */
|
|
839 NULL, /* remove_group */
|
|
840 NULL, /* get_cb_real_name */
|
|
841 irc_chat_set_topic, /* set_chat_topic */
|
|
842 NULL, /* find_blist_chat */
|
|
843 irc_roomlist_get_list, /* roomlist_get_list */
|
|
844 irc_roomlist_cancel, /* roomlist_cancel */
|
|
845 NULL, /* roomlist_expand_category */
|
|
846 NULL, /* can_receive_file */
|
|
847 irc_dccsend_send_file, /* send_file */
|
|
848 irc_dccsend_new_xfer, /* new_xfer */
|
|
849 NULL, /* offline_message */
|
|
850 NULL, /* whiteboard_prpl_ops */
|
|
851 };
|
|
852
|
|
853
|
|
854 static GaimPluginInfo info =
|
|
855 {
|
|
856 GAIM_PLUGIN_MAGIC,
|
|
857 GAIM_MAJOR_VERSION,
|
|
858 GAIM_MINOR_VERSION,
|
|
859 GAIM_PLUGIN_PROTOCOL, /**< type */
|
|
860 NULL, /**< ui_requirement */
|
|
861 0, /**< flags */
|
|
862 NULL, /**< dependencies */
|
|
863 GAIM_PRIORITY_DEFAULT, /**< priority */
|
|
864
|
|
865 "prpl-irc", /**< id */
|
|
866 "IRC", /**< name */
|
|
867 VERSION, /**< version */
|
|
868 N_("IRC Protocol Plugin"), /** summary */
|
|
869 N_("The IRC Protocol Plugin that Sucks Less"), /** description */
|
|
870 NULL, /**< author */
|
|
871 GAIM_WEBSITE, /**< homepage */
|
|
872
|
|
873 NULL, /**< load */
|
|
874 NULL, /**< unload */
|
|
875 NULL, /**< destroy */
|
|
876
|
|
877 NULL, /**< ui_info */
|
|
878 &prpl_info, /**< extra_info */
|
|
879 NULL, /**< prefs_info */
|
|
880 irc_actions
|
|
881 };
|
|
882
|
|
883 static void _init_plugin(GaimPlugin *plugin)
|
|
884 {
|
|
885 GaimAccountUserSplit *split;
|
|
886 GaimAccountOption *option;
|
|
887
|
|
888 split = gaim_account_user_split_new(_("Server"), IRC_DEFAULT_SERVER, '@');
|
|
889 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
|
|
890
|
|
891 option = gaim_account_option_int_new(_("Port"), "port", IRC_DEFAULT_PORT);
|
|
892 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
|
|
893
|
|
894 option = gaim_account_option_string_new(_("Encodings"), "encoding", IRC_DEFAULT_CHARSET);
|
|
895 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
|
|
896
|
|
897 option = gaim_account_option_string_new(_("Username"), "username", "");
|
|
898 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
|
|
899
|
|
900 option = gaim_account_option_string_new(_("Real name"), "realname", "");
|
|
901 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
|
|
902
|
|
903 /*
|
|
904 option = gaim_account_option_string_new(_("Quit message"), "quitmsg", IRC_DEFAULT_QUIT);
|
|
905 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
|
|
906 */
|
|
907
|
|
908 option = gaim_account_option_bool_new(_("Use SSL"), "ssl", FALSE);
|
|
909 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
|
|
910
|
|
911 _irc_plugin = plugin;
|
|
912
|
|
913 gaim_prefs_remove("/plugins/prpl/irc/quitmsg");
|
|
914 gaim_prefs_remove("/plugins/prpl/irc");
|
|
915
|
|
916 irc_register_commands();
|
|
917 }
|
|
918
|
|
919 GAIM_INIT_PLUGIN(irc, _init_plugin, info);
|