sync password hashes of users UID>1000 between host A and B

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP











up vote
1
down vote

favorite












I have two CentOS systems, I want to do syncing of password hashes (only) which are stored in /etc/shadow, from system A (local) to system B (remote) but only for these users which have UID > 1000 and exist on both systems (basing on username, not UID which can vary for the same username on A an B).



I can't use rsync or solutions like LDAP or NIS. I also can't touch any accounts with UID < 1000 on these systems.



As UID of a user can differ on host A and B - to sync password hash from A to B following is important:
(1) the username must exist on both systems
(2) the username's UID must be >1000 (can differ) on both A and B systems



I have found a nice perl script of Renaud Bompuis which probably would need some adjustements to my requirements, it should not modify /etc/passwd or /etc/group. I am not a Perl programmer so I am looking for help here. Thank you in advance.



#!/usr/bin/perl -w
use Net::SCP qw(scp);
use strict;

use constant TRUE => (1==1);
use constant FALSE => (1==0);

#--------------------------------------------------------
# Configuration
# Modify as needed
#--------------------------------------------------------
my $remoteHost = '10.13.113.2'; # email backup server
my $minUID = 500;
my $maxUID = 30000;
my $minGID = 500;
my $maxGID = 30000;

#--------------------------------------------------------
# Internal variables, normally not to be modified.
#--------------------------------------------------------
my $systemConfigDir = '/etc';
my $tmpDir = $ENVTMPDIR || $ENVTMP || $ENVTEMP || '/tmp';

#--------------------------------------------------------
# Main
#--------------------------------------------------------
# STEP 1
# Get the remote files to /tmp and
# clean them of their normal users
ProcessFiles('remote');

# STEP 2
# Append the local normal users to the temp files
# and then send them back to the remote
ProcessFiles('local');

#--------------------------------------------------------
# ProcessFiles sub does one of two things:
# - if the passed argument is 'remote', then fetch each
# user account file from the remote server, then remove
# all normal users from each file, only keeping the
# system users.
# - if the passed argument is 'local', then appends all
# normal local users to the previously fetched and
# cleaned-up files, then copies them back to the remote.
#--------------------------------------------------------
sub ProcessFiles
my $which = shift;
my $tmpfile;
my %username = ();
my %usergroup = ();
my %userUID = ();
my %userGID = ();
my @info;
foreach my $f ('passwd','group','shadow','gshadow')
my $tmpfile = "$tmpDir/$f.REMOTE";
if ($which eq 'remote')
# Fetch the remote file
unlink $tmpfile if -e $tmpfile;
scp("$remoteHost:$systemConfigDir/$f", $tmpfile)
or die ("Could not get '$f' from '$remoteHost'");

# Glob the file content
open CONFIGFILE, (($which eq 'remote') ? $tmpfile : "$systemConfigDir/$f");
my @lines = <CONFIGFILE>;
close CONFIGFILE;
# Open the temp file, either truncating it or in append mode
open TMPFILE, (($which eq 'remote') ? ">$tmpfile" : ">>$tmpfile" )
or die "Could not open '$tmpfile' for processing";
foreach my $line (@lines)
# Skip comments, although they should be illegal in these files
next if $f =~ /^s*#/;
@info = (split ':', $line);
if ($f eq 'passwd')
my $uid = $info[2];
my $isnormaluser = ($uid > $minUID) && ($uid < $maxUID);
next if (($which eq 'remote') ? $isnormaluser : !$isnormaluser);
$username$info[0] = TRUE;
$userUID$uid = TRUE;
$userGID$info[3] = TRUE;
elsif ($f eq 'group')
my $gid = $info[2];
my $isnormalgroup = ($gid > $minGID) && ($gid < $maxGID);
next if (($which eq 'remote') ? $isnormalgroup : !$isnormalgroup);
$usergroup$info[0] = TRUE;
elsif ($f eq 'shadow')
next if !exists $username$info[0];
else
next if !exists $usergroup$info[0];

# Any line that reaches this point is valid
print TMPFILE $line;

close TMPFILE;
if ($which eq 'local')
# send the file back
scp($tmpfile, "$remoteHost:$systemConfigDir/$f") or
die ("Could not send '$f' to '$remoteHost'");
unlink $tmpfile;




#--------------------------------------------------------
# Make sure we cleanup the temp files when we exit
#--------------------------------------------------------
END
my $tmpfile;
foreach my $f ('passwd','group','shadow','gshadow')
$tmpfile = "$tmpDir/$f.REMOTE";
unlink $tmpfile if -e $tmpfile;








share|improve this question






















  • I don't see necessity to copy a user password from system A to B if the user does not exist on B. Of course when such a user is one day created on system B, then his password should be synced from A to B.
    – DonJ
    Mar 6 at 21:48










  • You refer to UID<1000 and UID>1000. What about UID=1000?
    – roaima
    Mar 6 at 22:37














up vote
1
down vote

favorite












I have two CentOS systems, I want to do syncing of password hashes (only) which are stored in /etc/shadow, from system A (local) to system B (remote) but only for these users which have UID > 1000 and exist on both systems (basing on username, not UID which can vary for the same username on A an B).



