xref: /onnv-gate/usr/src/ucbcmd/vipw/vipw.c (revision 1553:4d09cba4bd44)
10Sstevel@tonic-gate /*
20Sstevel@tonic-gate  * CDDL HEADER START
30Sstevel@tonic-gate  *
40Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
5*1553Scf46844  * Common Development and Distribution License (the "License").
6*1553Scf46844  * You may not use this file except in compliance with the License.
70Sstevel@tonic-gate  *
80Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
90Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
100Sstevel@tonic-gate  * See the License for the specific language governing permissions
110Sstevel@tonic-gate  * and limitations under the License.
120Sstevel@tonic-gate  *
130Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
140Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
150Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
160Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
170Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
180Sstevel@tonic-gate  *
190Sstevel@tonic-gate  * CDDL HEADER END
200Sstevel@tonic-gate  */
210Sstevel@tonic-gate /*
22*1553Scf46844  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
230Sstevel@tonic-gate  * Use is subject to license terms.
240Sstevel@tonic-gate  */
250Sstevel@tonic-gate 
260Sstevel@tonic-gate /*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T	*/
270Sstevel@tonic-gate /*	  All Rights Reserved  	*/
280Sstevel@tonic-gate 
290Sstevel@tonic-gate /*
300Sstevel@tonic-gate  * Portions of this source code were derived from Berkeley 4.3 BSD
310Sstevel@tonic-gate  * under license from the Regents of the University of California.
320Sstevel@tonic-gate  */
330Sstevel@tonic-gate 
34273Scf46844 #pragma ident	"%Z%%M%	%I%	%E% SMI"
350Sstevel@tonic-gate 
360Sstevel@tonic-gate #include <sys/types.h>
370Sstevel@tonic-gate #include <sys/stat.h>
380Sstevel@tonic-gate #include <sys/file.h>
390Sstevel@tonic-gate #include <sys/fcntl.h>
400Sstevel@tonic-gate 
410Sstevel@tonic-gate #include <stdio.h>
420Sstevel@tonic-gate #include <errno.h>
430Sstevel@tonic-gate #include <signal.h>
44273Scf46844 #include <stdlib.h>
45273Scf46844 #include <strings.h>
460Sstevel@tonic-gate 
470Sstevel@tonic-gate /*
480Sstevel@tonic-gate  * Password file editor with locking.
490Sstevel@tonic-gate  */
500Sstevel@tonic-gate 
510Sstevel@tonic-gate #define	DEFAULT_EDITOR	"/usr/bin/vi"
520Sstevel@tonic-gate 
53273Scf46844 static int copyfile(char *, char *);
54273Scf46844 static int editfile(char *, char *, char *, time_t *);
55273Scf46844 static int sanity_check(char *, time_t *, char *);
56273Scf46844 static int validsh(char *);
57273Scf46844 
580Sstevel@tonic-gate char	*ptemp = "/etc/ptmp";
590Sstevel@tonic-gate char	*stemp = "/etc/stmp";
600Sstevel@tonic-gate char	*passwd = "/etc/passwd";
610Sstevel@tonic-gate char	*shadow = "/etc/shadow";
620Sstevel@tonic-gate char	buf[BUFSIZ];
630Sstevel@tonic-gate 
64273Scf46844 int
main(void)65273Scf46844 main(void)
660Sstevel@tonic-gate {
670Sstevel@tonic-gate 	int fd;
680Sstevel@tonic-gate 	FILE *ft, *fp;
690Sstevel@tonic-gate 	char *editor;
700Sstevel@tonic-gate 	int ok = 0;
710Sstevel@tonic-gate 	time_t o_mtime, n_mtime;
720Sstevel@tonic-gate 	struct stat osbuf, sbuf, oshdbuf, shdbuf;
730Sstevel@tonic-gate 	char c;
740Sstevel@tonic-gate 
750Sstevel@tonic-gate 	(void)signal(SIGINT, SIG_IGN);
760Sstevel@tonic-gate 	(void)signal(SIGQUIT, SIG_IGN);
770Sstevel@tonic-gate 	(void)signal(SIGHUP, SIG_IGN);
780Sstevel@tonic-gate 	setbuf(stderr, (char *)NULL);
790Sstevel@tonic-gate 
800Sstevel@tonic-gate 	editor = getenv("VISUAL");
810Sstevel@tonic-gate 	if (editor == 0)
820Sstevel@tonic-gate 		editor = getenv("EDITOR");
830Sstevel@tonic-gate 	if (editor == 0)
840Sstevel@tonic-gate 		editor = DEFAULT_EDITOR;
850Sstevel@tonic-gate 
860Sstevel@tonic-gate 	(void)umask(0077);
870Sstevel@tonic-gate 	if (stat(passwd, &osbuf) < 0) {
880Sstevel@tonic-gate                 (void)fprintf(stderr,"vipw: can't stat passwd file.\n");
890Sstevel@tonic-gate                 goto bad;
900Sstevel@tonic-gate         }
910Sstevel@tonic-gate 
920Sstevel@tonic-gate 	if (copyfile(passwd, ptemp))
930Sstevel@tonic-gate 		goto bad;
940Sstevel@tonic-gate 
950Sstevel@tonic-gate 	if (stat(ptemp, &sbuf) < 0) {
960Sstevel@tonic-gate                 (void)fprintf(stderr,
970Sstevel@tonic-gate 			"vipw: can't stat ptemp file, %s unchanged\n",
980Sstevel@tonic-gate 			passwd);
990Sstevel@tonic-gate 		goto bad;
1000Sstevel@tonic-gate 	}
1010Sstevel@tonic-gate 
1020Sstevel@tonic-gate 	o_mtime = sbuf.st_mtime;
1030Sstevel@tonic-gate 
1040Sstevel@tonic-gate 	if (editfile(editor, ptemp, passwd, &n_mtime)) {
1050Sstevel@tonic-gate 		if (sanity_check(ptemp, &n_mtime, passwd))
1060Sstevel@tonic-gate 			goto bad;
1070Sstevel@tonic-gate 		if (o_mtime >= n_mtime)
1080Sstevel@tonic-gate 			goto bad;
1090Sstevel@tonic-gate 	}
1100Sstevel@tonic-gate 
1110Sstevel@tonic-gate 	ok++;
1120Sstevel@tonic-gate 	if (o_mtime < n_mtime) {
1130Sstevel@tonic-gate 		fprintf(stdout, "\nYou have modified the password file.\n");
1140Sstevel@tonic-gate 		fprintf(stdout,
1150Sstevel@tonic-gate 	"Press 'e' to edit the shadow file for consistency,\n 'q' to quit: ");
1160Sstevel@tonic-gate 		if ((c = getchar()) == 'q') {
1170Sstevel@tonic-gate 			if (chmod(ptemp, (osbuf.st_mode & 0644)) < 0) {
1180Sstevel@tonic-gate 				(void) fprintf(stderr, "vipw: %s: ", ptemp);
1190Sstevel@tonic-gate 				perror("chmod");
1200Sstevel@tonic-gate 				goto bad;
1210Sstevel@tonic-gate 			}
1220Sstevel@tonic-gate 			if (rename(ptemp, passwd) < 0) {
1230Sstevel@tonic-gate 				(void) fprintf(stderr, "vipw: %s: ", ptemp);
1240Sstevel@tonic-gate 				perror("rename");
1250Sstevel@tonic-gate 				goto bad;
1260Sstevel@tonic-gate 			}
1270Sstevel@tonic-gate 			if (((osbuf.st_gid != sbuf.st_gid) ||
1280Sstevel@tonic-gate 					(osbuf.st_uid != sbuf.st_uid)) &&
1290Sstevel@tonic-gate 			(chown(passwd, osbuf.st_uid, osbuf.st_gid) < 0)) {
1300Sstevel@tonic-gate 				(void) fprintf(stderr, "vipw: %s ", ptemp);
1310Sstevel@tonic-gate 				perror("chown");
1320Sstevel@tonic-gate 			}
1330Sstevel@tonic-gate 			goto bad;
1340Sstevel@tonic-gate 		} else if (c == 'e') {
1350Sstevel@tonic-gate 			if (stat(shadow, &oshdbuf) < 0) {
1360Sstevel@tonic-gate 				(void) fprintf(stderr,
1370Sstevel@tonic-gate 					"vipw: can't stat shadow file.\n");
1380Sstevel@tonic-gate 				goto bad;
1390Sstevel@tonic-gate 			}
1400Sstevel@tonic-gate 
1410Sstevel@tonic-gate 			if (copyfile(shadow, stemp))
1420Sstevel@tonic-gate 				goto bad;
1430Sstevel@tonic-gate 			if (stat(stemp, &shdbuf) < 0) {
1440Sstevel@tonic-gate 				(void) fprintf(stderr,
1450Sstevel@tonic-gate 					"vipw: can't stat stmp file.\n");
1460Sstevel@tonic-gate 				goto bad;
1470Sstevel@tonic-gate 			}
1480Sstevel@tonic-gate 
1490Sstevel@tonic-gate 			if (editfile(editor, stemp, shadow, &o_mtime))
1500Sstevel@tonic-gate 				goto bad;
1510Sstevel@tonic-gate 			ok++;
1520Sstevel@tonic-gate 			if (chmod(ptemp, (osbuf.st_mode & 0644)) < 0) {
1530Sstevel@tonic-gate 				(void) fprintf(stderr, "vipw: %s: ", ptemp);
1540Sstevel@tonic-gate 				perror("chmod");
1550Sstevel@tonic-gate 				goto bad;
1560Sstevel@tonic-gate 			}
1570Sstevel@tonic-gate 			if (chmod(stemp, (oshdbuf.st_mode & 0400)) < 0) {
1580Sstevel@tonic-gate 				(void) fprintf(stderr, "vipw: %s: ", stemp);
1590Sstevel@tonic-gate 				perror("chmod");
1600Sstevel@tonic-gate 				goto bad;
1610Sstevel@tonic-gate 			}
1620Sstevel@tonic-gate 			if (rename(ptemp, passwd) < 0) {
1630Sstevel@tonic-gate 				(void) fprintf(stderr, "vipw: %s: ", ptemp);
1640Sstevel@tonic-gate 				perror("rename");
1650Sstevel@tonic-gate 				goto bad;
1660Sstevel@tonic-gate 			}
1670Sstevel@tonic-gate 			if (((osbuf.st_gid != sbuf.st_gid) ||
1680Sstevel@tonic-gate 					(osbuf.st_uid != sbuf.st_uid)) &&
1690Sstevel@tonic-gate 			(chown(passwd, osbuf.st_uid, osbuf.st_gid) < 0)) {
1700Sstevel@tonic-gate 				(void) fprintf(stderr, "vipw: %s ", ptemp);
1710Sstevel@tonic-gate 				perror("chown");
1720Sstevel@tonic-gate 			}
1730Sstevel@tonic-gate 			if (rename(stemp, shadow) < 0) {
1740Sstevel@tonic-gate 				(void) fprintf(stderr, "vipw: %s: ", stemp);
1750Sstevel@tonic-gate 				perror("rename");
1760Sstevel@tonic-gate 				goto bad;
1770Sstevel@tonic-gate 			} else if (((oshdbuf.st_gid != shdbuf.st_gid) ||
1780Sstevel@tonic-gate 					(oshdbuf.st_uid != shdbuf.st_uid)) &&
1790Sstevel@tonic-gate 			(chown(shadow, oshdbuf.st_uid, oshdbuf.st_gid) < 0)) {
1800Sstevel@tonic-gate 				(void) fprintf(stderr, "vipw: %s ", stemp);
1810Sstevel@tonic-gate 				perror("chown");
1820Sstevel@tonic-gate 				}
1830Sstevel@tonic-gate 		}
1840Sstevel@tonic-gate 	}
1850Sstevel@tonic-gate bad:
1860Sstevel@tonic-gate 	(void) unlink(ptemp);
1870Sstevel@tonic-gate 	(void) unlink(stemp);
188273Scf46844 	return (ok ? 0 : 1);
1890Sstevel@tonic-gate 	/* NOTREACHED */
1900Sstevel@tonic-gate }
1910Sstevel@tonic-gate 
1920Sstevel@tonic-gate 
193273Scf46844 int
copyfile(char * from,char * to)194273Scf46844 copyfile(char *from, char *to)
1950Sstevel@tonic-gate {
1960Sstevel@tonic-gate 	int fd;
1970Sstevel@tonic-gate 	FILE *fp, *ft;
1980Sstevel@tonic-gate 
1990Sstevel@tonic-gate 	fd = open(to, O_WRONLY|O_CREAT|O_EXCL, 0600);
2000Sstevel@tonic-gate 	if (fd < 0) {
2010Sstevel@tonic-gate 		if (errno == EEXIST) {
2020Sstevel@tonic-gate 			(void) fprintf(stderr, "vipw: %s file busy\n", from);
2030Sstevel@tonic-gate 			exit(1);
2040Sstevel@tonic-gate 		}
2050Sstevel@tonic-gate 		(void) fprintf(stderr, "vipw: "); perror(to);
2060Sstevel@tonic-gate 		exit(1);
2070Sstevel@tonic-gate 	}
2080Sstevel@tonic-gate 	ft = fdopen(fd, "w");
2090Sstevel@tonic-gate 	if (ft == NULL) {
2100Sstevel@tonic-gate 		(void) fprintf(stderr, "vipw: "); perror(to);
2110Sstevel@tonic-gate 		return( 1 );
2120Sstevel@tonic-gate 	}
2130Sstevel@tonic-gate 	fp = fopen(from, "r");
2140Sstevel@tonic-gate 	if (fp == NULL) {
2150Sstevel@tonic-gate 		(void) fprintf(stderr, "vipw: "); perror(from);
2160Sstevel@tonic-gate 		return( 1 );
2170Sstevel@tonic-gate 	}
2180Sstevel@tonic-gate 	while (fgets(buf, sizeof (buf) - 1, fp) != NULL)
2190Sstevel@tonic-gate 		fputs(buf, ft);
2200Sstevel@tonic-gate 	(void) fclose(ft);
2210Sstevel@tonic-gate 	(void) fclose(fp);
2220Sstevel@tonic-gate 	return( 0 );
2230Sstevel@tonic-gate }
2240Sstevel@tonic-gate 
225273Scf46844 int
editfile(char * editor,char * temp,char * orig,time_t * mtime)226273Scf46844 editfile(char *editor, char *temp, char *orig, time_t *mtime)
2270Sstevel@tonic-gate {
2280Sstevel@tonic-gate 	(void)sprintf(buf, "%s %s", editor, temp);
2290Sstevel@tonic-gate 	if (system(buf) == 0) {
2300Sstevel@tonic-gate 		return (sanity_check(temp, mtime, orig));
2310Sstevel@tonic-gate 	}
2320Sstevel@tonic-gate 	return(1);
2330Sstevel@tonic-gate }
2340Sstevel@tonic-gate 
2350Sstevel@tonic-gate 
236273Scf46844 int
validsh(char * rootsh)237273Scf46844 validsh(char *rootsh)
2380Sstevel@tonic-gate {
2390Sstevel@tonic-gate 
2400Sstevel@tonic-gate 	char	*sh, *getusershell();
2410Sstevel@tonic-gate 	int	ret = 0;
2420Sstevel@tonic-gate 
2430Sstevel@tonic-gate 	setusershell();
2440Sstevel@tonic-gate 	while((sh = getusershell()) != NULL ) {
2450Sstevel@tonic-gate 		if( strcmp( rootsh, sh) == 0 ) {
2460Sstevel@tonic-gate 			ret = 1;
2470Sstevel@tonic-gate 			break;
2480Sstevel@tonic-gate 		}
2490Sstevel@tonic-gate 	}
2500Sstevel@tonic-gate 	endusershell();
2510Sstevel@tonic-gate 	return(ret);
2520Sstevel@tonic-gate }
2530Sstevel@tonic-gate 
2540Sstevel@tonic-gate /*
2550Sstevel@tonic-gate  * sanity checks
2560Sstevel@tonic-gate  * return 0 if ok, 1 otherwise
2570Sstevel@tonic-gate  */
258273Scf46844 int
sanity_check(char * temp,time_t * mtime,char * orig)259273Scf46844 sanity_check(char *temp, time_t *mtime, char *orig)
2600Sstevel@tonic-gate {
2610Sstevel@tonic-gate 	int i, ok = 0;
2620Sstevel@tonic-gate 	FILE *ft;
263*1553Scf46844 	struct stat sbuf, statbuf;
264*1553Scf46844 	char *ldir;
2650Sstevel@tonic-gate 	int isshadow = 0;
2660Sstevel@tonic-gate 
2670Sstevel@tonic-gate 	if (!strcmp(orig, shadow))
2680Sstevel@tonic-gate 		isshadow = 1;
2690Sstevel@tonic-gate 
2700Sstevel@tonic-gate 	/* sanity checks */
2710Sstevel@tonic-gate 	if (stat(temp, &sbuf) < 0) {
2720Sstevel@tonic-gate 		(void)fprintf(stderr,
2730Sstevel@tonic-gate 		    "vipw: can't stat %s file, %s unchanged\n",
2740Sstevel@tonic-gate 		    temp, orig);
2750Sstevel@tonic-gate 		return(1);
2760Sstevel@tonic-gate 	}
2770Sstevel@tonic-gate 	*mtime = sbuf.st_mtime;
2780Sstevel@tonic-gate 	if (sbuf.st_size == 0) {
2790Sstevel@tonic-gate 		(void)fprintf(stderr, "vipw: bad %s file, %s unchanged\n",
2800Sstevel@tonic-gate 		    temp, orig);
2810Sstevel@tonic-gate 		return(1);
2820Sstevel@tonic-gate 	}
2830Sstevel@tonic-gate 	ft = fopen(temp, "r");
2840Sstevel@tonic-gate 	if (ft == NULL) {
2850Sstevel@tonic-gate 		(void)fprintf(stderr,
2860Sstevel@tonic-gate 		    "vipw: can't reopen %s file, %s unchanged\n",
2870Sstevel@tonic-gate 		    temp, orig);
2880Sstevel@tonic-gate 		return(1);
2890Sstevel@tonic-gate 	}
2900Sstevel@tonic-gate 
2910Sstevel@tonic-gate 	while (fgets(buf, sizeof (buf) - 1, ft) != NULL) {
292273Scf46844 		char *cp;
2930Sstevel@tonic-gate 
2940Sstevel@tonic-gate 		cp = index(buf, '\n');
2950Sstevel@tonic-gate 		if (cp == 0)
2960Sstevel@tonic-gate 			continue;	/* ??? allow very long lines
2970Sstevel@tonic-gate 					 * and passwd files that do
2980Sstevel@tonic-gate 					 * not end in '\n' ???
2990Sstevel@tonic-gate 					 */
3000Sstevel@tonic-gate 		*cp = '\0';
3010Sstevel@tonic-gate 
3020Sstevel@tonic-gate 		cp = index(buf, ':');
3030Sstevel@tonic-gate 		if (cp == 0)		/* lines without colon
3040Sstevel@tonic-gate 					 * separated fields
3050Sstevel@tonic-gate 					 */
3060Sstevel@tonic-gate 			continue;
3070Sstevel@tonic-gate 		*cp = '\0';
3080Sstevel@tonic-gate 
3090Sstevel@tonic-gate 		if (strcmp(buf, "root"))
3100Sstevel@tonic-gate 			continue;
3110Sstevel@tonic-gate 
3120Sstevel@tonic-gate 		/* root password */
3130Sstevel@tonic-gate 		*cp = ':';
3140Sstevel@tonic-gate 		cp = index(cp + 1, ':');
3150Sstevel@tonic-gate 		if (cp == 0)
3160Sstevel@tonic-gate 			goto bad_root;
3170Sstevel@tonic-gate 
3180Sstevel@tonic-gate 		/* root uid for password */
3190Sstevel@tonic-gate 		if (!isshadow)
3200Sstevel@tonic-gate 			if (atoi(cp + 1) != 0) {
3210Sstevel@tonic-gate 
3220Sstevel@tonic-gate 				(void)fprintf(stderr, "root UID != 0:\n%s\n",
3230Sstevel@tonic-gate 				    buf);
3240Sstevel@tonic-gate 				break;
3250Sstevel@tonic-gate 			}
3260Sstevel@tonic-gate 		/* root uid for passwd and sp_lstchg for shadow */
3270Sstevel@tonic-gate 		cp = index(cp + 1, ':');
3280Sstevel@tonic-gate 		if (cp == 0)
3290Sstevel@tonic-gate 			goto bad_root;
3300Sstevel@tonic-gate 
3310Sstevel@tonic-gate 		/* root's gid for passwd and sp_min for shadow*/
3320Sstevel@tonic-gate 		cp = index(cp + 1, ':');
3330Sstevel@tonic-gate 		if (cp == 0)
3340Sstevel@tonic-gate 			goto bad_root;
3350Sstevel@tonic-gate 
3360Sstevel@tonic-gate 		/* root's gecos for passwd and sp_max for shadow*/
3370Sstevel@tonic-gate 		cp = index(cp + 1, ':');
3380Sstevel@tonic-gate 		if (isshadow) {
3390Sstevel@tonic-gate 			for (i=0; i<3; i++)
3400Sstevel@tonic-gate 				if ((cp = index(cp + 1, ':')) == 0)
3410Sstevel@tonic-gate 					goto bad_root;
3420Sstevel@tonic-gate 		} else {
3430Sstevel@tonic-gate 			if (cp == 0) {
3440Sstevel@tonic-gate bad_root:		(void)fprintf(stderr,
3450Sstevel@tonic-gate 				    "Missing fields in root entry:\n%s\n", buf);
3460Sstevel@tonic-gate 				break;
3470Sstevel@tonic-gate 			}
3480Sstevel@tonic-gate 		}
3490Sstevel@tonic-gate 		if (!isshadow) {
3500Sstevel@tonic-gate 			/* root's login directory */
351*1553Scf46844 			ldir = ++cp;
352*1553Scf46844 			cp = index(cp, ':');
353*1553Scf46844 			if (cp == 0)
354*1553Scf46844 				goto bad_root;
355*1553Scf46844 			*cp = '\0';
356*1553Scf46844 			if (stat(ldir, &statbuf) < 0) {
357*1553Scf46844 				*cp = ':';
358*1553Scf46844 				(void) fprintf(stderr,
359*1553Scf46844 				    "root login dir doesn't exist:\n%s\n",
360*1553Scf46844 				    buf);
361*1553Scf46844 				break;
362*1553Scf46844 			} else if (!S_ISDIR(statbuf.st_mode)) {
363*1553Scf46844 				*cp = ':';
364*1553Scf46844 				(void) fprintf(stderr,
365*1553Scf46844 				    "root login dir is not a directory:\n%s\n",
366*1553Scf46844 				    buf);
3670Sstevel@tonic-gate 				break;
3680Sstevel@tonic-gate 			}
3690Sstevel@tonic-gate 
370*1553Scf46844 			*cp = ':';
3710Sstevel@tonic-gate 			/* root's login shell */
372*1553Scf46844 			++cp;
3730Sstevel@tonic-gate 			if (*cp && ! validsh(cp)) {
3740Sstevel@tonic-gate 				(void)fprintf(stderr,
3750Sstevel@tonic-gate 				    "Invalid root shell:\n%s\n", buf);
3760Sstevel@tonic-gate 				break;
3770Sstevel@tonic-gate 			}
3780Sstevel@tonic-gate 		}
3790Sstevel@tonic-gate 
3800Sstevel@tonic-gate 		ok++;
3810Sstevel@tonic-gate 	}
3820Sstevel@tonic-gate 	(void)fclose(ft);
3830Sstevel@tonic-gate 	if (ok)
3840Sstevel@tonic-gate 		return(0);
3850Sstevel@tonic-gate 	else {
3860Sstevel@tonic-gate 		(void)fprintf(stderr,
3870Sstevel@tonic-gate 		    "vipw: you mangled the %s file, %s unchanged\n",
3880Sstevel@tonic-gate 		    temp, orig);
3890Sstevel@tonic-gate 		return(1);
3900Sstevel@tonic-gate 	}
3910Sstevel@tonic-gate }
392