2 * Copyright 1990 - 1993, Julianne Frances Haugh
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Julianne F. Haugh nor the names of its contributors
14 * may be used to endorse or promote products derived from this software
15 * without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * newusers - create users from a batch file
31 * newusers creates a collection of entries in /etc/passwd
32 * and related files by reading a passwd-format file and
33 * adding entries in the related directories.
39 RCSID (PKG_VER "$Id: newusers.c,v 1.15 2002/01/05 15:41:43 kloczek Exp $")
40 #include <sys/types.h>
42 #include "prototypes.h"
49 #include <security/pam_appl.h>
50 #include <security/pam_misc.h>
65 /* local function prototypes */
66 static void usage (void);
67 static int add_group (const char *, const char *, gid_t *);
68 static int add_user (const char *, const char *, uid_t *, gid_t);
69 static void update_passwd (struct passwd *, const char *);
70 static int add_passwd (struct passwd *, const char *);
73 * usage - display usage message and exit
76 static void usage (void)
78 fprintf (stderr, _("Usage: %s [input]\n"), Prog);
83 * add_group - create a new group or add a user to an existing group
86 static int add_group (const char *name, const char *gid, gid_t * ngid)
88 const struct passwd *pwd;
89 const struct group *grp;
95 * Start by seeing if the named group already exists. This will be
96 * very easy to deal with if it does.
99 if ((grp = gr_locate (gid))) {
102 *ngid = grent.gr_gid;
103 for (i = 0; grent.gr_mem[i] != (char *) 0; i++)
104 if (strcmp (grent.gr_mem[i], name) == 0)
108 (char **) xmalloc (sizeof (char *) * (i + 2));
109 memcpy (grent.gr_mem, grp->gr_mem,
110 sizeof (char *) * (i + 2));
111 grent.gr_mem[i] = xstrdup (name);
112 grent.gr_mem[i + 1] = (char *) 0;
114 return !gr_update (&grent);
118 * The group did not exist, so I try to figure out what the GID is
119 * going to be. The gid parameter is probably "", meaning I figure
120 * out the GID from the password file. I want the UID and GID to
121 * match, unless the GID is already used.
124 if (gid[0] == '\0') {
126 for (pw_rewind (); (pwd = pw_next ());) {
127 if (pwd->pw_uid >= i)
130 for (gr_rewind (); (grp = gr_next ());) {
131 if (grp->gr_gid == i) {
136 } else if (gid[0] >= '0' && gid[0] <= '9') {
139 * The GID is a number, which means either this is a brand
140 * new group, or an existing group. For existing groups I
141 * just add myself as a member, just like I did earlier.
145 for (gr_rewind (); (grp = gr_next ());)
146 if (grp->gr_gid == i)
150 * The last alternative is that the GID is a name which is
151 * not already the name of an existing group, and I need to
152 * figure out what group ID that group name is going to
159 * If I don't have a group ID by now, I'll go get the next one.
163 for (i = 100, gr_rewind (); (grp = gr_next ());)
164 if (grp->gr_gid >= i)
169 * Now I have all of the fields required to create the new group.
172 if (gid[0] && (gid[0] <= '0' || gid[0] >= '9'))
173 grent.gr_name = xstrdup (gid);
175 grent.gr_name = xstrdup (name);
177 grent.gr_passwd = "x"; /* XXX warning: const */
179 members[0] = xstrdup (name);
180 members[1] = (char *) 0;
181 grent.gr_mem = members;
183 *ngid = grent.gr_gid;
184 return !gr_update (&grent);
188 * add_user - create a new user ID
192 add_user (const char *name, const char *uid, uid_t * nuid, gid_t gid)
194 const struct passwd *pwd;
199 * The first guess for the UID is either the numerical UID that the
200 * caller provided, or the next available UID.
203 if (uid[0] >= '0' && uid[0] <= '9') {
205 } else if (uid[0] && (pwd = pw_locate (uid))) {
209 for (pw_rewind (); (pwd = pw_next ());)
210 if (pwd->pw_uid >= i)
215 * I don't want to fill in the entire password structure members
216 * JUST YET, since there is still more data to be added. So, I fill
217 * in the parts that I have.
220 pwent.pw_name = xstrdup (name);
221 pwent.pw_passwd = "x"; /* XXX warning: const */
226 pwent.pw_comment = "";
233 pwent.pw_gecos = ""; /* XXX warning: const */
234 pwent.pw_dir = ""; /* XXX warning: const */
235 pwent.pw_shell = ""; /* XXX warning: const */
238 return !pw_update (&pwent);
241 static void update_passwd (struct passwd *pwd, const char *passwd)
243 pwd->pw_passwd = pw_encrypt (passwd, crypt_make_salt ());
245 if (strlen (pwd->pw_age) == 4) {
246 static char newage[5];
247 extern char *l64a ();
249 strcpy (newage, pwd->pw_age);
250 strcpy (newage + 2, l64a (time ((time_t *) 0) / WEEK));
251 pwd->pw_age = newage;
257 * add_passwd - add or update the encrypted password
260 static int add_passwd (struct passwd *pwd, const char *passwd)
263 const struct spwd *sp;
268 * In the case of regular password files, this is real easy - pwd
269 * points to the entry in the password file. Shadow files are
270 * harder since there are zillions of things to do ...
274 update_passwd (pwd, passwd);
279 * Do the first and easiest shadow file case. The user already
280 * exists in the shadow password file.
283 if ((sp = spw_locate (pwd->pw_name))) {
285 spent.sp_pwdp = pw_encrypt (passwd, crypt_make_salt ());
286 return !spw_update (&spent);
290 * Pick the next easiest case - the user has an encrypted password
291 * which isn't equal to "x". The password was set to "x" earlier
292 * when the entry was created, so this user would have to have had
293 * the password set someplace else.
296 if (strcmp (pwd->pw_passwd, "x") != 0) {
297 update_passwd (pwd, passwd);
302 * Now the really hard case - I need to create an entirely new
303 * shadow password file entry.
306 spent.sp_namp = pwd->pw_name;
307 spent.sp_pwdp = pw_encrypt (passwd, crypt_make_salt ());
308 spent.sp_lstchg = time ((time_t *) 0) / SCALE;
309 spent.sp_min = getdef_num ("PASS_MIN_DAYS", 0);
310 /* 10000 is infinity this week */
311 spent.sp_max = getdef_num ("PASS_MAX_DAYS", 10000);
312 spent.sp_warn = getdef_num ("PASS_WARN_AGE", -1);
314 spent.sp_expire = -1;
317 return !spw_update (&spent);
322 static struct pam_conv conv = {
328 int main (int argc, char **argv)
334 const struct passwd *pw;
342 pam_handle_t *pamh = NULL;
343 struct passwd *pampw;
347 Prog = Basename (argv[0]);
349 setlocale (LC_ALL, "");
350 bindtextdomain (PACKAGE, LOCALEDIR);
351 textdomain (PACKAGE);
354 retval = PAM_SUCCESS;
356 pampw = getpwuid (getuid ());
358 retval = PAM_USER_UNKNOWN;
361 if (retval == PAM_SUCCESS) {
363 pam_start ("shadow", pampw->pw_name, &conv, &pamh);
366 if (retval == PAM_SUCCESS) {
367 retval = pam_authenticate (pamh, 0);
368 if (retval != PAM_SUCCESS) {
369 pam_end (pamh, retval);
373 if (retval == PAM_SUCCESS) {
374 retval = pam_acct_mgmt (pamh, 0);
375 if (retval != PAM_SUCCESS) {
376 pam_end (pamh, retval);
380 if (retval != PAM_SUCCESS) {
381 fprintf (stderr, _("%s: PAM authentication failed\n"),
387 if (argc > 1 && argv[1][0] == '-')
391 if (!freopen (argv[1], "r", stdin)) {
392 snprintf (buf, sizeof buf, "%s: %s", Prog,
400 * Lock the password files and open them for update. This will bring
401 * all of the entries into memory where they may be searched for an
402 * modified, or new entries added. The password file is the key - if
403 * it gets locked, assume the others can be locked right away.
407 fprintf (stderr, _("%s: can't lock /etc/passwd.\n"), Prog);
411 is_shadow = spw_file_present ();
413 if ((is_shadow && !spw_lock ()) || !gr_lock ())
419 _("%s: can't lock files, try again later\n"),
429 if (!pw_open (O_RDWR) || (is_shadow && !spw_open (O_RDWR))
430 || !gr_open (O_RDWR))
432 if (!pw_open (O_RDWR) || !gr_open (O_RDWR))
435 fprintf (stderr, _("%s: can't open files\n"), Prog);
446 * Read each line. The line has the same format as a password file
447 * entry, except that certain fields are not contrained to be
448 * numerical values. If a group ID is entered which does not already
449 * exist, an attempt is made to allocate the same group ID as the
450 * numerical user ID. Should that fail, the next available group ID
451 * over 100 is allocated. The pw_gid field will be updated with that
455 while (fgets (buf, sizeof buf, stdin) != (char *) 0) {
457 if ((cp = strrchr (buf, '\n'))) {
460 fprintf (stderr, _("%s: line %d: line too long\n"),
467 * Break the string into fields and screw around with them.
468 * There MUST be 7 colon separated fields, although the
469 * values aren't that particular.
472 for (cp = buf, nfields = 0; nfields < 7; nfields++) {
473 fields[nfields] = cp;
474 if ((cp = strchr (cp, ':')))
480 fprintf (stderr, _("%s: line %d: invalid line\n"),
486 * Now the fields are processed one by one. The first field
487 * to be processed is the group name. A new group will be
488 * created if the group name is non-numeric and does not
489 * already exist. The named user will be the only member. If
490 * there is no named group to be a member of, the UID will
491 * be figured out and that value will be a candidate for a
492 * new group, if that group ID exists, a whole new group ID
496 if (!(pw = pw_locate (fields[0])) &&
497 add_group (fields[0], fields[3], &gid)) {
499 _("%s: line %d: can't create GID\n"),
506 * Now we work on the user ID. It has to be specified either
507 * as a numerical value, or left blank. If it is a numerical
508 * value, that value will be used, otherwise the next
509 * available user ID is computed and used. After this there
510 * will at least be a (struct passwd) for the user.
513 if (!pw && add_user (fields[0], fields[2], &uid, gid)) {
515 _("%s: line %d: can't create UID\n"),
522 * The password, gecos field, directory, and shell fields
526 if (!(pw = pw_locate (fields[0]))) {
528 _("%s: line %d: cannot find user %s\n"),
529 Prog, line, fields[0]);
535 if (add_passwd (&newpw, fields[1])) {
537 _("%s: line %d: can't update password\n"),
543 newpw.pw_gecos = fields[4];
546 newpw.pw_dir = fields[5];
549 newpw.pw_shell = fields[6];
551 if (newpw.pw_dir[0] && access (newpw.pw_dir, F_OK)) {
552 if (mkdir (newpw.pw_dir,
553 0777 & ~getdef_num ("UMASK", 077)))
555 _("%s: line %d: mkdir failed\n"),
558 (newpw.pw_dir, newpw.pw_uid,
561 _("%s: line %d: chown failed\n"),
566 * Update the password entry with the new changes made.
569 if (!pw_update (&newpw)) {
571 _("%s: line %d: can't update entry\n"),
579 * Any detected errors will cause the entire set of changes to be
580 * aborted. Unlocking the password file will cause all of the
581 * changes to be ignored. Otherwise the file is closed, causing the
582 * changes to be written out all at once, and then unlocked
588 _("%s: error detected, changes ignored\n"), Prog);
598 if (!pw_close () || (is_shadow && !spw_close ()) || !gr_close ())
600 if (!pw_close () || !gr_close ())
603 fprintf (stderr, _("%s: error updating files\n"), Prog);
620 if (retval == PAM_SUCCESS) {
621 retval = pam_chauthtok (pamh, 0);
622 if (retval != PAM_SUCCESS) {
623 pam_end (pamh, retval);
627 if (retval != PAM_SUCCESS) {
628 fprintf (stderr, _("%s: PAM chauthtok failed\n"), Prog);
632 if (retval == PAM_SUCCESS)
633 pam_end (pamh, PAM_SUCCESS);