rlogind.c revision 13881
1/*- 2 * Copyright (c) 1983, 1988, 1989, 1993 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. All advertising materials mentioning features or use of this software 14 * must display the following acknowledgement: 15 * This product includes software developed by the University of 16 * California, Berkeley and its contributors. 17 * 4. Neither the name of the University nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 34#ifndef lint 35static char copyright[] = 36"@(#) Copyright (c) 1983, 1988, 1989, 1993\n\ 37 The Regents of the University of California. All rights reserved.\n"; 38#endif /* not lint */ 39 40#ifndef lint 41static char sccsid[] = "@(#)rlogind.c 8.1 (Berkeley) 6/4/93"; 42#endif /* not lint */ 43 44/* 45 * remote login server: 46 * \0 47 * remuser\0 48 * locuser\0 49 * terminal_type/speed\0 50 * data 51 */ 52 53#define FD_SETSIZE 16 /* don't need many bits for select */ 54#include <sys/param.h> 55#include <sys/stat.h> 56#include <sys/ioctl.h> 57#include <signal.h> 58#include <termios.h> 59 60#include <sys/socket.h> 61#include <netinet/in.h> 62#include <netinet/in_systm.h> 63#include <netinet/ip.h> 64#include <netinet/tcp.h> 65#include <arpa/inet.h> 66#include <netdb.h> 67 68#include <pwd.h> 69#include <syslog.h> 70#include <errno.h> 71#include <stdio.h> 72#include <unistd.h> 73#include <stdlib.h> 74#include <string.h> 75#include "pathnames.h" 76 77#ifndef TIOCPKT_WINDOW 78#define TIOCPKT_WINDOW 0x80 79#endif 80 81#ifdef KERBEROS 82#include <kerberosIV/des.h> 83#include <kerberosIV/krb.h> 84#define SECURE_MESSAGE "This rlogin session is using DES encryption for all transmissions.\r\n" 85 86AUTH_DAT *kdata; 87KTEXT ticket; 88u_char auth_buf[sizeof(AUTH_DAT)]; 89u_char tick_buf[sizeof(KTEXT_ST)]; 90Key_schedule schedule; 91int doencrypt, retval, use_kerberos, vacuous; 92 93#define ARGSTR "Dalnkvx" 94#else 95#define ARGSTR "Daln" 96#endif /* KERBEROS */ 97 98char *env[2]; 99#define NMAX 30 100char lusername[NMAX+1], rusername[NMAX+1]; 101static char term[64] = "TERM="; 102#define ENVSIZE (sizeof("TERM=")-1) /* skip null for concatenation */ 103int keepalive = 1; 104int check_all = 0; 105int no_delay; 106 107struct passwd *pwd; 108 109void doit __P((int, struct sockaddr_in *)); 110int control __P((int, char *, int)); 111void protocol __P((int, int)); 112void cleanup __P((int)); 113void fatal __P((int, char *, int)); 114int do_rlogin __P((struct sockaddr_in *)); 115void getstr __P((char *, int, char *)); 116void setup_term __P((int)); 117int do_krb_login __P((struct sockaddr_in *)); 118void usage __P((void)); 119int local_domain __P((char *)); 120char *topdomain __P((char *)); 121 122int 123main(argc, argv) 124 int argc; 125 char *argv[]; 126{ 127 extern int __check_rhosts_file; 128 struct sockaddr_in from; 129 int ch, fromlen, on; 130 131 openlog("rlogind", LOG_PID | LOG_CONS, LOG_AUTH); 132 133 opterr = 0; 134 while ((ch = getopt(argc, argv, ARGSTR)) != EOF) 135 switch (ch) { 136 case 'D': 137 no_delay = 1; 138 break; 139 case 'a': 140 check_all = 1; 141 break; 142 case 'l': 143 __check_rhosts_file = 0; 144 break; 145 case 'n': 146 keepalive = 0; 147 break; 148#ifdef KERBEROS 149 case 'k': 150 use_kerberos = 1; 151 break; 152 case 'v': 153 vacuous = 1; 154 break; 155#ifdef CRYPT 156 case 'x': 157 doencrypt = 1; 158 break; 159#endif 160#endif 161 case '?': 162 default: 163 usage(); 164 break; 165 } 166 argc -= optind; 167 argv += optind; 168 169#ifdef KERBEROS 170 if (use_kerberos && vacuous) { 171 usage(); 172 fatal(STDERR_FILENO, "only one of -k and -v allowed", 0); 173 } 174#endif 175 fromlen = sizeof (from); 176 if (getpeername(0, (struct sockaddr *)&from, &fromlen) < 0) { 177 syslog(LOG_ERR,"Can't get peer name of remote host: %m"); 178 fatal(STDERR_FILENO, "Can't get peer name of remote host", 1); 179 } 180 on = 1; 181 if (keepalive && 182 setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof (on)) < 0) 183 syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m"); 184 if (no_delay && 185 setsockopt(0, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) < 0) 186 syslog(LOG_WARNING, "setsockopt (TCP_NODELAY): %m"); 187 on = IPTOS_LOWDELAY; 188 if (setsockopt(0, IPPROTO_IP, IP_TOS, (char *)&on, sizeof(int)) < 0) 189 syslog(LOG_WARNING, "setsockopt (IP_TOS): %m"); 190 191 doit(0, &from); 192} 193 194int child; 195int netf; 196char line[MAXPATHLEN]; 197int confirmed; 198 199struct winsize win = { 0, 0, 0, 0 }; 200 201 202void 203doit(f, fromp) 204 int f; 205 struct sockaddr_in *fromp; 206{ 207 int master, pid, on = 1; 208 int authenticated = 0; 209 register struct hostent *hp; 210 char hostname[2 * MAXHOSTNAMELEN + 1]; 211 char c; 212 213 alarm(60); 214 read(f, &c, 1); 215 216 if (c != 0) 217 exit(1); 218#ifdef KERBEROS 219 if (vacuous) 220 fatal(f, "Remote host requires Kerberos authentication", 0); 221#endif 222 223 alarm(0); 224 fromp->sin_port = ntohs((u_short)fromp->sin_port); 225 hp = gethostbyaddr((char *)&fromp->sin_addr, sizeof(struct in_addr), 226 fromp->sin_family); 227 if (hp) 228 (void)strcpy(hostname, hp->h_name); 229 else 230 (void)strcpy(hostname, inet_ntoa(fromp->sin_addr)); 231 232#ifdef KERBEROS 233 if (use_kerberos) { 234 retval = do_krb_login(fromp); 235 if (retval == 0) 236 authenticated++; 237 else if (retval > 0) 238 fatal(f, krb_err_txt[retval], 0); 239 write(f, &c, 1); 240 confirmed = 1; /* we sent the null! */ 241 } else 242#endif 243 { 244 if (fromp->sin_family != AF_INET || 245 fromp->sin_port >= IPPORT_RESERVED || 246 fromp->sin_port < IPPORT_RESERVED/2) { 247 syslog(LOG_NOTICE, "Connection from %s on illegal port", 248 inet_ntoa(fromp->sin_addr)); 249 fatal(f, "Permission denied", 0); 250 } 251#ifdef IP_OPTIONS 252 { 253 u_char optbuf[BUFSIZ/3], *cp; 254 char lbuf[BUFSIZ], *lp; 255 int optsize = sizeof(optbuf), ipproto; 256 struct protoent *ip; 257 258 if ((ip = getprotobyname("ip")) != NULL) 259 ipproto = ip->p_proto; 260 else 261 ipproto = IPPROTO_IP; 262 if (getsockopt(0, ipproto, IP_OPTIONS, (char *)optbuf, 263 &optsize) == 0 && optsize != 0) { 264 lp = lbuf; 265 for (cp = optbuf; optsize > 0; cp++, optsize--, lp += 3) 266 sprintf(lp, " %2.2x", *cp); 267 syslog(LOG_NOTICE, 268 "Connection received using IP options (ignored):%s", 269 lbuf); 270 if (setsockopt(0, ipproto, IP_OPTIONS, 271 (char *)NULL, optsize) != 0) { 272 syslog(LOG_ERR, 273 "setsockopt IP_OPTIONS NULL: %m"); 274 exit(1); 275 } 276 } 277 } 278#endif 279 if (do_rlogin(fromp) == 0) 280 authenticated++; 281 } 282 if (confirmed == 0) { 283 write(f, "", 1); 284 confirmed = 1; /* we sent the null! */ 285 } 286#ifdef KERBEROS 287#ifdef CRYPT 288 if (doencrypt) 289 (void) des_write(f, SECURE_MESSAGE, sizeof(SECURE_MESSAGE) - 1); 290#endif 291#endif 292 netf = f; 293 294 pid = forkpty(&master, line, NULL, &win); 295 if (pid < 0) { 296 if (errno == ENOENT) 297 fatal(f, "Out of ptys", 0); 298 else 299 fatal(f, "Forkpty", 1); 300 } 301 if (pid == 0) { 302 if (f > 2) /* f should always be 0, but... */ 303 (void) close(f); 304 setup_term(0); 305 if (*lusername=='-') { 306 syslog(LOG_ERR, "tried to pass user \"%s\" to login", 307 lusername); 308 fatal(STDERR_FILENO, "invalid user", 0); 309 } 310 if (authenticated) { 311#ifdef KERBEROS 312 if (use_kerberos && (pwd->pw_uid == 0)) 313 syslog(LOG_INFO|LOG_AUTH, 314 "ROOT Kerberos login from %s.%s@%s on %s\n", 315 kdata->pname, kdata->pinst, kdata->prealm, 316 hostname); 317#endif 318 319 execl(_PATH_LOGIN, "login", "-p", 320 "-h", hostname, "-f", lusername, (char *)NULL); 321 } else 322 execl(_PATH_LOGIN, "login", "-p", 323 "-h", hostname, lusername, (char *)NULL); 324 fatal(STDERR_FILENO, _PATH_LOGIN, 1); 325 /*NOTREACHED*/ 326 } 327#ifdef CRYPT 328#ifdef KERBEROS 329 /* 330 * If encrypted, don't turn on NBIO or the des read/write 331 * routines will croak. 332 */ 333 334 if (!doencrypt) 335#endif 336#endif 337 ioctl(f, FIONBIO, &on); 338 ioctl(master, FIONBIO, &on); 339 ioctl(master, TIOCPKT, &on); 340 signal(SIGCHLD, cleanup); 341 protocol(f, master); 342 signal(SIGCHLD, SIG_IGN); 343 cleanup(0); 344} 345 346char magic[2] = { 0377, 0377 }; 347char oobdata[] = {TIOCPKT_WINDOW}; 348 349/* 350 * Handle a "control" request (signaled by magic being present) 351 * in the data stream. For now, we are only willing to handle 352 * window size changes. 353 */ 354int 355control(pty, cp, n) 356 int pty; 357 char *cp; 358 int n; 359{ 360 struct winsize w; 361 362 if (n < 4+sizeof (w) || cp[2] != 's' || cp[3] != 's') 363 return (0); 364 oobdata[0] &= ~TIOCPKT_WINDOW; /* we know he heard */ 365 bcopy(cp+4, (char *)&w, sizeof(w)); 366 w.ws_row = ntohs(w.ws_row); 367 w.ws_col = ntohs(w.ws_col); 368 w.ws_xpixel = ntohs(w.ws_xpixel); 369 w.ws_ypixel = ntohs(w.ws_ypixel); 370 (void)ioctl(pty, TIOCSWINSZ, &w); 371 return (4+sizeof (w)); 372} 373 374/* 375 * rlogin "protocol" machine. 376 */ 377void 378protocol(f, p) 379 register int f, p; 380{ 381 char pibuf[1024+1], fibuf[1024], *pbp, *fbp; 382 register pcc = 0, fcc = 0; 383 int cc, nfd, n; 384 char cntl; 385 386 /* 387 * Must ignore SIGTTOU, otherwise we'll stop 388 * when we try and set slave pty's window shape 389 * (our controlling tty is the master pty). 390 */ 391 (void) signal(SIGTTOU, SIG_IGN); 392 send(f, oobdata, 1, MSG_OOB); /* indicate new rlogin */ 393 if (f > p) 394 nfd = f + 1; 395 else 396 nfd = p + 1; 397 if (nfd > FD_SETSIZE) { 398 syslog(LOG_ERR, "select mask too small, increase FD_SETSIZE"); 399 fatal(f, "internal error (select mask too small)", 0); 400 } 401 for (;;) { 402 fd_set ibits, obits, ebits, *omask; 403 404 FD_ZERO(&ebits); 405 FD_ZERO(&ibits); 406 FD_ZERO(&obits); 407 omask = (fd_set *)NULL; 408 if (fcc) { 409 FD_SET(p, &obits); 410 omask = &obits; 411 } else 412 FD_SET(f, &ibits); 413 if (pcc >= 0) 414 if (pcc) { 415 FD_SET(f, &obits); 416 omask = &obits; 417 } else 418 FD_SET(p, &ibits); 419 FD_SET(p, &ebits); 420 if ((n = select(nfd, &ibits, omask, &ebits, 0)) < 0) { 421 if (errno == EINTR) 422 continue; 423 fatal(f, "select", 1); 424 } 425 if (n == 0) { 426 /* shouldn't happen... */ 427 sleep(5); 428 continue; 429 } 430#define pkcontrol(c) ((c)&(TIOCPKT_FLUSHWRITE|TIOCPKT_NOSTOP|TIOCPKT_DOSTOP)) 431 if (FD_ISSET(p, &ebits)) { 432 cc = read(p, &cntl, 1); 433 if (cc == 1 && pkcontrol(cntl)) { 434 cntl |= oobdata[0]; 435 send(f, &cntl, 1, MSG_OOB); 436 if (cntl & TIOCPKT_FLUSHWRITE) { 437 pcc = 0; 438 FD_CLR(p, &ibits); 439 } 440 } 441 } 442 if (FD_ISSET(f, &ibits)) { 443#ifdef CRYPT 444#ifdef KERBEROS 445 if (doencrypt) 446 fcc = des_read(f, fibuf, sizeof(fibuf)); 447 else 448#endif 449#endif 450 fcc = read(f, fibuf, sizeof(fibuf)); 451 if (fcc < 0 && errno == EWOULDBLOCK) 452 fcc = 0; 453 else { 454 register char *cp; 455 int left, n; 456 457 if (fcc <= 0) 458 break; 459 fbp = fibuf; 460 461 top: 462 for (cp = fibuf; cp < fibuf+fcc-1; cp++) 463 if (cp[0] == magic[0] && 464 cp[1] == magic[1]) { 465 left = fcc - (cp-fibuf); 466 n = control(p, cp, left); 467 if (n) { 468 left -= n; 469 if (left > 0) 470 bcopy(cp+n, cp, left); 471 fcc -= n; 472 goto top; /* n^2 */ 473 } 474 } 475 FD_SET(p, &obits); /* try write */ 476 } 477 } 478 479 if (FD_ISSET(p, &obits) && fcc > 0) { 480 cc = write(p, fbp, fcc); 481 if (cc > 0) { 482 fcc -= cc; 483 fbp += cc; 484 } 485 } 486 487 if (FD_ISSET(p, &ibits)) { 488 pcc = read(p, pibuf, sizeof (pibuf)); 489 pbp = pibuf; 490 if (pcc < 0 && errno == EWOULDBLOCK) 491 pcc = 0; 492 else if (pcc <= 0) 493 break; 494 else if (pibuf[0] == 0) { 495 pbp++, pcc--; 496#ifdef CRYPT 497#ifdef KERBEROS 498 if (!doencrypt) 499#endif 500#endif 501 FD_SET(f, &obits); /* try write */ 502 } else { 503 if (pkcontrol(pibuf[0])) { 504 pibuf[0] |= oobdata[0]; 505 send(f, &pibuf[0], 1, MSG_OOB); 506 } 507 pcc = 0; 508 } 509 } 510 if ((FD_ISSET(f, &obits)) && pcc > 0) { 511#ifdef CRYPT 512#ifdef KERBEROS 513 if (doencrypt) 514 cc = des_write(f, pbp, pcc); 515 else 516#endif 517#endif 518 cc = write(f, pbp, pcc); 519 if (cc < 0 && errno == EWOULDBLOCK) { 520 /* 521 * This happens when we try write after read 522 * from p, but some old kernels balk at large 523 * writes even when select returns true. 524 */ 525 if (!FD_ISSET(p, &ibits)) 526 sleep(5); 527 continue; 528 } 529 if (cc > 0) { 530 pcc -= cc; 531 pbp += cc; 532 } 533 } 534 } 535} 536 537void 538cleanup(signo) 539 int signo; 540{ 541 char *p; 542 543 p = line + sizeof(_PATH_DEV) - 1; 544 if (logout(p)) 545 logwtmp(p, "", ""); 546 (void)chmod(line, 0666); 547 (void)chown(line, 0, 0); 548 *p = 'p'; 549 (void)chmod(line, 0666); 550 (void)chown(line, 0, 0); 551 shutdown(netf, 2); 552 exit(1); 553} 554 555void 556fatal(f, msg, syserr) 557 int f; 558 char *msg; 559 int syserr; 560{ 561 int len; 562 char buf[BUFSIZ], *bp = buf; 563 564 /* 565 * Prepend binary one to message if we haven't sent 566 * the magic null as confirmation. 567 */ 568 if (!confirmed) 569 *bp++ = '\01'; /* error indicator */ 570 if (syserr) 571 len = sprintf(bp, "rlogind: %s: %s.\r\n", 572 msg, strerror(errno)); 573 else 574 len = sprintf(bp, "rlogind: %s.\r\n", msg); 575 (void) write(f, buf, bp + len - buf); 576 exit(1); 577} 578 579int 580do_rlogin(dest) 581 struct sockaddr_in *dest; 582{ 583 getstr(rusername, sizeof(rusername), "remuser too long"); 584 getstr(lusername, sizeof(lusername), "locuser too long"); 585 getstr(term+ENVSIZE, sizeof(term)-ENVSIZE, "Terminal type too long"); 586 587 pwd = getpwnam(lusername); 588 if (pwd == NULL) 589 return (-1); 590 /* XXX why don't we syslog() failure? */ 591 return (iruserok(dest->sin_addr.s_addr, pwd->pw_uid == 0, 592 rusername, lusername)); 593} 594 595void 596getstr(buf, cnt, errmsg) 597 char *buf; 598 int cnt; 599 char *errmsg; 600{ 601 char c; 602 603 do { 604 if (read(0, &c, 1) != 1) 605 exit(1); 606 if (--cnt < 0) 607 fatal(STDOUT_FILENO, errmsg, 0); 608 *buf++ = c; 609 } while (c != 0); 610} 611 612extern char **environ; 613 614void 615setup_term(fd) 616 int fd; 617{ 618 register char *cp = index(term+ENVSIZE, '/'); 619 char *speed; 620 struct termios tt; 621 622#ifndef notyet 623 tcgetattr(fd, &tt); 624 if (cp) { 625 *cp++ = '\0'; 626 speed = cp; 627 cp = index(speed, '/'); 628 if (cp) 629 *cp++ = '\0'; 630 cfsetspeed(&tt, atoi(speed)); 631 } 632 633 tt.c_iflag = TTYDEF_IFLAG; 634 tt.c_oflag = TTYDEF_OFLAG; 635 tt.c_lflag = TTYDEF_LFLAG; 636 tcsetattr(fd, TCSAFLUSH, &tt); 637#else 638 if (cp) { 639 *cp++ = '\0'; 640 speed = cp; 641 cp = index(speed, '/'); 642 if (cp) 643 *cp++ = '\0'; 644 tcgetattr(fd, &tt); 645 cfsetspeed(&tt, atoi(speed)); 646 tcsetattr(fd, TCSAFLUSH, &tt); 647 } 648#endif 649 650 env[0] = term; 651 env[1] = 0; 652 environ = env; 653} 654 655#ifdef KERBEROS 656#define VERSION_SIZE 9 657 658/* 659 * Do the remote kerberos login to the named host with the 660 * given inet address 661 * 662 * Return 0 on valid authorization 663 * Return -1 on valid authentication, no authorization 664 * Return >0 for error conditions 665 */ 666int 667do_krb_login(dest) 668 struct sockaddr_in *dest; 669{ 670 int rc; 671 char instance[INST_SZ], version[VERSION_SIZE]; 672 long authopts = 0L; /* !mutual */ 673 struct sockaddr_in faddr; 674 675 kdata = (AUTH_DAT *) auth_buf; 676 ticket = (KTEXT) tick_buf; 677 678 instance[0] = '*'; 679 instance[1] = '\0'; 680 681#ifdef CRYPT 682 if (doencrypt) { 683 rc = sizeof(faddr); 684 if (getsockname(0, (struct sockaddr *)&faddr, &rc)) 685 return (-1); 686 authopts = KOPT_DO_MUTUAL; 687 rc = krb_recvauth( 688 authopts, 0, 689 ticket, "rcmd", 690 instance, dest, &faddr, 691 kdata, "", schedule, version); 692 des_set_key_krb(&kdata->session, schedule); 693 694 } else 695#endif 696 rc = krb_recvauth( 697 authopts, 0, 698 ticket, "rcmd", 699 instance, dest, (struct sockaddr_in *) 0, 700 kdata, "", (bit_64 *) 0, version); 701 702 if (rc != KSUCCESS) 703 return (rc); 704 705 getstr(lusername, sizeof(lusername), "locuser"); 706 /* get the "cmd" in the rcmd protocol */ 707 getstr(term+ENVSIZE, sizeof(term)-ENVSIZE, "Terminal type"); 708 709 pwd = getpwnam(lusername); 710 if (pwd == NULL) 711 return (-1); 712 713 /* returns nonzero for no access */ 714 if (kuserok(kdata, lusername) != 0) 715 return (-1); 716 717 return (0); 718 719} 720#endif /* KERBEROS */ 721 722void 723usage() 724{ 725#ifdef KERBEROS 726 syslog(LOG_ERR, "usage: rlogind [-Daln] [-k | -v]"); 727#else 728 syslog(LOG_ERR, "usage: rlogind [-Daln]"); 729#endif 730} 731 732/* 733 * Check whether host h is in our local domain, 734 * defined as sharing the last two components of the domain part, 735 * or the entire domain part if the local domain has only one component. 736 * If either name is unqualified (contains no '.'), 737 * assume that the host is local, as it will be 738 * interpreted as such. 739 */ 740int 741local_domain(h) 742 char *h; 743{ 744 char localhost[MAXHOSTNAMELEN]; 745 char *p1, *p2; 746 747 localhost[0] = 0; 748 (void) gethostname(localhost, sizeof(localhost)); 749 p1 = topdomain(localhost); 750 p2 = topdomain(h); 751 if (p1 == NULL || p2 == NULL || !strcasecmp(p1, p2)) 752 return (1); 753 return (0); 754} 755 756char * 757topdomain(h) 758 char *h; 759{ 760 register char *p; 761 char *maybe = NULL; 762 int dots = 0; 763 764 for (p = h + strlen(h); p >= h; p--) { 765 if (*p == '.') { 766 if (++dots == 2) 767 return (p); 768 maybe = p; 769 } 770 } 771 return (maybe); 772} 773