#!/usr/bin/perl -w #******************************************************************************* # * # Program to combine the regular and shadow password files on AIX and generate * # matching files for Linux. * # * # Limitations, problems, and shortcomings: * # - The shadow file must already have been translated from multiline stanza * # format to one line per stanza by program odm-comb.pl * # - The output is to temporary normal and shadow passwd files to be merged * # into the system files. There is no check to see if the accounts already * # exist in the system files. * # - Other files in the /etc/security directory are not being processed since * # there doesn't seem to be any data we need: * # environ We haven't set any non-default values. * # lastlog Info on last successful and unsuccessful logins and count. * # limits We haven't set any non-default limits. * # user Has account expiration status, privileges, etc. * # - Not bothering to check that the GID is numeric. * # - Not bothering to check the length of the password. * # * #******************************************************************************* #******************************************************************************* # Misc setup #******************************************************************************* use strict; use English; use File::Basename; $PROGRAM_NAME=basename($PROGRAM_NAME); #******************************************************************************* # Process command line options #******************************************************************************* # Haven't implemented command line options. Using hardcoded values instead. my ($test_file,$test_acct_suffix,$test_home_suffix,$verbose)=("","","",0); #$test_file=".test"; #### Testing, use this suffix on input and output files #$test_acct_suffix="aix"; #### Testing, add suffix to account names #$test_home_suffix="aix"; #### Testing, add suffix to home directory names $verbose=1; #### Testing, more verbose messages #my $want_group=25; # Only accounts with this primary group my $want_group=11025; # Only accounts with this primary group # Don't process any accounts with these names my @exclude_name=qw( spamd ); my $work_base="/home/mk/xfer/user_mig"; # On michelob/dark my $in_data_dir=$work_base."/michelob"; # On michelob/dark #my $work_base="/work1/incoming/sluug/new/user_mig"; #### Testing at home #my $in_data_dir=$work_base."/aix/061027/michelob"; #### Testing at home my $out_data_dir=$work_base."/xlated"; my $in_normal_file_name=$in_data_dir."/passwd".$test_file; my $in_shadow_file_name=$in_data_dir."/security-passwd-comb".$test_file; my $out_normal_file_name=$out_data_dir."/passwd-additions".$test_file; my $out_shadow_file_name=$out_data_dir."/shadow-additions".$test_file; my $out_home_dir_file_name=$out_data_dir."/home_dir-additions".$test_file; #******************************************************************************* my $make_home_dir_script=<<'end_make_home_dir_script'; #!/bin/sh # Create empty home directories for accounts moved from the old system. PATH="${work_base}:$PATH" end_make_home_dir_script #******************************************************************************* #******************************************************************************* my $total_normal_in_lines = 0; # Grand total normal file lines input my $total_normal_out_lines = 0; # Grand total normal file lines output my $total_shadow_in_lines = 0; # Grand total shadow file lines input my $total_shadow_out_lines = 0; # Grand total shadow file lines output my $in_line; my $out_line; my ($key,$rest); my (@keyword_values,$keyword_value,$keyword,$value); my $new_shell; my $msg_keyword; my @rest; my @keep_keys=();; my $lastupdate_days; my ($out_normal_line1,$out_normal_line2); my $out_password; # Work around to allow symbolic references while still using strict. # Another way would have been to replace these variables with hash # keys having these names. #my ($pw_name,$pw_passwd,$pw_uid,$pw_gid,$pw_gecos,$pw_dir,$pw_shell); no strict("vars"); # Allow definition of local variables for later symbolic refs local ($pw_name,$pw_passwd,$pw_uid,$pw_gid,$pw_gecos,$pw_dir,$pw_shell); use vars qw($pw_name $pw_passwd $pw_uid $pw_gid $pw_gecos $pw_dir $pw_shell); use strict; # Turn them all on again my %normal_line=(); my %normal_val=(); my %shadow_val=(); #******************************************************************************* # End of setup #******************************************************************************* #******************************************************************************* # Start of input from shadow file #******************************************************************************* open(IN_SHADOW,$in_shadow_file_name) or die("Open of input file $in_shadow_file_name failed with \"$!\""); shadow_line: while (defined($in_line=)) { $total_shadow_in_lines++; # Count of lines input chomp($in_line); # strip record separator # printf STDERR "%s\n",$in_line; ######## Debug ############ Skip non-data lines if ($in_line =~ /^\#/) { next shadow_line; } if ($in_line =~ /^\s*$/) { next shadow_line; } #------------------------------------------------------------------------------- # Parse and store lines of interest from shadow file #------------------------------------------------------------------------------- #printf STDERR "line=\"%s\"\n", $in_line; ######## Debug #--------- Extract the key if ($in_line=~ m@^(\S+):\s*(\S{0,1}.*)$@) { $key=$1; $rest=$2; #printf STDERR "key=\"%s\" rest=\"%s\"\n", $key,$rest; ######## Debug } else { printf STDERR "Unable to find key on shadow line %3d, skipped\n", $total_shadow_in_lines; next shadow_line; } if (exists($shadow_val{$key})) { printf STDERR "Duplicate account name \"%s\" on shadow line %3d, skipped\n", $key,$total_shadow_in_lines; next shadow_line; } #--------- Extract each keyword and value pair @keyword_values=split(/\s+/,$rest); foreach $keyword_value (@keyword_values) { $keyword_value=~s/%1/ /g; # Change all %1 to blanks $keyword_value=~s/%2/\t/g; # Change all %2 to tabs $keyword_value=~s/%3/%/g; # Change all %3 to % #printf STDERR "keyword_value=\"%s\"\n", $keyword_value; ######## Debug if ($keyword_value=~ m@^([^=\s]*)\s*=\s*(\S{0,1}.*)\s*$@) { $keyword=$1; $value=$2; #printf STDERR "keyword=\"%s\" value=\"%s\"\n", $keyword,$value; ######## Debug if (exists($shadow_val{$key}{$keyword})) { printf STDERR "Duplicate keyword \"%s\" for account %s on shadow line %3d, skipped\n", $keyword,$key,$total_shadow_in_lines; next; } $shadow_val{$key}{$keyword}=$value; } else { printf STDERR "Unable to decode \"%s\" on shadow line %3d\n", $keyword_value,$total_shadow_in_lines; } } #------------------------------------------------------------------------------- # End of input from shadow file #------------------------------------------------------------------------------- #print STDERR "Bottom of loop\n"; ######## Debug } # print STDERR "Exited loop\n"; ######## Debug close(IN_SHADOW) or die("Close of input file $in_shadow_file_name failed with \"$!\""); # Dump the hash for debugging #for $key (sort keys %shadow_val) #### Debug # { #### Debug # printf STDERR "\n%s:\n",$key; #### Debug # for $keyword (sort keys %{$shadow_val{$key}}) #### Debug # { #### Debug # printf STDERR "\t%s = %s\n",$keyword,$shadow_val{$key}{$keyword}; #### Debug # } #### Debug # } #### Debug #******************************************************************************* # Start of input from normal file #******************************************************************************* open(IN_NORMAL,$in_normal_file_name) or die("Open of input file $in_normal_file_name failed with \"$!\""); normal_line: while (defined($in_line=)) { $total_normal_in_lines++; # Count of lines input chomp($in_line); # strip record separator # printf STDERR "%s\n",$in_line; ######## Debug ############ Skip non-data lines if ($in_line =~ /^\#/) { next normal_line; } if ($in_line =~ /^\s*$/) { next normal_line; } #------------------------------------------------------------------------------- # Parse and store lines of interest from normal file #------------------------------------------------------------------------------- #printf STDERR "line=\"%s\"\n", $in_line; ######## Debug #--------- Split into fields ( $pw_name, $pw_passwd, $pw_uid, $pw_gid, $pw_gecos, $pw_dir, $pw_shell, @rest )=split(/:/,$in_line); #--------- Check the validity of various required fields unless (defined($pw_name) && $pw_name) { printf STDERR "Unable to find account name on passwd line %3d, skipped\n", $total_normal_in_lines; next normal_line } if (exists($normal_line{$pw_name})) { printf STDERR "Duplicate account name \"%s\" on passwd line %3d, skipped\n", $key,$total_normal_in_lines; next normal_line } unless (defined($pw_uid) && $pw_uid=~m@^\d{1,10}$@) { printf STDERR "Unable to find valid UID on passwd line %3d, skipped\n", $total_normal_in_lines; next normal_line } if (scalar(@rest)) { printf STDERR "Warning: Extra values on passwd line %3d: %s\n", $total_normal_in_lines,join(":",@rest); } #--------- Save the data to a hash keyed by the account name $key=$pw_name; $normal_line{$key}=$in_line; $normal_val{$key}{pw_name}=$pw_name; $normal_val{$key}{pw_uid}=$pw_uid; foreach $keyword (qw(pw_passwd pw_gid pw_gecos pw_dir pw_shell)) { no strict("refs"); # Allow use of symbolic references if (${$keyword}) { $normal_val{$key}{$keyword}=${$keyword}; #printf STDERR "keyword=\"%s\" value=\"%s\"\n", $keyword,${$keyword}; ######## Debug } else { $normal_val{$key}{$keyword}=""; $msg_keyword=$keyword; $msg_keyword=~ s/^pw_//; printf STDERR "Warning: No value for %s on passwd line %3d for account %s\n", $msg_keyword,$total_normal_in_lines,$pw_name; } use strict; # Turn them all on again } #------------------------------------------------------------------------------- # End of input from normal file #------------------------------------------------------------------------------- # print STDERR "Bottom of loop\n"; ######## Debug } # print STDERR "Exited loop\n"; ######## Debug close(IN_NORMAL) or die("Close of input file $in_normal_file_name failed with \"$!\""); # Dump the hash for debugging #for $key (sort keys %normal_val) #### Debug # { #### Debug # printf STDERR "\n%s:\n",$key; #### Debug # for $keyword (sort keys %{$normal_val{$key}}) #### Debug # { #### Debug # $msg_keyword=$keyword; #### Debug # $msg_keyword=~ s/^pw_//; #### Debug # printf STDERR "\t%s = %s\n",$msg_keyword,$normal_val{$key}{$keyword}; #### Debug # } #### Debug # } #### Debug #******************************************************************************* # Validate that all shadow entries have a matching entry in the normal file #******************************************************************************* #******************************************************************************* # Validate that all accounts have a password in the shadow and ! in normal #******************************************************************************* #******************************************************************************* # Filter of account names #******************************************************************************* #@keep_keys=keys(%normal_line); account_filter: for $key (keys(%normal_line)) { # No locked accounts if ($normal_val{$key}{pw_name}=~m/^LCK/) { printf STDERR "Skipping account %-10s since locked\n",$key if $verbose; next account_filter; } # No accounts in the list to specifically exclude for $pw_name (@exclude_name) { if ($normal_val{$key}{pw_name} eq $pw_name) { printf STDERR "Skipping account %-10s since in exclusion list\n",$key if $verbose; next account_filter; } } # Only those in group 25 if ($normal_val{$key}{pw_gid} != $want_group) { printf STDERR "Skipping account %-10s since not group %d\n",$key,$want_group if $verbose; next account_filter; } printf STDERR "Adding account %-10s\n",$key if $verbose; push(@keep_keys,$key); } # End of for $key (keys(%normal_line)) #******************************************************************************* # Output the two new files #******************************************************************************* open(OUT_NORMAL,">".$out_normal_file_name) or die("Open of output file $out_normal_file_name failed with \"$!\""); open(OUT_SHADOW,">".$out_shadow_file_name) or die("Open of output file $out_shadow_file_name failed with \"$!\""); open(OUT_HOME_DIR,">".$out_home_dir_file_name) or die("Open of output file $out_home_dir_file_name failed with \"$!\""); print OUT_HOME_DIR $make_home_dir_script; #------------------------------------------------------------------------------- # Loop through accounts to output #------------------------------------------------------------------------------- for $key (sort @keep_keys) { $new_shell=$normal_val{$key}{pw_shell}; # Generate the normal passwd entry using the unparsed version $out_normal_line1=$normal_line{$key}; $out_normal_line1=~ s@^([^:]+):@${1}${test_acct_suffix}:@; # Add suffix to account $out_normal_line1=~ s@^((?:[^:]+:){5})([^:]+):@${1}$2${test_home_suffix}:@; # Add suffix to home dir $out_normal_line1=~ s@^([^:]+):!:@${1}:x:@; # Change flag for shadow password # Translate shell to only reference /bin instead of /usr/bin or /usr/local/bin $new_shell =~s@^(?:/usr/local|/usr)/bin/([^:]+)$@/bin/$1@; $out_normal_line1=~s@:(?:/usr/local|/usr)/bin/([^:]+)$@:/bin/$1@; # Generate the normal passwd entry using the parsed version $out_normal_line2=join(":", $normal_val{$key}{pw_name}.$test_acct_suffix, "x", $normal_val{$key}{pw_uid}, $normal_val{$key}{pw_gid}, $normal_val{$key}{pw_gecos}, $normal_val{$key}{pw_dir}.$test_home_suffix, $new_shell ); # Verify the normal passwd entry generated two ways if ($out_normal_line1 eq $out_normal_line2) { } else { printf STDERR "Mismatch generating new passwd entry for account %s, using Try 2\n", $key; printf STDERR "Try 1=\"%s\"\n",$out_normal_line1; printf STDERR "Try 2=\"%s\"\n",$out_normal_line2; } # Verify there is an shadow password # Not done when reading AIX shadow passwd file since # just stuffing keywords into array. if ( defined($shadow_val{$key}{password}) && $shadow_val{$key}{password} ) { $out_password=$shadow_val{$key}{password}; } else { printf STDERR "No shadow password for account %s, using non-shadow password\n", $key; $out_password=$normal_val{$key}{pw_passwd}; } # Translate password change time from time stamp to days if ( defined($shadow_val{$key}{lastupdate}) && $shadow_val{$key}{lastupdate} ) { if ($shadow_val{$key}{lastupdate}=~m@^\d{1,12}$@) { $lastupdate_days=int($shadow_val{$key}{lastupdate}/(60*60*24)); } else { printf STDERR "Invalid password change time stamp \"%s\" for account %s ignored\n", $shadow_val{$key}{lastupdate},$key; $lastupdate_days=""; } } else { $lastupdate_days=""; } # Output the normal passwd entry printf OUT_NORMAL "%s\n",$out_normal_line2; $total_normal_out_lines++; # Count of lines output # Output the shadow passwd entry printf OUT_SHADOW "%s:%s:%s:%s:%s:%s:%s:%s:%s\n", $normal_val{$key}{pw_name}.$test_acct_suffix, # Account name $out_password, # password $lastupdate_days, # lastupdate changed from time stamp to days "0", # minage changed from weeks to days "99999", # maxage changed from weeks to days "7", # pwdwarntime "", # maxexpired changed from weeks to days "", # expires changed from time stamp to days ""; # Reserved $total_shadow_out_lines++; # Count of lines output # Verify there is a home directory in the password file. # Write call to "make_home_dir" to create a new one if needed. if ( defined($normal_val{$key}{pw_dir}) && $normal_val{$key}{pw_dir} ) { printf OUT_HOME_DIR "make_home_dir %-15s %-20s %6s %6s\n", $normal_val{$key}{pw_name}.$test_acct_suffix, $normal_val{$key}{pw_dir}.$test_home_suffix, $normal_val{$key}{pw_uid}, $normal_val{$key}{pw_gid}; } else { printf STDERR "No home directory in the passsword file for account %s, can't create it\n", $key; $out_password=""; } } # End of for $key (sort @keep_keys) #------------------------------------------------------------------------------- # End of output #------------------------------------------------------------------------------- close(OUT_NORMAL) or die("Close of output file $out_normal_file_name failed with \"$!\""); close(OUT_SHADOW) or die("Close of output file $out_shadow_file_name failed with \"$!\""); close(OUT_HOME_DIR) or die("Close of output file $out_home_dir_file_name failed with \"$!\""); #******************************************************************************* # Start of end of file processing #******************************************************************************* printf STDERR "\n"; printf STDERR "Total normal file lines input=%d\n", $total_normal_in_lines; printf STDERR "Total shadow file lines input=%d\n", $total_shadow_in_lines; printf STDERR "Total normal file lines output=%d\n", $total_normal_out_lines; printf STDERR "Total shadow file lines output=%d\n", $total_shadow_out_lines; printf STDERR "\nCounts exclude commands and blank lines.\n"; #******************************************************************************* # End of end of file processing #*******************************************************************************