Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Re: [Samba] Users list and the date the password will expire

311 views
Skip to first unread message

Ole Traupe via samba

unread,
Feb 8, 2017, 12:00:02 PM2/8/17
to
Hi list,

long time no see! :)

I was looking for an email reminder script for users whose password will
expire. Some of our users are on long travels and will never see the
Domain's default notification. I haven't found any complete (and simple)
solution online. So I wrote one. In case it helps anyone, you find it below.

You should only have to fill in the blanks for the the "basedn" search
parameter. Time conversion methods are taken from here:
http://meinit.nl/convert-active-directory-lastlogon-time-to-unix-readable-time

Ole




--

#!/bin/sh

max_pwAge=`samba-tool domain passwordsettings show | grep "Maximum
password age" | tr -dc '0-9'`
user_list=`wbinfo -u`

basedn="OU=*,DC=*,DC=*,DC=*"

for user in $user_list; do

set_date=`ldbsearch -H /usr/local/samba/private/sam.ldb -s sub
-b $basedn cn=$user | grep pwdLastSet | tr -dc '0-9'`

if [ $set_date ] && [ $set_date -gt 1 ]; then

UNIXTimeStamp=$((($set_date/10000000)-11644473600))
then_sec=`date -d "1970-01-01 $UNIXTimeStamp sec GMT" +%s`
now_sec=`date +%s`
diff_days=$(( ( $now_sec - $then_sec )/60/60/24 ))
exp_days=$(( $max_pwAge - $diff_days ))

if [ $exp_days == 90 ] || [ $exp_days == 60 ] || [
$exp_days == 30 ]; then

mail_string=`ldbsearch -H
/usr/local/samba/private/sam.ldb -s sub -b $basedn cn=$user | grep mail`
echo "Gotcha: $user" | mail -s "WARNING: Your
domain account password will expire in $exp_days days!" ${mail_string:6}

fi
fi
done

--
To unsubscribe from this list go to the following URL and read the
instructions: https://lists.samba.org/mailman/options/samba

Ole Traupe via samba

unread,
Feb 8, 2017, 12:40:06 PM2/8/17
to
That was weird: didn't see (expect) there to be a discussion right on
the same topic going on at this very moment.

Ole

Rowland Penny via samba

unread,
Feb 8, 2017, 12:50:03 PM2/8/17
to
Yes and now you know that you are using the wrong attribute LOL

Rowland

Ole Traupe via samba

unread,
Feb 9, 2017, 5:40:03 AM2/9/17
to
Exactly, and got reminded that I don't have to grep anything but can ask
for specific parameters. Been a while that I used ldbsearch. ;)

Ole

mj via samba

unread,
Feb 9, 2017, 5:50:02 AM2/9/17
to


On 02/09/2017 11:25 AM, Ole Traupe via samba wrote:
> Exactly, and got reminded that I don't have to grep anything but can ask
> for specific parameters. Been a while that I used ldbsearch. ;)
>

So there will be an updated version of your script? :-)

Your script is something we could use as well, appreciated!

MJ

Rowland Penny via samba

unread,
Feb 9, 2017, 6:10:03 AM2/9/17
to
On Thu, 9 Feb 2017 11:26:55 +0100
Ole Traupe <ole.t...@tu-berlin.de> wrote:

> But I got the timestamp subtraction constant right from the beginning!


Hope you don't mind but I updated your script ;-)

#!/bin/bash

### Set system defaults

# Get path to sam.ldb
LDBDIR=$(samba -b | grep 'PRIVATE_DIR' | awk -F ':' '{print $NF}' | sed 's/^ *//g')
if [ -z "${LDBDIR}" ]; then
echo "This is supposed to be a DC, but cannot obtain the Private dir."
echo "Cannot Continue...Exiting."
exit 1
else
LDBDB="${LDBDIR}/sam.ldb"
fi

# Get the default naming context of the domain # DC=samdom,DC=example,DC=com
domainDN=$(ldbsearch -H "${LDBDB}" -b "" -s base defaultNamingContext | grep 'defaultNamingContext' | sed 's|defaultNamingContext: ||')
if [ -z "${domainDN}" ]; then
echo "Could not obtain AD rootDSE"
exit 1
fi

