[SuProxy]Ngnix+Lua 實現SSH2,LDAP,ORACLE,SQLSERVER等TCP/IP協議分析,劫持,代理,會話及負載

目錄

前言

Nginx+Lua或Openresty已經是網關代理的流行架構,比如Openresty,KONG,API6等,現有大多數網關均在http協議上工作,雖然Nginx已經支持了TCP、IP stream,但由於對除http協議外,其他協議解析和分析模塊的缺乏,使得其應用條件非常有限,除基本的代理功能外,其他一些在http下可以實現的功能,均無法在其他協議,比如在SSH2、TDS(SQL server)、TNS(ORACLE)、LDAP等協議上實現、劫持、修改、響應、負載及會話管理等能力。而對上述能力的支持,均需要建立在對各種協議的詳盡解析之上。SuProxy正是在這個條件下出現,目標是建立一個基於nginx+lua的四層的協議分析與代理庫,把nginx的穩定性能引入到除http外更廣泛的領域,並作為進一步審計,安全攻防,身份管理,協議分析的基礎組件。同時Suproxy也設計為一個能靈活的可擴展架構,第三方可以自行擴展協議解析,進一步豐富開源協議解析領域的公共知識。

SuProxy當前還在試驗階段,使用示例和文檔將會陸續在博客園,CSDN博客,Git上發佈,歡迎大家測試,並提出意見與建議,

項目Git地址:
//github.com/yizhu2000/suproxy

文檔地址(英文):
//github.com/yizhu2000/suproxy/blob/main/readme.md

有問題可以在此留言,或直接在git上提交
//github.com/yizhu2000/suproxy/issues

也可以通過郵件[email protected]進行溝通

介紹

SuProxy是事件驅動的Lua TCP/IP代理庫,用於分析,攔截,負載平衡和會話管理。它提供以下API:

  • 身份驗證攔截:在身份驗證期間讀取或更改憑據,或引入自定義身份驗證器。
  • 命令輸入攔截:監視,過濾或更改命令輸入。
  • 命令輸出攔截:監視,過濾或更改命令相應。
  • 上下文收集:獲取網絡、用戶、客戶端和服務器信息,例如IP、端口、版本等。
  • 會話管理:將會話存儲在Redis中,提供會話列表,終止和搜索會話的API。
  • 協議解析器:解析和編碼協議數據包。
  • 負載平衡:具有容錯能力的負載均衡。

當前,支持的協議包括SSH2,ORACLE TNS,SQLSERVER TDS,LDAP。

SSH協議 SQL服務器 甲骨文 LDAP
獲取用戶名 Y [^ 1] Y [^ 2] Y Y [^ 6]
獲取密碼 Y [^ 1] Y [^ 2] N Y [^ 6]
變更使用者名稱 Y Y Y [^ 4] Y
更改密碼 Y Y N Y
第三方認證 Y Y Y [^ 5] Y
獲取命令 Y Y Y Y [^ 7]
得到回復 Y Y N Y [^ 7]
變更指令 Y Y [^ 3] Y [^ 3] N
獲取網絡上下文 (IP,端口等)。 Y Y Y Y
獲取客戶端上下文 (客戶端/服務器程序名稱 和版本等) Y Y Y N
  • [^ 1]:僅密碼認證
  • [^ 2]:獲取SQL Server的用戶名和密碼會禁用SSL加密
  • [^ 3]:更改SQL命令未經過全面測試,某些更改(例如,更改選擇命令到刪除命令)可能無法成功
  • [^ 4]:不支持為oracle10更改用戶名
  • [^ 5]:僅支持基於用戶名的身份驗證
  • [^ 6]:不支持SSL
  • [^ 7]:僅支持搜索請求和回復

SuProxy由純粹的Lua編寫,基於事件驅動模式,SuProxy庫的使用和擴展很簡單:啟動偵聽器通道並處理其事件。下面的示例顯示如何啟動SSH2偵聽器並處理SSH連接的身份驗證成功事件。

