{ 文件名:RealICQProxy.pas 功 能:定义代理类,代理类用于存储代理连接的相关信息。 建 立:尹进 历 史: 2005.12.23:补文件说明信息(尹进) } unit RealICQProxy; interface uses RealICQSocket, WinSock2, OverbyteIcsNtlmMsgs, SysUtils, Classes, Windows, EncdDecd; const ProxyErrorMsgs: array[0..9] of String = ( '成功', '普通的SOCKS服务器请求失败', '现有的规则不允许的连接', '网络不可达', '主机不可达', '连接被拒', 'TTL超时', '不支持的命令', '不支持的地址类型', '未知错误'); type //代理类型 TProxyType = (ptNone,ptSocks5,ptHttp); //代理相关信息 TProxy = class(TPersistent) private FProxyType: TProxyType; //代理类型 FAddress: string; //代理服务器地址 FPort: Integer; //代理服务器端口号 FUsername: string; //代理服务器验证用户名 FPassword: string; //密码 FDomain: string; //域 FOnChange: TNotifyEvent; procedure SetProxyType(Value: TProxyType); procedure SetAddress(Value: string); procedure SetPort(Value: Integer); procedure SetUsername(Value: string); procedure SetPassword(Value: string); procedure SetDomain(Value: string); protected procedure DoChange; public procedure Assign(Source: TPersistent); override; published property ProxyType: TProxyType read FProxyType write SetProxyType default ptNone; property Address: string read FAddress write SetAddress; property Port: Integer read FPort write SetPort; property Username: string read FUsername write SetUsername; property Password: string read FPassword write SetPassword; property Domain: string read FDomain write SetDomain; property OnChange: TNotifyEvent read FOnChange write FOnChange; end; //代理协议类型 TProxyProtocolType = (ppTCP, ppUDP, ppBind); function ConnectToSocks5Proxy(ADestOrLocalAddress: String; ADestOrLocalPort: Word; AProxyAddress: String; AProxyPort: Word; AUserName: String; APassword: String; AProxyProtocolType: TProxyProtocolType; var BindProxyAddr :TSockAddrIn):TSocket; function ConnectToHTTPProxy(ADestAddress: String; ADestPort: Word; AProxyAddress: String; AProxyPort: Word; AUserName: String; APassword: String; ADomain: String):TSocket; implementation //------------------------------------------------------------------------------ function ConnectToSocks5Proxy(ADestOrLocalAddress: String; ADestOrLocalPort: Word; AProxyAddress: String; AProxyPort: Word; AUserName: String; APassword: String; AProxyProtocolType: TProxyProtocolType; var BindProxyAddr :TSockAddrIn):TSocket; var ProxySocket: TSocket; ProxyAddr: TSockAddrIn; ServerAddr: TSockAddrIn; LastError: Integer; ret, nIndex, iLoop, ReturnValue: Integer; Buf: array[0..255] of Byte; begin ProxySocket := Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if ProxySocket = INVALID_SOCKET then raise TSocketException.CreateFmt('创建套接字失败,错误代码:%d',[WSAGetLastError]); ProxyAddr.sin_family:= AF_INET; ProxyAddr.sin_port:= htons(AProxyPort); ProxyAddr.sin_addr.S_addr:= inet_addr(PChar(AProxyAddress)); ret := connect(ProxySocket,@ProxyAddr,SizeOf(ProxyAddr)); if ret = SOCKET_ERROR then begin LastError := WSAGetLastError(); if LastError <> 0 then begin closesocket(ProxySocket); raise TSocketException.CreateFmt('无法建立与代理服务器的连接,错误代码:%d', [LastError]); end; end; nIndex := 0; Buf[nIndex]:= $05; // Socks5协议 Inc(nIndex, 1); Buf[nIndex]:= $02; // 包中有两种验证方式 Inc(nIndex, 1); Buf[nIndex]:= $00; // 无需校验 Inc(nIndex, 1); Buf[nIndex]:= $02; // 需用户名密码校验 //Inc(nIndex, 1); Send(ProxySocket, Buf, 4, 0); FillChar(Buf, 256, #0); ReturnValue := Recv(ProxySocket, Buf, 2, 0); if ReturnValue <> 2 then begin closesocket(ProxySocket); raise TSocketException.Create('代理服务器上返回了错误的数据'); end; if Buf[1] = $FF then begin closesocket(ProxySocket); raise TSocketException.Create('客户端所列出的方法列表中没有一个方法被代理服务器选中,连接必须被关闭'); end; if Buf[1] = $02 then // 需用户名密码校验 begin FillChar(Buf, 256, #0); Buf[0]:= $01; //填充用户名 Buf[1]:= Length(AUsername); for iLoop := 0 to Buf[1] - 1 do Buf[2 + iLoop] := Ord(AUsername[iLoop + 1]); //填充密码 Buf[2 + Length(AUsername)] := Length(APassword); for iLoop := 0 to Buf[2 + Length(AUsername)] - 1 do Buf[3 + Length(AUsername) + iLoop] := Ord(APassword[iLoop + 1]); Send(ProxySocket, Buf, Length(AUsername) + Length(APassword) + 3, 0); ReturnValue := recv(ProxySocket, Buf, 2, 0); if ReturnValue <> 2 then begin closesocket(ProxySocket); raise TSocketException.Create('代理服务器上返回了错误的数据'); end; if Buf[1] <> $00 then begin closesocket(ProxySocket); raise TSocketException.Create('代理服务器登录名或密码错误'); end; end; //发送具体的命令 nIndex := 0; Buf[nIndex]:= $05; //协议版本Socks5 Inc(nIndex, 1); if AProxyProtocolType = ppTCP then Buf[nIndex]:= $01 // 命令类型 = 连接(connect) else if AProxyProtocolType = ppBind then Buf[nIndex]:= $02 // 命令类型 = BIND else if AProxyProtocolType = ppUDP then Buf[nIndex]:= $03; // 命令类型 = UDP(UDP ASSOCIATE) Inc(nIndex, 1); Buf[nIndex]:= $00; //保留 Inc(nIndex, 1); //填充地址和端口,如果是TCP代理,则为要连接到的远程服务器的地址和端口 //如果是UDP代理,则为UDP本地绑定的地址和端口 ServerAddr.sin_family:= AF_INET; ServerAddr.sin_port:= htons(ADestOrLocalPort); ServerAddr.sin_addr.S_addr:= inet_addr(PChar(ADestOrLocalAddress)); if ServerAddr.sin_addr.S_addr = INADDR_NONE then begin Buf[nIndex]:= $03; //地址类型(域名) Inc(nIndex, 1); Buf[nIndex]:= Length(ADestOrLocalAddress); //域名长度 Inc(nIndex, 1); CopyMemory(@Buf[nIndex], PChar(ADestOrLocalAddress), Length(ADestOrLocalAddress)); Inc(nIndex, Length(ADestOrLocalAddress)); end else begin Buf[nIndex]:= $01; //地址类型IPv4 Inc(nIndex, 1); CopyMemory(@Buf[nIndex], @ServerAddr.sin_addr, 4); Inc(nIndex, 4); end; CopyMemory(@Buf[nIndex], @ServerAddr.sin_port, 2); Inc(nIndex, 2); Send(ProxySocket, Buf, nIndex, 0); FillChar(Buf, 256, #0); Recv(ProxySocket, Buf, 256, 0); if (Buf[0] <> $05) and (Buf[1] <> $00) then begin closesocket(ProxySocket); raise TSocketException.Create(ProxyErrorMsgs[Buf[1]]); end; BindProxyAddr.sin_family:= AF_INET; CopyMemory(@BindProxyAddr.sin_addr, @Buf[4], 4); //获取Proxy的映射地址 CopyMemory(@BindProxyAddr.sin_port, @Buf[8], 2); //获取Proxy的映射端口号 Result := ProxySocket; end; //------------------------------------------------------------------------------ function ConnectToHTTPProxy(ADestAddress: String; ADestPort: Word; AProxyAddress: String; AProxyPort: Word; AUserName: String; APassword: String; ADomain: String):TSocket; var ProxySocket: TSocket; ProxyAddr: TSockAddrIn; LastError: Integer; StrHttpProxySend: String; ChallengString: String; //NTLM验证时用到 ReturnValue: Integer; Buf: array[0..8191] of Byte; LMMsg2:TNTLM_Msg2_Info; procedure AddHttpHeaders; begin StrHttpProxySend := StrHttpProxySend + 'Accept: */*' + #$D#$A + 'Content-Type: text/html' + #$D#$A + 'Connection: Keep-Alive' + #$D#$A + 'Proxy-Connection: Keep-Alive' + #$D#$A + 'Content-length: 0' + #$D#$A#$D#$A; end; procedure SendRequest(ANeedResetConnect: Boolean = True); begin AddHttpHeaders; if ANeedResetConnect then begin if ProxySocket <> INVALID_SOCKET then closesocket(ProxySocket); ProxySocket := Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if ProxySocket = INVALID_SOCKET then begin closesocket(ProxySocket); raise TSocketException.CreateFmt('创建套接字失败,错误代码:%d',[WSAGetLastError]); end; ProxyAddr.sin_family:= AF_INET; ProxyAddr.sin_port:= htons(AProxyPort); ProxyAddr.sin_addr.S_addr:= inet_addr(PChar(AProxyAddress)); connect(ProxySocket,@ProxyAddr,SizeOf(ProxyAddr)); LastError := WSAGetLastError(); if LastError <> 0 then begin closesocket(ProxySocket); raise TSocketException.CreateFmt('无法建立与代理服务器的连接,错误代码:%d', [LastError]); end; end; //MessageBox(0, Pchar(StrHttpProxySend), '发送消息', MB_OK); CopyMemory(@Buf[0], PChar(StrHttpProxySend), Length(StrHttpProxySend)); Send(ProxySocket, Buf, Length(StrHttpProxySend), 0); FillChar(Buf, 8192, #0); ReturnValue := Recv(ProxySocket, Buf, 8192, 0); if ReturnValue <= 0 then begin closesocket(ProxySocket); raise TSocketException.Create('未能通过HTTP代理连接到指定的主机'); end; SetLength(StrHttpProxySend, ReturnValue); CopyMemory(PChar(StrHttpProxySend), @Buf[0], ReturnValue); end; begin ProxySocket := INVALID_SOCKET; if Length(Trim(AUserName)) > 0 then StrHttpProxySend := Format('CONNECT %s:%d HTTP/1.1' + #$D#$A + 'Proxy-Authorization:Basic %s' + #$D#$A, [ADestAddress, ADestPort, EncodeString(AUserName + ':' + APassword)]) else StrHttpProxySend := Format('CONNECT %s:%d HTTP/1.1' + #$D#$A, [ADestAddress, ADestPort]); SendRequest; //MessageBox(0, Pchar(StrHttpProxySend), '收到反馈消息', MB_OK); if (Copy(StrHttpProxySend, 1, 12) <> 'HTTP/1.0 200') and (Copy(StrHttpProxySend, 1, 12) <> 'HTTP/1.1 200') then begin if (Copy(StrHttpProxySend, 1, 12) = 'HTTP/1.0 407') or (Copy(StrHttpProxySend, 1, 12) = 'HTTP/1.1 407') then begin //尝试以 NTLM 方式验证 if (Length(Trim(ADomain)) > 0) and (Pos('NTLM', StrHttpProxySend) >= 1) then begin StrHttpProxySend := Format('CONNECT %s:%d HTTP/1.1' + #$D#$A + 'Proxy-Authorization:NTLM %s' + #$D#$A, [ADestAddress, ADestPort, NtlmGetMessage1(ADestAddress, ADomain)]); SendRequest; //MessageBox(0, Pchar(StrHttpProxySend), '收到反馈消息', MB_OK); if (Copy(StrHttpProxySend, 1, 12) = 'HTTP/1.0 407') or (Copy(StrHttpProxySend, 1, 12) = 'HTTP/1.1 407') then begin if Pos('NTLM', StrHttpProxySend) >= 1 then begin ChallengString := Copy(StrHttpProxySend, Pos('NTLM ', StrHttpProxySend) + 5, Length(StrHttpProxySend)); ChallengString := Copy(ChallengString, 1, Pos(#$D#$A, ChallengString) - 1); LMMsg2 := NtlmGetMessage2(ChallengString); StrHttpProxySend := Format('CONNECT %s:%d HTTP/1.1' + #$D#$A + 'Proxy-Authorization:NTLM %s' + #$D#$A, [ADestAddress, ADestPort, NtlmGetMessage3(ADomain, ADestAddress, AUserName, APassword, LMMsg2.Challenge)]); SendRequest(False); //MessageBox(0, Pchar(StrHttpProxySend), '收到反馈消息', MB_OK); if (Copy(StrHttpProxySend, 1, 12) = 'HTTP/1.0 200') or (Copy(StrHttpProxySend, 1, 12) = 'HTTP/1.1 200') then begin Result := ProxySocket; Exit; end; end; end; end; closesocket(ProxySocket); raise TSocketException.Create('未能建立HTTP代理连接,用户名和密码错误') end else begin closesocket(ProxySocket); raise TSocketException.CreateFmt('未能建立HTTP代理连接,错误信息:%s', [StrHttpProxySend]); end; end; Result := ProxySocket; end; {TProxy} //------------------------------------------------------------------------------ procedure TProxy.DoChange; begin if Assigned(FOnChange) then FOnChange(Self); end; //------------------------------------------------------------------------------ procedure TProxy.Assign(Source: TPersistent); begin if Source is TProxy then begin FProxyType := TProxy(Source).ProxyType; FAddress := TProxy(Source).Address; FPort := TProxy(Source).Port; FUsername := TProxy(Source).Username; FPassword := TProxy(Source).Password; FDomain := TProxy(Source).Domain; //inherited Assign(Source); end; end; //------------------------------------------------------------------------------ procedure TProxy.SetProxyType(Value: TProxyType); begin if FProxyType = Value then Exit; FProxyType := Value; DoChange; end; //------------------------------------------------------------------------------ procedure TProxy.SetAddress(Value: string); begin if FAddress = Value then Exit; FAddress := Value; DoChange; end; //------------------------------------------------------------------------------ procedure TProxy.SetPort(Value: Integer); begin if FPort = Value then Exit; if (Value<0) or (Value>65535) then raise TSocketException.Create('端口号必须为0-65535之间的数值'); FPort := Value; DoChange; end; //------------------------------------------------------------------------------ procedure TProxy.SetUsername(Value: string); begin if FUsername = Value then Exit; FUsername := Value; DoChange; end; //------------------------------------------------------------------------------ procedure TProxy.SetPassword(Value: string); begin if FPassword = Value then Exit; FPassword := Value; DoChange; end; //------------------------------------------------------------------------------ procedure TProxy.SetDomain(Value: string); begin if FDomain = Value then Exit; FDomain := Value; DoChange; end; end.