diff --git a/ChangeLog b/ChangeLog index 03409053..524d65d5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -24075,3 +24075,13 @@ * Modified rdcastmanager(1) so that the 'Title' and 'Description' field of the 'Editing Item' dialog default to empty after posting a new item when AutoPost is disabled. +2023-05-14 Fred Gleason + * Added a 'FEEDS.CDN_PURGE_PLUGIN_PATH' field to the database. + * Incremented the database version to 369. + * Added ' RDFeed::cdnPurgePluginPath()' and + 'RDFeed::setCdnPurgePluginPath()' methods. + * Removed the RD_MODULES_DIR #define from 'lib/rd.h'. + * Changed the RD_PYPAD_SCRIPT_DIR #define in 'lib/rd.h' to be + a static value of "/usr/lib/rivendell/pypad". + * Added a 'CDN Purge Plug-In' control to the 'Feed' dialog in + rdadmin(1). diff --git a/apis/Makefile.am b/apis/Makefile.am index ade30637..53634374 100644 --- a/apis/Makefile.am +++ b/apis/Makefile.am @@ -2,7 +2,7 @@ ## ## Makefile.am for rivendell/apis ## -## (C) Copyright 2018-2022 Fred Gleason +## (C) Copyright 2018-2023 Fred Gleason ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as @@ -20,7 +20,8 @@ ## ## Use automake to process this into a Makefile.in -SUBDIRS = pypad\ +SUBDIRS = cdn\ + pypad\ rivwebcapi\ rivwebpyapi diff --git a/apis/cdn/Makefile.am b/apis/cdn/Makefile.am new file mode 100644 index 00000000..a642dd3b --- /dev/null +++ b/apis/cdn/Makefile.am @@ -0,0 +1,38 @@ +## automake.am +## +## Automake.am for Rivendell CDN interface +## +## (C) Copyright 2023 Fred Gleason +## +## This 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. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public +## License along with this program; if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +## +## Use automake to process this into a Makefile.in + +SUBDIRS = scripts + +CLEANFILES = *~\ + *.idb\ + *ilk\ + *.obj\ + *.pdb\ + *.qm\ + moc_* + +MAINTAINERCLEANFILES = *~\ + *.tar.gz\ + aclocal.m4\ + configure\ + Makefile.in\ + moc_* diff --git a/apis/cdn/scripts/Makefile.am b/apis/cdn/scripts/Makefile.am new file mode 100644 index 00000000..988ddb86 --- /dev/null +++ b/apis/cdn/scripts/Makefile.am @@ -0,0 +1,48 @@ +## Makefile.am +## +## Makefile.am for Rivendell apis/cdn/scripts +## +## (C) Copyright 2023 Fred Gleason +## +## This 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. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public +## License along with this program; if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +## +## Use automake to process this into a Makefile.in + +install-exec-am: + mkdir -p $(DESTDIR)/usr/lib/rivendell/cdn + cp aka_purge.sh $(DESTDIR)/usr/lib/rivendell/cdn/ + cp akamai_purge.cdn $(DESTDIR)/usr/lib/rivendell/cdn/ + +uninstall-local: + rm -f $(DESTDIR)/usr/lib/rivendell/cdn/aka_purge.sh + rm -f $(DESTDIR)/usr/lib/rivendell/cdn/akamai_purge.cdn + +EXTRA_DIST = aka_purge.sh\ + akamai_purge.cdn + +CLEANFILES = *~\ + *.idb\ + *ilk\ + *.obj\ + *.pdb\ + *.qm\ + moc_* + +MAINTAINERCLEANFILES = *~\ + *.tar.gz\ + aclocal.m4\ + configure\ + Makefile.in\ + moc_* diff --git a/apis/cdn/scripts/aka_purge.sh b/apis/cdn/scripts/aka_purge.sh new file mode 100755 index 00000000..52fd2029 --- /dev/null +++ b/apis/cdn/scripts/aka_purge.sh @@ -0,0 +1,573 @@ +#!/bin/bash + +## ---------------------------------------------------------------------------- +# +# SCRIPT NAME: aka_purge.sh +# +# DESCRIPTION: Purge the Akamai cache via CCU REST API v3. +# +# REVISION: v0.9 +# DATE: May 2019 +# AUTHORS: Luca, Genasci +# Marco, De March +# +# CONTRIBUTORS: Raffaele, Rusconi +# +# TESTED: Tested in: +# GNU bash, version 4.4.12(3)-release (x86_64-unknown-cygwin) +# GNU bash, version 5.0.7(1)-release (x86_64-pc-linux-gnu) +# GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu) +# GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18) +# +## ---------------------------------------------------------------------------- + +## Definition if functions ---------------------------------------------------- + +function aa_set { + local key="${1}" + local val="${2}" + shift 2 + + declare -a arr=( ${@} ) + + local t_key + local idx=0 + for el in "${arr[@]}" + do + t_key="${el%%:*}" + [ "${key}" == "${t_key}" ] && { arr[idx]="${key}:${val}"; echo "${arr[@]}"; return 0; } + let idx++ + done + + arr[idx]="${key}:${val}" + echo "${arr[@]}" + return 0 +} + +function aa_get { + local key="${1}" + shift 1 + + declare -a arr=( ${@} ) + + for el in "${arr[@]}" + do + t_key="${el%%:*}" + [ "${key}" == "${t_key}" ] && { echo "${el#*:}"; return 0; } + done + + return 1 +} + +function _get_property { + [ "$#" -lt 2 ] && return 1 + local RC=$1 + local PROP=$2 + local value=$(cat ${RC} | sed "/^\s*#/d;s/\s*#[^\"']*$//" | grep ${PROP} | tail -n 1 | cut -d '=' -f2- ) + if [ -z "${value}" ]; then + return 1 + else + echo ${value} + return 0 + fi +} + +function get_properties { + local file="${1}" + + declare -a aka_props + local tmp + + tmp=$( _get_property "${file}" client_secret ) + [ -z "${tmp}" ] && { >&2 echo "ERROR: Please, set variable client_secret in file ${file}!!!"; exit 1; } + aka_props=( $( aa_set client_secret ${tmp} "${aka_props[@]}" ) ) + + tmp=$( _get_property "${file}" client_token ) + [ -z "${tmp}" ] && { >&2 echo "ERROR: Please, set variable client_token in file ${file}!!!"; exit 1; } + aka_props=( $( aa_set client_token ${tmp} "${aka_props[@]}" ) ) + + tmp=$( _get_property "${file}" access_token ) + [ -z "${tmp}" ] && { >&2 echo "ERROR: Please, set variable access_token in file ${file}!!!"; exit 1; } + aka_props=( $( aa_set access_token ${tmp} "${aka_props[@]}" ) ) + + tmp=$( _get_property "${file}" host ) + [ -z "${tmp}" ] && { >&2 echo "ERROR: Please, set variable host in file ${file}!!!"; exit 1; } + aka_props=( $( aa_set host ${tmp} "${aka_props[@]}" ) ) + + tmp=$( _get_property "${file}" network ) + [ -z "${tmp}" ] && aka_props=( $( aa_set network staging "${aka_props[@]}" ) ) || aka_props=( $( aa_set network ${tmp} "${aka_props[@]}" ) ) + + tmp=$( _get_property "${file}" action ) + [ -z "${tmp}" ] && aka_props=( $( aa_set action invalidate "${aka_props[@]}" ) ) || aka_props=( $( aa_set action ${tmp} "${aka_props[@]}" ) ) + + tmp=$( _get_property "${file}" type ) + [ -z "${tmp}" ] && aka_props=( $( aa_set type url "${aka_props[@]}" ) ) || aka_props=( $( aa_set type ${tmp} "${aka_props[@]}" ) ) + + tmp=$( _get_property "${file}" n_retries ) + [ -z "${tmp}" ] && aka_props=( $( aa_set n_retries 3 "${aka_props[@]}" ) ) || aka_props=( $( aa_set n_retries ${tmp} "${aka_props[@]}" ) ) + + tmp=$( _get_property "${file}" t_retry ) + [ -z "${tmp}" ] && aka_props=( $( aa_set t_retry 60 "${aka_props[@]}" ) ) || aka_props=( $( aa_set t_retry ${tmp} "${aka_props[@]}" ) ) + + echo "${aka_props[@]}" + + return 0 +} + +function mk_nonce { + local s=$1 + if [ -z ${s} ]; then s=$( date -u +'%Y%m%dT%H:%M:%S%z' ); fi + if [ $(uname) == 'Darwin' ]; then + echo -n "${s}" | md5 -r | cut -d ' ' -f1 | sed 's/.\{4\}/&-/g' | sed 's/.$//' + else + echo -n "${s}" | md5sum | cut -d ' ' -f1 | sed 's/.\{4\}/&-/g' | sed 's/.$//' + fi +} + +function base64_hmac_sha256 { + [ "$#" -lt 2 ] && return 1 + local key=$1 + local value=$2 + + echo -ne "${value}"| openssl sha256 -binary -hmac "${key}" | openssl base64 +} + +function base64_sha256 { + [ "$#" -lt 1 ] && return 1 + local value=$1 + + echo -ne "${value}" | openssl dgst -binary -sha256 | openssl base64 +} + +function mk_auth_header { + [ "$#" -lt 3 ] && return 1 + local timestamp=${1} + local nonce=${2} + shift 2 + + declare -a aka_props=( ${@} ) + + # EG1-HMAC-SHA256 + echo -n "client_token="$(aa_get client_token "${aka_props[@]}")";access_token="$( aa_get access_token "${aka_props[@]}" )";timestamp=${timestamp};nonce=${nonce};" +} + +function sign_data { + [ "$#" -lt 2 ] && return 1 + local key=${1} + shift + + declare -a data_to_sign=( ${@} ) + + local method=$( aa_get method "${data_to_sign[@]}" ) + local scheme=$( aa_get scheme "${data_to_sign[@]}" ) + local host=$( aa_get host "${data_to_sign[@]}" ) + local request_uri=$( aa_get request_uri "${data_to_sign[@]}" ) + local hash_content=$( aa_get hash_content "${data_to_sign[@]}" ) + local auth_header=$( aa_get auth_header "${data_to_sign[@]}" ) + + local data="${method}\t${scheme}\t${host}\t${request_uri}\t\t${hash_content}\tEG1-HMAC-SHA256 ${auth_header}" + base64_hmac_sha256 "${key}" "${data}" +} + +function mk_body { + local type="${1}" + local objects="${2}" + local domain="${3}" + + local arr_objects + local objs + + IFS=',' read -r -a arr_objects <<< "${objects}" + for i in ${!arr_objects[@]} + do + local tmp=$( echo ${arr_objects[i]} | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e 's,/,\\/,g' ) + if [ "${type}" == "cpcode" ]; then + objs="${objs},${tmp}" + else + objs="${objs},\"${tmp}\"" + fi + done + + objs=$( echo "${objs}" | sed 's/^,//' ) + + if [ "${type}" == "url" ]; then + echo "{\"hostname\":\"${domain}\",\"objects\":[${objs}]}" + else + echo "{\"objects\":[${objs}]}" + fi +} + +function get_objects { + local file="${1}" + local domain="${2}" + local type="${3}" + + local objs + + case ${type} in + url ) + cat "${file}" | grep -E '^(http(s)?://'${domain}')?/' | sed -E 's|^http(s)?://'${domain}'||' | paste -sd ',' + ;; + cpcode ) + cat "${file}" | sed -E '/^[0-9]+$/!d' | paste -sd ',' + ;; + tag ) + cat "${file}" | paste -sd ',' + ;; + * ) return 1 ;; + esac + + return 0 + +} + +function separate_objects { + local objs="${1}" + + local limit=49000 + local bytes buffer + local b_len=0 + + declare -a arr + + bytes=$( echo "${objs}" | wc -c ) + [ ${bytes} -le ${limit} ] && { echo ${objs}; return 0; } + + while true + do + buffer=${objs:${b_len}:${limit}} + [ -z ${buffer} ] && break + buffer=${buffer%,*} + arr+=( ${buffer} ) + b_len=$(( ${b_len} + ${#buffer} + 1 )) + echo ${b_len} + done + + echo "${arr[@]}" + return 0 + +} + +function countdown(){ + local cn=${1} + local lb=${2} + + date1=$((`date +%s` + ${cn})); + while [ "$date1" -ge `date +%s` ]; do + echo -ne "${lb}: $(date -u --date @$(($date1 - `date +%s`)) +%H:%M:%S)\r"; + sleep 0.1 + done +} + +function getopt_type { + local type="BSD" + getopt -T > /dev/null + [ $? -eq 4 ] && type="GNU" + + echo "${type}" +} + + +## Options of script ---------------------------------------------------------- + +P_NAME=${0##*\/} + +GETOPT_TYPE=$( getopt_type ) + +WARNING= +[ "${GETOPT_TYPE}" != "GNU" ] && { + WARNING+=$'\n'$'\e[93m'; + WARNING+="WARNING"$'\n'; + WARNING+=" The version of your getopt is not GNU."$'\n'; + WARNING+=" If you want use the long options use the brew command to install gnu-getopt."$'\n'; + WARNING+=" ${P_NAME} work fine with short options."$'\n'; + WARNING+=$'\e[0m'; +} + +CONF=/usr/share/rivendell/keys/edgegrid.conf + +declare -a AKA_PROPS=$( get_properties "${CONF}" ) + +usage() { +cat << EOF +usage: ${P_NAME} [OPTIONS] -o + +Purge the Akamai cache via CCU REST API v3. + +REMARKS + Please create a config file in /usr/share/rivendell/keys +${WARNING} +PARAMETERS: + -o, --objects List of objects to purge (with comma separated values) + + -i, --input-file Input file that contain a list of objects. + The separator value in the file mode is the carriage + return + + Type | Comment + -------------------------------------------------------- + url | The addresses considered, depend of the domain + | (see the option -d). If an address not have a + | domain it's included in the purge procedure. + -------------------------------------------------------- + cpcode | List of the cpcodes. The items must be numbers + | otherwise will be excluded. + -------------------------------------------------------- + tag | List of a tags + + +OPTIONS: + -t, --type Type of objects + Possible values: url, cpcode or tag + Default: ${TYPE} + + -d, --domain Domain site (es. your.domain.com). + To use with type=url + + -a, --action The desired action to manage the cache + Possible values: invalidate or delete + Default: ${ACTION} + + -n, --network The network on which you want to invalidate or delete content + Possible values: staging or production + Default: ${NETWORK} + + -r, --show-quests Show the requests + + -h, --help Show this message + -v, --version Show version + + +CONFIG FILE: + + In the config file the following values have to been declared: + client_secret = + client_token = + access_token = + host = + + There is the possibility to set the default values: + network = + action = + type = + + If no values are declared the default ones are: + network = staging + action = invalidate + type = url + + For manage an error: + n_retries = (default 3) + t_retry =