#!/bin/sh # # postfix-chroot.sh - enable or disable Postfix chroot # # (C) 2003 Luca Berra <bluca@vodka.it> # # originally based on postfix-chroot.sh # (C) 2003 Simon J Mudd <sjmudd@pobox.com> # # This script is intended to enable you to enable or disable the Postfix # chroot environment. # # License: # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You may have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, # USA. # # An on-line copy of the GNU General Public License can be found # http://www.fsf.org/copyleft/gpl.html. usage () { myname=${0##*/} cat <<EOF Usage: $myname [-q|-h] {enable|disable|update|check|check_update} Options: -h - this help -q - do not issue action/error messages Commands: enable - setup Postfix chroot (removing the previous setup) disable - remove Postfix chroot update - update files in Postfix chroot check - check differences between files in chroot and system check_update - check differences between files in chroot and system and update if needed EOF } # print an error message and exit error () { echo "Error: $@" >&2 exit 1 } # print a warning message warn () { echo "Warning: $@" >&2 } # if $verbose==1 print message ($2) otherwise do nothing info () { [ "$verbose" = 1 ] && echo "$@" || : } # Link source file to chroot directory if possible. If the source is a # symbolic link, make a copy of the link in the chroot directory, # otherwise copy the file. copy() { local T L if [ -n "$2" ]; then T=${chroot%/}/${2#/} else T=${chroot%/}/${1#/} fi info " $1 -> $T" mkdir -p ${T%/*} if [ -L $1 ]; then L=`readlink -f $1` ln -sf $L $T copy $L else ln -f $1 $T 2>/dev/null || \ cp -pf $1 $T fi } rmtree() { local d=${1#$chroot}/ while [ -n "${d%/*}" ]; do rmdir ${chroot}${d} 2>/dev/null || return -1 done } remove() { local L local f=${1#$chroot} f=${chroot%/}/${f#/} if [ -L $f ]; then L=`readlink -f $f` [ -f ${chroot}$L ] && rm -f ${chroot}$L rmtree ${chroot}${L%/*} rm -f $f elif [ -f $f ];then rm -f $f fi rmtree ${f%/*} } # update syslog configuration (rsyslog, sysklogd or syslog-ng) update_syslog() { # sysklogd if [ -f '/etc/sysconfig/syslog' ]; then local SYSLOGD_OPTIONS="" local NEW_SYSLOGD_OPTIONS="" [ -s /etc/sysconfig/syslog ] && . /etc/sysconfig/syslog cp -p /etc/sysconfig/syslog /etc/sysconfig/syslog.${bckext} case $1 in enable) mkdir -p ${chroot}/dev 2>/dev/null if [ -z "${SYSLOGD_OPTIONS}" ]; then info "Adding SYSLOGD_OPTIONS in the /etc/sysconfig/syslog file." echo "SYSLOGD_OPTIONS=\"-a ${chroot}/dev/log\"" >> /etc/sysconfig/syslog service syslog condrestart else if ! grep -q "^SYSLOGD_OPTIONS=.*-a ${chroot}/dev/log" /etc/sysconfig/syslog; then NEW_SYSLOGD_OPTIONS="${SYSLOGD_OPTIONS} -a ${chroot}/dev/log" sed -e "s!^[[:space:]]*SYSLOGD_OPTIONS=.*\$!SYSLOGD_OPTIONS=\"${NEW_SYSLOGD_OPTIONS}\"!" < /etc/sysconfig/syslog.${bckext} > /etc/sysconfig/syslog service syslog condrestart fi fi ;; disable) NEW_SYSLOGD_OPTIONS="`echo ${SYSLOGD_OPTIONS} | sed -e \"s! -a ${chroot}/dev/log!!\"`" if [ "${NEW_SYSLOGD_OPTIONS}" != "${SYSLOGD_OPTIONS}" ]; then sed -e "s!^[[:space:]]*SYSLOGD_OPTIONS=.*\$!SYSLOGD_OPTIONS=\"${NEW_SYSLOGD_OPTIONS}\"!" < /etc/sysconfig/syslog.${bckext} > /etc/sysconfig/syslog service syslog condrestart fi rmdir ${chroot}/dev 2>/dev/null ;; esac rm -f /etc/sysconfig/syslog.${bckext} fi # rsyslog if [ -f '/etc/sysconfig/rsyslog' ]; then case $1 in enable) mkdir -p ${chroot}/dev 2>/dev/null if [ ! -f /etc/rsyslog.d/postfix.conf ]; then echo '$AddUnixListenSocket /var/spool/postfix/dev/log' > /etc/rsyslog.d/postfix.conf service rsyslog condrestart fi ;; disable) if [ -f /etc/rsyslog.d/postfix.conf ]; then rm -f /etc/rsyslog.d/postfix.conf service rsyslog condrestart fi rmdir ${chroot}/dev 2>/dev/null ;; esac fi if [ -f '/etc/syslog-ng.conf' ]; then case $1 in enable) mkdir -p ${chroot}/dev 2>/dev/null if ! grep -q -E "(unix-stream|file) *\(([\"'])${chroot}/dev/log\2" /etc/syslog-ng.conf; then sed -e "s!@CHROOT@!${chroot}!" /etc/postfix/syslog-ng.conf >> /etc/syslog-ng.conf <<EOF EOF service syslog-ng condrestart fi ;; disable) if grep -q "^# BEGIN: Automatically added by postfix chroot setup script" /etc/syslog-ng.conf; then sed -i -e '/^# BEGIN: Automatically added by postfix chroot setup script/,/^# END/d' /etc/syslog-ng.conf service syslog-ng condrestart fi rmdir ${chroot}/dev 2>/dev/null ;; esac fi } ########################################################################## # # remove chroot jail remove_chroot () { verbose=1 [ "$1" = "quiet" ] && verbose=0 [ "$1" = "noconf" ] && verbose=0 && noconf=1 info "removing chroot from: ${chroot}" # remove system files info "remove system files from chroot" for i in etc/localtime etc/host.conf \ etc/resolv.conf etc/nsswitch.conf \ etc/hosts etc/passwd etc/services \ etc/samba/smb.conf etc/samba/lmhosts \ etc/ldap.conf ; do remove $i done info "remove additional files from chroot" for i in $ADDITIONAL_FILES; do # in case we have /path/file=/newpath/newfile remove ${i##*=} done info "removing chroot libraries from: ${chroot}" # remove nss libraries libs="" for l in ${chroot}/${_lib}/lib*.so* ${chroot}/usr/${_lib}/lib*.so*; do [ -f $l ] && \ libs="$libs $l `ldd $l | awk -v c=${chroot} '{print c $3}'`" done if [ -n "$libs" ]; then info "remove nss files from chroot" for l in $libs; do remove $l done fi info "remove system directories from chroot" for dir in var/lib/sasl2 var/lib var usr/share/zoneinfo \ usr/share usr/${_lib} usr ${_lib} etc; do [ -d ${chroot}/${dir} ] && \ info " ${chroot}/${dir}" && \ rmtree ${chroot}/${dir} done [ "$noconf" = "1" ] && return # remove chroot settings from master.cf awk -v ALWAYS_CHROOT_PROGRAM="$ALWAYS_CHROOT_PROGRAM" \ -v ALWAYS_CHROOT_SERVICE="$ALWAYS_CHROOT_SERVICE" ' BEGIN { IFS="[ \t]+"; OFS="\t"; } /^#/ { print; next; } /^ / { print; next; } $1 ~ ALWAYS_CHROOT_SERVICE { print; next; } $8 ~ ALWAYS_CHROOT_PROGRAM { print; next; } $5 == "y" { $5="n"; print $0; next; } { print; } ' ${confdir}/master.cf.${bckext} > ${confdir}/master.cf } # ########################################################################## ########################################################################## # # setup chroot jail setup_chroot () { verbose=1 [ -n "$1" ] && [ "$1" = quiet ] && verbose=0 # Check master.cf is where we expect it [ -f ${confdir}/master.cf ] || error "${confdir}/master.cf missing, exiting" info "setting up chroot at: ${chroot}" # copy system files into chroot environment info "copy system files into chroot" for i in /etc/localtime /etc/host.conf \ /etc/resolv.conf /etc/nsswitch.conf \ /etc/hosts /etc/services; do [ -e ${i} ] && copy ${i} done info "copy additional files into chroot" for i in $ADDITIONAL_FILES; do # in case we have /path/file=/newpath/newfile copy ${i%%=*} ${i##*=} done # for sasl mkdir -p ${chroot}/var/lib/sasl2 2>/dev/null # check smtpd's dependencies to determine which libraries # don't need to be copied into the chroot smtpd=`${postconf} -c ${confdir} -h daemon_directory`/smtpd if [ ! -x "${smtpd}" ];then warn "cannot find \$daemon_directory/smtpd" fi # check also dynamic maps dependencies [ -s ${confdir}/dynamicmaps.cf ] && \ dynmaps=`awk '/^[[:space:]]*#/ {next} {print $2}' ${confdir}/dynamicmaps.cf` prunedeps="none" for i in ${smtpd} ${dynmaps}; do prunedeps=`echo $prunedeps;/usr/bin/ldd $i | awk '/\// {print $(NF-1)}'` done nss_libs= for i in ${nss_databases}; do nss=`[ -s /etc/nsswitch.conf ] && \ awk -v db=$i: -v IGNORE_NSS_LIBS="${IGNORE_NSS_LIBS}" ' /^[[:space:]]*#/ {next} $1 == db { for (i=2;i<=NF;i++) { if (match($i,"#")) {next} if (!match($i,"=") && !match($i,IGNORE_NSS_LIBS)) {print $i} } } ' /etc/nsswitch.conf` [ -z "$nss" ] && eval `echo nss=\\\$nss_default_$i` nss_libs="$nss_libs $nss" done libs= for i in ${nss_libs}; do case $i in wins) conf="/etc/samba/smb.conf /etc/samba/lmhosts" ;; ldap) conf="/etc/ldap.conf" ;; # i purposefully ignore copying of /etc/ldap.secret *) conf="" ;; esac l=/${_lib}/libnss_$i.$nss_soname [ -e ${l} -a "${libs##*$l*}" == "${libs}" ] && \ libs=`echo $conf; echo $l;echo $libs;/usr/bin/ldd $l | awk '/\// {print $(NF-1)}'` done #now copy nss libraries and dependencies if we don't already have them loaded info "copy nss libraries into chroot" for i in $libs; do [ -n "${prunedeps##*$i*}" ] && copy ${i} done # chroot master.cf change all lines except pipe, local, proxymap, # virtual and spawn awk -v NEVER_CHROOT_PROGRAM="$NEVER_CHROOT_PROGRAM" \ -v NEVER_CHROOT_SERVICE="$NEVER_CHROOT_SERVICE" ' BEGIN { IFS="[ \t]+"; OFS="\t"; } /^#/ { print; next; } /^ / { print; next; } $1 ~ NEVER_CHROOT_SERVICE { print; next; } $8 ~ NEVER_CHROOT_PROGRAM { print; next; } $5 == "n" { $5="y"; print $0; next; } { print; } ' ${confdir}/master.cf.${bckext} > ${confdir}/master.cf } # ########################################################################## ########################################################################## # # check files in chroot check_files() { local i j f rc rc=0 cd $chroot for i in `find bin etc lib sbin usr -type f -print 2>/dev/null`; do f=$i for j in $ADDITIONAL_FILES; do [ "$i" = "${j%%=*}" ] && f=${i##*=} done if [ -f /$f ]; then cmp -s $f /$f || { info "files ${chroot%/}/${f} and /$f differ" rc=-1 } fi done return $rc } # ########################################################################## ########################################################################## # # create/update /etc/sysconfig/postfix create_sysconfig() { cat > /etc/sysconfig/postfix <<-EOF # this file is automatically generated by postfix-chroot.sh # you can modify the parameters and they will be mantained # comments will not be preserved # use postfix-chroot.sh to change this CHROOT=${CHROOT} # this are regular expressions matching a whole line NEVER_CHROOT_PROGRAM='${NEVER_CHROOT_PROGRAM}' ALWAYS_CHROOT_PROGRAM='${ALWAYS_CHROOT_PROGRAM}' NEVER_CHROOT_SERVICE='${NEVER_CHROOT_SERVICE}' ALWAYS_CHROOT_SERVICE='${ALWAYS_CHROOT_SERVICE}' # nss names as they would appear in nsswitch.conf IGNORE_NSS_LIBS='${IGNORE_NSS_LIBS}' # space separated list of full pathname of file you want to be copied # use /path/file=/newpath/newfile for path mappings ADDITIONAL_FILES='${ADDITIONAL_FILES}' # Automatically rebuild maps at daemon startup REBUILD_ALIASES=${REBUILD_ALIASES} REBUILD_MAPS=${REBUILD_MAPS} EOF } # ########################################################################## verbose=1 [ "$1" = "-q" ] && { quiet=quiet; verbose=0; shift; } [ "$1" = "-h" ] && { usage; exit 0; } [ $# = 1 ] || { usage; exit 1; } [ "$UID" = "0" ] || error "your must be root to run this script" _lib=`rpm --eval '%_lib'` global_config_directory=/etc/postfix postconf=/usr/sbin/postconf [ -d ${global_config_directory} ] || error "no postfix directory ${global_config_directory}" [ -x ${postconf} ] || error "can not find postconf" alternate_config_directories=`postconf -c ${global_config_directory} -h slave_config_directories 2>/dev/null` # defaults CHROOT=0 NEVER_CHROOT_PROGRAM='^(proxymap|local|pipe|virtual|spawn)$' NEVER_CHROOT_SERVICE='^cyrus$' ALWAYS_CHROOT_PROGRAM='^$' ALWAYS_CHROOT_SERVICE='^$' IGNORE_NSS_LIBS='^$' ADDITIONAL_FILES='' REBUILD_ALIASES=1 REBUILD_MAPS=1 [ -s /etc/sysconfig/postfix ] && . /etc/sysconfig/postfix # defaults for NSS # do i really need to look at passwd and group? nss_databases='passwd group hosts services' nss_default_passwd="compat" nss_default_group="compat" nss_default_hosts="dns files" nss_default_services="nis files" nss_soname=so.2 bckext=`date '+%Y%m%d-%H%M'` get_chroot() { local chroot=`${postconf} -c ${confdir} -h queue_directory 2>/dev/null` if [ -z "${chroot}" ]; then warn "chroot (${chroot}) is not defined" return fi if [ "${chroot}" = "/" ]; then warn "chroot (${chroot}) is set to /" return fi if [ ! -d "${chroot}" ]; then warn "chroot (${chroot}) is not a directory" return fi echo ${chroot} } enable_chroot() { for confdir in ${global_config_directory} ${alternate_config_directories}; do chroot=`get_chroot` [ -z "${chroot}" ] && continue cp -p ${confdir}/master.cf ${confdir}/master.cf.${bckext} remove_chroot noconf setup_chroot $quiet cmp -s ${confdir}/master.cf ${confdir}/master.cf.${bckext} && rm -f ${confdir}/master.cf.${bckext} [ -f ${confdir}/master.cf.${bckext} ] && info "${confdir}/master.cf backed up as ${confdir}/master.cf.${bckext}" update_syslog enable done } disable_chroot() { for confdir in ${global_config_directory} ${alternate_config_directories}; do chroot=`get_chroot` [ -z "${chroot}" ] && continue cp -p ${confdir}/master.cf ${confdir}/master.cf.${bckext} remove_chroot ${1} cmp -s ${confdir}/master.cf ${confdir}/master.cf.${bckext} && rm -f ${confdir}/master.cf.${bckext} [ -f ${confdir}/master.cf.${bckext} ] && info "${confdir}/master.cf backed up as ${confdir}/master.cf.${bckext}" update_syslog disable done } check_chroot() { for confdir in ${global_config_directory} ${alternate_config_directories}; do chroot=`get_chroot` [ -z "${chroot}" ] && continue check_files done } update_chroot() { for confdir in ${global_config_directory} ${alternate_config_directories}; do chroot=`get_chroot` [ -z "${chroot}" ] && continue check_files && continue cp -p ${confdir}/master.cf ${confdir}/master.cf.${bckext} remove_chroot noconf setup_chroot $quiet cmp -s ${confdir}/master.cf ${confdir}/master.cf.${bckext} && rm -f ${confdir}/master.cf.${bckext} [ -f ${confdir}/master.cf.${bckext} ] && info "${confdir}/master.cf backed up as ${confdir}/master.cf.${bckext}" update_syslog enable done } # create readable directories into the chroot umask 022 # See how we were called. case "$1" in enable) CHROOT=1 create_sysconfig enable_chroot ;; disable) CHROOT=0 create_sysconfig disable_chroot $quiet ;; remove) # used by rpm preuninstall script disable_chroot noconf update_syslog disable exit 0 ;; update) if [ $CHROOT = 0 ];then info "chroot disabled in /etc/sysconfig/postfix" exit 0 else enable_chroot fi ;; check) if [ $CHROOT = 0 ];then info "chroot disabled in /etc/sysconfig/postfix" exit 0 else check_chroot fi ;; check_update) if [ $CHROOT = 0 ];then info "chroot disabled in /etc/sysconfig/postfix" exit 0 else update_chroot fi ;; *) usage exit 1 ;; esac [ -f /var/lock/subsys/postfix ] && service postfix reload exit 0 # vim:ts=8:sw=4