PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i5h-3LL

HOME


5h-3LL 1.0
DIR: /usr/share/php/Auth/Container
/usr/share/php/Auth/Container/
Upload File:
Current File : /usr/share/php/Auth/Container/LDAP.php
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */

/**
 * Storage driver for use against an LDAP server
 *
 * PHP versions 4 and 5
 *
 * LICENSE: This source file is subject to version 3.01 of the PHP license
 * that is available through the world-wide-web at the following URI:
 * http://www.php.net/license/3_01.txt.  If you did not receive a copy of
 * the PHP License and are unable to obtain it through the web, please
 * send a note to license@php.net so we can mail you a copy immediately.
 *
 * @category   Authentication
 * @package    Auth
 * @author     Jan Wagner <wagner@netsols.de>
 * @author     Adam Ashley <aashley@php.net>
 * @author     Hugues Peeters <hugues.peeters@claroline.net>
 * @copyright  2001-2006 The PHP Group
 * @license    http://www.php.net/license/3_01.txt  PHP License 3.01
 * @version    CVS: $Id: LDAP.php 237449 2007-06-12 03:11:27Z aashley $
 * @link       http://pear.php.net/package/Auth
 */

/**
 * Include Auth_Container base class
 */
require_once "Auth/Container.php";
/**
 * Include PEAR package for error handling
 */
require_once "PEAR.php";

