Added SILC Thread Queue API
[crypto.git] / apps / irssi / src / core / chat-commands.c
1 /*
2  chat-commands.c : irssi
3
4     Copyright (C) 2000 Timo Sirainen
5
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.
10
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.
15
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include "module.h"
22 #include "network.h"
23 #include "signals.h"
24 #include "commands.h"
25 #include "special-vars.h"
26 #include "settings.h"
27 #include "recode.h"
28
29 #include "chat-protocols.h"
30 #include "servers.h"
31 #include "servers-setup.h"
32 #include "servers-reconnect.h"
33 #include "channels.h"
34 #include "queries.h"
35 #include "window-item-def.h"
36 #include "rawlog.h"
37
38 static SERVER_CONNECT_REC *get_server_connect(const char *data, int *plus_addr,
39                                               char **rawlog_file)
40 {
41         CHAT_PROTOCOL_REC *proto;
42         SERVER_CONNECT_REC *conn;
43         GHashTable *optlist;
44         char *addr, *portstr, *password, *nick, *chatnet, *host, *tmp;
45         void *free_arg;
46
47         g_return_val_if_fail(data != NULL, NULL);
48
49         if (!cmd_get_params(data, &free_arg, 4 | PARAM_FLAG_OPTIONS,
50                             "connect", &optlist, &addr, &portstr,
51                             &password, &nick))
52                 return NULL;
53         if (plus_addr != NULL) *plus_addr = *addr == '+';
54         if (*addr == '+') addr++;
55         if (*addr == '\0') {
56                 signal_emit("error command", 1,
57                             GINT_TO_POINTER(CMDERR_NOT_ENOUGH_PARAMS));
58                 cmd_params_free(free_arg);
59                 return NULL;
60         }
61
62         if (strcmp(password, "-") == 0)
63                 *password = '\0';
64
65         /* check if -<chatnet> option is used to specify chat protocol */
66         proto = chat_protocol_find_net(optlist);
67
68         /* connect to server */
69         chatnet = proto == NULL ? NULL :
70                 g_hash_table_lookup(optlist, proto->chatnet);
71
72         if (chatnet == NULL)
73                 chatnet = g_hash_table_lookup(optlist, "network");
74         
75         conn = server_create_conn(proto != NULL ? proto->id : -1, addr,
76                                   atoi(portstr), chatnet, password, nick);
77         if (proto == NULL)
78                 proto = chat_protocol_find_id(conn->chat_type);
79
80         if (proto->not_initialized) {
81                 /* trying to use protocol that isn't yet initialized */
82                 signal_emit("chat protocol unknown", 1, proto->name);
83                 server_connect_unref(conn);
84                 cmd_params_free(free_arg);
85                 return NULL;
86         }
87
88         if (strchr(addr, '/') != NULL)
89                 conn->unix_socket = TRUE;
90
91         if (g_hash_table_lookup(optlist, "6") != NULL)
92                 conn->family = AF_INET6;
93         else if (g_hash_table_lookup(optlist, "4") != NULL)
94                 conn->family = AF_INET;
95
96         if (g_hash_table_lookup(optlist, "ssl") != NULL)
97                 conn->use_ssl = TRUE;
98         if ((tmp = g_hash_table_lookup(optlist, "ssl_cert")) != NULL)
99                 conn->ssl_cert = g_strdup(tmp);
100         if ((tmp = g_hash_table_lookup(optlist, "ssl_pkey")) != NULL)
101                 conn->ssl_pkey = g_strdup(tmp);
102         if (g_hash_table_lookup(optlist, "ssl_verify") != NULL)
103                 conn->ssl_verify = TRUE;
104         if ((tmp = g_hash_table_lookup(optlist, "ssl_cafile")) != NULL)
105                 conn->ssl_cafile = g_strdup(tmp);
106         if ((tmp = g_hash_table_lookup(optlist, "ssl_capath")) != NULL)
107                 conn->ssl_capath = g_strdup(tmp);
108         if ((conn->ssl_capath != NULL && conn->ssl_capath[0] != '\0')
109         ||  (conn->ssl_cafile != NULL && conn->ssl_cafile[0] != '\0'))
110                 conn->ssl_verify = TRUE;
111         if ((conn->ssl_cert != NULL && conn->ssl_cert[0] != '\0') || conn->ssl_verify)
112                 conn->use_ssl = TRUE;
113
114         if (g_hash_table_lookup(optlist, "!") != NULL)
115                 conn->no_autojoin_channels = TRUE;
116
117         if (g_hash_table_lookup(optlist, "noproxy") != NULL)
118                 g_free_and_null(conn->proxy);
119
120
121         *rawlog_file = g_strdup(g_hash_table_lookup(optlist, "rawlog"));
122
123         host = g_hash_table_lookup(optlist, "host");
124         if (host != NULL && *host != '\0') {
125                 IPADDR ip4, ip6;
126
127                 if (net_gethostbyname(host, &ip4, &ip6) == 0)
128                         server_connect_own_ip_save(conn, &ip4, &ip6);
129         }
130
131         cmd_params_free(free_arg);
132         return conn;
133 }
134
135 /* SYNTAX: CONNECT [-4 | -6] [-ssl] [-ssl_cert <cert>] [-ssl_pkey <pkey>]
136                    [-ssl_verify] [-ssl_cafile <cafile>] [-ssl_capath <capath>]
137                    [-noproxy] [-network <network>] [-host <hostname>]
138                    [-rawlog <file>]
139                    <address>|<chatnet> [<port> [<password> [<nick>]]] */
140 /* NOTE: -network replaces the old -ircnet flag. */
141 static void cmd_connect(const char *data)
142 {
143         SERVER_CONNECT_REC *conn;
144         SERVER_REC *server;
145         char *rawlog_file;
146
147         conn = get_server_connect(data, NULL, &rawlog_file);
148         if (conn != NULL) {
149                 server = server_connect(conn);
150                 server_connect_unref(conn);
151
152                 if (server != NULL && rawlog_file != NULL)
153                         rawlog_open(server->rawlog, rawlog_file);
154
155                 g_free(rawlog_file);
156         }
157 }
158
159 static RECONNECT_REC *find_reconnect_server(int chat_type,
160                                             const char *addr, int port)
161 {
162         RECONNECT_REC *match, *last_proto_match;
163         GSList *tmp;
164         int count;
165
166         g_return_val_if_fail(addr != NULL, NULL);
167
168         /* check if there's a reconnection to the same host and maybe even
169            the same port */
170         match = last_proto_match = NULL; count = 0;
171         for (tmp = reconnects; tmp != NULL; tmp = tmp->next) {
172                 RECONNECT_REC *rec = tmp->data;
173
174                 if (rec->conn->chat_type == chat_type) {
175                         count++; last_proto_match = rec;
176                         if (g_strcasecmp(rec->conn->address, addr) == 0) {
177                                 if (rec->conn->port == port)
178                                         return rec;
179                                 match = rec;
180                         }
181                 }
182         }
183
184         if (count == 1) {
185                 /* only one reconnection with wanted protocol,
186                    we probably want to use it */
187                 return last_proto_match;
188         }
189
190         return match;
191 }
192
193 static void update_reconnection(SERVER_CONNECT_REC *conn, SERVER_REC *server)
194 {
195         SERVER_CONNECT_REC *oldconn;
196         RECONNECT_REC *recon;
197
198         if (server != NULL) {
199                 oldconn = server->connrec;
200                 server_connect_ref(oldconn);
201                 reconnect_save_status(conn, server);
202         } else {
203                 /* maybe we can reconnect some server from
204                    reconnection queue */
205                 recon = find_reconnect_server(conn->chat_type,
206                                               conn->address, conn->port);
207                 if (recon == NULL) return;
208
209                 oldconn = recon->conn;
210                 server_connect_ref(oldconn);
211                 server_reconnect_destroy(recon);
212
213                 conn->away_reason = g_strdup(oldconn->away_reason);
214                 conn->channels = g_strdup(oldconn->channels);
215         }
216
217         conn->reconnection = TRUE;
218
219         if (conn->chatnet == NULL && oldconn->chatnet != NULL)
220                 conn->chatnet = g_strdup(oldconn->chatnet);
221
222         server_connect_unref(oldconn);
223         if (server != NULL) {
224                 signal_emit("command disconnect", 2,
225                             "* Changing server", server);
226         }
227 }
228
229 static void cmd_server(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
230 {
231         command_runsub("server", data, server, item);
232 }
233
234 static void sig_default_command_server(const char *data, SERVER_REC *server,
235                                        WI_ITEM_REC *item)
236 {
237         signal_emit("command server connect", 3, data, server, item);
238 }
239
240 /* SYNTAX: SERVER [-4 | -6] [-ssl] [-ssl_cert <cert>] [-ssl_pkey <pkey>]
241                   [-ssl_verify] [-ssl_cafile <cafile>] [-ssl_capath <capath>]
242                   [-noproxy] [-network <network>] [-host <hostname>]
243                   [-rawlog <file>]
244                   [+]<address>|<chatnet> [<port> [<password> [<nick>]]] */
245 /* NOTE: -network replaces the old -ircnet flag. */
246 static void cmd_server_connect(const char *data, SERVER_REC *server)
247 {
248         SERVER_CONNECT_REC *conn;
249         char *rawlog_file;
250         int plus_addr;
251
252         g_return_if_fail(data != NULL);
253
254         /* create connection record */
255         conn = get_server_connect(data, &plus_addr, &rawlog_file);
256         if (conn != NULL) {
257                 if (!plus_addr)
258                         update_reconnection(conn, server);
259                 server = server_connect(conn);
260                 server_connect_unref(conn);
261
262                 if (server != NULL && rawlog_file != NULL)
263                         rawlog_open(server->rawlog, rawlog_file);
264
265                 g_free(rawlog_file);
266         }
267 }
268
269 /* SYNTAX: DISCONNECT *|<tag> [<message>] */
270 static void cmd_disconnect(const char *data, SERVER_REC *server)
271 {
272         char *tag, *msg;
273         void *free_arg;
274
275         g_return_if_fail(data != NULL);
276
277         if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &tag, &msg))
278                 return;
279
280         if (*tag != '\0' && strcmp(tag, "*") != 0) {
281                 server = server_find_tag(tag);
282                 if (server == NULL)
283                         server = server_find_lookup_tag(tag);
284         }
285         if (server == NULL) cmd_param_error(CMDERR_NOT_CONNECTED);
286
287         if (*msg == '\0') msg = (char *) settings_get_str("quit_message");
288         signal_emit("server quit", 2, server, msg);
289
290         cmd_params_free(free_arg);
291         server_disconnect(server);
292 }
293
294 /* SYNTAX: QUIT [<message>] */
295 static void cmd_quit(const char *data)
296 {
297         GSList *tmp, *next;
298         const char *quitmsg;
299         char *str;
300
301         g_return_if_fail(data != NULL);
302
303         quitmsg = *data != '\0' ? data :
304                 settings_get_str("quit_message");
305
306         /* disconnect from every server */
307         for (tmp = servers; tmp != NULL; tmp = next) {
308                 next = tmp->next;
309
310                 str = g_strdup_printf("* %s", quitmsg);
311                 cmd_disconnect(str, tmp->data);
312                 g_free(str);
313         }
314
315         signal_emit("gui exit", 0);
316 }
317
318 /* SYNTAX: JOIN [-invite] [-<server tag>] <channels> [<keys>] */
319 static void cmd_join(const char *data, SERVER_REC *server)
320 {
321         GHashTable *optlist;
322         char *channels;
323         void *free_arg;
324
325         g_return_if_fail(data != NULL);
326
327         if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
328                             PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
329                             "join", &optlist, &channels))
330                 return;
331
332         /* -<server tag> */
333         server = cmd_options_get_server("join", optlist, server);
334         if (server == NULL || !server->connected)
335                 cmd_param_error(CMDERR_NOT_CONNECTED);
336
337         if (g_hash_table_lookup(optlist, "invite"))
338                 channels = server->last_invite;
339         else {
340                 if (*channels == '\0')
341                         cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
342         }
343
344         if (channels != NULL)
345                 server->channels_join(server, channels, FALSE);
346         cmd_params_free(free_arg);
347 }
348
349 /* SYNTAX: MSG [-<server tag>] [-channel | -nick] <targets> <message> */
350 static void cmd_msg(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
351 {
352         GHashTable *optlist;
353         char *target, *origtarget, *msg, *recoded;
354         void *free_arg;
355         int free_ret, target_type = SEND_TARGET_NICK;
356
357         g_return_if_fail(data != NULL);
358
359         if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS |
360                             PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
361                             "msg", &optlist, &target, &msg))
362                 return;
363         if (*target == '\0' || *msg == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
364
365         server = cmd_options_get_server("msg", optlist, server);
366         if (server == NULL || !server->connected)
367                 cmd_param_error(CMDERR_NOT_CONNECTED);
368
369         origtarget = target;
370         free_ret = FALSE;
371         if (strcmp(target, ",") == 0 || strcmp(target, ".") == 0) {
372                 target = parse_special(&target, server, item,
373                                        NULL, &free_ret, NULL, 0);
374                 if (target != NULL && *target == '\0') {
375                         if (free_ret)
376                                 g_free(target);
377                         target = NULL;
378                         free_ret = FALSE;
379                 }
380         }
381
382         if (target != NULL) {
383                 if (strcmp(target, "*") == 0) {
384                         /* send to active channel/query */
385                         if (item == NULL)
386                                 cmd_param_error(CMDERR_NOT_JOINED);
387
388                         target_type = IS_CHANNEL(item) ?
389                                 SEND_TARGET_CHANNEL : SEND_TARGET_NICK;
390                         target = (char *) window_item_get_target(item);
391                 } else if (g_hash_table_lookup(optlist, "channel") != NULL)
392                         target_type = SEND_TARGET_CHANNEL;
393                 else if (g_hash_table_lookup(optlist, "nick") != NULL)
394                         target_type = SEND_TARGET_NICK;
395                 else {
396                         /* Need to rely on server_ischannel(). If the protocol
397                            doesn't really know if it's channel or nick based on
398                            the name, it should just assume it's nick, because 
399                            when typing text to channels it's always sent with
400                            /MSG -channel. */
401                         target_type = server_ischannel(server, target) ?
402                                 SEND_TARGET_CHANNEL : SEND_TARGET_NICK;
403                 }
404         }
405         recoded = recode_out(server, msg, target);
406         if (target != NULL) {
407                 signal_emit("server sendmsg", 4, server, target, recoded,
408                             GINT_TO_POINTER(target_type));
409         }
410         signal_emit(target != NULL && target_type == SEND_TARGET_CHANNEL ?
411                     "message own_public" : "message own_private", 4,
412                     server, recoded, target, origtarget);
413                     
414         g_free(recoded);
415         if (free_ret && target != NULL) g_free(target);
416         cmd_params_free(free_arg);
417 }
418
419 static void sig_server_sendmsg(SERVER_REC *server, const char *target,
420                                const char *msg, void *target_type_p)
421 {
422         server->send_message(server, target, msg,
423                              GPOINTER_TO_INT(target_type_p));
424 }
425
426 static void cmd_foreach(const char *data, SERVER_REC *server,
427                         WI_ITEM_REC *item)
428 {
429         command_runsub("foreach", data, server, item);
430 }
431
432 /* SYNTAX: FOREACH SERVER <command> */
433 static void cmd_foreach_server(const char *data, SERVER_REC *server)
434 {
435         GSList *list;
436
437         list = g_slist_copy(servers);
438         while (list != NULL) {
439                 signal_emit("send command", 3, data, list->data, NULL);
440                 list = g_slist_remove(list, list->data);
441         }
442 }
443
444 /* SYNTAX: FOREACH CHANNEL <command> */
445 static void cmd_foreach_channel(const char *data)
446 {
447         GSList *list;
448
449         list = g_slist_copy(channels);
450         while (list != NULL) {
451                 CHANNEL_REC *rec = list->data;
452
453                 signal_emit("send command", 3, data, rec->server, rec);
454                 list = g_slist_remove(list, list->data);
455         }
456 }
457
458 /* SYNTAX: FOREACH QUERY <command> */
459 static void cmd_foreach_query(const char *data)
460 {
461         GSList *list;
462
463         list = g_slist_copy(queries);
464         while (list != NULL) {
465                 QUERY_REC *rec = list->data;
466
467                 signal_emit("send command", 3, data, rec->server, rec);
468                 list = g_slist_remove(list, list->data);
469         }
470 }
471
472 void chat_commands_init(void)
473 {
474         settings_add_str("misc", "quit_message", "leaving");
475
476         command_bind("server", NULL, (SIGNAL_FUNC) cmd_server);
477         command_bind("server connect", NULL, (SIGNAL_FUNC) cmd_server_connect);
478         command_bind("connect", NULL, (SIGNAL_FUNC) cmd_connect);
479         command_bind("disconnect", NULL, (SIGNAL_FUNC) cmd_disconnect);
480         command_bind("quit", NULL, (SIGNAL_FUNC) cmd_quit);
481         command_bind("join", NULL, (SIGNAL_FUNC) cmd_join);
482         command_bind("msg", NULL, (SIGNAL_FUNC) cmd_msg);
483         command_bind("foreach", NULL, (SIGNAL_FUNC) cmd_foreach);
484         command_bind("foreach server", NULL, (SIGNAL_FUNC) cmd_foreach_server);
485         command_bind("foreach channel", NULL, (SIGNAL_FUNC) cmd_foreach_channel);
486         command_bind("foreach query", NULL, (SIGNAL_FUNC) cmd_foreach_query);
487
488         signal_add("default command server", (SIGNAL_FUNC) sig_default_command_server);
489         signal_add("server sendmsg", (SIGNAL_FUNC) sig_server_sendmsg);
490
491         command_set_options("connect", "4 6 !! -network ssl +ssl_cert +ssl_pkey ssl_verify +ssl_cafile +ssl_capath +host noproxy -rawlog");
492         command_set_options("join", "invite");
493         command_set_options("msg", "channel nick");
494 }
495
496 void chat_commands_deinit(void)
497 {
498         command_unbind("server", (SIGNAL_FUNC) cmd_server);
499         command_unbind("server connect", (SIGNAL_FUNC) cmd_server_connect);
500         command_unbind("connect", (SIGNAL_FUNC) cmd_connect);
501         command_unbind("disconnect", (SIGNAL_FUNC) cmd_disconnect);
502         command_unbind("quit", (SIGNAL_FUNC) cmd_quit);
503         command_unbind("join", (SIGNAL_FUNC) cmd_join);
504         command_unbind("msg", (SIGNAL_FUNC) cmd_msg);
505         command_unbind("foreach", (SIGNAL_FUNC) cmd_foreach);
506         command_unbind("foreach server", (SIGNAL_FUNC) cmd_foreach_server);
507         command_unbind("foreach channel", (SIGNAL_FUNC) cmd_foreach_channel);
508         command_unbind("foreach query", (SIGNAL_FUNC) cmd_foreach_query);
509
510         signal_remove("default command server", (SIGNAL_FUNC) sig_default_command_server);
511         signal_remove("server sendmsg", (SIGNAL_FUNC) sig_server_sendmsg);
512 }