user_list=$(wbinfo -u)

for user in $user_list; do
user=$(echo "${user}" | awk -F '\\' '{print $2}')
user_expire_date=$(ldbsearch --url="${LDBDB}" -b "${domainDN}" -s sub "(&(objectCategory=person)(objectClass=user)(sAMAccountName=$user))" msDS-UserPasswordExpiryTimeComputed | grep "msDS-UserPasswordExpiryTimeComputed: " | sed "s|msDS-UserPasswordExpiryTimeComputed: ||")
UNIXTimeStamp=$((("${user_expire_date}"/10000000)-11644473600))
date_now=$(date +%s)
exp_days=$((("${UNIXTimeStamp}" - "${date_now}") / 3600 / 24))
if [ "${exp_days}" -le "0" ]; then
mail_string=$(ldbsearch --url="${LDBDB}" -b "${domainDN}" -s sub "(&(objectCategory=person)(objectClass=user)(sAMAccountName=$user))" mail | grep mail: | sed "s|mail: ||")
if [ -n "${mail_string}" ]; then
echo "Gotcha: ${user}" | mail -s "WARNING: Your domain account password has expired!!!" "${mail_string}"
fi
elif [ "${exp_days}" == "90" ] || [ "${exp_days}" == "60" ] || [ "${exp_days}" == "30" ]; then
mail_string=$(ldbsearch --url="${LDBDB}" -b "${domainDN}" -s sub "(&(objectCategory=person)(objectClass=user)(sAMAccountName=$user))" mail | grep mail: | sed "s|mail: ||")
if [ -n "${mail_string}" ]; then
echo echo "Gotcha: ${user}" | mail -s "WARNING: Your domain account password will expire in ${exp_days} days!" "${mail_string}"
fi
fi
done

exit 0

Ole Traupe via samba

unread,
Feb 9, 2017, 6:10:03 AM2/9/17
to
Well, that was a little premature. Querying the attribute directly
actually leads to a longer (and partly redundant) statement:

exp_date=`ldbsearch -H /usr/local/samba/private/sam.ldb -s sub -b
$basedn cn=$user msDS-UserPasswordExpiryTimeComputed | grep
msDS-UserPasswordExpiryTimeComputed | tr -dc '0-9'`

Ole

Ole Traupe via samba

unread,
Feb 9, 2017, 6:30:03 AM2/9/17
to
NOBODY updates my scripts! Except whoever wants, of course. ;)

Would you mind going into details regarding you changes?

Ole

Ole Traupe via samba

unread,
Feb 9, 2017, 6:30:03 AM2/9/17
to
Welcome, good to be able to give something back.

Find the update below. Also replaced container name ('cn') with
'sAMAccountName' for stability reasons (in case sub-units contain
machines as well).

Comment out 'echo' lines for (rooted) cron-based use - except the mail
send line, of course.

Ole


#!/bin/sh

max_pwAge=`samba-tool domain passwordsettings show | grep "Maximum
password age" | tr -dc '0-9'`
user_list=`wbinfo -u`

basedn="OU=*,DC=*,DC=*,DC=*"

for user in $user_list; do

exp_date=`ldbsearch -H /usr/local/samba/private/sam.ldb -s sub
-b $basedn sAMAccountName=$user msDS-UserPasswordExpiryTimeComputed |
grep msDS-UserPasswordExpiryTimeComputed | tr -dc '0-9'`

echo "User: " $user
echo "Password expiry date: " $exp_date

if [ $exp_date ] && [ $exp_date -gt 1 ]; then

UNIXTimeStamp=$((($exp_date/10000000)-11644473600))
exp_sec=`date -d "1970-01-01 $UNIXTimeStamp sec GMT" +%s`
now_sec=`date +%s`
exp_days=$(( ( $exp_sec - $now_sec )/60/60/24 ))

echo "Days to expiration: " $exp_days

if [ $exp_days == 90 ] || [ $exp_days == 60 ] || [
$exp_days == 30 ] || [ $exp_days == 20 ] || [ $exp_days == 10 ]; then