I can't use rsync or solutions like LDAP or NIS. I also can't touch any accounts with UID < 1000 on these systems.



As UID of a user can differ on host A and B - to sync password hash from A to B following is important:
(1) the username must exist on both systems
(2) the username's UID must be >1000 (can differ) on both A and B systems



I have found a nice perl script of Renaud Bompuis which probably would need some adjustements to my requirements, it should not modify /etc/passwd or /etc/group. I am not a Perl programmer so I am looking for help here. Thank you in advance.



#!/usr/bin/perl -w
use Net::SCP qw(scp);
use strict;

use constant TRUE => (1==1);
use constant FALSE => (1==0);

#--------------------------------------------------------
# Configuration
# Modify as needed
#--------------------------------------------------------
my $remoteHost = '10.13.113.2'; # email backup server
my $minUID = 500;
my $maxUID = 30000;
my $minGID = 500;
my $maxGID = 30000;

#--------------------------------------------------------
# Internal variables, normally not to be modified.
#--------------------------------------------------------
my $systemConfigDir = '/etc';
my $tmpDir = $ENVTMPDIR || $ENVTMP || $ENVTEMP || '/tmp';

#--------------------------------------------------------
# Main
#--------------------------------------------------------
# STEP 1
# Get the remote files to /tmp and
# clean them of their normal users
ProcessFiles('remote');

# STEP 2
# Append the local normal users to the temp files
# and then send them back to the remote
ProcessFiles('local');

#--------------------------------------------------------
# ProcessFiles sub does one of two things:
# - if the passed argument is 'remote', then fetch each
# user account file from the remote server, then remove
# all normal users from each file, only keeping the
# system users.
# - if the passed argument is 'local', then appends all
# normal local users to the previously fetched and
# cleaned-up files, then copies them back to the remote.
#--------------------------------------------------------
sub ProcessFiles
my $which = shift;
my $tmpfile;
my %username = ();
my %usergroup = ();
my %userUID = ();
my %userGID = ();
my @info;
foreach my $f ('passwd','group','shadow','gshadow')
my $tmpfile = "$tmpDir/$f.REMOTE";
if ($which eq 'remote')
# Fetch the remote file
unlink $tmpfile if -e $tmpfile;
scp("$remoteHost:$systemConfigDir/$f", $tmpfile)
or die ("Could not get '$f' from '$remoteHost'");

# Glob the file content
open CONFIGFILE, (($which eq 'remote') ? $tmpfile : "$systemConfigDir/$f");
my @lines = <CONFIGFILE>;
close CONFIGFILE;
# Open the temp file, either truncating it or in append mode
open TMPFILE, (($which eq 'remote') ? ">$tmpfile" : ">>$tmpfile" )
or die "Could not open '$tmpfile' for processing";
foreach my $line (@lines)
# Skip comments, although they should be illegal in these files
next if $f =~ /^s*#/;
@info = (split ':', $line);
if ($f eq 'passwd')
my $uid = $info[2];
my $isnormaluser = ($uid > $minUID) && ($uid < $maxUID);
next if (($which eq 'remote') ? $isnormaluser : !$isnormaluser);
$username$info[0] = TRUE;
$userUID$uid = TRUE;
$userGID$info[3] = TRUE;
elsif ($f eq 'group')
my $gid = $info[2];
my $isnormalgroup = ($gid > $minGID) && ($gid < $maxGID);
next if (($which eq 'remote') ? $isnormalgroup : !$isnormalgroup);
$usergroup$info[0] = TRUE;
elsif ($f eq 'shadow')
next if !exists $username$info[0];
else
next if !exists $usergroup$info[0];

# Any line that reaches this point is valid
print TMPFILE $line;

close TMPFILE;
if ($which eq 'local')
# send the file back
scp($tmpfile, "$remoteHost:$systemConfigDir/$f") or
die ("Could not send '$f' to '$remoteHost'");
unlink $tmpfile;




#--------------------------------------------------------
# Make sure we cleanup the temp files when we exit
#--------------------------------------------------------
END
my $tmpfile;
foreach my $f ('passwd','group','shadow','gshadow')
$tmpfile = "$tmpDir/$f.REMOTE";
unlink $tmpfile if -e $tmpfile;








share|improve this question






















  • I don't see necessity to copy a user password from system A to B if the user does not exist on B. Of course when such a user is one day created on system B, then his password should be synced from A to B.
    – DonJ
    Mar 6 at 21:48










  • You refer to UID<1000 and UID>1000. What about UID=1000?
    – roaima
    Mar 6 at 22:37












up vote
1
down vote

favorite









up vote
1
down vote

favorite











I have two CentOS systems, I want to do syncing of password hashes (only) which are stored in /etc/shadow, from system A (local) to system B (remote) but only for these users which have UID > 1000 and exist on both systems (basing on username, not UID which can vary for the same username on A an B).



I can't use rsync or solutions like LDAP or NIS. I also can't touch any accounts with UID < 1000 on these systems.



As UID of a user can differ on host A and B - to sync password hash from A to B following is important:
(1) the username must exist on both systems
(2) the username's UID must be >1000 (can differ) on both A and B systems



I have found a nice perl script of Renaud Bompuis which probably would need some adjustements to my requirements, it should not modify /etc/passwd or /etc/group. I am not a Perl programmer so I am looking for help here. Thank you in advance.



