Login Window Pass Expire Notice

DavidEAcosta
New Contributor

As per Apple's 10.10 AD best practices whitepaper, trying to implement this:
defaults write /Library/Preferences/com.apple.loginwindow PasswordExpirationDays -int 5

I tried a script, and various settings in Managed prefences with no luck
What am I missing?5acc1f1578154b5685667713cae6cf29

17 REPLIES 17

perrycj
Contributor III

So correct me if I'm wrong, but this functionality is already built-in to OS X and has been since 10.6. What I mean by that basically is if the Mac is bound to AD and the user who is logging in has an expiration limit on their account (say 90 days), built-in functionality will alert the user when they log in that their password is expiring and gives them a chance to change it before logging in.

Are you looking to do something different?

DavidEAcosta
New Contributor

I am not getting a notice....only the actual day of expire do I get a notice. I'm using an AD PSO, so I can adjust settings on the fly to test...all my Windows Machines give notice 5 days before, and on the expire day...the 10.10 test machine, same user, (in same OU as windows test machine)....just gives the day of notice....not 5 days before.

nessts
Valued Contributor II

I did some checking on this a while back and I believe the problem has to do with the fact that the locally cached account rarely if ever updates the SMBPasswordLastSet field.

to check this if you do dscl . read users/username SMBPasswordLastSet
and
dscl "/Active Directory/YOURDOMAIN/All Domains" read users/username SMBPasswordLastSet

you will likely see two different results, and the local one is way in the past so its not going to warn you of anything.

I have written a script and launch daemon that go out and compare the two values and update the local if it is wrong. I believe I reported this as a bug to Apple who does not seem to care about bugs anymore, just making the most gorgeous this or that, not worrying about if it works properly or not. I take that back I bet if the bug related to being able to buy things out of the App or iTunes stores they would fix it.

mm2270
Legendary Contributor III
I believe I reported this as a bug to Apple who does not seem to care about bugs anymore, just making the most gorgeous this or that, not worrying about if it works properly or not. I take that back I bet if the bug related to being able to buy things out of the App or iTunes stores they would fix it.

Boy, ain't that the truth! I feel like the only way Apple will ever actually make their stuff work correctly is if it starts to hit their bottom line. That day may yet come. Organizations are beginning to notice the lack of concern Apple seems to have over fixing stuff that impacts an organization anymore. If it impacts regular Joe home user they jump right on it, but companies? Who cares? Quite annoying.

bentoms
Release Candidate Programs Tester

The bug I logged with Apple was reported as "resolved" in 10.10, but I've not had a chance to test much.

Hell, I even forked ADPassMon because of this issue.

@nessts can you share your scripts?

nessts
Valued Contributor II

Oh I suppose I can share its all perl of course and I had to pull some of the functions out of my library so hopefully I did not miss any it should work.

#!/usr/bin/perl -w

use strict;
use IO::File;
use Getopt::Std;
use Sys::Syslog;
use DateTime;

(my $progname =$0) =~ s#.*/##;
$ENV{PATH} = '/sbin:/bin:/usr/bin:/usr/sbin';
umask 0022;

# static variables
my $today       = DateTime->now();
my $ds          = 86400;
my $consoleuser = undef;
my $count       = 0;
my $domain;
my $DOMAIN;
my $specifiDomain;
my $notifyscript = $sbindir . "passwordExpireNotify.pl";

while(!defined $consoleuser) {
    sleep 1;
    syslog('notice', "waiting for firstboot user to login") if ($count == 1);
    $count ++;
    $count %= 60;
    $consoleuser = getConsoleUser();
}
open CONFIGAD, "dsconfigad -show 2>&1 |" or die "$progname: dsconfigad: $!
";
while (<CONFIGAD>) {
    chomp;
    next unless ((/^Active Directory Domain/) || (/Authentication from any domain/));
    if (/^Active Directory Domain/) {
        $domain = (split "=")[-1];
        $domain =~ s/^s*//;
        $domain =~ s/s*$//;
        $DOMAIN = (split '.', $domain)[0];
        $DOMAIN = uc($DOMAIN);
    }
    else {
        my $flag = (split "=")[-1];
        $flag =~ s/^s*//;
        $flag =~ s/s*$//;
        if ($flag eq "Disabled") {
            $specifiDomain = $domain;
        }
        else {
            $specifiDomain = "All Domains";
        }
    }
}
my $adlc        = `dscl "/Active Directory/$DOMAIN/$specifiDomain" read users/$consoleuser SMBPasswordLastSet`;
chomp($adlc);
my $ladlc       = `dscl . read users/$consoleuser SMBPasswordLastSet`;
chomp($ladlc);
$adlc           = (split ' ', $adlc)[-1];
$ladlc          = (split ' ', $ladlc)[-1];
sub getConsoleUser {
    my $console = "/dev/console";
    my $tmpuser = undef;
    syslog('notice', "checking for logged-in user");
    $tmpuser = getpwuid((stat $console)[4]);
    $tmpuser = undef if ($tmpuser eq "root");
    return $tmpuser;
}

