Start working on config improvements for 3.0.5.
[jelmer/ctrlproxy.git] / src / settings.c
1 /*
2         ctrlproxy: A modular IRC proxy
3         (c) 2002-2007 Jelmer Vernooij <jelmer@nl.linux.org>
4
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.
9
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.
14
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.
18 */
19
20
21 #include "internals.h"
22 #include "ssl.h"
23
24 #ifdef HAVE_SYS_SOCKET_H
25 #include <sys/socket.h>
26 #endif
27
28 #include <netdb.h>
29 #include <sys/socket.h>
30 #include <glib/gstdio.h>
31
32 #define DEFAULT_ADMIN_PORT 6680
33 #define DEFAULT_SOCKS_PORT 1080
34
35 gboolean g_key_file_save_to_file(GKeyFile *kf, const gchar *file, GError **error)
36 {
37         gsize length, nr;
38         char *data = g_key_file_to_data(kf, &length, error);
39         GIOChannel *gio;
40
41         if (!data)
42                 return FALSE;
43
44         gio = g_io_channel_new_file(file, "w+", error);
45         if (!gio) {
46                 g_free(data);
47                 return FALSE;
48         }
49         
50         g_io_channel_write_chars(gio, data, length, &nr, error);
51
52         g_free(data);
53
54         g_io_channel_unref(gio);
55
56         return TRUE;
57 }
58
59 static void config_save_tcp_servers(struct network_config *n, GKeyFile *kf)
60 {
61         GList *gl;
62         int i = 0; 
63         gchar **values = g_new0(gchar *, g_list_length(n->type_settings.tcp_servers)+1);
64         
65         for (gl = n->type_settings.tcp_servers; gl; gl = gl->next) {
66                 struct tcp_server_config *ts = gl->data;
67                 char *name = g_strdup_printf("%s:%s", ts->host, ts->port);
68
69                 values[i] = name;
70
71                 if (g_key_file_has_key(kf, name, "ssl", NULL) || ts->ssl)
72                         g_key_file_set_boolean(kf, name, "ssl", ts->ssl);
73
74                 if (ts->password)
75                         g_key_file_set_string(kf, name, "password", ts->password);
76                 else
77                         g_key_file_remove_key(kf, name, "password", NULL);
78
79                 if (ts->bind_address) {
80                         char *tmp;
81                         if (ts->bind_port) 
82                                 tmp = g_strdup_printf("%s:%s", 
83                                                       ts->bind_address,
84                                                       ts->bind_port);
85                         else
86                                 tmp = g_strdup(ts->bind_address);
87
88                         g_key_file_set_string(kf, name, "bind", tmp);
89
90                         g_free(tmp);
91                 } else 
92                         g_key_file_remove_key(kf, name, "bind", NULL);
93
94                 i++;
95         }
96
97         g_key_file_set_string_list(kf, "global", "servers", (const gchar **)values, i);
98
99         g_strfreev(values);
100 }
101
102 static void config_save_network(const char *dir, struct network_config *n)
103 {
104         GList *gl;
105         GKeyFile *kf;
106         char *fn;
107         
108         if (!n->keyfile) {
109                 n->keyfile = g_key_file_new();
110         }
111
112         kf = n->keyfile;
113
114         g_key_file_set_string(kf, "global", "fullname", n->fullname);
115         g_key_file_set_string(kf, "global", "nick", n->nick);
116         g_key_file_set_string(kf, "global", "username", n->username);
117         if (n->queue_speed)
118                 g_key_file_set_integer(kf, "global", "queue-speed", n->queue_speed);
119         g_key_file_set_integer(kf, "global", "reconnect-interval", n->reconnect_interval);
120
121         switch(n->type) {
122         case NETWORK_VIRTUAL:
123                 g_key_file_set_string(kf, "global", "virtual", n->type_settings.virtual_type);
124                 break;
125         case NETWORK_PROGRAM:
126                 g_key_file_set_string(kf, "global", "program", n->type_settings.program_location);
127                 break;
128         case NETWORK_TCP:
129                 config_save_tcp_servers(n, kf);
130                 break;
131         default:break;
132         }
133         
134         for (gl = n->channels; gl; gl = gl->next) {
135                 struct channel_config *c = gl->data;
136
137                 if (c->key)
138                         g_key_file_set_string(kf, c->name, "key", c->key);
139                 else 
140                         g_key_file_remove_key(kf, c->name, "key", NULL);
141                 
142                 g_key_file_set_boolean(kf, c->name, "autojoin", c->autojoin);
143         }
144
145         fn = g_build_filename(dir, n->name, NULL);
146         g_key_file_save_to_file(kf, fn, NULL);
147         g_free(fn);
148 }
149
150 static void config_save_listeners(struct ctrlproxy_config *cfg, const char *path)
151 {
152         GList *gl;
153         char *filename;
154         GKeyFile *kf; 
155         GError *error = NULL;
156         gboolean empty = TRUE;
157         char *default_password;
158
159         default_password = g_key_file_get_string(cfg->keyfile, "listener", "password", NULL);
160
161         if (cfg->auto_listener) {
162                 g_key_file_set_boolean(cfg->keyfile, "listener", "auto", cfg->auto_listener);
163                 g_key_file_set_integer(cfg->keyfile, "listener", "autoport", cfg->listener_autoport);
164         }
165
166         filename = g_build_filename(path, "listener", NULL);
167
168         kf = g_key_file_new();
169
170         for (gl = cfg->listeners; gl; gl = gl->next) {
171                 struct listener_config *l = gl->data;
172
173                 if (l->is_default) {
174                         g_key_file_set_string(cfg->keyfile, "global", "port", l->port);
175                         if (l->address != NULL)
176                                 g_key_file_set_string(cfg->keyfile, "global", "bind", l->address);
177                         if (l->password != NULL)
178                                 g_key_file_set_string(cfg->keyfile, "global", "password", l->password);
179
180                         if (g_key_file_has_key(cfg->keyfile, "global", "ssl", NULL) || l->ssl)
181                                 g_key_file_set_boolean(cfg->keyfile, "global", "ssl", l->ssl);
182
183                         if (l->network != NULL)
184                                 g_key_file_set_string(cfg->keyfile, "global", "default-network",
185                                                                   l->network);
186                 } else {
187                         char *tmp;
188                         empty = FALSE;
189                         if (!l->address) 
190                                 tmp = g_strdup(l->port);
191                         else
192                                 tmp = g_strdup_printf("%s:%s", l->address, l->port);
193
194                         if (l->password != NULL && 
195                                 !(default_password != NULL && strcmp(l->password, default_password) == 0)) 
196                                 g_key_file_set_string(kf, tmp, "password", l->password);
197
198                         if (l->network != NULL) {
199                                 g_key_file_set_string(kf, tmp, "network", l->network);
200                         }
201
202                         g_key_file_set_boolean(kf, tmp, "ssl", l->ssl);
203                 
204                         g_free(tmp);
205                 }
206         }
207
208         if (empty) {
209                 unlink(filename);
210         } else { 
211                 if (!g_key_file_save_to_file(kf, filename, &error)) {
212                         log_global(LOG_WARNING, "Unable to save to \"%s\": %s", filename, error->message);
213                 }
214         }
215         
216         g_free(filename);
217 }
218
219 static void config_save_networks(const char *config_dir, GList *networks)
220 {
221         char *networksdir = g_build_filename(config_dir, "networks", NULL);
222         GList *gl;
223
224         if (!g_file_test(networksdir, G_FILE_TEST_IS_DIR)) {
225                 if (g_mkdir(networksdir, 0700) != 0) {
226                         log_global(LOG_ERROR, "Can't create networks directory '%s': %s", networksdir, strerror(errno));
227                         return;
228                 }
229         }
230
231         for (gl = networks; gl; gl = gl->next) {
232                 struct network_config *n = gl->data;            
233                 config_save_network(networksdir, n);
234         }
235
236         g_free(networksdir);
237 }
238
239 void save_configuration(struct ctrlproxy_config *cfg, const char *configuration_dir)
240 {
241         char *fn, **list;
242         int i;
243         GList *gl;
244
245         if (!g_file_test(configuration_dir, G_FILE_TEST_IS_DIR)) {
246                 if (g_mkdir(configuration_dir, 0700) != 0) {
247                         log_global(LOG_ERROR, "Unable to open configuration directory '%s'\n", configuration_dir);
248                         return;
249                 }
250         }
251
252         if (!cfg->keyfile)
253                 cfg->keyfile = g_key_file_new();
254
255         g_key_file_set_boolean(cfg->keyfile, "global", "autosave", cfg->autosave);
256         if (cfg->admin_user != NULL)
257                 g_key_file_set_string(cfg->keyfile, "global", "admin-user", cfg->admin_user);
258
259         if (g_key_file_has_key(cfg->keyfile, "global", "admin-log", NULL) ||
260                 !cfg->admin_log)
261                 g_key_file_set_boolean(cfg->keyfile, "global", "admin-log", cfg->admin_log);
262
263         if (g_key_file_has_key(cfg->keyfile, "global", "max_who_age", NULL) ||
264                 cfg->max_who_age != 0)
265                 g_key_file_set_integer(cfg->keyfile, "global", "max_who_age", cfg->max_who_age);
266
267         if (g_key_file_has_key(cfg->keyfile, "global", "learn-nickserv", NULL) ||
268                 !cfg->learn_nickserv)
269                 g_key_file_set_boolean(cfg->keyfile, "global", "learn-nickserv", cfg->learn_nickserv);
270
271         if (g_key_file_has_key(cfg->keyfile, "global", "learn-network-name", NULL) ||
272                 !cfg->learn_network_name)
273                 g_key_file_set_boolean(cfg->keyfile, "global", "learn-network-name", cfg->learn_network_name);
274
275         if (cfg->client_charset != NULL)
276                 g_key_file_set_string(cfg->keyfile, "global", "client-charset", cfg->client_charset);
277         if (cfg->replication)
278                 g_key_file_set_string(cfg->keyfile, "global", "replication", cfg->replication);
279         if (cfg->linestack_backend) 
280                 g_key_file_set_string(cfg->keyfile, "global", "linestack", cfg->linestack_backend);
281         if (cfg->motd_file != NULL)
282                 g_key_file_set_string(cfg->keyfile, "global", "motd-file", cfg->motd_file);
283
284         g_key_file_set_boolean(cfg->keyfile, "global", "report-time", cfg->report_time);
285
286         config_save_networks(configuration_dir, cfg->networks);
287
288         config_save_listeners(cfg, configuration_dir);
289
290         i = 0;
291         list = g_new0(char *, g_list_length(cfg->networks)+1);
292         for (gl = cfg->networks; gl; gl = gl->next) {
293                 struct network_config *nc = gl->data;
294
295                 if (nc->autoconnect) {
296                         list[i] = nc->name;
297                         i++;
298                 }
299         }
300         
301         if (i > 0) 
302                 g_key_file_set_string_list(cfg->keyfile, "global", "autoconnect", (const gchar **)list, i);
303
304         g_free(list);
305
306         fn = g_build_filename(configuration_dir, "config", NULL);
307         g_key_file_save_to_file(cfg->keyfile, fn, NULL);
308         g_free(fn);
309 }
310
311 static void config_load_channel(struct network_config *n, GKeyFile *kf, const char *name)
312 {
313         struct channel_config *ch = g_new0(struct channel_config, 1);
314
315         ch->name = g_strdup(name);
316         if (g_key_file_has_key(kf, name, "key", NULL)) 
317                 ch->key = g_key_file_get_string(kf, name, "key", NULL);
318
319         if (g_key_file_has_key(kf, name, "autojoin", NULL))
320                 ch->autojoin = g_key_file_get_boolean(kf, name, "autojoin", NULL);
321         
322         n->channels = g_list_append(n->channels, ch);
323 }
324
325 static void config_load_servers(struct network_config *n)
326 {
327         gsize size;
328         char **servers;
329         int i;
330         
331         servers = g_key_file_get_string_list(n->keyfile, "global", "servers", &size, NULL);
332
333         if (!servers)
334                 return;
335
336         for (i = 0; i < size; i++) {
337                 char *tmp;
338                 struct tcp_server_config *s = g_new0(struct tcp_server_config, 1);
339                 
340                 s->password = g_key_file_get_string(n->keyfile, servers[i], "password", NULL);
341                 if (g_key_file_has_key(n->keyfile, servers[i], "ssl", NULL))
342                         s->ssl = g_key_file_get_boolean(n->keyfile, servers[i], "ssl", NULL);
343
344                 tmp = strrchr(servers[i], ':');
345
346                 if (tmp) {
347                         *tmp = '\0';
348                         tmp++;
349                 }
350                 
351                 s->host = servers[i];
352                 s->port = g_strdup(tmp != NULL?tmp:DEFAULT_IRC_PORT);
353                 s->bind_address = g_key_file_get_string(n->keyfile, servers[i], "bind", NULL);
354                 if (s->bind_address && (tmp = strchr(s->bind_address, ':'))) {
355                         *tmp = '\0';
356                         s->bind_port = tmp+1;
357                 }
358
359                 n->type_settings.tcp_servers = g_list_append(n->type_settings.tcp_servers, s);
360         }
361
362         g_free(servers);
363 }
364
365 static struct network_config *config_load_network(struct ctrlproxy_config *cfg, const char *dirname, const char *name)
366 {
367         GKeyFile *kf;
368         struct network_config *n;
369         char *filename;
370         int i;
371         char **groups;
372         GError *error = NULL;
373         gsize size;
374
375         kf = g_key_file_new();
376
377         filename = g_build_filename(dirname, name, NULL);
378
379         if (!g_key_file_load_from_file(kf, filename, G_KEY_FILE_KEEP_COMMENTS, &error)) {       
380                 log_global(LOG_ERROR, "Can't parse configuration file '%s': %s", filename, error->message);
381                 g_key_file_free(kf);
382                 return NULL;
383         }       
384
385         n = network_config_init(cfg);
386         n->keyfile = kf;
387
388         g_free(filename);
389
390         if (g_key_file_has_key(kf, "global", "fullname", NULL)) {
391                 g_free(n->fullname);
392                 n->fullname = g_key_file_get_string(kf, "global", "fullname", NULL);
393                 if (!strcmp(n->fullname, "") || n->fullname[0] == ' ')
394                         log_global(LOG_WARNING, "Invalid fullname `%s' set for network `%s'", n->fullname, n->name);
395         }
396
397         if (g_key_file_has_key(kf, "global", "nick", NULL)) {
398                 g_free(n->nick);
399                 n->nick = g_key_file_get_string(kf, "global", "nick", NULL);
400                 if (!strcmp(n->nick, "") || n->nick[0] == ' ')
401                         log_global(LOG_WARNING, "Invalid nick name `%s' set for `%s'", n->nick, n->name);
402         }
403
404         if (g_key_file_has_key(kf, "global", "reconnect-interval", NULL)) {
405                 n->reconnect_interval = g_key_file_get_integer(kf, "global", "reconnect-interval", NULL);
406         }
407
408         if (g_key_file_has_key(kf, "global", "queue-speed", NULL)) {
409                 n->queue_speed = g_key_file_get_integer(kf, "global", "queue-speed", NULL);
410         }
411
412         if (g_key_file_has_key(kf, "global", "username", NULL)) {
413                 g_free(n->username);
414                 n->username = g_key_file_get_string(kf, "global", "username", NULL);
415                 if (!strcmp(n->username, "") || n->username[0] == ' ')
416                         log_global(LOG_WARNING, "Invalid username `%s' set for network `%s'", n->username, n->name);
417         }
418
419         if (g_key_file_has_key(kf, "global", "ignore_first_nick", NULL)) {
420                 n->ignore_first_nick = g_key_file_get_boolean(kf, "global", "ignore_first_nick", NULL);
421         }
422
423         if (g_key_file_has_key(kf, "global", "password", NULL)) {
424                 g_free(n->password);
425                 n->password = g_key_file_get_string(kf, "global", "password", NULL);
426         }
427
428         n->name = g_strdup(name);
429
430         if (g_key_file_has_key(kf, "global", "program", NULL)) 
431                 n->type = NETWORK_PROGRAM;
432         else if (g_key_file_has_key(kf, "global", "virtual", NULL)) 
433                 n->type = NETWORK_VIRTUAL;
434         else 
435                 n->type = NETWORK_TCP;
436
437         switch (n->type) {
438         case NETWORK_TCP:
439                 config_load_servers(n);
440                 break;
441         case NETWORK_PROGRAM:
442                 n->type_settings.program_location = g_key_file_get_string(kf, "global", "program", NULL);
443                 break;
444         case NETWORK_VIRTUAL:
445                 n->type_settings.virtual_type = g_key_file_get_string(kf, "global", "virtual", NULL);
446                 break;
447         case NETWORK_IOCHANNEL:
448                 /* Don't store */
449                 break;
450         }
451
452         groups = g_key_file_get_groups(kf, &size);
453         for (i = 0; i < size; i++) {
454                 if (!g_ascii_isalpha(groups[i][0]))
455                         config_load_channel(n, kf, groups[i]);
456         }
457
458         g_strfreev(groups);
459
460         return n;
461 }
462
463 static struct network_config *find_create_network_config(struct ctrlproxy_config *cfg, const char *name)
464 {
465         GList *gl;
466         struct network_config *nc;
467         struct tcp_server_config *tc;
468
469         for (gl = cfg->networks; gl; gl = gl->next) {
470                 GList *gl1;
471                 nc = gl->data;
472
473                 if (g_strcasecmp(nc->name, name) == 0)
474                         return nc;
475
476                 if (nc->type != NETWORK_TCP) 
477                         continue;
478
479                 for (gl1 = nc->type_settings.tcp_servers; gl1; gl1 = gl1->next) {
480                         char *tmp;
481                         struct tcp_server_config *sc = gl1->data;
482
483                         if (g_strcasecmp(sc->host, name) == 0)
484                                 return nc;
485
486                         if (g_strncasecmp(sc->host, name, strlen(sc->host)) != 0)
487                                 continue;
488
489                         tmp = g_strdup_printf("%s:%s", sc->host, sc->port);
490
491                         if (g_strcasecmp(tmp, name) == 0)
492                                 return nc;
493
494                         g_free(tmp);
495                 }
496         }
497
498         nc = network_config_init(cfg);
499         nc->name = g_strdup(name);
500         nc->autoconnect = FALSE;
501         nc->reconnect_interval = DEFAULT_RECONNECT_INTERVAL;
502         nc->type = NETWORK_TCP;
503         tc = g_new0(struct tcp_server_config, 1);
504         tc->host = g_strdup(name);
505         if (strchr(tc->host, ':')) {
506                 tc->port = tc->host+1;
507                 *tc->port = '\0';
508         } else {
509                 tc->port = g_strdup(DEFAULT_IRC_PORT);
510         }
511
512         nc->type_settings.tcp_servers = g_list_append(nc->type_settings.tcp_servers, tc);
513
514         cfg->networks = g_list_append(cfg->networks, nc);
515
516         return nc;
517 }
518
519 static void config_load_listeners_socks(struct ctrlproxy_config *cfg)
520 {
521         char **allows;
522         gsize size, i;
523         GKeyFile *kf = cfg->keyfile;
524         struct listener_config *l;
525
526         allows = g_key_file_get_string_list(kf, "socks", "allow", &size, NULL);
527
528         if (allows == NULL)
529                 return;
530
531         g_key_file_remove_key(kf, "socks", "allow", NULL);
532
533         l = g_new0(struct listener_config, 1);
534
535         if (g_key_file_has_key(kf, "socks", "port", NULL)) 
536                 l->port = g_key_file_get_string(kf, "socks", "port", NULL);
537         else 
538                 l->port = g_strdup_printf("%d", DEFAULT_SOCKS_PORT);
539
540         /* We can use the socks listener as default listener, if there was 
541          * no default listener specified */
542         if (cfg->listeners == NULL ||
543                 !((struct listener_config *)cfg->listeners->data)->is_default)
544                 l->is_default = TRUE;
545
546         g_key_file_remove_key(kf, "socks", "port", NULL);
547
548         for (i = 0; i < size; i++) {
549                 struct allow_rule *r = g_new0(struct allow_rule, 1);
550                 char **parts = g_strsplit(allows[i], ":", 2);
551                                         
552                 r->username = parts[0];
553                 r->password = parts[1];
554
555                 g_free(parts);
556                 l->allow_rules = g_list_append(l->allow_rules, r);
557         }
558
559         g_strfreev(allows);
560
561         cfg->listeners = g_list_append(cfg->listeners, l);
562 }
563
564 static void config_load_listeners(struct ctrlproxy_config *cfg)
565 {
566         char *filename = g_build_filename(cfg->config_dir, "listener", NULL);
567         int i;
568         char **groups;
569         gsize size;
570         GKeyFile *kf;
571         char *default_password;
572         GError *error = NULL;
573
574         default_password = g_key_file_get_string(cfg->keyfile, "listener", "password", NULL);
575         if (g_key_file_has_key(cfg->keyfile, "listener", "auto", NULL))
576                 cfg->auto_listener = g_key_file_get_boolean(cfg->keyfile, "listener", "auto", NULL);
577
578         if (g_key_file_has_key(cfg->keyfile, "listener", "autoport", NULL))
579                 cfg->listener_autoport = g_key_file_get_integer(cfg->keyfile, "listener", "autoport", NULL);
580
581         if (g_key_file_has_key(cfg->keyfile, "global", "port", NULL)) {
582                 struct listener_config *l = g_new0(struct listener_config, 1);
583                 l->port = g_key_file_get_string(cfg->keyfile, "global", "port", NULL);
584                 l->password = g_key_file_get_string(cfg->keyfile, "global", "password", NULL);
585                 l->address = g_key_file_get_string(cfg->keyfile, "global", "bind", NULL);
586                 l->ssl = g_key_file_has_key(cfg->keyfile, "global", "ssl", NULL) &&
587                                  g_key_file_get_boolean(cfg->keyfile, "global", "ssl", NULL);
588                 l->is_default = TRUE;
589
590                 l->network = g_key_file_get_string(cfg->keyfile, "global", "default-network", NULL);
591
592                 cfg->listeners = g_list_append(cfg->listeners, l);
593         }
594
595         kf = g_key_file_new();
596
597         if (!g_key_file_load_from_file(kf, filename, G_KEY_FILE_KEEP_COMMENTS, &error)) {
598                 if (error->code != G_FILE_ERROR_NOENT)
599                         log_global(LOG_ERROR, "Can't parse configuration file '%s': %s", filename, error->message);
600                 g_free(filename);
601                 return;
602         }
603                 
604         groups = g_key_file_get_groups(kf, &size);
605
606         for (i = 0; i < size; i++)
607         {
608                 struct listener_config *l;
609                 char *tmp;
610                 
611                 l = g_new0(struct listener_config, 1);
612
613                 tmp = g_strdup(groups[i]);
614                 l->port = strrchr(tmp, ':');
615                 if (l->port != NULL) {
616                         l->address = tmp;
617                         *l->port = '\0';
618                         l->port++;
619                 } else {
620                         l->port = tmp;
621                         l->address = NULL;
622                 }
623
624                 l->password = g_key_file_get_string(kf, groups[i], "password", NULL);
625                 if (l->password == NULL)
626                         l->password = default_password;
627
628                 if (g_key_file_has_key(kf, groups[i], "ssl", NULL))
629                         l->ssl = g_key_file_get_boolean(kf, groups[i], "ssl", NULL);
630
631 #ifdef HAVE_GNUTLS
632                 if (l->ssl)
633                         l->ssl_credentials = ssl_create_server_credentials(cfg, kf, groups[i]);
634 #endif
635
636                 if (g_key_file_has_key(kf, groups[i], "network", NULL))
637                         l->network = g_key_file_get_string(kf, groups[i], "network", NULL);
638
639                 cfg->listeners = g_list_append(cfg->listeners, l);
640         }
641
642         g_strfreev(groups);
643         g_free(filename);
644 }
645
646 static void config_load_networks(struct ctrlproxy_config *cfg)
647 {
648         char *networksdir = g_build_filename(cfg->config_dir, "networks", NULL);
649         GDir *dir;
650         const char *name;
651
652         dir = g_dir_open(networksdir, 0, NULL);
653         if (!dir)
654                 return;
655
656         while ((name = g_dir_read_name(dir))) {
657                 if (name[0] == '.' || name[strlen(name)-1] == '~')
658                         continue;
659                 config_load_network(cfg, networksdir, name);
660         }
661
662         g_free(networksdir);
663
664         g_dir_close(dir);
665 }
666
667 #define FETCH_SETTING(data, kf, section, prefix, name) (data)->name = g_key_file_get_string((kf), (section), prefix __STRING(name), NULL)
668
669 static void config_load_log(struct ctrlproxy_config *config)
670 {
671         GKeyFile *kf = config->keyfile;
672         struct log_file_config *data;
673         char *logbasedir;
674         char *logging = NULL;
675
676         if (g_key_file_get_string(kf, "global", "logging", NULL) != NULL) {
677                 logging = g_key_file_get_string(kf, "global", "logging", NULL);
678         }
679
680         if (g_key_file_has_group(kf, "log-custom")) {
681                 data = g_new0(struct log_file_config, 1);
682
683                 FETCH_SETTING(data, kf, "log-custom", "", nickchange);
684                 FETCH_SETTING(data, kf, "log-custom", "", logfilename);
685                 FETCH_SETTING(data, kf, "log-custom", "", topic);
686                 FETCH_SETTING(data, kf, "log-custom", "", notopic);
687                 FETCH_SETTING(data, kf, "log-custom", "", part);
688                 FETCH_SETTING(data, kf, "log-custom", "", join);
689                 FETCH_SETTING(data, kf, "log-custom", "", msg);
690                 FETCH_SETTING(data, kf, "log-custom", "", notice);
691                 FETCH_SETTING(data, kf, "log-custom", "", action);
692                 FETCH_SETTING(data, kf, "log-custom", "", kick);
693                 FETCH_SETTING(data, kf, "log-custom", "", quit);
694                 FETCH_SETTING(data, kf, "log-custom", "", mode);
695
696                 log_custom_load(data);
697         }
698
699         if (logging != NULL && !strcmp(logging, "custom")) {
700                 data = g_new0(struct log_file_config, 1);
701
702                 FETCH_SETTING(data, kf, "global", "", logfilename);
703                 FETCH_SETTING(data, kf, "global", "log-format-", nickchange);
704                 FETCH_SETTING(data, kf, "global", "log-format-", topic);
705                 FETCH_SETTING(data, kf, "global", "log-format-", notopic);
706                 FETCH_SETTING(data, kf, "global", "log-format-", part);
707                 FETCH_SETTING(data, kf, "global", "log-format-", join);
708                 FETCH_SETTING(data, kf, "global", "log-format-", msg);
709                 FETCH_SETTING(data, kf, "global", "log-format-", notice);
710                 FETCH_SETTING(data, kf, "global", "log-format-", action);
711                 FETCH_SETTING(data, kf, "global", "log-format-", kick);
712                 FETCH_SETTING(data, kf, "global", "log-format-", quit);
713                 FETCH_SETTING(data, kf, "global", "log-format-", mode);
714
715                 log_custom_load(data);
716         }
717
718         if (g_key_file_has_group(kf, "log-irssi") || 
719                 (logging != NULL && !strcmp(logging, "irssi"))) {
720                 data = g_new0(struct log_file_config, 1);
721
722                 data->join = "%h:%M -!- %n [%u] has joined %c";
723                 data->part = "%h:%M -!- %n [%u] has left %c [%m]";
724                 data->msg = "%h:%M < %n> %m";
725                 data->notice = "%h:%M < %n> %m";
726                 data->action = "%h:%M  * %n %m";
727                 data->mode = "%h:%M -!- mode/%t [%p %c] by %n";
728                 data->quit = "%h:%M -!- %n [%u] has quit [%m]";
729                 data->kick = "%h:%M -!- %t has been kicked by %n [%m]";
730                 data->topic = "%h:%M -!- %n has changed the topic to %t";
731                 data->notopic = "%h:%M -!- %n has removed the topic";
732                 data->nickchange = "%h:%M -!- %n is now known as %r";
733
734                 if (g_key_file_has_key(kf, "log-irssi", "logfile", NULL)) {
735                         logbasedir = g_key_file_get_string(kf, "log-irssi", "logfile", NULL);
736                 } else if (g_key_file_has_key(kf, "global", "logfile", NULL)) {
737                         logbasedir = g_key_file_get_string(kf, "global", "logfile", NULL);
738                 } else {
739                         logbasedir = g_build_filename(config->config_dir, 
740                                                                                   "log_irssi", NULL);
741                 }
742
743                 data->logfilename = g_strdup_printf("%s/%%N/%%@", logbasedir);
744
745                 g_free(logbasedir);
746
747                 log_custom_load(data);
748         }
749
750         if (logging != NULL && 
751                         strcmp(logging, "irssi") != 0 && 
752                         strcmp(logging, "custom") != 0 &&
753                         strcmp(logging, "none") != 0) {
754                 log_global(LOG_WARNING, "Unknown log type `%s'", logging);
755         }
756
757         g_free(logging);
758 }
759
760 static void config_load_auto_away(struct ctrlproxy_config *config)
761 {
762         struct auto_away_config *d;
763         GKeyFile *kf = config->keyfile;
764
765         if (g_key_file_has_group(kf, "auto-away")) {
766                 d = g_new0(struct auto_away_config, 1);
767                 
768                 d->message = g_key_file_get_string(kf, "auto-away", "message", NULL);
769                 d->nick = g_key_file_get_string(kf, "auto-away", "nick", NULL);
770                 if (g_key_file_has_key(kf, "auto-away", "client_limit", NULL)) {
771                         d->client_limit = g_key_file_get_integer(kf, "auto-away", "client_limit", NULL);
772                         if (g_key_file_has_key(kf, "auto-away", "only_noclient", NULL))
773                                 log_global(LOG_WARNING, "auto-away: not using only_noclient because client_limit is set");
774                 }
775                 else if (g_key_file_has_key(kf, "auto-away", "only_noclient", NULL)) {
776                         d->client_limit = g_key_file_get_boolean(kf, "auto-away", "only_noclient", NULL) ? 0 : -1;
777                         log_global(LOG_WARNING, "auto-away: only_noclient is deprecated, please use client_limit instead");
778                 }
779                 else
780                         d->client_limit = -1;
781                 if (g_key_file_has_key(kf, "auto-away", "time", NULL))
782                         d->max_idle_time = g_key_file_get_integer(kf, "auto-away", "time", NULL);
783                 else
784                         d->max_idle_time = AUTO_AWAY_DEFAULT_TIME;
785         } else if (g_key_file_has_key(kf, "global", "auto-away-enable", NULL) &&
786                            g_key_file_get_boolean(kf, "global", "auto-away-enable", NULL)) {
787                 d = g_new0(struct auto_away_config, 1);
788                 
789                 d->message = g_key_file_get_string(kf, "global", "auto-away-message", NULL);
790                 d->nick = g_key_file_get_string(kf, "global", "auto-away-nick", NULL);
791                 if (g_key_file_has_key(kf, "global", "auto-away-client-limit", NULL)) {
792                         d->client_limit = g_key_file_get_integer(kf, "global", "auto-away-client-limit", NULL);
793                 }
794                 else
795                         d->client_limit = -1;
796                 if (g_key_file_has_key(kf, "global", "auto-away-time", NULL))
797                         d->max_idle_time = g_key_file_get_integer(kf, "global", "auto-away-time", NULL);
798                 else
799                         d->max_idle_time = AUTO_AWAY_DEFAULT_TIME;
800         } else {
801                 return;
802         }
803
804         config->auto_away = d;
805 }
806
807 struct ctrlproxy_config *init_configuration(void)
808 {
809         struct ctrlproxy_config *cfg;
810         cfg = g_new0(struct ctrlproxy_config, 1);
811
812         return cfg;
813 }
814
815 struct ctrlproxy_config *load_configuration(const char *dir) 
816 {
817         GKeyFile *kf;
818         GError *error = NULL;
819         struct ctrlproxy_config *cfg;
820         char *file;
821         char **autoconnect_list;
822         GList *gl;
823         gsize size;
824         int i;
825
826         file = g_build_filename(dir, "config", NULL);
827
828         cfg = init_configuration();
829         cfg->config_dir = g_strdup(dir);
830         cfg->network_socket = g_build_filename(cfg->config_dir, "socket", NULL);
831         cfg->admin_socket = g_build_filename(cfg->config_dir, "admin", NULL);
832
833         kf = cfg->keyfile = g_key_file_new();
834
835         if (!g_key_file_load_from_file(kf, file, G_KEY_FILE_KEEP_COMMENTS, &error)) {
836                 log_global(LOG_ERROR, "Can't parse configuration file '%s': %s", file, error->message);
837                 g_key_file_free(kf);
838                 g_free(file);
839                 g_free(cfg);
840                 return NULL;
841         }
842
843         cfg->autosave = TRUE;
844         if (g_key_file_has_key(kf, "global", "autosave", NULL) &&
845                 !g_key_file_get_boolean(kf, "global", "autosave", NULL))
846                 cfg->autosave = FALSE;
847
848         if (g_key_file_has_key(kf, "global", "max_who_age", NULL))
849                 cfg->max_who_age = g_key_file_get_integer(kf, "global", "max_who_age", NULL);
850
851         cfg->replication = g_key_file_get_string(kf, "global", "replication", NULL);
852         cfg->linestack_backend = g_key_file_get_string(kf, "global", "linestack", NULL);
853
854         if (g_key_file_has_key(kf, "global", "report-time", NULL))
855                 cfg->report_time = g_key_file_get_boolean(kf, "global", "report-time", NULL);
856
857     if (g_key_file_has_key(kf, "global", "motd-file", NULL))
858                 cfg->motd_file = g_key_file_get_string(kf, "global", "motd-file", NULL);
859     else 
860             cfg->motd_file = g_build_filename(SHAREDIR, "motd", NULL);
861
862     if (g_key_file_has_key(kf, "client", "charset", NULL))
863                 cfg->client_charset = g_key_file_get_string(kf, "client", "charset", NULL);
864         else if (g_key_file_has_key(kf, "global", "client-charset", NULL))
865                 cfg->client_charset = g_key_file_get_string(kf, "global", "client-charset", NULL);
866     else 
867             cfg->client_charset = NULL;
868
869     if (g_key_file_has_key(kf, "global", "learn-nickserv", NULL))
870                 cfg->learn_nickserv = g_key_file_get_boolean(kf, "global", "learn-nicksev", NULL);
871     else 
872             cfg->learn_nickserv = TRUE;
873
874     if (g_key_file_has_key(kf, "global", "learn-network-name", NULL))
875                 cfg->learn_network_name = g_key_file_get_boolean(kf, "global", "learn-network-name", NULL);
876     else 
877             cfg->learn_network_name = TRUE;
878
879         if (!g_file_test(cfg->motd_file, G_FILE_TEST_EXISTS))
880                 log_global(LOG_ERROR, "Can't open MOTD file '%s' for reading", cfg->motd_file);
881
882     if (g_key_file_has_key(kf, "admin", "without_privmsg", NULL)) {
883                 if (g_key_file_get_boolean(kf, "admin", "without_privmsg", NULL)) {
884                         cfg->admin_user = NULL;
885                 } else {
886                         cfg->admin_user = g_strdup("ctrlproxy");
887                 }
888                 g_key_file_remove_key(kf, "admin", "without_privmsg", NULL);
889         }
890
891         if (g_key_file_has_key(kf, "global", "admin-user", NULL)) {
892                 cfg->admin_user = g_key_file_get_string(kf, "global", "admin-user", NULL);
893         }
894
895         cfg->admin_log = TRUE;
896     if (g_key_file_has_key(kf, "admin", "log", NULL) && !g_key_file_get_boolean(kf, "admin", "log", NULL))
897         cfg->admin_log = FALSE;
898         g_key_file_remove_key(kf, "admin", "log", NULL);
899     if (g_key_file_has_key(kf, "global", "admin-log", NULL) && !g_key_file_get_boolean(kf, "global", "admin-log", NULL))
900         cfg->admin_log = FALSE;
901
902         for (gl = cfg->networks; gl; gl = gl->next) {
903                 struct network_config *nc = gl->data;
904
905                 nc->autoconnect = FALSE;
906         }
907
908         config_load_listeners(cfg);
909         config_load_listeners_socks(cfg);
910         config_load_log(cfg);
911         config_load_auto_away(cfg);
912         config_load_networks(cfg);
913
914         /* FIXME: Check for unknown parameters */
915
916         size = 0;
917         autoconnect_list = g_key_file_get_string_list(kf, "global", "autoconnect", &size, NULL);
918                 
919         for (i = 0; i < size; i++) {
920                 struct network_config *nc = find_create_network_config(cfg, autoconnect_list[i]);
921
922                 g_assert(nc);
923                 nc->autoconnect = TRUE;
924         }
925
926         g_strfreev(autoconnect_list);
927         g_free(file);
928
929         return cfg;
930 }
931
932 struct network_config *network_config_init(struct ctrlproxy_config *cfg) 
933 {
934         struct network_config *s = g_new0(struct network_config, 1);
935
936         s->autoconnect = FALSE;
937         s->nick = g_strdup(g_get_user_name());
938         s->username = g_strdup(g_get_user_name());
939         g_assert(s->username != NULL && strlen(s->username) > 0);
940         s->fullname = g_strdup(g_get_real_name());
941         if (s->fullname == NULL || 
942                 strlen(s->fullname) == 0) {
943                 g_free(s->fullname);
944                 s->fullname = g_strdup(s->username);
945         }
946         s->reconnect_interval = DEFAULT_RECONNECT_INTERVAL;
947
948         if (cfg) 
949                 cfg->networks = g_list_append(cfg->networks, s);
950         return s;
951 }
952
953 void free_config(struct ctrlproxy_config *cfg)
954 {
955         while (cfg->networks) {
956                 struct network_config *nc = cfg->networks->data;
957                 g_free(nc->name);
958                 g_free(nc->nick);
959                 g_free(nc->fullname);
960                 g_free(nc->username);
961                 g_free(nc->password);
962                 while (nc->channels) {
963                         struct channel_config *cc = nc->channels->data;
964                         g_free(cc->name);
965                         g_free(cc->key);
966                         nc->channels = g_list_remove(nc->channels, cc); 
967                         g_free(cc);
968                 }
969                 switch (nc->type) {
970                 case NETWORK_TCP: 
971                         while (nc->type_settings.tcp_servers) {
972                                 struct tcp_server_config *tc = nc->type_settings.tcp_servers->data;
973                                 g_free(tc->host);
974                                 g_free(tc->port);
975                                 g_free(tc->bind_address);
976                                 g_free(tc->password);
977                                 nc->type_settings.tcp_servers = g_list_remove(nc->type_settings.tcp_servers, tc);
978                                 g_free(tc);
979                         }
980                         break;
981                 case NETWORK_VIRTUAL:
982                         g_free(nc->type_settings.virtual_type);
983                         break;
984                 case NETWORK_PROGRAM:
985                         g_free(nc->type_settings.program_location);
986                         break;
987                 case NETWORK_IOCHANNEL:
988                         /* Nothing to free */
989                         break;
990                 }
991                 cfg->networks = g_list_remove(cfg->networks, nc);
992                 if (nc->keyfile) g_key_file_free(nc->keyfile);
993                 g_free(nc);
994         }
995         g_free(cfg->config_dir);
996         g_free(cfg->network_socket);
997         g_free(cfg->admin_socket);
998         g_free(cfg->replication);
999         g_free(cfg->linestack_backend);
1000         g_free(cfg->motd_file);
1001         g_free(cfg->admin_user);
1002         g_key_file_free(cfg->keyfile);
1003         g_free(cfg);
1004 }
1005
1006 gboolean create_configuration(const char *config_dir)
1007 {
1008         struct global *global;
1009         char port[250];
1010         struct listener_config *l;
1011         char *pass;
1012
1013         if (g_file_test(config_dir, G_FILE_TEST_IS_DIR)) {
1014                 fprintf(stderr, "%s already exists\n", config_dir);
1015                 return FALSE;
1016         }
1017
1018         if (g_mkdir(config_dir, 0700) != 0) {
1019                 fprintf(stderr, "Can't create config directory '%s': %s\n", config_dir, strerror(errno));
1020                 return FALSE;
1021         }
1022
1023         global = load_global(DEFAULT_CONFIG_DIR);       
1024         if (global == NULL) { 
1025                 fprintf(stderr, "Unable to load default configuration '%s'\n", DEFAULT_CONFIG_DIR);     
1026                 return FALSE;
1027         }
1028         global->config->config_dir = g_strdup(config_dir);
1029         save_configuration(global->config, config_dir);
1030
1031         snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
1032         printf("Please specify port the administration interface should listen on.\n"
1033                    "Prepend with a colon to listen on a specific address.\n"
1034                    "Example: localhost:6668\n\nPort [%s]: ", port); fflush(stdout);
1035         if (!fgets(port, sizeof(port), stdin))
1036                 snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
1037
1038         if (port[strlen(port)-1] == '\n')
1039                 port[strlen(port)-1] = '\0';
1040
1041         if (strlen(port) == 0) 
1042                 snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
1043
1044         l = g_new0(struct listener_config, 1);
1045         pass = getpass("Please specify a password for the administration interface: "); 
1046         l->port = port;
1047         if (!strcmp(pass, "")) {
1048                 fprintf(stderr, "Warning: no password specified. Authentication disabled!\n");
1049         } else {
1050                 l->password = pass;
1051         }
1052
1053         global->config->listeners = g_list_append(global->config->listeners, l);
1054
1055         return TRUE;
1056 }