How to Assign User-Account Name and Directory to Array in Bash/sh? - bash

I am attempting to create a bash script for the STIG test with the vulnerability ID V-72017 on a Red Hat Enterprise Linux (RHEL) system. I am tasked with making sure all user permissions have the octal value of 0750 or less
I have the ability to gather the permission octal value a user by using
stat -c "%a" /home/$username
I am trying to create a $username (or directory) array by utilizing the command (outputs name of each user on the system):
eval getent passwd {$(awk '/^UID_MIN/ {print $2}' /etc/login.defs)..$(awk '/^UID_MAX/ {print $2}' /etc/login.defs)} | cut -d: -f1
I plan to map this output to an array, possibly a while loop. Is this a possible solution?
Syntax Error from the following:
(eval getent passwd {$(awk '/^UID_MIN/ {print $2}' /etc/login.defs)..$(awk '/^UID_MAX/ {print $2}' /etc/login.defs)} | cut -d: -f1) | while read -r line
do
myarray+=line
stat -c "%a" /home/$line
done
Desired Output Case 1:
Users:
rob
bob
Exit Fail: bob has permission octal value 0755.
Desired Output Case 2:
Users:
rob
bob
Exit Pass: All users have permission octal value of 0750 or less.

You have found all login users. Regexp can be used to check home dir's permissions.
echo "Users: "
(eval getent passwd {$(awk '/^UID_MIN/ {print $2}' /etc/login.defs)..$(awk '/^UID_MAX/ {print $2}' /etc/login.defs)} | cut -d: -f1) | while read -r line
do
echo $line
perm=$(stat -c "%a" /home/$line)
[[ "$perm" =~ [0-7][0,1,4,5][0] ]] || echo "Exit fail: $line has permission octal value $perm"
done
Maybe you want to adjust the output form.

