FoxPro Programming
LDAP Authentication in 2023
Gravatar is a globally recognized avatar based on your email address. LDAP Authentication in 2023
  J Ryan
  All
  Feb 26, 2023 @ 09:03pm

Hoping somebody can assist!

We have a customer wanting Active Directory authentication of usernames. LogonUser modeled on the West Wind Logonuser routine has worked well elsewhere, but not for this customer where it invariably fails with "wrong credentials" errors. I see nothing special except that the customer has a very heavily locked down environment and a forest of over 100 AD servers.

Anyway, the customer's network guru advocates use of LDAP authentication that he says is used by everybody else, with only 1% of apps having an issue. Hence interest in getting LDAP going!

I've seen various VFP solutions using GetObject("LDAP") but those seem to traverse LDAP looking for a user match. With tens of thousands of users and a LDAP limit to number of returns, that's unlikely to be the answer.

By reviewing ldp.exe output I've found the LDAP API... but I see the same issue against LDAP sites like ldap.forumsys.com : invalid credentials.

Has anybody else got LDAP bind going with VFP? Even simple bind will do, as the customer offers ssl port 689 connection.

Gravatar is a globally recognized avatar based on your email address. re: LDAP Authentication in 2023
  Jukka Salminen
  J Ryan
  Feb 27, 2023 @ 06:05am

This is the way I have done it against a server with thousands of users

Local lcNTUserName, lcUserDomain, lcUserName, loNetwork, ccADS_NAME_INITTYPE_GC, ccADS_NAME_TYPE_NT4
Local ccADS_NAME_TYPE_1779, lcDN, loNameTrans, lNoAd
* un = Username
ccADS_NAME_INITTYPE_GC = 3
ccADS_NAME_TYPE_NT4 = 3
ccADS_NAME_TYPE_1779 = 1
lNoAd = .F.

Odse = Getobject("LDAP://RootDse") 
loNetwork = Createobject("WScript.Network")
lcUserDomain = loNetwork.UserDomain
loNameTrans = Createobject("NameTranslate")
If Vartype(loNameTrans) <> "O"
	return .f.