#!/usr/bin/perl -w
use Net::SCP qw(scp);
use strict;

use constant TRUE => (1==1);
use constant FALSE => (1==0);

#--------------------------------------------------------
# Configuration
# Modify as needed
#--------------------------------------------------------
my $remoteHost = '10.13.113.2'; # email backup server
my $minUID = 500;
my $maxUID = 30000;
my $minGID = 500;
my $maxGID = 30000;

#--------------------------------------------------------
# Internal variables, normally not to be modified.
#--------------------------------------------------------
my $systemConfigDir = '/etc';
my $tmpDir = $ENVTMPDIR || $ENVTMP || $ENVTEMP || '/tmp';

#--------------------------------------------------------
# Main
#--------------------------------------------------------
# STEP 1
# Get the remote files to /tmp and
# clean them of their normal users
ProcessFiles('remote');

# STEP 2
# Append the local normal users to the temp files
# and then send them back to the remote
ProcessFiles('local');

#--------------------------------------------------------
# ProcessFiles sub does one of two things:
# - if the passed argument is 'remote', then fetch each
# user account file from the remote server, then remove
# all normal users from each file, only keeping the
# system users.
# - if the passed argument is 'local', then appends all
# normal local users to the previously fetched and
# cleaned-up files, then copies them back to the remote.
#--------------------------------------------------------
sub ProcessFiles
my $which = shift;
my $tmpfile;
my %username = ();
my %usergroup = ();
my %userUID = ();
my %userGID = ();
my @info;
foreach my $f ('passwd','group','shadow','gshadow')
my $tmpfile = "$tmpDir/$f.REMOTE";
if ($which eq 'remote')
# Fetch the remote file
unlink $tmpfile if -e $tmpfile;
scp("$remoteHost:$systemConfigDir/$f", $tmpfile)
or die ("Could not get '$f' from '$remoteHost'");

# Glob the file content
open CONFIGFILE, (($which eq 'remote') ? $tmpfile : "$systemConfigDir/$f");
my @lines = <CONFIGFILE>;
close CONFIGFILE;
# Open the temp file, either truncating it or in append mode
open TMPFILE, (($which eq 'remote') ? ">$tmpfile" : ">>$tmpfile" )
or die "Could not open '$tmpfile' for processing";
foreach my $line (@lines)
# Skip comments, although they should be illegal in these files
next if $f =~ /^s*#/;
@info = (split ':', $line);
if ($f eq 'passwd')
my $uid = $info[2];
my $isnormaluser = ($uid > $minUID) && ($uid < $maxUID);
next if (($which eq 'remote') ? $isnormaluser : !$isnormaluser);
$username$info[0] = TRUE;
$userUID$uid = TRUE;
$userGID$info[3] = TRUE;
elsif ($f eq 'group')
my $gid = $info[2];
my $isnormalgroup = ($gid > $minGID) && ($gid < $maxGID);
next if (($which eq 'remote') ? $isnormalgroup : !$isnormalgroup);
$usergroup$info[0] = TRUE;
elsif ($f eq 'shadow')
next if !exists $username$info[0];
else
next if !exists $usergroup$info[0];

# Any line that reaches this point is valid
print TMPFILE $line;

close TMPFILE;
if ($which eq 'local')
# send the file back
scp($tmpfile, "$remoteHost:$systemConfigDir/$f") or
die ("Could not send '$f' to '$remoteHost'");
unlink $tmpfile;




#--------------------------------------------------------
# Make sure we cleanup the temp files when we exit
#--------------------------------------------------------
END
my $tmpfile;
foreach my $f ('passwd','group','shadow','gshadow')
$tmpfile = "$tmpDir/$f.REMOTE";
unlink $tmpfile if -e $tmpfile;








share|improve this question














I have two CentOS systems, I want to do syncing of password hashes (only) which are stored in /etc/shadow, from system A (local) to system B (remote) but only for these users which have UID > 1000 and exist on both systems (basing on username, not UID which can vary for the same username on A an B).



I can't use rsync or solutions like LDAP or NIS. I also can't touch any accounts with UID < 1000 on these systems.



As UID of a user can differ on host A and B - to sync password hash from A to B following is important:
(1) the username must exist on both systems
(2) the username's UID must be >1000 (can differ) on both A and B systems



I have found a nice perl script of Renaud Bompuis which probably would need some adjustements to my requirements, it should not modify /etc/passwd or /etc/group. I am not a Perl programmer so I am looking for help here. Thank you in advance.



#!/usr/bin/perl -w
use Net::SCP qw(scp);
use strict;

use constant TRUE => (1==1);
use constant FALSE => (1==0);

#--------------------------------------------------------
# Configuration
# Modify as needed
#--------------------------------------------------------
my $remoteHost = '10.13.113.2'; # email backup server
my $minUID = 500;
my $maxUID = 30000;
my $minGID = 500;
my $maxGID = 30000;

#--------------------------------------------------------
# Internal variables, normally not to be modified.
#--------------------------------------------------------
my $systemConfigDir = '/etc';
my $tmpDir = $ENVTMPDIR || $ENVTMP || $ENVTEMP || '/tmp';