/**
 * Storage driver for fetching login data from LDAP
 *
 * This class is heavily based on the DB and File containers. By default it
 * connects to localhost:389 and searches for uid=$username with the scope
 * "sub". If no search base is specified, it will try to determine it via
 * the namingContexts attribute. It takes its parameters in a hash, connects
 * to the ldap server, binds anonymously, searches for the user, and tries
 * to bind as the user with the supplied password. When a group was set, it
 * will look for group membership of the authenticated user. If all goes
 * well the authentication was successful.
 *
 * Parameters:
 *
 * host:        localhost (default), ldap.netsols.de or 127.0.0.1
 * port:        389 (default) or 636 or whereever your server runs
 * url:         ldap://localhost:389/
 *              useful for ldaps://, works only with openldap2 ?
 *              it will be preferred over host and port
 * version:     LDAP version to use, ususally 2 (default) or 3,
 *              must be an integer!
 * referrals:   If set, determines whether the LDAP library automatically
 *              follows referrals returned by LDAP servers or not. Possible
 *              values are true (default) or false.
 * binddn:      If set, searching for user will be done after binding
 *              as this user, if not set the bind will be anonymous.
 *              This is reported to make the container work with MS
 *              Active Directory, but should work with any server that
 *              is configured this way.
 *              This has to be a complete dn for now (basedn and
 *              userdn will not be appended).
 * bindpw:      The password to use for binding with binddn
 * basedn:      the base dn of your server
 * userdn:      gets prepended to basedn when searching for user
 * userscope:   Scope for user searching: one, sub (default), or base
 * userattr:    the user attribute to search for (default: uid)
 * userfilter:  filter that will be added to the search filter
 *              this way: (&(userattr=username)(userfilter))
 *              default: (objectClass=posixAccount)
 * attributes:  array of additional attributes to fetch from entry.
 *              these will added to auth data and can be retrieved via
 *              Auth::getAuthData(). An empty array will fetch all attributes,
 *              array('') will fetch no attributes at all (default)
 *              If you add 'dn' as a value to this array, the users DN that was
 *              used for binding will be added to auth data as well.
 * attrformat:  The returned format of the additional data defined in the
 *              'attributes' option. Two formats are available.
 *              LDAP returns data formatted in a
 *              multidimensional array where each array starts with a
 *              'count' element providing the number of attributes in the
 *              entry, or the number of values for attributes. When set
 *              to this format, the only way to retrieve data from the
 *              Auth object is by calling getAuthData('attributes').
 *              AUTH returns data formatted in a
 *              structure more compliant with other Auth Containers,
 *              where each attribute element can be directly called by
 *              getAuthData() method from Auth.
 *              For compatibily with previous LDAP container versions,
 *              the default format is LDAP.
 * groupdn:     gets prepended to basedn when searching for group
 * groupattr:   the group attribute to search for (default: cn)
 * groupfilter: filter that will be added to the search filter when
 *              searching for a group:
 *              (&(groupattr=group)(memberattr=username)(groupfilter))
 *              default: (objectClass=groupOfUniqueNames)
 * memberattr : the attribute of the group object where the user dn
 *              may be found (default: uniqueMember)
 * memberisdn:  whether the memberattr is the dn of the user (default)
 *              or the value of userattr (usually uid)
 * group:       the name of group to search for
 * groupscope:  Scope for group searching: one, sub (default), or base
 * start_tls:   enable/disable the use of START_TLS encrypted connection
 *              (default: false)
 * debug:       Enable/Disable debugging output (default: false)
 * try_all:     Whether to try all user accounts returned from the search
 *              or just the first one. (default: false)
 *
 * To use this storage container, you have to use the following syntax:
 *
 * <?php
 * ...
 *
 * $a1 = new Auth("LDAP", array(
 *       'host' => 'localhost',
 *       'port' => '389',
 *       'version' => 3,
 *       'basedn' => 'o=netsols,c=de',
 *       'userattr' => 'uid'
 *       'binddn' => 'cn=admin,o=netsols,c=de',
 *       'bindpw' => 'password'));
 *
 * $a2 = new Auth('LDAP', array(
 *       'url' => 'ldaps://ldap.netsols.de',
 *       'basedn' => 'o=netsols,c=de',
 *       'userscope' => 'one',
 *       'userdn' => 'ou=People',
 *       'groupdn' => 'ou=Groups',
 *       'groupfilter' => '(objectClass=posixGroup)',
 *       'memberattr' => 'memberUid',
 *       'memberisdn' => false,
 *       'group' => 'admin'
 *       ));
 *
 * $a3 = new Auth('LDAP', array(
 *       'host' => 'ldap.netsols.de',
 *       'port' => 389,
 *       'version' => 3,
 *       'referrals' => false,
 *       'basedn' => 'dc=netsols,dc=de',
 *       'binddn' => 'cn=Jan Wagner,cn=Users,dc=netsols,dc=de',
 *       'bindpw' => 'password',
 *       'userattr' => 'samAccountName',
 *       'userfilter' => '(objectClass=user)',
 *       'attributes' => array(''),
 *       'group' => 'testing',
 *       'groupattr' => 'samAccountName',
 *       'groupfilter' => '(objectClass=group)',
 *       'memberattr' => 'member',
 *       'memberisdn' => true,
 *       'groupdn' => 'cn=Users',
 *       'groupscope' => 'one',
 *       'debug' => true);
 *
 * The parameter values have to correspond
 * to the ones for your LDAP server of course.
 *
 * When talking to a Microsoft ActiveDirectory server you have to
 * use 'samaccountname' as the 'userattr' and follow special rules
 * to translate the ActiveDirectory directory names into 'basedn'.
 * The 'basedn' for the default 'Users' folder on an ActiveDirectory
 * server for the ActiveDirectory Domain (which is not related to
 * its DNS name) "win2000.example.org" would be:
 * "CN=Users, DC=win2000, DC=example, DC=org'
 * where every component of the domain name becomes a DC attribute
 * of its own. If you want to use a custom users folder you have to
 * replace "CN=Users" with a sequence of "OU" attributes that specify
 * the path to your custom folder in reverse order.
 * So the ActiveDirectory folder
 *   "win2000.example.org\Custom\Accounts"
 * would become
 *   "OU=Accounts, OU=Custom, DC=win2000, DC=example, DC=org'
 *
 * It seems that binding anonymously to an Active Directory
 * is not allowed, so you have to set binddn and bindpw for
 * user searching.
 *
 * LDAP Referrals need to be set to false for AD to work sometimes.
 *
 * Example a3 shows a full blown and tested example for connection to
 * Windows 2000 Active Directory with group mebership checking
 *
 * Note also that if you want an encrypted connection to an MS LDAP
 * server, then, on your webserver, you must specify
 *        TLS_REQCERT never
 * in /etc/ldap/ldap.conf or in the webserver user's ~/.ldaprc (which
 * may or may not be read depending on your configuration).
 *
 *
 * @category   Authentication
 * @package    Auth
 * @author     Jan Wagner <wagner@netsols.de>
 * @author     Adam Ashley <aashley@php.net>
 * @author     Hugues Peeters <hugues.peeters@claroline.net>
 * @copyright  2001-2006 The PHP Group
 * @license    http://www.php.net/license/3_01.txt  PHP License 3.01
 * @version    Release: @package_version@  File: $Revision: 237449 $
 * @link       http://pear.php.net/package/Auth
 */
