/* $Id: ssh_sys_svr4.c,v 1.13 2001/02/10 23:16:34 tls Exp $ */ /* * Copyright (c) 2001 Thor Lancelot Simon. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Copyright 1999 RedBack Networks, Incorporated. * All rights reserved. * * This software is not in the public domain. It is distributed * under the terms of the license in the file LICENSE in the * same directory as this file. If you have received a copy of this * software without the LICENSE file (which means that whoever gave * you this software violated its license) you may obtain a copy from * http://www.panix.com/~tls/LICENSE.txt */ /*- * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "options.h" #include "sshd.h" #include "ssh_buffer.h" #include "ssh_crypto.h" #include "ssh_global.h" #include "ssh_paths.h" #include "ssh_sys.h" #include "ssh_util.h" #include "ssh_transport.h" #include "ssh_parse.h" #include "ssh_keyfile.h" #include "ssh_event.h" #include "pathnames.h" static char *childenv[16]; /* XXX */ static char shell[MAXINTERP + 1]; static inline int openpty(int *amaster, int *aslave, char *name, struct termios *termp, struct winsize *winp) { char *line; const char *cp1, *cp2; int master, slave; gid_t ttygid; struct group *gr; _DIAGASSERT(amaster != NULL); _DIAGASSERT(aslave != NULL); /* name may be NULL */ /* termp may be NULL */ /* winp may be NULL */ master = open("/dev/ptmx", O_RDWR); if (master < 0) { return -1; } line = ptsname(master); if (grantpt(master) < 0) { close(master); return -1; } if (unlockpt(master) < 0) { close(master); return -1; } slave = open(line, O_RDWR); if (slave < 0) { close(master); return -1; } if(ioctl(slave, I_PUSH, "ptem") < 0) goto failed; if(ioctl(slave, I_PUSH, "ldterm") < 0) goto failed; if(ioctl(slave, I_PUSH, "ttcompat") < 0) { failed: close(master); close(slave); return -1; } *amaster = master; *aslave = slave; if (name) strcpy(name, line); if (termp) (void) tcsetattr(slave, TCSAFLUSH, termp); if (winp) (void) ioctl(slave, TIOCSWINSZ, winp); return 0; } static inline pid_t forkpty(int *amaster, char *name, struct termios *termp, struct winsize *winp) { int master, slave; pid_t pid; struct sigaction act, oact; _DIAGASSERT(amaster != NULL); /* name may be NULL */ /* termp may be NULL */ /* winp may be NULL */ if (openpty(&master, &slave, name, termp, winp) == -1) return (-1); switch (pid = fork()) { case -1: return (-1); case 0: /* * child */ (void) close(master); (void)setsid(); /* Is this *really* necessary or even useful with clone ptys? */ act.sa_handler = SIG_IGN; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGHUP, &act, &oact); vhangup(); sigaction(SIGHUP, &oact, NULL); return (0); } /* * parent */ *amaster = master; (void) close(slave); return (pid); } /* * ssh_sys_allocpty: doesn't actually allocate the pty, emulating 4.4BSD. * * Instead, it sets everything up, and ssh_sys_execcmd inlines * a function from BSD libutil which includes the pty * allocation. * This means the other end may think we successfully * allocated a pty, but then failed to execute the requested * command, if, for example, we're out of ptys. NBD. */ int ssh_sys_allocpty(ssh_context_t *context, char *term, int y_row, int x_col, int x_pix, int y_pix, u_int8_t *modes, size_t msize) { static char envterm[255 + 1]; char **newenv; if ((context->term = malloc(strlen(term) + 1)) == NULL) { SSH_ERROR("ssh_sys_allocpty: unable to alloc mem for" "term: %s\n", strerror(errno)); return(1); } strncpy(context->term, term, strlen(term)); snprintf(envterm, sizeof(envterm), "TERM=%s", term); for(newenv = childenv; *newenv != NULL ; newenv++); *newenv = envterm; newenv++; *newenv = NULL; context->modes = malloc(msize); if (context->modes == NULL) { SSH_ERROR("ssh_sys_allocpty: unable to alloc mem for tty modes: %s\n", strerror(errno)); free(context->term); context->term = NULL; return(1); } memcpy(context->modes, modes, msize); context->msize = msize; /* Set window size. */ context->win.ws_row = y_row; context->win.ws_col = x_col; context->win.ws_xpixel = x_pix; context->win.ws_ypixel = y_pix; /* Set flag: ssh_sys_execcmd will check this and allocate a pty then. */ context->usepty = 1; return(0); } /* * ssh_sys_setbufsize: Set socket buffer size to increase throughput. */ void ssh_sys_setbufsize(ssh_context_t *context, int fd0, int fd1) { int size; int a, b, c, d; size = (context->transport_ctx.get_sendsize)(&context->transport_ctx); if (size > SSH_MAX_READSIZE) size = SSH_MAX_READSIZE; if (size < SSH_MIN_READSIZE) size = SSH_MIN_READSIZE; while (size > SSH_MIN_READSIZE) { a = setsockopt(fd0, SOL_SOCKET, SO_SNDBUF, &size, sizeof(int)); b = setsockopt(fd0, SOL_SOCKET, SO_RCVBUF, &size, sizeof(int)); c = setsockopt(fd1, SOL_SOCKET, SO_SNDBUF, &size, sizeof(int)); d = setsockopt(fd1, SOL_SOCKET, SO_RCVBUF, &size, sizeof(int)); if (a == 0 && b == 0 && c == 0 && d == 0) break; size -= 2048; } if (size < SSH_MIN_READSIZE) context->v1_ctx.readsize = SSH_MIN_READSIZE; else context->v1_ctx.readsize = size; } /* * ssh_sys_execcmd: execute a command. */ int ssh_sys_execcmd(ssh_context_t *context, char *cmd, int *fds) { int stdfd[2]; int errfd[2]; int mfd, sfd; char slave_name[MAXPATHLEN + 1]; struct termios term; char *shell_name; int cpid; SSH_DLOG(3, ("ssh_sys_execcmd: CMD=%s\n", cmd)); if (context->usepty) { if ((cpid = forkpty(&mfd, slave_name, NULL, &context->win)) == 0) { /* child: */ if (tcgetattr(0, &term) < 0) { SSH_ERROR("ssh_sys_execcmd: couldn't get modes" " for pty.\n"); exit(1); } if (ssh_sys_set_tty_modes(&term, context->modes, context->msize) != 0) { SSH_ERROR("ssh_sys_execcmd: set_tty_modes " "failed: %s\n", strerror(errno)); exit(1); } if (tcsetattr(0, TCSANOW, &term) < 0) { SSH_ERROR("ssh_sys_execcmd: couldn't set" " modes for pty.\n"); exit(1); } if (close_files_for_exec(context) < 0) { SSH_ERROR("ssh_sys_execcmd: unable to close " "files.\n"); exit(1); } #if 0 clear_sensitive_data(); #endif reset_signals(); /* XXX set environment: LOGNAME, TZ, SSH_CLIENT, SSH_TTY */ if (cmd != NULL && strlen(cmd) != 0) { shell_name = strrchr(shell, '/'); execle(shell, (shell_name++ == NULL) ? shell : shell_name , "-c", cmd, NULL, childenv); } else { /* XXX do further /bin/login stuff */ shell_name = strrchr(shell, '/'); execle(shell, (shell_name++ == NULL) ? shell : shell_name, "-", NULL, childenv); } SSH_DLOG(1, ("exec failed: %s\n", strerror(errno))); exit(1); } else { int size, len; int ret; /* parent: */ close(sfd); fds[0] = mfd; fds[1] = mfd; len = sizeof(int); ret = getsockopt(mfd, SOL_SOCKET, SO_RCVBUF, &size, &len); if (ret < 0) context->v1_ctx.sock_bufsize = SSH_MIN_READSIZE; else context->v1_ctx.sock_bufsize = size; } } else { /* create socket pairs. */ if (socketpair(AF_UNIX, SOCK_STREAM, 0, stdfd) < 0 || socketpair(AF_UNIX, SOCK_STREAM, 0, errfd) < 0) { SSH_DLOG(1, ("doExecCmd: socketpair failed: %s\n", strerror(errno))); return(1); } ssh_sys_setbufsize(context, stdfd[0], stdfd[1]); if ((cpid = fork()) == 0) { /* child: */ { int i; for(i = 0; i <= NSIG; i++) signal(i, SIG_DFL); } if (dup2(stdfd[0], fileno(stdin)) < 0) { SSH_ERROR("doExecCmd: dup2 stdfd -> stdin failed: %s\n", strerror(errno)); exit(1); } if (dup2(stdfd[0], fileno(stdout)) < 0) { SSH_ERROR("doExecCmd: dup2 stdfd -> stdout failed: %s\n", strerror(errno)); exit(1); } close(stdfd[0]); close(stdfd[1]); if (dup2(errfd[0], fileno(stderr)) < 0) { SSH_ERROR("doExecCmd: dup2 errfd -> stderr failed: %s\n", strerror(errno)); exit(1); } close(errfd[0]); close(errfd[1]); if (close_files_for_exec(context) < 0) { SSH_ERROR("ssh_sys_execcmd: unable to close files.\n"); exit(1); } #if 0 clear_sensitive_data(); #endif reset_signals(); shell_name = strrchr(shell, '/'); if (cmd != NULL && strlen(cmd) != 0) { execle(shell, (shell_name++ == NULL) ? shell : shell_name, "-c", cmd, NULL, childenv); } else { execle(PATH_LOGIN, "login", "-f", "-p", /*"-h", inet_ntoa(context->saddr.sin_addr),*/ "--", context->username, NULL, childenv); } SSH_DLOG(1, ("exec failed: %s\n", strerror(errno))); exit(1); } else { /* parent: */ /* Close child's side descriptors. */ close(errfd[0]); close(stdfd[0]); /* Save the descriptors. */ fds[0] = stdfd[1]; fds[1] = errfd[1]; } } context->child_pid = cpid; return(0); } /* * PRNG wrapper functions. Replace with calls to your favorite PRNG if * desired. * * A note on randinit/randclean: If you have hardware randomness, you * may want to modify randinit to use it. On NetBSD, we use the system * RNG/PRNG, specifying the device node which generates to a PRNG if no * "hard" random data is available so that we don't block at startup. We * do mix in some other gunk just in case, but you *really* want the kernel * random source turned on -- no great effort is made elsewise. * * In such an implementation, randclean just zeroes out the internal PRNG * state for neatness/safety. But an alternate implementation might try to * preserve PRNG state across runs; then randclean would save the state, and * randinit would load it, hopefully mixing in some new seed randomness. * */ /* * ssh_sys_randinit: initialize random number generator. */ void ssh_sys_randinit() { struct timeval tv; int urandom; u_int8_t ubuf[128]; gettimeofday(&tv, (struct timezone *)NULL); ssh_rand_feed((void *)&(tv.tv_sec), sizeof(tv.tv_sec)); ssh_rand_feed((void *)&(tv.tv_usec), sizeof(tv.tv_usec)); memset(&tv, 0, sizeof(tv)); system("/usr/bin/wc -c /usr/share/dict/words > /dev/null 2>&1"); system("/bin/ps auxl > /dev/null 2>&1"); system("/bin/ls -lR /home > /dev/null 2>&1"); urandom = open(_PATH_URANDOM, O_RDONLY); if (urandom >= 0) { if (read(urandom, ubuf, sizeof(ubuf)) != sizeof(ubuf)) { close(urandom); goto wingit; } else { close(urandom); ssh_rand_feed(ubuf, sizeof(ubuf)); } } else /* System PRNG unavailable -- seed with some more data and pray */ { wingit: { int numfs, whichfs = 0; struct statfs *mntbuf; numfs = getmntinfo(&mntbuf, MNT_NOWAIT); while(whichfs < numfs) { ssh_rand_feed((void *)mntbuf, sizeof(struct statfs)); memset(mntbuf, 0, sizeof(struct statfs)); mntbuf++; whichfs++; } } } gettimeofday(&tv, NULL); ssh_rand_feed((void *)&(tv.tv_usec), sizeof(tv.tv_usec)); memset(ubuf, 0, sizeof(ubuf)); memset(&tv, 0, sizeof(tv)); } /* * ssh_sys_randclean: clean up (possibly save) PRNG state. */ void ssh_sys_randclean() { ssh_rand_clean(); } /* * ssh_sys_randadd: Add some external randomness to the mix. Must be * fast, called during normal operation. */ void ssh_sys_randadd() { struct timeval tv; int urandom; u_int8_t ubuf[128]; urandom = open(_PATH_URANDOM, O_RDONLY); if (urandom < 0) { gettimeofday(&tv, NULL); ssh_rand_feed((void *)&(tv.tv_usec), sizeof(tv.tv_usec)); } else { if (read(urandom, ubuf, sizeof(ubuf)) != sizeof(ubuf)) { close(urandom); gettimeofday(&tv, NULL); ssh_rand_feed((void *)&(tv.tv_usec), sizeof(tv.tv_usec)); } else { close(urandom); ssh_rand_feed(ubuf, sizeof(ubuf)); } } memset(&tv, 0, sizeof(struct timeval)); memset(ubuf, 0, sizeof(ubuf)); } void ssh_sys_exit(int retval) { /* cleanup whatever. */ SSH_DLOG(5, ("ssh_sys_exit: exiting with %d\n", retval)); exit(retval); } /* XXX option decoding and usage probably doesn't need to be in this file. */ void ssh_sys_usage() { fprintf(stderr, "Usage: sshd %s %s %s %s %s\n", "[-Ddk]", "[-a address]", "[-h host key]", "[-p port]", "[-P pidfile]"); } int ssh_sys_configuration(int argc, char *argv, ssh_context_t *context) { int ch, d; struct servent *srv; off_t hksiz; void *keydata; while ((ch = getopt(argc, argv, "Ddka:h:p:P:")) != -1) { switch(ch) { case 'D': nodaemon = 1; break; case 'd': debug_inc(0); break; case 'k': context->opts.keepalive = 1; break; case 'h': /* Set the host key file */ context->opts.hostkey = optarg; break; case 'a': /* IP address to listen on */ context->opts.address = optarg; break; case 'p': /* Port to listen on */ context->opts.port = optarg; break; case 'P': context->opts.pidfile = optarg; break; case '?': default: ssh_sys_usage(); return(1); } } if(!context->opts.hostkey) context->opts.hostkey = _PATH_SSHD_SERVER_PRIVKEY; if(!context->opts.pidfile) context->opts.pidfile = _PATH_SSHD_PIDFILE; if(!context->opts.address) context->opts.address = "0.0.0.0"; if (!context->v1_ctx.max_packet_size) context->v1_ctx.max_packet_size = SSH_MAX_PACKET_SIZE; /* One might call this a foolish consistency. We keep 'port', like 'address', as a string. But getservbyname gives us a ready-to-use short in network byte order. So we convert it back. Oh well. */ if(!context->opts.port) { srv = getservbyname("ssh", "tcp"); if(srv != NULL) { context->opts.port = malloc(6 * sizeof(char)); snprintf(context->opts.port, 6, "%d", ntohs(srv->s_port)); } else { context->opts.port = SSHD_PORT; } } if ((d = open(context->opts.hostkey, O_RDONLY, 0600)) < 0) { SSH_ERROR("couldn't open host key file %s: %s\n", context->opts.hostkey, strerror(errno)); ssh_sys_usage(); return(1); } hksiz = lseek(d, 0, SEEK_END); keydata = mmap(NULL, (size_t)hksiz, PROT_READ, MAP_PRIVATE, d, (off_t)0); close(d); if (keydata == NULL) { SSH_ERROR("couldn't access host key %s: %s\n", context->opts.hostkey, strerror(errno)); ssh_sys_usage(); return(1); } if (decode_keyfile(keydata, hksiz, NULL, 0, &context->v1_ctx.hostkey, NULL, NULL) != 0) { SSH_ERROR("host key %s is corrupt (did not decode).\n"); munmap(keydata, hksiz); ssh_sys_usage(); return(1); } munmap(keydata, hksiz); return(0); } /* * ssh_sys_setuser: * Set up the initial environment. * Switch uid/gid. * chdir to home directory. */ int ssh_sys_setuser(ssh_context_t *context, char *uname) { char **newenv = childenv; struct ssh_password *pwent; static char ename[5 + MAXLOGNAME + 1]; static char ehome[5 + MAXPATHLEN + 1]; static char eshell[6 + MAXINTERP + 1]; static char epath[32]; if (!context->pwent.pw_name) if (ssh_sys_lookupuser(context, uname) != 0) return(-1); pwent = &context->pwent; (void)setgid(pwent->pw_gid); (void)initgroups(pwent->pw_name, pwent->pw_gid); (void)setuid(pwent->pw_uid); snprintf(ename, sizeof(ename), "USER=%s", pwent->pw_name); snprintf(ehome, sizeof(ehome), "HOME=%s", pwent->pw_dir); snprintf(eshell, sizeof(eshell), "SHELL=%s", pwent->pw_shell); snprintf(epath, sizeof(epath), "PATH=/bin:/usr/bin:/usr/pkg/bin"); *newenv = ename; newenv++; *newenv = ehome; newenv++; *newenv = eshell; newenv++; *newenv = epath; newenv++; *newenv = NULL; chdir(pwent->pw_dir); snprintf(shell, sizeof(shell), "%s", pwent->pw_shell); return 0; } int ssh_sys_daemonize() { /* return daemon(0, 0); */ int fd; switch(fork()) { case -1: return (-1); case 0: break; default: _exit(0); } if (setsid() == -1) return (-1); (void)chdir("/"); if(fd = open("/dev/null", O_RDWR, 0) != -1) { (void)dup2(fd, STDIN_FILENO); (void)dup2(fd, STDOUT_FILENO); (void)dup2(fd, STDERR_FILENO); if (fd > STDERR_FILENO) (void)close(fd); } return(0); } int ssh_sys_create_eventq(struct ssh_context *context) { int opt = sizeof(struct ssh_event), s = 0; if(socketpair(AF_LOCAL, SOCK_DGRAM, 0, context->eventq) < 0) return -1; s+= setsockopt(context->eventq[0], SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt)); s+= setsockopt(context->eventq[0], SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)); s+= setsockopt(context->eventq[1], SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt)); s+= setsockopt(context->eventq[1], SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)); if (s < 0) { close(context->eventq[0]); close(context->eventq[1]); context->eventq[0] = context->eventq[1] = -1; return -1; } return 0; } void ssh_sys_collapse_eventq(struct ssh_context *context) { if (context->thread_id == SSH_THREAD_RECV) { /* swap em, so that we always close [1] and always use [0] */ int t = context->eventq[0]; context->eventq[0] = context->eventq[1]; context->eventq[1] = t; } close(context->eventq[1]); } int ssh_sys_select(struct ssh_context *context, int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout, struct ssh_event *ev) { int num; int my_eventfd = -1; my_eventfd = context->eventq[0]; ev->event_type = SSH_EVENT_NONE; if(my_eventfd > 0) { FD_SET(my_eventfd, readfds); nfds++; num = select(nfds, readfds, writefds, exceptfds, timeout); if(num > 0 && FD_ISSET(my_eventfd, readfds)) { int resid = sizeof(struct ssh_event), howmuch; void *tmpev = ev; while(resid != 0) { howmuch = read(my_eventfd, tmpev, resid); if(howmuch <= 0) { if(errno == EINTR) continue; else { SSH_ERROR("Short message" "read: %s", strerror(errno)); return -1; } } resid -= howmuch; tmpev += howmuch; } FD_CLR(my_eventfd, readfds); num--; } } else { num = select(nfds, readfds, writefds, exceptfds, timeout); } return num; } int ssh_sys_sendevent(struct ssh_context *context, struct ssh_event *ev) { int dest_eventfd = -1; int resid = sizeof(struct ssh_event), howmuch; void *tmpev = ev; dest_eventfd = context->eventq[1]; if(context->thread_id == SSH_THREAD_SEND) { SSH_ERROR("Tried to send message from THREAD_SEND\n"); return -1; } if(ev->event_type == SSH_EVENT_NONE) { SSH_ERROR("Tried to send EVENT_NONE\n"); return -1; } while(resid != 0) { howmuch = write(dest_eventfd, ev, resid); if(howmuch < 0) { if(errno == EINTR) continue; else { SSH_ERROR("Short message write: %s", strerror(errno)); return -1; } } resid -= howmuch; tmpev += howmuch; } return 0; } void * ssh_sys_readin(const char *filename, off_t *len) { int d; void *v; d = open(filename, O_RDONLY, 0600); if(d < 0) return NULL; *len = lsesk(d, 0, SEEK_END); v = mmap(NULL, (size_t) *len, PROT_READ, MAP_PRIVATE, d, (off_t)0); close(d); return v; } int ssh_sys_checkpw(char *uname, const char *pw) { int ret; struct spwd *pwent; pwent = getspnam(uname); if (pwent == NULL) return -1; if (pwent->sp_pwdp == NULL) { ret = 0; goto out; } if (!strcmp(crypt(pw, pwent->sp_pwdp), pwent->sp_pwdp)) ret = 0; else ret = -1; out: memset(pwent->sp_pwdp, 0, strlen(pwent->sp_pwdp)); endspent(); return (ret); }