Actually remove networks from disk when they have been removed in the configuration.
[jelmer/ctrlproxy.git] / src / settings.c
index a33848fe01d8e8b7dcbc24285b5d926ccdf3aeed..a8548fbcb1a020accbce7540475473450fff3eaf 100644 (file)
@@ -4,7 +4,7 @@
 
        This program is free software; you can redistribute it and/or modify
        it under the terms of the GNU General Public License as published by
-       the Free Software Foundation; either version 2 of the License, or
+       the Free Software Foundation; either version 3 of the License, or
        (at your option) any later version.
 
        This program is distributed in the hope that it will be useful,
@@ -20,6 +20,7 @@
 
 #include "internals.h"
 #include "ssl.h"
+#include "keyfile.h"
 
 #ifdef HAVE_SYS_SOCKET_H
 #include <sys/socket.h>
 #define DEFAULT_ADMIN_PORT 6680
 #define DEFAULT_SOCKS_PORT 1080
 
+#define CHANNEL_KEY_FILE_HEADER \
+       "; This file contains channel keys.\n" \
+       "; It has the same format as the ppp secrets files.\n" \
+       "; It should contain one entry per line, each entry consisting of: \n" \
+       "; a channel name, channel key and network name, separated by tabs.\n" \
+       ";\n\n"
+
+static GList *known_keys = NULL;
+
+static void config_cleanup_networks_dir(struct ctrlproxy_config *cfg);
+static void config_save_log(struct log_file_config *data,
+                                                       struct ctrlproxy_config *config);
+static void config_save_auto_away(struct auto_away_config *d, 
+                                                                 struct ctrlproxy_config *config);
+
+static const char *builtin_known_keys[] = {
+       "autoconnect",
+       "autosave",
+       "auto-away-enable",
+       "auto-away-message",
+       "auto-away-nick",
+       "auto-away-client-limit",
+       "auto-away-time",
+       "max_who_age",
+       "replication",
+       "linestack",
+       "report-time",
+       "report-time-offset",
+       "motd-file",
+       "default-client-charset",
+       "learn-nickserv",
+       "learn-network-name",
+       "admin-log",
+       "admin-user",
+       "password",
+       "listener-auto",
+       "listener-autoport",
+       "logging",
+       "logfile",
+       "logdir",
+       "port",
+       "bind",
+       "ssl",
+       "default-network",
+       "log-logfilename",
+       "log-format-nickchange",
+       "log-format-topic",
+       "log-format-notopic",
+       "log-format-part",
+       "log-format-join",
+       "log-format-msg",
+       "log-format-notice",
+       "log-format-action",
+       "log-format-kick",
+       "log-format-quit",
+       "log-format-mode",
+       NULL
+};
+
+static gboolean config_known_key(const char *name)
+{
+       int i;
+
+       if (g_list_find_custom(known_keys, name, (GCompareFunc)strcmp) != NULL)
+               return TRUE;
+
+       for (i = 0; builtin_known_keys[i]; i++)
+               if (!strcmp(builtin_known_keys[i], name))
+                       return TRUE;
+
+       return FALSE;
+}
+
+void config_register_known_key(char *name)
+{
+       if (config_known_key(name))
+               return;
+       known_keys = g_list_insert_sorted(known_keys, g_strdup(name), (GCompareFunc)strcmp);
+}
+
+
+
 gboolean g_key_file_save_to_file(GKeyFile *kf, const gchar *file, GError **error)
 {
        gsize length, nr;
@@ -64,11 +147,10 @@ static void config_save_tcp_servers(struct network_config *n, GKeyFile *kf)
        
        for (gl = n->type_settings.tcp_servers; gl; gl = gl->next) {
                struct tcp_server_config *ts = gl->data;
-               char *name = g_strdup_printf("%s:%s", ts->host, ts->port);
+               char *name = irc_create_url(ts->host, ts->port, ts->ssl);
 
                values[i] = name;
 
-               g_key_file_set_boolean(kf, name, "ssl", ts->ssl);
                if (ts->password)
                        g_key_file_set_string(kf, name, "password", ts->password);
                else
@@ -97,11 +179,13 @@ static void config_save_tcp_servers(struct network_config *n, GKeyFile *kf)
        g_strfreev(values);
 }
 
-static void config_save_network(const char *dir, struct network_config *n)
+static void config_save_network(const char *dir, struct network_config *n, GList **channel_keys)
 {
        GList *gl;
        GKeyFile *kf;
        char *fn;
+       char **autojoin_list;
+       int autojoin_list_count;
        
        if (!n->keyfile) {
                n->keyfile = g_key_file_new();
@@ -114,7 +198,8 @@ static void config_save_network(const char *dir, struct network_config *n)
        g_key_file_set_string(kf, "global", "username", n->username);
        if (n->queue_speed)
                g_key_file_set_integer(kf, "global", "queue-speed", n->queue_speed);
-       g_key_file_set_integer(kf, "global", "reconnect-interval", n->reconnect_interval);
+       if (n->reconnect_interval != -1)
+               g_key_file_set_integer(kf, "global", "reconnect-interval", n->reconnect_interval);
 
        switch(n->type) {
        case NETWORK_VIRTUAL:
@@ -128,18 +213,39 @@ static void config_save_network(const char *dir, struct network_config *n)
                break;
        default:break;
        }
-       
+
+       autojoin_list = g_new0(char *, g_list_length(n->channels));
+       autojoin_list_count = 0;
+
        for (gl = n->channels; gl; gl = gl->next) {
                struct channel_config *c = gl->data;
+               struct keyfile_entry *key;
+
+               if (c->key) {
+                       key = g_new0(struct keyfile_entry, 1);
+                       key->network = n->name;
+                       key->nick = c->name;
+                       key->pass = c->key;
 
-               if (c->key)
-                       g_key_file_set_string(kf, c->name, "key", c->key);
-               else 
-                       g_key_file_remove_key(kf, c->name, "key", NULL);
+                       *channel_keys = g_list_append(*channel_keys, key);
+               }
                
-               g_key_file_set_boolean(kf, c->name, "autojoin", c->autojoin);
+               if (c->autojoin) {
+                       autojoin_list[autojoin_list_count] = c->name;
+                       autojoin_list_count++;
+               }
+
+               g_key_file_remove_group(kf, c->name, NULL);
        }
 
+       if (autojoin_list == NULL)
+               g_key_file_remove_key(kf, "global", "autojoin", NULL);
+       else
+               g_key_file_set_string_list(kf, "global", "autojoin", (const gchar **)autojoin_list, 
+                                                          autojoin_list_count);
+
+       g_free(autojoin_list);
+
        fn = g_build_filename(dir, n->name, NULL);
        g_key_file_save_to_file(kf, fn, NULL);
        g_free(fn);
@@ -154,11 +260,11 @@ static void config_save_listeners(struct ctrlproxy_config *cfg, const char *path
        gboolean empty = TRUE;
        char *default_password;
 
-       default_password = g_key_file_get_string(cfg->keyfile, "listener", "password", NULL);
+       default_password = g_key_file_get_string(cfg->keyfile, "global", "password", NULL);
 
        if (cfg->auto_listener) {
-               g_key_file_set_boolean(cfg->keyfile, "listener", "auto", cfg->auto_listener);
-               g_key_file_set_integer(cfg->keyfile, "listener", "autoport", cfg->listener_autoport);
+               g_key_file_set_boolean(cfg->keyfile, "global", "listener-auto", cfg->auto_listener);
+               g_key_file_set_integer(cfg->keyfile, "global", "listener-autoport", cfg->listener_autoport);
        }
 
        filename = g_build_filename(path, "listener", NULL);
@@ -175,7 +281,12 @@ static void config_save_listeners(struct ctrlproxy_config *cfg, const char *path
                        if (l->password != NULL)
                                g_key_file_set_string(cfg->keyfile, "global", "password", l->password);
 
-                       g_key_file_set_boolean(cfg->keyfile, "global", "ssl", l->ssl);
+                       if (g_key_file_has_key(cfg->keyfile, "global", "ssl", NULL) || l->ssl)
+                               g_key_file_set_boolean(cfg->keyfile, "global", "ssl", l->ssl);
+
+                       if (l->network != NULL)
+                               g_key_file_set_string(cfg->keyfile, "global", "default-network",
+                                                                 l->network);
                } else {
                        char *tmp;
                        empty = FALSE;
@@ -206,13 +317,15 @@ static void config_save_listeners(struct ctrlproxy_config *cfg, const char *path
                }
        }
        
+       g_free(default_password);
        g_free(filename);
 }
 
-static void config_save_networks(const char *config_dir, GList *networks)
+static void config_save_networks(struct ctrlproxy_config *cfg, const char *config_dir, GList *networks)
 {
        char *networksdir = g_build_filename(config_dir, "networks", NULL);
        GList *gl;
+       GList *channel_keys = NULL;
 
        if (!g_file_test(networksdir, G_FILE_TEST_IS_DIR)) {
                if (g_mkdir(networksdir, 0700) != 0) {
@@ -222,13 +335,36 @@ static void config_save_networks(const char *config_dir, GList *networks)
        }
 
        for (gl = networks; gl; gl = gl->next) {
-               struct network_config *n = gl->data;            
-               config_save_network(networksdir, n);
+               struct network_config *n = gl->data;
+               if (!n->implicit) 
+                       config_save_network(networksdir, n, &channel_keys);
+       }
+
+       config_cleanup_networks_dir(cfg);
+
+       if (channel_keys != NULL) {
+               char *filename = g_build_filename(cfg->config_dir, "keys", 
+                                                                         NULL);
+               keyfile_write_file(channel_keys, CHANNEL_KEY_FILE_HEADER, filename);
+               g_free(filename);
+
+               while (channel_keys) {
+                       g_free(channel_keys->data);
+                       channel_keys = channel_keys->next;
+               }
+
+               g_list_free(channel_keys);
        }
 
        g_free(networksdir);
 }
 
+/**
+ * Save configuration to a configuration directory.
+ *
+ * @param cfg The configuration to save.
+ * @param configuration_dir Directory to save to.
+ */
 void save_configuration(struct ctrlproxy_config *cfg, const char *configuration_dir)
 {
        char *fn, **list;
@@ -248,11 +384,26 @@ void save_configuration(struct ctrlproxy_config *cfg, const char *configuration_
        g_key_file_set_boolean(cfg->keyfile, "global", "autosave", cfg->autosave);
        if (cfg->admin_user != NULL)
                g_key_file_set_string(cfg->keyfile, "global", "admin-user", cfg->admin_user);
-       g_key_file_set_boolean(cfg->keyfile, "global", "admin-log", cfg->admin_log);
-       g_key_file_set_integer(cfg->keyfile, "global", "max_who_age", cfg->max_who_age);
+
+       if (g_key_file_has_key(cfg->keyfile, "global", "admin-log", NULL) ||
+               !cfg->admin_log)
+               g_key_file_set_boolean(cfg->keyfile, "global", "admin-log", cfg->admin_log);
+
+       if (g_key_file_has_key(cfg->keyfile, "global", "max_who_age", NULL) ||
+               cfg->max_who_age != 0)
+               g_key_file_set_integer(cfg->keyfile, "global", "max_who_age", cfg->max_who_age);
+
+       if (g_key_file_has_key(cfg->keyfile, "global", "learn-nickserv", NULL) ||
+               !cfg->learn_nickserv)
+               g_key_file_set_boolean(cfg->keyfile, "global", "learn-nickserv", cfg->learn_nickserv);
+
+       if (g_key_file_has_key(cfg->keyfile, "global", "learn-network-name", NULL) ||
+               !cfg->learn_network_name)
+               g_key_file_set_boolean(cfg->keyfile, "global", "learn-network-name", cfg->learn_network_name);
 
        if (cfg->client_charset != NULL)
-               g_key_file_set_string(cfg->keyfile, "client", "charset", cfg->client_charset);
+               g_key_file_set_string(cfg->keyfile, "global", "default-client-charset", cfg->client_charset);
+       
        if (cfg->replication)
                g_key_file_set_string(cfg->keyfile, "global", "replication", cfg->replication);
        if (cfg->linestack_backend) 
@@ -260,12 +411,33 @@ void save_configuration(struct ctrlproxy_config *cfg, const char *configuration_
        if (cfg->motd_file != NULL)
                g_key_file_set_string(cfg->keyfile, "global", "motd-file", cfg->motd_file);
 
-       g_key_file_set_boolean(cfg->keyfile, "global", "report-time", cfg->report_time);
+       switch (cfg->report_time) {
+       case REPORT_TIME_ALWAYS:
+               g_key_file_set_string(cfg->keyfile, "global", "report-time", 
+                                                         "always");
+               break;
+       case REPORT_TIME_NEVER:
+               g_key_file_set_string(cfg->keyfile, "global", "report-time", 
+                                                         "never");
+               break;
+       case REPORT_TIME_REPLICATION:
+               g_key_file_set_string(cfg->keyfile, "global", "report-time", 
+                                                         "replication");
+               break;
+       }
+
+       if (cfg->report_time_offset != 0 ||
+               g_key_file_has_key(cfg->keyfile, "global", "report-time-offset", NULL))
+               g_key_file_set_integer(cfg->keyfile, "global", "report-time-offset", cfg->report_time_offset);
 
-       config_save_networks(configuration_dir, cfg->networks);
+       config_save_networks(cfg, configuration_dir, cfg->networks);
 
        config_save_listeners(cfg, configuration_dir);
 
+       config_save_log(cfg->log_file, cfg);
+
+       config_save_auto_away(&cfg->auto_away, cfg);
+
        i = 0;
        list = g_new0(char *, g_list_length(cfg->networks)+1);
        for (gl = cfg->networks; gl; gl = gl->next) {
@@ -315,20 +487,13 @@ static void config_load_servers(struct network_config *n)
        for (i = 0; i < size; i++) {
                char *tmp;
                struct tcp_server_config *s = g_new0(struct tcp_server_config, 1);
+
+               irc_parse_url(servers[i], &s->host, &s->port, &s->ssl);
                
                s->password = g_key_file_get_string(n->keyfile, servers[i], "password", NULL);
                if (g_key_file_has_key(n->keyfile, servers[i], "ssl", NULL))
                        s->ssl = g_key_file_get_boolean(n->keyfile, servers[i], "ssl", NULL);
 
-               tmp = strrchr(servers[i], ':');
-
-               if (tmp) {
-                       *tmp = '\0';
-                       tmp++;
-               }
-               
-               s->host = servers[i];
-               s->port = g_strdup(tmp != NULL?tmp:DEFAULT_IRC_PORT);
                s->bind_address = g_key_file_get_string(n->keyfile, servers[i], "bind", NULL);
                if (s->bind_address && (tmp = strchr(s->bind_address, ':'))) {
                        *tmp = '\0';
@@ -336,16 +501,40 @@ static void config_load_servers(struct network_config *n)
                }
 
                n->type_settings.tcp_servers = g_list_append(n->type_settings.tcp_servers, s);
+
+               g_key_file_remove_group(n->keyfile, servers[i], NULL);
        }
 
-       g_free(servers);
+       g_strfreev(servers);
 }
 
-static struct network_config *config_load_network(struct ctrlproxy_config *cfg, const char *dirname, const char *name)
+static struct channel_config *config_find_add_channel(struct network_config *nc, const char *name)
+{
+       GList *gl;
+       struct channel_config *cc;
+
+       for (gl = nc->channels; gl; gl = gl->next) { 
+               cc = gl->data;
+               if (!strcasecmp(cc->name, name)) 
+                       return cc;
+
+       }
+
+       cc = g_new0(struct channel_config, 1);
+       cc->name = g_strdup(name);
+
+       nc->channels = g_list_append(nc->channels, cc);
+
+       return cc;
+}
+
+static struct network_config *config_load_network(struct ctrlproxy_config *cfg, const char *dirname, 
+                                                                                                 const char *name, GList *channel_keys)
 {
        GKeyFile *kf;
        struct network_config *n;
        char *filename;
+       GList *gl;
        int i;
        char **groups;
        GError *error = NULL;
@@ -436,6 +625,29 @@ static struct network_config *config_load_network(struct ctrlproxy_config *cfg,
 
        g_strfreev(groups);
 
+       for (gl = channel_keys; gl; gl = gl->next) {
+               struct keyfile_entry *ke = gl->data;
+
+               if (!strcasecmp(ke->network, n->name)) {
+                       struct channel_config *cc = config_find_add_channel(n, n->nick);
+
+                       g_free(cc->key);
+                       cc->key = g_strdup(n->password);
+               }
+       }
+
+       if (g_key_file_has_key(n->keyfile, "global", "autojoin", NULL)) {
+               char **autojoin_channels;
+               autojoin_channels = g_key_file_get_string_list(n->keyfile, "global", "autojoin", &size, NULL);
+               for (i = 0; i < size; i++) {
+                       struct channel_config *cc = config_find_add_channel(n, autojoin_channels[i]);
+
+                       cc->autojoin = TRUE;
+               }
+
+               g_strfreev(autojoin_channels);
+       }
+
        return n;
 }
 
@@ -465,7 +677,7 @@ static struct network_config *find_create_network_config(struct ctrlproxy_config
                        if (g_strncasecmp(sc->host, name, strlen(sc->host)) != 0)
                                continue;
 
-                       tmp = g_strdup_printf("%s:%s", sc->host, sc->port);
+                       tmp = irc_create_url(sc->host, sc->port, FALSE);
 
                        if (g_strcasecmp(tmp, name) == 0)
                                return nc;
@@ -477,17 +689,10 @@ static struct network_config *find_create_network_config(struct ctrlproxy_config
        nc = network_config_init(cfg);
        nc->name = g_strdup(name);
        nc->autoconnect = FALSE;
-       nc->reconnect_interval = DEFAULT_RECONNECT_INTERVAL;
+       nc->reconnect_interval = -1;
        nc->type = NETWORK_TCP;
        tc = g_new0(struct tcp_server_config, 1);
-       tc->host = g_strdup(name);
-       if (strchr(tc->host, ':')) {
-               tc->port = tc->host+1;
-               *tc->port = '\0';
-       } else {
-               tc->port = g_strdup(DEFAULT_IRC_PORT);
-       }
-
+       irc_parse_url(name, &tc->host, &tc->port, &tc->ssl);
        nc->type_settings.tcp_servers = g_list_append(nc->type_settings.tcp_servers, tc);
 
        cfg->networks = g_list_append(cfg->networks, nc);
@@ -535,6 +740,8 @@ static void config_load_listeners_socks(struct ctrlproxy_config *cfg)
                l->allow_rules = g_list_append(l->allow_rules, r);
        }
 
+       g_key_file_remove_group(kf, "socks", NULL);
+
        g_strfreev(allows);
 
        cfg->listeners = g_list_append(cfg->listeners, l);
@@ -550,12 +757,25 @@ static void config_load_listeners(struct ctrlproxy_config *cfg)
        char *default_password;
        GError *error = NULL;
 
-       default_password = g_key_file_get_string(cfg->keyfile, "listener", "password", NULL);
-       if (g_key_file_has_key(cfg->keyfile, "listener", "auto", NULL))
+       if (g_key_file_has_key(cfg->keyfile, "listener", "pasword", NULL)) {
+               g_key_file_set_string(cfg->keyfile, "global", "password", 
+                                                         g_key_file_get_string(cfg->keyfile, "listener", "password", NULL));
+               g_key_file_remove_key(cfg->keyfile, "listener", "password", NULL);
+       }
+       default_password = g_key_file_get_string(cfg->keyfile, "global", "password", NULL);
+       if (g_key_file_has_key(cfg->keyfile, "global", "listener-auto", NULL)) {
+               cfg->auto_listener = g_key_file_get_boolean(cfg->keyfile, "global", "listener-auto", NULL);
+       } else if (g_key_file_has_key(cfg->keyfile, "listener", "auto", NULL)) {
                cfg->auto_listener = g_key_file_get_boolean(cfg->keyfile, "listener", "auto", NULL);
+               g_key_file_remove_key(cfg->keyfile, "listener", "auto", NULL);
+       }
 
-       if (g_key_file_has_key(cfg->keyfile, "listener", "autoport", NULL))
+       if (g_key_file_has_key(cfg->keyfile, "global", "listener-autoport", NULL)) {
+               cfg->listener_autoport = g_key_file_get_integer(cfg->keyfile, "global", "listener-autoport", NULL);
+       } else if (g_key_file_has_key(cfg->keyfile, "listener", "autoport", NULL)) {
                cfg->listener_autoport = g_key_file_get_integer(cfg->keyfile, "listener", "autoport", NULL);
+               g_key_file_remove_key(cfg->keyfile, "listener", "autoport", NULL);
+       }
 
        if (g_key_file_has_key(cfg->keyfile, "global", "port", NULL)) {
                struct listener_config *l = g_new0(struct listener_config, 1);
@@ -566,6 +786,8 @@ static void config_load_listeners(struct ctrlproxy_config *cfg)
                                 g_key_file_get_boolean(cfg->keyfile, "global", "ssl", NULL);
                l->is_default = TRUE;
 
+               l->network = g_key_file_get_string(cfg->keyfile, "global", "default-network", NULL);
+
                cfg->listeners = g_list_append(cfg->listeners, l);
        }
 
@@ -575,6 +797,7 @@ static void config_load_listeners(struct ctrlproxy_config *cfg)
                if (error->code != G_FILE_ERROR_NOENT)
                        log_global(LOG_ERROR, "Can't parse configuration file '%s': %s", filename, error->message);
                g_free(filename);
+               g_free(default_password);
                return;
        }
                
@@ -600,7 +823,7 @@ static void config_load_listeners(struct ctrlproxy_config *cfg)
 
                l->password = g_key_file_get_string(kf, groups[i], "password", NULL);
                if (l->password == NULL)
-                       l->password = default_password;
+                       l->password = g_strdup(default_password);
 
                if (g_key_file_has_key(kf, groups[i], "ssl", NULL))
                        l->ssl = g_key_file_get_boolean(kf, groups[i], "ssl", NULL);
@@ -617,23 +840,63 @@ static void config_load_listeners(struct ctrlproxy_config *cfg)
        }
 
        g_strfreev(groups);
+       g_free(default_password);
        g_free(filename);
 }
 
-static void config_load_networks(struct ctrlproxy_config *cfg)
+struct network_config *config_find_network(struct ctrlproxy_config *cfg, 
+                                                                                  const char *name)
+{
+       GList *gl;
+       for (gl = cfg->networks; gl; gl = gl->next) {
+               struct network_config *nc = gl->data;
+               if (!g_strcasecmp(nc->name, name))
+                       return nc;
+       }
+       return NULL;
+}
+
+#define IS_SPECIAL_FILE(name) (name[0] == '.' || name[strlen(name)-1] == '~')
+
+static void config_cleanup_networks_dir(struct ctrlproxy_config *cfg)
+{
+       char *networksdir = g_build_filename(cfg->config_dir, "networks", NULL);
+       GDir *dir;
+       const char *name;
+
+       dir = g_dir_open(networksdir, 0, NULL);
+       if (dir == NULL)
+               return;
+
+       while ((name = g_dir_read_name(dir))) {
+               char *path;
+               if (IS_SPECIAL_FILE(name) || config_find_network(cfg, name))
+                       continue;
+
+               path = g_build_filename(networksdir, name, NULL);
+               g_unlink(path);
+               g_free(path);
+       }
+
+       g_free(networksdir);
+
+       g_dir_close(dir);
+}
+
+static void config_load_networks(struct ctrlproxy_config *cfg, GList *channel_keys)
 {
        char *networksdir = g_build_filename(cfg->config_dir, "networks", NULL);
        GDir *dir;
        const char *name;
 
        dir = g_dir_open(networksdir, 0, NULL);
-       if (!dir)
+       if (dir == NULL)
                return;
 
        while ((name = g_dir_read_name(dir))) {
-               if (name[0] == '.' || name[strlen(name)-1] == '~')
+               if (IS_SPECIAL_FILE(name))
                        continue;
-               config_load_network(cfg, networksdir, name);
+               config_load_network(cfg, networksdir, name, channel_keys);
        }
 
        g_free(networksdir);
@@ -641,6 +904,201 @@ static void config_load_networks(struct ctrlproxy_config *cfg)
        g_dir_close(dir);
 }
 
+#define FETCH_SETTING(data, kf, section, prefix, name) (data)->name = g_key_file_get_string((kf), (section), prefix __STRING(name), NULL)
+#define STORE_SETTING(data, kf, section, prefix, name) g_key_file_set_string((kf), (section), prefix __STRING(name), (data)->name)
+
+static void config_save_log(struct log_file_config *data,
+                                                       struct ctrlproxy_config *config)
+{
+       if (data == NULL) {
+               g_key_file_set_string(config->keyfile, "global", "logging", "none");
+               return;
+       }
+
+       if (data->is_irssi) {
+               g_key_file_set_string(config->keyfile, "global", "logging", "irssi");
+               if (data->logbasedir) {
+                       if (!data->logbasedir_is_default)
+                               g_key_file_set_string(config->keyfile, "global", "logdir", data->logbasedir);
+               } else {
+                       g_key_file_set_string(config->keyfile, "global", "logfile", data->logfilename);
+               }
+       } else {
+               STORE_SETTING(data, config->keyfile, "global", "", logfilename);
+               STORE_SETTING(data, config->keyfile, "global", "log-format-", nickchange);
+               STORE_SETTING(data, config->keyfile, "global", "log-format-", topic);
+               STORE_SETTING(data, config->keyfile, "global", "log-format-", notopic);
+               STORE_SETTING(data, config->keyfile, "global", "log-format-", part);
+               STORE_SETTING(data, config->keyfile, "global", "log-format-", join);
+               STORE_SETTING(data, config->keyfile, "global", "log-format-", msg);
+               STORE_SETTING(data, config->keyfile, "global", "log-format-", notice);
+               STORE_SETTING(data, config->keyfile, "global", "log-format-", action);
+               STORE_SETTING(data, config->keyfile, "global", "log-format-", kick);
+               STORE_SETTING(data, config->keyfile, "global", "log-format-", quit);
+               STORE_SETTING(data, config->keyfile, "global", "log-format-", mode);
+       }
+}
+
+static void config_load_log(struct ctrlproxy_config *config)
+{
+       GKeyFile *kf = config->keyfile;
+       struct log_file_config *data;
+       char *logging = NULL;
+
+       if (g_key_file_has_key(kf, "global", "logging", NULL)) {
+               logging = g_key_file_get_string(kf, "global", "logging", NULL);
+       }
+
+       if (g_key_file_has_group(kf, "log-custom")) {
+               data = g_new0(struct log_file_config, 1);
+
+               FETCH_SETTING(data, kf, "log-custom", "", nickchange);
+               FETCH_SETTING(data, kf, "log-custom", "", logfilename);
+               FETCH_SETTING(data, kf, "log-custom", "", topic);
+               FETCH_SETTING(data, kf, "log-custom", "", notopic);
+               FETCH_SETTING(data, kf, "log-custom", "", part);
+               FETCH_SETTING(data, kf, "log-custom", "", join);
+               FETCH_SETTING(data, kf, "log-custom", "", msg);
+               FETCH_SETTING(data, kf, "log-custom", "", notice);
+               FETCH_SETTING(data, kf, "log-custom", "", action);
+               FETCH_SETTING(data, kf, "log-custom", "", kick);
+               FETCH_SETTING(data, kf, "log-custom", "", quit);
+               FETCH_SETTING(data, kf, "log-custom", "", mode);
+
+               g_key_file_remove_group(kf, "log-custom", NULL);
+               config->log_file = data;
+               log_custom_load(data);
+       }
+
+       if (logging != NULL && !strcmp(logging, "custom")) {
+               data = g_new0(struct log_file_config, 1);
+
+               FETCH_SETTING(data, kf, "global", "", logfilename);
+               FETCH_SETTING(data, kf, "global", "log-format-", nickchange);
+               FETCH_SETTING(data, kf, "global", "log-format-", topic);
+               FETCH_SETTING(data, kf, "global", "log-format-", notopic);
+               FETCH_SETTING(data, kf, "global", "log-format-", part);
+               FETCH_SETTING(data, kf, "global", "log-format-", join);
+               FETCH_SETTING(data, kf, "global", "log-format-", msg);
+               FETCH_SETTING(data, kf, "global", "log-format-", notice);
+               FETCH_SETTING(data, kf, "global", "log-format-", action);
+               FETCH_SETTING(data, kf, "global", "log-format-", kick);
+               FETCH_SETTING(data, kf, "global", "log-format-", quit);
+               FETCH_SETTING(data, kf, "global", "log-format-", mode);
+
+               config->log_file = data;
+               log_custom_load(data);
+       }
+
+       if (g_key_file_has_group(kf, "log-irssi") || 
+               (logging != NULL && !strcmp(logging, "irssi"))) {
+               data = g_new0(struct log_file_config, 1);
+               data->is_irssi = TRUE;
+
+               data->join = "%h:%M -!- %n [%u] has joined %c";
+               data->part = "%h:%M -!- %n [%u] has left %c [%m]";
+               data->msg = "%h:%M < %n> %m";
+               data->notice = "%h:%M < %n> %m";
+               data->action = "%h:%M  * %n %m";
+               data->mode = "%h:%M -!- mode/%t [%p %c] by %n";
+               data->quit = "%h:%M -!- %n [%u] has quit [%m]";
+               data->kick = "%h:%M -!- %t has been kicked by %n [%m]";
+               data->topic = "%h:%M -!- %n has changed the topic to %t";
+               data->notopic = "%h:%M -!- %n has removed the topic";
+               data->nickchange = "%h:%M -!- %n is now known as %r";
+
+               if (g_key_file_has_key(kf, "global", "logfile", NULL)) {
+                       data->logfilename= g_key_file_get_string(kf, "global", "logfile", NULL);
+               } else if (g_key_file_has_key(kf, "global", "logdir", NULL)) {
+                       data->logbasedir = g_key_file_get_string(kf, "global", "logdir", NULL);
+                       data->logfilename = g_strdup_printf("%s/%%N/%%@", data->logbasedir);
+               } else if (g_key_file_has_key(kf, "log-irssi", "logfile", NULL)) {
+                       data->logbasedir = g_key_file_get_string(kf, "log-irssi", "logfile", NULL);
+                       data->logfilename = g_strdup_printf("%s/%%N/%%@", data->logbasedir);
+               } else {
+                       data->logbasedir = g_build_filename(config->config_dir, 
+                                                                                 "log_irssi", NULL);
+
+                       data->logbasedir_is_default = TRUE;
+
+                       data->logfilename = g_strdup_printf("%s/%%N/%%@", data->logbasedir);
+               }
+               g_key_file_remove_group(kf, "log-irssi", NULL);
+
+               config->log_file = data;
+               log_custom_load(data);
+       }
+
+       if (logging != NULL && 
+                       strcmp(logging, "irssi") != 0 && 
+                       strcmp(logging, "custom") != 0 &&
+                       strcmp(logging, "none") != 0) {
+               log_global(LOG_WARNING, "Unknown log type `%s'", logging);
+       }
+
+       g_free(logging);
+}
+
+static void config_save_auto_away(struct auto_away_config *d, struct ctrlproxy_config *config)
+{
+       GKeyFile *kf = config->keyfile;
+       
+       if (g_key_file_has_key(kf, "global", "auto-away-enable", NULL) ||
+               d->enabled)
+               g_key_file_set_boolean(kf, "global", "auto-away-enable", d->enabled);
+
+       if (d->message != NULL)
+               g_key_file_set_string(kf, "global", "auto-away-message", d->message);
+
+       if (d->nick != NULL)
+               g_key_file_set_string(kf, "global", "auto-away-nick", d->nick);
+
+       if (d->client_limit != -1)
+               g_key_file_set_integer(kf, "global", "auto-away-client-limit", d->client_limit);
+
+       if (d->max_idle_time != -1)
+               g_key_file_set_integer(kf, "global", "auto-away-time", d->max_idle_time);
+}
+
+static void config_load_auto_away(struct auto_away_config *d, GKeyFile *kf)
+{
+       if (g_key_file_has_group(kf, "auto-away")) {
+               d->enabled = TRUE;
+               d->message = g_key_file_get_string(kf, "auto-away", "message", NULL);
+               d->nick = g_key_file_get_string(kf, "auto-away", "nick", NULL);
+               if (g_key_file_has_key(kf, "auto-away", "client_limit", NULL)) {
+                       d->client_limit = g_key_file_get_integer(kf, "auto-away", "client_limit", NULL);
+                       if (g_key_file_has_key(kf, "auto-away", "only_noclient", NULL))
+                               log_global(LOG_WARNING, "auto-away: not using only_noclient because client_limit is set");
+               }
+               else if (g_key_file_has_key(kf, "auto-away", "only_noclient", NULL)) {
+                       d->client_limit = g_key_file_get_boolean(kf, "auto-away", "only_noclient", NULL) ? 0 : -1;
+                       log_global(LOG_WARNING, "auto-away: only_noclient is deprecated, please use client_limit instead");
+               }
+               else
+                       d->client_limit = -1;
+               if (g_key_file_has_key(kf, "auto-away", "time", NULL))
+                       d->max_idle_time = g_key_file_get_integer(kf, "auto-away", "time", NULL);
+               else
+                       d->max_idle_time = -1;
+
+               g_key_file_remove_group(kf, "auto-away", NULL);
+       } else {
+               if (g_key_file_has_key(kf, "global", "auto-away-enable", NULL))
+                       d->enabled = g_key_file_get_boolean(kf, "global", "auto-away-enable", NULL);
+               d->message = g_key_file_get_string(kf, "global", "auto-away-message", NULL);
+               d->nick = g_key_file_get_string(kf, "global", "auto-away-nick", NULL);
+               if (g_key_file_has_key(kf, "global", "auto-away-client-limit", NULL)) {
+                       d->client_limit = g_key_file_get_integer(kf, "global", "auto-away-client-limit", NULL);
+               } else
+                       d->client_limit = -1;
+               if (g_key_file_has_key(kf, "global", "auto-away-time", NULL))
+                       d->max_idle_time = g_key_file_get_integer(kf, "global", "auto-away-time", NULL);
+               else
+                       d->max_idle_time = -1;
+       }
+}
+
 struct ctrlproxy_config *init_configuration(void)
 {
        struct ctrlproxy_config *cfg;
@@ -654,11 +1112,14 @@ struct ctrlproxy_config *load_configuration(const char *dir)
        GKeyFile *kf;
        GError *error = NULL;
        struct ctrlproxy_config *cfg;
+       char **keys;
        char *file;
        char **autoconnect_list;
        GList *gl;
        gsize size;
        int i;
+       GList *channel_keys = NULL;
+       char *keyfile_filename;
 
        file = g_build_filename(dir, "config", NULL);
 
@@ -682,24 +1143,59 @@ struct ctrlproxy_config *load_configuration(const char *dir)
                !g_key_file_get_boolean(kf, "global", "autosave", NULL))
                cfg->autosave = FALSE;
 
+
        if (g_key_file_has_key(kf, "global", "max_who_age", NULL))
                cfg->max_who_age = g_key_file_get_integer(kf, "global", "max_who_age", NULL);
 
        cfg->replication = g_key_file_get_string(kf, "global", "replication", NULL);
        cfg->linestack_backend = g_key_file_get_string(kf, "global", "linestack", NULL);
 
-       if (g_key_file_has_key(kf, "global", "report-time", NULL))
-               cfg->report_time = g_key_file_get_boolean(kf, "global", "report-time", NULL);
+       if (g_key_file_has_key(kf, "global", "report-time", NULL)) {
+               char *setting = g_key_file_get_string(kf, "global", "report-time", NULL);
+               if (!g_strcasecmp(setting, "never") || !g_strcasecmp(setting, "false")) 
+                       cfg->report_time = REPORT_TIME_NEVER;
+               else if (!g_strcasecmp(setting, "always"))
+                       cfg->report_time = REPORT_TIME_ALWAYS;
+               else if  (!g_strcasecmp(setting, "replication") || 
+                                 !g_strcasecmp(setting, "true"))
+                       cfg->report_time = REPORT_TIME_REPLICATION;
+               else {
+                       log_global(LOG_WARNING, "Unknown value `%s' for report-time in configuration file", setting);
+               }
+               g_free(setting);
+       }
+
+       cfg->report_time_offset = 0;
+       if (g_key_file_has_key(kf, "global", "report-time-offset", NULL)) {
+               cfg->report_time_offset = g_key_file_get_integer(kf, "global", "report-time-offset", NULL);
+       }
 
     if (g_key_file_has_key(kf, "global", "motd-file", NULL))
                cfg->motd_file = g_key_file_get_string(kf, "global", "motd-file", NULL);
     else 
            cfg->motd_file = g_build_filename(SHAREDIR, "motd", NULL);
 
-    if (g_key_file_has_key(kf, "client", "charset", NULL))
+    if (g_key_file_has_key(kf, "client", "charset", NULL)) {
                cfg->client_charset = g_key_file_get_string(kf, "client", "charset", NULL);
-    else 
+               g_key_file_remove_key(cfg->keyfile, "client", "charset", NULL); /* deprecated */
+       } else if (g_key_file_has_key(kf, "global", "client-charset", NULL)) {
+               cfg->client_charset = g_key_file_get_string(kf, "global", "client-charset", NULL);
+               g_key_file_remove_key(cfg->keyfile, "global", "client-charset", NULL); /* deprecated */
+       } else if (g_key_file_has_key(kf, "global", "default-client-charset", NULL)) {
+               cfg->client_charset = g_key_file_get_string(kf, "global", "default-client-charset", NULL);
+       } else {
            cfg->client_charset = NULL;
+       }
+
+    if (g_key_file_has_key(kf, "global", "learn-nickserv", NULL))
+               cfg->learn_nickserv = g_key_file_get_boolean(kf, "global", "learn-nicksev", NULL);
+    else 
+           cfg->learn_nickserv = TRUE;
+
+    if (g_key_file_has_key(kf, "global", "learn-network-name", NULL))
+               cfg->learn_network_name = g_key_file_get_boolean(kf, "global", "learn-network-name", NULL);
+    else 
+           cfg->learn_network_name = TRUE;
 
        if (!g_file_test(cfg->motd_file, G_FILE_TEST_EXISTS))
                log_global(LOG_ERROR, "Can't open MOTD file '%s' for reading", cfg->motd_file);
@@ -723,6 +1219,7 @@ struct ctrlproxy_config *load_configuration(const char *dir)
        g_key_file_remove_key(kf, "admin", "log", NULL);
     if (g_key_file_has_key(kf, "global", "admin-log", NULL) && !g_key_file_get_boolean(kf, "global", "admin-log", NULL))
         cfg->admin_log = FALSE;
+       g_key_file_remove_group(kf, "admin", NULL);
 
        for (gl = cfg->networks; gl; gl = gl->next) {
                struct network_config *nc = gl->data;
@@ -730,10 +1227,31 @@ struct ctrlproxy_config *load_configuration(const char *dir)
                nc->autoconnect = FALSE;
        }
 
-       config_load_networks(cfg);
-
        config_load_listeners(cfg);
        config_load_listeners_socks(cfg);
+       config_load_log(cfg);
+       config_load_auto_away(&cfg->auto_away, cfg->keyfile);
+
+       keyfile_filename = g_build_filename(cfg->config_dir, "keys", 
+                                                                         NULL);
+
+       if (g_file_test(keyfile_filename, G_FILE_TEST_EXISTS)) {
+               if (!keyfile_read_file(keyfile_filename, ';', &channel_keys)) {
+                       log_global(LOG_WARNING, "Unable to read keys file");
+               }
+       }
+
+       g_free(keyfile_filename);
+
+       config_load_networks(cfg, channel_keys);
+
+       /* Check for unknown parameters */
+       keys = g_key_file_get_keys(kf, "global", NULL, NULL);
+       for (i = 0; keys[i] != NULL; i++) {
+               if (!config_known_key(keys[i])) 
+                       log_global(LOG_WARNING, "Unknown setting `%s' in configuration file", keys[i]);
+       }
+       g_strfreev(keys);
 
        size = 0;
        autoconnect_list = g_key_file_get_string_list(kf, "global", "autoconnect", &size, NULL);
@@ -746,6 +1264,7 @@ struct ctrlproxy_config *load_configuration(const char *dir)
        }
 
        g_strfreev(autoconnect_list);
+
        g_free(file);
 
        return cfg;
@@ -765,7 +1284,7 @@ struct network_config *network_config_init(struct ctrlproxy_config *cfg)
                g_free(s->fullname);
                s->fullname = g_strdup(s->username);
        }
-       s->reconnect_interval = DEFAULT_RECONNECT_INTERVAL;
+       s->reconnect_interval = -1;
 
        if (cfg) 
                cfg->networks = g_list_append(cfg->networks, s);
@@ -820,6 +1339,7 @@ void free_config(struct ctrlproxy_config *cfg)
        g_free(cfg->replication);
        g_free(cfg->linestack_backend);
        g_free(cfg->motd_file);
+       g_free(cfg->admin_user);
        g_key_file_free(cfg->keyfile);
        g_free(cfg);
 }
@@ -842,7 +1362,10 @@ gboolean create_configuration(const char *config_dir)
        }
 
        global = load_global(DEFAULT_CONFIG_DIR);       
-       if (global == NULL) return FALSE;
+       if (global == NULL) { 
+               fprintf(stderr, "Unable to load default configuration '%s'\n", DEFAULT_CONFIG_DIR);     
+               return FALSE;
+       }
        global->config->config_dir = g_strdup(config_dir);
        save_configuration(global->config, config_dir);
 
@@ -862,13 +1385,16 @@ gboolean create_configuration(const char *config_dir)
        l = g_new0(struct listener_config, 1);
        pass = getpass("Please specify a password for the administration interface: "); 
        l->port = port;
+       l->is_default = TRUE;
        if (!strcmp(pass, "")) {
                fprintf(stderr, "Warning: no password specified. Authentication disabled!\n");
        } else {
-               l->password = pass;
+               l->password = g_strdup(pass);
        }
 
        global->config->listeners = g_list_append(global->config->listeners, l);
 
+       save_configuration(global->config, config_dir);
+
        return TRUE;
 }