Optionally install baseconv.
[doldaconnect.git] / config / dolconf.c
CommitLineData
eb54b70d 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>
48b166ff 32#include <arpa/inet.h>
eb54b70d 33
34#ifdef HAVE_CONFIG_H
35#include <config.h>
36#endif
37#include <utils.h>
38
39struct validation {
40 int (*check)(const char *val);
41 char *invmsg;
42};
43
44struct cfvar {
45 char *name;
46 char *rname;
47 char *val;
48 struct validation *vld;
49};
50
51char *cfname;
52GtkWindow *rootwnd = NULL;
53GtkListStore *shares;
54
55int v_nonempty(const char *val)
56{
57 return(strlen(val) > 0);
58}
59
60int v_dcstring(const char *val)
61{
62 return((strchr(val, ' ') == NULL) &&
63 (strchr(val, '|') == NULL) &&
64 (strchr(val, '$') == NULL));
65}
66
48b166ff 67int v_natural(const char *val)
eb54b70d 68{
48b166ff 69 if(!*val)
70 return(0);
71 for(; *val; val++) {
72 if(!isdigit(*val))
eb54b70d 73 return(0);
74 }
75 return(1);
76}
77
48b166ff 78int 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
93int v_ipv4(const char *val)
94{
95 struct in_addr buf;
96
97 return(inet_aton(val, &buf) != 0);
98}
99
eb54b70d 100#define _(text) text
101
102struct validation nonempty = {
103 .check = v_nonempty,
104 .invmsg = _("%s must not be empty"),
105};
106
107struct validation dcstring = {
108 .check = v_dcstring,
109 .invmsg = _("%s must not contain spaces, `|' or `$'"),
110};
111
48b166ff 112struct validation natural = {
113 .check = v_natural,
114 .invmsg = _("%s must be a natural number"),
115};
116
117struct validation integer = {
118 .check = v_integer,
119 .invmsg = _("%s must be an integer"),
120};
121
122struct validation ipv4 = {
123 .check = v_ipv4,
124 .invmsg = _("%s must be an IP address"),
eb54b70d 125};
126
127struct validation *vldxlate[] = {
48b166ff 128 &nonempty, &dcstring, &natural, &integer, &ipv4,
eb54b70d 129 NULL
130};
131
132struct cfvar config[] = {
133 {"cli.defnick", _("Nickname"), "", &dcstring},
48b166ff 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},
eb54b70d 140 {"dc.speedstring", _("Connection speed"), "DSL", &dcstring},
141 {"dc.desc", _("Share description"), "", NULL},
48b166ff 142 {"dc.tcpport", _("Direct Connect TCP port"), "0", &natural},
143 {"dc.udpport", _("Direct Connect UDP port"), "0", &natural},
eb54b70d 144 {NULL}
145};
146
147#undef _
148#define _(text) gettext(text)
149
150void astcancel(GtkWidget *widget, gpointer uudata);
151void astupdate(GtkWidget *widget, GtkWidget *page, gpointer uudata);
152void cb_ast_wnd_apply(GtkWidget *widget, gpointer uudata);
153void cb_ast_nick_changed(GtkWidget *widget, gpointer uudata);
154void cb_ast_shareadd_clicked(GtkWidget *widget, gpointer uudata);
155void cb_ast_sharerem_clicked(GtkWidget *widget, gpointer uudata);
48b166ff 156void cb_ast_checkports(GtkWidget *widget, gpointer uudata);
157void cb_ast_mode_nat_toggled(GtkWidget *widget, gpointer uudata);
eb54b70d 158
159#include "dolconf-assistant.gtk"
160
161struct 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
172void 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
181int 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
199void 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
213char *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
241char *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
279int 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
343void 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
392void astcancel(GtkWidget *widget, gpointer uudata)
393{
394 gtk_main_quit();
395}
396
397#define bufcats(buf, str) bufcat(buf, str, strlen(str))
398
399void 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)));
48b166ff 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 }
eb54b70d 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
447void cb_ast_wnd_apply(GtkWidget *widget, gpointer uudata)
448{
449 writeconfig();
450 gtk_main_quit();
451}
452
453void 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
461int 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
479void 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
526void 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
48b166ff 536void 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
544void 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
eb54b70d 555int 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}