Namdak Tonpa (maxim) wrote,
Namdak Tonpa
maxim

Как я написал LDAP Server на Эрланг за 30 минут

Вот может кому интересно почему я начал писать SyncML сервер на эрланг. Если кратко, мне понравилось. Если длиннее: я определяю насколько просто писать на компьютерном языке по тому насколько просто прострелить себе ногу написать LDAP сервер. Задача такая: написать приложение которое отвечает Windows Mail/Apple Mail, etc. и возвращает по LDAP протоколу спискок адресатов для автоматической подставновки (inplace search) в адресных полях письма.

Т.е. что бы из такого:



получить вот такое:



Прежде все я зашел на http://www.rfc-editor.org/rfc/rfc4511.txt и начал скачивать определение LDAP протокола в ASN.1 нотации. Простая копи паста примерно такого кода:

LDAP DEFINITIONS IMPLICIT TAGS ::=
BEGIN

	LDAPMessage ::= SEQUENCE {
             messageID       MessageID,
             protocolOp      CHOICE {
                  bindRequest           BindRequest,
                  bindResponse          BindResponse,
                  unbindRequest         UnbindRequest,
                  searchRequest         SearchRequest,
                  searchResEntry        SearchResultEntry,
                  searchResDone         SearchResultDone,
                  searchResRef          SearchResultReference,
                  modifyRequest         ModifyRequest,
                  modifyResponse        ModifyResponse,
                  addRequest            AddRequest,
                  addResponse           AddResponse,
                  delRequest            DelRequest,
                  delResponse           DelResponse,
                  modDNRequest          ModifyDNRequest,
                  modDNResponse         ModifyDNResponse,
                  compareRequest        CompareRequest,
                  compareResponse       CompareResponse,
                  abandonRequest        AbandonRequest,
                  extendedReq           ExtendedRequest,
                  extendedResp          ExtendedResponse,
				  ...,
                  intermediateResponse  IntermediateResponse },
             controls       [0] Controls OPTIONAL }

	MessageID ::= INTEGER (0 ..  maxInt)

    maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) --

	LDAPString ::= OCTET STRING -- UTF-8 encoded, [ISO10646] characters


Дальше я с помощью Эрланг сгенерировал парсер этой хуеты.

1> asn1ct:compile("LDAP.asn1").

=PROGRESS REPORT==== 18-Oct-2010::23:42:39 ===
          supervisor: {local,kernel_safe_sup}
             started: [{pid,<0.43.0>},
                       {name,disk_log_sup},
                       {mfargs,{disk_log_sup,start_link,[]}},
                       {restart_type,permanent},
                       {shutdown,1000},
                       {child_type,supervisor}]

=PROGRESS REPORT==== 18-Oct-2010::23:42:39 ===
          supervisor: {local,kernel_safe_sup}
             started: [{pid,<0.44.0>},
                       {name,disk_log_server},
                       {mfargs,{disk_log_server,start_link,[]}},
                       {restart_type,permanent},
                       {shutdown,2000},
                       {child_type,worker}]
ok
2>


Потом я написал код который отвечает на LDAP запросы:

-module(eds).
-author('Maxim Sokhatsky ').
-export([start/0]).                                                              
-compile(export_all).
-include("LDAP.hrl").
-include("roster.hrl").
-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).

start() ->
	listen(389).

listen(Port) ->
	{ok, LSocket} = gen_tcp:listen(Port, ?TCP_OPTIONS),
	accept(LSocket).
	                                     
accept(LSocket) ->
	{ok, Socket} = gen_tcp:accept(LSocket),
	spawn(fun() -> loop(Socket) end),
	accept(LSocket).

loop(Socket) ->
	case gen_tcp:recv(Socket, 0) of
		{ok, Data} ->
			Decoded = asn1rt:decode('LDAP','LDAPMessage',Data),
			case Decoded of
				{ok,{'LDAPMessage',No,Message,Asn}} -> message(No,Message,Socket);
				_Else -> noLDAP
			end,
			loop(Socket);
		{error, closed} ->
			ok
	end.

