1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright (c) 2013 Gary Mills
  23  *
  24  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
  25  * Use is subject to license terms.
  26  */
  27 
  28 /*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T     */
  29 /*        All Rights Reserved   */
  30 
  31 /*
  32  * Copyright (c) 2013 RackTop Systems.
  33  */
  34 
  35 #include <sys/types.h>
  36 #include <sys/stat.h>
  37 #include <sys/param.h>
  38 #include <stdio.h>
  39 #include <stdlib.h>
  40 #include <ctype.h>
  41 #include <limits.h>
  42 #include <string.h>
  43 #include <userdefs.h>
  44 #include <user_attr.h>
  45 #include <nss_dbdefs.h>
  46 #include <errno.h>
  47 #include <project.h>
  48 #include "users.h"
  49 #include "messages.h"
  50 #include "funcs.h"
  51 
  52 /*
  53  *  usermod [-u uid [-o] | -g group | -G group [[,group]...]
  54  *              | -d dir [-m [-z|Z]]
  55  *              | -s shell | -c comment | -l new_logname]
  56  *              | -f inactive | -e expire ]
  57  *              [ -A authorization [, authorization ...]]
  58  *              [ -P profile [, profile ...]]
  59  *              [ -R role [, role ...]]
  60  *              [ -K key=value ]
  61  *              [ -p project [, project]] login
  62  *
  63  *      This command adds new user logins to the system.  Arguments are:
  64  *
  65  *      uid - an integer less than MAXUID
  66  *      group - an existing group's integer ID or char string name
  67  *      dir - a directory
  68  *      shell - a program to be used as a shell
  69  *      comment - any text string
  70  *      skel_dir - a directory
  71  *      base_dir - a directory
  72  *      rid - an integer less than 2**16 (USHORT)
  73  *      login - a string of printable chars except colon (:)
  74  *      inactive - number of days a login maybe inactive before it is locked
  75  *      expire - date when a login is no longer valid
  76  *      authorization - One or more comma separated authorizations defined
  77  *                      in auth_attr(4).
  78  *      profile - One or more comma separated execution profiles defined
  79  *                in prof_attr(4)
  80  *      role - One or more comma-separated role names defined in user_attr(4)
  81  *      key=value - One or more -K options each specifying a valid user_attr(4)
  82  *              attribute.
  83  *
  84  */
  85 
  86 extern int **valid_lgroup(), isbusy(), get_default_zfs_flags();
  87 extern int valid_uid(), check_perm(), create_home(), move_dir();
  88 extern int valid_expire(), edit_group(), call_passmgmt();
  89 extern projid_t **valid_lproject();
  90 
  91 static uid_t uid;               /* new uid */
  92 static gid_t gid;                       /* gid of new login */
  93 static char *new_logname = NULL;        /* new login name with -l option */
  94 static char *uidstr = NULL;             /* uid from command line */
  95 static char *group = NULL;              /* group from command line */
  96 static char *grps = NULL;               /* multi groups from command line */
  97 static char *dir = NULL;                /* home dir from command line */
  98 static char *shell = NULL;              /* shell from command line */
  99 static char *comment = NULL;            /* comment from command line */
 100 static char *logname = NULL;            /* login name to add */
 101 static char *inactstr = NULL;           /* inactive from command line */
 102 static char *expire = NULL;             /* expiration date from command line */
 103 static char *projects = NULL;           /* project ids from command line */
 104 static char *usertype;
 105 
 106 char *cmdname;
 107 static char gidstring[32], uidstring[32];
 108 char inactstring[10];
 109 
 110 char *
 111 strcpmalloc(str)
 112 char *str;
 113 {
 114         if (str == NULL)
 115                 return (NULL);
 116 
 117         return (strdup(str));
 118 }
 119 struct passwd *
 120 passwd_cpmalloc(opw)
 121 struct passwd *opw;
 122 {
 123         struct passwd *npw;
 124 
 125         if (opw == NULL)
 126                 return (NULL);
 127 
 128 
 129         npw = malloc(sizeof (struct passwd));
 130 
 131         npw->pw_name = strcpmalloc(opw->pw_name);
 132         npw->pw_passwd = strcpmalloc(opw->pw_passwd);
 133         npw->pw_uid = opw->pw_uid;
 134         npw->pw_gid = opw->pw_gid;
 135         npw->pw_age = strcpmalloc(opw->pw_age);
 136         npw->pw_comment = strcpmalloc(opw->pw_comment);
 137         npw->pw_gecos  = strcpmalloc(opw->pw_gecos);
 138         npw->pw_dir = strcpmalloc(opw->pw_dir);
 139         npw->pw_shell = strcpmalloc(opw->pw_shell);
 140 
 141         return (npw);
 142 }
 143 
 144 int
 145 main(argc, argv)
 146 int argc;
 147 char **argv;
 148 {
 149         int ch, ret = EX_SUCCESS, call_pass = 0, oflag = 0, zfs_flags = 0;
 150         int tries, mflag = 0, inact, **gidlist, flag = 0, zflag = 0, Zflag = 0;
 151         boolean_t fail_if_busy = B_FALSE;
 152         char *ptr;
 153         struct passwd *pstruct;         /* password struct for login */
 154         struct passwd *pw;
 155         struct group *g_ptr;    /* validated group from -g */
 156         struct stat statbuf;            /* status buffer for stat */
 157 #ifndef att
 158         FILE *pwf;              /* fille ptr for opened passwd file */
 159 #endif
 160         int warning;
 161         projid_t **projlist;
 162         char **nargv;                   /* arguments for execvp of passmgmt */
 163         int argindex;                   /* argument index into nargv */
 164         userattr_t *ua;
 165         char *val;
 166         int isrole;                     /* current account is role */
 167 
 168         cmdname = argv[0];
 169 
 170         if (geteuid() != 0) {
 171                 errmsg(M_PERM_DENIED);
 172                 exit(EX_NO_PERM);
 173         }
 174 
 175         opterr = 0;                     /* no print errors from getopt */
 176         /* get user type based on the program name */
 177         usertype = getusertype(argv[0]);
 178 
 179         while ((ch = getopt(argc, argv,
 180                                 "c:d:e:f:G:g:l:mzZop:s:u:A:P:R:K:")) != EOF)
 181                 switch (ch) {
 182                 case 'c':
 183                         comment = optarg;
 184                         flag++;
 185                         break;
 186                 case 'd':
 187                         dir = optarg;
 188                         fail_if_busy = B_TRUE;
 189                         flag++;
 190                         break;
 191                 case 'e':
 192                         expire = optarg;
 193                         flag++;
 194                         break;
 195                 case 'f':
 196                         inactstr = optarg;
 197                         flag++;
 198                         break;
 199                 case 'G':
 200                         grps = optarg;
 201                         flag++;
 202                         break;
 203                 case 'g':
 204                         group = optarg;
 205                         fail_if_busy = B_TRUE;
 206                         flag++;
 207                         break;
 208                 case 'l':
 209                         new_logname = optarg;
 210                         fail_if_busy = B_TRUE;
 211                         flag++;
 212                         break;
 213                 case 'm':
 214                         mflag++;
 215                         flag++;
 216                         fail_if_busy = B_TRUE;
 217                         break;
 218                 case 'o':
 219                         oflag++;
 220                         flag++;
 221                         fail_if_busy = B_TRUE;
 222                         break;
 223                 case 'p':
 224                         projects = optarg;
 225                         flag++;
 226                         break;
 227                 case 's':
 228                         shell = optarg;
 229                         flag++;
 230                         break;
 231                 case 'u':
 232                         uidstr = optarg;
 233                         flag++;
 234                         fail_if_busy = B_TRUE;
 235                         break;
 236                 case 'Z':
 237                         Zflag++;
 238                         break;
 239                 case 'z':
 240                         zflag++;
 241                         break;
 242                 case 'A':
 243                         change_key(USERATTR_AUTHS_KW, optarg);
 244                         flag++;
 245                         break;
 246                 case 'P':
 247                         change_key(USERATTR_PROFILES_KW, optarg);
 248                         flag++;
 249                         break;
 250                 case 'R':
 251                         change_key(USERATTR_ROLES_KW, optarg);
 252                         flag++;
 253                         break;
 254                 case 'K':
 255                         change_key(NULL, optarg);
 256                         flag++;
 257                         break;
 258                 default:
 259                 case '?':
 260                         if (is_role(usertype))
 261                                 errmsg(M_MRUSAGE);
 262                         else
 263                                 errmsg(M_MUSAGE);
 264                         exit(EX_SYNTAX);
 265                 }
 266 
 267         if (((!mflag) && (zflag || Zflag)) || (zflag && Zflag) ||
 268             (mflag > 1 && (zflag || Zflag))) {
 269                 if (is_role(usertype))
 270                         errmsg(M_ARUSAGE);
 271                 else
 272                         errmsg(M_AUSAGE);
 273                 exit(EX_SYNTAX);
 274         }
 275 
 276 
 277         if (optind != argc - 1 || flag == 0) {
 278                 if (is_role(usertype))
 279                         errmsg(M_MRUSAGE);
 280                 else
 281                         errmsg(M_MUSAGE);
 282                 exit(EX_SYNTAX);
 283         }
 284 
 285         if ((!uidstr && oflag) || (mflag && !dir)) {
 286                 if (is_role(usertype))
 287                         errmsg(M_MRUSAGE);
 288                 else
 289                         errmsg(M_MUSAGE);
 290                 exit(EX_SYNTAX);
 291         }
 292 
 293         logname = argv[optind];
 294 
 295         /* Determine whether the account is a role or not */
 296         if ((ua = getusernam(logname)) == NULL ||
 297             (val = kva_match(ua->attr, USERATTR_TYPE_KW)) == NULL ||
 298             strcmp(val, USERATTR_TYPE_NONADMIN_KW) != 0)
 299                 isrole = 0;
 300         else
 301                 isrole = 1;
 302 
 303         /* Verify that rolemod is used for roles and usermod for users */
 304         if (isrole != is_role(usertype)) {
 305                 if (isrole)
 306                         errmsg(M_ISROLE);
 307                 else
 308                         errmsg(M_ISUSER);
 309                 exit(EX_SYNTAX);
 310         }
 311 
 312         /* Set the usertype key; defaults to the commandline  */
 313         usertype = getsetdefval(USERATTR_TYPE_KW, usertype);
 314 
 315         if (is_role(usertype)) {
 316                 /* Roles can't have roles */
 317                 if (getsetdefval(USERATTR_ROLES_KW, NULL) != NULL) {
 318                         errmsg(M_MRUSAGE);
 319                         exit(EX_SYNTAX);
 320                 }
 321                 /* If it was an ordinary user, delete its roles */
 322                 if (!isrole)
 323                         change_key(USERATTR_ROLES_KW, "");
 324         }
 325 
 326 #ifdef att
 327         pw = getpwnam(logname);
 328 #else
 329         /*
 330          * Do this with fgetpwent to make sure we are only looking on local
 331          * system (since passmgmt only works on local system).
 332          */
 333         if ((pwf = fopen("/etc/passwd", "r")) == NULL) {
 334                 errmsg(M_OOPS, "open", "/etc/passwd");
 335                 exit(EX_FAILURE);
 336         }
 337         while ((pw = fgetpwent(pwf)) != NULL)
 338                 if (strcmp(pw->pw_name, logname) == 0)
 339                         break;
 340 
 341         fclose(pwf);
 342 #endif
 343 
 344         if (pw == NULL) {
 345                 char            pwdb[NSS_BUFLEN_PASSWD];
 346                 struct passwd   pwd;
 347 
 348                 if (getpwnam_r(logname, &pwd, pwdb, sizeof (pwdb)) == NULL) {
 349                         /* This user does not exist. */
 350                         errmsg(M_EXIST, logname);
 351                         exit(EX_NAME_NOT_EXIST);
 352                 } else {
 353                         /* This user exists in non-local name service. */
 354                         errmsg(M_NONLOCAL, logname);
 355                         exit(EX_NOT_LOCAL);
 356                 }
 357         }
 358 
 359         pstruct = passwd_cpmalloc(pw);
 360 
 361         /*
 362          * We can't modify a logged in user if any of the following
 363          * are being changed:
 364          * uid (-u & -o), group (-g), home dir (-m), loginname (-l).
 365          * If none of those are specified it is okay to go ahead
 366          * some types of changes only take effect on next login, some
 367          * like authorisations and profiles take effect instantly.
 368          * One might think that -K type=role should require that the
 369          * user not be logged in, however this would make it very
 370          * difficult to make the root account a role using this command.
 371          */
 372         if (isbusy(logname)) {
 373                 if (fail_if_busy) {
 374                         errmsg(M_BUSY, logname, "change");
 375                         exit(EX_BUSY);
 376                 }
 377                 warningmsg(WARN_LOGGED_IN, logname);
 378         }
 379 
 380         if (new_logname && strcmp(new_logname, logname)) {
 381                 switch (valid_login(new_logname, (struct passwd **)NULL,
 382                         &warning)) {
 383                 case INVALID:
 384                         errmsg(M_INVALID, new_logname, "login name");
 385                         exit(EX_BADARG);
 386                         /*NOTREACHED*/
 387 
 388                 case NOTUNIQUE:
 389                         errmsg(M_USED, new_logname);
 390                         exit(EX_NAME_EXISTS);
 391                         /*NOTREACHED*/
 392 
 393                 case LONGNAME:
 394                         errmsg(M_TOO_LONG, new_logname);
 395                         exit(EX_BADARG);
 396                         /*NOTREACHED*/
 397 
 398                 default:
 399                         call_pass = 1;
 400                         break;
 401                 }
 402                 if (warning)
 403                         warningmsg(warning, logname);
 404         }
 405 
 406         if (uidstr) {
 407                 /* convert uidstr to integer */
 408                 errno = 0;
 409                 uid = (uid_t)strtol(uidstr, &ptr, (int)10);
 410                 if (*ptr || errno == ERANGE) {
 411                         errmsg(M_INVALID, uidstr, "user id");
 412                         exit(EX_BADARG);
 413                 }
 414 
 415                 if (uid != pstruct->pw_uid) {
 416                         switch (valid_uid(uid, NULL)) {
 417                         case NOTUNIQUE:
 418                                 if (!oflag) {
 419                                         /* override not specified */
 420                                         errmsg(M_UID_USED, uid);
 421                                         exit(EX_ID_EXISTS);
 422                                 }
 423                                 break;
 424                         case RESERVED:
 425                                 errmsg(M_RESERVED, uid);
 426                                 break;
 427                         case TOOBIG:
 428                                 errmsg(M_TOOBIG, "uid", uid);
 429                                 exit(EX_BADARG);
 430                                 break;
 431                         }
 432 
 433                         call_pass = 1;
 434 
 435                 } else {
 436                         /* uid's the same, so don't change anything */
 437                         uidstr = NULL;
 438                         oflag = 0;
 439                 }
 440 
 441         } else uid = pstruct->pw_uid;
 442 
 443         if (group) {
 444                 switch (valid_group(group, &g_ptr, &warning)) {
 445                 case INVALID:
 446                         errmsg(M_INVALID, group, "group id");
 447                         exit(EX_BADARG);
 448                         /*NOTREACHED*/
 449                 case TOOBIG:
 450                         errmsg(M_TOOBIG, "gid", group);
 451                         exit(EX_BADARG);
 452                         /*NOTREACHED*/
 453                 case UNIQUE:
 454                         errmsg(M_GRP_NOTUSED, group);
 455                         exit(EX_NAME_NOT_EXIST);
 456                         /*NOTREACHED*/
 457                 case RESERVED:
 458                         gid = (gid_t)strtol(group, &ptr, (int)10);
 459                         errmsg(M_RESERVED_GID, gid);
 460                         break;
 461                 }
 462                 if (warning)
 463                         warningmsg(warning, group);
 464 
 465                 if (g_ptr != NULL)
 466                         gid = g_ptr->gr_gid;
 467                 else
 468                         gid = pstruct->pw_gid;
 469 
 470                 /* call passmgmt if gid is different, else ignore group */
 471                 if (gid != pstruct->pw_gid)
 472                         call_pass = 1;
 473                 else group = NULL;
 474 
 475         } else gid = pstruct->pw_gid;
 476 
 477         if (grps && *grps) {
 478                 if (!(gidlist = valid_lgroup(grps, gid)))
 479                         exit(EX_BADARG);
 480         } else
 481                 gidlist = (int **)0;
 482 
 483         if (projects && *projects) {
 484                 if (! (projlist = valid_lproject(projects)))
 485                         exit(EX_BADARG);
 486         } else
 487                 projlist = (projid_t **)0;
 488 
 489         if (dir) {
 490                 if (REL_PATH(dir)) {
 491                         errmsg(M_RELPATH, dir);
 492                         exit(EX_BADARG);
 493                 }
 494                 if (strcmp(pstruct->pw_dir, dir) == 0) {
 495                         /* home directory is the same so ignore dflag & mflag */
 496                         dir = NULL;
 497                         mflag = 0;
 498                 } else call_pass = 1;
 499         }
 500 
 501         if (mflag) {
 502                 if (stat(dir, &statbuf) == 0) {
 503                         /* Home directory exists */
 504                         if (check_perm(statbuf, pstruct->pw_uid,
 505                             pstruct->pw_gid, S_IWOTH|S_IXOTH) != 0) {
 506                                 errmsg(M_NO_PERM, logname, dir);
 507                                 exit(EX_NO_PERM);
 508                         }
 509 
 510                 } else {
 511                         zfs_flags = get_default_zfs_flags();
 512                         if (zflag || mflag > 1)
 513                                 zfs_flags |= CHANGE_ZFS_FS;
 514                         else if (Zflag)
 515                                 zfs_flags &= ~CHANGE_ZFS_FS;
 516                         ret = create_home(dir, NULL, uid, gid, zfs_flags);
 517                 }
 518 
 519                 if (ret == EX_SUCCESS)
 520                         ret = move_dir(pstruct->pw_dir, dir,
 521                             logname, zfs_flags);
 522 
 523                 if (ret != EX_SUCCESS)
 524                         exit(ret);
 525         }
 526 
 527         if (shell) {
 528                 if (REL_PATH(shell)) {
 529                         errmsg(M_RELPATH, shell);
 530                         exit(EX_BADARG);
 531                 }
 532                 if (strcmp(pstruct->pw_shell, shell) == 0) {
 533                         /* ignore s option if shell is not different */
 534                         shell = NULL;
 535                 } else {
 536                         if (stat(shell, &statbuf) < 0 ||
 537                             (statbuf.st_mode & S_IFMT) != S_IFREG ||
 538                             (statbuf.st_mode & 0555) != 0555) {
 539 
 540                                 errmsg(M_INVALID, shell, "shell");
 541                                 exit(EX_BADARG);
 542                         }
 543 
 544                         call_pass = 1;
 545                 }
 546         }
 547 
 548         if (comment) {
 549                 /* ignore comment if comment is not changed */
 550                 if (strcmp(pstruct->pw_comment, comment))
 551                         call_pass = 1;
 552                 else
 553                         comment = NULL;
 554         }
 555 
 556         /* inactive string is a positive integer */
 557         if (inactstr) {
 558                 /* convert inactstr to integer */
 559                 inact = (int)strtol(inactstr, &ptr, 10);
 560                 if (*ptr || inact < 0) {
 561                         errmsg(M_INVALID, inactstr, "inactivity period");
 562                         exit(EX_BADARG);
 563                 }
 564                 call_pass = 1;
 565         }
 566 
 567         /* expiration string is a date, newer than today */
 568         if (expire) {
 569                 if (*expire &&
 570                     valid_expire(expire, (time_t *)0) == INVALID) {
 571                         errmsg(M_INVALID, expire, "expiration date");
 572                         exit(EX_BADARG);
 573                 }
 574                 call_pass = 1;
 575         }
 576 
 577         if (nkeys > 0)
 578                 call_pass = 1;
 579 
 580         /* that's it for validations - now do the work */
 581 
 582         if (grps) {
 583                 /* redefine login's supplentary group memberships */
 584                 ret = edit_group(logname, new_logname, gidlist, 1);
 585                 if (ret != EX_SUCCESS) {
 586                         errmsg(M_UPDATE, "modified");
 587                         exit(ret);
 588                 }
 589         }
 590         if (projects) {
 591                 ret = edit_project(logname, (char *)NULL, projlist, 0);
 592                 if (ret != EX_SUCCESS) {
 593                         errmsg(M_UPDATE, "modified");
 594                         exit(ret);
 595                 }
 596         }
 597 
 598 
 599         if (!call_pass) exit(ret);
 600 
 601         /* only get to here if need to call passmgmt */
 602         /* set up arguments to  passmgmt in nargv array */
 603         nargv = malloc((30 + nkeys * 2) * sizeof (char *));
 604 
 605         argindex = 0;
 606         nargv[argindex++] = PASSMGMT;
 607         nargv[argindex++] = "-m";       /* modify */
 608 
 609         if (comment) {  /* comment */
 610                 nargv[argindex++] = "-c";
 611                 nargv[argindex++] = comment;
 612         }
 613 
 614         if (dir) {
 615                 /* flags for home directory */
 616                 nargv[argindex++] = "-h";
 617                 nargv[argindex++] = dir;
 618         }
 619 
 620         if (group) {
 621                 /* set gid flag */
 622                 nargv[argindex++] = "-g";
 623                 (void) sprintf(gidstring, "%u", gid);
 624                 nargv[argindex++] = gidstring;
 625         }
 626 
 627         if (shell) {    /* shell */
 628                 nargv[argindex++] = "-s";
 629                 nargv[argindex++] = shell;
 630         }
 631 
 632         if (inactstr) {
 633                 nargv[argindex++] = "-f";
 634                 nargv[argindex++] = inactstr;
 635         }
 636 
 637         if (expire) {
 638                 nargv[argindex++] = "-e";
 639                 nargv[argindex++] = expire;
 640         }
 641 
 642         if (uidstr) {   /* set uid flag */
 643                 nargv[argindex++] = "-u";
 644                 (void) sprintf(uidstring, "%u", uid);
 645                 nargv[argindex++] = uidstring;
 646         }
 647 
 648         if (oflag) nargv[argindex++] = "-o";
 649 
 650         if (new_logname) {      /* redefine login name */
 651                 nargv[argindex++] = "-l";
 652                 nargv[argindex++] = new_logname;
 653         }
 654 
 655         if (nkeys > 0)
 656                 addkey_args(nargv, &argindex);
 657 
 658         /* finally - login name */
 659         nargv[argindex++] = logname;
 660 
 661         /* set the last to null */
 662         nargv[argindex++] = NULL;
 663 
 664         /* now call passmgmt */
 665         ret = PEX_FAILED;
 666         for (tries = 3; ret != PEX_SUCCESS && tries--; ) {
 667                 switch (ret = call_passmgmt(nargv)) {
 668                 case PEX_SUCCESS:
 669                 case PEX_BUSY:
 670                         break;
 671 
 672                 case PEX_HOSED_FILES:
 673                         errmsg(M_HOSED_FILES);
 674                         exit(EX_INCONSISTENT);
 675                         break;
 676 
 677                 case PEX_SYNTAX:
 678                 case PEX_BADARG:
 679                         /* should NEVER occur that passmgmt usage is wrong */
 680                         if (is_role(usertype))
 681                                 errmsg(M_MRUSAGE);
 682                         else
 683                                 errmsg(M_MUSAGE);
 684                         exit(EX_SYNTAX);
 685                         break;
 686 
 687                 case PEX_BADUID:
 688                         /* uid in use - shouldn't happen print message anyway */
 689                         errmsg(M_UID_USED, uid);
 690                         exit(EX_ID_EXISTS);
 691                         break;
 692 
 693                 case PEX_BADNAME:
 694                         /* invalid loname */
 695                         errmsg(M_USED, logname);
 696                         exit(EX_NAME_EXISTS);
 697                         break;
 698 
 699                 default:
 700                         errmsg(M_UPDATE, "modified");
 701                         exit(ret);
 702                         break;
 703                 }
 704         }
 705         if (tries == 0) {
 706                 errmsg(M_UPDATE, "modified");
 707         }
 708 
 709         exit(ret);
 710         /*NOTREACHED*/
 711 }