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