#--------------------------------------------------------
# Main
#--------------------------------------------------------
# STEP 1
# Get the remote files to /tmp and
# clean them of their normal users
ProcessFiles('remote');

# STEP 2
# Append the local normal users to the temp files
# and then send them back to the remote
ProcessFiles('local');

#--------------------------------------------------------
# ProcessFiles sub does one of two things:
# - if the passed argument is 'remote', then fetch each
# user account file from the remote server, then remove
# all normal users from each file, only keeping the
# system users.
# - if the passed argument is 'local', then appends all
# normal local users to the previously fetched and
# cleaned-up files, then copies them back to the remote.
#--------------------------------------------------------
sub ProcessFiles
my $which = shift;
my $tmpfile;
my %username = ();
my %usergroup = ();
my %userUID = ();
my %userGID = ();
my @info;
foreach my $f ('passwd','group','shadow','gshadow')
my $tmpfile = "$tmpDir/$f.REMOTE";
if ($which eq 'remote')
# Fetch the remote file
unlink $tmpfile if -e $tmpfile;
scp("$remoteHost:$systemConfigDir/$f", $tmpfile)
or die ("Could not get '$f' from '$remoteHost'");

# Glob the file content
open CONFIGFILE, (($which eq 'remote') ? $tmpfile : "$systemConfigDir/$f");
my @lines = <CONFIGFILE>;
close CONFIGFILE;
# Open the temp file, either truncating it or in append mode
open TMPFILE, (($which eq 'remote') ? ">$tmpfile" : ">>$tmpfile" )
or die "Could not open '$tmpfile' for processing";
foreach my $line (@lines)
# Skip comments, although they should be illegal in these files
next if $f =~ /^s*#/;
@info = (split ':', $line);
if ($f eq 'passwd')
my $uid = $info[2];
my $isnormaluser = ($uid > $minUID) && ($uid < $maxUID);
next if (($which eq 'remote') ? $isnormaluser : !$isnormaluser);
$username$info[0] = TRUE;
$userUID$uid = TRUE;
$userGID$info[3] = TRUE;
elsif ($f eq 'group')
my $gid = $info[2];
my $isnormalgroup = ($gid > $minGID) && ($gid < $maxGID);
next if (($which eq 'remote') ? $isnormalgroup : !$isnormalgroup);
$usergroup$info[0] = TRUE;
elsif ($f eq 'shadow')
next if !exists $username$info[0];
else
next if !exists $usergroup$info[0];

# Any line that reaches this point is valid
print TMPFILE $line;

close TMPFILE;
if ($which eq 'local')
# send the file back
scp($tmpfile, "$remoteHost:$systemConfigDir/$f") or
die ("Could not send '$f' to '$remoteHost'");
unlink $tmpfile;




#--------------------------------------------------------
# Make sure we cleanup the temp files when we exit
#--------------------------------------------------------
END
my $tmpfile;
foreach my $f ('passwd','group','shadow','gshadow')
$tmpfile = "$tmpDir/$f.REMOTE";
unlink $tmpfile if -e $tmpfile;










share|improve this question













share|improve this question




share|improve this question








edited Mar 6 at 21:14









ilkkachu

49.2k672136




49.2k672136










asked Mar 6 at 20:59









DonJ

768




768











  • I don't see necessity to copy a user password from system A to B if the user does not exist on B. Of course when such a user is one day created on system B, then his password should be synced from A to B.
    – DonJ
    Mar 6 at 21:48










  • You refer to UID<1000 and UID>1000. What about UID=1000?
    – roaima
    Mar 6 at 22:37
















  • I don't see necessity to copy a user password from system A to B if the user does not exist on B. Of course when such a user is one day created on system B, then his password should be synced from A to B.
    – DonJ
    Mar 6 at 21:48










  • You refer to UID<1000 and UID>1000. What about UID=1000?
    – roaima
    Mar 6 at 22:37















I don't see necessity to copy a user password from system A to B if the user does not exist on B. Of course when such a user is one day created on system B, then his password should be synced from A to B.
– DonJ
Mar 6 at 21:48




I don't see necessity to copy a user password from system A to B if the user does not exist on B. Of course when such a user is one day created on system B, then his password should be synced from A to B.
– DonJ
Mar 6 at 21:48












You refer to UID<1000 and UID>1000. What about UID=1000?
– roaima
Mar 6 at 22:37




You refer to UID<1000 and UID>1000. What about UID=1000?
– roaima
Mar 6 at 22:37










2 Answers
2






active

oldest

votes

















up vote
1
down vote













Based on @roaiama's use of the join command, this answer uses getent to get the passwd and shadow files rather than reading them directly, and then uses chpasswd on the remote host to change the passwords.



The password-changing code is simpler because of chpasswd, but making a backup copy of the old shadow entries is a little more complicated because we're using getent shadow on the remote host too.



join -t : -j 1 -o 2.1..2 
<(getent passwd | awk -F: '$3 > 1000 print $1' | sort)
<(getent shadow | sort) |
ssh remotehost 'umask 0027 &&
getent shadow > /etc/shadow.old &&
chgrp shadow /etc/shadow.old &&
chpasswd -e 2>/dev/null'


