[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