2 ctrlproxy: A modular IRC proxy
3 (c) 2002-2006 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 2 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"
23 #ifdef HAVE_SYS_SOCKET_H
24 #include <sys/socket.h>
28 #include <sys/socket.h>
29 #include <glib/gstdio.h>
31 #define DEFAULT_ADMIN_PORT 6680
33 gboolean g_key_file_save_to_file(GKeyFile *kf, const gchar *file, GError **error)
36 char *data = g_key_file_to_data(kf, &length, error);
42 gio = g_io_channel_new_file(file, "w+", error);
48 g_io_channel_write_chars(gio, data, length, &nr, error);
52 g_io_channel_unref(gio);
57 static void config_save_tcp_servers(struct network_config *n, GKeyFile *kf)
61 gchar **values = g_new0(gchar *, g_list_length(n->type_settings.tcp_servers)+1);
63 for (gl = n->type_settings.tcp_servers; gl; gl = gl->next) {
64 struct tcp_server_config *ts = gl->data;
65 char *name = g_strdup_printf("%s:%s", ts->host, ts->port);
69 g_key_file_set_boolean(kf, name, "ssl", ts->ssl);
71 g_key_file_set_string(kf, name, "password", ts->password);
73 g_key_file_remove_key(kf, name, "password", NULL);
75 if (ts->bind_address) {
78 tmp = g_strdup_printf("%s:%s",
82 tmp = g_strdup(ts->bind_address);
84 g_key_file_set_string(kf, name, "bind", tmp);
88 g_key_file_remove_key(kf, name, "bind", NULL);
93 g_key_file_set_string_list(kf, "global", "servers", (const gchar **)values, i);
98 static void config_save_network(const char *dir, struct network_config *n)
105 n->keyfile = g_key_file_new();
110 g_key_file_set_string(kf, "global", "fullname", n->fullname);
111 g_key_file_set_string(kf, "global", "nick", n->nick);
112 g_key_file_set_string(kf, "global", "username", n->username);
114 g_key_file_set_integer(kf, "global", "queue-speed", n->queue_speed);
115 g_key_file_set_integer(kf, "global", "reconnect-interval", n->reconnect_interval);
118 case NETWORK_VIRTUAL:
119 g_key_file_set_string(kf, "global", "virtual", n->type_settings.virtual_type);
121 case NETWORK_PROGRAM:
122 g_key_file_set_string(kf, "global", "program", n->type_settings.program_location);
125 config_save_tcp_servers(n, kf);
130 for (gl = n->channels; gl; gl = gl->next) {
131 struct channel_config *c = gl->data;
134 g_key_file_set_string(kf, c->name, "key", c->key);
136 g_key_file_remove_key(kf, c->name, "key", NULL);
138 g_key_file_set_boolean(kf, c->name, "autojoin", c->autojoin);
141 fn = g_build_filename(dir, n->name, NULL);
142 g_key_file_save_to_file(kf, fn, NULL);
146 static void config_save_networks(const char *config_dir, GList *networks)
148 char *networksdir = g_build_filename(config_dir, "networks", NULL);
151 if (!g_file_test(networksdir, G_FILE_TEST_IS_DIR)) {
152 if (g_mkdir(networksdir, 0700) != 0) {
153 log_global(LOG_ERROR, "Can't create networks directory '%s': %s", networksdir, strerror(errno));
158 for (gl = networks; gl; gl = gl->next) {
159 struct network_config *n = gl->data;
160 config_save_network(networksdir, n);
166 void save_configuration(struct ctrlproxy_config *cfg, const char *configuration_dir)
172 if (!g_file_test(configuration_dir, G_FILE_TEST_IS_DIR)) {
173 if (g_mkdir(configuration_dir, 0700) != 0) {
174 log_global(LOG_ERROR, "Unable to open configuration directory '%s'\n", configuration_dir);
180 cfg->keyfile = g_key_file_new();
182 g_key_file_set_boolean(cfg->keyfile, "global", "autosave", cfg->autosave);
183 g_key_file_set_boolean(cfg->keyfile, "admin", "without_privmsg", cfg->admin_noprivmsg);
184 g_key_file_set_boolean(cfg->keyfile, "admin", "log", cfg->admin_log);
185 g_key_file_set_integer(cfg->keyfile, "global", "max_who_age", cfg->max_who_age);
187 g_key_file_set_string(cfg->keyfile, "client", "charset", cfg->client_charset);
188 if (cfg->replication)
189 g_key_file_set_string(cfg->keyfile, "global", "replication", cfg->replication);
190 if (cfg->linestack_backend)
191 g_key_file_set_string(cfg->keyfile, "global", "linestack", cfg->linestack_backend);
192 g_key_file_set_string(cfg->keyfile, "global", "motd-file", cfg->motd_file);
194 g_key_file_set_boolean(cfg->keyfile, "global", "report-time", cfg->report_time);
196 config_save_networks(configuration_dir, cfg->networks);
199 list = g_new0(char *, g_list_length(cfg->networks)+1);
200 for (gl = cfg->networks; gl; gl = gl->next) {
201 struct network_config *nc = gl->data;
203 if (nc->autoconnect) {
210 g_key_file_set_string_list(cfg->keyfile, "global", "autoconnect", (const gchar **)list, i);
214 fn = g_build_filename(configuration_dir, "config", NULL);
215 g_key_file_save_to_file(cfg->keyfile, fn, NULL);
219 static void config_load_channel(struct network_config *n, GKeyFile *kf, const char *name)
221 struct channel_config *ch = g_new0(struct channel_config, 1);
223 ch->name = g_strdup(name);
224 if (g_key_file_has_key(kf, name, "key", NULL))
225 ch->key = g_key_file_get_string(kf, name, "key", NULL);
227 if (g_key_file_has_key(kf, name, "autojoin", NULL))
228 ch->autojoin = g_key_file_get_boolean(kf, name, "autojoin", NULL);
230 n->channels = g_list_append(n->channels, ch);
233 static void config_load_servers(struct network_config *n)
239 servers = g_key_file_get_string_list(n->keyfile, "global", "servers", &size, NULL);
244 for (i = 0; i < size; i++) {
246 struct tcp_server_config *s = g_new0(struct tcp_server_config, 1);
248 s->password = g_key_file_get_string(n->keyfile, servers[i], "password", NULL);
249 if (g_key_file_has_key(n->keyfile, servers[i], "ssl", NULL))
250 s->ssl = g_key_file_get_boolean(n->keyfile, servers[i], "ssl", NULL);
252 tmp = strrchr(servers[i], ':');
259 s->host = servers[i];
260 s->port = g_strdup(tmp?tmp:"6667");
261 s->bind_address = g_key_file_get_string(n->keyfile, servers[i], "bind", NULL);
262 if (s->bind_address && (tmp = strchr(s->bind_address, ':'))) {
264 s->bind_port = tmp+1;
267 n->type_settings.tcp_servers = g_list_append(n->type_settings.tcp_servers, s);
273 static struct network_config *config_load_network(struct ctrlproxy_config *cfg, const char *dirname, const char *name)
276 struct network_config *n;
280 GError *error = NULL;
283 kf = g_key_file_new();
285 filename = g_build_filename(dirname, name, NULL);
287 if (!g_key_file_load_from_file(kf, filename, G_KEY_FILE_KEEP_COMMENTS, &error)) {
288 log_global(LOG_ERROR, "Can't parse configuration file '%s': %s", filename, error->message);
293 n = network_config_init(cfg);
298 if (g_key_file_has_key(kf, "global", "fullname", NULL)) {
300 n->fullname = g_key_file_get_string(kf, "global", "fullname", NULL);
303 if (g_key_file_has_key(kf, "global", "nick", NULL)) {
305 n->nick = g_key_file_get_string(kf, "global", "nick", NULL);
308 if (g_key_file_has_key(kf, "global", "reconnect-interval", NULL)) {
309 n->reconnect_interval = g_key_file_get_integer(kf, "global", "reconnect-interval", NULL);
312 if (g_key_file_has_key(kf, "global", "queue-speed", NULL)) {
313 n->queue_speed = g_key_file_get_integer(kf, "global", "queue-speed", NULL);
316 if (g_key_file_has_key(kf, "global", "username", NULL)) {
318 n->username = g_key_file_get_string(kf, "global", "username", NULL);
321 if (g_key_file_has_key(kf, "global", "ignore_first_nick", NULL)) {
322 n->ignore_first_nick = g_key_file_get_boolean(kf, "global", "ignore_first_nick", NULL);
325 if (g_key_file_has_key(kf, "global", "password", NULL)) {
327 n->password = g_key_file_get_string(kf, "global", "password", NULL);
330 n->name = g_strdup(name);
332 if (g_key_file_has_key(kf, "global", "program", NULL))
333 n->type = NETWORK_PROGRAM;
334 else if (g_key_file_has_key(kf, "global", "virtual", NULL))
335 n->type = NETWORK_VIRTUAL;
337 n->type = NETWORK_TCP;
341 config_load_servers(n);
343 case NETWORK_PROGRAM:
344 n->type_settings.program_location = g_key_file_get_string(kf, "global", "program", NULL);
346 case NETWORK_VIRTUAL:
347 n->type_settings.virtual_type = g_key_file_get_string(kf, "global", "virtual", NULL);
349 case NETWORK_IOCHANNEL:
354 groups = g_key_file_get_groups(kf, &size);
355 for (i = 0; i < size; i++) {
356 if (!g_ascii_isalpha(groups[i][0]))
357 config_load_channel(n, kf, groups[i]);
365 static struct network_config *find_create_network_config(struct ctrlproxy_config *cfg, const char *name)
368 struct network_config *nc;
369 struct tcp_server_config *tc;
371 for (gl = cfg->networks; gl; gl = gl->next) {
375 if (g_strcasecmp(nc->name, name) == 0)
378 if (nc->type != NETWORK_TCP)
381 for (gl1 = nc->type_settings.tcp_servers; gl1; gl1 = gl1->next) {
383 struct tcp_server_config *sc = gl1->data;
385 if (g_strcasecmp(sc->host, name) == 0)
388 if (g_strncasecmp(sc->host, name, strlen(sc->host)) != 0)
391 tmp = g_strdup_printf("%s:%s", sc->host, sc->port);
393 if (g_strcasecmp(tmp, name) == 0)
400 nc = network_config_init(cfg);
401 nc->name = g_strdup(name);
402 nc->autoconnect = FALSE;
403 nc->reconnect_interval = DEFAULT_RECONNECT_INTERVAL;
404 nc->type = NETWORK_TCP;
405 tc = g_new0(struct tcp_server_config, 1);
406 tc->host = g_strdup(name);
407 if (strchr(tc->host, ':')) {
408 tc->port = tc->host+1;
411 tc->port = g_strdup("6667");
414 nc->type_settings.tcp_servers = g_list_append(nc->type_settings.tcp_servers, tc);
416 cfg->networks = g_list_append(cfg->networks, nc);
421 static void config_load_networks(struct ctrlproxy_config *cfg)
423 char *networksdir = g_build_filename(cfg->config_dir, "networks", NULL);
427 dir = g_dir_open(networksdir, 0, NULL);
431 while ((name = g_dir_read_name(dir))) {
432 if (name[0] == '.' || name[strlen(name)-1] == '~')
434 config_load_network(cfg, networksdir, name);
442 struct ctrlproxy_config *load_configuration(const char *dir)
445 GError *error = NULL;
446 struct ctrlproxy_config *cfg;
448 char **autoconnect_list;
453 file = g_build_filename(dir, "config", NULL);
455 cfg = g_new0(struct ctrlproxy_config, 1);
456 cfg->config_dir = g_strdup(dir);
457 cfg->network_socket = g_build_filename(cfg->config_dir, "socket", NULL);
458 cfg->admin_socket = g_build_filename(cfg->config_dir, "admin", NULL);
460 kf = cfg->keyfile = g_key_file_new();
462 if (!g_key_file_load_from_file(kf, file, G_KEY_FILE_KEEP_COMMENTS, &error)) {
463 log_global(LOG_ERROR, "Can't parse configuration file '%s': %s", file, error->message);
470 cfg->autosave = TRUE;
471 if (g_key_file_has_key(kf, "global", "autosave", NULL) &&
472 !g_key_file_get_boolean(kf, "global", "autosave", NULL))
473 cfg->autosave = FALSE;
475 if (g_key_file_has_key(kf, "global", "max_who_age", NULL))
476 cfg->max_who_age = g_key_file_get_integer(kf, "global", "max_who_age", NULL);
478 cfg->replication = g_key_file_get_string(kf, "global", "replication", NULL);
479 cfg->linestack_backend = g_key_file_get_string(kf, "global", "linestack", NULL);
481 if (g_key_file_has_key(kf, "global", "report-time", NULL))
482 cfg->report_time = g_key_file_get_boolean(kf, "global", "report-time", NULL);
484 if (g_key_file_has_key(kf, "global", "motd-file", NULL))
485 cfg->motd_file = g_key_file_get_string(kf, "global", "motd-file", NULL);
487 cfg->motd_file = g_build_filename(SHAREDIR, "motd", NULL);
489 if (g_key_file_has_key(kf, "client", "charset", NULL))
490 cfg->client_charset = g_key_file_get_string(kf, "client", "charset", NULL);
492 cfg->client_charset = g_strdup(DEFAULT_CLIENT_CHARSET);
494 if(!g_file_test(cfg->motd_file, G_FILE_TEST_EXISTS))
495 log_global(LOG_ERROR, "Can't open MOTD file '%s' for reading", cfg->motd_file);
497 if (g_key_file_has_key(kf, "admin", "without_privmsg", NULL))
498 cfg->admin_noprivmsg = g_key_file_get_boolean(kf, "admin", "without_privmsg", NULL);
500 cfg->admin_log = TRUE;
501 if (g_key_file_has_key(kf, "admin", "log", NULL) && !g_key_file_get_boolean(kf, "admin", "log", NULL))
502 cfg->admin_log = FALSE;
505 for (gl = cfg->networks; gl; gl = gl->next) {
506 struct network_config *nc = gl->data;
508 nc->autoconnect = FALSE;
511 config_load_networks(cfg);
514 autoconnect_list = g_key_file_get_string_list(kf, "global", "autoconnect", &size, NULL);
516 for (i = 0; i < size; i++) {
517 struct network_config *nc = find_create_network_config(cfg, autoconnect_list[i]);
520 nc->autoconnect = TRUE;
523 g_strfreev(autoconnect_list);
529 struct network_config *network_config_init(struct ctrlproxy_config *cfg)
531 struct network_config *s = g_new0(struct network_config, 1);
533 s->autoconnect = FALSE;
534 s->nick = g_strdup(g_get_user_name());
535 s->username = g_strdup(g_get_user_name());
536 s->fullname = g_strdup(g_get_real_name());
537 s->reconnect_interval = DEFAULT_RECONNECT_INTERVAL;
540 cfg->networks = g_list_append(cfg->networks, s);
544 void free_config(struct ctrlproxy_config *cfg)
546 while (cfg->networks) {
547 struct network_config *nc = cfg->networks->data;
550 g_free(nc->fullname);
551 g_free(nc->username);
552 g_free(nc->password);
553 while (nc->channels) {
554 struct channel_config *cc = nc->channels->data;
557 nc->channels = g_list_remove(nc->channels, cc);
562 while (nc->type_settings.tcp_servers) {
563 struct tcp_server_config *tc = nc->type_settings.tcp_servers->data;
566 g_free(tc->bind_address);
567 g_free(tc->password);
568 nc->type_settings.tcp_servers = g_list_remove(nc->type_settings.tcp_servers, tc);
572 case NETWORK_VIRTUAL:
573 g_free(nc->type_settings.virtual_type);
575 case NETWORK_PROGRAM:
576 g_free(nc->type_settings.program_location);
578 case NETWORK_IOCHANNEL:
579 /* Nothing to free */
582 cfg->networks = g_list_remove(cfg->networks, nc);
583 g_key_file_free(nc->keyfile);
586 g_free(cfg->config_dir);
587 g_free(cfg->network_socket);
588 g_free(cfg->admin_socket);
589 g_free(cfg->replication);
590 g_free(cfg->linestack_backend);
591 g_free(cfg->motd_file);
592 g_key_file_free(cfg->keyfile);
596 gboolean create_configuration(const char *config_dir)
599 struct global *global;
601 char *pass, *listenerfile;
602 GError *error = NULL;
604 if (g_file_test(config_dir, G_FILE_TEST_IS_DIR)) {
605 fprintf(stderr, "%s already exists\n", config_dir);
609 if (g_mkdir(config_dir, 0700) != 0) {
610 fprintf(stderr, "Can't create config directory '%s': %s\n", config_dir, strerror(errno));
614 global = new_global(DEFAULT_CONFIG_DIR);
615 global->config->config_dir = g_strdup(config_dir);
616 save_configuration(global->config, config_dir);
618 kf = g_key_file_new();
620 snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
621 printf("Please specify port the administration interface should listen on.\n"
622 "Prepend with a colon to listen on a specific address.\n"
623 "Example: localhost:6668\n\nPort [%s]: ", port); fflush(stdout);
624 if (fgets(port, sizeof(port), stdin))
625 snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
627 if (port[strlen(port)-1] == '\n')
628 port[strlen(port)-1] = '\0';
630 if (strlen(port) == 0)
631 snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
633 pass = getpass("Please specify a password for the administration interface: ");
634 g_key_file_set_string(kf, port, "network", "admin");
635 if (!strcmp(pass, "")) {
636 fprintf(stderr, "Warning: no password specified. Authentication disabled!\n");
638 g_key_file_set_string(kf, port, "password", pass);
641 listenerfile = g_build_filename(config_dir, "listener", NULL);
643 if (!g_key_file_save_to_file(kf, listenerfile, &error)) {
644 fprintf(stderr, "Error saving %s: %s\n", listenerfile, error->message);