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