6e0a1911e7538030c39b689795e88892aeedb086
[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         if (cfg->client_charset != NULL)
188                 g_key_file_set_string(cfg->keyfile, "client", "charset", cfg->client_charset);
189         if (cfg->replication)
190                 g_key_file_set_string(cfg->keyfile, "global", "replication", cfg->replication);
191         if (cfg->linestack_backend) 
192                 g_key_file_set_string(cfg->keyfile, "global", "linestack", cfg->linestack_backend);
193         if (cfg->motd_file != NULL)
194                 g_key_file_set_string(cfg->keyfile, "global", "motd-file", cfg->motd_file);
195
196         g_key_file_set_boolean(cfg->keyfile, "global", "report-time", cfg->report_time);
197
198         config_save_networks(configuration_dir, cfg->networks);
199
200         i = 0;
201         list = g_new0(char *, g_list_length(cfg->networks)+1);
202         for (gl = cfg->networks; gl; gl = gl->next) {
203                 struct network_config *nc = gl->data;
204
205                 if (nc->autoconnect) {
206                         list[i] = nc->name;
207                         i++;
208                 }
209         }
210         
211         if (i > 0) 
212                 g_key_file_set_string_list(cfg->keyfile, "global", "autoconnect", (const gchar **)list, i);
213
214         g_free(list);
215
216         fn = g_build_filename(configuration_dir, "config", NULL);
217         g_key_file_save_to_file(cfg->keyfile, fn, NULL);
218         g_free(fn);
219 }
220
221 static void config_load_channel(struct network_config *n, GKeyFile *kf, const char *name)
222 {
223         struct channel_config *ch = g_new0(struct channel_config, 1);
224
225         ch->name = g_strdup(name);
226         if (g_key_file_has_key(kf, name, "key", NULL)) 
227                 ch->key = g_key_file_get_string(kf, name, "key", NULL);
228
229         if (g_key_file_has_key(kf, name, "autojoin", NULL))
230                 ch->autojoin = g_key_file_get_boolean(kf, name, "autojoin", NULL);
231         
232         n->channels = g_list_append(n->channels, ch);
233 }
234
235 static void config_load_servers(struct network_config *n)
236 {
237         gsize size;
238         char **servers;
239         int i;
240         
241         servers = g_key_file_get_string_list(n->keyfile, "global", "servers", &size, NULL);
242
243         if (!servers)
244                 return;
245
246         for (i = 0; i < size; i++) {
247                 char *tmp;
248                 struct tcp_server_config *s = g_new0(struct tcp_server_config, 1);
249                 
250                 s->password = g_key_file_get_string(n->keyfile, servers[i], "password", NULL);
251                 if (g_key_file_has_key(n->keyfile, servers[i], "ssl", NULL))
252                         s->ssl = g_key_file_get_boolean(n->keyfile, servers[i], "ssl", NULL);
253
254                 tmp = strrchr(servers[i], ':');
255
256                 if (tmp) {
257                         *tmp = '\0';
258                         tmp++;
259                 }
260                 
261                 s->host = servers[i];
262                 s->port = g_strdup(tmp?tmp:"6667");
263                 s->bind_address = g_key_file_get_string(n->keyfile, servers[i], "bind", NULL);
264                 if (s->bind_address && (tmp = strchr(s->bind_address, ':'))) {
265                         *tmp = '\0';
266                         s->bind_port = tmp+1;
267                 }
268
269                 n->type_settings.tcp_servers = g_list_append(n->type_settings.tcp_servers, s);
270         }
271
272         g_free(servers);
273 }
274
275 static struct network_config *config_load_network(struct ctrlproxy_config *cfg, const char *dirname, const char *name)
276 {
277         GKeyFile *kf;
278         struct network_config *n;
279         char *filename;
280         int i;
281         char **groups;
282         GError *error = NULL;
283         gsize size;
284
285         kf = g_key_file_new();
286
287         filename = g_build_filename(dirname, name, NULL);
288
289         if (!g_key_file_load_from_file(kf, filename, G_KEY_FILE_KEEP_COMMENTS, &error)) {       
290                 log_global(LOG_ERROR, "Can't parse configuration file '%s': %s", filename, error->message);
291                 g_key_file_free(kf);
292                 return NULL;
293         }       
294
295         n = network_config_init(cfg);
296         n->keyfile = kf;
297
298         g_free(filename);
299
300         if (g_key_file_has_key(kf, "global", "fullname", NULL)) {
301                 g_free(n->fullname);
302                 n->fullname = g_key_file_get_string(kf, "global", "fullname", NULL);
303                 if (!strcmp(n->fullname, "") || n->fullname[0] == ' ')
304                         log_network(LOG_WARNING, n, "Invalid fullname `%s' set", n->fullname);
305         }
306
307         if (g_key_file_has_key(kf, "global", "nick", NULL)) {
308                 g_free(n->nick);
309                 n->nick = g_key_file_get_string(kf, "global", "nick", NULL);
310                 if (!strcmp(n->nick, "") || n->nick[0] == ' ')
311                         log_network(LOG_WARNING, n, "Invalid nick name `%s' set", n->fullname);
312         }
313
314         if (g_key_file_has_key(kf, "global", "reconnect-interval", NULL)) {
315                 n->reconnect_interval = g_key_file_get_integer(kf, "global", "reconnect-interval", NULL);
316         }
317
318         if (g_key_file_has_key(kf, "global", "queue-speed", NULL)) {
319                 n->queue_speed = g_key_file_get_integer(kf, "global", "queue-speed", NULL);
320         }
321
322         if (g_key_file_has_key(kf, "global", "username", NULL)) {
323                 g_free(n->username);
324                 n->username = g_key_file_get_string(kf, "global", "username", NULL);
325                 if (!strcmp(n->username, "") || n->username[0] == ' ')
326                         log_network(LOG_WARNING, n, "Invalid username `%s' set", n->username);
327         }
328
329         if (g_key_file_has_key(kf, "global", "ignore_first_nick", NULL)) {
330                 n->ignore_first_nick = g_key_file_get_boolean(kf, "global", "ignore_first_nick", NULL);
331         }
332
333         if (g_key_file_has_key(kf, "global", "password", NULL)) {
334                 g_free(n->password);
335                 n->password = g_key_file_get_string(kf, "global", "password", NULL);
336         }
337
338         n->name = g_strdup(name);
339
340         if (g_key_file_has_key(kf, "global", "program", NULL)) 
341                 n->type = NETWORK_PROGRAM;
342         else if (g_key_file_has_key(kf, "global", "virtual", NULL)) 
343                 n->type = NETWORK_VIRTUAL;
344         else 
345                 n->type = NETWORK_TCP;
346
347         switch (n->type) {
348         case NETWORK_TCP:
349                 config_load_servers(n);
350                 break;
351         case NETWORK_PROGRAM:
352                 n->type_settings.program_location = g_key_file_get_string(kf, "global", "program", NULL);
353                 break;
354         case NETWORK_VIRTUAL:
355                 n->type_settings.virtual_type = g_key_file_get_string(kf, "global", "virtual", NULL);
356                 break;
357         case NETWORK_IOCHANNEL:
358                 /* Don't store */
359                 break;
360         }
361
362         groups = g_key_file_get_groups(kf, &size);
363         for (i = 0; i < size; i++) {
364                 if (!g_ascii_isalpha(groups[i][0]))
365                         config_load_channel(n, kf, groups[i]);
366         }
367
368         g_strfreev(groups);
369
370         return n;
371 }
372
373 static struct network_config *find_create_network_config(struct ctrlproxy_config *cfg, const char *name)
374 {
375         GList *gl;
376         struct network_config *nc;
377         struct tcp_server_config *tc;
378
379         for (gl = cfg->networks; gl; gl = gl->next) {
380                 GList *gl1;
381                 nc = gl->data;
382
383                 if (g_strcasecmp(nc->name, name) == 0)
384                         return nc;
385
386                 if (nc->type != NETWORK_TCP) 
387                         continue;
388
389                 for (gl1 = nc->type_settings.tcp_servers; gl1; gl1 = gl1->next) {
390                         char *tmp;
391                         struct tcp_server_config *sc = gl1->data;
392
393                         if (g_strcasecmp(sc->host, name) == 0)
394                                 return nc;
395
396                         if (g_strncasecmp(sc->host, name, strlen(sc->host)) != 0)
397                                 continue;
398
399                         tmp = g_strdup_printf("%s:%s", sc->host, sc->port);
400
401                         if (g_strcasecmp(tmp, name) == 0)
402                                 return nc;
403
404                         g_free(tmp);
405                 }
406         }
407
408         nc = network_config_init(cfg);
409         nc->name = g_strdup(name);
410         nc->autoconnect = FALSE;
411         nc->reconnect_interval = DEFAULT_RECONNECT_INTERVAL;
412         nc->type = NETWORK_TCP;
413         tc = g_new0(struct tcp_server_config, 1);
414         tc->host = g_strdup(name);
415         if (strchr(tc->host, ':')) {
416                 tc->port = tc->host+1;
417                 *tc->port = '\0';
418         } else {
419                 tc->port = g_strdup("6667");
420         }
421
422         nc->type_settings.tcp_servers = g_list_append(nc->type_settings.tcp_servers, tc);
423
424         cfg->networks = g_list_append(cfg->networks, nc);
425
426         return nc;
427 }
428
429 static void config_load_networks(struct ctrlproxy_config *cfg)
430 {
431         char *networksdir = g_build_filename(cfg->config_dir, "networks", NULL);
432         GDir *dir;
433         const char *name;
434
435         dir = g_dir_open(networksdir, 0, NULL);
436         if (!dir)
437                 return;
438
439         while ((name = g_dir_read_name(dir))) {
440                 if (name[0] == '.' || name[strlen(name)-1] == '~')
441                         continue;
442                 config_load_network(cfg, networksdir, name);
443         }
444
445         g_free(networksdir);
446
447         g_dir_close(dir);
448 }
449
450 struct ctrlproxy_config *init_configuration(void)
451 {
452         struct ctrlproxy_config *cfg;
453         cfg = g_new0(struct ctrlproxy_config, 1);
454
455         return cfg;
456 }
457
458 struct ctrlproxy_config *load_configuration(const char *dir) 
459 {
460         GKeyFile *kf;
461         GError *error = NULL;
462         struct ctrlproxy_config *cfg;
463         char *file;
464         char **autoconnect_list;
465         GList *gl;
466         gsize size;
467         int i;
468
469         file = g_build_filename(dir, "config", NULL);
470
471         cfg = init_configuration();
472         cfg->config_dir = g_strdup(dir);
473         cfg->network_socket = g_build_filename(cfg->config_dir, "socket", NULL);
474         cfg->admin_socket = g_build_filename(cfg->config_dir, "admin", NULL);
475
476         kf = cfg->keyfile = g_key_file_new();
477
478         if (!g_key_file_load_from_file(kf, file, G_KEY_FILE_KEEP_COMMENTS, &error)) {
479                 log_global(LOG_ERROR, "Can't parse configuration file '%s': %s", file, error->message);
480                 g_key_file_free(kf);
481                 g_free(file);
482                 g_free(cfg);
483                 return NULL;
484         }
485
486         cfg->autosave = TRUE;
487         if (g_key_file_has_key(kf, "global", "autosave", NULL) &&
488                 !g_key_file_get_boolean(kf, "global", "autosave", NULL))
489                 cfg->autosave = FALSE;
490
491         if (g_key_file_has_key(kf, "global", "max_who_age", NULL))
492                 cfg->max_who_age = g_key_file_get_integer(kf, "global", "max_who_age", NULL);
493
494         cfg->replication = g_key_file_get_string(kf, "global", "replication", NULL);
495         cfg->linestack_backend = g_key_file_get_string(kf, "global", "linestack", NULL);
496
497         if (g_key_file_has_key(kf, "global", "report-time", NULL))
498                 cfg->report_time = g_key_file_get_boolean(kf, "global", "report-time", NULL);
499
500     if (g_key_file_has_key(kf, "global", "motd-file", NULL))
501                 cfg->motd_file = g_key_file_get_string(kf, "global", "motd-file", NULL);
502     else 
503             cfg->motd_file = g_build_filename(SHAREDIR, "motd", NULL);
504
505     if (g_key_file_has_key(kf, "client", "charset", NULL))
506                 cfg->client_charset = g_key_file_get_string(kf, "client", "charset", NULL);
507     else 
508             cfg->client_charset = g_strdup(DEFAULT_CLIENT_CHARSET);
509
510         if(!g_file_test(cfg->motd_file, G_FILE_TEST_EXISTS))
511                 log_global(LOG_ERROR, "Can't open MOTD file '%s' for reading", cfg->motd_file);
512
513     if (g_key_file_has_key(kf, "admin", "without_privmsg", NULL))
514         cfg->admin_noprivmsg = g_key_file_get_boolean(kf, "admin", "without_privmsg", NULL);
515
516         cfg->admin_log = TRUE;
517     if (g_key_file_has_key(kf, "admin", "log", NULL) && !g_key_file_get_boolean(kf, "admin", "log", NULL))
518         cfg->admin_log = FALSE;
519
520
521         for (gl = cfg->networks; gl; gl = gl->next) {
522                 struct network_config *nc = gl->data;
523
524                 nc->autoconnect = FALSE;
525         }
526
527         config_load_networks(cfg);
528
529         size = 0;
530         autoconnect_list = g_key_file_get_string_list(kf, "global", "autoconnect", &size, NULL);
531                 
532         for (i = 0; i < size; i++) {
533                 struct network_config *nc = find_create_network_config(cfg, autoconnect_list[i]);
534
535                 g_assert(nc);
536                 nc->autoconnect = TRUE;
537         }
538
539         g_strfreev(autoconnect_list);
540         g_free(file);
541
542         return cfg;
543 }
544
545 struct network_config *network_config_init(struct ctrlproxy_config *cfg) 
546 {
547         struct network_config *s = g_new0(struct network_config, 1);
548
549         s->autoconnect = FALSE;
550         s->nick = g_strdup(g_get_user_name());
551         s->username = g_strdup(g_get_user_name());
552         s->fullname = g_strdup(g_get_real_name());
553         s->reconnect_interval = DEFAULT_RECONNECT_INTERVAL;
554
555         if (cfg) 
556                 cfg->networks = g_list_append(cfg->networks, s);
557         return s;
558 }
559
560 void free_config(struct ctrlproxy_config *cfg)
561 {
562         while (cfg->networks) {
563                 struct network_config *nc = cfg->networks->data;
564                 g_free(nc->name);
565                 g_free(nc->nick);
566                 g_free(nc->fullname);
567                 g_free(nc->username);
568                 g_free(nc->password);
569                 while (nc->channels) {
570                         struct channel_config *cc = nc->channels->data;
571                         g_free(cc->name);
572                         g_free(cc->key);
573                         nc->channels = g_list_remove(nc->channels, cc); 
574                         g_free(cc);
575                 }
576                 switch (nc->type) {
577                 case NETWORK_TCP: 
578                         while (nc->type_settings.tcp_servers) {
579                                 struct tcp_server_config *tc = nc->type_settings.tcp_servers->data;
580                                 g_free(tc->host);
581                                 g_free(tc->port);
582                                 g_free(tc->bind_address);
583                                 g_free(tc->password);
584                                 nc->type_settings.tcp_servers = g_list_remove(nc->type_settings.tcp_servers, tc);
585                                 g_free(tc);
586                         }
587                         break;
588                 case NETWORK_VIRTUAL:
589                         g_free(nc->type_settings.virtual_type);
590                         break;
591                 case NETWORK_PROGRAM:
592                         g_free(nc->type_settings.program_location);
593                         break;
594                 case NETWORK_IOCHANNEL:
595                         /* Nothing to free */
596                         break;
597                 }
598                 cfg->networks = g_list_remove(cfg->networks, nc);
599                 g_key_file_free(nc->keyfile);
600                 g_free(nc);
601         }
602         g_free(cfg->config_dir);
603         g_free(cfg->network_socket);
604         g_free(cfg->admin_socket);
605         g_free(cfg->replication);
606         g_free(cfg->linestack_backend);
607         g_free(cfg->motd_file);
608         g_key_file_free(cfg->keyfile);
609         g_free(cfg);
610 }
611
612 gboolean create_configuration(const char *config_dir)
613 {
614         GKeyFile *kf;
615         struct global *global;
616         char port[250];
617         char *pass, *listenerfile;
618         GError *error = NULL;
619
620         if (g_file_test(config_dir, G_FILE_TEST_IS_DIR)) {
621                 fprintf(stderr, "%s already exists\n", config_dir);
622                 return FALSE;
623         }
624
625         if (g_mkdir(config_dir, 0700) != 0) {
626                 fprintf(stderr, "Can't create config directory '%s': %s\n", config_dir, strerror(errno));
627                 return FALSE;
628         }
629
630         global = load_global(DEFAULT_CONFIG_DIR);       
631         global->config->config_dir = g_strdup(config_dir);
632         save_configuration(global->config, config_dir);
633
634         kf = g_key_file_new();
635
636         snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
637         printf("Please specify port the administration interface should listen on.\n"
638                    "Prepend with a colon to listen on a specific address.\n"
639                    "Example: localhost:6668\n\nPort [%s]: ", port); fflush(stdout);
640         if (!fgets(port, sizeof(port), stdin))
641                 snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
642
643         if (port[strlen(port)-1] == '\n')
644                 port[strlen(port)-1] = '\0';
645
646         if (strlen(port) == 0) 
647                 snprintf(port, sizeof(port), "%d", DEFAULT_ADMIN_PORT);
648
649         pass = getpass("Please specify a password for the administration interface: "); 
650         g_key_file_set_string(kf, port, "network", "admin");
651         if (!strcmp(pass, "")) {
652                 fprintf(stderr, "Warning: no password specified. Authentication disabled!\n");
653         } else {
654                 g_key_file_set_string(kf, port, "password", pass);
655         }
656
657         listenerfile = g_build_filename(config_dir, "listener", NULL);
658
659         if (!g_key_file_save_to_file(kf, listenerfile, &error)) {
660                 fprintf(stderr, "Error saving %s: %s\n", listenerfile, error->message);
661                 return FALSE;
662         }
663
664         return TRUE;
665 }