sub runAsConsoleUser {
    my $consoleuser = getConsoleUser();
    my $pid = getConsolePID();
    my $command = shift;
    my $rc;
    if ((defined $consoleuser) && (defined $pid) && (defined $command)) {
        syslog('notice', "Running %s as %s on console process id %s", $command, $consoleuser, $pid);
        $rc = system("launchctl bsexec $pid su "$consoleuser" -c '"$command"'");
        syslog('notice', "return code from process %s is %s", $command, $rc);
        return $rc;
    }
    else {
        syslog('notice', "Something Not defined in properly cannot run as user");
    }
}

sub getConsolePID {
    my $pid = undef;
    open PS, "ps -A -e -w -O comm= |" or die "$progname: ps: $!
";
    my @running = <PS>;
    close PS;
    for (@running) {
        chomp;
        next unless (grep /loginwindow/, $_);
        print $_ . "
";
        $pid = (split " ", $_)[0];
        last;
    }
    return $pid;
}

main:
openlog $progname, undef, 'user';
while ($adlc eq 'valid.') {
    #give AD time to start communicating to the machine
    syslog('notice', "not connected to AD yet");
    sleep 60;
    $adlc       = `dscl "/Active Directory/ERICSSON/All Domains" read users/$consoleuser SMBPasswordLastSet`;
    chomp($adlc);
    $adlc           = (split ' ', $adlc)[-1];
}
if ($adlc != $ladlc) {
    system("dscl . change users/$consoleuser SMBPasswordLastSet $ladlc $adlc");
    syslog('notice', "Users local password date differs, updating to match AD");
}
# prompt the user to change password if necessary
runAsConsoleUser($notifyscript);
closelog;

Ah because I quoted it instead of script quoting it. should be better now.

nessts
Valued Contributor II

those $adlc lines should be wrapped in tick marks the single quote to the left of the 1 key on the top left of your keyboard not sure why this script thing redid all of that and it removed the hash before the comments and made them bold very strange.

perrycj
Contributor III

@bentoms @mm2270 @nessts I can say in my experiences with 10.10.3 and below so far, the login expiration notice has worked without issue at the login screen.

However, it also worked pretty reliably on 10.9.x for me but then of course, like most of you I assume, my users never log out and hardly ever restart. That's why I started implementing a script with a pop up that essentially did what @nessts 's script does, checking the values of smbPasswordLastSet value to get the expiration value and then display it to the user since there is nothing within OSX to let the user know when their password is expiring or about to expire. It didn't change the values to match automatically though and It wasn't always reliable, lots of times getting negative values after it was on the user's Mac for a while.

So then I started using @bentoms wonderful ADPassMon and it has worked great ever since. So @DavidEAcosta would something like ADPassMon work in your environment or you definitely want something at login? And like you all said, I agree. Bugs like this just seem unimportant to Apple nowadays.

bentoms
Release Candidate Programs Tester

@nessts be careful what you wish for eh?

grabs a big book on perl

Haha thanks fella.

nessts
Valued Contributor II

Gee the notify script is a cocoa dialog script here it is too:

#!/usr/bin/perl -w

use strict;
use IO::File;
use Getopt::Std;
use Sys::Syslog;
use DateTime;

(my $progname =$0) =~ s#.*/##;
$ENV{PATH} = '/sbin:/bin:/usr/bin:/usr/sbin';
umask 0022;
use vars qw($opt_h $opt_v);

# static variables
my $today               = DateTime->now();
my $ds                  = 86400;
my $warn;
if (defined $firstbootArgs{pwExpireWarn}) {
        $warn = $firstbootArgs{pwExpireWarn};
}
else {
        $warn = 90;
}
my $chpwscr             = $sbindir . "chpw.applescript";
my $count               = 0;
my $consoleuser = undef;
my $osascript   = '/usr/bin/osascript';
my $iconfile    = $etcdir . "HPCircleLogo.png";
while(!defined $consoleuser) {
        sleep 1;
        syslog('notice', "waiting for firstboot user to login") if ($count == 1);
        $count ++;
        $count %= 60;
        $consoleuser = getConsoleUser();
}
my $adlc                = `dscl . read users/$consoleuser SMBPasswordLastSet`;
chomp($adlc);
$adlc                   = (split ' ', $adlc)[-1];
my $lastchg             = int($adlc / 10000000 - 11644473600);
my $pwexpire    = $lastchg + 90 * $ds;
my $expdays             = int(($pwexpire - $today->epoch())/$ds) ;
my $rv                  = undef;
undef $opt_h;
undef $opt_v;

