ip_ftp_pxy.c revision 60857
1/* 2 * Simple FTP transparent proxy for in-kernel use. For use with the NAT 3 * code. 4 * $FreeBSD: head/sys/contrib/ipfilter/netinet/ip_ftp_pxy.c 60857 2000-05-24 04:40:17Z darrenr $ 5 */ 6#if SOLARIS && defined(_KERNEL) 7extern kmutex_t ipf_rw; 8#endif 9 10#define isdigit(x) ((x) >= '0' && (x) <= '9') 11#define isupper(x) ((unsigned)((x) - 'A') <= 'Z' - 'A') 12 13#define IPF_FTP_PROXY 14 15#define IPF_MINPORTLEN 18 16#define IPF_MAXPORTLEN 30 17#define IPF_MIN227LEN 39 18#define IPF_MAX227LEN 51 19#define IPF_FTPBUFSZ 96 /* This *MUST* be >= 53! */ 20 21 22int ippr_ftp_client __P((fr_info_t *, ip_t *, nat_t *, ftpinfo_t *, int)); 23int ippr_ftp_complete __P((char *, size_t)); 24int ippr_ftp_in __P((fr_info_t *, ip_t *, ap_session_t *, nat_t *)); 25int ippr_ftp_init __P((void)); 26int ippr_ftp_new __P((fr_info_t *, ip_t *, ap_session_t *, nat_t *)); 27int ippr_ftp_out __P((fr_info_t *, ip_t *, ap_session_t *, nat_t *)); 28int ippr_ftp_pasv __P((fr_info_t *, ip_t *, nat_t *, ftpside_t *, int)); 29int ippr_ftp_port __P((fr_info_t *, ip_t *, nat_t *, ftpside_t *, int)); 30int ippr_ftp_process __P((fr_info_t *, ip_t *, nat_t *, ftpinfo_t *, int)); 31int ippr_ftp_server __P((fr_info_t *, ip_t *, nat_t *, ftpinfo_t *, int)); 32int ippr_ftp_valid __P((char *, size_t)); 33u_short ippr_ftp_atoi __P((char **)); 34 35static frentry_t natfr; 36int ippr_ftp_pasvonly = 0; 37 38 39/* 40 * Initialize local structures. 41 */ 42int ippr_ftp_init() 43{ 44 bzero((char *)&natfr, sizeof(natfr)); 45 natfr.fr_ref = 1; 46 natfr.fr_flags = FR_INQUE|FR_PASS|FR_QUICK|FR_KEEPSTATE; 47 return 0; 48} 49 50 51int ippr_ftp_new(fin, ip, aps, nat) 52fr_info_t *fin; 53ip_t *ip; 54ap_session_t *aps; 55nat_t *nat; 56{ 57 ftpinfo_t *ftp; 58 ftpside_t *f; 59 60 KMALLOC(ftp, ftpinfo_t *); 61 if (ftp == NULL) 62 return -1; 63 aps->aps_data = ftp; 64 aps->aps_psiz = sizeof(ftpinfo_t); 65 66 bzero((char *)ftp, sizeof(*ftp)); 67 f = &ftp->ftp_side[0]; 68 f->ftps_rptr = f->ftps_buf; 69 f->ftps_wptr = f->ftps_buf; 70 f = &ftp->ftp_side[1]; 71 f->ftps_rptr = f->ftps_buf; 72 f->ftps_wptr = f->ftps_buf; 73 return 0; 74} 75 76 77int ippr_ftp_port(fin, ip, nat, f, dlen) 78fr_info_t *fin; 79ip_t *ip; 80nat_t *nat; 81ftpside_t *f; 82int dlen; 83{ 84 tcphdr_t *tcp, tcph, *tcp2 = &tcph; 85 char newbuf[IPF_FTPBUFSZ], *s; 86 u_short a5, a6, sp, dp; 87 u_int a1, a2, a3, a4; 88 struct in_addr swip; 89 size_t nlen, olen; 90 fr_info_t fi; 91 int inc, off; 92 nat_t *ipn; 93 mb_t *m; 94#if SOLARIS 95 mb_t *m1; 96#endif 97 98 tcp = (tcphdr_t *)fin->fin_dp; 99 off = f->ftps_seq - ntohl(tcp->th_seq); 100 if (off < 0) 101 return 0; 102 /* 103 * Check for client sending out PORT message. 104 */ 105 if (dlen < IPF_MINPORTLEN) 106 return 0; 107 /* 108 * Count the number of bytes in the PORT message is. 109 */ 110 if (off < 0) 111 return 0; 112 113 off += fin->fin_hlen + (tcp->th_off << 2); 114 /* 115 * Skip the PORT command + space 116 */ 117 s = f->ftps_rptr + 5; 118 /* 119 * Pick out the address components, two at a time. 120 */ 121 a1 = ippr_ftp_atoi(&s); 122 if (!s) 123 return 0; 124 a2 = ippr_ftp_atoi(&s); 125 if (!s) 126 return 0; 127 /* 128 * check that IP address in the PORT/PASV reply is the same as the 129 * sender of the command - prevents using PORT for port scanning. 130 */ 131 a1 <<= 16; 132 a1 |= a2; 133 if (a1 != ntohl(nat->nat_inip.s_addr)) 134 return 0; 135 136 a5 = ippr_ftp_atoi(&s); 137 if (!s) 138 return 0; 139 if (*s == ')') 140 s++; 141 142 /* 143 * check for CR-LF at the end. 144 */ 145 if (*s == '\n') 146 s--; 147 if ((*s == '\r') && (*(s + 1) == '\n')) { 148 s += 2; 149 a6 = a5 & 0xff; 150 } else 151 return 0; 152 a5 >>= 8; 153 /* 154 * Calculate new address parts for PORT command 155 */ 156 a1 = ntohl(ip->ip_src.s_addr); 157 a2 = (a1 >> 16) & 0xff; 158 a3 = (a1 >> 8) & 0xff; 159 a4 = a1 & 0xff; 160 a1 >>= 24; 161 olen = s - f->ftps_rptr; 162 /* DO NOT change this to sprintf! */ 163 (void) sprintf(newbuf, "%s %u,%u,%u,%u,%u,%u\r\n", 164 "PORT", a1, a2, a3, a4, a5, a6); 165 166 nlen = strlen(newbuf); 167 inc = nlen - olen; 168 if ((inc + ip->ip_len) > 65535) 169 return 0; 170 171#if SOLARIS 172 m = fin->fin_qfm; 173 for (m1 = m; m1->b_cont; m1 = m1->b_cont) 174 ; 175 if ((inc > 0) && (m1->b_datap->db_lim - m1->b_wptr < inc)) { 176 mblk_t *nm; 177 178 /* alloc enough to keep same trailer space for lower driver */ 179 nm = allocb(nlen, BPRI_MED); 180 PANIC((!nm),("ippr_ftp_out: allocb failed")); 181 182 nm->b_band = m1->b_band; 183 nm->b_wptr += nlen; 184 185 m1->b_wptr -= olen; 186 PANIC((m1->b_wptr < m1->b_rptr), 187 ("ippr_ftp_out: cannot handle fragmented data block")); 188 189 linkb(m1, nm); 190 } else { 191 if (m1->b_datap->db_struiolim == m1->b_wptr) 192 m1->b_datap->db_struiolim += inc; 193 m1->b_datap->db_struioflag &= ~STRUIO_IP; 194 m1->b_wptr += inc; 195 } 196 copyin_mblk(m, off, nlen, newbuf); 197#else 198 m = *((mb_t **)fin->fin_mp); 199 if (inc < 0) 200 m_adj(m, inc); 201 /* the mbuf chain will be extended if necessary by m_copyback() */ 202 m_copyback(m, off, nlen, newbuf); 203#endif 204 if (inc != 0) { 205#if SOLARIS || defined(__sgi) 206 register u_32_t sum1, sum2; 207 208 sum1 = ip->ip_len; 209 sum2 = ip->ip_len + inc; 210 211 /* Because ~1 == -2, We really need ~1 == -1 */ 212 if (sum1 > sum2) 213 sum2--; 214 sum2 -= sum1; 215 sum2 = (sum2 & 0xffff) + (sum2 >> 16); 216 217 fix_outcksum(&ip->ip_sum, sum2, 0); 218#endif 219 ip->ip_len += inc; 220 } 221 222 /* 223 * Add skeleton NAT entry for connection which will come back the 224 * other way. 225 */ 226 sp = htons(a5 << 8 | a6); 227 /* 228 * Don't allow the PORT command to specify a port < 1024 due to 229 * security crap. 230 */ 231 if (ntohs(sp) < 1024) 232 return 0; 233 /* 234 * The server may not make the connection back from port 20, but 235 * it is the most likely so use it here to check for a conflicting 236 * mapping. 237 */ 238 dp = htons(fin->fin_data[1] - 1); 239 ipn = nat_outlookup(fin->fin_ifp, IPN_TCP, nat->nat_p, nat->nat_inip, 240 ip->ip_dst, (dp << 16) | sp); 241 if (ipn == NULL) { 242 int slen; 243 244 slen = ip->ip_len; 245 ip->ip_len = fin->fin_hlen + sizeof(*tcp2); 246 bcopy((char *)fin, (char *)&fi, sizeof(fi)); 247 bzero((char *)tcp2, sizeof(*tcp2)); 248 tcp2->th_win = htons(8192); 249 tcp2->th_sport = sp; 250 tcp2->th_off = 5; 251 tcp2->th_dport = 0; /* XXX - don't specify remote port */ 252 fi.fin_data[0] = ntohs(sp); 253 fi.fin_data[1] = 0; 254 fi.fin_dp = (char *)tcp2; 255 swip = ip->ip_src; 256 ip->ip_src = nat->nat_inip; 257 ipn = nat_new(nat->nat_ptr, ip, &fi, IPN_TCP|FI_W_DPORT, 258 NAT_OUTBOUND); 259 if (ipn != NULL) { 260 ipn->nat_age = fr_defnatage; 261 (void) fr_addstate(ip, &fi, FI_W_DPORT); 262 } 263 ip->ip_len = slen; 264 ip->ip_src = swip; 265 } 266 return inc; 267} 268 269 270int ippr_ftp_client(fin, ip, nat, ftp, dlen) 271fr_info_t *fin; 272nat_t *nat; 273ftpinfo_t *ftp; 274ip_t *ip; 275int dlen; 276{ 277 char *rptr, *wptr; 278 ftpside_t *f; 279 int inc; 280 281 inc = 0; 282 f = &ftp->ftp_side[0]; 283 rptr = f->ftps_rptr; 284 wptr = f->ftps_wptr; 285 286 if ((ftp->ftp_passok == 0) && !strncmp(rptr, "USER ", 5)) 287 ftp->ftp_passok = 1; 288 else if ((ftp->ftp_passok == 2) && !strncmp(rptr, "PASS ", 5)) 289 ftp->ftp_passok = 3; 290 else if ((ftp->ftp_passok == 4) && !ippr_ftp_pasvonly && 291 !strncmp(rptr, "PORT ", 5)) { 292 inc = ippr_ftp_port(fin, ip, nat, f, dlen); 293 } 294 295 while ((*rptr++ != '\n') && (rptr < wptr)) 296 ; 297 f->ftps_seq += rptr - f->ftps_rptr; 298 f->ftps_rptr = rptr; 299 return inc; 300} 301 302 303int ippr_ftp_pasv(fin, ip, nat, f, dlen) 304fr_info_t *fin; 305ip_t *ip; 306nat_t *nat; 307ftpside_t *f; 308int dlen; 309{ 310 tcphdr_t *tcp, tcph, *tcp2 = &tcph; 311 struct in_addr swip, swip2; 312 u_short a5, a6, sp, dp; 313 u_int a1, a2, a3, a4; 314 fr_info_t fi; 315 int inc, off; 316 nat_t *ipn; 317 char *s; 318 319 /* 320 * Check for PASV reply message. 321 */ 322 if (dlen < IPF_MIN227LEN) 323 return 0; 324 else if (strncmp(f->ftps_rptr, "227 Entering Passive Mode", 25)) 325 return 0; 326 327 /* 328 * Count the number of bytes in the 227 reply is. 329 */ 330 tcp = (tcphdr_t *)fin->fin_dp; 331 off = f->ftps_seq - ntohl(tcp->th_seq); 332 if (off < 0) 333 return 0; 334 335 off += fin->fin_hlen + (tcp->th_off << 2); 336 /* 337 * Skip the PORT command + space 338 */ 339 s = f->ftps_rptr + 25; 340 while (*s && !isdigit(*s)) 341 s++; 342 /* 343 * Pick out the address components, two at a time. 344 */ 345 a1 = ippr_ftp_atoi(&s); 346 if (!s) 347 return 0; 348 a2 = ippr_ftp_atoi(&s); 349 if (!s) 350 return 0; 351 352 /* 353 * check that IP address in the PORT/PASV reply is the same as the 354 * sender of the command - prevents using PORT for port scanning. 355 */ 356 a1 <<= 16; 357 a1 |= a2; 358 if (a1 != ntohl(nat->nat_oip.s_addr)) 359 return 0; 360 361 a5 = ippr_ftp_atoi(&s); 362 if (!s) 363 return 0; 364 365 if (*s == ')') 366 s++; 367 if (*s == '\n') 368 s--; 369 /* 370 * check for CR-LF at the end. 371 */ 372 if ((*s == '\r') && (*(s + 1) == '\n')) { 373 s += 2; 374 a6 = a5 & 0xff; 375 } else 376 return 0; 377 a5 >>= 8; 378 /* 379 * Calculate new address parts for 227 reply 380 */ 381 a1 = ntohl(ip->ip_src.s_addr); 382 a2 = (a1 >> 16) & 0xff; 383 a3 = (a1 >> 8) & 0xff; 384 a4 = a1 & 0xff; 385 a1 >>= 24; 386 inc = 0; 387#if 0 388 olen = s - f->ftps_rptr; 389 (void) sprintf(newbuf, "%s %u,%u,%u,%u,%u,%u\r\n", 390 "227 Entering Passive Mode", a1, a2, a3, a4, a5, a6); 391 nlen = strlen(newbuf); 392 inc = nlen - olen; 393 if ((inc + ip->ip_len) > 65535) 394 return 0; 395 396#if SOLARIS 397 m = fin->fin_qfm; 398 for (m1 = m; m1->b_cont; m1 = m1->b_cont) 399 ; 400 if ((inc > 0) && (m1->b_datap->db_lim - m1->b_wptr < inc)) { 401 mblk_t *nm; 402 403 /* alloc enough to keep same trailer space for lower driver */ 404 nm = allocb(nlen, BPRI_MED); 405 PANIC((!nm),("ippr_ftp_out: allocb failed")); 406 407 nm->b_band = m1->b_band; 408 nm->b_wptr += nlen; 409 410 m1->b_wptr -= olen; 411 PANIC((m1->b_wptr < m1->b_rptr), 412 ("ippr_ftp_out: cannot handle fragmented data block")); 413 414 linkb(m1, nm); 415 } else { 416 m1->b_wptr += inc; 417 } 418 /*copyin_mblk(m, off, nlen, newbuf);*/ 419#else 420 m = *((mb_t **)fin->fin_mp); 421 if (inc < 0) 422 m_adj(m, inc); 423 /* the mbuf chain will be extended if necessary by m_copyback() */ 424 /*m_copyback(m, off, nlen, newbuf);*/ 425#endif 426 if (inc != 0) { 427#if SOLARIS || defined(__sgi) 428 register u_32_t sum1, sum2; 429 430 sum1 = ip->ip_len; 431 sum2 = ip->ip_len + inc; 432 433 /* Because ~1 == -2, We really need ~1 == -1 */ 434 if (sum1 > sum2) 435 sum2--; 436 sum2 -= sum1; 437 sum2 = (sum2 & 0xffff) + (sum2 >> 16); 438 439 fix_outcksum(&ip->ip_sum, sum2, 0); 440#endif 441 ip->ip_len += inc; 442 } 443#endif 444 445 /* 446 * Add skeleton NAT entry for connection which will come back the 447 * other way. 448 */ 449 sp = 0; 450 dp = htons(fin->fin_data[1] - 1); 451 ipn = nat_outlookup(fin->fin_ifp, IPN_TCP, nat->nat_p, nat->nat_inip, 452 ip->ip_dst, (dp << 16) | sp); 453 if (ipn == NULL) { 454 int slen; 455 456 slen = ip->ip_len; 457 ip->ip_len = fin->fin_hlen + sizeof(*tcp2); 458 bcopy((char *)fin, (char *)&fi, sizeof(fi)); 459 bzero((char *)tcp2, sizeof(*tcp2)); 460 tcp2->th_win = htons(8192); 461 tcp2->th_sport = 0; /* XXX - fake it for nat_new */ 462 tcp2->th_off = 5; 463 fi.fin_data[0] = a5 << 8 | a6; 464 tcp2->th_dport = htons(fi.fin_data[0]); 465 fi.fin_data[1] = 0; 466 fi.fin_dp = (char *)tcp2; 467 swip = ip->ip_src; 468 swip2 = ip->ip_dst; 469 ip->ip_dst = ip->ip_src; 470 ip->ip_src = nat->nat_inip; 471 ipn = nat_new(nat->nat_ptr, ip, &fi, IPN_TCP|FI_W_SPORT, 472 NAT_OUTBOUND); 473 if (ipn != NULL) { 474 ipn->nat_age = fr_defnatage; 475 (void) fr_addstate(ip, &fi, FI_W_SPORT); 476 } 477 ip->ip_len = slen; 478 ip->ip_src = swip; 479 ip->ip_dst = swip2; 480 } 481 return inc; 482} 483 484 485int ippr_ftp_server(fin, ip, nat, ftp, dlen) 486fr_info_t *fin; 487ip_t *ip; 488nat_t *nat; 489ftpinfo_t *ftp; 490int dlen; 491{ 492 char *rptr, *wptr; 493 ftpside_t *f; 494 int inc; 495 496 inc = 0; 497 f = &ftp->ftp_side[1]; 498 rptr = f->ftps_rptr; 499 wptr = f->ftps_wptr; 500 501 if ((ftp->ftp_passok == 1) && !strncmp(rptr, "331", 3)) 502 ftp->ftp_passok = 2; 503 else if ((ftp->ftp_passok == 3) && !strncmp(rptr, "230", 3)) 504 ftp->ftp_passok = 4; 505 else if ((ftp->ftp_passok == 3) && !strncmp(rptr, "530", 3)) 506 ftp->ftp_passok = 0; 507 else if ((ftp->ftp_passok == 4) && !strncmp(rptr, "227 ", 4)) { 508 inc = ippr_ftp_pasv(fin, ip, nat, f, dlen); 509 } 510 while ((*rptr++ != '\n') && (rptr < wptr)) 511 ; 512 f->ftps_seq += rptr - f->ftps_rptr; 513 f->ftps_rptr = rptr; 514 return inc; 515} 516 517 518/* 519 * Look to see if the buffer starts with something which we recognise as 520 * being the correct syntax for the FTP protocol. 521 */ 522int ippr_ftp_valid(buf, len) 523char *buf; 524size_t len; 525{ 526 register char *s, c; 527 register size_t i = len; 528 529 if (i < 5) 530 return 2; 531 s = buf; 532 c = *s++; 533 i--; 534 535 if (isdigit(c)) { 536 c = *s++; 537 i--; 538 if (isdigit(c)) { 539 c = *s++; 540 i--; 541 if (isdigit(c)) { 542 c = *s++; 543 i--; 544 if ((c != '-') && (c != ' ')) 545 return 1; 546 } else 547 return 1; 548 } else 549 return 1; 550 } else if (isupper(c)) { 551 c = *s++; 552 i--; 553 if (isupper(c)) { 554 c = *s++; 555 i--; 556 if (isupper(c)) { 557 c = *s++; 558 i--; 559 if (isupper(c)) { 560 c = *s++; 561 i--; 562 if ((c != ' ') && (c != '\r')) 563 return 1; 564 } else if ((c != ' ') && (c != '\r')) 565 return 1; 566 } else 567 return 1; 568 } else 569 return 1; 570 } else 571 return 1; 572 for (; i; i--) { 573 c = *s++; 574 if (c == '\n') 575 return 0; 576 } 577 return 2; 578} 579 580 581int ippr_ftp_process(fin, ip, nat, ftp, rv) 582fr_info_t *fin; 583ip_t *ip; 584nat_t *nat; 585ftpinfo_t *ftp; 586int rv; 587{ 588 int mlen, len, off, inc, i; 589 char *rptr, *wptr; 590 tcphdr_t *tcp; 591 ftpside_t *f; 592 mb_t *m; 593 594 tcp = (tcphdr_t *)fin->fin_dp; 595 off = fin->fin_hlen + (tcp->th_off << 2); 596 597#if SOLARIS 598 m = fin->fin_qfm; 599#else 600 m = *((mb_t **)fin->fin_mp); 601#endif 602 603#if SOLARIS 604 mlen = msgdsize(m) - off; 605#else 606 mlen = mbufchainlen(m) - off; 607#endif 608 if (!mlen) 609 return 0; 610 611 inc = 0; 612 f = &ftp->ftp_side[rv]; 613 rptr = f->ftps_rptr; 614 wptr = f->ftps_wptr; 615 if ((wptr == f->ftps_buf) && (f->ftps_seq <= ntohl(tcp->th_seq))) 616 f->ftps_seq = ntohl(tcp->th_seq); 617 618 /* 619 * XXX - Ideally, this packet should get dropped because we now know 620 * that it is out of order (and there is no real danger in doing so 621 * apart from causing packets to go through here ordered). 622 */ 623 if (ntohl(tcp->th_seq) != f->ftps_seq + (wptr - rptr)) { 624 return APR_ERR(0); 625 } 626 627 while (mlen > 0) { 628 len = MIN(mlen, FTP_BUFSZ / 2); 629 630#if SOLARIS 631 copyout_mblk(m, off, len, wptr); 632#else 633 m_copydata(m, off, len, wptr); 634#endif 635 mlen -= len; 636 off += len; 637 wptr += len; 638 f->ftps_wptr = wptr; 639 if (f->ftps_junk == 2) 640 f->ftps_junk = ippr_ftp_valid(rptr, wptr - rptr); 641 642 while ((f->ftps_junk == 0) && (wptr > rptr)) { 643 f->ftps_junk = ippr_ftp_valid(rptr, wptr - rptr); 644 if (f->ftps_junk == 0) { 645 len = wptr - rptr; 646 f->ftps_rptr = rptr; 647 if (rv) 648 inc += ippr_ftp_server(fin, ip, nat, 649 ftp, len); 650 else 651 inc += ippr_ftp_client(fin, ip, nat, 652 ftp, len); 653 rptr = f->ftps_rptr; 654 } 655 } 656 657 while ((f->ftps_junk == 1) && (rptr < wptr)) { 658 while ((rptr < wptr) && (*rptr != '\r')) 659 rptr++; 660 661 if ((*rptr == '\r') && (rptr + 1 < wptr)) { 662 if (*(rptr + 1) == '\n') { 663 rptr += 2; 664 f->ftps_junk = 0; 665 } else 666 rptr++; 667 } 668 f->ftps_seq += rptr - f->ftps_rptr; 669 f->ftps_rptr = rptr; 670 } 671 672 if (rptr == wptr) { 673 rptr = wptr = f->ftps_buf; 674 } else { 675 if ((wptr > f->ftps_buf + FTP_BUFSZ / 2)) { 676 i = wptr - rptr; 677 if ((rptr == f->ftps_buf) || 678 (wptr - rptr > FTP_BUFSZ / 2)) { 679 f->ftps_seq += i; 680 f->ftps_junk = 1; 681 rptr = wptr = f->ftps_buf; 682 } else { 683 bcopy(rptr, f->ftps_buf, i); 684 wptr = f->ftps_buf + i; 685 rptr = f->ftps_buf; 686 } 687 } 688 f->ftps_rptr = rptr; 689 f->ftps_wptr = wptr; 690 } 691 } 692 693 f->ftps_rptr = rptr; 694 f->ftps_wptr = wptr; 695 return inc; 696} 697 698 699int ippr_ftp_out(fin, ip, aps, nat) 700fr_info_t *fin; 701ip_t *ip; 702ap_session_t *aps; 703nat_t *nat; 704{ 705 ftpinfo_t *ftp; 706 707 ftp = aps->aps_data; 708 if (ftp == NULL) 709 return 0; 710 return ippr_ftp_process(fin, ip, nat, ftp, 0); 711} 712 713 714int ippr_ftp_in(fin, ip, aps, nat) 715fr_info_t *fin; 716ip_t *ip; 717ap_session_t *aps; 718nat_t *nat; 719{ 720 ftpinfo_t *ftp; 721 722 ftp = aps->aps_data; 723 if (ftp == NULL) 724 return 0; 725 return ippr_ftp_process(fin, ip, nat, ftp, 1); 726} 727 728 729/* 730 * ippr_ftp_atoi - implement a version of atoi which processes numbers in 731 * pairs separated by commas (which are expected to be in the range 0 - 255), 732 * returning a 16 bit number combining either side of the , as the MSB and 733 * LSB. 734 */ 735u_short ippr_ftp_atoi(ptr) 736char **ptr; 737{ 738 register char *s = *ptr, c; 739 register u_char i = 0, j = 0; 740 741 while ((c = *s++) && isdigit(c)) { 742 i *= 10; 743 i += c - '0'; 744 } 745 if (c != ',') { 746 *ptr = NULL; 747 return 0; 748 } 749 while ((c = *s++) && isdigit(c)) { 750 j *= 10; 751 j += c - '0'; 752 } 753 *ptr = s; 754 return (i << 8) | j; 755} 756