Never been to TextSnippets before?

Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world (or not, you can keep them private!)

« Newer Snippets
Older Snippets »
109 total  XML / RSS feed 

Compile ClamAV from source on Mac OS X

The following instructions for compiling & installing the ClamAV virus scanner on Mac OS X require an admin user account and Xcode. Works despite some compiler warnings. Use at your own risk!

1. disable an already installed ClamAV system

clamconf      # get your ClamAV configuration
clamconf -n
clamconf | grep DatabaseOwner


# the following applies to the already installed ClamAV system of user clamavadmin at /private/var/clamavadmin
# cf. Run ClamAV from a system service agent account, http://textsnippets.com/posts/show/1406
# cf. Automated virus scanning with ClamAV on Mac OS X 10.4, http://textsnippets.com/posts/show/1357


# view clamd.log in another Terminal window
open /bin/bash
sudo tail -n 50 -f /private/var/clamavadmin/log/clamd.log

# SHUTDOWN clamd 
# cf. man clamd
(sleep 3; echo PING; sleep 3; echo exit) | telnet -u /private/var/clamavadmin/tmp/clamd
(sleep 3; echo SHUTDOWN; sleep 3; echo exit) | telnet -u /private/var/clamavadmin/tmp/clamd

# alternative: sudo /opt/local/bin/port install socat
echo PING | /opt/local/bin/socat - /private/var/clamavadmin/tmp/clamd     
echo SHUTDOWN | /opt/local/bin/socat - /private/var/clamavadmin/tmp/clamd  

# alternative
function clamd_local_socket() {

   local_socket="$(/usr/local/bin/clamconf -n | awk -F '"' '/LocalSocket/ {print $2}')"

   if [[ -z "$local_socket" ]] || [[ ! -S "$local_socket" ]]; then    # cf. help test
      printf "No such local unix socket: $local_socket \n"
      return 1
   fi

   #printf -- "$@\x00$local_socket" | /usr/bin/sudo ruby -rsocket -n -e '
   printf -- "$@\x00$local_socket" | ruby -rsocket -n -e '
      args = $_.split(/\000/, 2)
      unix_socket = UNIXSocket::open(args.last)
      unix_socket.send(args.first, 0)
      print unix_socket.recvfrom(1000).first
      unix_socket.close
   '

return 0
}

clamd_local_socket PING
clamd_local_socket SHUTDOWN


function clamavadmin_perms() {

   if [[ -d "/private/var/clamavadmin" ]]; then
      printf '\nSetting permissions in the clamavadmin directory: /private/var/clamavadmin\n\n'
   else
      printf '\nNo clamavadmin directory at: /private/var/clamavadmin\n\n'
      return 1
   fi

   declare sudo=/usr/bin/sudo

   $sudo /bin/mkdir -p /private/var/clamavadmin/log
   $sudo /bin/mkdir -p /private/var/clamavadmin/tmp
   $sudo /bin/mkdir -p /private/var/clamavadmin/share/clamav   # ClamAV database directory
   $sudo /usr/bin/touch /private/var/clamavadmin/log/clamd.log 
   $sudo /usr/bin/touch /private/var/clamavadmin/log/freshclam.log

   $sudo /usr/sbin/chown -R clamavadmin:clamavadmin /private/var/clamavadmin
   $sudo /bin/chmod -R 0750 /private/var/clamavadmin 
   #$sudo /bin/chmod -R 0770 /private/var/clamavadmin   # for debugging only
   $sudo /bin/chmod 0777 /private/var/clamavadmin/tmp/clamd 2>/dev/null  # local unix socket file
   $sudo /usr/bin/find /private/var/clamavadmin -print0 | /usr/bin/xargs -0 $sudo /bin/ls -ldG
   return 0
}


# restart clamd after SHUTDOWN (socket file removed)
#if [[ -e "/private/var/clamavadmin/tmp/clamd" ]]; then sudo /bin/rm -f "/private/var/clamavadmin/tmp/clamd" ; fi
#sudo "$(/usr/bin/which clamd)" -c /private/var/clamavadmin/clamd.conf
#clamavadmin_perms


# get running clamav launchd services
launchctl list | grep -i clam
sudo launchctl list | grep -i clam

launchctl unload -w ~/Library/LaunchAgents/net.clamav.dirwatcher.plist 2>/dev/null
sudo launchctl unload -w /Library/LaunchDaemons/net.clamav.dirwatcherd.plist 2>/dev/null
sudo launchctl unload -w /Library/LaunchDaemons/net.clamav.clamd.plist 2>/dev/null
sudo launchctl unload -w /Library/LaunchDaemons/net.clamav.update.clamd.db.plist 2>/dev/null

# check for: Disabled
open -e ~/Library/LaunchAgents/net.clamav.dirwatcher.plist
sudo nano /Library/LaunchDaemons/net.clamav.dirwatcherd.plist
sudo nano /Library/LaunchDaemons/net.clamav.clamd.plist
sudo nano /Library/LaunchDaemons/net.clamav.update.clamd.db.plist


sudo reboot

export PATH=/usr/local/bin:/usr/local/sbin:/usr/local/lib:/usr/local/include:/usr/bin:/bin:/usr/sbin:/sbin


2. compile & install gawk on Mac OS X

# http://www.gnu.org/software/gawk/
cd ~/Desktop
curl -L -O http://ftp.gnu.org/pub/gnu/gawk/gawk-3.1.6.tar.gz
tar -xzf gawk-3.1.6.tar.gz
cd gawk-3.1.6
./configure --help
./configure --disable-nls --prefix=/usr/local
make
sudo make install
/usr/local/bin/gawk --version


3. compile & install the GNU Multiple Precision Arithmetic Library

# http://gmplib.org
# http://gmplib.org/macos.html

cd ~/Desktop
curl -L -O ftp://ftp.gnu.org/gnu/gmp/gmp-4.2.2.tar.gz   # cf. fwftp, http://textsnippets.com/posts/show/1284
tar -xzf gmp-4.2.2.tar.gz
cd gmp-4.2.2

./configure --help
./configure

make
make check
sudo make install

# test
sudo find /usr/local -iregex ".*gmp.*" -print0 | xargs -0 sudo ls -ldG
otool -Lv /usr/local/lib/libgmp.3.4.2.dylib


4. create ClamAV user & group

# to set up a ClamAV user & group run the first script from: http://textsnippets.com/posts/show/1405

#--------------------------------------------------

You are going to create a system service agent account!

Enter first name: clamavadmin

Note: The last name is optional and defaults to "agent" if you just press <return>!
Enter last name: 

Note: The user shell is optional and defaults to "/usr/bin/false" if you just press <return>!
Enter user shell: /bin/bash

Note: The home directory is optional and defaults to "/private/var/empty" if you just press <return>!
Enter home directory: /private/var/clamavadmin

System service agent account:  clamavadmin  successfully created!

#--------------------------------------------------


sudo mkdir -p /private/var/clamavadmin/log
sudo touch /private/var/clamavadmin/log/clamd.log 
sudo touch /private/var/clamavadmin/log/freshclam.log
sudo mkdir -p /private/var/clamavadmin/tmp

# make sure permissions are set correctly in /private/var/clamavadmin
clamavadmin_perms   # see function above


# add clamavadmin to group admin and group $(logname)
# add $(logname) to group clamavadmin
sudo dscl . -append /Groups/admin GroupMembership clamavadmin
sudo dscl . -append /Groups/$(logname) GroupMembership clamavadmin
sudo dscl . -append /Groups/clamavadmin GroupMembership $(logname)

#sudo dscl . -delete /Groups/admin GroupMembership clamavadmin 
#sudo dscl . -delete /Groups/$(logname) GroupMembership clamavadmin  
#sudo dscl . -delete /Groups/clamavadmin GroupMembership $(logname)   

# test
dscl . -list /Users  | grep -i clamav
dscl . -list /Groups  | grep -i clamav

dscl . -read  /Users/clamavadmin uid
dscl . -read  /Groups/clamavadmin gid

dscl . -search /Groups GroupMembership clamavadmin
printf -- "$(dscl . -search /Groups GroupMembership clamavadmin | awk '{print $1}' | tr '\n' ' ')\n"
id -Gn clamavadmin


5. compile & install ClamAV

# http://wiki.clamav.net/Main/InstallFromSource
# http://downloads.topicdesk.com/docs/Updating_clamav_on_OS_X_Server.pdf
# http://downloads.topicdesk.com/docs/clamav_extras.tar.gz

cd ~/Desktop
open http://freshmeat.net/projects/clamav/
curl -L -O http://freshmeat.net/redir/clamav/29355/url_tgz/clamav-0.92.1.tar.gz
tar -xzf clamav-0.92.1.tar.gz
cd clamav-0.92.1

./configure --help

# Compile bug for clamav 0.92 with latest xcode 
# http://discussions.apple.com/thread.jspa?messageID=6190171
# http://www.mail-archive.com/[email protected]/msg28420.html

CFLAGS="-O0" ./configure \
--prefix=/usr/local --mandir=/usr/local/share/man --sysconfdir=/private/var/clamavadmin \
--with-dbdir=/private/var/clamavadmin/share/clamav --with-datadir=/private/var/clamavadmin/share/clamav \
--with-user=clamavadmin --with-group=clamavadmin \
--enable-bigstack --enable-static --disable-shared

make

sudo make install


# test
man -w    # show directories searched for man pages
man -aW clamd 2>/dev/null
man -aW clamdscan 2>/dev/null
man -aW clamscan 2>/dev/null
man -aW freshclam 2>/dev/null
man -aW clamconf 2>/dev/null


# get an overview of your ClamAV file & folder permissions in /usr/local
sudo find /usr/local -iregex ".*clam.*" -print0 | xargs -0 sudo ls -ldG
#sudo find /usr/local \( -type f -or -type d \) -iregex ".*clam.*" -print0 | xargs -0 sudo ls -ldG

# set ClamAV file & folder permissions in /usr/local
# note: you may not need: -not -name "*.png"
sudo find /usr/local -not -name "*.png" -iregex ".*clam.*" -print0 | xargs -0 sudo chown clamavadmin:clamavadmin
sudo find /usr/local -not -name "*.png" -iregex ".*clam.*" -print0 | xargs -0 sudo chmod 0750

# reset permissions in /usr/local
# note: you may not need: -not -name "*.png"
#sudo find /usr/local  -not -name "*.png" -iregex ".*clam.*" -print0 | xargs -0 sudo chown root:wheel  
#sudo find /usr/local  -not -name "*.png" -iregex ".*clam.*" -print0 | xargs -0 sudo chmod 0755



# /private/var/clamavadmin/clamd.conf

sudo cp -p /private/var/clamavadmin/clamd.conf /private/var/clamavadmin/clamd.conf.orig

sudo sh -c '
cat << EOF > /private/var/clamavadmin/clamd.conf

LogFileMaxSize 10M
LogTime yes
FixStaleSocket yes
TCPAddr 127.0.0.1
MaxConnectionQueueLength 30
MaxThreads 20
ExitOnOOM yes
ScanOLE2 yes  # Microsoft Office documents and .msi files
ScanPDF yes
ArchiveMaxFileSize 100M
ArchiveMaxCompressionRatio 0
#VirusEvent echo virus: %v >> /path/to/file.txt

DatabaseDirectory /private/var/clamavadmin/share/clamav    # hardcoded
LogFile /private/var/clamavadmin/log/clamd.log
TemporaryDirectory /private/var/clamavadmin/tmp
LocalSocket /private/var/clamavadmin/tmp/clamd

EOF
'

sudo nano /private/var/clamavadmin/clamd.conf


# /private/var/clamavadmin/freshclam.conf

sudo cp -p /private/var/clamavadmin/freshclam.conf /private/var/clamavadmin/freshclam.conf.orig 

sudo sh -c '
cat << EOF > /private/var/clamavadmin/freshclam.conf

UpdateLogFile /private/var/clamavadmin/log/freshclam.log
LogFileMaxSize 2M
LogTime yes
LogVerbose yes
DatabaseOwner clamavadmin
Debug yes
NotifyClamd /private/var/clamavadmin/clamd.conf   # send the RELOAD command to clamd
DatabaseDirectory /private/var/clamavadmin/share/clamav    # hardcoded
DatabaseMirror database.clamav.net

#Checks 24   # number of database checks per day
#OnUpdateExecute command
#OnErrorExecute command
#OnOutdatedExecute command   # run command when freshclam reports outdated version
#ConnectTimeout 60
#ReceiveTimeout 60

EOF
'

sudo nano /private/var/clamavadmin/freshclam.conf


/usr/local/bin/clamconf
/usr/local/bin/clamconf -n


# make sure permissions are set correctly in /private/var/clamavadmin
clamavadmin_perms   # see function above


# view clamd.log in another Terminal window
open /bin/bash
sudo tail -n 50 -f /private/var/clamavadmin/log/clamd.log

# view freshclam.log in another Terminal window
open /bin/bash
sudo tail -n 50 -f /private/var/clamavadmin/log/freshclam.log


