| 623 | | } |
|---|
| | 654 | /// Get a data socket from the server. |
|---|
| | 655 | /// |
|---|
| | 656 | /// This sends PASV/PORT as necessary. |
|---|
| | 657 | /// |
|---|
| | 658 | /// Returns: the data socket or a listener |
|---|
| | 659 | protected SocketConduit getDataSocket() { |
|---|
| | 660 | // What type are we using? |
|---|
| | 661 | switch(this.inf_.type) { |
|---|
| | 662 | default: |
|---|
| | 663 | exception("unknown connection type"); |
|---|
| | 664 | |
|---|
| | 665 | // Passive is complicated. Handle it in another member. |
|---|
| | 666 | case FtpConnectionType.passive: |
|---|
| | 667 | return this.connectPassive(); |
|---|
| | 668 | |
|---|
| | 669 | // Active is simpler, but not as fool-proof. |
|---|
| | 670 | case FtpConnectionType.active: |
|---|
| | 671 | IPv4Address data_addr = cast(IPv4Address) this.inf_.address; |
|---|
| | 672 | |
|---|
| | 673 | // Start listening. |
|---|
| | 674 | SocketConduit listener = new SocketConduit(); |
|---|
| | 675 | listener.bind(this.inf_.listen); |
|---|
| | 676 | listener.socket.listen(32); |
|---|
| | 677 | |
|---|
| | 678 | // Use EPRT if we know it's supported. |
|---|
| | 679 | if(this.is_supported("EPRT")) { |
|---|
| | 680 | char[64] tmp = void; |
|---|
| | 681 | |
|---|
| | 682 | this.sendCommand("EPRT", Text.layout(tmp, "|1|%0|%1|", |
|---|
| | 683 | data_addr.toAddrString, data_addr.toPortString)); |
|---|
| | 684 | // this.sendCommand("EPRT", format("|1|%s|%s|", data_addr.toAddrString(), data_addr.toPortString())); |
|---|
| | 685 | this.readResponse("200"); |
|---|
| | 686 | } else { |
|---|
| | 687 | int h1, h2, h3, h4, p1, p2; |
|---|
| | 688 | h1 = (data_addr.addr() >> 24) % 256; |
|---|
| | 689 | h2 = (data_addr.addr() >> 16) % 256; |
|---|
| | 690 | h3 = (data_addr.addr() >> 8_) % 256; |
|---|
| | 691 | h4 = (data_addr.addr() >> 0_) % 256; |
|---|
| | 692 | p1 = (data_addr.port() >> 8_) % 256; |
|---|
| | 693 | p2 = (data_addr.port() >> 0_) % 256; |
|---|
| | 694 | |
|---|
| | 695 | // low overhead method to format a numerical string |
|---|
| | 696 | char[64] tmp = void; |
|---|
| | 697 | char[20] foo = void; |
|---|
| | 698 | auto str = Text.layout(tmp, "%0,%1,%2,%3,%4,%5", |
|---|
| | 699 | Integer.format(foo[0 .. 3], h1), Integer.format( |
|---|
| | 700 | foo[3 .. 6], h2), Integer.format( |
|---|
| | 701 | foo[6 .. 9], h3), Integer.format( |
|---|
| | 702 | foo[9 .. 12], h4), Integer.format( |
|---|
| | 703 | foo[12 .. 15], p1), Integer.format( |
|---|
| | 704 | foo[15 .. 18], p2)); |
|---|
| | 705 | |
|---|
| | 706 | // This formatting is weird. |
|---|
| | 707 | // this.sendCommand("PORT", format("%d,%d,%d,%d,%d,%d", h1, h2, h3, h4, p1, p2)); |
|---|
| | 708 | |
|---|
| | 709 | this.sendCommand("PORT", str); |
|---|
| | 710 | this.readResponse("200"); |
|---|
| | 711 | } |
|---|
| | 712 | |
|---|
| | 713 | return listener; |
|---|
| | 714 | } |
|---|
| | 715 | assert(false); |
|---|
| | 716 | } |
|---|
| | 717 | |
|---|
| | 718 | /// Send a PASV and initiate a connection. |
|---|
| | 719 | /// |
|---|
| | 720 | /// Returns: a connected socket |
|---|
| | 721 | public SocketConduit connectPassive() { |
|---|
| | 722 | Address connect_to = null; |
|---|
| | 723 | |
|---|
| | 724 | // SPSV, which is just a port number. |
|---|
| | 725 | if(this.is_supported("SPSV")) { |
|---|
| | 726 | this.sendCommand("SPSV"); |
|---|
| | 727 | auto response = this.readResponse("227"); |
|---|
| | 728 | |
|---|
| | 729 | // Connecting to the same host. |
|---|
| | 730 | IPv4Address |
|---|
| | 731 | remote = cast(IPv4Address) this.socket.socket.remoteAddress(); |
|---|
| | 732 | assert(remote !is null); |
|---|
| | 733 | |
|---|
| | 734 | uint address = remote.addr(); |
|---|
| | 735 | uint port = toInt(response.message); |
|---|
| | 736 | |
|---|
| | 737 | connect_to = new IPv4Address(address, cast(ushort) port); |
|---|
| | 738 | } |
|---|
| | 739 | // Extended passive mode (IP v6, etc.) |
|---|
| | 740 | else if(this.is_supported("EPSV")) { |
|---|
| | 741 | this.sendCommand("EPSV"); |
|---|
| | 742 | auto response = this.readResponse("229"); |
|---|
| | 743 | |
|---|
| | 744 | // Try to pull out the (possibly not parenthesized) address. |
|---|
| | 745 | auto r = Regex(`\([^0-9][^0-9][^0-9](\d+)[^0-9]\)`); |
|---|
| | 746 | if(!r.test(response.message)) |
|---|
| | 747 | throw new FtpException("CLIENT: Unable to parse address", "501"); |
|---|
| | 748 | |
|---|
| | 749 | IPv4Address |
|---|
| | 750 | remote = cast(IPv4Address) this.socket.socket.remoteAddress(); |
|---|
| | 751 | assert(remote !is null); |
|---|
| | 752 | |
|---|
| | 753 | uint address = remote.addr(); |
|---|
| | 754 | uint port = toInt(r.match(1)); |
|---|
| | 755 | |
|---|
| | 756 | connect_to = new IPv4Address(address, cast(ushort) port); |
|---|
| | 757 | } else { |
|---|
| | 758 | this.sendCommand("PASV"); |
|---|
| | 759 | auto response = this.readResponse("227"); |
|---|
| | 760 | |
|---|
| | 761 | // Try to pull out the (possibly not parenthesized) address. |
|---|
| | 762 | auto r = Regex( |
|---|
| | 763 | `(\d+),\s*(\d+),\s*(\d+),\s*(\d+),\s*(\d+)(,\s*(\d+))?`); |
|---|
| | 764 | if(!r.test(response.message)) |
|---|
| | 765 | throw new FtpException("CLIENT: Unable to parse address", "501"); |
|---|
| | 766 | |
|---|
| | 767 | // Now put it into something std.socket will understand. |
|---|
| | 768 | char[] |
|---|
| | 769 | address = r.match(1) ~ "." ~ r.match(2) ~ "." ~ r.match(3) ~ "." ~ r.match( |
|---|
| | 770 | 4); |
|---|
| | 771 | uint |
|---|
| | 772 | port = (toInt(r.match(5)) << 8) + (r.match(7).length > 0 ? toInt( |
|---|
| | 773 | r.match(7)) : 0); |
|---|
| | 774 | |
|---|
| | 775 | // Okay, we've got it! |
|---|
| | 776 | connect_to = new IPv4Address(address, port); |
|---|
| | 777 | } |
|---|
| | 778 | |
|---|
| | 779 | scope(exit) |
|---|
| | 780 | delete connect_to; |
|---|
| | 781 | |
|---|
| | 782 | // This will throw an exception if it cannot connect. |
|---|
| | 783 | SocketConduit sock = new SocketConduit(); |
|---|
| | 784 | sock.connect(connect_to); |
|---|
| | 785 | return sock; |
|---|
| | 786 | } |
|---|
| | 787 | |
|---|
| | 788 | public bool isSupported(char[] command) |
|---|
| | 789 | in { |
|---|
| | 790 | assert(command.length > 0); |
|---|
| | 791 | } |
|---|
| | 792 | body { |
|---|
| | 793 | if(this.supportedFeatures_.length == 0) |
|---|
| | 794 | return true; |
|---|
| | 795 | |
|---|
| | 796 | // Search through the list for the feature. |
|---|
| | 797 | foreach(FtpFeature feat; this.supportedFeatures_) { |
|---|
| | 798 | if(Ascii.icompare(feat.command, command) == 0) |
|---|
| | 799 | return true; |
|---|
| | 800 | } |
|---|
| | 801 | |
|---|
| | 802 | return false; |
|---|
| | 803 | } |
|---|
| | 804 | |
|---|
| | 805 | public bool is_supported(char[] command) { |
|---|
| | 806 | if(this.supportedFeatures_.length == 0) |
|---|
| | 807 | return false; |
|---|
| | 808 | |
|---|
| | 809 | return this.isSupported(command); |
|---|
| | 810 | } |
|---|
| | 811 | } |
|---|