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