1 /*
2  * cmd_funcs.c
3  * vim: expandtab:ts=4:sts=4:sw=4
4  *
5  * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
6  * Copyright (C) 2019 Michael Vetter <jubalh@iodoru.org>
7  * Copyright (C) 2020 William Wennerström <william@wstrm.dev>
8  *
9  * This file is part of Profanity.
10  *
11  * Profanity 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 3 of the License, or
14  * (at your option) any later version.
15  *
16  * Profanity 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 Profanity.  If not, see <https://www.gnu.org/licenses/>.
23  *
24  * In addition, as a special exception, the copyright holders give permission to
25  * link the code of portions of this program with the OpenSSL library under
26  * certain conditions as described in each individual source file, and
27  * distribute linked combinations including the two.
28  *
29  * You must obey the GNU General Public License in all respects for all of the
30  * code used other than OpenSSL. If you modify file(s) with this exception, you
31  * may extend this exception to your version of the file(s), but you are not
32  * obligated to do so. If you do not wish to do so, delete this exception
33  * statement from your version. If you delete this exception statement from all
34  * source files in the program, then also delete it here.
35  *
36  */
37 
38 #include "config.h"
39 
40 #include <string.h>
41 #include <stdlib.h>
42 #include <stdio.h>
43 #include <errno.h>
44 #include <assert.h>
45 #include <glib.h>
46 #include <glib/gstdio.h>
47 #include <gio/gio.h>
48 #include <sys/stat.h>
49 #include <sys/types.h>
50 #include <fcntl.h>
51 #include <unistd.h>
52 #include <langinfo.h>
53 #include <ctype.h>
54 
55 // fork / execl
56 #include <sys/types.h>
57 #include <unistd.h>
58 #include <sys/wait.h>
59 #include <readline/readline.h>
60 
61 #include "profanity.h"
62 #include "log.h"
63 #include "common.h"
64 #include "command/cmd_funcs.h"
65 #include "command/cmd_defs.h"
66 #include "command/cmd_ac.h"
67 #include "config/files.h"
68 #include "config/accounts.h"
69 #include "config/account.h"
70 #include "config/preferences.h"
71 #include "config/theme.h"
72 #include "config/tlscerts.h"
73 #include "config/scripts.h"
74 #include "event/client_events.h"
75 #include "tools/http_upload.h"
76 #include "tools/http_download.h"
77 #include "tools/autocomplete.h"
78 #include "tools/parser.h"
79 #include "tools/bookmark_ignore.h"
80 #include "plugins/plugins.h"
81 #include "ui/ui.h"
82 #include "ui/window_list.h"
83 #include "xmpp/xmpp.h"
84 #include "xmpp/connection.h"
85 #include "xmpp/contact.h"
86 #include "xmpp/roster_list.h"
87 #include "xmpp/jid.h"
88 #include "xmpp/muc.h"
89 #include "xmpp/chat_session.h"
90 #include "xmpp/avatar.h"
91 
92 #ifdef HAVE_LIBOTR
93 #include "otr/otr.h"
94 #endif
95 
96 #ifdef HAVE_LIBGPGME
97 #include "pgp/gpg.h"
98 #include "xmpp/ox.h"
99 #endif
100 
101 #ifdef HAVE_OMEMO
102 #include "omemo/omemo.h"
103 #include "xmpp/omemo.h"
104 #include "tools/aesgcm_download.h"
105 #endif
106 
107 #ifdef HAVE_GTK
108 #include "ui/tray.h"
109 #include "tools/clipboard.h"
110 #endif
111 
112 #ifdef HAVE_PYTHON
113 #include "plugins/python_plugins.h"
114 #endif
115 
116 static void _update_presence(const resource_presence_t presence,
117                              const char* const show, gchar** args);
118 static void _cmd_set_boolean_preference(gchar* arg, const char* const command,
119                                         const char* const display, preference_t pref);
120 static void _who_room(ProfWin* window, const char* const command, gchar** args);
121 static void _who_roster(ProfWin* window, const char* const command, gchar** args);
122 static gboolean _cmd_execute(ProfWin* window, const char* const command, const char* const inp);
123 static gboolean _cmd_execute_default(ProfWin* window, const char* inp);
124 static gboolean _cmd_execute_alias(ProfWin* window, const char* const inp, gboolean* ran);
125 
126 /*
127  * Take a line of input and process it, return TRUE if profanity is to
128  * continue, FALSE otherwise
129  */
130 gboolean
cmd_process_input(ProfWin * window,char * inp)131 cmd_process_input(ProfWin* window, char* inp)
132 {
133     log_debug("Input received: %s", inp);
134     gboolean result = FALSE;
135     g_strchomp(inp);
136 
137     // just carry on if no input
138     if (strlen(inp) == 0) {
139         result = TRUE;
140 
141         // handle command if input starts with a '/'
142     } else if (inp[0] == '/') {
143         char* inp_cpy = strdup(inp);
144         char* command = strtok(inp_cpy, " ");
145         char* question_mark = strchr(command, '?');
146         if (question_mark) {
147             *question_mark = '\0';
148             gchar* fakeinp = g_strdup_printf("/help %s", command + 1);
149             if (fakeinp) {
150                 result = _cmd_execute(window, "/help", fakeinp);
151                 g_free(fakeinp);
152             }
153         } else {
154             result = _cmd_execute(window, command, inp);
155         }
156         free(inp_cpy);
157 
158         // call a default handler if input didn't start with '/'
159     } else {
160         result = _cmd_execute_default(window, inp);
161     }
162 
163     return result;
164 }
165 
166 // Command execution
167 
168 void
cmd_execute_connect(ProfWin * window,const char * const account)169 cmd_execute_connect(ProfWin* window, const char* const account)
170 {
171     GString* command = g_string_new("/connect ");
172     g_string_append(command, account);
173     cmd_process_input(window, command->str);
174     g_string_free(command, TRUE);
175 }
176 
177 gboolean
cmd_tls_certpath(ProfWin * window,const char * const command,gchar ** args)178 cmd_tls_certpath(ProfWin* window, const char* const command, gchar** args)
179 {
180 #ifdef HAVE_LIBMESODE
181     if (g_strcmp0(args[1], "set") == 0) {
182         if (args[2] == NULL) {
183             cons_bad_cmd_usage(command);
184             return TRUE;
185         }
186 
187         if (g_file_test(args[2], G_FILE_TEST_IS_DIR)) {
188             prefs_set_string(PREF_TLS_CERTPATH, args[2]);
189             cons_show("Certificate path set to: %s", args[2]);
190         } else {
191             cons_show("Directory %s does not exist.", args[2]);
192         }
193         return TRUE;
194     } else if (g_strcmp0(args[1], "clear") == 0) {
195         prefs_set_string(PREF_TLS_CERTPATH, "none");
196         cons_show("Certificate path cleared");
197         return TRUE;
198     } else if (g_strcmp0(args[1], "default") == 0) {
199         prefs_set_string(PREF_TLS_CERTPATH, NULL);
200         cons_show("Certificate path defaulted to finding system certpath.");
201         return TRUE;
202     } else if (args[1] == NULL) {
203         char* path = prefs_get_tls_certpath();
204         if (path) {
205             cons_show("Trusted certificate path: %s", path);
206             free(path);
207         } else {
208             cons_show("No trusted certificate path set.");
209         }
210         return TRUE;
211     } else {
212         cons_bad_cmd_usage(command);
213         return TRUE;
214     }
215 #else
216     cons_show("Certificate path setting only supported when built with libmesode.");
217     return TRUE;
218 #endif
219 }
220 
221 gboolean
cmd_tls_trust(ProfWin * window,const char * const command,gchar ** args)222 cmd_tls_trust(ProfWin* window, const char* const command, gchar** args)
223 {
224 #ifdef HAVE_LIBMESODE
225     jabber_conn_status_t conn_status = connection_get_status();
226     if (conn_status != JABBER_CONNECTED) {
227         cons_show("You are currently not connected.");
228         return TRUE;
229     }
230     if (!connection_is_secured()) {
231         cons_show("No TLS connection established");
232         return TRUE;
233     }
234     TLSCertificate* cert = connection_get_tls_peer_cert();
235     if (!cert) {
236         cons_show("Error getting TLS certificate.");
237         return TRUE;
238     }
239     if (tlscerts_exists(cert->fingerprint)) {
240         cons_show("Certificate %s already trusted.", cert->fingerprint);
241         tlscerts_free(cert);
242         return TRUE;
243     }
244     cons_show("Adding %s to trusted certificates.", cert->fingerprint);
245     tlscerts_add(cert);
246     tlscerts_free(cert);
247     return TRUE;
248 #else
249     cons_show("Manual certificate trust only supported when built with libmesode.");
250     return TRUE;
251 #endif
252 }
253 
254 gboolean
cmd_tls_trusted(ProfWin * window,const char * const command,gchar ** args)255 cmd_tls_trusted(ProfWin* window, const char* const command, gchar** args)
256 {
257 #ifdef HAVE_LIBMESODE
258     GList* certs = tlscerts_list();
259     GList* curr = certs;
260 
261     if (curr) {
262         cons_show("Trusted certificates:");
263         cons_show("");
264     } else {
265         cons_show("No trusted certificates found.");
266     }
267     while (curr) {
268         TLSCertificate* cert = curr->data;
269         cons_show_tlscert_summary(cert);
270         cons_show("");
271         curr = g_list_next(curr);
272     }
273     g_list_free_full(certs, (GDestroyNotify)tlscerts_free);
274     return TRUE;
275 #else
276     cons_show("Manual certificate trust only supported when built with libmesode.");
277     return TRUE;
278 #endif
279 }
280 
281 gboolean
cmd_tls_revoke(ProfWin * window,const char * const command,gchar ** args)282 cmd_tls_revoke(ProfWin* window, const char* const command, gchar** args)
283 {
284 #ifdef HAVE_LIBMESODE
285     if (args[1] == NULL) {
286         cons_bad_cmd_usage(command);
287     } else {
288         gboolean res = tlscerts_revoke(args[1]);
289         if (res) {
290             cons_show("Trusted certificate revoked: %s", args[1]);
291         } else {
292             cons_show("Could not find certificate: %s", args[1]);
293         }
294     }
295     return TRUE;
296 #else
297     cons_show("Manual certificate trust only supported when built with libmesode.");
298     return TRUE;
299 #endif
300 }
301 
302 gboolean
cmd_tls_cert(ProfWin * window,const char * const command,gchar ** args)303 cmd_tls_cert(ProfWin* window, const char* const command, gchar** args)
304 {
305 #ifdef HAVE_LIBMESODE
306     if (args[1]) {
307         TLSCertificate* cert = tlscerts_get_trusted(args[1]);
308         if (!cert) {
309             cons_show("No such certificate.");
310         } else {
311             cons_show_tlscert(cert);
312             tlscerts_free(cert);
313         }
314         return TRUE;
315     } else {
316         jabber_conn_status_t conn_status = connection_get_status();
317         if (conn_status != JABBER_CONNECTED) {
318             cons_show("You are not currently connected.");
319             return TRUE;
320         }
321         if (!connection_is_secured()) {
322             cons_show("No TLS connection established");
323             return TRUE;
324         }
325         TLSCertificate* cert = connection_get_tls_peer_cert();
326         if (!cert) {
327             cons_show("Error getting TLS certificate.");
328             return TRUE;
329         }
330         cons_show_tlscert(cert);
331         cons_show("");
332         tlscerts_free(cert);
333         return TRUE;
334     }
335 #else
336     cons_show("Certificate fetching not supported.");
337     return TRUE;
338 #endif
339 }
340 
341 gboolean
cmd_connect(ProfWin * window,const char * const command,gchar ** args)342 cmd_connect(ProfWin* window, const char* const command, gchar** args)
343 {
344     jabber_conn_status_t conn_status = connection_get_status();
345     if (conn_status != JABBER_DISCONNECTED) {
346         cons_show("You are either connected already, or a login is in process.");
347         return TRUE;
348     }
349 
350     gchar* opt_keys[] = { "server", "port", "tls", "auth", NULL };
351     gboolean parsed;
352 
353     GHashTable* options = parse_options(&args[args[0] ? 1 : 0], opt_keys, &parsed);
354     if (!parsed) {
355         cons_bad_cmd_usage(command);
356         cons_show("");
357         options_destroy(options);
358         return TRUE;
359     }
360 
361     char* altdomain = g_hash_table_lookup(options, "server");
362 
363     char* tls_policy = g_hash_table_lookup(options, "tls");
364     if (tls_policy && (g_strcmp0(tls_policy, "force") != 0) && (g_strcmp0(tls_policy, "allow") != 0) && (g_strcmp0(tls_policy, "trust") != 0) && (g_strcmp0(tls_policy, "disable") != 0) && (g_strcmp0(tls_policy, "legacy") != 0)) {
365         cons_bad_cmd_usage(command);
366         cons_show("");
367         options_destroy(options);
368         return TRUE;
369     }
370 
371     char* auth_policy = g_hash_table_lookup(options, "auth");
372     if (auth_policy && (g_strcmp0(auth_policy, "default") != 0) && (g_strcmp0(auth_policy, "legacy") != 0)) {
373         cons_bad_cmd_usage(command);
374         cons_show("");
375         options_destroy(options);
376         return TRUE;
377     }
378 
379     int port = 0;
380     if (g_hash_table_contains(options, "port")) {
381         char* port_str = g_hash_table_lookup(options, "port");
382         char* err_msg = NULL;
383         gboolean res = strtoi_range(port_str, &port, 1, 65535, &err_msg);
384         if (!res) {
385             cons_show(err_msg);
386             cons_show("");
387             free(err_msg);
388             port = 0;
389             options_destroy(options);
390             return TRUE;
391         }
392     }
393 
394     char* user = args[0];
395     char* def = prefs_get_string(PREF_DEFAULT_ACCOUNT);
396     if (!user) {
397         if (def) {
398             user = def;
399             cons_show("Using default account %s.", user);
400         } else {
401             cons_show("No default account.");
402             options_destroy(options);
403             return TRUE;
404         }
405     }
406 
407     char* jid;
408     user = strdup(user);
409     g_free(def);
410 
411     // connect with account
412     ProfAccount* account = accounts_get_account(user);
413     if (account) {
414         // override account options with connect options
415         if (altdomain != NULL)
416             account_set_server(account, altdomain);
417         if (port != 0)
418             account_set_port(account, port);
419         if (tls_policy != NULL)
420             account_set_tls_policy(account, tls_policy);
421         if (auth_policy != NULL)
422             account_set_auth_policy(account, auth_policy);
423 
424         // use password if set
425         if (account->password) {
426             conn_status = cl_ev_connect_account(account);
427 
428             // use eval_password if set
429         } else if (account->eval_password) {
430             gboolean res = account_eval_password(account);
431             if (res) {
432                 conn_status = cl_ev_connect_account(account);
433                 free(account->password);
434                 account->password = NULL;
435             } else {
436                 cons_show("Error evaluating password, see logs for details.");
437                 account_free(account);
438                 free(user);
439                 options_destroy(options);
440                 return TRUE;
441             }
442 
443             // no account password setting, prompt
444         } else {
445             account->password = ui_ask_password(false);
446             conn_status = cl_ev_connect_account(account);
447             free(account->password);
448             account->password = NULL;
449         }
450 
451         jid = account_create_connect_jid(account);
452         account_free(account);
453 
454         // connect with JID
455     } else {
456         jid = g_utf8_strdown(user, -1);
457         char* passwd = ui_ask_password(false);
458         conn_status = cl_ev_connect_jid(jid, passwd, altdomain, port, tls_policy, auth_policy);
459         free(passwd);
460     }
461 
462     if (conn_status == JABBER_DISCONNECTED) {
463         cons_show_error("Connection attempt for %s failed.", jid);
464         log_info("Connection attempt for %s failed", jid);
465     }
466 
467     options_destroy(options);
468     free(jid);
469     free(user);
470 
471     return TRUE;
472 }
473 
474 gboolean
cmd_account_list(ProfWin * window,const char * const command,gchar ** args)475 cmd_account_list(ProfWin* window, const char* const command, gchar** args)
476 {
477     gchar** accounts = accounts_get_list();
478     cons_show_account_list(accounts);
479     g_strfreev(accounts);
480 
481     return TRUE;
482 }
483 
484 gboolean
cmd_account_show(ProfWin * window,const char * const command,gchar ** args)485 cmd_account_show(ProfWin* window, const char* const command, gchar** args)
486 {
487     char* account_name = args[1];
488     if (account_name == NULL) {
489         cons_bad_cmd_usage(command);
490         return TRUE;
491     }
492 
493     ProfAccount* account = accounts_get_account(account_name);
494     if (account == NULL) {
495         cons_show("No such account.");
496         cons_show("");
497     } else {
498         cons_show_account(account);
499         account_free(account);
500     }
501 
502     return TRUE;
503 }
504 
505 gboolean
cmd_account_add(ProfWin * window,const char * const command,gchar ** args)506 cmd_account_add(ProfWin* window, const char* const command, gchar** args)
507 {
508     char* account_name = args[1];
509     if (account_name == NULL) {
510         cons_bad_cmd_usage(command);
511         return TRUE;
512     }
513 
514     accounts_add(account_name, NULL, 0, NULL, NULL);
515     cons_show("Account created.");
516     cons_show("");
517 
518     return TRUE;
519 }
520 
521 gboolean
cmd_account_remove(ProfWin * window,const char * const command,gchar ** args)522 cmd_account_remove(ProfWin* window, const char* const command, gchar** args)
523 {
524     char* account_name = args[1];
525     if (!account_name) {
526         cons_bad_cmd_usage(command);
527         return TRUE;
528     }
529 
530     char* def = prefs_get_string(PREF_DEFAULT_ACCOUNT);
531     if (accounts_remove(account_name)) {
532         cons_show("Account %s removed.", account_name);
533         if (def && strcmp(def, account_name) == 0) {
534             prefs_set_string(PREF_DEFAULT_ACCOUNT, NULL);
535             cons_show("Default account removed because the corresponding account was removed.");
536         }
537     } else {
538         cons_show("Failed to remove account %s.", account_name);
539         cons_show("Either the account does not exist, or an unknown error occurred.");
540     }
541     cons_show("");
542     g_free(def);
543 
544     return TRUE;
545 }
546 
547 gboolean
cmd_account_enable(ProfWin * window,const char * const command,gchar ** args)548 cmd_account_enable(ProfWin* window, const char* const command, gchar** args)
549 {
550     char* account_name = args[1];
551     if (account_name == NULL) {
552         cons_bad_cmd_usage(command);
553         return TRUE;
554     }
555 
556     if (accounts_enable(account_name)) {
557         cons_show("Account enabled.");
558         cons_show("");
559     } else {
560         cons_show("No such account: %s", account_name);
561         cons_show("");
562     }
563 
564     return TRUE;
565 }
566 gboolean
cmd_account_disable(ProfWin * window,const char * const command,gchar ** args)567 cmd_account_disable(ProfWin* window, const char* const command, gchar** args)
568 {
569     char* account_name = args[1];
570     if (account_name == NULL) {
571         cons_bad_cmd_usage(command);
572         return TRUE;
573     }
574 
575     if (accounts_disable(account_name)) {
576         cons_show("Account disabled.");
577         cons_show("");
578     } else {
579         cons_show("No such account: %s", account_name);
580         cons_show("");
581     }
582 
583     return TRUE;
584 }
585 
586 gboolean
cmd_account_rename(ProfWin * window,const char * const command,gchar ** args)587 cmd_account_rename(ProfWin* window, const char* const command, gchar** args)
588 {
589     if (g_strv_length(args) != 3) {
590         cons_bad_cmd_usage(command);
591         return TRUE;
592     }
593 
594     char* account_name = args[1];
595     char* new_name = args[2];
596 
597     if (accounts_rename(account_name, new_name)) {
598         cons_show("Account renamed.");
599         cons_show("");
600     } else {
601         cons_show("Either account %s doesn't exist, or account %s already exists.", account_name, new_name);
602         cons_show("");
603     }
604 
605     return TRUE;
606 }
607 
608 gboolean
cmd_account_default(ProfWin * window,const char * const command,gchar ** args)609 cmd_account_default(ProfWin* window, const char* const command, gchar** args)
610 {
611     if (g_strv_length(args) == 1) {
612         char* def = prefs_get_string(PREF_DEFAULT_ACCOUNT);
613         if (def) {
614             cons_show("The default account is %s.", def);
615             free(def);
616         } else {
617             cons_show("No default account.");
618         }
619     } else if (g_strv_length(args) == 2) {
620         if (strcmp(args[1], "off") == 0) {
621             prefs_set_string(PREF_DEFAULT_ACCOUNT, NULL);
622             cons_show("Removed default account.");
623         } else {
624             cons_bad_cmd_usage(command);
625         }
626     } else if (g_strv_length(args) == 3) {
627         if (strcmp(args[1], "set") == 0) {
628             ProfAccount* account_p = accounts_get_account(args[2]);
629             if (account_p) {
630                 prefs_set_string(PREF_DEFAULT_ACCOUNT, args[2]);
631                 cons_show("Default account set to %s.", args[2]);
632                 account_free(account_p);
633             } else {
634                 cons_show("Account %s does not exist.", args[2]);
635             }
636         } else {
637             cons_bad_cmd_usage(command);
638         }
639     } else {
640         cons_bad_cmd_usage(command);
641     }
642 
643     return TRUE;
644 }
645 
646 gboolean
_account_set_jid(char * account_name,char * jid)647 _account_set_jid(char* account_name, char* jid)
648 {
649     Jid* jidp = jid_create(jid);
650     if (jidp == NULL) {
651         cons_show("Malformed jid: %s", jid);
652     } else {
653         accounts_set_jid(account_name, jidp->barejid);
654         cons_show("Updated jid for account %s: %s", account_name, jidp->barejid);
655         if (jidp->resourcepart) {
656             accounts_set_resource(account_name, jidp->resourcepart);
657             cons_show("Updated resource for account %s: %s", account_name, jidp->resourcepart);
658         }
659         cons_show("");
660     }
661     jid_destroy(jidp);
662 
663     return TRUE;
664 }
665 
666 gboolean
_account_set_server(char * account_name,char * server)667 _account_set_server(char* account_name, char* server)
668 {
669     accounts_set_server(account_name, server);
670     cons_show("Updated server for account %s: %s", account_name, server);
671     cons_show("");
672     return TRUE;
673 }
674 
675 gboolean
_account_set_port(char * account_name,char * port)676 _account_set_port(char* account_name, char* port)
677 {
678     int porti;
679     char* err_msg = NULL;
680     gboolean res = strtoi_range(port, &porti, 1, 65535, &err_msg);
681     if (!res) {
682         cons_show(err_msg);
683         cons_show("");
684         free(err_msg);
685     } else {
686         accounts_set_port(account_name, porti);
687         cons_show("Updated port for account %s: %s", account_name, port);
688         cons_show("");
689     }
690     return TRUE;
691 }
692 
693 gboolean
_account_set_resource(char * account_name,char * resource)694 _account_set_resource(char* account_name, char* resource)
695 {
696     accounts_set_resource(account_name, resource);
697     if (connection_get_status() == JABBER_CONNECTED) {
698         cons_show("Updated resource for account %s: %s, reconnect to pick up the change.", account_name, resource);
699     } else {
700         cons_show("Updated resource for account %s: %s", account_name, resource);
701     }
702     cons_show("");
703     return TRUE;
704 }
705 
706 gboolean
_account_set_password(char * account_name,char * password)707 _account_set_password(char* account_name, char* password)
708 {
709     ProfAccount* account = accounts_get_account(account_name);
710     if (account->eval_password) {
711         cons_show("Cannot set password when eval_password is set.");
712     } else {
713         accounts_set_password(account_name, password);
714         cons_show("Updated password for account %s", account_name);
715         cons_show("");
716     }
717     account_free(account);
718     return TRUE;
719 }
720 
721 gboolean
_account_set_eval_password(char * account_name,char * eval_password)722 _account_set_eval_password(char* account_name, char* eval_password)
723 {
724     ProfAccount* account = accounts_get_account(account_name);
725     if (account->password) {
726         cons_show("Cannot set eval_password when password is set.");
727     } else {
728         accounts_set_eval_password(account_name, eval_password);
729         cons_show("Updated eval_password for account %s", account_name);
730         cons_show("");
731     }
732     account_free(account);
733     return TRUE;
734 }
735 
736 gboolean
_account_set_muc(char * account_name,char * muc)737 _account_set_muc(char* account_name, char* muc)
738 {
739     accounts_set_muc_service(account_name, muc);
740     cons_show("Updated muc service for account %s: %s", account_name, muc);
741     cons_show("");
742     return TRUE;
743 }
744 
745 gboolean
_account_set_nick(char * account_name,char * nick)746 _account_set_nick(char* account_name, char* nick)
747 {
748     accounts_set_muc_nick(account_name, nick);
749     cons_show("Updated muc nick for account %s: %s", account_name, nick);
750     cons_show("");
751     return TRUE;
752 }
753 
754 gboolean
_account_set_otr(char * account_name,char * policy)755 _account_set_otr(char* account_name, char* policy)
756 {
757     if ((g_strcmp0(policy, "manual") != 0)
758         && (g_strcmp0(policy, "opportunistic") != 0)
759         && (g_strcmp0(policy, "always") != 0)) {
760         cons_show("OTR policy must be one of: manual, opportunistic or always.");
761     } else {
762         accounts_set_otr_policy(account_name, policy);
763         cons_show("Updated OTR policy for account %s: %s", account_name, policy);
764         cons_show("");
765     }
766     return TRUE;
767 }
768 
769 gboolean
_account_set_status(char * account_name,char * status)770 _account_set_status(char* account_name, char* status)
771 {
772     if (!valid_resource_presence_string(status) && (strcmp(status, "last") != 0)) {
773         cons_show("Invalid status: %s", status);
774     } else {
775         accounts_set_login_presence(account_name, status);
776         cons_show("Updated login status for account %s: %s", account_name, status);
777     }
778     cons_show("");
779     return TRUE;
780 }
781 
782 gboolean
_account_set_pgpkeyid(char * account_name,char * pgpkeyid)783 _account_set_pgpkeyid(char* account_name, char* pgpkeyid)
784 {
785 #ifdef HAVE_LIBGPGME
786     char* err_str = NULL;
787     if (!p_gpg_valid_key(pgpkeyid, &err_str)) {
788         cons_show("Invalid PGP key ID specified: %s, see /pgp keys", err_str);
789     } else {
790         accounts_set_pgp_keyid(account_name, pgpkeyid);
791         cons_show("Updated PGP key ID for account %s: %s", account_name, pgpkeyid);
792     }
793     free(err_str);
794 #else
795     cons_show("PGP support is not included in this build.");
796 #endif
797     cons_show("");
798     return TRUE;
799 }
800 
801 gboolean
_account_set_startscript(char * account_name,char * script)802 _account_set_startscript(char* account_name, char* script)
803 {
804     accounts_set_script_start(account_name, script);
805     cons_show("Updated start script for account %s: %s", account_name, script);
806     return TRUE;
807 }
808 
809 gboolean
_account_set_theme(char * account_name,char * theme)810 _account_set_theme(char* account_name, char* theme)
811 {
812     if (!theme_exists(theme)) {
813         cons_show("Theme does not exist: %s", theme);
814         return TRUE;
815     }
816 
817     accounts_set_theme(account_name, theme);
818     if (connection_get_status() == JABBER_CONNECTED) {
819         ProfAccount* account = accounts_get_account(session_get_account_name());
820         if (account) {
821             if (g_strcmp0(account->name, account_name) == 0) {
822                 theme_load(theme, false);
823                 ui_load_colours();
824                 if (prefs_get_boolean(PREF_ROSTER)) {
825                     ui_show_roster();
826                 } else {
827                     ui_hide_roster();
828                 }
829                 if (prefs_get_boolean(PREF_OCCUPANTS)) {
830                     ui_show_all_room_rosters();
831                 } else {
832                     ui_hide_all_room_rosters();
833                 }
834                 ui_redraw();
835             }
836             account_free(account);
837         }
838     }
839     cons_show("Updated theme for account %s: %s", account_name, theme);
840     return TRUE;
841 }
842 
843 gboolean
_account_set_tls(char * account_name,char * policy)844 _account_set_tls(char* account_name, char* policy)
845 {
846     if ((g_strcmp0(policy, "force") != 0)
847         && (g_strcmp0(policy, "allow") != 0)
848         && (g_strcmp0(policy, "trust") != 0)
849         && (g_strcmp0(policy, "disable") != 0)
850         && (g_strcmp0(policy, "legacy") != 0)) {
851         cons_show("TLS policy must be one of: force, allow, legacy or disable.");
852     } else {
853         accounts_set_tls_policy(account_name, policy);
854         cons_show("Updated TLS policy for account %s: %s", account_name, policy);
855         cons_show("");
856     }
857     return TRUE;
858 }
859 
860 gboolean
_account_set_auth(char * account_name,char * policy)861 _account_set_auth(char* account_name, char* policy)
862 {
863     if ((g_strcmp0(policy, "default") != 0)
864         && (g_strcmp0(policy, "legacy") != 0)) {
865         cons_show("Auth policy must be either default or legacy.");
866     } else {
867         accounts_set_auth_policy(account_name, policy);
868         cons_show("Updated auth policy for account %s: %s", account_name, policy);
869         cons_show("");
870     }
871     return TRUE;
872 }
873 
874 gboolean
_account_set_presence_priority(char * account_name,char * presence,char * priority)875 _account_set_presence_priority(char* account_name, char* presence, char* priority)
876 {
877     int intval;
878     char* err_msg = NULL;
879     gboolean res = strtoi_range(priority, &intval, -128, 127, &err_msg);
880     if (!res) {
881         cons_show(err_msg);
882         free(err_msg);
883         return TRUE;
884     }
885 
886     resource_presence_t presence_type = resource_presence_from_string(presence);
887     switch (presence_type) {
888     case (RESOURCE_ONLINE):
889         accounts_set_priority_online(account_name, intval);
890         break;
891     case (RESOURCE_CHAT):
892         accounts_set_priority_chat(account_name, intval);
893         break;
894     case (RESOURCE_AWAY):
895         accounts_set_priority_away(account_name, intval);
896         break;
897     case (RESOURCE_XA):
898         accounts_set_priority_xa(account_name, intval);
899         break;
900     case (RESOURCE_DND):
901         accounts_set_priority_dnd(account_name, intval);
902         break;
903     }
904 
905     jabber_conn_status_t conn_status = connection_get_status();
906     if (conn_status == JABBER_CONNECTED) {
907         char* connected_account = session_get_account_name();
908         resource_presence_t last_presence = accounts_get_last_presence(connected_account);
909         if (presence_type == last_presence) {
910             cl_ev_presence_send(last_presence, 0);
911         }
912     }
913     cons_show("Updated %s priority for account %s: %s", presence, account_name, priority);
914     cons_show("");
915     return TRUE;
916 }
917 
918 gboolean
cmd_account_set(ProfWin * window,const char * const command,gchar ** args)919 cmd_account_set(ProfWin* window, const char* const command, gchar** args)
920 {
921     if (g_strv_length(args) != 4) {
922         cons_bad_cmd_usage(command);
923         return TRUE;
924     }
925 
926     char* account_name = args[1];
927     if (!accounts_account_exists(account_name)) {
928         cons_show("Account %s doesn't exist", account_name);
929         cons_show("");
930         return TRUE;
931     }
932 
933     char* property = args[2];
934     char* value = args[3];
935     if (strcmp(property, "jid") == 0)
936         return _account_set_jid(account_name, value);
937     if (strcmp(property, "server") == 0)
938         return _account_set_server(account_name, value);
939     if (strcmp(property, "port") == 0)
940         return _account_set_port(account_name, value);
941     if (strcmp(property, "resource") == 0)
942         return _account_set_resource(account_name, value);
943     if (strcmp(property, "password") == 0)
944         return _account_set_password(account_name, value);
945     if (strcmp(property, "eval_password") == 0)
946         return _account_set_eval_password(account_name, value);
947     if (strcmp(property, "muc") == 0)
948         return _account_set_muc(account_name, value);
949     if (strcmp(property, "nick") == 0)
950         return _account_set_nick(account_name, value);
951     if (strcmp(property, "otr") == 0)
952         return _account_set_otr(account_name, value);
953     if (strcmp(property, "status") == 0)
954         return _account_set_status(account_name, value);
955     if (strcmp(property, "pgpkeyid") == 0)
956         return _account_set_pgpkeyid(account_name, value);
957     if (strcmp(property, "startscript") == 0)
958         return _account_set_startscript(account_name, value);
959     if (strcmp(property, "theme") == 0)
960         return _account_set_theme(account_name, value);
961     if (strcmp(property, "tls") == 0)
962         return _account_set_tls(account_name, value);
963     if (strcmp(property, "auth") == 0)
964         return _account_set_auth(account_name, value);
965 
966     if (valid_resource_presence_string(property)) {
967         return _account_set_presence_priority(account_name, property, value);
968     }
969 
970     cons_show("Invalid property: %s", property);
971     cons_show("");
972 
973     return TRUE;
974 }
975 
976 gboolean
cmd_account_clear(ProfWin * window,const char * const command,gchar ** args)977 cmd_account_clear(ProfWin* window, const char* const command, gchar** args)
978 {
979     if (g_strv_length(args) != 3) {
980         cons_bad_cmd_usage(command);
981         return TRUE;
982     }
983 
984     char* account_name = args[1];
985     if (!accounts_account_exists(account_name)) {
986         cons_show("Account %s doesn't exist", account_name);
987         cons_show("");
988         return TRUE;
989     }
990 
991     char* property = args[2];
992     if (strcmp(property, "password") == 0) {
993         accounts_clear_password(account_name);
994         cons_show("Removed password for account %s", account_name);
995         cons_show("");
996     } else if (strcmp(property, "eval_password") == 0) {
997         accounts_clear_eval_password(account_name);
998         cons_show("Removed eval password for account %s", account_name);
999         cons_show("");
1000     } else if (strcmp(property, "server") == 0) {
1001         accounts_clear_server(account_name);
1002         cons_show("Removed server for account %s", account_name);
1003         cons_show("");
1004     } else if (strcmp(property, "port") == 0) {
1005         accounts_clear_port(account_name);
1006         cons_show("Removed port for account %s", account_name);
1007         cons_show("");
1008     } else if (strcmp(property, "otr") == 0) {
1009         accounts_clear_otr(account_name);
1010         cons_show("OTR policy removed for account %s", account_name);
1011         cons_show("");
1012     } else if (strcmp(property, "pgpkeyid") == 0) {
1013         accounts_clear_pgp_keyid(account_name);
1014         cons_show("Removed PGP key ID for account %s", account_name);
1015         cons_show("");
1016     } else if (strcmp(property, "startscript") == 0) {
1017         accounts_clear_script_start(account_name);
1018         cons_show("Removed start script for account %s", account_name);
1019         cons_show("");
1020     } else if (strcmp(property, "theme") == 0) {
1021         accounts_clear_theme(account_name);
1022         cons_show("Removed theme for account %s", account_name);
1023         cons_show("");
1024     } else if (strcmp(property, "muc") == 0) {
1025         accounts_clear_muc(account_name);
1026         cons_show("Removed MUC service for account %s", account_name);
1027         cons_show("");
1028     } else if (strcmp(property, "resource") == 0) {
1029         accounts_clear_resource(account_name);
1030         cons_show("Removed resource for account %s", account_name);
1031         cons_show("");
1032     } else {
1033         cons_show("Invalid property: %s", property);
1034         cons_show("");
1035     }
1036 
1037     return TRUE;
1038 }
1039 
1040 gboolean
cmd_account(ProfWin * window,const char * const command,gchar ** args)1041 cmd_account(ProfWin* window, const char* const command, gchar** args)
1042 {
1043     if (args[0] != NULL) {
1044         cons_bad_cmd_usage(command);
1045         cons_show("");
1046         return TRUE;
1047     }
1048 
1049     if (connection_get_status() != JABBER_CONNECTED) {
1050         cons_bad_cmd_usage(command);
1051         return TRUE;
1052     }
1053 
1054     ProfAccount* account = accounts_get_account(session_get_account_name());
1055     if (account) {
1056         cons_show_account(account);
1057         account_free(account);
1058     } else {
1059         log_error("Could not get accounts");
1060     }
1061 
1062     return TRUE;
1063 }
1064 
1065 gboolean
cmd_script(ProfWin * window,const char * const command,gchar ** args)1066 cmd_script(ProfWin* window, const char* const command, gchar** args)
1067 {
1068     if ((g_strcmp0(args[0], "run") == 0) && args[1]) {
1069         gboolean res = scripts_exec(args[1]);
1070         if (!res) {
1071             cons_show("Could not find script %s", args[1]);
1072         }
1073     } else if (g_strcmp0(args[0], "list") == 0) {
1074         GSList* scripts = scripts_list();
1075         cons_show_scripts(scripts);
1076         g_slist_free_full(scripts, g_free);
1077     } else if ((g_strcmp0(args[0], "show") == 0) && args[1]) {
1078         GSList* commands = scripts_read(args[1]);
1079         cons_show_script(args[1], commands);
1080         g_slist_free_full(commands, g_free);
1081     } else {
1082         cons_bad_cmd_usage(command);
1083     }
1084 
1085     return TRUE;
1086 }
1087 
1088 /* escape a string into csv and write it to the file descriptor */
1089 static int
_writecsv(int fd,const char * const str)1090 _writecsv(int fd, const char* const str)
1091 {
1092     if (!str)
1093         return 0;
1094     size_t len = strlen(str);
1095     char* s = malloc(2 * len * sizeof(char));
1096     char* c = s;
1097     for (int i = 0; i < strlen(str); i++) {
1098         if (str[i] != '"')
1099             *c++ = str[i];
1100         else {
1101             *c++ = '"';
1102             *c++ = '"';
1103             len++;
1104         }
1105     }
1106     if (-1 == write(fd, s, len)) {
1107         cons_show("error: failed to write '%s' to the requested file: %s", s, strerror(errno));
1108         return -1;
1109     }
1110     free(s);
1111     return 0;
1112 }
1113 
1114 gboolean
cmd_export(ProfWin * window,const char * const command,gchar ** args)1115 cmd_export(ProfWin* window, const char* const command, gchar** args)
1116 {
1117     jabber_conn_status_t conn_status = connection_get_status();
1118 
1119     if (conn_status != JABBER_CONNECTED) {
1120         cons_show("You are not currently connected.");
1121         cons_show("");
1122         return TRUE;
1123     } else {
1124         int fd;
1125         GSList* list = NULL;
1126         char* path = get_expanded_path(args[0]);
1127 
1128         fd = open(path, O_WRONLY | O_CREAT, 00600);
1129 
1130         if (-1 == fd) {
1131             cons_show("error: cannot open %s: %s", args[0], strerror(errno));
1132             cons_show("");
1133             return TRUE;
1134         }
1135 
1136         if (-1 == write(fd, "jid,name\n", strlen("jid,name\n")))
1137             goto write_error;
1138 
1139         list = roster_get_contacts(ROSTER_ORD_NAME);
1140         if (list) {
1141             GSList* curr = list;
1142             while (curr) {
1143                 PContact contact = curr->data;
1144                 const char* jid = p_contact_barejid(contact);
1145                 const char* name = p_contact_name(contact);
1146 
1147                 /* write the data to the file */
1148                 if (-1 == write(fd, "\"", 1))
1149                     goto write_error;
1150                 if (-1 == _writecsv(fd, jid))
1151                     goto write_error;
1152                 if (-1 == write(fd, "\",\"", 3))
1153                     goto write_error;
1154                 if (-1 == _writecsv(fd, name))
1155                     goto write_error;
1156                 if (-1 == write(fd, "\"\n", 2))
1157                     goto write_error;
1158 
1159                 /* loop */
1160                 curr = g_slist_next(curr);
1161             }
1162             cons_show("Contacts exported successfully");
1163             cons_show("");
1164         } else {
1165             cons_show("No contacts in roster.");
1166             cons_show("");
1167         }
1168 
1169         g_slist_free(list);
1170         close(fd);
1171         return TRUE;
1172     write_error:
1173         cons_show("error: write failed: %s", strerror(errno));
1174         cons_show("");
1175         g_slist_free(list);
1176         close(fd);
1177         return TRUE;
1178     }
1179 }
1180 
1181 gboolean
cmd_sub(ProfWin * window,const char * const command,gchar ** args)1182 cmd_sub(ProfWin* window, const char* const command, gchar** args)
1183 {
1184     jabber_conn_status_t conn_status = connection_get_status();
1185 
1186     if (conn_status != JABBER_CONNECTED) {
1187         cons_show("You are currently not connected.");
1188         return TRUE;
1189     }
1190 
1191     char *subcmd, *jid;
1192     subcmd = args[0];
1193     jid = args[1];
1194 
1195     if (subcmd == NULL) {
1196         cons_bad_cmd_usage(command);
1197         return TRUE;
1198     }
1199 
1200     if (strcmp(subcmd, "sent") == 0) {
1201         cons_show_sent_subs();
1202         return TRUE;
1203     }
1204 
1205     if (strcmp(subcmd, "received") == 0) {
1206         cons_show_received_subs();
1207         return TRUE;
1208     }
1209 
1210     if ((window->type != WIN_CHAT) && (jid == NULL)) {
1211         cons_show("You must specify a contact.");
1212         return TRUE;
1213     }
1214 
1215     if (jid == NULL) {
1216         ProfChatWin* chatwin = (ProfChatWin*)window;
1217         assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
1218         jid = chatwin->barejid;
1219     }
1220 
1221     Jid* jidp = jid_create(jid);
1222 
1223     if (strcmp(subcmd, "allow") == 0) {
1224         presence_subscription(jidp->barejid, PRESENCE_SUBSCRIBED);
1225         cons_show("Accepted subscription for %s", jidp->barejid);
1226         log_info("Accepted subscription for %s", jidp->barejid);
1227     } else if (strcmp(subcmd, "deny") == 0) {
1228         presence_subscription(jidp->barejid, PRESENCE_UNSUBSCRIBED);
1229         cons_show("Deleted/denied subscription for %s", jidp->barejid);
1230         log_info("Deleted/denied subscription for %s", jidp->barejid);
1231     } else if (strcmp(subcmd, "request") == 0) {
1232         presence_subscription(jidp->barejid, PRESENCE_SUBSCRIBE);
1233         cons_show("Sent subscription request to %s.", jidp->barejid);
1234         log_info("Sent subscription request to %s.", jidp->barejid);
1235     } else if (strcmp(subcmd, "show") == 0) {
1236         PContact contact = roster_get_contact(jidp->barejid);
1237         if ((contact == NULL) || (p_contact_subscription(contact) == NULL)) {
1238             if (window->type == WIN_CHAT) {
1239                 win_println(window, THEME_DEFAULT, "-", "No subscription information for %s.", jidp->barejid);
1240             } else {
1241                 cons_show("No subscription information for %s.", jidp->barejid);
1242             }
1243         } else {
1244             if (window->type == WIN_CHAT) {
1245                 if (p_contact_pending_out(contact)) {
1246                     win_println(window, THEME_DEFAULT, "-", "%s subscription status: %s, request pending.",
1247                                 jidp->barejid, p_contact_subscription(contact));
1248                 } else {
1249                     win_println(window, THEME_DEFAULT, "-", "%s subscription status: %s.", jidp->barejid,
1250                                 p_contact_subscription(contact));
1251                 }
1252             } else {
1253                 if (p_contact_pending_out(contact)) {
1254                     cons_show("%s subscription status: %s, request pending.",
1255                               jidp->barejid, p_contact_subscription(contact));
1256                 } else {
1257                     cons_show("%s subscription status: %s.", jidp->barejid,
1258                               p_contact_subscription(contact));
1259                 }
1260             }
1261         }
1262     } else {
1263         cons_bad_cmd_usage(command);
1264     }
1265 
1266     jid_destroy(jidp);
1267 
1268     return TRUE;
1269 }
1270 
1271 gboolean
cmd_disconnect(ProfWin * window,const char * const command,gchar ** args)1272 cmd_disconnect(ProfWin* window, const char* const command, gchar** args)
1273 {
1274     if (connection_get_status() != JABBER_CONNECTED) {
1275         cons_show("You are not currently connected.");
1276         return TRUE;
1277     }
1278 
1279     cl_ev_disconnect();
1280 
1281     ui_redraw();
1282 
1283     return TRUE;
1284 }
1285 
1286 gboolean
cmd_quit(ProfWin * window,const char * const command,gchar ** args)1287 cmd_quit(ProfWin* window, const char* const command, gchar** args)
1288 {
1289     log_info("Profanity is shutting down...");
1290     exit(0);
1291     return FALSE;
1292 }
1293 
1294 gboolean
cmd_wins_unread(ProfWin * window,const char * const command,gchar ** args)1295 cmd_wins_unread(ProfWin* window, const char* const command, gchar** args)
1296 {
1297     cons_show_wins(TRUE);
1298     return TRUE;
1299 }
1300 
1301 gboolean
cmd_wins_attention(ProfWin * window,const char * const command,gchar ** args)1302 cmd_wins_attention(ProfWin* window, const char* const command, gchar** args)
1303 {
1304     cons_show_wins_attention();
1305     return TRUE;
1306 }
1307 
1308 gboolean
cmd_wins_prune(ProfWin * window,const char * const command,gchar ** args)1309 cmd_wins_prune(ProfWin* window, const char* const command, gchar** args)
1310 {
1311     ui_prune_wins();
1312     return TRUE;
1313 }
1314 
1315 gboolean
cmd_wins_swap(ProfWin * window,const char * const command,gchar ** args)1316 cmd_wins_swap(ProfWin* window, const char* const command, gchar** args)
1317 {
1318     if ((args[1] == NULL) || (args[2] == NULL)) {
1319         cons_bad_cmd_usage(command);
1320         return TRUE;
1321     }
1322 
1323     int source_win = atoi(args[1]);
1324     int target_win = atoi(args[2]);
1325 
1326     if ((source_win == 1) || (target_win == 1)) {
1327         cons_show("Cannot move console window.");
1328         return TRUE;
1329     }
1330 
1331     if (source_win == 10 || target_win == 10) {
1332         cons_show("Window 10 does not exist");
1333         return TRUE;
1334     }
1335 
1336     if (source_win == target_win) {
1337         cons_show("Same source and target window supplied.");
1338         return TRUE;
1339     }
1340 
1341     if (wins_get_by_num(source_win) == NULL) {
1342         cons_show("Window %d does not exist", source_win);
1343         return TRUE;
1344     }
1345 
1346     if (wins_get_by_num(target_win) == NULL) {
1347         cons_show("Window %d does not exist", target_win);
1348         return TRUE;
1349     }
1350 
1351     wins_swap(source_win, target_win);
1352     cons_show("Swapped windows %d <-> %d", source_win, target_win);
1353     return TRUE;
1354 }
1355 
1356 gboolean
cmd_wins(ProfWin * window,const char * const command,gchar ** args)1357 cmd_wins(ProfWin* window, const char* const command, gchar** args)
1358 {
1359     if (args[0] != NULL) {
1360         cons_bad_cmd_usage(command);
1361         return TRUE;
1362     }
1363 
1364     cons_show_wins(FALSE);
1365     return TRUE;
1366 }
1367 
1368 gboolean
cmd_close(ProfWin * window,const char * const command,gchar ** args)1369 cmd_close(ProfWin* window, const char* const command, gchar** args)
1370 {
1371     jabber_conn_status_t conn_status = connection_get_status();
1372 
1373     if (g_strcmp0(args[0], "all") == 0) {
1374         int count = ui_close_all_wins();
1375         if (count == 0) {
1376             cons_show("No windows to close.");
1377         } else if (count == 1) {
1378             cons_show("Closed 1 window.");
1379         } else {
1380             cons_show("Closed %d windows.", count);
1381         }
1382         rosterwin_roster();
1383         return TRUE;
1384     }
1385 
1386     if (g_strcmp0(args[0], "read") == 0) {
1387         int count = ui_close_read_wins();
1388         if (count == 0) {
1389             cons_show("No windows to close.");
1390         } else if (count == 1) {
1391             cons_show("Closed 1 window.");
1392         } else {
1393             cons_show("Closed %d windows.", count);
1394         }
1395         rosterwin_roster();
1396         return TRUE;
1397     }
1398 
1399     gboolean is_num = TRUE;
1400     int index = 0;
1401     if (args[0] != NULL) {
1402         for (int i = 0; i < strlen(args[0]); i++) {
1403             if (!isdigit((int)args[0][i])) {
1404                 is_num = FALSE;
1405                 break;
1406             }
1407         }
1408 
1409         if (is_num) {
1410             index = atoi(args[0]);
1411         }
1412     } else {
1413         index = wins_get_current_num();
1414     }
1415 
1416     if (is_num) {
1417         if (index < 0 || index == 10) {
1418             cons_show("No such window exists.");
1419             return TRUE;
1420         }
1421 
1422         if (index == 1) {
1423             cons_show("Cannot close console window.");
1424             return TRUE;
1425         }
1426 
1427         ProfWin* toclose = wins_get_by_num(index);
1428         if (!toclose) {
1429             cons_show("Window is not open.");
1430             return TRUE;
1431         }
1432 
1433         // check for unsaved form
1434         if (ui_win_has_unsaved_form(index)) {
1435             win_println(window, THEME_DEFAULT, "-", "You have unsaved changes, use /form submit or /form cancel");
1436             return TRUE;
1437         }
1438 
1439         // handle leaving rooms, or chat
1440         if (conn_status == JABBER_CONNECTED) {
1441             ui_close_connected_win(index);
1442         }
1443 
1444         // close the window
1445         ui_close_win(index);
1446         cons_show("Closed window %d", index);
1447         wins_tidy();
1448 
1449         rosterwin_roster();
1450         return TRUE;
1451     } else {
1452         if (g_strcmp0(args[0], "console") == 0) {
1453             cons_show("Cannot close console window.");
1454             return TRUE;
1455         }
1456 
1457         ProfWin* toclose = wins_get_by_string(args[0]);
1458         if (!toclose) {
1459             cons_show("Window \"%s\" does not exist.", args[0]);
1460             return TRUE;
1461         }
1462         index = wins_get_num(toclose);
1463 
1464         // check for unsaved form
1465         if (ui_win_has_unsaved_form(index)) {
1466             win_println(window, THEME_DEFAULT, "-", "You have unsaved changes, use /form submit or /form cancel");
1467             return TRUE;
1468         }
1469 
1470         // handle leaving rooms, or chat
1471         if (conn_status == JABBER_CONNECTED) {
1472             ui_close_connected_win(index);
1473         }
1474 
1475         // close the window
1476         ui_close_win(index);
1477         cons_show("Closed window %s", args[0]);
1478         wins_tidy();
1479 
1480         rosterwin_roster();
1481         return TRUE;
1482     }
1483 }
1484 
1485 gboolean
cmd_win(ProfWin * window,const char * const command,gchar ** args)1486 cmd_win(ProfWin* window, const char* const command, gchar** args)
1487 {
1488     gboolean is_num = TRUE;
1489     for (int i = 0; i < strlen(args[0]); i++) {
1490         if (!isdigit((int)args[0][i])) {
1491             is_num = FALSE;
1492             break;
1493         }
1494     }
1495 
1496     if (is_num) {
1497         int num = atoi(args[0]);
1498 
1499         ProfWin* focuswin = wins_get_by_num(num);
1500         if (!focuswin) {
1501             cons_show("Window %d does not exist.", num);
1502         } else {
1503             ui_focus_win(focuswin);
1504         }
1505     } else {
1506         ProfWin* focuswin = wins_get_by_string(args[0]);
1507         if (!focuswin) {
1508             cons_show("Window \"%s\" does not exist.", args[0]);
1509         } else {
1510             ui_focus_win(focuswin);
1511         }
1512     }
1513 
1514     return TRUE;
1515 }
1516 
1517 static void
_cmd_list_commands(GList * commands)1518 _cmd_list_commands(GList* commands)
1519 {
1520     int maxlen = 0;
1521     GList* curr = commands;
1522     while (curr) {
1523         gchar* cmd = curr->data;
1524         int len = strlen(cmd);
1525         if (len > maxlen)
1526             maxlen = len;
1527         curr = g_list_next(curr);
1528     }
1529 
1530     GString* cmds = g_string_new("");
1531     curr = commands;
1532     int count = 0;
1533     while (curr) {
1534         gchar* cmd = curr->data;
1535         if (count == 5) {
1536             cons_show(cmds->str);
1537             g_string_free(cmds, TRUE);
1538             cmds = g_string_new("");
1539             count = 0;
1540         }
1541         g_string_append_printf(cmds, "%-*s", maxlen + 1, cmd);
1542         curr = g_list_next(curr);
1543         count++;
1544     }
1545     cons_show(cmds->str);
1546     g_string_free(cmds, TRUE);
1547     g_list_free(curr);
1548 
1549     cons_show("");
1550     cons_show("Use /help [command] without the leading slash, for help on a specific command");
1551     cons_show("");
1552 }
1553 
1554 static void
_cmd_help_cmd_list(const char * const tag)1555 _cmd_help_cmd_list(const char* const tag)
1556 {
1557     cons_show("");
1558     ProfWin* console = wins_get_console();
1559     if (tag) {
1560         win_println(console, THEME_HELP_HEADER, "-", "%s commands", tag);
1561     } else {
1562         win_println(console, THEME_HELP_HEADER, "-", "All commands");
1563     }
1564 
1565     GList* ordered_commands = NULL;
1566 
1567     if (g_strcmp0(tag, "plugins") == 0) {
1568         GList* plugins_cmds = plugins_get_command_names();
1569         GList* curr = plugins_cmds;
1570         while (curr) {
1571             ordered_commands = g_list_insert_sorted(ordered_commands, curr->data, (GCompareFunc)g_strcmp0);
1572             curr = g_list_next(curr);
1573         }
1574         g_list_free(plugins_cmds);
1575     } else {
1576         ordered_commands = cmd_get_ordered(tag);
1577 
1578         // add plugins if showing all commands
1579         if (!tag) {
1580             GList* plugins_cmds = plugins_get_command_names();
1581             GList* curr = plugins_cmds;
1582             while (curr) {
1583                 ordered_commands = g_list_insert_sorted(ordered_commands, curr->data, (GCompareFunc)g_strcmp0);
1584                 curr = g_list_next(curr);
1585             }
1586             g_list_free(plugins_cmds);
1587         }
1588     }
1589 
1590     _cmd_list_commands(ordered_commands);
1591     g_list_free(ordered_commands);
1592 }
1593 
1594 gboolean
cmd_help(ProfWin * window,const char * const command,gchar ** args)1595 cmd_help(ProfWin* window, const char* const command, gchar** args)
1596 {
1597     int num_args = g_strv_length(args);
1598     if (num_args == 0) {
1599         cons_help();
1600     } else if (strcmp(args[0], "search_all") == 0) {
1601         if (args[1] == NULL) {
1602             cons_bad_cmd_usage(command);
1603         } else {
1604             GList* cmds = cmd_search_index_all(args[1]);
1605             if (cmds == NULL) {
1606                 cons_show("No commands found.");
1607             } else {
1608                 GList* curr = cmds;
1609                 GList* results = NULL;
1610                 while (curr) {
1611                     results = g_list_insert_sorted(results, curr->data, (GCompareFunc)g_strcmp0);
1612                     curr = g_list_next(curr);
1613                 }
1614                 cons_show("Search results:");
1615                 _cmd_list_commands(results);
1616                 g_list_free(results);
1617             }
1618             g_list_free(cmds);
1619         }
1620     } else if (strcmp(args[0], "search_any") == 0) {
1621         if (args[1] == NULL) {
1622             cons_bad_cmd_usage(command);
1623         } else {
1624             GList* cmds = cmd_search_index_any(args[1]);
1625             if (cmds == NULL) {
1626                 cons_show("No commands found.");
1627             } else {
1628                 GList* curr = cmds;
1629                 GList* results = NULL;
1630                 while (curr) {
1631                     results = g_list_insert_sorted(results, curr->data, (GCompareFunc)g_strcmp0);
1632                     curr = g_list_next(curr);
1633                 }
1634                 cons_show("Search results:");
1635                 _cmd_list_commands(results);
1636                 g_list_free(results);
1637             }
1638             g_list_free(cmds);
1639         }
1640     } else if (strcmp(args[0], "commands") == 0) {
1641         if (args[1]) {
1642             if (!cmd_valid_tag(args[1])) {
1643                 cons_bad_cmd_usage(command);
1644             } else {
1645                 _cmd_help_cmd_list(args[1]);
1646             }
1647         } else {
1648             _cmd_help_cmd_list(NULL);
1649         }
1650     } else if (strcmp(args[0], "navigation") == 0) {
1651         cons_navigation_help();
1652     } else {
1653         char* cmd = args[0];
1654         char *cmd_with_slash = g_strdup_printf("/%s", cmd);
1655 
1656         Command* command = cmd_get(cmd_with_slash);
1657         if (command) {
1658             cons_show_help(cmd_with_slash, &command->help);
1659         } else {
1660             CommandHelp* commandHelp = plugins_get_help(cmd_with_slash);
1661             if (commandHelp) {
1662                 cons_show_help(cmd_with_slash, commandHelp);
1663             } else {
1664                 cons_show("No such command.");
1665             }
1666         }
1667         cons_show("");
1668         g_free(cmd_with_slash);
1669     }
1670 
1671     return TRUE;
1672 }
1673 
1674 gboolean
cmd_about(ProfWin * window,const char * const command,gchar ** args)1675 cmd_about(ProfWin* window, const char* const command, gchar** args)
1676 {
1677     cons_show("");
1678     cons_about();
1679     return TRUE;
1680 }
1681 
1682 gboolean
cmd_prefs(ProfWin * window,const char * const command,gchar ** args)1683 cmd_prefs(ProfWin* window, const char* const command, gchar** args)
1684 {
1685     if (args[0] == NULL) {
1686         cons_prefs();
1687         cons_show("Use the /account command for preferences for individual accounts.");
1688     } else if (strcmp(args[0], "ui") == 0) {
1689         cons_show("");
1690         cons_show_ui_prefs();
1691         cons_show("");
1692     } else if (strcmp(args[0], "desktop") == 0) {
1693         cons_show("");
1694         cons_show_desktop_prefs();
1695         cons_show("");
1696     } else if (strcmp(args[0], "chat") == 0) {
1697         cons_show("");
1698         cons_show_chat_prefs();
1699         cons_show("");
1700     } else if (strcmp(args[0], "log") == 0) {
1701         cons_show("");
1702         cons_show_log_prefs();
1703         cons_show("");
1704     } else if (strcmp(args[0], "conn") == 0) {
1705         cons_show("");
1706         cons_show_connection_prefs();
1707         cons_show("");
1708     } else if (strcmp(args[0], "presence") == 0) {
1709         cons_show("");
1710         cons_show_presence_prefs();
1711         cons_show("");
1712     } else if (strcmp(args[0], "otr") == 0) {
1713         cons_show("");
1714         cons_show_otr_prefs();
1715         cons_show("");
1716     } else if (strcmp(args[0], "pgp") == 0) {
1717         cons_show("");
1718         cons_show_pgp_prefs();
1719         cons_show("");
1720     } else if (strcmp(args[0], "omemo") == 0) {
1721         cons_show("");
1722         cons_show_omemo_prefs();
1723         cons_show("");
1724     } else {
1725         cons_bad_cmd_usage(command);
1726     }
1727 
1728     return TRUE;
1729 }
1730 
1731 gboolean
cmd_theme(ProfWin * window,const char * const command,gchar ** args)1732 cmd_theme(ProfWin* window, const char* const command, gchar** args)
1733 {
1734     // 'full-load' means to load the theme including the settings (not just [colours])
1735     gboolean fullload = (g_strcmp0(args[0], "full-load") == 0);
1736 
1737     // list themes
1738     if (g_strcmp0(args[0], "list") == 0) {
1739         GSList* themes = theme_list();
1740         cons_show_themes(themes);
1741         g_slist_free_full(themes, g_free);
1742 
1743         // load a theme
1744     } else if (g_strcmp0(args[0], "load") == 0 || fullload) {
1745         if (args[1] == NULL) {
1746             cons_bad_cmd_usage(command);
1747         } else if (theme_load(args[1], fullload)) {
1748             ui_load_colours();
1749             prefs_set_string(PREF_THEME, args[1]);
1750             if (prefs_get_boolean(PREF_ROSTER)) {
1751                 ui_show_roster();
1752             } else {
1753                 ui_hide_roster();
1754             }
1755             if (prefs_get_boolean(PREF_OCCUPANTS)) {
1756                 ui_show_all_room_rosters();
1757             } else {
1758                 ui_hide_all_room_rosters();
1759             }
1760             ui_resize();
1761             cons_show("Loaded theme: %s", args[1]);
1762         } else {
1763             cons_show("Couldn't find theme: %s", args[1]);
1764         }
1765 
1766         // show colours
1767     } else if (g_strcmp0(args[0], "colours") == 0) {
1768         cons_theme_colours();
1769     } else if (g_strcmp0(args[0], "properties") == 0) {
1770         cons_theme_properties();
1771     } else {
1772         cons_bad_cmd_usage(command);
1773     }
1774 
1775     return TRUE;
1776 }
1777 
1778 static void
_who_room(ProfWin * window,const char * const command,gchar ** args)1779 _who_room(ProfWin* window, const char* const command, gchar** args)
1780 {
1781     if ((g_strv_length(args) == 2) && args[1]) {
1782         cons_show("Argument group is not applicable to chat rooms.");
1783         return;
1784     }
1785 
1786     // bad arg
1787     if (args[0] && (g_strcmp0(args[0], "online") != 0) && (g_strcmp0(args[0], "available") != 0) && (g_strcmp0(args[0], "unavailable") != 0) && (g_strcmp0(args[0], "away") != 0) && (g_strcmp0(args[0], "chat") != 0) && (g_strcmp0(args[0], "xa") != 0) && (g_strcmp0(args[0], "dnd") != 0) && (g_strcmp0(args[0], "any") != 0) && (g_strcmp0(args[0], "moderator") != 0) && (g_strcmp0(args[0], "participant") != 0) && (g_strcmp0(args[0], "visitor") != 0) && (g_strcmp0(args[0], "owner") != 0) && (g_strcmp0(args[0], "admin") != 0) && (g_strcmp0(args[0], "member") != 0) && (g_strcmp0(args[0], "outcast") != 0)) {
1788         cons_bad_cmd_usage(command);
1789         return;
1790     }
1791 
1792     ProfMucWin* mucwin = (ProfMucWin*)window;
1793     assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
1794 
1795     // presence filter
1796     if (args[0] == NULL || (g_strcmp0(args[0], "online") == 0) || (g_strcmp0(args[0], "available") == 0) || (g_strcmp0(args[0], "unavailable") == 0) || (g_strcmp0(args[0], "away") == 0) || (g_strcmp0(args[0], "chat") == 0) || (g_strcmp0(args[0], "xa") == 0) || (g_strcmp0(args[0], "dnd") == 0) || (g_strcmp0(args[0], "any") == 0)) {
1797 
1798         char* presence = args[0];
1799         GList* occupants = muc_roster(mucwin->roomjid);
1800 
1801         // no arg, show all contacts
1802         if ((presence == NULL) || (g_strcmp0(presence, "any") == 0)) {
1803             mucwin_roster(mucwin, occupants, NULL);
1804 
1805             // available
1806         } else if (strcmp("available", presence) == 0) {
1807             GList* filtered = NULL;
1808 
1809             while (occupants) {
1810                 Occupant* occupant = occupants->data;
1811                 if (muc_occupant_available(occupant)) {
1812                     filtered = g_list_append(filtered, occupant);
1813                 }
1814                 occupants = g_list_next(occupants);
1815             }
1816 
1817             mucwin_roster(mucwin, filtered, "available");
1818 
1819             // unavailable
1820         } else if (strcmp("unavailable", presence) == 0) {
1821             GList* filtered = NULL;
1822 
1823             while (occupants) {
1824                 Occupant* occupant = occupants->data;
1825                 if (!muc_occupant_available(occupant)) {
1826                     filtered = g_list_append(filtered, occupant);
1827                 }
1828                 occupants = g_list_next(occupants);
1829             }
1830 
1831             mucwin_roster(mucwin, filtered, "unavailable");
1832 
1833             // show specific status
1834         } else {
1835             GList* filtered = NULL;
1836 
1837             while (occupants) {
1838                 Occupant* occupant = occupants->data;
1839                 const char* presence_str = string_from_resource_presence(occupant->presence);
1840                 if (strcmp(presence_str, presence) == 0) {
1841                     filtered = g_list_append(filtered, occupant);
1842                 }
1843                 occupants = g_list_next(occupants);
1844             }
1845 
1846             mucwin_roster(mucwin, filtered, presence);
1847         }
1848 
1849         g_list_free(occupants);
1850 
1851         // role or affiliation filter
1852     } else {
1853         if (g_strcmp0(args[0], "moderator") == 0) {
1854             mucwin_show_role_list(mucwin, MUC_ROLE_MODERATOR);
1855             return;
1856         }
1857         if (g_strcmp0(args[0], "participant") == 0) {
1858             mucwin_show_role_list(mucwin, MUC_ROLE_PARTICIPANT);
1859             return;
1860         }
1861         if (g_strcmp0(args[0], "visitor") == 0) {
1862             mucwin_show_role_list(mucwin, MUC_ROLE_VISITOR);
1863             return;
1864         }
1865 
1866         if (g_strcmp0(args[0], "owner") == 0) {
1867             mucwin_show_affiliation_list(mucwin, MUC_AFFILIATION_OWNER);
1868             return;
1869         }
1870         if (g_strcmp0(args[0], "admin") == 0) {
1871             mucwin_show_affiliation_list(mucwin, MUC_AFFILIATION_ADMIN);
1872             return;
1873         }
1874         if (g_strcmp0(args[0], "member") == 0) {
1875             mucwin_show_affiliation_list(mucwin, MUC_AFFILIATION_MEMBER);
1876             return;
1877         }
1878         if (g_strcmp0(args[0], "outcast") == 0) {
1879             mucwin_show_affiliation_list(mucwin, MUC_AFFILIATION_OUTCAST);
1880             return;
1881         }
1882     }
1883 }
1884 
1885 static void
_who_roster(ProfWin * window,const char * const command,gchar ** args)1886 _who_roster(ProfWin* window, const char* const command, gchar** args)
1887 {
1888     char* presence = args[0];
1889 
1890     // bad arg
1891     if (presence
1892         && (strcmp(presence, "online") != 0)
1893         && (strcmp(presence, "available") != 0)
1894         && (strcmp(presence, "unavailable") != 0)
1895         && (strcmp(presence, "offline") != 0)
1896         && (strcmp(presence, "away") != 0)
1897         && (strcmp(presence, "chat") != 0)
1898         && (strcmp(presence, "xa") != 0)
1899         && (strcmp(presence, "dnd") != 0)
1900         && (strcmp(presence, "any") != 0)) {
1901         cons_bad_cmd_usage(command);
1902         return;
1903     }
1904 
1905     char* group = NULL;
1906     if ((g_strv_length(args) == 2) && args[1]) {
1907         group = args[1];
1908     }
1909 
1910     cons_show("");
1911     GSList* list = NULL;
1912     if (group) {
1913         list = roster_get_group(group, ROSTER_ORD_NAME);
1914         if (list == NULL) {
1915             cons_show("No such group: %s.", group);
1916             return;
1917         }
1918     } else {
1919         list = roster_get_contacts(ROSTER_ORD_NAME);
1920         if (list == NULL) {
1921             cons_show("No contacts in roster.");
1922             return;
1923         }
1924     }
1925 
1926     // no arg, show all contacts
1927     if ((presence == NULL) || (g_strcmp0(presence, "any") == 0)) {
1928         if (group) {
1929             if (list == NULL) {
1930                 cons_show("No contacts in group %s.", group);
1931             } else {
1932                 cons_show("%s:", group);
1933                 cons_show_contacts(list);
1934             }
1935         } else {
1936             if (list == NULL) {
1937                 cons_show("You have no contacts.");
1938             } else {
1939                 cons_show("All contacts:");
1940                 cons_show_contacts(list);
1941             }
1942         }
1943 
1944         // available
1945     } else if (strcmp("available", presence) == 0) {
1946         GSList* filtered = NULL;
1947 
1948         GSList* curr = list;
1949         while (curr) {
1950             PContact contact = curr->data;
1951             if (p_contact_is_available(contact)) {
1952                 filtered = g_slist_append(filtered, contact);
1953             }
1954             curr = g_slist_next(curr);
1955         }
1956 
1957         if (group) {
1958             if (filtered == NULL) {
1959                 cons_show("No contacts in group %s are %s.", group, presence);
1960             } else {
1961                 cons_show("%s (%s):", group, presence);
1962                 cons_show_contacts(filtered);
1963             }
1964         } else {
1965             if (filtered == NULL) {
1966                 cons_show("No contacts are %s.", presence);
1967             } else {
1968                 cons_show("Contacts (%s):", presence);
1969                 cons_show_contacts(filtered);
1970             }
1971         }
1972         g_slist_free(filtered);
1973 
1974         // unavailable
1975     } else if (strcmp("unavailable", presence) == 0) {
1976         GSList* filtered = NULL;
1977 
1978         GSList* curr = list;
1979         while (curr) {
1980             PContact contact = curr->data;
1981             if (!p_contact_is_available(contact)) {
1982                 filtered = g_slist_append(filtered, contact);
1983             }
1984             curr = g_slist_next(curr);
1985         }
1986 
1987         if (group) {
1988             if (filtered == NULL) {
1989                 cons_show("No contacts in group %s are %s.", group, presence);
1990             } else {
1991                 cons_show("%s (%s):", group, presence);
1992                 cons_show_contacts(filtered);
1993             }
1994         } else {
1995             if (filtered == NULL) {
1996                 cons_show("No contacts are %s.", presence);
1997             } else {
1998                 cons_show("Contacts (%s):", presence);
1999                 cons_show_contacts(filtered);
2000             }
2001         }
2002         g_slist_free(filtered);
2003 
2004         // online, available resources
2005     } else if (strcmp("online", presence) == 0) {
2006         GSList* filtered = NULL;
2007 
2008         GSList* curr = list;
2009         while (curr) {
2010             PContact contact = curr->data;
2011             if (p_contact_has_available_resource(contact)) {
2012                 filtered = g_slist_append(filtered, contact);
2013             }
2014             curr = g_slist_next(curr);
2015         }
2016 
2017         if (group) {
2018             if (filtered == NULL) {
2019                 cons_show("No contacts in group %s are %s.", group, presence);
2020             } else {
2021                 cons_show("%s (%s):", group, presence);
2022                 cons_show_contacts(filtered);
2023             }
2024         } else {
2025             if (filtered == NULL) {
2026                 cons_show("No contacts are %s.", presence);
2027             } else {
2028                 cons_show("Contacts (%s):", presence);
2029                 cons_show_contacts(filtered);
2030             }
2031         }
2032         g_slist_free(filtered);
2033 
2034         // offline, no available resources
2035     } else if (strcmp("offline", presence) == 0) {
2036         GSList* filtered = NULL;
2037 
2038         GSList* curr = list;
2039         while (curr) {
2040             PContact contact = curr->data;
2041             if (!p_contact_has_available_resource(contact)) {
2042                 filtered = g_slist_append(filtered, contact);
2043             }
2044             curr = g_slist_next(curr);
2045         }
2046 
2047         if (group) {
2048             if (filtered == NULL) {
2049                 cons_show("No contacts in group %s are %s.", group, presence);
2050             } else {
2051                 cons_show("%s (%s):", group, presence);
2052                 cons_show_contacts(filtered);
2053             }
2054         } else {
2055             if (filtered == NULL) {
2056                 cons_show("No contacts are %s.", presence);
2057             } else {
2058                 cons_show("Contacts (%s):", presence);
2059                 cons_show_contacts(filtered);
2060             }
2061         }
2062         g_slist_free(filtered);
2063 
2064         // show specific status
2065     } else {
2066         GSList* filtered = NULL;
2067 
2068         GSList* curr = list;
2069         while (curr) {
2070             PContact contact = curr->data;
2071             if (strcmp(p_contact_presence(contact), presence) == 0) {
2072                 filtered = g_slist_append(filtered, contact);
2073             }
2074             curr = g_slist_next(curr);
2075         }
2076 
2077         if (group) {
2078             if (filtered == NULL) {
2079                 cons_show("No contacts in group %s are %s.", group, presence);
2080             } else {
2081                 cons_show("%s (%s):", group, presence);
2082                 cons_show_contacts(filtered);
2083             }
2084         } else {
2085             if (filtered == NULL) {
2086                 cons_show("No contacts are %s.", presence);
2087             } else {
2088                 cons_show("Contacts (%s):", presence);
2089                 cons_show_contacts(filtered);
2090             }
2091         }
2092         g_slist_free(filtered);
2093     }
2094 
2095     g_slist_free(list);
2096 }
2097 
2098 gboolean
cmd_who(ProfWin * window,const char * const command,gchar ** args)2099 cmd_who(ProfWin* window, const char* const command, gchar** args)
2100 {
2101     jabber_conn_status_t conn_status = connection_get_status();
2102 
2103     if (conn_status != JABBER_CONNECTED) {
2104         cons_show("You are not currently connected.");
2105     } else if (window->type == WIN_MUC) {
2106         _who_room(window, command, args);
2107     } else {
2108         _who_roster(window, command, args);
2109     }
2110 
2111     if (window->type != WIN_CONSOLE && window->type != WIN_MUC) {
2112         status_bar_new(1, WIN_CONSOLE, "console");
2113     }
2114 
2115     return TRUE;
2116 }
2117 
2118 static void
_cmd_msg_chatwin(const char * const barejid,const char * const msg)2119 _cmd_msg_chatwin(const char* const barejid, const char* const msg)
2120 {
2121     ProfChatWin* chatwin = wins_get_chat(barejid);
2122     if (!chatwin) {
2123         // NOTE: This will also start the new OMEMO session and send a MAM request.
2124         chatwin = chatwin_new(barejid);
2125     }
2126     ui_focus_win((ProfWin*)chatwin);
2127 
2128     if (msg) {
2129         // NOTE: In case the message is OMEMO encrypted, we can't be sure
2130         // whether the key bundles of the recipient have already been
2131         // received. In the case that *no* bundles have been received yet,
2132         // the message won't be sent, and an error is shown to the user.
2133         // Other cases are not handled here.
2134         cl_ev_send_msg(chatwin, msg, NULL);
2135     } else {
2136 #ifdef HAVE_LIBOTR
2137         // Start the OTR session after this (i.e. the first) message was sent
2138         if (otr_is_secure(barejid)) {
2139             chatwin_otr_secured(chatwin, otr_is_trusted(barejid));
2140         }
2141 #endif // HAVE_LIBOTR
2142     }
2143 }
2144 
2145 gboolean
cmd_msg(ProfWin * window,const char * const command,gchar ** args)2146 cmd_msg(ProfWin* window, const char* const command, gchar** args)
2147 {
2148     char* usr = args[0];
2149     char* msg = args[1];
2150 
2151     jabber_conn_status_t conn_status = connection_get_status();
2152 
2153     if (conn_status != JABBER_CONNECTED) {
2154         cons_show("You are not currently connected.");
2155         return TRUE;
2156     }
2157 
2158     // send private message when in MUC room
2159     if (window->type == WIN_MUC) {
2160         ProfMucWin* mucwin = (ProfMucWin*)window;
2161         assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
2162 
2163         Occupant* occupant = muc_roster_item(mucwin->roomjid, usr);
2164         if (occupant) {
2165             // in case of non-anon muc send regular chatmessage
2166             if (muc_anonymity_type(mucwin->roomjid) == MUC_ANONYMITY_TYPE_NONANONYMOUS) {
2167                 Jid* jidp = jid_create(occupant->jid);
2168 
2169                 _cmd_msg_chatwin(jidp->barejid, msg);
2170                 win_println(window, THEME_DEFAULT, "-", "Starting direct message with occupant \"%s\" from room \"%s\" as \"%s\".", usr, mucwin->roomjid, jidp->barejid);
2171                 cons_show("Starting direct message with occupant \"%s\" from room \"%s\" as \"%s\".", usr, mucwin->roomjid, jidp->barejid);
2172 
2173                 jid_destroy(jidp);
2174             } else {
2175                 // otherwise send mucpm
2176                 GString* full_jid = g_string_new(mucwin->roomjid);
2177                 g_string_append(full_jid, "/");
2178                 g_string_append(full_jid, usr);
2179 
2180                 ProfPrivateWin* privwin = wins_get_private(full_jid->str);
2181                 if (!privwin) {
2182                     privwin = (ProfPrivateWin*)wins_new_private(full_jid->str);
2183                 }
2184                 ui_focus_win((ProfWin*)privwin);
2185 
2186                 if (msg) {
2187                     cl_ev_send_priv_msg(privwin, msg, NULL);
2188                 }
2189 
2190                 g_string_free(full_jid, TRUE);
2191             }
2192 
2193         } else {
2194             win_println(window, THEME_DEFAULT, "-", "No such participant \"%s\" in room.", usr);
2195         }
2196 
2197         return TRUE;
2198 
2199         // send chat message
2200     } else {
2201         char* barejid = roster_barejid_from_name(usr);
2202         if (barejid == NULL) {
2203             barejid = usr;
2204         }
2205 
2206         _cmd_msg_chatwin(barejid, msg);
2207 
2208         return TRUE;
2209     }
2210 }
2211 
2212 gboolean
cmd_group(ProfWin * window,const char * const command,gchar ** args)2213 cmd_group(ProfWin* window, const char* const command, gchar** args)
2214 {
2215     jabber_conn_status_t conn_status = connection_get_status();
2216 
2217     if (conn_status != JABBER_CONNECTED) {
2218         cons_show("You are not currently connected.");
2219         return TRUE;
2220     }
2221 
2222     // list all groups
2223     if (args[1] == NULL) {
2224         GList* groups = roster_get_groups();
2225         GList* curr = groups;
2226         if (curr) {
2227             cons_show("Groups:");
2228             while (curr) {
2229                 cons_show("  %s", curr->data);
2230                 curr = g_list_next(curr);
2231             }
2232 
2233             g_list_free_full(groups, g_free);
2234         } else {
2235             cons_show("No groups.");
2236         }
2237         return TRUE;
2238     }
2239 
2240     // show contacts in group
2241     if (strcmp(args[1], "show") == 0) {
2242         char* group = args[2];
2243         if (group == NULL) {
2244             cons_bad_cmd_usage(command);
2245             return TRUE;
2246         }
2247 
2248         GSList* list = roster_get_group(group, ROSTER_ORD_NAME);
2249         cons_show_roster_group(group, list);
2250         return TRUE;
2251     }
2252 
2253     // add contact to group
2254     if (strcmp(args[1], "add") == 0) {
2255         char* group = args[2];
2256         char* contact = args[3];
2257 
2258         if ((group == NULL) || (contact == NULL)) {
2259             cons_bad_cmd_usage(command);
2260             return TRUE;
2261         }
2262 
2263         char* barejid = roster_barejid_from_name(contact);
2264         if (barejid == NULL) {
2265             barejid = contact;
2266         }
2267 
2268         PContact pcontact = roster_get_contact(barejid);
2269         if (pcontact == NULL) {
2270             cons_show("Contact not found in roster: %s", barejid);
2271             return TRUE;
2272         }
2273 
2274         if (p_contact_in_group(pcontact, group)) {
2275             const char* display_name = p_contact_name_or_jid(pcontact);
2276             ui_contact_already_in_group(display_name, group);
2277         } else {
2278             roster_send_add_to_group(group, pcontact);
2279         }
2280 
2281         return TRUE;
2282     }
2283 
2284     // remove contact from group
2285     if (strcmp(args[1], "remove") == 0) {
2286         char* group = args[2];
2287         char* contact = args[3];
2288 
2289         if ((group == NULL) || (contact == NULL)) {
2290             cons_bad_cmd_usage(command);
2291             return TRUE;
2292         }
2293 
2294         char* barejid = roster_barejid_from_name(contact);
2295         if (barejid == NULL) {
2296             barejid = contact;
2297         }
2298 
2299         PContact pcontact = roster_get_contact(barejid);
2300         if (pcontact == NULL) {
2301             cons_show("Contact not found in roster: %s", barejid);
2302             return TRUE;
2303         }
2304 
2305         if (!p_contact_in_group(pcontact, group)) {
2306             const char* display_name = p_contact_name_or_jid(pcontact);
2307             ui_contact_not_in_group(display_name, group);
2308         } else {
2309             roster_send_remove_from_group(group, pcontact);
2310         }
2311 
2312         return TRUE;
2313     }
2314 
2315     cons_bad_cmd_usage(command);
2316     return TRUE;
2317 }
2318 
2319 gboolean
cmd_roster(ProfWin * window,const char * const command,gchar ** args)2320 cmd_roster(ProfWin* window, const char* const command, gchar** args)
2321 {
2322     jabber_conn_status_t conn_status = connection_get_status();
2323 
2324     // show roster
2325     if (args[0] == NULL) {
2326         if (conn_status != JABBER_CONNECTED) {
2327             cons_show("You are not currently connected.");
2328             return TRUE;
2329         }
2330 
2331         GSList* list = roster_get_contacts(ROSTER_ORD_NAME);
2332         cons_show_roster(list);
2333         g_slist_free(list);
2334         return TRUE;
2335 
2336         // show roster, only online contacts
2337     } else if (g_strcmp0(args[0], "online") == 0) {
2338         if (conn_status != JABBER_CONNECTED) {
2339             cons_show("You are not currently connected.");
2340             return TRUE;
2341         }
2342 
2343         GSList* list = roster_get_contacts_online();
2344         cons_show_roster(list);
2345         g_slist_free(list);
2346         return TRUE;
2347 
2348         // set roster size
2349     } else if (g_strcmp0(args[0], "size") == 0) {
2350         if (!args[1]) {
2351             cons_bad_cmd_usage(command);
2352             return TRUE;
2353         }
2354         int intval = 0;
2355         char* err_msg = NULL;
2356         gboolean res = strtoi_range(args[1], &intval, 1, 99, &err_msg);
2357         if (res) {
2358             prefs_set_roster_size(intval);
2359             cons_show("Roster screen size set to: %d%%", intval);
2360             if (conn_status == JABBER_CONNECTED && prefs_get_boolean(PREF_ROSTER)) {
2361                 wins_resize_all();
2362             }
2363             return TRUE;
2364         } else {
2365             cons_show(err_msg);
2366             free(err_msg);
2367             return TRUE;
2368         }
2369 
2370         // set line wrapping
2371     } else if (g_strcmp0(args[0], "wrap") == 0) {
2372         if (!args[1]) {
2373             cons_bad_cmd_usage(command);
2374             return TRUE;
2375         } else {
2376             _cmd_set_boolean_preference(args[1], command, "Roster panel line wrap", PREF_ROSTER_WRAP);
2377             rosterwin_roster();
2378             return TRUE;
2379         }
2380 
2381         // header settings
2382     } else if (g_strcmp0(args[0], "header") == 0) {
2383         if (g_strcmp0(args[1], "char") == 0) {
2384             if (!args[2]) {
2385                 cons_bad_cmd_usage(command);
2386             } else if (g_strcmp0(args[2], "none") == 0) {
2387                 prefs_clear_roster_header_char();
2388                 cons_show("Roster header char removed.");
2389                 rosterwin_roster();
2390             } else {
2391                 prefs_set_roster_header_char(args[2][0]);
2392                 cons_show("Roster header char set to %c.", args[2][0]);
2393                 rosterwin_roster();
2394             }
2395         } else {
2396             cons_bad_cmd_usage(command);
2397         }
2398         return TRUE;
2399 
2400         // contact settings
2401     } else if (g_strcmp0(args[0], "contact") == 0) {
2402         if (g_strcmp0(args[1], "char") == 0) {
2403             if (!args[2]) {
2404                 cons_bad_cmd_usage(command);
2405             } else if (g_strcmp0(args[2], "none") == 0) {
2406                 prefs_clear_roster_contact_char();
2407                 cons_show("Roster contact char removed.");
2408                 rosterwin_roster();
2409             } else {
2410                 prefs_set_roster_contact_char(args[2][0]);
2411                 cons_show("Roster contact char set to %c.", args[2][0]);
2412                 rosterwin_roster();
2413             }
2414         } else if (g_strcmp0(args[1], "indent") == 0) {
2415             if (!args[2]) {
2416                 cons_bad_cmd_usage(command);
2417             } else {
2418                 int intval = 0;
2419                 char* err_msg = NULL;
2420                 gboolean res = strtoi_range(args[2], &intval, 0, 10, &err_msg);
2421                 if (res) {
2422                     prefs_set_roster_contact_indent(intval);
2423                     cons_show("Roster contact indent set to: %d", intval);
2424                     rosterwin_roster();
2425                 } else {
2426                     cons_show(err_msg);
2427                     free(err_msg);
2428                 }
2429             }
2430         } else {
2431             cons_bad_cmd_usage(command);
2432         }
2433         return TRUE;
2434 
2435         // resource settings
2436     } else if (g_strcmp0(args[0], "resource") == 0) {
2437         if (g_strcmp0(args[1], "char") == 0) {
2438             if (!args[2]) {
2439                 cons_bad_cmd_usage(command);
2440             } else if (g_strcmp0(args[2], "none") == 0) {
2441                 prefs_clear_roster_resource_char();
2442                 cons_show("Roster resource char removed.");
2443                 rosterwin_roster();
2444             } else {
2445                 prefs_set_roster_resource_char(args[2][0]);
2446                 cons_show("Roster resource char set to %c.", args[2][0]);
2447                 rosterwin_roster();
2448             }
2449         } else if (g_strcmp0(args[1], "indent") == 0) {
2450             if (!args[2]) {
2451                 cons_bad_cmd_usage(command);
2452             } else {
2453                 int intval = 0;
2454                 char* err_msg = NULL;
2455                 gboolean res = strtoi_range(args[2], &intval, 0, 10, &err_msg);
2456                 if (res) {
2457                     prefs_set_roster_resource_indent(intval);
2458                     cons_show("Roster resource indent set to: %d", intval);
2459                     rosterwin_roster();
2460                 } else {
2461                     cons_show(err_msg);
2462                     free(err_msg);
2463                 }
2464             }
2465         } else if (g_strcmp0(args[1], "join") == 0) {
2466             _cmd_set_boolean_preference(args[2], command, "Roster join", PREF_ROSTER_RESOURCE_JOIN);
2467             rosterwin_roster();
2468             return TRUE;
2469         } else {
2470             cons_bad_cmd_usage(command);
2471         }
2472         return TRUE;
2473 
2474         // presence settings
2475     } else if (g_strcmp0(args[0], "presence") == 0) {
2476         if (g_strcmp0(args[1], "indent") == 0) {
2477             if (!args[2]) {
2478                 cons_bad_cmd_usage(command);
2479             } else {
2480                 int intval = 0;
2481                 char* err_msg = NULL;
2482                 gboolean res = strtoi_range(args[2], &intval, -1, 10, &err_msg);
2483                 if (res) {
2484                     prefs_set_roster_presence_indent(intval);
2485                     cons_show("Roster presence indent set to: %d", intval);
2486                     rosterwin_roster();
2487                 } else {
2488                     cons_show(err_msg);
2489                     free(err_msg);
2490                 }
2491             }
2492         } else {
2493             cons_bad_cmd_usage(command);
2494         }
2495         return TRUE;
2496 
2497         // show/hide roster
2498     } else if ((g_strcmp0(args[0], "show") == 0) || (g_strcmp0(args[0], "hide") == 0)) {
2499         preference_t pref;
2500         const char* pref_str;
2501         if (args[1] == NULL) {
2502             pref = PREF_ROSTER;
2503             pref_str = "";
2504         } else if (g_strcmp0(args[1], "offline") == 0) {
2505             pref = PREF_ROSTER_OFFLINE;
2506             pref_str = "offline";
2507         } else if (g_strcmp0(args[1], "resource") == 0) {
2508             pref = PREF_ROSTER_RESOURCE;
2509             pref_str = "resource";
2510         } else if (g_strcmp0(args[1], "presence") == 0) {
2511             pref = PREF_ROSTER_PRESENCE;
2512             pref_str = "presence";
2513         } else if (g_strcmp0(args[1], "status") == 0) {
2514             pref = PREF_ROSTER_STATUS;
2515             pref_str = "status";
2516         } else if (g_strcmp0(args[1], "empty") == 0) {
2517             pref = PREF_ROSTER_EMPTY;
2518             pref_str = "empty";
2519         } else if (g_strcmp0(args[1], "priority") == 0) {
2520             pref = PREF_ROSTER_PRIORITY;
2521             pref_str = "priority";
2522         } else if (g_strcmp0(args[1], "contacts") == 0) {
2523             pref = PREF_ROSTER_CONTACTS;
2524             pref_str = "contacts";
2525         } else if (g_strcmp0(args[1], "rooms") == 0) {
2526             pref = PREF_ROSTER_ROOMS;
2527             pref_str = "rooms";
2528         } else if (g_strcmp0(args[1], "unsubscribed") == 0) {
2529             pref = PREF_ROSTER_UNSUBSCRIBED;
2530             pref_str = "unsubscribed";
2531         } else {
2532             cons_bad_cmd_usage(command);
2533             return TRUE;
2534         }
2535 
2536         gboolean val;
2537         if (g_strcmp0(args[0], "show") == 0) {
2538             val = TRUE;
2539         } else { // "hide"
2540             val = FALSE;
2541         }
2542 
2543         cons_show("Roster%s%s %s (was %s)", strlen(pref_str) == 0 ? "" : " ", pref_str,
2544                   val == TRUE ? "enabled" : "disabled",
2545                   prefs_get_boolean(pref) == TRUE ? "enabled" : "disabled");
2546         prefs_set_boolean(pref, val);
2547         if (conn_status == JABBER_CONNECTED) {
2548             if (pref == PREF_ROSTER) {
2549                 if (val == TRUE) {
2550                     ui_show_roster();
2551                 } else {
2552                     ui_hide_roster();
2553                 }
2554             } else {
2555                 rosterwin_roster();
2556             }
2557         }
2558         return TRUE;
2559 
2560         // roster grouping
2561     } else if (g_strcmp0(args[0], "by") == 0) {
2562         if (g_strcmp0(args[1], "group") == 0) {
2563             cons_show("Grouping roster by roster group");
2564             prefs_set_string(PREF_ROSTER_BY, "group");
2565             if (conn_status == JABBER_CONNECTED) {
2566                 rosterwin_roster();
2567             }
2568             return TRUE;
2569         } else if (g_strcmp0(args[1], "presence") == 0) {
2570             cons_show("Grouping roster by presence");
2571             prefs_set_string(PREF_ROSTER_BY, "presence");
2572             if (conn_status == JABBER_CONNECTED) {
2573                 rosterwin_roster();
2574             }
2575             return TRUE;
2576         } else if (g_strcmp0(args[1], "none") == 0) {
2577             cons_show("Roster grouping disabled");
2578             prefs_set_string(PREF_ROSTER_BY, "none");
2579             if (conn_status == JABBER_CONNECTED) {
2580                 rosterwin_roster();
2581             }
2582             return TRUE;
2583         } else {
2584             cons_bad_cmd_usage(command);
2585             return TRUE;
2586         }
2587 
2588         // roster item order
2589     } else if (g_strcmp0(args[0], "order") == 0) {
2590         if (g_strcmp0(args[1], "name") == 0) {
2591             cons_show("Ordering roster by name");
2592             prefs_set_string(PREF_ROSTER_ORDER, "name");
2593             if (conn_status == JABBER_CONNECTED) {
2594                 rosterwin_roster();
2595             }
2596             return TRUE;
2597         } else if (g_strcmp0(args[1], "presence") == 0) {
2598             cons_show("Ordering roster by presence");
2599             prefs_set_string(PREF_ROSTER_ORDER, "presence");
2600             if (conn_status == JABBER_CONNECTED) {
2601                 rosterwin_roster();
2602             }
2603             return TRUE;
2604         } else {
2605             cons_bad_cmd_usage(command);
2606             return TRUE;
2607         }
2608 
2609     } else if (g_strcmp0(args[0], "count") == 0) {
2610         if (g_strcmp0(args[1], "zero") == 0) {
2611             _cmd_set_boolean_preference(args[2], command, "Roster header zero count", PREF_ROSTER_COUNT_ZERO);
2612             if (conn_status == JABBER_CONNECTED) {
2613                 rosterwin_roster();
2614             }
2615             return TRUE;
2616         } else if (g_strcmp0(args[1], "unread") == 0) {
2617             cons_show("Roster header count set to unread");
2618             prefs_set_string(PREF_ROSTER_COUNT, "unread");
2619             if (conn_status == JABBER_CONNECTED) {
2620                 rosterwin_roster();
2621             }
2622             return TRUE;
2623         } else if (g_strcmp0(args[1], "items") == 0) {
2624             cons_show("Roster header count set to items");
2625             prefs_set_string(PREF_ROSTER_COUNT, "items");
2626             if (conn_status == JABBER_CONNECTED) {
2627                 rosterwin_roster();
2628             }
2629             return TRUE;
2630         } else if (g_strcmp0(args[1], "off") == 0) {
2631             cons_show("Disabling roster header count");
2632             prefs_set_string(PREF_ROSTER_COUNT, "off");
2633             if (conn_status == JABBER_CONNECTED) {
2634                 rosterwin_roster();
2635             }
2636             return TRUE;
2637         } else {
2638             cons_bad_cmd_usage(command);
2639             return TRUE;
2640         }
2641 
2642     } else if (g_strcmp0(args[0], "color") == 0) {
2643         _cmd_set_boolean_preference(args[1], command, "Roster consistent colors", PREF_ROSTER_COLOR_NICK);
2644         ui_show_roster();
2645         return TRUE;
2646 
2647     } else if (g_strcmp0(args[0], "unread") == 0) {
2648         if (g_strcmp0(args[1], "before") == 0) {
2649             cons_show("Roster unread message count: before");
2650             prefs_set_string(PREF_ROSTER_UNREAD, "before");
2651             if (conn_status == JABBER_CONNECTED) {
2652                 rosterwin_roster();
2653             }
2654             return TRUE;
2655         } else if (g_strcmp0(args[1], "after") == 0) {
2656             cons_show("Roster unread message count: after");
2657             prefs_set_string(PREF_ROSTER_UNREAD, "after");
2658             if (conn_status == JABBER_CONNECTED) {
2659                 rosterwin_roster();
2660             }
2661             return TRUE;
2662         } else if (g_strcmp0(args[1], "off") == 0) {
2663             cons_show("Roster unread message count: off");
2664             prefs_set_string(PREF_ROSTER_UNREAD, "off");
2665             if (conn_status == JABBER_CONNECTED) {
2666                 rosterwin_roster();
2667             }
2668             return TRUE;
2669         } else {
2670             cons_bad_cmd_usage(command);
2671             return TRUE;
2672         }
2673 
2674     } else if (g_strcmp0(args[0], "private") == 0) {
2675         if (g_strcmp0(args[1], "char") == 0) {
2676             if (!args[2]) {
2677                 cons_bad_cmd_usage(command);
2678             } else if (g_strcmp0(args[2], "none") == 0) {
2679                 prefs_clear_roster_private_char();
2680                 cons_show("Roster private room chat char removed.");
2681                 rosterwin_roster();
2682             } else {
2683                 prefs_set_roster_private_char(args[2][0]);
2684                 cons_show("Roster private room chat char set to %c.", args[2][0]);
2685                 rosterwin_roster();
2686             }
2687             return TRUE;
2688         } else if (g_strcmp0(args[1], "room") == 0) {
2689             cons_show("Showing room private chats under room.");
2690             prefs_set_string(PREF_ROSTER_PRIVATE, "room");
2691             if (conn_status == JABBER_CONNECTED) {
2692                 rosterwin_roster();
2693             }
2694             return TRUE;
2695         } else if (g_strcmp0(args[1], "group") == 0) {
2696             cons_show("Showing room private chats as roster group.");
2697             prefs_set_string(PREF_ROSTER_PRIVATE, "group");
2698             if (conn_status == JABBER_CONNECTED) {
2699                 rosterwin_roster();
2700             }
2701             return TRUE;
2702         } else if (g_strcmp0(args[1], "off") == 0) {
2703             cons_show("Hiding room private chats in roster.");
2704             prefs_set_string(PREF_ROSTER_PRIVATE, "off");
2705             if (conn_status == JABBER_CONNECTED) {
2706                 rosterwin_roster();
2707             }
2708             return TRUE;
2709         } else {
2710             cons_bad_cmd_usage(command);
2711             return TRUE;
2712         }
2713 
2714     } else if (g_strcmp0(args[0], "room") == 0) {
2715         if (g_strcmp0(args[1], "char") == 0) {
2716             if (!args[2]) {
2717                 cons_bad_cmd_usage(command);
2718             } else if (g_strcmp0(args[2], "none") == 0) {
2719                 prefs_clear_roster_room_char();
2720                 cons_show("Roster room char removed.");
2721                 rosterwin_roster();
2722             } else {
2723                 prefs_set_roster_room_char(args[2][0]);
2724                 cons_show("Roster room char set to %c.", args[2][0]);
2725                 rosterwin_roster();
2726             }
2727             return TRUE;
2728         } else if (g_strcmp0(args[1], "position") == 0) {
2729             if (g_strcmp0(args[2], "first") == 0) {
2730                 cons_show("Showing rooms first in roster.");
2731                 prefs_set_string(PREF_ROSTER_ROOMS_POS, "first");
2732                 if (conn_status == JABBER_CONNECTED) {
2733                     rosterwin_roster();
2734                 }
2735                 return TRUE;
2736             } else if (g_strcmp0(args[2], "last") == 0) {
2737                 cons_show("Showing rooms last in roster.");
2738                 prefs_set_string(PREF_ROSTER_ROOMS_POS, "last");
2739                 if (conn_status == JABBER_CONNECTED) {
2740                     rosterwin_roster();
2741                 }
2742                 return TRUE;
2743             } else {
2744                 cons_bad_cmd_usage(command);
2745                 return TRUE;
2746             }
2747         } else if (g_strcmp0(args[1], "order") == 0) {
2748             if (g_strcmp0(args[2], "name") == 0) {
2749                 cons_show("Ordering roster rooms by name");
2750                 prefs_set_string(PREF_ROSTER_ROOMS_ORDER, "name");
2751                 if (conn_status == JABBER_CONNECTED) {
2752                     rosterwin_roster();
2753                 }
2754                 return TRUE;
2755             } else if (g_strcmp0(args[2], "unread") == 0) {
2756                 cons_show("Ordering roster rooms by unread messages");
2757                 prefs_set_string(PREF_ROSTER_ROOMS_ORDER, "unread");
2758                 if (conn_status == JABBER_CONNECTED) {
2759                     rosterwin_roster();
2760                 }
2761                 return TRUE;
2762             } else {
2763                 cons_bad_cmd_usage(command);
2764                 return TRUE;
2765             }
2766         } else if (g_strcmp0(args[1], "unread") == 0) {
2767             if (g_strcmp0(args[2], "before") == 0) {
2768                 cons_show("Roster rooms unread message count: before");
2769                 prefs_set_string(PREF_ROSTER_ROOMS_UNREAD, "before");
2770                 if (conn_status == JABBER_CONNECTED) {
2771                     rosterwin_roster();
2772                 }
2773                 return TRUE;
2774             } else if (g_strcmp0(args[2], "after") == 0) {
2775                 cons_show("Roster rooms unread message count: after");
2776                 prefs_set_string(PREF_ROSTER_ROOMS_UNREAD, "after");
2777                 if (conn_status == JABBER_CONNECTED) {
2778                     rosterwin_roster();
2779                 }
2780                 return TRUE;
2781             } else if (g_strcmp0(args[2], "off") == 0) {
2782                 cons_show("Roster rooms unread message count: off");
2783                 prefs_set_string(PREF_ROSTER_ROOMS_UNREAD, "off");
2784                 if (conn_status == JABBER_CONNECTED) {
2785                     rosterwin_roster();
2786                 }
2787                 return TRUE;
2788             } else {
2789                 cons_bad_cmd_usage(command);
2790                 return TRUE;
2791             }
2792         } else if (g_strcmp0(args[1], "private") == 0) {
2793             if (g_strcmp0(args[2], "char") == 0) {
2794                 if (!args[3]) {
2795                     cons_bad_cmd_usage(command);
2796                 } else if (g_strcmp0(args[3], "none") == 0) {
2797                     prefs_clear_roster_room_private_char();
2798                     cons_show("Roster room private char removed.");
2799                     rosterwin_roster();
2800                 } else {
2801                     prefs_set_roster_room_private_char(args[3][0]);
2802                     cons_show("Roster room private char set to %c.", args[3][0]);
2803                     rosterwin_roster();
2804                 }
2805                 return TRUE;
2806             } else {
2807                 cons_bad_cmd_usage(command);
2808                 return TRUE;
2809             }
2810         } else if (g_strcmp0(args[1], "by") == 0) {
2811             if (g_strcmp0(args[2], "service") == 0) {
2812                 cons_show("Grouping rooms by service");
2813                 prefs_set_string(PREF_ROSTER_ROOMS_BY, "service");
2814                 if (conn_status == JABBER_CONNECTED) {
2815                     rosterwin_roster();
2816                 }
2817                 return TRUE;
2818             } else if (g_strcmp0(args[2], "none") == 0) {
2819                 cons_show("Roster room grouping disabled");
2820                 prefs_set_string(PREF_ROSTER_ROOMS_BY, "none");
2821                 if (conn_status == JABBER_CONNECTED) {
2822                     rosterwin_roster();
2823                 }
2824                 return TRUE;
2825             } else {
2826                 cons_bad_cmd_usage(command);
2827                 return TRUE;
2828             }
2829         } else if (g_strcmp0(args[1], "show") == 0) {
2830             if (g_strcmp0(args[2], "server") == 0) {
2831                 cons_show("Roster room server enabled.");
2832                 prefs_set_boolean(PREF_ROSTER_ROOMS_SERVER, TRUE);
2833                 if (conn_status == JABBER_CONNECTED) {
2834                     rosterwin_roster();
2835                 }
2836                 return TRUE;
2837             } else {
2838                 cons_bad_cmd_usage(command);
2839                 return TRUE;
2840             }
2841         } else if (g_strcmp0(args[1], "hide") == 0) {
2842             if (g_strcmp0(args[2], "server") == 0) {
2843                 cons_show("Roster room server disabled.");
2844                 prefs_set_boolean(PREF_ROSTER_ROOMS_SERVER, FALSE);
2845                 if (conn_status == JABBER_CONNECTED) {
2846                     rosterwin_roster();
2847                 }
2848                 return TRUE;
2849             } else {
2850                 cons_bad_cmd_usage(command);
2851                 return TRUE;
2852             }
2853         } else if (g_strcmp0(args[1], "use") == 0) {
2854             if (g_strcmp0(args[2], "jid") == 0) {
2855                 cons_show("Roster room display jid as name.");
2856                 prefs_set_string(PREF_ROSTER_ROOMS_USE_AS_NAME, "jid");
2857                 if (conn_status == JABBER_CONNECTED) {
2858                     rosterwin_roster();
2859                 }
2860                 return TRUE;
2861             } else if (g_strcmp0(args[2], "name") == 0) {
2862                 cons_show("Roster room display room name as name.");
2863                 prefs_set_string(PREF_ROSTER_ROOMS_USE_AS_NAME, "name");
2864                 if (conn_status == JABBER_CONNECTED) {
2865                     rosterwin_roster();
2866                 }
2867                 return TRUE;
2868             } else {
2869                 cons_bad_cmd_usage(command);
2870                 return TRUE;
2871             }
2872         } else {
2873             cons_bad_cmd_usage(command);
2874             return TRUE;
2875         }
2876 
2877         // add contact
2878     } else if (strcmp(args[0], "add") == 0) {
2879         if (conn_status != JABBER_CONNECTED) {
2880             cons_show("You are not currently connected.");
2881             return TRUE;
2882         }
2883         char* jid = args[1];
2884         if (jid == NULL) {
2885             cons_bad_cmd_usage(command);
2886         } else {
2887             char* name = args[2];
2888             roster_send_add_new(jid, name);
2889         }
2890         return TRUE;
2891 
2892         // remove contact
2893     } else if (strcmp(args[0], "remove") == 0) {
2894         if (conn_status != JABBER_CONNECTED) {
2895             cons_show("You are not currently connected.");
2896             return TRUE;
2897         }
2898         char* jid = args[1];
2899         if (jid == NULL) {
2900             cons_bad_cmd_usage(command);
2901         } else {
2902             roster_send_remove(jid);
2903         }
2904         return TRUE;
2905 
2906     } else if (strcmp(args[0], "remove_all") == 0) {
2907         if (g_strcmp0(args[1], "contacts") != 0) {
2908             cons_bad_cmd_usage(command);
2909             return TRUE;
2910         }
2911         if (conn_status != JABBER_CONNECTED) {
2912             cons_show("You are not currently connected.");
2913             return TRUE;
2914         }
2915 
2916         GSList* all = roster_get_contacts(ROSTER_ORD_NAME);
2917         GSList* curr = all;
2918         while (curr) {
2919             PContact contact = curr->data;
2920             roster_send_remove(p_contact_barejid(contact));
2921             curr = g_slist_next(curr);
2922         }
2923 
2924         g_slist_free(all);
2925         return TRUE;
2926 
2927         // change nickname
2928     } else if (strcmp(args[0], "nick") == 0) {
2929         if (conn_status != JABBER_CONNECTED) {
2930             cons_show("You are not currently connected.");
2931             return TRUE;
2932         }
2933         char* jid = args[1];
2934         if (jid == NULL) {
2935             cons_bad_cmd_usage(command);
2936             return TRUE;
2937         }
2938 
2939         char* name = args[2];
2940         if (name == NULL) {
2941             cons_bad_cmd_usage(command);
2942             return TRUE;
2943         }
2944 
2945         // contact does not exist
2946         PContact contact = roster_get_contact(jid);
2947         if (contact == NULL) {
2948             cons_show("Contact not found in roster: %s", jid);
2949             return TRUE;
2950         }
2951 
2952         const char* barejid = p_contact_barejid(contact);
2953 
2954         // TODO wait for result stanza before updating
2955         const char* oldnick = p_contact_name(contact);
2956         wins_change_nick(barejid, oldnick, name);
2957         roster_change_name(contact, name);
2958         GSList* groups = p_contact_groups(contact);
2959         roster_send_name_change(barejid, name, groups);
2960 
2961         cons_show("Nickname for %s set to: %s.", jid, name);
2962 
2963         return TRUE;
2964 
2965         // remove nickname
2966     } else if (strcmp(args[0], "clearnick") == 0) {
2967         if (conn_status != JABBER_CONNECTED) {
2968             cons_show("You are not currently connected.");
2969             return TRUE;
2970         }
2971         char* jid = args[1];
2972         if (jid == NULL) {
2973             cons_bad_cmd_usage(command);
2974             return TRUE;
2975         }
2976 
2977         // contact does not exist
2978         PContact contact = roster_get_contact(jid);
2979         if (contact == NULL) {
2980             cons_show("Contact not found in roster: %s", jid);
2981             return TRUE;
2982         }
2983 
2984         const char* barejid = p_contact_barejid(contact);
2985 
2986         // TODO wait for result stanza before updating
2987         const char* oldnick = p_contact_name(contact);
2988         wins_remove_nick(barejid, oldnick);
2989         roster_change_name(contact, NULL);
2990         GSList* groups = p_contact_groups(contact);
2991         roster_send_name_change(barejid, NULL, groups);
2992 
2993         cons_show("Nickname for %s removed.", jid);
2994 
2995         return TRUE;
2996     } else {
2997         cons_bad_cmd_usage(command);
2998         return TRUE;
2999     }
3000 }
3001 
3002 gboolean
cmd_blocked(ProfWin * window,const char * const command,gchar ** args)3003 cmd_blocked(ProfWin* window, const char* const command, gchar** args)
3004 {
3005     if (connection_get_status() != JABBER_CONNECTED) {
3006         cons_show("You are not currently connected.");
3007         return TRUE;
3008     }
3009 
3010     if (!connection_supports(XMPP_FEATURE_BLOCKING)) {
3011         cons_show("Blocking (%s) not supported by server.", XMPP_FEATURE_BLOCKING);
3012         return TRUE;
3013     }
3014 
3015     blocked_report br = BLOCKED_NO_REPORT;
3016 
3017     if (g_strcmp0(args[0], "add") == 0) {
3018         char* jid = args[1];
3019 
3020         if (jid == NULL && (window->type == WIN_CHAT)) {
3021             ProfChatWin* chatwin = (ProfChatWin*)window;
3022             jid = chatwin->barejid;
3023         }
3024 
3025         if (jid == NULL) {
3026             cons_bad_cmd_usage(command);
3027             return TRUE;
3028         }
3029 
3030         gboolean res = blocked_add(jid, br, NULL);
3031         if (!res) {
3032             cons_show("User %s already blocked.", jid);
3033         }
3034 
3035         return TRUE;
3036     }
3037 
3038     if (g_strcmp0(args[0], "remove") == 0) {
3039         if (args[1] == NULL) {
3040             cons_bad_cmd_usage(command);
3041             return TRUE;
3042         }
3043 
3044         gboolean res = blocked_remove(args[1]);
3045         if (!res) {
3046             cons_show("User %s is not currently blocked.", args[1]);
3047         }
3048 
3049         return TRUE;
3050     }
3051 
3052     if (args[0] && strncmp(args[0], "report-", 7) == 0) {
3053         char* jid = NULL;
3054         char* msg = NULL;
3055         guint argn = g_strv_length(args);
3056 
3057         if (argn >= 2) {
3058             jid = args[1];
3059         } else {
3060             cons_bad_cmd_usage(command);
3061             return TRUE;
3062         }
3063 
3064         if (argn >= 3) {
3065             msg = args[2];
3066         }
3067 
3068         if (args[1] && g_strcmp0(args[0], "report-abuse") == 0) {
3069             br = BLOCKED_REPORT_ABUSE;
3070         } else if (args[1] && g_strcmp0(args[0], "report-spam") == 0) {
3071             br = BLOCKED_REPORT_SPAM;
3072         } else {
3073             cons_bad_cmd_usage(command);
3074             return TRUE;
3075         }
3076 
3077         if (!connection_supports(XMPP_FEATURE_SPAM_REPORTING)) {
3078             cons_show("Spam reporting (%s) not supported by server.", XMPP_FEATURE_SPAM_REPORTING);
3079             return TRUE;
3080         }
3081 
3082         gboolean res = blocked_add(jid, br, msg);
3083         if (!res) {
3084             cons_show("User %s already blocked.", args[1]);
3085         }
3086         return TRUE;
3087     }
3088 
3089     GList* blocked = blocked_list();
3090     GList* curr = blocked;
3091     if (curr) {
3092         cons_show("Blocked users:");
3093         while (curr) {
3094             cons_show("  %s", curr->data);
3095             curr = g_list_next(curr);
3096         }
3097     } else {
3098         cons_show("No blocked users.");
3099     }
3100 
3101     return TRUE;
3102 }
3103 
3104 gboolean
cmd_resource(ProfWin * window,const char * const command,gchar ** args)3105 cmd_resource(ProfWin* window, const char* const command, gchar** args)
3106 {
3107     char* cmd = args[0];
3108     char* setting = NULL;
3109     if (g_strcmp0(cmd, "message") == 0) {
3110         setting = args[1];
3111         if (!setting) {
3112             cons_bad_cmd_usage(command);
3113             return TRUE;
3114         } else {
3115             _cmd_set_boolean_preference(setting, command, "Message resource", PREF_RESOURCE_MESSAGE);
3116             return TRUE;
3117         }
3118     } else if (g_strcmp0(cmd, "title") == 0) {
3119         setting = args[1];
3120         if (!setting) {
3121             cons_bad_cmd_usage(command);
3122             return TRUE;
3123         } else {
3124             _cmd_set_boolean_preference(setting, command, "Title resource", PREF_RESOURCE_TITLE);
3125             return TRUE;
3126         }
3127     }
3128 
3129     if (window->type != WIN_CHAT) {
3130         cons_show("Resource can only be changed in chat windows.");
3131         return TRUE;
3132     }
3133 
3134     jabber_conn_status_t conn_status = connection_get_status();
3135     if (conn_status != JABBER_CONNECTED) {
3136         cons_show("You are not currently connected.");
3137         return TRUE;
3138     }
3139 
3140     ProfChatWin* chatwin = (ProfChatWin*)window;
3141 
3142     if (g_strcmp0(cmd, "set") == 0) {
3143         char* resource = args[1];
3144         if (!resource) {
3145             cons_bad_cmd_usage(command);
3146             return TRUE;
3147         }
3148 
3149 #ifdef HAVE_LIBOTR
3150         if (otr_is_secure(chatwin->barejid)) {
3151             cons_show("Cannot choose resource during an OTR session.");
3152             return TRUE;
3153         }
3154 #endif
3155 
3156         PContact contact = roster_get_contact(chatwin->barejid);
3157         if (!contact) {
3158             cons_show("Cannot choose resource for contact not in roster.");
3159             return TRUE;
3160         }
3161 
3162         if (!p_contact_get_resource(contact, resource)) {
3163             cons_show("No such resource %s.", resource);
3164             return TRUE;
3165         }
3166 
3167         chatwin->resource_override = strdup(resource);
3168         chat_state_free(chatwin->state);
3169         chatwin->state = chat_state_new();
3170         chat_session_resource_override(chatwin->barejid, resource);
3171         return TRUE;
3172 
3173     } else if (g_strcmp0(cmd, "off") == 0) {
3174         FREE_SET_NULL(chatwin->resource_override);
3175         chat_state_free(chatwin->state);
3176         chatwin->state = chat_state_new();
3177         chat_session_remove(chatwin->barejid);
3178         return TRUE;
3179     } else {
3180         cons_bad_cmd_usage(command);
3181         return TRUE;
3182     }
3183 }
3184 
3185 static void
_cmd_status_show_status(char * usr)3186 _cmd_status_show_status(char* usr)
3187 {
3188     char* usr_jid = roster_barejid_from_name(usr);
3189     if (usr_jid == NULL) {
3190         usr_jid = usr;
3191     }
3192     cons_show_status(usr_jid);
3193 }
3194 
3195 gboolean
cmd_status_set(ProfWin * window,const char * const command,gchar ** args)3196 cmd_status_set(ProfWin* window, const char* const command, gchar** args)
3197 {
3198     char* state = args[1];
3199 
3200     if (g_strcmp0(state, "online") == 0) {
3201         _update_presence(RESOURCE_ONLINE, "online", args);
3202     } else if (g_strcmp0(state, "away") == 0) {
3203         _update_presence(RESOURCE_AWAY, "away", args);
3204     } else if (g_strcmp0(state, "dnd") == 0) {
3205         _update_presence(RESOURCE_DND, "dnd", args);
3206     } else if (g_strcmp0(state, "chat") == 0) {
3207         _update_presence(RESOURCE_CHAT, "chat", args);
3208     } else if (g_strcmp0(state, "xa") == 0) {
3209         _update_presence(RESOURCE_XA, "xa", args);
3210     } else {
3211         cons_bad_cmd_usage(command);
3212     }
3213 
3214     return TRUE;
3215 }
3216 
3217 gboolean
cmd_status_get(ProfWin * window,const char * const command,gchar ** args)3218 cmd_status_get(ProfWin* window, const char* const command, gchar** args)
3219 {
3220     char* usr = args[1];
3221 
3222     jabber_conn_status_t conn_status = connection_get_status();
3223 
3224     if (conn_status != JABBER_CONNECTED) {
3225         cons_show("You are not currently connected.");
3226         return TRUE;
3227     }
3228 
3229     switch (window->type) {
3230     case WIN_MUC:
3231         if (usr) {
3232             ProfMucWin* mucwin = (ProfMucWin*)window;
3233             assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
3234             Occupant* occupant = muc_roster_item(mucwin->roomjid, usr);
3235             if (occupant) {
3236                 win_show_occupant(window, occupant);
3237             } else {
3238                 win_println(window, THEME_DEFAULT, "-", "No such participant \"%s\" in room.", usr);
3239             }
3240         } else {
3241             win_println(window, THEME_DEFAULT, "-", "You must specify a nickname.");
3242         }
3243         break;
3244     case WIN_CHAT:
3245         if (usr) {
3246             _cmd_status_show_status(usr);
3247         } else {
3248             ProfChatWin* chatwin = (ProfChatWin*)window;
3249             assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
3250             PContact pcontact = roster_get_contact(chatwin->barejid);
3251             if (pcontact) {
3252                 win_show_contact(window, pcontact);
3253             } else {
3254                 win_println(window, THEME_DEFAULT, "-", "Error getting contact info.");
3255             }
3256         }
3257         break;
3258     case WIN_PRIVATE:
3259         if (usr) {
3260             _cmd_status_show_status(usr);
3261         } else {
3262             ProfPrivateWin* privatewin = (ProfPrivateWin*)window;
3263             assert(privatewin->memcheck == PROFPRIVATEWIN_MEMCHECK);
3264             Jid* jid = jid_create(privatewin->fulljid);
3265             Occupant* occupant = muc_roster_item(jid->barejid, jid->resourcepart);
3266             if (occupant) {
3267                 win_show_occupant(window, occupant);
3268             } else {
3269                 win_println(window, THEME_DEFAULT, "-", "Error getting contact info.");
3270             }
3271             jid_destroy(jid);
3272         }
3273         break;
3274     case WIN_CONSOLE:
3275         if (usr) {
3276             _cmd_status_show_status(usr);
3277         } else {
3278             cons_bad_cmd_usage(command);
3279         }
3280         break;
3281     default:
3282         break;
3283     }
3284 
3285     return TRUE;
3286 }
3287 
3288 static void
_cmd_info_show_contact(char * usr)3289 _cmd_info_show_contact(char* usr)
3290 {
3291     char* usr_jid = roster_barejid_from_name(usr);
3292     if (usr_jid == NULL) {
3293         usr_jid = usr;
3294     }
3295     PContact pcontact = roster_get_contact(usr_jid);
3296     if (pcontact) {
3297         cons_show_info(pcontact);
3298     } else {
3299         cons_show("No such contact \"%s\" in roster.", usr);
3300     }
3301 }
3302 
3303 gboolean
cmd_info(ProfWin * window,const char * const command,gchar ** args)3304 cmd_info(ProfWin* window, const char* const command, gchar** args)
3305 {
3306     char* usr = args[0];
3307 
3308     jabber_conn_status_t conn_status = connection_get_status();
3309 
3310     if (conn_status != JABBER_CONNECTED) {
3311         cons_show("You are not currently connected.");
3312         return TRUE;
3313     }
3314 
3315     switch (window->type) {
3316     case WIN_MUC:
3317         if (usr) {
3318             ProfMucWin* mucwin = (ProfMucWin*)window;
3319             assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
3320             Occupant* occupant = muc_roster_item(mucwin->roomjid, usr);
3321             if (occupant) {
3322                 win_show_occupant_info(window, mucwin->roomjid, occupant);
3323             } else {
3324                 win_println(window, THEME_DEFAULT, "-", "No such occupant \"%s\" in room.", usr);
3325             }
3326         } else {
3327             ProfMucWin* mucwin = (ProfMucWin*)window;
3328             assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
3329             iq_room_info_request(mucwin->roomjid, TRUE);
3330             mucwin_info(mucwin);
3331             return TRUE;
3332         }
3333         break;
3334     case WIN_CHAT:
3335         if (usr) {
3336             _cmd_info_show_contact(usr);
3337         } else {
3338             ProfChatWin* chatwin = (ProfChatWin*)window;
3339             assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
3340             PContact pcontact = roster_get_contact(chatwin->barejid);
3341             if (pcontact) {
3342                 win_show_info(window, pcontact);
3343             } else {
3344                 win_println(window, THEME_DEFAULT, "-", "Error getting contact info.");
3345             }
3346         }
3347         break;
3348     case WIN_PRIVATE:
3349         if (usr) {
3350             _cmd_info_show_contact(usr);
3351         } else {
3352             ProfPrivateWin* privatewin = (ProfPrivateWin*)window;
3353             assert(privatewin->memcheck == PROFPRIVATEWIN_MEMCHECK);
3354             Jid* jid = jid_create(privatewin->fulljid);
3355             Occupant* occupant = muc_roster_item(jid->barejid, jid->resourcepart);
3356             if (occupant) {
3357                 win_show_occupant_info(window, jid->barejid, occupant);
3358             } else {
3359                 win_println(window, THEME_DEFAULT, "-", "Error getting contact info.");
3360             }
3361             jid_destroy(jid);
3362         }
3363         break;
3364     case WIN_CONSOLE:
3365         if (usr) {
3366             _cmd_info_show_contact(usr);
3367         } else {
3368             cons_bad_cmd_usage(command);
3369         }
3370         break;
3371     default:
3372         break;
3373     }
3374 
3375     return TRUE;
3376 }
3377 
3378 gboolean
cmd_caps(ProfWin * window,const char * const command,gchar ** args)3379 cmd_caps(ProfWin* window, const char* const command, gchar** args)
3380 {
3381     jabber_conn_status_t conn_status = connection_get_status();
3382     Occupant* occupant = NULL;
3383 
3384     if (conn_status != JABBER_CONNECTED) {
3385         cons_show("You are not currently connected.");
3386         return TRUE;
3387     }
3388 
3389     switch (window->type) {
3390     case WIN_MUC:
3391         if (args[0]) {
3392             ProfMucWin* mucwin = (ProfMucWin*)window;
3393             assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
3394             occupant = muc_roster_item(mucwin->roomjid, args[0]);
3395             if (occupant) {
3396                 Jid* jidp = jid_create_from_bare_and_resource(mucwin->roomjid, args[0]);
3397                 cons_show_caps(jidp->fulljid, occupant->presence);
3398                 jid_destroy(jidp);
3399             } else {
3400                 cons_show("No such participant \"%s\" in room.", args[0]);
3401             }
3402         } else {
3403             cons_show("No nickname supplied to /caps in chat room.");
3404         }
3405         break;
3406     case WIN_CHAT:
3407     case WIN_CONSOLE:
3408         if (args[0]) {
3409             Jid* jid = jid_create(args[0]);
3410 
3411             if (jid->fulljid == NULL) {
3412                 cons_show("You must provide a full jid to the /caps command.");
3413             } else {
3414                 PContact pcontact = roster_get_contact(jid->barejid);
3415                 if (pcontact == NULL) {
3416                     cons_show("Contact not found in roster: %s", jid->barejid);
3417                 } else {
3418                     Resource* resource = p_contact_get_resource(pcontact, jid->resourcepart);
3419                     if (resource == NULL) {
3420                         cons_show("Could not find resource %s, for contact %s", jid->barejid, jid->resourcepart);
3421                     } else {
3422                         cons_show_caps(jid->fulljid, resource->presence);
3423                     }
3424                 }
3425             }
3426             jid_destroy(jid);
3427         } else {
3428             cons_show("You must provide a jid to the /caps command.");
3429         }
3430         break;
3431     case WIN_PRIVATE:
3432         if (args[0]) {
3433             cons_show("No parameter needed to /caps when in private chat.");
3434         } else {
3435             ProfPrivateWin* privatewin = (ProfPrivateWin*)window;
3436             assert(privatewin->memcheck == PROFPRIVATEWIN_MEMCHECK);
3437             Jid* jid = jid_create(privatewin->fulljid);
3438             if (jid) {
3439                 occupant = muc_roster_item(jid->barejid, jid->resourcepart);
3440                 cons_show_caps(jid->resourcepart, occupant->presence);
3441                 jid_destroy(jid);
3442             }
3443         }
3444         break;
3445     default:
3446         break;
3447     }
3448 
3449     return TRUE;
3450 }
3451 
3452 static void
_send_software_version_iq_to_fulljid(char * request)3453 _send_software_version_iq_to_fulljid(char* request)
3454 {
3455     char* mybarejid = connection_get_barejid();
3456     Jid* jid = jid_create(request);
3457 
3458     if (jid == NULL || jid->fulljid == NULL) {
3459         cons_show("You must provide a full jid to the /software command.");
3460     } else if (g_strcmp0(jid->barejid, mybarejid) == 0) {
3461         cons_show("Cannot request software version for yourself.");
3462     } else {
3463         iq_send_software_version(jid->fulljid);
3464     }
3465     free(mybarejid);
3466     jid_destroy(jid);
3467 }
3468 
3469 gboolean
cmd_software(ProfWin * window,const char * const command,gchar ** args)3470 cmd_software(ProfWin* window, const char* const command, gchar** args)
3471 {
3472     jabber_conn_status_t conn_status = connection_get_status();
3473 
3474     if (conn_status != JABBER_CONNECTED) {
3475         cons_show("You are not currently connected.");
3476         return TRUE;
3477     }
3478 
3479     switch (window->type) {
3480     case WIN_MUC:
3481         if (args[0]) {
3482             ProfMucWin* mucwin = (ProfMucWin*)window;
3483             assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
3484             Occupant* occupant = muc_roster_item(mucwin->roomjid, args[0]);
3485             if (occupant) {
3486                 Jid* jid = jid_create_from_bare_and_resource(mucwin->roomjid, args[0]);
3487                 iq_send_software_version(jid->fulljid);
3488                 jid_destroy(jid);
3489             } else {
3490                 cons_show("No such participant \"%s\" in room.", args[0]);
3491             }
3492         } else {
3493             cons_show("No nickname supplied to /software in chat room.");
3494         }
3495         break;
3496     case WIN_CHAT:
3497         if (args[0]) {
3498             _send_software_version_iq_to_fulljid(args[0]);
3499             break;
3500         } else {
3501             ProfChatWin* chatwin = (ProfChatWin*)window;
3502             assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
3503 
3504             char* resource = NULL;
3505             ChatSession* session = chat_session_get(chatwin->barejid);
3506             if (chatwin->resource_override) {
3507                 resource = chatwin->resource_override;
3508             } else if (session && session->resource) {
3509                 resource = session->resource;
3510             }
3511 
3512             if (resource) {
3513                 GString* fulljid = g_string_new(chatwin->barejid);
3514                 g_string_append_printf(fulljid, "/%s", resource);
3515                 iq_send_software_version(fulljid->str);
3516                 g_string_free(fulljid, TRUE);
3517             } else {
3518                 win_println(window, THEME_DEFAULT, "-", "Unknown resource for /software command. See /help resource.");
3519             }
3520             break;
3521         }
3522     case WIN_CONSOLE:
3523         if (args[0]) {
3524             _send_software_version_iq_to_fulljid(args[0]);
3525         } else {
3526             cons_show("You must provide a jid to the /software command.");
3527         }
3528         break;
3529     case WIN_PRIVATE:
3530         if (args[0]) {
3531             cons_show("No parameter needed to /software when in private chat.");
3532         } else {
3533             ProfPrivateWin* privatewin = (ProfPrivateWin*)window;
3534             assert(privatewin->memcheck == PROFPRIVATEWIN_MEMCHECK);
3535             iq_send_software_version(privatewin->fulljid);
3536         }
3537         break;
3538     default:
3539         break;
3540     }
3541 
3542     return TRUE;
3543 }
3544 
3545 gboolean
cmd_serversoftware(ProfWin * window,const char * const command,gchar ** args)3546 cmd_serversoftware(ProfWin* window, const char* const command, gchar** args)
3547 {
3548     jabber_conn_status_t conn_status = connection_get_status();
3549 
3550     if (conn_status != JABBER_CONNECTED) {
3551         cons_show("You are not currently connected.");
3552         return TRUE;
3553     }
3554 
3555     if (args[0]) {
3556         iq_send_software_version(args[0]);
3557     } else {
3558         cons_show("You must provide a jid to the /serversoftware command.");
3559     }
3560 
3561     return TRUE;
3562 }
3563 
3564 gboolean
cmd_join(ProfWin * window,const char * const command,gchar ** args)3565 cmd_join(ProfWin* window, const char* const command, gchar** args)
3566 {
3567     jabber_conn_status_t conn_status = connection_get_status();
3568     if (conn_status != JABBER_CONNECTED) {
3569         cons_show("You are not currently connected.");
3570         return TRUE;
3571     }
3572 
3573     if (args[0] == NULL) {
3574         char* account_name = session_get_account_name();
3575         ProfAccount* account = accounts_get_account(account_name);
3576         if (account->muc_service) {
3577             GString* room_str = g_string_new("");
3578             char* uuid = connection_create_uuid();
3579             g_string_append_printf(room_str, "private-chat-%s@%s", uuid, account->muc_service);
3580             connection_free_uuid(uuid);
3581 
3582             presence_join_room(room_str->str, account->muc_nick, NULL);
3583             muc_join(room_str->str, account->muc_nick, NULL, FALSE);
3584 
3585             g_string_free(room_str, TRUE);
3586             account_free(account);
3587         } else {
3588             cons_show("Account MUC service property not found.");
3589         }
3590 
3591         return TRUE;
3592     }
3593 
3594     Jid* room_arg = jid_create(args[0]);
3595     if (room_arg == NULL) {
3596         cons_show_error("Specified room has incorrect format.");
3597         cons_show("");
3598         return TRUE;
3599     }
3600 
3601     char* room = NULL;
3602     char* nick = NULL;
3603     char* passwd = NULL;
3604     char* account_name = session_get_account_name();
3605     ProfAccount* account = accounts_get_account(account_name);
3606 
3607     // full room jid supplied (room@server)
3608     if (room_arg->localpart) {
3609         room = g_strdup(args[0]);
3610 
3611         // server not supplied (room), use account preference
3612     } else if (account->muc_service) {
3613         GString* room_str = g_string_new("");
3614         g_string_append(room_str, args[0]);
3615         g_string_append(room_str, "@");
3616         g_string_append(room_str, account->muc_service);
3617         room = room_str->str;
3618         g_string_free(room_str, FALSE);
3619 
3620         // no account preference
3621     } else {
3622         cons_show("Account MUC service property not found.");
3623         return TRUE;
3624     }
3625 
3626     // Additional args supplied
3627     gchar* opt_keys[] = { "nick", "password", NULL };
3628     gboolean parsed;
3629 
3630     GHashTable* options = parse_options(&args[1], opt_keys, &parsed);
3631     if (!parsed) {
3632         cons_bad_cmd_usage(command);
3633         cons_show("");
3634         g_free(room);
3635         jid_destroy(room_arg);
3636         return TRUE;
3637     }
3638 
3639     nick = g_hash_table_lookup(options, "nick");
3640     passwd = g_hash_table_lookup(options, "password");
3641 
3642     options_destroy(options);
3643 
3644     // In the case that a nick wasn't provided by the optional args...
3645     if (!nick) {
3646         nick = account->muc_nick;
3647     }
3648 
3649     // When no password, check for invite with password
3650     if (!passwd) {
3651         passwd = muc_invite_password(room);
3652     }
3653 
3654     if (!muc_active(room)) {
3655         presence_join_room(room, nick, passwd);
3656         muc_join(room, nick, passwd, FALSE);
3657         iq_room_affiliation_list(room, "member", false);
3658         iq_room_affiliation_list(room, "admin", false);
3659         iq_room_affiliation_list(room, "owner", false);
3660     } else if (muc_roster_complete(room)) {
3661         ui_switch_to_room(room);
3662     }
3663 
3664     g_free(room);
3665     jid_destroy(room_arg);
3666     account_free(account);
3667 
3668     return TRUE;
3669 }
3670 
3671 gboolean
cmd_invite(ProfWin * window,const char * const command,gchar ** args)3672 cmd_invite(ProfWin* window, const char* const command, gchar** args)
3673 {
3674     jabber_conn_status_t conn_status = connection_get_status();
3675 
3676     if (conn_status != JABBER_CONNECTED) {
3677         cons_show("You are not currently connected.");
3678         return TRUE;
3679     }
3680 
3681     if (g_strcmp0(args[0], "send") == 0) {
3682         char* contact = args[1];
3683         char* reason = args[2];
3684 
3685         if (window->type != WIN_MUC) {
3686             cons_show("You must be in a chat room to send an invite.");
3687             return TRUE;
3688         }
3689 
3690         char* usr_jid = roster_barejid_from_name(contact);
3691         if (usr_jid == NULL) {
3692             usr_jid = contact;
3693         }
3694 
3695         ProfMucWin* mucwin = (ProfMucWin*)window;
3696         assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
3697         message_send_invite(mucwin->roomjid, usr_jid, reason);
3698         if (reason) {
3699             cons_show("Room invite sent, contact: %s, room: %s, reason: \"%s\".",
3700                       contact, mucwin->roomjid, reason);
3701         } else {
3702             cons_show("Room invite sent, contact: %s, room: %s.",
3703                       contact, mucwin->roomjid);
3704         }
3705     } else if (g_strcmp0(args[0], "list") == 0) {
3706         GList* invites = muc_invites();
3707         cons_show_room_invites(invites);
3708         g_list_free_full(invites, g_free);
3709     } else if (g_strcmp0(args[0], "decline") == 0) {
3710         if (!muc_invites_contain(args[1])) {
3711             cons_show("No such invite exists.");
3712         } else {
3713             muc_invites_remove(args[1]);
3714             cons_show("Declined invite to %s.", args[1]);
3715         }
3716     }
3717 
3718     return TRUE;
3719 }
3720 
3721 gboolean
cmd_form_field(ProfWin * window,char * tag,gchar ** args)3722 cmd_form_field(ProfWin* window, char* tag, gchar** args)
3723 {
3724     if (window->type != WIN_CONFIG) {
3725         return TRUE;
3726     }
3727 
3728     ProfConfWin* confwin = (ProfConfWin*)window;
3729     DataForm* form = confwin->form;
3730     if (form) {
3731         if (!form_tag_exists(form, tag)) {
3732             win_println(window, THEME_DEFAULT, "-", "Form does not contain a field with tag %s", tag);
3733             return TRUE;
3734         }
3735 
3736         form_field_type_t field_type = form_get_field_type(form, tag);
3737         char* cmd = NULL;
3738         char* value = NULL;
3739         gboolean valid = FALSE;
3740         gboolean added = FALSE;
3741         gboolean removed = FALSE;
3742 
3743         switch (field_type) {
3744         case FIELD_BOOLEAN:
3745             value = args[0];
3746             if (g_strcmp0(value, "on") == 0) {
3747                 form_set_value(form, tag, "1");
3748                 win_println(window, THEME_DEFAULT, "-", "Field updated...");
3749                 confwin_show_form_field(confwin, form, tag);
3750             } else if (g_strcmp0(value, "off") == 0) {
3751                 form_set_value(form, tag, "0");
3752                 win_println(window, THEME_DEFAULT, "-", "Field updated...");
3753                 confwin_show_form_field(confwin, form, tag);
3754             } else {
3755                 win_println(window, THEME_DEFAULT, "-", "Invalid command, usage:");
3756                 confwin_field_help(confwin, tag);
3757                 win_println(window, THEME_DEFAULT, "-", "");
3758             }
3759             break;
3760 
3761         case FIELD_TEXT_PRIVATE:
3762         case FIELD_TEXT_SINGLE:
3763         case FIELD_JID_SINGLE:
3764             value = args[0];
3765             if (value == NULL) {
3766                 win_println(window, THEME_DEFAULT, "-", "Invalid command, usage:");
3767                 confwin_field_help(confwin, tag);
3768                 win_println(window, THEME_DEFAULT, "-", "");
3769             } else {
3770                 form_set_value(form, tag, value);
3771                 win_println(window, THEME_DEFAULT, "-", "Field updated...");
3772                 confwin_show_form_field(confwin, form, tag);
3773             }
3774             break;
3775         case FIELD_LIST_SINGLE:
3776             value = args[0];
3777             if ((value == NULL) || !form_field_contains_option(form, tag, value)) {
3778                 win_println(window, THEME_DEFAULT, "-", "Invalid command, usage:");
3779                 confwin_field_help(confwin, tag);
3780                 win_println(window, THEME_DEFAULT, "-", "");
3781             } else {
3782                 form_set_value(form, tag, value);
3783                 win_println(window, THEME_DEFAULT, "-", "Field updated...");
3784                 confwin_show_form_field(confwin, form, tag);
3785             }
3786             break;
3787 
3788         case FIELD_TEXT_MULTI:
3789             cmd = args[0];
3790             if (cmd) {
3791                 value = args[1];
3792             }
3793             if ((g_strcmp0(cmd, "add") != 0) && (g_strcmp0(cmd, "remove"))) {
3794                 win_println(window, THEME_DEFAULT, "-", "Invalid command, usage:");
3795                 confwin_field_help(confwin, tag);
3796                 win_println(window, THEME_DEFAULT, "-", "");
3797                 break;
3798             }
3799             if (value == NULL) {
3800                 win_println(window, THEME_DEFAULT, "-", "Invalid command, usage:");
3801                 confwin_field_help(confwin, tag);
3802                 win_println(window, THEME_DEFAULT, "-", "");
3803                 break;
3804             }
3805             if (g_strcmp0(cmd, "add") == 0) {
3806                 form_add_value(form, tag, value);
3807                 win_println(window, THEME_DEFAULT, "-", "Field updated...");
3808                 confwin_show_form_field(confwin, form, tag);
3809                 break;
3810             }
3811             if (g_strcmp0(args[0], "remove") == 0) {
3812                 if (!g_str_has_prefix(value, "val")) {
3813                     win_println(window, THEME_DEFAULT, "-", "Invalid command, usage:");
3814                     confwin_field_help(confwin, tag);
3815                     win_println(window, THEME_DEFAULT, "-", "");
3816                     break;
3817                 }
3818                 if (strlen(value) < 4) {
3819                     win_println(window, THEME_DEFAULT, "-", "Invalid command, usage:");
3820                     confwin_field_help(confwin, tag);
3821                     win_println(window, THEME_DEFAULT, "-", "");
3822                     break;
3823                 }
3824 
3825                 int index = strtol(&value[3], NULL, 10);
3826                 if ((index < 1) || (index > form_get_value_count(form, tag))) {
3827                     win_println(window, THEME_DEFAULT, "-", "Invalid command, usage:");
3828                     confwin_field_help(confwin, tag);
3829                     win_println(window, THEME_DEFAULT, "-", "");
3830                     break;
3831                 }
3832 
3833                 removed = form_remove_text_multi_value(form, tag, index);
3834                 if (removed) {
3835                     win_println(window, THEME_DEFAULT, "-", "Field updated...");
3836                     confwin_show_form_field(confwin, form, tag);
3837                 } else {
3838                     win_println(window, THEME_DEFAULT, "-", "Could not remove %s from %s", value, tag);
3839                 }
3840             }
3841             break;
3842         case FIELD_LIST_MULTI:
3843             cmd = args[0];
3844             if (cmd) {
3845                 value = args[1];
3846             }
3847             if ((g_strcmp0(cmd, "add") != 0) && (g_strcmp0(cmd, "remove"))) {
3848                 win_println(window, THEME_DEFAULT, "-", "Invalid command, usage:");
3849                 confwin_field_help(confwin, tag);
3850                 win_println(window, THEME_DEFAULT, "-", "");
3851                 break;
3852             }
3853             if (value == NULL) {
3854                 win_println(window, THEME_DEFAULT, "-", "Invalid command, usage:");
3855                 confwin_field_help(confwin, tag);
3856                 win_println(window, THEME_DEFAULT, "-", "");
3857                 break;
3858             }
3859             if (g_strcmp0(args[0], "add") == 0) {
3860                 valid = form_field_contains_option(form, tag, value);
3861                 if (valid) {
3862                     added = form_add_unique_value(form, tag, value);
3863                     if (added) {
3864                         win_println(window, THEME_DEFAULT, "-", "Field updated...");
3865                         confwin_show_form_field(confwin, form, tag);
3866                     } else {
3867                         win_println(window, THEME_DEFAULT, "-", "Value %s already selected for %s", value, tag);
3868                     }
3869                 } else {
3870                     win_println(window, THEME_DEFAULT, "-", "Invalid command, usage:");
3871                     confwin_field_help(confwin, tag);
3872                     win_println(window, THEME_DEFAULT, "-", "");
3873                 }
3874                 break;
3875             }
3876             if (g_strcmp0(args[0], "remove") == 0) {
3877                 valid = form_field_contains_option(form, tag, value);
3878                 if (valid == TRUE) {
3879                     removed = form_remove_value(form, tag, value);
3880                     if (removed) {
3881                         win_println(window, THEME_DEFAULT, "-", "Field updated...");
3882                         confwin_show_form_field(confwin, form, tag);
3883                     } else {
3884                         win_println(window, THEME_DEFAULT, "-", "Value %s is not currently set for %s", value, tag);
3885                     }
3886                 } else {
3887                     win_println(window, THEME_DEFAULT, "-", "Invalid command, usage:");
3888                     confwin_field_help(confwin, tag);
3889                     win_println(window, THEME_DEFAULT, "-", "");
3890                 }
3891             }
3892             break;
3893         case FIELD_JID_MULTI:
3894             cmd = args[0];
3895             if (cmd) {
3896                 value = args[1];
3897             }
3898             if ((g_strcmp0(cmd, "add") != 0) && (g_strcmp0(cmd, "remove"))) {
3899                 win_println(window, THEME_DEFAULT, "-", "Invalid command, usage:");
3900                 confwin_field_help(confwin, tag);
3901                 win_println(window, THEME_DEFAULT, "-", "");
3902                 break;
3903             }
3904             if (value == NULL) {
3905                 win_println(window, THEME_DEFAULT, "-", "Invalid command, usage:");
3906                 confwin_field_help(confwin, tag);
3907                 win_println(window, THEME_DEFAULT, "-", "");
3908                 break;
3909             }
3910             if (g_strcmp0(args[0], "add") == 0) {
3911                 added = form_add_unique_value(form, tag, value);
3912                 if (added) {
3913                     win_println(window, THEME_DEFAULT, "-", "Field updated...");
3914                     confwin_show_form_field(confwin, form, tag);
3915                 } else {
3916                     win_println(window, THEME_DEFAULT, "-", "JID %s already exists in %s", value, tag);
3917                 }
3918                 break;
3919             }
3920             if (g_strcmp0(args[0], "remove") == 0) {
3921                 removed = form_remove_value(form, tag, value);
3922                 if (removed) {
3923                     win_println(window, THEME_DEFAULT, "-", "Field updated...");
3924                     confwin_show_form_field(confwin, form, tag);
3925                 } else {
3926                     win_println(window, THEME_DEFAULT, "-", "Field %s does not contain %s", tag, value);
3927                 }
3928             }
3929             break;
3930 
3931         default:
3932             break;
3933         }
3934     }
3935 
3936     return TRUE;
3937 }
3938 
3939 gboolean
cmd_form(ProfWin * window,const char * const command,gchar ** args)3940 cmd_form(ProfWin* window, const char* const command, gchar** args)
3941 {
3942     jabber_conn_status_t conn_status = connection_get_status();
3943 
3944     if (conn_status != JABBER_CONNECTED) {
3945         cons_show("You are not currently connected.");
3946         return TRUE;
3947     }
3948 
3949     if (window->type != WIN_CONFIG) {
3950         cons_show("Command '/form' does not apply to this window.");
3951         return TRUE;
3952     }
3953 
3954     if ((g_strcmp0(args[0], "submit") != 0) && (g_strcmp0(args[0], "cancel") != 0) && (g_strcmp0(args[0], "show") != 0) && (g_strcmp0(args[0], "help") != 0)) {
3955         cons_bad_cmd_usage(command);
3956         return TRUE;
3957     }
3958 
3959     ProfConfWin* confwin = (ProfConfWin*)window;
3960     assert(confwin->memcheck == PROFCONFWIN_MEMCHECK);
3961 
3962     if (g_strcmp0(args[0], "show") == 0) {
3963         confwin_show_form(confwin);
3964         return TRUE;
3965     }
3966 
3967     if (g_strcmp0(args[0], "help") == 0) {
3968         char* tag = args[1];
3969         if (tag) {
3970             confwin_field_help(confwin, tag);
3971         } else {
3972             confwin_form_help(confwin);
3973 
3974             gchar** help_text = NULL;
3975             Command* command = cmd_get("/form");
3976 
3977             if (command) {
3978                 help_text = command->help.synopsis;
3979             }
3980 
3981             ui_show_lines((ProfWin*)confwin, help_text);
3982         }
3983         win_println(window, THEME_DEFAULT, "-", "");
3984         return TRUE;
3985     }
3986 
3987     if (g_strcmp0(args[0], "submit") == 0 && confwin->submit != NULL) {
3988         confwin->submit(confwin);
3989     }
3990 
3991     if (g_strcmp0(args[0], "cancel") == 0 && confwin->cancel != NULL) {
3992         confwin->cancel(confwin);
3993     }
3994 
3995     if ((g_strcmp0(args[0], "submit") == 0) || (g_strcmp0(args[0], "cancel") == 0)) {
3996         if (confwin->form) {
3997             cmd_ac_remove_form_fields(confwin->form);
3998         }
3999 
4000         int num = wins_get_num(window);
4001 
4002         ProfWin* new_current = (ProfWin*)wins_get_muc(confwin->roomjid);
4003         if (!new_current) {
4004             new_current = wins_get_console();
4005         }
4006         ui_focus_win(new_current);
4007         wins_close_by_num(num);
4008         wins_tidy();
4009     }
4010 
4011     return TRUE;
4012 }
4013 
4014 gboolean
cmd_kick(ProfWin * window,const char * const command,gchar ** args)4015 cmd_kick(ProfWin* window, const char* const command, gchar** args)
4016 {
4017     jabber_conn_status_t conn_status = connection_get_status();
4018 
4019     if (conn_status != JABBER_CONNECTED) {
4020         cons_show("You are not currently connected.");
4021         return TRUE;
4022     }
4023 
4024     if (window->type != WIN_MUC) {
4025         cons_show("Command '/kick' only applies in chat rooms.");
4026         return TRUE;
4027     }
4028 
4029     ProfMucWin* mucwin = (ProfMucWin*)window;
4030     assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
4031 
4032     char* nick = args[0];
4033     if (nick) {
4034         if (muc_roster_contains_nick(mucwin->roomjid, nick)) {
4035             char* reason = args[1];
4036             iq_room_kick_occupant(mucwin->roomjid, nick, reason);
4037         } else {
4038             win_println(window, THEME_DEFAULT, "!", "Occupant does not exist: %s", nick);
4039         }
4040     } else {
4041         cons_bad_cmd_usage(command);
4042     }
4043 
4044     return TRUE;
4045 }
4046 
4047 gboolean
cmd_ban(ProfWin * window,const char * const command,gchar ** args)4048 cmd_ban(ProfWin* window, const char* const command, gchar** args)
4049 {
4050     jabber_conn_status_t conn_status = connection_get_status();
4051 
4052     if (conn_status != JABBER_CONNECTED) {
4053         cons_show("You are not currently connected.");
4054         return TRUE;
4055     }
4056 
4057     if (window->type != WIN_MUC) {
4058         cons_show("Command '/ban' only applies in chat rooms.");
4059         return TRUE;
4060     }
4061 
4062     ProfMucWin* mucwin = (ProfMucWin*)window;
4063     assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
4064 
4065     char* jid = args[0];
4066     if (jid) {
4067         char* reason = args[1];
4068         iq_room_affiliation_set(mucwin->roomjid, jid, "outcast", reason);
4069     } else {
4070         cons_bad_cmd_usage(command);
4071     }
4072     return TRUE;
4073 }
4074 
4075 gboolean
cmd_subject(ProfWin * window,const char * const command,gchar ** args)4076 cmd_subject(ProfWin* window, const char* const command, gchar** args)
4077 {
4078     jabber_conn_status_t conn_status = connection_get_status();
4079 
4080     if (conn_status != JABBER_CONNECTED) {
4081         cons_show("You are not currently connected.");
4082         return TRUE;
4083     }
4084 
4085     if (window->type != WIN_MUC) {
4086         cons_show("Command '/room' does not apply to this window.");
4087         return TRUE;
4088     }
4089 
4090     ProfMucWin* mucwin = (ProfMucWin*)window;
4091     assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
4092 
4093     if (args[0] == NULL) {
4094         char* subject = muc_subject(mucwin->roomjid);
4095         if (subject) {
4096             win_print(window, THEME_ROOMINFO, "!", "Room subject: ");
4097             win_appendln(window, THEME_DEFAULT, "%s", subject);
4098         } else {
4099             win_println(window, THEME_ROOMINFO, "!", "Room has no subject");
4100         }
4101         return TRUE;
4102     }
4103 
4104     if (g_strcmp0(args[0], "set") == 0) {
4105         if (args[1]) {
4106             message_send_groupchat_subject(mucwin->roomjid, args[1]);
4107         } else {
4108             cons_bad_cmd_usage(command);
4109         }
4110         return TRUE;
4111     }
4112 
4113     if (g_strcmp0(args[0], "edit") == 0) {
4114         if (args[1]) {
4115             message_send_groupchat_subject(mucwin->roomjid, args[1]);
4116         } else {
4117             cons_bad_cmd_usage(command);
4118         }
4119         return TRUE;
4120     }
4121 
4122     if (g_strcmp0(args[0], "prepend") == 0) {
4123         if (args[1]) {
4124             char* old_subject = muc_subject(mucwin->roomjid);
4125             if (old_subject) {
4126                 GString* new_subject = g_string_new(args[1]);
4127                 g_string_append(new_subject, old_subject);
4128                 message_send_groupchat_subject(mucwin->roomjid, new_subject->str);
4129                 g_string_free(new_subject, TRUE);
4130             } else {
4131                 win_print(window, THEME_ROOMINFO, "!", "Room does not have a subject, use /subject set <subject>");
4132             }
4133         } else {
4134             cons_bad_cmd_usage(command);
4135         }
4136         return TRUE;
4137     }
4138 
4139     if (g_strcmp0(args[0], "append") == 0) {
4140         if (args[1]) {
4141             char* old_subject = muc_subject(mucwin->roomjid);
4142             if (old_subject) {
4143                 GString* new_subject = g_string_new(old_subject);
4144                 g_string_append(new_subject, args[1]);
4145                 message_send_groupchat_subject(mucwin->roomjid, new_subject->str);
4146                 g_string_free(new_subject, TRUE);
4147             } else {
4148                 win_print(window, THEME_ROOMINFO, "!", "Room does not have a subject, use /subject set <subject>");
4149             }
4150         } else {
4151             cons_bad_cmd_usage(command);
4152         }
4153         return TRUE;
4154     }
4155 
4156     if (g_strcmp0(args[0], "clear") == 0) {
4157         message_send_groupchat_subject(mucwin->roomjid, NULL);
4158         return TRUE;
4159     }
4160 
4161     cons_bad_cmd_usage(command);
4162     return TRUE;
4163 }
4164 
4165 gboolean
cmd_affiliation(ProfWin * window,const char * const command,gchar ** args)4166 cmd_affiliation(ProfWin* window, const char* const command, gchar** args)
4167 {
4168     jabber_conn_status_t conn_status = connection_get_status();
4169 
4170     if (conn_status != JABBER_CONNECTED) {
4171         cons_show("You are not currently connected.");
4172         return TRUE;
4173     }
4174 
4175     if (window->type != WIN_MUC) {
4176         cons_show("Command '/affiliation' does not apply to this window.");
4177         return TRUE;
4178     }
4179 
4180     char* cmd = args[0];
4181     if (cmd == NULL) {
4182         cons_bad_cmd_usage(command);
4183         return TRUE;
4184     }
4185 
4186     char* affiliation = args[1];
4187     if (affiliation && (g_strcmp0(affiliation, "owner") != 0) && (g_strcmp0(affiliation, "admin") != 0) && (g_strcmp0(affiliation, "member") != 0) && (g_strcmp0(affiliation, "none") != 0) && (g_strcmp0(affiliation, "outcast") != 0)) {
4188         cons_bad_cmd_usage(command);
4189         return TRUE;
4190     }
4191 
4192     ProfMucWin* mucwin = (ProfMucWin*)window;
4193     assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
4194 
4195     if (g_strcmp0(cmd, "list") == 0) {
4196         if (!affiliation) {
4197             iq_room_affiliation_list(mucwin->roomjid, "owner", true);
4198             iq_room_affiliation_list(mucwin->roomjid, "admin", true);
4199             iq_room_affiliation_list(mucwin->roomjid, "member", true);
4200             iq_room_affiliation_list(mucwin->roomjid, "outcast", true);
4201         } else if (g_strcmp0(affiliation, "none") == 0) {
4202             win_println(window, THEME_DEFAULT, "!", "Cannot list users with no affiliation.");
4203         } else {
4204             iq_room_affiliation_list(mucwin->roomjid, affiliation, true);
4205         }
4206         return TRUE;
4207     }
4208 
4209     if (g_strcmp0(cmd, "set") == 0) {
4210         if (!affiliation) {
4211             cons_bad_cmd_usage(command);
4212             return TRUE;
4213         }
4214 
4215         char* jid = args[2];
4216         if (jid == NULL) {
4217             cons_bad_cmd_usage(command);
4218             return TRUE;
4219         } else {
4220             char* reason = args[3];
4221             iq_room_affiliation_set(mucwin->roomjid, jid, affiliation, reason);
4222             return TRUE;
4223         }
4224     }
4225 
4226     if (g_strcmp0(cmd, "request") == 0) {
4227         message_request_voice(mucwin->roomjid);
4228         return TRUE;
4229     }
4230 
4231     if (g_strcmp0(cmd, "register") == 0) {
4232         iq_muc_register_nick(mucwin->roomjid);
4233         return TRUE;
4234     }
4235 
4236     cons_bad_cmd_usage(command);
4237     return TRUE;
4238 }
4239 
4240 gboolean
cmd_role(ProfWin * window,const char * const command,gchar ** args)4241 cmd_role(ProfWin* window, const char* const command, gchar** args)
4242 {
4243     jabber_conn_status_t conn_status = connection_get_status();
4244 
4245     if (conn_status != JABBER_CONNECTED) {
4246         cons_show("You are not currently connected.");
4247         return TRUE;
4248     }
4249 
4250     if (window->type != WIN_MUC) {
4251         cons_show("Command '/role' does not apply to this window.");
4252         return TRUE;
4253     }
4254 
4255     char* cmd = args[0];
4256     if (cmd == NULL) {
4257         cons_bad_cmd_usage(command);
4258         return TRUE;
4259     }
4260 
4261     char* role = args[1];
4262     if (role && (g_strcmp0(role, "visitor") != 0) && (g_strcmp0(role, "participant") != 0) && (g_strcmp0(role, "moderator") != 0) && (g_strcmp0(role, "none") != 0)) {
4263         cons_bad_cmd_usage(command);
4264         return TRUE;
4265     }
4266 
4267     ProfMucWin* mucwin = (ProfMucWin*)window;
4268     assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
4269 
4270     if (g_strcmp0(cmd, "list") == 0) {
4271         if (!role) {
4272             iq_room_role_list(mucwin->roomjid, "moderator");
4273             iq_room_role_list(mucwin->roomjid, "participant");
4274             iq_room_role_list(mucwin->roomjid, "visitor");
4275         } else if (g_strcmp0(role, "none") == 0) {
4276             win_println(window, THEME_DEFAULT, "!", "Cannot list users with no role.");
4277         } else {
4278             iq_room_role_list(mucwin->roomjid, role);
4279         }
4280         return TRUE;
4281     }
4282 
4283     if (g_strcmp0(cmd, "set") == 0) {
4284         if (!role) {
4285             cons_bad_cmd_usage(command);
4286             return TRUE;
4287         }
4288 
4289         char* nick = args[2];
4290         if (nick == NULL) {
4291             cons_bad_cmd_usage(command);
4292             return TRUE;
4293         } else {
4294             char* reason = args[3];
4295             iq_room_role_set(mucwin->roomjid, nick, role, reason);
4296             return TRUE;
4297         }
4298     }
4299 
4300     cons_bad_cmd_usage(command);
4301     return TRUE;
4302 }
4303 
4304 gboolean
cmd_room(ProfWin * window,const char * const command,gchar ** args)4305 cmd_room(ProfWin* window, const char* const command, gchar** args)
4306 {
4307     jabber_conn_status_t conn_status = connection_get_status();
4308 
4309     if (conn_status != JABBER_CONNECTED) {
4310         cons_show("You are not currently connected.");
4311         return TRUE;
4312     }
4313 
4314     if (window->type != WIN_MUC) {
4315         cons_show("Command '/room' does not apply to this window.");
4316         return TRUE;
4317     }
4318 
4319     ProfMucWin* mucwin = (ProfMucWin*)window;
4320     assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
4321 
4322     if (g_strcmp0(args[0], "accept") == 0) {
4323         gboolean requires_config = muc_requires_config(mucwin->roomjid);
4324         if (!requires_config) {
4325             win_println(window, THEME_ROOMINFO, "!", "Current room does not require configuration.");
4326             return TRUE;
4327         } else {
4328             iq_confirm_instant_room(mucwin->roomjid);
4329             muc_set_requires_config(mucwin->roomjid, FALSE);
4330             win_println(window, THEME_ROOMINFO, "!", "Room unlocked.");
4331             return TRUE;
4332         }
4333     } else if (g_strcmp0(args[0], "destroy") == 0) {
4334         iq_destroy_room(mucwin->roomjid);
4335         return TRUE;
4336     } else if (g_strcmp0(args[0], "config") == 0) {
4337         ProfConfWin* confwin = wins_get_conf(mucwin->roomjid);
4338 
4339         if (confwin) {
4340             ui_focus_win((ProfWin*)confwin);
4341         } else {
4342             iq_request_room_config_form(mucwin->roomjid);
4343         }
4344         return TRUE;
4345     } else {
4346         cons_bad_cmd_usage(command);
4347     }
4348 
4349     return TRUE;
4350 }
4351 
4352 gboolean
cmd_occupants(ProfWin * window,const char * const command,gchar ** args)4353 cmd_occupants(ProfWin* window, const char* const command, gchar** args)
4354 {
4355     if (g_strcmp0(args[0], "size") == 0) {
4356         if (!args[1]) {
4357             cons_bad_cmd_usage(command);
4358             return TRUE;
4359         } else {
4360             int intval = 0;
4361             char* err_msg = NULL;
4362             gboolean res = strtoi_range(args[1], &intval, 1, 99, &err_msg);
4363             if (res) {
4364                 prefs_set_occupants_size(intval);
4365                 cons_show("Occupants screen size set to: %d%%", intval);
4366                 wins_resize_all();
4367                 return TRUE;
4368             } else {
4369                 cons_show(err_msg);
4370                 free(err_msg);
4371                 return TRUE;
4372             }
4373         }
4374     }
4375 
4376     if (g_strcmp0(args[0], "indent") == 0) {
4377         if (!args[1]) {
4378             cons_bad_cmd_usage(command);
4379             return TRUE;
4380         } else {
4381             int intval = 0;
4382             char* err_msg = NULL;
4383             gboolean res = strtoi_range(args[1], &intval, 0, 10, &err_msg);
4384             if (res) {
4385                 prefs_set_occupants_indent(intval);
4386                 cons_show("Occupants indent set to: %d", intval);
4387 
4388                 occupantswin_occupants_all();
4389             } else {
4390                 cons_show(err_msg);
4391                 free(err_msg);
4392             }
4393             return TRUE;
4394         }
4395     }
4396 
4397     if (g_strcmp0(args[0], "wrap") == 0) {
4398         if (!args[1]) {
4399             cons_bad_cmd_usage(command);
4400             return TRUE;
4401         } else {
4402             _cmd_set_boolean_preference(args[1], command, "Occupants panel line wrap", PREF_OCCUPANTS_WRAP);
4403             occupantswin_occupants_all();
4404             return TRUE;
4405         }
4406     }
4407 
4408     if (g_strcmp0(args[0], "char") == 0) {
4409         if (!args[1]) {
4410             cons_bad_cmd_usage(command);
4411         } else if (g_strcmp0(args[1], "none") == 0) {
4412             prefs_clear_occupants_char();
4413             cons_show("Occupants char removed.");
4414 
4415             occupantswin_occupants_all();
4416         } else {
4417             prefs_set_occupants_char(args[1][0]);
4418             cons_show("Occupants char set to %c.", args[1][0]);
4419 
4420             occupantswin_occupants_all();
4421         }
4422         return TRUE;
4423     }
4424 
4425     if (g_strcmp0(args[0], "color") == 0) {
4426         _cmd_set_boolean_preference(args[1], command, "Occupants consistent colors", PREF_OCCUPANTS_COLOR_NICK);
4427         occupantswin_occupants_all();
4428         return TRUE;
4429     }
4430 
4431     if (g_strcmp0(args[0], "default") == 0) {
4432         if (g_strcmp0(args[1], "show") == 0) {
4433             if (g_strcmp0(args[2], "jid") == 0) {
4434                 cons_show("Occupant jids enabled.");
4435                 prefs_set_boolean(PREF_OCCUPANTS_JID, TRUE);
4436             } else if (g_strcmp0(args[2], "offline") == 0) {
4437                 cons_show("Occupants offline enabled.");
4438                 prefs_set_boolean(PREF_OCCUPANTS_OFFLINE, TRUE);
4439             } else {
4440                 cons_show("Occupant list enabled.");
4441                 prefs_set_boolean(PREF_OCCUPANTS, TRUE);
4442             }
4443             return TRUE;
4444         } else if (g_strcmp0(args[1], "hide") == 0) {
4445             if (g_strcmp0(args[2], "jid") == 0) {
4446                 cons_show("Occupant jids disabled.");
4447                 prefs_set_boolean(PREF_OCCUPANTS_JID, FALSE);
4448             } else if (g_strcmp0(args[2], "offline") == 0) {
4449                 cons_show("Occupants offline disabled.");
4450                 prefs_set_boolean(PREF_OCCUPANTS_OFFLINE, FALSE);
4451             } else {
4452                 cons_show("Occupant list disabled.");
4453                 prefs_set_boolean(PREF_OCCUPANTS, FALSE);
4454             }
4455             return TRUE;
4456         } else {
4457             cons_bad_cmd_usage(command);
4458             return TRUE;
4459         }
4460     }
4461 
4462     // header settings
4463     if (g_strcmp0(args[0], "header") == 0) {
4464         if (g_strcmp0(args[1], "char") == 0) {
4465             if (!args[2]) {
4466                 cons_bad_cmd_usage(command);
4467             } else if (g_strcmp0(args[2], "none") == 0) {
4468                 prefs_clear_occupants_header_char();
4469                 cons_show("Occupants header char removed.");
4470 
4471                 occupantswin_occupants_all();
4472             } else {
4473                 prefs_set_occupants_header_char(args[2][0]);
4474                 cons_show("Occupants header char set to %c.", args[2][0]);
4475 
4476                 occupantswin_occupants_all();
4477             }
4478         } else {
4479             cons_bad_cmd_usage(command);
4480         }
4481         return TRUE;
4482     }
4483 
4484     jabber_conn_status_t conn_status = connection_get_status();
4485     if (conn_status != JABBER_CONNECTED) {
4486         cons_show("You are not currently connected.");
4487         return TRUE;
4488     }
4489 
4490     if (window->type != WIN_MUC) {
4491         cons_show("Cannot apply setting when not in chat room.");
4492         return TRUE;
4493     }
4494 
4495     ProfMucWin* mucwin = (ProfMucWin*)window;
4496     assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
4497 
4498     if (g_strcmp0(args[0], "show") == 0) {
4499         if (g_strcmp0(args[1], "jid") == 0) {
4500             mucwin->showjid = TRUE;
4501             mucwin_update_occupants(mucwin);
4502         } else if (g_strcmp0(args[1], "offline") == 0) {
4503             mucwin->showoffline = TRUE;
4504             mucwin_update_occupants(mucwin);
4505         } else {
4506             mucwin_show_occupants(mucwin);
4507         }
4508     } else if (g_strcmp0(args[0], "hide") == 0) {
4509         if (g_strcmp0(args[1], "jid") == 0) {
4510             mucwin->showjid = FALSE;
4511             mucwin_update_occupants(mucwin);
4512         } else if (g_strcmp0(args[1], "offline") == 0) {
4513             mucwin->showoffline = FALSE;
4514             mucwin_update_occupants(mucwin);
4515         } else {
4516             mucwin_hide_occupants(mucwin);
4517         }
4518     } else {
4519         cons_bad_cmd_usage(command);
4520     }
4521 
4522     return TRUE;
4523 }
4524 
4525 gboolean
cmd_rooms(ProfWin * window,const char * const command,gchar ** args)4526 cmd_rooms(ProfWin* window, const char* const command, gchar** args)
4527 {
4528     jabber_conn_status_t conn_status = connection_get_status();
4529 
4530     if (conn_status != JABBER_CONNECTED) {
4531         cons_show("You are not currently connected.");
4532         return TRUE;
4533     }
4534 
4535     gchar* service = NULL;
4536     gchar* filter = NULL;
4537     if (args[0] != NULL) {
4538         if (g_strcmp0(args[0], "service") == 0) {
4539             if (args[1] == NULL) {
4540                 cons_bad_cmd_usage(command);
4541                 cons_show("");
4542                 return TRUE;
4543             }
4544             service = g_strdup(args[1]);
4545         } else if (g_strcmp0(args[0], "filter") == 0) {
4546             if (args[1] == NULL) {
4547                 cons_bad_cmd_usage(command);
4548                 cons_show("");
4549                 return TRUE;
4550             }
4551             filter = g_strdup(args[1]);
4552         } else if (g_strcmp0(args[0], "cache") == 0) {
4553             if (g_strv_length(args) != 2) {
4554                 cons_bad_cmd_usage(command);
4555                 cons_show("");
4556                 return TRUE;
4557             } else if (g_strcmp0(args[1], "on") == 0) {
4558                 prefs_set_boolean(PREF_ROOM_LIST_CACHE, TRUE);
4559                 cons_show("Rooms list cache enabled.");
4560                 return TRUE;
4561             } else if (g_strcmp0(args[1], "off") == 0) {
4562                 prefs_set_boolean(PREF_ROOM_LIST_CACHE, FALSE);
4563                 cons_show("Rooms list cache disabled.");
4564                 return TRUE;
4565             } else if (g_strcmp0(args[1], "clear") == 0) {
4566                 iq_rooms_cache_clear();
4567                 cons_show("Rooms list cache cleared.");
4568                 return TRUE;
4569             } else {
4570                 cons_bad_cmd_usage(command);
4571                 cons_show("");
4572                 return TRUE;
4573             }
4574         } else {
4575             cons_bad_cmd_usage(command);
4576             cons_show("");
4577             return TRUE;
4578         }
4579     }
4580     if (g_strv_length(args) >= 3) {
4581         if (g_strcmp0(args[2], "service") == 0) {
4582             if (args[3] == NULL) {
4583                 cons_bad_cmd_usage(command);
4584                 cons_show("");
4585                 g_free(service);
4586                 g_free(filter);
4587                 return TRUE;
4588             }
4589             g_free(service);
4590             service = g_strdup(args[3]);
4591         } else if (g_strcmp0(args[2], "filter") == 0) {
4592             if (args[3] == NULL) {
4593                 cons_bad_cmd_usage(command);
4594                 cons_show("");
4595                 g_free(service);
4596                 g_free(filter);
4597                 return TRUE;
4598             }
4599             g_free(filter);
4600             filter = g_strdup(args[3]);
4601         } else {
4602             cons_bad_cmd_usage(command);
4603             cons_show("");
4604             g_free(service);
4605             g_free(filter);
4606             return TRUE;
4607         }
4608     }
4609 
4610     if (service == NULL) {
4611         ProfAccount* account = accounts_get_account(session_get_account_name());
4612         if (account->muc_service) {
4613             service = g_strdup(account->muc_service);
4614             account_free(account);
4615         } else {
4616             cons_show("Account MUC service property not found.");
4617             account_free(account);
4618             g_free(service);
4619             g_free(filter);
4620             return TRUE;
4621         }
4622     }
4623 
4624     cons_show("");
4625     if (filter) {
4626         cons_show("Room list request sent: %s, filter: '%s'", service, filter);
4627     } else {
4628         cons_show("Room list request sent: %s", service);
4629     }
4630     iq_room_list_request(service, filter);
4631 
4632     g_free(service);
4633     g_free(filter);
4634 
4635     return TRUE;
4636 }
4637 
4638 gboolean
cmd_bookmark(ProfWin * window,const char * const command,gchar ** args)4639 cmd_bookmark(ProfWin* window, const char* const command, gchar** args)
4640 {
4641     jabber_conn_status_t conn_status = connection_get_status();
4642 
4643     if (conn_status != JABBER_CONNECTED) {
4644         cons_show("You are not currently connected.");
4645         cons_alert(NULL);
4646         return TRUE;
4647     }
4648 
4649     int num_args = g_strv_length(args);
4650     gchar* cmd = args[0];
4651     if (window->type == WIN_MUC
4652         && num_args < 2
4653         && (cmd == NULL || g_strcmp0(cmd, "add") == 0)) {
4654         // default to current nickname, password, and autojoin "on"
4655         ProfMucWin* mucwin = (ProfMucWin*)window;
4656         assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
4657         char* nick = muc_nick(mucwin->roomjid);
4658         char* password = muc_password(mucwin->roomjid);
4659         gboolean added = bookmark_add(mucwin->roomjid, nick, password, "on", NULL);
4660         if (added) {
4661             win_println(window, THEME_DEFAULT, "!", "Bookmark added for %s.", mucwin->roomjid);
4662         } else {
4663             win_println(window, THEME_DEFAULT, "!", "Bookmark already exists for %s.", mucwin->roomjid);
4664         }
4665         return TRUE;
4666     }
4667 
4668     if (window->type == WIN_MUC
4669         && num_args < 2
4670         && g_strcmp0(cmd, "remove") == 0) {
4671         ProfMucWin* mucwin = (ProfMucWin*)window;
4672         assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
4673         gboolean removed = bookmark_remove(mucwin->roomjid);
4674         if (removed) {
4675             win_println(window, THEME_DEFAULT, "!", "Bookmark removed for %s.", mucwin->roomjid);
4676         } else {
4677             win_println(window, THEME_DEFAULT, "!", "Bookmark does not exist for %s.", mucwin->roomjid);
4678         }
4679         return TRUE;
4680     }
4681 
4682     if (cmd == NULL) {
4683         cons_bad_cmd_usage(command);
4684         cons_alert(NULL);
4685         return TRUE;
4686     }
4687 
4688     if (strcmp(cmd, "invites") == 0) {
4689         if (g_strcmp0(args[1], "on") == 0) {
4690             prefs_set_boolean(PREF_BOOKMARK_INVITE, TRUE);
4691             cons_show("Auto bookmarking accepted invites enabled.");
4692         } else if (g_strcmp0(args[1], "off") == 0) {
4693             prefs_set_boolean(PREF_BOOKMARK_INVITE, FALSE);
4694             cons_show("Auto bookmarking accepted invites disabled.");
4695         } else {
4696             cons_bad_cmd_usage(command);
4697             cons_show("");
4698         }
4699         cons_alert(NULL);
4700         return TRUE;
4701     }
4702 
4703     if (strcmp(cmd, "list") == 0) {
4704         char* bookmark_jid = args[1];
4705         if (bookmark_jid == NULL) {
4706             // list all bookmarks
4707             GList* bookmarks = bookmark_get_list();
4708             cons_show_bookmarks(bookmarks);
4709             g_list_free(bookmarks);
4710         } else {
4711              // list one bookmark
4712             Bookmark *bookmark = bookmark_get_by_jid(bookmark_jid);
4713             cons_show_bookmark(bookmark);
4714         }
4715 
4716         return TRUE;
4717     }
4718 
4719     char* jid = args[1];
4720     if (jid == NULL) {
4721         cons_bad_cmd_usage(command);
4722         cons_show("");
4723         cons_alert(NULL);
4724         return TRUE;
4725     }
4726     if (strchr(jid, '@') == NULL) {
4727         cons_show("Invalid room, must be of the form room@domain.tld");
4728         cons_show("");
4729         cons_alert(NULL);
4730         return TRUE;
4731     }
4732 
4733     if (strcmp(cmd, "remove") == 0) {
4734         gboolean removed = bookmark_remove(jid);
4735         if (removed) {
4736             cons_show("Bookmark removed for %s.", jid);
4737         } else {
4738             cons_show("No bookmark exists for %s.", jid);
4739         }
4740         cons_alert(NULL);
4741         return TRUE;
4742     }
4743 
4744     if (strcmp(cmd, "join") == 0) {
4745         gboolean joined = bookmark_join(jid);
4746         if (!joined) {
4747             cons_show("No bookmark exists for %s.", jid);
4748         }
4749         cons_alert(NULL);
4750         return TRUE;
4751     }
4752 
4753     gchar* opt_keys[] = { "autojoin", "nick", "password", "name", NULL };
4754     gboolean parsed;
4755 
4756     GHashTable* options = parse_options(&args[2], opt_keys, &parsed);
4757     if (!parsed) {
4758         cons_bad_cmd_usage(command);
4759         cons_show("");
4760         cons_alert(NULL);
4761         return TRUE;
4762     }
4763 
4764     char* autojoin = g_hash_table_lookup(options, "autojoin");
4765 
4766     if (autojoin && ((strcmp(autojoin, "on") != 0) && (strcmp(autojoin, "off") != 0))) {
4767         cons_bad_cmd_usage(command);
4768         cons_show("");
4769         options_destroy(options);
4770         cons_alert(NULL);
4771         return TRUE;
4772     }
4773 
4774     char* nick = g_hash_table_lookup(options, "nick");
4775     char* password = g_hash_table_lookup(options, "password");
4776     char* name = g_hash_table_lookup(options, "name");
4777 
4778     if (strcmp(cmd, "add") == 0) {
4779         gboolean added = bookmark_add(jid, nick, password, autojoin, name);
4780         if (added) {
4781             cons_show("Bookmark added for %s.", jid);
4782         } else {
4783             cons_show("Bookmark already exists, use /bookmark update to edit.");
4784         }
4785         options_destroy(options);
4786         cons_alert(NULL);
4787         return TRUE;
4788     }
4789 
4790     if (strcmp(cmd, "update") == 0) {
4791         gboolean updated = bookmark_update(jid, nick, password, autojoin, name);
4792         if (updated) {
4793             cons_show("Bookmark updated.");
4794         } else {
4795             cons_show("No bookmark exists for %s.", jid);
4796         }
4797         options_destroy(options);
4798         cons_alert(NULL);
4799         return TRUE;
4800     }
4801 
4802     cons_bad_cmd_usage(command);
4803     options_destroy(options);
4804     cons_alert(NULL);
4805 
4806     return TRUE;
4807 }
4808 
4809 gboolean
cmd_bookmark_ignore(ProfWin * window,const char * const command,gchar ** args)4810 cmd_bookmark_ignore(ProfWin* window, const char* const command, gchar** args)
4811 {
4812     jabber_conn_status_t conn_status = connection_get_status();
4813 
4814     if (conn_status != JABBER_CONNECTED) {
4815         cons_show("You are not currently connected.");
4816         cons_alert(NULL);
4817         return TRUE;
4818     }
4819 
4820     // `/bookmark ignore` lists them
4821     if (args[1] == NULL) {
4822         gsize len = 0;
4823         gchar** list = bookmark_ignore_list(&len);
4824         cons_show_bookmarks_ignore(list, len);
4825         g_strfreev(list);
4826         return TRUE;
4827     }
4828 
4829     if (strcmp(args[1], "add") == 0 && args[2] != NULL) {
4830         bookmark_ignore_add(args[2]);
4831         cons_show("Autojoin for bookmark %s added to ignore list.", args[2]);
4832         return TRUE;
4833     }
4834 
4835     if (strcmp(args[1], "remove") == 0 && args[2] != NULL) {
4836         bookmark_ignore_remove(args[2]);
4837         cons_show("Autojoin for bookmark %s removed from ignore list.", args[2]);
4838         return TRUE;
4839     }
4840 
4841     cons_bad_cmd_usage(command);
4842     return TRUE;
4843 }
4844 
4845 gboolean
cmd_disco(ProfWin * window,const char * const command,gchar ** args)4846 cmd_disco(ProfWin* window, const char* const command, gchar** args)
4847 {
4848     jabber_conn_status_t conn_status = connection_get_status();
4849 
4850     if (conn_status != JABBER_CONNECTED) {
4851         cons_show("You are not currently connected.");
4852         return TRUE;
4853     }
4854 
4855     GString* jid = g_string_new("");
4856     if (args[1]) {
4857         jid = g_string_append(jid, args[1]);
4858     } else {
4859         Jid* jidp = jid_create(connection_get_fulljid());
4860         jid = g_string_append(jid, jidp->domainpart);
4861         jid_destroy(jidp);
4862     }
4863 
4864     if (g_strcmp0(args[0], "info") == 0) {
4865         iq_disco_info_request(jid->str);
4866     } else {
4867         iq_disco_items_request(jid->str);
4868     }
4869 
4870     g_string_free(jid, TRUE);
4871 
4872     return TRUE;
4873 }
4874 
4875 // TODO: Move this into its own tools such as HTTPUpload or AESGCMDownload.
4876 #ifdef HAVE_OMEMO
4877 char*
_add_omemo_stream(int * fd,FILE ** fh,char ** err)4878 _add_omemo_stream(int* fd, FILE** fh, char** err)
4879 {
4880     // Create temporary file for writing ciphertext.
4881     int tmpfd;
4882     char* tmpname = NULL;
4883     if ((tmpfd = g_file_open_tmp("profanity.XXXXXX", &tmpname, NULL)) == -1) {
4884         *err = "Unable to create temporary file for encrypted transfer.";
4885         return NULL;
4886     }
4887     FILE* tmpfh = fdopen(tmpfd, "wb");
4888 
4889     // The temporary ciphertext file should be removed after it has
4890     // been closed.
4891     remove(tmpname);
4892     free(tmpname);
4893 
4894     int crypt_res;
4895     char* fragment;
4896     fragment = omemo_encrypt_file(*fh, tmpfh, file_size(*fd), &crypt_res);
4897     if (crypt_res != 0) {
4898         fclose(tmpfh);
4899         return NULL;
4900     }
4901 
4902     // Force flush as the upload will read from the same stream.
4903     fflush(tmpfh);
4904     rewind(tmpfh);
4905 
4906     fclose(*fh); // Also closes descriptor.
4907 
4908     // Switch original stream with temporary ciphertext stream.
4909     *fd = tmpfd;
4910     *fh = tmpfh;
4911 
4912     return fragment;
4913 }
4914 #endif
4915 
4916 gboolean
cmd_sendfile(ProfWin * window,const char * const command,gchar ** args)4917 cmd_sendfile(ProfWin* window, const char* const command, gchar** args)
4918 {
4919     jabber_conn_status_t conn_status = connection_get_status();
4920     gchar* filename;
4921     char* alt_scheme = NULL;
4922     char* alt_fragment = NULL;
4923 
4924     // expand ~ to $HOME
4925     filename = get_expanded_path(args[0]);
4926 
4927     if (access(filename, R_OK) != 0) {
4928         cons_show_error("Uploading '%s' failed: File not found!", filename);
4929         goto out;
4930     }
4931 
4932     if (!is_regular_file(filename)) {
4933         cons_show_error("Uploading '%s' failed: Not a file!", filename);
4934         goto out;
4935     }
4936 
4937     if (conn_status != JABBER_CONNECTED) {
4938         cons_show("You are not currently connected.");
4939         goto out;
4940     }
4941 
4942     if (window->type != WIN_CHAT && window->type != WIN_PRIVATE && window->type != WIN_MUC) {
4943         cons_show_error("Unsupported window for file transmission.");
4944         goto out;
4945     }
4946 
4947     int fd;
4948     if ((fd = open(filename, O_RDONLY)) == -1) {
4949         cons_show_error("Unable to open file descriptor for '%s'.", filename);
4950         goto out;
4951     }
4952 
4953     FILE* fh = fdopen(fd, "rb");
4954 
4955     gboolean omemo_enabled = FALSE;
4956     gboolean sendfile_enabled = TRUE;
4957 
4958     switch (window->type) {
4959     case WIN_MUC:
4960     {
4961         ProfMucWin* mucwin = (ProfMucWin*)window;
4962         assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
4963         omemo_enabled = mucwin->is_omemo == TRUE;
4964         break;
4965     }
4966     case WIN_CHAT:
4967     {
4968         ProfChatWin* chatwin = (ProfChatWin*)window;
4969         assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
4970         omemo_enabled = chatwin->is_omemo == TRUE;
4971         sendfile_enabled = !((chatwin->pgp_send == TRUE && !prefs_get_boolean(PREF_PGP_SENDFILE))
4972                              || (chatwin->is_otr == TRUE && !prefs_get_boolean(PREF_OTR_SENDFILE)));
4973         break;
4974     }
4975 
4976     case WIN_PRIVATE: // We don't support encryption in private MUC windows.
4977     default:
4978         cons_show_error("Unsupported window for file transmission.");
4979         goto out;
4980     }
4981 
4982     if (!sendfile_enabled) {
4983         cons_show_error("Uploading unencrypted files disabled. See /otr sendfile or /pgp sendfile.");
4984         win_println(window, THEME_ERROR, "-", "Sending encrypted files via http_upload is not possible yet.");
4985         goto out;
4986     }
4987 
4988     if (omemo_enabled) {
4989 #ifdef HAVE_OMEMO
4990         char* err = NULL;
4991         alt_scheme = OMEMO_AESGCM_URL_SCHEME;
4992         alt_fragment = _add_omemo_stream(&fd, &fh, &err);
4993         if (err != NULL) {
4994             cons_show_error(err);
4995             win_println(window, THEME_ERROR, "-", err);
4996             goto out;
4997         }
4998 #endif
4999     }
5000 
5001     HTTPUpload* upload = malloc(sizeof(HTTPUpload));
5002     upload->window = window;
5003 
5004     upload->filename = strdup(filename);
5005     upload->filehandle = fh;
5006     upload->filesize = file_size(fd);
5007     upload->mime_type = file_mime_type(filename);
5008 
5009     if (alt_scheme != NULL) {
5010         upload->alt_scheme = strdup(alt_scheme);
5011     } else {
5012         upload->alt_scheme = NULL;
5013     }
5014 
5015     if (alt_fragment != NULL) {
5016         upload->alt_fragment = strdup(alt_fragment);
5017     } else {
5018         upload->alt_fragment = NULL;
5019     }
5020 
5021     iq_http_upload_request(upload);
5022 
5023 out:
5024 #ifdef HAVE_OMEMO
5025     if (alt_fragment != NULL)
5026         omemo_free(alt_fragment);
5027 #endif
5028     if (filename != NULL)
5029         free(filename);
5030 
5031     return TRUE;
5032 }
5033 
5034 gboolean
cmd_lastactivity(ProfWin * window,const char * const command,gchar ** args)5035 cmd_lastactivity(ProfWin* window, const char* const command, gchar** args)
5036 {
5037     if ((g_strcmp0(args[0], "set") == 0)) {
5038         if ((g_strcmp0(args[1], "on") == 0) || (g_strcmp0(args[1], "off") == 0)) {
5039             _cmd_set_boolean_preference(args[1], command, "Last activity", PREF_LASTACTIVITY);
5040             if (g_strcmp0(args[1], "on") == 0) {
5041                 caps_add_feature(XMPP_FEATURE_LASTACTIVITY);
5042             }
5043             if (g_strcmp0(args[1], "off") == 0) {
5044                 caps_remove_feature(XMPP_FEATURE_LASTACTIVITY);
5045             }
5046             return TRUE;
5047         } else {
5048             cons_bad_cmd_usage(command);
5049             return TRUE;
5050         }
5051     }
5052 
5053     jabber_conn_status_t conn_status = connection_get_status();
5054 
5055     if (conn_status != JABBER_CONNECTED) {
5056         cons_show("You are not currently connected.");
5057         return TRUE;
5058     }
5059 
5060     if ((g_strcmp0(args[0], "get") == 0)) {
5061         if (args[1] == NULL) {
5062             Jid* jidp = jid_create(connection_get_fulljid());
5063             GString* jid = g_string_new(jidp->domainpart);
5064 
5065             iq_last_activity_request(jid->str);
5066 
5067             g_string_free(jid, TRUE);
5068             jid_destroy(jidp);
5069 
5070             return TRUE;
5071         } else {
5072             iq_last_activity_request(args[1]);
5073             return TRUE;
5074         }
5075     }
5076 
5077     cons_bad_cmd_usage(command);
5078     return TRUE;
5079 }
5080 
5081 gboolean
cmd_nick(ProfWin * window,const char * const command,gchar ** args)5082 cmd_nick(ProfWin* window, const char* const command, gchar** args)
5083 {
5084     jabber_conn_status_t conn_status = connection_get_status();
5085 
5086     if (conn_status != JABBER_CONNECTED) {
5087         cons_show("You are not currently connected.");
5088         return TRUE;
5089     }
5090     if (window->type != WIN_MUC) {
5091         cons_show("You can only change your nickname in a chat room window.");
5092         return TRUE;
5093     }
5094 
5095     ProfMucWin* mucwin = (ProfMucWin*)window;
5096     assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
5097     char* nick = args[0];
5098     presence_change_room_nick(mucwin->roomjid, nick);
5099 
5100     return TRUE;
5101 }
5102 
5103 gboolean
cmd_alias(ProfWin * window,const char * const command,gchar ** args)5104 cmd_alias(ProfWin* window, const char* const command, gchar** args)
5105 {
5106     char* subcmd = args[0];
5107 
5108     if (strcmp(subcmd, "add") == 0) {
5109         char* alias = args[1];
5110         if (alias == NULL) {
5111             cons_bad_cmd_usage(command);
5112             return TRUE;
5113         } else {
5114             char* alias_p = alias;
5115             GString* ac_value = g_string_new("");
5116             if (alias[0] == '/') {
5117                 g_string_append(ac_value, alias);
5118                 alias_p = &alias[1];
5119             } else {
5120                 g_string_append(ac_value, "/");
5121                 g_string_append(ac_value, alias);
5122             }
5123 
5124             char* value = args[2];
5125             if (value == NULL) {
5126                 cons_bad_cmd_usage(command);
5127                 g_string_free(ac_value, TRUE);
5128                 return TRUE;
5129             } else if (cmd_ac_exists(ac_value->str)) {
5130                 cons_show("Command or alias '%s' already exists.", ac_value->str);
5131                 g_string_free(ac_value, TRUE);
5132                 return TRUE;
5133             } else {
5134                 prefs_add_alias(alias_p, value);
5135                 cmd_ac_add(ac_value->str);
5136                 cmd_ac_add_alias_value(alias_p);
5137                 cons_show("Command alias added %s -> %s", ac_value->str, value);
5138                 g_string_free(ac_value, TRUE);
5139                 return TRUE;
5140             }
5141         }
5142     } else if (strcmp(subcmd, "remove") == 0) {
5143         char* alias = args[1];
5144         if (alias == NULL) {
5145             cons_bad_cmd_usage(command);
5146             return TRUE;
5147         } else {
5148             if (alias[0] == '/') {
5149                 alias = &alias[1];
5150             }
5151             gboolean removed = prefs_remove_alias(alias);
5152             if (!removed) {
5153                 cons_show("No such command alias /%s", alias);
5154             } else {
5155                 GString* ac_value = g_string_new("/");
5156                 g_string_append(ac_value, alias);
5157                 cmd_ac_remove(ac_value->str);
5158                 cmd_ac_remove_alias_value(alias);
5159                 g_string_free(ac_value, TRUE);
5160                 cons_show("Command alias removed -> /%s", alias);
5161             }
5162             return TRUE;
5163         }
5164     } else if (strcmp(subcmd, "list") == 0) {
5165         GList* aliases = prefs_get_aliases();
5166         cons_show_aliases(aliases);
5167         prefs_free_aliases(aliases);
5168         return TRUE;
5169     } else {
5170         cons_bad_cmd_usage(command);
5171         return TRUE;
5172     }
5173 }
5174 
5175 gboolean
cmd_clear(ProfWin * window,const char * const command,gchar ** args)5176 cmd_clear(ProfWin* window, const char* const command, gchar** args)
5177 {
5178     if (args[0] == NULL) {
5179         win_clear(window);
5180         return TRUE;
5181     } else {
5182         if ((g_strcmp0(args[0], "persist_history") == 0)) {
5183 
5184             if (args[1] != NULL) {
5185                 if ((g_strcmp0(args[1], "on") == 0) || (g_strcmp0(args[1], "off") == 0)) {
5186                     _cmd_set_boolean_preference(args[1], command, "Persistent history", PREF_CLEAR_PERSIST_HISTORY);
5187                     return TRUE;
5188                 }
5189             } else {
5190                 if (prefs_get_boolean(PREF_CLEAR_PERSIST_HISTORY)) {
5191                     win_println(window, THEME_DEFAULT, "!", "  Persistently clear screen  : ON");
5192                 } else {
5193                     win_println(window, THEME_DEFAULT, "!", "  Persistently clear screen  : OFF");
5194                 }
5195                 return TRUE;
5196             }
5197         }
5198     }
5199     cons_bad_cmd_usage(command);
5200 
5201     return TRUE;
5202 }
5203 
5204 gboolean
cmd_privileges(ProfWin * window,const char * const command,gchar ** args)5205 cmd_privileges(ProfWin* window, const char* const command, gchar** args)
5206 {
5207     _cmd_set_boolean_preference(args[0], command, "MUC privileges", PREF_MUC_PRIVILEGES);
5208 
5209     ui_redraw_all_room_rosters();
5210 
5211     return TRUE;
5212 }
5213 
5214 gboolean
cmd_charset(ProfWin * window,const char * const command,gchar ** args)5215 cmd_charset(ProfWin* window, const char* const command, gchar** args)
5216 {
5217     char* codeset = nl_langinfo(CODESET);
5218     char* lang = getenv("LANG");
5219 
5220     cons_show("Charset information:");
5221 
5222     if (lang) {
5223         cons_show("  LANG:       %s", lang);
5224     }
5225     if (codeset) {
5226         cons_show("  CODESET:    %s", codeset);
5227     }
5228     cons_show("  MB_CUR_MAX: %d", MB_CUR_MAX);
5229     cons_show("  MB_LEN_MAX: %d", MB_LEN_MAX);
5230 
5231     return TRUE;
5232 }
5233 
5234 gboolean
cmd_beep(ProfWin * window,const char * const command,gchar ** args)5235 cmd_beep(ProfWin* window, const char* const command, gchar** args)
5236 {
5237     _cmd_set_boolean_preference(args[0], command, "Sound", PREF_BEEP);
5238     return TRUE;
5239 }
5240 
5241 gboolean
cmd_console(ProfWin * window,const char * const command,gchar ** args)5242 cmd_console(ProfWin* window, const char* const command, gchar** args)
5243 {
5244     gboolean isMuc = (g_strcmp0(args[0], "muc") == 0);
5245 
5246     if ((g_strcmp0(args[0], "chat") != 0) && !isMuc && (g_strcmp0(args[0], "private") != 0)) {
5247         cons_bad_cmd_usage(command);
5248         return TRUE;
5249     }
5250 
5251     gchar* setting = args[1];
5252     if ((g_strcmp0(setting, "all") != 0) && (g_strcmp0(setting, "first") != 0) && (g_strcmp0(setting, "none") != 0)) {
5253         if (!(isMuc && (g_strcmp0(setting, "mention") == 0))) {
5254             cons_bad_cmd_usage(command);
5255             return TRUE;
5256         }
5257     }
5258 
5259     if (g_strcmp0(args[0], "chat") == 0) {
5260         prefs_set_string(PREF_CONSOLE_CHAT, setting);
5261         cons_show("Console chat messages set: %s", setting);
5262         return TRUE;
5263     }
5264 
5265     if (g_strcmp0(args[0], "muc") == 0) {
5266         prefs_set_string(PREF_CONSOLE_MUC, setting);
5267         cons_show("Console MUC messages set: %s", setting);
5268         return TRUE;
5269     }
5270 
5271     if (g_strcmp0(args[0], "private") == 0) {
5272         prefs_set_string(PREF_CONSOLE_PRIVATE, setting);
5273         cons_show("Console private room messages set: %s", setting);
5274         return TRUE;
5275     }
5276 
5277     return TRUE;
5278 }
5279 
5280 gboolean
cmd_presence(ProfWin * window,const char * const command,gchar ** args)5281 cmd_presence(ProfWin* window, const char* const command, gchar** args)
5282 {
5283     if (strcmp(args[0], "console") != 0 && strcmp(args[0], "chat") != 0 && strcmp(args[0], "room") != 0 && strcmp(args[0], "titlebar") != 0) {
5284         cons_bad_cmd_usage(command);
5285         return TRUE;
5286     }
5287 
5288     if (strcmp(args[0], "titlebar") == 0) {
5289         _cmd_set_boolean_preference(args[1], command, "Contact presence", PREF_PRESENCE);
5290         return TRUE;
5291     }
5292 
5293     if (strcmp(args[1], "all") != 0 && strcmp(args[1], "online") != 0 && strcmp(args[1], "none") != 0) {
5294         cons_bad_cmd_usage(command);
5295         return TRUE;
5296     }
5297 
5298     if (strcmp(args[0], "console") == 0) {
5299         prefs_set_string(PREF_STATUSES_CONSOLE, args[1]);
5300         if (strcmp(args[1], "all") == 0) {
5301             cons_show("All presence updates will appear in the console.");
5302         } else if (strcmp(args[1], "online") == 0) {
5303             cons_show("Only online/offline presence updates will appear in the console.");
5304         } else {
5305             cons_show("Presence updates will not appear in the console.");
5306         }
5307     }
5308 
5309     if (strcmp(args[0], "chat") == 0) {
5310         prefs_set_string(PREF_STATUSES_CHAT, args[1]);
5311         if (strcmp(args[1], "all") == 0) {
5312             cons_show("All presence updates will appear in chat windows.");
5313         } else if (strcmp(args[1], "online") == 0) {
5314             cons_show("Only online/offline presence updates will appear in chat windows.");
5315         } else {
5316             cons_show("Presence updates will not appear in chat windows.");
5317         }
5318     }
5319 
5320     if (strcmp(args[0], "room") == 0) {
5321         prefs_set_string(PREF_STATUSES_MUC, args[1]);
5322         if (strcmp(args[1], "all") == 0) {
5323             cons_show("All presence updates will appear in chat room windows.");
5324         } else if (strcmp(args[1], "online") == 0) {
5325             cons_show("Only join/leave presence updates will appear in chat room windows.");
5326         } else {
5327             cons_show("Presence updates will not appear in chat room windows.");
5328         }
5329     }
5330 
5331     return TRUE;
5332 }
5333 
5334 gboolean
cmd_wrap(ProfWin * window,const char * const command,gchar ** args)5335 cmd_wrap(ProfWin* window, const char* const command, gchar** args)
5336 {
5337     _cmd_set_boolean_preference(args[0], command, "Word wrap", PREF_WRAP);
5338 
5339     wins_resize_all();
5340 
5341     return TRUE;
5342 }
5343 
5344 gboolean
cmd_time(ProfWin * window,const char * const command,gchar ** args)5345 cmd_time(ProfWin* window, const char* const command, gchar** args)
5346 {
5347     if (g_strcmp0(args[0], "lastactivity") == 0) {
5348         if (args[1] == NULL) {
5349             char* format = prefs_get_string(PREF_TIME_LASTACTIVITY);
5350             cons_show("Last activity time format: '%s'.", format);
5351             g_free(format);
5352             return TRUE;
5353         } else if (g_strcmp0(args[1], "set") == 0 && args[2] != NULL) {
5354             prefs_set_string(PREF_TIME_LASTACTIVITY, args[2]);
5355             cons_show("Last activity time format set to '%s'.", args[2]);
5356             ui_redraw();
5357             return TRUE;
5358         } else if (g_strcmp0(args[1], "off") == 0) {
5359             cons_show("Last activity time cannot be disabled.");
5360             ui_redraw();
5361             return TRUE;
5362         } else {
5363             cons_bad_cmd_usage(command);
5364             return TRUE;
5365         }
5366     } else if (g_strcmp0(args[0], "statusbar") == 0) {
5367         if (args[1] == NULL) {
5368             char* format = prefs_get_string(PREF_TIME_STATUSBAR);
5369             cons_show("Status bar time format: '%s'.", format);
5370             g_free(format);
5371             return TRUE;
5372         } else if (g_strcmp0(args[1], "set") == 0 && args[2] != NULL) {
5373             prefs_set_string(PREF_TIME_STATUSBAR, args[2]);
5374             cons_show("Status bar time format set to '%s'.", args[2]);
5375             ui_redraw();
5376             return TRUE;
5377         } else if (g_strcmp0(args[1], "off") == 0) {
5378             prefs_set_string(PREF_TIME_STATUSBAR, "off");
5379             cons_show("Status bar time display disabled.");
5380             ui_redraw();
5381             return TRUE;
5382         } else {
5383             cons_bad_cmd_usage(command);
5384             return TRUE;
5385         }
5386     } else if (g_strcmp0(args[0], "console") == 0) {
5387         if (args[1] == NULL) {
5388             char* format = prefs_get_string(PREF_TIME_CONSOLE);
5389             cons_show("Console time format: '%s'.", format);
5390             g_free(format);
5391             return TRUE;
5392         } else if (g_strcmp0(args[1], "set") == 0 && args[2] != NULL) {
5393             prefs_set_string(PREF_TIME_CONSOLE, args[2]);
5394             cons_show("Console time format set to '%s'.", args[2]);
5395             wins_resize_all();
5396             return TRUE;
5397         } else if (g_strcmp0(args[1], "off") == 0) {
5398             prefs_set_string(PREF_TIME_CONSOLE, "off");
5399             cons_show("Console time display disabled.");
5400             wins_resize_all();
5401             return TRUE;
5402         } else {
5403             cons_bad_cmd_usage(command);
5404             return TRUE;
5405         }
5406     } else if (g_strcmp0(args[0], "chat") == 0) {
5407         if (args[1] == NULL) {
5408             char* format = prefs_get_string(PREF_TIME_CHAT);
5409             cons_show("Chat time format: '%s'.", format);
5410             g_free(format);
5411             return TRUE;
5412         } else if (g_strcmp0(args[1], "set") == 0 && args[2] != NULL) {
5413             prefs_set_string(PREF_TIME_CHAT, args[2]);
5414             cons_show("Chat time format set to '%s'.", args[2]);
5415             wins_resize_all();
5416             return TRUE;
5417         } else if (g_strcmp0(args[1], "off") == 0) {
5418             prefs_set_string(PREF_TIME_CHAT, "off");
5419             cons_show("Chat time display disabled.");
5420             wins_resize_all();
5421             return TRUE;
5422         } else {
5423             cons_bad_cmd_usage(command);
5424             return TRUE;
5425         }
5426     } else if (g_strcmp0(args[0], "muc") == 0) {
5427         if (args[1] == NULL) {
5428             char* format = prefs_get_string(PREF_TIME_MUC);
5429             cons_show("MUC time format: '%s'.", format);
5430             g_free(format);
5431             return TRUE;
5432         } else if (g_strcmp0(args[1], "set") == 0 && args[2] != NULL) {
5433             prefs_set_string(PREF_TIME_MUC, args[2]);
5434             cons_show("MUC time format set to '%s'.", args[2]);
5435             wins_resize_all();
5436             return TRUE;
5437         } else if (g_strcmp0(args[1], "off") == 0) {
5438             prefs_set_string(PREF_TIME_MUC, "off");
5439             cons_show("MUC time display disabled.");
5440             wins_resize_all();
5441             return TRUE;
5442         } else {
5443             cons_bad_cmd_usage(command);
5444             return TRUE;
5445         }
5446     } else if (g_strcmp0(args[0], "config") == 0) {
5447         if (args[1] == NULL) {
5448             char* format = prefs_get_string(PREF_TIME_CONFIG);
5449             cons_show("config time format: '%s'.", format);
5450             g_free(format);
5451             return TRUE;
5452         } else if (g_strcmp0(args[1], "set") == 0 && args[2] != NULL) {
5453             prefs_set_string(PREF_TIME_CONFIG, args[2]);
5454             cons_show("config time format set to '%s'.", args[2]);
5455             wins_resize_all();
5456             return TRUE;
5457         } else if (g_strcmp0(args[1], "off") == 0) {
5458             prefs_set_string(PREF_TIME_CONFIG, "off");
5459             cons_show("config time display disabled.");
5460             wins_resize_all();
5461             return TRUE;
5462         } else {
5463             cons_bad_cmd_usage(command);
5464             return TRUE;
5465         }
5466     } else if (g_strcmp0(args[0], "private") == 0) {
5467         if (args[1] == NULL) {
5468             char* format = prefs_get_string(PREF_TIME_PRIVATE);
5469             cons_show("Private chat time format: '%s'.", format);
5470             g_free(format);
5471             return TRUE;
5472         } else if (g_strcmp0(args[1], "set") == 0 && args[2] != NULL) {
5473             prefs_set_string(PREF_TIME_PRIVATE, args[2]);
5474             cons_show("Private chat time format set to '%s'.", args[2]);
5475             wins_resize_all();
5476             return TRUE;
5477         } else if (g_strcmp0(args[1], "off") == 0) {
5478             prefs_set_string(PREF_TIME_PRIVATE, "off");
5479             cons_show("Private chat time display disabled.");
5480             wins_resize_all();
5481             return TRUE;
5482         } else {
5483             cons_bad_cmd_usage(command);
5484             return TRUE;
5485         }
5486     } else if (g_strcmp0(args[0], "xml") == 0) {
5487         if (args[1] == NULL) {
5488             char* format = prefs_get_string(PREF_TIME_XMLCONSOLE);
5489             cons_show("XML Console time format: '%s'.", format);
5490             g_free(format);
5491             return TRUE;
5492         } else if (g_strcmp0(args[1], "set") == 0 && args[2] != NULL) {
5493             prefs_set_string(PREF_TIME_XMLCONSOLE, args[2]);
5494             cons_show("XML Console time format set to '%s'.", args[2]);
5495             wins_resize_all();
5496             return TRUE;
5497         } else if (g_strcmp0(args[1], "off") == 0) {
5498             prefs_set_string(PREF_TIME_XMLCONSOLE, "off");
5499             cons_show("XML Console time display disabled.");
5500             wins_resize_all();
5501             return TRUE;
5502         } else {
5503             cons_bad_cmd_usage(command);
5504             return TRUE;
5505         }
5506     } else if (g_strcmp0(args[0], "all") == 0) {
5507         if (args[1] == NULL) {
5508             cons_time_setting();
5509             return TRUE;
5510         } else if (g_strcmp0(args[1], "set") == 0 && args[2] != NULL) {
5511             prefs_set_string(PREF_TIME_CONSOLE, args[2]);
5512             cons_show("Console time format set to '%s'.", args[2]);
5513             prefs_set_string(PREF_TIME_CHAT, args[2]);
5514             cons_show("Chat time format set to '%s'.", args[2]);
5515             prefs_set_string(PREF_TIME_MUC, args[2]);
5516             cons_show("MUC time format set to '%s'.", args[2]);
5517             prefs_set_string(PREF_TIME_CONFIG, args[2]);
5518             cons_show("config time format set to '%s'.", args[2]);
5519             prefs_set_string(PREF_TIME_PRIVATE, args[2]);
5520             cons_show("Private chat time format set to '%s'.", args[2]);
5521             prefs_set_string(PREF_TIME_XMLCONSOLE, args[2]);
5522             cons_show("XML Console time format set to '%s'.", args[2]);
5523             wins_resize_all();
5524             return TRUE;
5525         } else if (g_strcmp0(args[1], "off") == 0) {
5526             prefs_set_string(PREF_TIME_CONSOLE, "off");
5527             cons_show("Console time display disabled.");
5528             prefs_set_string(PREF_TIME_CHAT, "off");
5529             cons_show("Chat time display disabled.");
5530             prefs_set_string(PREF_TIME_MUC, "off");
5531             cons_show("MUC time display disabled.");
5532             prefs_set_string(PREF_TIME_CONFIG, "off");
5533             cons_show("config time display disabled.");
5534             prefs_set_string(PREF_TIME_PRIVATE, "off");
5535             cons_show("config time display disabled.");
5536             prefs_set_string(PREF_TIME_XMLCONSOLE, "off");
5537             cons_show("XML Console time display disabled.");
5538             ui_redraw();
5539             return TRUE;
5540         } else {
5541             cons_bad_cmd_usage(command);
5542             return TRUE;
5543         }
5544     } else {
5545         cons_bad_cmd_usage(command);
5546         return TRUE;
5547     }
5548 }
5549 
5550 gboolean
cmd_states(ProfWin * window,const char * const command,gchar ** args)5551 cmd_states(ProfWin* window, const char* const command, gchar** args)
5552 {
5553     if (args[0] == NULL) {
5554         return FALSE;
5555     }
5556 
5557     _cmd_set_boolean_preference(args[0], command, "Sending chat states", PREF_STATES);
5558 
5559     // if disabled, disable outtype and gone
5560     if (strcmp(args[0], "off") == 0) {
5561         prefs_set_boolean(PREF_OUTTYPE, FALSE);
5562         prefs_set_gone(0);
5563     }
5564 
5565     return TRUE;
5566 }
5567 
5568 gboolean
cmd_wintitle(ProfWin * window,const char * const command,gchar ** args)5569 cmd_wintitle(ProfWin* window, const char* const command, gchar** args)
5570 {
5571     if (g_strcmp0(args[0], "show") != 0 && g_strcmp0(args[0], "goodbye") != 0) {
5572         cons_bad_cmd_usage(command);
5573         return TRUE;
5574     }
5575     if (g_strcmp0(args[0], "show") == 0 && g_strcmp0(args[1], "off") == 0) {
5576         ui_clear_win_title();
5577     }
5578     if (g_strcmp0(args[0], "show") == 0) {
5579         _cmd_set_boolean_preference(args[1], command, "Window title show", PREF_WINTITLE_SHOW);
5580     } else {
5581         _cmd_set_boolean_preference(args[1], command, "Window title goodbye", PREF_WINTITLE_GOODBYE);
5582     }
5583 
5584     return TRUE;
5585 }
5586 
5587 gboolean
cmd_outtype(ProfWin * window,const char * const command,gchar ** args)5588 cmd_outtype(ProfWin* window, const char* const command, gchar** args)
5589 {
5590     if (args[0] == NULL) {
5591         return FALSE;
5592     }
5593 
5594     _cmd_set_boolean_preference(args[0], command, "Sending typing notifications", PREF_OUTTYPE);
5595 
5596     // if enabled, enable states
5597     if (strcmp(args[0], "on") == 0) {
5598         prefs_set_boolean(PREF_STATES, TRUE);
5599     }
5600 
5601     return TRUE;
5602 }
5603 
5604 gboolean
cmd_gone(ProfWin * window,const char * const command,gchar ** args)5605 cmd_gone(ProfWin* window, const char* const command, gchar** args)
5606 {
5607     char* value = args[0];
5608 
5609     gint period = atoi(value);
5610     prefs_set_gone(period);
5611     if (period == 0) {
5612         cons_show("Automatic leaving conversations after period disabled.");
5613     } else if (period == 1) {
5614         cons_show("Leaving conversations after 1 minute of inactivity.");
5615     } else {
5616         cons_show("Leaving conversations after %d minutes of inactivity.", period);
5617     }
5618 
5619     // if enabled, enable states
5620     if (period > 0) {
5621         prefs_set_boolean(PREF_STATES, TRUE);
5622     }
5623 
5624     return TRUE;
5625 }
5626 
5627 gboolean
cmd_notify(ProfWin * window,const char * const command,gchar ** args)5628 cmd_notify(ProfWin* window, const char* const command, gchar** args)
5629 {
5630     if (!args[0]) {
5631         ProfWin* current = wins_get_current();
5632         if (current->type == WIN_MUC) {
5633             win_println(current, THEME_DEFAULT, "-", "");
5634             ProfMucWin* mucwin = (ProfMucWin*)current;
5635 
5636             win_println(window, THEME_DEFAULT, "!", "Notification settings for %s:", mucwin->roomjid);
5637             if (prefs_has_room_notify(mucwin->roomjid)) {
5638                 if (prefs_get_room_notify(mucwin->roomjid)) {
5639                     win_println(window, THEME_DEFAULT, "!", "  Message  : ON");
5640                 } else {
5641                     win_println(window, THEME_DEFAULT, "!", "  Message  : OFF");
5642                 }
5643             } else {
5644                 if (prefs_get_boolean(PREF_NOTIFY_ROOM)) {
5645                     win_println(window, THEME_DEFAULT, "!", "  Message  : ON (global setting)");
5646                 } else {
5647                     win_println(window, THEME_DEFAULT, "!", "  Message  : OFF (global setting)");
5648                 }
5649             }
5650             if (prefs_has_room_notify_mention(mucwin->roomjid)) {
5651                 if (prefs_get_room_notify_mention(mucwin->roomjid)) {
5652                     win_println(window, THEME_DEFAULT, "!", "  Mention  : ON");
5653                 } else {
5654                     win_println(window, THEME_DEFAULT, "!", "  Mention  : OFF");
5655                 }
5656             } else {
5657                 if (prefs_get_boolean(PREF_NOTIFY_ROOM_MENTION)) {
5658                     win_println(window, THEME_DEFAULT, "!", "  Mention  : ON (global setting)");
5659                 } else {
5660                     win_println(window, THEME_DEFAULT, "!", "  Mention  : OFF (global setting)");
5661                 }
5662             }
5663             if (prefs_has_room_notify_trigger(mucwin->roomjid)) {
5664                 if (prefs_get_room_notify_trigger(mucwin->roomjid)) {
5665                     win_println(window, THEME_DEFAULT, "!", "  Triggers : ON");
5666                 } else {
5667                     win_println(window, THEME_DEFAULT, "!", "  Triggers : OFF");
5668                 }
5669             } else {
5670                 if (prefs_get_boolean(PREF_NOTIFY_ROOM_TRIGGER)) {
5671                     win_println(window, THEME_DEFAULT, "!", "  Triggers : ON (global setting)");
5672                 } else {
5673                     win_println(window, THEME_DEFAULT, "!", "  Triggers : OFF (global setting)");
5674                 }
5675             }
5676             win_println(current, THEME_DEFAULT, "-", "");
5677         } else {
5678             cons_show("");
5679             cons_notify_setting();
5680             cons_bad_cmd_usage(command);
5681         }
5682         return TRUE;
5683     }
5684 
5685     // chat settings
5686     if (g_strcmp0(args[0], "chat") == 0) {
5687         if (g_strcmp0(args[1], "on") == 0) {
5688             cons_show("Chat notifications enabled.");
5689             prefs_set_boolean(PREF_NOTIFY_CHAT, TRUE);
5690         } else if (g_strcmp0(args[1], "off") == 0) {
5691             cons_show("Chat notifications disabled.");
5692             prefs_set_boolean(PREF_NOTIFY_CHAT, FALSE);
5693         } else if (g_strcmp0(args[1], "current") == 0) {
5694             if (g_strcmp0(args[2], "on") == 0) {
5695                 cons_show("Current window chat notifications enabled.");
5696                 prefs_set_boolean(PREF_NOTIFY_CHAT_CURRENT, TRUE);
5697             } else if (g_strcmp0(args[2], "off") == 0) {
5698                 cons_show("Current window chat notifications disabled.");
5699                 prefs_set_boolean(PREF_NOTIFY_CHAT_CURRENT, FALSE);
5700             } else {
5701                 cons_show("Usage: /notify chat current on|off");
5702             }
5703         } else if (g_strcmp0(args[1], "text") == 0) {
5704             if (g_strcmp0(args[2], "on") == 0) {
5705                 cons_show("Showing text in chat notifications enabled.");
5706                 prefs_set_boolean(PREF_NOTIFY_CHAT_TEXT, TRUE);
5707             } else if (g_strcmp0(args[2], "off") == 0) {
5708                 cons_show("Showing text in chat notifications disabled.");
5709                 prefs_set_boolean(PREF_NOTIFY_CHAT_TEXT, FALSE);
5710             } else {
5711                 cons_show("Usage: /notify chat text on|off");
5712             }
5713         }
5714 
5715         // chat room settings
5716     } else if (g_strcmp0(args[0], "room") == 0) {
5717         if (g_strcmp0(args[1], "on") == 0) {
5718             cons_show("Room notifications enabled.");
5719             prefs_set_boolean(PREF_NOTIFY_ROOM, TRUE);
5720         } else if (g_strcmp0(args[1], "off") == 0) {
5721             cons_show("Room notifications disabled.");
5722             prefs_set_boolean(PREF_NOTIFY_ROOM, FALSE);
5723         } else if (g_strcmp0(args[1], "mention") == 0) {
5724             if (g_strcmp0(args[2], "on") == 0) {
5725                 cons_show("Room notifications with mention enabled.");
5726                 prefs_set_boolean(PREF_NOTIFY_ROOM_MENTION, TRUE);
5727             } else if (g_strcmp0(args[2], "off") == 0) {
5728                 cons_show("Room notifications with mention disabled.");
5729                 prefs_set_boolean(PREF_NOTIFY_ROOM_MENTION, FALSE);
5730             } else if (g_strcmp0(args[2], "case_sensitive") == 0) {
5731                 cons_show("Room mention matching set to case sensitive.");
5732                 prefs_set_boolean(PREF_NOTIFY_MENTION_CASE_SENSITIVE, TRUE);
5733             } else if (g_strcmp0(args[2], "case_insensitive") == 0) {
5734                 cons_show("Room mention matching set to case insensitive.");
5735                 prefs_set_boolean(PREF_NOTIFY_MENTION_CASE_SENSITIVE, FALSE);
5736             } else if (g_strcmp0(args[2], "word_whole") == 0) {
5737                 cons_show("Room mention matching set to whole word.");
5738                 prefs_set_boolean(PREF_NOTIFY_MENTION_WHOLE_WORD, TRUE);
5739             } else if (g_strcmp0(args[2], "word_part") == 0) {
5740                 cons_show("Room mention matching set to partial word.");
5741                 prefs_set_boolean(PREF_NOTIFY_MENTION_WHOLE_WORD, FALSE);
5742             } else {
5743                 cons_show("Usage: /notify room mention on|off");
5744             }
5745         } else if (g_strcmp0(args[1], "current") == 0) {
5746             if (g_strcmp0(args[2], "on") == 0) {
5747                 cons_show("Current window chat room message notifications enabled.");
5748                 prefs_set_boolean(PREF_NOTIFY_ROOM_CURRENT, TRUE);
5749             } else if (g_strcmp0(args[2], "off") == 0) {
5750                 cons_show("Current window chat room message notifications disabled.");
5751                 prefs_set_boolean(PREF_NOTIFY_ROOM_CURRENT, FALSE);
5752             } else {
5753                 cons_show("Usage: /notify room current on|off");
5754             }
5755         } else if (g_strcmp0(args[1], "text") == 0) {
5756             if (g_strcmp0(args[2], "on") == 0) {
5757                 cons_show("Showing text in chat room message notifications enabled.");
5758                 prefs_set_boolean(PREF_NOTIFY_ROOM_TEXT, TRUE);
5759             } else if (g_strcmp0(args[2], "off") == 0) {
5760                 cons_show("Showing text in chat room message notifications disabled.");
5761                 prefs_set_boolean(PREF_NOTIFY_ROOM_TEXT, FALSE);
5762             } else {
5763                 cons_show("Usage: /notify room text on|off");
5764             }
5765         } else if (g_strcmp0(args[1], "trigger") == 0) {
5766             if (g_strcmp0(args[2], "add") == 0) {
5767                 if (!args[3]) {
5768                     cons_bad_cmd_usage(command);
5769                 } else {
5770                     gboolean res = prefs_add_room_notify_trigger(args[3]);
5771                     if (res) {
5772                         cons_show("Adding room notification trigger: %s", args[3]);
5773                     } else {
5774                         cons_show("Room notification trigger already exists: %s", args[3]);
5775                     }
5776                 }
5777             } else if (g_strcmp0(args[2], "remove") == 0) {
5778                 if (!args[3]) {
5779                     cons_bad_cmd_usage(command);
5780                 } else {
5781                     gboolean res = prefs_remove_room_notify_trigger(args[3]);
5782                     if (res) {
5783                         cons_show("Removing room notification trigger: %s", args[3]);
5784                     } else {
5785                         cons_show("Room notification trigger does not exist: %s", args[3]);
5786                     }
5787                 }
5788             } else if (g_strcmp0(args[2], "list") == 0) {
5789                 GList* triggers = prefs_get_room_notify_triggers();
5790                 GList* curr = triggers;
5791                 if (curr) {
5792                     cons_show("Room notification triggers:");
5793                 } else {
5794                     cons_show("No room notification triggers");
5795                 }
5796                 while (curr) {
5797                     cons_show("  %s", curr->data);
5798                     curr = g_list_next(curr);
5799                 }
5800                 g_list_free_full(triggers, free);
5801             } else if (g_strcmp0(args[2], "on") == 0) {
5802                 cons_show("Enabling room notification triggers");
5803                 prefs_set_boolean(PREF_NOTIFY_ROOM_TRIGGER, TRUE);
5804             } else if (g_strcmp0(args[2], "off") == 0) {
5805                 cons_show("Disabling room notification triggers");
5806                 prefs_set_boolean(PREF_NOTIFY_ROOM_TRIGGER, FALSE);
5807             } else {
5808                 cons_bad_cmd_usage(command);
5809             }
5810         } else {
5811             cons_show("Usage: /notify room on|off|mention");
5812         }
5813 
5814         // typing settings
5815     } else if (g_strcmp0(args[0], "typing") == 0) {
5816         if (g_strcmp0(args[1], "on") == 0) {
5817             cons_show("Typing notifications enabled.");
5818             prefs_set_boolean(PREF_NOTIFY_TYPING, TRUE);
5819         } else if (g_strcmp0(args[1], "off") == 0) {
5820             cons_show("Typing notifications disabled.");
5821             prefs_set_boolean(PREF_NOTIFY_TYPING, FALSE);
5822         } else if (g_strcmp0(args[1], "current") == 0) {
5823             if (g_strcmp0(args[2], "on") == 0) {
5824                 cons_show("Current window typing notifications enabled.");
5825                 prefs_set_boolean(PREF_NOTIFY_TYPING_CURRENT, TRUE);
5826             } else if (g_strcmp0(args[2], "off") == 0) {
5827                 cons_show("Current window typing notifications disabled.");
5828                 prefs_set_boolean(PREF_NOTIFY_TYPING_CURRENT, FALSE);
5829             } else {
5830                 cons_show("Usage: /notify typing current on|off");
5831             }
5832         } else {
5833             cons_show("Usage: /notify typing on|off");
5834         }
5835 
5836         // invite settings
5837     } else if (g_strcmp0(args[0], "invite") == 0) {
5838         if (g_strcmp0(args[1], "on") == 0) {
5839             cons_show("Chat room invite notifications enabled.");
5840             prefs_set_boolean(PREF_NOTIFY_INVITE, TRUE);
5841         } else if (g_strcmp0(args[1], "off") == 0) {
5842             cons_show("Chat room invite notifications disabled.");
5843             prefs_set_boolean(PREF_NOTIFY_INVITE, FALSE);
5844         } else {
5845             cons_show("Usage: /notify invite on|off");
5846         }
5847 
5848         // subscription settings
5849     } else if (g_strcmp0(args[0], "sub") == 0) {
5850         if (g_strcmp0(args[1], "on") == 0) {
5851             cons_show("Subscription notifications enabled.");
5852             prefs_set_boolean(PREF_NOTIFY_SUB, TRUE);
5853         } else if (g_strcmp0(args[1], "off") == 0) {
5854             cons_show("Subscription notifications disabled.");
5855             prefs_set_boolean(PREF_NOTIFY_SUB, FALSE);
5856         } else {
5857             cons_show("Usage: /notify sub on|off");
5858         }
5859 
5860         // remind settings
5861     } else if (g_strcmp0(args[0], "remind") == 0) {
5862         if (!args[1]) {
5863             cons_bad_cmd_usage(command);
5864         } else {
5865             gint period = atoi(args[1]);
5866             prefs_set_notify_remind(period);
5867             if (period == 0) {
5868                 cons_show("Message reminders disabled.");
5869             } else if (period == 1) {
5870                 cons_show("Message reminder period set to 1 second.");
5871             } else {
5872                 cons_show("Message reminder period set to %d seconds.", period);
5873             }
5874         }
5875 
5876         // current chat room settings
5877     } else if (g_strcmp0(args[0], "on") == 0) {
5878         jabber_conn_status_t conn_status = connection_get_status();
5879 
5880         if (conn_status != JABBER_CONNECTED) {
5881             cons_show("You are not currently connected.");
5882         } else {
5883             ProfWin* window = wins_get_current();
5884             if (window->type != WIN_MUC) {
5885                 cons_show("You must be in a chat room.");
5886             } else {
5887                 ProfMucWin* mucwin = (ProfMucWin*)window;
5888                 prefs_set_room_notify(mucwin->roomjid, TRUE);
5889                 win_println(window, THEME_DEFAULT, "!", "Notifications enabled for %s", mucwin->roomjid);
5890             }
5891         }
5892     } else if (g_strcmp0(args[0], "off") == 0) {
5893         jabber_conn_status_t conn_status = connection_get_status();
5894 
5895         if (conn_status != JABBER_CONNECTED) {
5896             cons_show("You are not currently connected.");
5897         } else {
5898             ProfWin* window = wins_get_current();
5899             if (window->type != WIN_MUC) {
5900                 cons_show("You must be in a chat room.");
5901             } else {
5902                 ProfMucWin* mucwin = (ProfMucWin*)window;
5903                 prefs_set_room_notify(mucwin->roomjid, FALSE);
5904                 win_println(window, THEME_DEFAULT, "!", "Notifications disabled for %s", mucwin->roomjid);
5905             }
5906         }
5907     } else if (g_strcmp0(args[0], "mention") == 0) {
5908         jabber_conn_status_t conn_status = connection_get_status();
5909 
5910         if (conn_status != JABBER_CONNECTED) {
5911             cons_show("You are not currently connected.");
5912         } else {
5913             if (g_strcmp0(args[1], "on") == 0) {
5914                 ProfWin* window = wins_get_current();
5915                 if (window->type != WIN_MUC) {
5916                     cons_show("You must be in a chat room.");
5917                 } else {
5918                     ProfMucWin* mucwin = (ProfMucWin*)window;
5919                     prefs_set_room_notify_mention(mucwin->roomjid, TRUE);
5920                     win_println(window, THEME_DEFAULT, "!", "Mention notifications enabled for %s", mucwin->roomjid);
5921                 }
5922             } else if (g_strcmp0(args[1], "off") == 0) {
5923                 ProfWin* window = wins_get_current();
5924                 if (window->type != WIN_MUC) {
5925                     cons_show("You must be in a chat rooms.");
5926                 } else {
5927                     ProfMucWin* mucwin = (ProfMucWin*)window;
5928                     prefs_set_room_notify_mention(mucwin->roomjid, FALSE);
5929                     win_println(window, THEME_DEFAULT, "!", "Mention notifications disabled for %s", mucwin->roomjid);
5930                 }
5931             } else {
5932                 cons_bad_cmd_usage(command);
5933             }
5934         }
5935     } else if (g_strcmp0(args[0], "trigger") == 0) {
5936         jabber_conn_status_t conn_status = connection_get_status();
5937 
5938         if (conn_status != JABBER_CONNECTED) {
5939             cons_show("You are not currently connected.");
5940         } else {
5941             if (g_strcmp0(args[1], "on") == 0) {
5942                 ProfWin* window = wins_get_current();
5943                 if (window->type != WIN_MUC) {
5944                     cons_show("You must be in a chat room.");
5945                 } else {
5946                     ProfMucWin* mucwin = (ProfMucWin*)window;
5947                     prefs_set_room_notify_trigger(mucwin->roomjid, TRUE);
5948                     win_println(window, THEME_DEFAULT, "!", "Custom trigger notifications enabled for %s", mucwin->roomjid);
5949                 }
5950             } else if (g_strcmp0(args[1], "off") == 0) {
5951                 ProfWin* window = wins_get_current();
5952                 if (window->type != WIN_MUC) {
5953                     cons_show("You must be in a chat rooms.");
5954                 } else {
5955                     ProfMucWin* mucwin = (ProfMucWin*)window;
5956                     prefs_set_room_notify_trigger(mucwin->roomjid, FALSE);
5957                     win_println(window, THEME_DEFAULT, "!", "Custom trigger notifications disabled for %s", mucwin->roomjid);
5958                 }
5959             } else {
5960                 cons_bad_cmd_usage(command);
5961             }
5962         }
5963     } else if (g_strcmp0(args[0], "reset") == 0) {
5964         jabber_conn_status_t conn_status = connection_get_status();
5965 
5966         if (conn_status != JABBER_CONNECTED) {
5967             cons_show("You are not currently connected.");
5968         } else {
5969             ProfWin* window = wins_get_current();
5970             if (window->type != WIN_MUC) {
5971                 cons_show("You must be in a chat room.");
5972             } else {
5973                 ProfMucWin* mucwin = (ProfMucWin*)window;
5974                 gboolean res = prefs_reset_room_notify(mucwin->roomjid);
5975                 if (res) {
5976                     win_println(window, THEME_DEFAULT, "!", "Notification settings set to global defaults for %s", mucwin->roomjid);
5977                 } else {
5978                     win_println(window, THEME_DEFAULT, "!", "No custom notification settings for %s", mucwin->roomjid);
5979                 }
5980             }
5981         }
5982     } else {
5983         cons_bad_cmd_usage(command);
5984     }
5985 
5986     return TRUE;
5987 }
5988 
5989 gboolean
cmd_inpblock(ProfWin * window,const char * const command,gchar ** args)5990 cmd_inpblock(ProfWin* window, const char* const command, gchar** args)
5991 {
5992     char* subcmd = args[0];
5993     char* value = args[1];
5994 
5995     if (g_strcmp0(subcmd, "timeout") == 0) {
5996         if (value == NULL) {
5997             cons_bad_cmd_usage(command);
5998             return TRUE;
5999         }
6000 
6001         int intval = 0;
6002         char* err_msg = NULL;
6003         gboolean res = strtoi_range(value, &intval, 1, 1000, &err_msg);
6004         if (res) {
6005             cons_show("Input blocking set to %d milliseconds.", intval);
6006             prefs_set_inpblock(intval);
6007             inp_nonblocking(FALSE);
6008         } else {
6009             cons_show(err_msg);
6010             free(err_msg);
6011         }
6012 
6013         return TRUE;
6014     }
6015 
6016     if (g_strcmp0(subcmd, "dynamic") == 0) {
6017         if (value == NULL) {
6018             cons_bad_cmd_usage(command);
6019             return TRUE;
6020         }
6021 
6022         if (g_strcmp0(value, "on") != 0 && g_strcmp0(value, "off") != 0) {
6023             cons_show("Dynamic must be one of 'on' or 'off'");
6024             return TRUE;
6025         }
6026 
6027         _cmd_set_boolean_preference(value, command, "Dynamic input blocking", PREF_INPBLOCK_DYNAMIC);
6028         return TRUE;
6029     }
6030 
6031     cons_bad_cmd_usage(command);
6032 
6033     return TRUE;
6034 }
6035 
6036 gboolean
cmd_titlebar(ProfWin * window,const char * const command,gchar ** args)6037 cmd_titlebar(ProfWin* window, const char* const command, gchar** args)
6038 {
6039     if (g_strcmp0(args[0], "up") == 0) {
6040         gboolean result = prefs_titlebar_pos_up();
6041         if (result) {
6042             ui_resize();
6043             cons_show("Title bar moved up.");
6044         } else {
6045             cons_show("Could not move title bar up.");
6046         }
6047 
6048         return TRUE;
6049     }
6050     if (g_strcmp0(args[0], "down") == 0) {
6051         gboolean result = prefs_titlebar_pos_down();
6052         if (result) {
6053             ui_resize();
6054             cons_show("Title bar moved down.");
6055         } else {
6056             cons_show("Could not move title bar down.");
6057         }
6058 
6059         return TRUE;
6060     }
6061 
6062     cons_bad_cmd_usage(command);
6063 
6064     return TRUE;
6065 }
6066 
6067 gboolean
cmd_titlebar_show_hide(ProfWin * window,const char * const command,gchar ** args)6068 cmd_titlebar_show_hide(ProfWin* window, const char* const command, gchar** args)
6069 {
6070     if (args[1] != NULL) {
6071         if (g_strcmp0(args[0], "show") == 0) {
6072 
6073             if (g_strcmp0(args[1], "tls") == 0) {
6074                 cons_show("TLS titlebar indicator enabled.");
6075                 prefs_set_boolean(PREF_TLS_SHOW, TRUE);
6076             } else if (g_strcmp0(args[1], "encwarn") == 0) {
6077                 cons_show("Encryption warning titlebar indicator enabled.");
6078                 prefs_set_boolean(PREF_ENC_WARN, TRUE);
6079             } else if (g_strcmp0(args[1], "resource") == 0) {
6080                 cons_show("Showing resource in titlebar enabled.");
6081                 prefs_set_boolean(PREF_RESOURCE_TITLE, TRUE);
6082             } else if (g_strcmp0(args[1], "presence") == 0) {
6083                 cons_show("Showing contact presence in titlebar enabled.");
6084                 prefs_set_boolean(PREF_PRESENCE, TRUE);
6085             } else if (g_strcmp0(args[1], "jid") == 0) {
6086                 cons_show("Showing MUC JID in titlebar as title enabled.");
6087                 prefs_set_boolean(PREF_TITLEBAR_MUC_TITLE_JID, TRUE);
6088             } else if (g_strcmp0(args[1], "name") == 0) {
6089                 cons_show("Showing MUC name in titlebar as title enabled.");
6090                 prefs_set_boolean(PREF_TITLEBAR_MUC_TITLE_NAME, TRUE);
6091             } else {
6092                 cons_bad_cmd_usage(command);
6093             }
6094         } else if (g_strcmp0(args[0], "hide") == 0) {
6095 
6096             if (g_strcmp0(args[1], "tls") == 0) {
6097                 cons_show("TLS titlebar indicator disabled.");
6098                 prefs_set_boolean(PREF_TLS_SHOW, FALSE);
6099             } else if (g_strcmp0(args[1], "encwarn") == 0) {
6100                 cons_show("Encryption warning titlebar indicator disabled.");
6101                 prefs_set_boolean(PREF_ENC_WARN, FALSE);
6102             } else if (g_strcmp0(args[1], "resource") == 0) {
6103                 cons_show("Showing resource in titlebar disabled.");
6104                 prefs_set_boolean(PREF_RESOURCE_TITLE, FALSE);
6105             } else if (g_strcmp0(args[1], "presence") == 0) {
6106                 cons_show("Showing contact presence in titlebar disabled.");
6107                 prefs_set_boolean(PREF_PRESENCE, FALSE);
6108             } else if (g_strcmp0(args[1], "jid") == 0) {
6109                 cons_show("Showing MUC JID in titlebar as title disabled.");
6110                 prefs_set_boolean(PREF_TITLEBAR_MUC_TITLE_JID, FALSE);
6111             } else if (g_strcmp0(args[1], "name") == 0) {
6112                 cons_show("Showing MUC name in titlebar as title disabled.");
6113                 prefs_set_boolean(PREF_TITLEBAR_MUC_TITLE_NAME, FALSE);
6114             } else {
6115                 cons_bad_cmd_usage(command);
6116             }
6117         } else {
6118             cons_bad_cmd_usage(command);
6119         }
6120     }
6121 
6122     return TRUE;
6123 }
6124 
6125 gboolean
cmd_mainwin(ProfWin * window,const char * const command,gchar ** args)6126 cmd_mainwin(ProfWin* window, const char* const command, gchar** args)
6127 {
6128     if (g_strcmp0(args[0], "up") == 0) {
6129         gboolean result = prefs_mainwin_pos_up();
6130         if (result) {
6131             ui_resize();
6132             cons_show("Main window moved up.");
6133         } else {
6134             cons_show("Could not move main window up.");
6135         }
6136 
6137         return TRUE;
6138     }
6139     if (g_strcmp0(args[0], "down") == 0) {
6140         gboolean result = prefs_mainwin_pos_down();
6141         if (result) {
6142             ui_resize();
6143             cons_show("Main window moved down.");
6144         } else {
6145             cons_show("Could not move main window down.");
6146         }
6147 
6148         return TRUE;
6149     }
6150 
6151     cons_bad_cmd_usage(command);
6152 
6153     return TRUE;
6154 }
6155 
6156 gboolean
cmd_statusbar(ProfWin * window,const char * const command,gchar ** args)6157 cmd_statusbar(ProfWin* window, const char* const command, gchar** args)
6158 {
6159     if (g_strcmp0(args[0], "show") == 0) {
6160         if (g_strcmp0(args[1], "name") == 0) {
6161             prefs_set_boolean(PREF_STATUSBAR_SHOW_NAME, TRUE);
6162             cons_show("Enabled showing tab names.");
6163             ui_resize();
6164             return TRUE;
6165         }
6166         if (g_strcmp0(args[1], "number") == 0) {
6167             prefs_set_boolean(PREF_STATUSBAR_SHOW_NUMBER, TRUE);
6168             cons_show("Enabled showing tab numbers.");
6169             ui_resize();
6170             return TRUE;
6171         }
6172         if (g_strcmp0(args[1], "read") == 0) {
6173             prefs_set_boolean(PREF_STATUSBAR_SHOW_READ, TRUE);
6174             cons_show("Enabled showing inactive tabs.");
6175             ui_resize();
6176             return TRUE;
6177         }
6178         cons_bad_cmd_usage(command);
6179         return TRUE;
6180     }
6181 
6182     if (g_strcmp0(args[0], "hide") == 0) {
6183         if (g_strcmp0(args[1], "name") == 0) {
6184             if (prefs_get_boolean(PREF_STATUSBAR_SHOW_NUMBER) == FALSE) {
6185                 cons_show("Cannot disable both names and numbers in statusbar.");
6186                 cons_show("Use '/statusbar maxtabs 0' to hide tabs.");
6187                 return TRUE;
6188             }
6189             prefs_set_boolean(PREF_STATUSBAR_SHOW_NAME, FALSE);
6190             cons_show("Disabled showing tab names.");
6191             ui_resize();
6192             return TRUE;
6193         }
6194         if (g_strcmp0(args[1], "number") == 0) {
6195             if (prefs_get_boolean(PREF_STATUSBAR_SHOW_NAME) == FALSE) {
6196                 cons_show("Cannot disable both names and numbers in statusbar.");
6197                 cons_show("Use '/statusbar maxtabs 0' to hide tabs.");
6198                 return TRUE;
6199             }
6200             prefs_set_boolean(PREF_STATUSBAR_SHOW_NUMBER, FALSE);
6201             cons_show("Disabled showing tab numbers.");
6202             ui_resize();
6203             return TRUE;
6204         }
6205         if (g_strcmp0(args[1], "read") == 0) {
6206             prefs_set_boolean(PREF_STATUSBAR_SHOW_READ, FALSE);
6207             cons_show("Disabled showing inactive tabs.");
6208             ui_resize();
6209             return TRUE;
6210         }
6211         cons_bad_cmd_usage(command);
6212         return TRUE;
6213     }
6214 
6215     if (g_strcmp0(args[0], "maxtabs") == 0) {
6216         if (args[1] == NULL) {
6217             cons_bad_cmd_usage(command);
6218             return TRUE;
6219         }
6220 
6221         char* value = args[1];
6222         int intval = 0;
6223         char* err_msg = NULL;
6224         gboolean res = strtoi_range(value, &intval, 0, INT_MAX, &err_msg);
6225         if (res) {
6226             if (intval < 0 || intval > 10) {
6227                 cons_bad_cmd_usage(command);
6228                 return TRUE;
6229             }
6230 
6231             prefs_set_statusbartabs(intval);
6232             if (intval == 0) {
6233                 cons_show("Status bar tabs disabled.");
6234             } else {
6235                 cons_show("Status bar tabs set to %d.", intval);
6236             }
6237             ui_resize();
6238             return TRUE;
6239         } else {
6240             cons_show(err_msg);
6241             cons_bad_cmd_usage(command);
6242             free(err_msg);
6243             return TRUE;
6244         }
6245     }
6246 
6247     if (g_strcmp0(args[0], "tablen") == 0) {
6248         if (args[1] == NULL) {
6249             cons_bad_cmd_usage(command);
6250             return TRUE;
6251         }
6252 
6253         char* value = args[1];
6254         int intval = 0;
6255         char* err_msg = NULL;
6256         gboolean res = strtoi_range(value, &intval, 0, INT_MAX, &err_msg);
6257         if (res) {
6258             if (intval < 0) {
6259                 cons_bad_cmd_usage(command);
6260                 return TRUE;
6261             }
6262 
6263             prefs_set_statusbartablen(intval);
6264             if (intval == 0) {
6265                 cons_show("Maximum tab length disabled.");
6266             } else {
6267                 cons_show("Maximum tab length set to %d.", intval);
6268             }
6269             ui_resize();
6270             return TRUE;
6271         } else {
6272             cons_show(err_msg);
6273             cons_bad_cmd_usage(command);
6274             free(err_msg);
6275             return TRUE;
6276         }
6277     }
6278 
6279     if (g_strcmp0(args[0], "self") == 0) {
6280         if (g_strcmp0(args[1], "barejid") == 0) {
6281             prefs_set_string(PREF_STATUSBAR_SELF, "barejid");
6282             cons_show("Using barejid for statusbar title.");
6283             ui_resize();
6284             return TRUE;
6285         }
6286         if (g_strcmp0(args[1], "fulljid") == 0) {
6287             prefs_set_string(PREF_STATUSBAR_SELF, "fulljid");
6288             cons_show("Using fulljid for statusbar title.");
6289             ui_resize();
6290             return TRUE;
6291         }
6292         if (g_strcmp0(args[1], "user") == 0) {
6293             prefs_set_string(PREF_STATUSBAR_SELF, "user");
6294             cons_show("Using user for statusbar title.");
6295             ui_resize();
6296             return TRUE;
6297         }
6298         if (g_strcmp0(args[1], "off") == 0) {
6299             prefs_set_string(PREF_STATUSBAR_SELF, "off");
6300             cons_show("Disabling statusbar title.");
6301             ui_resize();
6302             return TRUE;
6303         }
6304         cons_bad_cmd_usage(command);
6305         return TRUE;
6306     }
6307 
6308     if (g_strcmp0(args[0], "chat") == 0) {
6309         if (g_strcmp0(args[1], "jid") == 0) {
6310             prefs_set_string(PREF_STATUSBAR_CHAT, "jid");
6311             cons_show("Using jid for chat tabs.");
6312             ui_resize();
6313             return TRUE;
6314         }
6315         if (g_strcmp0(args[1], "user") == 0) {
6316             prefs_set_string(PREF_STATUSBAR_CHAT, "user");
6317             cons_show("Using user for chat tabs.");
6318             ui_resize();
6319             return TRUE;
6320         }
6321         cons_bad_cmd_usage(command);
6322         return TRUE;
6323     }
6324 
6325     if (g_strcmp0(args[0], "room") == 0) {
6326         if (g_strcmp0(args[1], "jid") == 0) {
6327             prefs_set_string(PREF_STATUSBAR_ROOM, "jid");
6328             cons_show("Using jid for room tabs.");
6329             ui_resize();
6330             return TRUE;
6331         }
6332         if (g_strcmp0(args[1], "room") == 0) {
6333             prefs_set_string(PREF_STATUSBAR_ROOM, "room");
6334             cons_show("Using room name for room tabs.");
6335             ui_resize();
6336             return TRUE;
6337         }
6338         cons_bad_cmd_usage(command);
6339         return TRUE;
6340     }
6341 
6342     if (g_strcmp0(args[0], "up") == 0) {
6343         gboolean result = prefs_statusbar_pos_up();
6344         if (result) {
6345             ui_resize();
6346             cons_show("Status bar moved up");
6347         } else {
6348             cons_show("Could not move status bar up.");
6349         }
6350 
6351         return TRUE;
6352     }
6353     if (g_strcmp0(args[0], "down") == 0) {
6354         gboolean result = prefs_statusbar_pos_down();
6355         if (result) {
6356             ui_resize();
6357             cons_show("Status bar moved down.");
6358         } else {
6359             cons_show("Could not move status bar down.");
6360         }
6361 
6362         return TRUE;
6363     }
6364 
6365     cons_bad_cmd_usage(command);
6366 
6367     return TRUE;
6368 }
6369 
6370 gboolean
cmd_inputwin(ProfWin * window,const char * const command,gchar ** args)6371 cmd_inputwin(ProfWin* window, const char* const command, gchar** args)
6372 {
6373     if (g_strcmp0(args[0], "up") == 0) {
6374         gboolean result = prefs_inputwin_pos_up();
6375         if (result) {
6376             ui_resize();
6377             cons_show("Input window moved up.");
6378         } else {
6379             cons_show("Could not move input window up.");
6380         }
6381 
6382         return TRUE;
6383     }
6384     if (g_strcmp0(args[0], "down") == 0) {
6385         gboolean result = prefs_inputwin_pos_down();
6386         if (result) {
6387             ui_resize();
6388             cons_show("Input window moved down.");
6389         } else {
6390             cons_show("Could not move input window down.");
6391         }
6392 
6393         return TRUE;
6394     }
6395 
6396     cons_bad_cmd_usage(command);
6397 
6398     return TRUE;
6399 }
6400 
6401 gboolean
cmd_log(ProfWin * window,const char * const command,gchar ** args)6402 cmd_log(ProfWin* window, const char* const command, gchar** args)
6403 {
6404     char* subcmd = args[0];
6405     char* value = args[1];
6406 
6407     if (strcmp(subcmd, "maxsize") == 0) {
6408         if (value == NULL) {
6409             cons_bad_cmd_usage(command);
6410             return TRUE;
6411         }
6412 
6413         int intval = 0;
6414         char* err_msg = NULL;
6415         gboolean res = strtoi_range(value, &intval, PREFS_MIN_LOG_SIZE, INT_MAX, &err_msg);
6416         if (res) {
6417             prefs_set_max_log_size(intval);
6418             cons_show("Log maximum size set to %d bytes", intval);
6419         } else {
6420             cons_show(err_msg);
6421             free(err_msg);
6422         }
6423         return TRUE;
6424     }
6425 
6426     if (strcmp(subcmd, "rotate") == 0) {
6427         if (value == NULL) {
6428             cons_bad_cmd_usage(command);
6429             return TRUE;
6430         }
6431         _cmd_set_boolean_preference(value, command, "Log rotate", PREF_LOG_ROTATE);
6432         return TRUE;
6433     }
6434 
6435     if (strcmp(subcmd, "shared") == 0) {
6436         if (value == NULL) {
6437             cons_bad_cmd_usage(command);
6438             return TRUE;
6439         }
6440         _cmd_set_boolean_preference(value, command, "Shared log", PREF_LOG_SHARED);
6441         cons_show("Setting only takes effect after saving and restarting Profanity.");
6442         return TRUE;
6443     }
6444 
6445     if (strcmp(subcmd, "where") == 0) {
6446         cons_show("Log file: %s", get_log_file_location());
6447         return TRUE;
6448     }
6449 
6450     cons_bad_cmd_usage(command);
6451 
6452     /* TODO: make 'level' subcommand for debug level */
6453 
6454     return TRUE;
6455 }
6456 
6457 gboolean
cmd_reconnect(ProfWin * window,const char * const command,gchar ** args)6458 cmd_reconnect(ProfWin* window, const char* const command, gchar** args)
6459 {
6460     char* value = args[0];
6461 
6462     int intval = 0;
6463     char* err_msg = NULL;
6464     gboolean res = strtoi_range(value, &intval, 0, INT_MAX, &err_msg);
6465     if (res) {
6466         prefs_set_reconnect(intval);
6467         if (intval == 0) {
6468             cons_show("Reconnect disabled.", intval);
6469         } else {
6470             cons_show("Reconnect interval set to %d seconds.", intval);
6471         }
6472     } else {
6473         cons_show(err_msg);
6474         cons_bad_cmd_usage(command);
6475         free(err_msg);
6476     }
6477 
6478     return TRUE;
6479 }
6480 
6481 gboolean
cmd_autoping(ProfWin * window,const char * const command,gchar ** args)6482 cmd_autoping(ProfWin* window, const char* const command, gchar** args)
6483 {
6484     char* cmd = args[0];
6485     char* value = args[1];
6486 
6487     if (g_strcmp0(cmd, "set") == 0) {
6488         int intval = 0;
6489         char* err_msg = NULL;
6490         gboolean res = strtoi_range(value, &intval, 0, INT_MAX, &err_msg);
6491         if (res) {
6492             prefs_set_autoping(intval);
6493             iq_set_autoping(intval);
6494             if (intval == 0) {
6495                 cons_show("Autoping disabled.");
6496             } else {
6497                 cons_show("Autoping interval set to %d seconds.", intval);
6498             }
6499         } else {
6500             cons_show(err_msg);
6501             cons_bad_cmd_usage(command);
6502             free(err_msg);
6503         }
6504 
6505     } else if (g_strcmp0(cmd, "timeout") == 0) {
6506         int intval = 0;
6507         char* err_msg = NULL;
6508         gboolean res = strtoi_range(value, &intval, 0, INT_MAX, &err_msg);
6509         if (res) {
6510             prefs_set_autoping_timeout(intval);
6511             if (intval == 0) {
6512                 cons_show("Autoping timeout disabled.");
6513             } else {
6514                 cons_show("Autoping timeout set to %d seconds.", intval);
6515             }
6516         } else {
6517             cons_show(err_msg);
6518             cons_bad_cmd_usage(command);
6519             free(err_msg);
6520         }
6521 
6522     } else {
6523         cons_bad_cmd_usage(command);
6524     }
6525 
6526     return TRUE;
6527 }
6528 
6529 gboolean
cmd_ping(ProfWin * window,const char * const command,gchar ** args)6530 cmd_ping(ProfWin* window, const char* const command, gchar** args)
6531 {
6532     jabber_conn_status_t conn_status = connection_get_status();
6533 
6534     if (conn_status != JABBER_CONNECTED) {
6535         cons_show("You are not currently connected.");
6536         return TRUE;
6537     }
6538 
6539     if (args[0] == NULL && connection_supports(XMPP_FEATURE_PING) == FALSE) {
6540         cons_show("Server does not support ping requests (%s).", XMPP_FEATURE_PING);
6541         return TRUE;
6542     }
6543 
6544     if (args[0] != NULL && caps_jid_has_feature(args[0], XMPP_FEATURE_PING) == FALSE) {
6545         cons_show("%s does not support ping requests.", args[0]);
6546         return TRUE;
6547     }
6548 
6549     iq_send_ping(args[0]);
6550 
6551     if (args[0] == NULL) {
6552         cons_show("Pinged server...");
6553     } else {
6554         cons_show("Pinged %s...", args[0]);
6555     }
6556     return TRUE;
6557 }
6558 
6559 gboolean
cmd_autoaway(ProfWin * window,const char * const command,gchar ** args)6560 cmd_autoaway(ProfWin* window, const char* const command, gchar** args)
6561 {
6562     if ((g_strcmp0(args[0], "mode") != 0) && (g_strcmp0(args[0], "time") != 0) && (g_strcmp0(args[0], "message") != 0) && (g_strcmp0(args[0], "check") != 0)) {
6563         cons_show("Setting must be one of 'mode', 'time', 'message' or 'check'");
6564         return TRUE;
6565     }
6566 
6567     if (g_strcmp0(args[0], "mode") == 0) {
6568         if ((g_strcmp0(args[1], "idle") != 0) && (g_strcmp0(args[1], "away") != 0) && (g_strcmp0(args[1], "off") != 0)) {
6569             cons_show("Mode must be one of 'idle', 'away' or 'off'");
6570         } else {
6571             prefs_set_string(PREF_AUTOAWAY_MODE, args[1]);
6572             cons_show("Auto away mode set to: %s.", args[1]);
6573         }
6574 
6575         return TRUE;
6576     }
6577 
6578     if ((g_strcmp0(args[0], "time") == 0) && (args[2] != NULL)) {
6579         if (g_strcmp0(args[1], "away") == 0) {
6580             int minutesval = 0;
6581             char* err_msg = NULL;
6582             gboolean res = strtoi_range(args[2], &minutesval, 1, INT_MAX, &err_msg);
6583             if (res) {
6584                 prefs_set_autoaway_time(minutesval);
6585                 if (minutesval == 1) {
6586                     cons_show("Auto away time set to: 1 minute.");
6587                 } else {
6588                     cons_show("Auto away time set to: %d minutes.", minutesval);
6589                 }
6590             } else {
6591                 cons_show(err_msg);
6592                 free(err_msg);
6593             }
6594 
6595             return TRUE;
6596         } else if (g_strcmp0(args[1], "xa") == 0) {
6597             int minutesval = 0;
6598             char* err_msg = NULL;
6599             gboolean res = strtoi_range(args[2], &minutesval, 0, INT_MAX, &err_msg);
6600             if (res) {
6601                 int away_time = prefs_get_autoaway_time();
6602                 if (minutesval != 0 && minutesval <= away_time) {
6603                     cons_show("Auto xa time must be larger than auto away time.");
6604                 } else {
6605                     prefs_set_autoxa_time(minutesval);
6606                     if (minutesval == 0) {
6607                         cons_show("Auto xa time disabled.");
6608                     } else if (minutesval == 1) {
6609                         cons_show("Auto xa time set to: 1 minute.");
6610                     } else {
6611                         cons_show("Auto xa time set to: %d minutes.", minutesval);
6612                     }
6613                 }
6614             } else {
6615                 cons_show(err_msg);
6616                 free(err_msg);
6617             }
6618 
6619             return TRUE;
6620         } else {
6621             cons_bad_cmd_usage(command);
6622             return TRUE;
6623         }
6624     } else {
6625         cons_bad_cmd_usage(command);
6626         return TRUE;
6627     }
6628 
6629     if (g_strcmp0(args[0], "message") == 0) {
6630         if (g_strcmp0(args[1], "away") == 0 && args[2] != NULL) {
6631             if (g_strcmp0(args[2], "off") == 0) {
6632                 prefs_set_string(PREF_AUTOAWAY_MESSAGE, NULL);
6633                 cons_show("Auto away message cleared.");
6634             } else {
6635                 prefs_set_string(PREF_AUTOAWAY_MESSAGE, args[2]);
6636                 cons_show("Auto away message set to: \"%s\".", args[2]);
6637             }
6638 
6639             return TRUE;
6640         } else if (g_strcmp0(args[1], "xa") == 0) {
6641             if (g_strcmp0(args[2], "off") == 0) {
6642                 prefs_set_string(PREF_AUTOXA_MESSAGE, NULL);
6643                 cons_show("Auto xa message cleared.");
6644             } else {
6645                 prefs_set_string(PREF_AUTOXA_MESSAGE, args[2]);
6646                 cons_show("Auto xa message set to: \"%s\".", args[2]);
6647             }
6648 
6649             return TRUE;
6650         } else {
6651             cons_bad_cmd_usage(command);
6652             return TRUE;
6653         }
6654     }
6655 
6656     if (g_strcmp0(args[0], "check") == 0) {
6657         _cmd_set_boolean_preference(args[1], command, "Online check", PREF_AUTOAWAY_CHECK);
6658         return TRUE;
6659     }
6660 
6661     return TRUE;
6662 }
6663 
6664 gboolean
cmd_priority(ProfWin * window,const char * const command,gchar ** args)6665 cmd_priority(ProfWin* window, const char* const command, gchar** args)
6666 {
6667     jabber_conn_status_t conn_status = connection_get_status();
6668 
6669     if (conn_status != JABBER_CONNECTED) {
6670         cons_show("You are not currently connected.");
6671         return TRUE;
6672     }
6673 
6674     char* value = args[0];
6675 
6676     int intval = 0;
6677     char* err_msg = NULL;
6678     gboolean res = strtoi_range(value, &intval, -128, 127, &err_msg);
6679     if (res) {
6680         accounts_set_priority_all(session_get_account_name(), intval);
6681         resource_presence_t last_presence = accounts_get_last_presence(session_get_account_name());
6682         cl_ev_presence_send(last_presence, 0);
6683         cons_show("Priority set to %d.", intval);
6684     } else {
6685         cons_show(err_msg);
6686         free(err_msg);
6687     }
6688 
6689     return TRUE;
6690 }
6691 
6692 gboolean
cmd_vercheck(ProfWin * window,const char * const command,gchar ** args)6693 cmd_vercheck(ProfWin* window, const char* const command, gchar** args)
6694 {
6695     int num_args = g_strv_length(args);
6696 
6697     if (num_args == 0) {
6698         cons_check_version(TRUE);
6699         return TRUE;
6700     } else {
6701         _cmd_set_boolean_preference(args[0], command, "Version checking", PREF_VERCHECK);
6702         return TRUE;
6703     }
6704 }
6705 
6706 gboolean
cmd_xmlconsole(ProfWin * window,const char * const command,gchar ** args)6707 cmd_xmlconsole(ProfWin* window, const char* const command, gchar** args)
6708 {
6709     ProfXMLWin* xmlwin = wins_get_xmlconsole();
6710     if (xmlwin) {
6711         ui_focus_win((ProfWin*)xmlwin);
6712     } else {
6713         ProfWin* window = wins_new_xmlconsole();
6714         ui_focus_win(window);
6715     }
6716 
6717     return TRUE;
6718 }
6719 
6720 gboolean
cmd_flash(ProfWin * window,const char * const command,gchar ** args)6721 cmd_flash(ProfWin* window, const char* const command, gchar** args)
6722 {
6723     _cmd_set_boolean_preference(args[0], command, "Screen flash", PREF_FLASH);
6724     return TRUE;
6725 }
6726 
6727 gboolean
cmd_tray(ProfWin * window,const char * const command,gchar ** args)6728 cmd_tray(ProfWin* window, const char* const command, gchar** args)
6729 {
6730 #ifdef HAVE_GTK
6731     if (g_strcmp0(args[0], "timer") == 0) {
6732         if (args[1] == NULL) {
6733             cons_bad_cmd_usage(command);
6734             return TRUE;
6735         }
6736 
6737         if (prefs_get_boolean(PREF_TRAY) == FALSE) {
6738             cons_show("Tray icon not currently enabled, see /help tray");
6739             return TRUE;
6740         }
6741 
6742         int intval = 0;
6743         char* err_msg = NULL;
6744         gboolean res = strtoi_range(args[1], &intval, 1, 10, &err_msg);
6745         if (res) {
6746             if (intval == 1) {
6747                 cons_show("Tray timer set to 1 second.");
6748             } else {
6749                 cons_show("Tray timer set to %d seconds.", intval);
6750             }
6751             prefs_set_tray_timer(intval);
6752             if (prefs_get_boolean(PREF_TRAY)) {
6753                 tray_set_timer(intval);
6754             }
6755         } else {
6756             cons_show(err_msg);
6757             free(err_msg);
6758         }
6759 
6760         return TRUE;
6761     } else if (g_strcmp0(args[0], "read") == 0) {
6762         if (prefs_get_boolean(PREF_TRAY) == FALSE) {
6763             cons_show("Tray icon not currently enabled, see /help tray");
6764         } else if (g_strcmp0(args[1], "on") == 0) {
6765             prefs_set_boolean(PREF_TRAY_READ, TRUE);
6766             cons_show("Tray icon enabled when no unread messages.");
6767         } else if (g_strcmp0(args[1], "off") == 0) {
6768             prefs_set_boolean(PREF_TRAY_READ, FALSE);
6769             cons_show("Tray icon disabled when no unread messages.");
6770         } else {
6771             cons_bad_cmd_usage(command);
6772         }
6773 
6774         return TRUE;
6775     } else {
6776         gboolean old = prefs_get_boolean(PREF_TRAY);
6777         _cmd_set_boolean_preference(args[0], command, "Tray icon", PREF_TRAY);
6778         gboolean new = prefs_get_boolean(PREF_TRAY);
6779         if (old != new) {
6780             if (new) {
6781                 tray_enable();
6782             } else {
6783                 tray_disable();
6784             }
6785         }
6786 
6787         return TRUE;
6788     }
6789 #else
6790     cons_show("This version of Profanity has not been built with GTK Tray Icon support enabled");
6791     return TRUE;
6792 #endif
6793 }
6794 
6795 gboolean
cmd_intype(ProfWin * window,const char * const command,gchar ** args)6796 cmd_intype(ProfWin* window, const char* const command, gchar** args)
6797 {
6798     if (g_strcmp0(args[0], "console") == 0) {
6799         _cmd_set_boolean_preference(args[1], command, "Show contact typing in console", PREF_INTYPE_CONSOLE);
6800     } else if (g_strcmp0(args[0], "titlebar") == 0) {
6801         _cmd_set_boolean_preference(args[1], command, "Show contact typing in titlebar", PREF_INTYPE);
6802     } else {
6803         cons_bad_cmd_usage(command);
6804     }
6805 
6806     return TRUE;
6807 }
6808 
6809 gboolean
cmd_splash(ProfWin * window,const char * const command,gchar ** args)6810 cmd_splash(ProfWin* window, const char* const command, gchar** args)
6811 {
6812     _cmd_set_boolean_preference(args[0], command, "Splash screen", PREF_SPLASH);
6813     return TRUE;
6814 }
6815 
6816 gboolean
cmd_autoconnect(ProfWin * window,const char * const command,gchar ** args)6817 cmd_autoconnect(ProfWin* window, const char* const command, gchar** args)
6818 {
6819     if (strcmp(args[0], "off") == 0) {
6820         prefs_set_string(PREF_CONNECT_ACCOUNT, NULL);
6821         cons_show("Autoconnect account disabled.");
6822     } else if (strcmp(args[0], "set") == 0) {
6823         if (args[1] == NULL || strlen(args[1]) == 0) {
6824             cons_bad_cmd_usage(command);
6825         } else {
6826             if (accounts_account_exists(args[1])) {
6827                 prefs_set_string(PREF_CONNECT_ACCOUNT, args[1]);
6828                 cons_show("Autoconnect account set to: %s.", args[1]);
6829             } else {
6830                 cons_show_error("Account '%s' does not exist.", args[1]);
6831             }
6832         }
6833     } else {
6834         cons_bad_cmd_usage(command);
6835     }
6836     return TRUE;
6837 }
6838 
6839 gboolean
cmd_logging(ProfWin * window,const char * const command,gchar ** args)6840 cmd_logging(ProfWin* window, const char* const command, gchar** args)
6841 {
6842     if (args[0] == NULL) {
6843         cons_logging_setting();
6844         return TRUE;
6845     }
6846 
6847     if (strcmp(args[0], "chat") == 0 && args[1] != NULL) {
6848         _cmd_set_boolean_preference(args[1], command, "Chat logging", PREF_CHLOG);
6849 
6850         // if set to off, disable history
6851         if (strcmp(args[1], "off") == 0) {
6852             prefs_set_boolean(PREF_HISTORY, FALSE);
6853         }
6854 
6855         return TRUE;
6856     } else if (g_strcmp0(args[0], "group") == 0 && args[1] != NULL) {
6857         if (g_strcmp0(args[1], "on") == 0 || g_strcmp0(args[1], "off") == 0) {
6858             _cmd_set_boolean_preference(args[1], command, "Groupchat logging", PREF_GRLOG);
6859             return TRUE;
6860         }
6861     }
6862 
6863     cons_bad_cmd_usage(command);
6864     return TRUE;
6865 }
6866 
6867 gboolean
cmd_history(ProfWin * window,const char * const command,gchar ** args)6868 cmd_history(ProfWin* window, const char* const command, gchar** args)
6869 {
6870     if (args[0] == NULL) {
6871         return FALSE;
6872     }
6873 
6874     _cmd_set_boolean_preference(args[0], command, "Chat history", PREF_HISTORY);
6875 
6876     // if set to on, set chlog (/logging chat on)
6877     if (strcmp(args[0], "on") == 0) {
6878         prefs_set_boolean(PREF_CHLOG, TRUE);
6879     }
6880 
6881     return TRUE;
6882 }
6883 
6884 gboolean
cmd_carbons(ProfWin * window,const char * const command,gchar ** args)6885 cmd_carbons(ProfWin* window, const char* const command, gchar** args)
6886 {
6887     if (args[0] == NULL) {
6888         return FALSE;
6889     }
6890 
6891     _cmd_set_boolean_preference(args[0], command, "Message carbons preference", PREF_CARBONS);
6892 
6893     jabber_conn_status_t conn_status = connection_get_status();
6894 
6895     if (conn_status == JABBER_CONNECTED) {
6896         // enable carbons
6897         if (strcmp(args[0], "on") == 0) {
6898             iq_enable_carbons();
6899         } else if (strcmp(args[0], "off") == 0) {
6900             iq_disable_carbons();
6901         }
6902     }
6903 
6904     return TRUE;
6905 }
6906 
6907 gboolean
cmd_receipts(ProfWin * window,const char * const command,gchar ** args)6908 cmd_receipts(ProfWin* window, const char* const command, gchar** args)
6909 {
6910     if (g_strcmp0(args[0], "send") == 0) {
6911         _cmd_set_boolean_preference(args[1], command, "Send delivery receipts", PREF_RECEIPTS_SEND);
6912         if (g_strcmp0(args[1], "on") == 0) {
6913             caps_add_feature(XMPP_FEATURE_RECEIPTS);
6914         }
6915         if (g_strcmp0(args[1], "off") == 0) {
6916             caps_remove_feature(XMPP_FEATURE_RECEIPTS);
6917         }
6918     } else if (g_strcmp0(args[0], "request") == 0) {
6919         _cmd_set_boolean_preference(args[1], command, "Request delivery receipts", PREF_RECEIPTS_REQUEST);
6920     } else {
6921         cons_bad_cmd_usage(command);
6922     }
6923 
6924     return TRUE;
6925 }
6926 
6927 gboolean
cmd_plugins_sourcepath(ProfWin * window,const char * const command,gchar ** args)6928 cmd_plugins_sourcepath(ProfWin* window, const char* const command, gchar** args)
6929 {
6930     if (args[1] == NULL) {
6931         char* sourcepath = prefs_get_string(PREF_PLUGINS_SOURCEPATH);
6932         if (sourcepath) {
6933             cons_show("Current plugins sourcepath: %s", sourcepath);
6934             g_free(sourcepath);
6935         } else {
6936             cons_show("Plugins sourcepath not currently set.");
6937         }
6938         return TRUE;
6939     }
6940 
6941     if (g_strcmp0(args[1], "clear") == 0) {
6942         prefs_set_string(PREF_PLUGINS_SOURCEPATH, NULL);
6943         cons_show("Plugins sourcepath cleared.");
6944         return TRUE;
6945     }
6946 
6947     if (g_strcmp0(args[1], "set") == 0) {
6948         if (args[2] == NULL) {
6949             cons_bad_cmd_usage(command);
6950             return TRUE;
6951         }
6952 
6953         char* path = get_expanded_path(args[2]);
6954 
6955         if (!is_dir(path)) {
6956             cons_show("Plugins sourcepath must be a directory.");
6957             free(path);
6958             return TRUE;
6959         }
6960 
6961         cons_show("Setting plugins sourcepath: %s", path);
6962         prefs_set_string(PREF_PLUGINS_SOURCEPATH, path);
6963         free(path);
6964         return TRUE;
6965     }
6966 
6967     cons_bad_cmd_usage(command);
6968     return TRUE;
6969 }
6970 
6971 gboolean
cmd_plugins_install(ProfWin * window,const char * const command,gchar ** args)6972 cmd_plugins_install(ProfWin* window, const char* const command, gchar** args)
6973 {
6974     char* path;
6975 
6976     if (args[1] == NULL) {
6977         char* sourcepath = prefs_get_string(PREF_PLUGINS_SOURCEPATH);
6978         if (sourcepath) {
6979             path = strdup(sourcepath);
6980             g_free(sourcepath);
6981         } else {
6982             cons_show("Either a path must be provided or the sourcepath property must be set, see /help plugins");
6983             return TRUE;
6984         }
6985     } else {
6986         path = get_expanded_path(args[1]);
6987     }
6988 
6989     if (is_regular_file(path)) {
6990         if (!g_str_has_suffix(path, ".py") && !g_str_has_suffix(path, ".so")) {
6991             cons_show("Plugins must have one of the following extensions: '.py' '.so'");
6992             free(path);
6993             return TRUE;
6994         }
6995 
6996         GString* error_message = g_string_new(NULL);
6997         gchar* plugin_name = g_path_get_basename(path);
6998         gboolean result = plugins_install(plugin_name, path, error_message);
6999         if (result) {
7000             cons_show("Plugin installed: %s", plugin_name);
7001         } else {
7002             cons_show("Failed to install plugin: %s. %s", plugin_name, error_message->str);
7003         }
7004         g_free(plugin_name);
7005         g_string_free(error_message, TRUE);
7006         free(path);
7007         return TRUE;
7008     } else if (is_dir(path)) {
7009         PluginsInstallResult* result = plugins_install_all(path);
7010         if (result->installed || result->failed) {
7011             if (result->installed) {
7012                 cons_show("");
7013                 cons_show("Installed plugins:");
7014                 GSList* curr = result->installed;
7015                 while (curr) {
7016                     cons_show("  %s", curr->data);
7017                     curr = g_slist_next(curr);
7018                 }
7019             }
7020             if (result->failed) {
7021                 cons_show("");
7022                 cons_show("Failed installs:");
7023                 GSList* curr = result->failed;
7024                 while (curr) {
7025                     cons_show("  %s", curr->data);
7026                     curr = g_slist_next(curr);
7027                 }
7028             }
7029         } else {
7030             cons_show("No plugins found in: %s", path);
7031         }
7032         free(path);
7033         plugins_free_install_result(result);
7034         return TRUE;
7035     } else {
7036         cons_show("Argument must be a file or directory.");
7037     }
7038 
7039     free(path);
7040     return TRUE;
7041 }
7042 
7043 gboolean
cmd_plugins_update(ProfWin * window,const char * const command,gchar ** args)7044 cmd_plugins_update(ProfWin* window, const char* const command, gchar** args)
7045 {
7046     char* path;
7047 
7048     if (args[1] == NULL) {
7049         char* sourcepath = prefs_get_string(PREF_PLUGINS_SOURCEPATH);
7050         if (sourcepath) {
7051             path = strdup(sourcepath);
7052             g_free(sourcepath);
7053         } else {
7054             cons_show("Either a path must be provided or the sourcepath property must be set, see /help plugins");
7055             return TRUE;
7056         }
7057     } else {
7058         path = get_expanded_path(args[1]);
7059     }
7060 
7061     if (access(path, R_OK) != 0) {
7062         cons_show("File not found: %s", path);
7063         free(path);
7064         return TRUE;
7065     }
7066 
7067     if (is_regular_file(path)) {
7068         if (!g_str_has_suffix(path, ".py") && !g_str_has_suffix(path, ".so")) {
7069             cons_show("Plugins must have one of the following extensions: '.py' '.so'");
7070             free(path);
7071             return TRUE;
7072         }
7073 
7074         GString* error_message = g_string_new(NULL);
7075         gchar* plugin_name = g_path_get_basename(path);
7076         if (plugins_unload(plugin_name)) {
7077             if (plugins_uninstall(plugin_name)) {
7078                 if (plugins_install(plugin_name, path, error_message)) {
7079                     cons_show("Plugin installed: %s", plugin_name);
7080                 } else {
7081                     cons_show("Failed to install plugin: %s. %s", plugin_name, error_message->str);
7082                 }
7083             } else {
7084                 cons_show("Failed to uninstall plugin: %s.", plugin_name);
7085             }
7086         } else {
7087             cons_show("Failed to unload plugin: %s.", plugin_name);
7088         }
7089         g_free(plugin_name);
7090         g_string_free(error_message, TRUE);
7091         free(path);
7092         return TRUE;
7093     }
7094 
7095     if (is_dir(path)) {
7096         free(path);
7097         return FALSE;
7098     }
7099 
7100     free(path);
7101     cons_show("Argument must be a file or directory.");
7102     return TRUE;
7103 }
7104 
7105 gboolean
cmd_plugins_uninstall(ProfWin * window,const char * const command,gchar ** args)7106 cmd_plugins_uninstall(ProfWin* window, const char* const command, gchar** args)
7107 {
7108     if (args[1] == NULL) {
7109         return FALSE;
7110     }
7111 
7112     gboolean res = plugins_uninstall(args[1]);
7113     if (res) {
7114         cons_show("Uninstalled plugin: %s", args[1]);
7115     } else {
7116         cons_show("Failed to uninstall plugin: %s", args[1]);
7117     }
7118 
7119     return TRUE;
7120 }
7121 
7122 gboolean
cmd_plugins_load(ProfWin * window,const char * const command,gchar ** args)7123 cmd_plugins_load(ProfWin* window, const char* const command, gchar** args)
7124 {
7125     if (args[1] == NULL) {
7126         GSList* loaded = plugins_load_all();
7127         if (loaded) {
7128             cons_show("Loaded plugins:");
7129             GSList* curr = loaded;
7130             while (curr) {
7131                 cons_show("  %s", curr->data);
7132                 curr = g_slist_next(curr);
7133             }
7134             g_slist_free_full(loaded, g_free);
7135         } else {
7136             cons_show("No plugins loaded.");
7137         }
7138         return TRUE;
7139     }
7140 
7141     GString* error_message = g_string_new(NULL);
7142     gboolean res = plugins_load(args[1], error_message);
7143     if (res) {
7144         cons_show("Loaded plugin: %s", args[1]);
7145     } else {
7146         cons_show("Failed to load plugin: %s. %s", args[1], error_message->str);
7147     }
7148     g_string_free(error_message, TRUE);
7149 
7150     return TRUE;
7151 }
7152 
7153 gboolean
cmd_plugins_unload(ProfWin * window,const char * const command,gchar ** args)7154 cmd_plugins_unload(ProfWin* window, const char* const command, gchar** args)
7155 {
7156     if (args[1] == NULL) {
7157         gboolean res = plugins_unload_all();
7158         if (res) {
7159             cons_show("Unloaded all plugins.");
7160         } else {
7161             cons_show("No plugins unloaded.");
7162         }
7163         return TRUE;
7164     }
7165 
7166     gboolean res = plugins_unload(args[1]);
7167     if (res) {
7168         cons_show("Unloaded plugin: %s", args[1]);
7169     } else {
7170         cons_show("Failed to unload plugin: %s", args[1]);
7171     }
7172 
7173     return TRUE;
7174 }
7175 
7176 gboolean
cmd_plugins_reload(ProfWin * window,const char * const command,gchar ** args)7177 cmd_plugins_reload(ProfWin* window, const char* const command, gchar** args)
7178 {
7179     if (args[1] == NULL) {
7180         plugins_reload_all();
7181         cons_show("Reloaded all plugins");
7182         return TRUE;
7183     }
7184 
7185     GString* error_message = g_string_new(NULL);
7186     gboolean res = plugins_reload(args[1], error_message);
7187     if (res) {
7188         cons_show("Reloaded plugin: %s", args[1]);
7189     } else {
7190         cons_show("Failed to reload plugin: %s, %s", args[1], error_message);
7191     }
7192     g_string_free(error_message, TRUE);
7193 
7194     return TRUE;
7195 }
7196 
7197 gboolean
cmd_plugins_python_version(ProfWin * window,const char * const command,gchar ** args)7198 cmd_plugins_python_version(ProfWin* window, const char* const command, gchar** args)
7199 {
7200 #ifdef HAVE_PYTHON
7201     const char* version = python_get_version_string();
7202     cons_show("Python version:");
7203     cons_show("%s", version);
7204 #else
7205     cons_show("This build does not support python plugins.");
7206 #endif
7207     return TRUE;
7208 }
7209 
7210 gboolean
cmd_plugins(ProfWin * window,const char * const command,gchar ** args)7211 cmd_plugins(ProfWin* window, const char* const command, gchar** args)
7212 {
7213     GList* plugins = plugins_loaded_list();
7214     if (plugins == NULL) {
7215         cons_show("No plugins installed.");
7216         return TRUE;
7217     }
7218 
7219     GList* curr = plugins;
7220     cons_show("Installed plugins:");
7221     while (curr) {
7222         cons_show("  %s", curr->data);
7223         curr = g_list_next(curr);
7224     }
7225     g_list_free(plugins);
7226 
7227     return TRUE;
7228 }
7229 
7230 gboolean
cmd_pgp(ProfWin * window,const char * const command,gchar ** args)7231 cmd_pgp(ProfWin* window, const char* const command, gchar** args)
7232 {
7233 #ifdef HAVE_LIBGPGME
7234     if (args[0] == NULL) {
7235         cons_bad_cmd_usage(command);
7236         return TRUE;
7237     }
7238 
7239     if (strcmp(args[0], "char") == 0) {
7240         if (args[1] == NULL) {
7241             cons_bad_cmd_usage(command);
7242             return TRUE;
7243         } else if (g_utf8_strlen(args[1], 4) == 1) {
7244             if (prefs_set_pgp_char(args[1])) {
7245                 cons_show("PGP char set to %s.", args[1]);
7246             } else {
7247                 cons_show_error("Could not set PGP char: %s.", args[1]);
7248             }
7249             return TRUE;
7250         }
7251         cons_bad_cmd_usage(command);
7252         return TRUE;
7253     } else if (g_strcmp0(args[0], "log") == 0) {
7254         char* choice = args[1];
7255         if (g_strcmp0(choice, "on") == 0) {
7256             prefs_set_string(PREF_PGP_LOG, "on");
7257             cons_show("PGP messages will be logged as plaintext.");
7258             if (!prefs_get_boolean(PREF_CHLOG)) {
7259                 cons_show("Chat logging is currently disabled, use '/logging chat on' to enable.");
7260             }
7261         } else if (g_strcmp0(choice, "off") == 0) {
7262             prefs_set_string(PREF_PGP_LOG, "off");
7263             cons_show("PGP message logging disabled.");
7264         } else if (g_strcmp0(choice, "redact") == 0) {
7265             prefs_set_string(PREF_PGP_LOG, "redact");
7266             cons_show("PGP messages will be logged as '[redacted]'.");
7267             if (!prefs_get_boolean(PREF_CHLOG)) {
7268                 cons_show("Chat logging is currently disabled, use '/logging chat on' to enable.");
7269             }
7270         } else {
7271             cons_bad_cmd_usage(command);
7272         }
7273         return TRUE;
7274     }
7275 
7276     if (g_strcmp0(args[0], "keys") == 0) {
7277         GHashTable* keys = p_gpg_list_keys();
7278         if (!keys || g_hash_table_size(keys) == 0) {
7279             cons_show("No keys found");
7280             return TRUE;
7281         }
7282 
7283         cons_show("PGP keys:");
7284         GList* keylist = g_hash_table_get_keys(keys);
7285         GList* curr = keylist;
7286         while (curr) {
7287             ProfPGPKey* key = g_hash_table_lookup(keys, curr->data);
7288             cons_show("  %s", key->name);
7289             cons_show("    ID          : %s", key->id);
7290             char* format_fp = p_gpg_format_fp_str(key->fp);
7291             cons_show("    Fingerprint : %s", format_fp);
7292             free(format_fp);
7293             if (key->secret) {
7294                 cons_show("    Type        : PUBLIC, PRIVATE");
7295             } else {
7296                 cons_show("    Type        : PUBLIC");
7297             }
7298             curr = g_list_next(curr);
7299         }
7300         g_list_free(keylist);
7301         p_gpg_free_keys(keys);
7302         return TRUE;
7303     }
7304 
7305     if (g_strcmp0(args[0], "setkey") == 0) {
7306         jabber_conn_status_t conn_status = connection_get_status();
7307         if (conn_status != JABBER_CONNECTED) {
7308             cons_show("You are not currently connected.");
7309             return TRUE;
7310         }
7311 
7312         char* jid = args[1];
7313         if (!args[1]) {
7314             cons_bad_cmd_usage(command);
7315             return TRUE;
7316         }
7317 
7318         char* keyid = args[2];
7319         if (!args[2]) {
7320             cons_bad_cmd_usage(command);
7321             return TRUE;
7322         }
7323 
7324         gboolean res = p_gpg_addkey(jid, keyid);
7325         if (!res) {
7326             cons_show("Key ID not found.");
7327         } else {
7328             cons_show("Key %s set for %s.", keyid, jid);
7329         }
7330 
7331         return TRUE;
7332     }
7333 
7334     if (g_strcmp0(args[0], "contacts") == 0) {
7335         jabber_conn_status_t conn_status = connection_get_status();
7336         if (conn_status != JABBER_CONNECTED) {
7337             cons_show("You are not currently connected.");
7338             return TRUE;
7339         }
7340         GHashTable* pubkeys = p_gpg_pubkeys();
7341         GList* jids = g_hash_table_get_keys(pubkeys);
7342         if (!jids) {
7343             cons_show("No contacts found with PGP public keys assigned.");
7344             return TRUE;
7345         }
7346 
7347         cons_show("Assigned PGP public keys:");
7348         GList* curr = jids;
7349         while (curr) {
7350             char* jid = curr->data;
7351             ProfPGPPubKeyId* pubkeyid = g_hash_table_lookup(pubkeys, jid);
7352             if (pubkeyid->received) {
7353                 cons_show("  %s: %s (received)", jid, pubkeyid->id);
7354             } else {
7355                 cons_show("  %s: %s (stored)", jid, pubkeyid->id);
7356             }
7357             curr = g_list_next(curr);
7358         }
7359         g_list_free(jids);
7360         return TRUE;
7361     }
7362 
7363     if (g_strcmp0(args[0], "libver") == 0) {
7364         const char* libver = p_gpg_libver();
7365         if (!libver) {
7366             cons_show("Could not get libgpgme version");
7367             return TRUE;
7368         }
7369 
7370         GString* fullstr = g_string_new("Using libgpgme version ");
7371         g_string_append(fullstr, libver);
7372         cons_show("%s", fullstr->str);
7373         g_string_free(fullstr, TRUE);
7374 
7375         return TRUE;
7376     }
7377 
7378     if (g_strcmp0(args[0], "start") == 0) {
7379         jabber_conn_status_t conn_status = connection_get_status();
7380         if (conn_status != JABBER_CONNECTED) {
7381             cons_show("You must be connected to start PGP encrpytion.");
7382             return TRUE;
7383         }
7384 
7385         if (window->type != WIN_CHAT && args[1] == NULL) {
7386             cons_show("You must be in a regular chat window to start PGP encrpytion.");
7387             return TRUE;
7388         }
7389 
7390         ProfChatWin* chatwin = NULL;
7391 
7392         if (args[1]) {
7393             char* contact = args[1];
7394             char* barejid = roster_barejid_from_name(contact);
7395             if (barejid == NULL) {
7396                 barejid = contact;
7397             }
7398 
7399             chatwin = wins_get_chat(barejid);
7400             if (!chatwin) {
7401                 chatwin = chatwin_new(barejid);
7402             }
7403             ui_focus_win((ProfWin*)chatwin);
7404         } else {
7405             chatwin = (ProfChatWin*)window;
7406             assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
7407         }
7408 
7409         if (chatwin->is_otr) {
7410             win_println(window, THEME_DEFAULT, "!", "You must end the OTR session to start PGP encryption.");
7411             return TRUE;
7412         }
7413 
7414         if (chatwin->pgp_send) {
7415             win_println(window, THEME_DEFAULT, "!", "You have already started PGP encryption.");
7416             return TRUE;
7417         }
7418 
7419         if (chatwin->is_omemo) {
7420             win_println(window, THEME_DEFAULT, "!", "You must disable OMEMO before starting an PGP encrypted session.");
7421             return TRUE;
7422         }
7423 
7424         ProfAccount* account = accounts_get_account(session_get_account_name());
7425         char* err_str = NULL;
7426         if (!p_gpg_valid_key(account->pgp_keyid, &err_str)) {
7427             win_println(window, THEME_DEFAULT, "!", "Invalid PGP key ID %s: %s, cannot start PGP encryption.", account->pgp_keyid, err_str);
7428             free(err_str);
7429             account_free(account);
7430             return TRUE;
7431         }
7432         free(err_str);
7433         account_free(account);
7434 
7435         if (!p_gpg_available(chatwin->barejid)) {
7436             win_println(window, THEME_DEFAULT, "!", "No PGP key found for %s.", chatwin->barejid);
7437             return TRUE;
7438         }
7439 
7440         chatwin->pgp_send = TRUE;
7441         win_println(window, THEME_DEFAULT, "!", "PGP encryption enabled.");
7442         return TRUE;
7443     }
7444 
7445     if (g_strcmp0(args[0], "end") == 0) {
7446         jabber_conn_status_t conn_status = connection_get_status();
7447         if (conn_status != JABBER_CONNECTED) {
7448             cons_show("You are not currently connected.");
7449             return TRUE;
7450         }
7451 
7452         if (window->type != WIN_CHAT) {
7453             cons_show("You must be in a regular chat window to end PGP encrpytion.");
7454             return TRUE;
7455         }
7456 
7457         ProfChatWin* chatwin = (ProfChatWin*)window;
7458         if (chatwin->pgp_send == FALSE) {
7459             win_println(window, THEME_DEFAULT, "!", "PGP encryption is not currently enabled.");
7460             return TRUE;
7461         }
7462 
7463         chatwin->pgp_send = FALSE;
7464         win_println(window, THEME_DEFAULT, "!", "PGP encryption disabled.");
7465         return TRUE;
7466     }
7467 
7468     if (g_strcmp0(args[0], "sendfile") == 0) {
7469         _cmd_set_boolean_preference(args[1], command, "Sending unencrypted files using /sendfile while otherwise using PGP", PREF_PGP_SENDFILE);
7470         return TRUE;
7471     }
7472 
7473     cons_bad_cmd_usage(command);
7474     return TRUE;
7475 #else
7476     cons_show("This version of Profanity has not been built with PGP support enabled");
7477     return TRUE;
7478 #endif
7479 }
7480 
7481 #ifdef HAVE_LIBGPGME
7482 
7483 /*!
7484  * \brief Command for XEP-0373: OpenPGP for XMPP
7485  *
7486  */
7487 
7488 gboolean
cmd_ox(ProfWin * window,const char * const command,gchar ** args)7489 cmd_ox(ProfWin* window, const char* const command, gchar** args)
7490 {
7491     if (args[0] == NULL) {
7492         cons_bad_cmd_usage(command);
7493         return TRUE;
7494     }
7495 
7496     if (strcmp(args[0], "char") == 0) {
7497         if (args[1] == NULL) {
7498             cons_bad_cmd_usage(command);
7499             return TRUE;
7500         } else if (g_utf8_strlen(args[1], 4) == 1) {
7501             if (prefs_set_ox_char(args[1])) {
7502                 cons_show("OX char set to %s.", args[1]);
7503             } else {
7504                 cons_show_error("Could not set OX char: %s.", args[1]);
7505             }
7506             return TRUE;
7507         }
7508         cons_bad_cmd_usage(command);
7509         return TRUE;
7510     }
7511 
7512     // The '/ox keys' command - same like in pgp
7513     // Should we move this to a common command
7514     // e.g. '/openpgp keys'?.
7515     else if (g_strcmp0(args[0], "keys") == 0) {
7516         GHashTable* keys = p_gpg_list_keys();
7517         if (!keys || g_hash_table_size(keys) == 0) {
7518             cons_show("No keys found");
7519             return TRUE;
7520         }
7521 
7522         cons_show("OpenPGP keys:");
7523         GList* keylist = g_hash_table_get_keys(keys);
7524         GList* curr = keylist;
7525         while (curr) {
7526             ProfPGPKey* key = g_hash_table_lookup(keys, curr->data);
7527             cons_show("  %s", key->name);
7528             cons_show("    ID          : %s", key->id);
7529             char* format_fp = p_gpg_format_fp_str(key->fp);
7530             cons_show("    Fingerprint : %s", format_fp);
7531             free(format_fp);
7532             if (key->secret) {
7533                 cons_show("    Type        : PUBLIC, PRIVATE");
7534             } else {
7535                 cons_show("    Type        : PUBLIC");
7536             }
7537             curr = g_list_next(curr);
7538         }
7539         g_list_free(keylist);
7540         p_gpg_free_keys(keys);
7541         return TRUE;
7542     }
7543 
7544     else if (g_strcmp0(args[0], "contacts") == 0) {
7545         GHashTable* keys = ox_gpg_public_keys();
7546         cons_show("OpenPGP keys:");
7547         GList* keylist = g_hash_table_get_keys(keys);
7548         GList* curr = keylist;
7549 
7550         GSList* roster_list = NULL;
7551         jabber_conn_status_t conn_status = connection_get_status();
7552         if (conn_status != JABBER_CONNECTED) {
7553             cons_show("You are not currently connected.");
7554         } else {
7555             roster_list = roster_get_contacts(ROSTER_ORD_NAME);
7556         }
7557 
7558         while (curr) {
7559             ProfPGPKey* key = g_hash_table_lookup(keys, curr->data);
7560             PContact contact = NULL;
7561             if (roster_list) {
7562                 GSList* curr_c = roster_list;
7563                 while (!contact && curr_c) {
7564                     contact = curr_c->data;
7565                     const char* jid = p_contact_barejid(contact);
7566                     GString* xmppuri = g_string_new("xmpp:");
7567                     g_string_append(xmppuri, jid);
7568                     if (g_strcmp0(key->name, xmppuri->str)) {
7569                         contact = NULL;
7570                     }
7571                     curr_c = g_slist_next(curr_c);
7572                 }
7573             }
7574 
7575             if (contact) {
7576                 cons_show("%s - %s", key->fp, key->name);
7577             } else {
7578                 cons_show("%s - %s (not in roster)", key->fp, key->name);
7579             }
7580             curr = g_list_next(curr);
7581         }
7582 
7583     } else if (g_strcmp0(args[0], "start") == 0) {
7584         jabber_conn_status_t conn_status = connection_get_status();
7585         if (conn_status != JABBER_CONNECTED) {
7586             cons_show("You must be connected to start OX encrpytion.");
7587             return TRUE;
7588         }
7589 
7590         if (window->type != WIN_CHAT && args[1] == NULL) {
7591             cons_show("You must be in a regular chat window to start OX encrpytion.");
7592             return TRUE;
7593         }
7594 
7595         ProfChatWin* chatwin = NULL;
7596 
7597         if (args[1]) {
7598             char* contact = args[1];
7599             char* barejid = roster_barejid_from_name(contact);
7600             if (barejid == NULL) {
7601                 barejid = contact;
7602             }
7603 
7604             chatwin = wins_get_chat(barejid);
7605             if (!chatwin) {
7606                 chatwin = chatwin_new(barejid);
7607             }
7608             ui_focus_win((ProfWin*)chatwin);
7609         } else {
7610             chatwin = (ProfChatWin*)window;
7611             assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
7612         }
7613 
7614         if (chatwin->is_otr) {
7615             win_println(window, THEME_DEFAULT, "!", "You must end the OTR session to start OX encryption.");
7616             return TRUE;
7617         }
7618 
7619         if (chatwin->pgp_send) {
7620             win_println(window, THEME_DEFAULT, "!", "You must end the PGP session to start OX encryption.");
7621             return TRUE;
7622         }
7623 
7624         if (chatwin->is_ox) {
7625             win_println(window, THEME_DEFAULT, "!", "You have already started OX encryption.");
7626             return TRUE;
7627         }
7628 
7629         ProfAccount* account = accounts_get_account(session_get_account_name());
7630 
7631         if (!ox_is_private_key_available(account->jid)) {
7632             win_println(window, THEME_DEFAULT, "!", "No private OpenPGP found, cannot start OX encryption.");
7633             account_free(account);
7634             return TRUE;
7635         }
7636         account_free(account);
7637 
7638         if (!ox_is_public_key_available(chatwin->barejid)) {
7639             win_println(window, THEME_DEFAULT, "!", "No OX-OpenPGP key found for %s.", chatwin->barejid);
7640             return TRUE;
7641         }
7642 
7643         chatwin->is_ox = TRUE;
7644         win_println(window, THEME_DEFAULT, "!", "OX encryption enabled.");
7645         return TRUE;
7646     } else if (g_strcmp0(args[0], "announce") == 0) {
7647         if (args[1]) {
7648             ox_announce_public_key(args[1]);
7649         } else {
7650             cons_show("Filename is required");
7651         }
7652     } else if (g_strcmp0(args[0], "discover") == 0) {
7653         if (args[1]) {
7654             ox_discover_public_key(args[1]);
7655         } else {
7656             cons_show("To discover the OpenPGP keys of an user, the JID is required");
7657         }
7658     } else if (g_strcmp0(args[0], "request") == 0) {
7659         if (args[1] && args[2]) {
7660             ox_request_public_key(args[1], args[2]);
7661         } else {
7662             cons_show("JID and OpenPGP Key ID are required");
7663         }
7664     } else {
7665         cons_show("OX not implemented");
7666     }
7667     return TRUE;
7668 }
7669 #endif // HAVE_LIBGPGME
7670 
7671 gboolean
cmd_otr_char(ProfWin * window,const char * const command,gchar ** args)7672 cmd_otr_char(ProfWin* window, const char* const command, gchar** args)
7673 {
7674 #ifdef HAVE_LIBOTR
7675     if (args[1] == NULL) {
7676         cons_bad_cmd_usage(command);
7677         return TRUE;
7678     } else if (g_utf8_strlen(args[1], 4) == 1) {
7679         if (prefs_set_otr_char(args[1])) {
7680             cons_show("OTR char set to %s.", args[1]);
7681         } else {
7682             cons_show_error("Could not set OTR char: %s.", args[1]);
7683         }
7684         return TRUE;
7685     }
7686     cons_bad_cmd_usage(command);
7687 #else
7688     cons_show("This version of Profanity has not been built with OTR support enabled");
7689 #endif
7690     return TRUE;
7691 }
7692 
7693 gboolean
cmd_otr_log(ProfWin * window,const char * const command,gchar ** args)7694 cmd_otr_log(ProfWin* window, const char* const command, gchar** args)
7695 {
7696 #ifdef HAVE_LIBOTR
7697     char* choice = args[1];
7698     if (g_strcmp0(choice, "on") == 0) {
7699         prefs_set_string(PREF_OTR_LOG, "on");
7700         cons_show("OTR messages will be logged as plaintext.");
7701         if (!prefs_get_boolean(PREF_CHLOG)) {
7702             cons_show("Chat logging is currently disabled, use '/logging chat on' to enable.");
7703         }
7704     } else if (g_strcmp0(choice, "off") == 0) {
7705         prefs_set_string(PREF_OTR_LOG, "off");
7706         cons_show("OTR message logging disabled.");
7707     } else if (g_strcmp0(choice, "redact") == 0) {
7708         prefs_set_string(PREF_OTR_LOG, "redact");
7709         cons_show("OTR messages will be logged as '[redacted]'.");
7710         if (!prefs_get_boolean(PREF_CHLOG)) {
7711             cons_show("Chat logging is currently disabled, use '/logging chat on' to enable.");
7712         }
7713     } else {
7714         cons_bad_cmd_usage(command);
7715     }
7716     return TRUE;
7717 #else
7718     cons_show("This version of Profanity has not been built with OTR support enabled");
7719     return TRUE;
7720 #endif
7721 }
7722 
7723 gboolean
cmd_otr_libver(ProfWin * window,const char * const command,gchar ** args)7724 cmd_otr_libver(ProfWin* window, const char* const command, gchar** args)
7725 {
7726 #ifdef HAVE_LIBOTR
7727     char* version = otr_libotr_version();
7728     cons_show("Using libotr version %s", version);
7729     return TRUE;
7730 #else
7731     cons_show("This version of Profanity has not been built with OTR support enabled");
7732     return TRUE;
7733 #endif
7734 }
7735 
7736 gboolean
cmd_otr_policy(ProfWin * window,const char * const command,gchar ** args)7737 cmd_otr_policy(ProfWin* window, const char* const command, gchar** args)
7738 {
7739 #ifdef HAVE_LIBOTR
7740     if (args[1] == NULL) {
7741         char* policy = prefs_get_string(PREF_OTR_POLICY);
7742         cons_show("OTR policy is now set to: %s", policy);
7743         g_free(policy);
7744         return TRUE;
7745     }
7746 
7747     char* choice = args[1];
7748     if ((g_strcmp0(choice, "manual") != 0) && (g_strcmp0(choice, "opportunistic") != 0) && (g_strcmp0(choice, "always") != 0)) {
7749         cons_show("OTR policy can be set to: manual, opportunistic or always.");
7750         return TRUE;
7751     }
7752 
7753     char* contact = args[2];
7754     if (contact == NULL) {
7755         prefs_set_string(PREF_OTR_POLICY, choice);
7756         cons_show("OTR policy is now set to: %s", choice);
7757         return TRUE;
7758     }
7759 
7760     if (connection_get_status() != JABBER_CONNECTED) {
7761         cons_show("You must be connected to set the OTR policy for a contact.");
7762         return TRUE;
7763     }
7764 
7765     char* contact_jid = roster_barejid_from_name(contact);
7766     if (contact_jid == NULL) {
7767         contact_jid = contact;
7768     }
7769     accounts_add_otr_policy(session_get_account_name(), contact_jid, choice);
7770     cons_show("OTR policy for %s set to: %s", contact_jid, choice);
7771     return TRUE;
7772 #else
7773     cons_show("This version of Profanity has not been built with OTR support enabled");
7774     return TRUE;
7775 #endif
7776 }
7777 
7778 gboolean
cmd_otr_gen(ProfWin * window,const char * const command,gchar ** args)7779 cmd_otr_gen(ProfWin* window, const char* const command, gchar** args)
7780 {
7781 #ifdef HAVE_LIBOTR
7782     if (connection_get_status() != JABBER_CONNECTED) {
7783         cons_show("You must be connected with an account to load OTR information.");
7784         return TRUE;
7785     }
7786 
7787     ProfAccount* account = accounts_get_account(session_get_account_name());
7788     otr_keygen(account);
7789     account_free(account);
7790     return TRUE;
7791 #else
7792     cons_show("This version of Profanity has not been built with OTR support enabled");
7793     return TRUE;
7794 #endif
7795 }
7796 
7797 gboolean
cmd_otr_myfp(ProfWin * window,const char * const command,gchar ** args)7798 cmd_otr_myfp(ProfWin* window, const char* const command, gchar** args)
7799 {
7800 #ifdef HAVE_LIBOTR
7801     if (connection_get_status() != JABBER_CONNECTED) {
7802         cons_show("You must be connected with an account to load OTR information.");
7803         return TRUE;
7804     }
7805 
7806     if (!otr_key_loaded()) {
7807         win_println(window, THEME_DEFAULT, "!", "You have not generated or loaded a private key, use '/otr gen'");
7808         return TRUE;
7809     }
7810 
7811     char* fingerprint = otr_get_my_fingerprint();
7812     win_println(window, THEME_DEFAULT, "!", "Your OTR fingerprint: %s", fingerprint);
7813     free(fingerprint);
7814     return TRUE;
7815 #else
7816     cons_show("This version of Profanity has not been built with OTR support enabled");
7817     return TRUE;
7818 #endif
7819 }
7820 
7821 gboolean
cmd_otr_theirfp(ProfWin * window,const char * const command,gchar ** args)7822 cmd_otr_theirfp(ProfWin* window, const char* const command, gchar** args)
7823 {
7824 #ifdef HAVE_LIBOTR
7825     if (connection_get_status() != JABBER_CONNECTED) {
7826         cons_show("You must be connected with an account to load OTR information.");
7827         return TRUE;
7828     }
7829 
7830     if (window->type != WIN_CHAT) {
7831         win_println(window, THEME_DEFAULT, "-", "You must be in a regular chat window to view a recipient's fingerprint.");
7832         return TRUE;
7833     }
7834 
7835     ProfChatWin* chatwin = (ProfChatWin*)window;
7836     assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
7837     if (chatwin->is_otr == FALSE) {
7838         win_println(window, THEME_DEFAULT, "!", "You are not currently in an OTR session.");
7839         return TRUE;
7840     }
7841 
7842     char* fingerprint = otr_get_their_fingerprint(chatwin->barejid);
7843     win_println(window, THEME_DEFAULT, "!", "%s's OTR fingerprint: %s", chatwin->barejid, fingerprint);
7844     free(fingerprint);
7845     return TRUE;
7846 #else
7847     cons_show("This version of Profanity has not been built with OTR support enabled");
7848     return TRUE;
7849 #endif
7850 }
7851 
7852 gboolean
cmd_otr_start(ProfWin * window,const char * const command,gchar ** args)7853 cmd_otr_start(ProfWin* window, const char* const command, gchar** args)
7854 {
7855 #ifdef HAVE_LIBOTR
7856     if (connection_get_status() != JABBER_CONNECTED) {
7857         cons_show("You must be connected with an account to load OTR information.");
7858         return TRUE;
7859     }
7860 
7861     // recipient supplied
7862     if (args[1]) {
7863         char* contact = args[1];
7864         char* barejid = roster_barejid_from_name(contact);
7865         if (barejid == NULL) {
7866             barejid = contact;
7867         }
7868 
7869         ProfChatWin* chatwin = wins_get_chat(barejid);
7870         if (!chatwin) {
7871             chatwin = chatwin_new(barejid);
7872         }
7873         ui_focus_win((ProfWin*)chatwin);
7874 
7875         if (chatwin->pgp_send) {
7876             win_println(window, THEME_DEFAULT, "!", "You must disable PGP encryption before starting an OTR session.");
7877             return TRUE;
7878         }
7879 
7880         if (chatwin->is_omemo) {
7881             win_println(window, THEME_DEFAULT, "!", "You must disable OMEMO before starting an OTR session.");
7882             return TRUE;
7883         }
7884 
7885         if (chatwin->is_otr) {
7886             win_println(window, THEME_DEFAULT, "!", "You are already in an OTR session.");
7887             return TRUE;
7888         }
7889 
7890         if (!otr_key_loaded()) {
7891             win_println(window, THEME_DEFAULT, "!", "You have not generated or loaded a private key, use '/otr gen'");
7892             return TRUE;
7893         }
7894 
7895         if (!otr_is_secure(barejid)) {
7896             char* otr_query_message = otr_start_query();
7897             char* id = message_send_chat_otr(barejid, otr_query_message, FALSE, NULL);
7898             free(id);
7899             return TRUE;
7900         }
7901 
7902         chatwin_otr_secured(chatwin, otr_is_trusted(barejid));
7903         return TRUE;
7904 
7905         // no recipient, use current chat
7906     } else {
7907         if (window->type != WIN_CHAT) {
7908             win_println(window, THEME_DEFAULT, "-", "You must be in a regular chat window to start an OTR session.");
7909             return TRUE;
7910         }
7911 
7912         ProfChatWin* chatwin = (ProfChatWin*)window;
7913         assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
7914         if (chatwin->pgp_send) {
7915             win_println(window, THEME_DEFAULT, "!", "You must disable PGP encryption before starting an OTR session.");
7916             return TRUE;
7917         }
7918 
7919         if (chatwin->is_otr) {
7920             win_println(window, THEME_DEFAULT, "!", "You are already in an OTR session.");
7921             return TRUE;
7922         }
7923 
7924         if (!otr_key_loaded()) {
7925             win_println(window, THEME_DEFAULT, "!", "You have not generated or loaded a private key, use '/otr gen'");
7926             return TRUE;
7927         }
7928 
7929         char* otr_query_message = otr_start_query();
7930         char* id = message_send_chat_otr(chatwin->barejid, otr_query_message, FALSE, NULL);
7931 
7932         free(id);
7933         return TRUE;
7934     }
7935 #else
7936     cons_show("This version of Profanity has not been built with OTR support enabled");
7937     return TRUE;
7938 #endif
7939 }
7940 
7941 gboolean
cmd_otr_end(ProfWin * window,const char * const command,gchar ** args)7942 cmd_otr_end(ProfWin* window, const char* const command, gchar** args)
7943 {
7944 #ifdef HAVE_LIBOTR
7945     if (connection_get_status() != JABBER_CONNECTED) {
7946         cons_show("You must be connected with an account to load OTR information.");
7947         return TRUE;
7948     }
7949 
7950     if (window->type != WIN_CHAT) {
7951         win_println(window, THEME_DEFAULT, "-", "You must be in a regular chat window to use OTR.");
7952         return TRUE;
7953     }
7954 
7955     ProfChatWin* chatwin = (ProfChatWin*)window;
7956     assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
7957     if (chatwin->is_otr == FALSE) {
7958         win_println(window, THEME_DEFAULT, "!", "You are not currently in an OTR session.");
7959         return TRUE;
7960     }
7961 
7962     chatwin_otr_unsecured(chatwin);
7963     otr_end_session(chatwin->barejid);
7964     return TRUE;
7965 #else
7966     cons_show("This version of Profanity has not been built with OTR support enabled");
7967     return TRUE;
7968 #endif
7969 }
7970 
7971 gboolean
cmd_otr_trust(ProfWin * window,const char * const command,gchar ** args)7972 cmd_otr_trust(ProfWin* window, const char* const command, gchar** args)
7973 {
7974 #ifdef HAVE_LIBOTR
7975     if (connection_get_status() != JABBER_CONNECTED) {
7976         cons_show("You must be connected with an account to load OTR information.");
7977         return TRUE;
7978     }
7979 
7980     if (window->type != WIN_CHAT) {
7981         win_println(window, THEME_DEFAULT, "-", "You must be in an OTR session to trust a recipient.");
7982         return TRUE;
7983     }
7984 
7985     ProfChatWin* chatwin = (ProfChatWin*)window;
7986     assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
7987     if (chatwin->is_otr == FALSE) {
7988         win_println(window, THEME_DEFAULT, "!", "You are not currently in an OTR session.");
7989         return TRUE;
7990     }
7991 
7992     chatwin_otr_trust(chatwin);
7993     otr_trust(chatwin->barejid);
7994     return TRUE;
7995 #else
7996     cons_show("This version of Profanity has not been built with OTR support enabled");
7997     return TRUE;
7998 #endif
7999 }
8000 
8001 gboolean
cmd_otr_untrust(ProfWin * window,const char * const command,gchar ** args)8002 cmd_otr_untrust(ProfWin* window, const char* const command, gchar** args)
8003 {
8004 #ifdef HAVE_LIBOTR
8005     if (connection_get_status() != JABBER_CONNECTED) {
8006         cons_show("You must be connected with an account to load OTR information.");
8007         return TRUE;
8008     }
8009 
8010     if (window->type != WIN_CHAT) {
8011         win_println(window, THEME_DEFAULT, "-", "You must be in an OTR session to untrust a recipient.");
8012         return TRUE;
8013     }
8014 
8015     ProfChatWin* chatwin = (ProfChatWin*)window;
8016     assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
8017     if (chatwin->is_otr == FALSE) {
8018         win_println(window, THEME_DEFAULT, "!", "You are not currently in an OTR session.");
8019         return TRUE;
8020     }
8021 
8022     chatwin_otr_untrust(chatwin);
8023     otr_untrust(chatwin->barejid);
8024     return TRUE;
8025 #else
8026     cons_show("This version of Profanity has not been built with OTR support enabled");
8027     return TRUE;
8028 #endif
8029 }
8030 
8031 gboolean
cmd_otr_secret(ProfWin * window,const char * const command,gchar ** args)8032 cmd_otr_secret(ProfWin* window, const char* const command, gchar** args)
8033 {
8034 #ifdef HAVE_LIBOTR
8035     if (connection_get_status() != JABBER_CONNECTED) {
8036         cons_show("You must be connected with an account to load OTR information.");
8037         return TRUE;
8038     }
8039 
8040     if (window->type != WIN_CHAT) {
8041         win_println(window, THEME_DEFAULT, "-", "You must be in an OTR session to trust a recipient.");
8042         return TRUE;
8043     }
8044 
8045     ProfChatWin* chatwin = (ProfChatWin*)window;
8046     assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
8047     if (chatwin->is_otr == FALSE) {
8048         win_println(window, THEME_DEFAULT, "!", "You are not currently in an OTR session.");
8049         return TRUE;
8050     }
8051 
8052     char* secret = args[1];
8053     if (secret == NULL) {
8054         cons_bad_cmd_usage(command);
8055         return TRUE;
8056     }
8057 
8058     otr_smp_secret(chatwin->barejid, secret);
8059     return TRUE;
8060 #else
8061     cons_show("This version of Profanity has not been built with OTR support enabled");
8062     return TRUE;
8063 #endif
8064 }
8065 
8066 gboolean
cmd_otr_question(ProfWin * window,const char * const command,gchar ** args)8067 cmd_otr_question(ProfWin* window, const char* const command, gchar** args)
8068 {
8069 #ifdef HAVE_LIBOTR
8070     if (connection_get_status() != JABBER_CONNECTED) {
8071         cons_show("You must be connected with an account to load OTR information.");
8072         return TRUE;
8073     }
8074 
8075     char* question = args[1];
8076     char* answer = args[2];
8077     if (question == NULL || answer == NULL) {
8078         cons_bad_cmd_usage(command);
8079         return TRUE;
8080     }
8081 
8082     if (window->type != WIN_CHAT) {
8083         win_println(window, THEME_DEFAULT, "-", "You must be in an OTR session to trust a recipient.");
8084         return TRUE;
8085     }
8086 
8087     ProfChatWin* chatwin = (ProfChatWin*)window;
8088     assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
8089     if (chatwin->is_otr == FALSE) {
8090         win_println(window, THEME_DEFAULT, "!", "You are not currently in an OTR session.");
8091         return TRUE;
8092     }
8093 
8094     otr_smp_question(chatwin->barejid, question, answer);
8095     return TRUE;
8096 #else
8097     cons_show("This version of Profanity has not been built with OTR support enabled");
8098     return TRUE;
8099 #endif
8100 }
8101 
8102 gboolean
cmd_otr_answer(ProfWin * window,const char * const command,gchar ** args)8103 cmd_otr_answer(ProfWin* window, const char* const command, gchar** args)
8104 {
8105 #ifdef HAVE_LIBOTR
8106     if (connection_get_status() != JABBER_CONNECTED) {
8107         cons_show("You must be connected with an account to load OTR information.");
8108         return TRUE;
8109     }
8110 
8111     if (window->type != WIN_CHAT) {
8112         win_println(window, THEME_DEFAULT, "-", "You must be in an OTR session to trust a recipient.");
8113         return TRUE;
8114     }
8115 
8116     ProfChatWin* chatwin = (ProfChatWin*)window;
8117     assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
8118     if (chatwin->is_otr == FALSE) {
8119         win_println(window, THEME_DEFAULT, "!", "You are not currently in an OTR session.");
8120         return TRUE;
8121     }
8122 
8123     char* answer = args[1];
8124     if (answer == NULL) {
8125         cons_bad_cmd_usage(command);
8126         return TRUE;
8127     }
8128 
8129     otr_smp_answer(chatwin->barejid, answer);
8130     return TRUE;
8131 #else
8132     cons_show("This version of Profanity has not been built with OTR support enabled");
8133     return TRUE;
8134 #endif
8135 }
8136 
8137 gboolean
cmd_otr_sendfile(ProfWin * window,const char * const command,gchar ** args)8138 cmd_otr_sendfile(ProfWin* window, const char* const command, gchar** args)
8139 {
8140 #ifdef HAVE_LIBOTR
8141     _cmd_set_boolean_preference(args[1], command, "Sending unencrypted files in an OTR session via /sendfile", PREF_OTR_SENDFILE);
8142 
8143     return TRUE;
8144 #else
8145     cons_show("This version of Profanity has not been built with OTR support enabled");
8146     return TRUE;
8147 #endif
8148 }
8149 
8150 gboolean
cmd_command_list(ProfWin * window,const char * const command,gchar ** args)8151 cmd_command_list(ProfWin* window, const char* const command, gchar** args)
8152 {
8153     jabber_conn_status_t conn_status = connection_get_status();
8154 
8155     if (conn_status != JABBER_CONNECTED) {
8156         cons_show("You are not currently connected.");
8157         return TRUE;
8158     }
8159 
8160     if (connection_supports(XMPP_FEATURE_COMMANDS) == FALSE) {
8161         cons_show("Server does not support ad hoc commands (%s).", XMPP_FEATURE_COMMANDS);
8162         return TRUE;
8163     }
8164 
8165     char* jid = args[1];
8166     if (jid == NULL) {
8167         switch (window->type) {
8168         case WIN_MUC:
8169         {
8170             ProfMucWin* mucwin = (ProfMucWin*)window;
8171             assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
8172             jid = mucwin->roomjid;
8173             break;
8174         }
8175         case WIN_CHAT:
8176         {
8177             ProfChatWin* chatwin = (ProfChatWin*)window;
8178             assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
8179             jid = chatwin->barejid;
8180             break;
8181         }
8182         case WIN_PRIVATE:
8183         {
8184             ProfPrivateWin* privatewin = (ProfPrivateWin*)window;
8185             assert(privatewin->memcheck == PROFPRIVATEWIN_MEMCHECK);
8186             jid = privatewin->fulljid;
8187             break;
8188         }
8189         case WIN_CONSOLE:
8190         {
8191             jid = connection_get_domain();
8192             break;
8193         }
8194         default:
8195             cons_show("Cannot send ad hoc commands.");
8196             return TRUE;
8197         }
8198     }
8199 
8200     iq_command_list(jid);
8201 
8202     cons_show("List available ad hoc commands");
8203     return TRUE;
8204 }
8205 
8206 gboolean
cmd_command_exec(ProfWin * window,const char * const command,gchar ** args)8207 cmd_command_exec(ProfWin* window, const char* const command, gchar** args)
8208 {
8209     jabber_conn_status_t conn_status = connection_get_status();
8210 
8211     if (conn_status != JABBER_CONNECTED) {
8212         cons_show("You are not currently connected.");
8213         return TRUE;
8214     }
8215 
8216     if (connection_supports(XMPP_FEATURE_COMMANDS) == FALSE) {
8217         cons_show("Server does not support ad hoc commands (%s).", XMPP_FEATURE_COMMANDS);
8218         return TRUE;
8219     }
8220 
8221     if (args[1] == NULL) {
8222         cons_bad_cmd_usage(command);
8223         return TRUE;
8224     }
8225 
8226     char* jid = args[2];
8227     if (jid == NULL) {
8228         switch (window->type) {
8229         case WIN_MUC:
8230         {
8231             ProfMucWin* mucwin = (ProfMucWin*)window;
8232             assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
8233             jid = mucwin->roomjid;
8234             break;
8235         }
8236         case WIN_CHAT:
8237         {
8238             ProfChatWin* chatwin = (ProfChatWin*)window;
8239             assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
8240             jid = chatwin->barejid;
8241             break;
8242         }
8243         case WIN_PRIVATE:
8244         {
8245             ProfPrivateWin* privatewin = (ProfPrivateWin*)window;
8246             assert(privatewin->memcheck == PROFPRIVATEWIN_MEMCHECK);
8247             jid = privatewin->fulljid;
8248             break;
8249         }
8250         case WIN_CONSOLE:
8251         {
8252             jid = connection_get_domain();
8253             break;
8254         }
8255         default:
8256             cons_show("Cannot send ad hoc commands.");
8257             return TRUE;
8258         }
8259     }
8260 
8261     iq_command_exec(jid, args[1]);
8262 
8263     cons_show("Execute %s...", args[1]);
8264     return TRUE;
8265 }
8266 
8267 static gboolean
_cmd_execute(ProfWin * window,const char * const command,const char * const inp)8268 _cmd_execute(ProfWin* window, const char* const command, const char* const inp)
8269 {
8270     if (g_str_has_prefix(command, "/field") && window->type == WIN_CONFIG) {
8271         gboolean result = FALSE;
8272         gchar** args = parse_args_with_freetext(inp, 1, 2, &result);
8273         if (!result) {
8274             win_println(window, THEME_DEFAULT, "!", "Invalid command, see /form help");
8275             result = TRUE;
8276         } else {
8277             gchar** tokens = g_strsplit(inp, " ", 2);
8278             char* field = tokens[0] + 1;
8279             result = cmd_form_field(window, field, args);
8280             g_strfreev(tokens);
8281         }
8282 
8283         g_strfreev(args);
8284         return result;
8285     }
8286 
8287     Command* cmd = cmd_get(command);
8288     gboolean result = FALSE;
8289 
8290     if (cmd) {
8291         gchar** args = cmd->parser(inp, cmd->min_args, cmd->max_args, &result);
8292         if (result == FALSE) {
8293             ui_invalid_command_usage(cmd->cmd, cmd->setting_func);
8294             return TRUE;
8295         }
8296         if (args[0] && cmd->sub_funcs[0][0]) {
8297             int i = 0;
8298             while (cmd->sub_funcs[i][0]) {
8299                 if (g_strcmp0(args[0], (char*)cmd->sub_funcs[i][0]) == 0) {
8300                     gboolean (*func)(ProfWin * window, const char* const command, gchar** args) = cmd->sub_funcs[i][1];
8301                     gboolean result = func(window, command, args);
8302                     g_strfreev(args);
8303                     return result;
8304                 }
8305                 i++;
8306             }
8307         }
8308         if (!cmd->func) {
8309             ui_invalid_command_usage(cmd->cmd, cmd->setting_func);
8310             return TRUE;
8311         }
8312         gboolean result = cmd->func(window, command, args);
8313         g_strfreev(args);
8314         return result;
8315     } else if (plugins_run_command(inp)) {
8316         return TRUE;
8317     } else {
8318         gboolean ran_alias = FALSE;
8319         gboolean alias_result = _cmd_execute_alias(window, inp, &ran_alias);
8320         if (!ran_alias) {
8321             return _cmd_execute_default(window, inp);
8322         } else {
8323             return alias_result;
8324         }
8325     }
8326 }
8327 
8328 static gboolean
_cmd_execute_default(ProfWin * window,const char * inp)8329 _cmd_execute_default(ProfWin* window, const char* inp)
8330 {
8331     // handle escaped commands - treat as normal message
8332     if (g_str_has_prefix(inp, "//")) {
8333         inp++;
8334 
8335         // handle unknown commands
8336     } else if ((inp[0] == '/') && (!g_str_has_prefix(inp, "/me "))) {
8337         cons_show("Unknown command: %s", inp);
8338         cons_alert(NULL);
8339         return TRUE;
8340     }
8341 
8342     // handle non commands in non chat or plugin windows
8343     if (window->type != WIN_CHAT && window->type != WIN_MUC && window->type != WIN_PRIVATE && window->type != WIN_PLUGIN && window->type != WIN_XML) {
8344         cons_show("Unknown command: %s", inp);
8345         cons_alert(NULL);
8346         return TRUE;
8347     }
8348 
8349     // handle plugin window
8350     if (window->type == WIN_PLUGIN) {
8351         ProfPluginWin* pluginwin = (ProfPluginWin*)window;
8352         plugins_win_process_line(pluginwin->tag, inp);
8353         return TRUE;
8354     }
8355 
8356     jabber_conn_status_t status = connection_get_status();
8357     if (status != JABBER_CONNECTED) {
8358         win_println(window, THEME_DEFAULT, "-", "You are not currently connected.");
8359         return TRUE;
8360     }
8361 
8362     switch (window->type) {
8363     case WIN_CHAT:
8364     {
8365         ProfChatWin* chatwin = (ProfChatWin*)window;
8366         assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
8367         cl_ev_send_msg(chatwin, inp, NULL);
8368         break;
8369     }
8370     case WIN_PRIVATE:
8371     {
8372         ProfPrivateWin* privatewin = (ProfPrivateWin*)window;
8373         assert(privatewin->memcheck == PROFPRIVATEWIN_MEMCHECK);
8374         cl_ev_send_priv_msg(privatewin, inp, NULL);
8375         break;
8376     }
8377     case WIN_MUC:
8378     {
8379         ProfMucWin* mucwin = (ProfMucWin*)window;
8380         assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
8381         cl_ev_send_muc_msg(mucwin, inp, NULL);
8382         break;
8383     }
8384     case WIN_XML:
8385     {
8386         connection_send_stanza(inp);
8387         break;
8388     }
8389     default:
8390         break;
8391     }
8392 
8393     return TRUE;
8394 }
8395 
8396 static gboolean
_cmd_execute_alias(ProfWin * window,const char * const inp,gboolean * ran)8397 _cmd_execute_alias(ProfWin* window, const char* const inp, gboolean* ran)
8398 {
8399     if (inp[0] != '/') {
8400         *ran = FALSE;
8401         return TRUE;
8402     }
8403 
8404     char* alias = strdup(inp + 1);
8405     char* value = prefs_get_alias(alias);
8406     free(alias);
8407     if (value) {
8408         *ran = TRUE;
8409         gboolean result = cmd_process_input(window, value);
8410         g_free(value);
8411         return result;
8412     }
8413 
8414     *ran = FALSE;
8415     return TRUE;
8416 }
8417 
8418 // helper function for status change commands
8419 static void
_update_presence(const resource_presence_t resource_presence,const char * const show,gchar ** args)8420 _update_presence(const resource_presence_t resource_presence,
8421                  const char* const show, gchar** args)
8422 {
8423     char* msg = NULL;
8424     int num_args = g_strv_length(args);
8425 
8426     // if no message, use status as message
8427     if (num_args == 2) {
8428         msg = args[1];
8429     } else {
8430         msg = args[2];
8431     }
8432 
8433     jabber_conn_status_t conn_status = connection_get_status();
8434 
8435     if (conn_status != JABBER_CONNECTED) {
8436         cons_show("You are not currently connected.");
8437     } else {
8438         connection_set_presence_msg(msg);
8439         cl_ev_presence_send(resource_presence, 0);
8440         ui_update_presence(resource_presence, msg, show);
8441     }
8442 }
8443 
8444 // helper function for boolean preference commands
8445 static void
_cmd_set_boolean_preference(gchar * arg,const char * const command,const char * const display,preference_t pref)8446 _cmd_set_boolean_preference(gchar* arg, const char* const command,
8447                             const char* const display, preference_t pref)
8448 {
8449     if (arg == NULL) {
8450         cons_bad_cmd_usage(command);
8451     } else if (strcmp(arg, "on") == 0) {
8452         GString* enabled = g_string_new(display);
8453         g_string_append(enabled, " enabled.");
8454 
8455         cons_show(enabled->str);
8456         prefs_set_boolean(pref, TRUE);
8457 
8458         g_string_free(enabled, TRUE);
8459     } else if (strcmp(arg, "off") == 0) {
8460         GString* disabled = g_string_new(display);
8461         g_string_append(disabled, " disabled.");
8462 
8463         cons_show(disabled->str);
8464         prefs_set_boolean(pref, FALSE);
8465 
8466         g_string_free(disabled, TRUE);
8467     } else {
8468         cons_bad_cmd_usage(command);
8469     }
8470 }
8471 
8472 gboolean
cmd_omemo_gen(ProfWin * window,const char * const command,gchar ** args)8473 cmd_omemo_gen(ProfWin* window, const char* const command, gchar** args)
8474 {
8475 #ifdef HAVE_OMEMO
8476     if (connection_get_status() != JABBER_CONNECTED) {
8477         cons_show("You must be connected with an account to initialize OMEMO.");
8478         return TRUE;
8479     }
8480 
8481     if (omemo_loaded()) {
8482         cons_show("OMEMO crytographic materials have already been generated.");
8483         return TRUE;
8484     }
8485 
8486     cons_show("Generating OMEMO crytographic materials, it may take a while...");
8487     ui_update();
8488     ProfAccount* account = accounts_get_account(session_get_account_name());
8489     omemo_generate_crypto_materials(account);
8490     cons_show("OMEMO crytographic materials generated. Your Device ID is %d.", omemo_device_id());
8491     return TRUE;
8492 #else
8493     cons_show("This version of Profanity has not been built with OMEMO support enabled");
8494     return TRUE;
8495 #endif
8496 }
8497 
8498 gboolean
cmd_omemo_start(ProfWin * window,const char * const command,gchar ** args)8499 cmd_omemo_start(ProfWin* window, const char* const command, gchar** args)
8500 {
8501 #ifdef HAVE_OMEMO
8502     if (connection_get_status() != JABBER_CONNECTED) {
8503         cons_show("You must be connected with an account to load OMEMO information.");
8504         return TRUE;
8505     }
8506 
8507     if (!omemo_loaded()) {
8508         win_println(window, THEME_DEFAULT, "!", "You have not generated or loaded a cryptographic materials, use '/omemo gen'");
8509         return TRUE;
8510     }
8511 
8512     ProfChatWin* chatwin = NULL;
8513 
8514     // recipient supplied
8515     if (args[1]) {
8516         char* contact = args[1];
8517         char* barejid = roster_barejid_from_name(contact);
8518         if (barejid == NULL) {
8519             barejid = contact;
8520         }
8521 
8522         chatwin = wins_get_chat(barejid);
8523         if (!chatwin) {
8524             chatwin = chatwin_new(barejid);
8525         }
8526         ui_focus_win((ProfWin*)chatwin);
8527     } else {
8528         if (window->type == WIN_CHAT) {
8529             chatwin = (ProfChatWin*)window;
8530             assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
8531         }
8532     }
8533 
8534     if (chatwin) {
8535         if (chatwin->pgp_send) {
8536             win_println((ProfWin*)chatwin, THEME_DEFAULT, "!", "You must disable PGP encryption before starting an OMEMO session.");
8537             return TRUE;
8538         }
8539 
8540         if (chatwin->is_otr) {
8541             win_println((ProfWin*)chatwin, THEME_DEFAULT, "!", "You must disable OTR encryption before starting an OMEMO session.");
8542             return TRUE;
8543         }
8544 
8545         if (chatwin->is_omemo) {
8546             win_println((ProfWin*)chatwin, THEME_DEFAULT, "!", "You are already in an OMEMO session.");
8547             return TRUE;
8548         }
8549 
8550         accounts_add_omemo_state(session_get_account_name(), chatwin->barejid, TRUE);
8551         omemo_start_session(chatwin->barejid);
8552         chatwin->is_omemo = TRUE;
8553     } else if (window->type == WIN_MUC) {
8554         ProfMucWin* mucwin = (ProfMucWin*)window;
8555         assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
8556 
8557         if (muc_anonymity_type(mucwin->roomjid) == MUC_ANONYMITY_TYPE_NONANONYMOUS
8558             && muc_member_type(mucwin->roomjid) == MUC_MEMBER_TYPE_MEMBERS_ONLY) {
8559             accounts_add_omemo_state(session_get_account_name(), mucwin->roomjid, TRUE);
8560             omemo_start_muc_sessions(mucwin->roomjid);
8561             mucwin->is_omemo = TRUE;
8562         } else {
8563             win_println(window, THEME_DEFAULT, "!", "MUC must be non-anonymous (i.e. be configured to present real jid to anyone) and members-only in order to support OMEMO.");
8564         }
8565     } else {
8566         win_println(window, THEME_DEFAULT, "-", "You must be in a regular chat window to start an OMEMO session.");
8567     }
8568 
8569     return TRUE;
8570 #else
8571     cons_show("This version of Profanity has not been built with OMEMO support enabled");
8572     return TRUE;
8573 #endif
8574 }
8575 
8576 gboolean
cmd_omemo_trust_mode(ProfWin * window,const char * const command,gchar ** args)8577 cmd_omemo_trust_mode(ProfWin* window, const char* const command, gchar** args)
8578 {
8579 #ifdef HAVE_OMEMO
8580 
8581     if (!args[1]) {
8582         cons_show("Current trust mode is %s", prefs_get_string(PREF_OMEMO_TRUST_MODE));
8583         return TRUE;
8584     }
8585 
8586     if (g_strcmp0(args[1], "manual") == 0) {
8587         cons_show("Current trust mode is %s - setting to %s", prefs_get_string(PREF_OMEMO_TRUST_MODE), args[1]);
8588         cons_show("You need to trust all OMEMO fingerprints manually");
8589     } else if (g_strcmp0(args[1], "firstusage") == 0) {
8590         cons_show("Current trust mode is %s - setting to %s", prefs_get_string(PREF_OMEMO_TRUST_MODE), args[1]);
8591         cons_show("The first seen OMEMO fingerprints will be trusted automatically - new keys must be trusted manually");
8592     } else if (g_strcmp0(args[1], "blind") == 0) {
8593         cons_show("Current trust mode is %s - setting to %s", prefs_get_string(PREF_OMEMO_TRUST_MODE), args[1]);
8594         cons_show("ALL OMEMO fingerprints will be trusted automatically");
8595     } else {
8596         cons_bad_cmd_usage(command);
8597         return TRUE;
8598     }
8599     prefs_set_string(PREF_OMEMO_TRUST_MODE, args[1]);
8600 
8601 #else
8602     cons_show("This version of Profanity has not been built with OMEMO support enabled");
8603 #endif
8604     return TRUE;
8605 }
8606 
8607 gboolean
cmd_omemo_char(ProfWin * window,const char * const command,gchar ** args)8608 cmd_omemo_char(ProfWin* window, const char* const command, gchar** args)
8609 {
8610 #ifdef HAVE_OMEMO
8611     if (args[1] == NULL) {
8612         cons_bad_cmd_usage(command);
8613         return TRUE;
8614     } else if (g_utf8_strlen(args[1], 4) == 1) {
8615         if (prefs_set_omemo_char(args[1])) {
8616             cons_show("OMEMO char set to %s.", args[1]);
8617         } else {
8618             cons_show_error("Could not set OMEMO char: %s.", args[1]);
8619         }
8620         return TRUE;
8621     }
8622     cons_bad_cmd_usage(command);
8623 #else
8624     cons_show("This version of Profanity has not been built with OMEMO support enabled");
8625 #endif
8626     return TRUE;
8627 }
8628 
8629 gboolean
cmd_omemo_log(ProfWin * window,const char * const command,gchar ** args)8630 cmd_omemo_log(ProfWin* window, const char* const command, gchar** args)
8631 {
8632 #ifdef HAVE_OMEMO
8633     char* choice = args[1];
8634     if (g_strcmp0(choice, "on") == 0) {
8635         prefs_set_string(PREF_OMEMO_LOG, "on");
8636         cons_show("OMEMO messages will be logged as plaintext.");
8637         if (!prefs_get_boolean(PREF_CHLOG)) {
8638             cons_show("Chat logging is currently disabled, use '/logging chat on' to enable.");
8639         }
8640     } else if (g_strcmp0(choice, "off") == 0) {
8641         prefs_set_string(PREF_OMEMO_LOG, "off");
8642         cons_show("OMEMO message logging disabled.");
8643     } else if (g_strcmp0(choice, "redact") == 0) {
8644         prefs_set_string(PREF_OMEMO_LOG, "redact");
8645         cons_show("OMEMO messages will be logged as '[redacted]'.");
8646         if (!prefs_get_boolean(PREF_CHLOG)) {
8647             cons_show("Chat logging is currently disabled, use '/logging chat on' to enable.");
8648         }
8649     } else {
8650         cons_bad_cmd_usage(command);
8651     }
8652     return TRUE;
8653 #else
8654     cons_show("This version of Profanity has not been built with OMEMO support enabled");
8655     return TRUE;
8656 #endif
8657 }
8658 
8659 gboolean
cmd_omemo_end(ProfWin * window,const char * const command,gchar ** args)8660 cmd_omemo_end(ProfWin* window, const char* const command, gchar** args)
8661 {
8662 #ifdef HAVE_OMEMO
8663     if (connection_get_status() != JABBER_CONNECTED) {
8664         cons_show("You must be connected with an account to load OMEMO information.");
8665         return TRUE;
8666     }
8667 
8668     if (window->type == WIN_CHAT) {
8669         ProfChatWin* chatwin = (ProfChatWin*)window;
8670         assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
8671 
8672         if (!chatwin->is_omemo) {
8673             win_println(window, THEME_DEFAULT, "!", "You are not currently in an OMEMO session.");
8674             return TRUE;
8675         }
8676 
8677         chatwin->is_omemo = FALSE;
8678         accounts_add_omemo_state(session_get_account_name(), chatwin->barejid, FALSE);
8679     } else if (window->type == WIN_MUC) {
8680         ProfMucWin* mucwin = (ProfMucWin*)window;
8681         assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
8682 
8683         if (!mucwin->is_omemo) {
8684             win_println(window, THEME_DEFAULT, "!", "You are not currently in an OMEMO session.");
8685             return TRUE;
8686         }
8687 
8688         mucwin->is_omemo = FALSE;
8689         accounts_add_omemo_state(session_get_account_name(), mucwin->roomjid, FALSE);
8690     } else {
8691         win_println(window, THEME_DEFAULT, "-", "You must be in a regular chat window to start an OMEMO session.");
8692         return TRUE;
8693     }
8694 
8695     return TRUE;
8696 #else
8697     cons_show("This version of Profanity has not been built with OMEMO support enabled");
8698     return TRUE;
8699 #endif
8700 }
8701 
8702 gboolean
cmd_omemo_fingerprint(ProfWin * window,const char * const command,gchar ** args)8703 cmd_omemo_fingerprint(ProfWin* window, const char* const command, gchar** args)
8704 {
8705 #ifdef HAVE_OMEMO
8706     if (connection_get_status() != JABBER_CONNECTED) {
8707         cons_show("You must be connected with an account to load OMEMO information.");
8708         return TRUE;
8709     }
8710 
8711     if (!omemo_loaded()) {
8712         win_println(window, THEME_DEFAULT, "!", "You have not generated or loaded a cryptographic materials, use '/omemo gen'");
8713         return TRUE;
8714     }
8715 
8716     Jid* jid;
8717     if (!args[1]) {
8718         if (window->type == WIN_CONSOLE) {
8719             char* fingerprint = omemo_own_fingerprint(TRUE);
8720             cons_show("Your OMEMO fingerprint: %s", fingerprint);
8721             free(fingerprint);
8722             jid = jid_create(connection_get_fulljid());
8723         } else if (window->type == WIN_CHAT) {
8724             ProfChatWin* chatwin = (ProfChatWin*)window;
8725             jid = jid_create(chatwin->barejid);
8726         } else {
8727             win_println(window, THEME_DEFAULT, "-", "You must be in a regular chat window to print fingerprint without providing the contact.");
8728             return TRUE;
8729         }
8730     } else {
8731         char* barejid = roster_barejid_from_name(args[1]);
8732         if (barejid) {
8733             jid = jid_create(barejid);
8734         } else {
8735             jid = jid_create(args[1]);
8736             if (!jid) {
8737                 cons_show("%s is not a valid jid", args[1]);
8738                 return TRUE;
8739             }
8740         }
8741     }
8742 
8743     GList* fingerprints = omemo_known_device_identities(jid->barejid);
8744 
8745     if (!fingerprints) {
8746         win_println(window, THEME_DEFAULT, "-", "There is no known fingerprints for %s", jid->barejid);
8747         return TRUE;
8748     }
8749 
8750     for (GList* fingerprint = fingerprints; fingerprint != NULL; fingerprint = fingerprint->next) {
8751         char* formatted_fingerprint = omemo_format_fingerprint(fingerprint->data);
8752         gboolean trusted = omemo_is_trusted_identity(jid->barejid, fingerprint->data);
8753 
8754         win_println(window, THEME_DEFAULT, "-", "%s's OMEMO fingerprint: %s%s", jid->barejid, formatted_fingerprint, trusted ? " (trusted)" : "");
8755 
8756         free(formatted_fingerprint);
8757     }
8758 
8759     g_list_free(fingerprints);
8760 
8761     win_println(window, THEME_DEFAULT, "-", "You can trust it with '/omemo trust <fingerprint>'");
8762     win_println(window, THEME_DEFAULT, "-", "You can untrust it with '/omemo untrust <fingerprint>'");
8763 
8764     return TRUE;
8765 #else
8766     cons_show("This version of Profanity has not been built with OMEMO support enabled");
8767     return TRUE;
8768 #endif
8769 }
8770 
8771 gboolean
cmd_omemo_trust(ProfWin * window,const char * const command,gchar ** args)8772 cmd_omemo_trust(ProfWin* window, const char* const command, gchar** args)
8773 {
8774 #ifdef HAVE_OMEMO
8775     if (connection_get_status() != JABBER_CONNECTED) {
8776         cons_show("You must be connected with an account to load OMEMO information.");
8777         return TRUE;
8778     }
8779 
8780     if (!args[1]) {
8781         cons_bad_cmd_usage(command);
8782         return TRUE;
8783     }
8784 
8785     if (!omemo_loaded()) {
8786         win_println(window, THEME_DEFAULT, "!", "You have not generated or loaded a cryptographic materials, use '/omemo gen'");
8787         return TRUE;
8788     }
8789 
8790     char* fingerprint;
8791     char* barejid;
8792 
8793     /* Contact not provided */
8794     if (!args[2]) {
8795         fingerprint = args[1];
8796 
8797         if (window->type != WIN_CHAT) {
8798             win_println(window, THEME_DEFAULT, "-", "You must be in a regular chat window to trust a device without providing the contact.");
8799             return TRUE;
8800         }
8801 
8802         ProfChatWin* chatwin = (ProfChatWin*)window;
8803         assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
8804         barejid = chatwin->barejid;
8805     } else {
8806         fingerprint = args[2];
8807         char* contact = args[1];
8808         barejid = roster_barejid_from_name(contact);
8809         if (barejid == NULL) {
8810             barejid = contact;
8811         }
8812     }
8813 
8814     omemo_trust(barejid, fingerprint);
8815 
8816     char* unformatted_fingerprint = malloc(strlen(fingerprint));
8817     int i;
8818     int j;
8819     for (i = 0, j = 0; fingerprint[i] != '\0'; i++) {
8820         if (!g_ascii_isxdigit(fingerprint[i])) {
8821             continue;
8822         }
8823         unformatted_fingerprint[j++] = fingerprint[i];
8824     }
8825 
8826     unformatted_fingerprint[j] = '\0';
8827     gboolean trusted = omemo_is_trusted_identity(barejid, unformatted_fingerprint);
8828 
8829     win_println(window, THEME_DEFAULT, "-", "%s's OMEMO fingerprint: %s%s", barejid, fingerprint, trusted ? " (trusted)" : "");
8830 
8831     free(unformatted_fingerprint);
8832 
8833     return TRUE;
8834 #else
8835     cons_show("This version of Profanity has not been built with OMEMO support enabled");
8836     return TRUE;
8837 #endif
8838 }
8839 
8840 gboolean
cmd_omemo_untrust(ProfWin * window,const char * const command,gchar ** args)8841 cmd_omemo_untrust(ProfWin* window, const char* const command, gchar** args)
8842 {
8843 #ifdef HAVE_OMEMO
8844     if (connection_get_status() != JABBER_CONNECTED) {
8845         cons_show("You must be connected with an account to load OMEMO information.");
8846         return TRUE;
8847     }
8848 
8849     if (!args[1]) {
8850         cons_bad_cmd_usage(command);
8851         return TRUE;
8852     }
8853 
8854     if (!omemo_loaded()) {
8855         win_println(window, THEME_DEFAULT, "!", "You have not generated or loaded a cryptographic materials, use '/omemo gen'");
8856         return TRUE;
8857     }
8858 
8859     char* fingerprint;
8860     char* barejid;
8861 
8862     /* Contact not provided */
8863     if (!args[2]) {
8864         fingerprint = args[1];
8865 
8866         if (window->type != WIN_CHAT) {
8867             win_println(window, THEME_DEFAULT, "-", "You must be in a regular chat window to trust a device without providing the contact.");
8868             return TRUE;
8869         }
8870 
8871         ProfChatWin* chatwin = (ProfChatWin*)window;
8872         assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
8873         barejid = chatwin->barejid;
8874     } else {
8875         fingerprint = args[2];
8876         char* contact = args[1];
8877         barejid = roster_barejid_from_name(contact);
8878         if (barejid == NULL) {
8879             barejid = contact;
8880         }
8881     }
8882 
8883     omemo_untrust(barejid, fingerprint);
8884 
8885     char* unformatted_fingerprint = malloc(strlen(fingerprint));
8886     int i, j;
8887     for (i = 0, j = 0; fingerprint[i] != '\0'; i++) {
8888         if (!g_ascii_isxdigit(fingerprint[i])) {
8889             continue;
8890         }
8891         unformatted_fingerprint[j++] = fingerprint[i];
8892     }
8893 
8894     unformatted_fingerprint[j] = '\0';
8895     gboolean trusted = omemo_is_trusted_identity(barejid, unformatted_fingerprint);
8896 
8897     win_println(window, THEME_DEFAULT, "-", "%s's OMEMO fingerprint: %s%s", barejid, fingerprint, trusted ? " (trusted)" : "");
8898 
8899     free(unformatted_fingerprint);
8900 
8901     return TRUE;
8902 #else
8903     cons_show("This version of Profanity has not been built with OMEMO support enabled");
8904     return TRUE;
8905 #endif
8906 }
8907 
8908 gboolean
cmd_omemo_clear_device_list(ProfWin * window,const char * const command,gchar ** args)8909 cmd_omemo_clear_device_list(ProfWin* window, const char* const command, gchar** args)
8910 {
8911 #ifdef HAVE_OMEMO
8912     if (connection_get_status() != JABBER_CONNECTED) {
8913         cons_show("You must be connected with an account to initialize OMEMO.");
8914         return TRUE;
8915     }
8916 
8917     omemo_devicelist_publish(NULL);
8918     cons_show("Cleared OMEMO device list");
8919     return TRUE;
8920 #else
8921     cons_show("This version of Profanity has not been built with OMEMO support enabled");
8922     return TRUE;
8923 #endif
8924 }
8925 
8926 gboolean
cmd_omemo_policy(ProfWin * window,const char * const command,gchar ** args)8927 cmd_omemo_policy(ProfWin* window, const char* const command, gchar** args)
8928 {
8929 #ifdef HAVE_OMEMO
8930     if (args[1] == NULL) {
8931         char* policy = prefs_get_string(PREF_OMEMO_POLICY);
8932         cons_show("OMEMO policy is now set to: %s", policy);
8933         g_free(policy);
8934         return TRUE;
8935     }
8936 
8937     char* choice = args[1];
8938     if ((g_strcmp0(choice, "manual") != 0) && (g_strcmp0(choice, "automatic") != 0) && (g_strcmp0(choice, "always") != 0)) {
8939         cons_show("OMEMO policy can be set to: manual, automatic or always.");
8940         return TRUE;
8941     }
8942 
8943     prefs_set_string(PREF_OMEMO_POLICY, choice);
8944     cons_show("OMEMO policy is now set to: %s", choice);
8945     return TRUE;
8946 #else
8947     cons_show("This version of Profanity has not been built with OMEMO support enabled");
8948     return TRUE;
8949 #endif
8950 }
8951 
8952 gboolean
cmd_save(ProfWin * window,const char * const command,gchar ** args)8953 cmd_save(ProfWin* window, const char* const command, gchar** args)
8954 {
8955     log_info("Saving preferences to configuration file");
8956     cons_show("Saving preferences.");
8957     prefs_save();
8958     return TRUE;
8959 }
8960 
8961 gboolean
cmd_reload(ProfWin * window,const char * const command,gchar ** args)8962 cmd_reload(ProfWin* window, const char* const command, gchar** args)
8963 {
8964     log_info("Reloading preferences");
8965     cons_show("Reloading preferences.");
8966     prefs_reload();
8967     return TRUE;
8968 }
8969 
8970 gboolean
cmd_paste(ProfWin * window,const char * const command,gchar ** args)8971 cmd_paste(ProfWin* window, const char* const command, gchar** args)
8972 {
8973 #ifdef HAVE_GTK
8974     char* clipboard_buffer = clipboard_get();
8975 
8976     if (clipboard_buffer) {
8977         switch (window->type) {
8978         case WIN_MUC:
8979         {
8980             ProfMucWin* mucwin = (ProfMucWin*)window;
8981             assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
8982             cl_ev_send_muc_msg(mucwin, clipboard_buffer, NULL);
8983             break;
8984         }
8985         case WIN_CHAT:
8986         {
8987             ProfChatWin* chatwin = (ProfChatWin*)window;
8988             assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
8989             cl_ev_send_msg(chatwin, clipboard_buffer, NULL);
8990             break;
8991         }
8992         case WIN_PRIVATE:
8993         {
8994             ProfPrivateWin* privatewin = (ProfPrivateWin*)window;
8995             assert(privatewin->memcheck == PROFPRIVATEWIN_MEMCHECK);
8996             cl_ev_send_priv_msg(privatewin, clipboard_buffer, NULL);
8997             break;
8998         }
8999         case WIN_CONSOLE:
9000         case WIN_XML:
9001         default:
9002             cons_bad_cmd_usage(command);
9003             break;
9004         }
9005 
9006         free(clipboard_buffer);
9007     }
9008 #else
9009     cons_show("This version of Profanity has not been built with GTK support enabled. It is needed for the clipboard feature to work.");
9010 #endif
9011 
9012     return TRUE;
9013 }
9014 
9015 gboolean
cmd_color(ProfWin * window,const char * const command,gchar ** args)9016 cmd_color(ProfWin* window, const char* const command, gchar** args)
9017 {
9018     if (g_strcmp0(args[0], "on") == 0) {
9019         prefs_set_string(PREF_COLOR_NICK, "true");
9020     } else if (g_strcmp0(args[0], "off") == 0) {
9021         prefs_set_string(PREF_COLOR_NICK, "false");
9022     } else if (g_strcmp0(args[0], "redgreen") == 0) {
9023         prefs_set_string(PREF_COLOR_NICK, "redgreen");
9024     } else if (g_strcmp0(args[0], "blue") == 0) {
9025         prefs_set_string(PREF_COLOR_NICK, "blue");
9026     } else if (g_strcmp0(args[0], "own") == 0) {
9027         if (g_strcmp0(args[1], "on") == 0) {
9028             _cmd_set_boolean_preference(args[1], command, "Color generation for own nick", PREF_COLOR_NICK_OWN);
9029         }
9030     } else {
9031         cons_bad_cmd_usage(command);
9032         return TRUE;
9033     }
9034 
9035     cons_show("Consistent color generation for nicks set to: %s", args[0]);
9036 
9037     char* theme = prefs_get_string(PREF_THEME);
9038     if (theme) {
9039         gboolean res = theme_load(theme, false);
9040 
9041         if (res) {
9042             cons_show("Theme reloaded: %s", theme);
9043         } else {
9044             theme_load("default", false);
9045         }
9046 
9047         g_free(theme);
9048     }
9049 
9050     return TRUE;
9051 }
9052 
9053 gboolean
cmd_avatar(ProfWin * window,const char * const command,gchar ** args)9054 cmd_avatar(ProfWin* window, const char* const command, gchar** args)
9055 {
9056     if (args[1] == NULL) {
9057         cons_bad_cmd_usage(command);
9058         return TRUE;
9059     }
9060 
9061     if (g_strcmp0(args[0], "get") == 0) {
9062         avatar_get_by_nick(args[1], false);
9063     } else if (g_strcmp0(args[0], "open") == 0) {
9064         avatar_get_by_nick(args[1], true);
9065     } else if (g_strcmp0(args[0], "cmd") == 0) {
9066         prefs_set_string(PREF_AVATAR_CMD, args[1]);
9067         cons_show("Avatar cmd set to: %s", args[1]);
9068     }
9069 
9070     return TRUE;
9071 }
9072 
9073 gboolean
cmd_os(ProfWin * window,const char * const command,gchar ** args)9074 cmd_os(ProfWin* window, const char* const command, gchar** args)
9075 {
9076     _cmd_set_boolean_preference(args[0], command, "Revealing OS name", PREF_REVEAL_OS);
9077 
9078     return TRUE;
9079 }
9080 
9081 gboolean
cmd_correction(ProfWin * window,const char * const command,gchar ** args)9082 cmd_correction(ProfWin* window, const char* const command, gchar** args)
9083 {
9084     // enable/disable
9085     if (g_strcmp0(args[0], "on") == 0) {
9086         _cmd_set_boolean_preference(args[0], command, "Last Message Correction", PREF_CORRECTION_ALLOW);
9087         caps_add_feature(XMPP_FEATURE_LAST_MESSAGE_CORRECTION);
9088         return TRUE;
9089     } else if (g_strcmp0(args[0], "off") == 0) {
9090         _cmd_set_boolean_preference(args[0], command, "Last Message Correction", PREF_CORRECTION_ALLOW);
9091         caps_remove_feature(XMPP_FEATURE_LAST_MESSAGE_CORRECTION);
9092         return TRUE;
9093     }
9094 
9095     // char
9096     if (g_strcmp0(args[0], "char") == 0) {
9097         if (args[1] == NULL) {
9098             cons_bad_cmd_usage(command);
9099         } else if (strlen(args[1]) != 1) {
9100             cons_bad_cmd_usage(command);
9101         } else {
9102             prefs_set_correction_char(args[1][0]);
9103             cons_show("LMC char set to %c.", args[1][0]);
9104         }
9105     }
9106 
9107     return TRUE;
9108 }
9109 
9110 gboolean
cmd_correct(ProfWin * window,const char * const command,gchar ** args)9111 cmd_correct(ProfWin* window, const char* const command, gchar** args)
9112 {
9113     jabber_conn_status_t conn_status = connection_get_status();
9114     if (conn_status != JABBER_CONNECTED) {
9115         cons_show("You are currently not connected.");
9116         return TRUE;
9117     }
9118 
9119     if (!prefs_get_boolean(PREF_CORRECTION_ALLOW)) {
9120         win_println(window, THEME_DEFAULT, "!", "Corrections not enabled. See /help correction.");
9121         return TRUE;
9122     }
9123 
9124     if (window->type == WIN_CHAT) {
9125         ProfChatWin* chatwin = (ProfChatWin*)window;
9126         assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
9127 
9128         if (chatwin->last_msg_id == NULL || chatwin->last_message == NULL) {
9129             win_println(window, THEME_DEFAULT, "!", "No last message to correct.");
9130             return TRUE;
9131         }
9132 
9133         // send message again, with replace flag
9134         gchar* message = g_strjoinv(" ", args);
9135         cl_ev_send_msg_correct(chatwin, message, FALSE, TRUE);
9136 
9137         free(message);
9138         return TRUE;
9139     } else if (window->type == WIN_MUC) {
9140         ProfMucWin* mucwin = (ProfMucWin*)window;
9141         assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
9142 
9143         if (mucwin->last_msg_id == NULL || mucwin->last_message == NULL) {
9144             win_println(window, THEME_DEFAULT, "!", "No last message to correct.");
9145             return TRUE;
9146         }
9147 
9148         // send message again, with replace flag
9149         gchar* message = g_strjoinv(" ", args);
9150         cl_ev_send_muc_msg_corrected(mucwin, message, FALSE, TRUE);
9151 
9152         free(message);
9153         return TRUE;
9154     }
9155 
9156     win_println(window, THEME_DEFAULT, "!", "Command /correct only valid in regular chat windows.");
9157     return TRUE;
9158 }
9159 
9160 gboolean
cmd_slashguard(ProfWin * window,const char * const command,gchar ** args)9161 cmd_slashguard(ProfWin* window, const char* const command, gchar** args)
9162 {
9163     if (args[0] == NULL) {
9164         return FALSE;
9165     }
9166 
9167     _cmd_set_boolean_preference(args[0], command, "Slashguard", PREF_SLASH_GUARD);
9168 
9169     return TRUE;
9170 }
9171 
9172 #ifdef HAVE_OMEMO
9173 void
_url_aesgcm_method(ProfWin * window,const char * cmd_template,const char * url,const char * filename)9174 _url_aesgcm_method(ProfWin* window, const char* cmd_template, const char* url, const char* filename)
9175 {
9176     AESGCMDownload* download = malloc(sizeof(AESGCMDownload));
9177     download->window = window;
9178     download->url = strdup(url);
9179     download->filename = strdup(filename);
9180     if (cmd_template != NULL) {
9181         download->cmd_template = strdup(cmd_template);
9182     } else {
9183         download->cmd_template = NULL;
9184     }
9185 
9186     pthread_create(&(download->worker), NULL, &aesgcm_file_get, download);
9187     aesgcm_download_add_download(download);
9188 }
9189 #endif
9190 
9191 void
_url_http_method(ProfWin * window,const char * cmd_template,const char * url,const char * filename)9192 _url_http_method(ProfWin* window, const char* cmd_template, const char* url, const char* filename)
9193 {
9194 
9195     HTTPDownload* download = malloc(sizeof(HTTPDownload));
9196     download->window = window;
9197     download->url = strdup(url);
9198     download->filename = strdup(filename);
9199     if (cmd_template != NULL) {
9200         download->cmd_template = strdup(cmd_template);
9201     } else {
9202         download->cmd_template = NULL;
9203     }
9204 
9205     pthread_create(&(download->worker), NULL, &http_file_get, download);
9206     http_download_add_download(download);
9207 }
9208 
9209 void
_url_external_method(const char * cmd_template,const char * url,const char * filename)9210 _url_external_method(const char* cmd_template, const char* url, const char* filename)
9211 {
9212     gchar** argv = format_call_external_argv(cmd_template, url, filename);
9213 
9214     if (!call_external(argv, NULL, NULL)) {
9215         cons_show_error("Unable to call external executable for url: check the logs for more information.");
9216     } else {
9217         cons_show("URL '%s' has been called with '%s'.", url, cmd_template);
9218     }
9219 
9220     g_strfreev(argv);
9221 }
9222 
9223 gboolean
cmd_url_open(ProfWin * window,const char * const command,gchar ** args)9224 cmd_url_open(ProfWin* window, const char* const command, gchar** args)
9225 {
9226     if (window->type != WIN_CHAT && window->type != WIN_MUC && window->type != WIN_PRIVATE) {
9227         cons_show_error("url open not supported in this window");
9228         return TRUE;
9229     }
9230 
9231     gchar* url = args[1];
9232     if (url == NULL) {
9233         cons_bad_cmd_usage(command);
9234         return TRUE;
9235     }
9236 
9237     gchar* scheme = NULL;
9238     char* cmd_template = NULL;
9239     char* filename = NULL;
9240 
9241     scheme = g_uri_parse_scheme(url);
9242     if (scheme == NULL) {
9243         cons_show_error("URL '%s' is not valid.", args[1]);
9244         goto out;
9245     }
9246 
9247     cmd_template = prefs_get_string(PREF_URL_OPEN_CMD);
9248     if (cmd_template == NULL) {
9249         cons_show_error("No default `url open` command found in executables preferences.");
9250         goto out;
9251     }
9252 
9253 #ifdef HAVE_OMEMO
9254     // OMEMO URLs (aesgcm://) must be saved and decrypted before being opened.
9255     if (g_strcmp0(scheme, "aesgcm") == 0) {
9256 
9257         // Ensure that the downloads directory exists for saving cleartexts.
9258         gchar* downloads_dir = files_get_data_path(DIR_DOWNLOADS);
9259         if (g_mkdir_with_parents(downloads_dir, S_IRWXU) != 0) {
9260             cons_show_error("Failed to create download directory "
9261                             "at '%s' with error '%s'",
9262                             downloads_dir, strerror(errno));
9263             g_free(downloads_dir);
9264             goto out;
9265         }
9266 
9267         // Generate an unique filename from the URL that should be stored in the
9268         // downloads directory.
9269         filename = unique_filename_from_url(url, downloads_dir);
9270         g_free(downloads_dir);
9271 
9272         // Download, decrypt and open the cleartext version of the AESGCM
9273         // encrypted file.
9274         _url_aesgcm_method(window, cmd_template, url, filename);
9275         goto out;
9276     }
9277 #endif
9278 
9279     _url_external_method(cmd_template, url, NULL);
9280 
9281 out:
9282 
9283     free(cmd_template);
9284     free(filename);
9285 
9286     g_free(scheme);
9287 
9288     return TRUE;
9289 }
9290 
9291 gboolean
cmd_url_save(ProfWin * window,const char * const command,gchar ** args)9292 cmd_url_save(ProfWin* window, const char* const command, gchar** args)
9293 {
9294     if (window->type != WIN_CHAT && window->type != WIN_MUC && window->type != WIN_PRIVATE) {
9295         cons_show_error("`/url save` is not supported in this window.");
9296         return TRUE;
9297     }
9298 
9299     if (args[1] == NULL) {
9300         cons_bad_cmd_usage(command);
9301         return TRUE;
9302     }
9303 
9304     gchar* url = args[1];
9305     gchar* path = g_strdup(args[2]);
9306     gchar* scheme = NULL;
9307     char* filename = NULL;
9308     char* cmd_template = NULL;
9309 
9310     scheme = g_uri_parse_scheme(url);
9311     if (scheme == NULL) {
9312         cons_show_error("URL '%s' is not valid.", args[1]);
9313         goto out;
9314     }
9315 
9316     filename = unique_filename_from_url(url, path);
9317     if (filename == NULL) {
9318         cons_show_error("Failed to generate unique filename"
9319                         "from URL '%s' for path '%s'",
9320                         url, path);
9321         goto out;
9322     }
9323 
9324     cmd_template = prefs_get_string(PREF_URL_SAVE_CMD);
9325     if (cmd_template == NULL && (g_strcmp0(scheme, "http") == 0 || g_strcmp0(scheme, "https") == 0)) {
9326         _url_http_method(window, cmd_template, url, filename);
9327 #ifdef HAVE_OMEMO
9328     } else if (g_strcmp0(scheme, "aesgcm") == 0) {
9329         _url_aesgcm_method(window, cmd_template, url, filename);
9330 #endif
9331     } else if (cmd_template != NULL) {
9332         _url_external_method(cmd_template, url, filename);
9333     } else {
9334         cons_show_error("No download method defined for the scheme '%s'.", scheme);
9335     }
9336 
9337 out:
9338 
9339     free(filename);
9340     free(cmd_template);
9341 
9342     g_free(scheme);
9343     g_free(path);
9344 
9345     return TRUE;
9346 }
9347 
9348 gboolean
cmd_executable_avatar(ProfWin * window,const char * const command,gchar ** args)9349 cmd_executable_avatar(ProfWin* window, const char* const command, gchar** args)
9350 {
9351     prefs_set_string(PREF_AVATAR_CMD, args[1]);
9352     cons_show("`avatar` command set to invoke '%s'", args[1]);
9353     return TRUE;
9354 }
9355 
9356 gboolean
cmd_executable_urlopen(ProfWin * window,const char * const command,gchar ** args)9357 cmd_executable_urlopen(ProfWin* window, const char* const command, gchar** args)
9358 {
9359     guint num_args = g_strv_length(args);
9360     if (num_args < 2) {
9361         cons_bad_cmd_usage(command);
9362         return TRUE;
9363     }
9364 
9365     if (g_strcmp0(args[1], "set") == 0 && num_args >= 3) {
9366         gchar* str = g_strjoinv(" ", &args[2]);
9367         prefs_set_string(PREF_URL_OPEN_CMD, str);
9368         cons_show("`url open` command set to invoke '%s'", str);
9369         g_free(str);
9370 
9371     } else if (g_strcmp0(args[1], "default") == 0) {
9372         prefs_set_string(PREF_URL_OPEN_CMD, NULL);
9373         gchar* def = prefs_get_string(PREF_URL_OPEN_CMD);
9374         cons_show("`url open` command set to invoke %s (default)", def);
9375         g_free(def);
9376     } else {
9377         cons_bad_cmd_usage(command);
9378     }
9379 
9380     return TRUE;
9381 }
9382 
9383 gboolean
cmd_executable_urlsave(ProfWin * window,const char * const command,gchar ** args)9384 cmd_executable_urlsave(ProfWin* window, const char* const command, gchar** args)
9385 {
9386 
9387     guint num_args = g_strv_length(args);
9388     if (num_args < 2) {
9389         cons_bad_cmd_usage(command);
9390         return TRUE;
9391     }
9392 
9393     if (g_strcmp0(args[1], "set") == 0 && num_args >= 3) {
9394         gchar* str = g_strjoinv(" ", &args[2]);
9395         prefs_set_string(PREF_URL_SAVE_CMD, str);
9396         cons_show("`url save` command set to invoke '%s'", str);
9397         g_free(str);
9398 
9399     } else if (g_strcmp0(args[1], "default") == 0) {
9400         prefs_set_string(PREF_URL_SAVE_CMD, NULL);
9401         cons_show("`url save` will use built-in download method (default)");
9402     } else {
9403         cons_bad_cmd_usage(command);
9404     }
9405 
9406     return TRUE;
9407 }
9408 
9409 gboolean
cmd_executable_editor(ProfWin * window,const char * const command,gchar ** args)9410 cmd_executable_editor(ProfWin* window, const char* const command, gchar** args)
9411 {
9412     guint num_args = g_strv_length(args);
9413 
9414     if (g_strcmp0(args[1], "set") == 0 && num_args >= 3) {
9415         prefs_set_string(PREF_COMPOSE_EDITOR, args[2]);
9416         cons_show("`editor` command set to invoke '%s'", args[2]);
9417     } else {
9418         cons_bad_cmd_usage(command);
9419     }
9420 
9421     return TRUE;
9422 }
9423 
9424 gboolean
cmd_mam(ProfWin * window,const char * const command,gchar ** args)9425 cmd_mam(ProfWin* window, const char* const command, gchar** args)
9426 {
9427     _cmd_set_boolean_preference(args[0], command, "Message Archive Management", PREF_MAM);
9428 
9429     return TRUE;
9430 }
9431 
9432 gboolean
cmd_change_password(ProfWin * window,const char * const command,gchar ** args)9433 cmd_change_password(ProfWin* window, const char* const command, gchar** args)
9434 {
9435     jabber_conn_status_t conn_status = connection_get_status();
9436 
9437     if (conn_status != JABBER_CONNECTED) {
9438         cons_show("You are not currently connected.");
9439         return TRUE;
9440     }
9441 
9442     char* user = connection_get_user();
9443     char* passwd = ui_ask_password(false);
9444     char* confirm_passwd = ui_ask_password(true);
9445 
9446     if (g_strcmp0(passwd, confirm_passwd) == 0) {
9447         iq_register_change_password(user, passwd);
9448     } else {
9449         cons_show("Aborted! The new password and the confirmed password do not match.");
9450     }
9451 
9452     free(user);
9453     free(passwd);
9454     free(confirm_passwd);
9455 
9456     return TRUE;
9457 }
9458 
9459 gboolean
cmd_editor(ProfWin * window,const char * const command,gchar ** args)9460 cmd_editor(ProfWin* window, const char* const command, gchar** args)
9461 {
9462     jabber_conn_status_t conn_status = connection_get_status();
9463 
9464     if (conn_status != JABBER_CONNECTED) {
9465         cons_show("You are currently not connected.");
9466         return TRUE;
9467     }
9468 
9469     // create editor dir if not present
9470     char *jid = connection_get_barejid();
9471     gchar *path = files_get_account_data_path(DIR_EDITOR, jid);
9472     if (g_mkdir_with_parents(path, S_IRWXU) != 0) {
9473         cons_show_error("Failed to create directory at '%s' with error '%s'", path, strerror(errno));
9474         free(jid);
9475         g_free(path);
9476         return TRUE;
9477     }
9478     // build temp file name. Example: /home/user/.local/share/profanity/editor/jid/compose.md
9479     char* filename = g_strdup_printf("%s/compose.md", path);
9480     free(jid);
9481     g_free(path);
9482 
9483     GError* creation_error = NULL;
9484     GFile* file = g_file_new_for_path(filename);
9485     GFileOutputStream* fos = g_file_create(file, G_FILE_CREATE_PRIVATE, NULL, &creation_error);
9486 
9487     free(filename);
9488 
9489     if (creation_error) {
9490         cons_show_error("Editor: could not create temp file");
9491         return TRUE;
9492     }
9493     g_object_unref(fos);
9494 
9495     char* editor = prefs_get_string(PREF_COMPOSE_EDITOR);
9496 
9497     // Fork / exec
9498     pid_t pid = fork();
9499     if (pid == 0) {
9500         int x = execlp(editor, editor, g_file_get_path(file), (char*)NULL);
9501         if (x == -1) {
9502             cons_show_error("Editor:Failed to exec %s", editor);
9503         }
9504         _exit(EXIT_FAILURE);
9505     } else {
9506         if (pid == -1) {
9507             return TRUE;
9508         }
9509         int status = 0;
9510         waitpid(pid, &status, 0);
9511         int fd_input_file = open(g_file_get_path(file), O_RDONLY);
9512         const size_t COUNT = 8192;
9513         char buf[COUNT];
9514         ssize_t size_read = read(fd_input_file, buf, COUNT);
9515         if (size_read > 0 && size_read <= COUNT) {
9516             buf[size_read - 1] = '\0';
9517             GString* text = g_string_new(buf);
9518             rl_insert_text(text->str);
9519             g_string_free(text, TRUE);
9520         }
9521         close(fd_input_file);
9522 
9523         GError* deletion_error = NULL;
9524         g_file_delete(file, NULL, &deletion_error);
9525         if (deletion_error) {
9526             cons_show("Editor: error during file deletion");
9527             return TRUE;
9528         }
9529         g_object_unref(file);
9530         ui_resize();
9531         rl_point = rl_end;
9532         rl_forced_update_display();
9533     }
9534     return TRUE;
9535 }
9536 
9537 gboolean
cmd_silence(ProfWin * window,const char * const command,gchar ** args)9538 cmd_silence(ProfWin* window, const char* const command, gchar** args)
9539 {
9540     _cmd_set_boolean_preference(args[0], command, "Block all messages from JIDs that are not in the roster", PREF_SILENCE_NON_ROSTER);
9541 
9542     return TRUE;
9543 }
9544