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