class Auth_Container_LDAP extends Auth_Container
{

    // {{{ properties

    /**
     * Options for the class
     * @var array
     */
    var $options = array();

    /**
     * Connection ID of LDAP Link
     * @var string
     */
    var $conn_id = false;

    // }}}

    // {{{ Auth_Container_LDAP() [constructor]

    /**
     * Constructor of the container class
     *
     * @param  $params, associative hash with host,port,basedn and userattr key
     * @return object Returns an error object if something went wrong
     */
    function Auth_Container_LDAP($params)
    {
        if (false === extension_loaded('ldap')) {
            return PEAR::raiseError('Auth_Container_LDAP: LDAP Extension not loaded',
                    41, PEAR_ERROR_DIE);
        }

        $this->_setDefaults();

        if (is_array($params)) {
            $this->_parseOptions($params);
        }
    }

    // }}}
    // {{{ _prepare()

    /**
     * Prepare LDAP connection
     *
     * This function checks if we have already opened a connection to
     * the LDAP server. If that's not the case, a new connection is opened.
     *
     * @access private
     * @return mixed True or a PEAR error object.
     */
    function _prepare()
    {
        if (!$this->_isValidLink()) {
            $res = $this->_connect();
            if (PEAR::isError($res)) {
                return $res;
            }
        }
        return true;
    }

    // }}}
    // {{{ _connect()