mail_string=`ldbsearch -H
/usr/local/samba/private/sam.ldb -s sub -b $basedn cn=$user mail | grep
mail`
echo "Gotcha: $user" | mail -s "WARNING: Your
domain account password will expire in $exp_days days!" ${mail_string:6}

echo "mail sent to user $user via
${mail_string:6}, password will expire in $exp_days days"

fi
fi

echo ""

done

Rowland Penny via samba

unread,
Feb 9, 2017, 6:50:02 AM2/9/17
to
On Thu, 9 Feb 2017 12:21:35 +0100
Ole Traupe via samba <sa...@lists.samba.org> wrote:

> NOBODY updates my scripts! Except whoever wants, of course. ;)
>
> Would you mind going into details regarding you changes?
>
> Ole

It will run a Samba AD DC and find the path to sam.ldb, this way it
work on any DC

It then finds the default naming context i.e.
DC=samdom,DC=example,DC=com
This way you don't have to enter it.
It then uses this in the searches

It also uses '(objectCategory=person)', this ensures you only get users
and not computers (you did know that a computer is also a user, didn't
you)

It uses the users 'msDS-UserPasswordExpiryTimeComputed' attribute
contents.

If a user doesn't change the password and it expires, it sends a
different email.

Rowland

Ole Traupe via samba

unread,
Feb 9, 2017, 7:00:04 AM2/9/17
to
Never mind. However, with your update I get the following error right on
the first found "user":

./mailtest_rowland.sh: line 27: (""/10000000)-11644473600: syntax error:
operand expected (error token is """/10000000)-11644473600")

Ole

Rowland Penny via samba

unread,
Feb 9, 2017, 7:20:04 AM2/9/17
to
On Thu, 9 Feb 2017 12:49:12 +0100
Ole Traupe via samba <sa...@lists.samba.org> wrote:

> Never mind. However, with your update I get the following error right
> on the first found "user":
>
> ./mailtest_rowland.sh: line 27: (""/10000000)-11644473600: syntax
> error: operand expected (error token is """/10000000)-11644473600")
>

I initially got that, so I added:
user=$(echo "${user}" | awk -F '\\' '{print $2}')

because, 'wbinfo -u' gives you 'DOMAIN\username'

It looks like for some reason this is failing, are you using 'dash'
instead of 'bash' ?

You could try adding 'echo "User: ${user}" ' above and below line 25

i.e. Change:

for user in $user_list; do
user=$(echo "${user}" | awk -F '\\' '{print $2}')

To:

for user in $user_list; do
echo "User: ${user}"
user=$(echo "${user}" | awk -F '\\' '{print $2}')
echo "User: ${user}"
break

This should print the username before and after the removal of the
domain name and then break out of the loop.

Rowland

mj via samba

unread,
Feb 9, 2017, 7:40:02 AM2/9/17
to
Hi Rowland,

I'm getting the same error here, on bash.

Edited the script per your request, and the output looks sane:

> root@dc4:~# ./expired_passwords
> User: DOMAIN\onyteemenam
> User: onyteemenam
> root@dc4:~#

So no problem there?

I'm on debian wheezy with samba 4.4.4.

MJ

Ole Traupe via samba

unread,
Feb 9, 2017, 7:50:02 AM2/9/17
to
Actually, there were 2 problems. These lines work for me:

#user=$(echo "${user}" | awk -F '\\' '{print $2}')
user_expire_date=$(ldbsearch --url="${LDBDB}" -b "${domainDN}" -s
sub "(&(objectCategory=person)(objectClass=user)(sAMAccountName=$user))"
msDS-UserPasswordExpiryTimeComputed | grep
"msDS-UserPasswordExpiryTimeComputed: " | sed
"s|msDS-UserPasswordExpiryTimeComputed: ||")
UNIXTimeStamp=$(((${user_expire_date}/10000000)-11644473600))
date_now=$(date +%s)
exp_days=$(((${UNIXTimeStamp} - ${date_now}) / 3600 / 24))

With the 'awk' the user is empty. Querying $user before the awk shows
the correct user name without "DOMAIN\". This line seems not to be
necessary for me.

