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"
25 #ifdef HAVE_SYS_SOCKET_H
26 #include <sys/socket.h>
30 #include <sys/socket.h>
31 #include <glib/gstdio.h>
33 #define DEFAULT_ADMIN_PORT 6680
34 #define DEFAULT_SOCKS_PORT 1080
36 #define CHANNEL_KEY_FILE_HEADER \
37 "; This file contains channel keys.\n" \
38 "; It has the same format as the ppp secrets files.\n" \
39 "; It should contain one entry per line, each entry consisting of: \n" \
40 "; a channel name, channel key and network name, separated by tabs.\n" \
43 static GList *known_keys = NULL;
45 static void config_save_log(struct log_file_config *data,
46 struct ctrlproxy_config *config);
47 static void config_save_auto_away(struct auto_away_config *d,
48 struct ctrlproxy_config *config);
50 static const char *builtin_known_keys[] = {
56 "auto-away-client-limit",
64 "default-client-charset",
80 "log-format-nickchange",
94 static gboolean config_known_key(const char *name)
98 if (g_list_find_custom(known_keys, name, (GCompareFunc)strcmp) != NULL)
101 for (i = 0; builtin_known_keys[i]; i++)
102 if (!strcmp(builtin_known_keys[i], name))
108 void config_register_known_key(char *name)
110 if (config_known_key(name))
112 known_keys = g_list_insert_sorted(known_keys, g_strdup(name), (GCompareFunc)strcmp);
117 gboolean g_key_file_save_to_file(GKeyFile *kf, const gchar *file, GError **error)
120 char *data = g_key_file_to_data(kf, &length, error);
126 gio = g_io_channel_new_file(file, "w+", error);
132 g_io_channel_write_chars(gio, data, length, &nr, error);
136 g_io_channel_unref(gio);
141 static void config_save_tcp_servers(struct network_config *n, GKeyFile *kf)
145 gchar **values = g_new0(gchar *, g_list_length(n->type_settings.tcp_servers)+1);
147 for (gl = n->type_settings.tcp_servers; gl; gl = gl->next) {
148 struct tcp_server_config *ts = gl->data;
149 char *name = g_strdup_printf("%s:%s", ts->host, ts->port);
153 if (g_key_file_has_key(kf, name, "ssl", NULL) || ts->ssl)
154 g_key_file_set_boolean(kf, name, "ssl", ts->ssl);
157 g_key_file_set_string(kf, name, "password", ts->password);
159 g_key_file_remove_key(kf, name, "password", NULL);
161 if (ts->bind_address) {
164 tmp = g_strdup_printf("%s:%s",
168 tmp = g_strdup(ts->bind_address);
170 g_key_file_set_string(kf, name, "bind", tmp);
174 g_key_file_remove_key(kf, name, "bind", NULL);
179 g_key_file_set_string_list(kf, "global", "servers", (const gchar **)values, i);
184 static void config_save_network(const char *dir, struct network_config *n, GList **channel_keys)
189 char **autojoin_list;
190 int autojoin_list_count;
193 n->keyfile = g_key_file_new();
198 g_key_file_set_string(kf, "global", "fullname", n->fullname);
199 g_key_file_set_string(kf, "global", "nick", n->nick);
200 g_key_file_set_string(kf, "global", "username", n->username);
202 g_key_file_set_integer(kf, "global", "queue-speed", n->queue_speed);
203 if (n->reconnect_interval != -1)
204 g_key_file_set_integer(kf, "global", "reconnect-interval", n->reconnect_interval);
207 case NETWORK_VIRTUAL:
208 g_key_file_set_string(kf, "global", "virtual", n->type_settings.virtual_type);
210 case NETWORK_PROGRAM:
211 g_key_file_set_string(kf, "global", "program", n->type_settings.program_location);
214 config_save_tcp_servers(n, kf);
219 autojoin_list = g_new0(char *, g_list_length(n->channels));
220 autojoin_list_count = 0;
222 for (gl = n->channels; gl; gl = gl->next) {
223 struct channel_config *c = gl->data;
224 struct keyfile_entry *key;
227 key = g_new0(struct keyfile_entry, 1);
228 key->network = n->name;
232 *channel_keys = g_list_append(*channel_keys, key);
236 autojoin_list[autojoin_list_count] = c->name;
237 autojoin_list_count++;
240 g_key_file_remove_group(kf, c->name, NULL);
243 if (autojoin_list == NULL)
244 g_key_file_remove_key(kf, "global", "autojoin", NULL);
246 g_key_file_set_string_list(kf, "global", "autojoin", (const gchar **)autojoin_list,
247 autojoin_list_count);
249 g_free(autojoin_list);
251 fn = g_build_filename(dir, n->name, NULL);
252 g_key_file_save_to_file(kf, fn, NULL);
256 static void config_save_listeners(struct ctrlproxy_config *cfg, const char *path)
261 GError *error = NULL;
262 gboolean empty = TRUE;
263 char *default_password;
265 default_password = g_key_file_get_string(cfg->keyfile, "global", "password", NULL);
267 if (cfg->auto_listener) {
268 g_key_file_set_boolean(cfg->keyfile, "global", "listener-auto", cfg->auto_listener);
269 g_key_file_set_integer(cfg->keyfile, "global", "listener-autoport", cfg->listener_autoport);
272 filename = g_build_filename(path, "listener", NULL);
274 kf = g_key_file_new();
276 for (gl = cfg->listeners; gl; gl = gl->next) {
277 struct listener_config *l = gl->data;
280 g_key_file_set_string(cfg->keyfile, "global", "port", l->port);
281 if (l->address != NULL)
282 g_key_file_set_string(cfg->keyfile, "global", "bind", l->address);
283 if (l->password != NULL)
284 g_key_file_set_string(cfg->keyfile, "global", "password", l->password);
286 if (g_key_file_has_key(cfg->keyfile, "global", "ssl", NULL) || l->ssl)
287 g_key_file_set_boolean(cfg->keyfile, "global", "ssl", l->ssl);
289 if (l->network != NULL)
290 g_key_file_set_string(cfg->keyfile, "global", "default-network",
296 tmp = g_strdup(l->port);
298 tmp = g_strdup_printf("%s:%s", l->address, l->port);
300 if (l->password != NULL &&
301 !(default_password != NULL && strcmp(l->password, default_password) == 0))
302 g_key_file_set_string(kf, tmp, "password", l->password);
304 if (l->network != NULL) {
305 g_key_file_set_string(kf, tmp, "network", l->network);
308 g_key_file_set_boolean(kf, tmp, "ssl", l->ssl);
317 if (!g_key_file_save_to_file(kf, filename, &error)) {
318 log_global(LOG_WARNING, "Unable to save to \"%s\": %s", filename, error->message);
322 g_free(default_password);
326 static void config_save_networks(struct ctrlproxy_config *cfg, const char *config_dir, GList *networks)
328 char *networksdir = g_build_filename(config_dir, "networks", NULL);
330 GList *channel_keys = NULL;
332 if (!g_file_test(networksdir, G_FILE_TEST_IS_DIR)) {
333 if (g_mkdir(networksdir, 0700) != 0) {
334 log_global(LOG_ERROR, "Can't create networks directory '%s': %s", networksdir, strerror(errno));
339 for (gl = networks; gl; gl = gl->next) {
340 struct network_config *n = gl->data;
341 config_save_network(networksdir, n, &channel_keys);
344 if (channel_keys != NULL) {
345 char *filename = g_build_filename(cfg->config_dir, "keys",
347 keyfile_write_file(channel_keys, CHANNEL_KEY_FILE_HEADER, filename);
350 while (channel_keys) {
351 g_free(channel_keys->data);
352 channel_keys = channel_keys->next;
355 g_list_free(channel_keys);
361 void save_configuration(struct ctrlproxy_config *cfg, const char *configuration_dir)
367 if (!g_file_test(configuration_dir, G_FILE_TEST_IS_DIR)) {
368 if (g_mkdir(configuration_dir, 0700) != 0) {
369 log_global(LOG_ERROR, "Unable to open configuration directory '%s'\n", configuration_dir);
375 cfg->keyfile = g_key_file_new();
377 g_key_file_set_boolean(cfg->keyfile, "global", "autosave", cfg->autosave);
378 if (cfg->admin_user != NULL)
379 g_key_file_set_string(cfg->keyfile, "global", "admin-user", cfg->admin_user);
381 if (g_key_file_has_key(cfg->keyfile, "global", "admin-log", NULL) ||
383 g_key_file_set_boolean(cfg->keyfile, "global", "admin-log", cfg->admin_log);
385 if (g_key_file_has_key(cfg->keyfile, "global", "max_who_age", NULL) ||
386 cfg->max_who_age != 0)
387 g_key_file_set_integer(cfg->keyfile, "global", "max_who_age", cfg->max_who_age);
389 if (g_key_file_has_key(cfg->keyfile, "global", "learn-nickserv", NULL) ||
390 !cfg->learn_nickserv)
391 g_key_file_set_boolean(cfg->keyfile, "global", "learn-nickserv", cfg->learn_nickserv);
393 if (g_key_file_has_key(cfg->keyfile, "global", "learn-network-name", NULL) ||
394 !cfg->learn_network_name)
395 g_key_file_set_boolean(cfg->keyfile, "global", "learn-network-name", cfg->learn_network_name);
397 if (cfg->client_charset != NULL)
398 g_key_file_set_string(cfg->keyfile, "global", "default-client-charset", cfg->client_charset);
400 if (cfg->replication)
401 g_key_file_set_string(cfg->keyfile, "global", "replication", cfg->replication);
402 if (cfg->linestack_backend)
403 g_key_file_set_string(cfg->keyfile, "global", "linestack", cfg->linestack_backend);
404 if (cfg->motd_file != NULL)
405 g_key_file_set_string(cfg->keyfile, "global", "motd-file", cfg->motd_file);
407 switch (cfg->report_time) {
408 case REPORT_TIME_ALWAYS:
409 g_key_file_set_string(cfg->keyfile, "global", "report-time",
412 case REPORT_TIME_NEVER:
413 g_key_file_set_string(cfg->keyfile, "global", "report-time",
416 case REPORT_TIME_REPLICATION:
417 g_key_file_set_string(cfg->keyfile, "global", "report-time",
422 if (cfg->report_time_offset != 0 ||
423 g_key_file_has_key(cfg->keyfile, "global", "report-time-offset", NULL))
424 g_key_file_set_integer(cfg->keyfile, "global", "report-time-offset", cfg->report_time_offset);
426 config_save_networks(cfg, configuration_dir, cfg->networks);
428 config_save_listeners(cfg, configuration_dir);
430 config_save_log(cfg->log_file, cfg);
432 config_save_auto_away(cfg->auto_away, cfg);
435 list = g_new0(char *, g_list_length(cfg->networks)+1);
436 for (gl = cfg->networks; gl; gl = gl->next) {
437 struct network_config *nc = gl->data;
439 if (nc->autoconnect) {
446 g_key_file_set_string_list(cfg->keyfile, "global", "autoconnect", (const gchar **)list, i);
450 fn = g_build_filename(configuration_dir, "config", NULL);
451 g_key_file_save_to_file(cfg->keyfile, fn, NULL);
455 static void config_load_channel(struct network_config *n, GKeyFile *kf, const char *name)
457 struct channel_config *ch = g_new0(struct channel_config, 1);
459 ch->name = g_strdup(name);
460 if (g_key_file_has_key(kf, name, "key", NULL))
461 ch->key = g_key_file_get_string(kf, name, "key", NULL);
463 if (g_key_file_has_key(kf, name, "autojoin", NULL))
464 ch->autojoin = g_key_file_get_boolean(kf, name, "autojoin", NULL);
466 n->channels = g_list_append(n->channels, ch);
469 static void config_load_servers(struct network_config *n)
475 servers = g_key_file_get_string_list(n->keyfile, "global", "servers", &size, NULL);
480 for (i = 0; i < size; i++) {
482 struct tcp_server_config *s = g_new0(struct tcp_server_config, 1);
484 s->password = g_key_file_get_string(n->keyfile, servers[i], "password", NULL);
485 if (g_key_file_has_key(n->keyfile, servers[i], "ssl", NULL))
486 s->ssl = g_key_file_get_boolean(n->keyfile, servers[i], "ssl", NULL);
488 tmp = strrchr(servers[i], ':');
495 s->host = servers[i];
496 s->port = g_strdup(tmp != NULL?tmp:DEFAULT_IRC_PORT);
497 s->bind_address = g_key_file_get_string(n->keyfile, servers[i], "bind", NULL);
498 if (s->bind_address && (tmp = strchr(s->bind_address, ':'))) {
500 s->bind_port = tmp+1;
503 n->type_settings.tcp_servers = g_list_append(n->type_settings.tcp_servers, s);
509 static struct channel_config *config_find_add_channel(struct network_config *nc, const char *name)
512 struct channel_config *cc;
514 for (gl = nc->channels; gl; gl = gl->next) {
516 if (!strcasecmp(cc->name, name))
521 cc = g_new0(struct channel_config, 1);
522 cc->name = g_strdup(name);
524 nc->channels = g_list_append(nc->channels, cc);
529 static struct network_config *config_load_network(struct ctrlproxy_config *cfg, const char *dirname,
530 const char *name, GList *channel_keys)
533 struct network_config *n;
538 GError *error = NULL;
541 kf = g_key_file_new();
543 filename = g_build_filename(dirname, name, NULL);
545 if (!g_key_file_load_from_file(kf, filename, G_KEY_FILE_KEEP_COMMENTS, &error)) {
546 log_global(LOG_ERROR, "Can't parse configuration file '%s': %s", filename, error->message);
551 n = network_config_init(cfg);
556 if (g_key_file_has_key(kf, "global", "fullname", NULL)) {
558 n->fullname = g_key_file_get_string(kf, "global", "fullname", NULL);
559 if (!strcmp(n->fullname, "") || n->fullname[0] == ' ')
560 log_global(LOG_WARNING, "Invalid fullname `%s' set for network `%s'", n->fullname, n->name);
563 if (g_key_file_has_key(kf, "global", "nick", NULL)) {
565 n->nick = g_key_file_get_string(kf, "global", "nick", NULL);
566 if (!strcmp(n->nick, "") || n->nick[0] == ' ')
567 log_global(LOG_WARNING, "Invalid nick name `%s' set for `%s'", n->nick, n->name);
570 if (g_key_file_has_key(kf, "global", "reconnect-interval", NULL)) {
571 n->reconnect_interval = g_key_file_get_integer(kf, "global", "reconnect-interval", NULL);
574 if (g_key_file_has_key(kf, "global", "queue-speed", NULL)) {
575 n->queue_speed = g_key_file_get_integer(kf, "global", "queue-speed", NULL);
578 if (g_key_file_has_key(kf, "global", "username", NULL)) {
580 n->username = g_key_file_get_string(kf, "global", "username", NULL);
581 if (!strcmp(n->username, "") || n->username[0] == ' ')
582 log_global(LOG_WARNING, "Invalid username `%s' set for network `%s'", n->username, n->name);
585 if (g_key_file_has_key(kf, "global", "ignore_first_nick", NULL)) {
586 n->ignore_first_nick = g_key_file_get_boolean(kf, "global", "ignore_first_nick", NULL);
589 if (g_key_file_has_key(kf, "global", "password", NULL)) {
591 n->password = g_key_file_get_string(kf, "global", "password", NULL);
594 n->name = g_strdup(name);
596 if (g_key_file_has_key(kf, "global", "program", NULL))
597 n->type = NETWORK_PROGRAM;
598 else if (g_key_file_has_key(kf, "global", "virtual", NULL))
599 n->type = NETWORK_VIRTUAL;
601 n->type = NETWORK_TCP;
605 config_load_servers(n);
607 case NETWORK_PROGRAM:
608 n->type_settings.program_location = g_key_file_get_string(kf, "global", "program", NULL);
610 case NETWORK_VIRTUAL:
611 n->type_settings.virtual_type = g_key_file_get_string(kf, "global", "virtual", NULL);
613 case NETWORK_IOCHANNEL:
618 groups = g_key_file_get_groups(kf, &size);
619 for (i = 0; i < size; i++) {
620 if (!g_ascii_isalpha(groups[i][0]))
621 config_load_channel(n, kf, groups[i]);
626 for (gl = channel_keys; gl; gl = gl->next) {
627 struct keyfile_entry *ke = gl->data;
629 if (!strcasecmp(ke->network, n->name)) {
630 struct channel_config *cc = config_find_add_channel(n, n->nick);
633 cc->key = g_strdup(n->password);
637 if (g_key_file_has_key(n->keyfile, "global", "autojoin", NULL)) {
638 char **autojoin_channels;
639 autojoin_channels = g_key_file_get_string_list(n->keyfile, "global", "autojoin", &size, NULL);
640 for (i = 0; i < size; i++) {
641 struct channel_config *cc = config_find_add_channel(n, autojoin_channels[i]);
646 g_strfreev(autojoin_channels);
652 static struct network_config *find_create_network_config(struct ctrlproxy_config *cfg, const char *name)
655 struct network_config *nc;
656 struct tcp_server_config *tc;
658 for (gl = cfg->networks; gl; gl = gl->next) {
662 if (g_strcasecmp(nc->name, name) == 0)
665 if (nc->type != NETWORK_TCP)
668 for (gl1 = nc->type_settings.tcp_servers; gl1; gl1 = gl1->next) {
670 struct tcp_server_config *sc = gl1->data;
672 if (g_strcasecmp(sc->host, name) == 0)
675 if (g_strncasecmp(sc->host, name, strlen(sc->host)) != 0)
678 tmp = g_strdup_printf("%s:%s", sc->host, sc->port);
680 if (g_strcasecmp(tmp, name) == 0)
687 nc = network_config_init(cfg);
688 nc->name = g_strdup(name);
689 nc->autoconnect = FALSE;
690 nc->reconnect_interval = -1;
691 nc->type = NETWORK_TCP;
692 tc = g_new0(struct tcp_server_config, 1);
693 tc->host = g_strdup(name);
694 if (strchr(tc->host, ':')) {
695 tc->port = tc->host+1;
698 tc->port = g_strdup(DEFAULT_IRC_PORT);
701 nc->type_settings.tcp_servers = g_list_append(nc->type_settings.tcp_servers, tc);
703 cfg->networks = g_list_append(cfg->networks, nc);
708 static void config_load_listeners_socks(struct ctrlproxy_config *cfg)
712 GKeyFile *kf = cfg->keyfile;
713 struct listener_config *l;
715 allows = g_key_file_get_string_list(kf, "socks", "allow", &size, NULL);
720 g_key_file_remove_key(kf, "socks", "allow", NULL);
722 l = g_new0(struct listener_config, 1);
724 if (g_key_file_has_key(kf, "socks", "port", NULL))
725 l->port = g_key_file_get_string(kf, "socks", "port", NULL);
727 l->port = g_strdup_printf("%d", DEFAULT_SOCKS_PORT);
729 /* We can use the socks listener as default listener, if there was
730 * no default listener specified */
731 if (cfg->listeners == NULL ||
732 !((struct listener_config *)cfg->listeners->data)->is_default)
733 l->is_default = TRUE;
735 g_key_file_remove_key(kf, "socks", "port", NULL);
737 for (i = 0; i < size; i++) {
738 struct allow_rule *r = g_new0(struct allow_rule, 1);
739 char **parts = g_strsplit(allows[i], ":", 2);
741 r->username = parts[0];
742 r->password = parts[1];
745 l->allow_rules = g_list_append(l->allow_rules, r);
748 g_key_file_remove_group(kf, "socks", NULL);
752 cfg->listeners = g_list_append(cfg->listeners, l);
755 static void config_load_listeners(struct ctrlproxy_config *cfg)
757 char *filename = g_build_filename(cfg->config_dir, "listener", NULL);
762 char *default_password;
763 GError *error = NULL;
765 if (g_key_file_has_key(cfg->keyfile, "listener", "pasword", NULL)) {
766 g_key_file_set_string(cfg->keyfile, "global", "password",
767 g_key_file_get_string(cfg->keyfile, "listener", "password", NULL));
768 g_key_file_remove_key(cfg->keyfile, "listener", "password", NULL);
770 default_password = g_key_file_get_string(cfg->keyfile, "global", "password", NULL);
771 if (g_key_file_has_key(cfg->keyfile, "global", "listener-auto", NULL)) {
772 cfg->auto_listener = g_key_file_get_boolean(cfg->keyfile, "global", "listener-auto", NULL);
773 } else if (g_key_file_has_key(cfg->keyfile, "listener", "auto", NULL)) {
774 cfg->auto_listener = g_key_file_get_boolean(cfg->keyfile, "listener", "auto", NULL);
775 g_key_file_remove_key(cfg->keyfile, "listener", "auto", NULL);
778 if (g_key_file_has_key(cfg->keyfile, "global", "listener-autoport", NULL)) {
779 cfg->listener_autoport = g_key_file_get_integer(cfg->keyfile, "global", "listener-autoport", NULL);
780 } else if (g_key_file_has_key(cfg->keyfile, "listener", "autoport", NULL)) {
781 cfg->listener_autoport = g_key_file_get_integer(cfg->keyfile, "listener", "autoport", NULL);
782 g_key_file_remove_key(cfg->keyfile, "listener", "autoport", NULL);
785 if (g_key_file_has_key(cfg->keyfile, "global", "port", NULL)) {
786 struct listener_config *l = g_new0(struct listener_config, 1);
787 l->port = g_key_file_get_string(cfg->keyfile, "global", "port", NULL);
788 l->password = g_key_file_get_string(cfg->keyfile, "global", "password", NULL);
789 l->address = g_key_file_get_string(cfg->keyfile, "global", "bind", NULL);
790 l->ssl = g_key_file_has_key(cfg->keyfile, "global", "ssl", NULL) &&
791 g_key_file_get_boolean(cfg->keyfile, "global", "ssl", NULL);
792 l->is_default = TRUE;
794 l->network = g_key_file_get_string(cfg->keyfile, "global", "default-network", NULL);
796 cfg->listeners = g_list_append(cfg->listeners, l);
799 kf = g_key_file_new();
801 if (!g_key_file_load_from_file(kf, filename, G_KEY_FILE_KEEP_COMMENTS, &error)) {
802 if (error->code != G_FILE_ERROR_NOENT)
803 log_global(LOG_ERROR, "Can't parse configuration file '%s': %s", filename, error->message);
805 g_free(default_password);
809 groups = g_key_file_get_groups(kf, &size);
811 for (i = 0; i < size; i++)
813 struct listener_config *l;
816 l = g_new0(struct listener_config, 1);
818 tmp = g_strdup(groups[i]);
819 l->port = strrchr(tmp, ':');
820 if (l->port != NULL) {
829 l->password = g_key_file_get_string(kf, groups[i], "password", NULL);
830 if (l->password == NULL)
831 l->password = g_strdup(default_password);
833 if (g_key_file_has_key(kf, groups[i], "ssl", NULL))
834 l->ssl = g_key_file_get_boolean(kf, groups[i], "ssl", NULL);
838 l->ssl_credentials = ssl_create_server_credentials(cfg, kf, groups[i]);
841 if (g_key_file_has_key(kf, groups[i], "network", NULL))
842 l->network = g_key_file_get_string(kf, groups[i], "network", NULL);
844 cfg->listeners = g_list_append(cfg->listeners, l);
848 g_free(default_password);
852 static void config_load_networks(struct ctrlproxy_config *cfg, GList *channel_keys)
854 char *networksdir = g_build_filename(cfg->config_dir, "networks", NULL);
858 dir = g_dir_open(networksdir, 0, NULL);
862 while ((name = g_dir_read_name(dir))) {
863 if (name[0] == '.' || name[strlen(name)-1] == '~')
865 config_load_network(cfg, networksdir, name, channel_keys);
873 #define FETCH_SETTING(data, kf, section, prefix, name) (data)->name = g_key_file_get_string((kf), (section), prefix __STRING(name), NULL)
874 #define STORE_SETTING(data, kf, section, prefix, name) g_key_file_set_string((kf), (section), prefix __STRING(name), (data)->name)
876 static void config_save_log(struct log_file_config *data,
877 struct ctrlproxy_config *config)
880 g_key_file_set_string(config->keyfile, "global", "logging", "none");
884 if (data->is_irssi) {
885 g_key_file_set_string(config->keyfile, "global", "logging", "irssi");
886 if (data->logbasedir)
887 g_key_file_set_string(config->keyfile, "global", "logdir", data->logbasedir);
889 g_key_file_set_string(config->keyfile, "global", "logfile", data->logfilename);
891 STORE_SETTING(data, config->keyfile, "global", "", logfilename);
892 STORE_SETTING(data, config->keyfile, "global", "log-format-", nickchange);
893 STORE_SETTING(data, config->keyfile, "global", "log-format-", topic);
894 STORE_SETTING(data, config->keyfile, "global", "log-format-", notopic);
895 STORE_SETTING(data, config->keyfile, "global", "log-format-", part);
896 STORE_SETTING(data, config->keyfile, "global", "log-format-", join);
897 STORE_SETTING(data, config->keyfile, "global", "log-format-", msg);
898 STORE_SETTING(data, config->keyfile, "global", "log-format-", notice);
899 STORE_SETTING(data, config->keyfile, "global", "log-format-", action);
900 STORE_SETTING(data, config->keyfile, "global", "log-format-", kick);
901 STORE_SETTING(data, config->keyfile, "global", "log-format-", quit);
902 STORE_SETTING(data, config->keyfile, "global", "log-format-", mode);
906 static void config_load_log(struct ctrlproxy_config *config)
908 GKeyFile *kf = config->keyfile;
909 struct log_file_config *data;
910 char *logging = NULL;
912 if (g_key_file_has_key(kf, "global", "logging", NULL)) {
913 logging = g_key_file_get_string(kf, "global", "logging", NULL);
916 if (g_key_file_has_group(kf, "log-custom")) {
917 data = g_new0(struct log_file_config, 1);
919 FETCH_SETTING(data, kf, "log-custom", "", nickchange);
920 FETCH_SETTING(data, kf, "log-custom", "", logfilename);
921 FETCH_SETTING(data, kf, "log-custom", "", topic);
922 FETCH_SETTING(data, kf, "log-custom", "", notopic);
923 FETCH_SETTING(data, kf, "log-custom", "", part);
924 FETCH_SETTING(data, kf, "log-custom", "", join);
925 FETCH_SETTING(data, kf, "log-custom", "", msg);
926 FETCH_SETTING(data, kf, "log-custom", "", notice);
927 FETCH_SETTING(data, kf, "log-custom", "", action);
928 FETCH_SETTING(data, kf, "log-custom", "", kick);
929 FETCH_SETTING(data, kf, "log-custom", "", quit);
930 FETCH_SETTING(data, kf, "log-custom", "", mode);
932 g_key_file_remove_group(kf, "log-custom", NULL);
933 config->log_file = data;
934 log_custom_load(data);
937 if (logging != NULL && !strcmp(logging, "custom")) {
938 data = g_new0(struct log_file_config, 1);
940 FETCH_SETTING(data, kf, "global", "", logfilename);
941 FETCH_SETTING(data, kf, "global", "log-format-", nickchange);
942 FETCH_SETTING(data, kf, "global", "log-format-", topic);
943 FETCH_SETTING(data, kf, "global", "log-format-", notopic);
944 FETCH_SETTING(data, kf, "global", "log-format-", part);
945 FETCH_SETTING(data, kf, "global", "log-format-", join);
946 FETCH_SETTING(data, kf, "global", "log-format-", msg);
947 FETCH_SETTING(data, kf, "global", "log-format-", notice);
948 FETCH_SETTING(data, kf, "global", "log-format-", action);
949 FETCH_SETTING(data, kf, "global", "log-format-", kick);
950 FETCH_SETTING(data, kf, "global", "log-format-", quit);
951 FETCH_SETTING(data, kf, "global", "log-format-", mode);
953 config->log_file = data;
954 log_custom_load(data);
957 if (g_key_file_has_group(kf, "log-irssi") ||
958 (logging != NULL && !strcmp(logging, "irssi"))) {
959 data = g_new0(struct log_file_config, 1);
960 data->is_irssi = TRUE;
962 data->join = "%h:%M -!- %n [%u] has joined %c";
963 data->part = "%h:%M -!- %n [%u] has left %c [%m]";
964 data->msg = "%h:%M < %n> %m";
965 data->notice = "%h:%M < %n> %m";
966 data->action = "%h:%M * %n %m";
967 data->mode = "%h:%M -!- mode/%t [%p %c] by %n";
968 data->quit = "%h:%M -!- %n [%u] has quit [%m]";
969 data->kick = "%h:%M -!- %t has been kicked by %n [%m]";
970 data->topic = "%h:%M -!- %n has changed the topic to %t";
971 data->notopic = "%h:%M -!- %n has removed the topic";
972 data->nickchange = "%h:%M -!- %n is now known as %r";
974 if (g_key_file_has_key(kf, "global", "logfile", NULL)) {
975 data->logfilename= g_key_file_get_string(kf, "global", "logfile", NULL);
976 } else if (g_key_file_has_key(kf, "global", "logdir", NULL)) {
977 data->logbasedir = g_key_file_get_string(kf, "global", "logdir", NULL);
978 data->logfilename = g_strdup_printf("%s/%%N/%%@", data->logbasedir);
979 } else if (g_key_file_has_key(kf, "log-irssi", "logfile", NULL)) {
980 data->logbasedir = g_key_file_get_string(kf, "log-irssi", "logfile", NULL);
981 data->logfilename = g_strdup_printf("%s/%%N/%%@", data->logbasedir);
983 data->logbasedir = g_build_filename(config->config_dir,
986 data->logfilename = g_strdup_printf("%s/%%N/%%@", data->logbasedir);
988 g_key_file_remove_group(kf, "log-irssi", NULL);
990 config->log_file = data;
991 log_custom_load(data);
994 if (logging != NULL &&
995 strcmp(logging, "irssi") != 0 &&
996 strcmp(logging, "custom") != 0 &&
997 strcmp(logging, "none") != 0) {
998 log_global(LOG_WARNING, "Unknown log type `%s'", logging);
1004 static void config_save_auto_away(struct auto_away_config *d, struct ctrlproxy_config *config)
1006 GKeyFile *kf = config->keyfile;
1008 if (config->auto_away == NULL) {
1009 g_key_file_set_boolean(kf, "global", "auto-away-enable", FALSE);
1012 g_key_file_set_boolean(kf, "global", "auto-away-enable", TRUE);
1014 if (d->message != NULL)
1015 g_key_file_set_string(kf, "global", "auto-away-message", d->message);
1017 if (d->nick != NULL)
1018 g_key_file_set_string(kf, "global", "auto-away-nick", d->nick);
1020 if (d->client_limit != -1)
1021 g_key_file_set_integer(kf, "global", "auto-away-client-limit", d->client_limit);
1023 if (d->max_idle_time != -1)
1024 g_key_file_set_integer(kf, "global", "auto-away-time", d->max_idle_time);
1027 static void config_load_auto_away(struct ctrlproxy_config *config)
1029 struct auto_away_config *d;
1030 GKeyFile *kf = config->keyfile;
1032 if (g_key_file_has_group(kf, "auto-away")) {
1033 d = g_new0(struct auto_away_config, 1);
1035 d->message = g_key_file_get_string(kf, "auto-away", "message", NULL);
1036 d->nick = g_key_file_get_string(kf, "auto-away", "nick", NULL);
1037 if (g_key_file_has_key(kf, "auto-away", "client_limit", NULL)) {
1038 d->client_limit = g_key_file_get_integer(kf, "auto-away", "client_limit", NULL);
1039 if (g_key_file_has_key(kf, "auto-away", "only_noclient", NULL))
1040 log_global(LOG_WARNING, "auto-away: not using only_noclient because client_limit is set");
1042 else if (g_key_file_has_key(kf, "auto-away", "only_noclient", NULL)) {
1043 d->client_limit = g_key_file_get_boolean(kf, "auto-away", "only_noclient", NULL) ? 0 : -1;
1044 log_global(LOG_WARNING, "auto-away: only_noclient is deprecated, please use client_limit instead");
1047 d->client_limit = -1;
1048 if (g_key_file_has_key(kf, "auto-away", "time", NULL))
1049 d->max_idle_time = g_key_file_get_integer(kf, "auto-away", "time", NULL);
1051 d->max_idle_time = -1;
1053 g_key_file_remove_group(kf, "auto-away", NULL);
1054 } else if (g_key_file_has_key(kf, "global", "auto-away-enable", NULL) &&
1055 g_key_file_get_boolean(kf, "global", "auto-away-enable", NULL)) {
1056 d = g_new0(struct auto_away_config, 1);
1058 d->message = g_key_file_get_string(kf, "global", "auto-away-message", NULL);
1059 d->nick = g_key_file_get_string(kf, "global", "auto-away-nick", NULL);
1060 if (g_key_file_has_key(kf, "global", "auto-away-client-limit", NULL)) {
1061 d->client_limit = g_key_file_get_integer(kf, "global", "auto-away-client-limit", NULL);
1064 d->client_limit = -1;
1065 if (g_key_file_has_key(kf, "global", "auto-away-time", NULL))
1066 d->max_idle_time = g_key_file_get_integer(kf, "global", "auto-away-time", NULL);
1068 d->max_idle_time = -1;
1073 config->auto_away = d;
1076 struct ctrlproxy_config *init_configuration(void)
1078 struct ctrlproxy_config *cfg;
1079 cfg = g_new0(struct ctrlproxy_config, 1);
1084 struct ctrlproxy_config *load_configuration(const char *dir)
1087 GError *error = NULL;
1088 struct ctrlproxy_config *cfg;
1091 char **autoconnect_list;
1095 GList *channel_keys = NULL;
1096 char *keyfile_filename;
1098 file = g_build_filename(dir, "config", NULL);
1100 cfg = init_configuration();
1101 cfg->config_dir = g_strdup(dir);
1102 cfg->network_socket = g_build_filename(cfg->config_dir, "socket", NULL);
1103 cfg->admin_socket = g_build_filename(cfg->config_dir, "admin", NULL);
1105 kf = cfg->keyfile = g_key_file_new();
1107 if (!g_key_file_load_from_file(kf, file, G_KEY_FILE_KEEP_COMMENTS, &error)) {
1108 log_global(LOG_ERROR, "Can't parse configuration file '%s': %s", file, error->message);
1109 g_key_file_free(kf);
1115 cfg->autosave = TRUE;
1116 if (g_key_file_has_key(kf, "global", "autosave", NULL) &&
1117 !g_key_file_get_boolean(kf, "global", "autosave", NULL))
1118 cfg->autosave = FALSE;
1121 if (g_key_file_has_key(kf, "global", "max_who_age", NULL))
1122 cfg->max_who_age = g_key_file_get_integer(kf, "global", "max_who_age", NULL);
1124 cfg->replication = g_key_file_get_string(kf, "global", "replication", NULL);
1125 cfg->linestack_backend = g_key_file_get_string(kf, "global", "linestack", NULL);
1127 if (g_key_file_has_key(kf, "global", "report-time", NULL)) {
1128 char *setting = g_key_file_get_string(kf, "global", "report-time", NULL);
1129 if (!g_strcasecmp(setting, "never") || !g_strcasecmp(setting, "false"))
1130 cfg->report_time = REPORT_TIME_NEVER;
1131 else if (!g_strcasecmp(setting, "always"))
1132 cfg->report_time = REPORT_TIME_ALWAYS;
1133 else if (!g_strcasecmp(setting, "replication") ||
1134 !g_strcasecmp(setting, "true"))
1135 cfg->report_time = REPORT_TIME_REPLICATION;
1137 log_global(LOG_WARNING, "Unknown value `%s' for report-time in configuration file", setting);
1142 cfg->report_time_offset = 0;
1143 if (g_key_file_has_key(kf, "global", "report-time-offset", NULL)) {
1144 cfg->report_time_offset = g_key_file_get_integer(kf, "global", "report-time-offset", NULL);
1147 if (g_key_file_has_key(kf, "global", "motd-file", NULL))
1148 cfg->motd_file = g_key_file_get_string(kf, "global", "motd-file", NULL);
1150 cfg->motd_file = g_build_filename(SHAREDIR, "motd", NULL);
1152 if (g_key_file_has_key(kf, "client", "charset", NULL)) {
1153 cfg->client_charset = g_key_file_get_string(kf, "client", "charset", NULL);
1154 g_key_file_remove_key(cfg->keyfile, "client", "charset", NULL); /* deprecated */
1155 } else if (g_key_file_has_key(kf, "global", "client-charset", NULL)) {
1156 cfg->client_charset = g_key_file_get_string(kf, "global", "client-charset", NULL);
1157 g_key_file_remove_key(cfg->keyfile, "global", "client-charset", NULL); /* deprecated */
1158 } else if (g_key_file_has_key(kf, "global", "default-client-charset", NULL)) {
1159 cfg->client_charset = g_key_file_get_string(kf, "global", "default-client-charset", NULL);
1161 cfg->client_charset = NULL;
1164 if (g_key_file_has_key(kf, "global", "learn-nickserv", NULL))
1165 cfg->learn_nickserv = g_key_file_get_boolean(kf, "global", "learn-nicksev", NULL);
1167 cfg->learn_nickserv = TRUE;
1169 if (g_key_file_has_key(kf, "global", "learn-network-name", NULL))
1170 cfg->learn_network_name = g_key_file_get_boolean(kf, "global", "learn-network-name", NULL);
1172 cfg->learn_network_name = TRUE;
1174 if (!g_file_test(cfg->motd_file, G_FILE_TEST_EXISTS))
1175 log_global(LOG_ERROR, "Can't open MOTD file '%s' for reading", cfg->motd_file);
1177 if (g_key_file_has_key(kf, "admin", "without_privmsg", NULL)) {
1178 if (g_key_file_get_boolean(kf, "admin", "without_privmsg", NULL)) {
1179 cfg->admin_user = NULL;
1181 cfg->admin_user = g_strdup("ctrlproxy");
1183 g_key_file_remove_key(kf, "admin", "without_privmsg", NULL);
1186 if (g_key_file_has_key(kf, "global", "admin-user", NULL)) {
1187 cfg->admin_user = g_key_file_get_string(kf, "global", "admin-user", NULL);
1190 cfg->admin_log = TRUE;
1191 if (g_key_file_has_key(kf, "admin", "log", NULL) && !g_key_file_get_boolean(kf, "admin", "log", NULL))
1192 cfg->admin_log = FALSE;
1193 g_key_file_remove_key(kf, "admin", "log", NULL);
1194 if (g_key_file_has_key(kf, "global", "admin-log", NULL) && !g_key_file_get_boolean(kf, "global", "admin-log", NULL))
1195 cfg->admin_log = FALSE;
1196 g_key_file_remove_group(kf, "admin", NULL);
1198 for (gl = cfg->networks; gl; gl = gl->next) {
1199 struct network_config *nc = gl->data;
1201 nc->autoconnect = FALSE;
1204 config_load_listeners(cfg);
1205 config_load_listeners_socks(cfg);
1206 config_load_log(cfg);
1207 config_load_auto_away(cfg);
1209 keyfile_filename = g_build_filename(cfg->config_dir, "keys",
1212 if (g_file_test(keyfile_filename, G_FILE_TEST_EXISTS)) {
1213 if (!keyfile_read_file(keyfile_filename, ';', &channel_keys)) {
1214 log_global(LOG_WARNING, "Unable to read keys file");
1218 g_free(keyfile_filename);
1220 config_load_networks(cfg, channel_keys);
1222 /* Check for unknown parameters */
1223 keys = g_key_file_get_keys(kf, "global", NULL, NULL);
1224 for (i = 0; keys[i] != NULL; i++) {
1225 if (!config_known_key(keys[i]))
1226 log_global(LOG_WARNING, "Unknown setting `%s' in configuration file", keys[i]);
1231 autoconnect_list = g_key_file_get_string_list(kf, "global", "autoconnect", &size, NULL);
1233 for (i = 0; i < size; i++) {
1234 struct network_config *nc = find_create_network_config(cfg, autoconnect_list[i]);
1237 nc->autoconnect = TRUE;
1240 g_strfreev(autoconnect_list);
1247 struct network_config *network_config_init(struct ctrlproxy_config *cfg)
1249 struct network_config *s = g_new0(struct network_config, 1);
1251 s->autoconnect = FALSE;
1252 s->nick = g_strdup(g_get_user_name());
1253 s->username = g_strdup(g_get_user_name());
1254 g_assert(s->username != NULL && strlen(s->username) > 0);
1255 s->fullname = g_strdup(g_get_real_name());
1256 if (s->fullname == NULL ||
1257 strlen(s->fullname) == 0) {
1258 g_free(s->fullname);
1259 s->fullname = g_strdup(s->username);
1261 s->reconnect_interval = -1;
1264 cfg->networks = g_list_append(cfg->networks, s);
1268 void free_config(struct ctrlproxy_config *cfg)
1270 while (cfg->networks) {
1271 struct network_config *nc = cfg->networks->data;
1274 g_free(nc->fullname);
1275 g_free(nc->username);
1276 g_free(nc->password);
1277 while (nc->channels) {
1278 struct channel_config *cc = nc->channels->data;
1281 nc->channels = g_list_remove(nc->channels, cc);
1286 while (nc->type_settings.tcp_servers) {
1287 struct tcp_server_config *tc = nc->type_settings.tcp_servers->data;
1290 g_free(tc->bind_address);
1291 g_free(tc->password);
1292 nc->type_settings.tcp_servers = g_list_remove(nc->type_settings.tcp_servers, tc);
1296 case NETWORK_VIRTUAL:
1297 g_free(nc->type_settings.virtual_type);
1299 case NETWORK_PROGRAM:
1300 g_free(nc->type_settings.program_location);
1302 case NETWORK_IOCHANNEL:
1303 /* Nothing to free */
1306 cfg->networks = g_list_remove(cfg->networks, nc);
1307 if (nc->keyfile) g_key_file_free(nc->keyfile);
1310 g_free(cfg->config_dir);
1311 g_free(cfg->network_socket);
1312 g_free(cfg->admin_socket);
1313 g_free(cfg->replication);
1314 g_free(cfg->linestack_backend);
1315 g_free(cfg->motd_file);
1316 g_free(cfg->admin_user);
1317 g_key_file_free(cfg->keyfile);
1321 gboolean create_configuration(const char *config_dir)
1323 struct global *global;
1325 struct listener_config *l;
1328 if (g_file_test(config_dir, G_FILE_TEST_IS_DIR)) {
1329 fprintf(stderr, "%s already exists\n", config_dir);
1333 if (g_mkdir(config_dir, 0700) != 0) {
1334 fprintf(stderr, "Can't create config directory '%s': %s\n", config_dir, strerror(errno));
1338 global = load_global(DEFAULT_CONFIG_DIR);
1339 if (global == NULL) {
1340 fprintf(stderr, "Unable to load default configuration '%s'\n", DEFAULT_CONFIG_DIR);
1343 global->config->config_dir = g_strdup(config_dir);
1344 save_configuration(global->config, config_dir);
1346 snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
1347 printf("Please specify port the administration interface should listen on.\n"
1348 "Prepend with a colon to listen on a specific address.\n"
1349 "Example: localhost:6668\n\nPort [%s]: ", port); fflush(stdout);
1350 if (!fgets(port, sizeof(port), stdin))
1351 snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
1353 if (port[strlen(port)-1] == '\n')
1354 port[strlen(port)-1] = '\0';
1356 if (strlen(port) == 0)
1357 snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
1359 l = g_new0(struct listener_config, 1);
1360 pass = getpass("Please specify a password for the administration interface: ");
1362 if (!strcmp(pass, "")) {
1363 fprintf(stderr, "Warning: no password specified. Authentication disabled!\n");
1368 global->config->listeners = g_list_append(global->config->listeners, l);