    /**
     * Connect to the LDAP server using the global options
     *
     * @access private
     * @return object  Returns a PEAR error object if an error occurs.
     */
    function _connect()
    {
        $this->log('Auth_Container_LDAP::_connect() called.', AUTH_LOG_DEBUG);
        // connect
        if (isset($this->options['url']) && $this->options['url'] != '') {
            $this->log('Connecting with URL', AUTH_LOG_DEBUG);
            $conn_params = array($this->options['url']);
        } else {
            $this->log('Connecting with host:port', AUTH_LOG_DEBUG);
            $conn_params = array($this->options['host'], $this->options['port']);
        }

        if (($this->conn_id = @call_user_func_array('ldap_connect', $conn_params)) === false) {
            $this->log('Connection to server failed.', AUTH_LOG_DEBUG);
            $this->log('LDAP ERROR: '.ldap_errno($this->conn_id).': '.ldap_error($this->conn_id), AUTH_LOG_DEBUG);
            return PEAR::raiseError('Auth_Container_LDAP: Could not connect to server.', 41);
        }
        $this->log('Successfully connected to server', AUTH_LOG_DEBUG);

        // switch LDAP version
        if (is_numeric($this->options['version']) && $this->options['version'] > 2) {
            $this->log("Switching to LDAP version {$this->options['version']}", AUTH_LOG_DEBUG);
            @ldap_set_option($this->conn_id, LDAP_OPT_PROTOCOL_VERSION, $this->options['version']);

            // start TLS if available
            if (isset($this->options['start_tls']) && $this->options['start_tls']) {
                $this->log("Starting TLS session", AUTH_LOG_DEBUG);
                if (@ldap_start_tls($this->conn_id) === false) {
                    $this->log('Could not start TLS session', AUTH_LOG_DEBUG);
                    $this->log('LDAP ERROR: '.ldap_errno($this->conn_id).': '.ldap_error($this->conn_id), AUTH_LOG_DEBUG);
                    return PEAR::raiseError('Auth_Container_LDAP: Could not start tls.', 41);
                }
            }
        }

        // switch LDAP referrals
        if (is_bool($this->options['referrals'])) {
            $this->log("Switching LDAP referrals to " . (($this->options['referrals']) ? 'true' : 'false'), AUTH_LOG_DEBUG);
            if (@ldap_set_option($this->conn_id, LDAP_OPT_REFERRALS, $this->options['referrals']) === false) {
                $this->log('Could not change LDAP referrals options', AUTH_LOG_DEBUG);
                $this->log('LDAP ERROR: '.ldap_errno($this->conn_id).': '.ldap_error($this->conn_id), AUTH_LOG_DEBUG);
            }
        }

        // bind with credentials or anonymously
        if (strlen($this->options['binddn']) && strlen($this->options['bindpw'])) {
            $this->log('Binding with credentials', AUTH_LOG_DEBUG);
            $bind_params = array($this->conn_id, $this->options['binddn'], $this->options['bindpw']);
        } else {
            $this->log('Binding anonymously', AUTH_LOG_DEBUG);
            $bind_params = array($this->conn_id);
        }

        // bind for searching
        if ((@call_user_func_array('ldap_bind', $bind_params)) === false) {
            $this->log('Bind failed', AUTH_LOG_DEBUG);
            $this->log('LDAP ERROR: '.ldap_errno($this->conn_id).': '.ldap_error($this->conn_id), AUTH_LOG_DEBUG);
            $this->_disconnect();
            return PEAR::raiseError("Auth_Container_LDAP: Could not bind to LDAP server.", 41);
        }
        $this->log('Binding was successful', AUTH_LOG_DEBUG);

        return true;
    }

    // }}}
    // {{{ _disconnect()

    /**
     * Disconnects (unbinds) from ldap server
     *
     * @access private
     */
    function _disconnect()
    {
        $this->log('Auth_Container_LDAP::_disconnect() called.', AUTH_LOG_DEBUG);
        if ($this->_isValidLink()) {
            $this->log('disconnecting from server');
            @ldap_unbind($this->conn_id);
        }
    }

    // }}}
    // {{{ _getBaseDN()

    /**
     * Tries to find Basedn via namingContext Attribute
     *
     * @access private
     */
    function _getBaseDN()
    {
        $this->log('Auth_Container_LDAP::_getBaseDN() called.', AUTH_LOG_DEBUG);
        $err = $this->_prepare();
        if ($err !== true) {
            return PEAR::raiseError($err->getMessage(), $err->getCode());
        }

        if ($this->options['basedn'] == "" && $this->_isValidLink()) {
            $this->log("basedn not set, searching via namingContexts.", AUTH_LOG_DEBUG);

            $result_id = @ldap_read($this->conn_id, "", "(objectclass=*)", array("namingContexts"));

            if (@ldap_count_entries($this->conn_id, $result_id) == 1) {

                $this->log("got result for namingContexts", AUTH_LOG_DEBUG);

                $entry_id = @ldap_first_entry($this->conn_id, $result_id);
                $attrs = @ldap_get_attributes($this->conn_id, $entry_id);
                $basedn = $attrs['namingContexts'][0];

                if ($basedn != "") {
                    $this->log("result for namingContexts was $basedn", AUTH_LOG_DEBUG);
                    $this->options['basedn'] = $basedn;
                }
            }
            @ldap_free_result($result_id);
        }

        // if base ist still not set, raise error
        if ($this->options['basedn'] == "") {
            return PEAR::raiseError("Auth_Container_LDAP: LDAP search base not specified!", 41);
        }
        return true;
    }