It is suggested to avoid using eval as much as possible. All the more
if you are investigating the system security status. Please try the
following instead:
#!/bin/bash
perm=0750 # system policy
uid_min=$(sed -n '/^UID_MIN/ s/[^0-9]*\([0-9]\+\).*/\1/p' "/etc/login.defs")
uid_max=$(sed -n '/^UID_MAX/ s/[^0-9]*\([0-9]\+\).*/\1/p' "/etc/login.defs")
# read /etc/passwd and process line by line
while IFS=: read -ra a; do
# now ${a[0]} holds username and ${a[2]} holds uid
if (( ${a[2]} >= uid_min && ${a[2]} <= uid_max )); then
# narrow down the users whose uid is within the range
users+=("${a[0]}")
# check the user's permission
userperm="0$(stat -c "%a" "/home/${a[0]}")"
if (( (~ perm) & userperm )); then
# the user's permission exceeds the limitation $perm
fail+=("$(printf "%s has permission octal value 0%o." "${a[0]}" "$userperm")")
fi
fi
done < "/etc/passwd"
echo "Users:"
for i in "${users[#]}"; do
echo "$i"
done
if (( ${#fail[#]} == 0 )); then
printf "Exit Pass: All users have permission octal value of 0%o or less.\n" "$perm"
else
for i in "${fail[#]}"; do
printf "Exit Fail: %s\n" "$i"
done
fi
Hope this helps.

Related

my script deletes user accounts that have not been logged in for more than 30 days, in fact it works, but it deletes some system accounts

It works, but also deletes some system accounts...i don't know why
#!/bin/bash
# This script takes everyone with id>1000 from /etc/passwd and removes every user account in case if it hasn't been used for the last 30 days.
# Make sure that script is being executed with root priviligies.
if [[ "${UID}" -ne 0 ]]
then
echo "You should run this script as a root!"
exit 1
fi
# First of all we need to know id limit (min & max)
USER_MIN=$(grep "^UID_MIN" /etc/login.defs)
USER_MAX=$(grep "^UID_MAX" /etc/login.defs)
# Print all users accounts with id>=1000 and <=6000 (default).
awk -F':' -v "min=${USER_MIN##UID_MIN}" -v "max=${USER_MAX##UID_MAX}" ' { if ( $3 >= min && $3 <= max ) print $0}' /etc/passwd
# This function deletes users which hasn't log in in the last 30 days
# Make a color output message
for accounts in ` lastlog -b 30 | sed "1d" | awk ' { print $1 } '`
do
userdel $accounts 2>/dev/null
done
echo -e "\e[36mYou have successfully deleted all user's account which nobody logged in in the past 30 days.\e[0,"
exit 0
You do not apply the awk -F':' -v "min=${USER_MIN##UID_MIN}" -v "max=${USER_MAX##UID_MAX}" ' { if ( $3 >= min && $3 <= max ) print $0}' /etc/passwd filter to lastlog output....
Join the list from lastlog with the list of users from /etc/passwd and filter UID with proper number range.
lastlog -b30 | sed 1d | awk '{print $1}' |
sort | join -t: -11 -21 -o2.1,2.3 - <(sort /etc/passwd) |
awk -F: -v "min=${USER_MIN##UID_MIN}" -v "max=${USER_MAX##UID_MAX}" '$2 >= min && $2 <= max {print $1}' |
xargs -n1 echo userdel
Notes:
Do not use backticks `. Use $(...) instead. Obsolete and deprecated syntax
how to read a stream line by line in bash bashfaq
With bash and GNU grep:
#!/bin/bash
# I left out the check for root user
# get only the numbers from each line
# \K removes the matching part before \K (from left to right)
USER_MIN=$(grep -Po "^UID_MIN *\K.*" /etc/login.defs)
USER_MAX=$(grep -Po "^UID_MAX *\K.*" /etc/login.defs)
# create an associative array, see: help declare
declare -A users
# read all users with its uid to this associative array
while IFS=":" read -r user x uid x; do users[$user]="$uid"; done </etc/passwd
# see output of: declare -p users
# remove all unwanted lines including headline. NR is line number
lastlog -b 30 | awk '! /Never logged in/ && NR>1 {print $1}' |
while read -r user; do
# -ge: greater-than-or-equal
# -le: less-than-or-equal
if [[ ${users[$user]} -ge $USER_MIN ]] && [[ ${users[$user]} -le $USER_MAX ]]; then
echo "delete user $user with uid ${users[$user]}"
# add your code here to delete user $user
fi
done

Reading file in while loop bash scripting

I've got this code which reads an example file of /etc/passwd:
#!/bin/bash
OLDIFS=$IFS
IFS=$'\n'
while read linea resto
do
echo $linea
echo $resto
if [[ $(echo $linea | cut -d: -f6 | egrep -c 'al-03-04') == 1 ]]
then
finger $(cut -d: -f1) 2> fich
if [[ $(egrep -c fich) == 1 ]]
then
echo $(echo $linea | cut -d: -f1). Inactive user
else
echo $(echo $linea | cut -d: -f1). Active user
fi
fi
done < <(cat fichpasswd)
IFS=$OLDIFS
and this is the example file of /etc/passwd:
jfer:x:5214:1007:Javier Lopez,,,:/home/al-03-04/jfer:/bin/bash
jperez:x:10912:1009:Juan Perez,,,:/home/al-03-04/jperez:/bin/bash
mfernan:x:10913:1009:Manuel Fernandez,,,:/home/al-02-03/mfernan:/bin/bash
The problem is that the while loop only reads the first line, ignoring the others. The script's output is:
jfer:x:5214:1007:Javier Lopez,,,:/home/al-03-04/jfer:/bin/bash
jfer. Active user
You could try something like :
#!/bin/bash
FILE="test.txt"
while IFS=":" read -a data; do
echo "${data[#]}"
if [[ $(echo ${data[5]}|egrep -c 'al-03-04') -eq 1 ]]; then
if [[ $(finger "${data[0]}" 2>&1) =~ "no such user" ]]; then
echo "${data[0]}. Inactive user"
else
echo "${data[0]}. Active user"
fi
fi
done < "$FILE"
Here's the output :
ineumann ~ $ cat test.txt
ineumann:x:5214:1007:Javier Lopez,,,:/home/al-03-04/jfer:/bin/bash
jperez:x:10912:1009:Juan Perez,,,:/home/al-03-04/jperez:/bin/bash
mfernan:x:10913:1009:Manuel Fernandez,,,:/home/al-02-03/mfernan:/bin/bash
ineumann ~ $ ./test.sh
ineumann x 5214 1007 Javier Lopez,,, /home/al-03-04/jfer /bin/bash
ineumann. Active user
jperez x 10912 1009 Juan Perez,,, /home/al-03-04/jperez /bin/bash
jperez. Inactive user
mfernan x 10913 1009 Manuel Fernandez,,, /home/al-02-03/mfernan /bin/bash
A few comments on your script :
No need to use cat to read your file in a loop.
finger $(cut -d: -f1) 2> fich : cut need an input. And no need to use a temporary file to catch the output of finger (moreover this is not thread safe).
No need to use cut in your script when you choose the right IFS to split a line in multiple parts. In your case, I think the smartest choice would be :.
You can change the IFS only inside the loop with the syntax while IFS=':' read; do ...; done. No need to re-assign IFS with OLDIFS.
You can also use the while IFS=':' read var1 var2 var3 trash; do ...; done syntax to avoid to use an array with read -a (but I'd prefer to use an array as I wrote in my version of your script).

Shell Script : Assign the outputs to different variables

In a shell script I need to assign the output of few values to different varialbes, need help please.
cat file1.txt
uid: user1
cn: User One
employeenumber: 1234567
absJobAction: HIRED
I need to assign the value of each attribute to different variables so that I can call them them in script. For example uid should be assigned to a new variable name current_uid and when $current_uid is called it should give user1 and so forth for all other attributes.
And if the output does not contain any of the attributes then that attribute value should be considered as "NULL". Example if the output does not have absJobAction then the value of $absJobAction should be "NULL"
This is what I did with my array
#!/bin/bash
IFS=$'\n'
array=($(cat /tmp/file1.txt | egrep -i '^uid:|^cn:|^employeenumber|^absJobAction'))
current_uid=`echo ${array[0]} | grep -w uid | awk -F ': ' '{print $2}'`
current_cn=`echo ${array[1]} | grep -w cn | awk -F ': ' '{print $2}'`
current_employeenumber=`echo ${array[2]} | grep -w employeenumber | awk -F ': ' '{print $2}'`
current_absJobAction=`echo ${array[3]} | grep -w absJobAction | awk -F ': ' '{print $2}'`
echo $current_uid
echo $current_cn
echo $current_employeenumber
echo $current_absJobAction
Output from sh /tmp/testscript.sh follows:
user1
User One
1234567
HIRED
#!/usr/bin/env bash
# assuming bash 4.0 or newer: create an associative array
declare -A vars=( )
while IFS= read -r line; do ## See http://mywiki.wooledge.org/BashFAQ/001
if [[ $line = *": "* ]]; then ## skip lines not containing ": "
key=${line%%": "*} ## strip everything after ": " for key
value=${line#*": "} ## strip everything before ": " for value
vars[$key]=$value
else
printf 'Skipping unrecognized line: <%s>\n' "$line" >&2
fi
done <file1.txt # or < <(ldapsearch ...)
# print all variables read, just to demonstrate
declare -p vars >&2
# extract and print a single variable by name
echo "Variable uid has value ${vars[uid]}"
Note that this must be run with bash yourscript, not sh yourscript.
By the way -- if you don't have bash 4.0, you might consider a different approach:
while IFS= read -r line; do
if [[ $line = *": "* ]]; then
key=${line%%": "*}
value=${line#*": "}
printf -v "ldap_$key" %s "$value"
fi
done <file1.txt # or < <(ldapsearch ...)
will create separate variables of the form "$ldap_cn" or "$ldap_uid", as opposed to putting everything in a single associative array.
Here's a simple example of what you are trying to do that should get you started. It assumes 1 set of data in the file. Although a tad brute-force, I believe its easy to understand.
Given a file called file.txt in the current directory with the following contents (absJobAction intentionally left out):
$ cat file1.txt
uid: user1
cn: User One
employeenumber: 1234567
$
This script gets each value into a local variable and prints it out:
# Use /bin/bash to run this script
#!/bin/bash
# Make SOURCEFILE a readonly variable. Make it uppercase to show its a constant. This is the file the LDAP values come from.
typeset -r SOURCEFILE=./file1.txt
# Each line sets a variable using awk.
# -F is the field delimiter. It's a colon and a space.
# Next is the value to look for. ^ matches the start of the line.
# When the above is found, return the second field ($2)
current_uid="$(awk -F': ' '/^uid/ {print $2}' ${SOURCEFILE})"
current_cn="$(awk -F': ' '/^cn/ {print $2}' ${SOURCEFILE})"
current_enbr="$(awk -F': ' '/^employeenumber/ {print $2}' ${SOURCEFILE})"
current_absja="$(awk -F': ' '/^absJobAction/ {print $2}' ${SOURCEFILE})"
# Print the contents of the variables. Note since absJobAction was not in the file,
# it's value is NULL.
echo "uid: ${current_uid}"
echo "cn: ${current_cn}"
echo "EmployeeNumber: ${current_enbr}"
echo "absJobAction: ${current_absja}"
~
When run:
$ ./test.sh
uid: user1
cn: User One
EmployeeNumber: 1234567
absJobAction:
$

Why does my bash script hang?

I'm working on a bash script that will check +1000 domains if they are expired. I use a a for loop to iterate over all users in /var/cpanel/users/*. It works great for like the 10 first users (loops) then it just hangs.
A weird thing is that I can stop the script with Ctrl+Z and then start the script again with fg and it continues to work normal for about +10 users but then it hangs again.
This is my scirpt:
# File that will have the result.
file="domain-result.txt"
printf "USER\t\tDOMAIN\t\t\tREPORT\n" > "$file"
printf "\n" >> "$file"
# For loop to iterate over all users in cpanel.
for z in /var/cpanel/users/*;
do
# Only files can be used.
if [[ -f "$z" ]]
then
# Get the domain name.
awk -F'=' '/DNS=/ {print $2}' "$z" | while read row;
do
# If there's no domain name than skip to next account.
if [[ -z "$row" ]]; then continue; fi
printf "Checking domain: %s...done\n" "$row"
# Execute whois command on the domain.
whois=$( /usr/bin/whois $row | grep 'not found' )
# Get the username.
user=$( echo "$z" | awk -F'/' '{print $5}' )
if [[ -n "$whois" ]]
then
printf "%s\t\t%s\t\t%s - EXPIRED\n" "$user" "$row" "$whois" >> "$file"
break
else
continue
fi
done
else
continue
fi
done
printf "\n"
printf "Total: $( sed '1,2d' "$file" | wc -l ) expired domains.\n"
This is a sample of how the files in /var/cpanel/users/* look like:
DNS=stackoverflow.com
Thank you Ignacio Vazquez-Abrams for pointing out WHOIS abuse. I got it to work by adding a sleep 2 to the for loop. Now it works great.

bash script and greping with command line

new to bash scripting so just wondering if i am doing this code right at all. im trying to search /etc/passwd and then grep and print users.
usage ()
{
echo "usage: ./file.sk user"
}
# test if we have two arguments on the command line
if [ $# != 1 ]
then
usage
exit
fi
if [[ $# < 0 ]];then
usage
exit
fi
# Search for user
fullname=`grep $1 /etc/passwd | cut -f 5 -d :`
firstname=`grep $1 /etc/passwd | cut -f 5 -d : | cut -f 1 -d " "`
#check if there. if name is founf: print msg and line entry
not sure as how to this or if im doing this right...
am i doing this right?
grep $1 /etc/passwd | while IFS=: read -r username passwd uid gid info home shell
do
echo $username: $info
done
This might work for you:
fullname=$(awk -F: '/'$1'/{print $5}' /etc/passwd)
firstname=${fullname/ *}
You're on the right track.
But I think the 2nd if [[ $# < 0 ]] .... fi block doesn't get you much. Your first test case gets the situation right, 'This script requires 1 argument or quits'.
Also, I don't see what you need firstname for, so a basic test is
case "${fullname:--1}" in
-[1] ) printf "No userID found for input=$1\n" ; exit 1 ;;
* )
# assume it is OK
# do what every you want after this case block
;;
esac
You can of course, duplicate this using "${firstname}" if you really need the check.
OR as an equivalent if ... fi is
if [[ "${fullname}" == "" ]] ; then
printf "No userID found for input=$1\n" ; exit 1
fi
note to be more efficient, you can parse ${fullname} to get firstname without all the calls to grep etc, i.e.
firstname=${fullname%% *}
Let me know if you need for me to explain :--1} and %% *} variable modifiers.
I hope this helps.
Instead of this:
fullname=`grep $1 /etc/passwd | cut -f 5 -d :`
firstname=`grep $1 /etc/passwd | cut -f 5 -d : | cut -f 1 -d " "`
Try this:
fullname=$(cut -f5 -d: /etc/passwd | grep "$1")
if [[ $? -ne 0 ]]; then
# not found, do something
fi
firstname=${fullname%% *} # remove the space and everything after
Note that I changed my answer to cut before grep so that it doesn't get false positives if some other field matches the full name you are searching for.
You can simply by reading your input to an array and then printing out your desired fields, something like this -
grep $1 /etc/passwd | while IFS=: read -a arry; do
echo ${arry[0]}:${arry[4]};
done
Test:
jaypal:~/Temp] echo "root:*:0:0:System Administrator:/var/root:/bin/sh" |
while IFS=: read -a arry; do
echo ${arry[0]}:${arry[4]};
done
root:System Administrator

Resources