I have deployed AADDS on my AzureAD domain.
I have changed passwords of users to generate the initial sync hash.
I have created a FreeRADIUS VM under Ubuntu 18.04 LTS capable of connecting through LDAP inside the ADDDS subnet with an User with "AAD DC Administrators" group.
I have setup an Ubiquiti Uni-Fi UAP nanoHD WPA2 Enterprise wireless network with a RADIUS profile to authenticate with the FreeRADIUS VM.
Testing Wi-Fi login with iPhone XR and a Windows 10 laptop.
The initial LDAP authentication to bind is successful.
User is matched succesfully on the directory.
User attributes are processed with warnings.
(2) ldap: Processing user attributes
(2) ldap: WARNING: No "known good" password added. Ensure the admin user has permission to read the password attribute
(2) ldap: WARNING: PAP authentication will *NOT* work with Active Directory (if that is what you were trying to configure)
Authentication fails since there is not a mapped "User-Password" attribute available.
(2) [ldap] = ok
(2) if ((ok || updated) && User-Password) {
(2) if ((ok || updated) && User-Password) -> FALSE
(2) [expiration] = noop
(2) [logintime] = noop
(2) } # authorize = ok
(2) ERROR: No Auth-Type found: rejecting the user via Post-Auth-Type = Reject
(2) Failed to authenticate the user
I have researched and tried the following.
"ntlm_auth" is not possible at the moment because of Samba limitations (only for Azure Files).
Changing the value of "dsHeuristics" in the Active Directory settings to enable "userPassword" attribute is not possible because of permissions limitations of AADDS.
***Call Modify...
ldap_modify_s(ld, 'CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,DC=example,DC=com',[1] attrs);
Error: Modify: Insufficient Rights. <50>
Server error: 00002098: SecErr: DSID-03150E49, problem 4003 (INSUFF_ACCESS_RIGHTS), data 0
Error 0x2098 Insufficient access rights to perform the operation.
My settings are exactly as shown at https://stackoverflow.com/a/55931232/5163441
This person claims it works as it for him so it is finding an attribute to compare for the password.
I don't see any relevant attribute I could use for authentication.
Dn: CN=John Smith,OU=AADDC Users,DC=example,DC=com
accountExpires: 9223372036854775807 (never);
badPasswordTime: 0 (never);
badPwdCount: 0;
cn: John Smith;
codePage: 0;
countryCode: 0;
displayName: John Smith;
distinguishedName: CN=John Smith,OU=AADDC Users,DC=example,DC=com;
dSCorePropagationData (2): 8/13/2019 7:53:04 PM Coordinated Universal Time; 0x0 = ( );
instanceType: 0x4 = ( WRITE );
lastLogoff: 0 (never);
lastLogon: 8/14/2019 6:17:50 PM Coordinated Universal Time;
lastLogonTimestamp: 8/14/2019 4:05:51 PM Coordinated Universal Time;
logonCount: 4;
mail: jsmith#example.com;
memberOf (13): OU=AADDC Users,DC=example,DC=com; CN=AAD DC Administrators,OU=AADDC Users,DC=chr,DC=cl;
msDS-AzureADMailNickname: jsmith;
msDS-AzureADObjectId: <ldp: Binary blob 16 bytes>;
name: John Smith;
objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=example,DC=com;
objectClass (4): top; person; organizationalPerson; user;
objectGUID: a8123123-3f4f-4123-9123-b530ff123123;
objectSid: S-1-5-21-545123123123-358123123-844123123-1123;
preferredLanguage: en-US;
primaryGroupID: 513 = ( GROUP_RID_USERS );
pwdLastSet: 8/14/2019 2:19:10 PM Coordinated Universal Time;
sAMAccountName: jsmith;
sAMAccountType: 805306368 = ( NORMAL_USER_ACCOUNT );
userAccountControl: 0x200 = ( NORMAL_ACCOUNT );
userPrincipalName: jsmith#example.com;
uSNChanged: 30696;
uSNCreated: 20588;
whenChanged: 8/14/2019 4:06:06 PM Coordinated Universal Time;
whenCreated: 8/13/2019 7:21:29 PM Coordinated Universal Time;
I have yet to test Kerberos and maybe OAuth2.
EDIT:
I didn't check the packets that were being sent by the client and there was something I didn't study before.
By sending a plain text login with a tool such as NTRadPing it will authenticate correctly since it will contain the "User-Password" attribute.
In the other hand trying to login through Wi-Fi will be usually an "EAP" hashed password.
(10) Received Access-Request Id 21 from 10.0.0.50:56480 to 10.0.0.10:1812 length 217
(10) User-Name = "jsmith#example.com"
(10) NAS-Identifier = "18e829123123"
(10) Called-Station-Id = "18-E5-39-B1-E3-D1:Test"
(10) NAS-Port-Type = Wireless-802.11
(10) Service-Type = Framed-User
(10) Calling-Station-Id = "C0-91-C0-58-BA-AC"
(10) Connect-Info = "CONNECT 0Mbps 802.11a"
(10) Acct-Session-Id = "7394227D45123123"
(10) WLAN-Pairwise-Cipher = 1123123
(10) WLAN-Group-Cipher = 1123123
(10) WLAN-AKM-Suite = 1123123
(10) Framed-MTU = 1400
(10) EAP-Message = 0x02fe001231236d617274696e657a40636872123123
(10) Message-Authenticator = 0x5fd0a8123123984b6b996f2941123123
I will continue researching.
EDIT 2:
I cannot find a viable way to do this as of now but I have found another way to make RADIUS work through NPS with AADDS.
Create a Windows Server VM in the AADDS subnet and install the NPS role.
Configure NPS but don't register it into the domain since it won't work because AADDS doesn't gives you the required permissions to do so.
Configure your RADIUS client to aim to this NPS server and it will still work, the NPS server doesn't has to be registered into the domain for RADIUS to work.
I wish to mimic the ldapsearch -z flag behavior of retrieving only a specific amount of entries from LDAP using python-ldap.
However, it keeps failing with the exception SIZELIMIT_EXCEEDED.
There are multiple links where the problem is reported, but the suggested solution doesn't seem to work
Python-ldap search: Size Limit Exceeded
LDAP: ldap.SIZELIMIT_EXCEEDED
I am using search_ext_s() with sizelimit parameter set to 1, which I am sure is not more than the server limit
On Wireshark, I see that 1 entry is returned and the server raises SIZELIMIT_EXCEEDED. This is the same as ldapsearch -z behavior
But the following line raises an exception and I don't know how to retrieve the returned entry
conn.search_ext_s(<base>,ldap.SCOPE_SUBTREE,'(cn=demo_user*)',['dn'],sizelimit=1)
Based upon the discussion in the comments, this is how I achieved it:
import ldap
# These are not mandatory, I just have a habit
# of setting against Microsoft Active Directory
ldap.set_option(ldap.OPT_REFERRALS, 0)
ldap.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
conn = ldap.initialize('ldap://<SERVER-IP>')
conn.simple_bind(<username>, <password>)
# Using async search version
ldap_result_id = conn.search_ext(<base-dn>, ldap.SCOPE_SUBTREE,
<filter>, [desired-attrs],
sizelimit=<your-desired-sizelimit>)
result_set = []
try:
while 1:
result_type, result_data = conn.result(ldap_result_id, 0)
if (result_data == []):
break
else:
# Handle the singular entry anyway you wish.
# I am appending here
if result_type == ldap.RES_SEARCH_ENTRY:
result_set.append(result_data)
except ldap.SIZELIMIT_EXCEEDED:
print 'Hitting sizelimit'
print result_set
Sample Output:
# My server has about 500 entries for 'demo_user' - 1,2,3 etc.
# My filter is '(cn=demo_user*)', attrs = ['cn'] with sizelimit of 5
$ python ldap_sizelimit.py
Hitting sizelimit
[[('CN=demo_user0,OU=DemoUsers,DC=ad,DC=local', {'cn': ['demo_user0']})],
[('CN=demo_user1,OU=DemoUsers,DC=ad,DC=local', {'cn': ['demo_user1']})],
[('CN=demo_user10,OU=DemoUsers,DC=ad,DC=local', {'cn': ['demo_user10']})],
[('CN=demo_user100,OU=DemoUsers,DC=ad,DC=local', {'cn': ['demo_user100']})],
[('CN=demo_user101,OU=DemoUsers,DC=ad,DC=local', {'cn': ['demo_user101']})]]
You may use play around with more srv controls to sort these etc. but I think the basic idea is conveyed ;)
You have to use the async search method LDAPObject.search_ext() and separate collect the results with LDAPObject.result() until the exception ldap.SIZELIMIT_EXCEEDED is raised.
The accepted answer works if you are searching for less users than specified by the server's sizelimit, but will fail if you wish to gather more than that (the default for AD is 1000 users).
Here's a Python3 implementation that I came up with after heavily editing what I found here and in the official documentation. At the time of writing this it works with the pip3 package python-ldap version 3.2.0.
def get_list_of_ldap_users():
hostname = "google.com"
username = "username_here"
password = "password_here"
base = "dc=google,dc=com"
print(f"Connecting to the LDAP server at '{hostname}'...")
connect = ldap.initialize(f"ldap://{hostname}")
connect.set_option(ldap.OPT_REFERRALS, 0)
connect.simple_bind_s(username, password)
connect=ldap_server
search_flt = "(cn=demo_user*)" # get all users with a specific cn
page_size = 1 # how many users to search for in each page, this depends on the server maximum setting (default is 1000)
searchreq_attrlist=["cn", "sn", "name", "userPrincipalName"] # change these to the attributes you care about
req_ctrl = SimplePagedResultsControl(criticality=True, size=page_size, cookie='')
msgid = connect.search_ext_s(base=base, scope=ldap.SCOPE_SUBTREE, filterstr=search_flt, attrlist=searchreq_attrlist, serverctrls=[req_ctrl])
total_results = []
pages = 0
while True: # loop over all of the pages using the same cookie, otherwise the search will fail
pages += 1
rtype, rdata, rmsgid, serverctrls = connect.result3(msgid)
for user in rdata:
total_results.append(user)
pctrls = [c for c in serverctrls if c.controlType == SimplePagedResultsControl.controlType]
if pctrls:
if pctrls[0].cookie: # Copy cookie from response control to request control
req_ctrl.cookie = pctrls[0].cookie
msgid = connect.search_ext_s(base=base, scope=ldap.SCOPE_SUBTREE, filterstr=search_flt, attrlist=searchreq_attrlist, serverctrls=[req_ctrl])
else:
break
else:
break
return total_results
API: https://github.com/satom99/litcord
How can I check my valiable if its a valid ID?
local cmd, serverID, channelID, arg = string.match(message.content, '(%S+) (%d+) (%d+) (%S+.*)')
local server = client.servers:get('id', serverID)
serverID is the variable and I need to check if the serverID is a valid ID
Otherwise I will get an error that server is a nil value.
I am trying days to finish one command and this is a part of it.
If you need more contents then please tell me, I will link it to you.
Full Code:
client:on(
'message',
function(message)
local userID = message.author.id
local cmd, serverID, channelID, arg = string.match(message.content, '(%S+) (%d+) (%d+) (%S+.*)')
local server = client.servers:get('id', serverID)
local channel = server.channels:get('id', channelID)
local cmd = cmd or message.content
if (cmd == "!say") and message.parent.is_private then
if (userID == "187590758360940545") then
if not server then
return
end
if (server == servers) then
if (channel == channels) then
message.channel:sendMessage(arg)
else
message:reply("I don't know this channel.")
return
end
message:reply("I don't know this server.")
end
else
message:reply(":sob: Stop!!!!")
end
end
end
)
And how I can let it write in the channel I want with functions
message.channel:sendMessage(arg)
this is like message:reply
it replies back where the message came from.
Let's forget about validating the serverID for one moment.
Of course you should always handle the case client.servers:get('id', serverID) returing nil.
Simply validating serverID somehow and hoping that you will get a valid server handle back is not an option.
So either use Luas error handling features https://www.lua.org/manual/5.3/manual.html#2.3
or simply check server with an if statement so you won't use server if it is nil.
Simplified:
local server = client.servers:get('id', serverID)
if not server then
print("No server with id '" .. serverID .. "' found.")
return -- or do something clever here, show a message box or whatever...
end
-- server won't be nil from here
Unless you know for sure that there is no other way for nil being returned you should handle that possibility properly.
I have set configuration:
'allowAutoLogin'=>true,
After successful login (with checked remember me checkbox) special cookie (with hash) is created. But after closing browser and opening website again I am not logged in but instead redirected to login page. When I look in application.log I have:
2014/07/30 09:44:11 [trace] [system.db.CDbCommand] Querying SQL: SELECT `itemname`
FROM `AuthAssignment`
WHERE itemname=:itemname AND userid=:userid. Bound with :itemname='Admin', :userid='10'
But after invoking filteraccessControler userId is set to 0
2014/07/30 09:44:11 [trace] [system.web.filters.CFilterChain] Running filter AController.filterforcedPasswordChange()
2014/07/30 09:44:11 [trace] [system.web.filters.CFilterChain] Running filter AController.filteraccessControl()
2014/07/30 09:44:11 [trace] [system.db.CDbCommand] Querying SQL: SELECT *
FROM `AuthAssignment`
WHERE userid=:userid. Bound with :userid=0
What could be the problem ?
I'm trying to communicate with HiveServer2 via ruby TCPSocket. As per Thrift SASL spec, I send START message and then plain auth information.
Server returns COMPLETE status with an empty payload. It should return challenge as a payload but an empty string.
START = 0x01
OK = 0x02
COMPLETE = 0x05
auth = 'PLAIN'
header = [START, auth.length].pack('cl>')
auth_string = ['anonymous'].pack('u')
auth_message = "[LOGIN] \u0000 #{auth_string} \u0000 #{auth_string}"
auth_header = [OK, auth_message.length].pack('cl>')
socket = TCPSocket.new localhost, 10000
socket.write header + auth
socket.write auth_header + auth_message
socket.read(5).unpack('cl>')
=> [5,0]
HiveServer2 returns 5 status that is COMPLETE.
No further communication is possible via this socket as the server returns nothing anymore.
I suspect auth_message constructed in a wrong way or something else is wrong.
Can anyone suggest the way HiveServer2 will understand my requests?
Any help will be appreciated.
UPD: Thrift SASL spec
UPD2: Solved! STARTTLS block should look like following below:
START = 0x01
OK = 0x02
COMPLETE = 0x05
auth = 'PLAIN'
header = [START, auth.length].pack('cl>')
auth_message = "[ANONYMOUS]\u0000anonymous\u0000anonymous"
auth_header = [OK, auth_message.length].pack('cl>')
socket = TCPSocket.new localhost, 10000
socket.write header + auth
socket.write auth_header + auth_message
socket.read(5).unpack('cl>')
=> [5,0]
After COMPLETE status received from the server, I can use TCLIService::Client to communicate with the HiveServer2. Only one thing to notice:
All writes to the underlying transport must be prefixed by the 4-byte length of the payload data, followed by the payload. All reads from this transport
should read the 4-byte length word, then read the full quantity of bytes
specified by this length word.
Try to use thrift gem and consider https://github.com/dallasmarlow/hiveserver2 instead of Ruby sockets.