Merge fixes from trunk.
[jelmer/ctrlproxy.git] / src / settings.c
1 /*
2         ctrlproxy: A modular IRC proxy
3         (c) 2002-2006 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 2 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
23 #ifdef HAVE_SYS_SOCKET_H
24 #include <sys/socket.h>
25 #endif
26
27 #include <netdb.h>
28 #include <sys/socket.h>
29 #include <glib/gstdio.h>
30
31 #define DEFAULT_ADMIN_PORT 6680
32
33 gboolean g_key_file_save_to_file(GKeyFile *kf, const gchar *file, GError **error)
34 {
35         gsize length, nr;
36         char *data = g_key_file_to_data(kf, &length, error);
37         GIOChannel *gio;
38
39         if (!data)
40                 return FALSE;
41
42         gio = g_io_channel_new_file(file, "w+", error);
43         if (!gio) {
44                 g_free(data);
45                 return FALSE;
46         }
47         
48         g_io_channel_write_chars(gio, data, length, &nr, error);
49
50         g_free(data);
51
52         g_io_channel_unref(gio);
53
54         return TRUE;
55 }
56
57 static void config_save_tcp_servers(struct network_config *n, GKeyFile *kf)
58 {
59         GList *gl;
60         int i = 0; 
61         gchar **values = g_new0(gchar *, g_list_length(n->type_settings.tcp_servers)+1);
62         
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);
66
67                 values[i] = name;
68
69                 g_key_file_set_boolean(kf, name, "ssl", ts->ssl);
70                 if (ts->password)
71                         g_key_file_set_string(kf, name, "password", ts->password);
72                 else
73                         g_key_file_remove_key(kf, name, "password", NULL);
74
75                 if (ts->bind_address) {
76                         char *tmp;
77                         if (ts->bind_port) 
78                                 tmp = g_strdup_printf("%s:%s", 
79                                                       ts->bind_address,
80                                                       ts->bind_port);
81                         else
82                                 tmp = g_strdup(ts->bind_address);
83
84                         g_key_file_set_string(kf, name, "bind", tmp);
85
86                         g_free(tmp);
87                 } else 
88                         g_key_file_remove_key(kf, name, "bind", NULL);
89
90                 i++;
91         }
92
93         g_key_file_set_string_list(kf, "global", "servers", (const gchar **)values, i);
94
95         g_strfreev(values);
96 }
97
98 static void config_save_network(const char *dir, struct network_config *n)
99 {
100         GList *gl;
101         GKeyFile *kf;
102         char *fn;
103         
104         if (!n->keyfile) {
105                 n->keyfile = g_key_file_new();
106         }
107
108         kf = n->keyfile;
109
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);
113         if (n->queue_speed)
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);
116
117         switch(n->type) {
118         case NETWORK_VIRTUAL:
119                 g_key_file_set_string(kf, "global", "virtual", n->type_settings.virtual_type);
120                 break;
121         case NETWORK_PROGRAM:
122                 g_key_file_set_string(kf, "global", "program", n->type_settings.program_location);
123                 break;
124         case NETWORK_TCP:
125                 config_save_tcp_servers(n, kf);
126                 break;
127         default:break;
128         }
129         
130         for (gl = n->channels; gl; gl = gl->next) {
131                 struct channel_config *c = gl->data;
132
133                 if (c->key)
134                         g_key_file_set_string(kf, c->name, "key", c->key);
135                 else 
136                         g_key_file_remove_key(kf, c->name, "key", NULL);
137                 
138                 g_key_file_set_boolean(kf, c->name, "autojoin", c->autojoin);
139         }
140
141         fn = g_build_filename(dir, n->name, NULL);
142         g_key_file_save_to_file(kf, fn, NULL);
143         g_free(fn);
144 }
145
146 static void config_save_networks(const char *config_dir, GList *networks)
147 {
148         char *networksdir = g_build_filename(config_dir, "networks", NULL);
149         GList *gl;
150
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));
154                         return;
155                 }
156         }
157
158         for (gl = networks; gl; gl = gl->next) {
159                 struct network_config *n = gl->data;            
160                 config_save_network(networksdir, n);
161         }
162
163         g_free(networksdir);
164 }
165
166 void save_configuration(struct ctrlproxy_config *cfg, const char *configuration_dir)
167 {
168         char *fn, **list;
169         int i;
170         GList *gl;
171
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);
175                         return;
176                 }
177         }
178
179         if (!cfg->keyfile)
180                 cfg->keyfile = g_key_file_new();
181
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);
186
187         g_key_file_set_string(cfg->keyfile, "client", "charset", cfg->client_charset);
188         if (cfg->replication)
189                 g_key_file_set_string(cfg->keyfile, "global", "replication", cfg->replication);
190         if (cfg->linestack_backend) 
191                 g_key_file_set_string(cfg->keyfile, "global", "linestack", cfg->linestack_backend);
192         g_key_file_set_string(cfg->keyfile, "global", "motd-file", cfg->motd_file);
193
194         g_key_file_set_boolean(cfg->keyfile, "global", "report-time", cfg->report_time);
195
196         config_save_networks(configuration_dir, cfg->networks);
197
198         i = 0;
199         list = g_new0(char *, g_list_length(cfg->networks)+1);
200         for (gl = cfg->networks; gl; gl = gl->next) {
201                 struct network_config *nc = gl->data;
202
203                 if (nc->autoconnect) {
204                         list[i] = nc->name;
205                         i++;
206                 }
207         }
208         
209         if (i > 0) 
210                 g_key_file_set_string_list(cfg->keyfile, "global", "autoconnect", (const gchar **)list, i);
211
212         g_free(list);
213
214         fn = g_build_filename(configuration_dir, "config", NULL);
215         g_key_file_save_to_file(cfg->keyfile, fn, NULL);
216         g_free(fn);
217 }
218
219 static void config_load_channel(struct network_config *n, GKeyFile *kf, const char *name)
220 {
221         struct channel_config *ch = g_new0(struct channel_config, 1);
222
223         ch->name = g_strdup(name);
224         if (g_key_file_has_key(kf, name, "key", NULL)) 
225                 ch->key = g_key_file_get_string(kf, name, "key", NULL);
226
227         if (g_key_file_has_key(kf, name, "autojoin", NULL))
228                 ch->autojoin = g_key_file_get_boolean(kf, name, "autojoin", NULL);
229         
230         n->channels = g_list_append(n->channels, ch);
231 }
232
233 static void config_load_servers(struct network_config *n)
234 {
235         gsize size;
236         char **servers;
237         int i;
238         
239         servers = g_key_file_get_string_list(n->keyfile, "global", "servers", &size, NULL);
240
241         if (!servers)
242                 return;
243
244         for (i = 0; i < size; i++) {
245                 char *tmp;
246                 struct tcp_server_config *s = g_new0(struct tcp_server_config, 1);
247                 
248                 s->password = g_key_file_get_string(n->keyfile, servers[i], "password", NULL);
249                 if (g_key_file_has_key(n->keyfile, servers[i], "ssl", NULL))
250                         s->ssl = g_key_file_get_boolean(n->keyfile, servers[i], "ssl", NULL);
251
252                 tmp = strrchr(servers[i], ':');
253
254                 if (tmp) {
255                         *tmp = '\0';
256                         tmp++;
257                 }
258                 
259                 s->host = servers[i];
260                 s->port = g_strdup(tmp?tmp:"6667");
261                 s->bind_address = g_key_file_get_string(n->keyfile, servers[i], "bind", NULL);
262                 if (s->bind_address && (tmp = strchr(s->bind_address, ':'))) {
263                         *tmp = '\0';
264                         s->bind_port = tmp+1;
265                 }
266
267                 n->type_settings.tcp_servers = g_list_append(n->type_settings.tcp_servers, s);
268         }
269
270         g_free(servers);
271 }
272
273 static struct network_config *config_load_network(struct ctrlproxy_config *cfg, const char *dirname, const char *name)
274 {
275         GKeyFile *kf;
276         struct network_config *n;
277         char *filename;
278         int i;
279         char **groups;
280         GError *error = NULL;
281         gsize size;
282
283         kf = g_key_file_new();
284
285         filename = g_build_filename(dirname, name, NULL);
286
287         if (!g_key_file_load_from_file(kf, filename, G_KEY_FILE_KEEP_COMMENTS, &error)) {       
288                 log_global(LOG_ERROR, "Can't parse configuration file '%s': %s", filename, error->message);
289                 g_key_file_free(kf);
290                 return NULL;
291         }       
292
293         n = network_config_init(cfg);
294         n->keyfile = kf;
295
296         g_free(filename);
297
298         if (g_key_file_has_key(kf, "global", "fullname", NULL)) {
299                 g_free(n->fullname);
300                 n->fullname = g_key_file_get_string(kf, "global", "fullname", NULL);
301         }
302
303         if (g_key_file_has_key(kf, "global", "nick", NULL)) {
304                 g_free(n->nick);
305                 n->nick = g_key_file_get_string(kf, "global", "nick", NULL);
306         }
307
308         if (g_key_file_has_key(kf, "global", "reconnect-interval", NULL)) {
309                 n->reconnect_interval = g_key_file_get_integer(kf, "global", "reconnect-interval", NULL);
310         }
311
312         if (g_key_file_has_key(kf, "global", "queue-speed", NULL)) {
313                 n->queue_speed = g_key_file_get_integer(kf, "global", "queue-speed", NULL);
314         }
315
316         if (g_key_file_has_key(kf, "global", "username", NULL)) {
317                 g_free(n->username);
318                 n->username = g_key_file_get_string(kf, "global", "username", NULL);
319         }
320
321         if (g_key_file_has_key(kf, "global", "ignore_first_nick", NULL)) {
322                 n->ignore_first_nick = g_key_file_get_boolean(kf, "global", "ignore_first_nick", NULL);
323         }
324
325         if (g_key_file_has_key(kf, "global", "password", NULL)) {
326                 g_free(n->password);
327                 n->password = g_key_file_get_string(kf, "global", "password", NULL);
328         }
329
330         n->name = g_strdup(name);
331
332         if (g_key_file_has_key(kf, "global", "program", NULL)) 
333                 n->type = NETWORK_PROGRAM;
334         else if (g_key_file_has_key(kf, "global", "virtual", NULL)) 
335                 n->type = NETWORK_VIRTUAL;
336         else 
337                 n->type = NETWORK_TCP;
338
339         switch (n->type) {
340         case NETWORK_TCP:
341                 config_load_servers(n);
342                 break;
343         case NETWORK_PROGRAM:
344                 n->type_settings.program_location = g_key_file_get_string(kf, "global", "program", NULL);
345                 break;
346         case NETWORK_VIRTUAL:
347                 n->type_settings.virtual_type = g_key_file_get_string(kf, "global", "virtual", NULL);
348                 break;
349         case NETWORK_IOCHANNEL:
350                 /* Don't store */
351                 break;
352         }
353
354         groups = g_key_file_get_groups(kf, &size);
355         for (i = 0; i < size; i++) {
356                 if (!g_ascii_isalpha(groups[i][0]))
357                         config_load_channel(n, kf, groups[i]);
358         }
359
360         g_strfreev(groups);
361
362         return n;
363 }
364
365 static struct network_config *find_create_network_config(struct ctrlproxy_config *cfg, const char *name)
366 {
367         GList *gl;
368         struct network_config *nc;
369         struct tcp_server_config *tc;
370
371         for (gl = cfg->networks; gl; gl = gl->next) {
372                 GList *gl1;
373                 nc = gl->data;
374
375                 if (g_strcasecmp(nc->name, name) == 0)
376                         return nc;
377
378                 if (nc->type != NETWORK_TCP) 
379                         continue;
380
381                 for (gl1 = nc->type_settings.tcp_servers; gl1; gl1 = gl1->next) {
382                         char *tmp;
383                         struct tcp_server_config *sc = gl1->data;
384
385                         if (g_strcasecmp(sc->host, name) == 0)
386                                 return nc;
387
388                         if (g_strncasecmp(sc->host, name, strlen(sc->host)) != 0)
389                                 continue;
390
391                         tmp = g_strdup_printf("%s:%s", sc->host, sc->port);
392
393                         if (g_strcasecmp(tmp, name) == 0)
394                                 return nc;
395
396                         g_free(tmp);
397                 }
398         }
399
400         nc = network_config_init(cfg);
401         nc->name = g_strdup(name);
402         nc->autoconnect = FALSE;
403         nc->reconnect_interval = DEFAULT_RECONNECT_INTERVAL;
404         nc->type = NETWORK_TCP;
405         tc = g_new0(struct tcp_server_config, 1);
406         tc->host = g_strdup(name);
407         if (strchr(tc->host, ':')) {
408                 tc->port = tc->host+1;
409                 *tc->port = '\0';
410         } else {
411                 tc->port = g_strdup("6667");
412         }
413
414         nc->type_settings.tcp_servers = g_list_append(nc->type_settings.tcp_servers, tc);
415
416         cfg->networks = g_list_append(cfg->networks, nc);
417
418         return nc;
419 }
420
421 static void config_load_networks(struct ctrlproxy_config *cfg)
422 {
423         char *networksdir = g_build_filename(cfg->config_dir, "networks", NULL);
424         GDir *dir;
425         const char *name;
426
427         dir = g_dir_open(networksdir, 0, NULL);
428         if (!dir)
429                 return;
430
431         while ((name = g_dir_read_name(dir))) {
432                 if (name[0] == '.' || name[strlen(name)-1] == '~')
433                         continue;
434                 config_load_network(cfg, networksdir, name);
435         }
436
437         g_free(networksdir);
438
439         g_dir_close(dir);
440 }
441
442 struct ctrlproxy_config *load_configuration(const char *dir) 
443 {
444         GKeyFile *kf;
445         GError *error = NULL;
446         struct ctrlproxy_config *cfg;
447         char *file;
448         char **autoconnect_list;
449         GList *gl;
450         gsize size;
451         int i;
452
453         file = g_build_filename(dir, "config", NULL);
454
455         cfg = g_new0(struct ctrlproxy_config, 1);
456         cfg->config_dir = g_strdup(dir);
457         cfg->network_socket = g_build_filename(cfg->config_dir, "socket", NULL);
458         cfg->admin_socket = g_build_filename(cfg->config_dir, "admin", NULL);
459
460         kf = cfg->keyfile = g_key_file_new();
461
462         if (!g_key_file_load_from_file(kf, file, G_KEY_FILE_KEEP_COMMENTS, &error)) {
463                 log_global(LOG_ERROR, "Can't parse configuration file '%s': %s", file, error->message);
464                 g_key_file_free(kf);
465                 g_free(file);
466                 g_free(cfg);
467                 return NULL;
468         }
469
470         cfg->autosave = TRUE;
471         if (g_key_file_has_key(kf, "global", "autosave", NULL) &&
472                 !g_key_file_get_boolean(kf, "global", "autosave", NULL))
473                 cfg->autosave = FALSE;
474
475         if (g_key_file_has_key(kf, "global", "max_who_age", NULL))
476                 cfg->max_who_age = g_key_file_get_integer(kf, "global", "max_who_age", NULL);
477
478         cfg->replication = g_key_file_get_string(kf, "global", "replication", NULL);
479         cfg->linestack_backend = g_key_file_get_string(kf, "global", "linestack", NULL);
480
481         if (g_key_file_has_key(kf, "global", "report-time", NULL))
482                 cfg->report_time = g_key_file_get_boolean(kf, "global", "report-time", NULL);
483
484     if (g_key_file_has_key(kf, "global", "motd-file", NULL))
485                 cfg->motd_file = g_key_file_get_string(kf, "global", "motd-file", NULL);
486     else 
487             cfg->motd_file = g_build_filename(SHAREDIR, "motd", NULL);
488
489     if (g_key_file_has_key(kf, "client", "charset", NULL))
490                 cfg->client_charset = g_key_file_get_string(kf, "client", "charset", NULL);
491     else 
492             cfg->client_charset = g_strdup(DEFAULT_CLIENT_CHARSET);
493
494         if(!g_file_test(cfg->motd_file, G_FILE_TEST_EXISTS))
495                 log_global(LOG_ERROR, "Can't open MOTD file '%s' for reading", cfg->motd_file);
496
497     if (g_key_file_has_key(kf, "admin", "without_privmsg", NULL))
498         cfg->admin_noprivmsg = g_key_file_get_boolean(kf, "admin", "without_privmsg", NULL);
499
500         cfg->admin_log = TRUE;
501     if (g_key_file_has_key(kf, "admin", "log", NULL) && !g_key_file_get_boolean(kf, "admin", "log", NULL))
502         cfg->admin_log = FALSE;
503
504
505         for (gl = cfg->networks; gl; gl = gl->next) {
506                 struct network_config *nc = gl->data;
507
508                 nc->autoconnect = FALSE;
509         }
510
511         config_load_networks(cfg);
512
513         size = 0;
514         autoconnect_list = g_key_file_get_string_list(kf, "global", "autoconnect", &size, NULL);
515                 
516         for (i = 0; i < size; i++) {
517                 struct network_config *nc = find_create_network_config(cfg, autoconnect_list[i]);
518
519                 g_assert(nc);
520                 nc->autoconnect = TRUE;
521         }
522
523         g_strfreev(autoconnect_list);
524         g_free(file);
525
526         return cfg;
527 }
528
529 struct network_config *network_config_init(struct ctrlproxy_config *cfg) 
530 {
531         struct network_config *s = g_new0(struct network_config, 1);
532
533         s->autoconnect = FALSE;
534         s->nick = g_strdup(g_get_user_name());
535         s->username = g_strdup(g_get_user_name());
536         s->fullname = g_strdup(g_get_real_name());
537         s->reconnect_interval = DEFAULT_RECONNECT_INTERVAL;
538
539         if (cfg) 
540                 cfg->networks = g_list_append(cfg->networks, s);
541         return s;
542 }
543
544 void free_config(struct ctrlproxy_config *cfg)
545 {
546         while (cfg->networks) {
547                 struct network_config *nc = cfg->networks->data;
548                 g_free(nc->name);
549                 g_free(nc->nick);
550                 g_free(nc->fullname);
551                 g_free(nc->username);
552                 g_free(nc->password);
553                 while (nc->channels) {
554                         struct channel_config *cc = nc->channels->data;
555                         g_free(cc->name);
556                         g_free(cc->key);
557                         nc->channels = g_list_remove(nc->channels, cc); 
558                         g_free(cc);
559                 }
560                 switch (nc->type) {
561                 case NETWORK_TCP: 
562                         while (nc->type_settings.tcp_servers) {
563                                 struct tcp_server_config *tc = nc->type_settings.tcp_servers->data;
564                                 g_free(tc->host);
565                                 g_free(tc->port);
566                                 g_free(tc->bind_address);
567                                 g_free(tc->password);
568                                 nc->type_settings.tcp_servers = g_list_remove(nc->type_settings.tcp_servers, tc);
569                                 g_free(tc);
570                         }
571                         break;
572                 case NETWORK_VIRTUAL:
573                         g_free(nc->type_settings.virtual_type);
574                         break;
575                 case NETWORK_PROGRAM:
576                         g_free(nc->type_settings.program_location);
577                         break;
578                 case NETWORK_IOCHANNEL:
579                         /* Nothing to free */
580                         break;
581                 }
582                 cfg->networks = g_list_remove(cfg->networks, nc);
583                 g_key_file_free(nc->keyfile);
584                 g_free(nc);
585         }
586         g_free(cfg->config_dir);
587         g_free(cfg->network_socket);
588         g_free(cfg->admin_socket);
589         g_free(cfg->replication);
590         g_free(cfg->linestack_backend);
591         g_free(cfg->motd_file);
592         g_key_file_free(cfg->keyfile);
593         g_free(cfg);
594 }
595
596 gboolean create_configuration(const char *config_dir)
597 {
598         GKeyFile *kf;
599         struct global *global;
600         char port[250];
601         char *pass, *listenerfile;
602         GError *error = NULL;
603
604         if (g_file_test(config_dir, G_FILE_TEST_IS_DIR)) {
605                 fprintf(stderr, "%s already exists\n", config_dir);
606                 return FALSE;
607         }
608
609         if (g_mkdir(config_dir, 0700) != 0) {
610                 fprintf(stderr, "Can't create config directory '%s': %s\n", config_dir, strerror(errno));
611                 return FALSE;
612         }
613
614         global = new_global(DEFAULT_CONFIG_DIR);        
615         global->config->config_dir = g_strdup(config_dir);
616         save_configuration(global->config, config_dir);
617
618         kf = g_key_file_new();
619
620         snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
621         printf("Please specify port the administration interface should listen on.\n"
622                    "Prepend with a colon to listen on a specific address.\n"
623                    "Example: localhost:6668\n\nPort [%s]: ", port); fflush(stdout);
624         if (fgets(port, sizeof(port), stdin))
625                 snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
626
627         if (port[strlen(port)-1] == '\n')
628                 port[strlen(port)-1] = '\0';
629
630         if (strlen(port) == 0) 
631                 snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
632
633         pass = getpass("Please specify a password for the administration interface: "); 
634         g_key_file_set_string(kf, port, "network", "admin");
635         if (!strcmp(pass, "")) {
636                 fprintf(stderr, "Warning: no password specified. Authentication disabled!\n");
637         } else {
638                 g_key_file_set_string(kf, port, "password", pass);
639         }
640
641         listenerfile = g_build_filename(config_dir, "listener", NULL);
642
643         if (!g_key_file_save_to_file(kf, listenerfile, &error)) {
644                 fprintf(stderr, "Error saving %s: %s\n", listenerfile, error->message);
645                 return FALSE;
646         }
647
648         return TRUE;
649 }