    // }}}
    // {{{ _isValidLink()

    /**
     * determines whether there is a valid ldap conenction or not
     *
     * @accessd private
     * @return boolean
     */
    function _isValidLink()
    {
        if (is_resource($this->conn_id)) {
            if (get_resource_type($this->conn_id) == 'ldap link') {
                return true;
            }
        }
        return false;
    }

    // }}}
    // {{{ _setDefaults()

    /**
     * Set some default options
     *
     * @access private
     */
    function _setDefaults()
    {
        $this->options['url']         = '';
        $this->options['host']        = 'localhost';
        $this->options['port']        = '389';
        $this->options['version']     = 2;
        $this->options['referrals']   = true;
        $this->options['binddn']      = '';
        $this->options['bindpw']      = '';
        $this->options['basedn']      = '';
        $this->options['userdn']      = '';
        $this->options['userscope']   = 'sub';
        $this->options['userattr']    = 'uid';
        $this->options['userfilter']  = '(objectClass=posixAccount)';
        $this->options['attributes']  = array(''); // no attributes
        $this->options['attrformat']  = 'AUTH'; // returns attribute like other Auth containers
        $this->options['group']       = '';
        $this->options['groupdn']     = '';
        $this->options['groupscope']  = 'sub';
        $this->options['groupattr']   = 'cn';
        $this->options['groupfilter'] = '(objectClass=groupOfUniqueNames)';
        $this->options['memberattr']  = 'uniqueMember';
        $this->options['memberisdn']  = true;
        $this->options['start_tls']   = false;
        $this->options['debug']       = false;
        $this->options['try_all']     = false; // Try all user ids returned not just the first one
    }

    // }}}
    // {{{ _parseOptions()

    /**
     * Parse options passed to the container class
     *
     * @access private
     * @param  array
     */
    function _parseOptions($array)
    {
        $array = $this->_setV12OptionsToV13($array);

        foreach ($array as $key => $value) {
            if (array_key_exists($key, $this->options)) {
                if ($key == 'attributes') {
                    if (is_array($value)) {
                        $this->options[$key] = $value;
                    } else {
                        $this->options[$key] = explode(',', $value);
                    }
                } else {
                    $this->options[$key] = $value;
                }
            }
        }
    }

    // }}}
    // {{{ _setV12OptionsToV13()

    /**
     * Adapt deprecated options from Auth 1.2 LDAP to Auth 1.3 LDAP
     *
     * @author Hugues Peeters <hugues.peeters@claroline.net>
     * @access private
     * @param array
     * @return array
     */
    function _setV12OptionsToV13($array)
    {
        if (isset($array['useroc']))
            $array['userfilter'] = "(objectClass=".$array['useroc'].")";
        if (isset($array['groupoc']))
            $array['groupfilter'] = "(objectClass=".$array['groupoc'].")";
        if (isset($array['scope']))
            $array['userscope'] = $array['scope'];

        return $array;
    }

    // }}}
    // {{{ _scope2function()

    /**
     * Get search function for scope
     *
     * @param  string scope
     * @return string ldap search function
     */
    function _scope2function($scope)
    {
        switch($scope) {
        case 'one':
            $function = 'ldap_list';
            break;
        case 'base':
            $function = 'ldap_read';
            break;
        default:
            $function = 'ldap_search';
            break;
        }
        return $function;
    }

    // }}}
    // {{{ fetchData()

