84c76c7b735343d332cf02493886dcef87b8c629
[doldaconnect.git] / config / dolconf.c
1 /*
2  *  Dolda Connect - Modular multiuser Direct Connect-style client
3  *  Copyright (C) 2007 Fredrik Tolf (fredrik@dolda2000.com)
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 */
19
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <unistd.h>
23 #include <string.h>
24 #include <ctype.h>
25 #include <signal.h>
26 #include <errno.h>
27 #include <gtk/gtk.h>
28 #include <locale.h>
29 #include <libintl.h>
30 #include <pwd.h>
31 #include <stdarg.h>
32 #include <arpa/inet.h>
33
34 #ifdef HAVE_CONFIG_H
35 #include <config.h>
36 #endif
37 #include <utils.h>
38
39 struct validation {
40     int (*check)(const char *val);
41     char *invmsg;
42 };
43
44 struct cfvar {
45     char *name;
46     char *rname;
47     char *val;
48     struct validation *vld;
49 };
50
51 char *cfname;
52 GtkWindow *rootwnd = NULL;
53 GtkListStore *shares;
54
55 int v_nonempty(const char *val)
56 {
57     return(strlen(val) > 0);
58 }
59
60 int v_dcstring(const char *val)
61 {
62     return((strchr(val, ' ') == NULL) &&
63            (strchr(val, '|') == NULL) &&
64            (strchr(val, '$') == NULL));
65 }
66
67 int v_natural(const char *val)
68 {
69     if(!*val)
70         return(0);
71     for(; *val; val++) {
72         if(!isdigit(*val))
73             return(0);
74     }
75     return(1);
76 }
77
78 int v_integer(const char *val)
79 {
80     int f, d;
81     
82     for(f = 1, d = 0; *val; val++, f = 0) {
83         if(isdigit(*val)) {
84             d = 1;
85         } else if(f && (*val == '-')) {
86         } else {
87             return(0);
88         }
89     }
90     return(d);
91 }
92
93 int v_ipv4(const char *val)
94 {
95     struct in_addr buf;
96     
97     return(inet_aton(val, &buf) != 0);
98 }
99
100 #define _(text) text
101
102 struct validation nonempty = {
103     .check = v_nonempty,
104     .invmsg = _("%s must not be empty"),
105 };
106
107 struct validation dcstring = {
108     .check = v_dcstring,
109     .invmsg = _("%s must not contain spaces, `|' or `$'"),
110 };
111
112 struct validation natural = {
113     .check = v_natural,
114     .invmsg = _("%s must be a natural number"),
115 };
116
117 struct validation integer = {
118     .check = v_integer,
119     .invmsg = _("%s must be an integer"),
120 };
121
122 struct validation ipv4 = {
123     .check = v_ipv4,
124     .invmsg = _("%s must be an IP address"),
125 };
126
127 struct validation *vldxlate[] = {
128     &nonempty, &dcstring, &natural, &integer, &ipv4,
129     NULL
130 };
131
132 struct cfvar config[] = {
133     {"cli.defnick", _("Nickname"), "", &dcstring},
134     {"net.mode", NULL, "0", &natural},
135     {"net.visibleipv4", "IP address", "0.0.0.0", &ipv4},
136     {"ui.onlylocal", NULL, "0", &natural},
137     {"ui.port", NULL, "-1", &integer},
138     {"auth.authless", NULL, "0", &natural},
139     {"transfer.slots", _("Upload slots"), "6", &natural},
140     {"dc.speedstring", _("Connection speed"), "DSL", &dcstring},
141     {"dc.desc", _("Share description"), "", NULL},
142     {"dc.tcpport", _("Direct Connect TCP port"), "0", &natural},
143     {"dc.udpport", _("Direct Connect UDP port"), "0", &natural},
144     {NULL}
145 };
146
147 #undef _
148 #define _(text) gettext(text)
149
150 void astcancel(GtkWidget *widget, gpointer uudata);
151 void astupdate(GtkWidget *widget, GtkWidget *page, gpointer uudata);
152 void cb_ast_wnd_apply(GtkWidget *widget, gpointer uudata);
153 void cb_ast_nick_changed(GtkWidget *widget, gpointer uudata);
154 void cb_ast_shareadd_clicked(GtkWidget *widget, gpointer uudata);
155 void cb_ast_sharerem_clicked(GtkWidget *widget, gpointer uudata);
156 void cb_ast_checkports(GtkWidget *widget, gpointer uudata);
157 void cb_ast_mode_nat_toggled(GtkWidget *widget, gpointer uudata);
158
159 #include "dolconf-assistant.gtk"
160
161 struct cfvar *findcfvar(char *name)
162 {
163     struct cfvar *v;
164     
165     for(v = config; v->name != NULL; v++) {
166         if(!strcmp(v->name, name))
167             return(v);
168     }
169     return(NULL);
170 }
171
172 void setcfvar(char *name, const char *val)
173 {
174     struct cfvar *v;
175     
176     v = findcfvar(name);
177     free(v->val);
178     v->val = sstrdup(val);
179 }
180
181 int msgbox(int type, int buttons, char *format, ...)
182 {
183     GtkWidget *swnd;
184     va_list args;
185     char *buf;
186     int resp;
187     
188     va_start(args, format);
189     buf = vsprintf2(format, args);
190     va_end(args);
191     swnd = gtk_message_dialog_new(rootwnd, GTK_DIALOG_MODAL, type, buttons, "%s", buf);
192     gtk_window_set_title(GTK_WINDOW(swnd), _("Dolda Connect configurator"));
193     resp = gtk_dialog_run(GTK_DIALOG(swnd));
194     gtk_widget_destroy(swnd);
195     free(buf);
196     return(resp);
197 }
198
199 void prepstatic(void)
200 {
201     struct validation **v;
202     struct cfvar *c;
203     
204     for(v = vldxlate; *v != NULL; v++)
205         (*v)->invmsg = gettext((*v)->invmsg);
206     for(c = config; c->name != NULL; c++) {
207         if(c->rname != NULL)
208             c->rname = gettext(c->rname);
209         c->val = sstrdup(c->val);
210     }
211 }
212
213 char *getword(char **p)
214 {
215     char *buf, *p2, delim;
216     size_t len;
217     
218     if(**p == '\"')
219         delim = '\"';
220     else
221         delim = ' ';
222     p2 = *p;
223     while((p2 = strchr(p2 + 1, delim)) != NULL) {
224         if(p2[-1] != '\\')
225             break;
226     }
227     if(p2 == NULL)
228         p2 = *p + strlen(*p);
229     len = p2 - *p;
230     buf = smalloc(len + 1);
231     memcpy(buf, *p, len);
232     buf[len] = 0;
233     *p = p2 + ((*p2 == '\"')?1:0);
234     for(p2 = buf; *p2; p2++, len--) {
235         if(*p2 == '\\')
236             memmove(p2, p2 + 1, len--);
237     }
238     return(buf);
239 }
240
241 char *quoteword(char *word)
242 {
243     char *wp, *buf, *bp;
244     int dq, numbs, numc;
245     
246     dq = 0;
247     numbs = 0;
248     numc = 0;
249     if(*word == '\0')
250     {
251         dq = 1;
252     } else {
253         for(wp = word; *wp != '\0'; wp++)
254         {
255             if(!dq && isspace(*wp))
256                 dq = 1;
257             if((*wp == '\\') || (*wp == '\"'))
258                 numbs++;
259             numc++;
260         }
261     }
262     if(!dq && !numbs)
263         return(NULL);
264     bp = buf = smalloc(sizeof(wchar_t) * (numc + numbs + (dq?2:0) + 1));
265     if(dq)
266         *(bp++) = '\"';
267     for(wp = word; *wp != '\0'; wp++)
268     {
269         if((*wp == '\\') || (*wp == '\"'))
270             *(bp++) = '\\';
271         *(bp++) = *wp;
272     }
273     if(dq)
274         *(bp++) = '\"';
275     *(bp++) = '\0';
276     return(buf);
277 }
278
279 int readconfig(void)
280 {
281     int rv;
282     FILE *cf;
283     char lbuf[1024];
284     char *key, *val, *p;
285     size_t len;
286     struct cfvar *var;
287     GtkTreeIter iter;
288     
289     rv = 0;
290     if((cf = fopen(cfname, "r")) == NULL) {
291         msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("Could not open the configuration file for reading: %s"), strerror(errno));
292         return(-1);
293     }
294     key = val = NULL;
295     while(fgets(lbuf, sizeof(lbuf), cf) != NULL) {
296         len = strlen(lbuf);
297         if(lbuf[len - 1] == '\n')
298             lbuf[len - 1] = 0;
299         if(key != NULL) {
300             free(key);
301             key = NULL;
302         }
303         if(val != NULL) {
304             free(val);
305             val = NULL;
306         }
307         if(!strncmp(lbuf, "set ", 4)) {
308             p = lbuf + 4;
309             if(((key = getword(&p)) == NULL) || (*(p++) != ' ') || ((val = getword(&p)) == NULL)) {
310                 rv = 1;
311                 continue;
312             }
313             for(var = config; var->name != NULL; var++) {
314                 if(!strcmp(var->name, key)) {
315                     free(var->val);
316                     var->val = sstrdup(val);
317                     break;
318                 }
319             }
320             if(var->name == NULL)
321                 rv = 1;
322         } else if(!strncmp(lbuf, "share ", 6)) {
323             p = lbuf + 6;
324             if(((key = getword(&p)) == NULL) || (*(p++) != ' ') || ((val = getword(&p)) == NULL)) {
325                 rv = 1;
326                 continue;
327             }
328             gtk_list_store_append(shares, &iter);
329             gtk_list_store_set(shares, &iter, 0, key, 1, val, -1);
330         } else if(!lbuf[0] || lbuf[0] == '#') {
331         } else {
332             rv = 1;
333         }
334     }
335     if(key != NULL)
336         free(key);
337     if(val != NULL)
338         free(val);
339     fclose(cf);
340     return(rv);
341 }
342
343 void writeconfig(void)
344 {
345     FILE *cf;
346     struct cfvar *var;
347     GtkTreeIter iter;
348     char *buf, *buf2;
349     
350     if((cf = fopen(cfname, "w")) == NULL) {
351         msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("Could not open the configuration file for writing: %s"), strerror(errno));
352         return;
353     }
354     fputs("# This file was generated by dolconf v" VERSION "\n\n", cf);
355     for(var = config; var->name != NULL; var++) {
356         fprintf(cf, "set %s ", var->name);
357         if((buf = quoteword(var->val)) == NULL) {
358             fputs(var->val, cf);
359         } else {
360             fputs(buf, cf);
361             free(buf);
362         }
363         fputc('\n', cf);
364     }
365     if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(shares), &iter)) {
366         fputc('\n', cf);
367         do {
368             fputs("share ", cf);
369             gtk_tree_model_get(GTK_TREE_MODEL(shares), &iter, 0, &buf2, -1);
370             if((buf = quoteword(buf2)) == NULL) {
371                 fputs(buf2, cf);
372             } else {
373                 fputs(buf, cf);
374                 free(buf);
375             }
376             g_free(buf2);
377             fputc(' ', cf);
378             gtk_tree_model_get(GTK_TREE_MODEL(shares), &iter, 1, &buf2, -1);
379             if((buf = quoteword(buf2)) == NULL) {
380                 fputs(buf2, cf);
381             } else {
382                 fputs(buf, cf);
383                 free(buf);
384             }
385             g_free(buf2);
386             fputc('\n', cf);
387         } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(shares), &iter));
388     }
389     fclose(cf);
390 }
391
392 void astcancel(GtkWidget *widget, gpointer uudata)
393 {
394     gtk_main_quit();
395 }
396
397 #define bufcats(buf, str) bufcat(buf, str, strlen(str))
398
399 void astupdate(GtkWidget *widget, GtkWidget *page, gpointer uudata)
400 {
401     char *s, *buf;
402     size_t sdata, ssize;
403     struct cfvar *var;
404     GtkTreeIter iter;
405     
406     setcfvar("cli.defnick", gtk_entry_get_text(GTK_ENTRY(ast_nick)));
407     setcfvar("dc.desc", gtk_entry_get_text(GTK_ENTRY(ast_desc)));
408     if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ast_mode_psv))) {
409         setcfvar("net.mode", "1");
410     } else {
411         setcfvar("net.mode", "0");
412         if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ast_mode_nat))) {
413             setcfvar("net.visibleipv4", gtk_entry_get_text(GTK_ENTRY(ast_extip)));
414             setcfvar("dc.tcpport", gtk_entry_get_text(GTK_ENTRY(ast_udpport)));
415             setcfvar("dc.udpport", gtk_entry_get_text(GTK_ENTRY(ast_tcpport)));
416         } else {
417             setcfvar("net.visibleipv4", "0.0.0.0");
418             setcfvar("dc.tcpport", "0");
419             setcfvar("dc.udpport", "0");
420         }
421     }
422     s = NULL;
423     sdata = ssize = 0;
424     for(var = config; var->name != NULL; var++) {
425         if(var->rname == NULL)
426             continue;
427         bufcats(s, var->rname);
428         bufcats(s, ": ");
429         bufcat(s, var->val, strlen(var->val));
430         addtobuf(s, '\n');
431     }
432     if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(shares), &iter)) {
433         addtobuf(s, '\n');
434         bufcats(s, _("Shares:\n"));
435         do {
436             addtobuf(s, '\t');
437             gtk_tree_model_get(GTK_TREE_MODEL(shares), &iter, 1, &buf, -1);
438             bufcats(s, buf);
439             g_free(buf);
440             addtobuf(s, '\n');
441         } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(shares), &iter));
442     }
443     gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(ast_summary)), s, sdata);
444     free(s);
445 }
446
447 void cb_ast_wnd_apply(GtkWidget *widget, gpointer uudata)
448 {
449     writeconfig();
450     gtk_main_quit();
451 }
452
453 void cb_ast_nick_changed(GtkWidget *widget, gpointer uudata)
454 {
455     if(v_dcstring(gtk_entry_get_text(GTK_ENTRY(ast_nick))))
456         gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page1, TRUE);
457     else
458         gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page1, FALSE);
459 }
460
461 int hasshare(int col, char *name)
462 {
463     GtkTreeIter iter;
464     char *buf;
465     
466     if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(shares), &iter)) {
467         do {
468             gtk_tree_model_get(GTK_TREE_MODEL(shares), &iter, col, &buf, -1);
469             if(!strcmp(buf, name)) {
470                 g_free(buf);
471                 return(1);
472             }
473             g_free(buf);
474         } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(shares), &iter));
475     }
476     return(0);
477 }
478
479 void cb_ast_shareadd_clicked(GtkWidget *widget, gpointer uudata)
480 {
481     int i;
482     GSList *fns, *next;
483     char *fn, *sn, *p;
484     GtkTreeIter iter;
485     GtkWidget *chd;
486     int resp;
487     
488     chd = gtk_file_chooser_dialog_new(_("Shared directories"), rootwnd, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
489     gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(chd), TRUE);
490     gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(chd), TRUE);
491     resp = gtk_dialog_run(GTK_DIALOG(chd));
492     if(resp != GTK_RESPONSE_ACCEPT) {
493         gtk_widget_destroy(chd);
494         return;
495     }
496     fns = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(chd));
497     gtk_widget_destroy(chd);
498     while(fns != NULL) {
499         fn = fns->data;
500         if(!hasshare(1, fn)) {
501             if((p = strrchr(fn, '/')) == NULL)
502                 p = fn;
503             else
504                 p++;
505             sn = sstrdup(p);
506             if(hasshare(0, sn)) {
507                 for(i = 2; 1; i++) {
508                     free(sn);
509                     sn = sprintf2("%s%i", p, i);
510                     if(!hasshare(0, sn))
511                         break;
512                 }
513             }
514             gtk_list_store_append(shares, &iter);
515             gtk_list_store_set(shares, &iter, 0, sn, 1, fn, -1);
516             free(sn);
517             gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page2, TRUE);
518         }
519         g_free(fn);
520         next = fns->next;
521         g_slist_free_1(fns);
522         fns = next;
523     }
524 }
525
526 void cb_ast_sharerem_clicked(GtkWidget *widget, gpointer uudata)
527 {
528     GtkTreeIter iter;
529     
530     if(gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(ast_sharelist)), NULL, &iter))
531         gtk_list_store_remove(shares, &iter);
532     if(gtk_tree_model_iter_n_children(GTK_TREE_MODEL(shares), NULL) == 0)
533         gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page2, FALSE);
534 }
535
536 void cb_ast_checkports(GtkWidget *widget, gpointer uudata)
537 {
538     gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page3,
539                                     v_natural(gtk_entry_get_text(GTK_ENTRY(ast_tcpport))) &&
540                                     v_natural(gtk_entry_get_text(GTK_ENTRY(ast_udpport))) &&
541                                     v_ipv4(gtk_entry_get_text(GTK_ENTRY(ast_extip))));
542 }
543
544 void cb_ast_mode_nat_toggled(GtkWidget *widget, gpointer uudata)
545 {
546     if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
547         gtk_widget_set_sensitive(GTK_WIDGET(ast_portbox), TRUE);
548         cb_ast_checkports(widget, NULL);
549     } else {
550         gtk_widget_set_sensitive(GTK_WIDGET(ast_portbox), FALSE);
551         gtk_assistant_set_page_complete(GTK_ASSISTANT(ast_wnd), ast_page3, TRUE);
552     }
553 }
554
555 int main(int argc, char **argv)
556 {
557     struct passwd *pwd;
558     
559     setlocale(LC_ALL, "");
560     bindtextdomain(PACKAGE, LOCALEDIR);
561     textdomain(PACKAGE);
562     prepstatic();
563     
564     gtk_init(&argc, &argv);
565     create_ast_wnd();
566     shares = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
567     gtk_tree_view_set_model(GTK_TREE_VIEW(ast_sharelist), GTK_TREE_MODEL(shares));
568     
569     cfname = NULL;
570     if(getenv("HOME") != NULL) {
571         cfname = sprintf2("%s/.doldacond.conf", getenv("HOME"));
572     } else {
573         if((pwd = getpwuid(getuid())) != NULL)
574             cfname = sprintf2("%s/.doldacond.conf", pwd->pw_dir);
575     }
576     if(cfname == NULL) {
577         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not get your home directory!"));
578         exit(1);
579     }
580     
581     if(access(cfname, F_OK)) {
582         if(msgbox(GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, _("It appears that you have not run this setup program before. Would you like to run the first-time setup assistant?")) == GTK_RESPONSE_YES) {
583             gtk_window_set_default_size(GTK_WINDOW(ast_wnd), 500, 350);
584             gtk_widget_show(ast_wnd);
585         }
586     } else {
587         if(readconfig() == 1) {
588             if(msgbox(GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, _("The configuration file appears to have been edited outside the control of this program. If you continue using this program, all settings not handled by it will be lost. Do you wish to continue?")) == GTK_RESPONSE_NO)
589                 exit(1);
590         }
591     }
592     gtk_main();
593     return(0);
594 }