User Tools

Site Tools


build:filter_passwd.pl
#!/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=<IN_SHADOW>))
  {

$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=<IN_NORMAL>))
  {

$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
#*******************************************************************************
build/filter_passwd.pl.txt · Last modified: 2007/06/13 17:25 by 206.197.251.70