Proper sysadminning

As part of my duties as owner and sysadmin of dustpuppy.excelsiorlodge.org (soon to be dustpuppy.whatever.org) now dustpuppy.lakemasoniccenter.org (and dustpuppy.firstlutheranelca.org, and dustpuppy.flippingcoinsband.com), part of my duty is to ensure that the system is stable and secure so my hosting clients have access to their websites and junk. As part of that, I’ve been going back and rewriting some of the scripts I remember writing for the USAF Reserve back when I sysadminned the 3B2. I wish I could get my hands on a 3B2 again. Or even an AT&T RISC emulator. I seriously adored that old 22MHz behemoth.

Anyway, here are some of the administrative scripts I’ve written for the system so far. Mainly for the aid of aspiring UNIX sysadmins 🙂

checkpasswd.sh

#!/bin/bash
#
# Checks permissions on /etc/passwd and friends
#

cd /etc
READ_TESTS="passwd group shadow gshadow"
for file in $READ_TESTS
do
        test -r $file || mail -s "/etc/${file} not readable" root@dustpuppy << EOF
/etc/${file} not readable at `date`
Permissions:
`ls -laZ $file`

EOF
        test -O $file || mail -s "/etc/${file} not owned by root" root@dustpuppy << EOF
/etc/${file} not owned by root at `date`
Permissions:
`ls -laZ $file`

EOF

        test -G $file || mail -s "/etc/${file} not group owned by root" root@dustpuppy << EOF
/etc/${file} not group owned by root at `date`
Permissions:
`ls -laZ $file`

EOF

done

Explanation:

Line 1: Tells the shell that this script is executable using the /bin/bash command. Although there’s nothing in here that couldn’t be used with any Bourne-compatible shell, e.g. Dash, ksh, zsh, etc.

Lines 2-4: Comments explaining the script

Line 6: Change to the /etc directory, where system config files live.

Line 7: Define a constant array called READ_TESTS and put all the important user management files in it.

Lines 8-9: Loop through the array of user management files.

Lines 10-15: Check to see if each file is readable. If not, send mail to root letting him know that the files aren’t readable.

Lines 16-21: Check to see if each file is owned by root. If not, send mail to root letting him know that the files aren’t owned by root.

Lines 23-28: Check to see if each file is group owned by root. If not send mail to root letting him know that the files aren’t group owned by root.

Line 30: End the loop.

For those not familiar with SELinux, the -Z argument to ‘ls -l’ shows the SELinux context for the given file. On systems without SELinux, there is no -Z argument to ls.

findaltroots.sh

#!/bin/sh

for id in `awk 'FS=":" {if(($3 == 0 && $1 != "root" )) print $1}' /etc/passwd`
do
        mail -s "Root Access Alert" root@dustpuppy << EOF

*********************************************************************************
*                                                                               *
*                 ALERT! Login ID `echo ${id}` has uid 0                        *
*                `date "+Detected On Date :%D Time :%r"`                        *
*                                                                               *
*********************************************************************************
EOF
done

Explanation

Line 1: Tell the shell that the script can be executed by the /bin/sh command. Since I copied this out of a sysadmin handbook, I preserved the original shebang line.

Lines 3-4: <grin>AWK magic here: </grin> We’ll start with the actual AWK script:

awk ‘FS=”:” {if(($3 == 0 && $1 != “root” )) print $1}’ /etc/passwd

FS=”:” tells AWK that the Field Separator is a :
{if(($3 == 0 says “if the third column for a given row is 0…”
&& $1 != “root” says “and the first column for a given row is not “root””
)) print $1} says “then print the first column of that row.”
/etc/passwd specifies the file on which to run this AWK script. For those who don’t know, /etc/passwd contains the login information for all users on a UNIX system. The first column contains the username and the third column contains the user ID.

The output of this awk script (which is the name of any login other than root with an id of 0), is piped to the shell which uses it in the for loop “for id in…do” which loops through each line in the /etc/passwd file.

Lines 5-10: Send mail to root telling him that some other login has and ID of 0 and therefore root-level access.

Line 11: End the loop.

verifyperms.sh

#!/bin/bash
#
# Looks for bad directory persimmons and fixes them.
#

FS_LIST="/ /tmp /home /opt /usr /usr/local /var /var/lib/mysql /var/ftp /var/cache /var/spool /var/log /var/www"