It pipes only the first two fields, the username and the encrypted password (output format is one "username:password" pair per line), into ssh. After making a backup copy of the old shadow file, the remote shell runs chpasswd to change the passwords as specified on stdin.



The -e option tells chpasswd that the passwords are already encrypted. Without that option, it would re-encrypt the supplied passwords.



chpasswd will complain on stderr about any usernames that don't exist on the remote system, but will still change the passwords for usernames that do exist. chpasswd's stderr can be redirected to /dev/null as i've shown above.



NOTE: It would be better to pipe stderr into a script that drops only the expected & harmless "username doesn't exist" errors while still displaying other errors. On my testing VM, the errors output by chpasswd for non-existent users look like this:



# printf '%sn' "foo:bar" "xyzzy:fool" | chpasswd
chpasswd: (user foo) pam_chauthtok() failed, error:
Authentication token manipulation error
chpasswd: (line 1, user foo) password not changed
chpasswd: (user xyzzy) pam_chauthtok() failed, error:
Authentication token manipulation error
chpasswd: (line 2, user xyzzy) password not changed





share|improve this answer






















  • Join your input to chpasswd with the first column from getent passwd to remove the "user not found" issue.
    – roaima
    Mar 10 at 13:43











  • yes, that would work. or exclude UIDs <= 1000 again. something like join -t : -j 1 -o 2.1..2 <(getent passwd | awk -F: '$3 > 1000 print $1' | sort) - | chpasswd -e as the last line.
    – cas
    Mar 10 at 23:38

















up vote
1
down vote













This will synchronise the entries in /etc/shadow from the local system to the remote system (here called remotehost) for all user accounts with UID > 1000 that exist on both systems:



getent passwd |
awk -F: '$3>1000 print $1' |
sort |
join -t : -j 1 -o 2.1..9 - <(getent shadow | sort) |
ssh remotehost '
cp -fp /etc/shadow /etc/shadow.old &&
join -t : -j 1 -o 1.1..9 - <(getent shadow | sort) |
awk -F: "!h[$1]++" - /etc/shadow >/etc/shadow.new &&
: cp -f /etc/shadow.new /etc/shadow
'


I strongly recommend that you split the command into pieces to see what it's doing at each stage of the pipeline, and that you DO NOT remove the no-op colon from the : cp on the last line until you are confident that it is working as you expect.



Essentially



  1. Extract a list of usernames from /etc/passwd with UID > 1000

  2. Use this list to extract the corresponding lines from /etc/shadow

  3. Copy across to the remote system

  4. Write out members of the new shadow list that exist in the current /etc/shadow

  5. Write out lines from the old /etc/shadow whose usernames have not already been output

  6. Save the original and new copies of shadow (in known places, for emergency rescue if needed)

  7. Install the resulting merged file as /etc/shadow





share|improve this answer






















  • +1. BTW, instead of using join, awk, sort, and cat on the remote host, you could extract the username:password pairs on the local machine and pipe them to chpasswd -e on the remote host. chpasswd will complain about unknown users (actually, PAM does the complaining), but still changes the usernames that exist. Optionally redirect stderr to /dev/null.
    – cas
    Mar 7 at 4:11











Your Answer







StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "106"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);








 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f428611%2fsync-password-hashes-of-users-uid1000-between-host-a-and-b%23new-answer', 'question_page');

);

Post as a guest






























2 Answers
2






active

oldest

votes








2 Answers
2






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
1
down vote













Based on @roaiama's use of the join command, this answer uses getent to get the passwd and shadow files rather than reading them directly, and then uses chpasswd on the remote host to change the passwords.



The password-changing code is simpler because of chpasswd, but making a backup copy of the old shadow entries is a little more complicated because we're using getent shadow on the remote host too.



join -t : -j 1 -o 2.1..2 
<(getent passwd | awk -F: '$3 > 1000 print $1' | sort)
<(getent shadow | sort) |
ssh remotehost 'umask 0027 &&
getent shadow > /etc/shadow.old &&
chgrp shadow /etc/shadow.old &&
chpasswd -e 2>/dev/null'


It pipes only the first two fields, the username and the encrypted password (output format is one "username:password" pair per line), into ssh. After making a backup copy of the old shadow file, the remote shell runs chpasswd to change the passwords as specified on stdin.



The -e option tells chpasswd that the passwords are already encrypted. Without that option, it would re-encrypt the supplied passwords.



chpasswd will complain on stderr about any usernames that don't exist on the remote system, but will still change the passwords for usernames that do exist. chpasswd's stderr can be redirected to /dev/null as i've shown above.



NOTE: It would be better to pipe stderr into a script that drops only the expected & harmless "username doesn't exist" errors while still displaying other errors. On my testing VM, the errors output by chpasswd for non-existent users look like this:



# printf '%sn' "foo:bar" "xyzzy:fool" | chpasswd
chpasswd: (user foo) pam_chauthtok() failed, error:
Authentication token manipulation error
chpasswd: (line 1, user foo) password not changed
chpasswd: (user xyzzy) pam_chauthtok() failed, error:
Authentication token manipulation error
chpasswd: (line 2, user xyzzy) password not changed





