COMMAND
mgetty+sendfax
SYSTEMS AFFECTED
Linux (RedHat)
PROBLEM
Gert Doering found a security hole has been found in the auxiliary
fax scripts "faxq" and "faxrunq" in the mgetty+sendfax package.
It has been in there since the first day those scripts were
written.
Due to improper quoting in these shell scripts, it's possible to
execute code with a foreign user id, and get root access to the
machine. The exploit is actually quite trivial.
Appended below, you'll find replacement scripts that fix the
problems. The scripts can be used "as is" with mgetty+sendfax
1.0.* and 1.1.*, and they should work with all older versions as
well. Credits for finding the problem go to Herbert Thielen.
SOLUTION
There is release of new versions of mgetty+sendfax (1.1.8 for the
development cycle, 1.0.1 as "stable release"), which will have
the changes worked in:
http://www.leo.org/~doering/mgetty/
---------
#!/bin/sh
# This is a shell archive (produced by shar 3.52.3)
# To extract the files from this archive, save it to a file, remove
# everything above the "!/bin/sh" line above, and type "sh file_name".
#
# made 07/25/1997 07:55 UTC by gert@greenie
# Source directory /u/gert/src/mgetty/fax
#
# existing files will NOT be overwritten unless -c is specified
#
# This shar contains:
# length mode name
# ------ ---------- ------------------------------------------
# 3507 -rw-r--r-- faxq
# 8247 -rw-r--r-- faxrunq
#
touch -am 1231235999 $$.touch >/dev/null 2>&1
if test ! -f 1231235999 && test -f $$.touch; then
shar_touch=touch
else
shar_touch=:
echo 'WARNING: not restoring timestamps'
fi
rm -f 1231235999 $$.touch
#
# ============= faxq ==============
if test -f 'faxq' && test X"$1" != X"-c"; then
echo 'x - skipping faxq (File already exists)'
else
echo 'x - extracting faxq (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'faxq' &&
#!/bin/sh
#
# faxq program
#
# like "lpq" or "mailq", show jobs waiting in the output queue
#
# SCCS: @(#)faxq.in 4.3 97/07/24 Copyright (C) 1994 Gert Doering
#
FAX_SPOOL=/usr/spool/fax
FAX_SPOOL_OUT=/usr/spool/fax/outgoing
X
#
# echo program that will accept escapes (bash: "echo -e", sun: /usr/5bin/echo)
#
echo="echo"
X
#
# an awk that is not stone-old-brain-dead (that is, not oawk...)
#
AWK=awk
X
if cd $FAX_SPOOL_OUT
then :
else
X $echo "cannot chdir to $FAX_SPOOL_OUT..." >&2
X exit 1
fi
X
jobs="*/JOB"
requeue=""
X
for flag
do
X case $flag in
X -v) verbose="true" ;;
X -o) jobs="*/JOB.done" ;;
X -s) jobs="*/JOB.s*" ;;
X -a) jobs="*/JOB*" ;;
X -r) jobs="*/JOB.s*"; requeue="true" ;;
X *) cat <<EOF_MSG >&2
$0: invalid option: $flag
valid options:
X -o: show old jobs
X -s: show suspended jobs
X -a: show all jobs
X -v: verbose output
X -r: restart suspended jobs
EOF_MSG
X exit 1 ;;
X esac
done
X
jobs=`ls $jobs 2>/dev/null`
[ -z "$jobs" ] && $echo "no jobs."
for i in $jobs
do
X USER=""; PHONE=""; PAGES=""; MAILTO=""; VERBTO="";
X ACCT=""; INPUT=""; PRI=""; RE=""
X
X if [ -z "$verbose" ]
X then
X eval `tr -d '\042\047\140\134\044\073' <$i | \
X $AWK '$1=="user" { printf "USER=%s;", $2 }
X $1=="phone" { printf "PHONE=%s;", $2 }
X $1=="pages" { printf "PAGES=%d;", NF-1 }
X $1=="priority" { printf "PRI=\" pri=%s.\"", $2}' -`
X $echo "$i: queued by $USER. $PAGES page(s) to $PHONE.$PRI"
X else
X eval `tr -d '\042\047\140\134\044\073' <$i | \
X $AWK '$1=="user" { printf "USER=%s;", $2 }
X $1=="mail" { printf "MAILTO=\"%s\";", substr( $0, 6 ) }
X $1=="phone" { printf "PHONE=%s;", $2 }
X $1=="verbose_to" \
X { printf "VERBTO=\"%s\";", substr( $0, 12 ) }
X $1=="acct_handle" \
X { printf "ACCT=\"%s\";", substr( $0, 13 ) }
X $1=="input" { printf "INPUT=\"%s\";", substr( $0, 7 ) }
X $1=="time" { printf "TIME=\"%s:%s\";",
X substr( $0, 6, 2 ), substr( $0, 8,2 ) }
X $1=="subject"{ printf "RE=\"%s\";", substr( $0, 9 ) }
X $1=="priority"{ printf "PRI=\"%s\";", $2 }
X $1=="pages" { if ( NF==2 ) printf "PAGES=\"%s\";", $2
X else if ( NF==3 )
X printf "PAGES=\"%s %s\";", $2, $3
X else
X printf "PAGES=\"%s ... %s\";", $2, $NF
X }' -`
X $echo "$i:"
X $echo "\tQueued by: $USER"
X if [ -z "$VERBTO" ]
X then
X $echo "\t to: $PHONE"
X else
X $echo "\t to: $VERBTO ($PHONE)"
X fi
X test ! -z "$RE" && \
X $echo "\t Re: $RE"
X test ! -z "$MAILTO" && \
X $echo "\t E-Mail: $MAILTO"
X test ! -z "$INPUT" && \
X $echo "\t Input: $INPUT"
X $echo "\t Pages: $PAGES"
X test ! -z "$TIME" && \
X $echo "\tSend time: $TIME"
X test ! -z "$ACCT" && \
X $echo "\tAcct info: $ACCT"
X test ! -z "$PRI" && \
X $echo "\t Priority: $PRI"
X
X sed -e '/Status/!d' -e 's/Status/ Status:/' $i
X if [ -f "$i.locked" ] ; then
X $echo "\t Status: LOCKED (being sent right now)"
X else
X expr $i : ".*done$" >/dev/null ||
X $echo "\t Status: not sent yet"
X fi
X fi
X
# if "requeue", requeue *.suspended-Jobs
X if [ -n "$requeue" ] && expr "$i" : ".*JOB.s" >/dev/null
X then
X d=`dirname $i`
X if [ ! -w $d ] ; then
X $echo "$i: not owner, can't restart"
X else
X $echo "Status "`date`" - reactivated by $LOGNAME" >>$i
X mv $i $d/JOB
X fi
X fi
X
done
X
test -n "$verbose" -a -n "$jobs" -a -r .last_run &&
X $echo "\nLast \`\`faxrunq'' run at: `cat .last_run`"
X
SHAR_EOF
$shar_touch -am 0724205497 'faxq' &&
chmod 0644 'faxq' ||
echo 'restore of faxq failed'
shar_count="`wc -c < 'faxq'`"
test 3507 -eq "$shar_count" ||
echo "faxq: original size 3507, current size $shar_count"
fi
# ============= faxrunq ==============
if test -f 'faxrunq' && test X"$1" != X"-c"; then
echo 'x - skipping faxrunq (File already exists)'
else
echo 'x - extracting faxrunq (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'faxrunq' &&
#!/bin/sh
#
# faxrunq
#
# look for outgoing fax jobs, send them via sendfax, if succesful, remove
# them from the outgoing queue (and send a mail to the originator of the
# job)
#
# There are still a lot rough edges - but it works, and should give you an
# idea how to improve it
#
# SCCS: @(#)faxrunq.in 4.3 97/07/24 Copyright (C) 1994 Gert Doering
X
FAX_SPOOL=/usr/spool/fax
FAX_SPOOL_OUT=/usr/spool/fax/outgoing
FAX_SENDER="/usr/local/sbin/sendfax"
FAX_ACCT=$FAX_SPOOL/acct.log
X
MAILER="/usr/lib/mail/execmail"
X
CONF_FILE="/usr/local/etc/mgetty+sendfax/faxrunq.config"
X
#
# echo program that will accept escapes (bash: "echo -e", sun: /usr/5bin/echo)
#
echo="echo"
X
#
# awk program that is not stone-old-brain-dead (that is, not oawk...)
#
AWK=awk
X
#
# make sure we'll find "newslock" and other good stuff when run from "cron"...
#
PATH=/usr/local/bin:$PATH
X
#
# set defaults, then process configuration file (if it exists)
#
do_mail_s="TRUE"
do_mail_f="TRUE"
exec_pgm_s=""
exec_pgm_f=""
max_fail_costly=5
max_fail_total=10
delete_sent=""
X
if [ -r $CONF_FILE ] ; then
X eval `$AWK '/^ *#/ { next }
X $1 == "success-send-mail" \
X { printf "do_mail_s=\"%s\";", ($2 ~ /^[yYjJtT1]/)? "T":"" }
X $1 == "failure-send-mail" \
X { printf "do_mail_f=\"%s\";", ($2 ~ /^[yYjJtT1]/)? "T":"" }
X $1 == "success-call-program" \
X { printf "exec_pgm_s=\"%s\";", $2 }
X $1 == "failure-call-program" \
X { printf "exec_pgm_f=\"%s\";", $2 }
X $1 == "maxfail-costly" && $2 ~ /^[0-9]/ \
X { printf "max_fail_costly=\"%s\";", $2 }
X $1 == "maxfail-total" && $2 ~ /^[0-9]/ \
X { printf "max_fail_total=\"%s\";", $2 }
X $1 == "delete-sent-jobs" \
X { printf "delete_sent=\"%s\";", ($2 ~ /^[yYjJtT1]/)? "T":"" }
X END { printf "\n" }' $CONF_FILE`
fi
X
#
# command line arguments
#
usage="usage: $0 [-s] [-q]"
X
while :
do
X case "$1" in
# sleep 30 seconds after each job, give modem time to settle
X -s) sleepwait=30;shift;;
# quiet operation
X -q) exec >/dev/null ; shift ;;
# invalid option
X -*) $echo "$0: unknown option: $1" >&2
X $echo "$usage" >&2
X exit 1
X ;;
X *) break
X esac
done
X
if [ $# -gt 0 ]
then
X $echo "$usage" >&2
X exit 1
fi
X
#
# go to fax spool directory, process all JOB files
#
X
cd $FAX_SPOOL_OUT || exit 1
X
status="0"
jobs=`ls */JOB 2>/dev/null`
for job in $jobs
do
X if [ $status -eq 4 -a -n "$sleepwait" ]
X then
# old stat :no connect; modem allows next redial in $sleepwait secs
X $echo "sleeping $sleepwait seconds"
X sleep $sleepwait
X fi
X cd $FAX_SPOOL_OUT/`dirname $job`
X $echo "processing $job..."
#
# lock JOB file (by 'link(2)'ing it to JOB.locked)
# 'newslock' is a small C program that just calls link(argv[1], argv[2])
#
X # make sure Lock will be removed in case the shell aborts
X trap "rm -f JOB.locked 2>/dev/null" 0
X trap "rm -f JOB.locked 2>/dev/null ; exit 20" 1 2 3 15
X
X newslock JOB JOB.locked 2>/dev/null
X if [ $? -ne 0 ]
X then
X $echo "already locked"
X trap 0 1 2 3 15
X continue
X fi
#
# get user to notify (->$MAIL_TO), phone number (->$PHONE) and
# earliest send time (->$TIME)
#
X eval `tr -d '\042\047\140\134\044\073' <JOB | \
X $AWK 'BEGIN { user=""; mail=""; verbto=""; time=""; re=""; }
X $1=="user" { user=$2 }
X $1=="mail" { mail=substr( $0, 6) }
X $1=="phone" { printf "PHONE=%s;", $2 }
X $1=="time" { time=$2 }
X $1=="verbose_to" { verbto=substr($0,12) }
X $1=="subject" { re=substr($0,9) }
X END { if ( mail != "" ) printf "MAIL_TO=\"%s\";", mail
X else printf "MAIL_TO=\"%s\";", user
X printf "TIME=\"%s\";", time
X printf "VERBOSE_TO=\"%s\";", verbto
X printf "RE=\"%s\"", re }' - `
X
#
# check whether send time is reached
#
X if [ ! -z "$TIME" ]
X then
X if [ `date "+%H""%M"` -lt $TIME ]
X then
X $echo "...send time not reached, postponing job"
X rm JOB.locked
X continue
X fi
X fi
X
#
# construct command line to execute
#
X command=`tr -d '\042\047\140\134\044\073' <JOB | \
X $AWK 'BEGIN { phone="-"; flags=""; pages="" }
X $1=="phone" { phone=$2 }
X $1=="header" { flags=flags" -h "$2 }
X $1=="poll" { flags=flags" -p" }
X $1=="normal_res" { flags=flags" -n" }
X $1=="acct_handle" { flags=flags" -A \""substr($0,13)"\"" }
X $1=="pages" { for( i=2; i<=NF; i++) pages=pages$i" " }
X END { printf "'"$FAX_SENDER"' -v%s %s %s", \
X flags, phone, pages }' -`
X
#
# execute faxsend command
#
X $echo "$command"
X eval $command
#
# handle return values
#
X status=$?
X $echo "command exited with status $status"
X
#
# string to include in subject line
#
X if [ -z "$VERBOSE_TO" ]
X then
X subject="your fax to $PHONE"
X else
X subject="your fax to $VERBOSE_TO ($PHONE)"
X fi
X
#
# evaluate return codes, if success, remove fax job from queue
#
X if [ $status -eq 0 ]
X then
X # transmission successful
X $echo "Status "`date`" successfully sent" >>JOB
X
X # update accounting log
X $echo "$MAIL_TO $PHONE "`date`" success" >>$FAX_ACCT
X
X # send mail, if requested
X if [ -n "$do_mail_s" ] ; then
X $echo " send mail to $MAIL_TO..."
X (
X trap 0 # catch BASH bug
X $echo "To: $MAIL_TO"
X $echo "Subject: OK: $subject"
X $echo "From: root (Fax Subsystem)\n"
X $echo "Your fax has been sent successfully at: \c"
X date
X test -z "$RE" || \
X $echo "(Subject was: $RE)\n"
X $echo "\n\nJob / Log file:"
X cat JOB
X tries=`grep Status JOB | sed -e '1d' | wc -l`
X $echo "\nSending succeeded after" $tries "unsuccessful tries."
X ) |
X $MAILER "$MAIL_TO"
X fi
X
X # call "success" handler program (if requested)
X if [ -n "$exec_pgm_s" ] ; then
X $echo " calling program $exec_pgm_s..."
X $exec_pgm_s $FAX_SPOOL_OUT/$job
X fi
X
X # job is done -> remove it from the queue
X mv JOB JOB.done
X
X # completely remove JOB directory (only if requested)
X # instead of this, the job could be archived by "$exec_pgm_s" or so
X if [ -n "$delete_sent" ] ; then
X $echo " deleting job files + directory..."
X cd $FAX_SPOOL_OUT
X rm -rf `dirname $job`
X fi
X
X elif [ $status -lt 10 ]
X then
X # error before starting to transmit (try again)
X why="unknown" ; case $status in
X 1) why="errors in command line" ;;
X 2) why="cannot open fax device (locked?)" ;;
X 3) why="modem initialization error" ;;
X 4) why="dial failed - BUSY" ;;
X 5) why="dial failed - NO DIALTONE" ;;
X esac
X $echo "Status "`date`" failed, exit($status): $why" >>JOB
X else
X # error while transmitting, considered fatal
X why="unknown" ; case $status in
X 10) why="dial failed - NO CARRIER" ;;
X 11) why="protocol failure, waiting for XON" ;;
X 12) why="protocol failure sending page" ;;
X esac
X $echo "Status "`date`" FATAL FAILURE, exit($status): $why" >>JOB
X
X # update accounting log
X $echo "$MAIL_TO $PHONE "`date`" fail: $why" >>$FAX_ACCT
X
X # if failed <max_fail_costly> times, suspend job
X if [ `grep "FATAL FAILURE" JOB | wc -l` -ge $max_fail_costly ]
X then
X $echo "Status "`date`" job suspended: too many FATAL errors" >>JOB
X
X # send mail, if requested
X if [ -n "$do_mail_f" ] ; then
X echo " send mail to $MAIL_TO..."
X (
X trap 0 # catch BASH bug
X $echo "To: $MAIL_TO"
X $echo "Subject: FAIL: $subject failed"
X $echo "From: root (Fax Subsystem)\n"
X $echo "It was not possible to send your fax to $PHONE!\n"
X test -z "$RE" || \
X $echo "(Subject was: $RE)\n"
X $echo "The fax job is suspended, you can requeue it with the command:"
X $echo " cd $FAX_SPOOL_OUT/"`dirname $job`
X $echo " mv JOB.suspended JOB\n"
X $echo "log file follows:"
X cat JOB ) |
X $MAILER "$MAIL_TO"
X fi
X
X # call error handler, if requested
X if [ -n "$exec_pgm_f" ] ; then
X $echo " calling program $exec_pgm_f..."
X $exec_pgm_f $FAX_SPOOL_OUT/$job
X fi
X
X #
X # suspend job (but do not delete it)
X #
X mv JOB JOB.suspended 2>/dev/null
X fi
X fi
#
# unlock job (even if the JOB has been renamed to JOB.suspended or
# JOB.done, the link to JOB.locked still exists!)
#
X rm -f JOB.locked
done
X
trap 0 1 2 3 15
X
#
# touch the time stamp, to make faxspool happy
#
rm -f $FAX_SPOOL_OUT/.last_run
date >$FAX_SPOOL_OUT/.last_run
chmod 644 $FAX_SPOOL_OUT/.last_run
SHAR_EOF
$shar_touch -am 0724205497 'faxrunq' &&
chmod 0644 'faxrunq' ||
echo 'restore of faxrunq failed'
shar_count="`wc -c < 'faxrunq'`"
test 8247 -eq "$shar_count" ||
echo "faxrunq: original size 8247, current size $shar_count"
fi
exit 0