I also had to remove the quotes in the 3rd and last of these lines:

e.g.
./mailtest_rowland.sh: line 29:
("131479598790000000"/10000000)-11644473600: syntax error: operand
expected (error token is ""131479598790000000"/10000000)-11644473600")

Ole

Rowland Penny via samba

unread,
Feb 9, 2017, 8:10:03 AM2/9/17
to
On Thu, 9 Feb 2017 13:40:29 +0100
Ole Traupe <ole.t...@tu-berlin.de> wrote:

> Actually, there were 2 problems. These lines work for me:
>

There you go for relying on 'shellcheck', it didn't raise an error on
the quotes, but it did after I removed them ;-)

so here is the latest version of the script:

#!/bin/bash

# Get path to sam.ldb
LDBDIR=$(samba -b | grep 'PRIVATE_DIR' | awk -F ':' '{print $NF}' | sed 's/^ *//g')
if [ -z "${LDBDIR}" ]; then
echo "This is supposed to be a DC, but cannot obtain the Private dir."
echo "Cannot Continue...Exiting."
exit 1
else
LDBDB="${LDBDIR}/sam.ldb"
fi

# Get the default naming context of the domain # DC=samdom,DC=example,DC=com
domainDN=$(ldbsearch -H "${LDBDB}" -b "" -s base defaultNamingContext | grep 'defaultNamingContext' | sed 's|defaultNamingContext: ||')
if [ -z "${domainDN}" ]; then
echo "Could not obtain AD rootDSE"
exit 1
fi

user_list=$(wbinfo -u)

for user in $user_list; do
user=$(echo "${user}" | awk -F '\\' '{print $2}')
user_expire_date=$(ldbsearch --url="${LDBDB}" -b "${domainDN}" -s sub "(&(objectCategory=person)(objectClass=user)(sAMAccountName=$user))" msDS-UserPasswordExpiryTimeComputed | grep "msDS-UserPasswordExpiryTimeComputed: " | sed "s|msDS-UserPasswordExpiryTimeComputed: ||")
UNIXTimeStamp=$(((user_expire_date/10000000)-11644473600))
date_now=$(date +%s)
exp_days=$(((UNIXTimeStamp - date_now) / 3600 / 24))
if [ "${exp_days}" -le "0" ]; then
mail_string=$(ldbsearch --url="${LDBDB}" -b "${domainDN}" -s sub "(&(objectCategory=person)(objectClass=user)(sAMAccountName=$user))" mail | grep mail: | sed "s|mail: ||")
if [ -n "${mail_string}" ]; then
echo "Gotcha: ${user}" | mail -s "WARNING: Your domain account password has expired!!!" "${mail_string}"
fi
elif [ "${exp_days}" == "90" ] || [ "${exp_days}" == "60" ] || [ "${exp_days}" == "30" ]; then
mail_string=$(ldbsearch --url="${LDBDB}" -b "${domainDN}" -s sub "(&(objectCategory=person)(objectClass=user)(sAMAccountName=$user))" mail | grep mail: | sed "s|mail: ||")
if [ -n "${mail_string}" ]; then
echo echo "Gotcha: ${user}" | mail -s "WARNING: Your domain account password will expire in ${exp_days} days!" "${mail_string}"
fi
fi
done

exit 0

It has been tested on bash, don't know if it will work on dash etc

I have to have the line:

user=$(echo "${user}" | awk -F '\\' '{print $2}')

Or I get:

ldb_handler_fold: unable to casefold string [SAMDOM�ministrator]

and All my users have expired passwords, which they haven't

Rowland Penny via samba

unread,
Feb 9, 2017, 8:20:03 AM2/9/17
to
On Thu, 9 Feb 2017 13:40:29 +0100
Ole Traupe <ole.t...@tu-berlin.de> wrote:

>
> With the 'awk' the user is empty. Querying $user before the awk shows
> the correct user name without "DOMAIN\". This line seems not to be
> necessary for me.
>

Just noticed this, are you running this on a Samba AD DC and if so, why
are you not getting the DOMAIN name in front of the username ??

Ole Traupe via samba