share|improve this answer






















  • Join your input to chpasswd with the first column from getent passwd to remove the "user not found" issue.
    – roaima
    Mar 10 at 13:43











  • yes, that would work. or exclude UIDs <= 1000 again. something like join -t : -j 1 -o 2.1..2 <(getent passwd | awk -F: '$3 > 1000 print $1' | sort) - | chpasswd -e as the last line.
    – cas
    Mar 10 at 23:38














up vote
1
down vote













Based on @roaiama's use of the join command, this answer uses getent to get the passwd and shadow files rather than reading them directly, and then uses chpasswd on the remote host to change the passwords.



The password-changing code is simpler because of chpasswd, but making a backup copy of the old shadow entries is a little more complicated because we're using getent shadow on the remote host too.



join -t : -j 1 -o 2.1..2 
<(getent passwd | awk -F: '$3 > 1000 print $1' | sort)
<(getent shadow | sort) |
ssh remotehost 'umask 0027 &&
getent shadow > /etc/shadow.old &&
chgrp shadow /etc/shadow.old &&
chpasswd -e 2>/dev/null'


It pipes only the first two fields, the username and the encrypted password (output format is one "username:password" pair per line), into ssh. After making a backup copy of the old shadow file, the remote shell runs chpasswd to change the passwords as specified on stdin.



The -e option tells chpasswd that the passwords are already encrypted. Without that option, it would re-encrypt the supplied passwords.



chpasswd will complain on stderr about any usernames that don't exist on the remote system, but will still change the passwords for usernames that do exist. chpasswd's stderr can be redirected to /dev/null as i've shown above.



NOTE: It would be better to pipe stderr into a script that drops only the expected & harmless "username doesn't exist" errors while still displaying other errors. On my testing VM, the errors output by chpasswd for non-existent users look like this:



# printf '%sn' "foo:bar" "xyzzy:fool" | chpasswd
chpasswd: (user foo) pam_chauthtok() failed, error:
Authentication token manipulation error
chpasswd: (line 1, user foo) password not changed
chpasswd: (user xyzzy) pam_chauthtok() failed, error:
Authentication token manipulation error
chpasswd: (line 2, user xyzzy) password not changed





share|improve this answer






















  • Join your input to chpasswd with the first column from getent passwd to remove the "user not found" issue.
    – roaima
    Mar 10 at 13:43











  • yes, that would work. or exclude UIDs <= 1000 again. something like join -t : -j 1 -o 2.1..2 <(getent passwd | awk -F: '$3 > 1000 print $1' | sort) - | chpasswd -e as the last line.
    – cas
    Mar 10 at 23:38












up vote
1
down vote










up vote
1
down vote









Based on @roaiama's use of the join command, this answer uses getent to get the passwd and shadow files rather than reading them directly, and then uses chpasswd on the remote host to change the passwords.



The password-changing code is simpler because of chpasswd, but making a backup copy of the old shadow entries is a little more complicated because we're using getent shadow on the remote host too.



join -t : -j 1 -o 2.1..2 
<(getent passwd | awk -F: '$3 > 1000 print $1' | sort)
<(getent shadow | sort) |
ssh remotehost 'umask 0027 &&
getent shadow > /etc/shadow.old &&
chgrp shadow /etc/shadow.old &&
chpasswd -e 2>/dev/null'


It pipes only the first two fields, the username and the encrypted password (output format is one "username:password" pair per line), into ssh. After making a backup copy of the old shadow file, the remote shell runs chpasswd to change the passwords as specified on stdin.



The -e option tells chpasswd that the passwords are already encrypted. Without that option, it would re-encrypt the supplied passwords.



chpasswd will complain on stderr about any usernames that don't exist on the remote system, but will still change the passwords for usernames that do exist. chpasswd's stderr can be redirected to /dev/null as i've shown above.



NOTE: It would be better to pipe stderr into a script that drops only the expected & harmless "username doesn't exist" errors while still displaying other errors. On my testing VM, the errors output by chpasswd for non-existent users look like this:



# printf '%sn' "foo:bar" "xyzzy:fool" | chpasswd
chpasswd: (user foo) pam_chauthtok() failed, error:
Authentication token manipulation error
chpasswd: (line 1, user foo) password not changed
chpasswd: (user xyzzy) pam_chauthtok() failed, error:
Authentication token manipulation error
chpasswd: (line 2, user xyzzy) password not changed





share|improve this answer














Based on @roaiama's use of the join command, this answer uses getent to get the passwd and shadow files rather than reading them directly, and then uses chpasswd on the remote host to change the passwords.



The password-changing code is simpler because of chpasswd, but making a backup copy of the old shadow entries is a little more complicated because we're using getent shadow on the remote host too.



join -t : -j 1 -o 2.1..2 
<(getent passwd | awk -F: '$3 > 1000 print $1' | sort)
<(getent shadow | sort) |
ssh remotehost 'umask 0027 &&
getent shadow > /etc/shadow.old &&
chgrp shadow /etc/shadow.old &&
chpasswd -e 2>/dev/null'


It pipes only the first two fields, the username and the encrypted password (output format is one "username:password" pair per line), into ssh. After making a backup copy of the old shadow file, the remote shell runs chpasswd to change the passwords as specified on stdin.