server {
    listen 22;
    content_by_lua_block {
        local ssh=require("suproxy.ssh2"):new()
        local channel=require("suproxy.channel"):new({{ip="192.168.1.135",port=22}},tds)
        channel:run()
		ssh.AuthSuccessEvent:addHandler(ssh,logAuth)
    }
}

SuProxy提供基本的負載平衡功能。以下示例顯示了如何將多個upstream傳遞給通道。

package.loaded.my_SSHB=package.loaded.my_SSHB or
require ("suproxy.balancer.balancer"):new{
    {ip="127.0.0.1",port=2222,id="local",gid="linuxServer"},
    {ip="192.168.46.128",port=22,id="remote",gid="linuxServer"},
    {ip="192.168.1.121",port=22,id="UBUNTU14",gid="testServer"}
}
local channel=require("suproxy.channel"):new(package.loaded.my_SSHB,ssh)

SuProxy可以在內存或Redis中收集和維護會話上下文,以下是SuProxy在ssh連接中收集的信息

{
	"sid": "xxxxxxxxxxxx",
	"uid": "xxxx",
	"stype": "ssh2",
	"uptime": 1600831353.066,
	"ctime": 1600831353.066,
	"ctx": {
		"srvIP": "127.0.0.1",
		"client": "SSH-2.0-PuTTY_Release_0.74",
		"clientIP": "127.0.0.1",
		"clientPort": "56127",
        "username": "xxxx",
		"srvPort": 2222,
		"server": "SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.1"
	}
}

安裝

下載並拷貝

從這裡//github.com/yizhu2000/suproxy/releases 下載tar或zip包,解壓並複製到Openresty的lualib目錄。

依賴的庫包括

lua-resty-openssl> 0.6 //github.com/fffonion/lua-resty-openssl

lua-resty-logger-socket(僅用於示例項目)//github.com/cloudflare/lua-resty-logger-socket

使用LuaRocks安裝

在安裝了luarocks環境的機器上可以使用下列命令安裝本庫及其依賴項

luarocks install suproxy

運行測試

luajit.exe ./suproxy/test.lua

使用簡介

初始化Suproxy有4個步驟

  1. 創建處理器(processor),傳入初始參數
  2. 創建通道(channel),傳入upstream服務器列表及上一步創建的processor
  3. 處理處理器(processor)或通道(channel)的事件
  4. 啟動通道(channel)

以下代碼創建一個TNS通道並處理它的事件

--Create a TNS processor and passing server version to it
local tns=require("suproxy.tns"):new({oracleVersion=11})
--Create a channel with upstreams and TNS processor
local channel=require("suproxy.channel"):new({{ip="192.168.1.96",port=1521}},tns)
--Processing events
tns.AuthSuccessEvent:addHandler(tns,logAuth)
tns.CommandEnteredEvent:addHandler(tns,forbidden)
tns.CommandFinishedEvent:addHandler(tns,logCmd)
tns.BeforeAuthEvent:addHandler(tns,simpleUserPassOracle)
channel.OnConnectEvent:addHandler(channel,logConnect)
--start the channel
channel:run()

執行channel:run()之後,通道將在套接字上偵聽。然後,事件將在不同的場合觸發。用戶程序應處理這些事件以完成其工作。通道和處理器都可以觸發事件

處理器(processor)創建

處理器使用協議特定的解析器解析網絡流。

--xxx can be ssh2,ldap,tns,tds for now
require("suproxy.xxx"):new(options)

使用上面的代碼可以創建處理器(將xxx更改為處理器名稱)。目前,SSH,TDS,TNS和LDAP處理器已準備就緒。處理器是否可以具有自定義的初始選項。例如,TNS處理器可以接受兩個參數oracleVersion及swapPass,oracleVersion指定服務器主版本,而swapPass則告訴處理器是否需要在登錄時更改用戶密碼。有關詳細信息,請參閱每個處理器的文檔。

通道(channel)創建

