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