Home | History | Annotate | Line # | Download | only in newsyslog
newsyslog.c revision 1.31
      1 /*	$NetBSD: newsyslog.c,v 1.31 2000/07/13 11:28:50 ad Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1999, 2000 Andrew Doran <ad (at) NetBSD.org>
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
     17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
     20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     26  * SUCH DAMAGE.
     27  *
     28  */
     29 
     30 /*
     31  * This file contains changes from the Open Software Foundation.
     32  */
     33 
     34 /*
     35  * Copyright 1988, 1989 by the Massachusetts Institute of Technology
     36  *
     37  * Permission to use, copy, modify, and distribute this software
     38  * and its documentation for any purpose and without fee is
     39  * hereby granted, provided that the above copyright notice
     40  * appear in all copies and that both that copyright notice and
     41  * this permission notice appear in supporting documentation,
     42  * and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
     43  * used in advertising or publicity pertaining to distribution
     44  * of the software without specific, written prior permission.
     45  * M.I.T. and the M.I.T. S.I.P.B. make no representations about
     46  * the suitability of this software for any purpose.  It is
     47  * provided "as is" without express or implied warranty.
     48  *
     49  */
     50 
     51 /*
     52  * newsyslog(1) - a program to roll over log files provided that specified
     53  * critera are met, optionally preserving a number of historical log files.
     54  */
     55 
     56 #include <sys/cdefs.h>
     57 #ifndef lint
     58 __RCSID("$NetBSD: newsyslog.c,v 1.31 2000/07/13 11:28:50 ad Exp $");
     59 #endif /* not lint */
     60 
     61 #include <sys/types.h>
     62 #include <sys/time.h>
     63 #include <sys/stat.h>
     64 #include <sys/param.h>
     65 
     66 #include <ctype.h>
     67 #include <fcntl.h>
     68 #include <grp.h>
     69 #include <pwd.h>
     70 #include <signal.h>
     71 #include <stdio.h>
     72 #include <stdlib.h>
     73 #include <stdarg.h>
     74 #include <string.h>
     75 #include <time.h>
     76 #include <unistd.h>
     77 #include <errno.h>
     78 #include <err.h>
     79 #include <util.h>
     80 #include <paths.h>
     81 
     82 #include "pathnames.h"
     83 
     84 #define	PRHDRINFO(x)	((void)(verbose ? printf x : 0))
     85 #define	PRINFO(x)	((void)(verbose ? printf("  ") + printf x : 0))
     86 
     87 #define	CE_COMPRESS	0x01	/* Compress the achived log files */
     88 #define	CE_BINARY	0x02	/* Logfile is a binary file/non-syslog */
     89 #define	CE_NOSIGNAL	0x04	/* Don't send a signal when trimmed */
     90 #define CE_CREATE	0x08	/* Create log file if none exists */
     91 #define CE_PLAIN0	0x10	/* Do not compress zero'th history file */
     92 
     93 struct conf_entry {
     94 	uid_t	uid;			/* Owner of log */
     95 	gid_t	gid;			/* Group of log */
     96 	mode_t	mode;			/* File permissions */
     97 	int	numhist;		/* Number of historical logs to keep */
     98 	size_t	maxsize;		/* Maximum log size */
     99 	int	maxage;			/* Hours between log trimming */
    100 	int	flags;			/* Flags (CE_*) */
    101 	int	signum;			/* Signal to send */
    102 	char	pidfile[MAXPATHLEN];	/* File containing PID to signal */
    103 	char	logfile[MAXPATHLEN];	/* Path to log file */
    104 };
    105 
    106 int     verbose = 0;			/* Be verbose */
    107 int	noaction = 0;			/* Take no action */
    108 char    hostname[MAXHOSTNAMELEN + 1];	/* Hostname, stripped of domain */
    109 
    110 int	main(int, char **);
    111 int	parse(struct conf_entry *, FILE *, size_t *);
    112 
    113 void	log_compress(struct conf_entry *, const char *);
    114 void	log_create(struct conf_entry *);
    115 void	log_examine(struct conf_entry *, int);
    116 void	log_trim(struct conf_entry *);
    117 void	log_trimmed(struct conf_entry *);
    118 
    119 int	getsig(const char *);
    120 int	isnumber(const char *);
    121 int	parseuserspec(const char *, struct passwd **, struct group **);
    122 pid_t	readpidfile(const char *);
    123 void	usage(void);
    124 
    125 /*
    126  * Program entry point.
    127  */
    128 int
    129 main(int argc, char **argv)
    130 {
    131 	struct conf_entry log;
    132 	FILE *fd;
    133 	char *p, *cfile;
    134 	int c, force, needroot;
    135 	size_t lineno;
    136 
    137 	force = 0;
    138 	needroot = 1;
    139 	cfile = _PATH_NEWSYSLOGCONF;
    140 
    141 	gethostname(hostname, sizeof(hostname));
    142 	hostname[sizeof(hostname) - 1] = '\0';
    143 
    144 	/* Truncate domain */
    145 	if ((p = strchr(hostname, '.')) != NULL)
    146 		*p = '\0';
    147 
    148 	/* Parse command line options */
    149 	while ((c = getopt(argc, argv, "f:nrvF")) != -1) {
    150 		switch (c) {
    151 		case 'f':
    152 			cfile = optarg;
    153 			break;
    154 		case 'n':
    155 			noaction = 1;
    156 			verbose = 1;
    157 			break;
    158 		case 'r':
    159 			needroot = 0;
    160 			break;
    161 		case 'v':
    162 			verbose = 1;
    163 			break;
    164 		case 'F':
    165 			force = 1;
    166 			break;
    167 		default:
    168 			usage();
    169 			/* NOTREACHED */
    170 		}
    171 	}
    172 
    173 	if (needroot && geteuid() != 0)
    174 		errx(EXIT_FAILURE, "must be run as root");
    175 
    176 	if (strcmp(cfile, "-") == 0)
    177 		fd = stdin;
    178 	else if ((fd = fopen(cfile, "rt")) == NULL)
    179 		err(EXIT_FAILURE, "%s", cfile);
    180 
    181 	for (lineno = 0; !parse(&log, fd, &lineno);)
    182 		log_examine(&log, force);
    183 
    184 	if (fd != stdin)
    185 		fclose(fd);
    186 
    187 	exit(EXIT_SUCCESS);
    188 	/* NOTREACHED */
    189 }
    190 
    191 /*
    192  * Parse a single line from the configuration file.
    193  */
    194 int
    195 parse(struct conf_entry *log, FILE *fd, size_t *_lineno)
    196 {
    197 	char *line, *q, **ap, *argv[10];
    198 	struct passwd *pw;
    199 	struct group *gr;
    200 	int nf, lineno, i;
    201 
    202 	memset(log, 0, sizeof(*log));
    203 
    204 	if ((line = fparseln(fd, NULL, _lineno, NULL, 0)) == NULL)
    205 		return (-1);
    206 	lineno = (int)*_lineno;
    207 
    208 	for (ap = argv, nf = 0; (*ap = strsep(&line, " \t")) != NULL;)
    209 		if (**ap != '\0') {
    210 			if (++nf == sizeof(argv) / sizeof(argv[0])) {
    211 				warnx("config line %d: too many fields",
    212 				    lineno);
    213 				return (-1);
    214 			}
    215 			ap++;
    216 		}
    217 
    218 	if (nf == 0)
    219 		return (0);
    220 
    221 	if (nf < 6)
    222 		errx(EXIT_FAILURE, "config line %d: too few fields", lineno);
    223 
    224 	ap = argv;
    225 	strlcpy(log->logfile, *ap++, sizeof(log->logfile));
    226 
    227 	if (strchr(*ap, ':') != NULL || strchr(*ap, '.') != NULL) {
    228 		if (parseuserspec(*ap++, &pw, &gr)) {
    229 			warnx("config line %d: unknown user/group", lineno);
    230 			return (-1);
    231 		}
    232 		log->uid = pw->pw_uid;
    233 		log->gid = gr->gr_gid;
    234 		if (nf < 7)
    235 			errx(EXIT_FAILURE, "config line %d: too few fields",
    236 			    lineno);
    237 	}
    238 
    239 	if (sscanf(*ap++, "%o", &i) != 1) {
    240 		warnx("config line %d: bad permissions", lineno);
    241 		return (-1);
    242 	}
    243 	log->mode = (mode_t)i;
    244 
    245 	if (sscanf(*ap++, "%d", &log->numhist) != 1) {
    246 		warnx("config line %d: bad log count", lineno);
    247 		return (-1);
    248 	}
    249 
    250 	if (isdigit(**ap))
    251 		log->maxsize = atoi(*ap);
    252 	else if (**ap == '*')
    253 		log->maxsize = (size_t)-1;
    254 	else {
    255 		warnx("config line %d: bad log size", lineno);
    256 		return (-1);
    257 	}
    258 	ap++;
    259 
    260 	if (isdigit(**ap))
    261 		log->maxage = atoi(*ap);
    262 	else if (**ap == '*')
    263 		log->maxage = -1;
    264 	else {
    265 		warnx("config line %d: bad log age", lineno);
    266 		return (-1);
    267 	}
    268 	ap++;
    269 
    270 	log->flags = 0;
    271 	for (q = *ap++; q != NULL && *q != '\0'; q++) {
    272 		switch (tolower(*q)) {
    273 		case 'b':
    274 			log->flags |= CE_BINARY;
    275 			break;
    276 		case 'c':
    277 			log->flags |= CE_CREATE;
    278 			break;
    279 		case 'n':
    280 			log->flags |= CE_NOSIGNAL;
    281 			break;
    282 		case 'p':
    283 			log->flags |= CE_PLAIN0;
    284 			break;
    285 		case 'z':
    286 			log->flags |= CE_COMPRESS;
    287 			break;
    288 		case '-':
    289 			break;
    290 		default:
    291 			warnx("config line %d: bad flags", lineno);
    292 			return (-1);
    293 		}
    294 	}
    295 
    296 	if (*ap != NULL && **ap == '/')
    297 		strlcpy(log->pidfile, *ap++, sizeof(log->pidfile));
    298 	else
    299 		log->pidfile[0] = '\0';
    300 
    301 	if (*ap != NULL && (log->signum = getsig(*ap++)) < 0) {
    302 		warnx("config line %d: bad signal type", lineno);
    303 		return (-1);
    304 	} else
    305 		log->signum = SIGHUP;
    306 
    307 	return (0);
    308 }
    309 
    310 /*
    311  * Examine a log file.  If the trim conditions are met, call log_trim() to
    312  * trim the log file.
    313  */
    314 void
    315 log_examine(struct conf_entry *log, int force)
    316 {
    317 	struct stat sb;
    318 	size_t size;
    319 	int age;
    320 	char tmp[MAXPATHLEN];
    321 	time_t now;
    322 
    323 	if (log->logfile[0] == '\0')
    324 		return;
    325 
    326 	PRHDRINFO(("\n%s <%d%s>: ", log->logfile, log->numhist,
    327 	    (log->flags & CE_COMPRESS) != 0 ? "Z" : ""));
    328 
    329 	if (stat(log->logfile, &sb) < 0) {
    330 		if (errno == ENOENT && (log->flags & CE_CREATE) != 0) {
    331 			PRHDRINFO(("creating; "));
    332 			if (!noaction)
    333 				log_create(log);
    334 			else {
    335 				PRHDRINFO(("can't proceed with `-n'\n"));
    336 				return;
    337 			}
    338 			if (stat(log->logfile, &sb))
    339 				err(EXIT_FAILURE, "%s", log->logfile);
    340 		} else if (errno == ENOENT) {
    341 			PRHDRINFO(("does not exist --> skip log\n"));
    342 			return;
    343 		} else if (errno != 0)
    344 			err(EXIT_FAILURE, "%s", log->logfile);
    345 	}
    346 
    347 
    348 	size = ((size_t)sb.st_blocks * S_BLKSIZE) >> 10;
    349 
    350 	now = time(NULL);
    351 	strlcpy(tmp, log->logfile, sizeof(tmp));
    352 	strlcat(tmp, ".0", sizeof(tmp));
    353 	if (stat(tmp, &sb) < 0) {
    354 		strlcat(tmp, ".gz", sizeof(tmp));
    355 		if (stat(tmp, &sb) < 0)
    356 			age = -1;
    357 		else
    358 			age = (int)(now - sb.st_mtime + 1800) / 3600;
    359 	} else
    360 		age = (int)(now - sb.st_mtime + 1800) / 3600;
    361 
    362 	if (verbose) {
    363 		if (log->maxsize != (size_t)-1)
    364 			PRHDRINFO(("size (Kb): %lu [%lu] ",
    365 				(u_long)size,
    366 				(u_long)log->maxsize));
    367 		if (log->maxage > 0)
    368 			PRHDRINFO(("age (hr): %d [%d] ", age, log->maxage));
    369 	}
    370 
    371 	/*
    372 	 * Note: if maxage is used as a trim condition, we need at least one
    373 	 * historical log file to determine the `age' of the active log file.
    374 	 */
    375 	if ((log->maxage > 0 && (age >= log->maxage || age < 0)) ||
    376 	    size >= log->maxsize || force) {
    377 		PRHDRINFO(("--> trim log\n"));
    378 		log_trim(log);
    379 	} else
    380 		PRHDRINFO(("--> skip log\n"));
    381 }
    382 
    383 /*
    384  * Trim the specified log file.
    385  */
    386 void
    387 log_trim(struct conf_entry *log)
    388 {
    389 	char file1[MAXPATHLEN], file2[MAXPATHLEN];
    390 	int i;
    391 	struct stat st;
    392 	pid_t pid;
    393 
    394 	/* Remove oldest historical log */
    395 	snprintf(file1, sizeof(file1), "%s.%d", log->logfile, log->numhist - 1);
    396 
    397 	PRINFO(("rm -f %s\n", file1));
    398 	if (!noaction)
    399 		unlink(file1);
    400 	strlcat(file1, ".gz", sizeof(file1));
    401 	PRINFO(("rm -f %s\n", file1));
    402 	if (!noaction)
    403 		unlink(file1);
    404 
    405 	/* Move down log files */
    406 	for (i = log->numhist - 1; i != 0; i--) {
    407 		snprintf(file1, sizeof(file1), "%s.%d", log->logfile, i - 1);
    408 		snprintf(file2, sizeof(file2), "%s.%d", log->logfile, i);
    409 
    410 		if (lstat(file1, &st) != 0) {
    411 			strlcat(file1, ".gz", sizeof(file1));
    412 			strlcat(file2, ".gz", sizeof(file2));
    413 			if (lstat(file1, &st) != 0)
    414 				continue;
    415 		}
    416 
    417 		PRINFO(("mv %s %s\n", file1, file2));
    418 		if (!noaction)
    419 			if (rename(file1, file2))
    420 				err(EXIT_FAILURE, "%s", file1);
    421 		PRINFO(("chmod %o %s\n", log->mode, file2));
    422 		if (!noaction)
    423 			if (chmod(file2, log->mode))
    424 				err(EXIT_FAILURE, "%s", file2);
    425 		PRINFO(("chown %d:%d %s\n", log->uid, log->gid, file2));
    426 		if (!noaction)
    427 			if (chown(file2, log->uid, log->gid))
    428 				err(EXIT_FAILURE, "%s", file2);
    429 
    430 	}
    431 
    432 	/*
    433 	 * If a historical log file isn't compressed, and 'z' has been
    434 	 * specified, compress it.  (This is convenient, but is also needed
    435 	 * if 'p' has been specified.)  It should be noted that gzip(1)
    436 	 * preserves file ownership and file mode.
    437 	 */
    438 	for (i = (log->flags & CE_PLAIN0) != 0; i < log->numhist; i++) {
    439 		snprintf(file1, sizeof(file1), "%s.%d", log->logfile, i);
    440 		if (lstat(file1, &st) != 0)
    441 			continue;
    442 		snprintf(file2, sizeof(file2), "%s.gz", file1);
    443 		if (lstat(file2, &st) == 0)
    444 			continue;
    445 		log_compress(log, file1);
    446 	}
    447 
    448 	log_trimmed(log);
    449 
    450 	if (log->numhist == 0) {
    451 		PRINFO(("rm -f %s\n", log->logfile));
    452 		if (!noaction)
    453 			if (unlink(log->logfile))
    454 				err(EXIT_FAILURE, "%s", log->logfile);
    455 	} else {
    456 		snprintf(file1, sizeof(file1), "%s.0", log->logfile);
    457 		PRINFO(("mv %s %s\n", log->logfile, file1));
    458 		if (!noaction)
    459 			if (rename(log->logfile, file1))
    460 				err(EXIT_FAILURE, "%s", log->logfile);
    461 	}
    462 
    463 	PRINFO(("(create new log)\n"));
    464 	log_create(log);
    465 	log_trimmed(log);
    466 
    467 	PRINFO(("chmod %o %s\n", log->mode, log->logfile));
    468 	if (!noaction)
    469 		if (chmod(log->logfile, log->mode))
    470 			err(EXIT_FAILURE, "%s", log->logfile);
    471 
    472 	if ((log->flags & CE_NOSIGNAL) == 0) {
    473 		if (log->pidfile[0] != '\0')
    474 			pid = readpidfile(log->pidfile);
    475 		else
    476 			pid = readpidfile(_PATH_SYSLOGDPID);
    477 
    478 		if (pid != (pid_t)-1) {
    479 			PRINFO(("kill -%s %lu\n", sys_signame[log->signum],
    480 			    (u_long)pid));
    481 			if (!noaction)
    482 				if (kill(pid, log->signum))
    483 					warn("kill");
    484 		}
    485 	}
    486 
    487 	if ((log->flags & (CE_PLAIN0 | CE_COMPRESS)) == CE_COMPRESS) {
    488 		snprintf(file1, sizeof(file1), "%s.0", log->logfile);
    489 		log_compress(log, file1);
    490 	}
    491 }
    492 
    493 /*
    494  * Write an entry to the log file recording the fact that it was trimmed.
    495  */
    496 void
    497 log_trimmed(struct conf_entry *log)
    498 {
    499 	FILE *fd;
    500 	time_t now;
    501 	char *daytime;
    502 
    503 	if ((log->flags & CE_BINARY) != 0)
    504 		return;
    505 	PRINFO(("(append rotation notice to %s)\n", log->logfile));
    506 	if (noaction)
    507 		return;
    508 
    509 	if ((fd = fopen(log->logfile, "at")) == NULL)
    510 		err(EXIT_FAILURE, "%s", log->logfile);
    511 
    512 	now = time(NULL);
    513 	daytime = ctime(&now) + 4;
    514 	daytime[15] = '\0';
    515 
    516 	fprintf(fd, "%s %s newsyslog[%lu]: log file turned over\n", daytime,
    517 	    hostname, (u_long)getpid());
    518 	fclose(fd);
    519 }
    520 
    521 /*
    522  * Create a new log file.
    523  */
    524 void
    525 log_create(struct conf_entry *log)
    526 {
    527 	int fd;
    528 
    529 	if (noaction)
    530 		return;
    531 
    532 	if ((fd = creat(log->logfile, log->mode)) < 0)
    533 		err(EXIT_FAILURE, "%s", log->logfile);
    534 	if (fchown(fd, log->uid, log->gid) < 0)
    535 		err(EXIT_FAILURE, "%s", log->logfile);
    536 	close(fd);
    537 }
    538 
    539 /*
    540  * Compress a log file.  This routine takes an additional string argument:
    541  * it is also used to compress historical log files.
    542  */
    543 void
    544 log_compress(struct conf_entry *log, const char *fn)
    545 {
    546 	pid_t pid;
    547 
    548 	PRINFO(("gzip %s\n", fn));
    549 	if (!noaction) {
    550 		if ((pid = fork()) < 0)
    551 			err(EXIT_FAILURE, "fork");
    552 		else if (pid == 0) {
    553 			execl(_PATH_GZIP, "gzip", "-f", fn, NULL);
    554 			err(EXIT_FAILURE, _PATH_GZIP);
    555 		}
    556 	}
    557 }
    558 
    559 /*
    560  * Display program usage information.
    561  */
    562 void
    563 usage(void)
    564 {
    565 
    566 	fprintf(stderr, "usage: newsyslog [-Frv] [-f config-file]\n");
    567 	exit(EXIT_FAILURE);
    568 }
    569 
    570 /*
    571  * Return non-zero if a string represents a decimal value.
    572  */
    573 int
    574 isnumber(const char *string)
    575 {
    576 
    577 	while (isdigit(*string))
    578 		string++;
    579 
    580 	return (*string == '\0');
    581 }
    582 
    583 /*
    584  * Given a signal name, attempt to find the corresponding signal number.
    585  */
    586 int
    587 getsig(const char *sig)
    588 {
    589 	char *p;
    590 	int n;
    591 
    592 	if (isnumber(sig)) {
    593 		n = (int)strtol(sig, &p, 0);
    594 		if (p != '\0' || n < 0 || n >= NSIG)
    595 			return (-1);
    596 		return (n);
    597 	}
    598 
    599 	if (strncasecmp(sig, "SIG", 3) == 0)
    600 		sig += 3;
    601 	for (n = 1; n < NSIG; n++)
    602 		if (strcasecmp(sys_signame[n], sig) == 0)
    603 			return (n);
    604 	return (-1);
    605 }
    606 
    607 /*
    608  * Given a path to a PID file, return the PID contained within.
    609  */
    610 pid_t
    611 readpidfile(const char *file)
    612 {
    613 	FILE *fd;
    614 	char line[BUFSIZ];
    615 	pid_t pid;
    616 
    617 #ifdef notyet
    618 	if (file[0] != '/')
    619 		snprintf(tmp, sizeof(tmp), "%s%s", _PATH_VARRUN, file);
    620 	else
    621 		strlcpy(tmp, file, sizeof(tmp));
    622 #endif
    623 
    624 	if ((fd = fopen(file, "rt")) == NULL) {
    625 		warn("%s", file);
    626 		return (-1);
    627 	}
    628 
    629 	if (fgets(line, sizeof(line) - 1, fd) != NULL) {
    630 		line[sizeof(line) - 1] = '\0';
    631 		pid = (pid_t)strtol(line, NULL, 0);
    632 	} else {
    633 		warnx("unable to read %s", file);
    634 		pid = (pid_t)-1;
    635 	}
    636 
    637 	fclose(fd);
    638 	return (pid);
    639 }
    640 
    641 /*
    642  * Parse a user:group specification.
    643  *
    644  * XXX This is over the top for newsyslog(1).  It should be moved to libutil.
    645  */
    646 int
    647 parseuserspec(const char *name, struct passwd **pw, struct group **gr)
    648 {
    649 	char buf[MAXLOGNAME * 2 + 2], *group;
    650 
    651 	strlcpy(buf, name, sizeof(buf));
    652 	*gr = NULL;
    653 
    654 	/*
    655 	 * Before attempting to use '.' as a separator, see if the whole
    656 	 * string resolves as a user name.
    657 	 */
    658 	if ((*pw = getpwnam(buf)) != NULL) {
    659 		*gr = getgrgid((*pw)->pw_gid);
    660 		return (0);
    661 	}
    662 
    663 	/* Split the user and group name. */
    664 	if ((group = strchr(buf, ':')) != NULL ||
    665 	    (group = strchr(buf, '.')) != NULL)
    666 		*group++ = '\0';
    667 
    668 	if (isnumber(buf))
    669 		*pw = getpwuid((uid_t)atoi(buf));
    670 	else
    671 		*pw = getpwnam(buf);
    672 
    673 	/*
    674 	 * Find the group.  If a group wasn't specified, use the user's
    675 	 * `natural' group.  We get to this point even if no user was found.
    676 	 * This is to allow the caller to get a better idea of what went
    677 	 * wrong, if anything.
    678 	 */
    679 	if (group == NULL || *group == '\0') {
    680 		if (*pw == NULL)
    681 			return (-1);
    682 		*gr = getgrgid((*pw)->pw_gid);
    683 	} else if (isnumber(group))
    684 		*gr = getgrgid((gid_t)atoi(group));
    685 	else
    686 		*gr = getgrnam(group);
    687 
    688 	return (*pw != NULL && *gr != NULL ? 0 : -1);
    689 }
    690