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_cleanup_networks_dir(struct ctrlproxy_config *cfg);
46 static void config_save_log(struct log_file_config *data,
47 struct ctrlproxy_config *config);
48 static void config_save_auto_away(struct auto_away_config *d,
49 struct ctrlproxy_config *config);
51 static const char *builtin_known_keys[] = {
57 "auto-away-client-limit",
66 "default-client-charset",
83 "log-format-nickchange",
97 static gboolean config_known_key(const char *name)
101 if (g_list_find_custom(known_keys, name, (GCompareFunc)strcmp) != NULL)
104 for (i = 0; builtin_known_keys[i]; i++)
105 if (!strcmp(builtin_known_keys[i], name))
111 void config_register_known_key(char *name)
113 if (config_known_key(name))
115 known_keys = g_list_insert_sorted(known_keys, g_strdup(name), (GCompareFunc)strcmp);
120 gboolean g_key_file_save_to_file(GKeyFile *kf, const gchar *file, GError **error)
123 char *data = g_key_file_to_data(kf, &length, error);
129 gio = g_io_channel_new_file(file, "w+", error);
135 g_io_channel_write_chars(gio, data, length, &nr, error);
139 g_io_channel_unref(gio);
144 static void config_save_tcp_servers(struct network_config *n, GKeyFile *kf)
148 gchar **values = g_new0(gchar *, g_list_length(n->type_settings.tcp.servers)+1);
150 for (gl = n->type_settings.tcp.servers; gl; gl = gl->next) {
151 struct tcp_server_config *ts = gl->data;
152 char *name = irc_create_url(ts->host, ts->port, 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) {
162 g_key_file_set_string(kf, name, "bind", ts->bind_address);
164 g_key_file_remove_key(kf, name, "bind", NULL);
169 g_key_file_set_string_list(kf, "global", "servers", (const gchar **)values, i);
174 static void config_save_network(const char *dir, struct network_config *n, GList **channel_keys)
179 char **autojoin_list;
180 int autojoin_list_count;
183 n->keyfile = g_key_file_new();
188 g_key_file_set_string(kf, "global", "fullname", n->fullname);
189 g_key_file_set_string(kf, "global", "nick", n->nick);
190 g_key_file_set_string(kf, "global", "username", n->username);
192 g_key_file_set_string_list(kf, "global", "autocmd", n->autocmd,
193 g_strv_length(n->autocmd));
195 g_key_file_remove_key(kf, "global", "autocmd", NULL);
199 g_key_file_set_integer(kf, "global", "queue-speed", n->queue_speed);
200 if (n->reconnect_interval != -1)
201 g_key_file_set_integer(kf, "global", "reconnect-interval", n->reconnect_interval);
204 case NETWORK_VIRTUAL:
205 g_key_file_set_string(kf, "global", "virtual", n->type_settings.virtual_type);
207 case NETWORK_PROGRAM:
208 g_key_file_set_string(kf, "global", "program", n->type_settings.program_location);
211 config_save_tcp_servers(n, kf);
212 if (n->type_settings.tcp.default_bind_address != NULL)
213 g_key_file_set_string(kf, "global", "bind",
214 n->type_settings.tcp.default_bind_address);
216 g_key_file_remove_key(kf, "global", "bind", NULL);
221 autojoin_list = g_new0(char *, g_list_length(n->channels));
222 autojoin_list_count = 0;
224 for (gl = n->channels; gl; gl = gl->next) {
225 struct channel_config *c = gl->data;
226 struct keyfile_entry *key;
229 key = g_new0(struct keyfile_entry, 1);
230 key->network = n->name;
234 *channel_keys = g_list_append(*channel_keys, key);
238 autojoin_list[autojoin_list_count] = c->name;
239 autojoin_list_count++;
242 g_key_file_remove_group(kf, c->name, NULL);
245 if (autojoin_list == NULL)
246 g_key_file_remove_key(kf, "global", "autojoin", NULL);
248 g_key_file_set_string_list(kf, "global", "autojoin", (const gchar **)autojoin_list,
249 autojoin_list_count);
251 g_free(autojoin_list);
253 fn = g_build_filename(dir, n->name, NULL);
254 g_key_file_save_to_file(kf, fn, NULL);
258 static void config_save_listeners(struct ctrlproxy_config *cfg, const char *path)
263 GError *error = NULL;
264 gboolean empty = TRUE;
266 if (cfg->password != NULL)
267 g_key_file_set_string(cfg->keyfile, "global", "password", cfg->password);
269 if (cfg->auto_listener) {
270 g_key_file_set_boolean(cfg->keyfile, "global", "listener-auto", cfg->auto_listener);
271 g_key_file_set_integer(cfg->keyfile, "global", "listener-autoport", cfg->listener_autoport);
274 filename = g_build_filename(path, "listener", NULL);
276 kf = g_key_file_new();
278 for (gl = cfg->listeners; gl; gl = gl->next) {
279 struct listener_config *l = gl->data;
282 g_key_file_set_string(cfg->keyfile, "global", "port", l->port);
283 if (l->address != NULL)
284 g_key_file_set_string(cfg->keyfile, "global", "bind", l->address);
285 if (l->password != NULL)
286 g_key_file_set_string(cfg->keyfile, "global", "password", l->password);
288 if (g_key_file_has_key(cfg->keyfile, "global", "ssl", NULL) || l->ssl)
289 g_key_file_set_boolean(cfg->keyfile, "global", "ssl", l->ssl);
291 if (l->network != NULL)
292 g_key_file_set_string(cfg->keyfile, "global", "default-network",
294 cfg->default_listener = l;
299 tmp = g_strdup(l->port);
301 tmp = g_strdup_printf("%s:%s", l->address, l->port);
303 if (l->password != NULL)
304 g_key_file_set_string(kf, tmp, "password", l->password);
306 if (l->network != NULL) {
307 g_key_file_set_string(kf, tmp, "network", l->network);
310 g_key_file_set_boolean(kf, tmp, "ssl", l->ssl);
319 if (!g_key_file_save_to_file(kf, filename, &error)) {
320 log_global(LOG_WARNING, "Unable to save to \"%s\": %s", filename, error->message);
327 static void config_save_networks(struct ctrlproxy_config *cfg, const char *config_dir, GList *networks)
329 char *networksdir = g_build_filename(config_dir, "networks", NULL);
331 GList *channel_keys = NULL;
333 if (!g_file_test(networksdir, G_FILE_TEST_IS_DIR)) {
334 if (g_mkdir(networksdir, 0700) != 0) {
335 log_global(LOG_ERROR, "Can't create networks directory '%s': %s", networksdir, strerror(errno));
340 for (gl = networks; gl; gl = gl->next) {
341 struct network_config *n = gl->data;
343 config_save_network(networksdir, n, &channel_keys);
346 config_cleanup_networks_dir(cfg);
348 if (channel_keys != NULL) {
349 char *filename = g_build_filename(cfg->config_dir, "keys",
351 keyfile_write_file(channel_keys, CHANNEL_KEY_FILE_HEADER, filename);
354 while (channel_keys) {
355 g_free(channel_keys->data);
356 channel_keys = channel_keys->next;
359 g_list_free(channel_keys);
366 * Save configuration to a configuration directory.
368 * @param cfg The configuration to save.
369 * @param configuration_dir Directory to save to.
371 void save_configuration(struct ctrlproxy_config *cfg, const char *configuration_dir)
377 if (!g_file_test(configuration_dir, G_FILE_TEST_IS_DIR)) {
378 if (g_mkdir(configuration_dir, 0700) != 0) {
379 log_global(LOG_ERROR, "Unable to open configuration directory '%s'\n", configuration_dir);
385 cfg->keyfile = g_key_file_new();
387 g_key_file_set_boolean(cfg->keyfile, "global", "autosave", cfg->autosave);
388 if (cfg->admin_user != NULL)
389 g_key_file_set_string(cfg->keyfile, "global", "admin-user", cfg->admin_user);
391 if (g_key_file_has_key(cfg->keyfile, "global", "admin-log", NULL) ||
393 g_key_file_set_boolean(cfg->keyfile, "global", "admin-log", cfg->admin_log);
395 if (g_key_file_has_key(cfg->keyfile, "global", "max_who_age", NULL) ||
396 cfg->max_who_age != 0)
397 g_key_file_set_integer(cfg->keyfile, "global", "max_who_age", cfg->max_who_age);
399 if (g_key_file_has_key(cfg->keyfile, "global", "learn-nickserv", NULL) ||
400 !cfg->learn_nickserv)
401 g_key_file_set_boolean(cfg->keyfile, "global", "learn-nickserv", cfg->learn_nickserv);
403 if (g_key_file_has_key(cfg->keyfile, "global", "learn-network-name", NULL) ||
404 !cfg->learn_network_name)
405 g_key_file_set_boolean(cfg->keyfile, "global", "learn-network-name", cfg->learn_network_name);
407 if (g_key_file_has_key(cfg->keyfile, "global", "create-implicit", NULL) || !cfg->create_implicit)
408 g_key_file_set_boolean(cfg->keyfile, "global", "create-implicit", cfg->create_implicit);
410 if (cfg->client_charset != NULL)
411 g_key_file_set_string(cfg->keyfile, "global", "default-client-charset", cfg->client_charset);
413 if (cfg->replication)
414 g_key_file_set_string(cfg->keyfile, "global", "replication", cfg->replication);
415 if (cfg->linestack_backend)
416 g_key_file_set_string(cfg->keyfile, "global", "linestack", cfg->linestack_backend);
417 if (cfg->motd_file != NULL)
418 g_key_file_set_string(cfg->keyfile, "global", "motd-file", cfg->motd_file);
420 switch (cfg->report_time) {
421 case REPORT_TIME_ALWAYS:
422 g_key_file_set_string(cfg->keyfile, "global", "report-time",
425 case REPORT_TIME_NEVER:
426 g_key_file_set_string(cfg->keyfile, "global", "report-time",
429 case REPORT_TIME_REPLICATION:
430 g_key_file_set_string(cfg->keyfile, "global", "report-time",
435 if (cfg->report_time_offset != 0 ||
436 g_key_file_has_key(cfg->keyfile, "global", "report-time-offset", NULL))
437 g_key_file_set_integer(cfg->keyfile, "global", "report-time-offset", cfg->report_time_offset);
439 config_save_networks(cfg, configuration_dir, cfg->networks);
441 config_save_listeners(cfg, configuration_dir);
443 config_save_log(cfg->log_file, cfg);
445 config_save_auto_away(&cfg->auto_away, cfg);
448 list = g_new0(char *, g_list_length(cfg->networks)+1);
449 for (gl = cfg->networks; gl; gl = gl->next) {
450 struct network_config *nc = gl->data;
452 if (nc->autoconnect) {
459 g_key_file_set_string_list(cfg->keyfile, "global", "autoconnect", (const gchar **)list, i);
463 fn = g_build_filename(configuration_dir, "config", NULL);
464 g_key_file_save_to_file(cfg->keyfile, fn, NULL);
468 static void config_load_channel(struct network_config *n, GKeyFile *kf, const char *name)
470 struct channel_config *ch = g_new0(struct channel_config, 1);
472 ch->name = g_strdup(name);
473 if (g_key_file_has_key(kf, name, "key", NULL))
474 ch->key = g_key_file_get_string(kf, name, "key", NULL);
476 if (g_key_file_has_key(kf, name, "autojoin", NULL))
477 ch->autojoin = g_key_file_get_boolean(kf, name, "autojoin", NULL);
479 n->channels = g_list_append(n->channels, ch);
482 static void config_load_servers(struct network_config *n)
488 servers = g_key_file_get_string_list(n->keyfile, "global", "servers", &size, NULL);
493 for (i = 0; i < size; i++) {
494 struct tcp_server_config *s = g_new0(struct tcp_server_config, 1);
496 irc_parse_url(servers[i], &s->host, &s->port, &s->ssl);
498 s->password = g_key_file_get_string(n->keyfile, servers[i], "password", NULL);
499 if (g_key_file_has_key(n->keyfile, servers[i], "ssl", NULL))
500 s->ssl = g_key_file_get_boolean(n->keyfile, servers[i], "ssl", NULL);
502 s->bind_address = g_key_file_get_string(n->keyfile, servers[i], "bind", NULL);
504 n->type_settings.tcp.servers = g_list_append(n->type_settings.tcp.servers, s);
506 g_key_file_remove_group(n->keyfile, servers[i], NULL);
512 static struct channel_config *config_find_add_channel(struct network_config *nc, const char *name)
515 struct channel_config *cc;
517 for (gl = nc->channels; gl; gl = gl->next) {
519 if (!strcasecmp(cc->name, name))
524 cc = g_new0(struct channel_config, 1);
525 cc->name = g_strdup(name);
527 nc->channels = g_list_append(nc->channels, cc);
532 static struct network_config *config_load_network(struct ctrlproxy_config *cfg, const char *dirname,
533 const char *name, GList *channel_keys)
536 struct network_config *n;
541 GError *error = NULL;
544 kf = g_key_file_new();
546 filename = g_build_filename(dirname, name, NULL);
548 if (!g_key_file_load_from_file(kf, filename, G_KEY_FILE_KEEP_COMMENTS, &error)) {
549 log_global(LOG_ERROR, "Can't parse configuration file '%s': %s", filename, error->message);
555 n = network_config_init(cfg);
560 if (g_key_file_has_key(kf, "global", "fullname", NULL)) {
562 n->fullname = g_key_file_get_string(kf, "global", "fullname", NULL);
563 if (!strcmp(n->fullname, "") || n->fullname[0] == ' ')
564 log_global(LOG_WARNING, "Invalid fullname `%s' set for network `%s'", n->fullname, n->name);
567 if (g_key_file_has_key(kf, "global", "nick", NULL)) {
569 n->nick = g_key_file_get_string(kf, "global", "nick", NULL);
570 if (!strcmp(n->nick, "") || n->nick[0] == ' ')
571 log_global(LOG_WARNING, "Invalid nick name `%s' set for `%s'", n->nick, n->name);
574 if (g_key_file_has_key(kf, "global", "reconnect-interval", NULL)) {
575 n->reconnect_interval = g_key_file_get_integer(kf, "global", "reconnect-interval", NULL);
578 if (g_key_file_has_key(kf, "global", "queue-speed", NULL)) {
579 n->queue_speed = g_key_file_get_integer(kf, "global", "queue-speed", NULL);
582 if (g_key_file_has_key(kf, "global", "username", NULL)) {
584 n->username = g_key_file_get_string(kf, "global", "username", NULL);
585 if (!strcmp(n->username, "") || n->username[0] == ' ')
586 log_global(LOG_WARNING, "Invalid username `%s' set for network `%s'", n->username, n->name);
589 if (g_key_file_has_key(kf, "global", "ignore_first_nick", NULL)) {
590 n->ignore_first_nick = g_key_file_get_boolean(kf, "global", "ignore_first_nick", NULL);
593 if (g_key_file_has_key(kf, "global", "password", NULL)) {
595 n->password = g_key_file_get_string(kf, "global", "password", NULL);
598 if (g_key_file_has_key(kf, "global", "autocmd", NULL)) {
599 g_strfreev(n->autocmd);
600 n->autocmd = g_key_file_get_string_list(n->keyfile, "global", "autocmd", &size, NULL);
603 n->name = g_strdup(name);
605 if (g_key_file_has_key(kf, "global", "program", NULL))
606 n->type = NETWORK_PROGRAM;
607 else if (g_key_file_has_key(kf, "global", "virtual", NULL))
608 n->type = NETWORK_VIRTUAL;
610 n->type = NETWORK_TCP;
614 n->type_settings.tcp.default_bind_address = g_key_file_get_string(kf, "global", "bind", NULL);
615 config_load_servers(n);
617 case NETWORK_PROGRAM:
618 n->type_settings.program_location = g_key_file_get_string(kf, "global", "program", NULL);
620 case NETWORK_VIRTUAL:
621 n->type_settings.virtual_type = g_key_file_get_string(kf, "global", "virtual", NULL);
623 case NETWORK_IOCHANNEL:
628 groups = g_key_file_get_groups(kf, &size);
629 for (i = 0; i < size; i++) {
630 if (!g_ascii_isalpha(groups[i][0]))
631 config_load_channel(n, kf, groups[i]);
636 for (gl = channel_keys; gl; gl = gl->next) {
637 struct keyfile_entry *ke = gl->data;
639 if (!strcasecmp(ke->network, n->name)) {
640 struct channel_config *cc = config_find_add_channel(n, n->nick);
643 cc->key = g_strdup(n->password);
647 if (g_key_file_has_key(n->keyfile, "global", "autojoin", NULL)) {
648 char **autojoin_channels;
649 autojoin_channels = g_key_file_get_string_list(n->keyfile, "global", "autojoin", &size, NULL);
650 for (i = 0; i < size; i++) {
651 struct channel_config *cc = config_find_add_channel(n, autojoin_channels[i]);
656 g_strfreev(autojoin_channels);
662 static struct network_config *find_create_network_config(struct ctrlproxy_config *cfg, const char *name)
665 struct network_config *nc;
666 struct tcp_server_config *tc;
668 for (gl = cfg->networks; gl; gl = gl->next) {
672 if (g_strcasecmp(nc->name, name) == 0)
675 if (nc->type != NETWORK_TCP)
678 for (gl1 = nc->type_settings.tcp.servers; gl1; gl1 = gl1->next) {
680 struct tcp_server_config *sc = gl1->data;
682 if (g_strcasecmp(sc->host, name) == 0)
685 if (g_strncasecmp(sc->host, name, strlen(sc->host)) != 0)
688 tmp = irc_create_url(sc->host, sc->port, FALSE);
690 if (g_strcasecmp(tmp, name) == 0)
697 nc = network_config_init(cfg);
698 nc->name = g_strdup(name);
699 nc->autoconnect = FALSE;
700 nc->reconnect_interval = -1;
701 nc->type = NETWORK_TCP;
702 tc = g_new0(struct tcp_server_config, 1);
703 irc_parse_url(name, &tc->host, &tc->port, &tc->ssl);
704 nc->type_settings.tcp.servers = g_list_append(nc->type_settings.tcp.servers, tc);
706 cfg->networks = g_list_append(cfg->networks, nc);
711 static void config_load_listeners_socks(struct ctrlproxy_config *cfg)
715 GKeyFile *kf = cfg->keyfile;
716 struct listener_config *l;
718 allows = g_key_file_get_string_list(kf, "socks", "allow", &size, NULL);
723 g_key_file_remove_key(kf, "socks", "allow", NULL);
725 l = g_new0(struct listener_config, 1);
727 if (g_key_file_has_key(kf, "socks", "port", NULL))
728 l->port = g_key_file_get_string(kf, "socks", "port", NULL);
730 l->port = g_strdup_printf("%d", DEFAULT_SOCKS_PORT);
732 /* We can use the socks listener as default listener, if there was
733 * no default listener specified */
734 if (cfg->listeners == NULL ||
735 !((struct listener_config *)cfg->listeners->data)->is_default)
736 l->is_default = TRUE;
738 g_key_file_remove_key(kf, "socks", "port", NULL);
740 for (i = 0; i < size; i++) {
741 struct allow_rule *r = g_new0(struct allow_rule, 1);
742 char **parts = g_strsplit(allows[i], ":", 2);
744 r->username = parts[0];
745 r->password = parts[1];
748 l->allow_rules = g_list_append(l->allow_rules, r);
751 g_key_file_remove_group(kf, "socks", NULL);
755 cfg->listeners = g_list_append(cfg->listeners, l);
758 static void config_load_listeners(struct ctrlproxy_config *cfg)
760 char *filename = g_build_filename(cfg->config_dir, "listener", NULL);
765 GError *error = NULL;
767 if (g_key_file_has_key(cfg->keyfile, "listener", "pasword", NULL)) {
768 g_key_file_set_string(cfg->keyfile, "global", "password",
769 g_key_file_get_string(cfg->keyfile, "listener", "password", NULL));
770 g_key_file_remove_key(cfg->keyfile, "listener", "password", NULL);
772 cfg->password = g_key_file_get_string(cfg->keyfile, "global", "password", NULL);
774 if (g_key_file_has_key(cfg->keyfile, "global", "listener-auto", NULL)) {
775 cfg->auto_listener = g_key_file_get_boolean(cfg->keyfile, "global", "listener-auto", NULL);
776 } else if (g_key_file_has_key(cfg->keyfile, "listener", "auto", NULL)) {
777 cfg->auto_listener = g_key_file_get_boolean(cfg->keyfile, "listener", "auto", NULL);
778 g_key_file_remove_key(cfg->keyfile, "listener", "auto", NULL);
781 if (g_key_file_has_key(cfg->keyfile, "global", "listener-autoport", NULL)) {
782 cfg->listener_autoport = g_key_file_get_integer(cfg->keyfile, "global", "listener-autoport", NULL);
783 } else if (g_key_file_has_key(cfg->keyfile, "listener", "autoport", NULL)) {
784 cfg->listener_autoport = g_key_file_get_integer(cfg->keyfile, "listener", "autoport", NULL);
785 g_key_file_remove_key(cfg->keyfile, "listener", "autoport", NULL);
788 if (g_key_file_has_key(cfg->keyfile, "global", "port", NULL)) {
789 struct listener_config *l = g_new0(struct listener_config, 1);
790 l->port = g_key_file_get_string(cfg->keyfile, "global", "port", NULL);
791 l->password = g_key_file_get_string(cfg->keyfile, "global", "password", NULL);
792 l->address = g_key_file_get_string(cfg->keyfile, "global", "bind", NULL);
793 l->ssl = g_key_file_has_key(cfg->keyfile, "global", "ssl", NULL) &&
794 g_key_file_get_boolean(cfg->keyfile, "global", "ssl", NULL);
795 l->is_default = TRUE;
797 l->network = g_key_file_get_string(cfg->keyfile, "global", "default-network", NULL);
799 cfg->listeners = g_list_append(cfg->listeners, l);
802 kf = g_key_file_new();
804 if (!g_key_file_load_from_file(kf, filename, G_KEY_FILE_KEEP_COMMENTS, &error)) {
805 if (error->code != G_FILE_ERROR_NOENT)
806 log_global(LOG_ERROR, "Can't parse configuration file '%s': %s", filename, error->message);
811 groups = g_key_file_get_groups(kf, &size);
813 for (i = 0; i < size; i++)
815 struct listener_config *l;
818 l = g_new0(struct listener_config, 1);
820 tmp = g_strdup(groups[i]);
821 l->port = strrchr(tmp, ':');
822 if (l->port != NULL) {
831 l->password = g_key_file_get_string(kf, groups[i], "password", NULL);
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);
852 struct network_config *config_find_network(struct ctrlproxy_config *cfg,
856 for (gl = cfg->networks; gl; gl = gl->next) {
857 struct network_config *nc = gl->data;
858 if (!g_strcasecmp(nc->name, name))
864 void config_del_network(struct ctrlproxy_config *cfg, const char *name)
866 cfg->networks = g_list_remove(cfg->networks, config_find_network(cfg, name));
869 #define IS_SPECIAL_FILE(name) (name[0] == '.' || name[strlen(name)-1] == '~')
871 static void config_cleanup_networks_dir(struct ctrlproxy_config *cfg)
873 char *networksdir = g_build_filename(cfg->config_dir, "networks", NULL);
877 dir = g_dir_open(networksdir, 0, NULL);
881 while ((name = g_dir_read_name(dir))) {
883 if (IS_SPECIAL_FILE(name) || config_find_network(cfg, name))
886 path = g_build_filename(networksdir, name, NULL);
896 static void config_load_networks(struct ctrlproxy_config *cfg, GList *channel_keys)
898 char *networksdir = g_build_filename(cfg->config_dir, "networks", NULL);
902 dir = g_dir_open(networksdir, 0, NULL);
906 while ((name = g_dir_read_name(dir))) {
907 if (IS_SPECIAL_FILE(name))
909 config_load_network(cfg, networksdir, name, channel_keys);
917 #define FETCH_SETTING(data, kf, section, prefix, name) (data)->name = g_key_file_get_string((kf), (section), prefix __STRING(name), NULL)
918 #define STORE_SETTING(data, kf, section, prefix, name) g_key_file_set_string((kf), (section), prefix __STRING(name), (data)->name)
920 static void config_save_log(struct log_file_config *data,
921 struct ctrlproxy_config *config)
924 g_key_file_set_string(config->keyfile, "global", "logging", "none");
928 if (data->is_irssi) {
929 g_key_file_set_string(config->keyfile, "global", "logging", "irssi");
930 if (data->logbasedir) {
931 if (!data->logbasedir_is_default)
932 g_key_file_set_string(config->keyfile, "global", "logdir", data->logbasedir);
934 g_key_file_set_string(config->keyfile, "global", "logfile", data->logfilename);
937 STORE_SETTING(data, config->keyfile, "global", "", logfilename);
938 STORE_SETTING(data, config->keyfile, "global", "log-format-", nickchange);
939 STORE_SETTING(data, config->keyfile, "global", "log-format-", topic);
940 STORE_SETTING(data, config->keyfile, "global", "log-format-", notopic);
941 STORE_SETTING(data, config->keyfile, "global", "log-format-", part);
942 STORE_SETTING(data, config->keyfile, "global", "log-format-", join);
943 STORE_SETTING(data, config->keyfile, "global", "log-format-", msg);
944 STORE_SETTING(data, config->keyfile, "global", "log-format-", notice);
945 STORE_SETTING(data, config->keyfile, "global", "log-format-", action);
946 STORE_SETTING(data, config->keyfile, "global", "log-format-", kick);
947 STORE_SETTING(data, config->keyfile, "global", "log-format-", quit);
948 STORE_SETTING(data, config->keyfile, "global", "log-format-", mode);
952 static void config_load_log(struct ctrlproxy_config *config)
954 GKeyFile *kf = config->keyfile;
955 struct log_file_config *data;
956 char *logging = NULL;
958 if (g_key_file_has_key(kf, "global", "logging", NULL)) {
959 logging = g_key_file_get_string(kf, "global", "logging", NULL);
962 if (g_key_file_has_group(kf, "log-custom")) {
963 data = g_new0(struct log_file_config, 1);
965 FETCH_SETTING(data, kf, "log-custom", "", nickchange);
966 FETCH_SETTING(data, kf, "log-custom", "", logfilename);
967 FETCH_SETTING(data, kf, "log-custom", "", topic);
968 FETCH_SETTING(data, kf, "log-custom", "", notopic);
969 FETCH_SETTING(data, kf, "log-custom", "", part);
970 FETCH_SETTING(data, kf, "log-custom", "", join);
971 FETCH_SETTING(data, kf, "log-custom", "", msg);
972 FETCH_SETTING(data, kf, "log-custom", "", notice);
973 FETCH_SETTING(data, kf, "log-custom", "", action);
974 FETCH_SETTING(data, kf, "log-custom", "", kick);
975 FETCH_SETTING(data, kf, "log-custom", "", quit);
976 FETCH_SETTING(data, kf, "log-custom", "", mode);
978 g_key_file_remove_group(kf, "log-custom", NULL);
979 config->log_file = data;
980 log_custom_load(data);
983 if (logging != NULL && !strcmp(logging, "custom")) {
984 data = g_new0(struct log_file_config, 1);
986 FETCH_SETTING(data, kf, "global", "", logfilename);
987 FETCH_SETTING(data, kf, "global", "log-format-", nickchange);
988 FETCH_SETTING(data, kf, "global", "log-format-", topic);
989 FETCH_SETTING(data, kf, "global", "log-format-", notopic);
990 FETCH_SETTING(data, kf, "global", "log-format-", part);
991 FETCH_SETTING(data, kf, "global", "log-format-", join);
992 FETCH_SETTING(data, kf, "global", "log-format-", msg);
993 FETCH_SETTING(data, kf, "global", "log-format-", notice);
994 FETCH_SETTING(data, kf, "global", "log-format-", action);
995 FETCH_SETTING(data, kf, "global", "log-format-", kick);
996 FETCH_SETTING(data, kf, "global", "log-format-", quit);
997 FETCH_SETTING(data, kf, "global", "log-format-", mode);
999 config->log_file = data;
1000 log_custom_load(data);
1003 if (g_key_file_has_group(kf, "log-irssi") ||
1004 (logging != NULL && !strcmp(logging, "irssi"))) {
1005 data = g_new0(struct log_file_config, 1);
1006 data->is_irssi = TRUE;
1008 data->join = "%h:%M -!- %n [%u] has joined %c";
1009 data->part = "%h:%M -!- %n [%u] has left %c [%m]";
1010 data->msg = "%h:%M < %n> %m";
1011 data->notice = "%h:%M < %n> %m";
1012 data->action = "%h:%M * %n %m";
1013 data->mode = "%h:%M -!- mode/%t [%p %c] by %n";
1014 data->quit = "%h:%M -!- %n [%u] has quit [%m]";
1015 data->kick = "%h:%M -!- %t has been kicked by %n [%m]";
1016 data->topic = "%h:%M -!- %n has changed the topic to %t";
1017 data->notopic = "%h:%M -!- %n has removed the topic";
1018 data->nickchange = "%h:%M -!- %n is now known as %r";
1020 if (g_key_file_has_key(kf, "global", "logfile", NULL)) {
1021 data->logfilename= g_key_file_get_string(kf, "global", "logfile", NULL);
1022 } else if (g_key_file_has_key(kf, "global", "logdir", NULL)) {
1023 data->logbasedir = g_key_file_get_string(kf, "global", "logdir", NULL);
1024 data->logfilename = g_strdup_printf("%s/%%N/%%@", data->logbasedir);
1025 } else if (g_key_file_has_key(kf, "log-irssi", "logfile", NULL)) {
1026 data->logbasedir = g_key_file_get_string(kf, "log-irssi", "logfile", NULL);
1027 data->logfilename = g_strdup_printf("%s/%%N/%%@", data->logbasedir);
1029 data->logbasedir = g_build_filename(config->config_dir,
1032 data->logbasedir_is_default = TRUE;
1034 data->logfilename = g_strdup_printf("%s/%%N/%%@", data->logbasedir);
1036 g_key_file_remove_group(kf, "log-irssi", NULL);
1038 config->log_file = data;
1039 log_custom_load(data);
1042 if (logging != NULL &&
1043 strcmp(logging, "irssi") != 0 &&
1044 strcmp(logging, "custom") != 0 &&
1045 strcmp(logging, "none") != 0) {
1046 log_global(LOG_WARNING, "Unknown log type `%s'", logging);
1052 static void config_save_auto_away(struct auto_away_config *d, struct ctrlproxy_config *config)
1054 GKeyFile *kf = config->keyfile;
1056 if (g_key_file_has_key(kf, "global", "auto-away-enable", NULL) ||
1058 g_key_file_set_boolean(kf, "global", "auto-away-enable", d->enabled);
1060 if (d->message != NULL)
1061 g_key_file_set_string(kf, "global", "auto-away-message", d->message);
1063 if (d->nick != NULL)
1064 g_key_file_set_string(kf, "global", "auto-away-nick", d->nick);
1066 if (d->client_limit != -1)
1067 g_key_file_set_integer(kf, "global", "auto-away-client-limit", d->client_limit);
1069 if (d->max_idle_time != -1)
1070 g_key_file_set_integer(kf, "global", "auto-away-time", d->max_idle_time);
1073 static void config_load_auto_away(struct auto_away_config *d, GKeyFile *kf)
1075 if (g_key_file_has_group(kf, "auto-away")) {
1077 d->message = g_key_file_get_string(kf, "auto-away", "message", NULL);
1078 d->nick = g_key_file_get_string(kf, "auto-away", "nick", NULL);
1079 if (g_key_file_has_key(kf, "auto-away", "client_limit", NULL)) {
1080 d->client_limit = g_key_file_get_integer(kf, "auto-away", "client_limit", NULL);
1081 if (g_key_file_has_key(kf, "auto-away", "only_noclient", NULL))
1082 log_global(LOG_WARNING, "auto-away: not using only_noclient because client_limit is set");
1084 else if (g_key_file_has_key(kf, "auto-away", "only_noclient", NULL)) {
1085 d->client_limit = g_key_file_get_boolean(kf, "auto-away", "only_noclient", NULL) ? 0 : -1;
1086 log_global(LOG_WARNING, "auto-away: only_noclient is deprecated, please use client_limit instead");
1089 d->client_limit = -1;
1090 if (g_key_file_has_key(kf, "auto-away", "time", NULL))
1091 d->max_idle_time = g_key_file_get_integer(kf, "auto-away", "time", NULL);
1093 d->max_idle_time = -1;
1095 g_key_file_remove_group(kf, "auto-away", NULL);
1097 if (g_key_file_has_key(kf, "global", "auto-away-enable", NULL))
1098 d->enabled = g_key_file_get_boolean(kf, "global", "auto-away-enable", NULL);
1099 d->message = g_key_file_get_string(kf, "global", "auto-away-message", NULL);
1100 d->nick = g_key_file_get_string(kf, "global", "auto-away-nick", NULL);
1101 if (g_key_file_has_key(kf, "global", "auto-away-client-limit", NULL)) {
1102 d->client_limit = g_key_file_get_integer(kf, "global", "auto-away-client-limit", NULL);
1104 d->client_limit = -1;
1105 if (g_key_file_has_key(kf, "global", "auto-away-time", NULL))
1106 d->max_idle_time = g_key_file_get_integer(kf, "global", "auto-away-time", NULL);
1108 d->max_idle_time = -1;
1112 struct ctrlproxy_config *init_configuration(void)
1114 struct ctrlproxy_config *cfg;
1115 cfg = g_new0(struct ctrlproxy_config, 1);
1120 struct ctrlproxy_config *load_configuration(const char *dir)
1123 GError *error = NULL;
1124 struct ctrlproxy_config *cfg;
1127 char **autoconnect_list;
1131 GList *channel_keys = NULL;
1132 char *keyfile_filename;
1134 file = g_build_filename(dir, "config", NULL);
1136 cfg = init_configuration();
1137 cfg->config_dir = g_strdup(dir);
1138 cfg->network_socket = g_build_filename(cfg->config_dir, "socket", NULL);
1139 cfg->admin_socket = g_build_filename(cfg->config_dir, "admin", NULL);
1141 kf = cfg->keyfile = g_key_file_new();
1143 if (!g_key_file_load_from_file(kf, file, G_KEY_FILE_KEEP_COMMENTS, &error)) {
1144 log_global(LOG_ERROR, "Can't parse configuration file '%s': %s", file, error->message);
1145 g_key_file_free(kf);
1151 cfg->autosave = TRUE;
1152 if (g_key_file_has_key(kf, "global", "autosave", NULL) &&
1153 !g_key_file_get_boolean(kf, "global", "autosave", NULL))
1154 cfg->autosave = FALSE;
1156 if (g_key_file_has_key(kf, "global", "keytab", NULL)) {
1158 keytab = g_key_file_get_string(kf, "global", "keytab", NULL);
1159 #ifdef HAVE_GSSKRB5_REGISTER_ACCEPTOR_IDENTITY
1160 gsskrb5_register_acceptor_identity(keytab);
1165 if (g_key_file_has_key(kf, "global", "max_who_age", NULL))
1166 cfg->max_who_age = g_key_file_get_integer(kf, "global", "max_who_age", NULL);
1168 cfg->replication = g_key_file_get_string(kf, "global", "replication", NULL);
1169 cfg->linestack_backend = g_key_file_get_string(kf, "global", "linestack", NULL);
1171 if (g_key_file_has_key(kf, "global", "report-time", NULL)) {
1172 char *setting = g_key_file_get_string(kf, "global", "report-time", NULL);
1173 if (!g_strcasecmp(setting, "never") || !g_strcasecmp(setting, "false"))
1174 cfg->report_time = REPORT_TIME_NEVER;
1175 else if (!g_strcasecmp(setting, "always"))
1176 cfg->report_time = REPORT_TIME_ALWAYS;
1177 else if (!g_strcasecmp(setting, "replication") ||
1178 !g_strcasecmp(setting, "true"))
1179 cfg->report_time = REPORT_TIME_REPLICATION;
1181 log_global(LOG_WARNING, "Unknown value `%s' for report-time in configuration file", setting);
1186 cfg->report_time_offset = 0;
1187 if (g_key_file_has_key(kf, "global", "report-time-offset", NULL)) {
1188 cfg->report_time_offset = g_key_file_get_integer(kf, "global", "report-time-offset", NULL);
1191 if (g_key_file_has_key(kf, "global", "motd-file", NULL))
1192 cfg->motd_file = g_key_file_get_string(kf, "global", "motd-file", NULL);
1194 cfg->motd_file = g_build_filename(SHAREDIR, "motd", NULL);
1196 if (g_key_file_has_key(kf, "client", "charset", NULL)) {
1197 cfg->client_charset = g_key_file_get_string(kf, "client", "charset", NULL);
1198 g_key_file_remove_key(cfg->keyfile, "client", "charset", NULL); /* deprecated */
1199 } else if (g_key_file_has_key(kf, "global", "client-charset", NULL)) {
1200 cfg->client_charset = g_key_file_get_string(kf, "global", "client-charset", NULL);
1201 g_key_file_remove_key(cfg->keyfile, "global", "client-charset", NULL); /* deprecated */
1202 } else if (g_key_file_has_key(kf, "global", "default-client-charset", NULL)) {
1203 cfg->client_charset = g_key_file_get_string(kf, "global", "default-client-charset", NULL);
1205 cfg->client_charset = NULL;
1208 if (g_key_file_has_key(kf, "global", "learn-nickserv", NULL))
1209 cfg->learn_nickserv = g_key_file_get_boolean(kf, "global", "learn-nickserv", NULL);
1211 cfg->learn_nickserv = TRUE;
1213 if (g_key_file_has_key(kf, "global", "learn-network-name", NULL))
1214 cfg->learn_network_name = g_key_file_get_boolean(kf, "global", "learn-network-name", NULL);
1216 cfg->learn_network_name = TRUE;
1218 if (g_key_file_has_key(kf, "global", "create-implicit", NULL))
1219 cfg->create_implicit = g_key_file_get_boolean(kf, "global", "create-implicit", NULL);
1221 cfg->create_implicit = TRUE;
1223 if (!g_file_test(cfg->motd_file, G_FILE_TEST_EXISTS))
1224 log_global(LOG_ERROR, "Can't open MOTD file '%s' for reading", cfg->motd_file);
1226 if (g_key_file_has_key(kf, "admin", "without_privmsg", NULL)) {
1227 if (g_key_file_get_boolean(kf, "admin", "without_privmsg", NULL)) {
1228 cfg->admin_user = NULL;
1230 cfg->admin_user = g_strdup("ctrlproxy");
1232 g_key_file_remove_key(kf, "admin", "without_privmsg", NULL);
1235 if (g_key_file_has_key(kf, "global", "admin-user", NULL)) {
1236 cfg->admin_user = g_key_file_get_string(kf, "global", "admin-user", NULL);
1239 cfg->admin_log = TRUE;
1240 if (g_key_file_has_key(kf, "admin", "log", NULL) && !g_key_file_get_boolean(kf, "admin", "log", NULL))
1241 cfg->admin_log = FALSE;
1242 g_key_file_remove_key(kf, "admin", "log", NULL);
1243 if (g_key_file_has_key(kf, "global", "admin-log", NULL) && !g_key_file_get_boolean(kf, "global", "admin-log", NULL))
1244 cfg->admin_log = FALSE;
1245 g_key_file_remove_group(kf, "admin", NULL);
1247 for (gl = cfg->networks; gl; gl = gl->next) {
1248 struct network_config *nc = gl->data;
1250 nc->autoconnect = FALSE;
1253 config_load_listeners(cfg);
1254 config_load_listeners_socks(cfg);
1255 config_load_log(cfg);
1256 config_load_auto_away(&cfg->auto_away, cfg->keyfile);
1258 keyfile_filename = g_build_filename(cfg->config_dir, "keys",
1261 if (g_file_test(keyfile_filename, G_FILE_TEST_EXISTS)) {
1262 if (!keyfile_read_file(keyfile_filename, ';', &channel_keys)) {
1263 log_global(LOG_WARNING, "Unable to read keys file");
1267 g_free(keyfile_filename);
1269 config_load_networks(cfg, channel_keys);
1271 /* Check for unknown parameters */
1272 keys = g_key_file_get_keys(kf, "global", NULL, NULL);
1273 for (i = 0; keys[i] != NULL; i++) {
1274 if (!config_known_key(keys[i]))
1275 log_global(LOG_WARNING, "Unknown setting `%s' in configuration file", keys[i]);
1280 autoconnect_list = g_key_file_get_string_list(kf, "global", "autoconnect", &size, NULL);
1282 for (i = 0; i < size; i++) {
1283 struct network_config *nc = find_create_network_config(cfg, autoconnect_list[i]);
1286 nc->autoconnect = TRUE;
1289 g_strfreev(autoconnect_list);
1296 struct network_config *network_config_init(struct ctrlproxy_config *cfg)
1298 struct network_config *s = g_new0(struct network_config, 1);
1300 s->autoconnect = FALSE;
1301 s->nick = g_strdup(g_get_user_name());
1302 s->username = g_strdup(g_get_user_name());
1303 g_assert(s->username != NULL && strlen(s->username) > 0);
1304 s->fullname = g_strdup(g_get_real_name());
1305 if (s->fullname == NULL ||
1306 strlen(s->fullname) == 0) {
1307 g_free(s->fullname);
1308 s->fullname = g_strdup(s->username);
1310 s->reconnect_interval = -1;
1313 cfg->networks = g_list_append(cfg->networks, s);
1317 void free_config(struct ctrlproxy_config *cfg)
1319 while (cfg->networks) {
1320 struct network_config *nc = cfg->networks->data;
1323 g_free(nc->fullname);
1324 g_free(nc->username);
1325 g_free(nc->password);
1326 g_strfreev(nc->autocmd);
1327 while (nc->channels) {
1328 struct channel_config *cc = nc->channels->data;
1331 nc->channels = g_list_remove(nc->channels, cc);
1336 while (nc->type_settings.tcp.servers) {
1337 struct tcp_server_config *tc = nc->type_settings.tcp.servers->data;
1340 g_free(tc->bind_address);
1341 g_free(tc->password);
1342 nc->type_settings.tcp.servers = g_list_remove(nc->type_settings.tcp.servers, tc);
1345 g_free(nc->type_settings.tcp.default_bind_address);
1347 case NETWORK_VIRTUAL:
1348 g_free(nc->type_settings.virtual_type);
1350 case NETWORK_PROGRAM:
1351 g_free(nc->type_settings.program_location);
1353 case NETWORK_IOCHANNEL:
1354 /* Nothing to free */
1357 cfg->networks = g_list_remove(cfg->networks, nc);
1358 if (nc->keyfile) g_key_file_free(nc->keyfile);
1361 g_free(cfg->config_dir);
1362 g_free(cfg->network_socket);
1363 g_free(cfg->admin_socket);
1364 g_free(cfg->replication);
1365 g_free(cfg->linestack_backend);
1366 g_free(cfg->motd_file);
1367 g_free(cfg->admin_user);
1368 g_key_file_free(cfg->keyfile);
1372 gboolean create_configuration(const char *config_dir)
1374 struct global *global;
1376 struct listener_config *l;
1379 if (g_file_test(config_dir, G_FILE_TEST_IS_DIR)) {
1380 fprintf(stderr, "%s already exists\n", config_dir);
1384 if (g_mkdir(config_dir, 0700) != 0) {
1385 fprintf(stderr, "Can't create config directory '%s': %s\n", config_dir, strerror(errno));
1389 global = load_global(DEFAULT_CONFIG_DIR);
1390 if (global == NULL) {
1391 fprintf(stderr, "Unable to load default configuration '%s'\n", DEFAULT_CONFIG_DIR);
1394 global->config->config_dir = g_strdup(config_dir);
1395 save_configuration(global->config, config_dir);
1397 snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
1398 printf("Please specify port the administration interface should listen on.\n"
1399 "Prepend with a colon to listen on a specific address.\n"
1400 "Example: localhost:6668\n\nPort [%s]: ", port); fflush(stdout);
1401 if (!fgets(port, sizeof(port), stdin))
1402 snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
1404 if (port[strlen(port)-1] == '\n')
1405 port[strlen(port)-1] = '\0';
1407 if (strlen(port) == 0)
1408 snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
1410 l = g_new0(struct listener_config, 1);
1411 pass = getpass("Please specify a password for the administration interface: ");
1413 l->is_default = TRUE;
1414 if (!strcmp(pass, "")) {
1415 fprintf(stderr, "Warning: no password specified. Authentication disabled!\n");
1417 l->password = g_strdup(pass);
1420 global->config->default_listener = l;
1422 global->config->listeners = g_list_append(global->config->listeners, l);
1424 save_configuration(global->config, config_dir);