[Libreoffice-commits] tb/tb tb/tb_internals.sh tb/tb_phases.sh tb/tb_send_email

Libreoffice Gerrit user logerrit at kemper.freedesktop.org
Thu Jan 17 19:00:25 PST 2013


 tb/tb              |  390 +++++++++
 tb/tb_internals.sh | 1471 ++++++++++++++++++++++++++++++++++
 tb/tb_phases.sh    |  159 +++
 tb/tb_send_email   | 2235 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 4255 insertions(+)

New commits:
commit 1af3c181f449d9c2975043f0473cb94a14b25ffa
Author: Norbert Thiebaud <nthiebaud at gmail.com>
Date:   Thu Jan 17 20:59:53 2013 -0600

    tb: preliminary version of a new tinderbox script
    
    Not complete yet... work on-going

diff --git a/tb/tb b/tb/tb
new file mode 100755
index 0000000..33fcfb5
--- /dev/null
+++ b/tb/tb
@@ -0,0 +1,390 @@
+#!/usr/bin/env bash
+# -*- tab-width : 4; indent-tabs-mode : nil -*-
+#
+#    Copyright (C) 2011-2013 Norbert Thiebaud
+#    License: GPLv3
+#
+
+if [ -n "$debug" ] ; then
+    set -x
+fi
+
+tb_BIN_DIR=$(dirname "$0")
+
+source "${tb_BIN_DIR?}/tb_internals.sh"
+
+do_help()
+{
+
+cat <<EOF
+
+tb : tinderbox script for libreoffice
+=====================================
+
+tb is a script to run un-attended build on multiple repos, for multiple branches
+and for gerrit patch review system.
+
+tb has two main mode of operation. a tinderbox (tb) mode and a gerrit mode.
+It can also operate in 'dual' mode, doing both tb and gerrit mode.
+
+In 'tb' mode, tb monitor a certain number of defined 'branches' for new commits.
+When one or more new commits are detected on a monitored branches
+tb initiate a build on that branch and typically inform a tinderbox-server
+of the result of that build.
+
+In 'gerrit' mode, tb poll a buildbot gerrit plugin for new patch to test build.
+when such a task is found, tb checkout the patch concerned, test build it and
+report the result to gerrit.
+
+In 'dual' mode, tb alternate between the tb and gerrit mode. In dual mode
+there is 3 diffrent scheduling mode:
+The 'fair' mode, which try  tb and gerrit
+build alternatively.
+The tb-gerrit mode, in which tb does primarily tb build unless there is no such
+build to do, in which case it does gerrit build until a new tb build is needed
+The gerrit-tb mode, in which tb does primarily gerrit build unless there is no
+ssuch build to do, in which case it does tb build until a new gerrit build is
+needed.
+
+tb configuration is organized in 'profile' which contains configuration for
+one or more 'branches' to monitor.
+tb can only run one profile at the time, but monitor and can therefore build
+any of the branches configured under that profile.
+
+The configuration is organized as:
+
+  ~/.tb/config
+       /meta/
+       /phases.sh
+       /profiles/<profile_name>/autogen.lastrun
+       /profiles/<profile_name>/autogen.lastrun_gerrit
+       /profiles/<profile_name>/autogen.lastrun_tb
+       /profiles/<profile_name>/branches/<branch_name>/autogen.lastrun
+       /profiles/<profile_name>/branches/<branch_name>/autogen.lastrun_gerrit
+       /profiles/<profile_name>/branches/<branch_name>/autogen.lastrun_tb
+       /profiles/<profile_name>/branches/<branch_name>/config
+       /profiles/<profile_name>/branches/<branch_name>/config_gerrit
+       /profiles/<profile_name>/branches/<branch_name>/config_tb
+       /profiles/<profile_name>/branches/<branch_name>/false_negatives
+       /profiles/<profile_name>/config
+       /profiles/<profile_name>/false_negatives
+       /profiles/<profile_name>/phases.sh
+
+The configuration is obtained for a given branche by sourcing, in this order,
+
+~/tb/config
+    /profiles/<profile_name>/config
+    /profiles/<profile_name>/branches/<branch_name>/config
+and
+    /profiles/<profile_name>/branches/<branch_name>/config_tb
+for tb-build, or
+    /profiles/<profile_name>/branches/<branch_name>/config_gerrit
+for gerrit-build.
+
+Each build need a autogen configuration for the building of libreoffice.
+for a branch the autogen configuration to is is the _first_ found of
+
+for tb-build :
+    /profiles/<profile_name>/branches/<branch_name>/autogen.lastrun_tb
+    /profiles/<profile_name>/branches/<branch_name>/autogen.lastrun
+    /profiles/<profile_name>/autogen.lastrun_tb
+    /profiles/<profile_name>/autogen.lastrun
+
+for gerrit-build:
+    /profiles/<profile_name>/branches/<branch_name>/autogen.lastrun_gerrit
+    /profiles/<profile_name>/branches/<branch_name>/autogen.lastrun
+    /profiles/<profile_name>/autogen.lastrun_gerrit
+    /profiles/<profile_name>/autogen.lastrun
+
+Note: Contrary to config for tb itself, autogen config do not aggregate.
+The first file found according to the rules above is then the entire
+configuration that will be use as autogen.lastrun for the build.
+
+Some tb configuration parameters can be specified at the global level 'G',
+some at the profile level 'P' and some at the branch level 'B'.
+Unless otherwise specified a parameter can be specificed at any level.
+If a parameter is specified at multiple level then the lowest level prevail.
+In other words: B prevail over P, which prevail over G.
+
+The following variables can be specified in tb's configuration files.
+
+TB_BIBISECT_DIR=<path> Location of a git bibisect repository
+                       This is a tb-mode only paramter. This is ingored in gerrit mode
+                       This is a tb-mode only paramter. This is ingored in gerrit mode
+TB_BIBISECT_GC=[0|1] Indicate if a git gc should be run after each bibisect version
+                     is stored
+                     Default is 0.
+                     This is a tb-mode only paramter. This is ingored in gerrit mode
+TB_BIBISECT_PUSH=[0|1] Indicate if a git push should be issued after each bibisect
+                       version is stored.
+                       Default is 0.
+                       This is a tb-mode only paramter. This is ingored in gerrit mode
+TB_BIBISECT=[0|1] Indicate if a bibisect version should be saved.
+                  Default is 0.
+                  This is a tb-mode only paramter. This is ingored in gerrit mode
+TB_BRANCHES=<nane> [<name>...] Space separated list of branch names configured, to monitor.
+                               This is G or P-level parameter only
+                               Default:"master"
+TB_BRANCH_LOCAL_REFSPEC=<ref> Name of the branche in the local repo
+                              Default to the name of the branch in the configuration
+                              This is a B-level only paramter
+                              This is a tb-mode only paramter. This is ingored in gerrit mode
+TB_BRANCH_REMOTE_REFSPEC=<ref> Name of the branch on the remote repo.
+                               Default to the name of the branch in the configuration
+                               This is a B-level only paramter
+                               This is a tb-mode only paramter. This is ingored in gerrit mode
+TB_BUILD_DIR=<path> Directory to use a a build directory. If specified, it must exist.
+                    Default to TB_GIT_DIR.
+TB_DEFAULT_MODE=[gerrit|gerrit-tb|tb-gerrit|tb] Default mode of operation.
+                                                This is a G or P level parameter
+                                                The command line --mode= override both level.
+                                                Default is "tb".
+TB_GERRIT_HOST<host> Gerrit host to contact in gerrit mode.
+                     This parameter is mandatory to use a gerrit mode.
+                     This is G or P-level parameter only
+TB_GIT_DIR=<path> Location of the libreoffice core git repo to use for a build.
+                  This parameter is mandatory.
+TB_ID=<integer> Id of the tinderbox. see http://wiki/documentfoundation.org/Development/Tinderbox
+                for information on how to acquire such an ID.
+TB_LOGFILE=<path> File where tb will log message about it's operation.
+                  The file does not have to exist, but the path containing the file must.
+                  This is only for message issued by tb itself.. this does no relate to build
+                  logs.
+TB_MATADATA_DIR<path> directory where tb will store state information regarding the defferent
+                      branches it monitor. In tb mode, tb is keeping track of the last
+                      sucessfull build point, in order to be able to notify the committers
+                      involved in patches since that point in case of build failure.
+                      tb also keep track of the last time it uploaded a version
+                      when such upload options is specified.
+                      All the files used to keep track of theses are store in this directory,
+                      tb will attempt to create the directory if it does not exist
+                      This is G or P-level parameter only
+                      This paramter is mandatory.
+TB_NAME=<name> Name of the tinderbox. see http://wiki/documentfoundation.org/Development/Tinderbox
+               for naming conventions.
+               This is G or P-level parameter only
+               This paramter is mandatory.
+TB_OWNER=<email> Email of the owner/operator of the tinderbox
+                 This is G or P-level parameter only
+                 This paramter is mandatory.
+TB_POLL_DELAY=<num_of_seconds> Delay in second between two attempt to pool the states of branches.
+TB_POST_BUILD_DELAY=<num_of_seconds> Pause in second after a build.
+TB_SMTP_HOST=<smtp_hostname> Hostname of the smtp server to use to send email.
+                             This paramter is mandatory in tb mode
+
+TB_SMTP_PASSWORD=<password> Password associated with TB_SMTP_USER.
+                            This paramter is mandatory if TB_SMTP_USER is specified
+TB_SMTP_USER=<username> Username to login to the smtp server.
+                        If your smtp server does not require authotification, you can omit this.
+TB_TINDERBOX_BRANCH=<name> Name assocaited witha  branch on the tibderbox server.
+                           The tinderbox server need to have a branch name assocaited with any
+                           build repport. the server only recognize a preset list of such name
+                           and they do not necessarely match you local branch name.
+                           This is a tb-mode only paramter. This is ignored in gerrit mode
+
+In general, when a command line argument influence on of the paramter listed
+above, it can only override a G-level specification. P-level take, for the most
+part precedence of the command-line, and B-level always take precedence.
+
+The rational is that tb is meant to be a un-attended long running job, Ideally
+taking no argument what-so-ever except -p <profile_name> and possibly --mode=
+
+
+Note for tinbuild2 user: tb does _not_ require a primer build. but it will not
+send email to committers in case a failure until it has had a prior sucessful
+build for a given branch.
+
+EOF
+}
+
+
+
+#
+# Main
+#
+
+case "$1" in
+    ""|help)
+        do_help "$@"
+        exit $?
+        ;;
+    stop)
+        touch ~/.tb/meta/stop
+        exit $?
+        ;;
+esac
+
+set_factory_default
+
+load_config
+
+while [ "${1}" != "" ]; do
+    parm=${1%%=*}
+    arg=${1#*=}
+    has_arg=
+    if [ "${1}" != "${parm?}" ] ; then
+        has_arg=1
+    else
+        arg=""
+    fi
+
+    case "${parm}" in
+        -0)  # avoid calling *_clean functions (incremental build)
+            DO_NOT_CLEAN=1
+            ;;
+        -a)  # email to the branch's author for out-of-tinderbox branch buildbot
+            if [ -z "${has_arg}" ] ; then
+                shift;
+                arg="$1"
+            fi
+            if [ -z "${arg}" ] ; then
+                die "Missing argument for option $parm"
+            else
+                tb_BRANCH_AUTHOR="$arg"
+            fi
+            ;;
+        -b|--branch|--brandhes)  # to list the branches to build
+            if [ -z "${has_arg}" ] ; then
+                shift;
+                arg="$1"
+            fi
+            if [ -z "${arg}" ] ; then
+                echo "Missing argument for option $parm" 1>&2
+                exit -1
+            else
+                tb_BRANCHES="$arg"
+            fi
+            ;;
+        -d)  # wrap make in a watchdog
+            if [ -z "${has_arg}" ] ; then
+                shift;
+                arg="$1"
+            fi
+            if [ -z "${arg}" ] ; then
+                echo "Missing argument for option $parm" 1>&2
+                exit -1
+            else
+                TB_WATCHDOG="makewatchdog $arg -- "
+            fi
+            ;;
+        -h|--help)  # display help
+            do_help
+            exit
+            ;;
+        -i)  # be gentle on i/o
+            TB_NICE_IO="ionice -c3"
+            ;;
+        -k)  # do not override the local autogen.lastrun if present
+            tb_KEEP_AUTOGEN="1"
+            ;;
+        -m)  # which email to wend and to whom
+            if [ -z "${has_arg}" ] ; then
+                shift;
+                arg="$1"
+            fi
+            if [ -z "${arg}" ] ; then
+                echo "Missing argument for option $parm" 1>&2
+                exit -1
+            else
+                tb_SEND_MAIL="${arg}"
+            fi
+            ;;
+        --mode)
+            # operating mode of the tinbuild
+            if [ -z "${has_arg}" ] ; then
+                shift;
+                arg="$1"
+            fi
+            if [ -z "${arg}" ] ; then
+                die "Missing argument for option $parm"
+            fi
+            case "$arg" in
+                fair)
+                    tb_MODE="dual"
+                    TB_DUAL_PRIORITY="fair"
+                    ;;
+                gerrit)
+                    tb_MODE="gerrit"
+                    ;;
+                gerrit-tb)
+                    tb_MODE="dual"
+                    TB_DUAL_PRIORITY="gerrit"
+                    ;;
+                prime)
+                    tb_MODE="tb"
+                    tb_ONE_SHOT="1"
+                    ;;
+                tb)
+                    tb_MODE="tb"
+                    ;;
+                tb-gerrit)
+                    tb_MODE="dual"
+                    TB_DUAL_PRIORITY="tb"
+                    ;;
+                *)
+                    die "Unsupported mode of operation : $arg"
+                    ;;
+            esac
+            ;;
+        -n)  # build 'nicely' :-)
+            TB_NICE_CPU="nice"
+            ;;
+        -p)  # profile to use to deterine extra parameter (email mostly) and autogen arguments
+            if [ -z "${has_arg}" ] ; then
+                shift;
+                arg="$1"
+            fi
+            if [ -z "${arg}" ] ; then
+                die "Missing argument for option $parm"
+            else
+                P="${arg}"
+            fi
+            ;;
+        -v)  # print more messages
+            V=1
+            ;;
+        -x|--bibisect)  # Push builds to bibisect repo (per buildbot and major version)
+            TB_BIBISECT=1
+            ;;
+        -z)
+            # to run an initial build (without sending any email) to establish a 'baseline'
+            # synonym of --mode=prime
+            tb_MODE="tb"
+            tb_ONE_SHOT="1"
+            ;;
+        -*)
+            die "Invalid option $1"
+            ;;
+        *)
+            die "Invalid argument $1"
+            ;;
+    esac
+    shift
+done
+
+
+load_profile "${P}"
+
+setup_profile_defaults
+
+verify_command
+
+# remove lingering stop semaphore file
+rm -f ~/.tb/meta/stop
+
+case "${tb_MODE?}" in
+    dual)
+        run_loop
+        ;;
+    gerrit)
+        run_loop
+        ;;
+    tb)
+        if [ "${tb_ONE_SHOT}" = "1" ] ; then
+            run_primer
+        else
+            run_loop
+        fi
+        ;;
+esac
+
diff --git a/tb/tb_internals.sh b/tb/tb_internals.sh
new file mode 100644
index 0000000..ee24fe0
--- /dev/null
+++ b/tb/tb_internals.sh
@@ -0,0 +1,1471 @@
+#!/usr/bin/env bash
+# -*- tab-width : 4; indent-tabs-mode : nil -*-
+#
+#    Copyright (C) 2011-2013 Norbert Thiebaud
+#    License: GPLv3
+#
+
+# Naming convention
+#
+# lowercase variable: local variable. must be declared as 'local'
+#
+# tb_UPPER_CASE : global variable internal to the script. not to be set directly by config.
+#
+# TB_UPPER_CASE : global variable that can or must be set by config
+#
+# in general use readable variable name, use _ to separate the part of the variables name
+#
+# Exception: P : project name
+#            B : current branch name. gerrit_* are reserved branch names for gerrit works
+#            R : build result indicator
+#            V : verbose messages
+#         MAKE : environement variable is use if set to point to a gnu-make
+#                otherwise overriden to a gne-make found in the PATH
+#
+# Configuration files layout
+#
+#  ~/.tb/config
+#       /meta/
+#       /phases.sh
+#       /profiles/<profile_name>/autogen.lastrun
+#       /profiles/<profile_name>/autogen.lastrun_gerrit
+#       /profiles/<profile_name>/autogen.lastrun_tb
+#       /profiles/<profile_name>/branches/<branch_name>/autogen.lastrun
+#       /profiles/<profile_name>/branches/<branch_name>/autogen.lastrun_gerrit
+#       /profiles/<profile_name>/branches/<branch_name>/autogen.lastrun_tb
+#       /profiles/<profile_name>/branches/<branch_name>/config
+#       /profiles/<profile_name>/branches/<branch_name>/config_gerrit
+#       /profiles/<profile_name>/branches/<branch_name>/config_tb
+#       /profiles/<profile_name>/branches/<branch_name>/false_negatives
+#       /profiles/<profile_name>/config
+#       /profiles/<profile_name>/false_negatives
+#       /profiles/<profile_name>/phases.sh
+
+# Note: config are accumulated from high to low.
+#       autogen are 'lowest level prime'.
+
+
+# XRef :
+#
+# TB_BIBISECT_DIR :
+# TB_BIBISECT_GC :
+# TB_BIBISECT_PUSH :
+# TB_BIBISECT :
+# TB_BRANCHES :
+# TB_BRANCH_LOCAL_REFSPEC :
+# TB_BRANCH_REMOTE_REFSPEC :
+# TB_BUILD_DIR :
+# TB_CONFIG_DIR :
+# TB_DEFAULT_MODE:-tb :
+# TB_DO_TESTS :
+# TB_GERRIT_BRANCH :
+# TB_GERRIT_HOST :
+# TB_GIT_DIR :
+# TB_ID :
+# TB_LOGFILE :
+# TB_MATADATA_DIR :
+# TB_METADATA_DIR :
+# TB_NAME :
+# TB_NICE_CPU :
+# TB_NICE_IO :
+# TB_OWNER :
+# TB_POLL_DELAY :
+# TB_PROFILE_DIR :
+# TB_PROFILE :
+# TB_SMTP_HOST :
+# TB_SMTP_PASSWORD :
+# TB_SMTP_USER :
+# TB_TINDERBOX_BRANCH :
+# TB_WATCHDOG :
+
+# tb_BIN_DIR :
+# tb_BRANCHES :
+# tb_BRANCH_AUTHOR :
+# tb_BUILD_COMMIT :
+# tb_BUILD_TYPE :
+# tb_CONFIG_DIR :
+# tb_GERRIT_BRANCHES :
+# tb_GERRIT_PLATFORM :
+# tb_KEEP_AUTOGEN :
+# tb_LOGFILE :
+# tb_MODE :
+# tb_NEXT_PRIORITY :
+# tb_ONE_SHOT :
+# tb_OPT_DIR :
+# tb_PRIORITY :
+# tb_PRODILE_DIR :
+# tb_PROFILE_DIR :
+# tb_PUSH_NIGHTLIES :
+# tb_REPORT_LOG :
+# tb_REPORT_MSGS :
+# tb_SEND_MAIL :
+# tb_TB_BRANCHES :
+# tb_TINDERBOX_BRANCH :
+
+
+B=
+P=
+V=
+tb_LOGFILE="/dev/null"
+
+
+# please keep the function declaration in alphabetical order
+
+
+bibisect_post()
+{
+    pushd ${TB_BIBISECT_DIR?} > /dev/null
+    if [ "${TB_BIBISECT_GC}" = "1" ] ; then
+        git gc
+    fi
+    if [ "${TB_BIBISECT_PUSH}" = "1" ] ; then
+        git push
+    fi
+    popd > /dev/null
+}
+
+#
+# Check if a branch is suitably configured
+# to be build for tb and/or gerrit
+#
+check_branch_profile()
+{
+    local b="$1"
+    local type=
+    local ret=
+    local rb=
+
+    if [ ! -d "${tb_PROFILE_DIR?}/${b?}" ] ; then
+        msgs_log "No branch specific config for branch '${b?}', using default from profile"
+    fi
+    if [ "${tb_MODE?}" = "dual" -o "${tb_MODE?}" = "tb" ] ; then
+        rb=$(check_branch_profile_tb "$b")
+        ret=$?
+        if [ "$ret" = "0" ] ; then
+            if [ -z "${tb_TB_BRANCHES}" ] ; then
+                tb_TB_BRANCHES="${rb?}"
+            else
+                tb_TB_BRANCHES="${tb_TB_BRANCHES?} ${rb?}"
+            fi
+        fi
+    fi
+    if [ "${tb_MODE?}" = "dual" -o "${tb_MODE?}" = "gerrit" ] ; then
+        rb=$(check_branch_profile_gerrit "${b?}")
+        ret=$?
+        if [ "${ret?}" = "0" ] ; then
+            if [ -z "${tb_GERRIT_BRANCHES}" ; then
+                tb_GERRIT_BRANCHES="${rb?}"
+            else
+                tb_GERRIT_BRANCHES="${tb_GERRIT_BRANCHES?} ${rb?}"
+            fi
+        fi
+    fi
+}
+
+#
+# Checks common to both tb and gerrit
+# buildability of a branch profile
+#
+check_branch_profile_common()
+{
+    local b="$1"
+
+    if [ -z "${TB_GIT_DIR}" ] ; then
+        die "Missing TB_GIT_DIR for branch ${b?}"
+    else
+        if [ ! -d "${TB_GIT_DIR?}" ] ; then
+            die "TB_GIT_DIR:${TB_GIT_DIR?} for branch ${b?} is not a directory"
+        fi
+        if [ ! -d "${TB_GIT_DIR?}/.git" ] ; then
+            die "TB_GIT_DIR:${TB_GIT_DIR?} for branch ${b?} is not a git repository"
+        fi
+    fi
+    if [ -n "${TB_BUILD_DIR}" ] ; then
+        if [ ! -d "${TB_BUILD_DIR?}" ] ; then
+            die "TB_BUILD_DIR:${TB_BUILD_DIR?} for branch ${b?} does not exist"
+        fi
+    fi
+}
+
+#
+# Check specific to gerrit builability
+# of a branch profile
+#
+check_branch_profile_gerrit()
+{
+    local b="$1"
+
+    source_branch_level_config "${b?}" "gerrit"
+
+    # if we did not die yet... we are good for this branch: print it
+    echo "${b?}"
+
+}
+
+#
+# Checks psecific to tb buildability
+# of a branch
+#
+check_branch_profile_tb()
+{
+    local b="$1"
+    local sha=
+
+    source_branch_level_config "${b?}" "tb"
+
+    if [ -z "${TB_TINDERBOX_BRANCH}" ; then
+        # FIXME: determine if we can derive that value
+        # from ${b}
+        die "Missing TB_TINDERBOX_BRANCH to associate a BRANCH name on the tiderbox server to the branch ${b?}"
+    fi
+
+    if [ "${TB_BIBISECT}" == "1" ] ; then
+        if  [ -z "${TB_BIBISECT_DIR}" ] ; then
+            die "To do bibisect you must define TB_BIBISECT_DIR to point to your bibisect git repo"
+        fi
+        if [ ! -d "${TB_BIBISECT_DIR?}" -o ! -d "${TB_BIBISECT_DIR}/.git" ] ; then
+            die "TB_BIBISECT_DIR:${TB_BIBISECT_DIR?} is not a git repository"
+        fi
+        pushd "${TB_BIBISECT_DIR?}" > /dev/null || die "Cannot cd to ${TB_BIBISECT_DIR?} for branch ${b?}"
+        sha=$(git rev-parse "${b?}")
+        if [ "$?" != "0" ] ; then
+            die "Branch ${b?} does not exist in the bibisect repo, Cannot collect the requested bibisect"
+        fi
+    fi
+
+    # if we did not die yet... we are good for this branch: print it
+    echo "${b?}"
+}
+
+
+#
+# Check all the branches under consideration
+# for suitable configuration
+#
+check_branches_profile()
+{
+local b
+
+    tb_TB_BRANCHES=""
+    tb_GERRIT_BRANCHES=""
+
+    for b in ${tb_BRANCHES?} ; do
+        if [ -z "$b" ] ; then
+            die "Internal Error: trying to process and empty branch name"
+        fi
+        check_branch_profile "$b"
+    done
+
+    # Accumulate valid branches for tb and gerrit
+    # depending of the mode
+    if [ "$tb_MODE" = "dual" -o "$tb_MODE" = "gerrit" ] ; then
+        if [ -z "$tb_GERRIT_BRANCHES" ] ; then
+            die "No branches are configured properly for gerrit"
+        fi
+    fi
+    if [ "$tb_MODE" = "dual" -o "$tb_MODE" = "tb" ] ; then
+        if [ -z "$tb_TB_BRANCHES" ] ; then
+            die "No branches are configured properly for tb"
+        fi
+    fi
+}
+
+#
+# Determine if there are new commits
+# on a given branch
+#
+check_for_commit()
+{
+    local b="$1"
+    local err_msgs=
+    local rev=
+    local refspec=
+    local r="-1"
+
+    [ $V ] && echo "Checking for new commit for tb-branch ${b?}"
+
+    source_branch_level_config "${b?}" "tb"
+
+    pushd "${TB_GIT_DIR?}" > /dev/null || die "Cannot cd to git repo ${TB_GIT_DIR?} for tb-branche ${b?}"
+
+    err_msgs="$( $timeout git fetch 2>&1)"
+    if [ "$?" -ne "0" ] ; then
+        printf "Git repo broken - error is:\n\n$err_msgs" > error_log.log
+        report_error owner "$(print_date)" error_log.log
+        exit -1
+    else
+        refspec="origin/${b?}"
+        if [ -n "${TB_BRANCH_REMOTE_REFSPEC}" ] ; then
+            refspec="${TB_BRANCH_REMOTE_REFSPEC?}"
+        fi
+        [ $V ] && echo "collect current head for branch ${b?} refspec ${refspec?}"
+        rev=$(git rev-parse ${refspec?})
+        if [ "$?" = "0" ] ; then
+            echo "${rev?}" > "${TB_METADATA_DIR?}/${P}_${b?}_current-git-head.log"
+            print_date > "${TB_METADATA_DIR?}/${P}_${b?}_current-git-timestamp.log"
+
+            if [ ! -f "${TB_METADATA_DIR?}/prev-${P?}_${b?}_current-git-head.log" ] ; then
+                [ $V ] && echo "New commit for tb-branch ${b?} (no primer)"
+                r=0
+            elif [ "$(cat "${TB_METADATA_DIR?}/${P}_${b?}_current-git-head.log")" != "$(cat "${TB_METADATA_DIR?}/prev-${P?}_${b?}_current-git-head.log")" ] ; then
+                [ $V ] && echo "New commit for tb-branch ${b?}"
+                r=0
+            else
+                [ $V ] && echo "No New commit for tb-branch ${b?}"
+                r=1
+            fi
+        else
+            msgs_log "Git error while checking for commit on ${TB_GIT_REPO?} for branch ${b?}"
+            printf "Git repo broken - error is:\n\n$err_msgs" > error_log.log
+            report_error owner "$(print_date)" error_log.log
+            exit -1
+        fi
+    fi
+    [ $V ] && echo "pulling from the repo ${TB_GIT_REPO?} for branch ${b?} -> r=${r?}"
+    exit ${r?}
+}
+
+#
+# Save the sha assoicated with the HEAD of the current branch
+#
+collect_current_head()
+{
+    [ $V ] && echo "collect_current_head"
+    echo "core:$(git rev-parse HEAD)" > "${TB_METADATA_DIR?}/${P}_${B?}_current-git-head.log"
+    print_date > "${TB_METADATA_DIR?}/${P}_${B?}_current-git-timestamp.log"
+}
+
+#
+# Copy the autogen.lastrun in the builddir
+# this assume that the cwd is the builddir
+# and that B and tb_BUILD_TYPE are set
+# This is notmally called fromthe do_autogen()
+# phase.
+#
+copy_autogen_config()
+{
+    if [ -f "${tb_PROFILE_DIR?}/branches/${B}/autogen.lastrun_${tb_BUILD_TYPE?}" ] ; then
+        cp "${tb_PROFILE_DIR?}/branches/${B}/autogen.lastrun_${tb_BUILD_TYPE?}" autogen.lastrun
+    elif [ -f "${tb_PROFILE_DIR?}/branches/${B}/autogen" ] ; then
+        cp "${tb_PROFILE_DIR?}/branches/${B}/autogen.lastrun" autogen.lastrun
+    elif [ -f "${tb_PROFILE_DIR?}/autogen.lastrun_${tb_BUILD_TYPE?}" ] ; then
+        cp "${tb_PROFILE_DIR?}/autogen.lastrun_${tb_BUILD_TYPE?}" autogen.lastrun
+    elif [ -f "${tb_PROFILE_DIR?}/autogen.lastrun" ] ; then
+        cp "${tb_PROFILE_DIR?}/autogen.lastrun" autogen.lastrun
+    fi
+
+}
+
+deliver_lo_to_bibisect()
+{
+    # copy the content of lo proper to bibisect
+    # this is  separate function so it can easily be overriden
+    cp -fR ${tb_OPT_DIR?} ${TB_BIBISECT_DIR?}/
+
+}
+
+deliver_to_bibisect()
+{
+    local cc=""
+    local oc=""
+
+    [ $V ] && echo "deliver_to_bibisect()"
+
+    if [ -n ${tb_OPT_DIR} ] ; then
+        # verify that someone did not screw-up bibisect repo
+        # while we were running
+        if [ "${TB_BIBISECT}" != "0" ] ; then
+            # note: this function will exit if something is wrong
+
+            # avoid delivering the same build twice to bibisect
+            cc=$(git rev-list -1 HEAD)
+            if [ -f  "${TB_BIBISECT_DIR?}/commit.hash" ] ; then
+                oc="$(cat ${TB_BIBISECT_DIR?}/commit.hash)"
+            fi
+            if [ "${cc}" != "${oc}" ] ; then
+                deliver_lo_to_bibisect
+
+                git log -1 --pretty=format:"source-hash-%H%n%n" ${tb_BUILD_COMMIT?} > "${TB_BIBISECT_DIR?}/commitmsg"
+                git log -1 --pretty=fuller ${tb_BUILD_COMMIT?} >> "${TB_BIBISECT_DIR?}/commitmsg"
+
+                [ $V ] && echo "Bibisect: Include interesting logs/other data"
+                # Include the autogen log.
+                cp tb_${B?}_autogen.log "${TB_BIBISECT_DIR?}/."
+
+                # Include the build, test logs.
+                cp tb_${B?}_build.log "${TB_BIBISECT_DIR?}/."
+
+                # Make it easy to grab the commit id.
+                git rev-list -1 HEAD > "${TB_BIBISECT_DIR?}/commit.hash"
+
+                # Commit build to the local repo and push to the remote.
+                [ $V ] && echo "Bibisect: Committing to local bibisect repo"
+                pushd "${TB_BIBISECT_DIR?}" >/dev/null
+                git add -A
+                git commit -q --file=commitmsg
+                popd > /dev/null
+
+                bibisect_post
+
+            fi
+        fi
+    fi
+    [ $V ] && echo "unlock ${lock_file?}.bibisect"
+    [ $V ] && echo "unlock ${lock_file?}.bibisect"
+}
+
+#
+# Find a gnu make
+#
+determine_make()
+{
+    ## Determine how GNU make is called on the system
+    if [ -n "$MAKE" ] ; then
+        $MAKE --version 2> /dev/null | grep -q GNU
+        if test $? -eq 0;  then
+            return
+        else
+            MAKE=
+        fi
+    fi
+
+    for _g in make gmake gnumake; do
+        $_g --version 2> /dev/null | grep -q GNU
+        if test $? -eq 0;  then
+            MAKE=$_g
+            break
+        fi
+    done
+    if [ -z "$MAKE" ] ; then
+        die "Could not find a Gnu Make"
+    fi
+}
+
+#
+# Display an error message and exit
+# tb or a sub-shell
+#
+die()
+{
+    echo "[$(print_date) ${P?}] Error:" "$@" | tee -a ${tb_LOGFILE?}
+    exit -1;
+}
+
+epoch_from_utc()
+{
+    local utc="$@"
+
+    date -u '+%s' -d "$utc UTC"
+}
+
+epoch_to_utc()
+{
+    date -u -d @${1}
+}
+
+#
+# Fetch a gerrit changeset and check it out
+#
+fetch_gerrit()
+{
+    GERRIT_PREV_B=`git branch | grep '^\*' | sed 's/^..//' | sed 's/\//_/g'`
+    [ $V ] && echo "fetching gerrit path from ssh://${GERRIT_HOST?}/core ${GERRIT_TASK_REF}"
+    git fetch -q ssh://${GERRIT_HOST?}/core ${GERRIT_TASK_REF}
+    if [ "$?" != "0" ] ; then
+        retval="3"
+    else
+        git checkout -q FETCH_HEAD || die "fatal error checking out gerrit ref"
+        git submodule -q update
+        [ $V ] && echo "fetched gerrit path from ssh://${GERRIT_HOST?}/core ${GERRIT_TASK_REF}"
+        retval="0"
+    fi
+}
+
+find_dev_install_location()
+{
+    find . -name opt -type d
+}
+
+generate_cgit_link()
+{
+    local line="$1"
+    local repo=$(echo $line | cut -f 1 -d \:)
+    local sha=$(echo $line | cut -f 2 -d \:)
+
+    echo "<a href='http://cgit.freedesktop.org/libreoffice/${repo}/log/?id=$sha'>$repo</a>"
+}
+
+get_commits_since_last_good()
+{
+    local mode=$1
+    local head=
+    local repo=
+    local sha=
+
+    if [ -f "${TB_METADATA_DIR?}/${P?}_${B?}_last-success-git-head.txt" ] ; then
+        head=$(head -n1 "${TB_METADATA_DIR?}/${P?}_${B?}_last-success-git-head.txt")
+        repo=$(echo ${head?} | cut -d : -f 1)
+        sha=$(echo ${head?} | cut -d : -f 2)
+        if [ "${mode?}" = "people" ] ; then
+            git log '--pretty=tformat:%ce' ${sha?}..HEAD
+        else
+            echo "==== ${repo} ===="
+            git log '--pretty=tformat:%h  %s' ${sha?}..HEAD | sed 's/^/  /'
+        fi
+    else
+        if [ "${mode?}" = "people" ] ; then
+            echo "$TB_OWNER"
+        else
+            echo "==== ${repo} ===="
+            echo "no primer available, can't extract the relevant log"
+        fi
+    fi
+}
+
+get_committers()
+{
+    echo "get_committers: $(get_commits_since_last_good people)" 1>&2
+    get_commits_since_last_good people | sort | uniq | tr '\n' ','
+}
+
+load_config()
+{
+    tb_CONFIG_DIR="$HOME/.tb"
+    if [ ! -d "${tb_CONFIG_DIR?}" ] ; then
+        die "You need to configure tb to use it"
+    fi
+    if [ -f "${tb_CONFIG_DIR?}/config" ] ; then
+        source "${tb_CONFIG_DIR?}/config"
+    fi
+}
+
+load_profile()
+{
+    local p=$1
+    local rc=0
+    local config_file=
+
+
+    if [ -z "$p" ] ; then
+        die "A profile is needed to run: use -p or configure one"
+    else
+        tb_PROFILE_DIR="${tb_CONFIG_DIR?}/profiles/${p}"
+        if [ ! -d "${tb_PROFILE_DIR}" ] ; then
+            die "You need to configure the profile ${p} to use it"
+        fi
+        config_file="${tb_PROFILE_DIR?}/config"
+        if [ -f "${config_file?}" ] ; then
+            source "${config_file?}"
+        fi
+        # global level phase override
+        if [ -f "${tb_CONFIG_DIR?}/phases.sh" ] ; then
+            source "${tb_CONFIG_DIR?}/phases.sh"
+        fi
+        # project level phase override
+        if [ -f "${tb_PROFILE_DIR?}/phases.sh" ] ; then
+            source "${tb_PROFILE_DIR?}/phases.sh"
+        fi
+    fi
+}
+
+log_msgs()
+{
+    echo "[$(print_date) ${P?}]" "$@" | tee -a ${tb_LOGFILE?}
+}
+
+prepare_git_repo_for_gerrit()
+{
+    if [ -z "$TB_BUILD_DIR" ] ; then
+        TB_BUILD_DIR="${TB_GIT_DIR?}"
+    fi
+    pushd ${TB_GIT_DIR?} > /dev/null || die "Cannot cd to build dir : ${TB_GIT_DIR?}"
+    [ $V ] && echo "fetching gerrit path from ssh://${TB_GERRIT_HOST?}/core ${GERRIT_TASK_REF}"
+
+    (
+        git clean -fd && git fetch -q ssh://${GERRIT_HOST?}/core ${GERRIT_TASK_REF}
+        if [ "$?" = "0" ] ; then
+            git checkout -q FETCH_HEAD
+            git submodule -q update
+        else
+            exit -1
+        fi
+    ) 2>&1 > ${TB_BUILD_DIR}/error_log.log
+    popd > /dev/null
+
+    if [ "$?" != "0" ] ; then
+        report_error owner "$(print_date)" error_log.log
+        die "Cannot reposition repo ${TB_GIT_DIR} to the proper branch"
+    fi
+
+}
+
+prepare_git_repo_for_tb()
+{
+    local remote_sha="$(cat "${TB_METADATA_DIR?}/${P}_${B?}_current-git-head.log")"
+    local local_sha=
+    local refspec=
+    local remote_refspec=
+
+    refspec="${B?}"
+    if [ -n "${TB_BRANCH_LOCAL_REFSPEC}" ] ; then
+        refspec="${TB_BRANCH_LOCAL_REFSPEC?}"
+    fi
+
+    remote_refspec="${B?}"
+    if [ -n "${TB_BRANCH_REMOTE_REFSPEC}" ] ; then
+        remote_refspec="${TB_BRANCH_REMOTE_REFSPEC?}"
+    fi
+
+    if [ -z "$TB_BUILD_DIR" ] ; then
+        TB_BUILD_DIR="${TB_GIT_DIR?}"
+    fi
+    pushd ${TB_GIT_DIR?} > /dev/null || die "Cannot cd to build dir : ${TB_GIT_DIR?}"
+
+    (
+        local_sha=$(git rev-parse ${refspec?})
+        if [ "$?" = "0" ] ; then
+            cb=$(git rev-parse --abbrev-ref HEAD)
+            if [ "$?" = "0" -a "${cb?}" = "${refspec?}" ] ; then
+                git clean -fd &&  git reset --hard ${remote_refspec?} && git submodule update
+            else
+                git clean -fd && git checkout -q ${refspec?} && git reset --hard ${remote_refspec?} && git submodule update
+            fi
+        else
+            git clean -fd && git checkout -b ${refspec?} ${remote_refspec?} && git submodule update
+        fi
+    ) 2>&1 > ${TB_BUILD_DIR}/error_log.log
+
+    if [ "$?" != "0" ] ; then
+        report_error owner "$(print_date)" error_log.log
+        die "Cannot reposition repo ${TB_GIT_DIR} to the proper branch"
+    fi
+    popd > /dev/null
+
+}
+
+prepare_upload_manifest()
+{
+    local manifest_file="build_info.txt"
+
+    echo "Build Info" > $manifest_file
+
+    echo "tinderbox: administrator: ${TB_OWNER?}" >> $manifest_file
+    echo "tinderbox: buildname: ${TB_NAME?}" >> $manifest_file
+    echo "tinderbox: tree: ${tb_TINDERBOX_BRANCH?}" >> $manifest_file
+    echo "tinderbox: pull time $(cat "${TB_METADATA_DIR?}/${P?}_${B?}_current-git-timestamp.log")" >> $manifest_file
+    echo "tinderbox: git sha1s"  >> $manifest_file
+    cat "${TB_METADATA_DIR?}/{P?}_${B?}_current-git-head.log"  >> $manifest_file
+    echo ""  >> $manifest_file
+    echo "tinderbox: autogen log"  >> $manifest_file
+    cat tb_${P?}_${B?}_autogen.log  >> $manifest_file
+
+}
+
+print_date()
+{
+    date -u '+%Y-%m-%d %H:%M:%S'
+}
+
+print_local_date()
+{
+    date '+%Y-%m-%d %H:%M:%S'
+}
+
+position_bibisect_branch()
+{
+    pushd ${TB_BIBISECT_DIR?} > /dev/null
+    git checkout -q ${B?}
+    if [ "$?" -ne "0" ] ; then
+        echo "Error could not position the bibisect repository to the branch $B" 1>&2
+        exit 1;
+    fi
+    popd > /dev/null
+}
+
+push_bibisect()
+{
+    local curr_day=
+    local last_day_upload=
+
+    if [ ${TB_BIBISECT} = "1" -a -n "${tb_OPT_DIR}" ] ; then
+
+        [ $V ] && echo "Push: bibisec builds enabled"
+        curr_day=$(date -u '+%Y%j')
+        last_day_upload="$(cat "${TB_METADATA_DIR?}/${P}_${B?}_last-bibisect-day.txt" 2>/dev/null)"
+        if [ -z "$last_day_upload" ] ; then
+            last_day_upload=0
+        fi
+        [ $V ] && echo "bibisect curr_day=$curr_day"
+        [ $V ] && echo "bibisect last_day_upload=$last_day_upload"
+
+        # If it has been less than a day since we pushed the last build
+        # (based on calendar date), skip the rest of the push phase.
+        if [ $last_day_upload -ge $curr_day ] ; then
+            return 0;
+        fi
+        [ $V ] && echo "Record bibisect"
+        deliver_to_bibisect
+
+        echo "$curr_day" > "${TB_METADATA_DIR?}/${P}_${B?}_last-bibisect-day.txt"
+
+    fi
+}
+
+push_nightly()
+{
+    local curr_day=
+    local last_day_upload=
+    local upload_time=
+    local inpath=
+    local stage=
+    local file=
+    local target=
+    local tag=
+
+    # Push build up to the project server (if enabled).
+    [ $V ] && echo "Push: Nightly builds enabled"
+    curr_day=$(date -u '+%Y%j')
+    last_day_upload="$(cat "${TB_METADATA_DIR?}/${P}_${B?}_last-upload-day.txt" 2>/dev/null)"
+    if [ -z "$last_day_upload" ] ; then
+        last_day_upload=0
+    fi
+    [ $V ] && echo "curr_day=$curr_day"
+    [ $V ] && echo "last_day_upload=$last_day_upload"
+
+    # If it has been less than a day since we pushed the last build
+    # (based on calendar date), skip the rest of the push phase.
+    if [ $last_day_upload -ge $curr_day ] ; then
+        return 0;
+    fi
+    [ $V ] && echo "Push Nightly builds"
+    prepare_upload_manifest
+
+    upload_time="$(cat "${TB_METADATA_DIR?}/${P?}_${B?}_current-git-timestamp.log")"
+    ssh upload at gimli.documentfoundation.org "mkdir -p \"/srv/www/dev-builds.libreoffice.org/daily/${B?}/${TB_NAME?}/${upload_time?}\"" || return 1
+
+    if [ -f config_host.mk ] ; then
+        inpath=$(grep INPATH= config_host.mk | sed -e "s/.*=//")
+    else
+        return 1
+    fi
+    if [ -z "${inpath?}" -o ! -d "instsetoo_native/${inpath?}" ] ; then
+        return 1
+    fi
+    pushd instsetoo_native/${inpath?} > /dev/null
+    mkdir push 2>/dev/null || return 1
+    stage="./push"
+    tag="${B?}~${upload_time?}"
+
+    for file in $(find . -name "*.dmg" -o -name '*.apk' -o -name "Lib*.tar.gz" -o -name "Lib*.exe" -o -name "Lib*.zip" -o -path '*/native/install/*.msi' | grep -v "/push/")
+    do
+        target=$(basename $file)
+        target="${tag}_${target}"
+        mv $file "$stage/$target"
+    done;
+
+    rsync --bwlimit=${TB_BANDWIDTH_LIMIT} -avPe ssh ${stage}/${tag}_* "upload at gimli.documentfoundation.org:/srv/www/dev-builds.libreoffice.org/daily/${B?}/${TB_NAME?}/${upload_time?}/" || return 1
+    if [ "$?" == "0" ] ; then
+        ssh upload at gimli.documentfoundation.org "cd \"/srv/www/dev-builds.libreoffice.org/daily/${B?}/${TB_NAME?}/\" && { rm current; ln -s \"${upload_time}\" current ; }"
+    fi
+    echo "$curr_day" > "${TB_METADATA_DIR?}/${P}_${B?}_last-upload-day.txt"
+    return 0
+}
+
+report_error ()
+{
+    local_to_mail=
+    local tinder1=
+    local tinder2=
+    local error_kind="$1"
+    shift
+    local rough_time="$1"
+    shift
+    local error_log="$1"
+    shift
+
+    local last_success=$(cat "${TB_METADATA_DIR?}/${P}_${B?}_last-success-git-timestamp.txt")
+    to_mail=
+    if [ "${tb_SEND_MAIL?}" = "owner" -o "${tb_SEND_MAIL?}" = "debug" -o "${tb_SEND_MAIL?}" = "author" ] ; then
+        to_mail="${TB_OWNER?}"
+    else
+        if [ "${tb_SEND_MAIL?}" = "all" ] ; then
+            case "$error_kind" in
+                owner) to_mail="${TB_OWNER?}"
+                    message="box broken" ;;
+                *)
+                    if [ -z "$last_success" ] ; then
+                        # we need at least one successful build to
+                        # be reliable
+                        to_mail="${TB_OWNER?}"
+                    else
+                        to_mail="$(get_committers)"
+                    fi
+                    message="last success: ${last_success?}" ;;
+            esac
+        fi
+    fi
+    if [ -n "$to_mail" ] ; then
+        echo "$*" 1>&2
+        echo "Last success: ${last_success}" 1>&2
+        tinder1="`echo \"Full log available at http://tinderbox.libreoffice.org/${TB_TINDERBOX_BRANCH?}/status.html\"`"
+        tinder2="`echo \"Box name: ${TB_NAME?}\"`"
+
+        cat <<EOF | send_mail_msg "$to_mail" "Tinderbox failure, ${TB_NAME?}, ${TB_TINDERBOX_BRANCH?}, $message" "" "${TB_OWNER?}" ""
+Hi folks,
+
+One of you broke the build of LibreOffice with your commit :-(
+Please commit and push a fix ASAP!
+
+${tinder1}
+
+ Tinderbox info:
+
+ ${tinder2}
+ Branch: $TINDER_BRANCH
+ "starttime": $(epoch_from_utc "$rough_time")
+ Machine: `uname -a`
+ Configured with: `cat autogen.lastrun`
+
+ Commits since the last success:
+
+ $(get_commits_since_last_good commits)
+
+ The error is:
+
+ $(cat "$error_log")
+EOF
+    else
+        cat $error_log
+    fi
+}
+
+report_gerrit()
+{
+local log_type="$1"
+local status="failed"
+local gzlog=
+
+    [ $V ] && echo "report to gerrit retval=${retval} log_type=${log_type}"
+    if [ "$log_type" = "short"  -a "${R?}" = "0" ] ; then
+        gzlog="tinder.log.gz"
+        (
+            echo "gerrit_task_ticket:$GERRIT_TASK_TICKET"
+            echo "gerrit_task_branch:$GERRIT_TASK_BRANCH"
+            echo "gerrit task_ref:$GERRIT_TASK_REF"
+            echo ""
+            echo "Build: OK"
+            echo ""
+            cat tb_${B}_autogen.log 2>/dev/null
+        ) | gzip -c > "${gzlog}"
+    else
+        gzlog="tinder.log.gz"
+        (
+            echo "gerrit_task_ticket:$GERRIT_TASK_TICKET"
+            echo "gerrit_task_branch:$GERRIT_TASK_BRANCH"
+            echo "gerrit task_ref:$GERRIT_TASK_REF"
+            echo ""
+            if [ "${retval?}" = "0" ] ; then
+                echo "Build: OK"
+            else
+                echo "Build: FAIL"
+            fi
+            echo ""
+            cat tb_${B}_autogen.log tb_${B}_clean.log tb_${B}_build.log tb_${B}_tests.log 2>/dev/null
+        ) | gzip -c > "${gzlog}"
+    fi
+
+    if [ "${R?}" = "0" ] ; then
+        status="success"
+    elif [ "${R?}" = "2" ] ; then
+        status="canceled"
+    fi
+    log_msgs "Report Success for gerrit ref '$GERRIT_TASK_TICKET'."
+    cat "${gzlog}" | ssh ${TB_GERRIT_HOST?} buildbot put --id ${TB_ID?} --ticket "${GERRIT_TASK_TICKET}" --status $status --log -
+
+}
+
+
+report_to_tinderbox()
+{
+    [ $V ] && echo "report_to_tinderbox status=$2"
+    if [ -z "${tb_SEND_MAIL}" -o "${tb_SEND_MAIL}" = "none" -o -z "${TB_NAME}" ] ; then
+        return 0
+    fi
+
+    local start_date="$1"
+    local status="$2"
+    local log="$3"
+    local start_line=
+    local xtinder="X-Tinder: cookie"
+    local subject="tinderbox build start notification"
+    local gzlog=
+    local message_content=
+
+    start_line="tinderbox: starttime: $(epoch_from_utc ${start_date})"
+    message_content="
+tinderbox: administrator: ${TB_OWNER?}
+tinderbox: buildname: ${TB_NAME?}
+tinderbox: tree: ${TB_TINDERBOX_BRANCH?}
+$start_line
+tinderbox: timenow: `date -u '+%s'`
+tinderbox: errorparser: unix
+tinderbox: status: ${status?}
+tinderbox: END
+"
+
+    if [ "$log" = "yes" ] ; then
+        gzlog="tinder.log.gz"
+        (
+            echo "$message_content"
+            cat "${TB_METADATA_DIR?}/${P}_${B?}_current-git-timestamp.log"
+            for cm in $(cat ${TB_METADATA_DIR?}/${P?}_${B?}_current-git-head.log) ; do echo "TinderboxPrint: $(generate_cgit_link ${cm})" ; done
+            cat tb_${B?}_autogen.log tb_${B?}_clean.log tb_${B?}_build.log tb_${B?}_tests.log 2>/dev/null
+        ) | gzip -c > "${gzlog}"
+        xtinder="X-Tinder: gzookie"
+        subject="tinderbox gzipped logfile"
+    fi
+
+    if [ "$SEND_MAIL" = "debug" ] ; then
+        echo "$message_content" | send_mail_msg "${TB_OWNER?}" "${subject?}" "${xtinder?}" '' "${gzlog}"
+    elif [ "$SEND_MAIL" = "author" ] ; then
+        echo "$message_content" | send_mail_msg "${TB_OWNER?}" "${subject?}" "${xtinder?}" '' "${gzlog}"
+        if [ -n "${tb_BRANCH_AUTHOR}" ] ; then
+            echo "$message_content" | send_mail_msg "${tb_BRANCH_AUTHOR}" "${subject?}" "${xtinder?}" '' "${gzlog}"
+        fi
+    else
+        echo "$message_content" | send_mail_msg "tinderbox at gimli.documentfoundation.org" "${subject?}" "${xtinder?}" '' "${gzlog}"
+    fi
+}
+
+rotate_logs()
+{
+    if [ "${R?}" = "0" ] ; then
+        cp -f "${TB_METADATA_DIR?}/${P}_${B?}_current-git-head.log" "${TB_METADATA_DIR?}/${P}_${B?}_last-success-git-head.txt" 2>/dev/null
+        cp -f "${TB_METADATA_DIR?}/${P}_${B?}_current-git-timestamp.log" "${TB_METADATA_DIR?}/${P}_${B?}_last-success-git-timestamp.txt" 2>/dev/null
+    elif [ "${R}" != "2" ]; then # do not count abandonned false_negative loop as failure
+        cp -f "${TB_METADATA_DIR?}/${P}_${B?}_current-git-head.log" "${TB_METADATA_DIR?}/${P}_${B?}_last-failure-git-head.txt" 2>/dev/null
+        cp -f "${TB_METADATA_DIR?}/${P}_${B?}_current-git-timestamp.log" "${TB_METADATA_DIR?}/${P}_${B?}_last-failure-git-timestamp.txt" 2>/dev/null
+    fi
+}
+
+rotate_branches()
+{
+    local top="$1"
+
+    shift
+    if [ -n "$1" ] ; then
+        echo "$@ ${top}"
+    else
+        echo "$top"
+    fi
+}
+
+#
+# Main loop
+#
+run_loop()
+{
+local s=0
+
+    check_branches_profile
+
+    while true; do
+
+        # Check for stop request
+        if [ -f ${TB_METADATA_DIR?}/stop ] ; then
+            break;
+        else
+            sleep ${s?}
+        fi
+
+        # Select the next task
+        # this set tb_BUILD_TYPE and B
+        select_next_task
+
+        # based on the build type run the appropriate build
+        case "$tb_BUILD_TYPE" in
+            tb)
+                run_one_tb
+                s=${TB_POST_BUILD_DELAY?}
+                ;;
+            gerrit)
+                run_one_gerrit
+                s=${TB_POST_BUILD_DELAY?}
+                ;;
+            wait)
+                log_msgs "Nothing to do. waiting ${TB_POLL_DELAY?} seconds."
+                s=${TB_POLL_DELAY?}
+                ;;
+            *)
+                die "Invalid next mode $tb_BUILD_TYPE."
+                ;;
+        esac
+
+    done
+
+    # if we were stopped by request, let's log that
+    # clean the semaphore file
+    if [ -f ${TB_METADATA_DIR?}/stop ] ; then
+        log_msgs "Stoped by request"
+        rm ${TB_METADATA_DIR?}/stop
+    fi
+
+}
+
+#
+# Run a gerrit build
+#
+# Run a a subshell to isolate Branch-level config
+#
+run_one_gerrit()
+{
+    R=0
+    (
+        # source branch-level configuration
+        source_branch_level_config "${B?}" "gerrit"
+
+        if [ -z "$TB_BUILD_DIR" ] ; then
+            TB_BUILD_DIR="${TB_GIT_DIR?}"
+        fi
+
+        # clean-up the git repo associated with the
+        # branch and checkout the target sha
+        prepare_git_repo_for_gerrit
+
+        # gerrit build are not incremental
+        # always use all the phases
+        local phase_list="autogen clean make test push"
+
+        pushd ${TB_BUILD_DIR?} > /dev/null || die "Cannot cd to build dir : ${TB_BUILD_DIR?}"
+
+        # run the build
+        do_build ${phase_list?}
+
+        # tell teh gerrit buildbot of the result of the build
+        # R contain the overall result
+        report_gerrit
+
+        popd > /dev/null
+        exit $R
+    )
+    R="$?"
+}
+
+#
+# Run a tinderbox build
+#
+# Run as subshel to isolate Branch-level config
+#
+run_one_tb()
+{
+    R=0
+    (
+        source_branch_level_config "${B?}" "${tb_BUILD_TYPE?}"
+        if [ -z "$TB_BUILD_DIR" ] ; then
+            TB_BUILD_DIR="${TB_GIT_DIR?}"
+        fi
+
+        # for 'primer' build we expect the repo to be in a buildable
+        # condition already
+        if [ "${tb_ONE_SHOT?}" != "1" ] ; then
+            prepare_git_repo_for_tb
+        fi
+
+        local last_checkout_date="$(cat "${TB_METADATA_DIR?}/${P}_${B?}_current-git-timestamp.log")"
+        local phase_list
+        local retry_count=3
+
+        report_to_tinderbox "${last_checkout_date?}" "building" "no"
+
+
+        if [ "$TB_INCREMENTAL" = "1" ] ; then
+            phase_list="autogen make test push"
+        else
+            phase_list="autogen clean make test push"
+        fi
+
+        pushd ${TB_BUILD_DIR?} > /dev/null || die "Cannot cd to build dir : ${TB_BUILD_DIR?}"
+
+        while [ "${phase_list}" != "" ] ; do
+
+            do_build ${phase_list?}
+
+            if [ "$R" = "0" ] ; then
+                report_to_tinderbox "$last_checkout_date" "success" "yes"
+                phase_list=
+            elif [ "$R" = "2" ] ; then
+                if [ "${tb_ONE_SHOT?}" != "1" ] ; then
+                    report_to_tinderbox "${last_checkout_date?}" "fold" "no"
+                fi
+                log_msgs "False negative build, skip reporting"
+                    # false negative does not need a full clean build, let's just redo make and after
+                retry_count=$((retry_count - 1))
+                if [ "$retry_count" = "0" ] ; then
+                    phase_list=""
+                    R=2
+                else
+                    phase_list="make test push"
+                    R=0
+                fi
+            else
+                printf "${tb_REPORT_MSGS?}:\n\n" > report_error.log
+                echo "======" >> report_error.log
+                if [ "${tb_REPORT_LOG?}" == "tb_${B}_build.log" ] ; then
+                    tail -n1000 ${tb_REPORT_LOG?} >> report_error.log
+                else
+                    cat ${tb_REPORT_LOG?} >> report_error.log
+                fi
+                report_error committer "$last_checkout_date" report_error.log
+                report_to_tinderbox "${last_checkout_date?}" "build_failed" "yes"
+                phase_list=""
+            fi
+        done
+        popd > /dev/null
+        exit $R
+    )
+    R="$?"
+    rotate_logs
+}
+
+#
+# run a one-shot tb run
+#
+run_primer()
+{
+    check_branch_profile
+
+    # as a special case the select_next_task
+    # if tb_ONE_SHOT=1 return the first branch
+    # of the slected list
+    # a primer build build one and one branch only.
+    select_next_task
+
+    # as a special case tun_one_tb() does not reset the repo
+    # do it will just build what is there.
+    # it is the user responsability to make sure that the state
+    # of the repo is correct.
+    # the rational is to allow 'primer' build to be use
+    # to test uncommited changes and/or local commit
+    run_one_tb
+}
+
+#
+# Select a gerrit task from
+# the gerrit buildbot plugin
+#
+select_next_gerrit_task()
+{
+    local result
+    local has_task
+
+    [ $V ] && echo "Try to get a task for gerrit buildbot"
+
+    tb_BUILD_TYPE="wait"
+    GERRIT_TASK_TICKET=""
+    GERRIT_TASK_BRANCH=""
+    GERRIT_TASK_REF=""
+    GERRIT_TASK_FEATURE=""
+    result=$(ssh ${TB_GERRIT_HOST?} buildbot get -p core --id ${TB_ID?} -a ${tb_GERRIT_PLATFORM?} --format BASH)
+    [ $V ] && echo "Get task result:${result}"
+
+    has_task=$(echo "$result" | grep "^GERRIT_TASK_")
+    if [ -n "${has_task}" ] ; then
+        eval "${result}"
+        if [ -z "${GERRIT_TASK_TICKET}" -o -z "${GERRIT_TASK_REF}" -o -z "${GERRIT_TASK_BRANCH}" ] ; then
+            [ $V ] && echo "no valid task from gerrit buildbot"
+        else
+            tb_BUILD_TYPE="gerrit"
+            B="${GERRIT_TASK_BRANCH?}"
+            [ $V ] && echo "got task TASK_TICKET=$GERRIT_TASK_TICKET TASK_REF=$GERRIT_TASK_REF TASK_BRANCH=${B?} "
+        fi
+    else
+        [ $V ] && echo "no task from gerrit buildbot"
+    fi
+
+}
+
+#
+# Select the next task to do
+# either tb or gerrit depending
+# on the mode of operation
+#
+select_next_task()
+{
+
+    if [ tb_MODE="tb" ] ; then
+        select_next_tb_task
+    elif [ tb_MODE="gerrit" ] ; then
+        select_next_gerrit_task
+    else
+        if [ "${tb_NEXT_PRIORITY}" = "tb" ] ; then
+            select_next_tb_task
+            if [ "${tb_BUILD_TYPE?}" = "wait" ] ; then
+                select_next_gerrit_task
+            fi
+        else
+            select_next_gerrit_task
+            if [ "${tb_BUILD_TYPE?}" = "wait" ] ; then
+                select_next_tb_task
+            fi
+        fi
+
+        # if we use a 'fair' priority
+        # switch the order in which we try to get stuff
+        if [ "${tb_PRIORITY?}" = "fair" ] ; then
+            if [ "${tb_BUILD_TYPE?}" = "tb" ] ; then
+                tb_NEXT_PRIORITY="gerrit"
+            elif [ "${tb_BUILD_TYPE?}" = "gerrit" ] ; then
+                tb_NEXT_PRIORITY="tb"
+            fi
+        fi
+    fi
+}
+
+#
+# Select a Tinderbox task
+# by seraching for new commits
+# on on of the branches under consideration
+#
+select_next_tb_task()
+{
+    local b
+    local r
+
+    tb_BUILD_TYPE="wait"
+    for b in ${tb_TB_BRANCHES?} ; do
+        if [ "${tb_ONE_SHOT?}" = "1" ] ; then
+            B="${b?}"
+            tb_BUILD_TYPE="tb"
+            break
+        else
+            ( check_for_commit "$b" )
+            r="$?"
+            if [ ${r?} = 0 ] ; then
+                B="${b?}"
+                rotate_branches ${tb_TB_BRANCHES?}
+                tb_BUILD_TYPE="tb"
+                break
+            fi
+        fi
+    done
+}
+
+send_mail_msg()
+{
+    local to="$1"
+    local subject="$2"
+    local headers="$3"
+    local bcc="$4"
+    local log="$5"
+    local quiet="-q"
+    local smtp_auth=""
+
+    if [ -n "${TB_SMTP_USER}" ] ; then
+        smtp_auth="-xu ${TB_SMTP_USER?} -xp ${TB_SMTP_PASSWORD?}"
+    fi
+
+    log_msgs "send mail to ${to?} with subject \"${subject?}\""
+    [ $V ] && quiet=""
+    if [ -n "${log}" ] ; then
+        ${tb_BIN_DIR?}/tb_send_email $quiet -f "${TB_OWNER?}" -s "${TB_SMTP_HOST?}" $smtp_auth -t "${to?}" -bcc "${bcc?}" -u "${subject?}" -o "message-header=${headers?}" -a "${log?}"
+    elif [ -n "${headers?}" ] ; then
+        ${tb_BIN_DIR?}/tb_send_email $quiet -f "${TB_OWNER?}" -s "${TB_SMTP_HOST?}" $smtp_auth -t "${to?}" -bcc "${bcc?}" -u "${subject?}" -o "message-header=${headers?}"
+    else
+        ${tb_BIN_DIR?}/tb_send_email $quiet -f "${TB_OWNER?}" -s "${TB_SMTP_HOST?}" $smtp_auth -t "${to?}" -bcc "${bcc?}" -u "${subject?}"
+    fi
+}
+
+#
+# Setup factory default for variables
+#
+# this is invoked before the profile is known
+# so it cannot be overriden in the profile's phases.sh
+#
+set_factory_default()
+{
+    TB_INCREMENTAL=
+    TB_GERRIT_HOST="logerrit"
+    TB_POLL_DELAY=120
+    TB_POST_BUILD_DELAY=15
+    TB_BIBISECT=0
+    TB_PUSH_NIGHTLIES=0
+    TB_BANDWIDTH_LIMIT=2000
+
+    tb_ONE_SHOT=
+
+}
+
+#
+# Setup default at the profile level
+#
+# Based on the content of the profile
+# setup some interna variable
+# assign some default and
+# do some other housekeeping
+#
+# Verify that mandatory profile
+# variables are defined
+setup_profile_defaults()
+{
+    local os
+
+    if [  -n "${TB_LOGFILE}" ] ; then
+        if [ ! -f "${TB_LOGFILE?}" ] ; then
+            touch "${TB_LOGFILE?}" || die "Creating ${TB_LOGFILE?}"
+        fi
+        tb_LOGFILE="$TB_LOGFILES"
+    fi
+
+    if [ -z "{TB_METADATA_DIR}" ] ; then
+        TB_METADATA_DIR="${tb_CONFIG_DIR?}/meta/"
+    fi
+    if [ ! -d ${TB_METADATA_DIR} ] ; then
+        mkdir -p "${TB_METADATA_DIR?}" || die "Creating ${TB_METADATA_DIR?}"
+    fi
+
+    if [ -z "$TB_ID" ] ; then
+        die "Error: TB_ID is required to be configured"
+    fi
+
+    if [ -z "${TB_OWNER}" ] ; then
+        die "Error: TB_OWNER is required to be configured"
+    fi
+
+    if [ -z "${TB_NAME}" ] ; then
+        die "TB_NAME is required to be configured"
+    fi
+
+    if [ -z "${tb_GERRIT_PLATFORM}" ] ; then
+        os=$(uname)
+        case "$os" in
+            *Linux*)
+                tb_GERRIT_PLATFORM="LINUX"
+                ;;
+            Darwin)
+                tb_GERRIT_PLATFORM="MAC"
+                ;;
+            CYGWIN*)
+                tb_GERRIT_PLATFORM="WINDOWS"
+                ;;
+        esac
+    fi
+
+    if [ -z "${tb_MODE}" ] ; then
+        tb_MODE="${TB_DEFAULT_MODE:-tb}"
+    fi
+
+    if [ -z "${tb_BRANCHES}" ] ; then
+        tb_BRANCHES="${TB_BRANCHES}"
+        if [ -z "${tb_BRANCHES}" ] ; then
+            log_msgs "TB_BRANCHES and -b not specified. Default to 'master'"
+            tb_BRANCHES="master"
+        fi
+    fi
+}
+
+source_branch_level_config()
+{
+    local b="$1"
+    local t="$2"
+
+    if [ -f "${tb_PROFILE_DIR?}/branches/${b?}/config" ] ; then
+        source "${tb_PROFILE_DIR?}/branches/${B?}/config"
+    fi
+    if [ -f "${tb_PROFILE_DIR?}/branches/${b?}/config_${t?}" ] ; then
+        source "${tb_PROFILE_DIR?}/branches/${b?}/config_${t?}"
+    fi
+}
+
+#
+# Verify the coherence of the command line arguments
+#
+verify_command()
+{
+local rc
+
+    case "$tb_MODE" in
+        dual)
+            if [ -z "$tb_GERRIT_PLATFORM" ] ; then
+                die "tb_GERRIT_PLATFORM is required for mode involving gerrit"
+            fi
+            if [ -z "TB_DUAL_PRIORITY" ] ; then
+                TB_DUAL_PRIORITY="fair"
+            fi
+            ;;
+        gerrit)
+            if [ -z "$tb_GERRIT_PLATFORM" ] ; then
+                die "tb_GERRIT_PLATFORM is required for mode involving gerrit"
+            fi
+            ;;
+        gerrit-patch)
+            tb_SEND_MAIL="none"
+            tb_PUSH_NIGHTLIES=0
+            ;;
+        tb)
+            if [ "${tb_ONE_SHOT?}" = "1" ] ; then
+                tb_SEND_MAIL="none"
+                tb_BRANCHES=$(determine_current_branch ${tb_BRANCHES?})
+                B=${tb_BRANCHES}
+                if [ "${tb_PUSH_NIGHTLIES}" = "1" ] ; then
+                    rm -f "${METADATA_DIR?}/${P?}_${B?}_last-upload-day.txt"
+                fi
+            fi
+            ;;
+        *)
+            ;;
+    esac
+
+    if [ -z "$tb_SEND_MAIL" ] ; then
+        tb_SEND_MAIL="${TB_SEND_MAIL}"
+    fi
+    # if we want email to be sent, we must make sure that the required parameters are set in the profile (or in the environment)
+    case "$tb_SEND_MAIL" in
+        all|tb|owner|debug|author)
+            if [ -z "${TB_SMTP_HOST}" ] ; then
+                die "TB_SMTP_HOST is required in the config to send email"
+            fi
+            if [ -z "${TB_SMTP_USER}" ] ; then
+                echo "Warning: missing SMTPUSER (can work, depends on your smtp server)" 1>&2
+            fi
+            if [ -n "${TB_SMTP_USER}" -a -z "${TB_SMTP_PASSWORD}" ] ; then
+                die "TB_SMTP_PASSWRD is required with TB_SMTP_USER set"
+            fi
+            if [ "$rc" != "0" ] ; then
+                exit 1
+            fi
+            ;;
+        none)
+            ;;
+        *)
+            die "Invalid -m argument:${tb_SEND_MAIL}"
+            ;;
+    esac
+
+}
+
+################
+# ATTENTION:
+# Nothing below this point can be overriden at the platform-level
+# so you should probably add code above this point
+# unless you have a darn good reason not to
+
+# source the platform specific override
+
+mo="$(uname -o 2>/dev/null)"
+ms="$(uname -s 2>/dev/null)"
+if [ -n "${mo}" -a -f "${tb_BIN_DIR?}/tb_internals_${mo}.sh" ] ; then
+    source "${tb_BIN_DIR?}/tb_internals_${mo}.sh"
+else
+    if [ -n "${ms}" -a -f "${tb_BIN_DIR?}/tb_internals_${ms}.sh" ] ; then
+        source "${tb_BIN_DIR?}/tb_internals_${ms}.sh"
+    fi
+fi
+unset mo
+unset ms
+
+
+determine_make
+
+
+# source the standard build phases
+source ${tb_BIN_DIR?}/tb_phases.sh
diff --git a/tb/tb_phases.sh b/tb/tb_phases.sh
new file mode 100644
index 0000000..ed8a938
--- /dev/null
+++ b/tb/tb_phases.sh
@@ -0,0 +1,159 @@
+#!/usr/bin/env bash
+# -*- tab-width : 4; indent-tabs-mode : nil -*-
+#
+#    Copyright (C) 2011-2013 Norbert Thiebaud
+#    License: GPLv3
+#
+
+pre_autogen()
+{
+    if [ "${R}" = "0" ] ; then
+        if [ ! -f autogen.lastrun -o "${tb_KEEP_AUTOGEN}" != "YES" ] ; then
+            copy_autogen_config
+        fi
+    fi
+}
+
+do_autogen()
+{
+    if [ "${R}" = "0" ] ; then
+        if ! ${TB_NICE_CPU} ${TB_NICE_IO} ./autogen.sh >tb_${B}_autogen.log 2>&1 ; then
+            tb_REPORT_LOG=tb_${B}_autogen.log
+            tb_REPORT_MSGS="autogen/configure failed - error is:"
+            R=1
+        fi
+    fi
+}
+
+pre_clean()
+{
+    if [ "${R}" = "0" ] ; then
+        true # log files to clean, if any
+    fi
+}
+
+do_clean()
+{
+    if [ "${R}" = "0" ] ; then
+        if ! ${TB_NICE_CPU} ${TB_NICE_IO} ${TB_WATCHDOG} ${MAKE?} -sr clean > "tb_${B?}_clean.log" 2>&1 ; then
+            tb_REPORT_LOG="tb_${B?}_clean.log"
+            tb_REPORT_MSGS"cleaning up failed - error is:"
+            R=1
+        fi
+    fi
+}
+
+do_make()
+{
+local current_timestamp=
+local optdir=""
+local extra_buildid=""
+
+    tb_OPT_DIR=""
+    if [ "${tb_BUILD_TYPE?}" = "tb" ] ; then
+        current_timestamp=$(sed -e "s/ /_/" "${TB_METADATA_DIR?}/tb_${B}_current-git-timestamp.log")
+        extra_buildid="TinderBox: ${TB_NAME?}, Branch:${B}, Time: $current_timestamp"
+    fi
+    if [ "${R}" = "0" ] ; then
+        if ! ${TB_NICE_CPU} ${TB_NICE_IO} ${TB_WATCHDOG} ${MAKE?} EXTRA_BUILDID="$extra_buildid" -sr > "tb_${B?}_build.log" 2>&1 ; then
+            tb_REPORT_LOG="tb_${B?}_build.log"
+            tb_REPORT_MSGS="build failed - error is:"
+            R=1
+        else
+            # if we want to populate bibisect we need to 'install'
+            if [ "${tb_BUILD_TYPE?}" = "tb" -a ${TB_BIBISECT} != "0" ] ; then
+                if ! ${TB_NICE_CPU} ${TB_NICE_IO} ${TB_WATCHDOG} ${MAKE?} EXTRA_BUILDID="${extra_buildid}" -sr install-tb >>"tb_${B?}_build.log" 2>&1 ; then
+                    tb_REPORT_LOG="tb_${B}_build.log"
+                    tb_REPORT_MSGS="build failed - error is:"
+                    R=1
+                else
+                    tb_OPT_DIR="$(find_dev_install_location)"
+                fi
+            fi
+        fi
+    fi
+}
+
+
+do_test()
+{
+    if [ "${R}" = "0" ] ; then
+        if [ "${TB_DO_TESTS}" = "1" ] ; then
+            if ! ${TB_NICE_CPU} ${TB_NICE_IO} ${TB_WATCHDOG} ${MAKE?} -sr check > "tb_${B?}_tests.log" 2>&1 ; then
+                tb_REPORT_LOG="tb_${B?}_tests.log"
+                tb_REPORT_MSGS="check failed - error is:"
+                R=1
+            fi
+        fi
+    fi
+}
+
+post_make()
+{
+    if [ "${tb_BUILD_TYPE?}" = "tb" ] ; then
+        if [ "${R}" != "0" ] ; then
+            if [ -f "${tb_REPORT_LOG?}" ] ; then
+                if [ -f "${TB_PROFILE_DIR?}/${B?}/false_negatives" ] ; then
+                    grep -F "$(cat "${TB_PROFILE_DIR?}/${B?}/false_negatives")" "${tb_REPORT_LOG?}" && R="2"
+                    if [ "${R?}" == "2" ] ; then
+                        log_msgs "False negative detected"
+                    fi
+                elif [ -f "${TB_PROFILE_DIR?}/false_negatives" ] ; then
+                    grep -F "$(cat "${TB_PROFILE_DIR?}/false_negatives")" "${tb_REPORT_LOG?}" && R="2"
+                    if [ "${R?}" == "2" ] ; then
+                        log_msgs "False negative detected"
+                    fi
+                fi
+            fi
+        fi
+    fi
+}
+
+do_push()
+{
+    [ $V ] && echo "Push: phase starting"
+
+    if [ "${R}" != "0" ] ; then
+        return 0;
+    fi
+
+    if [ "${tb_BUILD_TYPE?}" = "tb" ] ; then
+        # Push nightly build if needed
+        if [ "$TB_PUSH_NIGHTLIES" = "1" ] ; then
+            push_nightly
+        fi
+        # Push bibisect to remote bibisect if needed
+        if [ "$TB_BIBISECT" = "1" ] ; then
+            push_bibisect
+        fi
+    fi
+    return 0;
+}
+
+tb_call()
+{
+    [ $V ] && declare -F "$1" > /dev/null && echo "call $1"
+    declare -F "$1" > /dev/null && $1
+}
+
+phase()
+{
+    local f=${1}
+    for x in {pre_,do_,post_}${f} ; do
+        tb_call ${x}
+    done
+}
+
+
+do_build()
+{
+    local phases="$@"
+    local p
+    [ $V ] && echo "do_build (${tb_BUILD_TYPE?}) phase_list=${phases?}"
+
+    for p in ${phases?} ; do
+        [ $V ] && echo "phase $p"
+        phase $p
+    done
+
+}
diff --git a/tb/tb_send_email b/tb/tb_send_email
new file mode 100755
index 0000000..a9da068
--- /dev/null
+++ b/tb/tb_send_email
@@ -0,0 +1,2235 @@
+#!/usr/bin/perl -w
+##############################################################################
+## sendEmail
+## Written by: Brandon Zehm <caspian at dotconf.net>
+##
+## License:
+##  sendEmail (hereafter referred to as "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.
+##  When redistributing modified versions of this source code it is recommended
+##  that that this disclaimer and the above coder's names are included in the
+##  modified code.
+##
+## Disclaimer:
+##  This program is provided with no warranty of any kind, either expressed or
+##  implied.  It is the responsibility of the user (you) to fully research and
+##  comprehend the usage of this program.  As with any tool, it can be misused,
+##  either intentionally (you're a vandal) or unintentionally (you're a moron).
+##  THE AUTHOR(S) IS(ARE) NOT RESPONSIBLE FOR ANYTHING YOU DO WITH THIS PROGRAM
+##  or anything that happens because of your use (or misuse) of this program,
+##  including but not limited to anything you, your lawyers, or anyone else
+##  can dream up.  And now, a relevant quote directly from the GPL:
+##
+## NO WARRANTY
+##
+##  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+##  FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+##  OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+##  PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+##  OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+##  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+##  TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+##  PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+##  REPAIR OR CORRECTION.
+##
+##############################################################################
+use strict;
+use IO::Socket;
+
+
+########################
+##  Global Variables  ##
+########################
+
+my %conf = (
+    ## General
+    "programName"          => $0,                                  ## The name of this program
+    "version"              => '1.56',                              ## The version of this program
+    "authorName"           => 'Brandon Zehm',                      ## Author's Name
+    "authorEmail"          => 'caspian at dotconf.net',               ## Author's Email Address
+    "timezone"             => '+0000',                             ## We always use +0000 for the time zone
+    "hostname"             => 'changeme',                          ## Used in printmsg() for all output (is updated later in the script).
+    "debug"                => 0,                                   ## Default debug level
+    "error"                => '',                                  ## Error messages will often be stored here
+    
+    ## Logging
+    "stdout"               => 1,
+    "logging"              => 0,                                   ## If this is true the printmsg function prints to the log file
+    "logFile"              => '',                                  ## If this is specified (form the command line via -l) this file will be used for logging.
+    
+    ## Network
+    "server"               => 'localhost',                         ## Default SMTP server
+    "port"                 => 25,                                  ## Default port
+    "bindaddr"             => '',                                  ## Default local bind address
+    "alarm"                => '',                                  ## Default timeout for connects and reads, this gets set from $opt{'timeout'}
+    "tls_client"           => 0,                                   ## If TLS is supported by the client (us)
+    "tls_server"           => 0,                                   ## If TLS is supported by the remote SMTP server
+    
+    ## Email
+    "delimiter"            => "----MIME delimiter for sendEmail-"  ## MIME Delimiter
+                              . rand(1000000),                     ## Add some randomness to the delimiter
+    "Message-ID"           => rand(1000000) . "-sendEmail",        ## Message-ID for email header
+    
+);
+
+
+## This hash stores the options passed on the command line via the -o option.
+my %opt = (
+    ## Addressing
+    "reply-to"             => '',                                  ## Reply-To field
+    
+    ## Message
+    "message-file"         => '',                                  ## File to read message body from
+    "message-header"       => '',                                  ## Additional email header line(s)
+    "message-format"       => 'normal',                            ## If "raw" is specified the message is sent unmodified
+    "message-charset"      => 'iso-8859-1',                        ## Message character-set
+    "message-content-type" => 'auto',                              ## auto, text, html or an actual string to put into the content-type header.
+    
+    ## Network
+    "timeout"              => 200,                                 ## Default timeout for connects and reads, this is copied to $conf{'alarm'} later.
+    "fqdn"                 => 'changeme',                          ## FQDN of this machine, used during SMTP communication (is updated later in the script).
+    
+    ## eSMTP
+    "username"             => '',                                  ## Username used in SMTP Auth
+    "password"             => '',                                  ## Password used in SMTP Auth
+    "tls"                  => 'auto',                              ## Enable or disable TLS support.  Options: auto, yes, no
+    
+);
+
+## More variables used later in the program
+my $SERVER;
+my $CRLF        = "\015\012";
+my $subject     = '';
+my $header      = '';
+my $message     = '';
+my $from        = '';
+my @to          = ();
+my @cc          = ();
+my @bcc         = ();
+my @attachments = ();
+my @attachments_names = ();
+
+## For printing colors to the console
+my ${colorRed}    = "\033[31;1m";
+my ${colorGreen}  = "\033[32;1m";
+my ${colorCyan}   = "\033[36;1m";
+my ${colorWhite}  = "\033[37;1m";
+my ${colorNormal} = "\033[m";
+my ${colorBold}   = "\033[1m";
+my ${colorNoBold} = "\033[0m";
+
+## Don't use shell escape codes on Windows systems
+if ($^O =~ /win/i) {
+    ${colorRed} = ${colorGreen} = ${colorCyan} = ${colorWhite} = ${colorNormal} = ${colorBold} = ${colorNoBold} = "";
+}
+
+## Load IO::Socket::SSL if it's available
+eval    { require IO::Socket::SSL; };
+if ($@) { $conf{'tls_client'} = 0; }
+else    { $conf{'tls_client'} = 1; }
+
+
+
+
+
+
+#############################
+##                          ##
+##      FUNCTIONS            ##
+##                          ##
+#############################
+
+
+
+
+
+###############################################################################################
+##  Function: initialize ()
+##  
+##  Does all the script startup jibberish.
+##  
+###############################################################################################
+sub initialize {
+
+    ## Set STDOUT to flush immediatly after each print  
+    $| = 1;
+    
+    ## Intercept signals
+    $SIG{'QUIT'}  = sub { quit("EXITING: Received SIG$_[0]", 1); };
+    $SIG{'INT'}   = sub { quit("EXITING: Received SIG$_[0]", 1); };
+    $SIG{'KILL'}  = sub { quit("EXITING: Received SIG$_[0]", 1); };
+    $SIG{'TERM'}  = sub { quit("EXITING: Received SIG$_[0]", 1); };
+  
+    ## ALARM and HUP signals are not supported in Win32
+    unless ($^O =~ /win/i) {
+        $SIG{'HUP'}   = sub { quit("EXITING: Received SIG$_[0]", 1); };
+        $SIG{'ALRM'}  = sub { quit("EXITING: Received SIG$_[0]", 1); };
+    }
+    
+    ## Fixup $conf{'programName'}
+    $conf{'programName'} =~ s/(.)*[\/,\\]//;
+    $0 = $conf{'programName'} . " " . join(" ", @ARGV);
+    
+    ## Fixup $conf{'hostname'} and $opt{'fqdn'}
+    if ($opt{'fqdn'} eq 'changeme') { $opt{'fqdn'} = get_hostname(1); }
+    if ($conf{'hostname'} eq 'changeme') { $conf{'hostname'} = $opt{'fqdn'}; $conf{'hostname'} =~ s/\..*//; }
+    
+    return(1);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+###############################################################################################
+##  Function: processCommandLine ()
+##  
+##  Processes command line storing important data in global vars (usually %conf)
+##  
+###############################################################################################
+sub processCommandLine {
+    
+    
+    ############################
+    ##  Process command line  ##
+    ############################
+    
+    my @ARGS = @ARGV;  ## This is so later we can re-parse the command line args later if we need to
+    my $numargv = @ARGS;
+    help() unless ($numargv);
+    my $counter = 0;
+    
+    for ($counter = 0; $counter < $numargv; $counter++) {
+  
+        if ($ARGS[$counter] =~ /^-h$/i) {                    ## Help ##
+            help();
+        }
+        
+        elsif ($ARGS[$counter] eq "") {                      ## Ignore null arguments
+            ## Do nothing
+        }
+        
+        elsif ($ARGS[$counter] =~ /^--help/) {               ## Topical Help ##
+            $counter++;
+            if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) {
+                helpTopic($ARGS[$counter]);
+            }
+            else {
+                help();
+            }
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-o$/i) {                 ## Options specified with -o ##
+            $counter++;
+            ## Loop through each option passed after the -o
+            while ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) {
+                
+                if ($ARGS[$counter] !~ /(\S+)=(\S.*)/) {
+                    printmsg("WARNING => Name/Value pair [$ARGS[$counter]] is not properly formatted", 0);
+                    printmsg("WARNING => Arguments proceeding -o should be in the form of \"name=value\"", 0);
+                }
+                else {
+                    if (exists($opt{$1})) {
+                        if ($1 eq 'message-header') {
+                            $opt{$1} .= $2 . $CRLF;
+                        }
+                        else {
+                            $opt{$1} = $2;
+                        }
+                        printmsg("DEBUG => Assigned \$opt{} key/value: $1 => $2", 3);
+                    }
+                    else {
+                        printmsg("WARNING => Name/Value pair [$ARGS[$counter]] will be ignored: unknown key [$1]", 0);
+                        printmsg("HINT => Try the --help option to find valid command line arguments", 1);
+                    }
+                }
+                $counter++;
+            }   $counter--;
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-f$/) {                  ## From ##
+            $counter++;
+            if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { $from = $ARGS[$counter]; }
+            else { printmsg("WARNING => The argument after -f was not an email address!", 0); $counter--; }
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-t$/) {                  ## To ##
+            $counter++;
+            while ($ARGS[$counter] && ($ARGS[$counter] !~ /^-/)) {
+                if ($ARGS[$counter] =~ /[;,]/) {
+                    push (@to, split(/[;,]/, $ARGS[$counter]));
+                }
+                else {
+                    push (@to,$ARGS[$counter]);
+                }
+                $counter++;
+            }   $counter--;
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-cc$/) {                 ## Cc ##
+            $counter++;
+            while ($ARGS[$counter] && ($ARGS[$counter] !~ /^-/)) {
+                if ($ARGS[$counter] =~ /[;,]/) {
+                    push (@cc, split(/[;,]/, $ARGS[$counter]));
+                }
+                else {
+                    push (@cc,$ARGS[$counter]);
+                }
+                $counter++;
+            }   $counter--;
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-bcc$/) {                ## Bcc ##
+            $counter++;
+            while ($ARGS[$counter] && ($ARGS[$counter] !~ /^-/)) {
+                if ($ARGS[$counter] =~ /[;,]/) {
+                    push (@bcc, split(/[;,]/, $ARGS[$counter]));
+                }
+                else {
+                    push (@bcc,$ARGS[$counter]);
+                }
+                $counter++;
+            }   $counter--;
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-m$/) {                  ## Message ##
+            $counter++;
+            $message = "";
+            while ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) {
+                if ($message) { $message .= " "; }
+                $message .= $ARGS[$counter];
+                $counter++;
+            }   $counter--;
+            
+            ## Replace '\n' with $CRLF.
+            ## This allows newlines with messages sent on the command line
+            $message =~ s/\\n/$CRLF/g;
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-u$/) {                  ## Subject ##
+            $counter++;
+            $subject = "";
+            while ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) {
+                if ($subject) { $subject .= " "; }
+                $subject .= $ARGS[$counter];
+                $counter++;
+            }   $counter--;
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-s$/) {                  ## Server ##
+            $counter++;
+            if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) {
+                $conf{'server'} = $ARGS[$counter];
+                if ($conf{'server'} =~ /:/) {                ## Port ##
+                    ($conf{'server'},$conf{'port'}) = split(":",$conf{'server'});
+                }
+            }
+            else { printmsg("WARNING - The argument after -s was not the server!", 0); $counter--; }
+        }
+
+        elsif ($ARGS[$counter] =~ /^-b$/) {                  ## Bind Address ##
+            $counter++;
+            if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) {
+                $conf{'bindaddr'} = $ARGS[$counter];
+            }
+            else { printmsg("WARNING - The argument after -b was not the bindaddr!", 0); $counter--; }
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-a$/) {                  ## Attachments ##
+            $counter++;
+            while ($ARGS[$counter] && ($ARGS[$counter] !~ /^-/)) {
+                push (@attachments,$ARGS[$counter]);
+                $counter++;
+            }   $counter--;
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-xu$/) {                  ## AuthSMTP Username ##
+            $counter++;
+            if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) {
+               $opt{'username'} = $ARGS[$counter];
+            }
+            else {
+                printmsg("WARNING => The argument after -xu was not valid username!", 0);
+                $counter--;
+            }
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-xp$/) {                  ## AuthSMTP Password ##
+            $counter++;
+            if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) {
+               $opt{'password'} = $ARGS[$counter];
+            }
+            else {
+                printmsg("WARNING => The argument after -xp was not valid password!", 0);
+                $counter--;
+            }
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-l$/) {                  ## Logging ##
+            $counter++;
+            $conf{'logging'} = 1;
+            if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { $conf{'logFile'} = $ARGS[$counter]; }
+            else { printmsg("WARNING - The argument after -l was not the log file!", 0); $counter--; }
+        }
+        
+        elsif ($ARGS[$counter] =~ s/^-v+//i) {               ## Verbosity ##
+            my $tmp = (length($&) - 1);
+            $conf{'debug'} += $tmp;
+        }
+        
+        elsif ($ARGS[$counter] =~ /^-q$/) {                  ## Quiet ##
+            $conf{'stdout'} = 0;
+        }
+        
+        else {
+            printmsg("Error: \"$ARGS[$counter]\" is not a recognized option!", 0);
+            help();
+        }
+        
+    }
+
+
+
+
+
+
+    
+    
+    ###################################################
+    ##  Verify required variables are set correctly  ##
+    ###################################################
+    
+    ## Make sure we have something in $conf{hostname} and $opt{fqdn}
+    if ($opt{'fqdn'} =~ /\./) {
+        $conf{'hostname'} = $opt{'fqdn'};
+        $conf{'hostname'} =~ s/\..*//;
+    }
+    
+    if (!$conf{'server'}) { $conf{'server'} = 'localhost'; }
+    if (!$conf{'port'})   { $conf{'port'} = 25; }
+    if (!$from) {
+        quit("ERROR => You must specify a 'from' field!  Try --help.", 1);
+    }
+    if ( ((scalar(@to)) + (scalar(@cc)) + (scalar(@bcc))) <= 0) {
+        quit("ERROR => You must specify at least one recipient via -t, -cc, or -bcc", 1);
+    }
+    
+    ## Make sure email addresses look OK.
+    foreach my $addr (@to, @cc, @bcc, $from, $opt{'reply-to'}) {
+        if ($addr) {
+            if (!returnAddressParts($addr)) {
+                printmsg("ERROR => Can't use improperly formatted email address: $addr", 0);
+                printmsg("HINT => Try viewing the extended help on addressing with \"--help addressing\"", 1);
+                quit("", 1);
+            }
+        }
+    }
+    
+    ## Make sure all attachments exist.
+    foreach my $file (@attachments) {
+        if ( (! -f $file) or (! -r $file) ) {
+            printmsg("ERROR => The attachment [$file] doesn't exist!", 0);
+            printmsg("HINT => Try specifying the full path to the file or reading extended help with \"--help message\"", 1);
+            quit("", 1);
+        }
+    }
+    
+    if ($conf{'logging'} and (!$conf{'logFile'})) {
+        quit("ERROR => You used -l to enable logging but didn't specify a log file!", 1);
+    }    
+    
+    if ( $opt{'username'} ) {
+        if (!$opt{'password'}) {
+            ## Prompt for a password since one wasn't specified with the -xp option.
+            $SIG{'ALRM'} = sub { quit("ERROR => Timeout waiting for password inpupt", 1); };
+            alarm(60) if ($^O !~ /win/i);  ## alarm() doesn't work in win32
+            print "Password: ";
+            $opt{'password'} = <STDIN>; chomp $opt{'password'};
+            if (!$opt{'password'}) {
+                quit("ERROR => A username for SMTP authentication was specified, but no password!", 1);
+            }
+        }
+    }
+    
+    ## Validate the TLS setting
+    $opt{'tls'} = lc($opt{'tls'});
+    if ($opt{'tls'} !~ /^(auto|yes|no)$/) {
+        quit("ERROR => Invalid TLS setting ($opt{'tls'}). Must be one of auto, yes, or no.", 1);
+    }
+    
+    ## If TLS is set to "yes", make sure sendEmail loaded the libraries needed.
+    if ($opt{'tls'} eq 'yes' and $conf{'tls_client'} == 0) {
+        quit("ERROR => No TLS support!  SendEmail can't load required libraries. (try installing Net::SSLeay and IO::Socket::SSL)", 1);
+    }
+    
+    ## Return 0 errors
+    return(0);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## getline($socketRef)
+sub getline {
+    my ($socketRef) = @_;
+    local ($/) = "\r\n";
+    return $$socketRef->getline;
+}
+
+
+
+
+## Receive a (multiline?) SMTP response from ($socketRef)
+sub getResponse {
+    my ($socketRef) = @_;
+    my ($tmp, $reply);
+    local ($/) = "\r\n";
+    return undef unless defined($tmp = getline($socketRef));
+    return("getResponse() socket is not open") unless ($$socketRef->opened);
+    ## Keep reading lines if it's a multi-line response
+    while ($tmp =~ /^\d{3}-/o) {
+        $reply .= $tmp;
+        return undef unless defined($tmp = getline($socketRef));
+    }
+    $reply .= $tmp;
+    $reply =~ s/\r?\n$//o;
+    return $reply;
+}
+
+
+
+
+###############################################################################################
+##  Function:    SMTPchat ( [string $command] )
+##
+##  Description: Sends $command to the SMTP server (on SERVER) and awaits a successful
+##               reply form the server.  If the server returns an error, or does not reply
+##               within $conf{'alarm'} seconds an error is generated.
+##               NOTE: $command is optional, if no command is specified then nothing will
+##               be sent to the server, but a valid response is still required from the server.
+##
+##  Input:       [$command]          A (optional) valid SMTP command (ex. "HELO")
+##  
+##  
+##  Output:      Returns zero on success, or non-zero on error.  
+##               Error messages will be stored in $conf{'error'}
+##               A copy of the last SMTP response is stored in the global variable
+##               $conf{'SMTPchat_response'}
+##               
+##  
+##  Example:     SMTPchat ("HELO mail.isp.net");
+###############################################################################################
+sub SMTPchat {
+    my ($command) = @_;
+    
+    printmsg("INFO => Sending: \t$command", 1) if ($command);
+    
+    ## Send our command
+    print $SERVER "$command$CRLF" if ($command);
+    
+    ## Read a response from the server
+    $SIG{'ALRM'} = sub { $conf{'error'} = "alarm"; $SERVER->close(); };
+    alarm($conf{'alarm'}) if ($^O !~ /win/i);  ## alarm() doesn't work in win32;
+    my $result = $conf{'SMTPchat_response'} = getResponse(\$SERVER); 
+    alarm(0) if ($^O !~ /win/i);  ## alarm() doesn't work in win32;
+    
+    ## Generate an alert if we timed out
+    if ($conf{'error'} eq "alarm") {
+        $conf{'error'} = "ERROR => Timeout while reading from $conf{'server'}:$conf{'port'} There was no response after $conf{'alarm'} seconds.";
+        return(1);
+    }
+    
+    ## Make sure the server actually responded
+    if (!$result) {
+        $conf{'error'} = "ERROR => $conf{'server'}:$conf{'port'} returned a zero byte response to our query.";
+        return(2);
+    }
+    
+    ## Validate the response
+    if (evalSMTPresponse($result)) {
+        ## conf{'error'} will already be set here
+        return(2);
+    }
+    
+    ## Print the success messsage
+    printmsg($conf{'error'}, 1);
+    
+    ## Return Success
+    return(0);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+###############################################################################################
+##  Function:    evalSMTPresponse (string $message )
+##
+##  Description: Searches $message for either an  SMTP success or error code, and returns
+##               0 on success, and the actual error code on error.
+##               
+##
+##  Input:       $message          Data received from a SMTP server (ex. "220 
+##                                
+##  
+##  Output:      Returns zero on success, or non-zero on error.  
+##               Error messages will be stored in $conf{'error'}
+##               
+##  
+##  Example:     SMTPchat ("HELO mail.isp.net");
+###############################################################################################
+sub evalSMTPresponse {
+    my ($message) = @_;
+    
+    ## Validate input
+    if (!$message) { 
+        $conf{'error'} = "ERROR => No message was passed to evalSMTPresponse().  What happened?";
+        return(1)
+    }
+    
+    printmsg("DEBUG => evalSMTPresponse() - Checking for SMTP success or error status in the message: $message ", 3);
+    
+    ## Look for a SMTP success code
+    if ($message =~ /^([23]\d\d)/) {
+        printmsg("DEBUG => evalSMTPresponse() - Found SMTP success code: $1", 2);
+        $conf{'error'} = "SUCCESS => Received: \t$message";
+        return(0);
+    }
+    
+    ## Look for a SMTP error code
+    if ($message =~ /^([45]\d\d)/) {
+        printmsg("DEBUG => evalSMTPresponse() - Found SMTP error code: $1", 2);
+        $conf{'error'} = "ERROR => Received: \t$message";
+        return($1);
+    }
+    
+    ## If no SMTP codes were found return an error of 1
+    $conf{'error'} = "ERROR => Received a message with no success or error code. The message received was: $message";
+    return(2);
+    
+}
+
+
+
+
+
+
+
+
+
+
+#########################################################
+# SUB: &return_month(0,1,etc)
+#  returns the name of the month that corrosponds
+#  with the number.  returns 0 on error.
+#########################################################
+sub return_month {
+    my $x = $_[0];
+    if ($x == 0)  { return 'Jan'; }
+    if ($x == 1)  { return 'Feb'; }
+    if ($x == 2)  { return 'Mar'; }
+    if ($x == 3)  { return 'Apr'; }
+    if ($x == 4)  { return 'May'; }
+    if ($x == 5)  { return 'Jun'; }
+    if ($x == 6)  { return 'Jul'; }
+    if ($x == 7)  { return 'Aug'; }
+    if ($x == 8)  { return 'Sep'; }
+    if ($x == 9)  { return 'Oct'; }
+    if ($x == 10) { return 'Nov'; }
+    if ($x == 11) { return 'Dec'; }
+    return (0);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#########################################################
+# SUB: &return_day(0,1,etc)
+#  returns the name of the day that corrosponds
+#  with the number.  returns 0 on error.
+#########################################################
+sub return_day {
+    my $x = $_[0];
+    if ($x == 0)  { return 'Sun'; }
+    if ($x == 1)  { return 'Mon'; }
+    if ($x == 2)  { return 'Tue'; }
+    if ($x == 3)  { return 'Wed'; }
+    if ($x == 4)  { return 'Thu'; }
+    if ($x == 5)  { return 'Fri'; }
+    if ($x == 6)  { return 'Sat'; }
+    return (0);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+###############################################################################################
+##  Function:    returnAddressParts(string $address)
+##
+##  Description: Returns a two element array containing the "Name" and "Address" parts of 
+##               an email address.
+##  
+## Example:      "Brandon Zehm <caspian at dotconf.net>"
+##               would return: ("Brandon Zehm", "caspian at dotconf.net");
+## 
+##               "caspian at dotconf.net"
+##               would return: ("caspian at dotconf.net", "caspian at dotconf.net")
+###############################################################################################
+sub returnAddressParts {
+    my $input = $_[0];
+    my $name = "";
+    my $address = "";
+    
+    ## Make sure to fail if it looks totally invalid
+    if ($input !~ /(\S+\@\S+)/) {
+        $conf{'error'} = "ERROR => The address [$input] doesn't look like a valid email address, ignoring it";
+        return(undef());
+    }
+    
+    ## Check 1, should find addresses like: "Brandon Zehm <caspian at dotconf.net>"
+    elsif ($input =~ /^\s*(\S(.*\S)?)\s*<(\S+\@\S+)>/o) {
+        ($name, $address) = ($1, $3);
+    }
+    
+    ## Otherwise if that failed, just get the address: <caspian at dotconf.net>
+    elsif ($input =~ /<(\S+\@\S+)>/o) {
+        $name = $address = $1;
+    }
+    
+    ## Or maybe it was formatted this way: caspian at dotconf.net
+    elsif ($input =~ /(\S+\@\S+)/o) {
+        $name = $address = $1;
+    }
+    
+    ## Something stupid happened, just return an error.
+    unless ($name and $address) {
+        printmsg("ERROR => Couldn't parse the address: $input", 0);
+        printmsg("HINT => If you think this should work, consider reporting this as a bug to $conf{'authorEmail'}", 1);
+        return(undef());
+    }
+    
+    ## Make sure there aren't invalid characters in the address, and return it.
+    my $ctrl        = '\000-\037';
+    my $nonASCII    = '\x80-\xff';
+    if ($address =~ /[<> ,;:"'\[\]\\$ctrl$nonASCII]/) {
+        printmsg("WARNING => The address [$address] seems to contain invalid characters: continuing anyway", 0);
+    }
+    return($name, $address);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+###############################################################################################
+##  Function:    base64_encode(string $data, bool $chunk)
+##
+##  Description: Returns $data as a base64 encoded string.
+##               If $chunk is true, the encoded data is returned in 76 character long lines
+##               with the final \CR\LF removed.
+##
+##  Note: This is only used from the smtp auth section of code.
+##        At some point it would be nice to merge the code that encodes attachments and this.
+###############################################################################################
+sub base64_encode {
+    my $data = $_[0];
+    my $chunk = $_[1];
+    my $tmp = '';
+    my $base64 = '';
+    my $CRLF = "\r\n";
+    
+    ###################################
+    ## Convert binary data to base64 ##
+    ###################################
+    while ($data =~ s/(.{45})//s) {        ## Get 45 bytes from the binary string
+        $tmp = substr(pack('u', $&), 1);   ## Convert the binary to uuencoded text
+        chop($tmp);
+        $tmp =~ tr|` -_|AA-Za-z0-9+/|;     ## Translate from uuencode to base64
+        $base64 .= $tmp;
+    }
+    
+    ##########################
+    ## Encode the leftovers ##
+    ##########################
+    my $padding = "";
+    if ( ($data) and (length($data) > 0) ) {
+        $padding = (3 - length($data) % 3) % 3;    ## Set flag if binary data isn't divisible by 3
+        $tmp = substr(pack('u', $data), 1);        ## Convert the binary to uuencoded text
+        chop($tmp);
+        $tmp =~ tr|` -_|AA-Za-z0-9+/|;             ## Translate from uuencode to base64
+        $base64 .= $tmp;
+    }
+    
+    ############################
+    ## Fix padding at the end ##
+    ############################
+    $data = '';
+    $base64 =~ s/.{$padding}$/'=' x $padding/e if $padding; ## Fix the end padding if flag (from above) is set
+    if ($chunk) {
+        while ($base64 =~ s/(.{1,76})//s) {                     ## Put $CRLF after each 76 characters
+            $data .= "$1$CRLF";
+        }
+    }
+    else {
+        $data = $base64;
+    }
+    
+    ## Remove any trailing CRLF's
+    $data =~ s/(\r|\n)*$//s;
+    return($data);
+}
+
+
+
+
+
+
+
+
+
+#########################################################
+# SUB: send_attachment("/path/filename")
+# Sends the mime headers and base64 encoded file
+# to the email server.
+#########################################################
+sub send_attachment {
+    my ($filename) = @_;                             ## Get filename passed
+    my (@fields, $y, $filename_name, $encoding,      ## Local variables
+        @attachlines, $content_type);
+    my $bin = 1;
+    
+    @fields = split(/\/|\\/, $filename);             ## Get the actual filename without the path  
+    $filename_name = pop(@fields);       
+    push @attachments_names, $filename_name;         ## FIXME: This is only used later for putting in the log file
+    
+    ##########################
+    ## Autodetect Mime Type ##
+    ##########################
+    
+    @fields = split(/\./, $filename_name);
+    $encoding = $fields[$#fields];
+    
+    if ($encoding =~ /txt|text|log|conf|^c$|cpp|^h$|inc|m3u/i) {   $content_type = 'text/plain';                      }
+    elsif ($encoding =~ /html|htm|shtml|shtm|asp|php|cfm/i) {      $content_type = 'text/html';                       }
+    elsif ($encoding =~ /sh$/i) {                                  $content_type = 'application/x-sh';                }
+    elsif ($encoding =~ /tcl/i) {                                  $content_type = 'application/x-tcl';               }
+    elsif ($encoding =~ /pl$/i) {                                  $content_type = 'application/x-perl';              }
+    elsif ($encoding =~ /js$/i) {                                  $content_type = 'application/x-javascript';        }
+    elsif ($encoding =~ /man/i) {                                  $content_type = 'application/x-troff-man';         }
+    elsif ($encoding =~ /gif/i) {                                  $content_type = 'image/gif';                       }
+    elsif ($encoding =~ /jpg|jpeg|jpe|jfif|pjpeg|pjp/i) {          $content_type = 'image/jpeg';                      }
+    elsif ($encoding =~ /tif|tiff/i) {                             $content_type = 'image/tiff';                      }
+    elsif ($encoding =~ /xpm/i) {                                  $content_type = 'image/x-xpixmap';                 }
+    elsif ($encoding =~ /bmp/i) {                                  $content_type = 'image/x-MS-bmp';                  }
+    elsif ($encoding =~ /pcd/i) {                                  $content_type = 'image/x-photo-cd';                }
+    elsif ($encoding =~ /png/i) {                                  $content_type = 'image/png';                       }
+    elsif ($encoding =~ /aif|aiff/i) {                             $content_type = 'audio/x-aiff';                    }
+    elsif ($encoding =~ /wav/i) {                                  $content_type = 'audio/x-wav';                     }
+    elsif ($encoding =~ /mp2|mp3|mpa/i) {                          $content_type = 'audio/x-mpeg';                    }
+    elsif ($encoding =~ /ra$|ram/i) {                              $content_type = 'audio/x-pn-realaudio';            }
+    elsif ($encoding =~ /mpeg|mpg/i) {                             $content_type = 'video/mpeg';                      }
+    elsif ($encoding =~ /mov|qt$/i) {                              $content_type = 'video/quicktime';                 }
+    elsif ($encoding =~ /avi/i) {                                  $content_type = 'video/x-msvideo';                 }
+    elsif ($encoding =~ /zip/i) {                                  $content_type = 'application/x-zip-compressed';    }
+    elsif ($encoding =~ /tar/i) {                                  $content_type = 'application/x-tar';               }
+    elsif ($encoding =~ /jar/i) {                                  $content_type = 'application/java-archive';        }
+    elsif ($encoding =~ /exe|bin/i) {                              $content_type = 'application/octet-stream';        }
+    elsif ($encoding =~ /ppt|pot|ppa|pps|pwz/i) {                  $content_type = 'application/vnd.ms-powerpoint';   }
+    elsif ($encoding =~ /mdb|mda|mde/i) {                          $content_type = 'application/vnd.ms-access';       }
+    elsif ($encoding =~ /xls|xlt|xlm|xld|xla|xlc|xlw|xll/i) {      $content_type = 'application/vnd.ms-excel';        }
+    elsif ($encoding =~ /doc|dot/i) {                              $content_type = 'application/msword';              }
+    elsif ($encoding =~ /rtf/i) {                                  $content_type = 'application/rtf';                 }
+    elsif ($encoding =~ /pdf/i) {                                  $content_type = 'application/pdf';                 }
+    elsif ($encoding =~ /tex/i) {                                  $content_type = 'application/x-tex';               }
+    elsif ($encoding =~ /latex/i) {                                $content_type = 'application/x-latex';             }
+    elsif ($encoding =~ /vcf/i) {                                  $content_type = 'application/x-vcard';             }
+    else { $content_type = 'application/octet-stream';  }
+  
+  
+  ############################
+  ## Process the attachment ##
+  ############################
+    
+    #####################################
+    ## Generate and print MIME headers ##
+    #####################################
+    
+    $y  = "$CRLF--$conf{'delimiter'}$CRLF";
+    $y .= "Content-Type: $content_type;$CRLF";
+    $y .= "        name=\"$filename_name\"$CRLF";
+    $y .= "Content-Transfer-Encoding: base64$CRLF";
+    $y .= "Content-Disposition: attachment; filename=\"$filename_name\"$CRLF";
+    $y .= "$CRLF";
+    print $SERVER $y;
+    
+    
+    ###########################################################
+    ## Convert the file to base64 and print it to the server ##
+    ###########################################################
+    
+    open (FILETOATTACH, $filename) || do {
+        printmsg("ERROR => Opening the file [$filename] for attachment failed with the error: $!", 0);
+        return(1);
+    };
+    binmode(FILETOATTACH);                 ## Hack to make Win32 work
+    
+    my $res = "";
+    my $tmp = "";
+    my $base64 = "";
+    while (<FILETOATTACH>) {               ## Read a line from the (binary) file
+        $res .= $_;
+        
+        ###################################
+        ## Convert binary data to base64 ##
+        ###################################
+        while ($res =~ s/(.{45})//s) {         ## Get 45 bytes from the binary string
+            $tmp = substr(pack('u', $&), 1);   ## Convert the binary to uuencoded text
+            chop($tmp);
+            $tmp =~ tr|` -_|AA-Za-z0-9+/|;     ## Translate from uuencode to base64
+            $base64 .= $tmp;
+        }
+        
+        ################################
+        ## Print chunks to the server ##
+        ################################
+        while ($base64 =~ s/(.{76})//s) {
+            print $SERVER "$1$CRLF";
+        }
+      
+    }
+    
+    ###################################
+    ## Encode and send the leftovers ##
+    ###################################
+    my $padding = "";
+    if ( ($res) and (length($res) >= 1) ) {
+        $padding = (3 - length($res) % 3) % 3;  ## Set flag if binary data isn't divisible by 3
+        $res = substr(pack('u', $res), 1);      ## Convert the binary to uuencoded text
+        chop($res);
+        $res =~ tr|` -_|AA-Za-z0-9+/|;          ## Translate from uuencode to base64
+    }
+    
+    ############################
+    ## Fix padding at the end ##
+    ############################
+    $res = $base64 . $res;                               ## Get left overs from above
+    $res =~ s/.{$padding}$/'=' x $padding/e if $padding; ## Fix the end padding if flag (from above) is set
+    if ($res) {
+        while ($res =~ s/(.{1,76})//s) {                 ## Send it to the email server.
+            print $SERVER "$1$CRLF";
+        }
+    }
+    
+    close (FILETOATTACH) || do {
+        printmsg("ERROR - Closing the filehandle for file [$filename] failed with the error: $!", 0);
+        return(2);
+    };
+    
+    ## Return 0 errors
+    return(0);
+
+}
+
+
+
+
+
+
+
+
+
+###############################################################################################
+##  Function:    $string = get_hostname (boot $fqdn)
+##  
+##  Description: Tries really hard to returns the short (or FQDN) hostname of the current
+##               system.  Uses techniques and code from the  Sys-Hostname module.
+##  
+##  Input:       $fqdn     A true value (1) will cause this function to return a FQDN hostname
+##                         rather than a short hostname.
+##  
+##  Output:      Returns a string
+###############################################################################################
+sub get_hostname {
+    ## Assign incoming parameters to variables
+    my ( $fqdn ) = @_;
+    my $hostname = "";
+    
+    ## STEP 1: Get short hostname
+    
+    ## Load Sys::Hostname if it's available
+    eval { require Sys::Hostname; };
+    unless ($@) {
+        $hostname = Sys::Hostname::hostname(); 
+    }
+    
+    ## If that didn't get us a hostname, try a few other things
+    else {
+        ## Windows systems
+        if ($^O !~ /win/i) {
+            if ($ENV{'COMPUTERNAME'}) { $hostname = $ENV{'COMPUTERNAME'}; }
+            if (!$hostname) { $hostname = gethostbyname('localhost'); }
+            if (!$hostname) { chomp($hostname = `hostname 2> NUL`) };
+        }
+        
+        ## Unix systems
+        else {
+            local $ENV{PATH} = '/usr/bin:/bin:/usr/sbin:/sbin';  ## Paranoia
+            
+            ## Try the environment first (Help!  What other variables could/should I be checking here?)
+            if ($ENV{'HOSTNAME'}) { $hostname = $ENV{'HOSTNAME'}; }
+            
+            ## Try the hostname command
+            eval { local $SIG{__DIE__}; local $SIG{CHLD}; $hostname = `hostname 2>/dev/null`; chomp($hostname); } ||
+            
+            ## Try POSIX::uname(), which strictly can't be expected to be correct
+            eval { local $SIG{__DIE__}; require POSIX; $hostname = (POSIX::uname())[1]; } ||
+            
+            ## Try the uname command
+            eval { local $SIG{__DIE__}; $hostname = `uname -n 2>/dev/null`; chomp($hostname); };
+            
+        }
+        
+        ## If we can't find anything else, return ""
+        if (!$hostname) {
+            print "WARNING => No hostname could be determined, please specify one with -o fqdn=FQDN option!\n";
+            return("unknown");
+        }
+    }
+    
+    ## Return the short hostname
+    unless ($fqdn) {
+        $hostname =~ s/\..*//;
+        return(lc($hostname));
+    }
+    
+    ## STEP 2: Determine the FQDN
+    
+    ## First, if we already have one return it.
+    if ($hostname =~ /\w\.\w/) { return(lc($hostname)); }
+    
+    ## Next try using 
+    eval { $fqdn = (gethostbyname($hostname))[0]; };
+    if ($fqdn) { return(lc($fqdn)); }
+    return(lc($hostname));
+}
+
+
+
+
+
+
+
+
+###############################################################################################
+##  Function:    printmsg (string $message, int $level)
+##
+##  Description: Handles all messages - printing them to the screen only if the messages
+##               $level is >= the global debug level.  If $conf{'logFile'} is defined it
+##               will also log the message to that file.
+##
+##  Input:       $message          A message to be printed, logged, etc.
+##               $level            The debug level of the message. If
+##                                 not defined 0 will be assumed.  0 is
+##                                 considered a normal message, 1 and 
+##                                 higher is considered a debug message.
+##  
+##  Output:      Prints to STDOUT
+##
+##  Assumptions: $conf{'hostname'} should be the name of the computer we're running on.
+##               $conf{'stdout'} should be set to 1 if you want to print to stdout
+##               $conf{'logFile'} should be a full path to a log file if you want that
+##               $conf{'debug'} should be an integer between 0 and 10.
+##
+##  Example:     printmsg("WARNING: We believe in generic error messages... NOT!", 0);
+###############################################################################################
+sub printmsg {
+    ## Assign incoming parameters to variables
+    my ( $message, $level ) = @_;
+    
+    ## Make sure input is sane
+    $level = 0 if (!defined($level));
+    $message =~ s/\s+$//sgo;
+    $message =~ s/\r?\n/, /sgo;
+    
+    ## Continue only if the debug level of the program is >= message debug level.
+    if ($conf{'debug'} >= $level) {
+        
+        ## Get the date in the format: Dec  3 11:14:04
+        my ($sec, $min, $hour, $mday, $mon) = localtime();
+        $mon = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')[$mon];
+        my $date = sprintf("%s %02d %02d:%02d:%02d", $mon, $mday, $hour, $min, $sec);
+        
+        ## Print to STDOUT always if debugging is enabled, or if conf{stdout} is true.
+        if ( ($conf{'debug'} >= 1) or ($conf{'stdout'} == 1) ) {
+            print "$date $conf{'hostname'} $conf{'programName'}\[$$\]: $message\n";
+        }
+        
+        ## Print to the log file if $conf{'logging'} is true
+        if ($conf{'logFile'}) {
+            if (openLogFile($conf{'logFile'})) { $conf{'logFile'} = ""; printmsg("ERROR => Opening the file [$conf{'logFile'}] for appending returned the error: $!", 1); }
+            print LOGFILE "$date $conf{'hostname'} $conf{'programName'}\[$$\]: $message\n";
+        }
+        
+    }
+    
+    ## Return 0 errors
+    return(0);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+###############################################################################################
+## FUNCTION:
+##   openLogFile ( $filename )
+## 
+## 
+## DESCRIPTION: 
+##   Opens the file $filename and attaches it to the filehandle "LOGFILE".  Returns 0 on success
+##   and non-zero on failure.  Error codes are listed below, and the error message gets set in
+##   global variable $!.
+##   
+##   
+## Example:
+##   openFile ("/var/log/sendEmail.log");
+##
+###############################################################################################
+sub openLogFile {
+    ## Get the incoming filename
+    my $filename = $_[0];
+    
+    ## Make sure our file exists, and if the file doesn't exist then create it
+    if ( ! -f $filename ) {
+        print STDERR "NOTICE: The log file [$filename] does not exist.  Creating it now with mode [0600].\n" if ($conf{'stdout'});
+        open (LOGFILE, ">>$filename");
+        close LOGFILE;
+        chmod (0600, $filename);
+    }
+    
+    ## Now open the file and attach it to a filehandle
+    open (LOGFILE,">>$filename") or return (1);
+    
+    ## Put the file into non-buffering mode
+    select LOGFILE;
+    $| = 1;
+    select STDOUT;
+    
+    ## Return success
+    return(0);
+}
+
+
+
+
+
+
+
+
+###############################################################################################
+##  Function:    read_file (string $filename)
+##  
+##  Description: Reads the contents of a file and returns a two part array:
+##               ($status, $file-contents)
+##               $status is 0 on success, non-zero on error.
+##               
+##  Example:     ($status, $file) = read_file("/etc/passwd");
+###############################################################################################
+sub read_file {
+    my ( $filename ) = @_;
+    
+    ## If the value specified is a file, load the file's contents
+    if ( (-e $filename and -r $filename) ) {
+        my $FILE;
+        if(!open($FILE, ' ' . $filename)) {
+            return((1, ""));
+        }
+        my $file = '';
+        while (<$FILE>) {
+            $file .= $_;
+        }
+        ## Strip an ending \r\n
+        $file =~ s/\r?\n$//os;
+    }
+    return((1, ""));
+}
+
+
+
+
+
+
+
+
+
+###############################################################################################
+##  Function:    quit (string $message, int $errorLevel)
+##  
+##  Description: Exits the program, optionally printing $message.  It 
+##               returns an exit error level of $errorLevel to the 
+##               system  (0 means no errors, and is assumed if empty.)
+##
+##  Example:     quit("Exiting program normally", 0);
+###############################################################################################
+sub quit {
+    my ( $message, $errorLevel ) = @_;
+    $errorLevel = 0 if (!defined($errorLevel));
+    
+    ## Print exit message
+    if ($message) { 
+        printmsg($message, 0);
+    }
+    
+    ## Exit
+    exit($errorLevel);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+###############################################################################################
+## Function:    help ()
+##
+## Description: For all those newbies ;)
+##              Prints a help message and exits the program.
+##
+###############################################################################################
+sub help {
+exit(1) if (!$conf{'stdout'});
+print <<EOM;
+
+${colorBold}$conf{'programName'}-$conf{'version'} by $conf{'authorName'} <$conf{'authorEmail'}>${colorNoBold}
+
+Synopsis:  $conf{'programName'} -f ADDRESS [options]
+
+  ${colorRed}Required:${colorNormal}
+    -f ADDRESS                from (sender) email address
+    * At least one recipient required via -t, -cc, or -bcc
+    * Message body required via -m, STDIN, or -o message-file=FILE
+
+  ${colorGreen}Common:${colorNormal}
+    -t ADDRESS [ADDR ...]     to email address(es)
+    -u SUBJECT                message subject
+    -m MESSAGE                message body
+    -s SERVER[:PORT]          smtp mail relay, default is $conf{'server'}:$conf{'port'}
+
+  ${colorGreen}Optional:${colorNormal}
+    -a   FILE [FILE ...]      file attachment(s)
+    -cc  ADDRESS [ADDR ...]   cc  email address(es)
+    -bcc ADDRESS [ADDR ...]   bcc email address(es)
+    -xu  USERNAME             username for SMTP authentication
+    -xp  PASSWORD             password for SMTP authentication
+
+  ${colorGreen}Paranormal:${colorNormal}
+    -b BINDADDR[:PORT]        local host bind address
+    -l LOGFILE                log to the specified file
+    -v                        verbosity, use multiple times for greater effect
+    -q                        be quiet (i.e. no STDOUT output)
+    -o NAME=VALUE             advanced options, for details try: --help misc
+        -o message-content-type=<auto|text|html>
+        -o message-file=FILE         -o message-format=raw
+        -o message-header=HEADER     -o message-charset=CHARSET
+        -o reply-to=ADDRESS          -o timeout=SECONDS
+        -o username=USERNAME         -o password=PASSWORD
+        -o tls=<auto|yes|no>         -o fqdn=FQDN
+
+
+  ${colorGreen}Help:${colorNormal}
+    --help                    the helpful overview you're reading now
+    --help addressing         explain addressing and related options
+    --help message            explain message body input and related options
+    --help networking         explain -s, -b, etc
+    --help output             explain logging and other output options
+    --help misc               explain -o options, TLS, SMTP auth, and more
+
+EOM
+exit(1);
+}
+
+
+
+
+
+
+
+
+
+###############################################################################################
+## Function:    helpTopic ($topic)
+##
+## Description: For all those newbies ;) 
+##              Prints a help message and exits the program.
+## 
+###############################################################################################
+sub helpTopic {
+    exit(1) if (!$conf{'stdout'});
+    my ($topic) = @_;
+
+    CASE: {
+
+
+
+
+## ADDRESSING
+        ($topic eq 'addressing') && do {
+            print <<EOM;
+
+${colorBold}ADDRESSING DOCUMENTATION${colorNormal}
+
+${colorGreen}Addressing Options${colorNormal}
+Options related to addressing:
+    -f   ADDRESS
+    -t   ADDRESS [ADDRESS ...]
+    -cc  ADDRESS [ADDRESS ...]
+    -bcc ADDRESS [ADDRESS ...]
+    -o   reply-to=ADDRESS
+    
+-f ADDRESS
+    This required option specifies who the email is from, I.E. the sender's
+    email address.
+    
+-t ADDRESS [ADDRESS ...]
+    This option specifies the primary recipient(s).  At least one recipient
+    address must be specified via the -t, -cc. or -bcc options.
+
+-cc ADDRESS [ADDRESS ...]
+    This option specifies the "carbon copy" recipient(s).  At least one 
+    recipient address must be specified via the -t, -cc. or -bcc options.
+
+-bcc ADDRESS [ADDRESS ...]
+    This option specifies the "blind carbon copy" recipient(s).  At least
+    one recipient address must be specified via the -t, -cc. or -bcc options.
+
+-o reply-to=ADDRESS
+    This option specifies that an optional "Reply-To" address should be
+    written in the email's headers.
+    
+
+${colorGreen}Email Address Syntax${colorNormal}
+Email addresses may be specified in one of two ways:
+    Full Name:     "John Doe <john.doe\@gmail.com>"
+    Just Address:  "john.doe\@gmail.com"
+
+The "Full Name" method is useful if you want a name, rather than a plain
+email address, to be displayed in the recipient's From, To, or Cc fields
+when they view the message.
+    
+
+${colorGreen}Multiple Recipients${colorNormal}
+The -t, -cc, and -bcc options each accept multiple addresses.  They may be
+specified by separating them by either a white space, comma, or semi-colon
+separated list.  You may also specify the -t, -cc, and -bcc options multiple
+times, each occurance will append the new recipients to the respective list.
+
+Examples:
+(I used "-t" in these examples, but it can be "-cc" or "-bcc" as well)
+
+  * Space separated list:
+    -t jane.doe\@yahoo.com "John Doe <john.doe\@gmail.com>"
+    
+  * Semi-colon separated list:
+    -t "jane.doe\@yahoo.com; John Doe <john.doe\@gmail.com>"
+ 
+  * Comma separated list:
+    -t "jane.doe\@yahoo.com, John Doe <john.doe\@gmail.com>"
+  
+  * Multiple -t, -cc, or -bcc options:
+    -t "jane.doe\@yahoo.com" -t "John Doe <john.doe\@gmail.com>"
+  
+
+EOM
+            last CASE;
+        };
+
+
+
+
+
+
+## MESSAGE
+        ($topic eq 'message') && do {
+            print <<EOM;
+
+${colorBold}MESSAGE DOCUMENTATION${colorNormal}
+
+${colorGreen}Message Options${colorNormal}
+Options related to the email message body:
+    -u  SUBJECT
+    -m  MESSAGE
+    -o  message-file=FILE
+    -o  message-content-type=<auto|text|html>
+    -o  message-header=EMAIL HEADER
+    -o  message-charset=CHARSET
+    -o  message-format=raw
+    
+-u SUBJECT
+    This option allows you to specify the subject for your email message.
+    It is not required (anymore) that the subject be quoted, although it 
+    is recommended.  The subject will be read until an argument starting
+    with a hyphen (-) is found.  
+    Examples:
+      -u "Contact information while on vacation"
+      -u New Microsoft vulnerability discovered
+
+-m MESSAGE
+    This option is one of three methods that allow you to specify the message
+    body for your email.  The message may be specified on the command line
+    with this -m option, read from a file with the -o message-file=FILE
+    option, or read from STDIN if neither of these options are present.
+    
+    It is not required (anymore) that the message be quoted, although it is
+    recommended.  The message will be read until an argument starting with a
+    hyphen (-) is found.
+    Examples:
+      -m "See you in South Beach, Hawaii.  -Todd"
+      -m Please ensure that you upgrade your systems right away
+    
+    Multi-line message bodies may be specified with the -m option by putting
+    a "\\n" into the message.  Example:
+      -m "This is line 1.\\nAnd this is line 2."
+    
+    HTML messages are supported, simply begin your message with "<html>" and
+    sendEmail will properly label the mime header so MUAs properly render
+    the message.  It is currently not possible without "-o message-format=raw"
+    to send a message with both text and html parts with sendEmail.
+
+-o message-file=FILE
+    This option is one of three methods that allow you to specify the message
+    body for your email.  To use this option simply specify a text file
+    containing the body of your email message. Examples:
+      -o message-file=/root/message.txt
+      -o message-file="C:\\Program Files\\output.txt"
+
+-o message-content-type=<auto|text|html>
+    This option allows you to specify the content-type of the email. If your
+    email message is an html message but is being displayed as a text message
+    just add "-o message-content-type=html" to the command line to force it
+    to display as an html message. This actually just changes the Content-Type:
+    header. Advanced users will be happy to know that if you specify anything
+    other than the three options listed above it will use that as the vaule
+    for the Content-Type header.
+
+-o message-header=EMAIL HEADER
+    This option allows you to specify additional email headers to be included.
+    To add more than one message header simply use this option on the command
+    line more than once.  If you specify a message header that sendEmail would
+    normally generate the one you specified will be used in it's place.
+    Do not use this unless you know what you are doing!
+    Example:
+      To scare a Microsoft Outlook user you may want to try this:
+      -o message-header="X-Message-Flag: Message contains illegal content"
+    Example:
+      To request a read-receipt try this:
+      -o message-header="Disposition-Notification-To: <user\@domain.com>"
+    Example:
+      To set the message priority try this:
+      -o message-header="X-Priority: 1"
+      Priority reference: 1=highest, 2=high, 3=normal, 4=low, 5=lowest
+
+-o message-charset=CHARSET
+    This option allows you to specify the character-set for the message body.
+    The default is iso-8859-1.
+
+-o message-format=raw
+    This option instructs sendEmail to assume the message (specified with -m,
+    read from STDIN, or read from the file specified in -o message-file=FILE)
+    is already a *complete* email message.  SendEmail will not generate any
+    headers and will transmit the message as-is to the remote SMTP server.
+    Due to the nature of this option the following command line options will
+    be ignored when this one is used:
+      -u SUBJECT
+      -o message-header=EMAIL HEADER
+      -o message-charset=CHARSET
+      -a ATTACHMENT
+      
+
+${colorGreen}The Message Body${colorNormal}
+The email message body may be specified in one of three ways:
+ 1) Via the -m MESSAGE command line option.
+    Example:
+      -m "This is the message body"
+      
+ 2) By putting the message body in a file and using the -o message-file=FILE
+    command line option.
+    Example:
+      -o message-file=/root/message.txt
+      
+ 3) By piping the message body to sendEmail when nither of the above command
+    line options were specified.
+    Example:
+      grep "ERROR" /var/log/messages | sendEmail -t you\@domain.com ...
+
+If the message body begins with "<html>" then the message will be treated as
+an HTML message and the MIME headers will be written so that a HTML capable
+email client will display the message in it's HTML form.
+Any of the above methods may be used with the -o message-format=raw option 
+to deliver an already complete email message.
+
+
+EOM
+            last CASE;
+        };
+
+
+
+
+
+
+## MISC
+        ($topic eq 'misc') && do {
+            print <<EOM;
+
+${colorBold}MISC DOCUMENTATION${colorNormal}
+
+${colorGreen}Misc Options${colorNormal}
+Options that don't fit anywhere else:
+    -a   ATTACHMENT [ATTACHMENT ...]
+    -xu  USERNAME
+    -xp  PASSWORD
+    -o   username=USERNAME
+    -o   password=PASSWORD
+    -o   tls=<auto|yes|no>
+    -o   timeout=SECONDS
+    -o   fqdn=FQDN
+
+-a   ATTACHMENT [ATTACHMENT ...]
+    This option allows you to attach any number of files to your email message.
+    To specify more than one attachment, simply separate each filename with a
+    space.  Example: -a file1.txt file2.txt file3.txt
+
+-xu  USERNAME
+    Alias for -o username=USERNAME
+
+-xp  PASSWORD
+    Alias for -o password=PASSWORD
+
+-o   username=USERNAME (synonym for -xu)
+    These options allow specification of a username to be used with SMTP
+    servers that require authentication.  If a username is specified but a
+    password is not, you will be prompted to enter one at runtime.
+
+-o   password=PASSWORD (synonym for -xp)
+    These options allow specification of a password to be used with SMTP
+    servers that require authentication.  If a username is specified but a
+    password is not, you will be prompted to enter one at runtime. 
+
+-o   tls=<auto|yes|no>
+    This option allows you to specify if TLS (SSL for SMTP) should be enabled
+    or disabled.  The default, auto, will use TLS automatically if your perl
+    installation has the IO::Socket::SSL and Net::SSLeay modules available,
+    and if the remote SMTP server supports TLS.  To require TLS for message
+    delivery set this to yes.  To disable TLS support set this to no.  A debug
+    level of one or higher will reveal details about the status of TLS.
+
+-o   timeout=SECONDS
+    This option sets the timeout value in seconds used for all network reads,
+    writes, and a few other things.
+
+-o   fqdn=FQDN
+    This option sets the Fully Qualified Domain Name used during the initial
+    SMTP greeting.  Normally this is automatically detected, but in case you
+    need to manually set it for some reason or get a warning about detection
+    failing, you can use this to override the default.
+
+
+EOM
+            last CASE;
+        };
+
+
+
+
+
+
+## NETWORKING
+        ($topic eq 'networking') && do {
+            print <<EOM;
+
+${colorBold}NETWORKING DOCUMENTATION${colorNormal}
+
+${colorGreen}Networking Options${colorNormal}
+Options related to networking:
+    -s   SERVER[:PORT]
+    -b   BINDADDR[:PORT]
+    -o   tls=<auto|yes|no>
+    -o   timeout=SECONDS
+
+-s SERVER[:PORT]
+    This option allows you to specify the SMTP server sendEmail should
+    connect to to deliver your email message to.  If this option is not
+    specified sendEmail will try to connect to localhost:25 to deliver
+    the message.  THIS IS MOST LIKELY NOT WHAT YOU WANT, AND WILL LIKELY
+    FAIL unless you have a email server (commonly known as an MTA) running
+    on your computer!
+    Typically you will need to specify your company or ISP's email server.
+    For example, if you use CableOne you will need to specify:
+       -s mail.cableone.net
+    If you have your own email server running on port 300 you would
+    probably use an option like this:
+       -s myserver.mydomain.com:300
+    If you're a GMail user try:
+       -s smtp.gmail.com:587 -xu me\@gmail.com -xp PASSWD
+
+-b BINDADDR[:PORT]
+    This option allows you to specify the local IP address (and optional
+    tcp port number) for sendEmail to bind to when connecting to the remote
+    SMTP server.  This useful for people who need to send an email from a
+    specific network interface or source address and are running sendEmail on
+    a firewall or other host with several network interfaces.
+
+-o   tls=<auto|yes|no>
+    This option allows you to specify if TLS (SSL for SMTP) should be enabled
+    or disabled.  The default, auto, will use TLS automatically if your perl
+    installation has the IO::Socket::SSL and Net::SSLeay modules available,
+    and if the remote SMTP server supports TLS.  To require TLS for message
+    delivery set this to yes.  To disable TLS support set this to no.  A debug
+    level of one or higher will reveal details about the status of TLS.
+
+-o timeout=SECONDS
+    This option sets the timeout value in seconds used for all network reads,
+    writes, and a few other things.
+
+    
+EOM
+            last CASE;
+        };
+
+
+
+
+
+
+## OUTPUT
+        ($topic eq 'output') && do {
+            print <<EOM;
+
+${colorBold}OUTPUT DOCUMENTATION${colorNormal}
+
+${colorGreen}Output Options${colorNormal}
+Options related to output:
+    -l LOGFILE
+    -v
+    -q
+
+-l LOGFILE
+    This option allows you to specify a log file to append to.  Every message
+    that is displayed to STDOUT is also written to the log file.  This may be
+    used in conjunction with -q and -v.
+
+-q
+    This option tells sendEmail to disable printing to STDOUT.  In other
+    words nothing will be printed to the console.  This does not affect the
+    behavior of the -l or -v options.
+
+-v
+    This option allows you to increase the debug level of sendEmail.  You may
+    either use this option more than once, or specify more than one v at a
+    time to obtain a debug level higher than one.  Examples:
+        Specifies a debug level of 1:  -v
+        Specifies a debug level of 2:  -vv
+        Specifies a debug level of 2:  -v -v
+    A debug level of one is recommended when doing any sort of debugging.  
+    At that level you will see the entire SMTP transaction (except the
+    body of the email message), and hints will be displayed for most
+    warnings and errors.  The highest debug level is three.
+
+
+EOM
+            last CASE;
+        };
+
+        ## Unknown option selected!
+        quit("ERROR => The help topic specified is not valid!", 1);
+    };
+
+exit(1);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#############################
+##                          ##
+##      MAIN PROGRAM         ##
+##                          ##
+#############################
+
+
+## Initialize
+initialize();
+
+## Process Command Line
+processCommandLine();
+$conf{'alarm'} = $opt{'timeout'};
+
+## Abort program after $conf{'alarm'} seconds to avoid infinite hangs
+alarm($conf{'alarm'}) if ($^O !~ /win/i);  ## alarm() doesn't work in win32
+
+
+
+
+###################################################
+##  Read $message from STDIN if -m was not used  ##
+###################################################
+
+if (!($message)) {
+    ## Read message body from a file specified with -o message-file=
+    if ($opt{'message-file'}) {
+        if (! -e $opt{'message-file'}) {
+            printmsg("ERROR => Message body file specified [$opt{'message-file'}] does not exist!", 0);
+            printmsg("HINT => 1) check spelling of your file; 2) fully qualify the path; 3) doubble quote it", 1);
+            quit("", 1);
+        }
+        if (! -r $opt{'message-file'}) {
+            printmsg("ERROR => Message body file specified can not be read due to restricted permissions!", 0);
+            printmsg("HINT => Check permissions on file specified to ensure it can be read", 1);
+            quit("", 1);
+        }
+        if (!open(MFILE, "< " . $opt{'message-file'})) {
+            printmsg("ERROR => Error opening message body file [$opt{'message-file'}]: $!", 0);
+            quit("", 1);
+        }
+        while (<MFILE>) {
+            $message .= $_;
+        }
+        close(MFILE);
+    }
+    
+    ## Read message body from STDIN
+    else {
+        alarm($conf{'alarm'}) if ($^O !~ /win/i);  ## alarm() doesn't work in win32
+        if ($conf{'stdout'}) {
+            print "Reading message body from STDIN because the '-m' option was not used.\n";
+            print "If you are manually typing in a message:\n";
+            print "  - First line must be received within $conf{'alarm'} seconds.\n" if ($^O !~ /win/i);
+            print "  - End manual input with a CTRL-D on its own line.\n\n" if ($^O !~ /win/i);
+            print "  - End manual input with a CTRL-Z on its own line.\n\n" if ($^O =~ /win/i);
+        }
+        while (<STDIN>) {                 ## Read STDIN into $message
+            $message .= $_;
+            alarm(0) if ($^O !~ /win/i);  ## Disable the alarm since at least one line was received
+        }
+        printmsg("Message input complete.", 0);
+    }
+}
+
+## Replace bare LF's with CRLF's (\012 should always have \015 with it)
+$message =~ s/(\015)?(\012|$)/\015\012/g;
+
+## Replace bare CR's with CRLF's (\015 should always have \012 with it)
+$message =~ s/(\015)(\012|$)?/\015\012/g;
+
+## Check message for bare periods and encode them
+$message =~ s/(^|$CRLF)(\.{1})($CRLF|$)/$1.$2$3/g;
+
+## Get the current date for the email header
+my ($sec,$min,$hour,$mday,$mon,$year,$day) = gmtime();
+$year += 1900; $mon = return_month($mon); $day = return_day($day);
+my $date = sprintf("%s, %s %s %d %.2d:%.2d:%.2d %s",$day, $mday, $mon, $year, $hour, $min, $sec, $conf{'timezone'});
+
+
+
+
+##################################
+##  Connect to the SMTP server  ##
+##################################
+printmsg("DEBUG => Connecting to $conf{'server'}:$conf{'port'}", 1);
+$SIG{'ALRM'} = sub { 
+    printmsg("ERROR => Timeout while connecting to $conf{'server'}:$conf{'port'}  There was no response after $conf{'alarm'} seconds.", 0); 
+    printmsg("HINT => Try specifying a different mail relay with the -s option.", 1);
+    quit("", 1);
+};
+alarm($conf{'alarm'}) if ($^O !~ /win/i);  ## alarm() doesn't work in win32;
+$SERVER = IO::Socket::INET->new( PeerAddr  => $conf{'server'},
+                                 PeerPort  => $conf{'port'},
+                                 LocalAddr => $conf{'bindaddr'},
+                                 Proto     => 'tcp',
+                                 Autoflush => 1,
+                                 timeout   => $conf{'alarm'},
+);
+alarm(0) if ($^O !~ /win/i);  ## alarm() doesn't work in win32;
+
+## Make sure we got connected
+if ( (!$SERVER) or (!$SERVER->opened()) ) {
+    printmsg("ERROR => Connection attempt to $conf{'server'}:$conf{'port'} failed: $@", 0);
+    printmsg("HINT => Try specifying a different mail relay with the -s option.", 1);
+    quit("", 1);
+}
+
+## Save our IP address for later
+$conf{'ip'} = $SERVER->sockhost();
+printmsg("DEBUG => My IP address is: $conf{'ip'}", 1);
+
+
+
+
+
+
+
+#########################
+##  Do the SMTP Dance  ##
+#########################
+
+## Read initial greeting to make sure we're talking to a live SMTP server
+if (SMTPchat()) { quit($conf{'error'}, 1); }
+
+## We're about to use $opt{'fqdn'}, make sure it isn't empty
+if (!$opt{'fqdn'}) {
+    ## Ok, that means we couldn't get a hostname, how about using the IP address for the HELO instead
+    $opt{'fqdn'} = "[" . $conf{'ip'} . "]";
+}
+
+## EHLO
+if (SMTPchat('EHLO ' . $opt{'fqdn'}))   {
+    printmsg($conf{'error'}, 0);
+    printmsg("NOTICE => EHLO command failed, attempting HELO instead");
+    if (SMTPchat('HELO ' . $opt{'fqdn'})) { quit($conf{'error'}, 1); }
+    if ( $opt{'username'} and $opt{'password'} ) {
+        printmsg("WARNING => The mail server does not support SMTP authentication!", 0);
+    }
+}
+else {
+    
+    ## Determin if the server supports TLS
+    if ($conf{'SMTPchat_response'} =~ /STARTTLS/) {
+        $conf{'tls_server'} = 1;
+        printmsg("DEBUG => The remote SMTP server supports TLS :)", 2);
+    }
+    else {
+        $conf{'tls_server'} = 0;
+        printmsg("DEBUG => The remote SMTP server does NOT support TLS :(", 2);
+    }
+    
+    ## Start TLS if possible
+    if ($conf{'tls_server'} == 1 and $conf{'tls_client'} == 1 and $opt{'tls'} =~ /^(yes|auto)$/) {
+        printmsg("DEBUG => Starting TLS", 2);
+        if (SMTPchat('STARTTLS')) { quit($conf{'error'}, 1); }
+        if (! IO::Socket::SSL->start_SSL($SERVER, SSL_version => 'SSLv3 TLSv1')) {
+            quit("ERROR => TLS setup failed: " . IO::Socket::SSL::errstr(), 1);
+        }
+        printmsg("DEBUG => TLS: Using cipher: ". $SERVER->get_cipher(), 3);
+        printmsg("DEBUG => TLS session initialized :)", 1);
+        
+        ## Restart our SMTP session
+        if (SMTPchat('EHLO ' . $opt{'fqdn'})) { quit($conf{'error'}, 1); }
+    }
+    elsif ($opt{'tls'} eq 'yes' and $conf{'tls_server'} == 0) {
+        quit("ERROR => TLS not possible! Remote SMTP server, $conf{'server'},  does not support it.", 1);
+    }
+    
+    
+    ## Do SMTP Auth if required
+    if ( $opt{'username'} and $opt{'password'} ) {
+        if ($conf{'SMTPchat_response'} !~ /AUTH\s/) {
+            printmsg("NOTICE => Authentication not supported by the remote SMTP server!", 0);
+        }
+        else {
+            my $auth_succeeded = 0;
+            my $mutual_method = 0;
+            
+            # ## SASL CRAM-MD5 authentication method
+            # if ($conf{'SMTPchat_response'} =~ /\bCRAM-MD5\b/i) {
+            #     printmsg("DEBUG => SMTP-AUTH: Using CRAM-MD5 authentication method", 1);
+            #     if (SMTPchat('AUTH CRAM-MD5')) { quit($conf{'error'}, 1); }
+            #     
+            #     ## FIXME!!
+            #     
+            #     printmsg("DEBUG => User authentication was successful", 1);
+            # }
+            
+            ## SASL LOGIN authentication method
+            if ($auth_succeeded == 0 and $conf{'SMTPchat_response'} =~ /\bLOGIN\b/i) {
+                $mutual_method = 1;
+                printmsg("DEBUG => SMTP-AUTH: Using LOGIN authentication method", 1);
+                if (!SMTPchat('AUTH LOGIN')) {
+                    if (!SMTPchat(base64_encode($opt{'username'}))) {
+                        if (!SMTPchat(base64_encode($opt{'password'}))) {
+                            $auth_succeeded = 1;
+                            printmsg("DEBUG => User authentication was successful (Method: LOGIN)", 1);
+                        }
+                    }
+                }
+                if ($auth_succeeded == 0) {
+                    printmsg("DEBUG => SMTP-AUTH: LOGIN authenticaion failed.", 1);
+                }
+            }
+            
+            ## SASL PLAIN authentication method
+            if ($auth_succeeded == 0 and $conf{'SMTPchat_response'} =~ /\bPLAIN\b/i) {
+                $mutual_method = 1;
+                printmsg("DEBUG => SMTP-AUTH: Using PLAIN authentication method", 1);
+                if (SMTPchat('AUTH PLAIN ' . base64_encode("$opt{'username'}\0$opt{'username'}\0$opt{'password'}"))) {
+                    printmsg("DEBUG => SMTP-AUTH: PLAIN authenticaion failed.", 1);
+                }
+                else {
+                    $auth_succeeded = 1;
+                    printmsg("DEBUG => User authentication was successful (Method: PLAIN)", 1);
+                }
+            }
+            
+            ## If none of the authentication methods supported by sendEmail were supported by the server, let the user know
+            if ($mutual_method == 0) {
+                printmsg("WARNING => SMTP-AUTH: No mutually supported authentication methods available", 0);
+            }
+            
+            ## If we didn't get authenticated, log an error message and exit
+            if ($auth_succeeded == 0) {
+                quit("ERROR => ERROR => SMTP-AUTH: Authentication to $conf{'server'}:$conf{'port'} failed.", 1);
+            }
+        }
+    }
+}
+
+## MAIL FROM
+if (SMTPchat('MAIL FROM:<' .(returnAddressParts($from))[1]. '>')) { quit($conf{'error'}, 1); }
+
+## RCPT TO
+my $oneRcptAccepted = 0;
+foreach my $rcpt (@to, @cc, @bcc) {
+    my ($name, $address) = returnAddressParts($rcpt);
+    if (SMTPchat('RCPT TO:<' . $address . '>')) {
+        printmsg("WARNING => The recipient <$address> was rejected by the mail server, error follows:", 0);
+        $conf{'error'} =~ s/^ERROR/WARNING/o;
+        printmsg($conf{'error'}, 0);
+    }
+    elsif ($oneRcptAccepted == 0) {
+        $oneRcptAccepted = 1;
+    }
+}
+## If no recipients were accepted we need to exit with an error.
+if ($oneRcptAccepted == 0) {
+    quit("ERROR => Exiting. No recipients were accepted for delivery by the mail server.", 1);
+}
+
+## DATA
+if (SMTPchat('DATA')) { quit($conf{'error'}, 1); }
+
+
+###############################
+##  Build and send the body  ##
+###############################
+printmsg("INFO => Sending message body",1);
+
+## If the message-format is raw just send the message as-is.
+if ($opt{'message-format'} =~ /^raw$/i) {
+    print $SERVER $message;
+}
+
+## If the message-format isn't raw, then build and send the message,
+else {
+    
+    ## Message-ID: <MessageID>
+    if ($opt{'message-header'} !~ /^Message-ID:/iom) {
+        $header .= 'Message-ID: <' . $conf{'Message-ID'} . '@' . $conf{'hostname'} . '>' . $CRLF;
+    }
+    
+    ## From: "Name" <address at domain.com> (the pointless test below is just to keep scoping correct)
+    if ($from and $opt{'message-header'} !~ /^From:/iom) {
+        my ($name, $address) = returnAddressParts($from);
+        $header .= 'From: "' . $name . '" <' . $address . '>' . $CRLF;
+    }
+    
+    ## Reply-To: 
+    if ($opt{'reply-to'} and $opt{'message-header'} !~ /^Reply-To:/iom) {
+        my ($name, $address) = returnAddressParts($opt{'reply-to'});
+        $header .= 'Reply-To: "' . $name . '" <' . $address . '>' . $CRLF;
+    }
+    
+    ## To: "Name" <address at domain.com>
+    if ($opt{'message-header'} =~ /^To:/iom) {
+        ## The user put the To: header in via -o message-header - dont do anything
+    }
+    elsif (scalar(@to) > 0) {
+        $header .= "To:";
+        for (my $a = 0; $a < scalar(@to); $a++) {
+            my $msg = "";
+            
+            my ($name, $address) = returnAddressParts($to[$a]);
+            $msg = " \"$name\" <$address>";
+            
+            ## If we're not on the last address add a comma to the end of the line.
+            if (($a + 1) != scalar(@to)) {
+                $msg .= ",";
+            }
+            
+            $header .= $msg . $CRLF;
+        }
+    }
+    ## We always want a To: line so if the only recipients were bcc'd they don't see who it was sent to
+    else {
+        $header .= "To: \"Undisclosed Recipients\" <>$CRLF";
+    }
+    
+    if (scalar(@cc) > 0 and $opt{'message-header'} !~ /^Cc:/iom) {
+        $header .= "Cc:";
+        for (my $a = 0; $a < scalar(@cc); $a++) {
+            my $msg = "";
+            
+            my ($name, $address) = returnAddressParts($cc[$a]);
+            $msg = " \"$name\" <$address>";
+            
+            ## If we're not on the last address add a comma to the end of the line.
+            if (($a + 1) != scalar(@cc)) {
+                $msg .= ",";
+            }
+            
+            $header .= $msg . $CRLF;
+        }
+    }
+    
+    if ($opt{'message-header'} !~ /^Subject:/iom) {
+        $header .= 'Subject: ' . $subject . $CRLF;                   ## Subject
+    }
+    if ($opt{'message-header'} !~ /^Date:/iom) {
+        $header .= 'Date: ' . $date . $CRLF;                         ## Date
+    }
+    if ($opt{'message-header'} !~ /^X-Mailer:/iom) {
+        $header .= 'X-Mailer: sendEmail-'.$conf{'version'}.$CRLF;    ## X-Mailer
+    }
+    ## I wonder if I should put this in by default?
+    # if ($opt{'message-header'} !~ /^X-Originating-IP:/iom) {
+    #     $header .= 'X-Originating-IP: ['.$conf{'ip'}.']'.$CRLF;      ## X-Originating-IP
+    # }
+    
+    ## Encode all messages with MIME.
+    if ($opt{'message-header'} !~ /^MIME-Version:/iom) {
+        $header .=  "MIME-Version: 1.0$CRLF";
+    }
+    if ($opt{'message-header'} !~ /^Content-Type:/iom) {
+        my $content_type = 'multipart/mixed';
+        if (scalar(@attachments) == 0) { $content_type = 'multipart/related'; }
+        $header .= "Content-Type: $content_type; boundary=\"$conf{'delimiter'}\"$CRLF";
+    }
+    
+    ## Send additional message header line(s) if specified
+    if ($opt{'message-header'}) {
+        $header .= $opt{'message-header'};
+    }
+    
+    ## Send the message header to the server
+    print $SERVER $header . $CRLF;
+    
+    ## Start sending the message body to the server
+    print $SERVER "This is a multi-part message in MIME format. To properly display this message you need a MIME-Version 1.0 compliant Email program.$CRLF";
+    print $SERVER "$CRLF";
+    
+    
+    ## Send message body
+    print $SERVER "--$conf{'delimiter'}$CRLF";
+    ## Send a message content-type header:
+    ## If the message contains HTML...
+    if ($opt{'message-content-type'} eq 'html' or ($opt{'message-content-type'} eq 'auto' and $message =~ /^\s*(<HTML|<!DOCTYPE)/i) ) {
+        printmsg("Setting content-type: text/html", 1);
+        print $SERVER "Content-Type: text/html;$CRLF";
+    }
+    ## Otherwise assume it's plain text...
+    elsif ($opt{'message-content-type'} eq 'text' or $opt{'message-content-type'} eq 'auto') {
+        printmsg("Setting content-type: text/plain", 1);
+        print $SERVER "Content-Type: text/plain;$CRLF";
+    }
+    ## If they've specified their own content-type string...
+    else {
+        printmsg("Setting custom content-type: ".$opt{'message-content-type'}, 1);
+        print $SERVER "Content-Type: ".$opt{'message-content-type'}.";$CRLF";
+    }
+    print $SERVER "        charset=\"" . $opt{'message-charset'} . "\"$CRLF";
+    print $SERVER "Content-Transfer-Encoding: 7bit$CRLF";
+    print $SERVER $CRLF . $message;
+    
+    
+    
+    ## Send Attachemnts
+    if (scalar(@attachments) > 0) {
+        ## Disable the alarm so people on modems can send big attachments
+        alarm(0) if ($^O !~ /win/i);  ## alarm() doesn't work in win32
+        
+        ## Send the attachments
+        foreach my $filename (@attachments) {
+            ## This is check 2, we already checked this above, but just in case...
+            if ( ! -f $filename ) {
+                printmsg("ERROR => The file [$filename] doesn't exist!  Email will be sent, but without that attachment.", 0);
+            }
+            elsif ( ! -r $filename ) {
+                printmsg("ERROR => Couldn't open the file [$filename] for reading: $!   Email will be sent, but without that attachment.", 0);
+            }
+            else {
+                printmsg("DEBUG => Sending the attachment [$filename]", 1);
+                send_attachment($filename);
+            }
+        }
+    }
+    
+    
+    ## End the mime encoded message
+    print $SERVER "$CRLF--$conf{'delimiter'}--$CRLF";  
+}
+
+
+## Tell the server we are done sending the email
+print $SERVER "$CRLF.$CRLF";
+if (SMTPchat()) { quit($conf{'error'}, 1); }
+
+
+
+####################
+#  We are done!!!  #
+####################
+
+## Disconnect from the server (don't SMTPchat(), it breaks when using TLS)
+print $SERVER "QUIT$CRLF";
+close $SERVER;
+
+
+
+
+
+
+#######################################
+##  Generate exit message/log entry  ##
+#######################################
+
+if ($conf{'debug'} or $conf{'logging'}) {
+    printmsg("Generating a detailed exit message", 3);
+    
+    ## Put the message together
+    my $output = "Email was sent successfully!  From: <" . (returnAddressParts($from))[1] . "> ";
+    
+    if (scalar(@to) > 0) {
+        $output .= "To: ";
+        for ($a = 0; $a < scalar(@to); $a++) {
+            $output .= "<" . (returnAddressParts($to[$a]))[1] . "> ";
+        }
+    }
+    if (scalar(@cc) > 0) {
+        $output .= "Cc: ";
+        for ($a = 0; $a < scalar(@cc); $a++) {
+            $output .= "<" . (returnAddressParts($cc[$a]))[1] . "> ";
+        }
+    }
+    if (scalar(@bcc) > 0) {
+        $output .= "Bcc: ";
+        for ($a = 0; $a < scalar(@bcc); $a++) {
+            $output .= "<" . (returnAddressParts($bcc[$a]))[1] . "> ";
+        }
+    }
+    $output .= "Subject: [$subject] " if ($subject);
+    if (scalar(@attachments_names) > 0) { 
+        $output .= "Attachment(s): ";
+        foreach(@attachments_names) {
+            $output .= "[$_] ";
+        }
+    }
+    $output .= "Server: [$conf{'server'}:$conf{'port'}]";
+    
+    
+######################
+#  Exit the program  #
+######################
+    
+    ## Print / Log the detailed message
+    quit($output, 0);
+}
+else {
+    ## Or the standard message
+    quit("Email was sent successfully!", 0);
+}
+


More information about the Libreoffice-commits mailing list