通道維護客戶端和服務器之間的連接,從套接字讀取數據並將手數據發送到不同的協議處理器以進行進一步處理,通道還負責將數據發送到上游服務器。

require("suproxy.channel"):new({{ip="192.168.1.97",port=1521}},tns)

上面的代碼創建了一個具有一個後端服務器(upstream)和TNS協議處理器的通道。如果將多個上游傳遞到通道,則[默認balancer]將從這些上游中隨機選擇。請注意,只有在調用channel.run()之後,通道才會啟動。

Channel.c2pSend將數據放入客戶端代理套接字

Channel.p2sSend將數據發送到代理服務器套接字

Channel.c2pRead從客戶端代理套接字讀取數據

Channel.p2cRead從代理服務器套接字讀取數據

如何使用它們,請參閱[閱讀和響應]

channel.new方法可以接受額外的選項來設置套接字超時

options.c2pConnTimeout -- client-proxy connect timeout default 10000
options.c2pSendTimeout -- client-proxy send timeout default 10000
options.c2pReadTimeout -- client-proxy read timeout default 3600000
options.p2sConnTimeout --proxy-server connect timeout default 10000
options.p2sSendTimeout --proxy-server send timeout default 10000
options.p2sReadTimeout --proxy-server read timeout 3600000

require("suproxy.channel"):new(upstream,processor,options)

負載均衡

