2 ctrlproxy: A modular IRC proxy
3 (c) 2002-2007 Jelmer Vernooij <jelmer@nl.linux.org>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 #include "internals.h"
24 #ifdef HAVE_SYS_SOCKET_H
25 #include <sys/socket.h>
29 #include <sys/socket.h>
30 #include <glib/gstdio.h>
32 #define DEFAULT_ADMIN_PORT 6680
33 #define DEFAULT_SOCKS_PORT 1080
35 static GList *known_keys = NULL;
37 static void config_save_log(struct log_file_config *data,
38 struct ctrlproxy_config *config);
40 static const char *builtin_known_keys[] = {
48 "default-client-charset",
62 "log-format-nickchange",
76 static gboolean config_known_key(const char *name)
80 if (g_list_find_custom(known_keys, name, (GCompareFunc)strcmp) != NULL)
83 for (i = 0; builtin_known_keys[i]; i++)
84 if (!strcmp(builtin_known_keys[i], name))
90 void config_register_known_key(char *name)
92 if (config_known_key(name))
94 known_keys = g_list_insert_sorted(known_keys, g_strdup(name), (GCompareFunc)strcmp);
99 gboolean g_key_file_save_to_file(GKeyFile *kf, const gchar *file, GError **error)
102 char *data = g_key_file_to_data(kf, &length, error);
108 gio = g_io_channel_new_file(file, "w+", error);
114 g_io_channel_write_chars(gio, data, length, &nr, error);
118 g_io_channel_unref(gio);
123 static void config_save_tcp_servers(struct network_config *n, GKeyFile *kf)
127 gchar **values = g_new0(gchar *, g_list_length(n->type_settings.tcp_servers)+1);
129 for (gl = n->type_settings.tcp_servers; gl; gl = gl->next) {
130 struct tcp_server_config *ts = gl->data;
131 char *name = g_strdup_printf("%s:%s", ts->host, ts->port);
135 if (g_key_file_has_key(kf, name, "ssl", NULL) || ts->ssl)
136 g_key_file_set_boolean(kf, name, "ssl", ts->ssl);
139 g_key_file_set_string(kf, name, "password", ts->password);
141 g_key_file_remove_key(kf, name, "password", NULL);
143 if (ts->bind_address) {
146 tmp = g_strdup_printf("%s:%s",
150 tmp = g_strdup(ts->bind_address);
152 g_key_file_set_string(kf, name, "bind", tmp);
156 g_key_file_remove_key(kf, name, "bind", NULL);
161 g_key_file_set_string_list(kf, "global", "servers", (const gchar **)values, i);
166 static void config_save_network(const char *dir, struct network_config *n)
173 n->keyfile = g_key_file_new();
178 g_key_file_set_string(kf, "global", "fullname", n->fullname);
179 g_key_file_set_string(kf, "global", "nick", n->nick);
180 g_key_file_set_string(kf, "global", "username", n->username);
182 g_key_file_set_integer(kf, "global", "queue-speed", n->queue_speed);
183 g_key_file_set_integer(kf, "global", "reconnect-interval", n->reconnect_interval);
186 case NETWORK_VIRTUAL:
187 g_key_file_set_string(kf, "global", "virtual", n->type_settings.virtual_type);
189 case NETWORK_PROGRAM:
190 g_key_file_set_string(kf, "global", "program", n->type_settings.program_location);
193 config_save_tcp_servers(n, kf);
198 for (gl = n->channels; gl; gl = gl->next) {
199 struct channel_config *c = gl->data;
202 g_key_file_set_string(kf, c->name, "key", c->key);
204 g_key_file_remove_key(kf, c->name, "key", NULL);
206 g_key_file_set_boolean(kf, c->name, "autojoin", c->autojoin);
209 fn = g_build_filename(dir, n->name, NULL);
210 g_key_file_save_to_file(kf, fn, NULL);
214 static void config_save_listeners(struct ctrlproxy_config *cfg, const char *path)
219 GError *error = NULL;
220 gboolean empty = TRUE;
221 char *default_password;
223 default_password = g_key_file_get_string(cfg->keyfile, "global", "password", NULL);
225 if (cfg->auto_listener) {
226 g_key_file_set_boolean(cfg->keyfile, "global", "listener-auto", cfg->auto_listener);
227 g_key_file_set_integer(cfg->keyfile, "global", "listener-autoport", cfg->listener_autoport);
230 filename = g_build_filename(path, "listener", NULL);
232 kf = g_key_file_new();
234 for (gl = cfg->listeners; gl; gl = gl->next) {
235 struct listener_config *l = gl->data;
238 g_key_file_set_string(cfg->keyfile, "global", "port", l->port);
239 if (l->address != NULL)
240 g_key_file_set_string(cfg->keyfile, "global", "bind", l->address);
241 if (l->password != NULL)
242 g_key_file_set_string(cfg->keyfile, "global", "password", l->password);
244 if (g_key_file_has_key(cfg->keyfile, "global", "ssl", NULL) || l->ssl)
245 g_key_file_set_boolean(cfg->keyfile, "global", "ssl", l->ssl);
247 if (l->network != NULL)
248 g_key_file_set_string(cfg->keyfile, "global", "default-network",
254 tmp = g_strdup(l->port);
256 tmp = g_strdup_printf("%s:%s", l->address, l->port);
258 if (l->password != NULL &&
259 !(default_password != NULL && strcmp(l->password, default_password) == 0))
260 g_key_file_set_string(kf, tmp, "password", l->password);
262 if (l->network != NULL) {
263 g_key_file_set_string(kf, tmp, "network", l->network);
266 g_key_file_set_boolean(kf, tmp, "ssl", l->ssl);
275 if (!g_key_file_save_to_file(kf, filename, &error)) {
276 log_global(LOG_WARNING, "Unable to save to \"%s\": %s", filename, error->message);
280 g_free(default_password);
284 static void config_save_networks(const char *config_dir, GList *networks)
286 char *networksdir = g_build_filename(config_dir, "networks", NULL);
289 if (!g_file_test(networksdir, G_FILE_TEST_IS_DIR)) {
290 if (g_mkdir(networksdir, 0700) != 0) {
291 log_global(LOG_ERROR, "Can't create networks directory '%s': %s", networksdir, strerror(errno));
296 for (gl = networks; gl; gl = gl->next) {
297 struct network_config *n = gl->data;
298 config_save_network(networksdir, n);
304 void save_configuration(struct ctrlproxy_config *cfg, const char *configuration_dir)
310 if (!g_file_test(configuration_dir, G_FILE_TEST_IS_DIR)) {
311 if (g_mkdir(configuration_dir, 0700) != 0) {
312 log_global(LOG_ERROR, "Unable to open configuration directory '%s'\n", configuration_dir);
318 cfg->keyfile = g_key_file_new();
320 g_key_file_set_boolean(cfg->keyfile, "global", "autosave", cfg->autosave);
321 if (cfg->admin_user != NULL)
322 g_key_file_set_string(cfg->keyfile, "global", "admin-user", cfg->admin_user);
324 if (g_key_file_has_key(cfg->keyfile, "global", "admin-log", NULL) ||
326 g_key_file_set_boolean(cfg->keyfile, "global", "admin-log", cfg->admin_log);
328 if (g_key_file_has_key(cfg->keyfile, "global", "max_who_age", NULL) ||
329 cfg->max_who_age != 0)
330 g_key_file_set_integer(cfg->keyfile, "global", "max_who_age", cfg->max_who_age);
332 if (g_key_file_has_key(cfg->keyfile, "global", "learn-nickserv", NULL) ||
333 !cfg->learn_nickserv)
334 g_key_file_set_boolean(cfg->keyfile, "global", "learn-nickserv", cfg->learn_nickserv);
336 if (g_key_file_has_key(cfg->keyfile, "global", "learn-network-name", NULL) ||
337 !cfg->learn_network_name)
338 g_key_file_set_boolean(cfg->keyfile, "global", "learn-network-name", cfg->learn_network_name);
340 if (cfg->client_charset != NULL)
341 g_key_file_set_string(cfg->keyfile, "global", "default-client-charset", cfg->client_charset);
343 if (cfg->replication)
344 g_key_file_set_string(cfg->keyfile, "global", "replication", cfg->replication);
345 if (cfg->linestack_backend)
346 g_key_file_set_string(cfg->keyfile, "global", "linestack", cfg->linestack_backend);
347 if (cfg->motd_file != NULL)
348 g_key_file_set_string(cfg->keyfile, "global", "motd-file", cfg->motd_file);
350 switch (cfg->report_time) {
351 case REPORT_TIME_ALWAYS:
352 g_key_file_set_string(cfg->keyfile, "global", "report-time",
355 case REPORT_TIME_NEVER:
356 g_key_file_set_string(cfg->keyfile, "global", "report-time",
359 case REPORT_TIME_REPLICATION:
360 g_key_file_set_string(cfg->keyfile, "global", "report-time",
365 config_save_networks(configuration_dir, cfg->networks);
367 config_save_listeners(cfg, configuration_dir);
369 config_save_log(cfg->log_file, cfg);
372 list = g_new0(char *, g_list_length(cfg->networks)+1);
373 for (gl = cfg->networks; gl; gl = gl->next) {
374 struct network_config *nc = gl->data;
376 if (nc->autoconnect) {
383 g_key_file_set_string_list(cfg->keyfile, "global", "autoconnect", (const gchar **)list, i);
387 fn = g_build_filename(configuration_dir, "config", NULL);
388 g_key_file_save_to_file(cfg->keyfile, fn, NULL);
392 static void config_load_channel(struct network_config *n, GKeyFile *kf, const char *name)
394 struct channel_config *ch = g_new0(struct channel_config, 1);
396 ch->name = g_strdup(name);
397 if (g_key_file_has_key(kf, name, "key", NULL))
398 ch->key = g_key_file_get_string(kf, name, "key", NULL);
400 if (g_key_file_has_key(kf, name, "autojoin", NULL))
401 ch->autojoin = g_key_file_get_boolean(kf, name, "autojoin", NULL);
403 n->channels = g_list_append(n->channels, ch);
406 static void config_load_servers(struct network_config *n)
412 servers = g_key_file_get_string_list(n->keyfile, "global", "servers", &size, NULL);
417 for (i = 0; i < size; i++) {
419 struct tcp_server_config *s = g_new0(struct tcp_server_config, 1);
421 s->password = g_key_file_get_string(n->keyfile, servers[i], "password", NULL);
422 if (g_key_file_has_key(n->keyfile, servers[i], "ssl", NULL))
423 s->ssl = g_key_file_get_boolean(n->keyfile, servers[i], "ssl", NULL);
425 tmp = strrchr(servers[i], ':');
432 s->host = servers[i];
433 s->port = g_strdup(tmp != NULL?tmp:DEFAULT_IRC_PORT);
434 s->bind_address = g_key_file_get_string(n->keyfile, servers[i], "bind", NULL);
435 if (s->bind_address && (tmp = strchr(s->bind_address, ':'))) {
437 s->bind_port = tmp+1;
440 n->type_settings.tcp_servers = g_list_append(n->type_settings.tcp_servers, s);
446 static struct network_config *config_load_network(struct ctrlproxy_config *cfg, const char *dirname, const char *name)
449 struct network_config *n;
453 GError *error = NULL;
456 kf = g_key_file_new();
458 filename = g_build_filename(dirname, name, NULL);
460 if (!g_key_file_load_from_file(kf, filename, G_KEY_FILE_KEEP_COMMENTS, &error)) {
461 log_global(LOG_ERROR, "Can't parse configuration file '%s': %s", filename, error->message);
466 n = network_config_init(cfg);
471 if (g_key_file_has_key(kf, "global", "fullname", NULL)) {
473 n->fullname = g_key_file_get_string(kf, "global", "fullname", NULL);
474 if (!strcmp(n->fullname, "") || n->fullname[0] == ' ')
475 log_global(LOG_WARNING, "Invalid fullname `%s' set for network `%s'", n->fullname, n->name);
478 if (g_key_file_has_key(kf, "global", "nick", NULL)) {
480 n->nick = g_key_file_get_string(kf, "global", "nick", NULL);
481 if (!strcmp(n->nick, "") || n->nick[0] == ' ')
482 log_global(LOG_WARNING, "Invalid nick name `%s' set for `%s'", n->nick, n->name);
485 if (g_key_file_has_key(kf, "global", "reconnect-interval", NULL)) {
486 n->reconnect_interval = g_key_file_get_integer(kf, "global", "reconnect-interval", NULL);
489 if (g_key_file_has_key(kf, "global", "queue-speed", NULL)) {
490 n->queue_speed = g_key_file_get_integer(kf, "global", "queue-speed", NULL);
493 if (g_key_file_has_key(kf, "global", "username", NULL)) {
495 n->username = g_key_file_get_string(kf, "global", "username", NULL);
496 if (!strcmp(n->username, "") || n->username[0] == ' ')
497 log_global(LOG_WARNING, "Invalid username `%s' set for network `%s'", n->username, n->name);
500 if (g_key_file_has_key(kf, "global", "ignore_first_nick", NULL)) {
501 n->ignore_first_nick = g_key_file_get_boolean(kf, "global", "ignore_first_nick", NULL);
504 if (g_key_file_has_key(kf, "global", "password", NULL)) {
506 n->password = g_key_file_get_string(kf, "global", "password", NULL);
509 n->name = g_strdup(name);
511 if (g_key_file_has_key(kf, "global", "program", NULL))
512 n->type = NETWORK_PROGRAM;
513 else if (g_key_file_has_key(kf, "global", "virtual", NULL))
514 n->type = NETWORK_VIRTUAL;
516 n->type = NETWORK_TCP;
520 config_load_servers(n);
522 case NETWORK_PROGRAM:
523 n->type_settings.program_location = g_key_file_get_string(kf, "global", "program", NULL);
525 case NETWORK_VIRTUAL:
526 n->type_settings.virtual_type = g_key_file_get_string(kf, "global", "virtual", NULL);
528 case NETWORK_IOCHANNEL:
533 groups = g_key_file_get_groups(kf, &size);
534 for (i = 0; i < size; i++) {
535 if (!g_ascii_isalpha(groups[i][0]))
536 config_load_channel(n, kf, groups[i]);
544 static struct network_config *find_create_network_config(struct ctrlproxy_config *cfg, const char *name)
547 struct network_config *nc;
548 struct tcp_server_config *tc;
550 for (gl = cfg->networks; gl; gl = gl->next) {
554 if (g_strcasecmp(nc->name, name) == 0)
557 if (nc->type != NETWORK_TCP)
560 for (gl1 = nc->type_settings.tcp_servers; gl1; gl1 = gl1->next) {
562 struct tcp_server_config *sc = gl1->data;
564 if (g_strcasecmp(sc->host, name) == 0)
567 if (g_strncasecmp(sc->host, name, strlen(sc->host)) != 0)
570 tmp = g_strdup_printf("%s:%s", sc->host, sc->port);
572 if (g_strcasecmp(tmp, name) == 0)
579 nc = network_config_init(cfg);
580 nc->name = g_strdup(name);
581 nc->autoconnect = FALSE;
582 nc->reconnect_interval = DEFAULT_RECONNECT_INTERVAL;
583 nc->type = NETWORK_TCP;
584 tc = g_new0(struct tcp_server_config, 1);
585 tc->host = g_strdup(name);
586 if (strchr(tc->host, ':')) {
587 tc->port = tc->host+1;
590 tc->port = g_strdup(DEFAULT_IRC_PORT);
593 nc->type_settings.tcp_servers = g_list_append(nc->type_settings.tcp_servers, tc);
595 cfg->networks = g_list_append(cfg->networks, nc);
600 static void config_load_listeners_socks(struct ctrlproxy_config *cfg)
604 GKeyFile *kf = cfg->keyfile;
605 struct listener_config *l;
607 allows = g_key_file_get_string_list(kf, "socks", "allow", &size, NULL);
612 g_key_file_remove_key(kf, "socks", "allow", NULL);
614 l = g_new0(struct listener_config, 1);
616 if (g_key_file_has_key(kf, "socks", "port", NULL))
617 l->port = g_key_file_get_string(kf, "socks", "port", NULL);
619 l->port = g_strdup_printf("%d", DEFAULT_SOCKS_PORT);
621 /* We can use the socks listener as default listener, if there was
622 * no default listener specified */
623 if (cfg->listeners == NULL ||
624 !((struct listener_config *)cfg->listeners->data)->is_default)
625 l->is_default = TRUE;
627 g_key_file_remove_key(kf, "socks", "port", NULL);
629 for (i = 0; i < size; i++) {
630 struct allow_rule *r = g_new0(struct allow_rule, 1);
631 char **parts = g_strsplit(allows[i], ":", 2);
633 r->username = parts[0];
634 r->password = parts[1];
637 l->allow_rules = g_list_append(l->allow_rules, r);
640 g_key_file_remove_group(kf, "socks", NULL);
644 cfg->listeners = g_list_append(cfg->listeners, l);
647 static void config_load_listeners(struct ctrlproxy_config *cfg)
649 char *filename = g_build_filename(cfg->config_dir, "listener", NULL);
654 char *default_password;
655 GError *error = NULL;
657 if (g_key_file_has_key(cfg->keyfile, "listener", "pasword", NULL)) {
658 g_key_file_remove_key(cfg->keyfile, "listener", "password", NULL);
659 g_key_file_set_string(cfg->keyfile, "global", "password",
660 g_key_file_get_string(cfg->keyfile, "listener", "password", NULL));
662 default_password = g_key_file_get_string(cfg->keyfile, "global", "password", NULL);
663 if (g_key_file_has_key(cfg->keyfile, "global", "listener-auto", NULL)) {
664 cfg->auto_listener = g_key_file_get_boolean(cfg->keyfile, "global", "listener-auto", NULL);
665 } else if (g_key_file_has_key(cfg->keyfile, "listener", "auto", NULL)) {
666 cfg->auto_listener = g_key_file_get_boolean(cfg->keyfile, "listener", "auto", NULL);
667 g_key_file_remove_key(cfg->keyfile, "listener", "auto", NULL);
670 if (g_key_file_has_key(cfg->keyfile, "global", "listener-autoport", NULL)) {
671 cfg->listener_autoport = g_key_file_get_integer(cfg->keyfile, "global", "listener-autoport", NULL);
672 } else if (g_key_file_has_key(cfg->keyfile, "listener", "autoport", NULL)) {
673 cfg->listener_autoport = g_key_file_get_integer(cfg->keyfile, "listener", "autoport", NULL);
674 g_key_file_remove_key(cfg->keyfile, "listener", "autoport", NULL);
677 if (g_key_file_has_key(cfg->keyfile, "global", "port", NULL)) {
678 struct listener_config *l = g_new0(struct listener_config, 1);
679 l->port = g_key_file_get_string(cfg->keyfile, "global", "port", NULL);
680 l->password = g_key_file_get_string(cfg->keyfile, "global", "password", NULL);
681 l->address = g_key_file_get_string(cfg->keyfile, "global", "bind", NULL);
682 l->ssl = g_key_file_has_key(cfg->keyfile, "global", "ssl", NULL) &&
683 g_key_file_get_boolean(cfg->keyfile, "global", "ssl", NULL);
684 l->is_default = TRUE;
686 l->network = g_key_file_get_string(cfg->keyfile, "global", "default-network", NULL);
688 cfg->listeners = g_list_append(cfg->listeners, l);
691 kf = g_key_file_new();
693 if (!g_key_file_load_from_file(kf, filename, G_KEY_FILE_KEEP_COMMENTS, &error)) {
694 if (error->code != G_FILE_ERROR_NOENT)
695 log_global(LOG_ERROR, "Can't parse configuration file '%s': %s", filename, error->message);
697 g_free(default_password);
701 groups = g_key_file_get_groups(kf, &size);
703 for (i = 0; i < size; i++)
705 struct listener_config *l;
708 l = g_new0(struct listener_config, 1);
710 tmp = g_strdup(groups[i]);
711 l->port = strrchr(tmp, ':');
712 if (l->port != NULL) {
721 l->password = g_key_file_get_string(kf, groups[i], "password", NULL);
722 if (l->password == NULL)
723 l->password = g_strdup(default_password);
725 if (g_key_file_has_key(kf, groups[i], "ssl", NULL))
726 l->ssl = g_key_file_get_boolean(kf, groups[i], "ssl", NULL);
730 l->ssl_credentials = ssl_create_server_credentials(cfg, kf, groups[i]);
733 if (g_key_file_has_key(kf, groups[i], "network", NULL))
734 l->network = g_key_file_get_string(kf, groups[i], "network", NULL);
736 cfg->listeners = g_list_append(cfg->listeners, l);
740 g_free(default_password);
744 static void config_load_networks(struct ctrlproxy_config *cfg)
746 char *networksdir = g_build_filename(cfg->config_dir, "networks", NULL);
750 dir = g_dir_open(networksdir, 0, NULL);
754 while ((name = g_dir_read_name(dir))) {
755 if (name[0] == '.' || name[strlen(name)-1] == '~')
757 config_load_network(cfg, networksdir, name);
765 #define FETCH_SETTING(data, kf, section, prefix, name) (data)->name = g_key_file_get_string((kf), (section), prefix __STRING(name), NULL)
766 #define STORE_SETTING(data, kf, section, prefix, name) g_key_file_set_string((kf), (section), prefix __STRING(name), (data)->name)
768 static void config_save_log(struct log_file_config *data,
769 struct ctrlproxy_config *config)
772 g_key_file_set_string(config->keyfile, "global", "logging", "none");
776 if (data->is_irssi) {
777 g_key_file_set_string(config->keyfile, "global", "logging", "irssi");
778 if (data->logbasedir)
779 g_key_file_set_string(config->keyfile, "global", "logdir", data->logbasedir);
781 g_key_file_set_string(config->keyfile, "global", "logfile", data->logfilename);
783 STORE_SETTING(data, config->keyfile, "global", "", logfilename);
784 STORE_SETTING(data, config->keyfile, "global", "log-format-", nickchange);
785 STORE_SETTING(data, config->keyfile, "global", "log-format-", topic);
786 STORE_SETTING(data, config->keyfile, "global", "log-format-", notopic);
787 STORE_SETTING(data, config->keyfile, "global", "log-format-", part);
788 STORE_SETTING(data, config->keyfile, "global", "log-format-", join);
789 STORE_SETTING(data, config->keyfile, "global", "log-format-", msg);
790 STORE_SETTING(data, config->keyfile, "global", "log-format-", notice);
791 STORE_SETTING(data, config->keyfile, "global", "log-format-", action);
792 STORE_SETTING(data, config->keyfile, "global", "log-format-", kick);
793 STORE_SETTING(data, config->keyfile, "global", "log-format-", quit);
794 STORE_SETTING(data, config->keyfile, "global", "log-format-", mode);
798 static void config_load_log(struct ctrlproxy_config *config)
800 GKeyFile *kf = config->keyfile;
801 struct log_file_config *data;
802 char *logging = NULL;
804 if (g_key_file_get_string(kf, "global", "logging", NULL) != NULL) {
805 logging = g_key_file_get_string(kf, "global", "logging", NULL);
808 if (g_key_file_has_group(kf, "log-custom")) {
809 data = g_new0(struct log_file_config, 1);
810 g_key_file_remove_group(kf, "log-custom", NULL);
812 FETCH_SETTING(data, kf, "log-custom", "", nickchange);
813 FETCH_SETTING(data, kf, "log-custom", "", logfilename);
814 FETCH_SETTING(data, kf, "log-custom", "", topic);
815 FETCH_SETTING(data, kf, "log-custom", "", notopic);
816 FETCH_SETTING(data, kf, "log-custom", "", part);
817 FETCH_SETTING(data, kf, "log-custom", "", join);
818 FETCH_SETTING(data, kf, "log-custom", "", msg);
819 FETCH_SETTING(data, kf, "log-custom", "", notice);
820 FETCH_SETTING(data, kf, "log-custom", "", action);
821 FETCH_SETTING(data, kf, "log-custom", "", kick);
822 FETCH_SETTING(data, kf, "log-custom", "", quit);
823 FETCH_SETTING(data, kf, "log-custom", "", mode);
825 config->log_file = data;
826 log_custom_load(data);
829 if (logging != NULL && !strcmp(logging, "custom")) {
830 data = g_new0(struct log_file_config, 1);
832 FETCH_SETTING(data, kf, "global", "", logfilename);
833 FETCH_SETTING(data, kf, "global", "log-format-", nickchange);
834 FETCH_SETTING(data, kf, "global", "log-format-", topic);
835 FETCH_SETTING(data, kf, "global", "log-format-", notopic);
836 FETCH_SETTING(data, kf, "global", "log-format-", part);
837 FETCH_SETTING(data, kf, "global", "log-format-", join);
838 FETCH_SETTING(data, kf, "global", "log-format-", msg);
839 FETCH_SETTING(data, kf, "global", "log-format-", notice);
840 FETCH_SETTING(data, kf, "global", "log-format-", action);
841 FETCH_SETTING(data, kf, "global", "log-format-", kick);
842 FETCH_SETTING(data, kf, "global", "log-format-", quit);
843 FETCH_SETTING(data, kf, "global", "log-format-", mode);
845 config->log_file = data;
846 log_custom_load(data);
849 if (g_key_file_has_group(kf, "log-irssi") ||
850 (logging != NULL && !strcmp(logging, "irssi"))) {
851 data = g_new0(struct log_file_config, 1);
852 data->is_irssi = TRUE;
854 data->join = "%h:%M -!- %n [%u] has joined %c";
855 data->part = "%h:%M -!- %n [%u] has left %c [%m]";
856 data->msg = "%h:%M < %n> %m";
857 data->notice = "%h:%M < %n> %m";
858 data->action = "%h:%M * %n %m";
859 data->mode = "%h:%M -!- mode/%t [%p %c] by %n";
860 data->quit = "%h:%M -!- %n [%u] has quit [%m]";
861 data->kick = "%h:%M -!- %t has been kicked by %n [%m]";
862 data->topic = "%h:%M -!- %n has changed the topic to %t";
863 data->notopic = "%h:%M -!- %n has removed the topic";
864 data->nickchange = "%h:%M -!- %n is now known as %r";
866 if (g_key_file_has_key(kf, "global", "logfile", NULL)) {
867 data->logfilename= g_key_file_get_string(kf, "global", "logfile", NULL);
868 } else if (g_key_file_has_key(kf, "global", "logdir", NULL)) {
869 data->logbasedir = g_key_file_get_string(kf, "global", "logdir", NULL);
870 data->logfilename = g_strdup_printf("%s/%%N/%%@", data->logbasedir);
871 } else if (g_key_file_has_key(kf, "log-irssi", "logfile", NULL)) {
872 data->logbasedir = g_key_file_get_string(kf, "log-irssi", "logfile", NULL);
873 data->logfilename = g_strdup_printf("%s/%%N/%%@", data->logbasedir);
875 data->logbasedir = g_build_filename(config->config_dir,
878 data->logfilename = g_strdup_printf("%s/%%N/%%@", data->logbasedir);
880 g_key_file_remove_group(kf, "log-irssi", NULL);
882 config->log_file = data;
883 log_custom_load(data);
886 if (logging != NULL &&
887 strcmp(logging, "irssi") != 0 &&
888 strcmp(logging, "custom") != 0 &&
889 strcmp(logging, "none") != 0) {
890 log_global(LOG_WARNING, "Unknown log type `%s'", logging);
896 static void config_load_auto_away(struct ctrlproxy_config *config)
898 struct auto_away_config *d;
899 GKeyFile *kf = config->keyfile;
901 if (g_key_file_has_group(kf, "auto-away")) {
902 d = g_new0(struct auto_away_config, 1);
904 d->message = g_key_file_get_string(kf, "auto-away", "message", NULL);
905 d->nick = g_key_file_get_string(kf, "auto-away", "nick", NULL);
906 if (g_key_file_has_key(kf, "auto-away", "client_limit", NULL)) {
907 d->client_limit = g_key_file_get_integer(kf, "auto-away", "client_limit", NULL);
908 if (g_key_file_has_key(kf, "auto-away", "only_noclient", NULL))
909 log_global(LOG_WARNING, "auto-away: not using only_noclient because client_limit is set");
911 else if (g_key_file_has_key(kf, "auto-away", "only_noclient", NULL)) {
912 d->client_limit = g_key_file_get_boolean(kf, "auto-away", "only_noclient", NULL) ? 0 : -1;
913 log_global(LOG_WARNING, "auto-away: only_noclient is deprecated, please use client_limit instead");
916 d->client_limit = -1;
917 if (g_key_file_has_key(kf, "auto-away", "time", NULL))
918 d->max_idle_time = g_key_file_get_integer(kf, "auto-away", "time", NULL);
920 d->max_idle_time = AUTO_AWAY_DEFAULT_TIME;
922 g_key_file_remove_group(kf, "auto-away", NULL);
923 } else if (g_key_file_has_key(kf, "global", "auto-away-enable", NULL) &&
924 g_key_file_get_boolean(kf, "global", "auto-away-enable", NULL)) {
925 d = g_new0(struct auto_away_config, 1);
927 d->message = g_key_file_get_string(kf, "global", "auto-away-message", NULL);
928 d->nick = g_key_file_get_string(kf, "global", "auto-away-nick", NULL);
929 if (g_key_file_has_key(kf, "global", "auto-away-client-limit", NULL)) {
930 d->client_limit = g_key_file_get_integer(kf, "global", "auto-away-client-limit", NULL);
933 d->client_limit = -1;
934 if (g_key_file_has_key(kf, "global", "auto-away-time", NULL))
935 d->max_idle_time = g_key_file_get_integer(kf, "global", "auto-away-time", NULL);
937 d->max_idle_time = AUTO_AWAY_DEFAULT_TIME;
942 config->auto_away = d;
945 struct ctrlproxy_config *init_configuration(void)
947 struct ctrlproxy_config *cfg;
948 cfg = g_new0(struct ctrlproxy_config, 1);
953 struct ctrlproxy_config *load_configuration(const char *dir)
956 GError *error = NULL;
957 struct ctrlproxy_config *cfg;
960 char **autoconnect_list;
965 file = g_build_filename(dir, "config", NULL);
967 cfg = init_configuration();
968 cfg->config_dir = g_strdup(dir);
969 cfg->network_socket = g_build_filename(cfg->config_dir, "socket", NULL);
970 cfg->admin_socket = g_build_filename(cfg->config_dir, "admin", NULL);
972 kf = cfg->keyfile = g_key_file_new();
974 if (!g_key_file_load_from_file(kf, file, G_KEY_FILE_KEEP_COMMENTS, &error)) {
975 log_global(LOG_ERROR, "Can't parse configuration file '%s': %s", file, error->message);
982 cfg->autosave = TRUE;
983 if (g_key_file_has_key(kf, "global", "autosave", NULL) &&
984 !g_key_file_get_boolean(kf, "global", "autosave", NULL))
985 cfg->autosave = FALSE;
988 if (g_key_file_has_key(kf, "global", "max_who_age", NULL))
989 cfg->max_who_age = g_key_file_get_integer(kf, "global", "max_who_age", NULL);
991 cfg->replication = g_key_file_get_string(kf, "global", "replication", NULL);
992 cfg->linestack_backend = g_key_file_get_string(kf, "global", "linestack", NULL);
994 if (g_key_file_has_key(kf, "global", "report-time", NULL)) {
995 char *setting = g_key_file_get_string(kf, "global", "report-time", NULL);
996 if (!g_strcasecmp(setting, "never") || !g_strcasecmp(setting, "false"))
997 cfg->report_time = REPORT_TIME_NEVER;
998 else if (!g_strcasecmp(setting, "always"))
999 cfg->report_time = REPORT_TIME_ALWAYS;
1000 else if (!g_strcasecmp(setting, "replication") ||
1001 !g_strcasecmp(setting, "true"))
1002 cfg->report_time = REPORT_TIME_REPLICATION;
1004 log_global(LOG_WARNING, "Unknown value `%s' for report-time in configuration file", setting);
1009 if (g_key_file_has_key(kf, "global", "motd-file", NULL))
1010 cfg->motd_file = g_key_file_get_string(kf, "global", "motd-file", NULL);
1012 cfg->motd_file = g_build_filename(SHAREDIR, "motd", NULL);
1014 if (g_key_file_has_key(kf, "client", "charset", NULL)) {
1015 cfg->client_charset = g_key_file_get_string(kf, "client", "charset", NULL);
1016 g_key_file_remove_key(cfg->keyfile, "client", "charset", NULL); /* deprecated */
1017 } else if (g_key_file_has_key(kf, "global", "client-charset", NULL)) {
1018 cfg->client_charset = g_key_file_get_string(kf, "global", "client-charset", NULL);
1019 g_key_file_remove_key(cfg->keyfile, "global", "client-charset", NULL); /* deprecated */
1020 } else if (g_key_file_has_key(kf, "global", "default-client-charset", NULL)) {
1021 cfg->client_charset = g_key_file_get_string(kf, "global", "default-client-charset", NULL);
1023 cfg->client_charset = NULL;
1026 if (g_key_file_has_key(kf, "global", "learn-nickserv", NULL))
1027 cfg->learn_nickserv = g_key_file_get_boolean(kf, "global", "learn-nicksev", NULL);
1029 cfg->learn_nickserv = TRUE;
1031 if (g_key_file_has_key(kf, "global", "learn-network-name", NULL))
1032 cfg->learn_network_name = g_key_file_get_boolean(kf, "global", "learn-network-name", NULL);
1034 cfg->learn_network_name = TRUE;
1036 if (!g_file_test(cfg->motd_file, G_FILE_TEST_EXISTS))
1037 log_global(LOG_ERROR, "Can't open MOTD file '%s' for reading", cfg->motd_file);
1039 if (g_key_file_has_key(kf, "admin", "without_privmsg", NULL)) {
1040 if (g_key_file_get_boolean(kf, "admin", "without_privmsg", NULL)) {
1041 cfg->admin_user = NULL;
1043 cfg->admin_user = g_strdup("ctrlproxy");
1045 g_key_file_remove_key(kf, "admin", "without_privmsg", NULL);
1048 if (g_key_file_has_key(kf, "global", "admin-user", NULL)) {
1049 cfg->admin_user = g_key_file_get_string(kf, "global", "admin-user", NULL);
1052 cfg->admin_log = TRUE;
1053 if (g_key_file_has_key(kf, "admin", "log", NULL) && !g_key_file_get_boolean(kf, "admin", "log", NULL))
1054 cfg->admin_log = FALSE;
1055 g_key_file_remove_key(kf, "admin", "log", NULL);
1056 if (g_key_file_has_key(kf, "global", "admin-log", NULL) && !g_key_file_get_boolean(kf, "global", "admin-log", NULL))
1057 cfg->admin_log = FALSE;
1058 g_key_file_remove_group(kf, "admin", NULL);
1060 for (gl = cfg->networks; gl; gl = gl->next) {
1061 struct network_config *nc = gl->data;
1063 nc->autoconnect = FALSE;
1066 config_load_listeners(cfg);
1067 config_load_listeners_socks(cfg);
1068 config_load_log(cfg);
1069 config_load_auto_away(cfg);
1070 config_load_networks(cfg);
1072 /* Check for unknown parameters */
1073 keys = g_key_file_get_keys(kf, "global", NULL, NULL);
1074 for (i = 0; keys[i] != NULL; i++) {
1075 if (!config_known_key(keys[i]))
1076 log_global(LOG_WARNING, "Unknown setting `%s' in configuration file", keys[i]);
1081 autoconnect_list = g_key_file_get_string_list(kf, "global", "autoconnect", &size, NULL);
1083 for (i = 0; i < size; i++) {
1084 struct network_config *nc = find_create_network_config(cfg, autoconnect_list[i]);
1087 nc->autoconnect = TRUE;
1090 g_strfreev(autoconnect_list);
1097 struct network_config *network_config_init(struct ctrlproxy_config *cfg)
1099 struct network_config *s = g_new0(struct network_config, 1);
1101 s->autoconnect = FALSE;
1102 s->nick = g_strdup(g_get_user_name());
1103 s->username = g_strdup(g_get_user_name());
1104 g_assert(s->username != NULL && strlen(s->username) > 0);
1105 s->fullname = g_strdup(g_get_real_name());
1106 if (s->fullname == NULL ||
1107 strlen(s->fullname) == 0) {
1108 g_free(s->fullname);
1109 s->fullname = g_strdup(s->username);
1111 s->reconnect_interval = DEFAULT_RECONNECT_INTERVAL;
1114 cfg->networks = g_list_append(cfg->networks, s);
1118 void free_config(struct ctrlproxy_config *cfg)
1120 while (cfg->networks) {
1121 struct network_config *nc = cfg->networks->data;
1124 g_free(nc->fullname);
1125 g_free(nc->username);
1126 g_free(nc->password);
1127 while (nc->channels) {
1128 struct channel_config *cc = nc->channels->data;
1131 nc->channels = g_list_remove(nc->channels, cc);
1136 while (nc->type_settings.tcp_servers) {
1137 struct tcp_server_config *tc = nc->type_settings.tcp_servers->data;
1140 g_free(tc->bind_address);
1141 g_free(tc->password);
1142 nc->type_settings.tcp_servers = g_list_remove(nc->type_settings.tcp_servers, tc);
1146 case NETWORK_VIRTUAL:
1147 g_free(nc->type_settings.virtual_type);
1149 case NETWORK_PROGRAM:
1150 g_free(nc->type_settings.program_location);
1152 case NETWORK_IOCHANNEL:
1153 /* Nothing to free */
1156 cfg->networks = g_list_remove(cfg->networks, nc);
1157 if (nc->keyfile) g_key_file_free(nc->keyfile);
1160 g_free(cfg->config_dir);
1161 g_free(cfg->network_socket);
1162 g_free(cfg->admin_socket);
1163 g_free(cfg->replication);
1164 g_free(cfg->linestack_backend);
1165 g_free(cfg->motd_file);
1166 g_free(cfg->admin_user);
1167 g_key_file_free(cfg->keyfile);
1171 gboolean create_configuration(const char *config_dir)
1173 struct global *global;
1175 struct listener_config *l;
1178 if (g_file_test(config_dir, G_FILE_TEST_IS_DIR)) {
1179 fprintf(stderr, "%s already exists\n", config_dir);
1183 if (g_mkdir(config_dir, 0700) != 0) {
1184 fprintf(stderr, "Can't create config directory '%s': %s\n", config_dir, strerror(errno));
1188 global = load_global(DEFAULT_CONFIG_DIR);
1189 if (global == NULL) {
1190 fprintf(stderr, "Unable to load default configuration '%s'\n", DEFAULT_CONFIG_DIR);
1193 global->config->config_dir = g_strdup(config_dir);
1194 save_configuration(global->config, config_dir);
1196 snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
1197 printf("Please specify port the administration interface should listen on.\n"
1198 "Prepend with a colon to listen on a specific address.\n"
1199 "Example: localhost:6668\n\nPort [%s]: ", port); fflush(stdout);
1200 if (!fgets(port, sizeof(port), stdin))
1201 snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
1203 if (port[strlen(port)-1] == '\n')
1204 port[strlen(port)-1] = '\0';
1206 if (strlen(port) == 0)
1207 snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
1209 l = g_new0(struct listener_config, 1);
1210 pass = getpass("Please specify a password for the administration interface: ");
1212 if (!strcmp(pass, "")) {
1213 fprintf(stderr, "Warning: no password specified. Authentication disabled!\n");
1218 global->config->listeners = g_list_append(global->config->listeners, l);