Merge commit 'crater/vendor/OPENPAM'
[dragonfly.git] / usr.sbin / tzsetup / tzsetup.c
1 /*
2  * Copyright 1996 Massachusetts Institute of Technology
3  *
4  * Permission to use, copy, modify, and distribute this software and
5  * its documentation for any purpose and without fee is hereby
6  * granted, provided that both the above copyright notice and this
7  * permission notice appear in all copies, that both the above
8  * copyright notice and this permission notice appear in all
9  * supporting documentation, and that the name of M.I.T. not be used
10  * in advertising or publicity pertaining to distribution of the
11  * software without specific, written prior permission.  M.I.T. makes
12  * no representations about the suitability of this software for any
13  * purpose.  It is provided "as is" without express or implied
14  * warranty.
15  * 
16  * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''.  M.I.T. DISCLAIMS
17  * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
18  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
20  * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
26  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  * $FreeBSD: src/usr.sbin/tzsetup/tzsetup.c,v 1.16.2.2 2002/03/06 06:17:41 obrien Exp $
30  * $DragonFly: src/usr.sbin/tzsetup/tzsetup.c,v 1.8 2008/06/03 09:33:27 swildner Exp $
31  */
32
33 /*
34  * Second attempt at a `tzmenu' program, using the separate description
35  * files provided in newer tzdata releases.
36  */
37
38 #include <sys/types.h>
39 #include <dialog.h>
40 #include <err.h>
41 #include <errno.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46
47 #include <sys/fcntl.h>
48 #include <sys/queue.h>
49 #include <sys/stat.h>
50
51 #include "paths.h"
52
53 static int reallydoit = 1;
54
55 static int continent_country_menu(dialogMenuItem *);
56 static int set_zone_multi(dialogMenuItem *);
57 static int set_zone_whole_country(dialogMenuItem *);
58 static int set_zone_menu(dialogMenuItem *);
59
60 struct continent {
61         dialogMenuItem *menu;
62         int nitems;
63         int ch;
64         int sc;
65 };
66
67 static struct continent africa, america, antarctica, arctic, asia, atlantic;
68 static struct continent australia, europe, indian, pacific;
69
70 static struct continent_names {
71         char *name;
72         struct continent *continent;
73 } continent_names[] = {
74         { "Africa", &africa }, { "America", &america },
75         { "Antarctica", &antarctica }, { "Arctic", &arctic }, 
76         { "Asia", &asia },
77         { "Atlantic", &atlantic }, { "Australia", &australia },
78         { "Europe", &europe }, { "Indian", &indian }, { "Pacific", &pacific }
79 };
80
81 static dialogMenuItem continents[] = {
82         { "1", "Africa", 0, continent_country_menu, 0, &africa },
83         { "2", "America -- North and South", 0, continent_country_menu, 0, 
84                   &america },
85         { "3", "Antarctica", 0, continent_country_menu, 0, &antarctica },
86         { "4", "Arctic Ocean", 0, continent_country_menu, 0, &arctic },
87         { "5", "Asia", 0, continent_country_menu, 0, &asia },
88         { "6", "Atlantic Ocean", 0, continent_country_menu, 0, &atlantic },
89         { "7", "Australia", 0, continent_country_menu, 0, &australia },
90         { "8", "Europe", 0, continent_country_menu, 0, &europe },
91         { "9", "Indian Ocean", 0, continent_country_menu, 0, &indian },
92         { "0", "Pacific Ocean", 0, continent_country_menu, 0, &pacific }
93 };
94 #define NCONTINENTS ((sizeof continents)/(sizeof continents[0]))
95 #define OCEANP(x) ((x) == 3 || (x) == 5 || (x) == 8 || (x) == 9)
96
97 static int
98 continent_country_menu(dialogMenuItem *continent)
99 {
100         int rv;
101         struct continent *contp = continent->data;
102         char title[256];
103         int isocean = OCEANP(continent - continents);
104         int menulen;
105
106         /* Short cut -- if there's only one country, don't post a menu. */
107         if (contp->nitems == 1)
108                 return (contp->menu[0].fire(&contp->menu[0]));
109
110         /* It's amazing how much good grammar really matters... */
111         if (!isocean)
112                 snprintf(title, sizeof title, "Countries in %s", 
113                          continent->title);
114         else 
115                 snprintf(title, sizeof title, "Islands and groups in the %s",
116                          continent->title);
117
118         menulen = contp->nitems < 16 ? contp->nitems : 16;
119         rv = dialog_menu(title, (isocean ? "Select an island or group"
120                                  : "Select a country"), -1, -1, menulen,
121                          -contp->nitems, contp->menu, 0, &contp->ch,
122                          &contp->sc);
123         if (rv == 0)
124                 return DITEM_LEAVE_MENU;
125         return DITEM_RECREATE;
126 }
127
128 static struct continent *
129 find_continent(const char *name)
130 {
131         int i;
132
133         for (i = 0; i < NCONTINENTS; i++) {
134                 if (strcmp(name, continent_names[i].name) == 0)
135                         return continent_names[i].continent;
136         }
137         return 0;
138 }
139
140 struct country {
141         char *name;
142         char *tlc;
143         int nzones;
144         char *filename;         /* use iff nzones < 0 */
145         struct continent *continent; /* use iff nzones < 0 */
146         TAILQ_HEAD(, zone) zones; /* use iff nzones > 0 */
147         dialogMenuItem *submenu; /* use iff nzones > 0 */
148 };
149
150 struct zone {
151         TAILQ_ENTRY(zone) link;
152         char *descr;
153         char *filename;
154         struct continent *continent;
155 };
156
157 /*
158  * This is the easiest organization... we use ISO 3166 country codes,
159  * of the two-letter variety, so we just size this array to suit.
160  * Beats worrying about dynamic allocation.
161  */
162 #define NCOUNTRIES      (26*26)
163 static struct country countries[NCOUNTRIES];
164 #define CODE2INT(s) ((s[0] - 'A') * 26 + (s[1] - 'A'))
165
166 /*
167  * Read the ISO 3166 country code database in _PATH_ISO3166
168  * (/usr/share/misc/iso3166).  On error, exit via err(3).
169  */
170 static void
171 read_iso3166_table(void)
172 {
173         FILE *fp;
174         char *s, *t, *name;
175         size_t len;
176         int lineno;
177         struct country *cp;
178
179         fp = fopen(_PATH_ISO3166, "r");
180         if (!fp)
181                 err(1, _PATH_ISO3166);
182         lineno = 0;
183
184         while ((s = fgetln(fp, &len)) != 0) {
185                 lineno++;
186                 if (s[len - 1] != '\n')
187                         errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
188                 s[len - 1] = '\0';
189                 if (s[0] == '#' || strspn(s, " \t") == len - 1)
190                         continue;
191
192                 /* Isolate the two-letter code. */
193                 t = strsep(&s, "\t");
194                 if (t == 0 || strlen(t) != 2)
195                         errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
196                 if (t[0] < 'A' || t[0] > 'Z' || t[1] < 'A' || t[1] > 'Z')
197                         errx(1, _PATH_ISO3166 ":%d: invalid code `%s'",
198                              lineno, t);
199
200                 name = s;
201
202                 cp = &countries[CODE2INT(t)];
203                 if (cp->name)
204                         errx(1, _PATH_ISO3166 
205                              ":%d: country code `%s' multiply defined: %s",
206                              lineno, t, cp->name);
207                 cp->name = strdup(name);
208                 if (cp->name == NULL)
209                         errx(1, "malloc failed");
210                 cp->tlc = strdup(t);
211                 if (cp->tlc == NULL)
212                         errx(1, "malloc failed");
213         }
214
215         fclose(fp);
216 }
217
218 static void
219 add_zone_to_country(int lineno, const char *tlc, const char *descr,
220                     const char *file, struct continent *cont)
221 {
222         struct zone *zp;
223         struct country *cp;
224
225         if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z')
226                 errx(1, _PATH_ZONETAB ":%d: country code `%s' invalid",
227                      lineno, tlc);
228         
229         cp = &countries[CODE2INT(tlc)];
230         if (cp->name == 0)
231                 errx(1, _PATH_ZONETAB ":%d: country code `%s' unknown",
232                      lineno, tlc);
233
234         if (descr) {
235                 if (cp->nzones < 0)
236                         errx(1, _PATH_ZONETAB 
237                              ":%d: conflicting zone definition", lineno);
238
239                 zp = malloc(sizeof *zp);
240                 if (zp == 0)
241                         errx(1, "malloc(%lu)", (unsigned long)sizeof *zp);
242                 
243                 if (cp->nzones == 0)
244                         TAILQ_INIT(&cp->zones);
245
246                 zp->descr = strdup(descr);
247                 if (zp->descr == NULL)
248                         errx(1, "malloc failed");
249                 zp->filename = strdup(file);
250                 if (zp->filename == NULL)
251                         errx(1, "malloc failed");
252                 zp->continent = cont;
253                 TAILQ_INSERT_TAIL(&cp->zones, zp, link);
254                 cp->nzones++;
255         } else {
256                 if (cp->nzones > 0)
257                         errx(1, _PATH_ZONETAB 
258                              ":%d: zone must have description", lineno);
259                 if (cp->nzones < 0)
260                         errx(1, _PATH_ZONETAB
261                              ":%d: zone multiply defined", lineno);
262                 cp->nzones = -1;
263                 cp->filename = strdup(file);
264                 if (cp->filename == NULL)
265                         errx(1, "malloc failed");
266                 cp->continent = cont;
267         }
268 }
269
270 /*
271  * This comparison function intentionally sorts all of the null-named
272  * ``countries''---i.e., the codes that don't correspond to a real
273  * country---to the end.  Everything else is lexical by country name.
274  */
275 static int
276 compare_countries(const void *xa, const void *xb)
277 {
278         const struct country *a = xa, *b = xb;
279
280         if (a->name == 0 && b->name == 0)
281                 return 0;
282         if (a->name == 0 && b->name != 0)
283                 return 1;
284         if (b->name == 0)
285                 return -1;
286
287         return strcmp(a->name, b->name);
288 }
289
290 /*
291  * This must be done AFTER all zone descriptions are read, since it breaks
292  * CODE2INT().
293  */
294 static void
295 sort_countries(void)
296 {
297         qsort(countries, NCOUNTRIES, sizeof countries[0], compare_countries);
298 }
299
300 static void
301 read_zones(void)
302 {
303         FILE *fp;
304         char *line;
305         size_t len;
306         int lineno;
307         char *tlc, *coord, *file, *descr, *p;
308         char contbuf[16];
309         struct continent *cont;
310
311         fp = fopen(_PATH_ZONETAB, "r");
312         if (!fp)
313                 err(1, _PATH_ZONETAB);
314         lineno = 0;
315
316         while ((line = fgetln(fp, &len)) != 0) {
317                 lineno++;
318                 if (line[len - 1] != '\n')
319                         errx(1, _PATH_ZONETAB ":%d: invalid format", lineno);
320                 line[len - 1] = '\0';
321                 if (line[0] == '#')
322                         continue;
323
324                 tlc = strsep(&line, "\t");
325                 if (strlen(tlc) != 2)
326                         errx(1, _PATH_ZONETAB ":%d: invalid country code `%s'",
327                              lineno, tlc);
328                 coord = strsep(&line, "\t");
329                 file = strsep(&line, "\t");
330                 p = strchr(file, '/');
331                 if (p == 0)
332                         errx(1, _PATH_ZONETAB ":%d: invalid zone name `%s'",
333                              lineno, file);
334                 contbuf[0] = '\0';
335                 strncat(contbuf, file, p - file);
336                 cont = find_continent(contbuf);
337                 if (!cont)
338                         errx(1, _PATH_ZONETAB ":%d: invalid region `%s'",
339                              lineno, contbuf);
340
341                 descr = (line && *line) ? line : 0;
342
343                 add_zone_to_country(lineno, tlc, descr, file, cont);
344         }
345         fclose(fp);
346 }
347
348 static void
349 make_menus(void)
350 {
351         struct country *cp;
352         struct zone *zp, *zp2;
353         struct continent *cont;
354         dialogMenuItem *dmi;
355         int i;
356
357         /*
358          * First, count up all the countries in each continent/ocean.
359          * Be careful to count those countries which have multiple zones
360          * only once for each.  NB: some countries are in multiple
361          * continents/oceans.
362          */
363         for (cp = countries; cp->name; cp++) {
364                 if (cp->nzones == 0)
365                         continue;
366                 if (cp->nzones < 0) {
367                         cp->continent->nitems++;
368                 } else {
369                         for (zp = cp->zones.tqh_first; zp; 
370                              zp = zp->link.tqe_next) {
371                                 cont = zp->continent;
372                                 for (zp2 = cp->zones.tqh_first;
373                                      zp2->continent != cont;
374                                      zp2 = zp2->link.tqe_next)
375                                         ;
376                                 if (zp2 == zp)
377                                         zp->continent->nitems++;
378                         }
379                 }
380         }
381
382         /*
383          * Now allocate memory for the country menus.  We set
384          * nitems back to zero so that we can use it for counting
385          * again when we actually build the menus.
386          */
387         for (i = 0; i < NCONTINENTS; i++) {
388                 continent_names[i].continent->menu =
389                         malloc(sizeof(dialogMenuItem) *
390                                continent_names[i].continent->nitems);
391                 if (continent_names[i].continent->menu == 0)
392                         errx(1, "malloc for continent menu");
393                 continent_names[i].continent->nitems = 0;
394         }
395
396         /*
397          * Now that memory is allocated, create the menu items for
398          * each continent.  For multiple-zone countries, also create
399          * the country's zone submenu.
400          */
401         for (cp = countries; cp->name; cp++) {
402                 if (cp->nzones == 0)
403                         continue;
404                 if (cp->nzones < 0) {
405                         dmi = &cp->continent->menu[cp->continent->nitems];
406                         memset(dmi, 0, sizeof *dmi);
407                         asprintf(&dmi->prompt, "%d", 
408                                  ++cp->continent->nitems);
409                         dmi->title = cp->name;
410                         dmi->checked = 0;
411                         dmi->fire = set_zone_whole_country;
412                         dmi->selected = 0;
413                         dmi->data = cp;
414                 } else {
415                         cp->submenu = malloc(cp->nzones * sizeof *dmi);
416                         if (cp->submenu == 0)
417                                 errx(1, "malloc for submenu");
418                         cp->nzones = 0;
419                         for (zp = cp->zones.tqh_first; zp; 
420                              zp = zp->link.tqe_next) {
421                                 cont = zp->continent;
422                                 dmi = &cp->submenu[cp->nzones];
423                                 memset(dmi, 0, sizeof *dmi);
424                                 asprintf(&dmi->prompt, "%d",
425                                          ++cp->nzones);
426                                 dmi->title = zp->descr;
427                                 dmi->checked = 0;
428                                 dmi->fire = set_zone_multi;
429                                 dmi->selected = 0;
430                                 dmi->data = zp;
431
432                                 for (zp2 = cp->zones.tqh_first;
433                                      zp2->continent != cont;
434                                      zp2 = zp2->link.tqe_next)
435                                         ;
436                                 if (zp2 != zp)
437                                         continue;
438
439                                 dmi = &cont->menu[cont->nitems];
440                                 memset(dmi, 0, sizeof *dmi);
441                                 asprintf(&dmi->prompt, "%d", ++cont->nitems);
442                                 dmi->title = cp->name;
443                                 dmi->checked = 0;
444                                 dmi->fire = set_zone_menu;
445                                 dmi->selected = 0;
446                                 dmi->data = cp;
447                         }
448                 }
449         }
450 }
451
452 static int
453 set_zone_menu(dialogMenuItem *dmi)
454 {
455         int rv;
456         char buf[256];
457         struct country *cp = dmi->data;
458         int menulen;
459
460         snprintf(buf, sizeof buf, "%s Time Zones", cp->name);
461         menulen = cp->nzones < 16 ? cp->nzones : 16;
462         rv = dialog_menu(buf, "Select a zone which observes the same time as "
463                          "your locality.", -1, -1, menulen, -cp->nzones,
464                          cp->submenu, 0, 0, 0);
465         if (rv != 0)
466                 return DITEM_RECREATE;
467         return DITEM_LEAVE_MENU;
468 }
469
470 static int
471 install_zone_file(const char *filename)
472 {
473         struct stat sb;
474         int fd1, fd2;
475         int copymode;
476         char *msg;
477         ssize_t len;
478         char buf[1024];
479
480         if (lstat(_PATH_LOCALTIME, &sb) < 0)
481                 /* Nothing there yet... */
482                 copymode = 1;
483         else if(S_ISLNK(sb.st_mode))
484                 copymode = 0;
485         else
486                 copymode = 1;
487
488 #ifdef VERBOSE
489         if (copymode)
490                 asprintf(&msg, "Copying %s to " _PATH_LOCALTIME, filename);
491         else
492                 asprintf(&msg, "Creating symbolic link " _PATH_LOCALTIME
493                          " to %s", filename);
494
495         dialog_notify(msg);
496         free(msg);
497 #endif
498
499         if (reallydoit) {
500                 if (copymode) {
501                         fd1 = open(filename, O_RDONLY, 0);
502                         if (fd1 < 0) {
503                                 asprintf(&msg, "Could not open %s: %s",
504                                          filename, strerror(errno));
505                                 dialog_mesgbox("Error", msg, 8, 72);
506                                 free(msg);
507                                 return DITEM_FAILURE | DITEM_RECREATE;
508                         }
509
510                         unlink(_PATH_LOCALTIME);
511                         fd2 = open(_PATH_LOCALTIME, 
512                                    O_CREAT|O_EXCL|O_WRONLY,
513                                    S_IRUSR|S_IRGRP|S_IROTH);
514                         if (fd2 < 0) {
515                                 asprintf(&msg, "Could not open "
516                                          _PATH_LOCALTIME ": %s", 
517                                          strerror(errno));
518                                 dialog_mesgbox("Error", msg, 8, 72);
519                                 free(msg);
520                                 return DITEM_FAILURE | DITEM_RECREATE;
521                         }
522
523                         while ((len = read(fd1, buf, sizeof buf)) > 0)
524                                 len = write(fd2, buf, len);
525
526                         if (len == -1) {
527                                 asprintf(&msg, "Error copying %s to "
528                                          _PATH_LOCALTIME ": %s",
529                                          filename, strerror(errno));
530                                 dialog_mesgbox("Error", msg, 8, 72);
531                                 free(msg);
532                                 /* Better to leave none than a corrupt one. */
533                                 unlink(_PATH_LOCALTIME);
534                                 return DITEM_FAILURE | DITEM_RECREATE;
535                         }
536                         close(fd1);
537                         close(fd2);
538                 } else {
539                         if (access(filename, R_OK) != 0) {
540                                 asprintf(&msg, "Cannot access %s: %s",
541                                          filename, strerror(errno));
542                                 dialog_mesgbox("Error", msg, 8, 72);
543                                 free(msg);
544                                 return DITEM_FAILURE | DITEM_RECREATE;
545                         }
546                         unlink(_PATH_LOCALTIME);
547                         if (symlink(filename, _PATH_LOCALTIME) < 0) {
548                                 asprintf(&msg, "Cannot create symbolic link "
549                                          _PATH_LOCALTIME " to %s: %s",
550                                          filename, strerror(errno));
551                                 dialog_mesgbox("Error", msg, 8, 72);
552                                 free(msg);
553                                 return DITEM_FAILURE | DITEM_RECREATE;
554                         }
555                 }
556         }
557
558 #ifdef VERBOSE
559         if (copymode)
560                 asprintf(&msg, "Copied timezone file from %s to " 
561                          _PATH_LOCALTIME, filename);
562         else
563                 asprintf(&msg, "Created symbolic link from " _PATH_LOCALTIME
564                          " to %s", filename);
565
566         dialog_mesgbox("Done", msg, 8, 72);
567         free(msg);
568 #endif
569         return DITEM_LEAVE_MENU;
570 }
571
572 static int
573 confirm_zone(const char *filename)
574 {
575         char *msg;
576         struct tm *tm;
577         time_t t = time(0);
578         int rv;
579         
580         if (setenv("TZ", filename, 1) == -1)
581                 err(1, "setenv: cannot set TZ=%s", filename);
582         tzset();
583         tm = localtime(&t);
584
585         asprintf(&msg, "Does the abbreviation `%s' look reasonable?",
586                  tm->tm_zone);
587         rv = !dialog_yesno("Confirmation", msg, 4, 72);
588         free(msg);
589         return rv;
590 }
591
592 static int
593 set_zone_multi(dialogMenuItem *dmi)
594 {
595         char *fn;
596         struct zone *zp = dmi->data;
597         int rv;
598
599         if (!confirm_zone(zp->filename))
600                 return DITEM_FAILURE | DITEM_RECREATE;
601
602         asprintf(&fn, "%s/%s", _PATH_ZONEINFO, zp->filename);
603         rv = install_zone_file(fn);
604         free(fn);
605         return rv;
606 }
607
608 static int
609 set_zone_whole_country(dialogMenuItem *dmi)
610 {
611         char *fn;
612         struct country *cp = dmi->data;
613         int rv;
614
615         if (!confirm_zone(cp->filename))
616                 return DITEM_FAILURE | DITEM_RECREATE;
617
618         asprintf(&fn, "%s/%s", _PATH_ZONEINFO, cp->filename);
619         rv = install_zone_file(fn);
620         free(fn);
621         return rv;
622 }
623
624 static void
625 usage(void)
626 {
627         fprintf(stderr, "usage: tzsetup [-n]\n");
628         exit(1);
629 }
630
631 int
632 main(int argc, char **argv)
633 {
634         int c, fd;
635         int (*dialog_utc)(unsigned char *, unsigned char *, int, int);
636
637         dialog_utc = dialog_noyes;
638
639         while ((c = getopt(argc, argv, "n")) != -1) {
640                 switch(c) {
641                 case 'n':
642                         reallydoit = 0;
643                         break;
644
645                 default:
646                         usage();
647                 }
648         }
649
650         if (argc - optind > 1)
651                 usage();
652
653         /* Override the user-supplied umask. */
654         umask(S_IWGRP|S_IWOTH);
655
656         read_iso3166_table();
657         read_zones();
658         sort_countries();
659         make_menus();
660
661         init_dialog();
662         if (!dialog_utc("Select local or UTC (Greenwich Mean Time) clock",
663                         "Is this machine's CMOS clock set to UTC?  If it is set to local time,\n"
664                         "or you don't know, please choose NO here!", 7, 72)) {
665                 if (reallydoit)
666                         unlink(_PATH_WALL_CMOS_CLOCK);
667         } else {
668                 if (reallydoit) {
669                         fd = open(_PATH_WALL_CMOS_CLOCK,
670                                   O_WRONLY|O_CREAT|O_TRUNC,
671                                   S_IRUSR|S_IRGRP|S_IROTH);
672                         if (fd < 0)
673                                 err(1, "create %s", _PATH_WALL_CMOS_CLOCK);
674                         close(fd);
675                 }
676         }
677         dialog_clear_norefresh();
678         if (optind == argc - 1) {
679                 char *msg;
680                 asprintf(&msg, "\nUse the default `%s' zone?", argv[optind]);
681                 if (!dialog_yesno("Default timezone provided", msg, 7, 72)) {
682                         install_zone_file(argv[optind]);
683                         dialog_clear();
684                         end_dialog();
685                         return 0;
686                 }
687                 free(msg);
688                 dialog_clear_norefresh();
689         }
690         dialog_menu("Time Zone Selector", "Select a region", -1, -1, 
691                     NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL);
692
693         dialog_clear();
694         end_dialog();
695         return 0;
696 }
697