1#!/bin/bash
2# Program to build and sign debian packages, and upload those to a public reprepro repository.
3# Copyright (c) 2015 Santiago Bassett <santiago.bassett@gmail.com>
4
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 3 of the License, or
8# (at your option) any later version.
9
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software Foundation,
17# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
18
19#
20# CONFIGURATION VARIABLES
21#
22
23ossec_version='2.8.2'
24source_file="ossec-hids-${ossec_version}.tar.gz"
25#packages=(ossec-hids ossec-hids-agent) # only options available
26packages=(ossec-hids ossec-hids-agent)
27
28# codenames=(sid jessie wheezy precise trusty utopic)
29codenames=(sid jessie wheezy precise trusty utopic)
30
31# For Debian use: sid, jessie or wheezy (hardcoded in update_changelog function)
32# For Ubuntu use: lucid, precise, trusty or utopic
33codenames_ubuntu=(precise trusty utopic)
34codenames_debian=(sid jessie wheezy)
35
36# architectures=(amd64 i386) only options available
37architectures=(amd64 i386)
38
39# GPG key
40signing_key='XXXX'
41signing_pass='XXXX'
42
43# Debian files
44debian_files_path="/home/ubuntu/debian_files"
45
46# Setting up logfile
47scriptpath=$( cd $(dirname $0) ; pwd -P )
48logfile=$scriptpath/ossec_packages.log
49
50
51#
52# Function to write to LOG_FILE
53#
54write_log()
55{
56  if [ ! -e "$logfile" ] ; then
57    touch "$logfile"
58  fi
59  while read text
60  do
61      local logtime=`date "+%Y-%m-%d %H:%M:%S"`
62      echo $logtime": $text" | tee -a $logfile;
63  done
64}
65
66
67#
68# Check if element is in an array
69# Arguments: element array
70#
71contains_element() {
72  local e
73  for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done
74  return 1
75}
76
77
78#
79# Show help function
80#
81show_help()
82{
83  echo "
84  This tool can be used to generate OSSEC packages for Ubuntu and Debian.
85
86  CONFIGURATION: The script is currently configured with the following variables:
87    * Packages: ${packages[*]}.
88    * Distributions: ${codenames[*]}.
89    * Architectures: ${architectures[*]}.
90    * OSSEC version: ${ossec_version}.
91    * Source file: ${source_file}.
92    * Signing key: ${signing_key}.
93
94  USAGE: Command line arguments available:
95    -h | --help     Displays this help.
96    -u | --update   Updates chroot environments.
97    -d | --download Downloads source file and prepares source directories.
98    -b | --build    Builds deb packages.
99    -s | --sync     Synchronizes with the apt-get repository.
100  "
101}
102
103
104#
105# Reads latest package version from changelog file
106# Argument: changelog_file
107#
108read_package_version()
109{
110  if [ ! -e "$1" ] ; then
111    echo "Error: Changelog file $1 does not exist" | write_log
112    exit 1
113  fi
114  local regex="^ossec-hids[A-Za-z-]* \([0-9]+.*[0-9]*.*[0-9]*-([0-9]+)[A-Za-z]*\)"
115  while read line
116  do
117    if [[ $line =~ $regex ]]; then
118      package_version="${BASH_REMATCH[1]}"
119      break
120    fi
121  done < $1
122  local check_regex='^[0-9]+$'
123  if ! [[ ${package_version} =~ ${check_regex} ]]; then
124    echo "Error: Package version could not be read from $1" | write_log
125    exit 1
126  fi
127}
128
129
130#
131# Updates changelog file with new codename, date and debdist.
132# Arguments: changelog_file codename
133#
134update_changelog()
135{
136  local changelog_file=$1
137  local changelog_file_tmp="${changelog_file}.tmp"
138  local codename=$2
139
140  if [ ! -e "$1" ] ; then
141    echo "Error: Changelog file $1 does not exist" | write_log
142    exit 1
143  fi
144
145  local check_codenames=( ${codenames_debian[*]} ${codenames_ubuntu[*]} )
146  if ! contains_element $codename ${check_codenames[*]} ; then
147    echo "Error: Codename $codename not contained in codenames for Debian or Ubuntu" | write_log
148    exit 1
149  fi
150
151  # For Debian
152  if [ $codename = "sid" ]; then
153    local debdist="unstable"
154  elif [ $codename = "jessie" ]; then
155    local debdist="testing"
156  elif [ $codename = "wheezy" ]; then
157    local debdist="stable"
158  fi
159
160  # For Ubuntu
161  if contains_element $codename ${codenames_ubuntu[*]} ; then
162    local debdist=$codename
163  fi
164
165  # Modifying file
166  local changelogtime=$(date -R)
167  local last_date_changed=0
168
169  local regex1="^(ossec-hids[A-Za-z-]* \([0-9]+.*[0-9]*.*[0-9]*-[0-9]+)[A-Za-z]*\)"
170  local regex2="( -- [[:alnum:]]*[^>]*>  )[[:alnum:]]*,"
171
172  if [ -f ${changelog_file_tmp} ]; then
173    rm -f ${changelog_file_tmp}
174  fi
175  touch ${changelog_file_tmp}
176
177  IFS='' #To preserve line leading whitespaces
178  while read line
179  do
180    if [[ $line =~ $regex1 ]]; then
181      line="${BASH_REMATCH[1]}$codename) $debdist; urgency=low"
182    fi
183    if [[ $line =~ $regex2 ]] && [ $last_date_changed -eq 0 ]; then
184      line="${BASH_REMATCH[1]}$changelogtime"
185      last_date_changed=1
186    fi
187    echo "$line" >> ${changelog_file_tmp}
188  done < ${changelog_file}
189
190  mv ${changelog_file_tmp} ${changelog_file}
191}
192
193
194#
195# Update chroot environments
196#
197update_chroots()
198{
199  for codename in ${codenames[@]}
200  do
201    for arch in ${architectures[@]}
202    do
203      echo "Updating chroot environment: ${codename}-${arch}" | write_log
204      if sudo DIST=$codename ARCH=$arch pbuilder update ; then
205        echo "Successfully updated chroot environment: ${codename}-${arch}" | write_log
206      else
207        echo "Error: Problem detected updating chroot environment: ${codename}-${arch}" | write_log
208      fi
209    done
210  done
211}
212
213
214#
215# Downloads packages and prepare source directories.
216# This is needed before building the packages.
217#
218download_source()
219{
220
221  # Checking that Debian files exist for this version
222  for package in ${packages[*]}
223  do
224    if [ ! -d ${debian_files_path}/${ossec_version}/$package/debian ]; then
225      echo "Error: Couldn't find debian files directory for $package, version ${ossec_version}" | write_log
226      exit 1
227    fi
228  done
229
230  # Downloading file
231  if wget -O $scriptpath/${source_file} -U ossec https://github.com/ossec/ossec-hids/archive/${ossec_version}.tar.gz ; then
232    echo "Successfully downloaded source file ${source_file} from ossec.net" | write_log
233  else
234    echo "Error: File ${source_file} was could not be downloaded" | write_log
235    exit 1
236  fi
237
238  # Uncompressing files
239  tmp_directory=$(echo ${source_file} | sed -e 's/.tar.gz$//')
240  if [ -d ${scriptpath}/${tmp_directory} ]; then
241    echo " + Deleting previous directory ${scriptpath}/${tmp_directory}" | write_log
242    sudo rm -rf ${scriptpath}/${tmp_directory}
243  fi
244  tar -xvzf ${scriptpath}/${source_file}
245  if [ ! -d ${scriptpath}/${tmp_directory} ]; then
246    echo "Error: Couldn't find uncompressed directory, named ${tmp_directory}" | write_log
247    exit 1
248  fi
249
250  # Organizing directories structure
251  for package in ${packages[*]}
252  do
253    if [ -d ${scriptpath}/$package ]; then
254      echo " + Deleting previous source directory ${scriptpath}/$package" | write_log
255      sudo rm -rf ${scriptpath}/$package
256    fi
257    mkdir $scriptpath/$package
258    cp -pr $scriptpath/${tmp_directory} $scriptpath/$package/$package-${ossec_version}
259    cp -p $scriptpath/${source_file} $scriptpath/$package/${package}_${ossec_version}.orig.tar.gz
260    cp -pr ${debian_files_path}/${ossec_version}/$package/debian $scriptpath/$package/${package}-${ossec_version}/debian
261  done
262  rm -rf $scriptpath/${tmp_directory}
263
264  echo "The packages directories for ${packages[*]} version ${ossec_version} have been successfully prepared." | write_log
265}
266
267
268#
269# Build packages
270#
271build_packages()
272{
273
274for package in ${packages[@]}
275do
276  for codename in ${codenames[@]}
277  do
278    for arch in ${architectures[@]}
279    do
280
281      echo "Building Debian package ${package} ${codename}-${arch}" | write_log
282
283      local source_path="$scriptpath/${package}/${package}-${ossec_version}"
284      local changelog_file="${source_path}/debian/changelog"
285      if [ ! -f ${changelog_file} ] ; then
286        echo "Error: Couldn't find changelog file for ${package}-${ossec_version}" | write_log
287        exit 1
288      fi
289
290      # Updating changelog file with new codename, date and debdist.
291      if update_changelog ${changelog_file} ${codename} ; then
292        echo " + Changelog file ${changelog_file} updated for $package ${codename}-${arch}" | write_log
293      else
294        echo "Error: Changelog file ${changelog_file} for $package ${codename}-${arch} could not be updated" | write_log
295        exit 1
296      fi
297
298      # Setting up global variable package_version, used for deb_file and changes_file
299      read_package_version ${changelog_file}
300      local deb_file="${package}_${ossec_version}-${package_version}${codename}_${arch}.deb"
301      local changes_file="${package}_${ossec_version}-${package_version}${codename}_${arch}.changes"
302      local dsc_file="${package}_${ossec_version}-${package_version}${codename}.dsc"
303      local results_dir="/var/cache/pbuilder/${codename}-${arch}/result/${package}"
304      local base_tgz="/var/cache/pbuilder/${codename}-${arch}-base.tgz"
305      local cache_dir="/var/cache/pbuilder/${codename}-${arch}/aptcache"
306
307      # Creating results directory if it does not exist
308      if [ ! -d ${results_dir} ]; then
309        sudo mkdir -p ${results_dir}
310      fi
311
312      # Building the package
313      cd ${source_path}
314      if sudo /usr/bin/pdebuild --use-pdebuild-internal --architecture ${arch} --buildresult ${results_dir} -- --basetgz \
315      ${base_tgz} --distribution ${codename} --architecture ${arch} --aptcache ${cache_dir} --override-config ; then
316        echo " + Successfully built Debian package ${package} ${codename}-${arch}" | write_log
317      else
318        echo "Error: Could not build package $package ${codename}-${arch}" | write_log
319        exit 1
320      fi
321
322      # Checking that resulting debian package exists
323      if [ ! -f ${results_dir}/${deb_file} ] ; then
324        echo "Error: Could not find ${results_dir}/${deb_file}" | write_log
325        exit 1
326      fi
327
328      # Checking that package has at least 50 files to confirm it has been built correctly
329      local files=$(sudo /usr/bin/dpkg --contents ${results_dir}/${deb_file} | wc -l)
330      if [ "${files}" -lt "50" ]; then
331        echo "Error: Package ${package} ${codename}-${arch} contains only ${files} files" | write_log
332        echo "Error: Check that the Debian package has been built correctly" | write_log
333        exit 1
334      else
335        echo " + Package ${results_dir}/${deb_file} ${codename}-${arch} contains ${files} files" | write_log
336      fi
337
338      # Signing Debian package
339      if [ ! -f "${results_dir}/${changes_file}" ] || [ ! -f "${results_dir}/${dsc_file}" ] ; then
340        echo "Error: Could not find dsc and changes file in ${results_dir}" | write_log
341        exit 1
342      fi
343      sudo /usr/bin/expect -c "
344        spawn sudo debsign --re-sign -k${signing_key} ${results_dir}/${changes_file}
345        expect -re \".*Enter passphrase:.*\"
346        send \"${signing_pass}\r\"
347        expect -re \".*Enter passphrase:.*\"
348        send \"${signing_pass}\r\"
349        expect -re \".*Successfully signed dsc and changes files.*\"
350      "
351      if [ $? -eq 0 ] ; then
352        echo " + Successfully signed Debian package ${changes_file} ${codename}-${arch}" | write_log
353      else
354        echo "Error: Could not sign Debian package ${changes_file} ${codename}-${arch}" | write_log
355        exit 1
356      fi
357
358      # Verifying signed changes and dsc files
359      if sudo gpg --verify "${results_dir}/${dsc_file}" && sudo gpg --verify "${results_dir}/${changes_file}" ; then
360        echo " + Successfully verified GPG signature for files ${dsc_file} and ${changes_file}" | write_log
361      else
362        echo "Error: Could not verify GPG signature for ${dsc_file} and ${changes_file}" | write_log
363        exit 1
364      fi
365
366      echo "Successfully built and signed Debian package ${package} ${codename}-${arch}" | write_log
367
368    done
369  done
370done
371}
372
373# Synchronizes with the external repository, uploading new packages and ubstituting old ones.
374sync_repository()
375{
376for package in ${packages[@]}
377do
378  for codename in ${codenames[@]}
379  do
380    for arch in ${architectures[@]}
381    do
382
383      # Reading package version from changelog file
384      local source_path="$scriptpath/${package}/${package}-${ossec_version}"
385      local changelog_file="${source_path}/debian/changelog"
386      if [ ! -f ${changelog_file} ] ; then
387        echo "Error: Couldn't find ${changelog_file} for package ${package} ${codename}-${arch}" | write_log
388        exit 1
389      fi
390
391      # Setting up global variable package_version, used for deb_file and changes_file.
392      read_package_version ${changelog_file}
393      local deb_file="${package}_${ossec_version}-${package_version}${codename}_${arch}.deb"
394      local changes_file="${package}_${ossec_version}-${package_version}${codename}_${arch}.changes"
395      local results_dir="/var/cache/pbuilder/${codename}-${arch}/result/${package}"
396      if [ ! -f ${results_dir}/${deb_file} ] || [ ! -f ${results_dir}/${changes_file} ] ; then
397        echo "Error: Couldn't find ${deb_file} or ${changes_file}" | write_log
398        exit 1
399      fi
400
401      # Uploading package to repository
402      cd ${results_dir}
403      echo "Uploading package ${changes_file} for ${codename} to OSSEC repository" | write_log
404      if sudo /usr/bin/dupload --nomail -f --to ossec-repository ${changes_file} ; then
405        echo " + Successfully uploaded package ${changes_file} for ${codename} to OSSEC repository" | write_log
406      else
407        echo "Error: Could not upload package ${changes_file} for ${codename} to the repository" | write_log
408        exit 1
409      fi
410
411      # Checking if it is an Ubuntu package
412      if contains_element $codename ${codenames_ubuntu[*]} ; then
413        local is_ubuntu=1
414      else
415        local is_ubuntu=0
416      fi
417
418      # Moving package to the right directory at the OSSEC apt repository server
419      echo " + Adding package /opt/incoming/${deb_file} to server repository for ${codename} distribution" | write_log
420      if [ $is_ubuntu -eq 1 ]; then
421        remove_package="cd /var/www/repos/apt/ubuntu; reprepro -A ${arch} remove ${codename} ${package}"
422        include_package="cd /var/www/repos/apt/ubuntu; reprepro includedeb ${codename} /opt/incoming/${deb_file}"
423      else
424        remove_package="cd /var/www/repos/apt/debian; reprepro -A ${arch} remove ${codename} ${package}"
425        include_package="cd /var/www/repos/apt/debian; reprepro includedeb ${codename} /opt/incoming/${deb_file}"
426      fi
427
428      /usr/bin/expect -c "
429        spawn sudo ssh root@ossec-repository \"${remove_package}\"
430        expect -re \"Not removed as not found.*\" { exit 1 }
431        expect -re \".*enter passphrase:.*\" { send \"${signing_pass}\r\" }
432        expect -re \".*enter passphrase:.*\" { send \"${signing_pass}\r\" }
433        expect -re \".*deleting.*\"
434      "
435
436      /usr/bin/expect -c "
437        spawn sudo ssh root@ossec-repository \"${include_package}\"
438        expect -re \"Skipping inclusion.*\" { exit 1 }
439        expect -re \".*enter passphrase:.*\"
440        send \"${signing_pass}\r\"
441        expect -re \".*enter passphrase:.*\"
442        send \"${signing_pass}\r\"
443        expect -re \".*Exporting.*\"
444      "
445      echo "Successfully added package ${deb_file} to server repository for ${codename} distribution" | write_log
446    done
447  done
448done
449}
450
451
452# If there are no arguments, display help
453if [ $# -eq 0 ]; then
454  show_help
455  exit 0
456fi
457
458# Reading command line arguments
459while [[ $# > 0 ]]
460do
461key="$1"
462shift
463
464case $key in
465  -h|--help)
466    show_help
467    exit 0
468    ;;
469  -u|--update)
470    update_chroots
471    shift
472    ;;
473  -d|--download)
474    download_source
475    shift
476    ;;
477  -b|--build)
478    build_packages
479    shift
480    ;;
481  -s|--sync)
482    sync_repository
483    shift
484    ;;
485  *)
486    echo "Unknown command line argument."
487    show_help
488    exit 0
489    ;;
490  esac
491done
492
493# vim: tabstop=2 expandtab shiftwidth=2 softtabstop=2
494