The -e option tells chpasswd that the passwords are already encrypted. Without that option, it would re-encrypt the supplied passwords.



chpasswd will complain on stderr about any usernames that don't exist on the remote system, but will still change the passwords for usernames that do exist. chpasswd's stderr can be redirected to /dev/null as i've shown above.



NOTE: It would be better to pipe stderr into a script that drops only the expected & harmless "username doesn't exist" errors while still displaying other errors. On my testing VM, the errors output by chpasswd for non-existent users look like this:



# printf '%sn' "foo:bar" "xyzzy:fool" | chpasswd
chpasswd: (user foo) pam_chauthtok() failed, error:
Authentication token manipulation error
chpasswd: (line 1, user foo) password not changed
chpasswd: (user xyzzy) pam_chauthtok() failed, error:
Authentication token manipulation error
chpasswd: (line 2, user xyzzy) password not changed






share|improve this answer














share|improve this answer



share|improve this answer








edited Mar 7 at 8:46

























answered Mar 7 at 5:00









cas

37.6k44392




37.6k44392











  • Join your input to chpasswd with the first column from getent passwd to remove the "user not found" issue.
    – roaima
    Mar 10 at 13:43











  • yes, that would work. or exclude UIDs <= 1000 again. something like join -t : -j 1 -o 2.1..2 <(getent passwd | awk -F: '$3 > 1000 print $1' | sort) - | chpasswd -e as the last line.
    – cas
    Mar 10 at 23:38
















  • Join your input to chpasswd with the first column from getent passwd to remove the "user not found" issue.
    – roaima
    Mar 10 at 13:43











  • yes, that would work. or exclude UIDs <= 1000 again. something like join -t : -j 1 -o 2.1..2 <(getent passwd | awk -F: '$3 > 1000 print $1' | sort) - | chpasswd -e as the last line.
    – cas
    Mar 10 at 23:38















Join your input to chpasswd with the first column from getent passwd to remove the "user not found" issue.
– roaima
Mar 10 at 13:43





Join your input to chpasswd with the first column from getent passwd to remove the "user not found" issue.
– roaima
Mar 10 at 13:43













yes, that would work. or exclude UIDs <= 1000 again. something like join -t : -j 1 -o 2.1..2 <(getent passwd | awk -F: '$3 > 1000 print $1' | sort) - | chpasswd -e as the last line.
– cas
Mar 10 at 23:38




yes, that would work. or exclude UIDs <= 1000 again. something like join -t : -j 1 -o 2.1..2 <(getent passwd | awk -F: '$3 > 1000 print $1' | sort) - | chpasswd -e as the last line.
– cas
Mar 10 at 23:38












up vote
1
down vote













This will synchronise the entries in /etc/shadow from the local system to the remote system (here called remotehost) for all user accounts with UID > 1000 that exist on both systems:



getent passwd |
awk -F: '$3>1000 print $1' |
sort |
join -t : -j 1 -o 2.1..9 - <(getent shadow | sort) |
ssh remotehost '
cp -fp /etc/shadow /etc/shadow.old &&
join -t : -j 1 -o 1.1..9 - <(getent shadow | sort) |
awk -F: "!h[$1]++" - /etc/shadow >/etc/shadow.new &&
: cp -f /etc/shadow.new /etc/shadow
'


I strongly recommend that you split the command into pieces to see what it's doing at each stage of the pipeline, and that you DO NOT remove the no-op colon from the : cp on the last line until you are confident that it is working as you expect.



Essentially



  1. Extract a list of usernames from /etc/passwd with UID > 1000

  2. Use this list to extract the corresponding lines from /etc/shadow

  3. Copy across to the remote system

  4. Write out members of the new shadow list that exist in the current /etc/shadow

  5. Write out lines from the old /etc/shadow whose usernames have not already been output

  6. Save the original and new copies of shadow (in known places, for emergency rescue if needed)

  7. Install the resulting merged file as /etc/shadow





share|improve this answer






















  • +1. BTW, instead of using join, awk, sort, and cat on the remote host, you could extract the username:password pairs on the local machine and pipe them to chpasswd -e on the remote host. chpasswd will complain about unknown users (actually, PAM does the complaining), but still changes the usernames that exist. Optionally redirect stderr to /dev/null.
    – cas
    Mar 7 at 4:11















up vote
1
down vote













This will synchronise the entries in /etc/shadow from the local system to the remote system (here called remotehost) for all user accounts with UID > 1000 that exist on both systems:



getent passwd |
awk -F: '$3>1000 print $1' |
sort |
join -t : -j 1 -o 2.1..9 - <(getent shadow | sort) |
ssh remotehost '
cp -fp /etc/shadow /etc/shadow.old &&
join -t : -j 1 -o 1.1..9 - <(getent shadow | sort) |
awk -F: "!h[$1]++" - /etc/shadow >/etc/shadow.new &&
: cp -f /etc/shadow.new /etc/shadow
'


I strongly recommend that you split the command into pieces to see what it's doing at each stage of the pipeline, and that you DO NOT remove the no-op colon from the : cp on the last line until you are confident that it is working as you expect.