Endif
Try
	loNameTrans.Init( ccADS_NAME_INITTYPE_GC, "")
	loNameTrans.Set(ccADS_NAME_TYPE_NT4, lcUserDomain + "\" + un)
	lcDN = loNameTrans.Get(ccADS_NAME_TYPE_1779)
	loAdUser = Getobject("LDAP://" + lcDN)
Catch To oException
Endtry
If Vartype(oException) = "O"
	Return .F.
Endif
oException = Null
Y=0
Do While .T.
	Try
		cMemberOf  = loAdUser.Getex("memberOf")
		If Not Empty(cMemberOf[1])
			Exit
		Endif
	Catch To oException
	Endtry
	If Vartype(oException) = "O"
		If oException.ErrorNo <> 0
			This.seterror("AD not available")
			Strtofile("Error occurred at: " + Transform(Datetime()) + CRLF +;
				[  Error: ] + Str(oException.ErrorNo) + CRLF +;
				[  LineNo: ] + Str(oException.Lineno)  + CRLF +;
				[  Message: ] + oException.Message  + CRLF +;
				[  Procedure: ] + oException.Procedure  + CRLF +;
				[  Details: ] + oException.Details  + CRLF +;
				[  RetryCount: ] + Str(Y)  + CRLF +;
				[  LineContents: ] + oException.LineContents + CRLF ;
				, This.cappstartpath + "Errors.log",.T.)
			lNoAd = .T.
		Endif
	Endif
	Y=Y+1
	If Y > 20
		Exit
	Endif
Enddo

If lNoAd
	Return .F.
Endif

Local isMember
isMember = .F.
For Each odata In cMemberOf
	If "Labraintranet"$odata
		isMember = .T.
		Exit
	Endif
Endfor
If isMember
* do something 
Endif 	
	
Gravatar is a globally recognized avatar based on your email address. re: LDAP Authentication in 2023
  J Ryan
  Jukka Salminen
  Feb 27, 2023 @ 11:08am

Thanks, Jukka, nice code.

Unfortunately this customer has the Windows Scripting Host locked down, and I need to authenticate username and password against their AD.

Here's what I've come up with so far:

Declare long ldap_init In Wldap32 String Hostname,long portnumber
Declare long ldap_bind_s In wldap32 Long iHandle,String cDN,string cCredential, long iMethod
DECLARE ldap_set_option IN wldap32 long iHandle,integer option,integer ivalue
Declare ldap_unbind In wldap32 Long iHandle

#Define LDAP_PORT               389
#Define LDAP_SSL_PORT           636
#Define LDAP_AUTH_SIMPLE        0x80

#Define LDAP_SUCCESS    	0x00

local lcUsername,lcpassword,lcAD,liPort,liSessionHandle,liTemp

lcUsername="cn=admin,dc=demo1,dc=freeipa,dc=org"
lcpassword="Secret123"
lcad="ipa.demo1.freeipa.org"  &&see https://www.freeipa.org/page/Demo
liport=389

liSessionHandle=LDAP_INIT(m.lcAD,m.liPort)  &&LDAP session handle

If m.liSessionHandle=0
	Error "Unable to connect to "+m.lcAD+": "+getwindowserror() &&getwindowserror function pulls latest Windows error
ELSE
	ldap_set_option(m.lisessionHandle,LDAP_OPT_VERSION,3)	&&version 3
	liAuth=LDAP_BIND_S(m.liSessionHandle,lcUsername,lcPassword,LDAP_AUTH_SIMPLE )
	ldap_unbind(m.liSessionHandle)

	If m.liAuth=LDAP_SUCCESS   &&user is authenticated
	Else
		Error "AD returned Error Code "+Transform(m.liAuth)
	Endif
Endif

With supplied username and password, I'd expect authentication at this public LDAP test site... but no luck. Invariably error 49, Invalid credentials. I've experimented with LDAP_OPEN rather than LDAP_INIT and combinations with and without CN=/ UID= / DC= etc, but still Error 49, or a collection of other errors if experiments too extreme. Any advice appreciated! This API LDAP is only a few lines of code and could be awesome, if it would only work!

Gravatar is a globally recognized avatar based on your email address. re: LDAP Authentication in 2023
  J Ryan
  J Ryan
  Feb 27, 2023 @ 08:25pm

Turns out it's the free LDAP site at fault. Installing OpenLDAP locally has it working fine.

Also works against ldap.forumsys.com (see https://www.forumsys.com/2022/05/10/online-ldap-test-server/ ) using UID= for the various users.

lcUsername="uid=einstein,dc=example,dc=com"
lcPassword="password"
lcAD="ldap.forumsys.com"
liPort=389

Against Active Directory, there are additional options, e.g. passing an identify packet rather than simple validation- but the above code is generic, also working against openLDAP in Linux.

Gravatar is a globally recognized avatar based on your email address. re: LDAP Authentication in 2023
  J Ryan
  J Ryan
  Feb 28, 2023 @ 11:02am

For completeness, herewith function to validate username/password credentials against AD or Linux LDAP like openLDAP:

function LDAP_Authenticate(lcDN,lcpassword,lcServer,liPort)

Declare long ldap_init In Wldap32 String Hostname,long portnumber
Declare long ldap_bind_s In wldap32 Long iHandle,String cDN,string cCredential, long iMethod
DECLARE ldap_set_option IN wldap32 long iHandle,integer option,integer ivalue
Declare ldap_unbind In wldap32 Long iHandle

#Define LDAP_PORT               389
#Define LDAP_SSL_PORT           636
#Define LDAP_AUTH_SIMPLE        0x80
#DEFINE LDAP_OPT_VERSION        0x11

#Define LDAP_SUCCESS    	0x00

local liSessionHandle,liTemp,liAuth

*--- sample credentials for free online LDAP
lcDN="uid=einstein,dc=example,dc=com"
lcPassword="password"
lcServer="ldap.forumsys.com"
liPort=389

liSessionHandle=LDAP_INIT(m.lcServer,m.liPort)  &&LDAP session handle

If m.liSessionHandle=0
	Error "Unable to connect to "+m.lcServer+": "+getwindowserror() &&getwindowserror function pulls latest Windows error
ELSE
	ldap_set_option(m.lisessionHandle,LDAP_OPT_VERSION,3)	&&version 3
	liAuth=-1
	liAuth=LDAP_BIND_S(m.liSessionHandle,m.lcDN,m.lcPassword,LDAP_AUTH_SIMPLE )
	ldap_unbind(m.liSessionHandle)

	If m.liAuth=LDAP_SUCCESS   &&user is authenticated
	Else
		Error "Server returned Error Code "+Transform(m.liAuth)
	Endif
Endif
© 1996-2024