unread,
Feb 9, 2017, 8:20:03 AM2/9/17
to
I am running this on a CentOS 6.7 DC with Samba version 4.2.5.

Ole

Ole Traupe via samba

unread,
Feb 9, 2017, 8:40:05 AM2/9/17
to
Beats me. ;)

Ole

Rowland Penny via samba

unread,
Feb 9, 2017, 8:50:03 AM2/9/17
to
On Thu, 9 Feb 2017 14:28:21 +0100
Ole Traupe via samba <sa...@lists.samba.org> wrote:

> Beats me. ;)

And me ;-)

If I run wbinfo -u , I get:

SAMDOM\albert
SAMDOM\administrator
SAMDOM\rowland
SAMDOM\suser5
SAMDOM\suser6
SAMDOM\suser3
..........
.......
....

Yours Mystified

Ole Traupe via samba

unread,
Feb 9, 2017, 9:00:03 AM2/9/17
to
I only get the usernames:

[...]
bcistudent01
bcistudent02
bcistudent03
bcistudent04
bcistudent05
bcistudent06
neuroergo10
neuroergo1
neuroergo2
neuroergo3
neuroergo4
[...]

Same on member servers, btw. Initially I thought this comes from
"winbind: use default domain", but this is neither present on my DCs nor
would it have any effect (afaik).

Anyways, no problem for me to accommodate your script to my environment.
Thank you for your valuable extensions!

Ole

Rowland Penny via samba

unread,
Feb 9, 2017, 9:30:03 AM2/9/17
to
On Thu, 9 Feb 2017 14:56:47 +0100
Ole Traupe via samba <sa...@lists.samba.org> wrote:

> I only get the usernames:

> Same on member servers, btw. Initially I thought this comes from
> "winbind: use default domain", but this is neither present on my DCs
> nor would it have any effect (afaik).

This is what is confusing me, I know of no way to get the username
without the domain on a DC and then yours goes and does it without
trying LOL

>
> Anyways, no problem for me to accommodate your script to my
> environment. Thank you for your valuable extensions!
>

No problem, glad to help.

Ole Traupe via samba

unread,
Feb 9, 2017, 10:00:03 AM2/9/17
to
For what it's worth, here is the output of "testparm" on the DC:


Load smb config files from /usr/local/samba/etc/smb.conf
rlimit_max: increasing rlimit_max (1024) to minimum Windows limit (16384)
Processing section "[netlogon]"
Processing section "[sysvol]"
Loaded services file OK.
Server role: ROLE_ACTIVE_DIRECTORY_DC

Press enter to see a dump of your service definitions

# Global parameters
[global]
workgroup = DOMAIN
realm = domain.university.tld
interfaces = lo eth0 eth0:0
bind interfaces only = Yes
server role = active directory domain controller
passdb backend = samba_dsdb
dns forwarder = forwarder_IP
rpc_server:tcpip = no
rpc_daemon:spoolssd = embedded
rpc_server:spoolss = embedded
rpc_server:winreg = embedded
rpc_server:ntsvcs = embedded
rpc_server:eventlog = embedded
rpc_server:srvsvc = embedded
rpc_server:svcctl = embedded
rpc_server:default = external
winbindd:use external pipes = true
idmap_ldb:use rfc2307 = yes
idmap config * : backend = tdb
map archive = No
map readonly = no
store dos attributes = Yes
vfs objects = dfs_samba4 acl_xattr


[netlogon]
path =
/usr/local/samba/var/locks/sysvol/domain.university.tld/scripts
read only = No


[sysvol]
path = /usr/local/samba/var/locks/sysvol
read only = No

Ole Traupe via samba

unread,
Feb 13, 2017, 10:50:03 AM2/13/17
to
Quick addendum: I just stumbled upon abandoned accounts receiving
"password expired" notifications forever, even if they get disabled
subsequently (by me). It might be helpful to include this in the script:

uAC_string=$(ldbsearch --url="${LDBDB}" -b "${domainDN}" -s sub
"(&(objectCategory=person)(objectClass=user)(sAMAccountName=$user))"
userAccountControl | grep userAccountControl: | sed
"s|userAccountControl: ||")


if [ "${uAC_string}" -eq "512" ]; then

