diff options
author | Michael Olbrich <m.olbrich@pengutronix.de> | 2008-03-14 14:54:26 +0000 |
---|---|---|
committer | Michael Olbrich <m.olbrich@pengutronix.de> | 2008-03-14 14:54:26 +0000 |
commit | ebe0291c3208e816d073db5b859276de668924c5 (patch) | |
tree | 30eccbe82438da6dddd3cb739ded538dcf8f202a /src | |
parent | f253162d08ef05d58b2d46bfddf1320db7d3e57a (diff) | |
download | urshd-ebe0291c3208e816d073db5b859276de668924c5.tar.gz urshd-ebe0291c3208e816d073db5b859276de668924c5.tar.xz |
* initial version of urshd
Diffstat (limited to 'src')
-rw-r--r-- | src/GNUmakefile.am | 8 | ||||
-rw-r--r-- | src/urshd.c | 727 |
2 files changed, 735 insertions, 0 deletions
diff --git a/src/GNUmakefile.am b/src/GNUmakefile.am new file mode 100644 index 0000000..629777d --- /dev/null +++ b/src/GNUmakefile.am @@ -0,0 +1,8 @@ + +bin_PROGRAMS = urshd + +urshd_SOURCES = \ + urshd.c + +MAINTAINERCLEANFILES = \ + GNUmakefile.in diff --git a/src/urshd.c b/src/urshd.c new file mode 100644 index 0000000..9aa7234 --- /dev/null +++ b/src/urshd.c @@ -0,0 +1,727 @@ +/*- + * Copyright (c) 1988, 1989 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. + */ + +#define PROG "urshd" + +/* + * PAM modifications by Michael K. Johnson <johnsonm@redhat.com> + */ + +char copyright[] = + "@(#) Copyright (c) 1988, 1989 The Regents of the University of California.\n" + "All rights reserved.\n"; + +/* + * From: @(#)rshd.c 5.38 (Berkeley) 3/2/91 + */ +char rcsid[] = + "$Id: rshd.c,v 1.25 2000/07/23 04:16:24 dholland Exp $"; +/*#include "../version.h"*/ + +/* + * remote shell server: + * [port]\0 + * remuser\0 + * locuser\0 + * command\0 + * data + */ +#include <sys/types.h> +#include <sys/param.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <fcntl.h> +#include <signal.h> + +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include <pwd.h> +#include <grp.h> +#include <syslog.h> +#include <resolv.h> +#include <unistd.h> +#include <errno.h> +#include <stdio.h> /* for vsnprintf */ +#include <stdlib.h> +#include <string.h> +#include <paths.h> +#include <stdarg.h> +#include <ctype.h> +#include <assert.h> +#include <sched.h> + +#include <urshd_config.h> + +#if defined(__GLIBC__) && (__GLIBC__ >= 2) +#define _check_rhosts_file __check_rhosts_file +#endif + +#ifdef USE_PAM +#include <security/pam_appl.h> +#include <security/pam_misc.h> +static pam_handle_t *pamh; +#endif /* USE_PAM */ + +#define OPTIONS "ahlLnp:P:" + +static int keepalive = 1; +static int check_all = 0; +static int paranoid = 0; +static int sent_null; +static int allow_root_rhosts=0; +static int priority = 0; +static int cmd_priority = 0; + +char username[20] = "USER="; +char homedir[64] = "HOME="; +char shell[64] = "SHELL="; +char path[100] = "PATH="; +char *envinit[] = + {homedir, shell, path, username, 0}; +extern char **environ; + +static void error(const char *fmt, ...); +static void doit(struct sockaddr_in *fromp); +static void getstr(char *buf, int cnt, const char *err); + +extern int _check_rhosts_file; + +/* + * Report error to client. + * Note: can't be used until second socket has connected + * to client, or older clients will hang waiting + * for that connection first. + */ +static void +error(const char *fmt, ...) { + va_list ap; + char buf[BUFSIZ], *bp = buf; + + if (sent_null == 0) *bp++ = 1; + va_start(ap, fmt); + vsnprintf(bp, sizeof(buf)-1, fmt, ap); + va_end(ap); + write(2, buf, strlen(buf)); +} + +static void fail(const char *errorstr, + const char *remuser, const char *hostname, + const char *locuser, + const char *cmdbuf) +{ + /* log the (failed) rsh request */ + syslog(LOG_INFO|LOG_AUTH, "rsh denied to %s@%s as %s: %s", + remuser, hostname, locuser, errorstr); + if (paranoid) { + syslog(LOG_INFO|LOG_AUTH, "rsh command was '%s'", cmdbuf); + } + error(errorstr, hostname); + exit(1); +} + +static void getstr(char *buf, int cnt, const char *err) { + char c; + do { + if (read(0, &c, 1) != 1) exit(1); + *buf++ = c; + if (--cnt == 0) { + error("%s too long\n", err); + exit(1); + } + } while (c != 0); +} + +static int getint(void) { + int port = 0; + char c; + do { + if (read(0, &c, 1) != 1) exit(1); + if (isascii(c) && isdigit(c)) port = port*10 + c-'0'; + } while (c != 0); + return port; +} + +static void stderr_parent(int sock, int pype, int pid) { + fd_set ready, readfrom; + char buf[BUFSIZ], sig; + int one = 1; + int nfd, cc, guys=2; + + ioctl(pype, FIONBIO, (char *)&one); + /* should set s nbio! */ + + FD_ZERO(&readfrom); + FD_SET(sock, &readfrom); + FD_SET(pype, &readfrom); + if (pype > sock) nfd = pype+1; + else nfd = sock+1; + + while (guys > 0) { + ready = readfrom; + if (select(nfd, &ready, NULL, NULL, NULL) < 0) { + if (errno != EINTR) { + break; + } + continue; + } + if (FD_ISSET(sock, &ready)) { + cc = read(sock, &sig, 1); + if (cc <= 0) { + FD_CLR(sock, &readfrom); + guys--; + } + else killpg(pid, sig); + } + if (FD_ISSET(pype, &ready)) { + cc = read(pype, buf, sizeof(buf)); + if (cc <= 0) { + shutdown(sock, 2); + FD_CLR(pype, &readfrom); + guys--; + } + else write(sock, buf, cc); + } + } + +#ifdef USE_PAM + /* + * This does not strike me as the right place for this; this is + * in a child process... what does this need to accomplish? + * + * No, it's not the child process, the code is just confusing. + */ + pam_close_session(pamh, 0); + pam_end(pamh, PAM_SUCCESS); +#endif + exit(0); +} + + +static struct passwd *doauth(const char *remuser, + const char *hostname, + const char *locuser) +{ +#ifdef USE_PAM + static struct pam_conv conv = { misc_conv, NULL }; + int retcode; +#endif + struct passwd *pwd = getpwnam(locuser); + if (pwd == NULL) return NULL; + if (pwd->pw_uid==0) paranoid = 1; + +#ifdef USE_PAM + retcode = pam_start("rsh", locuser, &conv, &pamh); + if (retcode != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_start: %s\n", pam_strerror(pamh, retcode)); + exit (1); + } + pam_set_item (pamh, PAM_RUSER, remuser); + pam_set_item (pamh, PAM_RHOST, hostname); + pam_set_item (pamh, PAM_TTY, "tty"); + + retcode = pam_authenticate(pamh, 0); + if (retcode == PAM_SUCCESS) { + retcode = pam_acct_mgmt(pamh, 0); + } + if (retcode == PAM_SUCCESS) { + /* + * Why do we need to set groups here? + * Also, this stuff should be moved down near where the setuid() is. + */ + if (setgid(pwd->pw_gid) != 0) { + pam_end(pamh, PAM_SYSTEM_ERR); + return NULL; + } + if (initgroups(locuser, pwd->pw_gid) != 0) { + pam_end(pamh, PAM_SYSTEM_ERR); + return NULL; + } + retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED); + } + + if (retcode == PAM_SUCCESS) { + retcode = pam_open_session(pamh,0); + } + if (retcode != PAM_SUCCESS) { + pam_end(pamh, retcode); + return NULL; + } + return pwd; +#else + if (pwd->pw_uid==0 && !allow_root_rhosts) return NULL; + if (_check_rhosts_file) { + if (ruserok(hostname, pwd->pw_uid==0, remuser, locuser) < 0) { + return NULL; + } + } + return pwd; +#endif +} + +static const char *findhostname(struct sockaddr_in *fromp, + const char *remuser, const char *locuser, + const char *cmdbuf) +{ + struct hostent *hp; + const char *hostname; + + hp = gethostbyaddr((char *)&fromp->sin_addr, sizeof (struct in_addr), + fromp->sin_family); + + errno = ENOMEM; /* malloc (thus strdup) may not set it */ + if (hp) hostname = strdup(hp->h_name); + else hostname = strdup(inet_ntoa(fromp->sin_addr)); + + if (hostname==NULL) { + /* out of memory? */ + error("strdup: %s\n", strerror(errno)); + exit(1); + } + + /* + * Attempt to confirm the DNS. + */ +#ifdef RES_DNSRCH + _res.options &= ~RES_DNSRCH; +#endif + hp = gethostbyname(hostname); + if (hp == NULL) { + syslog(LOG_INFO, "Couldn't look up address for %s", hostname); + fail("Couldn't get address for your host (%s)\n", + remuser, inet_ntoa(fromp->sin_addr), locuser, cmdbuf); + } + while (hp->h_addr_list[0] != NULL) { + if (!memcmp(hp->h_addr_list[0], &fromp->sin_addr, + sizeof(fromp->sin_addr))) { + return hostname; + } + hp->h_addr_list++; + } + syslog(LOG_NOTICE, "Host addr %s not listed for host %s", + inet_ntoa(fromp->sin_addr), hp->h_name); + fail("Host address mismatch for %s\n", + remuser, inet_ntoa(fromp->sin_addr), locuser, cmdbuf); + return NULL; /* not reachable */ +} + +static void +doit(struct sockaddr_in *fromp) +{ + char cmdbuf[ARG_MAX+1]; + const char *theshell, *shellname; + char locuser[16], remuser[16]; + struct passwd *pwd; + int sock = -1; + const char *hostname; + u_short port; + int pv[2], pid, ifd; + + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + + alarm(60); + port = getint(); + alarm(0); + + if (port != 0) { + int lport = IPPORT_RESERVED - 1; + sock = rresvport(&lport); + if (sock < 0) { + syslog(LOG_ERR, "can't get stderr port: %m"); + exit(1); + } + if (port >= IPPORT_RESERVED) { + syslog(LOG_ERR, "2nd port not reserved\n"); + exit(1); + } + fromp->sin_port = htons(port); + if (connect(sock, (struct sockaddr *)fromp, + sizeof(*fromp)) < 0) { + syslog(LOG_INFO, "connect second port: %m"); + exit(1); + } + } + +#if 0 + /* We're running from inetd; socket is already on 0, 1, 2 */ + dup2(f, 0); + dup2(f, 1); + dup2(f, 2); +#endif + + getstr(remuser, sizeof(remuser), "remuser"); + getstr(locuser, sizeof(locuser), "locuser"); + getstr(cmdbuf, sizeof(cmdbuf), "command"); + if (!strcmp(locuser, "root")) paranoid = 1; + + hostname = findhostname(fromp, remuser, locuser, cmdbuf); + + setpwent(); + pwd = doauth(remuser, hostname, locuser); + if (pwd == NULL) { + fail("Permission denied.\n", + remuser, hostname, locuser, cmdbuf); + } + + if (chdir(pwd->pw_dir) < 0) { + chdir("/"); + /* + * error("No remote directory.\n"); + * exit(1); + */ + } + + + if (pwd->pw_uid != 0 && !access(_PATH_NOLOGIN, F_OK)) { + error("Logins currently disabled.\n"); + exit(1); + } + + (void) write(2, "\0", 1); + sent_null = 1; + + if (port) { + if (pipe(pv) < 0) { + error("Can't make pipe.\n"); + exit(1); + } + pid = fork(); + if (pid == -1) { + error("Can't fork; try again.\n"); + exit(1); + } + if (pid) { + close(0); + close(1); + close(2); + close(pv[1]); + stderr_parent(sock, pv[0], pid); + /* NOTREACHED */ + } + setpgrp(); + close(sock); + close(pv[0]); + dup2(pv[1], 2); + close(pv[1]); + } + theshell = pwd->pw_shell; + if (!theshell || !*theshell) { + /* shouldn't we deny access? */ + theshell = _PATH_BSHELL; + } + +#if BSD > 43 + if (setlogin(pwd->pw_name) < 0) { + syslog(LOG_ERR, "setlogin() failed: %m"); + } +#endif +#ifndef USE_PAM + /* if PAM, already done */ + if (setgid(pwd->pw_gid)) { + syslog(LOG_ERR, "setgid: %m"); + exit(1); + } + if (initgroups(pwd->pw_name, pwd->pw_gid)) { + syslog(LOG_ERR, "initgroups: %m"); + exit(1); + } +#endif + if (setuid(pwd->pw_uid)) { + syslog(LOG_ERR, "setuid: %m"); + exit(1); + } + environ = envinit; + + strncat(homedir, pwd->pw_dir, sizeof(homedir)-6); + homedir[sizeof(homedir)-1] = 0; + + strcat(path, _PATH_DEFPATH); + + strncat(shell, theshell, sizeof(shell)-7); + shell[sizeof(shell)-1] = 0; + + strncat(username, pwd->pw_name, sizeof(username)-6); + username[sizeof(username)-1] = 0; + + shellname = strrchr(theshell, '/'); + if (shellname) shellname++; + else shellname = theshell; + + endpwent(); + if (paranoid) { + syslog(LOG_INFO|LOG_AUTH, "%s@%s as %s: cmd='%s'", + remuser, hostname, locuser, cmdbuf); + } + + /* + * Close all fds, in case libc has left fun stuff like + * /etc/shadow open. + */ + for (ifd = getdtablesize()-1; ifd > 2; ifd--) close(ifd); + + if (cmd_priority) { + struct sched_param schedp; + + memset(&schedp, 0, sizeof(schedp)); + schedp.sched_priority = cmd_priority; + sched_setscheduler(0, SCHED_FIFO, &schedp); + } + execl(theshell, shellname, "-c", cmdbuf, NULL); + perror(theshell); + exit(1); +} + +static void network_init(int fd, struct sockaddr_in *fromp) +{ + struct linger linger; + socklen_t fromlen; + int on=1; + int port; + + fromlen = sizeof(*fromp); + if (getpeername(fd, (struct sockaddr *) fromp, &fromlen) < 0) { + syslog(LOG_ERR, "getpeername: %m"); + _exit(1); + } + if (keepalive && + setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *)&on, + sizeof(on)) < 0) + syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m"); + linger.l_onoff = 1; + linger.l_linger = 60; /* XXX */ + if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *)&linger, + sizeof (linger)) < 0) + syslog(LOG_WARNING, "setsockopt (SO_LINGER): %m"); + + if (fromp->sin_family != AF_INET) { + syslog(LOG_ERR, "malformed \"from\" address (af %d)\n", + fromp->sin_family); + exit(1); + } +#ifdef IP_OPTIONS + { + u_char optbuf[BUFSIZ/3], *cp; + char lbuf[BUFSIZ+1], *lp; + socklen_t optsize = sizeof(optbuf); + int ipproto; + struct protoent *ip; + + if ((ip = getprotobyname("ip")) != NULL) + ipproto = ip->p_proto; + else + ipproto = IPPROTO_IP; + if (!getsockopt(0, ipproto, IP_OPTIONS, (char *)optbuf, &optsize) && + optsize != 0) { + lp = lbuf; + + /* + * If these are true, this will not run off the end of lbuf[]. + */ + assert(optsize <= BUFSIZ/3); + assert(3*optsize <= BUFSIZ); + for (cp = optbuf; optsize > 0; cp++, optsize--, lp += 3) + snprintf(lp, 4, " %2.2x", *cp); + + syslog(LOG_NOTICE, + "Connection received from %s using IP options" + " (ignored): %s", + inet_ntoa(fromp->sin_addr), lbuf); + + if (setsockopt(0, ipproto, IP_OPTIONS, NULL, optsize) != 0) { + syslog(LOG_ERR, "setsockopt IP_OPTIONS NULL: %m"); + exit(1); + } + } + } +#endif + + /* + * Check originating port for validity. + */ + port = ntohs(fromp->sin_port); + if (port >= IPPORT_RESERVED || port < IPPORT_RESERVED/2) { + syslog(LOG_NOTICE|LOG_AUTH, "Connection from %s on illegal port", + inet_ntoa(fromp->sin_addr)); + exit(1); + } +} + +int mainloop(char *port) +{ + int s; + struct servent *sp; + struct sockaddr_in sn; + int on = 1; + + memset(&sn, 0, sizeof(sn)); + sn.sin_family = AF_INET; + + if (priority) { + struct sched_param schedp; + + memset(&schedp, 0, sizeof(schedp)); + schedp.sched_priority = priority; + sched_setscheduler(0, SCHED_FIFO, &schedp); + } + + if (port) { + if ((sp = getservbyname(port, "tcp"))!=NULL) { + sn.sin_port = sp->s_port; + } + else { + int pt = atoi(port); + if (pt <= 0) { + fprintf(stderr, "telnetd: %s: bad port number\n", + port); + exit(1); + } + sn.sin_port = htons(pt); + } + } else { + sp = getservbyname("shell", "tcp"); + if (sp == 0) { + fprintf(stderr, PROG ": tcp/shell: unknown service\n"); + exit(1); + } + sn.sin_port = sp->s_port; + } + + s = socket(AF_INET, SOCK_STREAM, 0); + if (s < 0) { + perror(PROG ": socket");; + exit(1); + } + (void) setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + if (bind(s, (struct sockaddr *)&sn, sizeof(sn)) < 0) { + perror(PROG ": bind"); + exit(1); + } + if (listen(s, 1) < 0) { + perror(PROG ": listen"); + exit(1); + } + while (1) { + socklen_t len; + int ns, pid; + struct sockaddr_in from; + + fprintf(stderr, PROG ": waiting for connections.\n"); + len = sizeof(sn); + ns = accept(s, (struct sockaddr *)&sn, &len); + if (ns < 0) { + perror(PROG ": accept"); + exit(1); + } + if ((pid = fork()) < 0) { + perror(PROG ": fork"); + } + else if (pid == 0) { + close(s); + dup2(ns, 0); + dup2(ns, 1); + dup2(ns, 2); + close(ns); + network_init(0, &from); + doit(&from); + } + else { + close(ns); + } + } +} + +int +main(int argc, char *argv[]) +{ + int ch; + _check_rhosts_file=1; + + openlog(PROG, LOG_PID | LOG_ODELAY, LOG_DAEMON); + + opterr = 0; + while ((ch = getopt(argc, argv, OPTIONS)) != EOF) { + switch (ch) { + case 'a': + check_all = 1; + break; + + case 'h': + allow_root_rhosts = 1; + break; + + case 'l': + _check_rhosts_file = 0; + break; + + case 'n': + keepalive = 0; + break; + + case 'L': + paranoid = 1; + break; + + case 'p': + priority = atoi(optarg); + break; + + case 'P': + cmd_priority = atoi(optarg); + break; + + case '?': + default: + syslog(LOG_ERR, "usage: " PROG " [-%s]", OPTIONS); + exit(2); + } + } + argc -= optind; + argv += optind; + +#ifdef USE_PAM + if (_check_rhosts_file == 0 || allow_root_rhosts) + syslog(LOG_ERR, "-l and -h functionality has been moved to " + "pam_rhosts_auth in /etc/pam.conf"); +#endif /* USE_PAM */ + + mainloop(0); + return 0; +} + + + + |