summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMichael Olbrich <m.olbrich@pengutronix.de>2008-03-14 14:54:26 +0000
committerMichael Olbrich <m.olbrich@pengutronix.de>2008-03-14 14:54:26 +0000
commitebe0291c3208e816d073db5b859276de668924c5 (patch)
tree30eccbe82438da6dddd3cb739ded538dcf8f202a /src
parentf253162d08ef05d58b2d46bfddf1320db7d3e57a (diff)
downloadurshd-ebe0291c3208e816d073db5b859276de668924c5.tar.gz
urshd-ebe0291c3208e816d073db5b859276de668924c5.tar.xz
* initial version of urshd
Diffstat (limited to 'src')
-rw-r--r--src/GNUmakefile.am8
-rw-r--r--src/urshd.c727
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;
+}
+
+
+
+