1#!/bin/sh
2# Sign files and upload them.
3
4scriptversion=2018-05-19.18; # UTC
5
6# Copyright (C) 2004-2020 Free Software Foundation, Inc.
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation; either version 3, or (at your option)
11# any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program.  If not, see <https://www.gnu.org/licenses/>.
20
21# Originally written by Alexandre Duret-Lutz <adl@gnu.org>.
22# The master copy of this file is maintained in the gnulib Git repository.
23# Please send bug reports and feature requests to bug-gnulib@gnu.org.
24
25set -e
26
27GPG=gpg
28# Choose the proper version of gpg, so as to avoid a
29# "gpg-agent is not available in this session" error
30# when gpg-agent is version 3 but gpg is still version 1.
31# FIXME-2020: remove, once all major distros ship gpg version 3 as /usr/bin/gpg
32gpg_agent_version=`(gpg-agent --version) 2>/dev/null | sed -e '2,$d' -e 's/^[^0-9]*//'`
33case "$gpg_agent_version" in
34  2.*)
35    gpg_version=`(gpg --version) 2>/dev/null | sed -e '2,$d' -e 's/^[^0-9]*//'`
36    case "$gpg_version" in
37      1.*)
38        if (type gpg2) >/dev/null 2>/dev/null; then
39          # gpg2 is present.
40          GPG=gpg2
41        else
42          # gpg2 is missing. Ubuntu users should install the package 'gnupg2'.
43          echo "WARNING: Using 'gpg', which is too old. You should install 'gpg2'." 1>&2
44        fi
45        ;;
46    esac
47    ;;
48esac
49
50GPG="${GPG} --batch --no-tty"
51conffile=.gnuploadrc
52to=
53dry_run=false
54replace=
55symlink_files=
56delete_files=
57delete_symlinks=
58collect_var=
59dbg=
60nl='
61'
62
63usage="Usage: $0 [OPTION]... [CMD] FILE... [[CMD] FILE...]
64
65Sign all FILES, and process them at the destinations specified with --to.
66If CMD is not given, it defaults to uploading.  See examples below.
67
68Commands:
69  --delete                 delete FILES from destination
70  --symlink                create symbolic links
71  --rmsymlink              remove symbolic links
72  --                       treat the remaining arguments as files to upload
73
74Options:
75  --to DEST                specify a destination DEST for FILES
76                           (multiple --to options are allowed)
77  --user NAME              sign with key NAME
78  --replace                allow replacements of existing files
79  --symlink-regex[=EXPR]   use sed script EXPR to compute symbolic link names
80  -n, --dry-run            do nothing, show what would have been done
81                           (including the constructed directive file)
82  --version                output version information and exit
83  -h, --help               print this help text and exit
84
85If --symlink-regex is given without EXPR, then the link target name
86is created by replacing the version information with '-latest', e.g.:
87  foo-1.3.4.tar.gz -> foo-latest.tar.gz
88
89Recognized destinations are:
90  alpha.gnu.org:DIRECTORY
91  savannah.gnu.org:DIRECTORY
92  savannah.nongnu.org:DIRECTORY
93  ftp.gnu.org:DIRECTORY
94                           build directive files and upload files by FTP
95  download.gnu.org.ua:{alpha|ftp}/DIRECTORY
96                           build directive files and upload files by SFTP
97  [user@]host:DIRECTORY    upload files with scp
98
99Options and commands are applied in order.  If the file $conffile exists
100in the current working directory, its contents are prepended to the
101actual command line options.  Use this to keep your defaults.  Comments
102(#) and empty lines in $conffile are allowed.
103
104<https://www.gnu.org/prep/maintain/html_node/Automated-FTP-Uploads.html>
105gives some further background.
106
107Examples:
1081. Upload foobar-1.0.tar.gz to ftp.gnu.org:
109  gnupload --to ftp.gnu.org:foobar foobar-1.0.tar.gz
110
1112. Upload foobar-1.0.tar.gz and foobar-1.0.tar.xz to ftp.gnu.org:
112  gnupload --to ftp.gnu.org:foobar foobar-1.0.tar.gz foobar-1.0.tar.xz
113
1143. Same as above, and also create symbolic links to foobar-latest.tar.*:
115  gnupload --to ftp.gnu.org:foobar \\
116           --symlink-regex \\
117           foobar-1.0.tar.gz foobar-1.0.tar.xz
118
1194. Create a symbolic link foobar-latest.tar.gz -> foobar-1.0.tar.gz
120   and likewise for the corresponding .sig file:
121  gnupload --to ftp.gnu.org:foobar \\
122           --symlink foobar-1.0.tar.gz     foobar-latest.tar.gz \\
123                     foobar-1.0.tar.gz.sig foobar-latest.tar.gz.sig
124  or (equivalent):
125  gnupload --to ftp.gnu.org:foobar \\
126           --symlink foobar-1.0.tar.gz     foobar-latest.tar.gz \\
127           --symlink foobar-1.0.tar.gz.sig foobar-latest.tar.gz.sig
128
1295. Upload foobar-0.9.90.tar.gz to two sites:
130  gnupload --to alpha.gnu.org:foobar \\
131           --to sources.redhat.com:~ftp/pub/foobar \\
132           foobar-0.9.90.tar.gz
133
1346. Delete oopsbar-0.9.91.tar.gz and upload foobar-0.9.91.tar.gz
135   (the -- terminates the list of files to delete):
136  gnupload --to alpha.gnu.org:foobar \\
137           --to sources.redhat.com:~ftp/pub/foobar \\
138           --delete oopsbar-0.9.91.tar.gz \\
139           -- foobar-0.9.91.tar.gz
140
141gnupload executes a program ncftpput to do the transfers; if you don't
142happen to have an ncftp package installed, the ncftpput-ftp script in
143the build-aux/ directory of the gnulib package
144(https://savannah.gnu.org/projects/gnulib) may serve as a replacement.
145
146Send patches and bug reports to <bug-gnulib@gnu.org>."
147
148# Read local configuration file
149if test -r "$conffile"; then
150  echo "$0: Reading configuration file $conffile"
151  conf=`sed 's/#.*$//;/^$/d' "$conffile" | tr "\015$nl" '  '`
152  eval set x "$conf \"\$@\""
153  shift
154fi
155
156while test -n "$1"; do
157  case $1 in
158  -*)
159    collect_var=
160    case $1 in
161    -h | --help)
162      echo "$usage"
163      exit $?
164      ;;
165    --to)
166      if test -z "$2"; then
167        echo "$0: Missing argument for --to" 1>&2
168        exit 1
169      elif echo "$2" | grep 'ftp-upload\.gnu\.org' >/dev/null; then
170        echo "$0: Use ftp.gnu.org:PKGNAME or alpha.gnu.org:PKGNAME" >&2
171        echo "$0: for the destination, not ftp-upload.gnu.org (which" >&2
172        echo "$0:  is used for direct ftp uploads, not with gnupload)." >&2
173        echo "$0: See --help and its examples if need be." >&2
174        exit 1
175      else
176        to="$to $2"
177        shift
178      fi
179      ;;
180    --user)
181      if test -z "$2"; then
182        echo "$0: Missing argument for --user" 1>&2
183        exit 1
184      else
185        GPG="$GPG --local-user $2"
186        shift
187      fi
188      ;;
189    --delete)
190      collect_var=delete_files
191      ;;
192    --replace)
193      replace="replace: true"
194      ;;
195    --rmsymlink)
196      collect_var=delete_symlinks
197      ;;
198    --symlink-regex=*)
199      symlink_expr=`expr "$1" : '[^=]*=\(.*\)'`
200      ;;
201    --symlink-regex)
202      symlink_expr='s|-[0-9][0-9\.]*\(-[0-9][0-9]*\)\{0,1\}\.|-latest.|'
203      ;;
204    --symlink)
205      collect_var=symlink_files
206      ;;
207    -n | --dry-run)
208      dry_run=:
209      ;;
210    --version)
211      echo "gnupload $scriptversion"
212      exit $?
213      ;;
214    --)
215      shift
216      break
217      ;;
218    -*)
219      echo "$0: Unknown option '$1', try '$0 --help'" 1>&2
220      exit 1
221      ;;
222    esac
223    ;;
224  *)
225    if test -z "$collect_var"; then
226      break
227    else
228      eval "$collect_var=\"\$$collect_var $1\""
229    fi
230    ;;
231  esac
232  shift
233done
234
235dprint()
236{
237  echo "Running $* ..."
238}
239
240if $dry_run; then
241  dbg=dprint
242fi
243
244if test -z "$to"; then
245  echo "$0: Missing destination sites" >&2
246  exit 1
247fi
248
249if test -n "$symlink_files"; then
250  x=`echo "$symlink_files" | sed 's/[^ ]//g;s/  //g'`
251  if test -n "$x"; then
252    echo "$0: Odd number of symlink arguments" >&2
253    exit 1
254  fi
255fi
256
257if test $# = 0; then
258  if test -z "${symlink_files}${delete_files}${delete_symlinks}"; then
259    echo "$0: No file to upload" 1>&2
260    exit 1
261  fi
262else
263  # Make sure all files exist.  We don't want to ask
264  # for the passphrase if the script will fail.
265  for file
266  do
267    if test ! -f $file; then
268      echo "$0: Cannot find '$file'" 1>&2
269      exit 1
270    elif test -n "$symlink_expr"; then
271      linkname=`echo $file | sed "$symlink_expr"`
272      if test -z "$linkname"; then
273        echo "$0: symlink expression produces empty results" >&2
274        exit 1
275      elif test "$linkname" = $file; then
276        echo "$0: symlink expression does not alter file name" >&2
277        exit 1
278      fi
279    fi
280  done
281fi
282
283# Make sure passphrase is not exported in the environment.
284unset passphrase
285unset passphrase_fd_0
286GNUPGHOME=${GNUPGHOME:-$HOME/.gnupg}
287
288# Reset PATH to be sure that echo is a built-in.  We will later use
289# 'echo $passphrase' to output the passphrase, so it is important that
290# it is a built-in (third-party programs tend to appear in 'ps'
291# listings with their arguments...).
292# Remember this script runs with 'set -e', so if echo is not built-in
293# it will exit now.
294if $dry_run || grep -q "^use-agent" $GNUPGHOME/gpg.conf; then :; else
295  PATH=/empty echo -n "Enter GPG passphrase: "
296  stty -echo
297  read -r passphrase
298  stty echo
299  echo
300  passphrase_fd_0="--passphrase-fd 0"
301fi
302
303if test $# -ne 0; then
304  for file
305  do
306    echo "Signing $file ..."
307    rm -f $file.sig
308    echo "$passphrase" | $dbg $GPG $passphrase_fd_0 -ba -o $file.sig $file
309  done
310fi
311
312
313# mkdirective DESTDIR BASE FILE STMT
314# Arguments: See upload, below
315mkdirective ()
316{
317  stmt="$4"
318  if test -n "$3"; then
319    stmt="
320filename: $3$stmt"
321  fi
322
323  cat >${2}.directive<<EOF
324version: 1.2
325directory: $1
326comment: gnupload v. $scriptversion$stmt
327EOF
328  if $dry_run; then
329    echo "File ${2}.directive:"
330    cat ${2}.directive
331    echo "File ${2}.directive:" | sed 's/./-/g'
332  fi
333}
334
335mksymlink ()
336{
337  while test $# -ne 0
338  do
339    echo "symlink: $1 $2"
340    shift
341    shift
342  done
343}
344
345# upload DEST DESTDIR BASE FILE STMT FILES
346# Arguments:
347#  DEST     Destination site;
348#  DESTDIR  Destination directory;
349#  BASE     Base name for the directive file;
350#  FILE     Name of the file to distribute (may be empty);
351#  STMT     Additional statements for the directive file;
352#  FILES    List of files to upload.
353upload ()
354{
355  dest=$1
356  destdir=$2
357  base=$3
358  file=$4
359  stmt=$5
360  files=$6
361
362  rm -f $base.directive $base.directive.asc
363  case $dest in
364    alpha.gnu.org:*)
365      mkdirective "$destdir" "$base" "$file" "$stmt"
366      echo "$passphrase" | $dbg $GPG $passphrase_fd_0 --clearsign $base.directive
367      $dbg ncftpput ftp-upload.gnu.org /incoming/alpha $files $base.directive.asc
368      ;;
369    ftp.gnu.org:*)
370      mkdirective "$destdir" "$base" "$file" "$stmt"
371      echo "$passphrase" | $dbg $GPG $passphrase_fd_0 --clearsign $base.directive
372      $dbg ncftpput ftp-upload.gnu.org /incoming/ftp $files $base.directive.asc
373      ;;
374    savannah.gnu.org:*)
375      if test -z "$files"; then
376        echo "$0: warning: standalone directives not applicable for $dest" >&2
377      fi
378      $dbg ncftpput savannah.gnu.org /incoming/savannah/$destdir $files
379      ;;
380    savannah.nongnu.org:*)
381      if test -z "$files"; then
382        echo "$0: warning: standalone directives not applicable for $dest" >&2
383      fi
384      $dbg ncftpput savannah.nongnu.org /incoming/savannah/$destdir $files
385      ;;
386    download.gnu.org.ua:alpha/*|download.gnu.org.ua:ftp/*)
387      destdir_p1=`echo "$destdir" | sed 's,^[^/]*/,,'`
388      destdir_topdir=`echo "$destdir" | sed 's,/.*,,'`
389      mkdirective "$destdir_p1" "$base" "$file" "$stmt"
390      echo "$passphrase" | $dbg $GPG $passphrase_fd_0 --clearsign $base.directive
391      for f in $files $base.directive.asc
392      do
393        echo put $f
394      done | $dbg sftp -b - puszcza.gnu.org.ua:/incoming/$destdir_topdir
395      ;;
396    /*)
397      dest_host=`echo "$dest" | sed 's,:.*,,'`
398      mkdirective "$destdir" "$base" "$file" "$stmt"
399      echo "$passphrase" | $dbg $GPG $passphrase_fd_0 --clearsign $base.directive
400      $dbg cp $files $base.directive.asc $dest_host
401      ;;
402    *)
403      if test -z "$files"; then
404        echo "$0: warning: standalone directives not applicable for $dest" >&2
405      fi
406      $dbg scp $files $dest
407      ;;
408  esac
409  rm -f $base.directive $base.directive.asc
410}
411
412#####
413# Process any standalone directives
414stmt=
415if test -n "$symlink_files"; then
416  stmt="$stmt
417`mksymlink $symlink_files`"
418fi
419
420for file in $delete_files
421do
422  stmt="$stmt
423archive: $file"
424done
425
426for file in $delete_symlinks
427do
428  stmt="$stmt
429rmsymlink: $file"
430done
431
432if test -n "$stmt"; then
433  for dest in $to
434  do
435    destdir=`echo $dest | sed 's/[^:]*://'`
436    upload "$dest" "$destdir" "`hostname`-$$" "" "$stmt"
437  done
438fi
439
440# Process actual uploads
441for dest in $to
442do
443  for file
444  do
445    echo "Uploading $file to $dest ..."
446    stmt=
447    #
448    # allowing file replacement is all or nothing.
449    if test -n "$replace"; then stmt="$stmt
450$replace"
451    fi
452    #
453    files="$file $file.sig"
454    destdir=`echo $dest | sed 's/[^:]*://'`
455    if test -n "$symlink_expr"; then
456      linkname=`echo $file | sed "$symlink_expr"`
457      stmt="$stmt
458symlink: $file $linkname
459symlink: $file.sig $linkname.sig"
460    fi
461    upload "$dest" "$destdir" "$file" "$file" "$stmt" "$files"
462  done
463done
464
465exit 0
466
467# Local variables:
468# eval: (add-hook 'before-save-hook 'time-stamp)
469# time-stamp-start: "scriptversion="
470# time-stamp-format: "%:y-%02m-%02d.%02H"
471# time-stamp-time-zone: "UTC0"
472# time-stamp-end: "; # UTC"
473# End:
474