    /**
     * Fetch data from LDAP server
     *
     * Searches the LDAP server for the given username/password
     * combination.  Escapes all LDAP meta characters in username
     * before performing the query.
     *
     * @param  string Username
     * @param  string Password
     * @return boolean
     */
    function fetchData($username, $password)
    {
        $this->log('Auth_Container_LDAP::fetchData() called.', AUTH_LOG_DEBUG);
        $err = $this->_prepare();
        if ($err !== true) {
            return PEAR::raiseError($err->getMessage(), $err->getCode());
        }

        $err = $this->_getBaseDN();
        if ($err !== true) {
            return PEAR::raiseError($err->getMessage(), $err->getCode());
        }

        // UTF8 Encode username for LDAPv3
        if (@ldap_get_option($this->conn_id, LDAP_OPT_PROTOCOL_VERSION, $ver) && $ver == 3) {
            $this->log('UTF8 encoding username for LDAPv3', AUTH_LOG_DEBUG);
            $username = utf8_encode($username);
        }

        // make search filter
        $filter = sprintf('(&(%s=%s)%s)',
                          $this->options['userattr'],
                          $this->_quoteFilterString($username),
                          $this->options['userfilter']);

        // make search base dn
        $search_basedn = $this->options['userdn'];
        if ($search_basedn != '' && substr($search_basedn, -1) != ',') {
            $search_basedn .= ',';
        }
        $search_basedn .= $this->options['basedn'];

        // attributes
        $searchAttributes = $this->options['attributes'];

        // make functions params array
        $func_params = array($this->conn_id, $search_basedn, $filter, $searchAttributes);

        // search function to use
        $func_name = $this->_scope2function($this->options['userscope']);

        $this->log("Searching with $func_name and filter $filter in $search_basedn", AUTH_LOG_DEBUG);

        // search
        if (($result_id = @call_user_func_array($func_name, $func_params)) === false) {
            $this->log('User not found', AUTH_LOG_DEBUG);
        } elseif (@ldap_count_entries($this->conn_id, $result_id) >= 1) { // did we get some possible results?

            $this->log('User(s) found', AUTH_LOG_DEBUG);

            $first = true;
            $entry_id = null;

            do {

                // then get the user dn
                if ($first) {
                    $entry_id = @ldap_first_entry($this->conn_id, $result_id);
                    $first = false;
                } else {
                    $entry_id = @ldap_next_entry($this->conn_id, $entry_id);
                    if ($entry_id === false)
                        break;
                }
                $user_dn  = @ldap_get_dn($this->conn_id, $entry_id);

                // as the dn is not fetched as an attribute, we save it anyway
                if (is_array($searchAttributes) && in_array('dn', $searchAttributes)) {
                    $this->log('Saving DN to AuthData', AUTH_LOG_DEBUG);
                    $this->_auth_obj->setAuthData('dn', $user_dn);
                }

                // fetch attributes
                if ($attributes = @ldap_get_attributes($this->conn_id, $entry_id)) {

                    if (is_array($attributes) && isset($attributes['count']) &&
                         $attributes['count'] > 0) {

                        // ldap_get_attributes() returns a specific multi dimensional array
                        // format containing all the attributes and where each array starts
                        // with a 'count' element providing the number of attributes in the
                        // entry, or the number of values for attribute. For compatibility
                        // reasons, it remains the default format returned by LDAP container
                        // setAuthData().
                        // The code below optionally returns attributes in another format,
                        // more compliant with other Auth containers, where each attribute
                        // element are directly set in the 'authData' list. This option is
                        // enabled by setting 'attrformat' to
                        // 'AUTH' in the 'options' array.
                        // eg. $this->options['attrformat'] = 'AUTH'

                        if ( strtoupper($this->options['attrformat']) == 'AUTH' ) {
                            $this->log('Saving attributes to Auth data in AUTH format', AUTH_LOG_DEBUG);
                            unset ($attributes['count']);
                            foreach ($attributes as $attributeName => $attributeValue ) {
                                if (is_int($attributeName)) continue;
                                if (is_array($attributeValue) && isset($attributeValue['count'])) {
                                    unset ($attributeValue['count']);
                                }
                                if (count($attributeValue)<=1) $attributeValue = $attributeValue[0];
                                $this->log('Storing additional field: '.$attributeName, AUTH_LOG_DEBUG);
                                $this->_auth_obj->setAuthData($attributeName, $attributeValue);
                            }
                        }
                        else
                        {
                            $this->log('Saving attributes to Auth data in LDAP format', AUTH_LOG_DEBUG);
                            $this->_auth_obj->setAuthData('attributes', $attributes);
                        }
                    }
                }
                @ldap_free_result($result_id);

                // need to catch an empty password as openldap seems to return TRUE
                // if anonymous binding is allowed
                if ($password != "") {
                    $this->log("Bind as $user_dn", AUTH_LOG_DEBUG);

                    // try binding as this user with the supplied password
                    if (@ldap_bind($this->conn_id, $user_dn, $password)) {
                        $this->log('Bind successful', AUTH_LOG_DEBUG);

                        // check group if appropiate
                        if (strlen($this->options['group'])) {
                            // decide whether memberattr value is a dn or the username
                            $this->log('Checking group membership', AUTH_LOG_DEBUG);
                            $return = $this->checkGroup(($this->options['memberisdn']) ? $user_dn : $username);
                            $this->_disconnect();
                            return $return;
                        } else {
                            $this->log('Authenticated', AUTH_LOG_DEBUG);
                            $this->_disconnect();
                            return true; // user authenticated
                        } // checkGroup
                    } // bind
                } // non-empty password
            } while ($this->options['try_all'] == true); // interate through entries
        } // get results
        // default
        $this->log('NOT authenticated!', AUTH_LOG_DEBUG);
        $this->_disconnect();
        return false;
    }

