Libinfo-78.tar.gz
[apple/libinfo.git] / util.subproj / rcmd.c
1 /*
2 * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * Portions Copyright (c) 1999 Apple Computer, Inc. All Rights
7 * Reserved. This file contains Original Code and/or Modifications of
8 * Original Code as defined in and that are subject to the Apple Public
9 * Source License Version 1.1 (the "License"). You may not use this file
10 * except in compliance with the License. Please obtain a copy of the
11 * License at http://www.apple.com/publicsource and read it before using
12 * this file.
13 *
14 * The Original Code and all software distributed under the License are
15 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
16 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
17 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE OR NON- INFRINGEMENT. Please see the
19 * License for the specific language governing rights and limitations
20 * under the License.
21 *
22 * @APPLE_LICENSE_HEADER_END@
23 */
24 /*
25 * Copyright (c) 1995, 1996, 1998 Theo de Raadt. All rights reserved.
26 * Copyright (c) 1983, 1993, 1994
27 * The Regents of the University of California. All rights reserved.
28 *
29 * Redistribution and use in source and binary forms, with or without
30 * modification, are permitted provided that the following conditions
31 * are met:
32 * 1. Redistributions of source code must retain the above copyright
33 * notice, this list of conditions and the following disclaimer.
34 * 2. Redistributions in binary form must reproduce the above copyright
35 * notice, this list of conditions and the following disclaimer in the
36 * documentation and/or other materials provided with the distribution.
37 * 3. All advertising materials mentioning features or use of this software
38 * must display the following acknowledgement:
39 * This product includes software developed by the University of
40 * California, Berkeley and its contributors.
41 * This product includes software developed by Theo de Raadt.
42 * 4. Neither the name of the University nor the names of its contributors
43 * may be used to endorse or promote products derived from this software
44 * without specific prior written permission.
45 *
46 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
47 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
48 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
49 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
50 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
51 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
52 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
53 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
54 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
55 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
56 * SUCH DAMAGE.
57 */
58
59 #if defined(LIBC_SCCS) && !defined(lint)
60 static char *rcsid = "$OpenBSD: rcmd.c,v 1.30 1998/02/12 02:21:19 deraadt Exp $";
61 #endif /* LIBC_SCCS and not lint */
62
63 #include <sys/param.h>
64 #include <sys/socket.h>
65 #include <sys/stat.h>
66
67 #include <netinet/in.h>
68 #include <arpa/inet.h>
69 #include <netinfo/ni_util.h>
70
71 #include <signal.h>
72 #include <fcntl.h>
73 #include <netdb.h>
74 #include <unistd.h>
75 #include <pwd.h>
76 #include <errno.h>
77 #include <stdio.h>
78 #include <ctype.h>
79 #include <string.h>
80 #include <syslog.h>
81 #include <stdlib.h>
82
83 typedef u_int32_t in_addr_t; /* base type for internet address */
84 typedef u_int16_t in_port_t; /* IP port type */
85
86 extern int bindresvport(int sd, struct sockaddr_in *sin);
87 extern int getdomainname(char *val, size_t len);
88 extern int rcmdsh(char **ahost, int rport, const char *locuser, const char *remuser, const char *cmd, char *rshprog);
89
90 int __ivaliduser __P((FILE *, in_addr_t, const char *, const char *));
91 static int __icheckhost __P((u_int32_t, const char *));
92 static char *__gethostloop __P((u_int32_t));
93
94 int
95 rcmd(ahost, rport, locuser, remuser, cmd, fd2p)
96 char **ahost;
97 in_port_t rport;
98 const char *locuser, *remuser, *cmd;
99 int *fd2p;
100 {
101 struct hostent *hp;
102 struct sockaddr_in sin, from;
103 fd_set *readsp = NULL;
104 int oldmask;
105 pid_t pid;
106 int s, lport, timo;
107 char c, *p;
108
109 /* call rcmdsh() with specified remote shell if appropriate. */
110 if ((getuid() == geteuid()) && (p = getenv("RSH"))) {
111 struct servent *sp = getservbyname("shell", "tcp");
112
113 if (sp && sp->s_port == rport)
114 return (rcmdsh(ahost, rport, locuser, remuser,
115 cmd, p));
116 }
117
118 /* use rsh(1) if non-root and remote port is shell. */
119 if (geteuid()) {
120 struct servent *sp = getservbyname("shell", "tcp");
121
122 if (sp && sp->s_port == rport)
123 return (rcmdsh(ahost, rport, locuser, remuser,
124 cmd, NULL));
125 }
126
127 pid = getpid();
128 hp = gethostbyname(*ahost);
129 if (hp == NULL) {
130 herror(*ahost);
131 return (-1);
132 }
133 *ahost = hp->h_name;
134
135 oldmask = sigblock(sigmask(SIGURG));
136 for (timo = 1, lport = IPPORT_RESERVED - 1;;) {
137 s = rresvport(&lport);
138 if (s < 0) {
139 if (errno == EAGAIN)
140 (void)fprintf(stderr,
141 "rcmd: socket: All ports in use\n");
142 else
143 (void)fprintf(stderr, "rcmd: socket: %s\n",
144 strerror(errno));
145 sigsetmask(oldmask);
146 return (-1);
147 }
148 fcntl(s, F_SETOWN, pid);
149 bzero(&sin, sizeof sin);
150 sin.sin_len = sizeof(struct sockaddr_in);
151 sin.sin_family = hp->h_addrtype;
152 sin.sin_port = rport;
153 bcopy(hp->h_addr_list[0], &sin.sin_addr, hp->h_length);
154 if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) >= 0)
155 break;
156 (void)close(s);
157 if (errno == EADDRINUSE) {
158 lport--;
159 continue;
160 }
161 if (errno == ECONNREFUSED && timo <= 16) {
162 (void)sleep(timo);
163 timo *= 2;
164 continue;
165 }
166 if (hp->h_addr_list[1] != NULL) {
167 int oerrno = errno;
168
169 (void)fprintf(stderr, "connect to address %s: ",
170 inet_ntoa(sin.sin_addr));
171 errno = oerrno;
172 perror(0);
173 hp->h_addr_list++;
174 bcopy(hp->h_addr_list[0], &sin.sin_addr, hp->h_length);
175 (void)fprintf(stderr, "Trying %s...\n",
176 inet_ntoa(sin.sin_addr));
177 continue;
178 }
179 (void)fprintf(stderr, "%s: %s\n", hp->h_name, strerror(errno));
180 sigsetmask(oldmask);
181 return (-1);
182 }
183 #if 0
184 /*
185 * try to rresvport() to the same port. This will make rresvport()
186 * fail it's first bind, resulting in it choosing a random port.
187 */
188 lport--;
189 #endif
190 if (fd2p == 0) {
191 write(s, "", 1);
192 lport = 0;
193 } else {
194 char num[8];
195 int s2 = rresvport(&lport), s3;
196 int len = sizeof(from);
197 int fdssize = howmany(MAX(s, s2)+1, NFDBITS) * sizeof(fd_mask);
198
199 if (s2 < 0)
200 goto bad;
201 readsp = (fd_set *)malloc(fdssize);
202 if (readsp == NULL)
203 goto bad;
204 listen(s2, 1);
205 (void)snprintf(num, sizeof(num), "%d", lport);
206 if (write(s, num, strlen(num)+1) != strlen(num)+1) {
207 (void)fprintf(stderr,
208 "rcmd: write (setting up stderr): %s\n",
209 strerror(errno));
210 (void)close(s2);
211 goto bad;
212 }
213 again:
214 bzero(readsp, fdssize);
215 FD_SET(s, readsp);
216 FD_SET(s2, readsp);
217 errno = 0;
218 if (select(MAX(s, s2) + 1, readsp, 0, 0, 0) < 1 ||
219 !FD_ISSET(s2, readsp)) {
220 if (errno != 0)
221 (void)fprintf(stderr,
222 "rcmd: select (setting up stderr): %s\n",
223 strerror(errno));
224 else
225 (void)fprintf(stderr,
226 "select: protocol failure in circuit setup\n");
227 (void)close(s2);
228 goto bad;
229 }
230 s3 = accept(s2, (struct sockaddr *)&from, &len);
231 /*
232 * XXX careful for ftp bounce attacks. If discovered, shut them
233 * down and check for the real auxiliary channel to connect.
234 */
235 if (from.sin_family == AF_INET && from.sin_port == htons(20)) {
236 close(s3);
237 goto again;
238 }
239 (void)close(s2);
240 if (s3 < 0) {
241 (void)fprintf(stderr,
242 "rcmd: accept: %s\n", strerror(errno));
243 lport = 0;
244 goto bad;
245 }
246 *fd2p = s3;
247 from.sin_port = ntohs(from.sin_port);
248 if (from.sin_family != AF_INET ||
249 from.sin_port >= IPPORT_RESERVED ||
250 from.sin_port < IPPORT_RESERVED / 2) {
251 (void)fprintf(stderr,
252 "socket: protocol failure in circuit setup.\n");
253 goto bad2;
254 }
255 }
256 (void)write(s, locuser, strlen(locuser)+1);
257 (void)write(s, remuser, strlen(remuser)+1);
258 (void)write(s, cmd, strlen(cmd)+1);
259 if (read(s, &c, 1) != 1) {
260 (void)fprintf(stderr,
261 "rcmd: %s: %s\n", *ahost, strerror(errno));
262 goto bad2;
263 }
264 if (c != 0) {
265 while (read(s, &c, 1) == 1) {
266 (void)write(STDERR_FILENO, &c, 1);
267 if (c == '\n')
268 break;
269 }
270 goto bad2;
271 }
272 sigsetmask(oldmask);
273 free(readsp);
274 return (s);
275 bad2:
276 if (lport)
277 (void)close(*fd2p);
278 bad:
279 if (readsp)
280 free(readsp);
281 (void)close(s);
282 sigsetmask(oldmask);
283 return (-1);
284 }
285
286 int
287 rresvport(alport)
288 int *alport;
289 {
290 struct sockaddr_in sin;
291 int s;
292
293 bzero(&sin, sizeof sin);
294 sin.sin_len = sizeof(struct sockaddr_in);
295 sin.sin_family = AF_INET;
296 sin.sin_addr.s_addr = INADDR_ANY;
297 s = socket(AF_INET, SOCK_STREAM, 0);
298 if (s < 0)
299 return (-1);
300 sin.sin_port = htons((in_port_t)*alport);
301 if (*alport < IPPORT_RESERVED - 1) {
302 if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) >= 0)
303 return (s);
304 if (errno != EADDRINUSE) {
305 (void)close(s);
306 return (-1);
307 }
308 }
309 sin.sin_port = 0;
310 if (bindresvport(s, &sin) == -1) {
311 (void)close(s);
312 return (-1);
313 }
314 *alport = (int)ntohs(sin.sin_port);
315 return (s);
316 }
317
318 int __check_rhosts_file = 1;
319 char *__rcmd_errstr;
320
321 int
322 ruserok(rhost, superuser, ruser, luser)
323 const char *rhost, *ruser, *luser;
324 int superuser;
325 {
326 struct hostent *hp;
327 char **ap;
328 int i;
329 #define MAXADDRS 35
330 u_int32_t addrs[MAXADDRS + 1];
331
332 if ((hp = gethostbyname(rhost)) == NULL)
333 return (-1);
334 for (i = 0, ap = hp->h_addr_list; *ap && i < MAXADDRS; ++ap, ++i)
335 bcopy(*ap, &addrs[i], sizeof(addrs[i]));
336 addrs[i] = 0;
337
338 for (i = 0; i < MAXADDRS && addrs[i]; i++)
339 if (iruserok((in_addr_t)addrs[i], superuser, ruser, luser) == 0)
340 return (0);
341 return (-1);
342 }
343
344 /*
345 * New .rhosts strategy: We are passed an ip address. We spin through
346 * hosts.equiv and .rhosts looking for a match. When the .rhosts only
347 * has ip addresses, we don't have to trust a nameserver. When it
348 * contains hostnames, we spin through the list of addresses the nameserver
349 * gives us and look for a match.
350 *
351 * Returns 0 if ok, -1 if not ok.
352 */
353 int
354 iruserok(raddr, superuser, ruser, luser)
355 unsigned long raddr;
356 int superuser;
357 const char *ruser, *luser;
358 {
359 register char *cp;
360 struct stat sbuf;
361 struct passwd *pwd;
362 FILE *hostf;
363 uid_t uid;
364 int first;
365 char pbuf[MAXPATHLEN];
366
367 first = 1;
368 hostf = superuser ? NULL : fopen(_PATH_HEQUIV, "r");
369 again:
370 if (hostf) {
371 if (__ivaliduser(hostf, raddr, luser, ruser) == 0) {
372 (void)fclose(hostf);
373 return (0);
374 }
375 (void)fclose(hostf);
376 }
377 if (first == 1 && (__check_rhosts_file || superuser)) {
378 first = 0;
379 if ((pwd = getpwnam(luser)) == NULL)
380 return (-1);
381 (void)strcpy(pbuf, pwd->pw_dir);
382 (void)strcat(pbuf, "/.rhosts");
383
384 /*
385 * Change effective uid while opening .rhosts. If root and
386 * reading an NFS mounted file system, can't read files that
387 * are protected read/write owner only.
388 */
389 uid = geteuid();
390 (void)seteuid(pwd->pw_uid);
391 hostf = fopen(pbuf, "r");
392 (void)seteuid(uid);
393
394 if (hostf == NULL)
395 return (-1);
396 /*
397 * If not a regular file, or is owned by someone other than
398 * user or root or if writeable by anyone but the owner, quit.
399 */
400 cp = NULL;
401 if (lstat(pbuf, &sbuf) < 0)
402 cp = ".rhosts lstat failed";
403 else if (!S_ISREG(sbuf.st_mode))
404 cp = ".rhosts not regular file";
405 else if (fstat(fileno(hostf), &sbuf) < 0)
406 cp = ".rhosts fstat failed";
407 else if (sbuf.st_uid && sbuf.st_uid != pwd->pw_uid)
408 cp = "bad .rhosts owner";
409 else if (sbuf.st_mode & (S_IWGRP|S_IWOTH))
410 cp = ".rhosts writeable by other than owner";
411 /* If there were any problems, quit. */
412 if (cp) {
413 __rcmd_errstr = cp;
414 (void)fclose(hostf);
415 return (-1);
416 }
417 goto again;
418 }
419 return (-1);
420 }
421
422 /*
423 * XXX
424 * Don't make static, used by lpd(8).
425 *
426 * Returns 0 if ok, -1 if not ok.
427 */
428 int
429 __ivaliduser(hostf, raddrl, luser, ruser)
430 FILE *hostf;
431 in_addr_t raddrl;
432 const char *luser, *ruser;
433 {
434 register char *user, *p;
435 char *buf;
436 const char *auser, *ahost;
437 int hostok, userok;
438 char *rhost = (char *)-1;
439 char domain[MAXHOSTNAMELEN];
440 u_int32_t raddr = (u_int32_t)raddrl;
441 size_t buflen;
442
443 getdomainname(domain, sizeof(domain));
444
445 while ((buf = fgetln(hostf, &buflen))) {
446 p = buf;
447 if (*p == '#')
448 continue;
449 while (*p != '\n' && *p != ' ' && *p != '\t' && p < buf + buflen) {
450 if (!isprint(*p))
451 goto bail;
452 *p = isupper(*p) ? tolower(*p) : *p;
453 p++;
454 }
455 if (p >= buf + buflen)
456 continue;
457 if (*p == ' ' || *p == '\t') {
458 *p++ = '\0';
459 while (*p == ' ' || *p == '\t' && p < buf + buflen)
460 p++;
461 if (p >= buf + buflen)
462 continue;
463 user = p;
464 while (*p != '\n' && *p != ' ' &&
465 *p != '\t' && p < buf + buflen) {
466 if (!isprint(*p))
467 goto bail;
468 p++;
469 }
470 } else
471 user = p;
472 *p = '\0';
473
474 if (p == buf)
475 continue;
476
477 auser = *user ? user : luser;
478 ahost = buf;
479
480 if (strlen(ahost) >= MAXHOSTNAMELEN)
481 continue;
482
483 /*
484 * innetgr() must lookup a hostname (we do not attempt
485 * to change the semantics so that netgroups may have
486 * #.#.#.# addresses in the list.)
487 */
488 if (ahost[0] == '+')
489 switch (ahost[1]) {
490 case '\0':
491 hostok = 1;
492 break;
493 case '@':
494 if (rhost == (char *)-1)
495 rhost = __gethostloop(raddr);
496 hostok = 0;
497 if (rhost)
498 hostok = innetgr(&ahost[2], rhost,
499 NULL, domain);
500 break;
501 default:
502 hostok = __icheckhost(raddr, &ahost[1]);
503 break;
504 }
505 else if (ahost[0] == '-')
506 switch (ahost[1]) {
507 case '\0':
508 hostok = -1;
509 break;
510 case '@':
511 if (rhost == (char *)-1)
512 rhost = __gethostloop(raddr);
513 hostok = 0;
514 if (rhost)
515 hostok = -innetgr(&ahost[2], rhost,
516 NULL, domain);
517 break;
518 default:
519 hostok = -__icheckhost(raddr, &ahost[1]);
520 break;
521 }
522 else
523 hostok = __icheckhost(raddr, ahost);
524
525
526 if (auser[0] == '+')
527 switch (auser[1]) {
528 case '\0':
529 userok = 1;
530 break;
531 case '@':
532 userok = innetgr(&auser[2], NULL, ruser,
533 domain);
534 break;
535 default:
536 userok = strcmp(ruser, &auser[1]) ? 0 : 1;
537 break;
538 }
539 else if (auser[0] == '-')
540 switch (auser[1]) {
541 case '\0':
542 userok = -1;
543 break;
544 case '@':
545 userok = -innetgr(&auser[2], NULL, ruser,
546 domain);
547 break;
548 default:
549 userok = strcmp(ruser, &auser[1]) ? 0 : -1;
550 break;
551 }
552 else
553 userok = strcmp(ruser, auser) ? 0 : 1;
554
555 /* Check if one component did not match */
556 if (hostok == 0 || userok == 0)
557 continue;
558
559 /* Check if we got a forbidden pair */
560 if (userok <= -1 || hostok <= -1)
561 return (-1);
562
563 /* Check if we got a valid pair */
564 if (hostok >= 1 && userok >= 1)
565 return (0);
566 }
567 bail:
568 return (-1);
569 }
570
571 /*
572 * Returns "true" if match, 0 if no match. If we do not find any
573 * semblance of an A->PTR->A loop, allow a simple #.#.#.# match to work.
574 */
575 static int
576 __icheckhost(raddr, lhost)
577 u_int32_t raddr;
578 const char *lhost;
579 {
580 register struct hostent *hp;
581 register char **pp;
582 struct in_addr in;
583
584 hp = gethostbyname(lhost);
585 if (hp != NULL) {
586 /* Spin through ip addresses. */
587 for (pp = hp->h_addr_list; *pp; ++pp)
588 if (!bcmp(&raddr, *pp, sizeof(raddr)))
589 return (1);
590 }
591
592 in.s_addr = raddr;
593 if (strcmp(lhost, inet_ntoa(in)) == 0)
594 return (1);
595 return (0);
596 }
597
598 /*
599 * Return the hostname associated with the supplied address.
600 * Do a reverse lookup as well for security. If a loop cannot
601 * be found, pack the result of inet_ntoa() into the string.
602 */
603 static char *
604 __gethostloop(raddr)
605 u_int32_t raddr;
606 {
607 static char remotehost[MAXHOSTNAMELEN];
608 struct hostent *hp;
609 struct in_addr in;
610
611 hp = gethostbyaddr((char *) &raddr, sizeof(raddr), AF_INET);
612 if (hp == NULL)
613 return (NULL);
614
615 /*
616 * Look up the name and check that the supplied
617 * address is in the list
618 */
619 strncpy(remotehost, hp->h_name, sizeof(remotehost) - 1);
620 remotehost[sizeof(remotehost) - 1] = '\0';
621 hp = gethostbyname(remotehost);
622 if (hp == NULL)
623 return (NULL);
624
625 for (; hp->h_addr_list[0] != NULL; hp->h_addr_list++)
626 if (!bcmp(hp->h_addr_list[0], (caddr_t)&raddr, sizeof(raddr)))
627 return (remotehost);
628
629 /*
630 * either the DNS adminstrator has made a configuration
631 * mistake, or someone has attempted to spoof us
632 */
633 in.s_addr = raddr;
634 syslog(LOG_NOTICE, "rcmd: address %s not listed for host %s",
635 inet_ntoa(in), hp->h_name);
636 return (NULL);
637 }