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 along
17     with this program; if not, write to the Free Software Foundation, Inc.,
18     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 
get_server_connect(const char * data,int * plus_addr,char ** rawlog_file)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, *tmp;
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 (g_strcmp0(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 
71 	if (chatnet == NULL)
72 		chatnet = g_hash_table_lookup(optlist, "network");
73 
74 	conn = server_create_conn(proto != NULL ? proto->id : -1, addr,
75 				  atoi(portstr), chatnet, password, nick);
76 	if (conn == NULL) {
77 		signal_emit("error command", 1,
78 			    GINT_TO_POINTER(CMDERR_NO_SERVER_DEFINED));
79 		cmd_params_free(free_arg);
80 		return NULL;
81 	}
82 
83 	if (proto == NULL)
84 		proto = chat_protocol_find_id(conn->chat_type);
85 
86 	if (proto->not_initialized) {
87 		/* trying to use protocol that isn't yet initialized */
88 		signal_emit("chat protocol unknown", 1, proto->name);
89 		server_connect_unref(conn);
90                 cmd_params_free(free_arg);
91 		return NULL;
92 	}
93 
94 	if (strchr(addr, '/') != NULL)
95 		conn->unix_socket = TRUE;
96 
97 	if (g_hash_table_lookup(optlist, "6") != NULL)
98 		conn->family = AF_INET6;
99 	else if (g_hash_table_lookup(optlist, "4") != NULL)
100 		conn->family = AF_INET;
101 
102 	if (g_hash_table_lookup(optlist, "tls") != NULL || g_hash_table_lookup(optlist, "ssl") != NULL)
103 		conn->use_tls = TRUE;
104 	if ((tmp = g_hash_table_lookup(optlist, "tls_cert")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_cert")) != NULL)
105 		conn->tls_cert = g_strdup(tmp);
106 	if ((tmp = g_hash_table_lookup(optlist, "tls_pkey")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_pkey")) != NULL)
107 		conn->tls_pkey = g_strdup(tmp);
108 	if ((tmp = g_hash_table_lookup(optlist, "tls_pass")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_pass")) != NULL)
109 		conn->tls_pass = g_strdup(tmp);
110 	if (g_hash_table_lookup(optlist, "tls_verify") != NULL || g_hash_table_lookup(optlist, "ssl_verify") != NULL)
111 		conn->tls_verify = TRUE;
112 	if ((tmp = g_hash_table_lookup(optlist, "tls_cafile")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_cafile")) != NULL)
113 		conn->tls_cafile = g_strdup(tmp);
114 	if ((tmp = g_hash_table_lookup(optlist, "tls_capath")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_capath")) != NULL)
115 		conn->tls_capath = g_strdup(tmp);
116 	if ((tmp = g_hash_table_lookup(optlist, "tls_ciphers")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_ciphers")) != NULL)
117 		conn->tls_ciphers = g_strdup(tmp);
118 	if ((tmp = g_hash_table_lookup(optlist, "tls_pinned_cert")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_pinned_cert")) != NULL)
119 		conn->tls_pinned_cert = g_strdup(tmp);
120 	if ((tmp = g_hash_table_lookup(optlist, "tls_pinned_pubkey")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_pinned_pubkey")) != NULL)
121 		conn->tls_pinned_pubkey = g_strdup(tmp);
122 	if ((conn->tls_capath != NULL && conn->tls_capath[0] != '\0')
123 	||  (conn->tls_cafile != NULL && conn->tls_cafile[0] != '\0'))
124 		conn->tls_verify = TRUE;
125 	if ((conn->tls_cert != NULL && conn->tls_cert[0] != '\0') || conn->tls_verify)
126 		conn->use_tls = TRUE;
127 
128 	if (g_hash_table_lookup(optlist, "!") != NULL)
129 		conn->no_autojoin_channels = TRUE;
130 
131 	if (g_hash_table_lookup(optlist, "noautosendcmd") != NULL)
132 		conn->no_autosendcmd = TRUE;
133 
134 	if (g_hash_table_lookup(optlist, "noproxy") != NULL)
135                 g_free_and_null(conn->proxy);
136 
137 
138 	*rawlog_file = g_strdup(g_hash_table_lookup(optlist, "rawlog"));
139 
140         host = g_hash_table_lookup(optlist, "host");
141 	if (host != NULL && *host != '\0') {
142 		IPADDR ip4, ip6;
143 
144 		if (net_gethostbyname(host, &ip4, &ip6) == 0)
145                         server_connect_own_ip_save(conn, &ip4, &ip6);
146 	}
147 
148 	cmd_params_free(free_arg);
149         return conn;
150 }
151 
152 /* SYNTAX: CONNECT [-4 | -6] [-tls] [-tls_cert <cert>] [-tls_pkey <pkey>] [-tls_pass <password>]
153                    [-tls_verify] [-tls_cafile <cafile>] [-tls_capath <capath>]
154                    [-tls_ciphers <list>] [-tls_pinned_cert <fingerprint>] [-tls_pinned_pubkey <fingerprint>]
155                    [-!] [-noautosendcmd]
156 		   [-noproxy] [-network <network>] [-host <hostname>]
157 		   [-rawlog <file>]
158 		   <address>|<chatnet> [<port> [<password> [<nick>]]] */
159 /* NOTE: -network replaces the old -ircnet flag. */
cmd_connect(const char * data)160 static void cmd_connect(const char *data)
161 {
162 	SERVER_CONNECT_REC *conn;
163 	SERVER_REC *server;
164         char *rawlog_file;
165 
166 	conn = get_server_connect(data, NULL, &rawlog_file);
167 	if (conn != NULL) {
168 		server = server_connect(conn);
169                 server_connect_unref(conn);
170 
171 		if (server != NULL && rawlog_file != NULL)
172 			rawlog_open(server->rawlog, rawlog_file);
173 
174 		g_free(rawlog_file);
175 	}
176 }
177 
find_reconnect_server(int chat_type,const char * addr,int port)178 static RECONNECT_REC *find_reconnect_server(int chat_type,
179 					    const char *addr, int port)
180 {
181 	RECONNECT_REC *match, *last_proto_match;
182 	GSList *tmp;
183         int count;
184 
185 	g_return_val_if_fail(addr != NULL, NULL);
186 
187 	/* check if there's a reconnection to the same host and maybe even
188 	   the same port */
189         match = last_proto_match = NULL; count = 0;
190 	for (tmp = reconnects; tmp != NULL; tmp = tmp->next) {
191 		RECONNECT_REC *rec = tmp->data;
192 
193 		if (rec->conn->chat_type == chat_type) {
194 			count++; last_proto_match = rec;
195 			if (g_ascii_strcasecmp(rec->conn->address, addr) == 0) {
196 				if (rec->conn->port == port)
197 					return rec;
198 				match = rec;
199 			}
200 		}
201 	}
202 
203 	if (count == 1) {
204 		/* only one reconnection with wanted protocol,
205 		   we probably want to use it */
206                 return last_proto_match;
207 	}
208 
209 	return match;
210 }
211 
update_reconnection(SERVER_CONNECT_REC * conn,SERVER_REC * server)212 static void update_reconnection(SERVER_CONNECT_REC *conn, SERVER_REC *server)
213 {
214 	SERVER_CONNECT_REC *oldconn;
215 	RECONNECT_REC *recon;
216 
217 	if (server != NULL) {
218 		oldconn = server->connrec;
219                 server_connect_ref(oldconn);
220                 reconnect_save_status(conn, server);
221 	} else {
222 		/* maybe we can reconnect some server from
223 		   reconnection queue */
224 		recon = find_reconnect_server(conn->chat_type,
225 					      conn->address, conn->port);
226 		if (recon == NULL) return;
227 
228 		oldconn = recon->conn;
229                 server_connect_ref(oldconn);
230 		server_reconnect_destroy(recon);
231 
232 		conn->away_reason = g_strdup(oldconn->away_reason);
233 		conn->channels = g_strdup(oldconn->channels);
234 	}
235 
236 	conn->reconnection = TRUE;
237 
238 	if (conn->chatnet == NULL && oldconn->chatnet != NULL)
239 		conn->chatnet = g_strdup(oldconn->chatnet);
240 
241 	server_connect_unref(oldconn);
242 	if (server != NULL) {
243 		signal_emit("command disconnect", 2,
244 			    "* Changing server", server);
245 	}
246 }
247 
cmd_server(const char * data,SERVER_REC * server,WI_ITEM_REC * item)248 static void cmd_server(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
249 {
250 	command_runsub("server", data, server, item);
251 }
252 
253 /* SYNTAX: SERVER CONNECT [-4 | -6] [-tls] [-tls_cert <cert>] [-tls_pkey <pkey>]
254 		  [-tls_pass <password>] [-tls_verify] [-tls_cafile <cafile>]
255 		  [-tls_capath <capath>]
256 		  [-tls_ciphers <list>] [-tls_pinned_cert <fingerprint>] [-tls_pinned_pubkey <fingerprint>]
257 		  [-!] [-noautosendcmd]
258 		  [-noproxy] [-network <network>] [-host <hostname>]
259 		  [-rawlog <file>]
260 		  [+]<address>|<chatnet> [<port> [<password> [<nick>]]] */
261 /* NOTE: -network replaces the old -ircnet flag. */
cmd_server_connect(const char * data,SERVER_REC * server)262 static void cmd_server_connect(const char *data, SERVER_REC *server)
263 {
264 	SERVER_CONNECT_REC *conn;
265         char *rawlog_file;
266 	int plus_addr;
267 
268 	g_return_if_fail(data != NULL);
269 
270         /* create connection record */
271 	conn = get_server_connect(data, &plus_addr, &rawlog_file);
272 	if (conn != NULL) {
273 		if (!plus_addr)
274 			update_reconnection(conn, server);
275 		server = server_connect(conn);
276 		server_connect_unref(conn);
277 
278 		if (server != NULL && rawlog_file != NULL)
279 			rawlog_open(server->rawlog, rawlog_file);
280 
281 		g_free(rawlog_file);
282 	}
283 }
284 
285 /* SYNTAX: DISCONNECT *|<tag> [<message>] */
cmd_disconnect(const char * data,SERVER_REC * server)286 static void cmd_disconnect(const char *data, SERVER_REC *server)
287 {
288 	char *tag, *msg;
289 	void *free_arg;
290 
291 	g_return_if_fail(data != NULL);
292 
293 	if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &tag, &msg))
294 		return;
295 
296 	if (*tag != '\0' && g_strcmp0(tag, "*") != 0) {
297 		server = server_find_tag(tag);
298 		if (server == NULL)
299 			server = server_find_lookup_tag(tag);
300 	}
301 	if (server == NULL) cmd_param_error(CMDERR_NOT_CONNECTED);
302 
303 	if (*msg == '\0') msg = (char *) settings_get_str("quit_message");
304 	signal_emit("server quit", 2, server, msg);
305 
306 	cmd_params_free(free_arg);
307 	server_disconnect(server);
308 }
309 
310 /* SYNTAX: QUIT [<message>] */
cmd_quit(const char * data)311 static void cmd_quit(const char *data)
312 {
313 	GSList *tmp, *next;
314 	const char *quitmsg;
315 	char *str;
316 
317 	g_return_if_fail(data != NULL);
318 
319 	quitmsg = *data != '\0' ? data :
320 		settings_get_str("quit_message");
321 
322 	/* disconnect from every server */
323 	for (tmp = servers; tmp != NULL; tmp = next) {
324 		next = tmp->next;
325 
326 		str = g_strdup_printf("* %s", quitmsg);
327 		cmd_disconnect(str, tmp->data);
328 		g_free(str);
329 	}
330 
331 	signal_emit("gui exit", 0);
332 }
333 
334 /* SYNTAX: MSG [-<server tag>] [-channel | -nick] *|<targets> <message> */
cmd_msg(const char * data,SERVER_REC * server,WI_ITEM_REC * item)335 static void cmd_msg(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
336 {
337 	GHashTable *optlist;
338 	char *target, *origtarget, *msg;
339 	void *free_arg;
340 	int free_ret, target_type = SEND_TARGET_NICK;
341 
342 	g_return_if_fail(data != NULL);
343 
344 	if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS |
345 			    PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
346 			    "msg", &optlist, &target, &msg))
347 		return;
348 	if (*target == '\0' || *msg == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
349 
350 	server = cmd_options_get_server("msg", optlist, server);
351 	if (server == NULL || !server->connected)
352 		cmd_param_error(CMDERR_NOT_CONNECTED);
353 
354         origtarget = target;
355 	free_ret = FALSE;
356 	if (g_strcmp0(target, ",") == 0 || g_strcmp0(target, ".") == 0) {
357 		target = parse_special(&target, server, item,
358 				       NULL, &free_ret, NULL, 0);
359 		if (target != NULL && *target == '\0') {
360 			if (free_ret)
361 				g_free(target);
362 			target = NULL;
363 			free_ret = FALSE;
364 		}
365 	}
366 
367 	if (target != NULL) {
368 		if (g_strcmp0(target, "*") == 0) {
369                         /* send to active channel/query */
370 			if (item == NULL)
371 				cmd_param_error(CMDERR_NOT_JOINED);
372 
373 			target_type = IS_CHANNEL(item) ?
374 				SEND_TARGET_CHANNEL : SEND_TARGET_NICK;
375 			target = (char *) window_item_get_target(item);
376 		} else if (g_hash_table_lookup(optlist, "channel") != NULL)
377                         target_type = SEND_TARGET_CHANNEL;
378 		else if (g_hash_table_lookup(optlist, "nick") != NULL)
379 			target_type = SEND_TARGET_NICK;
380 		else {
381 			/* Need to rely on server_ischannel(). If the protocol
382 			   doesn't really know if it's channel or nick based on
383 			   the name, it should just assume it's nick, because
384 			   when typing text to channels it's always sent with
385 			   /MSG -channel. */
386 			target_type = server_ischannel(server, target) ?
387 				SEND_TARGET_CHANNEL : SEND_TARGET_NICK;
388 		}
389 	}
390 	if (target != NULL) {
391 		char **splitmsgs;
392 		char **tmp = NULL;
393 		char *singlemsg[] = { msg, NULL };
394 		char *m;
395 		int n = 0;
396 
397 		/*
398 		 * If split_message is NULL, the server doesn't need to split
399 		 * long messages.
400 		 */
401 		if (server->split_message != NULL)
402 			splitmsgs = tmp = server->split_message(server, target,
403 								msg);
404 		else
405 			splitmsgs = singlemsg;
406 
407 		while ((m = splitmsgs[n++])) {
408 			signal_emit("server sendmsg", 4, server, target, m,
409 				    GINT_TO_POINTER(target_type));
410 			signal_emit(target_type == SEND_TARGET_CHANNEL ?
411 				    "message own_public" :
412 				    "message own_private", 4, server, m,
413 				    target, origtarget);
414 		}
415 		g_strfreev(tmp);
416 	} else {
417 		signal_emit("message own_private", 4, server, msg, target,
418 			    origtarget);
419 	}
420 
421 	if (free_ret && target != NULL) g_free(target);
422 	cmd_params_free(free_arg);
423 }
424 
sig_server_sendmsg(SERVER_REC * server,const char * target,const char * msg,void * target_type_p)425 static void sig_server_sendmsg(SERVER_REC *server, const char *target,
426 			       const char *msg, void *target_type_p)
427 {
428 	server->send_message(server, target, msg,
429 			     GPOINTER_TO_INT(target_type_p));
430 }
431 
cmd_foreach(const char * data,SERVER_REC * server,WI_ITEM_REC * item)432 static void cmd_foreach(const char *data, SERVER_REC *server,
433 			WI_ITEM_REC *item)
434 {
435 	command_runsub("foreach", data, server, item);
436 }
437 
438 /* SYNTAX: FOREACH SERVER <command> */
cmd_foreach_server(const char * data,SERVER_REC * server)439 static void cmd_foreach_server(const char *data, SERVER_REC *server)
440 {
441 	GSList *list;
442 	const char *cmdchars;
443 	char *str;
444 
445 	cmdchars = settings_get_str("cmdchars");
446 	str = strchr(cmdchars, *data) != NULL ? g_strdup(data) :
447 		g_strdup_printf("%c%s", *cmdchars, data);
448 
449 	list = g_slist_copy(servers);
450 	while (list != NULL) {
451 		signal_emit("send command", 3, str, list->data, NULL);
452 		list = g_slist_remove(list, list->data);
453 	}
454 
455 	g_free(str);
456 }
457 
458 /* SYNTAX: FOREACH CHANNEL <command> */
cmd_foreach_channel(const char * data)459 static void cmd_foreach_channel(const char *data)
460 {
461 	GSList *list;
462 	const char *cmdchars;
463 	char *str;
464 
465 	cmdchars = settings_get_str("cmdchars");
466 	str = strchr(cmdchars, *data) != NULL ? g_strdup(data) :
467 		g_strdup_printf("%c%s", *cmdchars, data);
468 
469 	list = g_slist_copy(channels);
470 	while (list != NULL) {
471 		CHANNEL_REC *rec = list->data;
472 
473 		signal_emit("send command", 3, str, rec->server, rec);
474 		list = g_slist_remove(list, list->data);
475 	}
476 
477 	g_free(str);
478 }
479 
480 /* SYNTAX: FOREACH QUERY <command> */
cmd_foreach_query(const char * data)481 static void cmd_foreach_query(const char *data)
482 {
483 	GSList *list;
484 	const char *cmdchars;
485 	char *str;
486 
487 	cmdchars = settings_get_str("cmdchars");
488 	str = strchr(cmdchars, *data) != NULL ? g_strdup(data) :
489 		g_strdup_printf("%c%s", *cmdchars, data);
490 
491 
492 	list = g_slist_copy(queries);
493 	while (list != NULL) {
494 		QUERY_REC *rec = list->data;
495 
496 		signal_emit("send command", 3, str, rec->server, rec);
497 		list = g_slist_remove(list, list->data);
498 	}
499 
500 	g_free(str);
501 }
502 
chat_commands_init(void)503 void chat_commands_init(void)
504 {
505 	settings_add_str("misc", "quit_message", "leaving");
506 
507 	command_bind("server", NULL, (SIGNAL_FUNC) cmd_server);
508 	command_bind("server connect", NULL, (SIGNAL_FUNC) cmd_server_connect);
509 	command_bind("connect", NULL, (SIGNAL_FUNC) cmd_connect);
510 	command_bind("disconnect", NULL, (SIGNAL_FUNC) cmd_disconnect);
511 	command_bind("quit", NULL, (SIGNAL_FUNC) cmd_quit);
512 	command_bind("msg", NULL, (SIGNAL_FUNC) cmd_msg);
513 	command_bind("foreach", NULL, (SIGNAL_FUNC) cmd_foreach);
514 	command_bind("foreach server", NULL, (SIGNAL_FUNC) cmd_foreach_server);
515 	command_bind("foreach channel", NULL, (SIGNAL_FUNC) cmd_foreach_channel);
516 	command_bind("foreach query", NULL, (SIGNAL_FUNC) cmd_foreach_query);
517 
518 	signal_add("server sendmsg", (SIGNAL_FUNC) sig_server_sendmsg);
519 
520 	command_set_options("connect", "4 6 !! -network ssl +ssl_cert +ssl_pkey +ssl_pass ssl_verify +ssl_cafile +ssl_capath +ssl_ciphers +ssl_pinned_cert +ssl_pinned_pubkey tls +tls_cert +tls_pkey +tls_pass tls_verify +tls_cafile +tls_capath +tls_ciphers +tls_pinned_cert +tls_pinned_pubkey +host noproxy -rawlog noautosendcmd");
521 	command_set_options("msg", "channel nick");
522 }
523 
chat_commands_deinit(void)524 void chat_commands_deinit(void)
525 {
526 	command_unbind("server", (SIGNAL_FUNC) cmd_server);
527 	command_unbind("server connect", (SIGNAL_FUNC) cmd_server_connect);
528 	command_unbind("connect", (SIGNAL_FUNC) cmd_connect);
529 	command_unbind("disconnect", (SIGNAL_FUNC) cmd_disconnect);
530 	command_unbind("quit", (SIGNAL_FUNC) cmd_quit);
531 	command_unbind("msg", (SIGNAL_FUNC) cmd_msg);
532 	command_unbind("foreach", (SIGNAL_FUNC) cmd_foreach);
533 	command_unbind("foreach server", (SIGNAL_FUNC) cmd_foreach_server);
534 	command_unbind("foreach channel", (SIGNAL_FUNC) cmd_foreach_channel);
535 	command_unbind("foreach query", (SIGNAL_FUNC) cmd_foreach_query);
536 
537 	signal_remove("server sendmsg", (SIGNAL_FUNC) sig_server_sendmsg);
538 }
539