Correct BSD License clause numbering from 1-2-4 to 1-2-3.
[dragonfly.git] / libexec / rshd / rshd.c
1 /*-
2  * Copyright (c) 1988, 1989, 1992, 1993, 1994
3  *      The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the University nor the names of its contributors
14  *    may be used to endorse or promote products derived from this software
15  *    without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  * @(#) Copyright (c) 1988, 1989, 1992, 1993, 1994 The Regents of the University of California.  All rights reserved.
30  * @(#)rshd.c   8.2 (Berkeley) 4/6/94
31  * $FreeBSD: src/libexec/rshd/rshd.c,v 1.30.2.5 2002/05/14 22:27:21 des Exp $
32  */
33
34 /*
35  * remote shell server:
36  *      [port]\0
37  *      remuser\0
38  *      locuser\0
39  *      command\0
40  *      data
41  */
42 #include <sys/param.h>
43 #include <sys/ioctl.h>
44 #include <sys/time.h>
45 #include <sys/socket.h>
46
47 #include <netinet/in_systm.h>
48 #include <netinet/in.h>
49 #include <netinet/ip.h>
50 #include <netinet/tcp.h>
51 #include <arpa/inet.h>
52 #include <netdb.h>
53
54 #include <errno.h>
55 #include <fcntl.h>
56 #include <libutil.h>
57 #include <paths.h>
58 #include <pwd.h>
59 #include <signal.h>
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <string.h>
63 #include <syslog.h>
64 #include <unistd.h>
65 #include <stdarg.h>
66 #ifdef  LOGIN_CAP
67 #include <login_cap.h>
68 #endif
69
70 /* wrapper for KAME-special getnameinfo() */
71 #ifndef NI_WITHSCOPEID
72 #define NI_WITHSCOPEID  0
73 #endif
74
75 extern int __check_rhosts_file;
76 extern char *__rcmd_errstr;     /* syslog hook from libc/net/rcmd.c. */
77
78 int     keepalive = 1;
79 int     log_success;            /* If TRUE, log all successful accesses */
80 int     sent_null;
81 int     no_delay;
82 #ifdef CRYPT
83 int     doencrypt = 0;
84 #endif
85
86 union sockunion {
87         struct sockinet {
88                 u_char si_len;
89                 u_char si_family;
90                 u_short si_port;
91         } su_si;
92         struct sockaddr_in  su_sin;
93         struct sockaddr_in6 su_sin6;
94 };
95 #define su_len          su_si.si_len
96 #define su_family       su_si.si_family
97 #define su_port         su_si.si_port
98
99 void     doit(union sockunion *);
100 void     error(const char *, ...) __printflike(1, 2);
101 void     getstr(char *, int, const char *);
102 int      local_domain(char *);
103 char    *topdomain(char *);
104 void     usage(void);
105
106 char    slash[] = "/";
107 char    bshell[] = _PATH_BSHELL;
108
109 #define OPTIONS "alnDL"
110
111 int
112 main(int argc, char *argv[])
113 {
114         struct linger linger;
115         socklen_t fromlen;
116         int ch, on = 1;
117         struct sockaddr_storage from;
118
119         openlog("rshd", LOG_PID | LOG_ODELAY, LOG_DAEMON);
120
121         opterr = 0;
122         while ((ch = getopt(argc, argv, OPTIONS)) != -1)
123                 switch (ch) {
124                 case 'a':
125                         /* ignored for compatibility */
126                         break;
127                 case 'l':
128                         __check_rhosts_file = 0;
129                         break;
130                 case 'n':
131                         keepalive = 0;
132                         break;
133 #ifdef CRYPT
134                 case 'x':
135                         doencrypt = 1;
136                         break;
137 #endif
138                 case 'D':
139                         no_delay = 1;
140                         break;
141                 case 'L':
142                         log_success = 1;
143                         break;
144                 case '?':
145                 default:
146                         usage();
147                         break;
148                 }
149
150         argc -= optind;
151         argv += optind;
152
153 #ifdef CRYPT
154         if (doencrypt) {
155                 syslog(LOG_ERR, "-k is required for -x");
156                 exit(2);
157         }
158 #endif
159
160         fromlen = sizeof (from);
161         if (getpeername(0, (struct sockaddr *)&from, &fromlen) < 0) {
162                 syslog(LOG_ERR, "getpeername: %m");
163                 exit(1);
164         }
165         if (keepalive &&
166             setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, (char *)&on,
167             sizeof(on)) < 0)
168                 syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m");
169         linger.l_onoff = 1;
170         linger.l_linger = 60;                   /* XXX */
171         if (setsockopt(0, SOL_SOCKET, SO_LINGER, (char *)&linger,
172             sizeof (linger)) < 0)
173                 syslog(LOG_WARNING, "setsockopt (SO_LINGER): %m");
174         if (no_delay &&
175             setsockopt(0, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) < 0)
176                 syslog(LOG_WARNING, "setsockopt (TCP_NODELAY): %m");
177         doit((union sockunion *)&from);
178         /* NOTREACHED */
179         return(0);
180 }
181
182 char    username[20] = "USER=";
183 char    homedir[64] = "HOME=";
184 char    shell[64] = "SHELL=";
185 char    path[100] = "PATH=";
186 char    *envinit[] =
187             {homedir, shell, path, username, 0};
188 char    **environ;
189
190 void
191 doit(union sockunion *fromp)
192 {
193         struct passwd *pwd;
194         u_short port;
195         fd_set ready, readfrom;
196         int cc, nfd, pv[2], pid, s;
197         int one = 1;
198         const char *errorstr;
199         char *cp, sig, buf[BUFSIZ];
200         char cmdbuf[NCARGS+1], locuser[16], remuser[16];
201         char fromhost[2 * MAXHOSTNAMELEN + 1];
202         char numericname[INET6_ADDRSTRLEN];
203         int af = fromp->su_family, gaierror;
204 #ifdef  CRYPT
205         int rc;
206         int pv1[2], pv2[2];
207 #endif
208 #ifdef  LOGIN_CAP
209         login_cap_t *lc;
210 #endif
211
212         signal(SIGINT, SIG_DFL);
213         signal(SIGQUIT, SIG_DFL);
214         signal(SIGTERM, SIG_DFL);
215         fromp->su_port = ntohs((u_short)fromp->su_port);
216         if (af != AF_INET
217 #ifdef INET6
218             && af != AF_INET6
219 #endif
220             ) {
221                 syslog(LOG_ERR, "malformed \"from\" address (af %d)\n", af);
222                 exit(1);
223         }
224         gaierror = getnameinfo((struct sockaddr *)fromp, fromp->su_len,
225             numericname, sizeof(numericname), NULL, 0,
226             NI_NUMERICHOST|NI_WITHSCOPEID);
227         if (gaierror != 0) {
228                 syslog(LOG_ERR, "malformed \"from\" address (af %d): %s", af,
229                     gai_strerror(gaierror));
230                 exit(1);
231         }
232 #ifdef IP_OPTIONS
233       if (af == AF_INET) {
234         u_char optbuf[BUFSIZ/3];
235         socklen_t optsize = sizeof(optbuf), i;
236         int ipproto;
237         struct protoent *ip;
238
239         if ((ip = getprotobyname("ip")) != NULL)
240                 ipproto = ip->p_proto;
241         else
242                 ipproto = IPPROTO_IP;
243         if (!getsockopt(0, ipproto, IP_OPTIONS, (char *)optbuf, &optsize) &&
244             optsize != 0) {
245                 for (i = 0; i < optsize; ) {
246                         u_char c = optbuf[i];
247                         if (c == IPOPT_LSRR || c == IPOPT_SSRR) {
248                                 syslog(LOG_NOTICE,
249                                         "connection refused from %s with IP option %s",
250                                         numericname,
251                                         c == IPOPT_LSRR ? "LSRR" : "SSRR");
252                                 exit(1);
253                         }
254                         if (c == IPOPT_EOL)
255                                 break;
256                         i += (c == IPOPT_NOP) ? 1 : optbuf[i+1];
257                 }
258         }
259       }
260 #endif
261
262         if (fromp->su_port >= IPPORT_RESERVED ||
263             fromp->su_port < IPPORT_RESERVED/2) {
264                 syslog(LOG_NOTICE|LOG_AUTH,
265                     "connection from %s on illegal port %u",
266                     numericname,
267                     fromp->su_port);
268                 exit(1);
269         }
270
271         alarm(60);
272         port = 0;
273         s = 0;          /* not set or used if port == 0 */
274         for (;;) {
275                 char c;
276                 if ((cc = read(STDIN_FILENO, &c, 1)) != 1) {
277                         if (cc < 0)
278                                 syslog(LOG_NOTICE, "read: %m");
279                         shutdown(0, SHUT_RDWR);
280                         exit(1);
281                 }
282                 if (c == 0)
283                         break;
284                 port = port * 10 + c - '0';
285         }
286
287         alarm(0);
288         if (port != 0) {
289                 int lport = IPPORT_RESERVED - 1;
290                 s = rresvport_af(&lport, af);
291                 if (s < 0) {
292                         syslog(LOG_ERR, "can't get stderr port: %m");
293                         exit(1);
294                 }
295                 if (port >= IPPORT_RESERVED ||
296                     port < IPPORT_RESERVED/2) {
297                         syslog(LOG_NOTICE|LOG_AUTH,
298                             "2nd socket from %s on unreserved port %u",
299                             numericname,
300                             port);
301                         exit(1);
302                 }
303                 fromp->su_port = htons(port);
304                 if (connect(s, (struct sockaddr *)fromp, fromp->su_len) < 0) {
305                         syslog(LOG_INFO, "connect second port %d: %m", port);
306                         exit(1);
307                 }
308         }
309
310         errorstr = NULL;
311         realhostname_sa(fromhost, sizeof(fromhost) - 1,
312                         (struct sockaddr *)fromp,
313                         fromp->su_len);
314         fromhost[sizeof(fromhost) - 1] = '\0';
315
316 #ifdef CRYPT
317         if (doencrypt && af == AF_INET) {
318                 struct sockaddr_in local_addr;
319                 rc = sizeof(local_addr);
320                 if (getsockname(0, (struct sockaddr *)&local_addr,
321                     &rc) < 0) {
322                         syslog(LOG_ERR, "getsockname: %m");
323                         error("rlogind: getsockname: %m");
324                         exit(1);
325                 }
326                 authopts = KOPT_DO_MUTUAL;
327                 rc = krb_recvauth(authopts, 0, ticket,
328                         "rcmd", instance, &fromaddr,
329                         &local_addr, kdata, "", schedule,
330                         version);
331                 des_set_key(&kdata->session, schedule);
332         }
333 #endif
334         alarm(60);
335         getstr(remuser, sizeof(remuser), "remuser");
336         getstr(locuser, sizeof(locuser), "locuser");
337         getstr(cmdbuf, sizeof(cmdbuf), "command");
338         alarm(0);
339         setpwent();
340         pwd = getpwnam(locuser);
341         if (pwd == NULL) {
342                 syslog(LOG_INFO|LOG_AUTH,
343                     "%s@%s as %s: unknown login. cmd='%.80s'",
344                     remuser, fromhost, locuser, cmdbuf);
345                 if (errorstr == NULL)
346                         errorstr = "Login incorrect.\n";
347                 goto fail;
348         }
349 #ifdef  LOGIN_CAP
350         lc = login_getpwclass(pwd);
351 #endif
352         if (chdir(pwd->pw_dir) < 0) {
353 #ifdef  LOGIN_CAP
354                 if (chdir("/") < 0 ||
355                     login_getcapbool(lc, "requirehome", !!pwd->pw_uid)) {
356                         syslog(LOG_INFO|LOG_AUTH,
357                         "%s@%s as %s: no home directory. cmd='%.80s'",
358                         remuser, fromhost, locuser, cmdbuf);
359                         error("No remote home directory.\n");
360                         exit(0);
361                 }
362 #else
363                 chdir("/");
364 #ifdef notdef
365                 syslog(LOG_INFO|LOG_AUTH,
366                     "%s@%s as %s: no home directory. cmd='%.80s'",
367                     remuser, fromhost, locuser, cmdbuf);
368                 error("No remote directory.\n");
369                 exit(1);
370 #endif
371 #endif
372                 pwd->pw_dir = slash;
373         }
374
375                 if (errorstr ||
376                     (pwd->pw_expire && time(NULL) >= pwd->pw_expire) ||
377                     iruserok_sa(fromp, fromp->su_len, pwd->pw_uid == 0,
378                                  remuser, locuser) < 0) {
379                         if (__rcmd_errstr) {
380                                 syslog(LOG_INFO|LOG_AUTH,
381                             "%s@%s as %s: permission denied (%s). cmd='%.80s'",
382                                     remuser, fromhost, locuser, __rcmd_errstr,
383                                     cmdbuf);
384                         } else {
385                                 syslog(LOG_INFO|LOG_AUTH,
386                             "%s@%s as %s: permission denied. cmd='%.80s'",
387                                     remuser, fromhost, locuser, cmdbuf);
388                         }
389 fail:
390                         if (errorstr == NULL)
391                                 errorstr = "Login incorrect.\n";
392                         error(errorstr, fromhost);
393                         exit(1);
394                 }
395
396         if (pwd->pw_uid && !access(_PATH_NOLOGIN, F_OK)) {
397                 error("Logins currently disabled.\n");
398                 exit(1);
399         }
400 #ifdef  LOGIN_CAP
401         if (lc != NULL && fromp->su_family == AF_INET) {        /*XXX*/
402                 char    remote_ip[MAXHOSTNAMELEN];
403
404                 strncpy(remote_ip, numericname,
405                         sizeof(remote_ip) - 1);
406                 remote_ip[sizeof(remote_ip) - 1] = 0;
407                 if (!auth_hostok(lc, fromhost, remote_ip)) {
408                         syslog(LOG_INFO|LOG_AUTH,
409                             "%s@%s as %s: permission denied (%s). cmd='%.80s'",
410                             remuser, fromhost, locuser, __rcmd_errstr,
411                             cmdbuf);
412                         error("Login incorrect.\n");
413                         exit(1);
414                 }
415                 if (!auth_timeok(lc, time(NULL))) {
416                         error("Logins not available right now\n");
417                         exit(1);
418                 }
419         }
420 #endif  /* !LOGIN_CAP */
421 #if     BSD > 43
422         /* before fork, while we're session leader */
423         if (setlogin(pwd->pw_name) < 0)
424                 syslog(LOG_ERR, "setlogin() failed: %m");
425 #endif
426
427         write(STDERR_FILENO, "\0", 1);
428         sent_null = 1;
429
430         if (port) {
431                 if (pipe(pv) < 0) {
432                         error("Can't make pipe.\n");
433                         exit(1);
434                 }
435 #ifdef CRYPT
436                 if (doencrypt) {
437                         if (pipe(pv1) < 0) {
438                                 error("Can't make 2nd pipe.\n");
439                                 exit(1);
440                         }
441                         if (pipe(pv2) < 0) {
442                                 error("Can't make 3rd pipe.\n");
443                                 exit(1);
444                         }
445                 }
446 #endif
447                 pid = fork();
448                 if (pid == -1)  {
449                         error("Can't fork; try again.\n");
450                         exit(1);
451                 }
452                 if (pid) {
453 #ifdef CRYPT
454                         if (doencrypt) {
455                                 static char msg[] = SECURE_MESSAGE;
456                                 close(pv1[1]);
457                                 close(pv2[1]);
458                                 des_enc_write(s, msg, sizeof(msg) - 1, 
459                                         schedule, &kdata->session);
460
461                         } else
462 #endif
463                         {
464                                 close(0);
465                                 close(1);
466                         }
467                         close(2);
468                         close(pv[1]);
469
470                         FD_ZERO(&readfrom);
471                         FD_SET(s, &readfrom);
472                         FD_SET(pv[0], &readfrom);
473                         if (pv[0] > s)
474                                 nfd = pv[0];
475                         else
476                                 nfd = s;
477 #ifdef CRYPT
478                         if (doencrypt) {
479                                 FD_ZERO(&writeto);
480                                 FD_SET(pv2[0], &writeto);
481                                 FD_SET(pv1[0], &readfrom);
482
483                                 nfd = MAX(nfd, pv2[0]);
484                                 nfd = MAX(nfd, pv1[0]);
485                         } else
486 #endif
487                                 ioctl(pv[0], FIONBIO, (char *)&one);
488
489                         /* should set s nbio! */
490                         nfd++;
491                         do {
492                                 ready = readfrom;
493 #ifdef CRYPT
494                                 if (doencrypt) {
495                                         wready = writeto;
496                                         if (select(nfd, &ready,
497                                             &wready, NULL,
498                                             NULL) < 0)
499                                                 break;
500                                 } else
501 #endif
502                                         if (select(nfd, &ready, NULL,
503                                           NULL, NULL) < 0)
504                                                 break;
505                                 if (FD_ISSET(s, &ready)) {
506                                         int     ret;
507 #ifdef CRYPT
508                                         if (doencrypt)
509                                                 ret = des_enc_read(s, &sig, 1,
510                                                 schedule, &kdata->session);
511                                         else
512 #endif
513                                                 ret = read(s, &sig, 1);
514                                         if (ret <= 0)
515                                                 FD_CLR(s, &readfrom);
516                                         else
517                                                 killpg(pid, sig);
518                                 }
519                                 if (FD_ISSET(pv[0], &ready)) {
520                                         errno = 0;
521                                         cc = read(pv[0], buf, sizeof(buf));
522                                         if (cc <= 0) {
523                                                 shutdown(s, SHUT_RDWR);
524                                                 FD_CLR(pv[0], &readfrom);
525                                         } else {
526 #ifdef CRYPT
527                                                 if (doencrypt) {
528                                                         des_enc_write(s, buf,
529                                                             cc, schedule,
530                                                             &kdata->session);
531                                                 } else
532 #endif
533                                                         write(s, buf, cc);
534                                         }
535                                 }
536 #ifdef CRYPT
537                                 if (doencrypt && FD_ISSET(pv1[0], &ready)) {
538                                         errno = 0;
539                                         cc = read(pv1[0], buf, sizeof(buf));
540                                         if (cc <= 0) {
541                                                 shutdown(pv1[0], SHUT_RDWR);
542                                                 FD_CLR(pv1[0], &readfrom);
543                                         } else {
544                                                 des_enc_write(STDOUT_FILENO,
545                                                     buf, cc, schedule,
546                                                     &kdata->session);
547                                         }
548                                 }
549
550                                 if (doencrypt && FD_ISSET(pv2[0], &wready)) {
551                                         errno = 0;
552                                         cc = des_enc_read(STDIN_FILENO,
553                                             buf, sizeof(buf),
554                                                 schedule, &kdata->session);
555                                         if (cc <= 0) {
556                                                 shutdown(pv2[0], SHUT_RDWR);
557                                                 FD_CLR(pv2[0], &writeto);
558                                         } else {
559                                                 write(pv2[0], buf, cc);
560                                         }
561                                 }
562 #endif
563
564                         } while (FD_ISSET(s, &readfrom) ||
565 #ifdef CRYPT
566                             (doencrypt && FD_ISSET(pv1[0], &readfrom)) ||
567 #endif
568                             FD_ISSET(pv[0], &readfrom));
569                         exit(0);
570                 }
571                 setpgrp(0, getpid());
572                 close(s);
573                 close(pv[0]);
574 #ifdef CRYPT
575                 if (doencrypt) {
576                         close(pv1[0]); close(pv2[0]);
577                         dup2(pv1[1], 1);
578                         dup2(pv2[1], 0);
579                         close(pv1[1]);
580                         close(pv2[1]);
581                 }
582 #endif
583                 dup2(pv[1], 2);
584                 close(pv[1]);
585         }
586         if (*pwd->pw_shell == '\0')
587                 pwd->pw_shell = bshell;
588         environ = envinit;
589         strncat(homedir, pwd->pw_dir, sizeof(homedir)-6);
590         strcat(path, _PATH_DEFPATH);
591         strncat(shell, pwd->pw_shell, sizeof(shell)-7);
592         strncat(username, pwd->pw_name, sizeof(username)-6);
593         cp = strrchr(pwd->pw_shell, '/');
594         if (cp)
595                 cp++;
596         else
597                 cp = pwd->pw_shell;
598 #ifdef  LOGIN_CAP
599         if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETALL) != 0) {
600                 syslog(LOG_ERR, "setusercontext: %m");
601                 exit(1);
602         }
603         login_close(lc);
604 #else
605         setgid((gid_t)pwd->pw_gid);
606         initgroups(pwd->pw_name, pwd->pw_gid);
607         setuid((uid_t)pwd->pw_uid);
608 #endif
609         endpwent();
610         if (log_success || pwd->pw_uid == 0) {
611                     syslog(LOG_INFO|LOG_AUTH, "%s@%s as %s: cmd='%.80s'",
612                         remuser, fromhost, locuser, cmdbuf);
613         }
614         execl(pwd->pw_shell, cp, "-c", cmdbuf, NULL);
615         perror(pwd->pw_shell);
616         exit(1);
617 }
618
619 /*
620  * Report error to client.  Note: can't be used until second socket has
621  * connected to client, or older clients will hang waiting for that
622  * connection first.
623  */
624
625 void
626 error(const char *fmt, ...)
627 {
628         va_list ap;
629         int len;
630         char *bp, buf[BUFSIZ];
631         va_start(ap, fmt);
632         bp = buf;
633         if (sent_null == 0) {
634                 *bp++ = 1;
635                 len = 1;
636         } else
637                 len = 0;
638         vsnprintf(bp, sizeof(buf) - 1, fmt, ap);
639         write(STDERR_FILENO, buf, len + strlen(bp));
640 }
641
642 void
643 getstr(char *buf, int cnt, const char *err)
644 {
645         char c;
646
647         do {
648                 if (read(STDIN_FILENO, &c, 1) != 1)
649                         exit(1);
650                 *buf++ = c;
651                 if (--cnt == 0) {
652                         error("%s too long\n", err);
653                         exit(1);
654                 }
655         } while (c != 0);
656 }
657
658 /*
659  * Check whether host h is in our local domain,
660  * defined as sharing the last two components of the domain part,
661  * or the entire domain part if the local domain has only one component.
662  * If either name is unqualified (contains no '.'),
663  * assume that the host is local, as it will be
664  * interpreted as such.
665  */
666 int
667 local_domain(char *h)
668 {
669         char localhost[MAXHOSTNAMELEN];
670         char *p1, *p2;
671
672         localhost[0] = 0;
673         gethostname(localhost, sizeof(localhost) - 1);
674         localhost[sizeof(localhost) - 1] = '\0';
675         p1 = topdomain(localhost);
676         p2 = topdomain(h);
677         if (p1 == NULL || p2 == NULL || !strcasecmp(p1, p2))
678                 return (1);
679         return (0);
680 }
681
682 char *
683 topdomain(char *h)
684 {
685         char *p, *maybe = NULL;
686         int dots = 0;
687
688         for (p = h + strlen(h); p >= h; p--) {
689                 if (*p == '.') {
690                         if (++dots == 2)
691                                 return (p);
692                         maybe = p;
693                 }
694         }
695         return (maybe);
696 }
697
698 void
699 usage(void)
700 {
701
702         syslog(LOG_ERR, "usage: rshd [-%s]", OPTIONS);
703         exit(2);
704 }