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 if (cfg->client_charset != NULL)
188 g_key_file_set_string(cfg->keyfile, "client", "charset", cfg->client_charset);
189 if (cfg->replication)
190 g_key_file_set_string(cfg->keyfile, "global", "replication", cfg->replication);
191 if (cfg->linestack_backend)
192 g_key_file_set_string(cfg->keyfile, "global", "linestack", cfg->linestack_backend);
193 if (cfg->motd_file != NULL)
194 g_key_file_set_string(cfg->keyfile, "global", "motd-file", cfg->motd_file);
196 g_key_file_set_boolean(cfg->keyfile, "global", "report-time", cfg->report_time);
198 config_save_networks(configuration_dir, cfg->networks);
201 list = g_new0(char *, g_list_length(cfg->networks)+1);
202 for (gl = cfg->networks; gl; gl = gl->next) {
203 struct network_config *nc = gl->data;
205 if (nc->autoconnect) {
212 g_key_file_set_string_list(cfg->keyfile, "global", "autoconnect", (const gchar **)list, i);
216 fn = g_build_filename(configuration_dir, "config", NULL);
217 g_key_file_save_to_file(cfg->keyfile, fn, NULL);
221 static void config_load_channel(struct network_config *n, GKeyFile *kf, const char *name)
223 struct channel_config *ch = g_new0(struct channel_config, 1);
225 ch->name = g_strdup(name);
226 if (g_key_file_has_key(kf, name, "key", NULL))
227 ch->key = g_key_file_get_string(kf, name, "key", NULL);
229 if (g_key_file_has_key(kf, name, "autojoin", NULL))
230 ch->autojoin = g_key_file_get_boolean(kf, name, "autojoin", NULL);
232 n->channels = g_list_append(n->channels, ch);
235 static void config_load_servers(struct network_config *n)
241 servers = g_key_file_get_string_list(n->keyfile, "global", "servers", &size, NULL);
246 for (i = 0; i < size; i++) {
248 struct tcp_server_config *s = g_new0(struct tcp_server_config, 1);
250 s->password = g_key_file_get_string(n->keyfile, servers[i], "password", NULL);
251 if (g_key_file_has_key(n->keyfile, servers[i], "ssl", NULL))
252 s->ssl = g_key_file_get_boolean(n->keyfile, servers[i], "ssl", NULL);
254 tmp = strrchr(servers[i], ':');
261 s->host = servers[i];
262 s->port = g_strdup(tmp?tmp:"6667");
263 s->bind_address = g_key_file_get_string(n->keyfile, servers[i], "bind", NULL);
264 if (s->bind_address && (tmp = strchr(s->bind_address, ':'))) {
266 s->bind_port = tmp+1;
269 n->type_settings.tcp_servers = g_list_append(n->type_settings.tcp_servers, s);
275 static struct network_config *config_load_network(struct ctrlproxy_config *cfg, const char *dirname, const char *name)
278 struct network_config *n;
282 GError *error = NULL;
285 kf = g_key_file_new();
287 filename = g_build_filename(dirname, name, NULL);
289 if (!g_key_file_load_from_file(kf, filename, G_KEY_FILE_KEEP_COMMENTS, &error)) {
290 log_global(LOG_ERROR, "Can't parse configuration file '%s': %s", filename, error->message);
295 n = network_config_init(cfg);
300 if (g_key_file_has_key(kf, "global", "fullname", NULL)) {
302 n->fullname = g_key_file_get_string(kf, "global", "fullname", NULL);
303 if (!strcmp(n->fullname, "") || n->fullname[0] == ' ')
304 log_network(LOG_WARNING, n, "Invalid fullname `%s' set", n->fullname);
307 if (g_key_file_has_key(kf, "global", "nick", NULL)) {
309 n->nick = g_key_file_get_string(kf, "global", "nick", NULL);
310 if (!strcmp(n->nick, "") || n->nick[0] == ' ')
311 log_network(LOG_WARNING, n, "Invalid nick name `%s' set", n->fullname);
314 if (g_key_file_has_key(kf, "global", "reconnect-interval", NULL)) {
315 n->reconnect_interval = g_key_file_get_integer(kf, "global", "reconnect-interval", NULL);
318 if (g_key_file_has_key(kf, "global", "queue-speed", NULL)) {
319 n->queue_speed = g_key_file_get_integer(kf, "global", "queue-speed", NULL);
322 if (g_key_file_has_key(kf, "global", "username", NULL)) {
324 n->username = g_key_file_get_string(kf, "global", "username", NULL);
325 if (!strcmp(n->username, "") || n->username[0] == ' ')
326 log_network(LOG_WARNING, n, "Invalid username `%s' set", n->username);
329 if (g_key_file_has_key(kf, "global", "ignore_first_nick", NULL)) {
330 n->ignore_first_nick = g_key_file_get_boolean(kf, "global", "ignore_first_nick", NULL);
333 if (g_key_file_has_key(kf, "global", "password", NULL)) {
335 n->password = g_key_file_get_string(kf, "global", "password", NULL);
338 n->name = g_strdup(name);
340 if (g_key_file_has_key(kf, "global", "program", NULL))
341 n->type = NETWORK_PROGRAM;
342 else if (g_key_file_has_key(kf, "global", "virtual", NULL))
343 n->type = NETWORK_VIRTUAL;
345 n->type = NETWORK_TCP;
349 config_load_servers(n);
351 case NETWORK_PROGRAM:
352 n->type_settings.program_location = g_key_file_get_string(kf, "global", "program", NULL);
354 case NETWORK_VIRTUAL:
355 n->type_settings.virtual_type = g_key_file_get_string(kf, "global", "virtual", NULL);
357 case NETWORK_IOCHANNEL:
362 groups = g_key_file_get_groups(kf, &size);
363 for (i = 0; i < size; i++) {
364 if (!g_ascii_isalpha(groups[i][0]))
365 config_load_channel(n, kf, groups[i]);
373 static struct network_config *find_create_network_config(struct ctrlproxy_config *cfg, const char *name)
376 struct network_config *nc;
377 struct tcp_server_config *tc;
379 for (gl = cfg->networks; gl; gl = gl->next) {
383 if (g_strcasecmp(nc->name, name) == 0)
386 if (nc->type != NETWORK_TCP)
389 for (gl1 = nc->type_settings.tcp_servers; gl1; gl1 = gl1->next) {
391 struct tcp_server_config *sc = gl1->data;
393 if (g_strcasecmp(sc->host, name) == 0)
396 if (g_strncasecmp(sc->host, name, strlen(sc->host)) != 0)
399 tmp = g_strdup_printf("%s:%s", sc->host, sc->port);
401 if (g_strcasecmp(tmp, name) == 0)
408 nc = network_config_init(cfg);
409 nc->name = g_strdup(name);
410 nc->autoconnect = FALSE;
411 nc->reconnect_interval = DEFAULT_RECONNECT_INTERVAL;
412 nc->type = NETWORK_TCP;
413 tc = g_new0(struct tcp_server_config, 1);
414 tc->host = g_strdup(name);
415 if (strchr(tc->host, ':')) {
416 tc->port = tc->host+1;
419 tc->port = g_strdup("6667");
422 nc->type_settings.tcp_servers = g_list_append(nc->type_settings.tcp_servers, tc);
424 cfg->networks = g_list_append(cfg->networks, nc);
429 static void config_load_networks(struct ctrlproxy_config *cfg)
431 char *networksdir = g_build_filename(cfg->config_dir, "networks", NULL);
435 dir = g_dir_open(networksdir, 0, NULL);
439 while ((name = g_dir_read_name(dir))) {
440 if (name[0] == '.' || name[strlen(name)-1] == '~')
442 config_load_network(cfg, networksdir, name);
450 struct ctrlproxy_config *init_configuration(void)
452 struct ctrlproxy_config *cfg;
453 cfg = g_new0(struct ctrlproxy_config, 1);
458 struct ctrlproxy_config *load_configuration(const char *dir)
461 GError *error = NULL;
462 struct ctrlproxy_config *cfg;
464 char **autoconnect_list;
469 file = g_build_filename(dir, "config", NULL);
471 cfg = init_configuration();
472 cfg->config_dir = g_strdup(dir);
473 cfg->network_socket = g_build_filename(cfg->config_dir, "socket", NULL);
474 cfg->admin_socket = g_build_filename(cfg->config_dir, "admin", NULL);
476 kf = cfg->keyfile = g_key_file_new();
478 if (!g_key_file_load_from_file(kf, file, G_KEY_FILE_KEEP_COMMENTS, &error)) {
479 log_global(LOG_ERROR, "Can't parse configuration file '%s': %s", file, error->message);
486 cfg->autosave = TRUE;
487 if (g_key_file_has_key(kf, "global", "autosave", NULL) &&
488 !g_key_file_get_boolean(kf, "global", "autosave", NULL))
489 cfg->autosave = FALSE;
491 if (g_key_file_has_key(kf, "global", "max_who_age", NULL))
492 cfg->max_who_age = g_key_file_get_integer(kf, "global", "max_who_age", NULL);
494 cfg->replication = g_key_file_get_string(kf, "global", "replication", NULL);
495 cfg->linestack_backend = g_key_file_get_string(kf, "global", "linestack", NULL);
497 if (g_key_file_has_key(kf, "global", "report-time", NULL))
498 cfg->report_time = g_key_file_get_boolean(kf, "global", "report-time", NULL);
500 if (g_key_file_has_key(kf, "global", "motd-file", NULL))
501 cfg->motd_file = g_key_file_get_string(kf, "global", "motd-file", NULL);
503 cfg->motd_file = g_build_filename(SHAREDIR, "motd", NULL);
505 if (g_key_file_has_key(kf, "client", "charset", NULL))
506 cfg->client_charset = g_key_file_get_string(kf, "client", "charset", NULL);
508 cfg->client_charset = g_strdup(DEFAULT_CLIENT_CHARSET);
510 if(!g_file_test(cfg->motd_file, G_FILE_TEST_EXISTS))
511 log_global(LOG_ERROR, "Can't open MOTD file '%s' for reading", cfg->motd_file);
513 if (g_key_file_has_key(kf, "admin", "without_privmsg", NULL))
514 cfg->admin_noprivmsg = g_key_file_get_boolean(kf, "admin", "without_privmsg", NULL);
516 cfg->admin_log = TRUE;
517 if (g_key_file_has_key(kf, "admin", "log", NULL) && !g_key_file_get_boolean(kf, "admin", "log", NULL))
518 cfg->admin_log = FALSE;
521 for (gl = cfg->networks; gl; gl = gl->next) {
522 struct network_config *nc = gl->data;
524 nc->autoconnect = FALSE;
527 config_load_networks(cfg);
530 autoconnect_list = g_key_file_get_string_list(kf, "global", "autoconnect", &size, NULL);
532 for (i = 0; i < size; i++) {
533 struct network_config *nc = find_create_network_config(cfg, autoconnect_list[i]);
536 nc->autoconnect = TRUE;
539 g_strfreev(autoconnect_list);
545 struct network_config *network_config_init(struct ctrlproxy_config *cfg)
547 struct network_config *s = g_new0(struct network_config, 1);
549 s->autoconnect = FALSE;
550 s->nick = g_strdup(g_get_user_name());
551 s->username = g_strdup(g_get_user_name());
552 s->fullname = g_strdup(g_get_real_name());
553 s->reconnect_interval = DEFAULT_RECONNECT_INTERVAL;
556 cfg->networks = g_list_append(cfg->networks, s);
560 void free_config(struct ctrlproxy_config *cfg)
562 while (cfg->networks) {
563 struct network_config *nc = cfg->networks->data;
566 g_free(nc->fullname);
567 g_free(nc->username);
568 g_free(nc->password);
569 while (nc->channels) {
570 struct channel_config *cc = nc->channels->data;
573 nc->channels = g_list_remove(nc->channels, cc);
578 while (nc->type_settings.tcp_servers) {
579 struct tcp_server_config *tc = nc->type_settings.tcp_servers->data;
582 g_free(tc->bind_address);
583 g_free(tc->password);
584 nc->type_settings.tcp_servers = g_list_remove(nc->type_settings.tcp_servers, tc);
588 case NETWORK_VIRTUAL:
589 g_free(nc->type_settings.virtual_type);
591 case NETWORK_PROGRAM:
592 g_free(nc->type_settings.program_location);
594 case NETWORK_IOCHANNEL:
595 /* Nothing to free */
598 cfg->networks = g_list_remove(cfg->networks, nc);
599 g_key_file_free(nc->keyfile);
602 g_free(cfg->config_dir);
603 g_free(cfg->network_socket);
604 g_free(cfg->admin_socket);
605 g_free(cfg->replication);
606 g_free(cfg->linestack_backend);
607 g_free(cfg->motd_file);
608 g_key_file_free(cfg->keyfile);
612 gboolean create_configuration(const char *config_dir)
615 struct global *global;
617 char *pass, *listenerfile;
618 GError *error = NULL;
620 if (g_file_test(config_dir, G_FILE_TEST_IS_DIR)) {
621 fprintf(stderr, "%s already exists\n", config_dir);
625 if (g_mkdir(config_dir, 0700) != 0) {
626 fprintf(stderr, "Can't create config directory '%s': %s\n", config_dir, strerror(errno));
630 global = load_global(DEFAULT_CONFIG_DIR);
631 global->config->config_dir = g_strdup(config_dir);
632 save_configuration(global->config, config_dir);
634 kf = g_key_file_new();
636 snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
637 printf("Please specify port the administration interface should listen on.\n"
638 "Prepend with a colon to listen on a specific address.\n"
639 "Example: localhost:6668\n\nPort [%s]: ", port); fflush(stdout);
640 if (!fgets(port, sizeof(port), stdin))
641 snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
643 if (port[strlen(port)-1] == '\n')
644 port[strlen(port)-1] = '\0';
646 if (strlen(port) == 0)
647 snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
649 pass = getpass("Please specify a password for the administration interface: ");
650 g_key_file_set_string(kf, port, "network", "admin");
651 if (!strcmp(pass, "")) {
652 fprintf(stderr, "Warning: no password specified. Authentication disabled!\n");
654 g_key_file_set_string(kf, port, "password", pass);
657 listenerfile = g_build_filename(config_dir, "listener", NULL);
659 if (!g_key_file_save_to_file(kf, listenerfile, &error)) {
660 fprintf(stderr, "Error saving %s: %s\n", listenerfile, error->message);