message(No,Message,Socket) ->
	io:format("messageID: ~p~n",[No]),
	io:format("~p~n",[Message]),
	case Message of
		{bindRequest, {'BindRequest',Type,Uid,Auth}} -> bind(No,Uid,Auth,Socket);
		{abandonRequest,Type} -> abandon(No,Socket);
		{unbindRequest, Null} -> abandon(0,Socket);
		{searchRequest,	{'SearchRequest',SearchDN,Scope,Deref,SizeLimit,
			TimeLimit,TypesOnly,Filter,Attributes}} -> search(No, SearchDN, Scope,Deref,SizeLimit,
			TimeLimit,TypesOnly, Filter,Attributes,Socket)
	end.

bind(No,Uid,Auth,Socket) ->
	roster:init(Uid),
	Response = #'BindResponse'{resultCode = success, matchedDN = Uid, diagnosticMessage = "OK"},
	answer(Response,No,bindResponse,Socket).

answer(Response,No,ProtocolOp,Socket) ->
	Message = #'LDAPMessage'{messageID = No, protocolOp = {ProtocolOp, Response}},
	{ok, Bytes} = asn1rt:encode('LDAP', 'LDAPMessage', Message),
	io:format("~p~n", [Message]),
	gen_tcp:send(Socket, list_to_binary(Bytes)).

search(No,SearchDN,Scope,Deref,SizeLimit,TimeLimit,TypesOnly,Filter,Attributes,Socket) ->
	roster:traverse(fun({'ContactRecord',CommonName,GivenName,Mail}) -> 
		CN = {'PartialAttribute', "cn", [CommonName]},
		MAIL = {'PartialAttribute', "mail", [Mail]},
		Response = {'SearchResultEntry', CommonName, [CN,MAIL]},
		answer(Response,No,searchResEntry,Socket),
		continue
	end),
	Done = #'LDAPResult'{resultCode = success, matchedDN = SearchDN, diagnosticMessage = "OK"},
	answer(Done,No,searchResDone,Socket).

abandon(No,Socket) ->
	roster:done(),
	gen_tcp:close(Socket).


А также библиотеку для работы с контактной книгой

-module(roster).
-include("roster.hrl").
-include_lib("stdlib/include/qlc.hrl").
-compile(export_all).
-export([init/1,done/0,put/3,get/1,traverse/1]).

init(FileName) ->
	case ets:info(ram) of
		undefined -> ets:new(ram, [named_table,{keypos,#'ContactRecord'.cn},{write_concurrency,true},{read_concurrency,true}]);
		Else -> ok
	end,
	dets:open_file(disk, [{file, FileName},{keypos,#'ContactRecord'.cn}]),
	ets:from_dets(ram,disk).

traverse(Fun) -> dets:traverse(disk,Fun).

list() -> ets:foldl(fun(C,Acc) -> io:format("~p~n",[C]) end,none,ram).

done() ->
	case ets:info(ram) of
		undefined -> ram_empty;
		ElseClearRam -> ets:delete(ram)
	end,
	case dets:info(disk) of
		{error,Reason} -> disk_closed;
		ElseCloseDisk -> Res = dets:close(disk), done
	end.

put(CN,GivenName,EMail) ->
	Contact = #'ContactRecord'{cn = CN, givenName = GivenName, mail = EMail},
	ets:insert(ram, Contact),
	dets:insert(disk, Contact),
	ok.

get(CN) ->
	case ets:lookup(ram,CN) of
		[Contact] -> {ok, Contact};
		[] -> {error,instance}
	end.


...В виде эрланг рекордов:

-record('ContactRecord', {cn, givenName, mail}).


Откомпилировал это:

erlc +debug_info -o ebin src\LDAP.erl src\eds.erl src\roster.erl


И все :)

P.S. https://github.com/synrc/eds
Tags: cs
  • Post a new comment

    Error

    default userpic

    Your IP address will be recorded 

  • 9 comments