[do expiration parsing]

fi


Here is a list of possible values for the userAccountControl field:
http://www.netvision.com/ad_useraccountcontrol.php

Ole

Rowland Penny via samba

unread,
Feb 13, 2017, 11:30:03 AM2/13/17
to
On Mon, 13 Feb 2017 16:46:12 +0100
Ole Traupe via samba <sa...@lists.samba.org> wrote:

You could always replace:

> "(&(objectCategory=person)(objectClass=user)(sAMAccountName=$user))"
> userAccountControl | grep userAccountControl: | sed
> "s|userAccountControl: ||")
>
> if [ "${uAC_string}" -eq "512" ]; then
>
> [do expiration parsing]
> fi

With:

"(&(objectCategory=person)(objectClass=user)(sAMAccountName=$user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))"

This will do the same ;-)

Ole Traupe via samba

unread,
Feb 13, 2017, 12:00:03 PM2/13/17
to
"userAccountControl:1.2.840.113556.1.4.803:=2"

Sorry, I cannot read the Matrix. ;)

Ole

Rowland Penny via samba

unread,
Feb 13, 2017, 12:10:03 PM2/13/17
to
On Mon, 13 Feb 2017 17:49:41 +0100
Ole Traupe via samba <sa...@lists.samba.org> wrote:

> "userAccountControl:1.2.840.113556.1.4.803:=2"
>
> Sorry, I cannot read the Matrix. ;)
>
> Ole
>
>
>

(!(userAccountControl:1.2.840.113556.1.4.803:=2))

You can read about it here:

https://support.microsoft.com/en-gb/help/269181/how-to-query-active-directory-by-using-a-bitwise-filter

Basically it means that the user isn't disabled.
If you add '2' to a users 'userAccountControl' attribute, you disable
the users account, the above checks it isn't set ( '!' = not )

Ole Traupe via samba

unread,
Feb 14, 2017, 5:40:03 AM2/14/17
to
I see. This is the same with 512 and 514, I think.

Ole

Rowland Penny via samba

unread,
Feb 14, 2017, 6:40:02 AM2/14/17
to
On Tue, 14 Feb 2017 11:29:39 +0100
Ole Traupe via samba <sa...@lists.samba.org> wrote:

> I see. This is the same with 512 and 514, I think.
>

An enabled user account is 512, if you add 2 to it, you get 514, but if
you set the users account to never expire you get '66048' if the
account is enabled and '66050' if it is disabled.

Ole Traupe via samba

unread,
Feb 14, 2017, 7:20:03 AM2/14/17
to
Yes, but if the password never expires, I don't need to include it in
the check the script performs.

Ole

Rowland Penny via samba

unread,
Feb 14, 2017, 7:40:03 AM2/14/17
to
On Tue, 14 Feb 2017 13:07:17 +0100
Ole Traupe via samba <sa...@lists.samba.org> wrote:

> Yes, but if the password never expires, I don't need to include it in
> the check the script performs.
>

Yes and doing it my way ensures the user will get excluded.

Ole Traupe via samba

unread,
Feb 14, 2017, 9:00:04 AM2/14/17
to
Ok, maybe I haven't understood your idea then.

Mine was: check a user's password expiry if (and only if)
- the account requires a password
- the password can expire
- the account isn't disabled

And those criteria are met if the userAccountControl value is "512".

Oh wait: as you are checking bits, you have the SmartCard case covered
as well, right?

Ole

Rowland Penny via samba

unread,
Feb 14, 2017, 10:20:02 AM2/14/17
to
On Tue, 14 Feb 2017 14:50:59 +0100
Ole Traupe via samba <sa...@lists.samba.org> wrote:

>
> Oh wait: as you are checking bits, you have the SmartCard case
> covered as well, right?
>

It checks for 'ANY' disabled users.

Ole Traupe via samba

unread,
Feb 14, 2017, 11:40:03 AM2/14/17
to
Thanks, that was what I was trying to say (you cover all cases).

My idea was that you don't need to process any users whose password
won't expire or who don't have one in the first place. Depending on your
situation that might actually save some time.

However, I think your solution is more complete.

Ole
0 new messages