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 2007 Sun Microsystems, Inc.  All rights reserved.
  23  * Use is subject to license terms.
  24  */
  25 
  26 /*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T     */
  27 /*        All Rights Reserved   */
  28 
  29 
  30 #pragma ident   "%Z%%M% %I%     %E% SMI"
  31 
  32 #include <sys/param.h>
  33 #include <sys/types.h>
  34 #include <unistd.h>
  35 #include <stdlib.h>
  36 #include <stdio.h>
  37 #include <string.h>
  38 #include <ctype.h>
  39 #include <pwd.h>
  40 #include <errno.h>
  41 #include <locale.h>
  42 #include <limits.h>
  43 
  44 #define BADLINE "Too many/few fields"
  45 #define TOOLONG "Line too long"
  46 #define NONAME  "No group name"
  47 #define BADNAME "Bad character(s) in group name"
  48 #define BADGID  "Invalid GID"
  49 #define NULLNAME "Null login name"
  50 #define NOTFOUND "Logname not found in password file"
  51 #define DUPNAME "Duplicate logname entry"
  52 #define DUPNAME2 "Duplicate logname entry (gid first occurs in passwd entry)"
  53 #define NOMEM   "Out of memory"
  54 #define NGROUPS "Maximum groups exceeded for logname "
  55 #define BLANKLINE "Blank line detected. Please remove line"
  56 #define LONGNAME  "Group name too long"
  57 
  58 int eflag, badchar, baddigit, badlognam, colons, len;
  59 static int longnam = 0;
  60 int code;
  61 
  62 #define MYBUFSIZE (LINE_MAX)    /* max line length including newline and null */
  63 #define NUM_COLONS      3
  64 
  65 char *buf;
  66 char *nptr;
  67 char *cptr;
  68 FILE *fptr;
  69 gid_t gid;
  70 void error(char *msg);
  71 
  72 struct group {
  73         struct group *nxt;
  74         int cnt;
  75         gid_t grp;
  76 };
  77 
  78 struct node {
  79         struct node *next;
  80         int ngroups;
  81         struct group *groups;
  82         char user[1];
  83 };
  84 
  85 void *
  86 emalloc(size_t size)
  87 {
  88         void *vp;
  89         vp = malloc(size);
  90         if (vp == NULL) {
  91                 fprintf(stderr, "%s\n", gettext(NOMEM));
  92                 exit(1);
  93         }
  94         return (vp);
  95 }
  96 
  97 int
  98 main(int argc, char *argv[])
  99 {
 100         struct passwd *pwp;
 101         struct node *root = NULL;
 102         struct node *t;
 103         struct group *gp;
 104         int ngroups_max;
 105         int ngroups = 0;
 106         int listlen;
 107         int i;
 108         int lineno = 0;
 109         char *buf_off, *tmpbuf;
 110         int delim[NUM_COLONS + 1], buf_len, bufsize;
 111 
 112         (void) setlocale(LC_ALL, "");
 113 
 114 #if !defined(TEXT_DOMAIN)       /* Should be defined by cc -D */
 115 #define TEXT_DOMAIN "SYS_TEST"
 116 #endif
 117         (void) textdomain(TEXT_DOMAIN);
 118 
 119         code = 0;
 120         ngroups_max = sysconf(_SC_NGROUPS_MAX);
 121 
 122         if (argc == 1)
 123                 argv[1] = "/etc/group";
 124         else if (argc != 2) {
 125                 fprintf(stderr, gettext("usage: %s filename\n"), *argv);
 126                 exit(1);
 127         }
 128 
 129         if ((fptr = fopen(argv[1], "r")) == NULL) {
 130                 fprintf(stderr, gettext("cannot open file %s: %s\n"), argv[1],
 131                         strerror(errno));
 132                 exit(1);
 133         }
 134 
 135 #ifdef ORIG_SVR4
 136         while ((pwp = getpwent()) != NULL) {
 137                 t = (struct node *)emalloc(sizeof (*t) + strlen(pwp->pw_name));
 138                 t->next = root;
 139                 root = t;
 140                 strcpy(t->user, pwp->pw_name);
 141                 t->ngroups = 1;
 142                 if (!ngroups_max)
 143                         t->groups = NULL;
 144                 else {
 145                         t->groups = (struct group *)
 146                                 emalloc(sizeof (struct group));
 147                         t->groups->grp = pwp->pw_gid;
 148                         t->groups->cnt = 1;
 149                         t->groups->nxt = NULL;
 150                 }
 151         }
 152 #endif
 153 
 154         bufsize = MYBUFSIZE;
 155         if ((buf = malloc(bufsize)) == NULL) {
 156                 (void) fprintf(stderr, gettext(NOMEM));
 157                 exit(1);
 158         }
 159         while (!feof(fptr) && !ferror(fptr)) {
 160                 buf_len = 0;
 161                 buf_off = buf;
 162                 while (fgets(buf_off, (bufsize - buf_len), fptr) != NULL) {
 163                         buf_len += strlen(buf_off);
 164                         if (buf[buf_len - 1] == '\n' || feof(fptr))
 165                                 break;
 166                         tmpbuf = realloc(buf, (bufsize + MYBUFSIZE));
 167                         if (tmpbuf == NULL) {
 168                                 (void) fprintf(stderr, gettext(NOMEM));
 169                                 exit(1);
 170                         }
 171                         bufsize += MYBUFSIZE;
 172                         buf = tmpbuf;
 173                         buf_off = buf + buf_len;
 174                 }
 175                 if (buf_len == 0)
 176                         continue;
 177 
 178                 /* Report error to be consistent with libc */
 179                 if ((buf_len + 1) > LINE_MAX)
 180                         error(TOOLONG);
 181 
 182                 lineno++;
 183                 if (buf[0] == '\n')    /* blank lines are ignored */
 184                 {
 185                         code = 1;               /* exit with error code = 1 */
 186                         eflag = 0;      /* force print of "blank" line */
 187                         fprintf(stderr, "\n%s %d\n", gettext(BLANKLINE),
 188                                 lineno);
 189                         continue;
 190                 }
 191 
 192                 if (buf[buf_len - 1] == '\n') {
 193                         if ((tmpbuf = strdup(buf)) == NULL) {
 194                                 (void) fprintf(stderr, gettext(NOMEM));
 195                                 exit(1);
 196                         }
 197                         tmpbuf[buf_len - 1] = ',';
 198                 } else {
 199                         if ((tmpbuf = malloc(buf_len + 2)) == NULL) {
 200                                 (void) fprintf(stderr, gettext(NOMEM));
 201                                 exit(1);
 202                         }
 203                         (void) strcpy(tmpbuf, buf);
 204                         tmpbuf[buf_len++] = ',';
 205                         tmpbuf[buf_len] = '\0';
 206                 }
 207 
 208                 colons = 0;
 209                 eflag = 0;
 210                 badchar = 0;
 211                 baddigit = 0;
 212                 badlognam = 0;
 213                 gid = 0;
 214 
 215                 ngroups++;      /* Increment number of groups found */
 216                 /* Check that entry is not a nameservice redirection */
 217 
 218                 if (buf[0] == '+' || buf[0] == '-')  {
 219                         /*
 220                          * Should set flag here to allow special case checking
 221                          * in the rest of the code,
 222                          * but for now, we'll just ignore this entry.
 223                          */
 224                         free(tmpbuf);
 225                         continue;
 226                 }
 227 
 228                 /*      Check number of fields  */
 229 
 230                 for (i = 0; buf[i] != NULL; i++) {
 231                         if (buf[i] == ':') {
 232                                 delim[colons] = i;
 233                                 if (++colons > NUM_COLONS)
 234                                         break;
 235                         }
 236                 }
 237                 if (colons != NUM_COLONS) {
 238                         error(BADLINE);
 239                         free(tmpbuf);
 240                         continue;
 241                 }
 242 
 243                 /* check to see that group name is at least 1 character */
 244                 /* and that all characters are lowrcase or digits.      */
 245 
 246                 if (buf[0] == ':')
 247                         error(NONAME);
 248                 else {
 249                         for (i = 0; buf[i] != ':'; i++) {
 250                                 if (i >= LOGNAME_MAX)
 251                                         longnam++;
 252                                 if (!(islower(buf[i]) || isdigit(buf[i])))
 253                                         badchar++;
 254                         }
 255                         if (longnam > 0)
 256                                 error(LONGNAME);
 257                         if (badchar > 0)
 258                                 error(BADNAME);
 259                 }
 260 
 261                 /*      check that GID is numeric and <= 31 bits     */
 262 
 263                 len = (delim[2] - delim[1]) - 1;
 264 
 265                 if (len > 10 || len < 1)
 266                         error(BADGID);
 267                 else {
 268                         for (i = (delim[1]+1); i < delim[2]; i++) {
 269                                 if (! (isdigit(buf[i])))
 270                                         baddigit++;
 271                                 else if (baddigit == 0)
 272                                         gid = gid * 10 + (gid_t)(buf[i] - '0');
 273                                 /* converts ascii GID to decimal */
 274                         }
 275                         if (baddigit > 0)
 276                                 error(BADGID);
 277                         else if (gid > (gid_t)MAXUID)
 278                                 error(BADGID);
 279                 }
 280 
 281                 /*  check that logname appears in the passwd file  */
 282 
 283                 nptr = &tmpbuf[delim[2]];
 284                 nptr++;
 285 
 286                 listlen = strlen(nptr) - 1;
 287 
 288                 while ((cptr = strchr(nptr, ',')) != NULL) {
 289                         *cptr = NULL;
 290                         if (*nptr == NULL) {
 291                                 if (listlen)
 292                                         error(NULLNAME);
 293                                 nptr++;
 294                                 continue;
 295                         }
 296 
 297                         for (t = root; t != NULL; t = t->next) {
 298                                 if (strcmp(t->user, nptr) == 0)
 299                                         break;
 300                         }
 301                         if (t == NULL) {
 302 #ifndef ORIG_SVR4
 303                                 /*
 304                                  * User entry not found, so check if in
 305                                  *  password file
 306                                  */
 307                                 struct passwd *pwp;
 308 
 309                                 if ((pwp = getpwnam(nptr)) == NULL) {
 310 #endif
 311                                         badlognam++;
 312                                         error(NOTFOUND);
 313                                         goto getnext;
 314 #ifndef ORIG_SVR4
 315                                 }
 316 
 317                                 /* Usrname found, so add entry to user-list */
 318                                 t = (struct node *)
 319                                         emalloc(sizeof (*t) + strlen(nptr));
 320                                 t->next = root;
 321                                 root = t;
 322                                 strcpy(t->user, nptr);
 323                                 t->ngroups = 1;
 324                                 if (!ngroups_max)
 325                                         t->groups = NULL;
 326                                 else {
 327                                         t->groups = (struct group *)
 328                                                 emalloc(sizeof (struct group));
 329                                         t->groups->grp = pwp->pw_gid;
 330                                         t->groups->cnt = 1;
 331                                         t->groups->nxt = NULL;
 332                                 }
 333                         }
 334 #endif
 335                         if (!ngroups_max)
 336                                 goto getnext;
 337 
 338                         t->ngroups++;
 339 
 340                         /*
 341                          * check for duplicate logname in group
 342                          */
 343 
 344                         for (gp = t->groups; gp != NULL; gp = gp->nxt) {
 345                                 if (gid == gp->grp) {
 346                                         if (gp->cnt++ == 1) {
 347                                                 badlognam++;
 348                                                 if (gp->nxt == NULL)
 349                                                         error(DUPNAME2);
 350                                                 else
 351                                                         error(DUPNAME);
 352                                         }
 353                                         goto getnext;
 354                                 }
 355                         }
 356 
 357                         gp = (struct group *)emalloc(sizeof (struct group));
 358                         gp->grp = gid;
 359                         gp->cnt = 1;
 360                         gp->nxt = t->groups;
 361                         t->groups = gp;
 362 getnext:
 363                         nptr = ++cptr;
 364                 }
 365                 free(tmpbuf);
 366         }
 367 
 368         if (ngroups == 0) {
 369                 fprintf(stderr, gettext("Group file '%s' is empty\n"), argv[1]);
 370                 code = 1;
 371         }
 372 
 373         if (ngroups_max) {
 374                 for (t = root; t != NULL; t = t->next) {
 375                         if (t->ngroups > ngroups_max) {
 376                                 fprintf(stderr, "\n\n%s%s (%d)\n",
 377                                 NGROUPS, t->user, t->ngroups);
 378                                 code = 1;
 379                         }
 380                 }
 381         }
 382         return (code);
 383 }
 384 
 385 /*      Error printing routine  */
 386 
 387 void
 388 error(char *msg)
 389 {
 390         code = 1;
 391         if (eflag == 0) {
 392                 fprintf(stderr, "\n\n%s", buf);
 393                 eflag = 1;
 394         }
 395         if (longnam != 0) {
 396                 fprintf(stderr, "\t%s\n", gettext(msg));
 397                 longnam = 0;
 398                 return;
 399         }
 400         if (badchar != 0) {
 401                 fprintf(stderr, "\t%d %s\n", badchar, gettext(msg));
 402                 badchar = 0;
 403                 return;
 404         } else if (baddigit != 0) {
 405                 fprintf(stderr, "\t%s\n", gettext(msg));
 406                 baddigit = 0;
 407                 return;
 408         } else if (badlognam != 0) {
 409                 fprintf(stderr, "\t%s - %s\n", nptr, gettext(msg));
 410                 badlognam = 0;
 411                 return;
 412         } else {
 413                 fprintf(stderr, "\t%s\n", gettext(msg));
 414                 return;
 415         }
 416 }