for fs in $FS_LIST
do
        baddirs=`find ${fs}/ -xdev -type d \( -perm -0002 -a ! -perm -1000 \) -ls -exec chmod +t \{\} \;`
        linecount=`echo $baddirs | wc -m`
        #echo $linecount
        if [ $linecount -gt 2 ]
        then
                mail -s "Bad Directory Persimmons on $fs" root@dustpuppy << EOF
Bad Directory Persimmons found for the following directories:
$baddirs

They were all fixed.

-cron daemon on dustpuppy

EOF
        fi

        badfiles=`find ${fs}/ -xdev -type f -perm -0002 -ls`
        linecount=`echo $badfiles | wc -m`
        if [ $linecount -gt 2 ]
        then
                mail -s "Bad File Persimmons on $fs" root@dustpuppy << EOF
Bad File Persimmons found for the following files:
$badfiles

Please fix by setting proper group ownership and removing world-writable bit.

-cron daemon on dustpuppy

EOF

        fi

        badfiles=`find ${fs}/ -xdev \( -nouser -o -nogroup \) -ls`
        linecount=`echo $badfiles | wc -m`
        if [ $linecount -gt 2 ]
        then
                mail -s "Bad Ownership on $fs" root@dustpuppy << EOF
The following files are unowned:
$badfiles

They've been assigned to nobody:nogroup - please find out who they belong to and assign the proper ownership.

-cron daemon on dustpuppy

EOF

        fi
done

Explanation

Line 1: Tells the shell that the script can be executed by the /bin/bash command.

Lines 2-4: Comments explaining the script.

Line 6: Create a constant called FS_LIST and put all the relevant filesystems in it.

Lines 8-9: Loop through the list of filesystems.

Line 10: A find mini-script:

find ${fs}/ says “Find files in the directory specified in this iteration of the loop through the filesystem list.”
-xdev says “Look only on the filesystem on which the specified directory resides.”
-type d says “Look only at directory files.”
\( begins a test group
-perm -0002 says “Find files that have the bit set to make the file as world-writable.”
-a joins the tests in the test group together with a boolean AND
! -perm -1000 says “Find files that do not have their sticky bits set.”
\) ends the test group
-ls says “Print information about each file matching the given criteria.”
-exec chmod +t \{\} \; says “Also, change the permission mode of each file matching the given critera to add the sticky bit.”

The idea is that directories that anyone can delete other people’s files in directories that are world-writable unless the directory has its sticky bit set. The find script locates these dangerously-set directories and corrects the problem by setting their sticky bits, thus ensuring that each user can only delete files in that directory that he or she actually owns.

Line 11: Display the list of directories identified by the above find script but pipe it to the wc -m command. Store the output of the wc -l command in a variable called linecount.

The idea is to count the number of characters in the list of directories returned from the find command and store it in the linecount variable.

Line 12: A commented-out debugging statement that would print the list of directories to the screen.

Lines 13-14: If the number of characters in the list of directories is greater than 2, then…

Lines 15-24: Send mail to root apprising him of the situation.

Line 25: End the conditional that started on line 13.

Line 27: Another find mini-script:

find ${fs}/ says “Find files in the directory specified in this iteration of the filesystem list.”
-xdev says “Look only at files on the same filesystem as the specified directory.”
-type f says “Look only at regular files.”
-perm -0002 says “Find files that are world-writable.”
-ls says “Print information about the files that match the criteria.”
And store the output of the find command in a variable called badfiles.

Line 28: Count the number of characters in the output of the above find command and store the result in a variable called linecount.

Lines 29-30: If the number of characters in the output of the find command is greater than 2, then…

Lines 31-39: Send mail to root apprising him of the situation.

Line 41: End the conditional that started on line 29.

The idea is that world-writable files are bad because anyone can edit them. What if the file’s executable? Anyone can put malicious code in there. If it’s executable, world-writable, AND setuid to root, it’s a disaster waiting to happen.

Line 43: Yet another find mini-script:

find ${fs}/ says “Find files in the directory specified by this iteration of the filesystems list.”
-xdev says “Look only at files that are on the same filesystem as the specified directory.”
\( begins a test group
-nouser says “Find files that are owned by user IDs that don’t have entries in /etc/passwd.”
-o joins the tests in the group together with a boolean OR
-nogroup says “Find files that are group owned by group IDs that don’t have entries in /etc/group.”
\) ends the test group
-ls says “Print information about the files that meet the specified criteria.”
And store the output of the find command in a variable called badfiles.

