Merge from vendor branch LESS:
[dragonfly.git] / release / sysinstall / user.c
1 /*
2  * $FreeBSD: src/release/sysinstall/user.c,v 1.15.2.2 2001/07/19 01:33:02 kris Exp $
3  * $DragonFly: src/release/sysinstall/Attic/user.c,v 1.2 2003/06/17 04:27:21 dillon Exp $
4  *
5  * Copyright (c) 1996
6  *      Jörg Wunsch. All rights reserved.
7  *
8  * The basic structure has been taken from tcpip.c, which is:
9  *
10  * Copyright (c) 1995
11  *      Gary J Palmer. All rights reserved.
12  *      Jordan K Hubbard. All rights reserved.
13  *
14  * Redistribution and use in source and binary forms, with or without
15  * modification, are permitted provided that the following conditions
16  * are met:
17  * 1. Redistributions of source code must retain the above copyright
18  *    notice, this list of conditions and the following disclaimer,
19  *    verbatim and that no modifications are made prior to this
20  *    point in the file.
21  * 2. Redistributions in binary form must reproduce the above copyright
22  *    notice, this list of conditions and the following disclaimer in the
23  *    documentation and/or other materials provided with the distribution.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
26  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
27  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
28  * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
31  * OF USE, DATA, LIFE OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
32  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
33  * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35  *
36  */
37
38 #include "sysinstall.h"
39 #include <utmp.h>
40 #include <ctype.h>
41 #include <sys/param.h>
42 #include <sysexits.h>
43
44 /* The help file for the user mgmt screen */
45 #define USER_HELPFILE           "usermgmt"
46
47 /* XXX should they be moved out to sysinstall.h? */
48 #define GNAME_FIELD_LEN 32
49 #define GID_FIELD_LEN 10
50 #define GMEMB_FIELD_LEN 64
51
52 #define UID_FIELD_LEN 10
53 #define UGROUP_FIELD_LEN GNAME_FIELD_LEN
54 #define GECOS_FIELD_LEN 64
55 #define UMEMB_FIELD_LEN GMEMB_FIELD_LEN
56 #define HOMEDIR_FIELD_LEN 48
57 #define SHELL_FIELD_LEN 48
58 #define PASSWD_FIELD_LEN 32
59
60 /* These are nasty, but they make the layout structure a lot easier ... */
61
62 static char gname[GNAME_FIELD_LEN],
63         gid[GID_FIELD_LEN],
64         gmemb[GMEMB_FIELD_LEN],
65         uname[UT_NAMESIZE + 1],
66         passwd[PASSWD_FIELD_LEN],
67         uid[UID_FIELD_LEN],
68         ugroup[UGROUP_FIELD_LEN],
69         gecos[GECOS_FIELD_LEN],
70         umemb[UMEMB_FIELD_LEN],
71         homedir[HOMEDIR_FIELD_LEN],
72         shell[SHELL_FIELD_LEN];
73 #define CLEAR(v)        memset(v, 0, sizeof v)
74
75 static int      okbutton, cancelbutton;
76
77
78 /* What the screen size is meant to be */
79 #define USER_DIALOG_Y           0
80 #define USER_DIALOG_X           8
81 #define USER_DIALOG_WIDTH       COLS - 16
82 #define USER_DIALOG_HEIGHT      LINES - 2
83
84 /* The group configuration menu. */
85 static Layout groupLayout[] = {
86 #define LAYOUT_GNAME            0
87     { 4, 10, 20, GNAME_FIELD_LEN - 1,
88       "Group name:", "The alphanumeric name of the new group (mandatory)",
89       gname, STRINGOBJ, NULL },
90 #define LAYOUT_GID              1
91     { 4, 38, 10, GID_FIELD_LEN - 1,
92       "GID:", "The numerical ID for this group (leave blank for automatic choice)",
93       gid, STRINGOBJ, NULL },
94 #define LAYOUT_GMEMB            2
95     { 11, 10, 40, GMEMB_FIELD_LEN - 1,
96       "Group members:", "Who belongs to this group (i.e., gets access rights for it)",
97       gmemb, STRINGOBJ, NULL },
98 #define LAYOUT_OKBUTTON         3
99     { 18, 15, 0, 0,
100       "OK", "Select this if you are happy with these settings",
101       &okbutton, BUTTONOBJ, NULL },
102 #define LAYOUT_CANCELBUTTON     4
103     { 18, 35, 0, 0,
104       "CANCEL", "Select this if you wish to cancel this screen",
105       &cancelbutton, BUTTONOBJ, NULL },
106     { NULL },
107 };
108
109 /* The user configuration menu. */
110 static Layout userLayout[] = {
111 #define LAYOUT_UNAME            0
112     { 3, 6, UT_NAMESIZE, UT_NAMESIZE + 1,
113       "Login ID:", "The login name of the new user (mandatory)",
114       uname, STRINGOBJ, NULL },
115 #define LAYOUT_UID              1
116     { 3, 23, 8, UID_FIELD_LEN - 1,
117       "UID:", "The numerical ID for this user (leave blank for automatic choice)",
118       uid, STRINGOBJ, NULL },
119 #define LAYOUT_UGROUP           2
120     { 3, 33, 8, UGROUP_FIELD_LEN - 1,
121       "Group:", "The login group name for this user (leave blank for automatic choice)",
122       ugroup, STRINGOBJ, NULL },
123 #define LAYOUT_PASSWD           3
124     { 3, 43, 15, PASSWD_FIELD_LEN - 1,
125       "Password:", "The password for this user (enter this field with care!)",
126       passwd, NO_ECHO_OBJ(STRINGOBJ), NULL },
127 #define LAYOUT_GECOS            4
128     { 8, 6, 33, GECOS_FIELD_LEN - 1,
129       "Full name:", "The user's full name (comment)",
130       gecos, STRINGOBJ, NULL },
131 #define LAYOUT_UMEMB            5
132     { 8, 43, 15, UMEMB_FIELD_LEN - 1,
133       "Member groups:", "The groups this user belongs to (i.e. gets access rights for)",
134       umemb, STRINGOBJ, NULL },
135 #define LAYOUT_HOMEDIR          6
136     { 13, 6, 20, HOMEDIR_FIELD_LEN - 1,
137       "Home directory:", "The user's home directory (leave blank for default)",
138       homedir, STRINGOBJ, NULL },
139 #define LAYOUT_SHELL            7
140     { 13, 29, 29, SHELL_FIELD_LEN - 1,
141       "Login shell:", "The user's login shell (leave blank for default)",
142       shell, STRINGOBJ, NULL },
143 #define LAYOUT_U_OKBUTTON       8
144     { 18, 15, 0, 0,
145       "OK", "Select this if you are happy with these settings",
146         &okbutton, BUTTONOBJ, NULL },
147 #define LAYOUT_U_CANCELBUTTON   9
148     { 18, 35, 0, 0,
149       "CANCEL", "Select this if you wish to cancel this screen",
150       &cancelbutton, BUTTONOBJ, NULL },
151     { NULL },
152 };
153
154 /* whine */
155 static void
156 feepout(char *msg)
157 {
158     beep();
159     dialog_notify(msg);
160 }
161
162 /* Check for the settings on the screen. */
163
164 static int
165 verifyGroupSettings(void)
166 {
167     char tmp[256], *cp;
168     long lgid;
169
170     if (strlen(gname) == 0) {
171         feepout("The group name field must not be empty!");
172         return 0;
173     }
174     snprintf(tmp, 256, "pw group show -q -n %s > /dev/null", gname);
175     if (vsystem("%s", tmp) == 0) {
176         feepout("This group name is already in use.");
177         return 0;
178     }
179     if (strlen(gid) > 0) {
180         lgid = strtol(gid, &cp, 10);
181         if (lgid < 0 || lgid >= 65536 || (*cp != '\0' && !isspace(*cp))) {
182             feepout("The GID must be a number between 1 and 65535.");
183             return 0;
184         }
185     }
186     if (strlen(gmemb) > 0) {
187         if (strpbrk(gmemb, " \t") != NULL) {
188             feepout("The group member list must not contain any whitespace;\n"
189                     "use commas to separate the names.");
190             return 0;
191         }
192 #ifndef notyet  /* XXX */
193         feepout("Sorry, the group member list feature\n"
194                 "is currently not yet implemented.");
195         return 0;
196 #endif
197     }
198
199     return 1;
200 }
201
202 /*
203  * Ask pw(8) to fill in the blanks for us.
204  * Works solely on the global variables.
205  */
206
207 static void
208 completeGroup(void)
209 {
210     int pfd[2], i;
211     char tmp[256], *cp;
212     ssize_t l;
213     size_t amnt;
214     pid_t pid;
215     char *vec[4] =
216     {
217         "pw", "group", "next", 0
218     };
219
220     pipe (pfd);
221     if ((pid = fork()) == 0)
222     {
223         /* The kiddy. */
224         dup2(pfd[1], 1);
225         dup2(pfd[1], 2);
226         for (i = getdtablesize(); i > 2; i--)
227             close(i);
228
229         execv("/usr/sbin/pw", vec);
230         msgDebug("Cannot execv() /usr/sbin/pw.\n");
231         _exit(99);
232     }
233     else
234     {
235         /* The oldie. */
236         close(pfd[1]);
237         amnt = sizeof tmp;
238         i = 0;
239         while((l = read(pfd[0], &tmp[i], amnt)) > 0)
240         {
241             amnt -= l;
242             i += l;
243             if (amnt == 0)
244             {
245                 close(pfd[0]);
246                 break;
247             }
248         }
249         close(pfd[0]);
250         tmp[i] = '\0';
251         waitpid(pid, &i, 0);
252         if (WIFSIGNALED(i) || WEXITSTATUS(i) != 0)
253             /* ignore by now */
254             return;
255         if ((cp = strchr(tmp, '\n')) != NULL)
256             *cp = '\0';
257         strncpy(gid, tmp, sizeof gid);
258     }
259 }
260
261 static void
262 addGroup(WINDOW *ds_win)
263 {
264     char tmp[256];
265     int pfd[2], i;
266     ssize_t l;
267     size_t amnt;
268     pid_t pid;
269     char *vec[8] =
270     {
271         "pw", "group", "add", "-n", 0, "-g", 0, 0
272     };
273 #define VEC_GNAME 4
274 #define VEC_GID 6
275
276     msgNotify("Adding group \"%s\"...", gname);
277
278     pipe (pfd);
279     if ((pid = fork()) == 0)
280     {
281         /* The kiddy. */
282         dup2(pfd[1], 1);
283         dup2(pfd[1], 2);
284         for (i = getdtablesize(); i > 2; i--)
285             close(i);
286
287         vec[VEC_GNAME] = gname;
288
289         if (strlen(gid) > 0)
290             vec[VEC_GID] = gid;
291         else
292             vec[VEC_GID - 1] = 0;
293
294         execv("/usr/sbin/pw", vec);
295         msgDebug("Cannot execv() /usr/sbin/pw.\n");
296         _exit(99);
297     }
298     else
299     {
300         /* The oldie. */
301         close(pfd[1]);
302         amnt = sizeof tmp;
303         i = 0;
304         while((l = read(pfd[0], &tmp[i], amnt)) > 0)
305         {
306             amnt -= l;
307             i += l;
308             if (amnt == 0)
309             {
310                 close(pfd[0]);
311                 break;
312             }
313         }
314         close(pfd[0]);
315         tmp[i] = '\0';
316         waitpid(pid, &i, 0);
317         if (WIFSIGNALED(i))
318             msgDebug("pw(8) exited with signal %d.\n", WTERMSIG(i));
319         else if(WEXITSTATUS(i))
320         {
321             i = 0;
322             if(strncmp(tmp, "pw: ", 4) == 0)
323                 i = 4;
324             tmp[sizeof tmp - 1] = '\0'; /* sanity */
325             msgConfirm("The `pw' command exited with an error status.\n"
326                        "Its error message was:\n\n%s",
327                        &tmp[i]);
328         }
329     }
330 #undef VEC_GNAME
331 #undef VEC_GID
332 }
333
334 int
335 userAddGroup(dialogMenuItem *self)
336 {
337     WINDOW              *ds_win, *save;
338     ComposeObj          *obj = NULL;
339     int                 n = 0, cancel = FALSE, ret;
340     int                 max, firsttime = TRUE;
341
342     if (RunningAsInit && !strstr(variable_get(SYSTEM_STATE), "install")) {
343         msgConfirm("This option may only be used after the system is installed, sorry!");
344         return DITEM_FAILURE;
345     }
346
347     save = savescr();
348     dialog_clear_norefresh();
349     /* We need a curses window */
350     if (!(ds_win = openLayoutDialog(USER_HELPFILE, " User and Group Management ",
351                                     USER_DIALOG_X, USER_DIALOG_Y, USER_DIALOG_WIDTH, USER_DIALOG_HEIGHT))) {
352         beep();
353         msgConfirm("Cannot open addgroup dialog window!!");
354         return(DITEM_FAILURE);
355     }
356
357     /* Draw a group entry box */
358     draw_box(ds_win, USER_DIALOG_Y + 2, USER_DIALOG_X + 8, USER_DIALOG_HEIGHT - 8,
359              USER_DIALOG_WIDTH - 17, dialog_attr, border_attr);
360     wattrset(ds_win, dialog_attr);
361     mvwaddstr(ds_win, USER_DIALOG_Y + 2, USER_DIALOG_X + 22, " Add a new group ");
362
363     CLEAR(gname);
364     CLEAR(gid);
365     CLEAR(gmemb);
366
367     /* Some more initialisation before we go into the main input loop */
368     obj = initLayoutDialog(ds_win, groupLayout, USER_DIALOG_X, USER_DIALOG_Y, &max);
369
370 reenter:
371     cancelbutton = okbutton = 0;
372     if (firsttime) {
373         /* fill in the blanks, well, just the GID */
374         completeGroup();
375         RefreshStringObj(groupLayout[LAYOUT_GID].obj);
376         firsttime = FALSE;
377     }
378
379     while (layoutDialogLoop(ds_win, groupLayout, &obj, &n, max, &cancelbutton, &cancel));
380
381     if (!cancel && !verifyGroupSettings())
382         goto reenter;
383
384     /* Clear this crap off the screen */
385     delwin(ds_win);
386     dialog_clear_norefresh();
387     use_helpfile(NULL);
388
389     if (!cancel) {
390         addGroup(ds_win);
391         ret = DITEM_SUCCESS;
392     }
393     else
394         ret = DITEM_FAILURE;
395     restorescr(save);
396     return ret;
397 }
398
399 /* Check for the settings on the screen. */
400
401 static int
402 verifyUserSettings(WINDOW *ds_win)
403 {
404     char tmp[256], *cp;
405     long luid;
406     WINDOW *save;
407     int rv;
408
409     if (strlen(uname) == 0) {
410         feepout("The user name field must not be empty!");
411         return 0;
412     }
413     snprintf(tmp, 256, "pw user show -q -n %s > /dev/null", uname);
414     if (vsystem("%s", tmp) == 0) {
415         feepout("This user name is already in use.");
416         return 0;
417     }
418     if (strlen(uid) > 0) {
419         luid = strtol(uid, &cp, 10);
420         if (luid < 0 || luid >= 65536 || (*cp != '\0' && !isspace(*cp))) {
421             feepout("The UID must be a number between 1 and 65535.");
422             return 0;
423         }
424     }
425     if ((homedir[0]!=0) && (homedir[0]!='/')) {
426         feepout("The pathname for home directories must begin with a '/'.");
427         return 0;
428     }
429     if (strlen(shell) > 0) {
430         while((cp = getusershell()) != NULL)
431             if (strcmp(cp, shell) == 0)
432                 break;
433         endusershell();
434         if (cp == NULL) {
435             save = savescr();
436             rv = msgYesNo("Warning:\n\n"
437                           "The requested shell \"%s\" is not\n"
438                           "a valid user shell.\n\n"
439                           "Use it anyway?\n", shell);
440             restorescr(save);
441             wrefresh(ds_win);
442             if (rv != DITEM_SUCCESS)
443                 return 0;
444         }
445         
446     }
447
448     if (strlen(umemb) > 0) {
449         if (strpbrk(umemb, " \t") != NULL) {
450             feepout("The member groups list must not contain any whitespace;\n"
451                     "use commas to separate the names.");
452             return 0;
453         }
454     }
455
456     return 1;
457 }
458
459 /*
460  * Ask pw(8) to fill in the blanks for us.
461  * Works solely on the global variables.
462  */
463
464 static void
465 completeUser(void)
466 {
467     int pfd[2], i;
468     char tmp[256], *cp, *cp2;
469     ssize_t l;
470     size_t amnt;
471     pid_t pid;
472     char *vec[7] =
473     {
474         "pw", "user", "add", "-N", "-n", 0, 0
475     };
476 #define VEC_UNAME 5
477
478     pipe (pfd);
479     if ((pid = fork()) == 0)
480     {
481         /* The kiddy. */
482         dup2(pfd[1], 1);
483         dup2(pfd[1], 2);
484         for (i = getdtablesize(); i > 2; i--)
485             close(i);
486
487         vec[VEC_UNAME] = uname;
488
489         execv("/usr/sbin/pw", vec);
490         msgDebug("Cannot execv() /usr/sbin/pw.\n");
491         _exit(99);
492     }
493     else
494     {
495         /* The oldie. */
496         close(pfd[1]);
497         amnt = sizeof tmp;
498         i = 0;
499         while((l = read(pfd[0], &tmp[i], amnt)) > 0)
500         {
501             amnt -= l;
502             i += l;
503             if (amnt == 0)
504             {
505                 close(pfd[0]);
506                 break;
507             }
508         }
509         close(pfd[0]);
510         tmp[i] = '\0';
511         waitpid(pid, &i, 0);
512         if (WIFSIGNALED(i) || WEXITSTATUS(i) != 0)
513             /* ignore by now */
514             return;
515         if ((cp = strchr(tmp, '\n')) != NULL)
516             *cp = '\0';
517         if ((cp = strchr(tmp, ':')) == NULL || (cp = strchr(++cp, ':')) == NULL)
518             return;
519         cp++;
520         if ((cp2 = strchr(cp, ':')) == NULL)
521             return;
522         *cp2++ = '\0';
523         strncpy(uid, cp, sizeof uid);
524         cp = cp2;
525         if ((cp2 = strchr(cp, ':')) == NULL)
526             return;
527         *cp2++ = '\0';
528 #ifdef notyet /* XXX pw user add -g doesn't accept a numerical GID */
529         strncpy(ugroup, cp, sizeof ugroup);
530 #endif
531         cp = cp2;
532         if ((cp2 = strchr(cp, ':')) == NULL || (cp2 = strchr(++cp2, ':')) == NULL ||
533             (cp = cp2 = strchr(++cp2, ':')) == NULL || (cp2 = strchr(++cp2, ':')) == NULL)
534             return;
535         *cp2++ = '\0';
536         cp++;
537         strncpy(gecos, cp, sizeof gecos);
538         cp = cp2;
539         if ((cp2 = strchr(cp, ':')) == NULL)
540             return;
541         *cp2++ = '\0';
542         if (*cp2)
543             strncpy(shell, cp2, sizeof shell);
544     }
545 #undef VEC_UNAME
546 }
547
548 static void
549 addUser(WINDOW *ds_win)
550 {
551     char tmp[256], *msg;
552     int pfd[2], ipfd[2], i, j;
553     ssize_t l;
554     size_t amnt;
555     pid_t pid;
556     /*
557      * Maximal list:
558      * pw user add -m -n uname -g grp -u uid -c comment -d homedir -s shell -G grplist -h 0
559      */
560     char *vec[21] =
561     {
562         "pw", "user", "add", "-m", "-n", /* ... */
563     };
564 #define VEC_UNAME 5
565
566     msgNotify("Adding user \"%s\"...", uname);
567
568     pipe (pfd);
569     pipe (ipfd);
570     if ((pid = fork()) == 0)
571     {
572         /* The kiddy. */
573         dup2(ipfd[0], 0);
574         dup2(pfd[1], 1);
575         dup2(pfd[1], 2);
576         for (i = getdtablesize(); i > 2; i--)
577             close(i);
578
579         vec[i = VEC_UNAME] = uname;
580         i++;
581 #define ADDVEC(var, option) do { if (strlen(var) > 0) { vec[i++] = option; vec[i++] = var; } } while (0)
582         ADDVEC(ugroup, "-g");
583         ADDVEC(uid, "-u");
584         ADDVEC(gecos, "-c");
585         ADDVEC(homedir, "-d");
586         ADDVEC(shell, "-s");
587         ADDVEC(umemb, "-G");
588         if (passwd[0]) {
589             vec[i++] = "-h";
590             vec[i++] = "0";
591         }
592         vec[i] = 0;
593
594         execv("/usr/sbin/pw", vec);
595         msgDebug("Cannot execv() /usr/sbin/pw.\n");
596         _exit(99);
597     }
598     else
599     {
600         /* The oldie. */
601         close(pfd[1]);
602         close(ipfd[0]);
603
604         if (passwd[0])
605             write(ipfd[1], passwd, strlen(passwd));
606         close(ipfd[1]);
607         amnt = sizeof tmp;
608         i = 0;
609         while((l = read(pfd[0], &tmp[i], amnt)) > 0)
610         {
611             amnt -= l;
612             i += l;
613             if (amnt == 0)
614             {
615                 close(pfd[0]);
616                 break;
617             }
618         }
619         close(pfd[0]);
620         tmp[i] = '\0';
621         waitpid(pid, &i, 0);
622         if (WIFSIGNALED(i))
623         {
624             j = WTERMSIG(i);
625             msg = "The `pw' command exited with signal %d.\n";
626             goto sysfail;
627         }
628         else if((j = WEXITSTATUS(i)))
629         {
630             i = 0;
631             if(strncmp(tmp, "pw: ", 4) == 0)
632                 i = 4;
633             tmp[sizeof tmp - 1] = '\0'; /* sanity */
634             if (j == EX_DATAERR || j == EX_NOUSER || j == EX_SOFTWARE)
635                 msgConfirm("The `pw' command exited with an error status.\n"
636                            "Its error message was:\n\n%s",
637                            &tmp[i]);
638             else
639             {
640                 msg = "The `pw' command exited with unexpected status %d.\n";
641         sysfail:
642                 msgDebug(msg, j);
643                 msgDebug("Command stdout and stderr was:\n\n%s", tmp);
644                 msgConfirm(msg, j);
645             }
646         }
647         else if (!passwd[0])
648             msgConfirm("You will need to enter a password for this user\n"
649                        "later, using the passwd(1) command from the shell.\n\n"
650                        "The account for `%s' is currently still disabled.",
651                        uname);
652     }
653 #undef VEC_UNAME
654 #undef ADDVEC
655 }
656
657 int
658 userAddUser(dialogMenuItem *self)
659 {
660     WINDOW              *ds_win, *save;
661     ComposeObj          *obj = NULL;
662     int                 n = 0, cancel = FALSE, ret;
663     int                 max, firsttime = TRUE, filled=0;
664
665     if (RunningAsInit && !strstr(variable_get(SYSTEM_STATE), "install")) {
666         msgConfirm("This option may only be used after the system is installed, sorry!");
667         return DITEM_FAILURE;
668     }
669
670     save = savescr();
671     dialog_clear_norefresh();
672
673     /* We need a curses window */
674     if (!(ds_win = openLayoutDialog(USER_HELPFILE, " User and Group Management ",
675                                     USER_DIALOG_X, USER_DIALOG_Y, USER_DIALOG_WIDTH, USER_DIALOG_HEIGHT))) {
676         beep();
677         msgConfirm("Cannot open adduser dialog window!!");
678         return(DITEM_FAILURE);
679     }
680
681     /* Draw a user entry box */
682     draw_box(ds_win, USER_DIALOG_Y + 1, USER_DIALOG_X + 3, USER_DIALOG_HEIGHT - 6,
683              USER_DIALOG_WIDTH - 6, dialog_attr, border_attr);
684     wattrset(ds_win, dialog_attr);
685     mvwaddstr(ds_win, USER_DIALOG_Y + 1, USER_DIALOG_X + 22, " Add a new user ");
686
687     CLEAR(uname);
688     CLEAR(uid);
689     CLEAR(ugroup);
690     CLEAR(gecos);
691     CLEAR(passwd);
692     CLEAR(umemb);
693     CLEAR(homedir);
694     CLEAR(shell);
695
696     /* Some more initialisation before we go into the main input loop */
697     obj = initLayoutDialog(ds_win, userLayout, USER_DIALOG_X, USER_DIALOG_Y, &max);
698     
699 reenter:
700     cancelbutton = okbutton = 0;
701     if (firsttime) {
702         /* fill in the blanks, well, just the GID */
703         completeUser();
704         RefreshStringObj(userLayout[LAYOUT_UID].obj);
705         RefreshStringObj(userLayout[LAYOUT_UGROUP].obj);
706         RefreshStringObj(userLayout[LAYOUT_GECOS].obj);
707         RefreshStringObj(userLayout[LAYOUT_UMEMB].obj);
708         RefreshStringObj(userLayout[LAYOUT_HOMEDIR].obj);
709         RefreshStringObj(userLayout[LAYOUT_SHELL].obj);
710         firsttime = FALSE;
711     }
712
713     while (layoutDialogLoop(ds_win, userLayout, &obj, &n, max, &cancelbutton, &cancel)) {
714         /* Prevent this from being irritating if user really means NO */
715         if (filled < 3) {
716           if ((uname[0]) && !homedir[0]) {
717               SAFE_STRCPY(homedir,"/home/");
718               strcat(homedir,uname);
719               RefreshStringObj(userLayout[LAYOUT_HOMEDIR].obj);
720               ++filled;
721             }
722         }
723     };
724
725     if (!cancel && !verifyUserSettings(ds_win))
726         goto reenter;
727
728     /* Clear this crap off the screen */
729     delwin(ds_win);
730     dialog_clear_norefresh();
731     use_helpfile(NULL);
732
733     if (!cancel) {
734         addUser(ds_win);
735         ret = DITEM_SUCCESS;
736     }
737     else
738         ret = DITEM_FAILURE;
739     restorescr(save);
740     return ret;
741 }
742