sub usage {
        (my $usage = << "       USAGE") =~ s/^	//;
        usage: $progname -[hv]
        v: print the days until the password needs to be changed
        h: this help message
        USAGE
        die $usage;
}

main:
openlog $progname, undef, 'user';
# parse cmdline options
my $rc = getopts('hv');
& usage unless($rc);
& usage if($opt_h);
syslog('notice', "Checking if users password is to expire within next %s days", $warn);
if ((($expdays > 0) && ($expdays < $warn)) || (defined $opt_v)) {
        syslog('notice', "Users password expires in %s days", $expdays);
        my $txt = 'Password Expiration';
        my $itxt = "Your password for account $consoleuser will expire in $expdays days.";
        my $cdargs = "msgbox --timeout 43200 --float --icon-file "$iconfile" --string-output --no-newline --text "$txt" --informative-text "$itxt" --button1 "Change Password Now" --button2 "Remind Me"";
        $rv = `"$CD" $cdargs`;
}
else {
        syslog('notice', "Users password expires in %s days", $expdays);
}
if ((defined $rv) && ($rv eq 'Change Password Now')) {
        syslog('notice', "opening system preferences to change the password");
        system(""$osascript" "$chpwscr"");
}
closelog;

perrycj
Contributor III

@nessts Thanks for posting your work. Very much appreciated.

nessts
Valued Contributor II

Oh and then I run this from a Launch Daemon every day at 0930 if they get within the window it prompts them, because they rarely logout.

I have not tested the password age variable recently and I would have to remember to turn off my tool to check. But I know things like the SMBHome never update either, we just moved all the homes from one server to another and now the script that was looking at the local account fails to mount the home, and it was easy enough to tell it to read from AD.

nessts
Valued Contributor II

you are welcom @perrycj and @bentoms I usually refrain because my Library is 40K lines and this is all usually dependant on some variables in the the common library or custom one for each account, so that $firstbootArgs{pwExpireWarn} is going to throw errors because you dont have that perl library and hash defined. It will take some work for somebody to use this or me to clean it up more. but its close and you can hard code some stuff when you run into the errors to clean it up. Gee and looking there is the applescript for changing the pw. that is just

tell application "System Preferences"
        activate
        set the current pane to pane id "com.apple.preferences.users"
end tell

So, if anybody tries to use it and needs help getting it running let me know I will do my best to help out.

bpavlov
Honored Contributor

@nessts
That script was rather interesting to read because even though I don't know a bit of perl I understood what was going on. Not sure if that makes sense. You commented the script very well. I'm partially commenting this so I can come back to this post and read my logic and so others who can't read perl at all can have an easier time if they want to do this in another language.

Based on what you described regarding there being a difference in the password value from the local cached account and what AD spits out, I understood what you were looking to achieve but wasn't sure where the magic happened. I was literally going to ask you, how did you update the local value to the value from AD? But of course you posted the entire script for all to see. Well, for others who are looking for the 'magic' it's this special line:

dscl . change users/$consoleuser SMBPasswordLastSet $LocalValue $ADValue

Where variables are:
$consoleuser is person logged in
$LocalValue is the value of the local account which can be gotten with "dscl . read users/USERNAME SMBPasswordLastSet"
$ADValue is the value from AD which can be gotten with "dscl "/Active Directory/YOURDOMAIN/All Domains" read users/username SMBPasswordLastSet"

This can and should be easy to accomplish in a simple bash script. I'll leave that to someone else who feels inclined as you've provided good information here for others to go off of. I'll tackle this later this summer as one of the issues I deal with frequently is computers falling off the domain because our users are on laptops and almost never plug in and restart their computers. That leads to users almost never getting the prompt. We've also implemented ADPassMon as an aide as well, but obviously that doesn't work well if the computer cannot communicate with the domain properly. So a couple of different issues happening for me in my environment, but this thread has been quite helpful.

Thanks again for sharing!

mm2270
Legendary Contributor III

Yes, thanks for posting your scripts @nessts Good to see different ways of accomplishing the same stuff.

FWIW, I also see that my account's SMBPasswordLastSet value and the one from my corresponding AD record do not match. The one on my Mac is a lower number, and therefore, and older date value. (I did the calculation and its saying the last password change was on 7/30/14, which is both hilarious and sad at the same time)

So, just chiming in that its happening here too. Apparently its just too much for Apple to get Active Directory stuff fixed in the OS. Too many new photo/music related apps and fancy UI changes to work on to be bothered with this stuff I guess.

nessts
Valued Contributor II

you all are welcome. if enough of us post bugs about it they may fix it. I made sure i had another one posted today.

scottb
Honored Contributor

Well, if THIS is to be believed, maybe things will improve.
Grab a SCUBA tank or two in case you try to hold your breath...