Line 44: Count the number of characters in the output of the find command and store the result in a variable called linecount.

Lines 45-46: If the number of characters in the output of the find command is greater than 2, then…

Lines 47-55: Send mail to root apprising him of the situation.

Line 57: End the conditional that started on line 45.

Line 58: End the filesystem loop.

Unowned files don’t pose a direct threat but they indicate that something is misconfigured or malfunctioning – there shouldn’t be files on a system that are owned by people that the system doesn’t know about.

I’ll be writing more scripts in the coming days to assist me in my sysadminning duties. As I do, I’ll post them here.

UPDATE 1315 CDT:

motd

It doesn’t have an .sh extension because I want it to live in /usr/local/sbin (the .sh scripts live in /root/scripts are are run by cron). This one I want to run manually. The idea is to change the MOTD in a friendly way. It’s pretty much self-documenting. Post comments and questions here.

#!/bin/bash
#
# Edits the MOTD
#

TMPFILE=/tmp/motd.`date +%F`.$UID

# First, copy the existing MOTD
if [ -e /etc/motd.src ]
then
        cp /etc/motd.src $TMPFILE
else
        cp /etc/motd $TMPFILE
fi

# Edit the copy
vi $TMPFILE

# Check to see if the new MOTD is blank
chars=`cat $TMPFILE | wc -m` # yes, it is dumb but if I ask wc to read the file directly,
                             # it will print the filename after the character count which will cause
                             # bash's [ builtin to bork when I try to -gt on text
#echo $chars
if [ $chars -lt 2 ]
then
        # ask if the user really wants to clear the MOTD
        echo "Do you REALLY want to clear the MOTD?"
        read response

        if [ $response == "Y" ] || [ $response == "y" ] || [ $response == "yes" ]
        then
                cat /dev/null > /etc/motd
                rm /etc/motd.src
                rm $TMPFILE
                exit
        fi
fi

# Ask if the user really wants to set the MOTD
cat $TMPFILE
echo "Do you REALLY want to set this as the MOTD?"
read response

if [ $response == "Y" ] || [ $response == "y" ] || [ $response == "yes" ]
then
        date > /etc/motd
        cat $TMPFILE >> /etc/motd
        echo $USER >> /etc/motd
        cp $TMPFILE /etc/motd.src
fi

rm $TMPFILE

fix_dotfile_perms.sh

#!/bin/bash
#
# finds dotfiles in home directories, removes world write persimmons
#

DOTFILES=`find /home -xdev \( -type f -o -type d \) -name "\.*" \( -perm /0020 -o -perm /0002 \) -ls -exec chmod go-w \{\} \;`
#echo $DOTFILES
linecount=`echo $DOTFILES | wc -m`

if [ $linecount -gt 2 ]
then
        mail -s "Bad Dotfile Persimmons" root@dustpuppy << EOF
The following dotfiles had bad persimmons:
$DOTFILES

They were fixed.

-cron daemon on dustpuppy

EOF

fi

find_netrcs.sh

#!/bin/bash
#
# finds .netrc files which users use to do naughty things over ftp.
#

NETRCS=`find /home -type f -name "\.netrc" -ls -exec chmod a-rwx \{\} \;`
linecount=`echo $NETRCS | wc -m`

if [ $linecount -gt 2 ]
then
        mail -s "Alert:  .netrc files found!" root@dustpuppy << EOF

************************************************************
*                                                          *
*              ALERT!  Found a .netrc file!                *
*  $NETRCS *
*                                                          *
************************************************************

It's been defanged.  Please speak with the user about it.

-cron daemon on dustpuppy

EOF

fi
Advertisements
  1. #1 by Joshua on July 24, 2009 - 11:08 AM

    And people wonder why UNIX admins are demented and asocial beings…

    BTW, the “persimmons” reference is from this comic strip: UserFriendly: The Orbital Dyslexia Ray

  2. #2 by Joshua on July 24, 2009 - 11:16 AM

    LOL – doing this post helped me realize that I was missing some things from the last script: It’s supposed to change the file ownership on the unowned files to nobody:nogroup in addition to printing the file info. So I added “-exec chown nobody:nogroup \{\} \;” to the find statement in the last file.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: