123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612 |
- # Released under the terms of GPLv3 or at your option any later version.
- # No warranties.
- # Copyright Enrico Tassi <gares@fettunta.org>
-
- # Common stuff for smd-pull and smd-push
- # Convention:
- # - uppercase variables are global
- # - lowercase variables are local
- # - function arguments are documented assigning to local variable with
- # a decent name positional arguments
-
- ### Housekeeping ###
-
- __TOREMOVE=""
- __TOKILL=""
-
- atexit_rm() {
- local path="$1"
- __TOREMOVE="$__TOREMOVE $path"
- }
-
- atexit_kill() {
- local pid="$1"
- __TOKILL="$__TOKILL $pid"
- }
-
- gc_mktemp() {
- local tmp=`mktemp -q /tmp/smd.XXXXXXXXXX`
- atexit_rm $tmp
- RC="$tmp"
- }
-
- __cleanup() {
- rm -f $__TOREMOVE
- for p in $__TOKILL; do
- kill $p 2>/dev/null || true
- done
- }
-
- trap __cleanup "EXIT"
-
- ### Variables setup and sanity checks ###
-
- assert_executable() {
- if type $1 >/dev/null; then
- :
- else
- echo $1 not found, please install it or fix the paths
- echo PATH=$PATH
- echo type $1: `type $1`
- exit 1
- fi
- }
-
- init() {
- if [ `echo $PREFIX | cut -c -1` = "@" ]; then
- SMDSERVER=./smd-server
- SMDCLIENT=./smd-client
- MDDIFF=./mddiff
- # in development mode we assume that on the remote host
- # the software is installed such that binaries are in $PATH
- REMOTESMDSERVER=smd-server
- REMOTESMDCLIENT=smd-client
- else
- SMDSERVER=$PREFIX/bin/smd-server
- SMDCLIENT=$PREFIX/bin/smd-client
- MDDIFF=$PREFIX/bin/mddiff
- REMOTESMDSERVER="$SMDSERVER"
- REMOTESMDCLIENT="$SMDCLIENT"
- fi
-
- H=$HOME
- CONFDIR=$H/.smd
- LOCKFILE=$CONFDIR/lock
- SHOWTAGS=0
- VERBOSE=0
- DRYRUN=0
- TEMPLATE_ONLY=0
- CONFFILE=""
- WORKAREA=$CONFDIR/workarea
- REPNAME=default
- CHILDSARGS=
- REMOTEEXCLUDE=
- LOCALEXCLUDE=
- SMDCLIENTOPTS=
- SMDSERVEROPTS=
- RENAMEDB=.smd/rename-db
-
- # default values for the configuration file
- DEBUG=false
-
- # external programs
- SSH="@SSH@"
- if [ `echo "$SSH" | cut -c -1` = "@" ]; then
- SSH=ssh
- echo "`basename $0` not installed, assuming secure shell client is $SSH"
- fi
- SED="@SED@"
- if [ `echo "$SED" | cut -c -1` = "@" ]; then
- SED=sed
- echo "`basename $0` not installed, assuming stream editor is $SED"
- fi
-
- # sanity checks for required binaries
- assert_executable $SED
- assert_executable $SSH
- assert_executable $MDDIFF
- assert_executable $SMDSERVER
- assert_executable $SMDCLIENT
-
- # setup of confdir
- $MDDIFF --mkdir-p $CONFDIR/
- $MDDIFF --mkdir-p $CONFDIR/log
- $MDDIFF --mkdir-p $CONFDIR/fifo
- $MDDIFF --mkdir-p $CONFDIR/hooks
- cat > $CONFDIR/hooks/README <<-EOT
- From version 0.9.14, smd-push and smd-pull can run user defined
- hooks before and after doing their job.
-
- Sample hooks are available in the source tarball under sample-hooks/.
- The invocation of hooks is documented in the main README file.
- EOT
- $MDDIFF --mkdir-p $CONFDIR/hooks/pre-pull.d
- $MDDIFF --mkdir-p $CONFDIR/hooks/pre-push.d
- $MDDIFF --mkdir-p $CONFDIR/hooks/post-pull.d
- $MDDIFF --mkdir-p $CONFDIR/hooks/post-push.d
- CRUFT=`find $CONFDIR/workarea ! -type d -a ! -type l 2> /dev/null || true`
- if [ ! -z "$CRUFT" ]; then
- echo "Some files are left in $CONFDIR/workarea"
- echo "This is an internal error. Please report this inconvenience"
- echo "and examine the content of these files, they may be of value."
- echo
- echo $CRUFT
- exit 1
- fi
- rm -rf $CONFDIR/workarea
- $MDDIFF --mkdir-p $CONFDIR/workarea
- }
-
- is_absolute() {
- case "$1" in
- /*) return 0 ;;
- *) return 1 ;;
- esac
- }
-
- resolve_translator(){
- local T=${1%% *}
- if [ ! -z "$T" ]; then
- if is_absolute "$1"; then
- RC="$1"
- elif `type "$HOME/$T" >/dev/null 2>&1`; then
- RC="$HOME/$1"
- elif `type "$T" >/dev/null 2>&1`; then
- RC="$1"
- else
- echo "Unable to find the given translator: $T"
- echo "It is not an absolute path"
- echo "It is not in \$HOME=$HOME"
- echo "It is not in \$PATH=$PATH"
- if [ $showtags = 1 ]; then
- echo "$REPNAME: $localprog@$localhost: TAGS: error::context(conf) probable-cause(translator-not-found) human-intervention(necessary)"
- fi
- exit 1
- fi
- else
- RC="cat"
- fi
- }
-
- setup_workarea(){
- gc_mktemp
- local TMP_FIND="$RC"
- gc_mktemp
- local TMP_FIND_ERR="$RC"
- $MDDIFF $LOCALEXCLUDE -l $MAILBOX_LOCAL >$TMP_FIND 2>$TMP_FIND_ERR
- if [ ! $? -eq 0 ]; then
- echo "$MDDIFF gave an error while scanning $MAILBOX_LOCAL:"
- cat $TMP_FIND_ERR
- exit 1
- fi
-
- local FIFO_MKDIR="$CONFDIR/fifo/mkdir"
- [ -p "$FIFO_MKDIR" ] || $MDDIFF --mkfifo "$FIFO_MKDIR"
- local FIFO_MKDIRB="$CONFDIR/fifo/mkdir-back"
- [ -p "$FIFO_MKDIRB" ] || $MDDIFF --mkfifo "$FIFO_MKDIRB"
- $MDDIFF -s "$FIFO_MKDIR" > "$FIFO_MKDIRB" &
- local DIR_MAKER=$!
- atexit_kill $DIR_MAKER
- exec 6<$FIFO_MKDIRB
- exec 9>$FIFO_MKDIR
-
- gc_mktemp
- local TMP_T="$RC"
- local ERR=0
- eval "$TRANSLATOR_LR" <$TMP_FIND >$TMP_T || ERR=$?
- exec 7<$TMP_FIND
- exec 8<$TMP_T
-
- while read M <&7 && read TM <&8; do
- if [ $ERR -eq 1 -o "$TM" = "ERROR" ]; then
- echo "Error: translating $M"
- cat $TMP_T
- if [ $showtags = 1 ]; then
- echo "$REPNAME: $localprog@localhost: TAGS: error::context(workarea) probable-cause(bad-translator) human-intervention(necessary)"
- fi
- exit 1
- fi
- if [ $VERBOSE -eq 1 -a "$M" != "$TM" ]; then
- echo "translating:" $M "->" $TM
- fi
- if echo "$TM" | grep -q -e '\.\.'; then
- echo "Error: the translator returned a path containing .."
- exit 1
- fi
- echo "$HOME/$M" 1>&9
- echo ".smd/workarea/$TM" 1>&9
- read R <&6
- if [ "$R" != "OK" ]; then
- echo "$REPNAME: $localprog@localhost: TAGS: error::context(workarea) probable-cause(mddiff-s-error) human-intervention(necessary)"
- exit 1
- fi
- done
-
- exec 6<&-
- exec 7<&-
- exec 8<&-
- exec 9>&-
-
- wait $DIR_MAKER || ERR=1
- if [ $ERR -eq 1 ]; then
- echo "Error: creating symlinks"
- if [ $showtags = 1 ]; then
- echo "$REPNAME: $localprog@localhost: TAGS: error::context(workarea) probable-cause(fail-create-symlink) human-intervention(necessary)"
- fi
- exit 1
- fi
- }
-
- ### Command line argument parsing ###
-
- parse_args() {
- for arg in "$@"; do
- case $arg in
- -v|--verbose)
- VERBOSE=1
- SHOWTAGS=1
- CHILDSARGS="$CHILDSARGS -v"
- ;;
- -s|--show-tags)
- SHOWTAGS=1
- ;;
- -t|--template-only)
- TEMPLATE_ONLY=1
- ;;
- -d|--dry-run)
- DRYRUN=1
- CHILDSARGS="$CHILDSARGS -d"
- VERBOSE=1
- SHOWTAGS=1
- ;;
- -n|--no-delete)
- SMDSERVEROPTS="$SMDSERVEROPTS -n"
- ;;
- -*)
- cat <<-EOT
- usage: `basename $0` [options] [endpoint]
- Refer to the man page for `basename $0`
- EOT
- exit 1
- ;;
- *)
- REPNAME="$arg"
- ;;
-
- esac
- done
-
- CONFFILE=$CONFDIR/config.$REPNAME
- }
-
- ### Confdir setup ###
-
- myfakessh() {
- shift
- cd
- "$@"
- }
-
- read_conffile() {
- # backward compatibility code
- if [ ! -f $CONFFILE ] && \
- [ "$REPNAME" = "default" ] && \
- [ -f $CONFDIR/config ]; then
- # we import the old conffile
- echo "From version 0.9.4, configuration files are named"
- echo "$CONFDIR/config.\$FOO, where FOO is an optional argument"
- echo "to smd-pull/smd-push. The default value of FOO is 'default'."
- echo "I'm renaming $CONFDIR/config to $CONFFILE."
- mv $CONFDIR/config $CONFFILE
- fi
-
- if [ ! -f $CONFFILE ]; then
- cat > $CONFFILE <<- EOT
- # No config file found, this is a template. You want to edit it.
-
- # Host name to be used with ssh as the server (use ~/.ssh/config
- # for extra options). smd-pull will pull from this host, smd-push
- # will push to this host and use it as the id of the remote mailbox.
- #
- # You should create an alias within your ~/.ssh/config like the
- # following on:
- #
- # Host smd-server-foo
- # Compression yes
- # Hostname your.real.server.name
- # User you
- #
- SERVERNAME=smd-server-$REPNAME
-
- # Host name to be used as the client.
- # smd-pull will use this just as an id for the client. If you
- # plan to sync with multiple endpoints, you must use a different
- # client id for any of them, thus a pair localhostname-remotehostname
- # should be used
- #
- CLIENTNAME=`hostname`-$REPNAME
-
- # The mailbox to sync, in case the path is the same on both hosts.
- # The path MUST be relative to the home directory, use a symlink if
- # the mailbox is not rooted there. If these paths contain spaces,
- # they must be substituted with %20.
- #
- MAILBOX="Mail/"
-
- # Use different paths on the local and remote hosts
- #
- # Local and remote mailbox may differ in name, as well as their
- # sub directory/folder structure. In that case a translator must be
- # provided. A translator is a program that takes in input, as it
- # first and only argument, a directory name (ending in /cur or /new
- # or /tmp) and prints on stdout its translation. Refer to the
- # smd-config (5) manpage for more infos.
- #
- # MAILBOX_LOCAL="Mail/"
- # MAILBOX_REMOTE="OtherMail/"
- # TRANSLATOR_RL=command
- # TRANSLATOR_LR=command
-
- # Ignore some paths
- #
- # To exclude some paths from the synchronization you can specify
- # a space separated list of glob(7) expressions. Spaces in these
- # expressions must be replaced with %20.
- #
- # EXCLUDE="Mail/spam Mail/trash Mail/with%20spaces"
- #
- # If the local and remote mailbox differ in name or their
- # sub directory/folder structure you can specify different
- # excluded paths for the two endpoints.
- #
- # EXCLUDE_LOCAL="Mail/spam Mail/trash"
- # EXCLUDE_REMOTE="OtherMail/with%20spaces"
-
- # Local synchronization
- #
- # If the local and remote mailboxes are on the same host
- # the following option must be added to the configuration file:
- #
- # SMDCLIENTOPTS=-l
-
- # Avoid deletions
- #
- # In some cases, usually unidirectional synchronizations, one may want
- # to not propagate deletions. E.g. one keeps a slim working mailbox but
- # pushes to a backup mailbox to save every email.
- #
- # SMDSERVEROPTS=-n
-
- # If the local and remote mailboxes are on the same host
- # the following option must be added to the configuration file:
- #
- # SMDCLIENTOPTS=-l
-
- # Log client to server and server to client communication.
- #
- # This is useful only for debugging, since all network traffic
- # is dumped, including transmitted mails.
- #
- # DEBUG=true
- EOT
- echo No config file found: created a default one
- echo Please edit it: $CONFFILE
- if [ "$TEMPLATE_ONLY" = 1 ]; then
- exit 0
- else
- exit 1
- fi
- fi
-
- if [ "$TEMPLATE_ONLY" = 1 ]; then
- exit 0
- fi
-
- . $CONFFILE
-
- # sanityze
- MAILBOX="${MAILBOX%%/}"
- MAILBOX_LOCAL="${MAILBOX_LOCAL%%/}"
- MAILBOX_REMOTE="${MAILBOX_REMOTE%%/}"
-
- # default exclude
- if [ -z "$EXCLUDE_LOCAL" ]; then
- EXCLUDE_LOCAL="$EXCLUDE"
- fi
- if [ -z "$EXCLUDE_REMOTE" ]; then
- EXCLUDE_REMOTE="$EXCLUDE"
- fi
- for e in $EXCLUDE_LOCAL; do
- LOCALEXCLUDE="$LOCALEXCLUDE --exclude $e"
- done
- for e in $EXCLUDE_REMOTE; do
- REMOTEEXCLUDE="$REMOTEEXCLUDE --exclude $e"
- done
-
- # check for local synchronization
- if [ "$SERVERNAME" = "localhost" ]; then
- if echo "$SMDCLIENTOPTS" | grep -q -v -e "-l"; then
- echo "SERVERNAME is localhost but SMDCLIENTOPTS is not set."
- echo "Local synchronizations must set SMDCLIENTOPTS."
- else
- # no need to ssh
- SSH=myfakessh
- fi
- fi
- }
-
- ### Only one instance at a time please ###
-
- check_lockfile() {
- # could be relaxed to non related mailboxes/enpoints, but the test is
- # not straightforward
- if [ -f $LOCKFILE ]; then
- if ps -p `cat $LOCKFILE` 2> /dev/null | grep -E 'smd-(push|pull)'; then
- echo Already running.
- echo If this is not the case, remove $LOCKFILE by hand.
- echo "any: smd-pushpull@localhost: TAGS: error::context(locking) probable-cause(another-instance-is-running) human-intervention(necessary) suggested-actions(run(kill `cat $LOCKFILE`) run(rm $LOCKFILE))"
- exit 1
- else
- echo Found lockfile of a dead instance. Ignored.
- fi
- fi
-
- echo $$ > $LOCKFILE
- atexit_rm $LOCKFILE
- }
-
- ### Create all the needed pipes ###
-
- setup_plumbing() {
- CtL=$CONFDIR/fifo/c2l.$REPNAME
- LtC=$CONFDIR/fifo/l2c.$REPNAME
- LtS=$CONFDIR/fifo/l2s.$REPNAME
- StL=$CONFDIR/fifo/s2l.$REPNAME
- PRp=$CONFDIR/fifo/pr.$REPNAME
-
- [ -p $CtL ] || $MDDIFF --mkfifo $CtL
- [ -p $LtC ] || $MDDIFF --mkfifo $LtC
- [ -p $LtS ] || $MDDIFF --mkfifo $LtS
- [ -p $StL ] || $MDDIFF --mkfifo $StL
- [ -p $PRp ] || $MDDIFF --mkfifo $PRp
- }
-
- ### Logging ###
-
- mycat() {
- # like cat, but ignores arguments
- cat
- }
-
- myreporter() {
- tee -a $1 | grep --line-buffered ^PROGRESS: | sed 's?^PROGRESS: ??'
- }
-
- mysilentreporter() {
- cat >> $1
- }
-
- setup_logging() {
- CtS=$CONFDIR/log/c2s.$REPNAME.log
- StC=$CONFDIR/log/s2c.$REPNAME.log
- CL=$CONFDIR/log/client.$REPNAME.log
- SL=$CONFDIR/log/server.$REPNAME.log
-
- MITM=mycat
- if [ "$DEBUG" = "true" ]; then
- MITM=tee
- CHILDSARGS="$CHILDSARGS -v"
- fi
-
- PROGRESS_REPORTER=mysilentreporter
- if [ $VERBOSE -eq 1 ]; then
- PROGRESS_REPORTER=myreporter
- fi
- }
-
- setup_mailboxnames() {
- if [ -z "$MAILBOX" ]; then
- if [ -z "$MAILBOX_LOCAL" -o \
- -z "$MAILBOX_REMOTE" -o \
- -z "$TRANSLATOR_RL" -o \
- -z "$TRANSLATOR_LR" ]; then
- echo "The config file must define MAILBOX xor MAILBOX_LOCAL, MAILBOX_REMOTE, TRANSLATOR_LR and TRANSLATOR_RL"
- exit 1
- fi
- else
- if [ ! -z "$MAILBOX_LOCAL" -o ! -z "$MAILBOX_REMOTE" ]; then
- echo "The config file must define MAILBOX xor MAILBOX_LOCAL, MAILBOX_REMOTE, TRANSLATOR_LR and TRANSLATOR_RL"
- exit 1
- fi
- MAILBOX_LOCAL="$MAILBOX"
- MAILBOX_REMOTE="$MAILBOX"
- fi
-
- if echo "$MAILBOX_LOCAL $MAILBOX_REMOTE" | grep -q -e '\.\.'; then
- echo "Mailbox names can't contain .."
- exit 1
- fi
-
- resolve_translator "$TRANSLATOR_RL"
- TRANSLATOR_RL="$RC"
- resolve_translator "$TRANSLATOR_LR"
- TRANSLATOR_LR="$RC"
- }
-
- # this could be a system wide post-* hook
- report() {
- local exitcode="$1"
- local showtags="$2"
- local currcmd="$3"
- local inversecmd="$4"
- local localprog="$5"
- local remoteprog="$6"
- if [ $VERBOSE -eq 1 ]; then
- grep ^INFO: $SL | $SED 's/^INFO: //'
- grep ^INFO: $CL | $SED 's/^INFO: //'
- fi
- if [ $exitcode = 1 ]; then
- grep ^ERROR $SL \
- | $SED "s/^/$remoteprog: /" \
- | $SED "s/@@INVERSECOMMAND@@/$inversecmd/" \
- | $SED "s/@@ENDPOINT@@/$REPNAME/"
- grep ^ERROR $CL \
- | $SED "s/^/$localprog: /" \
- | $SED "s/@@INVERSECOMMAND@@/$inversecmd/" \
- | $SED "s/@@ENDPOINT@@/$REPNAME/"
- grep ^ssh: $SL \
- | $SED "s/^/$remoteprog: ERROR: /"
- fi
- if [ $showtags = 1 ]; then
- #echo "`date`: $currcmd $SERVERNAME" >> $CL
- grep ^TAGS $SL \
- | $SED "s/^/$REPNAME: $remoteprog@$SERVERNAME: /" \
- | $SED "s/@@INVERSECOMMAND@@/$inversecmd/" \
- | $SED "s/@@ENDPOINT@@/$REPNAME/"
- grep ^TAGS $CL \
- | $SED "s/^/$REPNAME: $localprog@localhost: /" \
- | $SED "s/@@INVERSECOMMAND@@/$inversecmd/" \
- | $SED "s/@@ENDPOINT@@/$REPNAME/"
- if [ `grep ^TAGS $SL|wc -l` = 0 ] && \
- [ `grep ^TAGS $CL|wc -l` = 0 ]; then
- # it may be that ssh failed to resolve the hostname
- # so we generate a fake tag for it
- cat $SL $CL
- echo "$REPNAME: $remoteprog@$SERVERNAME: TAGS: error::context(ssh) probable-cause(network) human-intervention(avoidable) suggested-actions(retry)"
- fi
- fi
- }
-
- ### Hooks ###
-
- run_hooks() {
- local dir="$1"
- local when="$2"
- local what="$3"
- local status="$4"
- for h in $dir/hooks/$when-$what.d/*; do
- if [ -x $h ]; then
- $h $when $what $REPNAME $status >> $CL 2>&1
- fi
- done
- }
-
- # running server and client with appropriate parameters
-
- run_local_client() {
- cd $WORKAREA; $SMDCLIENT $CHILDSARGS $SMDCLIENTOPTS -t "$TRANSLATOR_RL" $CLIENTNAME $MAILBOX_REMOTE
- }
-
- run_local_server() {
- cd $WORKAREA; $SMDSERVER $LOCALEXCLUDE $CHILDSARGS $SMDSERVEROPTS $CLIENTNAME $MAILBOX_REMOTE
- }
-
- run_remote_server() {
- $SSH $SERVERNAME $REMOTESMDSERVER $REMOTEEXCLUDE $CHILDSARGS $SMDSERVEROPTS $CLIENTNAME $MAILBOX_REMOTE
- }
-
- run_remote_client() {
- $SSH $SERVERNAME $REMOTESMDCLIENT $CHILDSARGS $SMDCLIENTOPTS $CLIENTNAME $MAILBOX_REMOTE
- }
-
- # vim:ts=4 filetype=sh:
|