# test
sudo /usr/local/bin/freshclam -u clamavadmin   # update virus database
sudo /usr/local/bin/clamscan ~/Desktop/clamav-0.92.1/test/*
sudo /usr/local/bin/clamdscan ~/Desktop/clamav-0.92.1/test/*

# now start clamd
if [[ -e "/private/var/clamavadmin/tmp/clamd" ]]; then sudo /bin/rm -f "/private/var/clamavadmin/tmp/clamd" ; fi
sudo /usr/local/sbin/clamd -c /private/var/clamavadmin/clamd.conf

# make sure permissions are set correctly in /private/var/clamavadmin
clamavadmin_perms   # see function above


#--------------------------------------------------


# set up ClamAV launchd items

sudo nano /Library/LaunchDaemons/net.clamav.clamd.plist

<?xml version="1.0" encoding="UTF-8"?>
DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>Disabledkey>
        />
        <key>GroupNamekey>
        clamavadmin</string>
        <key>Labelkey>
        net.clamav.clamd</string>
        <key>ProgramArgumentskey>
        
                /usr/local/sbin/clamdstring>
                -c</string>
                <string>/private/var/clamavadmin/clamd.confstring>
        </array>
        <key>RunAtLoadkey>
        />
        <key>UserNamekey>
        clamavadmin</string>
dict>
</plist>

if [[ -e "/private/var/clamavadmin/tmp/clamd" ]]; then sudo /bin/rm -f "/private/var/clamavadmin/tmp/clamd" ; fi
sudo launchctl load -w /Library/LaunchDaemons/net.clamav.clamd.plist 2>/dev/null
#sudo launchctl unload -w /Library/LaunchDaemons/net.clamav.clamd.plist 2>/dev/null

# make sure permissions are set correctly in /private/var/clamavadmin
clamavadmin_perms   # see function above


#--------------------------------------------------


sudo nano /Library/LaunchDaemons/net.clamav.update.clamd.db.plist

<?xml version="1.0" encoding="UTF-8"?>
DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>Disabledkey>
        />
        <key>GroupNamekey>
        clamavadmin</string>
        <key>Labelkey>
        net.clamav.update.clamd.db</string>
        <key>ProgramArgumentskey>
        
                /private/var/clamavadmin/update_clamd_db.shstring>
        </array>
        <key>RunAtLoadkey>
        />
        <key>StartIntervalkey>
        20000</integer>
        <key>UserNamekey>
        clamavadmin</string>
dict>
</plist>

sudo launchctl load -w /Library/LaunchDaemons/net.clamav.update.clamd.db.plist 2>/dev/null
#sudo launchctl unload -w /Library/LaunchDaemons/net.clamav.update.clamd.db.plist 2>/dev/null

# make sure permissions are set correctly in /private/var/clamavadmin
clamavadmin_perms   # see function above


#--------------------------------------------------


sudo nano /private/var/clamavadmin/update_clamd_db.sh

#!/bin/bash

/bin/sleep 120

exec >/dev/console 2>&1   # write stdout & stderr to console.log

/usr/bin/curl -I -L -s --max-time 15 database.clamav.net 1>/dev/null

if [[ $? -eq 0 ]]; then 

   /bin/sleep 3

   /usr/local/bin/freshclam -u clamavadmin

   /bin/sleep 3

   # cf. /private/var/clamavadmin/freshclam.conf above:
   # NotifyClamd /private/var/clamavadmin/clamd.conf   # send the RELOAD command to clamd

   #echo RELOAD | /opt/local/bin/socat - /private/var/clamavadmin/tmp/clamd
   #/bin/sleep 3
   #echo PING | /opt/local/bin/socat - /private/var/clamavadmin/tmp/clamd
   #/bin/sleep 3

   echo -e "\n$(/bin/date "+%Y-%m-%d %H:%M:%S %Z"): clamd virus database successfully updated\n"
   exit 0

else

   echo -e "\n$(/bin/date "+%Y-%m-%d %H:%M:%S %Z"): updating the clamd virus database failed; no internet connection to database.clamav.net established\n"
   exit 0   # leave launchd item /Library/LaunchDaemons/net.clamav.update.clamd.db.plist undisturbed

fi


#--------------------------------------------------


# make sure permissions are set correctly in /private/var/clamavadmin
clamavadmin_perms   # see function above


#--------------------------------------------------


# cf. http://textsnippets.com/posts/show/1357

sudo nano ~/Documents/ClamAV/watchdirs.sh     

# ...

find=/usr/bin/find
clamdscan=/usr/local/bin/clamdscan
clamscan=/usr/local/bin/clamscan

#clamdscan=/opt/local/bin/clamdscan
#clamscan=/opt/local/bin/clamscan

# ...


#--------------------------------------------------


# cf. http://textsnippets.com/posts/show/1400
open ~/Library/Workflows/Applications/Finder/virusscan.workflow

/usr/local/bin/clamdscan --quiet "$f" 2>/dev/null
#/opt/local/bin/clamdscan --quiet "$f" 2>/dev/null


#--------------------------------------------------

launchctl load -w ~/Library/LaunchAgents/net.clamav.dirwatcher.plist 2>/dev/null
#sudo launchctl load -w /Library/LaunchDaemons/net.clamav.dirwatcherd.plist 2>/dev/null
sudo launchctl load -w /Library/LaunchDaemons/net.clamav.clamd.plist 2>/dev/null
sudo launchctl load -w /Library/LaunchDaemons/net.clamav.update.clamd.db.plist 2>/dev/null


sudo reboot


open -a Console

open /bin/bash
sudo tail -n 50 -f /private/var/clamavadmin/log/clamd.log

open /bin/bash
sudo tail -n 50 -f /private/var/clamavadmin/log/freshclam.log

# make sure permissions are set correctly in /private/var/clamavadmin
clamavadmin_perms   # see function above

sudo find /private/var/clamavadmin -print0 | xargs -0 sudo ls -ldG
sudo find /usr/local -iregex ".*clam.*" -print0 | xargs -0 sudo ls -ldG

Run ClamAV from a system service agent account

The following instructions on how to run ClamAV from a system service agent account (non-root) require an admin user account, a ClamAV setup as described here and the creation of a clamavadmin system service agent account as described here.

Use at your own risk!


First uncomment the following lines of code in the first script at http://textsnippets.com/posts/show/1405:

# create further subdirectories if necessary
...
#fi
#
#fi


Then save the file, start the script and enter the following:

You are going to create a system service agent account!

Enter first name: clamavadmin

Note: The last name is optional and defaults to "agent" if you just press <return>!
Enter last name: 

Note: The user shell is optional and defaults to "/usr/bin/false" if you just press <return>!
Enter user shell: /bin/bash

Note: The home directory is optional and defaults to "/private/var/empty" if you just press <return>!
Enter home directory: /private/var/clamavadmin

System service agent account:  clamavadmin  successfully created!



# test
dscl . -read /Users/clamavadmin
dscl . -read /Groups/clamavadmin
dscl . list /Groups GroupMembership
dscl . read /Groups/clamavadmin GroupMembership
dscl . -search /Groups GroupMembership clamavadmin
echo $(dscl . -search /Groups GroupMembership clamavadmin | awk '{print $1}' | tr '\n' ' ')
printf -- "$(dscl . -search /Groups GroupMembership clamavadmin | awk '{print $1}' | tr '\n' ' ')\n"

sudo ls -ld /private/var/clamavadmin
sudo ls -l /private/var/clamavadmin/log/clamd.log
sudo ls -ld /private/var/clamavadmin/tmp
#sudo ls -l /private/var/clamavadmin/tmp/clamd   # will be created later by man clamd
sudo find /private/var/clamavadmin \( -type f -or -type d -or -type s \) -print0 | xargs -0 sudo ls -ldG



# add clamavadmin to group admin and group $(logname)
# add $(logname) to group clamavadmin
sudo dscl . -append /Groups/admin GroupMembership clamavadmin
sudo dscl . -append /Groups/$(logname) GroupMembership clamavadmin
sudo dscl . -append /Groups/clamavadmin GroupMembership $(logname)

#sudo dscl . -delete /Groups/admin GroupMembership clamavadmin 
#sudo dscl . -delete /Groups/$(logname) GroupMembership clamavadmin  
#sudo dscl . -delete /Groups/clamavadmin GroupMembership $(logname)   

# cf. http://textsnippets.com/posts/show/1402
dirmodes ~/Documents/ClamAV/Downloads/QueueDirectory


# copy update_clamd_db.sh
sudo cp /usr/local/sbin/update_clamd_db.sh /private/var/clamavadmin/update_clamd_db.sh
sudo chown clamavadmin:clamavadmin /private/var/clamavadmin/update_clamd_db.sh
sudo chmod 0750 /private/var/clamavadmin/update_clamd_db.sh
ls -l /private/var/clamavadmin/update_clamd_db.sh

# then change the following line in sudo nano  /private/var/clamavadmin/update_clamd_db.sh
/opt/local/bin/freshclam -u root
# ... to read ...
/opt/local/bin/freshclam -u clamavadmin


# get an overview of your ClamAV file & folder permissions in /opt
sudo find /opt \( -type f -or -type d \) -iregex ".*clam.*" -print0 | xargs -0 sudo ls -ldG

# set new ClamAV file & folder permissions
sudo find /opt  \( -type f -or -type d \) -iregex ".*clam.*" -print0 | xargs -0 sudo chown clamavadmin:clamavadmin
sudo find /opt  \( -type f -or -type d \) -iregex ".*clam.*" -print0 | xargs -0 sudo chmod 0750

# reset permissions
#sudo find /opt  \( -type f -or -type d \) -iregex ".*clam.*" -print0 | xargs -0 sudo chown root:wheel  
#sudo find /opt  \( -type f -or -type d \) -iregex ".*clam.*" -print0 | xargs -0 sudo chmod 0755


# test
sudo find /opt \( -type f -or -type d \) -iregex ".*clam.*" -print0 | xargs -0 sudo ls -ldG
sudo find /opt -not -user clamavadmin \( -type f -or -type d \) -iregex ".*clam.*" -print0 | xargs -0 sudo ls -ldG


# then modify clamd.conf
sudo nano /opt/local/etc/clamd.conf

LogFileMaxSize 10M
LogTime yes
FixStaleSocket yes
TCPAddr 127.0.0.1
MaxConnectionQueueLength 30
MaxThreads 20
ExitOnOOM yes
ScanOLE2 yes  # Microsoft Office documents and .msi files
ScanPDF yes
ArchiveMaxFileSize 100M
ArchiveMaxCompressionRatio 0
#VirusEvent echo virus: %v >> /path/to/file.txt

DatabaseDirectory /opt/local/share/clamav    # hardcoded
#LogFile /private/var/log/clamd.log
#TemporaryDirectory /private/var/tmp
#DatabaseDirectory /opt/local/share/clamav
#LocalSocket /tmp/clamd

#DatabaseDirectory /private/var/clamavadmin/share/clamav
LogFile /private/var/clamavadmin/log/clamd.log
TemporaryDirectory /private/var/clamavadmin/tmp
LocalSocket /private/var/clamavadmin/tmp/clamd



# modify net.clamav.clamd.plist

sudo launchctl unload -w /Library/LaunchDaemons/net.clamav.clamd.plist 2>/dev/null
sudo cp -p /Library/LaunchDaemons/net.clamav.clamd.plist /Library/LaunchDaemons/net.clamav.clamd.root-wheel

sudo nano /Library/LaunchDaemons/net.clamav.clamd.plist

<?xml version="1.0" encoding="UTF-8"?>
DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.$
"1.0">

        Disabled
        
        GroupName
        clamavadmin
        Label
        net.clamav.clamd
        ProgramArguments
        
                /opt/local/sbin/clamd
                -c
                /opt/local/etc/clamd.conf
        
        RunAtLoad
        
        UserName
        clamavadmin


sudo launchctl load -w /Library/LaunchDaemons/net.clamav.clamd.plist 2>/dev/null



# modify net.clamav.update.clamd.db.plist

sudo launchctl unload -w /Library/LaunchDaemons/net.clamav.update.clamd.db.plist 2>/dev/null
sudo cp -p /Library/LaunchDaemons/net.clamav.update.clamd.db.plist /Library/LaunchDaemons/net.clamav.update.clamd.db.plist.root-wheel

sudo nano /Library/LaunchDaemons/net.clamav.update.clamd.db.plist

"1.0" encoding="UTF-8"?>
"-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.$
<plist version="1.0">
<dict>
        <key>Disabledkey>
        />
        <key>GroupNamekey>
        clamavadmin</string>
        <key>Labelkey>
        net.clamav.update.clamd.db</string>
        <key>ProgramArgumentskey>
        
                /private/var/clamavadmin/update_clamd_db.shstring>
        </array>
        <key>RunAtLoadkey>
        />
        <key>StartIntervalkey>
        20000</integer>
        <key>UserNamekey>
        clamavadmin</string>
dict>


sudo launchctl load -w /Library/LaunchDaemons/net.clamav.update.clamd.db.plist 2>/dev/null



# watch clamd.log in a second Terminal window
open /bin/bash
sudo tail -n 50 -f /private/var/clamavadmin/log/clamd.log


# now restart clamd
if [[ -e "/private/var/clamavadmin/tmp/clamd" ]]; then sudo /bin/rm -f "/private/var/clamavadmin/tmp/clamd" ; fi
sudo /opt/local/sbin/clamd -c /opt/local/etc/clamd.conf


# test
(sleep 3; echo PING; sleep 3; echo exit) | sudo telnet -u /private/var/clamavadmin/tmp/clamd
echo PING | /opt/local/bin/socat - /private/var/clamavadmin/tmp/clamd    # requires: sudo port install socat
sudo find /private/var/clamavadmin \( -type f -or -type d -or -type s \) -print0 | xargs -0 sudo ls -ldG   # /private/var/clamavadmin/tmp/clamd

Creating & deleting system service agent accounts on Mac OS X

Inspired by: Description and guidelines on creating system service agent accounts on Mac OS X (Tiger) and HiddenAdminCreate

Use at your own risk!

1. create a system service agent account


#!/bin/bash

if [[ "$(/usr/bin/whoami)" != "root" ]]; then printf '\nMust be run as root!\n\n'; exit 1; fi

OPATH=$PATH
export PATH=/usr/bin:/usr/sbin:/bin:/sbin

OIFS=$IFS
export IFS=$' \t\n'

printf '\n\e[1mYou are going to create a system service agent account!\e[m\n\n'

declare sudo=/usr/bin/sudo dscl=/usr/bin/dscl


# first name 

printf "\e[1mEnter first name\e[m: "
read fn

# no spaces in names
if [[ -z "$(printf -- "$fn" | /usr/bin/grep -Eo "^[^[:space:]]+$")" ]]; then
   printf '\nUse a name without spaces! \nPlease, try again!\n\n'
   exit 1
fi

# name must not begin with a number
if [[ -n "$(printf -- "$fn" | /usr/bin/grep -E "^[[:digit:]]")" ]]; then
   printf '\nName must not begin with a number! \nPlease, try again!\n\n'
   exit 1
fi


# make sure the user name is unique
new_user="$(/usr/bin/dscl . -search /Users name "$fn" 2>/dev/null)"

if [[ -z "$new_user" ]]; then
  new_user="$fn"               
else
  printf "\nUser name already exists: $fn \nPlease, modify your name and try it again\x21\n\n"
  exit 1
fi 


# make sure the agent's primary group name is unique 
# note: the agent's primary group name is also based on the first name!

new_group="$(/usr/bin/dscl . -search /Groups name "$fn")"

if [[ -z "$new_group" ]]; then
  new_group="$fn"             
else
  printf "\nThe agent's primary group name already exists: $fn\x21 \nPlease, try again\x21\n\n"
  exit 1
fi 


# last name

printf '\nNote: The \e[1mlast name\e[m is \e[1moptional\e[m and defaults to "agent" if you just press !\n'
printf "\e[1mEnter last name\e[m: "
read ln

if [[ -z "$ln" ]]; then ln="agent"; fi

# no spaces in names
if [[ -z "$(printf -- "$ln" | /usr/bin/grep -Eo "^[^[:space:]]+$")" ]]; then
   printf '\nUse a name without spaces! \nPlease, try again!\n\n'
   exit 1
fi

# name must not begin with a number
if [[ -n "$(printf -- "$ln" | /usr/bin/grep -E "^[[:digit:]]")" ]]; then
   printf '\nName must not begin with a number! \nPlease, try again!\n\n'
   exit 1
fi


# user shell

printf '\nNote: The \e[1muser shell\e[m is \e[1moptional\e[m and defaults to "/usr/bin/false" if you just press !\n'
printf "\e[1mEnter user shell\e[m: "
read sh

if [[ -z "$sh" ]]; then sh="/usr/bin/false"; fi

# test if user shell exists
if [[ ! -e "$sh" ]]; then
   printf "\nUser shell does not exist: $sh\n\n"
   exit 1
fi


# home directory

printf '\nNote: The \e[1mhome directory\e[m is \e[1moptional\e[m and defaults to "/private/var/empty" if you just press !\n'
printf "\e[1mEnter home directory\e[m: "
read hd

if [[ -z "$hd" ]]; then 
   hd="/private/var/empty"
elif [[ "${hd:0:1}" != '/' ]]; then
   printf '\nThe home directory path does not begin with a slash "/".\nPlease, try again!\n\n'
   exit 1
elif [[ -e "$hd" ]]; then
   printf "\nHome directory already exists: $hd \nPlease, try again\x21\n\n"
   exit 1
fi

hd=${hd%/}      # remove a trailing slash character "/" if necessary



# get unique id numbers (uid, gid)
unset -v new_uid new_gid i
declare -i new_uid=0 new_gid=0 i=100

while [[ $i -lt 500 ]]; do      # try to get $new_uid and $new_gid between 100 and 500
   i=$[i+1]
   if [[ -z "$(/usr/bin/dscl . -search /Users uid $i)" ]] && [[ -z "$(/usr/bin/dscl . -search /Groups gid $i)" ]]; then
      new_uid=$i
      new_gid=$i
      break
   fi
done


if [[ $new_uid -eq 0 ]] || [[ $new_gid -eq 0 ]]; then     # get $new_uid and $new_gid greater than 500
   i=500
   idvar=0

   while [[ $idvar -eq 0 ]]; do 
      i=$[i+1]
      if [[ -z "$(/usr/bin/dscl . -search /Users uid $i)" ]] && [[ -z "$(/usr/bin/dscl . -search /Groups gid $i)" ]]; then
         new_uid=$i
         new_gid=$i
         idvar=1
         #break
      fi
   done

fi


if [[ $new_uid -eq 0 ]] || [[ $new_gid -eq 0 ]]; then printf 'Getting unique id numbers (uid, gid) failed!\n'; exit 1; fi



# check once again ...

if [[ $new_uid -eq $new_gid ]] && [[ "$new_user" == "$fn" ]] && [[ "$new_group" == "$fn" ]]; then

# create home directory
if [[ "$hd" != "/private/var/empty" ]]; then
   $sudo /bin/mkdir -p "$hd"
fi

# create the agent's primary group
$sudo /usr/sbin/dseditgroup -o create -r "$fn $ln" -i $new_gid "$new_group"
$sudo $dscl  . -append "/Groups/$new_group" passwd "*"

# create the system service agent
$sudo $dscl . -create /Users/$new_user
$sudo $dscl . -append /Users/$new_user RealName "$fn $ln"
$sudo $dscl . -append /Users/$new_user uid $new_uid
$sudo $dscl . -append /Users/$new_user gid $new_gid
$sudo $dscl . -append /Users/$new_user shell "$sh"
$sudo $dscl . -append /Users/$new_user home "$hd"
$sudo $dscl . -create /Users/$new_user passwd "*"

$sudo $dscl  . -append "/Groups/$new_group" GroupMembership "$new_user"    # add new agent to the agent's primary group
#$sudo /usr/sbin/dseditgroup -o edit -a "$new_group" -t user "$new_user"

else

   printf "\nConfiguration of system service agent account: $fn failed\x21 \nPlease, try again\x21\n\n"
   exit 1

fi

# create further subdirectories if necessary
#if [[ "$hd" != "/private/var/empty" ]]; then
#
#if [[ "$new_user" == "clamavadmin" ]]; then
#   # additional subdirectories for the clamavadmin system service agent account
#   $sudo /bin/mkdir  -p "$hd"/log
#   $sudo /usr/bin/touch "$hd"/log/clamd.log
#   $sudo /bin/mkdir  -p "$hd"/tmp
#   $sudo /bin/mkdir  -p "$hd"/share/clamav
#   $sudo /usr/sbin/chown -R $new_user:$new_user "$hd"
#   $sudo /bin/chmod -R 750 "$hd"
#fi
#
#fi


printf "\nSystem service agent account:  \e[1m$fn\e[m  successfully created\x21\n\n"

export IFS=$OIFS
export PATH=$OPATH

exit 0


#---------------------------

# test
dscl . list /Users
dscl . -read /Users/

dscl . list /Groups
dscl . -read /Groups/
dscl . list /Groups GroupMembership



2. delete a system service agent account

#!/bin/bash


if [[ "$(/usr/bin/whoami)" != "root" ]]; then printf '\nMust be run as root!\n\n'; exit 1; fi

OPATH=$PATH
export PATH=/usr/bin:/usr/sbin:/bin:/sbin

OIFS=$IFS
export IFS=$' \t\n'

declare sudo=/usr/bin/sudo


printf "\n\e[1mDelete system service agent account\e[m: "
read agent

if [[ -z "$agent" ]]; then printf '\nNo name for system service agent specified! Please, try again!\n\n'; exit 1; fi

# make sure the agent exists
agenttest="$(/usr/bin/dscl . -search /Users name "$agent")"

if [[ -z "$agenttest" ]]; then printf "\nThe system service agent does not exist: $agent\n\n"; exit 1; fi 


# find the agent's home directory
home=$(/usr/bin/dscl . -read /Users/$agent home | awk -F ': ' '{ print $NF; }' )


# get the agent's group memberships
groups_of_agent="$(/usr/bin/id -Gn $agent)"

# delete the agent's group memberships
if [[ $? -eq 0 ]] && [[ -n "$(/usr/bin/dscl . -search /Groups GroupMembership "$agent")" ]]; then

   for group in $groups_of_agent; do
      $sudo /usr/bin/dscl . -delete "/Groups/$group"  GroupMembership "$agent"
      #$sudo /usr/sbin/dseditgroup -o edit -d "$agent" -t user "$group"           
   done

fi


# delete the agent's primary group
if [[ -n "$(/usr/bin/dscl . -search /Groups name "$agent")" ]]; then
   $sudo /usr/sbin/dseditgroup -o delete "$agent"
fi

# if the agent's primary group has not been deleted ...
if [[ -n "$(/usr/bin/dscl . -search /Groups name "$agent")" ]]; then
printf "
   \e[1mWarning\e[m:
   The group memberships of the system service agent \e[1m$agent\e[m have been deleted\x21
   groups_of_agent: $groups_of_agent
   The agent's primary group \e[1m$agent\e[m, however, has not been deleted\x21
   Please, try again\x21
   Exiting ...\n
"
  exit 1
fi


# find the GeneratedUID of the agent and remove the password hash file 
# from /private/var/db/shadow/hash/
# sudo ls -a /private/var/db/shadow/hash
# sudo ls -l /private/var/db/shadow/hash/

guid="$(/usr/bin/dscl . -read "/Users/$agent" GeneratedUID | /usr/bin/awk '{print $NF;}')"

if [[ -f "/private/var/db/shadow/hash/$guid" ]]; then
   $sudo /bin/rm -f /private/var/db/shadow/hash/$guid
fi


# delete the agent
$sudo /usr/bin/dscl . -delete "/Users/$agent"

# make a backup
if [[ -d "$home" ]] && [[ "$home" != "/private/var/empty" ]]; then
   $sudo /usr/bin/ditto -rsrc -c -k "$home" "${home}-archive-$(/bin/date).zip"
fi

# remove the agent's home directory
if [[ -d "$home" ]] && [[ "$home" != "/private/var/empty" ]]; then
   $sudo /bin/rm -rf "$home"
fi


printf "\nSystem service agent account:  \e[1m$agent\e[m  successfully deleted\x21\n\n"


export IFS=$OIFS
export PATH=$OPATH

exit 0


showifs

function showifs() { 
printf "%q\n" "${IFS}"    # cf. help printf
#echo -n "${IFS}" | ruby -n -e 'p $_.to_s'
}

showifs    # show the Internal Field Separator

Rename files by reordering existing data (regular expression capturing submatches)

Takes all files in a directory that end with .jpg and that have a filename with format DD_MM_YY-HHMM.jpg and renames them as YYMMDDHHMM.jpg

#alter the regex to suit your needs, each () will return a submatch
my $regex = qr!(\d\d)[-](\d\d)[-](\d\d)[_](\d\d\d\d)!;

while(<*.jpg>) {
        $oldname = $_;
        if ($oldname=~m/$regex/) {

                #$1,$2,etc are submatchs
                rename $oldname,$3.$2.$1.$4.".jpg";
        }
}

dirmodes

Usage: dirmodes /path/to/directory


function dirmodes() {

   declare dir user group mods
   declare -a ar ret
   declare -i size i n 

   dir="$@"

   if [[ ! -e "$dir" ]]; then printf "%s\n" "Directory (or file) does not exist: $dir"; return 1; fi

   dir=${dir%/}      # remove a trailing slash character "/" if necessary

   OIFS="$IFS"
   export IFS=$'\n'


   i=-1
   while [[ -n "$dir" ]]; do
      i=$[i+1]
      user="$(/usr/bin/stat -f "%Su" "$dir")"
      group="$(/usr/bin/stat -f "%Sg" "$dir")"

      mods="$(/usr/bin/stat -f "%p" "$dir")"
      mods="${mods: -4}"
      #mods="$(/usr/bin/stat -f "%p" "$dir" | /usr/bin/grep -Eo "[[:digit:]]{4}$")"
  
      ar[$i]="$(printf "%-35s        %-50s\n" $user:$group:$mods $dir)"

      dir="$(/usr/bin/dirname "$dir")"

      if [[ "$dir" == '/' ]]; then 
         i=$[i+1] 
         ar[$i]="$(printf "%-35s        %-50s\n" $user:$group:$mods $dir)"
         dir=""
      fi

   done


   # get number of array elements 
   size=$(/bin/expr ${#ar[@]} - 1 )
   n=-1
   
   for (( i=$size; i>=0; i-- )); do    # reverse the array
      n=$[n+1]
      ret[$n]=${ar[$i]}
      printf "%s\n" "$(printf -- "${ar[$i]}" | tr -d '\r\n')"
   done

   export IFS="$OIFS"

   return 0

}

virusscan contextual menu item with Automator

Right-click on a file or folder and select Automator -> virusscan to scan the selected Finder item for viruses.
Requires Automator (Mac OS X 10.4 or later), ClamAV (man clamdscan) and CocoaDialog (for the notification pop-up).


# get the ClamAV icon
cd ~/Desktop
curl -L -O http://freshmeat.net/redir/clamav/29355/url_tgz/clamav-0.92.tar.gz
tar -xzf clamav-0.92.tar.gz
cd clamav-0.92
sudo cp ~/Desktop/clamav-0.92/docs/html/img2.png /usr/local/bin/CocoaDialog.app/Contents/Resources/clamav.png
#sudo sips -i /usr/local/bin/CocoaDialog.app/Contents/Resources/clamav.png
sudo chown -R root:wheel /usr/local/bin/CocoaDialog.app
sudo chmod -R 0755 /usr/local/bin/CocoaDialog.app


open -a Automator


#----------------------------------------------------


Drag or add actions here to build your workflow:
Library: Finder -> Action: Get Selected Finder Items
Library: Automator -> Action: Run Shell Script
                                 - Shell: /bin/bash
                                 - Pass input: as arguments

cocoadialog="/usr/local/bin/CocoaDialog.app/Contents/MacOS/CocoaDialog"
CD_Resources="/usr/local/bin/CocoaDialog.app/Contents/Resources"

for f in "$@"; do

   /opt/local/bin/clamdscan --quiet "$f" 2>/dev/null

   return_code=$?

   if [[ $return_code -eq 0 ]]; then
 
      /usr/bin/say OK

      $cocoadialog bubble --no-timeout --x-placement center --y-placement center --background-top "00FF00" \
          --background-bottom "00FF99" --icon-file $CD_Resources/clamav.png --title "Scanned item is OK!" \
          --text "$f"

   elif [[ $return_code -eq 1 ]]; then

      /usr/bin/say "virus alert"

      $cocoadialog bubble --no-timeout --x-placement center --y-placement center --background-top "FF0000" \
          --background-bottom "FF0066" --icon-file $CD_Resources/clamav.png --title "WARNING: VIRUS ALERT!!!" \
          --text "$f"

   else

      $cocoadialog bubble --no-timeout --x-placement center --y-placement center --background-top "FFCC00" \
          --background-bottom "FFCC33" --icon-file $CD_Resources/clamav.png --title "ClamAV virus scan failed!" \
          --text "Please, check your ClamAV setup! Return code of man clamdscan: $return_code"

      exit 1

   fi

done

exit 0


#----------------------------------------------------


# save the virusscan Automator workflow as a contextual menu item
Automator -> File -> Save As Plug-in ... -> Save Plug-in As: virusscan -> Plug-in for: Finder -> Save

open ~/Library/Workflows/Applications/Finder/virusscan.workflow

# test some files in ...
open ~/Desktop/clamav-0.92/test

Notifications from shell scripts with CocoaDialog


cd ~/Desktop
curl -L -O http://prdownloads.sourceforge.net/cocoadialog/CocoaDialog-2.1.1.dmg
hdiutil mount CocoaDialog-2.1.1.dmg
sudo mkdir -p /usr/local/bin
sudo ditto -rsrc /Volumes/CocoaDialog/CocoaDialog.app /usr/local/bin/CocoaDialog.app
sudo chown -R root:wheel /usr/local/bin/CocoaDialog.app
sudo chmod -R 0755 /usr/local/bin/CocoaDialog.app
hdiutil unmount /Volumes/CocoaDialog


alias cocoadialog=/usr/local/bin/CocoaDialog.app/Contents/MacOS/CocoaDialog

# cf. http://cocoadialog.sourceforge.net/examples/bubble.sh.txt
cocoadialog bubble --no-timeout --x-placement center --y-placement center --background-top "FF0000" --background-bottom "FF0066"  \
                   --icon-file /usr/local/bin/CocoaDialog.app/Contents/Resources/info.icns --title "News" --text ''



Further information:

- CocoaDialog Documentation
- CocoaDialog Examples
- growlnotify
- Pashua
- man automator

Progress indicator snippet for man find


find -x "$HOME" -type f -print0 2>/dev/null | while read -d $'\0' filename; do 
   if [[ ${#filename} -lt 85 ]]; then
      printf -- "\r\e[0K$(printf -- "$filename" | tr -d '\n\r')"; 
   else
      printf -- "\r\e[0K$(printf -- "$filename" | tr -d '\n\r' | sed -En 's/^(.{40}).*(.{40})$/\1.....\2/p')"; 
   fi
done; echo



find -x "$HOME" -type f -print0 2>/dev/null | while read -d $'\0' filename; do 
   i=$[i+1]
   if [[ ${#filename} -lt 85 ]]; then
      printf -- "\r\e[0K\e[1;32m$i\e[0m $(printf -- "$filename" | tr -d '\n\r')"; 
   else
      printf -- "\r\e[0K\e[1;32m$i\e[0m $(printf -- "$filename" | tr -d '\n\r' | sed -En 's/^(.{40}).*(.{40})$/\1.....\2/p')"; 
   fi
done; echo

Read local Unix mail in Mail.app

The following instructions on how to set up a local POP3 (SSL) server to read local Unix mail in Mail.app require an admin user account and Upgrading Postfix with TLS (SSL) support on Mac OS X (10.4). Use at your own risk!


export PATH="/usr/local/bin:/usr/local/sbin:/usr/local/lib:/usr/local/include:/usr/bin:/bin:/usr/sbin:/sbin"


# create a SSL certificate
# cf. http://www.npcole.com/usingimaptiger.html
# NOTE: put "localhost" in the "Common Name" field

cd /System/Library/OpenSSL/certs
sudo openssl req -new -x509 -nodes -days 3650 -out ipop3d.pem -keyout ipop3d.pem 

# If Mail.app later complains about the newly created certificate, just:
- Click "Show Certificate"
- Alt-Drag the icon for the certificate onto your desktop.
- Double-click the file.
- In the dialog box that will appear, use the drop down list to add the key to the "X509Anchors" keychain.

openssl x509 -in /System/Library/OpenSSL/certs/ipop3d.pem -noout -fingerprint -subject -issuer


sudo ln -s /usr/include/pam /usr/include/security
sudo cp -p /private/etc/pam.d/ftpd /private/etc/pam.d/pop
#sudo cp -p /private/etc/pam.d/login /private/etc/pam.d/pop

cd ~/Desktop
curl -L -O http://www.macosxguru.net/downloads/localmail.zip
unzip -qq localmail.zip

sudo mkdir -p /usr/local/libexec
sudo cp ~/Desktop/localmail/ipop3d /usr/local/libexec/

sudo chown root:wheel /usr/local/libexec/ipop3d
sudo chmod 755 /usr/local/libexec/ipop3d

sudo cp ~/Desktop/localmail/edu.washington.pop3s.plist  /Library/LaunchDaemons/edu.washington.pop3s.plist


sudo nano /Library/LaunchDaemons/edu.washington.pop3s.plist

<?xml version="1.0" encoding="UTF-8"?>
DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.
com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>Labelkey>
        edu.washington.pop3s</string>
        <key>ProgramArgumentskey>
        
                /usr/local/libexec/ipop3dstring>
        </array>
        <key>Socketskey>
        
                Listeners</key>
                <dict>
                        <key>Bonjourkey>
                        />
                        <key>SockServiceNamekey>
                        pop3s</string>
                        <key>SockTypekey>
                        stream</string>
                dict>
        </dict>
        <key>inetdCompatibilitykey>
        
                Wait</key>
                <false/>
        dict>
</dict>
plist>

sudo chown root:wheel /Library/LaunchDaemons/edu.washington.pop3s.plist
sudo chmod 644 /Library/LaunchDaemons/edu.washington.pop3s.plist


your_logname="$(/usr/bin/logname)"
echo $your_logname


sudo cp -p /private/etc/postfix/canonical /private/etc/postfix/canonical.orig
sudo nano /private/etc/postfix/canonical
<your_logname>: <your_logname>@localhost


sudo nano /private/etc/postfix/smtpd.conf
auxprop_plugin: login
pwcheck_method: auxprop
mech_list: plain login


sudo cp -p /private/etc/postfix/access /private/etc/postfix/access.orig
sudo nano /private/etc/postfix/access
localhost    OK
<your_logname>@localhost  OK
<your_logname>@local.local  OK
# cf. man hostname
<your_logname>@<your_hostname>  OK



# see man 5 postconf
# check with postconf -n
sudo nano /private/etc/postfix/main.cf

biff = no
command_directory = /usr/sbin
config_directory = /etc/postfix
daemon_directory = /usr/libexec/postfix
data_directory = /var/lib/postfix
debug_peer_level = 2
disable_vrfy_command = yes
html_directory = /private/etc/postfix/html
inet_interfaces = all
mail_owner = postfix
mailq_path = /usr/bin/mailq
manpage_directory = /usr/local/man
mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
mydomain = localdomain
myhostname = localhost.localdomain
mynetworks = 127.0.0.0/24
mynetworks_style = host
myorigin = $mydomain
newaliases_path = /usr/bin/newaliases
queue_directory = /var/spool/postfix
readme_directory = /private/etc/postfix/readme
relayhost = [127.0.0.1]
#relayhost = [SMTP_server_address_of_your_ISP]
sample_directory = /etc/postfix
sender_canonical_maps = hash:/etc/postfix/canonical
sendmail_path = /usr/sbin/sendmail
setgid_group = postdrop
smtp_tls_cert_file = $smtpd_tls_cert_file
smtp_tls_key_file = $smtpd_tls_cert_file
smtp_tls_loglevel = 3
smtp_tls_note_starttls_offer = yes
smtp_tls_scert_verifydepth = 5
smtp_tls_session_cache_database = btree:/var/lib/postfix/smtp_tls_session_cache
smtp_use_tls = yes
smtpd_client_restrictions = check_client_access hash:/etc/postfix/access, reject_unknown_client
smtpd_helo_required = yes
smtpd_helo_restrictions = permit_mynetworks, reject_invalid_hostname, reject_non_fqdn_hostname
smtpd_recipient_restrictions = permit_mynetworks, reject_unauth_destination, reject_non_fqdn_recipient
smtpd_sender_restrictions = check_sender_access hash:/etc/postfix/access, reject_unknown_sender_domain
smtpd_tls_auth_only = yes
smtpd_tls_cert_file = /System/Library/OpenSSL/certs/ipop3d.pem
smtpd_tls_key_file = $smtpd_tls_cert_file
smtpd_tls_loglevel = 3
smtpd_tls_received_header = yes
smtpd_tls_session_cache_database = btree:/var/lib/postfix/smtpd_tls_session_cache
smtpd_use_tls = yes
tls_random_source = dev:/dev/urandom
unknown_local_recipient_reject_code = 550



# reload Postfix
function rpf() {
/usr/bin/sudo /usr/sbin/postmap /private/etc/postfix/access
/usr/bin/sudo /usr/sbin/postalias hash:/private/etc/postfix/canonical
/usr/bin/sudo /usr/bin/newaliases
/usr/bin/sudo /usr/sbin/postfix reload

/usr/bin/sudo /bin/launchctl unload -w /Library/LaunchDaemons/edu.washington.pop3s.plist  2>/dev/null
/usr/bin/sudo /bin/launchctl unload -w /System/Library/LaunchDaemons/org.postfix.master.plist 2>/dev/null

/bin/sleep 2

/usr/bin/sudo /bin/launchctl load -w /Library/LaunchDaemons/edu.washington.pop3s.plist  2>/dev/null
/usr/bin/sudo /bin/launchctl load -w /System/Library/LaunchDaemons/org.postfix.master.plist   2>/dev/null

return 0
}


rpf

sudo ln -s "/Applications/Utilities/Network Utility.app/Contents/Resources/stroke" /bin/portscan
portscan localhost 1  1000     #  Open TCP Port:  25  smtp; Open TCP Port:   995   pop3s


# now create a new mail account in Mail.app
Account Description: POP Account
Full Name: Your Name
Email Address: <your_logname>@localhost
User Name: <your_logname>
Password: <your system user account password>
Incoming Mail Server: localhost
SSL: on
Outgoing Mail Server: localhost
SSL: on
Mail -> Preferences ... -> Accounts -> POP Account -> Account Information -> Server Settings ...: 
- Server port: 25
- Use SSL -> [yes]
- Authentication -> None
Mail -> Preferences ... -> Accounts -> POP Account -> Advanced:
- Port: 995
- Use SSL -> [yes]
- Authentication -> Password


# ignore error message ...
The POP server "localhost" is not responding. Check your network connection and that you entered 
the correct information in the "Incoming Mail Server" field. If it still doesn't respond, the 
server might be temporarily unavailable.

If you continue, you may not be able to receive any email.

# ... but make sure SSL is actually being used in:
- Mail -> Preferences ... -> Accounts -> POP Account -> Account Information -> Server Settings ...
- Mail -> Preferences ... -> Accounts -> POP Account -> Advanced

# now quit Mail.app and save the changes made in Mail -> Preferences ... -> Accounts -> POP Account ... if necessary


open -a Mail


# dialog box 1:
- Enter Password for Account "POP Account"
- Please enter the password for user  on POP server "localhost"
- 
- [yes] Remember this password in my keychain


# dialog box 2:
- Unable to verify SSL server localhost
- Mail was unable to verify the identity of this server, which has a certificate issued to "localhost". The error was:

- The root certificate for this server could not be verified.
- ...

# Once again: if Mail.app complains about the newly created SSL certificate in the above way, just:
- Click "Show Certificate"
- Alt-Drag the icon for the certificate onto your desktop.
- Double-click the file.
- In the dialog box that will appear, use the drop down list to add the key to the "X509Anchors" keychain.
- enter your system user account password if prompted

killall Mail


# check Keychain Access items
open -a 'Keychain Access'

# cf. dialog box 1 above
Keychains -> login -> localhost -> click [i] -> Attributes
Name: localhost
Kind: Internet password
Account: 
Where: pop://localhost
Modified: 

Keychains -> login -> localhost -> click [i] -> Access Control
- [yes] Confirm before allowing access
#- [yes] Ask for Keychain password
- Always allow access by these applications: Name: Mail, Location: /Applications, Version: ...


# cf. dialog box 2 above
Keychains -> X509Anchors -> [Click to unlock the X509Anchors keychain] -> select "localhost - certificate -- X509Anchors" 
-> click [i] -> Trust Settings -> select Use System Settings or Always Trust 


#-------------------


# test

# curl -L -O http://www.unflyingobject.com/tarkvara/files/ssldump.zip
open /bin/bash
sudo ssldump -i lo0 port 995

open /bin/bash
sudo tcpdump -i lo0 port 995 and host localhost

# test 
echo 'hello world' | mail -s 'test mail' $(logname)@localhost
open -a Mail


Further information:

- How to read local Unix mail in Mail.app under Tiger?
- Enable postfix without inducing insomnia
- What is the difference between POP and IMAP?
- Setting up a local IMAP archive of your mail on Tiger
- Setting up IMAP in Apple Mail

Upgrading Postfix with TLS (SSL) support on Mac OS X

1. fix the command search path

echo '
export PATH="/usr/local/bin:/usr/local/sbin:/usr/local/lib:/usr/local/include:/usr/bin:/bin:/usr/sbin:/sbin"
' >> $HOME/.bash_login

source $HOME/.bash_login


2. upgrade Postfix

# first make a backup of the entire /private/etc/postfix directory
sudo /usr/bin/ditto -rsrc -c -k /private/etc/postfix "/private/etc/postfixdir-$(/bin/date "+%Y-%m-%d-%H.%M.%S").zip"

cd ~/Desktop
curl -L -O http://mirrors.isc.org/pub/postfix/official/postfix-2.5.0.tar.gz
tar -xzf postfix-2.5.0.tar.gz
cd postfix-2.5.0

# make -h


# cf. http://www.postfix.org/TLS_README.html
# requires Xcode, http://developer.apple.com/tools/download/

find /usr -type f \( -name "libcrypto*" -or -name "libssl*" \) -print0 2>/dev/null | xargs -0 ls -l

make makefiles \
   CFLAGS='-arch $(/usr/bin/arch) -isysroot /Developer/SDKs/MacOSX10.4u.sdk' \
   CCARGS="-DUSE_TLS" \
   AUXLIBS="-lssl.0.9.7 -lcrypto.0.9.7"


make

# sudo make install
# or
sudo make upgrade


3. remove comments & empty lines from /private/etc/postfix/main.cf

sudo cp -p /private/etc/postfix/main.cf /private/etc/postfix/main.cf.orig   # backup
sudo sed -i "" -e 's/^[[:space:]]*#.*$//g' -e '/^[[:space:]]*$/d' /private/etc/postfix/main.cf
sudo nano /etc/postfix/main.cf


4. create a new Postfix launch daemon

# backup
sudo cp -p /System/Library/LaunchDaemons/org.postfix.master.plist /System/Library/LaunchDaemons/org.postfix.master.plist.orig  

sudo nano /System/Library/LaunchDaemons/org.postfix.master.plist

<?xml version="1.0" encoding="UTF-8"?>
DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>Disabledkey>
        />
        <key>Labelkey>
        org.postfix.master</string>
        <key>OnDemandkey>
        />
        <key>Programkey>
        /usr/libexec/postfix/masterstring>
        ProgramArguments</key>
        <array>
                <string>masterstring>
        </array>
        <key>QueueDirectorieskey>
        
                /var/spool/postfix/maildropstring>
        </array>
dict>
</plist>


sudo launchctl load -w /System/Library/LaunchDaemons/org.postfix.master.plist 2>/dev/null
#sudo launchctl unload -w /System/Library/LaunchDaemons/org.postfix.master.plist 2>/dev/null


5. initialize the configuration

sudo mv /private/etc/postfix/master.cf /private/etc/postfix/master.cf.orig
sudo cp -p /private/etc/postfix/master.cf.defaultserver /private/etc/postfix/master.cf

sudo nano /private/etc/postfix/master.cf
# uncomment the following line if necessary
#smtp      inet  n       -       n       -       -       smtpd     

sudo /usr/sbin/postfix reload


6. test

sudo ln -s "/Applications/Utilities/Network Utility.app/Contents/Resources/stroke" /bin/portscan
portscan localhost 25  25     #  Open TCP Port:  25  smtp

/usr/sbin/postconf -d | grep 'mail_version ='

sudo postfix check
sudo postfix status
#sudo postfix -vv status

open -a Console   # see /private/var/log/mail.log

dscl . -read /Users/postfix
dscl . -read /Groups/postfix
dscl . -read /Groups/postdrop
dscl . list /Groups GroupMembership

otool -Lv /usr/libexec/postfix/smtp       # libssl & libcrypto
otool -Lv /usr/libexec/postfix/smtpd
otool -Lv $(/usr/sbin/postconf -h daemon_directory)/smtpd


# send a mail 
echo 'hello world' | mail -s 'test mail' $(/usr/bin/logname)@localhost
mail   # quit with "q "

# send a mail via telnet

your_logname="$(/usr/bin/logname)"
echo $your_logname

telnet localhost 25
<...>
helo localhost
<...>
ehlo localhost
<...>
mail from:<your_logname>@localhost
<...>
rcpt to:<your_lognam>@localhost
<...>
data
<...>
subject: something
my message
.
<...>
quit

mail


Further information:

- Postfix Installation From Source Code
- Postfix Basic Configuration
- Postfix TLS Support
- Postfix on Mac OS X
- Troubleshooting Postfix
- DIYMacServer

Create & delete user accounts from the command line on Mac OS X

The following two Bash scripts will interactively prompt for user input to create or delete user accounts on Mac OS X 10.4.11. Use them at your own risk!

1. Create a user account

#!/bin/bash

# cf. Adding a User From the Command Line,
# http://developer.apple.com/documentation/Porting/Conceptual/PortingUnix/additionalfeatures/chapter_10_section_9.html

if [[ "$(/usr/bin/whoami)" != "root" ]]; then printf '\nMust be run as root!\n\n'; exit 1; fi

OPATH=$PATH
export PATH=/usr/bin:/usr/sbin:/bin:/sbin

OIFS=$IFS
export IFS=$' \t\n'

declare sudo=/usr/bin/sudo dscl=/usr/bin/dscl

printf "\e[1mEnter first name\e[m: "
read firstname

# no spaces in names
if [[ -z "$(printf -- "$firstname" | /usr/bin/grep -Eo "^[^[:space:]]+$")" ]]; then
   printf '\nUse a name without spaces! \nPlease, try again!\n\n'
   exit 1
fi

# name must not begin with a number
if [[ -n "$(printf -- "$firstname" | /usr/bin/grep -E "^[[:digit:]]")" ]]; then
   printf '\nName must not begin with a number! \nPlease, try again!\n\n'
   exit 1
fi


# make sure the user name is unique
new_user="$(/usr/bin/dscl . -search /Users name "$firstname" 2>/dev/null)"

if [[ -z "$new_user" ]]; then
  new_user="$firstname"               
else
  printf "\nUser name already exists: $firstname \nPlease, modify your name and try it again\x21\n\n"       # cf. man ascii for \x21
  exit 1
fi 


# make sure the user's primary group name is unique 
# note: the user's primary group name is also based on the first name!

new_group="$(/usr/bin/dscl . -search /Groups name "$firstname")"

if [[ -z "$new_group" ]]; then
  new_group="$firstname"             
else
  printf "\nThe user's primary group name already exists: $firstname\x21 \nPlease, try again\x21\n\n"
  exit 1
fi 


# make sure there is no (file or) home directory of the same name already
if [[ -e "/Users/$new_user" ]]; then
  printf "\nUser $new_user already exists at /Users/$new_user\x21 \nPlease, try again\x21\n\n"
  exit 1
fi


# last name

printf "\e[1mEnter last name\e[m: "
read lastname

# no spaces in names
if [[ -z "$(printf -- "$lastname" | /usr/bin/grep -Eo "^[^[:space:]]+$")" ]]; then
   printf '\nUse a name without spaces! \nPlease, try again!\n\n'
   exit 1
fi

# name must not begin with a number
if [[ -n "$(printf -- "$lastname" | /usr/bin/grep -E "^[[:digit:]]")" ]]; then
   printf '\nName must not begin with a number! \nPlease, try again!\n\n'
   exit 1
fi


# enter password

printf "\e[1mEnter password\e[m: "
stty_orig=$(/bin/stty -g) 
pass=''
blank='false'

while [[ "$blank" != "true" ]]; do

   /bin/stty -icanon -echo

   c=$(/bin/dd bs=6 count=1 2> /dev/null)

   # Check for a CR.
   if [[ -z "$(printf -- "$c" | /usr/bin/tr -d "\r\n")" ]]; then
      blank='true'
   else
      /bin/stty echo
      printf "*"
      pass="$pass$c"
      /bin/stty -echo
   fi
done

/bin/stty icanon echo
/bin/stty "$stty_orig"
passwd1="$pass"
printf "\n"


# check minimum password length: 6
if [[ -z "$(printf -- "$passwd1" | /usr/bin/grep -Eo "^([[:alnum:]]|[[:punct:]]){6,}$")" ]]; then
   printf '\nUse at least 6 characters (alphanumeric, punctuational) for your password! \nPlease, try again!\n\n'
   exit 1
fi


# confirm password

printf "\e[1mConfirm password\e[m: "
stty_orig=$(/bin/stty -g) 
pass=''
blank='false'

while [[ "$blank" != "true" ]]; do

   /bin/stty -icanon -echo

   c=$(/bin/dd bs=6 count=1 2> /dev/null)

   # Check for a CR.
   if [[ -z "$(printf -- "$c" | /usr/bin/tr -d "\r\n")" ]]; then
      blank='true'
   else
      /bin/stty echo
      printf "*"
      pass="$pass$c"
      /bin/stty -echo
   fi
done

/bin/stty icanon echo
/bin/stty "$stty_orig"
passwd2="$pass"
printf "\n"


if [[ "$passwd1" != "$passwd2" ]]; then
   printf '\nPasswords do not match. \nPlease, try again!\n\n'
   exit 1
else
   printf '\nPassword confirmation was successful!\n\n'
fi



# get unique id numbers (uid, gid) that are greater than 500
unset -v i new_uid new_gid idvar
declare -i new_uid=0 new_gid=0 i=500 idvar=0

while [[ $idvar -eq 0 ]]; do 
   i=$[i+1]
   if [[ -z "$(/usr/bin/dscl . -search /Users uid $i)" ]] && [[ -z "$(/usr/bin/dscl . -search /Groups gid $i)" ]]; then
      new_uid=$i
      new_gid=$i
      idvar=1
      #break
   fi
done

if [[ $new_uid -eq 0 ]] || [[ $new_gid -eq 0 ]]; then printf 'Getting unique id numbers (uid, gid) failed!\n'; exit 1; fi

# old version
# get unique id numbers (for uid, gid) by increasing the highest id number already in use by 1
#new_uid=$(($(/usr/bin/dscl . -list /Users uid | /usr/bin/awk '{print $NF;}' | /usr/bin/sort -n | /usr/bin/tail -n 1) + 1))
#new_gid=$(($(/usr/bin/dscl . -list /Groups gid | /usr/bin/awk '{print $NF;}' | /usr/bin/sort -n | /usr/bin/tail -n 1) + 1))

# make sure $new_uid and $new_gid are equal
#if [[ $new_uid -ne $new_gid ]]; then
#   if [[ $new_uid -gt $new_gid ]]; then new_gid="$new_uid"; else new_uid=$new_gid; fi
#fi



# check once again ...

if [[ $new_uid -eq $new_gid ]] && [[ "$new_user" == "$firstname" ]] && [[ "$new_group" == "$firstname" ]]; then

# create the user's primary group
$sudo /usr/sbin/dseditgroup -o create -r "$firstname $lastname" -i $new_gid "$new_group"
$sudo $dscl . -append "/Groups/$new_group" passwd "*"

$sudo $dscl . -create "/Users/$new_user"
$sudo $dscl . -append "/Users/$new_user" RealName "$firstname $lastname"
$sudo $dscl . -append "/Users/$new_user" NFSHomeDirectory "/Users/$new_user"
###$sudo $dscl . -append "/Users/$new_user" NFSHomeDirectory "/Local/Users/$new_user"
$sudo $dscl . -append "/Users/$new_user" UserShell /bin/bash   
$sudo $dscl . -append "/Users/$new_user" PrimaryGroupID $new_gid
$sudo $dscl . -append "/Users/$new_user" UniqueID $new_uid
$sudo $dscl . -append "/Users/$new_user" hint ""
$sudo $dscl . -append "/Users/$new_user" comment "user account \"$firstname $lastname\" created: $(/bin/date)"
$sudo $dscl . -append "/Users/$new_user" picture "/Library/User Pictures/Animals/Butterfly.tif"
$sudo $dscl . -append "/Users/$new_user" sharedDir Public
$sudo $dscl . -passwd "/Users/$new_user" "$passwd1"

# add some other properties that are usually set (Mac OS X 10.4)
$sudo $dscl . -append "/Users/$new_user" _shadow_passwd ""
$sudo $dscl . -append "/Users/$new_user" _writers_hint "$new_user"
$sudo $dscl . -append "/Users/$new_user" _writers_real_name "$new_user"

$sudo $dscl . -append "/Groups/$new_group" GroupMembership "$new_user"      # add new user to the user's primary group
#$sudo /usr/sbin/dseditgroup -o edit -a "$new_group" -t user "$new_user"

$sudo $dscl . -append /Groups/staff GroupMembership "$new_user"             # test: add new user to group staff

# add the new user to the admin group (Mac OS X 10.4)
# This should be part of a separate admin user account shell script or 
# at least require an additional user input prompt at the beginning!
#$sudo $dscl . -append /Groups/admin GroupMembership "$new_user"
#$sudo $dscl . -append /Groups/appserverusr GroupMembership "$new_user"
#$sudo $dscl . -append /Groups/appserveradm GroupMembership "$new_user"

# log out after running the script to see the new user account has been created
$sudo /usr/sbin/createhomedir -l -u "$new_user"

else

   printf "\nConfiguration of user account: $firstname failed\x21 \nPlease, try again\x21\n\n"
   exit 1

fi


printf "\nUser account:  $firstname  successfully created\x21 \nYou can now log in to your new user account\x21\n\n"

export IFS=$OIFS
export PATH=$OPATH

exit 0


#---------------------------


# test
dscl . list /Users
dscl . -read /Users/<firstname>

dscl . list /Groups
dscl . -read /Groups/<firstname>
dscl . list /Groups GroupMembership



2. Delete a user account


#!/bin/bash

# cf. http://www.macos.utah.edu/documentation/authentication/dscl.html

if [[ "$(/usr/bin/whoami)" != "root" ]]; then printf '\nMust be run as root!\n\n'; exit 1; fi

OPATH=$PATH
export PATH=/usr/bin:/usr/sbin:/bin:/sbin

OIFS=$IFS
export IFS=$' \t\n'

declare sudo=/usr/bin/sudo

printf "\e[1mDelete user account\e[m: "
read user

if [[ -z "$user" ]]; then printf '\nNo user specified! Please, try again!\n\n'; exit 1; fi

# make sure the user exists
usertest="$(/usr/bin/dscl . -search /Users name "$user" 2>/dev/null)"

if [[ -z "$usertest" ]]; then printf "\nUser does not exist: $user\n\n"; exit 1; fi 


# get user's group memberships
groups_of_user="$(/usr/bin/id -Gn $user)"

if [[ $? -eq 0 ]] && [[ -n "$(/usr/bin/dscl . -search /Groups GroupMembership "$user")" ]]; then 
   # delete the user's group memberships
   for group in $groups_of_user; do
      $sudo /usr/bin/dscl . -delete "/Groups/$group"  GroupMembership "$user"
      #$sudo /usr/sbin/dseditgroup -o edit -d "$user" -t user "$group"           
   done
fi


# delete the user's primary group
if [[ -n "$(/usr/bin/dscl . -search /Groups name "$user")" ]]; then
   $sudo /usr/sbin/dseditgroup -o delete "$user"
fi

# if the user's primary group has not been deleted ...
if [[ -n "$(/usr/bin/dscl . -search /Groups name "$user")" ]]; then
printf "
   \e[1mWarning\e[m:
   The group memberships of the user \e[1m$user\e[m have been deleted\x21
   groups_of_user: $groups_of_user
   The user's primary group \e[1m$user\e[m, however, has not been deleted\x21
   Please, try again\x21
   Exiting ...\n
"
  exit 1
fi


# find the GeneratedUID of the user and remove the password hash file 
# from /private/var/db/shadow/hash/
# sudo ls -a /private/var/db/shadow/hash
# sudo ls -l /private/var/db/shadow/hash/

guid="$(/usr/bin/dscl . -read "/Users/$user" GeneratedUID | /usr/bin/awk '{print $NF;}')"

if [[ -f "/private/var/db/shadow/hash/$guid" ]]; then
   $sudo /bin/rm -f /private/var/db/shadow/hash/$guid
fi


# delete the user
$sudo /usr/bin/dscl . -delete "/Users/$user"

# make a backup
if [[ -d "/Users/$user" ]]; then
   $sudo /usr/bin/ditto -rsrc -c -k "/Users/$user" "/Users/${user}-archive-$(/bin/date).zip"
fi

# remove the user's home directory
if [[ -d "/Users/$user" ]]; then
   $sudo /bin/rm -rf "/Users/$user"
fi

export IFS=$OIFS
export PATH=$OPATH

exit 0



Manually Starting & Stopping MySQL in Leopard

This is for software built from source according to:
http://hivelogic.com/articles/installing-mysql-on-mac-os-x/

Start MySQL
sudo launchctl unload -w /Library/LaunchDaemons/com.mysql.mysqld.plist


Stop MySQL
sudo launchctl load -w /Library/LaunchDaemons/com.mysql.mysqld.plist


Save & restore file permissions with xattr

Extended attributes make it possible to store (additional) file metadata on a per-file basis. This can, for example, be used to save & restore file permissions.


mkdir -p ~/Desktop/testdir
touch ~/Desktop/testdir/f{1,2,3}.txt
find -x ~/Desktop/testdir -print0 | xargs -0 stat -LF
stat -x ~/Desktop/testdir/f1.txt

which xattr   # /opt/local/bin/xattr; sudo port install xattr (after installing MacPorts, http://www.macports.org/install.php)


# store file permissions as additional Unix file metadata with xattr

find -x ~/Desktop/testdir -print0 | while read -d $'\0' filename; do
#find -x ~/Desktop/testdir -type f -or -type d -print0 | while read -d $'\0' filename; do
   fchmod="$(stat -f %p "$filename")"; \
   fchown="$(stat -f %u:%g "$filename")"; \
   xattr --set fchmod "$fchmod" "$filename"; \
   xattr --set fchown "$fchown" "$filename"; \
   #echo $fchown $fchmod; \
done


find -x ~/Desktop/testdir -print0 | xargs -0 xattr --list
find -x ~/Desktop/testdir -print0 | xargs -0 stat -LF

# change file permissions & ownership
sudo chown -R root:wheel ~/Desktop/testdir
sudo chmod -R 700 ~/Desktop/testdir

sudo find -x ~/Desktop/testdir -print0 | xargs -0 sudo stat -LF
find -x ~/Desktop/testdir -print0 | xargs -0 xattr --list
sudo find -x ~/Desktop/testdir -print0 | xargs -0 sudo xattr --list


# restore file permissions & ownership

sudo find -x ~/Desktop/testdir -print0 | while read -d $'\0' filename; do 
   fchown="$(sudo xattr --get fchown "$filename" | awk '/fchown/ {print $2}')"; \
   fchmod="$(sudo xattr --get fchmod "$filename" | awk '/fchmod/ {print $2}')"; \
   sudo chown "$fchown" "$filename"; \
   sudo chmod "$fchmod" "$filename"; \
   #echo $fchown $fchmod; \
done


find -x ~/Desktop/testdir -print0 | xargs -0 stat -LF
find -x ~/Desktop/testdir -print0 | xargs -0 xattr --list


Convert chmod file permissions between octal & symbolic notation


# convert from octal to symbolic format

function o2s () { touch ~/.octal2symbolic; chmod -vv "$@" $_;  ls -l $_ | awk '{print $1}'; rm -f ~/.octal2symbolic; }
#function o2s () { touch ~/.octal2symbolic; chmod "$@" $_;  ls -l $_ | awk '{print $1}'; rm -f ~/.octal2symbolic; }

o2s 755
o2s 644
o2s 1744
o2s 0100751      # cf. man 2 stat (st_mode)
o2s 0101751


# Cf. Bash 'umask' builtin doesn't set 'x' permissions?
# http://archives.devshed.com/forums/unix-linux-135/bash-umask-builtin-doesn-t-set-x-permissions-372356.html

UMASKO=$(umask)   # 0022

help umask
umask
umask -p
umask -S

umask 0077
umask
umask -S
o2s 755

umask 0000
umask
umask -S
o2s 654

umask $UMASKO



# convert from symbolic to octal format
# default file permissions of temporary file: chmod 777 ~/.symbolic2octal

function s2o () { touch ~/.symbolic2octal; chmod 777 $_; chmod "$@" $_;  stat -f %p $_; rm -f ~/.symbolic2octal; }

s2o a-x
s2o u=rwx,g=rx,o=x
s2o u=rwx,g=rx,o=x,+t

Automated virus scanning with ClamAV on Mac OS X 10.4

Author: jv
License: The MIT License, Copyright (c) 2008 jv
Description: basic setup to scan files added to specified directories automatically for viruses using ClamAV; for instructions on how to run ClamAV from a system service agent account (non-root) see here; use at your own risk
Platform: Mac OS X 10.4.11 Client
Requirements: sudo port install clamav (after installing MacPorts); to fix the command search path, insert the following statement at the end of your ~/.bash_login file: export PATH="/opt/local/bin:/opt/local/sbin:$PATH" and then: source ~/.bash_login



# download ClamAV
sudo port install clamav      # requires open firewall port 873
#sudo port uninstall clamav

man clamd
man clamd.conf
man clamdscan
man clamscan
man freshclam


# configure /opt/local/etc/freshclam.conf

sudo cp -p /opt/local/etc/example-freshclam.conf /opt/local/etc/freshclam.conf
sudo sed -i "" -e 's/^Example/#Example/' /opt/local/etc/freshclam.conf              # comment out 'Example' line
#sudo sed -i "" -e 's/^#Example/Example/' /opt/local/etc/freshclam.conf              # uncomment 'Example' line
sudo nano /opt/local/etc/freshclam.conf


# make sure you are a member of the wheel and admin group
id -G -n $(whoami) | grep -Eo 'wheel|admin'
dseditgroup -o checkmember -m $(whoami) wheel; echo $?
dseditgroup $(whoami)
dseditgroup wheel
dseditgroup admin

sudo dscl . -append /Groups/wheel GroupMembership $(whoami)      # add user to group if necessary
#sudo dseditgroup -o edit -a $(whoami) -t user wheel             # add user to group
#sudo dscl . -delete /Groups/wheel GroupMembership $(whoami)     # delete user from group
#sudo dseditgroup -o edit -d $(whoami) -t user wheel             # delete user from group


sudo chown -R root:wheel /opt/local/share/clamav  
sudo chmod -R 0770 /opt/local/share/clamav

freshclam       # update virus database

# test some clamav commands
clamscan /path/to/file
sudo clamscan -r /tmp
sudo clamscan -r /private/var/tmp
clamscan -r ~/Library/Caches
clamscan -r ~/Library/Caches/java
sudo clamscan -r ~/Library/Mail
clamscan -r ~/Library


# configure /opt/local/etc/clamd.conf
# open /opt/local/etc
# sudo nano /opt/local/etc/clamd.conf
# cf. http://www.silvester.org.uk/OSX/configuring_clamd.html

sudo cp -p /opt/local/etc/clamd.conf /opt/local/etc/clamd.conf.orig

sudo sh -c '
cat << EOF > /opt/local/etc/clamd.conf
LogFile /private/var/log/clamd.log
LogFileMaxSize 10M
LogTime yes
TemporaryDirectory /private/var/tmp
DatabaseDirectory /opt/local/share/clamav
LocalSocket /tmp/clamd
FixStaleSocket yes
TCPAddr 127.0.0.1
MaxConnectionQueueLength 30
MaxThreads 20
ExitOnOOM yes
ScanOLE2 yes  # Microsoft Office documents and .msi files
ScanPDF yes
ArchiveMaxFileSize 100M
ArchiveMaxCompressionRatio 0
#VirusEvent echo virus: %v >> /path/to/file.txt
EOF
'

sudo chown root:wheel /opt/local/etc/clamd.conf
sudo chmod 750 /opt/local/etc/clamd.conf



Create the launchd item /Library/LaunchDaemons/net.clamav.clamd.plist


sudo nano /Library/LaunchDaemons/net.clamav.clamd.plist

<?xml version="1.0" encoding="UTF-8"?>
DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
   <dict>
        <key>Disabledkey>
        />
        <key>Labelkey>
        net.clamav.clamd</string>
        <key>ProgramArgumentskey>
        
                /opt/local/sbin/clamdstring>
                -c</string>
                <string>/opt/local/etc/clamd.confstring>
        </array>
        <key>RunAtLoadkey>
        />
        <key>UserNamekey>
        root</string>
   dict>
</plist>


sudo chown root:wheel /Library/LaunchDaemons/net.clamav.clamd.plist
sudo chmod 0644 /Library/LaunchDaemons/net.clamav.clamd.plist

sudo launchctl load -w /Library/LaunchDaemons/net.clamav.clamd.plist
#sudo launchctl unload -w /Library/LaunchDaemons/net.clamav.clamd.plist



Create watchdirs.sh


# create ClamAV directories
mkdir -p "$HOME/Documents/ClamAV/Quarantine"
mkdir -p "$HOME/Documents/ClamAV/Downloads"           # Safari -> Preferences ... -> Save downloaded files to: 
mkdir -p "$HOME/Documents/ClamAV/EmailAttachments"
sudo chown -R $(whoami):wheel "$HOME/Documents/ClamAV"
sudo chmod -R 770 "$HOME/Documents/ClamAV"

# create a timestamp file
touch ~/.clamav_ timestamp
sudo chown $(whoami):wheel "$HOME/.clamav_timestamp"
sudo chmod 400 "$HOME/.clamav_timestamp"
stat -x $HOME/.clamav_timestamp


#---------------------------------


# Create a BASH script that will - controlled by ~/Library/LaunchAgents/net.clamav.dirwatcher.plist below - run 
# a clamdscan or clamscan command on files that have been changed or modified in the specified directories

# nano $HOME/Documents/ClamAV/watchdirs.sh

#!/bin/bash

exec >/dev/console 2>&1   # write stdout & stderr to console.log in /Library/Logs/Console/

echo -e "\n$(/bin/date "+%Y-%m-%d %H:%M:%S %Z"): ... WATCHDIRS.SH for ClamAV ... STARTED ...\n"


# All files added to the watched directories during the specified sleep period (in seconds) will be scanned for viruses.
# Files added to the directories while a virus scan is being done may not be included in the current virus scan, but
# they will get scanned next time a virus scan is scheduled to run which can, for example, be determined by the launch agent 
# variable StartInterval. Increase the value of the specified sleep period (in seconds) if you expect large files or 
# directories to be copied or downloaded to the watched directories.

/bin/sleep 60


SCANDIR1="$HOME/Documents/ClamAV/Downloads" 
SCANDIR2="$HOME/Documents/ClamAV/EmailAttachments"
QUARANTINEDIR="$HOME/Documents/ClamAV/Quarantine"
TOUCHFILE="$HOME/.clamav_timestamp"

find=/usr/bin/find
clamdscan=/opt/local/bin/clamdscan
clamscan=/opt/local/bin/clamscan

if [[ ! -e "$TOUCHFILE" ]]; then 
   /usr/bin/touch -afm "$TOUCHFILE"
   /usr/sbin/chown $(whoami):$(whoami) "$TOUCHFILE"
   /bin/chmod 400 "$TOUCHFILE"
   $clamdscan --copy="$QUARANTINEDIR" "$SCANDIR1" || $clamscan -i --copy="$QUARANTINEDIR" -r "$SCANDIR1"
   $clamdscan --copy="$QUARANTINEDIR" "$SCANDIR2" || $clamscan -i --copy="$QUARANTINEDIR" -r "$SCANDIR2"
   exit 0
fi


timestamp=$(/bin/date "+%Y%m%d%H%M.%S")    # store timestamp before starting the virus scan


if /bin/ps -ax | /usr/bin/grep clamd | /usr/bin/grep -v grep > /dev/null; then        

   # run clamdscan
   # scan all files that have been changed or modified after the $TOUCHFILE timestamp of the last virus scan
   # optional: $find -x "$SCANDIR1" "$SCANDIR2" -type f ... (but mind the 'sysctl kern.argmax' limit for xargs!)
   # optional: to exclude .DS_Store files add: \! -name ".DS_Store"

   # alternative: first find changed or modified directories
   # find -x "$SCANDIR1" "$SCANDIR2" -type d \( -newercm "$TOUCHFILE" -or -newermm "$TOUCHFILE" \) -print0 | while read -d $'\0' scandir; do echo "find -x $scandir -maxdepth 1 -type f ..."; done

   $find -x "$SCANDIR1" -type f \( -newercm "$TOUCHFILE" -or -newermm "$TOUCHFILE" \) -print0 | xargs -0 $clamdscan --copy="$QUARANTINEDIR"
   $find -x "$SCANDIR2" -type f \( -newercm "$TOUCHFILE" -or -newermm "$TOUCHFILE" \) -print0 | xargs -0 $clamdscan --copy="$QUARANTINEDIR"

else 

   # run clamscan
   $find -x "$SCANDIR1" -type f \( -newercm "$TOUCHFILE" -or -newermm "$TOUCHFILE" \) -print0 | xargs -0 $clamscan -i --copy="$QUARANTINEDIR"
   $find -x "$SCANDIR2" -type f \( -newercm "$TOUCHFILE" -or -newermm "$TOUCHFILE" \) -print0 | xargs -0 $clamscan -i --copy="$QUARANTINEDIR"

fi


# update the $TOUCHFILE timestamp with the pre-scan $timestamp
/usr/bin/touch -f -t $timestamp "$TOUCHFILE"    

echo -e "\n$(/bin/date "+%Y-%m-%d %H:%M:%S %Z"): ... WATCHDIRS.SH for ClamAV ... DONE ...\n"

exit 0


#---------------------------------


sudo chown root:wheel ~/Documents/ClamAV/watchdirs.sh 
sudo chmod 0770 ~/Documents/ClamAV/watchdirs.sh



Create the ~/Library/LaunchAgents/net.clamav.dirwatcher.plist launch agent


# nano ~/Library/LaunchAgents/net.clamav.dirwatcher.plist
# open -e ~/Library/LaunchAgents/net.clamav.dirwatcher.plist


# Note: Don't use $HOME or any other variables in the file paths you have to specify in the .plist files below! 
#       Use the full file paths without any variables!


# WatchPaths virus scanner launch agent (combined with StartInterval)
# scan the directories specified the same in watchdirs.sh and this .plist file when they are modified or after a specified time interval respectively

<?xml version="1.0" encoding="UTF-8"?>
DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>Disabledkey>
        />
        <key>Labelkey>
        net.clamav.dirwatcher</string>
        <key>LowPriorityIOkey>
        />
        <key>OnDemandkey>
        />
        <key>ProgramArgumentskey>
        
                /full/path/to/Documents/ClamAV/watchdirs.shstring>
        </array>
        <key>StartIntervalkey>
        1800</integer>
        <key>WatchPathskey>
        
                /full/path/to/Documents/ClamAV/Downloadsstring>
                /full/path/to/Documents/ClamAV/EmailAttachmentsstring>
        </array>
        
        
dict>
</plist>


#--------------------------------------------------------------------------------


# StartInterval virus scanner launch agent
# scan the directories specified in watchdirs.sh at the specified time intervals (given in seconds)

<?xml version="1.0" encoding="UTF-8"?>
DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>Disabledkey>
        />
        <key>Labelkey>
        net.clamav.dirwatcher</string>
        <key>LowPriorityIOkey>
        />
        <key>ProgramArgumentskey>
        
                /full/path/to/Documents/ClamAV/watchdirs.shstring>
        </array>
        <key>StartIntervalkey>
        7200</integer>
        
        
dict>
</plist>


#--------------------------------------------------------------------------------


# StartCalendarInterval virus scanner launch agent
# scan the directories specified in watchdirs.sh at the specified times of the day

<?xml version="1.0" encoding="UTF-8"?>
DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>Disabledkey>
        />
        <key>Labelkey>
        net.clamav.dirwatcher</string>
        <key>LowPriorityIOkey>
        />
        <key>ProgramArgumentskey>
        
                /full/path/to/Documents/ClamAV/watchdirs.shstring>
        </array>
        <key>StartCalendarIntervalkey>
        
                Hour</key>
                <integer>7integer>
                Minute</key>
                <integer>45integer>
        </dict>
        <key>StartCalendarIntervalkey>
        
                Hour</key>
                <integer>12integer>
                Minute</key>
                <integer>30integer>
        </dict>
        
        
dict>
</plist>


#--------------------------------------------------------------------------------


sudo chown root:wheel ~/Library/LaunchAgents/net.clamav.dirwatcher.plist 
sudo chmod 0770 ~/Library/LaunchAgents/net.clamav.dirwatcher.plist
ls -l ~/Library/LaunchAgents/net.clamav.dirwatcher.plist

launchctl load -w ~/Library/LaunchAgents/net.clamav.dirwatcher.plist
#launchctl unload -w ~/Library/LaunchAgents/net.clamav.dirwatcher.plist

sudo reboot

open -a Console

# update clamd virus database
freshclam; sleep 5; sudo clamd RELOAD



Alternative watchdirs.sh for a QueueDirectories virus scanner launch agent


# nano $HOME/Documents/ClamAV/watchdirs.sh

#!/bin/bash

# check downloaded files or directories in the specified $QueueDirectory for viruses and then move them to $MoveDir
# rename the files or folders to be moved to $MoveDir if necessary
# remove anything else from the $QueueDirectory

exec >/dev/console 2>&1   # write stdout & stderr to console.log in /Library/Logs/Console/

echo -e "\n$(/bin/date "+%Y-%m-%d %H:%M:%S %Z"):  ... WATCHDIRS.SH for ClamAV ... STARTED ...\n"

/bin/sleep 5

QUARANTINEDIR="$HOME/Documents/ClamAV/Quarantine"
TOUCHFILE="$HOME/.clamav_timestamp"

QueueDirectory="$HOME/Documents/ClamAV/Downloads/QueueDirectory"    # Safari -> Preferences ... -> Save downloaded files to: 
/bin/mkdir -p "$QueueDirectory"

MoveDir="$HOME/Documents/ClamAV/Downloads"
/bin/mkdir -p "$MoveDir"

find=/usr/bin/find
clamdscan=/opt/local/bin/clamdscan
clamscan=/opt/local/bin/clamscan

if [[ -f "$QueueDirectory"/.DS_Store ]]; then /bin/rm -f "$QueueDirectory"/.DS_Store; fi


# alternative to $TOUCHFILE time stamp
#/usr/bin/touch ~/Desktop/test.txt
#t1=$(/bin/date +%s)
#/bin/sleep 1
#/usr/bin/touch -f -am ~/Desktop/test.txt   # ... file is being downloaded ...
#t2=$(/usr/bin/stat -f %m ~/Desktop/test.txt)
#echo $(($t1 - $t2))
#if [[ $t1 -lt $t2 ]]; then echo "file was modified"; else echo "file was not modified"; fi


# directory test
DirTest="$($find -x "$QueueDirectory" -type d -maxdepth 1 -not -regex "^$QueueDirectory$")"

if [[ -n "$DirTest" ]]; then           # download is directory

   $find -x "$QueueDirectory" -type d -maxdepth 1 -not -regex "^$QueueDirectory$" -print0 | while read -d $'\0' dir; do

      /usr/bin/touch -f -am "$TOUCHFILE"
      /bin/sleep 3

      unset -v file_modified
      file_modified=0                      # check if a file within $dir has been modified
      while read -d $'\0' file; do  
         # set file_modified to 1 if at least one file has been modified
         if [[ "$TOUCHFILE" -ot "$file" ]]; then file_modified=1; break; fi     
      done < <($find -x "$dir" -type f -print0 2>/dev/null)

      if [[ -d "$dir" ]] && [[ "$TOUCHFILE" -nt "$dir" ]] && [[ $file_modified -eq 0 ]]; then
         $clamdscan --no-summary --copy="$QUARANTINEDIR" "$dir" || $clamscan -i -r --no-summary --copy="$QUARANTINEDIR" "$dir"
         bname="$(/usr/bin/basename "$dir")"
         if [[ ! -d "$MoveDir/$bname" ]]; then
            /bin/mv "$dir" "$MoveDir" 
         else
            newMoveDir="$MoveDir/$(/bin/date "+%Y-%m-%d-%H%M.%S")-$bname"
            /bin/mv "$dir" "$newMoveDir" 
         fi  
      fi

   done

else           

   $find -x "$QueueDirectory" -type f -maxdepth 1 -print0 | while read -d $'\0' file; do      # download is file

      /usr/bin/touch -f -am "$TOUCHFILE"
      /bin/sleep 3

      if [[ -f "$file" ]] && [[ "$TOUCHFILE" -nt "$file" ]]; then
         $clamdscan --no-summary --copy="$QUARANTINEDIR" "$file" || $clamscan -i --no-summary --copy="$QUARANTINEDIR" "$file"
         # the mv command preserves metadata and resource forks of files on Extended HFS volumes (Mac OS X 10.4)

         bname="$(/usr/bin/basename "$file")"
         if [[ ! -f "$MoveDir/$bname" ]]; then
            /bin/mv "$file" "$MoveDir" 
         else
            newMoveDir="$MoveDir/$(/bin/date "+%Y-%m-%d-%H%M.%S")-$bname"
            /bin/mv "$file" "$newMoveDir" 
         fi  
      fi

   done

fi


if [[ -f "$QueueDirectory"/.DS_Store ]]; then /bin/rm -f "$QueueDirectory"/.DS_Store; fi

$find -x "$QueueDirectory" -not -type f -not -type d -print0 | while read -d $'\0' item; do /bin/rm -f "$item"; done

echo -e "\n$(/bin/date "+%Y-%m-%d %H:%M:%S %Z"):  ... WATCHDIRS.SH for ClamAV ... DONE ...\n"

exit 0


#------------------------------------

# nano ~/Library/LaunchAgents/net.clamav.dirwatcher.plist





        Disabled
        
        Label
        net.clamav.dirwatcher
        ProgramArguments
        
                /full/path/to/Documents/ClamAV/watchdirs.sh
        
        QueueDirectories
        
                /full/path/to/Documents/ClamAV/Downloads/QueueDirectory
        





Update clamd virus database automatically


# set permissions
#sudo chown -R root:wheel /opt
#sudo chmod -R 755  /opt
#sudo chown -R root:wheel /opt/local/share/clamav  
#sudo chmod -R 0770 /opt/local/share/clamav


# bash script to update the clamd virus database
# sudo nano /usr/local/sbin/update_clamd_db.sh

#!/bin/bash

/bin/sleep 120

# write stdout & stderr to console.log
exec >/dev/console 2>&1   

# check if internet connection is alive and database.clamav.net is reachable
/usr/bin/curl -I -L -s --max-time 15 database.clamav.net 1>/dev/null   


if [[ $(echo $?) -eq 0 ]]; then

   /opt/local/bin/freshclam -u root

   /bin/sleep 3

   #if [[ -e "/tmp/clamd" ]]; then /bin/rm -f /tmp/clamd; fi
   #/opt/local/sbin/clamd -c /opt/local/etc/clamd.conf     # recreates the local socket file /tmp/clamd as specified in /opt/local/etc/clamd.conf above

   (/bin/sleep 3; echo RELOAD; /bin/sleep 3; echo "exit") | /usr/bin/telnet -u /tmp/clamd >/dev/null 2>&1
   #echo RELOAD | /opt/local/bin/socat - /tmp/clamd         # an alternative that requires: sudo port install socat

   /bin/sleep 3

   echo -e "\n$(/bin/date "+%Y-%m-%d %H:%M:%S %Z"): clamd virus database successfully updated\n"
   exit 0

else

   echo -e "\n$(/bin/date "+%Y-%m-%d %H:%M:%S %Z"): updating the clamd virus database failed; no internet connection to database.clamav.net established\n"
   exit 0     # leave launchd undisturbed

fi


# set permissions
sudo chown root:wheel /usr/local/sbin/update_clamd_db.sh
sudo chmod 0770 /usr/local/sbin/update_clamd_db.sh


#------------------------


# launchd item to update the clamd virus database using /usr/local/sbin/update_clamd_db.sh
# sudo nano /Library/LaunchDaemons/net.clamav.update.clamd.db.plist

<?xml version="1.0" encoding="UTF-8"?>
DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>Disabledkey>
        />
       <key>Labelkey>
        net.clamav.update.clamd.db</string>
        <key>ProgramArgumentskey>
        
                /usr/local/sbin/update_clamd_db.shstring>
        </array>
        <key>RunAtLoadkey>
        />
        <key>StartIntervalkey>
        10800</integer>
        <key>UserNamekey>
        root</string>
        <key>GroupNamekey>
        wheel</string>
dict>
</plist>


sudo chown root:wheel /Library/LaunchDaemons/net.clamav.update.clamd.db.plist
sudo chmod 0644 /Library/LaunchDaemons/net.clamav.update.clamd.db.plist

sudo launchctl load -w /Library/LaunchDaemons/net.clamav.update.clamd.db.plist
#sudo launchctl unload -w /Library/LaunchDaemons/net.clamav.update.clamd.db.plist

sudo reboot


# simple shell script syntax check
# for more see Debugging Bash scripts, http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_02_03.html
bash -n $HOME/Documents/ClamAV/watchdirs.sh
bash -n /usr/local/sbin/update_clamd_db.sh

# optional: convert XML .plist files to binary format
sudo plutil -convert binary1 -- /Library/LaunchDaemons/net.clamav.clamd.plist \
~/Library/LaunchAgents/net.clamav.dirwatcher.plist \
/Library/LaunchDaemons/net.clamav.update.clamd.db.plist

# check .plist file syntax
plutil -- /Library/LaunchDaemons/net.clamav.clamd.plist \
~/Library/LaunchAgents/net.clamav.dirwatcher.plist \
/Library/LaunchDaemons/net.clamav.update.clamd.db.plist


# list virus signatures
sigtool -l | grep -Ei 'adware|spy' | nl
sigtool -l | grep -i phish | nl



Further information:

- Run ClamAV from a system service agent account
- ClamAV an open-source anti-virus toolkit
- Configuring Clamav's clamd for enhanced virus-scanning performance
- Updating ClamAV on OS X Server 10.4.7-10.4.11
- Using Open Source Tools to Filter Email on Mac OS X Server
- The Anti-Virus Or Anti-Malware Test File
- Mac OS X Unix Tutorial: Part 4 - Managing Permissions
- launchd

Tweak your BASH startup files

Depending on whether the BASH shell is started as a login shell or a non-login shell, different BASH startup files are going to be run (see the references below and the Invocation section of man bash for more detailed information). To make sure your customized BASH shell works the way it should you can source /private/etc/bashrc from /private/etc/profile and ~/.bashrc from ~/.bash_login (or alternatives):


# BASH startup files

/private/etc/profile      # login shell
~/.bash_profile
~/.bash_login
~/.profile

/private/etc/bashrc      # non-login shell
~/.bashrc


# list your BASH startup files
ls -a ~ | grep \.bash
ls /private/etc | grep -E "bash|profile"

# backup the existing BASH startup files
sudo cp -p /private/etc/profile /private/etc/profile.orig
sudo cp -p /private/etc/bashrc /private/etc/bashrc.orig
cp -p ~/.bash_profile ~/.bash_profile.orig
cp -p ~/.bash_login ~/.bash_login.orig
cp -p ~/.profile ~/.profile.orig
cp -p ~/.bashrc ~/.bashrc.orig


# /private/etc/profile should contain: 
# if [[ -f /private/etc/bashrc ]]; then source /private/etc/bashrc; fi 

sudo sh -c 'echo "if [[ -f /private/etc/bashrc ]]; then source /private/etc/bashrc; fi" >> /private/etc/profile'
sudo nano /private/etc/profile


# ~/.bash_profile or ~/.bash_login or ~/.profile should contain: 
# if [[ -f ~/.bashrc ]]; then source ~/.bashrc; fi 

echo 'if [[ -f ~/.bashrc ]]; then source ~/.bashrc; fi' >> ~/.bash_login
nano ~/.bash_login


# then add a simple 'echo' command to the existing BASH startup files
sudo sh -c 'echo "echo \"... running /private/etc/profile ...\"" >> /private/etc/profile'
sudo sh -c 'echo "echo \"... running /private/etc/bashrc ...\"" >> /private/etc/bashrc'
echo 'echo "... running ~/.bash_profile ..."' >> ~/.bash_profile
echo 'echo "... running ~/.bash_login ..."' >> ~/.bash_login
echo 'echo "... running ~/.profile ..."' >> ~/.profile
echo 'echo "... running ~/.bashrc ..."' >> ~/.bashrc

# check added lines
sudo nano /private/etc/profile
sudo nano /private/etc/bashrc
nano ~/.bash_profile
nano ~/.bash_login
nano ~/.profile
nano ~/.bashrc

exit

# now relaunch Terminal.app and play around with ...
bash
echo $SHLVL
exec bash
echo $SHLVL
exec bash -$-
exec env -i /bin/bash
exec bash --login
exec bash -l
echo $0
echo $BASH_ENV
exec bash -norc
bash
bash --init-file file
bash --rcfile file
open-x11 xterm
ssh some_other_user@internal_IP_address     # cf. http://textsnippets.com/posts/show/1326
...


Further information:

- Controlling Bash At Startup
- Make X11 Xterm Launch Login Shells
- Initialisation Scripts
- Difference between .bashrc and .bash_profile
- Fun and profit by modifying your Bash startup files in OS X, Linux, and other fine unices
- bash shell aliases
- A Sample .bashrc File
- Theming Bash
- .bash_profile
- .bashrc

Local SOCKS Proxy for Safari

Surfing the web with Safari (3.0.4) on Mac OS X 10.4 can be made a bit more private & secure by setting up a local SOCKS Proxy on an admin user account.
Use the following BASH command-line instructions at your own risk!

I. Setting up a local SOCKS proxy for Safari on a single admin user account


# first enable remote login on your admin user account: System Preferences > Sharing > Services > Remote Login

# test if remote login is enabled
sudo launchctl list | grep com.openssh.sshd                               # com.openssh.sshd
defaults read /System/Library/LaunchDaemons/ssh
netstat -an | awk '/\*\.22[[:space:]]+.*LISTEN$/ {print}'                 # tcp4 ... *.22 ... LISTEN
service --test-if-available ssh; echo $?                                  # 0
service --test-if-configured-on ssh; echo $?                              # 0

# test if sshd daemon supports tcp_wrappers
# cf. http://www.la-samhna.de/library/brutessh.html#5
otool -L /usr/sbin/sshd | grep libwrap                                      

# then make sure you are connected to the internet
ping -c 10 checkip.dyndns.org
curl -L -s --max-time 10 http://checkip.dyndns.org | grep -Eo -m 1 '([[:digit:]]{1,3}\.){3}[[:digit:]]{1,3}'   


# then set up the local SOCKS Proxy
# cf. http://macapper.com/2007/05/22/advanced-os-x-secure-tunneling-via-ssh

#ssh -q -D 8080 -f -C -N -x $(whoami)@$(ipconfig getifaddr $(route -n get default | awk '/interface:/ { print $2 }') 2>/dev/null)
#ssh -v -D 8080 -f -C -N -x $(whoami)@$(ipconfig getifaddr $(route -n get default | awk '/interface:/ { print $2 }') 2>/dev/null) 

ssh -q -D 8080 -f -C -N -x $(whoami)@127.0.0.1     # cf. AllowUsers $(whoami)@127.0.0.1 below

# ... enter your user account login password


# SSH Without a Password
# http://www.csua.berkeley.edu/~ranga/notes/ssh_nopass.html
# http://homepage.mac.com/kelleherk/iblog/C1901548470/E20061128145420/index.html

# RSA
mkdir -p $HOME/.ssh
chmod -R 0700 $HOME/.ssh
ssh-keygen -t rsa -f $HOME/.ssh/id_rsa -P ''
cp -p $HOME/.ssh/id_rsa.pub $HOME/.ssh/authorized_keys2
chmod 0600 $HOME/.ssh/authorized_keys2
srm -v $HOME/.ssh/id_rsa.pub
#ls -ld $HOME/.ssh
#ls -l $HOME/.ssh/authorized_keys2

# encrypt the known_hosts file
ssh-keygen -H -f $HOME/.ssh/known_hosts 
srm -v $HOME/.ssh/known_hosts.old
chmod 0600 $HOME/.ssh/known_hosts


# securing SSH
# See:
# - man sshd_config
# - sudo nano /private/etc/sshd_config
# - http://switch.richard5.net/2006/09/24/securing-your-ssh-access/ 
# - http://www.mactech.com/articles/mactech/Vol.21/21.02/Security/index.html
# - Mac OS X Security Configuration Guides at http://www.apple.com/server/documentation/

sudo sh -c "
echo '
# added
Protocol 2
PermitRootLogin no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
PasswordAuthentication no
UsePAM no
PubkeyAuthentication yes
AuthorizedKeysFile $HOME/.ssh/authorized_keys2
#KeepAlive yes
MaxAuthTries 3
LoginGraceTime 40
LogLevel INFO     # QUIET, FATAL, ERROR, INFO, VERBOSE, DEBUG
#AllowUsers $(whoami)      # add more users if you like: ~  (in Terminal.app) or dscl . -list /Users
AllowUsers $(whoami)@127.0.0.1      # cf. ssh -q -D 8080 -f -C -N -x $(whoami)@127.0.0.1 above
#AllowUsers $(whoami)@$(ipconfig getifaddr $(route -n get default | awk '/interface:/ { print $2 }') 2>/dev/null)   # requires internet connection
#AllowGroups sshusersgroup     # cf. dscl . -list /Groups; groups
' >> /private/etc/sshd_config
"


# then open Safari ...

open -a Safari

# ... and go to:
# Safari > Preferences ... > Advanced > Proxies: Change Settings ... 
# > Select a proxy server to configure: SOCKS Proxy > SOCKS Proxy Server: 127.0.0.1 : 8080 > Apply Now


sudo reboot       # ... or just restart: System Preferences > Sharing > Services > Remote Login

ssh -q -D 8080 -f -C -N -x $(whoami)@127.0.0.1      # should now work without password; cf. man ssh_config for configuring SSH shortcuts


# check local SOCKS Proxy setup

scutil --proxy                   # SOCKSProxy : 127.0.0.1, SOCKSEnable : 1, SOCKSPort : 8080

sudo ln -s "/Applications/Utilities/Network Utility.app/Contents/Resources/stroke" /bin/portscan
portscan localhost 8000 8100     # Open TCP Port:  8080  http-alt

lsof -i :22 -P
lsof -i :8080 -P
lsof -i TCP -P
lsof -U -P             # list UNIX domain socket files
sudo lsof -U -P
netstat -n -f inet


#-------------------------- 


# now you can, for example, test if a website can discover your real internal IP address provided your computer
# is behind a DSL router and you have a firewall running (cf. http://textsnippets.com/posts/show/1267)
# cf. http://www.auditmypc.com/internal-ip.html

# first get your internal IP address
ipconfig getifaddr $(route -n get default | awk '/interface:/ { print $2 }')

# note: to run the 'real IP' test you first have to enable Plug-ins, Java & JavaScript in Safari
# Safari > Preferences ... > Security > Enable plug-ins & Enable Java & Enable JavaScript

open -a Safari http://www.auditmypc.com/software_audit.asp



II. Setting up a local SOCKS proxy for Safari using two different user accounts on the same computer


# The following BASH command-line instructions assume you have a regular user account 
# and an admin user account on the same computer!

# First, log in to the regular user account 
regular_user_name="$(whoami)"
regular_user_path="$HOME"
echo $regular_user_name $regular_user_path    

# note down the output from the echo command
# log out from the regular user account

# Then log in to the admin account for the following instructions!

# first make sure you are connected to the internet
ping -c 10 checkip.dyndns.org
curl -L -s --max-time 10 http://checkip.dyndns.org | grep -Eo -m 1 '([[:digit:]]{1,3}\.){3}[[:digit:]]{1,3}'   

# get internal IP address & set regular user account information
regular_user_name="...insert information from regular user account above..."
regular_user_path="...insert information from regular user account above..."
internal_IP_address=$(ipconfig getifaddr $(route -n get default | awk '/interface:/ { print $2 }'))
echo $internal_IP_address $regular_user_name $regular_user_path

# enable remote login: System Preferences > Sharing > Services > Remote Login

# test if remote login is enabled
service --test-if-available ssh; echo $?               # 0
service --test-if-configured-on ssh; echo $?           # 0


# SSH Without a Password

# admin user account
# RSA
mkdir -p $HOME/.ssh
chmod -R 0700 $HOME/.ssh
ssh-keygen -t rsa -f $HOME/.ssh/id_rsa -P ''
cp -p $HOME/.ssh/id_rsa.pub $HOME/.ssh/authorized_keys2
chmod 0600 $HOME/.ssh/authorized_keys2
srm -v $HOME/.ssh/id_rsa.pub
ls -ld $HOME/.ssh
ls -l $HOME/.ssh

# regular user account
sudo mkdir -p $regular_user_path/.ssh
sudo chmod -R 0700 $regular_user_path/.ssh
sudo cp $HOME/.ssh/authorized_keys2 $regular_user_path/.ssh/authorized_keys2
#scp ~/.ssh/authorized_keys2 $regular_user_name@$internal_IP_address:~/.ssh/authorized_keys2
sudo chown -R $regular_user_name:$regular_user_name $regular_user_path/.ssh
sudo chmod 0600 $regular_user_path/.ssh/authorized_keys2
sudo ls -l $regular_user_path/.ssh
sudo ls -ld $regular_user_path/.ssh

# delete all files in ~/.ssh on both user accounts
#sudo find $regular_user_path/.ssh -type f -exec srm -fv "{}" \;
#find $HOME/.ssh -type f -exec srm -fv "{}" \;


# log in to regular user account via SSH
# enter admin account login password if prompted
ssh -i $HOME/.ssh/id_rsa $regular_user_name@$internal_IP_address     
exit

# encrypt the known_hosts file
ssh-keygen -H -f $HOME/.ssh/known_hosts       
srm -v $HOME/.ssh/known_hosts.old
chmod 0600 $HOME/.ssh/known_hosts

ssh -i $HOME/.ssh/id_rsa $regular_user_name@$internal_IP_address    # test
ls 
exit


# securing SSH
# man sshd_config
# sudo nano /private/etc/sshd_config

sudo sh -c "
echo '
# added
Protocol 2
PermitRootLogin no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
PasswordAuthentication no
UsePAM no
PubkeyAuthentication yes
#AuthorizedKeysFile $regular_user_path/.ssh/authorized_keys2
#KeepAlive yes
MaxAuthTries 3
#PermitUserEnvironment yes     # requires ~/.ssh/environment file; see man ssh and man sshd_config
LoginGraceTime 40
LogLevel INFO     # QUIET, FATAL, ERROR, INFO, VERBOSE, DEBUG
#AllowUsers $(whoami) $regular_user_name
AllowUsers $(whoami)@$internal_IP_address $regular_user_name@$internal_IP_address
' >> /private/etc/sshd_config
"


# set up the local SOCKS Proxy
# enter admin account login password if prompted
ssh -q -D 8080 -f -C -N -x $regular_user_name@$internal_IP_address    

open -a Safari

# Safari > Preferences ... > Advanced > Proxies: Change Settings ... 
# > Select a proxy server to configure: SOCKS Proxy > SOCKS Proxy Server: 127.0.0.1 : 8080 > Apply Now

# restart sshd: System Preferences > Sharing > Services > Remote Login

# check local SOCKS Proxy setup
scutil --proxy                    # SOCKSProxy : 127.0.0.1, SOCKSEnable : 1, SOCKSPort : 8080
portscan localhost 8000 8100      # Open TCP Port:  8080  http-alt

ssh -p 22 $regular_user_name@$internal_IP_address ls
ssh -l $regular_user_name $internal_IP_address 'echo hello world; whoami; hostname; logname'

# test privacy of internal IP address
open -a Safari http://www.auditmypc.com/software_audit.asp



Further information on SSH & Mac OS X:

- Getting started with SSH
- Remote Login With SSH
- SSH Without A Password
- Exit Your SSH Session Without Killing Your Job
- SSH on Mac OS X
- SSH
- ssh tunnelling
- ssh X forwarding debugging
- Tutorial: SSH To Alternate Ports and Enabling Multiple SSH Daemons
- Route All Your Internet Traffic Through a Proxy
- SSH Notes
- SSH Host Key Protection
- Setup the SSH server to use keys for authentication
- Auto-closing SSH tunnels
- SSH Tunnelling (Port Forwarding)
- Defending against brute force ssh attacks
- SSH + Screen = Easy Administration
- SSH SOCKS Proxy From Behind a Gateway
- nylon - flexible Unix proxy server with mirror mode; sudo port install nylon
- tsocks - transparent SOCKS proxying library; sudo port install tsocks

portscan


# first create a symbolic link
sudo ln -s "/Applications/Utilities/Network Utility.app/Contents/Resources/stroke" /bin/portscan

# usage: portscan address startPort endPort

portscan localhost 1 5000

portscan $(/usr/sbin/ipconfig getifaddr en0 2>/dev/null || /bin/echo localhost) 1 10000

Show all files in Finder


# just put them into your ~/.bash_login
# and then: source ~/.bash_login 

function showall() { 
   /usr/bin/defaults write com.apple.finder AppleShowAllFiles TRUE
   /bin/sleep 1
   /usr/bin/killall Finder
   return 0
}     


function shownall() { 
   /usr/bin/defaults write com.apple.finder AppleShowAllFiles FALSE
   /bin/sleep 1
   /usr/bin/killall Finder
   return 0
}     

« Newer Snippets
Older Snippets »
109 total  XML / RSS feed