Essentially



  1. Extract a list of usernames from /etc/passwd with UID > 1000

  2. Use this list to extract the corresponding lines from /etc/shadow

  3. Copy across to the remote system

  4. Write out members of the new shadow list that exist in the current /etc/shadow

  5. Write out lines from the old /etc/shadow whose usernames have not already been output

  6. Save the original and new copies of shadow (in known places, for emergency rescue if needed)

  7. Install the resulting merged file as /etc/shadow





share|improve this answer






















  • +1. BTW, instead of using join, awk, sort, and cat on the remote host, you could extract the username:password pairs on the local machine and pipe them to chpasswd -e on the remote host. chpasswd will complain about unknown users (actually, PAM does the complaining), but still changes the usernames that exist. Optionally redirect stderr to /dev/null.
    – cas
    Mar 7 at 4:11













up vote
1
down vote










up vote
1
down vote









This will synchronise the entries in /etc/shadow from the local system to the remote system (here called remotehost) for all user accounts with UID > 1000 that exist on both systems:



getent passwd |
awk -F: '$3>1000 print $1' |
sort |
join -t : -j 1 -o 2.1..9 - <(getent shadow | sort) |
ssh remotehost '
cp -fp /etc/shadow /etc/shadow.old &&
join -t : -j 1 -o 1.1..9 - <(getent shadow | sort) |
awk -F: "!h[$1]++" - /etc/shadow >/etc/shadow.new &&
: cp -f /etc/shadow.new /etc/shadow
'


I strongly recommend that you split the command into pieces to see what it's doing at each stage of the pipeline, and that you DO NOT remove the no-op colon from the : cp on the last line until you are confident that it is working as you expect.



Essentially



  1. Extract a list of usernames from /etc/passwd with UID > 1000

  2. Use this list to extract the corresponding lines from /etc/shadow

  3. Copy across to the remote system

  4. Write out members of the new shadow list that exist in the current /etc/shadow

  5. Write out lines from the old /etc/shadow whose usernames have not already been output

  6. Save the original and new copies of shadow (in known places, for emergency rescue if needed)

  7. Install the resulting merged file as /etc/shadow





share|improve this answer














This will synchronise the entries in /etc/shadow from the local system to the remote system (here called remotehost) for all user accounts with UID > 1000 that exist on both systems:



getent passwd |
awk -F: '$3>1000 print $1' |
sort |
join -t : -j 1 -o 2.1..9 - <(getent shadow | sort) |
ssh remotehost '
cp -fp /etc/shadow /etc/shadow.old &&
join -t : -j 1 -o 1.1..9 - <(getent shadow | sort) |
awk -F: "!h[$1]++" - /etc/shadow >/etc/shadow.new &&
: cp -f /etc/shadow.new /etc/shadow
'


I strongly recommend that you split the command into pieces to see what it's doing at each stage of the pipeline, and that you DO NOT remove the no-op colon from the : cp on the last line until you are confident that it is working as you expect.



Essentially



  1. Extract a list of usernames from /etc/passwd with UID > 1000

  2. Use this list to extract the corresponding lines from /etc/shadow

  3. Copy across to the remote system

  4. Write out members of the new shadow list that exist in the current /etc/shadow

  5. Write out lines from the old /etc/shadow whose usernames have not already been output

  6. Save the original and new copies of shadow (in known places, for emergency rescue if needed)

  7. Install the resulting merged file as /etc/shadow






share|improve this answer














share|improve this answer



share|improve this answer








edited Mar 11 at 0:39

























answered Mar 6 at 22:34









roaima

39.5k545107




39.5k545107











  • +1. BTW, instead of using join, awk, sort, and cat on the remote host, you could extract the username:password pairs on the local machine and pipe them to chpasswd -e on the remote host. chpasswd will complain about unknown users (actually, PAM does the complaining), but still changes the usernames that exist. Optionally redirect stderr to /dev/null.
    – cas
    Mar 7 at 4:11

















  • +1. BTW, instead of using join, awk, sort, and cat on the remote host, you could extract the username:password pairs on the local machine and pipe them to chpasswd -e on the remote host. chpasswd will complain about unknown users (actually, PAM does the complaining), but still changes the usernames that exist. Optionally redirect stderr to /dev/null.
    – cas
    Mar 7 at 4:11
















+1. BTW, instead of using join, awk, sort, and cat on the remote host, you could extract the username:password pairs on the local machine and pipe them to chpasswd -e on the remote host. chpasswd will complain about unknown users (actually, PAM does the complaining), but still changes the usernames that exist. Optionally redirect stderr to /dev/null.
– cas
Mar 7 at 4:11





+1. BTW, instead of using join, awk, sort, and cat on the remote host, you could extract the username:password pairs on the local machine and pipe them to chpasswd -e on the remote host. chpasswd will complain about unknown users (actually, PAM does the complaining), but still changes the usernames that exist. Optionally redirect stderr to /dev/null.
– cas
Mar 7 at 4:11













 

draft saved


draft discarded


























 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f428611%2fsync-password-hashes-of-users-uid1000-between-host-a-and-b%23new-answer', 'question_page');

);

Post as a guest













































































Popular posts from this blog

How to check contact read email or not when send email to Individual?

Bahrain

Postfix configuration issue with fips on centos 7; mailgun relay