    // }}}
    // {{{ checkGroup()

    /**
     * Validate group membership
     *
     * Searches the LDAP server for group membership of the
     * supplied username.  Quotes all LDAP filter meta characters in
     * the user name before querying the LDAP server.
     *
     * @param  string Distinguished Name of the authenticated User
     * @return boolean
     */
    function checkGroup($user)
    {
        $this->log('Auth_Container_LDAP::checkGroup() called.', AUTH_LOG_DEBUG);
        $err = $this->_prepare();
        if ($err !== true) {
            return PEAR::raiseError($err->getMessage(), $err->getCode());
        }

        // make filter
        $filter = sprintf('(&(%s=%s)(%s=%s)%s)',
                          $this->options['groupattr'],
                          $this->options['group'],
                          $this->options['memberattr'],
                          $this->_quoteFilterString($user),
                          $this->options['groupfilter']);

        // make search base dn
        $search_basedn = $this->options['groupdn'];
        if ($search_basedn != '' && substr($search_basedn, -1) != ',') {
            $search_basedn .= ',';
        }
        $search_basedn .= $this->options['basedn'];

        $func_params = array($this->conn_id, $search_basedn, $filter,
                             array($this->options['memberattr']));
        $func_name = $this->_scope2function($this->options['groupscope']);

        $this->log("Searching with $func_name and filter $filter in $search_basedn", AUTH_LOG_DEBUG);

        // search
        if (($result_id = @call_user_func_array($func_name, $func_params)) != false) {
            if (@ldap_count_entries($this->conn_id, $result_id) == 1) {
                @ldap_free_result($result_id);
                $this->log('User is member of group', AUTH_LOG_DEBUG);
                return true;
            }
        }
        // default
        $this->log('User is NOT member of group', AUTH_LOG_DEBUG);
        return false;
    }

    // }}}
    // {{{ _quoteFilterString()

    /**
     * Escapes LDAP filter special characters as defined in RFC 2254.
     *
     * @access private
     * @param string Filter String
     */
    function _quoteFilterString($filter_str)
    {
        $metas        = array(  '\\',  '*',  '(',  ')',   "\x00");
        $quoted_metas = array('\\\\', '\*', '\(', '\)', "\\\x00");
        return str_replace($metas, $quoted_metas, $filter_str);
    }

    // }}}

}

?>