diff options
author | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
---|---|---|
committer | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
commit | 7c478bd95313f5f23a4c958a745db2134aa03244 (patch) | |
tree | c871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/lib/libsocket/inet/rcmd.c | |
download | illumos-joyent-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz |
OpenSolaris Launch
Diffstat (limited to 'usr/src/lib/libsocket/inet/rcmd.c')
-rw-r--r-- | usr/src/lib/libsocket/inet/rcmd.c | 820 |
1 files changed, 820 insertions, 0 deletions
diff --git a/usr/src/lib/libsocket/inet/rcmd.c b/usr/src/lib/libsocket/inet/rcmd.c new file mode 100644 index 0000000000..8cb2cd1291 --- /dev/null +++ b/usr/src/lib/libsocket/inet/rcmd.c @@ -0,0 +1,820 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (the "License"). You may not use this file except in compliance + * with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + +/* + * University Copyright- Copyright (c) 1982, 1986, 1988 + * The Regents of the University of California + * All Rights Reserved + * + * University Acknowledgment- Portions of this document are derived from + * software developed by the University of California, Berkeley, and its + * contributors. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <limits.h> +#include <stdio.h> +#include <ctype.h> +#include <pwd.h> +#include <sys/types.h> +#include <sys/param.h> +#include <sys/file.h> +#include <signal.h> +#include <libintl.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <inet/common.h> + +#include <netdb.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <grp.h> +#include <arpa/inet.h> + +#include <priv_utils.h> + +#ifdef SYSV +#define bcopy(s1, s2, len) (void) memcpy(s2, s1, len) +#define bzero(s, len) (void) memset(s, 0, len) +#define index(s, c) strchr(s, c) +char *strchr(); +#else +char *index(); +#endif /* SYSV */ + +extern char *_dgettext(); +extern int _sigaction(); +extern int _sigaddset(); +extern int _sigprocmask(); +extern int _fcntl(); +extern int usingypmap(); + +static int _validuser(FILE *hostf, char *rhost, const char *luser, + const char *ruser, int baselen); +static int _checkhost(char *rhost, char *lhost, int len); + + +#ifdef NIS +static char *domain; +#endif + +int rcmd(char **ahost, unsigned short rport, const char *locuser, + const char *remuser, const char *cmd, int *fd2p) +{ + int rcmd_ret; + + rcmd_ret = rcmd_af(ahost, rport, locuser, remuser, cmd, fd2p, + AF_INET); + return (rcmd_ret); +} + +int rcmd_af(char **ahost, unsigned short rport, const char *locuser, + const char *remuser, const char *cmd, int *fd2p, int af) +{ + int s, timo = 1; + ssize_t retval; + pid_t pid; + struct sockaddr_storage caddr, faddr; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + struct addrinfo hints; + struct addrinfo *res, *resp; + size_t addrlen; + int rc; +#define MAX_SHORTSTRLEN 6 + char aport[MAX_SHORTSTRLEN]; + char c; + int lport = 0; +#ifdef SYSV + sigset_t oldmask; + sigset_t newmask; + struct sigaction oldaction; + struct sigaction newaction; +#else + int oldmask; +#endif /* SYSV */ + fd_set fdset; + int selret; + char *addr; + static char hostname[MAXHOSTNAMELEN]; + socklen_t len; + char abuf[INET6_ADDRSTRLEN]; + + if (!(af == AF_INET || af == AF_INET6 || af == AF_UNSPEC)) { + errno = EAFNOSUPPORT; + return (-1); + } + + pid = getpid(); + memset(&hints, 0, sizeof (hints)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_CANONNAME; + if (af == AF_INET6) { + hints.ai_flags |= AI_V4MAPPED; + hints.ai_family = AF_UNSPEC; + } else { + hints.ai_family = af; + } + (void) snprintf(aport, MAX_SHORTSTRLEN, "%u", ntohs(rport)); + rc = getaddrinfo(*ahost, aport, &hints, &res); + if (rc != 0) { + (void) fprintf(stderr, + _dgettext(TEXT_DOMAIN, "%s: unknown host%s\n"), + *ahost, rc == EAI_AGAIN ? " (try again later)" : ""); + return (-1); + } + resp = res; + (void) strlcpy(hostname, res->ai_canonname, MAXHOSTNAMELEN); + *ahost = hostname; +#ifdef SYSV + /* ignore SIGPIPE */ + bzero((char *)&newaction, sizeof (newaction)); + newaction.sa_handler = SIG_IGN; + (void) _sigaction(SIGPIPE, &newaction, &oldaction); + + /* block SIGURG */ + bzero((char *)&newmask, sizeof (newmask)); + (void) _sigaddset(&newmask, SIGURG); + (void) _sigprocmask(SIG_BLOCK, &newmask, &oldmask); +#else + oldmask = _sigblock(sigmask(SIGURG)); +#endif /* SYSV */ + for (;;) { + s = rresvport_af(&lport, res->ai_family); + if (s < 0) { + int af = res->ai_family; + + /* + * See if we have any addresses of a different type + * to try. + */ + while (res != NULL && res->ai_family == af) + res = res->ai_next; + + if (res != NULL) + continue; + + if (errno == EAGAIN) + (void) fprintf(stderr, + _dgettext(TEXT_DOMAIN, + "socket: All ports in use\n")); + else + perror("rcmd: socket"); +#ifdef SYSV + /* restore original SIGPIPE handler */ + (void) _sigaction(SIGPIPE, &oldaction, + (struct sigaction *)0); + + /* restore original signal mask */ + (void) _sigprocmask(SIG_SETMASK, &oldmask, + (sigset_t *)0); +#else + sigsetmask(oldmask); +#endif /* SYSV */ + freeaddrinfo(resp); + return (-1); + } + bzero((char *)&caddr, sizeof (caddr)); + bcopy(res->ai_addr, &caddr, res->ai_addrlen); + addrlen = res->ai_addrlen; + if (af == AF_INET6 && res->ai_addr->sa_family == AF_INET) { + struct in6_addr ia6; + struct sockaddr_in6 *in6addr; + IN6_INADDR_TO_V4MAPPED(&((struct sockaddr_in *) + res->ai_addr)->sin_addr, &ia6); + in6addr = (struct sockaddr_in6 *)&caddr; + in6addr->sin6_addr = ia6; + in6addr->sin6_family = AF_INET6; + addrlen = sizeof (struct sockaddr_in6); + } + (void) _fcntl(s, F_SETOWN, pid); + if (connect(s, (struct sockaddr *)&caddr, addrlen) >= 0) + break; + (void) close(s); + if (errno == EADDRINUSE) { + lport = 0; + continue; + } + if (errno == ECONNREFUSED && timo <= 16) { + (void) sleep(timo); + timo *= 2; + continue; + } + if (res->ai_next != NULL) { + int oerrno = errno; + if (res->ai_addr->sa_family == AF_INET6) + addr = (char *)&((struct sockaddr_in6 *) + res->ai_addr)->sin6_addr; + else + addr = (char *)&((struct sockaddr_in *) + res->ai_addr)->sin_addr; + (void) fprintf(stderr, + _dgettext(TEXT_DOMAIN, "connect to address %s: "), + inet_ntop(res->ai_addr->sa_family, addr, + abuf, sizeof (abuf))); + errno = oerrno; + perror(0); + res = res->ai_next; + if (res->ai_addr->sa_family == AF_INET6) + addr = (char *)&((struct sockaddr_in6 *) + res->ai_addr)->sin6_addr; + else + addr = (char *)&((struct sockaddr_in *) + res->ai_addr)->sin_addr; + (void) fprintf(stderr, + _dgettext(TEXT_DOMAIN, "Trying %s...\n"), + inet_ntop(res->ai_addr->sa_family, addr, + abuf, sizeof (abuf))); + continue; + } + perror(*ahost); + freeaddrinfo(resp); +#ifdef SYSV + /* restore original SIGPIPE handler */ + (void) _sigaction(SIGPIPE, &oldaction, + (struct sigaction *)0); + + /* restore original signal mask */ + (void) _sigprocmask(SIG_SETMASK, &oldmask, (sigset_t *)0); +#else + sigsetmask(oldmask); +#endif /* SYSV */ + return (-1); + } + lport = 0; + if (fd2p == 0) { + (void) write(s, "", 1); + } else { + int s2 = rresvport_af(&lport, res->ai_family), s3; + + len = (socklen_t)sizeof (faddr); + + if (s2 < 0) + goto bad; + (void) listen(s2, 1); + (void) snprintf(aport, MAX_SHORTSTRLEN, "%d", lport); + if (write(s, aport, strlen(aport)+1) != strlen(aport)+1) { + perror(_dgettext(TEXT_DOMAIN, + "write: setting up stderr")); + (void) close(s2); + goto bad; + } + FD_ZERO(&fdset); + FD_SET(s, &fdset); + FD_SET(s2, &fdset); + while ((selret = select(FD_SETSIZE, &fdset, (fd_set *)0, + (fd_set *)0, (struct timeval *)0)) > 0) { + if (FD_ISSET(s, &fdset)) { + /* + * Something's wrong: we should get no + * data on this connection at this point, + * so we assume that the connection has + * gone away. + */ + (void) close(s2); + goto bad; + } + if (FD_ISSET(s2, &fdset)) { + /* + * We assume this is an incoming connect + * request and proceed normally. + */ + s3 = accept(s2, (struct sockaddr *)&faddr, + &len); + FD_CLR(s2, &fdset); + (void) close(s2); + if (s3 < 0) { + perror("accept"); + lport = 0; + goto bad; + } + else + break; + } + } + if (selret == -1) { + /* + * This should not happen, and we treat it as + * a fatal error. + */ + (void) close(s2); + goto bad; + } + + *fd2p = s3; + switch (faddr.ss_family) { + case AF_INET: + sin = (struct sockaddr_in *)&faddr; + if (ntohs(sin->sin_port) >= IPPORT_RESERVED) { + (void) fprintf(stderr, + _dgettext(TEXT_DOMAIN, + "socket: protocol failure in circuit " + "setup.\n")); + goto bad2; + } + break; + case AF_INET6: + sin6 = (struct sockaddr_in6 *)&faddr; + if (ntohs(sin6->sin6_port) >= IPPORT_RESERVED) { + (void) fprintf(stderr, + _dgettext(TEXT_DOMAIN, + "socket: protocol failure in circuit " + "setup.\n")); + goto bad2; + } + break; + default: + (void) fprintf(stderr, + _dgettext(TEXT_DOMAIN, + "socket: protocol failure in circuit setup.\n")); + goto bad2; + } + } + (void) write(s, locuser, strlen(locuser)+1); + (void) write(s, remuser, strlen(remuser)+1); + (void) write(s, cmd, strlen(cmd)+1); + retval = read(s, &c, 1); + if (retval != 1) { + if (retval == 0) { + (void) fprintf(stderr, + _dgettext(TEXT_DOMAIN, + "Protocol error, %s closed connection\n"), + *ahost); + } else if (retval < 0) { + perror(*ahost); + } else { + (void) fprintf(stderr, + _dgettext(TEXT_DOMAIN, + "Protocol error, %s sent %d bytes\n"), + *ahost, retval); + } + goto bad2; + } + if (c != 0) { + while (read(s, &c, 1) == 1) { + (void) write(2, &c, 1); + if (c == '\n') + break; + } + goto bad2; + } +#ifdef SYSV + /* restore original SIGPIPE handler */ + (void) _sigaction(SIGPIPE, &oldaction, (struct sigaction *)0); + + /* restore original signal mask */ + (void) _sigprocmask(SIG_SETMASK, &oldmask, (sigset_t *)0); +#else + sigsetmask(oldmask); +#endif /* SYSV */ + freeaddrinfo(resp); + return (s); +bad2: + if (lport) + (void) close(*fd2p); +bad: + (void) close(s); +#ifdef SYSV + /* restore original SIGPIPE handler */ + (void) _sigaction(SIGPIPE, &oldaction, (struct sigaction *)0); + + /* restore original signal mask */ + (void) _sigprocmask(SIG_SETMASK, &oldmask, (sigset_t *)0); +#else + sigsetmask(oldmask); +#endif /* SYSV */ + freeaddrinfo(resp); + return (-1); +} + +static int +_rresvport_addr(int *alport, struct sockaddr_storage *addr) +{ + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + int s; + socklen_t len; + int on = 1; + int off = 0; + + if (addr->ss_family == AF_INET) { + sin = (struct sockaddr_in *)addr; + len = sizeof (struct sockaddr_in); + } else if (addr->ss_family == AF_INET6) { + sin6 = (struct sockaddr_in6 *)addr; + len = sizeof (struct sockaddr_in6); + } else { + errno = EAFNOSUPPORT; + return (-1); + } + s = socket(addr->ss_family, SOCK_STREAM, 0); + if (s < 0) + return (-1); + + /* + * Set TCP_EXCLBIND to get a "unique" port, which is not bound + * to any other sockets. + */ + if (setsockopt(s, IPPROTO_TCP, TCP_EXCLBIND, &on, sizeof (on)) < 0) { + (void) close(s); + return (-1); + } + + /* Try to bind() to the given port first. */ + if (*alport != 0) { + if (addr->ss_family == AF_INET) { + sin->sin_port = htons((ushort_t)*alport); + } else { + sin6->sin6_port = htons((ushort_t)*alport); + } + if (bind(s, (struct sockaddr *)addr, len) >= 0) { + /* To be safe, need to turn off TCP_EXCLBIND. */ + (void) setsockopt(s, IPPROTO_TCP, TCP_EXCLBIND, &off, + sizeof (off)); + return (s); + } + if (errno != EADDRINUSE) { + (void) close(s); + return (-1); + } + } + + /* + * If no port is given or the above bind() does not succeed, set + * TCP_ANONPRIVBIND option to ask the kernel to pick a port in the + * priviledged range for us. + */ + if (setsockopt(s, IPPROTO_TCP, TCP_ANONPRIVBIND, &on, + sizeof (on)) < 0) { + (void) close(s); + return (-1); + } + if (addr->ss_family == AF_INET) { + sin->sin_port = 0; + } else { + sin6->sin6_port = 0; + } + if (bind(s, (struct sockaddr *)addr, len) >= 0) { + /* + * We need to tell the caller what the port is. + */ + if (getsockname(s, (struct sockaddr *)addr, &len) < 0) { + (void) close(s); + return (-1); + } + switch (addr->ss_family) { + case AF_INET6: + sin6 = (struct sockaddr_in6 *)addr; + *alport = ntohs(sin6->sin6_port); + break; + case AF_INET: + sin = (struct sockaddr_in *)addr; + *alport = ntohs(sin->sin_port); + break; + } + + /* + * To be safe, always turn off these options when we are done. + */ + (void) setsockopt(s, IPPROTO_TCP, TCP_ANONPRIVBIND, &off, + sizeof (off)); + (void) setsockopt(s, IPPROTO_TCP, TCP_EXCLBIND, &off, + sizeof (off)); + return (s); + } + (void) close(s); + return (-1); +} + +int +rresvport_addr(int *alport, struct sockaddr_storage *addr) +{ + int res, err; + + (void) __priv_bracket(PRIV_ON); + + res = _rresvport_addr(alport, addr); + + err = errno; + (void) __priv_bracket(PRIV_OFF); + errno = err; + + return (res); +} + +int +rresvport_af(int *alport, int af) +{ + struct sockaddr_storage laddr; + + bzero(&laddr, sizeof (laddr)); + if (af == AF_INET || af == AF_INET6) { + laddr.ss_family = (sa_family_t)af; + } else { + errno = EAFNOSUPPORT; + return (-1); + } + return (rresvport_addr(alport, &laddr)); +} + +int +rresvport(int *alport) +{ + return (rresvport_af(alport, AF_INET)); +} + +int +ruserok(const char *rhost, int superuser, const char *ruser, const char *luser) +{ + FILE *hostf; + char fhost[MAXHOSTNAMELEN]; + const char *sp; + char *p; + int baselen = -1; + + struct stat64 sbuf; + struct passwd *pwd; + char pbuf[MAXPATHLEN]; + uid_t uid = (uid_t)-1; + gid_t gid = (gid_t)-1; + gid_t grouplist[NGROUPS_MAX]; + int ngroups; + + sp = rhost; + p = fhost; + while (*sp) { + if (*sp == '.') { + if (baselen == -1) + baselen = (int)(sp - rhost); + *p++ = *sp++; + } else { + *p++ = isupper(*sp) ? tolower(*sp++) : *sp++; + } + } + *p = '\0'; + + /* check /etc/hosts.equiv */ + if (!superuser) { + if ((hostf = fopen("/etc/hosts.equiv", "r")) != NULL) { + if (!_validuser(hostf, fhost, luser, ruser, baselen)) { + (void) fclose(hostf); + return (0); + } + (void) fclose(hostf); + } + } + + /* check ~/.rhosts */ + + if ((pwd = getpwnam(luser)) == NULL) + return (-1); + (void) strcpy(pbuf, pwd->pw_dir); + (void) strcat(pbuf, "/.rhosts"); + + /* + * Read .rhosts as the local user to avoid NFS mapping the root uid + * to something that can't read .rhosts. + */ + gid = getegid(); + uid = geteuid(); + if ((ngroups = getgroups(NGROUPS_MAX, grouplist)) == -1) + return (-1); + + (void) setegid(pwd->pw_gid); + initgroups(pwd->pw_name, pwd->pw_gid); + (void) seteuid(pwd->pw_uid); + if ((hostf = fopen(pbuf, "r")) == NULL) { + if (gid != (gid_t)-1) + (void) setegid(gid); + if (uid != (uid_t)-1) + (void) seteuid(uid); + setgroups(ngroups, grouplist); + return (-1); + } + (void) fstat64(fileno(hostf), &sbuf); + if (sbuf.st_uid && sbuf.st_uid != pwd->pw_uid) { + (void) fclose(hostf); + if (gid != (gid_t)-1) + (void) setegid(gid); + if (uid != (uid_t)-1) + (void) seteuid(uid); + setgroups(ngroups, grouplist); + return (-1); + } + + if (!_validuser(hostf, fhost, luser, ruser, baselen)) { + (void) fclose(hostf); + if (gid != (gid_t)-1) + (void) setegid(gid); + if (uid != (uid_t)-1) + (void) seteuid(uid); + setgroups(ngroups, grouplist); + return (0); + } + + (void) fclose(hostf); + if (gid != (gid_t)-1) + (void) setegid(gid); + if (uid != (uid_t)-1) + (void) seteuid(uid); + setgroups(ngroups, grouplist); + return (-1); +} + +static int +_validuser(FILE *hostf, char *rhost, const char *luser, + const char *ruser, int baselen) +{ + char *user; + char ahost[BUFSIZ]; + char *uchost = (char *)NULL; + int hostmatch, usermatch; + char *p; + +#ifdef NIS + if (domain == NULL) { + (void) usingypmap(&domain, NULL); + } +#endif /* NIS */ + + while (fgets(ahost, (int)sizeof (ahost), hostf)) { + uchost = (char *)NULL; + hostmatch = usermatch = 0; + p = ahost; + /* + * We can get a line bigger than our buffer. If so we skip + * the offending line. + */ + if (strchr(p, '\n') == NULL) { + while (fgets(ahost, (int)sizeof (ahost), hostf) && + strchr(ahost, '\n') == NULL) + ; + continue; + } + while (*p != '\n' && *p != ' ' && *p != '\t' && *p != '\0') { + /* + * Both host and user ``names'' can be netgroups, + * and must have their case preserved. Case is + * preserved for user names because we break out + * of this loop when finding a field separator. + * To do so for host names, we must make a copy of + * the host name field. + */ + if (isupper(*p)) { + if (uchost == (char *)NULL) + uchost = strdup(ahost); + *p = tolower(*p); + } + p++; + } + if (*p != '\0' && uchost != (char *)NULL) + uchost[p - ahost] = '\0'; + if (*p == ' ' || *p == '\t') { + *p++ = '\0'; + while (*p == ' ' || *p == '\t') + p++; + user = p; + while (*p != '\n' && *p != ' ' && *p != '\t' && + *p != '\0') + p++; + } else + user = p; + *p = '\0'; + if (ahost[0] == '+' && ahost[1] == 0) + hostmatch = 1; +#ifdef NIS + else if (ahost[0] == '+' && ahost[1] == '@') + if (uchost != (char *)NULL) + hostmatch = innetgr(uchost + 2, rhost, + NULL, domain); + else + hostmatch = innetgr(ahost + 2, rhost, + NULL, domain); + else if (ahost[0] == '-' && ahost[1] == '@') { + if (uchost != (char *)NULL) { + if (innetgr(uchost + 2, rhost, NULL, domain)) + break; + } else { + if (innetgr(ahost + 2, rhost, NULL, domain)) + break; + } + } +#endif /* NIS */ + else if (ahost[0] == '-') { + if (_checkhost(rhost, ahost+1, baselen)) + break; + } + else + hostmatch = _checkhost(rhost, ahost, baselen); + if (user[0]) { + if (user[0] == '+' && user[1] == 0) + usermatch = 1; +#ifdef NIS + else if (user[0] == '+' && user[1] == '@') + usermatch = innetgr(user+2, NULL, + ruser, domain); + else if (user[0] == '-' && user[1] == '@') { + if (hostmatch && + innetgr(user+2, NULL, ruser, domain)) + break; + } +#endif /* NIS */ + else if (user[0] == '-') { + if (hostmatch && (strcmp(user+1, ruser) == 0)) + break; + } + else + usermatch = (strcmp(user, ruser) == 0); + } + else + usermatch = (strcmp(ruser, luser) == 0); + if (uchost != (char *)NULL) + free(uchost); + if (hostmatch && usermatch) + return (0); + } + + if (uchost != (char *)NULL) + free(uchost); + return (-1); +} + +static int +_checkhost(char *rhost, char *lhost, int len) +{ + static char *ldomain; + static char *domainp; + static int nodomain; + char *cp; + + if (ldomain == NULL) { + ldomain = (char *)malloc(MAXHOSTNAMELEN+1); + if (ldomain == 0) + return (0); + } + + if (len == -1) + return (strcmp(rhost, lhost) == 0); + if (strncmp(rhost, lhost, len)) + return (0); + if (strcmp(rhost, lhost) == 0) + return (1); + if (*(lhost + len) != '\0') + return (0); + if (nodomain) + return (0); + if (!domainp) { + /* + * "domainp" points after the first dot in the host name + */ + if (gethostname(ldomain, MAXHOSTNAMELEN) == -1) { + nodomain = 1; + return (0); + } + ldomain[MAXHOSTNAMELEN] = NULL; + if ((domainp = index(ldomain, '.')) == (char *)NULL) { + nodomain = 1; + return (0); + } + domainp++; + cp = domainp; + while (*cp) { + *cp = isupper(*cp) ? tolower(*cp) : *cp; + cp++; + } + } + return (strcmp(domainp, rhost + len + 1) == 0); +} |