1 /* $NetBSD: rshd.c,v 1.46 2008/07/20 01:09:07 lukem Exp $ */
4 * Copyright (C) 1998 WIDE Project.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 * must display the following acknowledgement:
17 * This product includes software developed by WIDE Project and
19 * 4. Neither the name of the project nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * Copyright (c) 1988, 1989, 1992, 1993, 1994
38 * The Regents of the University of California. All rights reserved.
40 * Redistribution and use in source and binary forms, with or without
41 * modification, are permitted provided that the following conditions
43 * 1. Redistributions of source code must retain the above copyright
44 * notice, this list of conditions and the following disclaimer.
45 * 2. Redistributions in binary form must reproduce the above copyright
46 * notice, this list of conditions and the following disclaimer in the
47 * documentation and/or other materials provided with the distribution.
48 * 3. Neither the name of the University nor the names of its contributors
49 * may be used to endorse or promote products derived from this software
50 * without specific prior written permission.
52 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
53 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
54 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
55 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
56 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
57 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
58 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
59 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
60 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
61 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
65 #include <sys/cdefs.h>
67 __COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1992, 1993, 1994\
68 The Regents of the University of California. All rights reserved.");
70 static char sccsid
[] = "@(#)rshd.c 8.2 (Berkeley) 4/6/94";
72 __RCSID("$NetBSD: rshd.c,v 1.46 2008/07/20 01:09:07 lukem Exp $");
77 * remote shell server:
84 #include <sys/param.h>
85 #include <sys/ioctl.h>
87 #include <sys/socket.h>
89 #include <netinet/in_systm.h>
90 #include <netinet/in.h>
91 #include <netinet/ip.h>
92 #include <netinet/tcp.h>
93 #include <arpa/inet.h>
108 #include <login_cap.h>
112 #include <security/pam_appl.h>
113 #include <security/openpam.h>
114 #include <sys/wait.h>
116 static struct pam_conv pamc
= { openpam_nullconv
, NULL
};
117 static pam_handle_t
*pamh
;
120 #define PAM_END do { \
121 if ((pam_err = pam_setcred(pamh, PAM_DELETE_CRED)) != PAM_SUCCESS) \
122 syslog(LOG_ERR|LOG_AUTH, "pam_setcred(): %s", \
123 pam_strerror(pamh, pam_err)); \
124 if ((pam_err = pam_close_session(pamh,0)) != PAM_SUCCESS) \
125 syslog(LOG_ERR|LOG_AUTH, "pam_close_session(): %s", \
126 pam_strerror(pamh, pam_err)); \
127 if ((pam_err = pam_end(pamh, pam_err)) != PAM_SUCCESS) \
128 syslog(LOG_ERR|LOG_AUTH, "pam_end(): %s", \
129 pam_strerror(pamh, pam_err)); \
130 } while (/*CONSTCOND*/0)
137 int log_success
; /* If TRUE, log all successful accesses */
140 void doit(struct sockaddr
*) __dead
;
141 void rshd_errx(int, const char *, ...)
142 __attribute__((__noreturn__
, __format__(__printf__
, 2, 3)));
143 void getstr(char *, int, const char *);
144 int local_domain(char *);
145 char *topdomain(char *);
147 int main(int, char *[]);
149 #define OPTIONS "aLln"
150 extern int __check_rhosts_file
;
151 extern char *__rcmd_errstr
; /* syslog hook from libc/net/rcmd.c. */
152 static const char incorrect
[] = "Login incorrect.";
155 main(int argc
, char *argv
[])
157 struct linger linger
;
160 struct sockaddr_storage from
;
161 struct protoent
*proto
;
163 openlog("rshd", LOG_PID
, LOG_DAEMON
);
166 while ((ch
= getopt(argc
, argv
, OPTIONS
)) != -1)
172 __check_rhosts_file
= 0;
189 fromlen
= sizeof(from
); /* xxx */
190 if (getpeername(STDIN_FILENO
, (struct sockaddr
*)&from
, &fromlen
) < 0) {
191 syslog(LOG_ERR
, "getpeername: %m");
195 if (((struct sockaddr
*)&from
)->sa_family
== AF_INET6
&&
196 IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6
*)&from
)->sin6_addr
) &&
197 sizeof(struct sockaddr_in
) <= sizeof(from
)) {
198 struct sockaddr_in sin
;
199 struct sockaddr_in6
*sin6
;
200 const int off
= sizeof(struct sockaddr_in6
) -
201 sizeof(struct sockaddr_in
);
203 sin6
= (struct sockaddr_in6
*)&from
;
204 (void)memset(&sin
, 0, sizeof(sin
));
205 sin
.sin_family
= AF_INET
;
206 sin
.sin_len
= sizeof(struct sockaddr_in
);
207 (void)memcpy(&sin
.sin_addr
, &sin6
->sin6_addr
.s6_addr
[off
],
208 sizeof(sin
.sin_addr
));
209 (void)memcpy(&from
, &sin
, sizeof(sin
));
210 fromlen
= sin
.sin_len
;
213 if (((struct sockaddr
*)&from
)->sa_family
== AF_INET6
&&
214 IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6
*)&from
)->sin6_addr
)) {
215 char hbuf
[NI_MAXHOST
];
216 if (getnameinfo((struct sockaddr
*)&from
, fromlen
, hbuf
,
217 sizeof(hbuf
), NULL
, 0, NI_NUMERICHOST
) != 0) {
218 strlcpy(hbuf
, "invalid", sizeof(hbuf
));
220 syslog(LOG_ERR
, "malformed \"from\" address (v4 mapped, %s)",
226 setsockopt(STDIN_FILENO
, SOL_SOCKET
, SO_KEEPALIVE
, (char *)&on
,
228 syslog(LOG_WARNING
, "setsockopt (SO_KEEPALIVE): %m");
230 linger
.l_linger
= 60; /* XXX */
231 if (setsockopt(STDIN_FILENO
, SOL_SOCKET
, SO_LINGER
, (char *)&linger
,
232 sizeof (linger
)) < 0)
233 syslog(LOG_WARNING
, "setsockopt (SO_LINGER): %m");
234 proto
= getprotobyname("tcp");
235 (void)setsockopt(STDIN_FILENO
, proto
->p_proto
, TCP_NODELAY
, &on
,
237 doit((struct sockaddr
*)&from
);
240 char username
[20] = "USER=";
241 char homedir
[64] = "HOME=";
242 char shell
[64] = "SHELL=";
243 char path
[100] = "PATH=";
245 {homedir
, shell
, path
, username
, 0};
249 doit(struct sockaddr
*fromp
)
251 struct passwd
*pwd
, pwres
;
253 struct pollfd set
[2];
254 int cc
, pv
[2], pid
, s
= -1; /* XXX gcc */
256 char *hostname
, *errorhost
= NULL
; /* XXX gcc */
258 char sig
, buf
[BUFSIZ
];
259 char cmdbuf
[NCARGS
+1], locuser
[16], remuser
[16];
260 char remotehost
[2 * MAXHOSTNAMELEN
+ 1];
261 char hostnamebuf
[2 * MAXHOSTNAMELEN
+ 1];
265 char naddr
[NI_MAXHOST
];
266 char saddr
[NI_MAXHOST
];
267 char raddr
[NI_MAXHOST
];
268 char pbuf
[NI_MAXSERV
];
269 int af
= fromp
->sa_family
;
271 struct addrinfo hints
, *res
, *res0
;
273 const int niflags
= NI_NUMERICHOST
| NI_NUMERICSERV
;
274 const char *errormsg
= NULL
, *errorstr
= NULL
;
277 (void)signal(SIGINT
, SIG_DFL
);
278 (void)signal(SIGQUIT
, SIG_DFL
);
279 (void)signal(SIGTERM
, SIG_DFL
);
282 int t
= open(_PATH_TTY
, O_RDWR
);
284 ioctl(t
, TIOCNOTTY
, NULL
);
291 portp
= &((struct sockaddr_in
*)fromp
)->sin_port
;
295 portp
= &((struct sockaddr_in6
*)fromp
)->sin6_port
;
299 syslog(LOG_ERR
, "malformed \"from\" address (af %d)", af
);
302 if (getnameinfo(fromp
, fromp
->sa_len
, naddr
, sizeof(naddr
),
303 pbuf
, sizeof(pbuf
), niflags
) != 0) {
304 syslog(LOG_ERR
, "malformed \"from\" address (af %d)", af
);
310 u_char optbuf
[BUFSIZ
/3];
311 socklen_t optsize
= sizeof(optbuf
);
316 if ((ip
= getprotobyname("ip")) != NULL
)
317 ipproto
= ip
->p_proto
;
319 ipproto
= IPPROTO_IP
;
320 if (!getsockopt(0, ipproto
, IP_OPTIONS
, (char *)optbuf
, &optsize
) &&
322 for (i
= 0; i
< optsize
;) {
323 u_char c
= optbuf
[i
];
324 if (c
== IPOPT_LSRR
|| c
== IPOPT_SSRR
) {
326 "Connection refused from %s "
329 (struct sockaddr_in
*)fromp
)->sin_addr
),
330 c
== IPOPT_LSRR
? "LSRR" : "SSRR");
335 i
+= (c
== IPOPT_NOP
) ? 1 : optbuf
[i
+ 1];
340 if (ntohs(*portp
) >= IPPORT_RESERVED
341 || ntohs(*portp
) < IPPORT_RESERVED
/ 2) {
342 syslog(LOG_NOTICE
|LOG_AUTH
,
343 "Connection from %s on illegal port %u",
344 naddr
, ntohs(*portp
));
353 if ((cc
= read(STDIN_FILENO
, &c
, 1)) != 1) {
355 syslog(LOG_ERR
, "read: %m");
356 (void)shutdown(0, SHUT_RDWR
);
361 port
= port
* 10 + c
- '0';
366 int lport
= IPPORT_RESERVED
- 1;
367 s
= rresvport_af(&lport
, af
);
369 syslog(LOG_ERR
, "can't get stderr port: %m");
372 if (port
>= IPPORT_RESERVED
) {
373 syslog(LOG_ERR
, "2nd port not reserved");
376 *portp
= htons(port
);
377 if (connect(s
, fromp
, fromp
->sa_len
) < 0) {
378 syslog(LOG_ERR
, "connect second port %d: %m", port
);
385 /* from inetd, socket is already on 0, 1, 2 */
386 (void)dup2(f
, STDIN_FILENO
);
387 (void)dup2(f
, STDOUT_FILENO
);
388 (void)dup2(f
, STDERR_FILENO
);
390 if (getnameinfo(fromp
, fromp
->sa_len
, saddr
, sizeof(saddr
),
391 NULL
, 0, NI_NAMEREQD
) == 0) {
393 * If name returned by getnameinfo is in our domain,
394 * attempt to verify that we haven't been fooled by someone
395 * in a remote net; look up the name and check that this
396 * address corresponds to the name.
400 if (check_all
|| local_domain(saddr
)) {
401 (void)strlcpy(remotehost
, saddr
, sizeof(remotehost
));
402 errorhost
= remotehost
;
403 (void)memset(&hints
, 0, sizeof(hints
));
404 hints
.ai_family
= fromp
->sa_family
;
405 hints
.ai_socktype
= SOCK_STREAM
;
406 hints
.ai_flags
= AI_CANONNAME
;
407 gaierror
= getaddrinfo(remotehost
, pbuf
, &hints
, &res0
);
410 "Couldn't look up address for %s: %s",
411 remotehost
, gai_strerror(gaierror
));
413 "Couldn't look up address for your host (%s)\n";
416 for (res
= res0
; res
; res
= res
->ai_next
) {
417 if (res
->ai_family
!= fromp
->sa_family
)
419 if (res
->ai_addrlen
!= fromp
->sa_len
)
421 if (getnameinfo(res
->ai_addr
,
423 raddr
, sizeof(raddr
), NULL
, 0,
425 && strcmp(naddr
, raddr
) == 0) {
426 hostname
= res
->ai_canonname
434 "Host addr %s not listed for host %s",
435 naddr
, res0
->ai_canonname
439 "Host address mismatch for %s\n";
444 (void)strlcpy(hostnamebuf
, hostname
, sizeof(hostnamebuf
));
445 hostname
= hostnamebuf
;
449 (void)strlcpy(hostnamebuf
, naddr
, sizeof(hostnamebuf
));
450 errorhost
= hostname
= hostnamebuf
;
454 getstr(remuser
, sizeof(remuser
), "remuser");
455 getstr(locuser
, sizeof(locuser
), "locuser");
456 getstr(cmdbuf
, sizeof(cmdbuf
), "command");
460 pam_err
= pam_start("rsh", locuser
, &pamc
, &pamh
);
461 if (pam_err
!= PAM_SUCCESS
) {
462 syslog(LOG_ERR
|LOG_AUTH
, "pam_start(): %s",
463 pam_strerror(pamh
, pam_err
));
464 rshd_errx(EXIT_FAILURE
, incorrect
);
467 if ((pam_err
= pam_set_item(pamh
, PAM_RUSER
, remuser
)) != PAM_SUCCESS
||
468 (pam_err
= pam_set_item(pamh
, PAM_RHOST
, hostname
) != PAM_SUCCESS
)){
469 syslog(LOG_ERR
|LOG_AUTH
, "pam_set_item(): %s",
470 pam_strerror(pamh
, pam_err
));
471 rshd_errx(EXIT_FAILURE
, incorrect
);
474 pam_err
= pam_authenticate(pamh
, 0);
475 if (pam_err
== PAM_SUCCESS
) {
476 if ((pam_err
= pam_get_user(pamh
, &cp
, NULL
)) == PAM_SUCCESS
) {
477 (void)strlcpy(locuser
, cp
, sizeof(locuser
));
478 /* XXX truncation! */
480 pam_err
= pam_acct_mgmt(pamh
, 0);
482 if (pam_err
!= PAM_SUCCESS
) {
483 errorstr
= incorrect
;
484 errormsg
= pam_strerror(pamh
, pam_err
);
489 if (getpwnam_r(locuser
, &pwres
, pwbuf
, sizeof(pwbuf
), &pwd
) != 0 ||
491 syslog(LOG_INFO
|LOG_AUTH
,
492 "%s@%s as %s: unknown login. cmd='%.80s'",
493 remuser
, hostname
, locuser
, cmdbuf
);
494 if (errorstr
== NULL
)
495 errorstr
= "Permission denied.";
496 rshd_errx(EXIT_FAILURE
, errorstr
, errorhost
);
499 lc
= login_getclass(pwd
? pwd
->pw_class
: NULL
);
502 if (chdir(pwd
->pw_dir
) < 0) {
505 || login_getcapbool(lc
, "requirehome", pwd
->pw_uid
? 1 : 0)
508 syslog(LOG_INFO
|LOG_AUTH
,
509 "%s@%s as %s: no home directory. cmd='%.80s'",
510 remuser
, hostname
, locuser
, cmdbuf
);
511 rshd_errx(EXIT_SUCCESS
, "No remote home directory.");
517 (pwd
->pw_passwd
!= 0 && *pwd
->pw_passwd
!= '\0' &&
518 iruserok_sa(fromp
, fromp
->sa_len
, pwd
->pw_uid
== 0, remuser
,
520 errormsg
= __rcmd_errstr
? __rcmd_errstr
: "unknown error";
521 if (errorstr
== NULL
)
522 errorstr
= "Permission denied.";
526 if (pwd
->pw_uid
&& !access(_PATH_NOLOGIN
, F_OK
))
527 rshd_errx(EXIT_FAILURE
, "Logins currently disabled.");
532 * PAM modules might add supplementary groups in
533 * pam_setcred(), so initialize them first.
534 * But we need to open the session as root.
536 if (setusercontext(lc
, pwd
, pwd
->pw_uid
, LOGIN_SETGROUP
) != 0) {
537 syslog(LOG_ERR
, "setusercontext: %m");
541 initgroups(pwd
->pw_name
, pwd
->pw_gid
);
545 if ((pam_err
= pam_open_session(pamh
, 0)) != PAM_SUCCESS
) {
546 syslog(LOG_ERR
, "pam_open_session: %s",
547 pam_strerror(pamh
, pam_err
));
548 } else if ((pam_err
= pam_setcred(pamh
, PAM_ESTABLISH_CRED
))
550 syslog(LOG_ERR
, "pam_setcred: %s", pam_strerror(pamh
, pam_err
));
554 (void)write(STDERR_FILENO
, "\0", 1);
559 rshd_errx(EXIT_FAILURE
, "Can't make pipe. (%s)",
563 rshd_errx(EXIT_FAILURE
, "Can't fork. (%s)",
566 (void)close(STDIN_FILENO
);
567 (void)close(STDOUT_FILENO
);
568 (void)close(STDERR_FILENO
);
572 set
[0].events
= POLLIN
;
574 set
[1].events
= POLLIN
;
575 ioctl(pv
[0], FIONBIO
, (char *)&one
);
577 /* should set s nbio! */
579 if (poll(set
, 2, INFTIM
) < 0)
581 if (set
[0].revents
& POLLIN
) {
584 ret
= read(s
, &sig
, 1);
590 if (set
[1].revents
& POLLIN
) {
592 cc
= read(pv
[0], buf
, sizeof(buf
));
594 shutdown(s
, SHUT_RDWR
);
597 (void)write(s
, buf
, cc
);
601 } while ((set
[0].revents
| set
[1].revents
) & POLLIN
);
607 (void)dup2(pv
[1], STDERR_FILENO
);
614 rshd_errx(EXIT_FAILURE
, "Can't fork. (%s)",
619 if ((xpid
= waitpid(pid
, &status
, 0)) != pid
) {
620 pam_err
= pam_close_session(pamh
, 0);
621 if (pam_err
!= PAM_SUCCESS
) {
623 "pam_close_session: %s",
624 pam_strerror(pamh
, pam_err
));
629 "wrong PID: %d != %d", pid
, xpid
);
632 "wait pid=%d failed %m", pid
);
641 (void)fcntl(STDERR_FILENO
+ 1, F_CLOSEM
, 0);
643 for (fd
= getdtablesize(); fd
> STDERR_FILENO
; fd
--)
647 syslog(LOG_ERR
, "setsid() failed: %m");
649 if (setlogin(pwd
->pw_name
) < 0)
650 syslog(LOG_ERR
, "setlogin() failed: %m");
652 if (*pwd
->pw_shell
== '\0')
653 pwd
->pw_shell
= __UNCONST(_PATH_BSHELL
);
655 (void)pam_setenv(pamh
, "HOME", pwd
->pw_dir
, 1);
656 (void)pam_setenv(pamh
, "SHELL", pwd
->pw_shell
, 1);
657 (void)pam_setenv(pamh
, "USER", pwd
->pw_name
, 1);
658 (void)pam_setenv(pamh
, "PATH", _PATH_DEFPATH
, 1);
659 environ
= pam_getenvlist(pamh
);
660 (void)pam_end(pamh
, pam_err
);
665 if ((sh
= login_getcapstr(lc
, "shell", NULL
, NULL
))) {
666 if(!(sh
= strdup(sh
))) {
667 syslog(LOG_ERR
, "Cannot alloc mem");
675 (void)strlcat(homedir
, pwd
->pw_dir
, sizeof(homedir
));
676 (void)strlcat(path
, _PATH_DEFPATH
, sizeof(path
));
677 (void)strlcat(shell
, pwd
->pw_shell
, sizeof(shell
));
678 (void)strlcat(username
, pwd
->pw_name
, sizeof(username
));
681 cp
= strrchr(pwd
->pw_shell
, '/');
688 if (setusercontext(lc
, pwd
, pwd
->pw_uid
,
689 LOGIN_SETALL
& ~LOGIN_SETGROUP
) < 0) {
690 syslog(LOG_ERR
, "setusercontext(): %m");
695 (void)setgid((gid_t
)pwd
->pw_gid
);
696 (void)setuid((uid_t
)pwd
->pw_uid
);
699 if (log_success
|| pwd
->pw_uid
== 0) {
700 syslog(LOG_INFO
|LOG_AUTH
, "%s@%s as %s: cmd='%.80s'",
701 remuser
, hostname
, locuser
, cmdbuf
);
703 (void)execl(pwd
->pw_shell
, cp
, "-c", cmdbuf
, NULL
);
704 rshd_errx(EXIT_FAILURE
, "%s: %s", pwd
->pw_shell
, strerror(errno
));
706 syslog(LOG_INFO
|LOG_AUTH
,
707 "%s@%s as %s: permission denied (%s). cmd='%.80s'",
708 remuser
, hostname
, locuser
, errormsg
, cmdbuf
);
709 rshd_errx(EXIT_FAILURE
, errorstr
, errorhost
);
713 * Report error to client. Note: can't be used until second socket has
714 * connected to client, or older clients will hang waiting for that
721 rshd_errx(int error
, const char *fmt
, ...)
725 char *bp
, buf
[BUFSIZ
];
728 if (sent_null
== 0) {
733 rv
= vsnprintf(bp
, sizeof(buf
) - 2, fmt
, ap
);
735 (void)write(STDERR_FILENO
, buf
, len
+ rv
);
741 getstr(char *buf
, int cnt
, const char *err
)
746 if (read(STDIN_FILENO
, &c
, 1) != 1)
750 rshd_errx(EXIT_FAILURE
, "%s too long", err
);
755 * Check whether host h is in our local domain,
756 * defined as sharing the last two components of the domain part,
757 * or the entire domain part if the local domain has only one component.
758 * If either name is unqualified (contains no '.'),
759 * assume that the host is local, as it will be
760 * interpreted as such.
763 local_domain(char *h
)
765 char localhost
[MAXHOSTNAMELEN
+ 1];
769 (void)gethostname(localhost
, sizeof(localhost
));
770 localhost
[sizeof(localhost
) - 1] = '\0';
771 p1
= topdomain(localhost
);
773 if (p1
== NULL
|| p2
== NULL
|| !strcasecmp(p1
, p2
))
781 char *p
, *maybe
= NULL
;
784 for (p
= h
+ strlen(h
); p
>= h
; p
--) {
798 syslog(LOG_ERR
, "Usage: %s [-%s]", getprogname(), OPTIONS
);