1 /* $NetBSD: configmenu.c,v 1.26 2026/06/26 19:03:49 martin Exp $ */ 2 3 /*- 4 * Copyright (c) 2012 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Jeffrey C. Rizzo 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 /* configmenu.c -- post-installation system configuration menu. */ 33 34 #include <stdio.h> 35 #include <curses.h> 36 #include <unistd.h> 37 #include <errno.h> 38 #include <time.h> 39 #include "defs.h" 40 #include "msg_defs.h" 41 #include "menu_defs.h" 42 43 44 static int set_network(struct menudesc*, void *); 45 static int set_timezone_menu(struct menudesc *, void *); 46 static int set_root_shell(struct menudesc *, void *); 47 static int change_root_password(struct menudesc *, void *); 48 static int add_new_user(struct menudesc *, void *); 49 #if CHECK_ENTROPY 50 static int add_entropy(struct menudesc *, void *); 51 #endif 52 static int set_binpkg(struct menudesc *, void *); 53 static int set_pkgsrc(struct menudesc *, void *); 54 static void config_list_init(void); 55 static void get_rootsh(void); 56 static int toggle_rcvar(struct menudesc *, void *); 57 static int toggle_mdnsd(struct menudesc *, void *); 58 static void configmenu_hdr(struct menudesc *, void *); 59 static int check_root_password(void); 60 static int run_bin_sh(struct menudesc *, void *); 61 static int time_and_date_setup(struct menudesc *menu, void *arg); 62 static void time_and_date_hdr(struct menudesc *, void *); 63 64 char pkgpath[STRSIZE]; 65 char pkgsrcpath[STRSIZE]; 66 67 extern const char *tz_default; 68 char cur_date_time[28]; 69 static const char generic_date_time_fmt[] = "%a, %d %b %Y %X"; 70 71 enum { 72 CONFIGOPT_NETCONF, 73 CONFIGOPT_TZ, 74 CONFIGOPT_SETDATETIME, 75 CONFIGOPT_ROOTSH, 76 CONFIGOPT_ROOTPW, 77 CONFIGOPT_BINPKG, 78 CONFIGOPT_PKGSRC, 79 CONFIGOPT_SSHD, 80 CONFIGOPT_NTPD, 81 CONFIGOPT_NTPDATE, 82 CONFIGOPT_MDNSD, 83 CONFIGOPT_XDM, 84 CONFIGOPT_CGD, 85 CONFIGOPT_LVM, 86 CONFIGOPT_RAIDFRAME, 87 CONFIGOPT_ADDUSER, 88 CONFIGOPT_ADD_ENTROPY, 89 CONFIGOPT_RUN_SH, 90 CONFIGOPT_LAST 91 }; 92 93 typedef struct configinfo { 94 const char *optname; 95 uint opt; 96 const char *rcvar; 97 int (*action)(struct menudesc *, void *); 98 const char *setting; 99 } configinfo; 100 101 102 configinfo config_list[] = { 103 {MSG_Configure_network, CONFIGOPT_NETCONF, NULL, set_network, MSG_configure}, 104 {MSG_timezone, CONFIGOPT_TZ, NULL, set_timezone_menu, NULL}, 105 {MSG_setdatetime, CONFIGOPT_SETDATETIME, NULL, time_and_date_setup, NULL}, 106 {MSG_Root_shell, CONFIGOPT_ROOTSH, NULL, set_root_shell, NULL}, 107 {MSG_change_rootpw, CONFIGOPT_ROOTPW, NULL, change_root_password, MSG_change}, 108 {MSG_enable_binpkg, CONFIGOPT_BINPKG, NULL, set_binpkg, MSG_install}, 109 {MSG_get_pkgsrc, CONFIGOPT_PKGSRC, NULL, set_pkgsrc, MSG_install}, 110 {MSG_enable_sshd, CONFIGOPT_SSHD, "sshd", toggle_rcvar, NULL}, 111 {MSG_enable_ntpd, CONFIGOPT_NTPD, "ntpd", toggle_rcvar, NULL}, 112 {MSG_run_ntpdate, CONFIGOPT_NTPDATE, "ntpdate", toggle_rcvar, NULL}, 113 {MSG_enable_mdnsd, CONFIGOPT_MDNSD, "mdnsd", toggle_mdnsd, NULL}, 114 {MSG_enable_xdm, CONFIGOPT_XDM, "xdm", toggle_rcvar, NULL}, 115 {MSG_enable_cgd, CONFIGOPT_CGD, "cgd", toggle_rcvar, NULL}, 116 {MSG_enable_lvm, CONFIGOPT_LVM, "lvm", toggle_rcvar, NULL}, 117 {MSG_enable_raid, CONFIGOPT_RAIDFRAME, "raidframe", toggle_rcvar, NULL}, 118 {MSG_add_a_user, CONFIGOPT_ADDUSER, NULL, add_new_user, ""}, 119 #if CHECK_ENTROPY 120 {MSG_Configure_entropy, CONFIGOPT_ADD_ENTROPY, NULL, add_entropy, ""}, 121 #endif 122 {MSG_Run_bin_sh, CONFIGOPT_RUN_SH, NULL, run_bin_sh, ""}, 123 {NULL, CONFIGOPT_LAST, NULL, NULL, NULL} 124 }; 125 126 static void 127 config_list_init(void) 128 { 129 int i; 130 131 for (i=0; i < CONFIGOPT_LAST; i++) { 132 switch (i) { 133 case CONFIGOPT_TZ: 134 get_tz_default(); 135 config_list[CONFIGOPT_TZ].setting = tz_default; 136 break; 137 case CONFIGOPT_SETDATETIME: 138 { 139 time_t now = time(NULL); 140 struct tm *lt = localtime(&now); 141 strftime(cur_date_time, sizeof(cur_date_time), 142 generic_date_time_fmt, lt); 143 config_list[CONFIGOPT_SETDATETIME].setting = 144 cur_date_time; 145 break; 146 } 147 case CONFIGOPT_ROOTSH: 148 get_rootsh(); 149 break; 150 case CONFIGOPT_ROOTPW: 151 if (check_root_password()) 152 config_list[i].setting = MSG_password_set; 153 else 154 config_list[i].setting = MSG_empty; 155 break; 156 default: 157 if (config_list[i].rcvar != NULL) { 158 if (check_rcvar(config_list[i].rcvar)) 159 config_list[i].setting = MSG_YES; 160 else 161 config_list[i].setting = MSG_NO; 162 } 163 break; 164 } 165 } 166 } 167 168 static void 169 get_rootsh(void) 170 { 171 static char *buf = NULL; 172 173 if (buf != NULL) 174 free(buf); 175 176 if (target_already_root()) 177 collect(T_OUTPUT, &buf, 178 "/usr/bin/awk -F: '$1==\"root\" { print $NF; exit }'" 179 " /etc/passwd"); 180 else 181 collect(T_OUTPUT, &buf, 182 "chroot %s /usr/bin/awk -F: '$1==\"root\" { print $NF; exit }'" 183 " /etc/passwd",target_prefix()); 184 185 config_list[CONFIGOPT_ROOTSH].setting = (const char *)buf; 186 } 187 188 static void 189 set_config(menudesc *menu, int opt, void *arg) 190 { 191 configinfo **configp = arg; 192 configinfo *config = configp[opt]; 193 const char *optname, *setting; 194 195 optname = config->optname; 196 setting = msg_string(config->setting); 197 198 wprintw(menu->mw, opt == CONFIGOPT_SETDATETIME ? 199 "%-30s %40s" : "%-50s %-10s", msg_string(optname), setting); 200 } 201 202 static int 203 init_config_menu(configinfo *conf, menu_ent *me, configinfo **ce) 204 { 205 int opt; 206 int configopts; 207 208 for (configopts = 0; ; conf++) { 209 opt = conf->opt; 210 if (opt == CONFIGOPT_LAST) 211 break; 212 #if CHECK_ENTROPY 213 if (opt == CONFIGOPT_ADD_ENTROPY && entropy_needed() == 0) 214 continue; 215 #endif 216 *ce = conf; 217 memset(me, 0, sizeof(*me)); 218 me->opt_action = conf->action; 219 configopts++; 220 ce++; 221 me++; 222 } 223 224 return configopts; 225 } 226 227 static int 228 /*ARGSUSED*/ 229 set_timezone_menu(struct menudesc *menu, void *arg) 230 { 231 configinfo **confp = arg; 232 set_timezone(); 233 get_tz_default(); 234 confp[menu->cursel]->setting = tz_default; 235 return 0; 236 } 237 238 static int 239 set_root_shell(struct menudesc *menu, void *arg) 240 { 241 configinfo **confp = arg; 242 243 process_menu(MENU_rootsh, &confp[menu->cursel]->setting); 244 if (run_program(RUN_PROGRESS | RUN_CHROOT, 245 "chpass -s %s root", confp[menu->cursel]->setting) != 0) 246 confp[menu->cursel]->setting = MSG_failed; 247 return 0; 248 } 249 250 static int 251 set_network(struct menudesc *menu, void *arg) 252 { 253 network_up = 0; 254 if (config_network(1)) 255 mnt_net_config(); 256 return 0; 257 } 258 259 static int 260 check_root_password(void) 261 { 262 char *buf; 263 int rval; 264 265 if (target_already_root()) 266 collect(T_OUTPUT, &buf, "getent passwd root | cut -d: -f2"); 267 else 268 collect(T_OUTPUT, &buf, "chroot %s getent passwd root | " 269 "chroot %s cut -d: -f2", 270 target_prefix(), target_prefix()); 271 272 if (logfp) 273 fprintf(logfp,"buf %s strlen(buf) %zu\n", buf, strlen(buf)); 274 275 if (strlen(buf) <= 1) /* newline */ 276 rval = 0; 277 else 278 rval = 1; 279 free(buf); 280 return rval; 281 } 282 283 #if CHECK_ENTROPY 284 static int 285 add_entropy(struct menudesc *menu, void *arg) 286 { 287 do_add_entropy(); 288 return 0; 289 } 290 #endif 291 292 static int 293 add_new_user(struct menudesc *menu, void *arg) 294 { 295 char username[STRSIZE] = ""; 296 int inwheel=0; 297 298 msg_prompt(MSG_addusername, NULL, username, sizeof username -1); 299 if (strlen(username) == 0) 300 return 0; 301 inwheel = ask_yesno(MSG_addusertowheel); 302 ushell = "/bin/csh"; 303 process_menu(MENU_usersh, NULL); 304 if (inwheel) 305 run_program(RUN_PROGRESS | RUN_CHROOT, 306 "/usr/sbin/useradd -m -s %s -G wheel %s", 307 ushell, username); 308 else 309 run_program(RUN_PROGRESS | RUN_CHROOT, 310 "/usr/sbin/useradd -m -s %s %s", ushell, username); 311 run_program(RUN_DISPLAY | RUN_PROGRESS | RUN_CHROOT, 312 "passwd -l %s", username); 313 return 0; 314 } 315 316 void 317 root_pw_setup(void) 318 { 319 msg_display(MSG_force_rootpw); 320 run_program(RUN_DISPLAY | RUN_PROGRESS | RUN_CHROOT | RUN_STDSCR, 321 "passwd -l root"); 322 } 323 324 static int 325 change_root_password(struct menudesc *menu, void *arg) 326 { 327 configinfo **confp = arg; 328 329 msg_display(MSG_rootpw); 330 if (ask_yesno(NULL)) { 331 if (run_program(RUN_DISPLAY | RUN_PROGRESS | RUN_CHROOT, 332 "passwd -l root") == 0) 333 confp[menu->cursel]->setting = MSG_password_set; 334 else 335 confp[menu->cursel]->setting = MSG_failed; 336 } 337 return 0; 338 } 339 340 static int 341 set_binpkg(struct menudesc *menu, void *arg) 342 { 343 configinfo **confp = arg; 344 char additional_pkgs[STRSIZE] = {0}; 345 int allok = 0; 346 arg_rv parm; 347 bool remove_target_resolv = false; 348 349 if (config_network(0)) 350 mnt_net_config(); 351 352 if (access(target_expand("/etc/resolv.conf"), R_OK) != 0) { 353 dup_file_into_target("/etc/resolv.conf"); 354 remove_target_resolv = true; 355 } 356 357 do { 358 parm.rv = -1; 359 parm.arg = additional_pkgs; 360 process_menu(MENU_binpkg, &parm); 361 if (parm.rv == SET_SKIP) { 362 confp[menu->cursel]->setting = MSG_abandoned; 363 return 0; 364 } 365 366 make_url(pkgpath, &pkg, pkg_dir); 367 if (run_program(RUN_DISPLAY | RUN_PROGRESS | RUN_CHROOT, 368 "pkg_add %s/pkgin", pkgpath) == 0) { 369 allok = 1; 370 } 371 } while (allok == 0); 372 373 /* configure pkgin to use $pkgpath as a repository */ 374 replace("/usr/pkg/etc/pkgin/repositories.conf", "s,^[^#].*$,%s,", 375 pkgpath); 376 377 run_program(RUN_DISPLAY | RUN_PROGRESS | RUN_CHROOT, 378 "/usr/pkg/bin/pkgin -y update"); 379 380 if (strlen(additional_pkgs) > 0) 381 run_program(RUN_DISPLAY | RUN_PROGRESS | RUN_CHROOT, 382 "/usr/pkg/bin/pkgin -y install %s", additional_pkgs); 383 384 hit_enter_to_continue(MSG_binpkg_installed, NULL); 385 386 if (remove_target_resolv) 387 unlink(target_expand("/etc/resolv.conf")); 388 389 confp[menu->cursel]->setting = MSG_DONE; 390 return 0; 391 } 392 393 static int 394 set_pkgsrc(struct menudesc *menu, void *arg) 395 { 396 configinfo **confp = arg; 397 distinfo dist; 398 399 dist.name = "pkgsrc"; 400 dist.set = SET_PKGSRC; 401 dist.desc = "source for 3rd-party packages"; 402 dist.marker_file = NULL; 403 404 int status = SET_RETRY; 405 406 do { 407 status = get_pkgsrc(); 408 if (status == SET_OK) { 409 status = extract_file(&dist, 0); 410 continue; 411 } else if (status == SET_SKIP) { 412 confp[menu->cursel]->setting = MSG_abandoned; 413 return 0; 414 } 415 if (!ask_yesno(MSG_retry_pkgsrc_network)) { 416 confp[menu->cursel]->setting = MSG_abandoned; 417 return 1; 418 } 419 } 420 while (status == SET_RETRY); 421 422 confp[menu->cursel]->setting = MSG_DONE; 423 return 0; 424 } 425 426 static int 427 toggle_rcvar(struct menudesc *menu, void *arg) 428 { 429 configinfo **confp = arg; 430 int s; 431 const char *setting, *varname; 432 char pattern[STRSIZE]; 433 char buf[STRSIZE]; 434 char *cp; 435 int found = 0; 436 FILE *fp; 437 438 varname = confp[menu->cursel]->rcvar; 439 440 s = check_rcvar(varname); 441 442 /* we're toggling, so invert the sense */ 443 if (s) { 444 confp[menu->cursel]->setting = MSG_NO; 445 setting = "NO"; 446 } else { 447 confp[menu->cursel]->setting = MSG_YES; 448 setting = "YES"; 449 } 450 451 if (!(fp = fopen(target_expand("/etc/rc.conf"), "r"))) { 452 msg_fmt_display(MSG_openfail, "%s%s", 453 target_expand("/etc/rc.conf"), strerror(errno)); 454 hit_enter_to_continue(NULL, NULL); 455 return 0; 456 } 457 458 while (fgets(buf, sizeof buf, fp) != NULL) { 459 cp = buf + strspn(buf, " \t"); /* Skip initial spaces */ 460 if (strncmp(cp, varname, strlen(varname)) == 0) { 461 cp += strlen(varname); 462 if (*cp != '=') 463 continue; 464 buf[strlen(buf) - 1] = 0; 465 snprintf(pattern, sizeof pattern, 466 "s,^%s$,%s=%s,", 467 buf, varname, setting); 468 found = 1; 469 break; 470 } 471 } 472 473 fclose(fp); 474 475 if (!found) { 476 add_rc_conf("%s=%s\n", varname, setting); 477 if (logfp) { 478 fprintf(logfp, "adding %s=%s\n", varname, setting); 479 fflush(logfp); 480 } 481 } else { 482 if (logfp) { 483 fprintf(logfp, "replacement pattern is %s\n", pattern); 484 fflush(logfp); 485 } 486 replace("/etc/rc.conf", "%s", pattern); 487 } 488 489 return 0; 490 } 491 492 static int 493 toggle_mdnsd(struct menudesc *menu, void *arg) 494 { 495 configinfo **confp = arg; 496 int s; 497 const char *setting, *varname; 498 499 varname = confp[menu->cursel]->rcvar; 500 501 s = check_rcvar(varname); 502 503 /* we're toggling, so invert the sense */ 504 if (s) { 505 confp[menu->cursel]->setting = MSG_NO; 506 setting = "files dns"; 507 } else { 508 confp[menu->cursel]->setting = MSG_YES; 509 setting = "files multicast_dns dns"; 510 } 511 512 if (logfp) { 513 fprintf(logfp, "setting hosts: %s\n", setting); 514 fflush(logfp); 515 } 516 replace("/etc/nsswitch.conf", "s/^hosts:.*/hosts:\t\t%s/", setting); 517 518 toggle_rcvar(menu, arg); 519 520 return 0; 521 } 522 523 static int 524 run_bin_sh(struct menudesc *menu, void *arg) 525 { 526 endwin(); 527 system("/bin/sh -i -E"); 528 529 return 0; 530 } 531 532 static int 533 time_and_date_setup(struct menudesc *menu, void *arg) 534 { 535 do_time_and_date_setup(""); 536 return 0; 537 } 538 539 static void 540 configmenu_hdr(struct menudesc *menu, void *arg) 541 { 542 msg_display(MSG_configmenu); 543 } 544 545 void 546 do_configmenu(struct install_partition_desc *install) 547 { 548 int menu_no; 549 int opts; 550 menu_ent me[CONFIGOPT_LAST]; 551 configinfo *ce[CONFIGOPT_LAST]; 552 553 memset(me, 0, sizeof(me)); 554 555 /* if the target isn't mounted already, figure it out. */ 556 if (install != NULL && target_mounted() == 0) { 557 partman_go = 0; 558 if (find_disks(msg_string(MSG_configure_prior), true) < 0) 559 return; 560 561 if (mount_disks(install) != 0) 562 return; 563 } 564 565 config_list_init(); 566 make_url(pkgpath, &pkg, pkg_dir); 567 opts = init_config_menu(config_list, me, ce); 568 569 wrefresh(curscr); 570 wmove(stdscr, 0, 0); 571 wclear(stdscr); 572 wrefresh(stdscr); 573 574 menu_no = new_menu(NULL, me, opts, 0, -4, 0, 70, 575 MC_SCROLL | MC_NOBOX | MC_DFLTEXIT, 576 configmenu_hdr, set_config, NULL, NULL, 577 MSG_doneconfig); 578 579 process_menu(menu_no, ce); 580 free_menu(menu_no); 581 } 582 583 static const msg struct_tm_opt_names[] = 584 { MSG_year, MSG_month, MSG_day, MSG_hour, MSG_minute, MSG_time_ok }; 585 586 struct date_and_time_menu_arg 587 { 588 const char *header; 589 struct tm new_time; 590 }; 591 592 static void 593 fill_date_time_val(char *buf, size_t sizeof_buf, int opt, 594 const struct date_and_time_menu_arg *time_opts) 595 { 596 switch (opt) { 597 case 0: 598 strftime(buf, sizeof_buf, "%Y", &time_opts->new_time); 599 break; 600 case 1: 601 strftime(buf, sizeof_buf, "%m", &time_opts->new_time); 602 break; 603 case 2: 604 strftime(buf, sizeof_buf, "%d", &time_opts->new_time); 605 break; 606 case 3: 607 strftime(buf, sizeof_buf, "%H", &time_opts->new_time); 608 break; 609 case 4: 610 strftime(buf, sizeof_buf, "%M", &time_opts->new_time); 611 break; 612 } 613 } 614 615 static void 616 update_new_time(menudesc *menu, int opt, void *arg) 617 { 618 char buf[50]; 619 620 fill_date_time_val(buf, sizeof(buf), opt, arg); 621 622 wprintw(menu->mw, "%-30s %-10s", msg_string(struct_tm_opt_names[opt]), 623 buf); 624 } 625 626 static int 627 set_new_time(menudesc *menu, void *arg) 628 { 629 struct date_and_time_menu_arg *ma = arg; 630 struct timeval tv; 631 632 memset(&tv, 0, sizeof(tv)); 633 tv.tv_sec = mktime(&ma->new_time); 634 settimeofday(&tv, NULL); 635 636 return 1; 637 } 638 639 static int 640 time_and_date_update(struct menudesc *menu, void *arg) 641 { 642 struct date_and_time_menu_arg *ma = arg; 643 char buf[50]; 644 struct tm saved_time; 645 646 buf[0] = 0; 647 648 msg_prompt_win(struct_tm_opt_names[menu->cursel], -1, 18, 0, 0, 649 "", buf, sizeof(buf)); 650 651 saved_time = ma->new_time; 652 switch (menu->cursel) { 653 case 0: 654 ma->new_time.tm_year = atoi(buf)-1900; 655 break; 656 case 1: 657 ma->new_time.tm_mon = atoi(buf)-1; 658 break; 659 case 2: 660 ma->new_time.tm_mday = atoi(buf); 661 break; 662 case 3: 663 ma->new_time.tm_hour = atoi(buf); 664 break; 665 case 4: 666 ma->new_time.tm_min = atoi(buf); 667 break; 668 } 669 670 if (mktime(&ma->new_time) == -1) 671 ma->new_time = saved_time; 672 673 return 0; 674 } 675 676 static void 677 time_and_date_hdr(struct menudesc *menuitem, void *arg) 678 { 679 const struct date_and_time_menu_arg *ma = arg; 680 681 strftime(cur_date_time, sizeof(cur_date_time), 682 generic_date_time_fmt, &ma->new_time); 683 684 msg_display_subst(MSG_date_and_time_hdr, 2, ma->header, cur_date_time); 685 } 686 687 void 688 do_time_and_date_setup(const char *header) 689 { 690 int menu_no; 691 #define NUM_DATE_TIME_ITEMS 6 /* year, month, day, hour, min, 692 save */ 693 menu_ent me[CONFIGOPT_LAST]; 694 struct date_and_time_menu_arg menu_arg; 695 time_t now; 696 697 wrefresh(curscr); 698 wmove(stdscr, 0, 0); 699 wclear(stdscr); 700 wrefresh(stdscr); 701 702 memset(me, 0, sizeof(me)); 703 for (size_t i = 0; i < NUM_DATE_TIME_ITEMS-1; i++) 704 me[i].opt_action = time_and_date_update; 705 me[NUM_DATE_TIME_ITEMS-1].opt_action = set_new_time; 706 me[NUM_DATE_TIME_ITEMS-1].opt_flags = OPT_EXIT; 707 708 now = time(NULL); 709 menu_arg.header = msg_string(header); 710 menu_arg.new_time = *localtime(&now); 711 712 menu_no = new_menu(NULL, me, NUM_DATE_TIME_ITEMS, 0, -4, 0, 70, 713 MC_SCROLL | MC_NOBOX | MC_DFLTEXIT, 714 time_and_date_hdr, update_new_time, NULL, NULL, 715 MSG_cancel); 716 717 process_menu(menu_no, &menu_arg); 718 free_menu(menu_no); 719 } 720