SuProxy提供基本的負載均衡功能。多個上游可以發送到通道。默認負載平衡器將從給定的上游服務器中隨機選擇一個。如果一個上游發生故障,負載均衡將暫時將該上游暫停一會兒。創建負載均衡:調用suproxy.balancer.balancer.new 方法,傳入upstream list和suspendSpan(可選,默認為30秒,然後將負載均衡傳遞給通道的構造函數,如下所示:

--here use "package.loaded" to ensure balancer only init once across multiple request, cause balancer will maintain the state of those upstreams.
package.loaded.my_SSHB=package.loaded.my_SSHB or
require ("suproxy.balancer.balancer"):new({
    {ip="127.0.0.1",port=2222,id="local",gid="linuxServer"},
    {ip="192.168.46.128",port=22,id="remote",gid="linuxServer"},
    {ip="192.168.1.121",port=22,id="UBUNTU14",gid="testServer"}
},10)
local channel=require("suproxy.channel"):new(package.loaded.my_SSHB,ssh)

每個上游必須包括IP,端口。ID和GID是可選字段,ID代表此上游服務器的標識符,GID代表該服務器所屬的組。事件處理器可以獲取這兩個參數。

通過實現getBestblame方法,可以輕鬆編寫自己的平衡器。有關更多信息,請參考balancer.balancer.lua。

會話信息和會話管理

SuProxy維護會話上下文包括:服務器IP,服務器端口,客戶端IP,客戶端端口,連接時間,用戶名以及某些處理器特定的屬性,例如客戶端版本或連接字符串。以下是SSH2處理器的會話上下文:

 {
     "srvIP": "127.0.0.1",
     "client": "SSH-2.0-PuTTY_Release_0.74",
     "clientIP": "127.0.0.1",
     "clientPort": "56127",
     "username": "xxxx",
     "srvPort": 2222,
     "connTime":1600831353.066
     "server": "SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.1"
}

這些信息將被傳遞到處理器的事件處理程序。

默認情況下,會話上下文存儲在本地,因此不會在請求之間共享。提供了Redis會話管理器以支持Redis存儲。Redis會話管理器還提供了簡單的會話管理操作,例如獲取活動會話列表和終止會話。更改默認會話管理器的方法如下:

local sessionManager= require ("suproxy.session.sessionManager"):new{ip="127.0.0.1",port=6379,expire=-1,extend=false,timeout=2000}
local channel=require("suproxy.channel"):new(package.loaded.my_OracleB,tns,{sessionMan=sessionManager})

其中IP端口是Redis服務器的地址。Expire設置會話默認值3600的默認過期時間跨度(以秒為單位),-1表示永不過期。extend指示從客戶端發送新數據包後是否延長會話租約,timeout指示Redis超時(以毫秒為單位),默認為5000。

LUA代碼example.session.lua顯示了如何使用瀏覽器管理會話。將以下行添加到nginx config進行測試。

 server {
    listen       80;
    server_name  localhost;
    
    ...

    location /suproxy/manage{
        content_by_lua_file  lualib/suproxy/example/session.lua;
    }
}

//localhost/suproxy/manage/session/all 列出所有會話

//localhost/suproxy/manage/session/clear 殺死所有會話

//localhost/suproxy/manage/session/kill?sid=xxxx 通過sessionID終止會話

//localhost/suproxy/manage/session/kill?uid=xxxx 通過uid終止會話

//localhost/suproxy/manage/session/get?sid=xxxx 通過sessionID獲取會話

//localhost/suproxy/manage/session/get?uid=xxxx 通過uid殺死會話

Event Handling

通道和處理器都會觸發事件,為事件添加處理程序的方法如下

event:addHandler(context,handler)

context表示程序將在哪個對象上執行,處理程序可以訪問上下文對象中定義的參數(self對象),handler是處理事件的函數。

典型的處理程序如下所示

function handler(context,eventSource,[other event params])
    -- handler logic here
end

處理程序至少會接收到兩個參數:context和eventSource。context是由addHandler方法定義的執行上下文,eventSource是觸發此事件的對象,大多數情況下是處理器本身。

NoReturnEvent 和 ReturnedEvent

事件有兩種:NoReturnEventReturnedEventReturnedEvents的處理程序可以返回值,而NoReturn Event的處理程序則不能。NoReturnEvent可以有多個處理程序,但Returned Event可以只有一個。將處理程序添加到已經具有處理程序的Returned Event中將覆蓋舊的處理程序。在相同情況下向NoReturnEvent添加更多處理程序將形成一個處理程序鏈,該鏈中的每個處理程序將一個接一個地執行。

addHandler 和 setHandler

對於NoReturnEvent,event:addHandler將新處理程序追加到處理程序鏈,setHandler方法清除該鏈並將處理程序追加到頭部。調用setHandler方法可確保該事件有且僅有一個處理程序。

Channel Event

OnConnectEvent

此事件在剛建立連接時觸發。handler參數為連接相關信息:

{
    clientIP, --client ip address
    clientPort, --client port if tcp is used
    srvIP, --upstream server ip
    srvPort=serverPort --upstream server port if tcp is used
}

這是處理通道的OnConnectEvent的示例,該事件將連接信息寫入日誌文件:

local function logConnect(context,source,connInfo)
    local rs={
        os.date("%Y.%m.%d %H:%M:%S", ngx.time()).."\t" ,
        connInfo.clientIP..":"..connInfo.clientPort.."\t",
        "connect to ",
        connInfo.srvIP..":"..connInfo.srvPort.."\r\n"
    }
    print(table.concat(rs))
end

結果如下

2020.09.24 18:28:03	127.0.0.1:60486	connect to 127.0.0.1:2222

Processor Events

不同的處理器可能具備不同的事件,但是所有處理器都實現以下的事件

  • BeforeAuthEvent
  • AuthSuccessEvent
  • AuthFailEvent
  • CommandEnteredEvent(對於ssh2Processor,這由commandCollector而非processor觸發)
  • CommandFinishedEvent(對於ssh2Processor,這由commandCollector而非processor觸發)
  • ContextUpdateEvent

每個處理器事件將不同的參數和一個會話上下文(session context)對象傳遞給其處理程序。會話上下文對象包含該會話中處理器收集的所有信息。以下是SSH2.0處理器中的典型會話上下文:

{
    "srvIP": "127.0.0.1",
    "client": "SSH-2.0-PuTTY_Release_0.74",
    "clientIP": "127.0.0.1",
    "clientPort": "56127",
    "username": "root"
    "srvPort": 2222,
    "server": "SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.1"
}

會話上下文信息隨處理器類型和連接階段的不同而有所不同。某些處理器沒有「client」字段或「server」字段(如LDAP),有些處理器有附加信息,例如連接字符串(比如TNS)。另外,在連接的某個階段,用戶名尚未傳輸,因此在此時,username不會出現在上下文信息中。但是,上下文至少包含srvIP,srvPort,clientIP,clientPort,connTime,並且username將在用戶身份驗證後立即添加到上下文中。

BeforeAuthEvent

此事件在用戶身份驗證之前觸發,這是交換用戶憑據的絕佳時機。傳遞給其處理程序的參數是認證憑證會話上下文對象,憑證對象定義如下(對於TNS處理器,除非滿足某些版本條件,否則不提供密碼字段)。

{
    username,--string, username entered by user
    password --string, password entered by user
}

此事件的事件處理程序可以返回新的憑據,如果返回了新的憑據,則新的憑據將轉發到上游,而不是舊的憑據。

下面是將原始憑證傳遞到遠程服務器並通過OAUTH2.0從中獲取新憑證的示例(此實例中用到的OAUTH驗證模塊ssoProcessors尚在實驗階段,用戶完全可以用成熟的OAUTH2.0客戶端替代)

local function oauth(context,source,cred,session)
 --show how to get password with oauth protocal,using username as code, an app should be add and a password attributes should be add
    local param={
        ssoProtocal="OAUTH",
        validate_code_url="//xxxxxxxxxxxx/oauth2/token",
        profile_url="//xxxxxxxxxxxxx/oauth2/userinfo",
        client_secret="xxxxxxxxxxx",
    }
    local authenticator=authFactory.getAuthenticator(param)
    local result=authenticator:valiate({username=cred.username,password=cred.password})
    local newCred={}
    if result.status==ssoProcessors.CHECK_STATUS.SUCCESS then
        --confirm oauth password attributes correctly configged
        newCred.username=result.accountData.user
        newCred.password=result.accountData.attributes.password
    end
    if not newCred then return nil,"can not get cred from remote server" end
    return newCred
end

OnAuthEvent

該事件在認證通過時觸發。這是交換用戶憑證或引入自定義身份驗證的絕佳時機。傳遞給其處理程序的參數是憑據(用戶名和密碼)和會話上下文對象,憑據對象定義如下(對於TNS處理器,除特定版本外無密碼字段)。

BeforeAuthEvent和OnAuthEvent之間的區別在於:BeforeAuthEvent在憑據的任何部分(如用戶名)傳輸到服務器之前觸發,而OnAuthEvent在身份驗證真正發生並將密碼傳輸到服務器時觸發。對於某些協議(例如LDAP和TDS),這兩個時機是相同的;對於處理器(例如SSH2或TNS),用戶名是在密碼之前傳輸的。如果需要為這些通道更改用戶名,則必須在BeforeAuthEvent處理程序中準備好新的用戶名。

此事件的事件處理程序可以返回驗證結果,錯誤消息和新憑據。使用新憑據字段在LDAP和TDS處理器中可以替代BeforeAuthEvent

本示例說明如何引入第三方認證。

local function authenticator(context,source,credential,session)
    --OAUTH or other auth protocol should be used to swap real credential in real world
    local result=credential.username=="test" and credential.password=="test"
	if result then
		return result
    else
		local message="login with "..credential.username.." failed"
		return result,message       
    end
end
ssh.OnAuthEvent:addHandler(tns,authenticator)

AuthSuccessEvent

用戶身份驗證成功完成後觸發此事件,這是寫入日誌文件的正確時機。傳遞給其處理程序的參數是用戶名會話上下文對象

下面是將登錄操作寫入日誌文件的示例。

local function logAuth(context,source,username,session)
    local rs={
        os.date("%Y.%m.%d %H:%M:%S", ngx.time()).."\t" ,
        session.clientIP..":"..session.clientPort.."\t",
        username.."\t",
        "login with ",
        (session and session.client) and session.client or "unknown client",
        (session and session.clientVersion) and session.clientVersion or ""
    }
    print(table.concat(rs))
end

結果如下

2020.09.24 19:03:40	127.0.0.1:60844	root	login with SSH-2.0-PuTTY_Release_0.74

AuthFailEvent

用戶身份驗證失敗時觸發此事件,這是寫入日誌文件的正確時機。傳遞給其處理程序的參數是failInfo會話上下文對象

failInfo定義如下

{
	username, --string, failed username 
	message --string, fail message passed
}

下面是將登錄失敗操作寫入日誌文件的示例。

local function logAuthFail(context,source,failInfo,session)
    local rs={
        os.date("%Y.%m.%d %H:%M:%S", ngx.time()).."\t" ,
        session.clientIP..":"..session.clientPort.."\t",
        failInfo.username.."\t",
        "login fail, fail message:  ",
        failInfo.message
    }
    print(table.concat(rs))
end

結果如下

2020.09.24 19:27:48	127.0.0.1:61020	zhuyi	login fail, fail message: wrong password

CommandEnteredEvent

該事件將在命令發送到服務器之前觸發。如果是數據庫或LDAP協議,則命令是SQL命令或LDAP請求;如果是SSH連接,則命令是shell命令。這是執行命令檢查或更改某些命令的最佳時機。

傳遞給其處理程序的參數是命令字符串會話上下文對象。處理程序可能會返回新命令錯誤消息。如果返回新命令,則新命令將取代原始命令被發送到服務器。如果返回錯誤消息,則該命令將不會執行,處理器將通知客戶端錯誤消息。(通知客戶端的方式隨處理器而異,有些可能會提示消息,有些可能沒有)以下是檢查命令並禁止命令中使用某些關鍵字的示例。

local function forbidden(context,source,command,session)
    if command:match("forbidden")then
        print("forbidden command triggered")
        return nil,{message=command.." is a forbidden command"}
    end
    return command
end

以下是sql執行對sqlserver的影響

以下是putty的響應

CommandFinishedEvent

從服務器回復命令時觸發此事件,如果是數據庫/ LDAP協議,則命令可能是SQL/ LDAP請求,如果是SSH連接,則命令可能是shell命令。這是編寫命令及其在文件中回復的最佳時機。

傳遞給其處理程序的參數是命令字符串回復字符串上下文對象。某些處理器中可能沒有答覆字符串(在當前版本中未收集TNS(ORACLE)答覆),以下是記錄命令和答覆的示例

local function logCmd(context,source,command,reply,session)
    local rs={
        os.date("%Y.%m.%d %H:%M:%S", ngx.time()).."\t" ,
        session.clientIP..":"..session.clientPort.."\t",
        username.."\t",
        command.."\r\n"
    }
    if reply then
        rs[#rs+1]="------------------reply--------------------\r\n"
        rs[#rs+1]=reply
        rs[#rs+1]="\r\n----------------reply end------------------\r\n")
    end
    print(table.concat(rs))
end

日誌文件如下

2020.09.24 19:28:43	127.0.0.1:61020	root	ls
------------------reply--------------------

libc6_2.31-0ubuntu8+lp1871129~1_amd64.deb

----------------reply end------------------

ContextUpdateEvent

處理器在會話中收集新信息時觸發此事件。聽此事件,程序可以獲取上下文信息並構造自己的會話狀態。以下示例顯示如何使用resty.redis庫將上下文記錄到Redis中

function contextHandler(self,source,ctx) 
    local red = require ("resty.redis"):new()
    red:set_timeouts(10000, 10000, 10000) 
    local ok, err = red:connect("127.0.0.1", 6379)
    if not ok then
        ngx.log(ngx.ERR,"failed to connect: ", err)
        return
    end
    red:set(ctx.clientIP..ctx.clientPort,cjson.encode(ctx))
end

Parser 及 Parser Events

每個處理器都有2個解析器c2pParser和s2pParser。c2pParser負責解析客戶端與代理間的通訊,s2pParser負責解析服務端和代理間的通訊。當這些解析器成功解析一個數據包時,將觸發一個解析器事件。用戶程序可以偵聽這些事件以進行攔截,更改發送到服務器的數據包或停止轉發。

傳遞給數據包事件處理程序的參數是解析器構造的數據包,其中包含協議特定的信息。處理程序可以獲取或設置其字段以獲取或更改發送到服務器的內容。例如,可以像這樣更改TNS連接中的版本。

function _M:ConnectHandler(src,packet)
    packet:setTnsVersion(314)
    packet:pack()
end

攔截,更改和停止轉發

設置packet的allBytes屬性可以修改包的內容,這意味着計算位元組長度,重新生成數據包頭和將發送到服務器的位元組流。每次更改字段時,都必須調用packet.pack()。位元組流可以通過packet.allBytes訪問。

如果我們不希望我們的數據包再轉發。(例如,處理程序已作出手動響應),只需將packet.allBytes字段設置為「」即可。

p.allBytes=""
p:pack()

讀取數據與響應

處理程序可以使用Channel提供的4種方法來讀取並響應客戶端或服務器:

Channel.c2pSend將數據發送到c2p套接字

Channel.p2sSend把數據P2S插座

Channel.c2pRead從c2p套接字讀取數據

Channel.p2cRead讀取P2S套接字數據

這四個方法包裝了lua stream socket。參數和返回值與socket.receive和send相同,xxxSendMehod採用一個參數:要發送的位元組。接收方法的輸入參數是讀取的長度或模式。

以下是攔截LDAP SearchRequest和構造響應的示例。

local function ldap_SearchRequestHandler(context,src,p)
    if context.command:match("pleasechangeme") then
        local packets=require("suproxy.ldap.ldapPackets")
        local response=packets.SearchResultEntry:new()
        local done=packets.SearchResultDone:new()
        response.objectName="cn=admin,dc=www,dc=test,dc=com"
        response.messageId=p.messageId
        response.attributes={
            {attrType="objectClass",values={"posixGroup","top"}},
            {attrType="cn",values={"group"}},
            {attrType="memberUid",values={"haha","zhuyi","joeyzhu"}},
            {attrType="gidNumber",values={"44789"}},
            {attrType="description",values={"group"}}
        }
        done.resultCode=packets.ResultCode.success
        done.messageId=p.messageId
        response:pack() done:pack()
        context.channel:c2pSend(response.allBytes..done.allBytes)
        --stop forwarding
        p.allBytes=""
    end
end

local ldap=require("suproxy.ldap"):new()
ldap.c2pParser.events.SearchRequest:addHandler(ldap,ldap_SearchRequestHandler)

Examples

example.gateway演示為TNS,TDS,SSH2,LDAP協議實現了一個簡單的網關。要測試此演示,請修改nginx配置,將以下部分添加到您的配置文件中。確保commands.log文件路徑有效。

stream {
	init_by_lua_file lualib/suproxy/init.lua;
    lua_code_cache off;
    #mock logserver if you do not have one
    server {
		listen 12080;
		content_by_lua_block {
            ngx.log(ngx.DEBUG,"logserver Triggerred")
            local reqsock, err = ngx.req.socket(true)
            reqsock:settimeout(100)
            while(not err) do
                local command,err=reqsock:receive()
                if(err) then ngx.exit(0) end
                local f = assert(io.open("/data/logs/commands.log", "a"))
                if(command) then
                    f:write(command .. "\n")
                    f:close()
                end
            end
        }
	}
	#listen on ports
    server {
        listen 389;
		listen 1521;
		listen 22;
		listen 1433;
        content_by_lua_file lualib/suproxy/example/gateway.lua;
    }
}
#Session manager interfaces. if you want to view and manage your session 
#over http, this should be set.
http {
    include       mime.types;
	lua_code_cache off;
    server {
        listen       80;
        server_name  localhost;
		default_type text/html;
		location /suproxy/manage{
			content_by_lua_file  lualib/suproxy/example/session.lua;
		}
}

關於會話管理的接口,請參閱會話管理。