mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Merge branch 'unstable' into backup_core_only
This commit is contained in:
commit
65dab2022c
44 changed files with 2779 additions and 273 deletions
22
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
22
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
## The problem
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
## PR Status
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
## How to test
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
- [ ] Principle agreement 0/2 :
|
||||||
|
- [ ] Quick review 0/1 :
|
||||||
|
- [ ] Simple test 0/1 :
|
||||||
|
- [ ] Deep review 0/1 :
|
|
@ -203,6 +203,30 @@ user:
|
||||||
extra:
|
extra:
|
||||||
pattern: *pattern_mailbox_quota
|
pattern: *pattern_mailbox_quota
|
||||||
|
|
||||||
|
### ssh_user_enable_ssh()
|
||||||
|
allow-ssh:
|
||||||
|
action_help: Allow the user to uses ssh
|
||||||
|
api: POST /ssh/user/enable-ssh
|
||||||
|
configuration:
|
||||||
|
authenticate: all
|
||||||
|
arguments:
|
||||||
|
username:
|
||||||
|
help: Username of the user
|
||||||
|
extra:
|
||||||
|
pattern: *pattern_username
|
||||||
|
|
||||||
|
### ssh_user_disable_ssh()
|
||||||
|
disallow-ssh:
|
||||||
|
action_help: Disallow the user to uses ssh
|
||||||
|
api: POST /ssh/user/disable-ssh
|
||||||
|
configuration:
|
||||||
|
authenticate: all
|
||||||
|
arguments:
|
||||||
|
username:
|
||||||
|
help: Username of the user
|
||||||
|
extra:
|
||||||
|
pattern: *pattern_username
|
||||||
|
|
||||||
### user_info()
|
### user_info()
|
||||||
info:
|
info:
|
||||||
action_help: Get user information
|
action_help: Get user information
|
||||||
|
@ -647,6 +671,19 @@ app:
|
||||||
authenticate: all
|
authenticate: all
|
||||||
authenticator: ldap-anonymous
|
authenticator: ldap-anonymous
|
||||||
|
|
||||||
|
### app_change_label()
|
||||||
|
change-label:
|
||||||
|
action_help: Change app label
|
||||||
|
api: PUT /apps/<app>/label
|
||||||
|
configuration:
|
||||||
|
authenticate: all
|
||||||
|
authenticator: ldap-anonymous
|
||||||
|
arguments:
|
||||||
|
app:
|
||||||
|
help: App ID
|
||||||
|
new_label:
|
||||||
|
help: New app label
|
||||||
|
|
||||||
### app_addaccess() TODO: Write help
|
### app_addaccess() TODO: Write help
|
||||||
addaccess:
|
addaccess:
|
||||||
action_help: Grant access right to users (everyone by default)
|
action_help: Grant access right to users (everyone by default)
|
||||||
|
@ -1312,6 +1349,74 @@ dyndns:
|
||||||
api: DELETE /dyndns/cron
|
api: DELETE /dyndns/cron
|
||||||
|
|
||||||
|
|
||||||
|
#############################
|
||||||
|
# SSH #
|
||||||
|
#############################
|
||||||
|
ssh:
|
||||||
|
category_help: Manage ssh keys and access
|
||||||
|
actions: {}
|
||||||
|
subcategories:
|
||||||
|
authorized-keys:
|
||||||
|
subcategory_help: Manage user's authorized ssh keys
|
||||||
|
|
||||||
|
actions:
|
||||||
|
### ssh_authorized_keys_list()
|
||||||
|
list:
|
||||||
|
action_help: Show user's authorized ssh keys
|
||||||
|
api: GET /ssh/authorized-keys
|
||||||
|
configuration:
|
||||||
|
authenticate: all
|
||||||
|
arguments:
|
||||||
|
username:
|
||||||
|
help: Username of the user
|
||||||
|
extra:
|
||||||
|
pattern: *pattern_username
|
||||||
|
|
||||||
|
### ssh_authorized_keys_add()
|
||||||
|
add:
|
||||||
|
action_help: Add a new authorized ssh key for this user
|
||||||
|
api: POST /ssh/authorized-keys
|
||||||
|
configuration:
|
||||||
|
authenticate: all
|
||||||
|
arguments:
|
||||||
|
username:
|
||||||
|
help: Username of the user
|
||||||
|
extra:
|
||||||
|
pattern: *pattern_username
|
||||||
|
-u:
|
||||||
|
full: --public
|
||||||
|
help: Public key
|
||||||
|
extra:
|
||||||
|
required: True
|
||||||
|
-i:
|
||||||
|
full: --private
|
||||||
|
help: Private key
|
||||||
|
extra:
|
||||||
|
required: True
|
||||||
|
-n:
|
||||||
|
full: --name
|
||||||
|
help: Key name
|
||||||
|
extra:
|
||||||
|
required: True
|
||||||
|
|
||||||
|
### ssh_authorized_keys_remove()
|
||||||
|
remove:
|
||||||
|
action_help: Remove an authorized ssh key for this user
|
||||||
|
api: DELETE /ssh/authorized-keys
|
||||||
|
configuration:
|
||||||
|
authenticate: all
|
||||||
|
arguments:
|
||||||
|
username:
|
||||||
|
help: Username of the user
|
||||||
|
extra:
|
||||||
|
pattern: *pattern_username
|
||||||
|
-k:
|
||||||
|
full: --key
|
||||||
|
help: Key as a string
|
||||||
|
extra:
|
||||||
|
required: True
|
||||||
|
|
||||||
|
|
||||||
#############################
|
#############################
|
||||||
# Tools #
|
# Tools #
|
||||||
#############################
|
#############################
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#
|
#
|
||||||
# usage: ynh_use_logrotate [logfile] [--non-append]
|
# usage: ynh_use_logrotate [logfile] [--non-append]
|
||||||
# | arg: logfile - absolute path of logfile
|
# | arg: logfile - absolute path of logfile
|
||||||
# | option: --non-append - Replace the config file instead of appending this new config.
|
# | arg: --non-append - (Option) Replace the config file instead of appending this new config.
|
||||||
#
|
#
|
||||||
# If no argument provided, a standard directory will be use. /var/log/${app}
|
# If no argument provided, a standard directory will be use. /var/log/${app}
|
||||||
# You can provide a path with the directory only or with the logfile.
|
# You can provide a path with the directory only or with the logfile.
|
||||||
|
@ -22,12 +22,12 @@ ynh_use_logrotate () {
|
||||||
fi
|
fi
|
||||||
if [ $# -gt 0 ]; then
|
if [ $# -gt 0 ]; then
|
||||||
if [ "$(echo ${1##*.})" == "log" ]; then # Keep only the extension to check if it's a logfile
|
if [ "$(echo ${1##*.})" == "log" ]; then # Keep only the extension to check if it's a logfile
|
||||||
logfile=$1 # In this case, focus logrotate on the logfile
|
local logfile=$1 # In this case, focus logrotate on the logfile
|
||||||
else
|
else
|
||||||
logfile=$1/*.log # Else, uses the directory and all logfile into it.
|
local logfile=$1/*.log # Else, uses the directory and all logfile into it.
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
logfile="/var/log/${app}/*.log" # Without argument, use a defaut directory in /var/log
|
local logfile="/var/log/${app}/*.log" # Without argument, use a defaut directory in /var/log
|
||||||
fi
|
fi
|
||||||
cat > ./${app}-logrotate << EOF # Build a config file for logrotate
|
cat > ./${app}-logrotate << EOF # Build a config file for logrotate
|
||||||
$logfile {
|
$logfile {
|
||||||
|
@ -64,19 +64,24 @@ ynh_remove_logrotate () {
|
||||||
|
|
||||||
# Create a dedicated systemd config
|
# Create a dedicated systemd config
|
||||||
#
|
#
|
||||||
# This will use a template in ../conf/systemd.service
|
# usage: ynh_add_systemd_config [service] [template]
|
||||||
# and will replace the following keywords with
|
# | arg: service - Service name (optionnal, $app by default)
|
||||||
# global variables that should be defined before calling
|
# | arg: template - Name of template file (optionnal, this is 'systemd' by default, meaning ./conf/systemd.service will be used as template)
|
||||||
|
#
|
||||||
|
# This will use the template ../conf/<templatename>.service
|
||||||
|
# to generate a systemd config, by replacing the following keywords
|
||||||
|
# with global variables that should be defined before calling
|
||||||
# this helper :
|
# this helper :
|
||||||
#
|
#
|
||||||
# __APP__ by $app
|
# __APP__ by $app
|
||||||
# __FINALPATH__ by $final_path
|
# __FINALPATH__ by $final_path
|
||||||
#
|
#
|
||||||
# usage: ynh_add_systemd_config
|
|
||||||
ynh_add_systemd_config () {
|
ynh_add_systemd_config () {
|
||||||
finalsystemdconf="/etc/systemd/system/$app.service"
|
local service_name="${1:-$app}"
|
||||||
|
|
||||||
|
finalsystemdconf="/etc/systemd/system/$service_name.service"
|
||||||
ynh_backup_if_checksum_is_different "$finalsystemdconf"
|
ynh_backup_if_checksum_is_different "$finalsystemdconf"
|
||||||
sudo cp ../conf/systemd.service "$finalsystemdconf"
|
sudo cp ../conf/${2:-systemd.service} "$finalsystemdconf"
|
||||||
|
|
||||||
# To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable.
|
# To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable.
|
||||||
# Substitute in a nginx config file only if the variable is not empty
|
# Substitute in a nginx config file only if the variable is not empty
|
||||||
|
@ -89,24 +94,31 @@ ynh_add_systemd_config () {
|
||||||
ynh_store_file_checksum "$finalsystemdconf"
|
ynh_store_file_checksum "$finalsystemdconf"
|
||||||
|
|
||||||
sudo chown root: "$finalsystemdconf"
|
sudo chown root: "$finalsystemdconf"
|
||||||
sudo systemctl enable $app
|
sudo systemctl enable $service_name
|
||||||
sudo systemctl daemon-reload
|
sudo systemctl daemon-reload
|
||||||
}
|
}
|
||||||
|
|
||||||
# Remove the dedicated systemd config
|
# Remove the dedicated systemd config
|
||||||
#
|
#
|
||||||
# usage: ynh_remove_systemd_config
|
# usage: ynh_remove_systemd_config [service]
|
||||||
|
# | arg: service - Service name (optionnal, $app by default)
|
||||||
|
#
|
||||||
ynh_remove_systemd_config () {
|
ynh_remove_systemd_config () {
|
||||||
finalsystemdconf="/etc/systemd/system/$app.service"
|
local service_name="${1:-$app}"
|
||||||
|
|
||||||
|
local finalsystemdconf="/etc/systemd/system/$service_name.service"
|
||||||
if [ -e "$finalsystemdconf" ]; then
|
if [ -e "$finalsystemdconf" ]; then
|
||||||
sudo systemctl stop $app
|
sudo systemctl stop $service_name
|
||||||
sudo systemctl disable $app
|
sudo systemctl disable $service_name
|
||||||
ynh_secure_remove "$finalsystemdconf"
|
ynh_secure_remove "$finalsystemdconf"
|
||||||
|
sudo systemctl daemon-reload
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create a dedicated nginx config
|
# Create a dedicated nginx config
|
||||||
#
|
#
|
||||||
|
# usage: ynh_add_nginx_config
|
||||||
|
#
|
||||||
# This will use a template in ../conf/nginx.conf
|
# This will use a template in ../conf/nginx.conf
|
||||||
# __PATH__ by $path_url
|
# __PATH__ by $path_url
|
||||||
# __DOMAIN__ by $domain
|
# __DOMAIN__ by $domain
|
||||||
|
@ -114,7 +126,6 @@ ynh_remove_systemd_config () {
|
||||||
# __NAME__ by $app
|
# __NAME__ by $app
|
||||||
# __FINALPATH__ by $final_path
|
# __FINALPATH__ by $final_path
|
||||||
#
|
#
|
||||||
# usage: ynh_add_nginx_config
|
|
||||||
ynh_add_nginx_config () {
|
ynh_add_nginx_config () {
|
||||||
finalnginxconf="/etc/nginx/conf.d/$domain.d/$app.conf"
|
finalnginxconf="/etc/nginx/conf.d/$domain.d/$app.conf"
|
||||||
ynh_backup_if_checksum_is_different "$finalnginxconf"
|
ynh_backup_if_checksum_is_different "$finalnginxconf"
|
||||||
|
@ -124,7 +135,7 @@ ynh_add_nginx_config () {
|
||||||
# Substitute in a nginx config file only if the variable is not empty
|
# Substitute in a nginx config file only if the variable is not empty
|
||||||
if test -n "${path_url:-}"; then
|
if test -n "${path_url:-}"; then
|
||||||
# path_url_slash_less is path_url, or a blank value if path_url is only '/'
|
# path_url_slash_less is path_url, or a blank value if path_url is only '/'
|
||||||
path_url_slash_less=${path_url%/}
|
local path_url_slash_less=${path_url%/}
|
||||||
ynh_replace_string "__PATH__/" "$path_url_slash_less/" "$finalnginxconf"
|
ynh_replace_string "__PATH__/" "$path_url_slash_less/" "$finalnginxconf"
|
||||||
ynh_replace_string "__PATH__" "$path_url" "$finalnginxconf"
|
ynh_replace_string "__PATH__" "$path_url" "$finalnginxconf"
|
||||||
fi
|
fi
|
||||||
|
@ -157,7 +168,17 @@ ynh_remove_nginx_config () {
|
||||||
#
|
#
|
||||||
# usage: ynh_add_fpm_config
|
# usage: ynh_add_fpm_config
|
||||||
ynh_add_fpm_config () {
|
ynh_add_fpm_config () {
|
||||||
finalphpconf="/etc/php5/fpm/pool.d/$app.conf"
|
# Configure PHP-FPM 7.0 by default
|
||||||
|
local fpm_config_dir="/etc/php/7.0/fpm"
|
||||||
|
local fpm_service="php7.0-fpm"
|
||||||
|
# Configure PHP-FPM 5 on Debian Jessie
|
||||||
|
if [ "$(ynh_get_debian_release)" == "jessie" ]; then
|
||||||
|
fpm_config_dir="/etc/php5/fpm"
|
||||||
|
fpm_service="php5-fpm"
|
||||||
|
fi
|
||||||
|
ynh_app_setting_set $app fpm_config_dir "$fpm_config_dir"
|
||||||
|
ynh_app_setting_set $app fpm_service "$fpm_service"
|
||||||
|
finalphpconf="$fpm_config_dir/pool.d/$app.conf"
|
||||||
ynh_backup_if_checksum_is_different "$finalphpconf"
|
ynh_backup_if_checksum_is_different "$finalphpconf"
|
||||||
sudo cp ../conf/php-fpm.conf "$finalphpconf"
|
sudo cp ../conf/php-fpm.conf "$finalphpconf"
|
||||||
ynh_replace_string "__NAMETOCHANGE__" "$app" "$finalphpconf"
|
ynh_replace_string "__NAMETOCHANGE__" "$app" "$finalphpconf"
|
||||||
|
@ -168,21 +189,27 @@ ynh_add_fpm_config () {
|
||||||
|
|
||||||
if [ -e "../conf/php-fpm.ini" ]
|
if [ -e "../conf/php-fpm.ini" ]
|
||||||
then
|
then
|
||||||
finalphpini="/etc/php5/fpm/conf.d/20-$app.ini"
|
finalphpini="$fpm_config_dir/conf.d/20-$app.ini"
|
||||||
ynh_backup_if_checksum_is_different "$finalphpini"
|
ynh_backup_if_checksum_is_different "$finalphpini"
|
||||||
sudo cp ../conf/php-fpm.ini "$finalphpini"
|
sudo cp ../conf/php-fpm.ini "$finalphpini"
|
||||||
sudo chown root: "$finalphpini"
|
sudo chown root: "$finalphpini"
|
||||||
ynh_store_file_checksum "$finalphpini"
|
ynh_store_file_checksum "$finalphpini"
|
||||||
fi
|
fi
|
||||||
|
sudo systemctl reload $fpm_service
|
||||||
sudo systemctl reload php5-fpm
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Remove the dedicated php-fpm config
|
# Remove the dedicated php-fpm config
|
||||||
#
|
#
|
||||||
# usage: ynh_remove_fpm_config
|
# usage: ynh_remove_fpm_config
|
||||||
ynh_remove_fpm_config () {
|
ynh_remove_fpm_config () {
|
||||||
ynh_secure_remove "/etc/php5/fpm/pool.d/$app.conf"
|
local fpm_config_dir=$(ynh_app_setting_get $app fpm_config_dir)
|
||||||
ynh_secure_remove "/etc/php5/fpm/conf.d/20-$app.ini" 2>&1
|
local fpm_service=$(ynh_app_setting_get $app fpm_service)
|
||||||
sudo systemctl reload php5-fpm
|
# Assume php version 5 if not set
|
||||||
|
if [ -z "$fpm_config_dir" ]; then
|
||||||
|
fpm_config_dir="/etc/php5/fpm"
|
||||||
|
fpm_service="php5-fpm"
|
||||||
|
fi
|
||||||
|
ynh_secure_remove "$fpm_config_dir/pool.d/$app.conf"
|
||||||
|
ynh_secure_remove "$fpm_config_dir/conf.d/20-$app.ini" 2>&1
|
||||||
|
sudo systemctl reload $fpm_service
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,6 +144,8 @@ ynh_restore () {
|
||||||
|
|
||||||
# Return the path in the archive where has been stocked the origin path
|
# Return the path in the archive where has been stocked the origin path
|
||||||
#
|
#
|
||||||
|
# [internal]
|
||||||
|
#
|
||||||
# usage: _get_archive_path ORIGIN_PATH
|
# usage: _get_archive_path ORIGIN_PATH
|
||||||
_get_archive_path () {
|
_get_archive_path () {
|
||||||
# For security reasons we use csv python library to read the CSV
|
# For security reasons we use csv python library to read the CSV
|
||||||
|
@ -188,12 +190,12 @@ ynh_restore_file () {
|
||||||
local ARCHIVE_PATH="$YNH_CWD${ORIGIN_PATH}"
|
local ARCHIVE_PATH="$YNH_CWD${ORIGIN_PATH}"
|
||||||
# Default value for DEST_PATH = /$ORIGIN_PATH
|
# Default value for DEST_PATH = /$ORIGIN_PATH
|
||||||
local DEST_PATH="${2:-$ORIGIN_PATH}"
|
local DEST_PATH="${2:-$ORIGIN_PATH}"
|
||||||
|
|
||||||
# If ARCHIVE_PATH doesn't exist, search for a corresponding path in CSV
|
# If ARCHIVE_PATH doesn't exist, search for a corresponding path in CSV
|
||||||
if [ ! -d "$ARCHIVE_PATH" ] && [ ! -f "$ARCHIVE_PATH" ] && [ ! -L "$ARCHIVE_PATH" ]; then
|
if [ ! -d "$ARCHIVE_PATH" ] && [ ! -f "$ARCHIVE_PATH" ] && [ ! -L "$ARCHIVE_PATH" ]; then
|
||||||
ARCHIVE_PATH="$YNH_BACKUP_DIR/$(_get_archive_path \"$ORIGIN_PATH\")"
|
ARCHIVE_PATH="$YNH_BACKUP_DIR/$(_get_archive_path \"$ORIGIN_PATH\")"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Restore ORIGIN_PATH into DEST_PATH
|
# Restore ORIGIN_PATH into DEST_PATH
|
||||||
mkdir -p $(dirname "$DEST_PATH")
|
mkdir -p $(dirname "$DEST_PATH")
|
||||||
|
|
||||||
|
@ -211,6 +213,9 @@ ynh_restore_file () {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Deprecated helper since it's a dangerous one!
|
# Deprecated helper since it's a dangerous one!
|
||||||
|
#
|
||||||
|
# [internal]
|
||||||
|
#
|
||||||
ynh_bind_or_cp() {
|
ynh_bind_or_cp() {
|
||||||
local AS_ROOT=${3:-0}
|
local AS_ROOT=${3:-0}
|
||||||
local NO_ROOT=0
|
local NO_ROOT=0
|
||||||
|
@ -221,6 +226,8 @@ ynh_bind_or_cp() {
|
||||||
|
|
||||||
# Create a directory under /tmp
|
# Create a directory under /tmp
|
||||||
#
|
#
|
||||||
|
# [internal]
|
||||||
|
#
|
||||||
# Deprecated helper
|
# Deprecated helper
|
||||||
#
|
#
|
||||||
# usage: ynh_mkdir_tmp
|
# usage: ynh_mkdir_tmp
|
||||||
|
@ -266,7 +273,7 @@ ynh_backup_if_checksum_is_different () {
|
||||||
then # Proceed only if a value was stored into the app settings
|
then # Proceed only if a value was stored into the app settings
|
||||||
if ! echo "$checksum_value $file" | sudo md5sum -c --status
|
if ! echo "$checksum_value $file" | sudo md5sum -c --status
|
||||||
then # If the checksum is now different
|
then # If the checksum is now different
|
||||||
backup_file="/home/yunohost.conf/backup/$file.backup.$(date '+%Y%m%d.%H%M%S')"
|
local backup_file="/home/yunohost.conf/backup/$file.backup.$(date '+%Y%m%d.%H%M%S')"
|
||||||
sudo mkdir -p "$(dirname "$backup_file")"
|
sudo mkdir -p "$(dirname "$backup_file")"
|
||||||
sudo cp -a "$file" "$backup_file" # Backup the current file
|
sudo cp -a "$file" "$backup_file" # Backup the current file
|
||||||
echo "File $file has been manually modified since the installation or last upgrade. So it has been duplicated in $backup_file" >&2
|
echo "File $file has been manually modified since the installation or last upgrade. So it has been duplicated in $backup_file" >&2
|
||||||
|
@ -280,8 +287,8 @@ ynh_backup_if_checksum_is_different () {
|
||||||
# usage: ynh_secure_remove path_to_remove
|
# usage: ynh_secure_remove path_to_remove
|
||||||
# | arg: path_to_remove - File or directory to remove
|
# | arg: path_to_remove - File or directory to remove
|
||||||
ynh_secure_remove () {
|
ynh_secure_remove () {
|
||||||
path_to_remove=$1
|
local path_to_remove=$1
|
||||||
forbidden_path=" \
|
local forbidden_path=" \
|
||||||
/var/www \
|
/var/www \
|
||||||
/home/yunohost.app"
|
/home/yunohost.app"
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
# Validate an IP address
|
# Validate an IP address
|
||||||
#
|
#
|
||||||
|
# usage: ynh_validate_ip [family] [ip_address]
|
||||||
|
# | ret: 0 for valid ip addresses, 1 otherwise
|
||||||
|
#
|
||||||
# example: ynh_validate_ip 4 111.222.333.444
|
# example: ynh_validate_ip 4 111.222.333.444
|
||||||
#
|
#
|
||||||
# usage: ynh_validate_ip <family> <ip_address>
|
|
||||||
#
|
|
||||||
# exit code : 0 for valid ip addresses, 1 otherwise
|
|
||||||
ynh_validate_ip()
|
ynh_validate_ip()
|
||||||
{
|
{
|
||||||
# http://stackoverflow.com/questions/319279/how-to-validate-ip-address-in-python#319298
|
# http://stackoverflow.com/questions/319279/how-to-validate-ip-address-in-python#319298
|
||||||
|
|
||||||
IP_ADDRESS_FAMILY=$1
|
local IP_ADDRESS_FAMILY=$1
|
||||||
IP_ADDRESS=$2
|
local IP_ADDRESS=$2
|
||||||
|
|
||||||
[ "$IP_ADDRESS_FAMILY" == "4" ] || [ "$IP_ADDRESS_FAMILY" == "6" ] || return 1
|
[ "$IP_ADDRESS_FAMILY" == "4" ] || [ "$IP_ADDRESS_FAMILY" == "6" ] || return 1
|
||||||
|
|
||||||
python /dev/stdin << EOF
|
python /dev/stdin << EOF
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
|
@ -31,8 +31,8 @@ EOF
|
||||||
# example: ynh_validate_ip4 111.222.333.444
|
# example: ynh_validate_ip4 111.222.333.444
|
||||||
#
|
#
|
||||||
# usage: ynh_validate_ip4 <ip_address>
|
# usage: ynh_validate_ip4 <ip_address>
|
||||||
|
# | ret: 0 for valid ipv4 addresses, 1 otherwise
|
||||||
#
|
#
|
||||||
# exit code : 0 for valid ipv4 addresses, 1 otherwise
|
|
||||||
ynh_validate_ip4()
|
ynh_validate_ip4()
|
||||||
{
|
{
|
||||||
ynh_validate_ip 4 $1
|
ynh_validate_ip 4 $1
|
||||||
|
@ -44,8 +44,8 @@ ynh_validate_ip4()
|
||||||
# example: ynh_validate_ip6 2000:dead:beef::1
|
# example: ynh_validate_ip6 2000:dead:beef::1
|
||||||
#
|
#
|
||||||
# usage: ynh_validate_ip6 <ip_address>
|
# usage: ynh_validate_ip6 <ip_address>
|
||||||
|
# | ret: 0 for valid ipv6 addresses, 1 otherwise
|
||||||
#
|
#
|
||||||
# exit code : 0 for valid ipv6 addresses, 1 otherwise
|
|
||||||
ynh_validate_ip6()
|
ynh_validate_ip6()
|
||||||
{
|
{
|
||||||
ynh_validate_ip 6 $1
|
ynh_validate_ip 6 $1
|
||||||
|
|
|
@ -8,7 +8,7 @@ MYSQL_ROOT_PWD_FILE=/etc/yunohost/mysql
|
||||||
# usage: ynh_mysql_connect_as user pwd [db]
|
# usage: ynh_mysql_connect_as user pwd [db]
|
||||||
# | arg: user - the user name to connect as
|
# | arg: user - the user name to connect as
|
||||||
# | arg: pwd - the user password
|
# | arg: pwd - the user password
|
||||||
# | arg: db - the database to connect to
|
# | arg: db - the database to connect to
|
||||||
ynh_mysql_connect_as() {
|
ynh_mysql_connect_as() {
|
||||||
mysql -u "$1" --password="$2" -B "${3:-}"
|
mysql -u "$1" --password="$2" -B "${3:-}"
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ ynh_mysql_connect_as() {
|
||||||
#
|
#
|
||||||
# usage: ynh_mysql_execute_as_root sql [db]
|
# usage: ynh_mysql_execute_as_root sql [db]
|
||||||
# | arg: sql - the SQL command to execute
|
# | arg: sql - the SQL command to execute
|
||||||
# | arg: db - the database to connect to
|
# | arg: db - the database to connect to
|
||||||
ynh_mysql_execute_as_root() {
|
ynh_mysql_execute_as_root() {
|
||||||
ynh_mysql_connect_as "root" "$(sudo cat $MYSQL_ROOT_PWD_FILE)" \
|
ynh_mysql_connect_as "root" "$(sudo cat $MYSQL_ROOT_PWD_FILE)" \
|
||||||
"${2:-}" <<< "$1"
|
"${2:-}" <<< "$1"
|
||||||
|
@ -27,7 +27,7 @@ ynh_mysql_execute_as_root() {
|
||||||
#
|
#
|
||||||
# usage: ynh_mysql_execute_file_as_root file [db]
|
# usage: ynh_mysql_execute_file_as_root file [db]
|
||||||
# | arg: file - the file containing SQL commands
|
# | arg: file - the file containing SQL commands
|
||||||
# | arg: db - the database to connect to
|
# | arg: db - the database to connect to
|
||||||
ynh_mysql_execute_file_as_root() {
|
ynh_mysql_execute_file_as_root() {
|
||||||
ynh_mysql_connect_as "root" "$(sudo cat $MYSQL_ROOT_PWD_FILE)" \
|
ynh_mysql_connect_as "root" "$(sudo cat $MYSQL_ROOT_PWD_FILE)" \
|
||||||
"${2:-}" < "$1"
|
"${2:-}" < "$1"
|
||||||
|
@ -35,14 +35,16 @@ ynh_mysql_execute_file_as_root() {
|
||||||
|
|
||||||
# Create a database and grant optionnaly privilegies to a user
|
# Create a database and grant optionnaly privilegies to a user
|
||||||
#
|
#
|
||||||
|
# [internal]
|
||||||
|
#
|
||||||
# usage: ynh_mysql_create_db db [user [pwd]]
|
# usage: ynh_mysql_create_db db [user [pwd]]
|
||||||
# | arg: db - the database name to create
|
# | arg: db - the database name to create
|
||||||
# | arg: user - the user to grant privilegies
|
# | arg: user - the user to grant privilegies
|
||||||
# | arg: pwd - the password to identify user by
|
# | arg: pwd - the password to identify user by
|
||||||
ynh_mysql_create_db() {
|
ynh_mysql_create_db() {
|
||||||
db=$1
|
local db=$1
|
||||||
|
|
||||||
sql="CREATE DATABASE ${db};"
|
local sql="CREATE DATABASE ${db};"
|
||||||
|
|
||||||
# grant all privilegies to user
|
# grant all privilegies to user
|
||||||
if [[ $# -gt 1 ]]; then
|
if [[ $# -gt 1 ]]; then
|
||||||
|
@ -56,6 +58,8 @@ ynh_mysql_create_db() {
|
||||||
|
|
||||||
# Drop a database
|
# Drop a database
|
||||||
#
|
#
|
||||||
|
# [internal]
|
||||||
|
#
|
||||||
# If you intend to drop the database *and* the associated user,
|
# If you intend to drop the database *and* the associated user,
|
||||||
# consider using ynh_mysql_remove_db instead.
|
# consider using ynh_mysql_remove_db instead.
|
||||||
#
|
#
|
||||||
|
@ -78,6 +82,8 @@ ynh_mysql_dump_db() {
|
||||||
|
|
||||||
# Create a user
|
# Create a user
|
||||||
#
|
#
|
||||||
|
# [internal]
|
||||||
|
#
|
||||||
# usage: ynh_mysql_create_user user pwd [host]
|
# usage: ynh_mysql_create_user user pwd [host]
|
||||||
# | arg: user - the user name to create
|
# | arg: user - the user name to create
|
||||||
# | arg: pwd - the password to identify user by
|
# | arg: pwd - the password to identify user by
|
||||||
|
@ -90,7 +96,7 @@ ynh_mysql_create_user() {
|
||||||
#
|
#
|
||||||
# usage: ynh_mysql_user_exists user
|
# usage: ynh_mysql_user_exists user
|
||||||
# | arg: user - the user for which to check existence
|
# | arg: user - the user for which to check existence
|
||||||
function ynh_mysql_user_exists()
|
ynh_mysql_user_exists()
|
||||||
{
|
{
|
||||||
local user=$1
|
local user=$1
|
||||||
if [[ -z $(ynh_mysql_execute_as_root "SELECT User from mysql.user WHERE User = '$user';") ]]
|
if [[ -z $(ynh_mysql_execute_as_root "SELECT User from mysql.user WHERE User = '$user';") ]]
|
||||||
|
@ -103,6 +109,8 @@ function ynh_mysql_user_exists()
|
||||||
|
|
||||||
# Drop a user
|
# Drop a user
|
||||||
#
|
#
|
||||||
|
# [internal]
|
||||||
|
#
|
||||||
# usage: ynh_mysql_drop_user user
|
# usage: ynh_mysql_drop_user user
|
||||||
# | arg: user - the user name to drop
|
# | arg: user - the user name to drop
|
||||||
ynh_mysql_drop_user() {
|
ynh_mysql_drop_user() {
|
||||||
|
@ -153,12 +161,12 @@ ynh_mysql_remove_db () {
|
||||||
# Sanitize a string intended to be the name of a database
|
# Sanitize a string intended to be the name of a database
|
||||||
# (More specifically : replace - and . by _)
|
# (More specifically : replace - and . by _)
|
||||||
#
|
#
|
||||||
# Exemple: dbname=$(ynh_sanitize_dbid $app)
|
# example: dbname=$(ynh_sanitize_dbid $app)
|
||||||
#
|
#
|
||||||
# usage: ynh_sanitize_dbid name
|
# usage: ynh_sanitize_dbid name
|
||||||
# | arg: name - name to correct/sanitize
|
# | arg: name - name to correct/sanitize
|
||||||
# | ret: the corrected name
|
# | ret: the corrected name
|
||||||
ynh_sanitize_dbid () {
|
ynh_sanitize_dbid () {
|
||||||
dbid=${1//[-.]/_} # We should avoid having - and . in the name of databases. They are replaced by _
|
local dbid=${1//[-.]/_} # We should avoid having - and . in the name of databases. They are replaced by _
|
||||||
echo $dbid
|
echo $dbid
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
# usage: ynh_normalize_url_path path_to_normalize
|
# usage: ynh_normalize_url_path path_to_normalize
|
||||||
# | arg: url_path_to_normalize - URL path to normalize before using it
|
# | arg: url_path_to_normalize - URL path to normalize before using it
|
||||||
ynh_normalize_url_path () {
|
ynh_normalize_url_path () {
|
||||||
path_url=$1
|
local path_url=$1
|
||||||
test -n "$path_url" || ynh_die "ynh_normalize_url_path expect a URL path as first argument and received nothing."
|
test -n "$path_url" || ynh_die "ynh_normalize_url_path expect a URL path as first argument and received nothing."
|
||||||
if [ "${path_url:0:1}" != "/" ]; then # If the first character is not a /
|
if [ "${path_url:0:1}" != "/" ]; then # If the first character is not a /
|
||||||
path_url="/$path_url" # Add / at begin of path variable
|
path_url="/$path_url" # Add / at begin of path variable
|
||||||
|
@ -29,7 +29,7 @@ ynh_normalize_url_path () {
|
||||||
# usage: ynh_find_port begin_port
|
# usage: ynh_find_port begin_port
|
||||||
# | arg: begin_port - port to start to search
|
# | arg: begin_port - port to start to search
|
||||||
ynh_find_port () {
|
ynh_find_port () {
|
||||||
port=$1
|
local port=$1
|
||||||
test -n "$port" || ynh_die "The argument of ynh_find_port must be a valid port."
|
test -n "$port" || ynh_die "The argument of ynh_find_port must be a valid port."
|
||||||
while netcat -z 127.0.0.1 $port # Check if the port is free
|
while netcat -z 127.0.0.1 $port # Check if the port is free
|
||||||
do
|
do
|
||||||
|
|
|
@ -26,6 +26,8 @@ ynh_package_version() {
|
||||||
|
|
||||||
# APT wrapper for non-interactive operation
|
# APT wrapper for non-interactive operation
|
||||||
#
|
#
|
||||||
|
# [internal]
|
||||||
|
#
|
||||||
# usage: ynh_apt update
|
# usage: ynh_apt update
|
||||||
ynh_apt() {
|
ynh_apt() {
|
||||||
DEBIAN_FRONTEND=noninteractive sudo apt-get -y -qq $@
|
DEBIAN_FRONTEND=noninteractive sudo apt-get -y -qq $@
|
||||||
|
@ -73,6 +75,8 @@ ynh_package_autopurge() {
|
||||||
|
|
||||||
# Build and install a package from an equivs control file
|
# Build and install a package from an equivs control file
|
||||||
#
|
#
|
||||||
|
# [internal]
|
||||||
|
#
|
||||||
# example: generate an empty control file with `equivs-control`, adjust its
|
# example: generate an empty control file with `equivs-control`, adjust its
|
||||||
# content and use helper to build and install the package:
|
# content and use helper to build and install the package:
|
||||||
# ynh_package_install_from_equivs /path/to/controlfile
|
# ynh_package_install_from_equivs /path/to/controlfile
|
||||||
|
@ -80,15 +84,15 @@ ynh_package_autopurge() {
|
||||||
# usage: ynh_package_install_from_equivs controlfile
|
# usage: ynh_package_install_from_equivs controlfile
|
||||||
# | arg: controlfile - path of the equivs control file
|
# | arg: controlfile - path of the equivs control file
|
||||||
ynh_package_install_from_equivs () {
|
ynh_package_install_from_equivs () {
|
||||||
controlfile=$1
|
local controlfile=$1
|
||||||
|
|
||||||
# Check if the equivs package is installed. Or install it.
|
# Check if the equivs package is installed. Or install it.
|
||||||
ynh_package_is_installed 'equivs' \
|
ynh_package_is_installed 'equivs' \
|
||||||
|| ynh_package_install equivs
|
|| ynh_package_install equivs
|
||||||
|
|
||||||
# retrieve package information
|
# retrieve package information
|
||||||
pkgname=$(grep '^Package: ' $controlfile | cut -d' ' -f 2) # Retrieve the name of the debian package
|
local pkgname=$(grep '^Package: ' $controlfile | cut -d' ' -f 2) # Retrieve the name of the debian package
|
||||||
pkgversion=$(grep '^Version: ' $controlfile | cut -d' ' -f 2) # And its version number
|
local pkgversion=$(grep '^Version: ' $controlfile | cut -d' ' -f 2) # And its version number
|
||||||
[[ -z "$pkgname" || -z "$pkgversion" ]] \
|
[[ -z "$pkgname" || -z "$pkgversion" ]] \
|
||||||
&& echo "Invalid control file" && exit 1 # Check if this 2 variables aren't empty.
|
&& echo "Invalid control file" && exit 1 # Check if this 2 variables aren't empty.
|
||||||
|
|
||||||
|
@ -96,7 +100,7 @@ ynh_package_install_from_equivs () {
|
||||||
ynh_package_update
|
ynh_package_update
|
||||||
|
|
||||||
# Build and install the package
|
# Build and install the package
|
||||||
TMPDIR=$(mktemp -d)
|
local TMPDIR=$(mktemp -d)
|
||||||
# Note that the cd executes into a sub shell
|
# Note that the cd executes into a sub shell
|
||||||
# Create a fake deb package with equivs-build and the given control file
|
# Create a fake deb package with equivs-build and the given control file
|
||||||
# Install the fake package without its dependencies with dpkg
|
# Install the fake package without its dependencies with dpkg
|
||||||
|
@ -118,13 +122,17 @@ ynh_package_install_from_equivs () {
|
||||||
# usage: ynh_install_app_dependencies dep [dep [...]]
|
# usage: ynh_install_app_dependencies dep [dep [...]]
|
||||||
# | arg: dep - the package name to install in dependence
|
# | arg: dep - the package name to install in dependence
|
||||||
ynh_install_app_dependencies () {
|
ynh_install_app_dependencies () {
|
||||||
dependencies=$@
|
local dependencies=$@
|
||||||
manifest_path="../manifest.json"
|
local manifest_path="../manifest.json"
|
||||||
if [ ! -e "$manifest_path" ]; then
|
if [ ! -e "$manifest_path" ]; then
|
||||||
manifest_path="../settings/manifest.json" # Into the restore script, the manifest is not at the same place
|
manifest_path="../settings/manifest.json" # Into the restore script, the manifest is not at the same place
|
||||||
fi
|
fi
|
||||||
version=$(grep '\"version\": ' "$manifest_path" | cut -d '"' -f 4) # Retrieve the version number in the manifest file.
|
|
||||||
dep_app=${app//_/-} # Replace all '_' by '-'
|
local version=$(grep '\"version\": ' "$manifest_path" | cut -d '"' -f 4) # Retrieve the version number in the manifest file.
|
||||||
|
if [ ${#version} -eq 0 ]; then
|
||||||
|
version="1.0"
|
||||||
|
fi
|
||||||
|
local dep_app=${app//_/-} # Replace all '_' by '-'
|
||||||
|
|
||||||
cat > /tmp/${dep_app}-ynh-deps.control << EOF # Make a control file for equivs-build
|
cat > /tmp/${dep_app}-ynh-deps.control << EOF # Make a control file for equivs-build
|
||||||
Section: misc
|
Section: misc
|
||||||
|
@ -148,6 +156,6 @@ EOF
|
||||||
#
|
#
|
||||||
# usage: ynh_remove_app_dependencies
|
# usage: ynh_remove_app_dependencies
|
||||||
ynh_remove_app_dependencies () {
|
ynh_remove_app_dependencies () {
|
||||||
dep_app=${app//_/-} # Replace all '_' by '-'
|
local dep_app=${app//_/-} # Replace all '_' by '-'
|
||||||
ynh_package_autopurge ${dep_app}-ynh-deps # Remove the fake package and its dependencies if they not still used.
|
ynh_package_autopurge ${dep_app}-ynh-deps # Remove the fake package and its dependencies if they not still used.
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,18 @@ ynh_die() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Ignore the yunohost-cli log to prevent errors with conditionals commands
|
# Ignore the yunohost-cli log to prevent errors with conditionals commands
|
||||||
|
#
|
||||||
|
# [internal]
|
||||||
|
#
|
||||||
# usage: ynh_no_log COMMAND
|
# usage: ynh_no_log COMMAND
|
||||||
|
#
|
||||||
# Simply duplicate the log, execute the yunohost command and replace the log without the result of this command
|
# Simply duplicate the log, execute the yunohost command and replace the log without the result of this command
|
||||||
# It's a very badly hack...
|
# It's a very badly hack...
|
||||||
ynh_no_log() {
|
ynh_no_log() {
|
||||||
ynh_cli_log=/var/log/yunohost/yunohost-cli.log
|
local ynh_cli_log=/var/log/yunohost/yunohost-cli.log
|
||||||
sudo cp -a ${ynh_cli_log} ${ynh_cli_log}-move
|
sudo cp -a ${ynh_cli_log} ${ynh_cli_log}-move
|
||||||
eval $@
|
eval $@
|
||||||
exit_code=$?
|
local exit_code=$?
|
||||||
sudo mv ${ynh_cli_log}-move ${ynh_cli_log}
|
sudo mv ${ynh_cli_log}-move ${ynh_cli_log}
|
||||||
return $?
|
return $?
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ ynh_replace_string () {
|
||||||
local match_string=$1
|
local match_string=$1
|
||||||
local replace_string=$2
|
local replace_string=$2
|
||||||
local workfile=$3
|
local workfile=$3
|
||||||
|
|
||||||
# Escape the delimiter if it's in the string.
|
# Escape the delimiter if it's in the string.
|
||||||
match_string=${match_string//${delimit}/"\\${delimit}"}
|
match_string=${match_string//${delimit}/"\\${delimit}"}
|
||||||
replace_string=${replace_string//${delimit}/"\\${delimit}"}
|
replace_string=${replace_string//${delimit}/"\\${delimit}"}
|
||||||
|
@ -47,13 +47,13 @@ ynh_replace_special_string () {
|
||||||
local replace_string=$2
|
local replace_string=$2
|
||||||
local workfile=$3
|
local workfile=$3
|
||||||
|
|
||||||
# Escape any backslash to preserve them as simple backslash.
|
# Escape any backslash to preserve them as simple backslash.
|
||||||
match_string=${match_string//\\/"\\\\"}
|
match_string=${match_string//\\/"\\\\"}
|
||||||
replace_string=${replace_string//\\/"\\\\"}
|
replace_string=${replace_string//\\/"\\\\"}
|
||||||
|
|
||||||
# Escape the & character, who has a special function in sed.
|
# Escape the & character, who has a special function in sed.
|
||||||
match_string=${match_string//&/"\&"}
|
match_string=${match_string//&/"\&"}
|
||||||
replace_string=${replace_string//&/"\&"}
|
replace_string=${replace_string//&/"\&"}
|
||||||
|
|
||||||
ynh_replace_string "$match_string" "$replace_string" "$workfile"
|
ynh_replace_string "$match_string" "$replace_string" "$workfile"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
# Manage a fail of the script
|
# Manage a fail of the script
|
||||||
#
|
#
|
||||||
# Print a warning to inform that the script was failed
|
# [internal]
|
||||||
# Execute the ynh_clean_setup function if used in the app script
|
|
||||||
#
|
#
|
||||||
# usage of ynh_clean_setup function
|
# usage:
|
||||||
# This function provide a way to clean some residual of installation that not managed by remove script.
|
# ynh_exit_properly is used only by the helper ynh_abort_if_errors.
|
||||||
# To use it, simply add in your script:
|
# You should not use it directly.
|
||||||
|
# Instead, add to your script:
|
||||||
# ynh_clean_setup () {
|
# ynh_clean_setup () {
|
||||||
# instructions...
|
# instructions...
|
||||||
# }
|
# }
|
||||||
# This function is optionnal.
|
|
||||||
#
|
#
|
||||||
# Usage: ynh_exit_properly is used only by the helper ynh_abort_if_errors.
|
# This function provide a way to clean some residual of installation that not managed by remove script.
|
||||||
# You must not use it directly.
|
#
|
||||||
|
# It prints a warning to inform that the script was failed, and execute the ynh_clean_setup function if used in the app script
|
||||||
|
#
|
||||||
ynh_exit_properly () {
|
ynh_exit_properly () {
|
||||||
exit_code=$?
|
local exit_code=$?
|
||||||
if [ "$exit_code" -eq 0 ]; then
|
if [ "$exit_code" -eq 0 ]; then
|
||||||
exit 0 # Exit without error if the script ended correctly
|
exit 0 # Exit without error if the script ended correctly
|
||||||
fi
|
fi
|
||||||
|
@ -31,13 +32,24 @@ ynh_exit_properly () {
|
||||||
ynh_die # Exit with error status
|
ynh_die # Exit with error status
|
||||||
}
|
}
|
||||||
|
|
||||||
# Exit if an error occurs during the execution of the script.
|
# Exits if an error occurs during the execution of the script.
|
||||||
#
|
#
|
||||||
# Stop immediatly the execution if an error occured or if a empty variable is used.
|
# usage: ynh_abort_if_errors
|
||||||
# The execution of the script is derivate to ynh_exit_properly function before exit.
|
#
|
||||||
|
# This configure the rest of the script execution such that, if an error occurs
|
||||||
|
# or if an empty variable is used, the execution of the script stops
|
||||||
|
# immediately and a call to `ynh_clean_setup` is triggered if it has been
|
||||||
|
# defined by your script.
|
||||||
#
|
#
|
||||||
# Usage: ynh_abort_if_errors
|
|
||||||
ynh_abort_if_errors () {
|
ynh_abort_if_errors () {
|
||||||
set -eu # Exit if a command fail, and if a variable is used unset.
|
set -eu # Exit if a command fail, and if a variable is used unset.
|
||||||
trap ynh_exit_properly EXIT # Capturing exit signals on shell script
|
trap ynh_exit_properly EXIT # Capturing exit signals on shell script
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Fetch the Debian release codename
|
||||||
|
#
|
||||||
|
# usage: ynh_get_debian_release
|
||||||
|
# | ret: The Debian release codename (i.e. jessie, stretch, ...)
|
||||||
|
ynh_get_debian_release () {
|
||||||
|
echo $(lsb_release --codename --short)
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Check if a YunoHost user exists
|
# Check if a YunoHost user exists
|
||||||
#
|
#
|
||||||
# example: ynh_user_exists 'toto' || exit 1
|
# example: ynh_user_exists 'toto' || exit 1
|
||||||
#
|
#
|
||||||
|
@ -31,7 +31,7 @@ ynh_user_list() {
|
||||||
| awk '/^##username$/{getline; print}'
|
| awk '/^##username$/{getline; print}'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if a user exists on the system
|
# Check if a user exists on the system
|
||||||
#
|
#
|
||||||
# usage: ynh_system_user_exists username
|
# usage: ynh_system_user_exists username
|
||||||
# | arg: username - the username to check
|
# | arg: username - the username to check
|
||||||
|
@ -48,9 +48,9 @@ ynh_system_user_create () {
|
||||||
if ! ynh_system_user_exists "$1" # Check if the user exists on the system
|
if ! ynh_system_user_exists "$1" # Check if the user exists on the system
|
||||||
then # If the user doesn't exist
|
then # If the user doesn't exist
|
||||||
if [ $# -ge 2 ]; then # If a home dir is mentioned
|
if [ $# -ge 2 ]; then # If a home dir is mentioned
|
||||||
user_home_dir="-d $2"
|
local user_home_dir="-d $2"
|
||||||
else
|
else
|
||||||
user_home_dir="--no-create-home"
|
local user_home_dir="--no-create-home"
|
||||||
fi
|
fi
|
||||||
sudo useradd $user_home_dir --system --user-group $1 --shell /usr/sbin/nologin || ynh_die "Unable to create $1 system account"
|
sudo useradd $user_home_dir --system --user-group $1 --shell /usr/sbin/nologin || ynh_die "Unable to create $1 system account"
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
# usage: ynh_get_plain_key key [subkey [subsubkey ...]]
|
# usage: ynh_get_plain_key key [subkey [subsubkey ...]]
|
||||||
# | ret: string - the key's value
|
# | ret: string - the key's value
|
||||||
ynh_get_plain_key() {
|
ynh_get_plain_key() {
|
||||||
prefix="#"
|
local prefix="#"
|
||||||
founded=0
|
local founded=0
|
||||||
key=$1
|
local key=$1
|
||||||
shift
|
shift
|
||||||
while read line; do
|
while read line; do
|
||||||
if [[ "$founded" == "1" ]] ; then
|
if [[ "$founded" == "1" ]] ; then
|
||||||
|
@ -36,7 +36,7 @@ ynh_get_plain_key() {
|
||||||
#
|
#
|
||||||
ynh_restore_upgradebackup () {
|
ynh_restore_upgradebackup () {
|
||||||
echo "Upgrade failed." >&2
|
echo "Upgrade failed." >&2
|
||||||
app_bck=${app//_/-} # Replace all '_' by '-'
|
local app_bck=${app//_/-} # Replace all '_' by '-'
|
||||||
|
|
||||||
# Check if an existing backup can be found before removing and restoring the application.
|
# Check if an existing backup can be found before removing and restoring the application.
|
||||||
if sudo yunohost backup list | grep -q $app_bck-pre-upgrade$backup_number
|
if sudo yunohost backup list | grep -q $app_bck-pre-upgrade$backup_number
|
||||||
|
@ -44,7 +44,7 @@ ynh_restore_upgradebackup () {
|
||||||
# Remove the application then restore it
|
# Remove the application then restore it
|
||||||
sudo yunohost app remove $app
|
sudo yunohost app remove $app
|
||||||
# Restore the backup
|
# Restore the backup
|
||||||
sudo yunohost backup restore --ignore-system $app_bck-pre-upgrade$backup_number --apps $app --force
|
sudo yunohost backup restore --ignore-system $app_bck-pre-upgrade$backup_number --apps $app --force --verbose
|
||||||
ynh_die "The app was restored to the way it was before the failed upgrade."
|
ynh_die "The app was restored to the way it was before the failed upgrade."
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
@ -65,8 +65,8 @@ ynh_backup_before_upgrade () {
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
backup_number=1
|
backup_number=1
|
||||||
old_backup_number=2
|
local old_backup_number=2
|
||||||
app_bck=${app//_/-} # Replace all '_' by '-'
|
local app_bck=${app//_/-} # Replace all '_' by '-'
|
||||||
|
|
||||||
# Check if a backup already exists with the prefix 1
|
# Check if a backup already exists with the prefix 1
|
||||||
if sudo yunohost backup list | grep -q $app_bck-pre-upgrade1
|
if sudo yunohost backup list | grep -q $app_bck-pre-upgrade1
|
||||||
|
@ -77,7 +77,7 @@ ynh_backup_before_upgrade () {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create backup
|
# Create backup
|
||||||
sudo BACKUP_CORE_ONLY=1 yunohost backup create --ignore-system --apps $app --name $app_bck-pre-upgrade$backup_number
|
sudo BACKUP_CORE_ONLY=1 yunohost backup create --ignore-system --apps $app --name $app_bck-pre-upgrade$backup_number --verbose
|
||||||
if [ "$?" -eq 0 ]
|
if [ "$?" -eq 0 ]
|
||||||
then
|
then
|
||||||
# If the backup succeeded, remove the previous backup
|
# If the backup succeeded, remove the previous backup
|
||||||
|
@ -94,7 +94,7 @@ ynh_backup_before_upgrade () {
|
||||||
# Download, check integrity, uncompress and patch the source from app.src
|
# Download, check integrity, uncompress and patch the source from app.src
|
||||||
#
|
#
|
||||||
# The file conf/app.src need to contains:
|
# The file conf/app.src need to contains:
|
||||||
#
|
#
|
||||||
# SOURCE_URL=Address to download the app archive
|
# SOURCE_URL=Address to download the app archive
|
||||||
# SOURCE_SUM=Control sum
|
# SOURCE_SUM=Control sum
|
||||||
# # (Optional) Program to check the integrity (sha256sum, md5sum...)
|
# # (Optional) Program to check the integrity (sha256sum, md5sum...)
|
||||||
|
@ -113,9 +113,9 @@ ynh_backup_before_upgrade () {
|
||||||
# Details:
|
# Details:
|
||||||
# This helper downloads sources from SOURCE_URL if there is no local source
|
# This helper downloads sources from SOURCE_URL if there is no local source
|
||||||
# archive in /opt/yunohost-apps-src/APP_ID/SOURCE_FILENAME
|
# archive in /opt/yunohost-apps-src/APP_ID/SOURCE_FILENAME
|
||||||
#
|
#
|
||||||
# Next, it checks the integrity with "SOURCE_SUM_PRG -c --status" command.
|
# Next, it checks the integrity with "SOURCE_SUM_PRG -c --status" command.
|
||||||
#
|
#
|
||||||
# If it's ok, the source archive will be uncompressed in $dest_dir. If the
|
# If it's ok, the source archive will be uncompressed in $dest_dir. If the
|
||||||
# SOURCE_IN_SUBDIR is true, the first level directory of the archive will be
|
# SOURCE_IN_SUBDIR is true, the first level directory of the archive will be
|
||||||
# removed.
|
# removed.
|
||||||
|
@ -130,7 +130,7 @@ ynh_backup_before_upgrade () {
|
||||||
ynh_setup_source () {
|
ynh_setup_source () {
|
||||||
local dest_dir=$1
|
local dest_dir=$1
|
||||||
local src_id=${2:-app} # If the argument is not given, source_id equals "app"
|
local src_id=${2:-app} # If the argument is not given, source_id equals "app"
|
||||||
|
|
||||||
# Load value from configuration file (see above for a small doc about this file
|
# Load value from configuration file (see above for a small doc about this file
|
||||||
# format)
|
# format)
|
||||||
local src_url=$(grep 'SOURCE_URL=' "$YNH_CWD/../conf/${src_id}.src" | cut -d= -f2-)
|
local src_url=$(grep 'SOURCE_URL=' "$YNH_CWD/../conf/${src_id}.src" | cut -d= -f2-)
|
||||||
|
@ -139,7 +139,7 @@ ynh_setup_source () {
|
||||||
local src_format=$(grep 'SOURCE_FORMAT=' "$YNH_CWD/../conf/${src_id}.src" | cut -d= -f2-)
|
local src_format=$(grep 'SOURCE_FORMAT=' "$YNH_CWD/../conf/${src_id}.src" | cut -d= -f2-)
|
||||||
local src_in_subdir=$(grep 'SOURCE_IN_SUBDIR=' "$YNH_CWD/../conf/${src_id}.src" | cut -d= -f2-)
|
local src_in_subdir=$(grep 'SOURCE_IN_SUBDIR=' "$YNH_CWD/../conf/${src_id}.src" | cut -d= -f2-)
|
||||||
local src_filename=$(grep 'SOURCE_FILENAME=' "$YNH_CWD/../conf/${src_id}.src" | cut -d= -f2-)
|
local src_filename=$(grep 'SOURCE_FILENAME=' "$YNH_CWD/../conf/${src_id}.src" | cut -d= -f2-)
|
||||||
|
|
||||||
# Default value
|
# Default value
|
||||||
src_sumprg=${src_sumprg:-sha256sum}
|
src_sumprg=${src_sumprg:-sha256sum}
|
||||||
src_in_subdir=${src_in_subdir:-true}
|
src_in_subdir=${src_in_subdir:-true}
|
||||||
|
@ -216,10 +216,11 @@ ynh_setup_source () {
|
||||||
# | arg: ... - (Optionnal) More POST keys and values
|
# | arg: ... - (Optionnal) More POST keys and values
|
||||||
ynh_local_curl () {
|
ynh_local_curl () {
|
||||||
# Define url of page to curl
|
# Define url of page to curl
|
||||||
full_page_url=https://localhost$path_url$1
|
local full_page_url=https://localhost$path_url$1
|
||||||
|
|
||||||
# Concatenate all other arguments with '&' to prepare POST data
|
# Concatenate all other arguments with '&' to prepare POST data
|
||||||
POST_data=""
|
local POST_data=""
|
||||||
|
local arg=""
|
||||||
for arg in "${@:2}"
|
for arg in "${@:2}"
|
||||||
do
|
do
|
||||||
POST_data="${POST_data}${arg}&"
|
POST_data="${POST_data}${arg}&"
|
||||||
|
|
|
@ -53,6 +53,9 @@ do_pre_regen() {
|
||||||
else
|
else
|
||||||
sudo cp services.yml /etc/yunohost/services.yml
|
sudo cp services.yml /etc/yunohost/services.yml
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$pending_dir"/etc/etckeeper/
|
||||||
|
cp etckeeper.conf "$pending_dir"/etc/etckeeper/
|
||||||
}
|
}
|
||||||
|
|
||||||
_update_services() {
|
_update_services() {
|
||||||
|
|
|
@ -581,5 +581,6 @@ enabled = true
|
||||||
port = http,https
|
port = http,https
|
||||||
protocol = tcp
|
protocol = tcp
|
||||||
filter = yunohost
|
filter = yunohost
|
||||||
logpath = /var/log/nginx*/*error.log
|
logpath = /var/log/nginx/*error.log
|
||||||
|
/var/log/nginx/*access.log
|
||||||
maxretry = 6
|
maxretry = 6
|
||||||
|
|
|
@ -14,8 +14,8 @@
|
||||||
# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
|
# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
|
||||||
# Values: TEXT
|
# Values: TEXT
|
||||||
#
|
#
|
||||||
failregex = helpers.lua:[1-9]+: authenticate\(\): Connection failed for: .*, client: <HOST>
|
failregex = helpers.lua:[0-9]+: authenticate\(\): Connection failed for: .*, client: <HOST>
|
||||||
^<HOST> -.*\"POST /yunohost/api/login HTTP/1.1\" 401 22
|
^<HOST> -.*\"POST /yunohost/api/login HTTP/1.1\" 401
|
||||||
|
|
||||||
# Option: ignoreregex
|
# Option: ignoreregex
|
||||||
# Notes.: regex to ignore. If this regex matches, the line is ignored.
|
# Notes.: regex to ignore. If this regex matches, the line is ignored.
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
server_tokens off;
|
server_tokens off;
|
||||||
|
gzip_types text/css text/javascript application/javascript;
|
||||||
|
|
|
@ -36,8 +36,18 @@ server {
|
||||||
# Uncomment the following directive after DH generation
|
# Uncomment the following directive after DH generation
|
||||||
# > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048
|
# > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048
|
||||||
#ssl_dhparam /etc/ssl/private/dh2048.pem;
|
#ssl_dhparam /etc/ssl/private/dh2048.pem;
|
||||||
|
|
||||||
add_header Strict-Transport-Security "max-age=31536000;";
|
# Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners
|
||||||
|
# https://wiki.mozilla.org/Security/Guidelines/Web_Security
|
||||||
|
# https://observatory.mozilla.org/
|
||||||
|
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
|
||||||
|
add_header 'Referrer-Policy' 'same-origin';
|
||||||
|
add_header Content-Security-Policy "upgrade-insecure-requests; object-src 'none'; script-src https: 'unsafe-eval'";
|
||||||
|
add_header X-Content-Type-Options nosniff;
|
||||||
|
add_header X-XSS-Protection "1; mode=block";
|
||||||
|
add_header X-Download-Options noopen;
|
||||||
|
add_header X-Permitted-Cross-Domain-Policies none;
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN";
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
return 302 https://$http_host/yunohost/admin;
|
return 302 https://$http_host/yunohost/admin;
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
location /yunohost/admin {
|
# Avoid the nginx path/alias traversal weakness ( #1037 )
|
||||||
|
rewrite ^/yunohost/admin$ /yunohost/admin/ permanent;
|
||||||
|
|
||||||
|
location /yunohost/admin/ {
|
||||||
alias /usr/share/yunohost/admin/;
|
alias /usr/share/yunohost/admin/;
|
||||||
default_type text/html;
|
default_type text/html;
|
||||||
index index.html;
|
index index.html;
|
||||||
|
|
|
@ -42,7 +42,16 @@ server {
|
||||||
# > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048
|
# > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048
|
||||||
#ssl_dhparam /etc/ssl/private/dh2048.pem;
|
#ssl_dhparam /etc/ssl/private/dh2048.pem;
|
||||||
|
|
||||||
add_header Strict-Transport-Security "max-age=31536000;";
|
# Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners
|
||||||
|
# https://wiki.mozilla.org/Security/Guidelines/Web_Security
|
||||||
|
# https://observatory.mozilla.org/
|
||||||
|
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
|
||||||
|
add_header Content-Security-Policy "upgrade-insecure-requests; object-src 'none'; script-src https: 'unsafe-eval'";
|
||||||
|
add_header X-Content-Type-Options nosniff;
|
||||||
|
add_header X-XSS-Protection "1; mode=block";
|
||||||
|
add_header X-Download-Options noopen;
|
||||||
|
add_header X-Permitted-Cross-Domain-Policies none;
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN";
|
||||||
|
|
||||||
access_by_lua_file /usr/share/ssowat/access.lua;
|
access_by_lua_file /usr/share/ssowat/access.lua;
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,9 @@ PrintLastLog yes
|
||||||
TCPKeepAlive yes
|
TCPKeepAlive yes
|
||||||
#UseLogin no
|
#UseLogin no
|
||||||
|
|
||||||
|
# keep ssh sessions fresh
|
||||||
|
ClientAliveInterval 60
|
||||||
|
|
||||||
#MaxStartups 10:30:60
|
#MaxStartups 10:30:60
|
||||||
Banner /etc/issue.net
|
Banner /etc/issue.net
|
||||||
|
|
||||||
|
|
43
data/templates/yunohost/etckeeper.conf
Normal file
43
data/templates/yunohost/etckeeper.conf
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# The VCS to use.
|
||||||
|
#VCS="hg"
|
||||||
|
VCS="git"
|
||||||
|
#VCS="bzr"
|
||||||
|
#VCS="darcs"
|
||||||
|
|
||||||
|
# Options passed to git commit when run by etckeeper.
|
||||||
|
GIT_COMMIT_OPTIONS="--quiet"
|
||||||
|
|
||||||
|
# Options passed to hg commit when run by etckeeper.
|
||||||
|
HG_COMMIT_OPTIONS=""
|
||||||
|
|
||||||
|
# Options passed to bzr commit when run by etckeeper.
|
||||||
|
BZR_COMMIT_OPTIONS=""
|
||||||
|
|
||||||
|
# Options passed to darcs record when run by etckeeper.
|
||||||
|
DARCS_COMMIT_OPTIONS="-a"
|
||||||
|
|
||||||
|
# Uncomment to avoid etckeeper committing existing changes
|
||||||
|
# to /etc automatically once per day.
|
||||||
|
#AVOID_DAILY_AUTOCOMMITS=1
|
||||||
|
|
||||||
|
# Uncomment the following to avoid special file warning
|
||||||
|
# (the option is enabled automatically by cronjob regardless).
|
||||||
|
#AVOID_SPECIAL_FILE_WARNING=1
|
||||||
|
|
||||||
|
# Uncomment to avoid etckeeper committing existing changes to
|
||||||
|
# /etc before installation. It will cancel the installation,
|
||||||
|
# so you can commit the changes by hand.
|
||||||
|
#AVOID_COMMIT_BEFORE_INSTALL=1
|
||||||
|
|
||||||
|
# The high-level package manager that's being used.
|
||||||
|
# (apt, pacman-g2, yum, zypper etc)
|
||||||
|
HIGHLEVEL_PACKAGE_MANAGER=apt
|
||||||
|
|
||||||
|
# The low-level package manager that's being used.
|
||||||
|
# (dpkg, rpm, pacman, pacman-g2, etc)
|
||||||
|
LOWLEVEL_PACKAGE_MANAGER=dpkg
|
||||||
|
|
||||||
|
# To push each commit to a remote, put the name of the remote here.
|
||||||
|
# (eg, "origin" for git). Space-separated lists of multiple remotes
|
||||||
|
# also work (eg, "origin gitlab github" for git).
|
||||||
|
PUSH_REMOTE=""
|
63
debian/changelog
vendored
63
debian/changelog
vendored
|
@ -1,3 +1,66 @@
|
||||||
|
yunohost (2.7.10) stable; urgency=low
|
||||||
|
|
||||||
|
* [fix] Fail2ban conf/filter was not matching failed login attempts...
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Wed, 07 Mar 2018 12:43:35 +0000
|
||||||
|
|
||||||
|
yunohost (2.7.9) stable; urgency=low
|
||||||
|
|
||||||
|
(Bumping version number for stable release)
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Tue, 30 Jan 2018 17:42:00 +0000
|
||||||
|
|
||||||
|
yunohost (2.7.8) testing; urgency=low
|
||||||
|
|
||||||
|
* [fix] Use HMAC-SHA512 for DynDNS TSIG
|
||||||
|
* [fix] Fix ynh_restore_upgradebackup
|
||||||
|
* [i18n] Improve french translation
|
||||||
|
|
||||||
|
Thanks to all contributors (Bram, Maniack, jibec, Aleks) ! <3
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Wed, 24 Jan 2018 12:15:12 -0500
|
||||||
|
|
||||||
|
yunohost (2.7.7) stable; urgency=low
|
||||||
|
|
||||||
|
(Bumping version number for stable release)
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Thu, 18 Jan 2018 17:45:21 -0500
|
||||||
|
|
||||||
|
yunohost (2.7.6.1) testing; urgency=low
|
||||||
|
|
||||||
|
* [fix] Fix Meltdown diagnosis
|
||||||
|
* [fix] Improve error handling of 'nginx -t' and Metdown diagnosis
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Wed, 17 Jan 2018 13:11:02 -0500
|
||||||
|
|
||||||
|
yunohost (2.7.6) testing; urgency=low
|
||||||
|
|
||||||
|
Major changes:
|
||||||
|
|
||||||
|
* [enh] Add new api entry point to check for Meltdown vulnerability
|
||||||
|
* [enh] New command 'app change-label'
|
||||||
|
|
||||||
|
Misc fixes/improvements:
|
||||||
|
|
||||||
|
* [helpers] Fix upgrade of fake package
|
||||||
|
* [helpers] Fix ynh_use_logrotate
|
||||||
|
* [helpers] Fix broken ynh_replace_string
|
||||||
|
* [helpers] Use local variables
|
||||||
|
* [enh/fix] Save the conf/ directory of app during installation and upgrade
|
||||||
|
* [enh] Improve UX for app messages
|
||||||
|
* [enh] Keep SSH sessions alive
|
||||||
|
* [enh] --version now display stable/testing/unstable information
|
||||||
|
* [enh] Backup: add ability to symlink the archives dir
|
||||||
|
* [enh] Add regen-conf messages, nginx -t and backports .deb to diagnosis output
|
||||||
|
* [fix] Comment line syntax for DNS zone recommendation (use ';')
|
||||||
|
* [fix] Fix a bug in disk diagnosis
|
||||||
|
* [mod] Use systemctl for all service operations
|
||||||
|
* [i18n] Improved Spanish and French translations
|
||||||
|
|
||||||
|
Thanks to all contributors (Maniack, Josue, Bram, ljf, Aleks, Jocelyn, JimboeJoe, David B, Lapineige, ...) ! <3
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Tue, 16 Jan 2018 17:17:34 -0500
|
||||||
|
|
||||||
yunohost (2.7.5) stable; urgency=low
|
yunohost (2.7.5) stable; urgency=low
|
||||||
|
|
||||||
(Bumping version number for stable release)
|
(Bumping version number for stable release)
|
||||||
|
|
2
debian/control
vendored
2
debian/control
vendored
|
@ -18,7 +18,7 @@ Depends: ${python:Depends}, ${misc:Depends}
|
||||||
, ca-certificates, netcat-openbsd, iproute
|
, ca-certificates, netcat-openbsd, iproute
|
||||||
, mariadb-server | mysql-server, php5-mysql | php5-mysqlnd
|
, mariadb-server | mysql-server, php5-mysql | php5-mysqlnd
|
||||||
, slapd, ldap-utils, sudo-ldap, libnss-ldapd, nscd
|
, slapd, ldap-utils, sudo-ldap, libnss-ldapd, nscd
|
||||||
, postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, procmail
|
, postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, procmail, mailutils
|
||||||
, dovecot-ldap, dovecot-lmtpd, dovecot-managesieved
|
, dovecot-ldap, dovecot-lmtpd, dovecot-managesieved
|
||||||
, dovecot-antispam, fail2ban
|
, dovecot-antispam, fail2ban
|
||||||
, nginx-extras (>=1.6.2), php5-fpm, php5-ldap, php5-intl
|
, nginx-extras (>=1.6.2), php5-fpm, php5-ldap, php5-intl
|
||||||
|
|
2
locales/ar.json
Normal file
2
locales/ar.json
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
{
|
||||||
|
}
|
|
@ -14,26 +14,29 @@
|
||||||
"app_change_url_identical_domains": "The old and new domain/url_path are identical ('{domain:s}{path:s}'), nothing to do.",
|
"app_change_url_identical_domains": "The old and new domain/url_path are identical ('{domain:s}{path:s}'), nothing to do.",
|
||||||
"app_change_url_no_script": "This application '{app_name:s}' doesn't support url modification yet. Maybe you should upgrade the application.",
|
"app_change_url_no_script": "This application '{app_name:s}' doesn't support url modification yet. Maybe you should upgrade the application.",
|
||||||
"app_change_url_success": "Successfully changed {app:s} url to {domain:s}{path:s}",
|
"app_change_url_success": "Successfully changed {app:s} url to {domain:s}{path:s}",
|
||||||
|
"app_checkurl_is_deprecated": "Packagers /!\\ 'app checkurl' is deprecated ! Please use 'app register-url' instead !",
|
||||||
"app_extraction_failed": "Unable to extract installation files",
|
"app_extraction_failed": "Unable to extract installation files",
|
||||||
"app_id_invalid": "Invalid app id",
|
"app_id_invalid": "Invalid app id",
|
||||||
"app_incompatible": "The app is incompatible with your YunoHost version",
|
"app_incompatible": "The app {app} is incompatible with your YunoHost version",
|
||||||
"app_install_files_invalid": "Invalid installation files",
|
"app_install_files_invalid": "Invalid installation files",
|
||||||
"app_location_already_used": "An app is already installed in this location",
|
"app_location_already_used": "The app '{app}' is already installed on that location ({path})",
|
||||||
"app_location_install_failed": "Unable to install the app in this location",
|
"app_make_default_location_already_used": "Can't make the app '{app}' the default on the domain {domain} is already used by the other app '{other_app}'",
|
||||||
|
"app_location_install_failed": "Unable to install the app in this location because it conflit with the app '{other_app}' already installed on '{other_path}'",
|
||||||
"app_location_unavailable": "This url is not available or conflicts with an already installed app",
|
"app_location_unavailable": "This url is not available or conflicts with an already installed app",
|
||||||
"app_manifest_invalid": "Invalid app manifest: {error}",
|
"app_manifest_invalid": "Invalid app manifest: {error}",
|
||||||
"app_no_upgrade": "No app to upgrade",
|
"app_no_upgrade": "No app to upgrade",
|
||||||
"app_not_correctly_installed": "{app:s} seems to be incorrectly installed",
|
"app_not_correctly_installed": "{app:s} seems to be incorrectly installed",
|
||||||
"app_not_installed": "{app:s} is not installed",
|
"app_not_installed": "{app:s} is not installed",
|
||||||
"app_not_properly_removed": "{app:s} has not been properly removed",
|
"app_not_properly_removed": "{app:s} has not been properly removed",
|
||||||
"app_package_need_update": "The app package needs to be updated to follow YunoHost changes",
|
"app_package_need_update": "The app {app} package needs to be updated to follow YunoHost changes",
|
||||||
"app_removed": "{app:s} has been removed",
|
"app_removed": "{app:s} has been removed",
|
||||||
"app_requirements_checking": "Checking required packages...",
|
"app_requirements_checking": "Checking required packages for {app}...",
|
||||||
"app_requirements_failed": "Unable to meet requirements: {error}",
|
"app_requirements_failed": "Unable to meet requirements for {app}: {error}",
|
||||||
"app_requirements_unmeet": "Requirements are not met, the package {pkgname} ({version}) must be {spec}",
|
"app_requirements_unmeet": "Requirements are not met for {app}, the package {pkgname} ({version}) must be {spec}",
|
||||||
"app_sources_fetch_failed": "Unable to fetch sources files",
|
"app_sources_fetch_failed": "Unable to fetch sources files",
|
||||||
"app_unknown": "Unknown app",
|
"app_unknown": "Unknown app",
|
||||||
"app_unsupported_remote_type": "Unsupported remote type used for the app",
|
"app_unsupported_remote_type": "Unsupported remote type used for the app",
|
||||||
|
"app_upgrade_app_name": "Upgrading app {app}...",
|
||||||
"app_upgrade_failed": "Unable to upgrade {app:s}",
|
"app_upgrade_failed": "Unable to upgrade {app:s}",
|
||||||
"app_upgrade_some_app_failed": "Unable to upgrade some applications",
|
"app_upgrade_some_app_failed": "Unable to upgrade some applications",
|
||||||
"app_upgraded": "{app:s} has been upgraded",
|
"app_upgraded": "{app:s} has been upgraded",
|
||||||
|
@ -99,6 +102,7 @@
|
||||||
"backup_output_directory_forbidden": "Forbidden output directory. Backups can't be created in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var or /home/yunohost.backup/archives sub-folders",
|
"backup_output_directory_forbidden": "Forbidden output directory. Backups can't be created in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var or /home/yunohost.backup/archives sub-folders",
|
||||||
"backup_output_directory_not_empty": "The output directory is not empty",
|
"backup_output_directory_not_empty": "The output directory is not empty",
|
||||||
"backup_output_directory_required": "You must provide an output directory for the backup",
|
"backup_output_directory_required": "You must provide an output directory for the backup",
|
||||||
|
"backup_output_symlink_dir_broken": "You have a broken symlink instead of your archives directory '{path:s}'. You may have a specific setup to backup your data on an other filesystem, in this case you probably forgot to remount or plug your hard dirve or usb key.",
|
||||||
"backup_running_app_script": "Running backup script of app '{app:s}'...",
|
"backup_running_app_script": "Running backup script of app '{app:s}'...",
|
||||||
"backup_running_hooks": "Running backup hooks...",
|
"backup_running_hooks": "Running backup hooks...",
|
||||||
"backup_system_part_failed": "Unable to backup the '{part:s}' system part",
|
"backup_system_part_failed": "Unable to backup the '{part:s}' system part",
|
||||||
|
@ -210,6 +214,14 @@
|
||||||
"mailbox_used_space_dovecot_down": "Dovecot mailbox service need to be up, if you want to get mailbox used space",
|
"mailbox_used_space_dovecot_down": "Dovecot mailbox service need to be up, if you want to get mailbox used space",
|
||||||
"maindomain_change_failed": "Unable to change the main domain",
|
"maindomain_change_failed": "Unable to change the main domain",
|
||||||
"maindomain_changed": "The main domain has been changed",
|
"maindomain_changed": "The main domain has been changed",
|
||||||
|
"migrate_tsig_end": "Migration to hmac-sha512 finished",
|
||||||
|
"migrate_tsig_failed": "Migrating the dyndns domain {domain} to hmac-sha512 failed, rolling back. Error: {error_code} - {error}",
|
||||||
|
"migrate_tsig_start": "Not secure enough key algorithm detected for TSIG signature of domain '{domain}', initiating migration to the more secure one hmac-sha512",
|
||||||
|
"migrate_tsig_wait": "Let's wait 3min for the dyndns server to take the new key into account...",
|
||||||
|
"migrate_tsig_wait_2": "2min...",
|
||||||
|
"migrate_tsig_wait_3": "1min...",
|
||||||
|
"migrate_tsig_wait_4": "30 secondes...",
|
||||||
|
"migrate_tsig_not_needed": "You do not appear to use a dyndns domain, so no migration is needed !",
|
||||||
"migrations_backward": "Migrating backward.",
|
"migrations_backward": "Migrating backward.",
|
||||||
"migrations_bad_value_for_target": "Invalide number for target argument, available migrations numbers are 0 or {}",
|
"migrations_bad_value_for_target": "Invalide number for target argument, available migrations numbers are 0 or {}",
|
||||||
"migrations_cant_reach_migration_file": "Can't access migrations files at path %s",
|
"migrations_cant_reach_migration_file": "Can't access migrations files at path %s",
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
"app_install_files_invalid": "Los archivos de instalación no son válidos",
|
"app_install_files_invalid": "Los archivos de instalación no son válidos",
|
||||||
"app_location_already_used": "Una aplicación ya está instalada en esta localización",
|
"app_location_already_used": "Una aplicación ya está instalada en esta localización",
|
||||||
"app_location_install_failed": "No se puede instalar la aplicación en esta localización",
|
"app_location_install_failed": "No se puede instalar la aplicación en esta localización",
|
||||||
"app_manifest_invalid": "El manifiesto de la aplicación no es válido",
|
"app_manifest_invalid": "El manifiesto de la aplicación no es válido: {error}",
|
||||||
"app_no_upgrade": "No hay aplicaciones para actualizar",
|
"app_no_upgrade": "No hay aplicaciones para actualizar",
|
||||||
"app_not_correctly_installed": "La aplicación {app:s} 8 parece estar incorrectamente instalada",
|
"app_not_correctly_installed": "La aplicación {app:s} 8 parece estar incorrectamente instalada",
|
||||||
"app_not_installed": "{app:s} 9 no está instalada",
|
"app_not_installed": "{app:s} 9 no está instalada",
|
||||||
|
@ -29,9 +29,9 @@
|
||||||
"app_unsupported_remote_type": "Tipo remoto no soportado por la aplicación",
|
"app_unsupported_remote_type": "Tipo remoto no soportado por la aplicación",
|
||||||
"app_upgrade_failed": "No se pudo actualizar la aplicación {app:s}",
|
"app_upgrade_failed": "No se pudo actualizar la aplicación {app:s}",
|
||||||
"app_upgraded": "{app:s} ha sido actualizada",
|
"app_upgraded": "{app:s} ha sido actualizada",
|
||||||
"appslist_fetched": "Lista de aplicaciones ha sido descargada",
|
"appslist_fetched": "La lista de aplicaciones {appslist:s} ha sido descargada",
|
||||||
"appslist_removed": "La lista de aplicaciones ha sido eliminada",
|
"appslist_removed": "La lista de aplicaciones {appslist:s} ha sido eliminada",
|
||||||
"appslist_retrieve_error": "No se pudo recuperar la lista remota de aplicaciones: {error}",
|
"appslist_retrieve_error": "No se pudo recuperar la lista remota de aplicaciones {appslist:s} : {error}",
|
||||||
"appslist_unknown": "Lista de aplicaciones desconocida",
|
"appslist_unknown": "Lista de aplicaciones desconocida",
|
||||||
"ask_current_admin_password": "Contraseña administrativa actual",
|
"ask_current_admin_password": "Contraseña administrativa actual",
|
||||||
"ask_email": "Dirección de correo electrónico",
|
"ask_email": "Dirección de correo electrónico",
|
||||||
|
@ -272,7 +272,7 @@
|
||||||
"certmanager_acme_not_configured_for_domain": "El certificado para el dominio {domain:s} no parece instalado correctamente. Ejecute primero cert-install para este dominio.",
|
"certmanager_acme_not_configured_for_domain": "El certificado para el dominio {domain:s} no parece instalado correctamente. Ejecute primero cert-install para este dominio.",
|
||||||
"certmanager_http_check_timeout": "Plazo expirado, el servidor no ha podido contactarse a si mismo a través de HTTP usando su dirección IP pública (dominio {domain:s} con ip {ip:s}). Puede ser debido a hairpinning o a una mala configuración del cortafuego/router al que está conectado su servidor.",
|
"certmanager_http_check_timeout": "Plazo expirado, el servidor no ha podido contactarse a si mismo a través de HTTP usando su dirección IP pública (dominio {domain:s} con ip {ip:s}). Puede ser debido a hairpinning o a una mala configuración del cortafuego/router al que está conectado su servidor.",
|
||||||
"certmanager_couldnt_fetch_intermediate_cert": "Plazo expirado, no se ha podido descargar el certificado intermedio de Let's Encrypt. La instalación/renovación del certificado ha sido cancelada - vuelva a intentarlo más tarde.",
|
"certmanager_couldnt_fetch_intermediate_cert": "Plazo expirado, no se ha podido descargar el certificado intermedio de Let's Encrypt. La instalación/renovación del certificado ha sido cancelada - vuelva a intentarlo más tarde.",
|
||||||
"appslist_retrieve_bad_format": "El archivo recuperado no es una lista de aplicaciones válida",
|
"appslist_retrieve_bad_format": "El archivo obtenido para la lista de aplicaciones {appslist:s} no es válido",
|
||||||
"domain_hostname_failed": "Error al establecer nuevo nombre de host",
|
"domain_hostname_failed": "Error al establecer nuevo nombre de host",
|
||||||
"yunohost_ca_creation_success": "Se ha creado la autoridad de certificación local.",
|
"yunohost_ca_creation_success": "Se ha creado la autoridad de certificación local.",
|
||||||
"app_already_installed_cant_change_url": "Esta aplicación ya está instalada. No se puede cambiar el URL únicamente mediante esta función. Compruebe si está disponible la opción 'app changeurl'.",
|
"app_already_installed_cant_change_url": "Esta aplicación ya está instalada. No se puede cambiar el URL únicamente mediante esta función. Compruebe si está disponible la opción 'app changeurl'.",
|
||||||
|
@ -283,10 +283,11 @@
|
||||||
"app_change_url_success": "El URL de la aplicación {app:s} ha sido cambiado correctamente a {domain:s} {path:s}",
|
"app_change_url_success": "El URL de la aplicación {app:s} ha sido cambiado correctamente a {domain:s} {path:s}",
|
||||||
"app_location_unavailable": "Este URL no está disponible o está en conflicto con otra aplicación instalada.",
|
"app_location_unavailable": "Este URL no está disponible o está en conflicto con otra aplicación instalada.",
|
||||||
"app_already_up_to_date": "La aplicación {app:s} ya está actualizada",
|
"app_already_up_to_date": "La aplicación {app:s} ya está actualizada",
|
||||||
"appslist_name_already_tracked": "Ya existe una lista de aplicaciones registrada con nombre {name:s}.",
|
"appslist_name_already_tracked": "Ya existe una lista de aplicaciones registrada con el nombre {name:s}.",
|
||||||
"appslist_url_already_tracked": "Ya existe una lista de aplicaciones registrada con el URL {url:s}.",
|
"appslist_url_already_tracked": "Ya existe una lista de aplicaciones registrada con el URL {url:s}.",
|
||||||
"appslist_migrating": "Migrando la lista de aplicaciones {applist:s} ...",
|
"appslist_migrating": "Migrando la lista de aplicaciones {applist:s} ...",
|
||||||
"appslist_could_not_migrate": "No se pudo migrar la lista de aplicaciones {appslist:s}! No se pudo analizar el URL ... El antiguo cronjob se ha mantenido en {bkp_file:s}.",
|
"appslist_could_not_migrate": "No se pudo migrar la lista de aplicaciones {appslist:s}! No se pudo analizar el URL ... El antiguo cronjob se ha mantenido en {bkp_file:s}.",
|
||||||
"appslist_corrupted_json": "No se pudieron cargar las listas de aplicaciones. Parece que {filename: s} está dañado.",
|
"appslist_corrupted_json": "No se pudieron cargar las listas de aplicaciones. Parece que {filename: s} está dañado.",
|
||||||
"invalid_url_format": "Formato de URL no válido"
|
"invalid_url_format": "Formato de URL no válido",
|
||||||
|
"app_upgrade_some_app_failed": "No se pudieron actualizar algunas aplicaciones"
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,21 +10,21 @@
|
||||||
"app_argument_required": "Le paramètre « {name:s} » est requis",
|
"app_argument_required": "Le paramètre « {name:s} » est requis",
|
||||||
"app_extraction_failed": "Impossible d'extraire les fichiers d'installation",
|
"app_extraction_failed": "Impossible d'extraire les fichiers d'installation",
|
||||||
"app_id_invalid": "Id d'application incorrect",
|
"app_id_invalid": "Id d'application incorrect",
|
||||||
"app_incompatible": "L'application est incompatible avec votre version de YunoHost",
|
"app_incompatible": "L'application {app} est incompatible avec votre version de YunoHost",
|
||||||
"app_install_files_invalid": "Fichiers d'installation incorrects",
|
"app_install_files_invalid": "Fichiers d'installation incorrects",
|
||||||
"app_location_already_used": "Une application est déjà installée à cet emplacement",
|
"app_location_already_used": "L'application '{app}' est déjà installée à cet emplacement ({path})",
|
||||||
"app_location_install_failed": "Impossible d'installer l'application à cet emplacement",
|
"app_location_install_failed": "Impossible d'installer l'application à cet emplacement pour cause de conflit avec l'app '{other_app}' déjà installée sur '{other_path}'",
|
||||||
"app_manifest_invalid": "Manifeste d'application incorrect : {error}",
|
"app_manifest_invalid": "Manifeste d'application incorrect : {error}",
|
||||||
"app_no_upgrade": "Aucune application à mettre à jour",
|
"app_no_upgrade": "Aucune application à mettre à jour",
|
||||||
"app_not_correctly_installed": "{app:s} semble être mal installé",
|
"app_not_correctly_installed": "{app:s} semble être mal installé",
|
||||||
"app_not_installed": "{app:s} n'est pas installé",
|
"app_not_installed": "{app:s} n'est pas installé",
|
||||||
"app_not_properly_removed": "{app:s} n'a pas été supprimé correctement",
|
"app_not_properly_removed": "{app:s} n'a pas été supprimé correctement",
|
||||||
"app_package_need_update": "Le paquet de l'application doit être mis à jour pour suivre les changements de YunoHost",
|
"app_package_need_update": "Le paquet de l'application {app} doit être mis à jour pour suivre les changements de YunoHost",
|
||||||
"app_recent_version_required": "{app:s} nécessite une version plus récente de YunoHost",
|
"app_recent_version_required": "{app:s} nécessite une version plus récente de YunoHost",
|
||||||
"app_removed": "{app:s} a été supprimé",
|
"app_removed": "{app:s} a été supprimé",
|
||||||
"app_requirements_checking": "Vérification des paquets requis...",
|
"app_requirements_checking": "Vérification des paquets requis pour {app}...",
|
||||||
"app_requirements_failed": "Impossible de satisfaire les pré-requis : {error}",
|
"app_requirements_failed": "Impossible de satisfaire les pré-requis pour {app} : {error}",
|
||||||
"app_requirements_unmeet": "Les pré-requis ne sont pas satisfaits, le paquet {pkgname} ({version}) doit être {spec}",
|
"app_requirements_unmeet": "Les pré-requis de {app} ne sont pas satisfaits, le paquet {pkgname} ({version}) doit être {spec}",
|
||||||
"app_sources_fetch_failed": "Impossible de récupérer les fichiers sources",
|
"app_sources_fetch_failed": "Impossible de récupérer les fichiers sources",
|
||||||
"app_unknown": "Application inconnue",
|
"app_unknown": "Application inconnue",
|
||||||
"app_unsupported_remote_type": "Le type distant utilisé par l'application n'est pas supporté",
|
"app_unsupported_remote_type": "Le type distant utilisé par l'application n'est pas supporté",
|
||||||
|
@ -367,5 +367,16 @@
|
||||||
"app_upgrade_some_app_failed": "Impossible de mettre à jour certaines applications",
|
"app_upgrade_some_app_failed": "Impossible de mettre à jour certaines applications",
|
||||||
"ask_path": "Chemin",
|
"ask_path": "Chemin",
|
||||||
"dyndns_could_not_check_provide": "Impossible de vérifier si {provider:s} peut fournir {domain:s}.",
|
"dyndns_could_not_check_provide": "Impossible de vérifier si {provider:s} peut fournir {domain:s}.",
|
||||||
"dyndns_domain_not_provided": "Le fournisseur Dyndns {provider:s} ne peut pas fournir le domaine {domain:s}."
|
"dyndns_domain_not_provided": "Le fournisseur Dyndns {provider:s} ne peut pas fournir le domaine {domain:s}.",
|
||||||
|
"app_make_default_location_already_used": "Impossible de configurer l'app '{app}' par défaut pour le domaine {domain}, déjà utilisé par l'autre app '{other_app}'",
|
||||||
|
"app_upgrade_app_name": "Mise à jour de l'application {app}...",
|
||||||
|
"backup_output_symlink_dir_broken": "Vous avez un lien symbolique cassé à la place de votre dossier d’archives « {path:s} ». Vous pourriez avoir une configuration personnalisée pour sauvegarder vos données sur un autre système de fichiers, dans ce cas, vous avez probablement oublié de monter ou de connecter votre disque / clef USB.",
|
||||||
|
"migrate_tsig_end": "La migration à hmac-sha512 est terminée",
|
||||||
|
"migrate_tsig_failed": "La migration du domaine dyndns {domain} à hmac-sha512 a échoué, annulation des modifications. Erreur : {error_code} - {error}",
|
||||||
|
"migrate_tsig_start": "L’algorithme de génération des clefs n’est pas suffisamment sécurisé pour la signature TSIG du domaine « {domain} », lancement de la migration vers hmac-sha512 qui est plus sécurisé",
|
||||||
|
"migrate_tsig_wait": "Attendons 3 minutes pour que le serveur dyndns prenne en compte la nouvelle clef…",
|
||||||
|
"migrate_tsig_wait_2": "2 minutes…",
|
||||||
|
"migrate_tsig_wait_3": "1 minute…",
|
||||||
|
"migrate_tsig_wait_4": "30 secondes…",
|
||||||
|
"migrate_tsig_not_needed": "Il ne semble pas que vous utilisez un domaine dyndns, donc aucune migration n’est nécessaire !"
|
||||||
}
|
}
|
||||||
|
|
|
@ -330,6 +330,9 @@ def app_info(app, show_status=False, raw=False):
|
||||||
if not _is_installed(app):
|
if not _is_installed(app):
|
||||||
raise MoulinetteError(errno.EINVAL,
|
raise MoulinetteError(errno.EINVAL,
|
||||||
m18n.n('app_not_installed', app=app))
|
m18n.n('app_not_installed', app=app))
|
||||||
|
|
||||||
|
app_setting_path = APPS_SETTING_PATH + app
|
||||||
|
|
||||||
if raw:
|
if raw:
|
||||||
ret = app_list(filter=app, raw=True)[app]
|
ret = app_list(filter=app, raw=True)[app]
|
||||||
ret['settings'] = _get_app_settings(app)
|
ret['settings'] = _get_app_settings(app)
|
||||||
|
@ -345,11 +348,10 @@ def app_info(app, show_status=False, raw=False):
|
||||||
upgradable = "no"
|
upgradable = "no"
|
||||||
|
|
||||||
ret['upgradable'] = upgradable
|
ret['upgradable'] = upgradable
|
||||||
|
ret['change_url'] = os.path.exists(os.path.join(app_setting_path, "scripts", "change_url"))
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
app_setting_path = APPS_SETTING_PATH + app
|
|
||||||
|
|
||||||
# Retrieve manifest and status
|
# Retrieve manifest and status
|
||||||
with open(app_setting_path + '/manifest.json') as f:
|
with open(app_setting_path + '/manifest.json') as f:
|
||||||
manifest = json.loads(str(f.read()))
|
manifest = json.loads(str(f.read()))
|
||||||
|
@ -430,7 +432,7 @@ def app_change_url(auth, app, domain, path):
|
||||||
path -- New path at which the application will be move
|
path -- New path at which the application will be move
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from yunohost.hook import hook_exec
|
from yunohost.hook import hook_exec, hook_callback
|
||||||
|
|
||||||
installed = _is_installed(app)
|
installed = _is_installed(app)
|
||||||
if not installed:
|
if not installed:
|
||||||
|
@ -483,6 +485,12 @@ def app_change_url(auth, app, domain, path):
|
||||||
shutil.copytree(os.path.join(APPS_SETTING_PATH, app, "scripts"),
|
shutil.copytree(os.path.join(APPS_SETTING_PATH, app, "scripts"),
|
||||||
os.path.join(APP_TMP_FOLDER, "scripts"))
|
os.path.join(APP_TMP_FOLDER, "scripts"))
|
||||||
|
|
||||||
|
if os.path.exists(os.path.join(APP_TMP_FOLDER, "conf")):
|
||||||
|
shutil.rmtree(os.path.join(APP_TMP_FOLDER, "conf"))
|
||||||
|
|
||||||
|
shutil.copytree(os.path.join(APPS_SETTING_PATH, app, "conf"),
|
||||||
|
os.path.join(APP_TMP_FOLDER, "conf"))
|
||||||
|
|
||||||
# Execute App change_url script
|
# Execute App change_url script
|
||||||
os.system('chown -R admin: %s' % INSTALL_TMP)
|
os.system('chown -R admin: %s' % INSTALL_TMP)
|
||||||
os.system('chmod +x %s' % os.path.join(os.path.join(APP_TMP_FOLDER, "scripts")))
|
os.system('chmod +x %s' % os.path.join(os.path.join(APP_TMP_FOLDER, "scripts")))
|
||||||
|
@ -519,6 +527,8 @@ def app_change_url(auth, app, domain, path):
|
||||||
logger.success(m18n.n("app_change_url_success",
|
logger.success(m18n.n("app_change_url_success",
|
||||||
app=app, domain=domain, path=path))
|
app=app, domain=domain, path=path))
|
||||||
|
|
||||||
|
hook_callback('post_app_change_url', args=args_list, env=env_dict)
|
||||||
|
|
||||||
|
|
||||||
def app_upgrade(auth, app=[], url=None, file=None):
|
def app_upgrade(auth, app=[], url=None, file=None):
|
||||||
"""
|
"""
|
||||||
|
@ -530,7 +540,8 @@ def app_upgrade(auth, app=[], url=None, file=None):
|
||||||
url -- Git url to fetch for upgrade
|
url -- Git url to fetch for upgrade
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from yunohost.hook import hook_add, hook_remove, hook_exec
|
from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback
|
||||||
|
|
||||||
|
|
||||||
# Retrieve interface
|
# Retrieve interface
|
||||||
is_api = msettings.get('interface') == 'api'
|
is_api = msettings.get('interface') == 'api'
|
||||||
|
@ -555,6 +566,7 @@ def app_upgrade(auth, app=[], url=None, file=None):
|
||||||
logger.info("Upgrading apps %s", ", ".join(app))
|
logger.info("Upgrading apps %s", ", ".join(app))
|
||||||
|
|
||||||
for app_instance_name in apps:
|
for app_instance_name in apps:
|
||||||
|
logger.warning(m18n.n('app_upgrade_app_name', app=app_instance_name))
|
||||||
installed = _is_installed(app_instance_name)
|
installed = _is_installed(app_instance_name)
|
||||||
if not installed:
|
if not installed:
|
||||||
raise MoulinetteError(errno.ENOPKG,
|
raise MoulinetteError(errno.ENOPKG,
|
||||||
|
@ -580,7 +592,7 @@ def app_upgrade(auth, app=[], url=None, file=None):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Check requirements
|
# Check requirements
|
||||||
_check_manifest_requirements(manifest)
|
_check_manifest_requirements(manifest, app_instance_name=app_instance_name)
|
||||||
|
|
||||||
app_setting_path = APPS_SETTING_PATH + '/' + app_instance_name
|
app_setting_path = APPS_SETTING_PATH + '/' + app_instance_name
|
||||||
|
|
||||||
|
@ -621,14 +633,20 @@ def app_upgrade(auth, app=[], url=None, file=None):
|
||||||
with open(app_setting_path + '/status.json', 'w+') as f:
|
with open(app_setting_path + '/status.json', 'w+') as f:
|
||||||
json.dump(status, f)
|
json.dump(status, f)
|
||||||
|
|
||||||
# Replace scripts and manifest
|
# Replace scripts and manifest and conf (if exists)
|
||||||
os.system('rm -rf "%s/scripts" "%s/manifest.json"' % (app_setting_path, app_setting_path))
|
os.system('rm -rf "%s/scripts" "%s/manifest.json %s/conf"' % (app_setting_path, app_setting_path, app_setting_path))
|
||||||
os.system('mv "%s/manifest.json" "%s/scripts" %s' % (extracted_app_folder, extracted_app_folder, app_setting_path))
|
os.system('mv "%s/manifest.json" "%s/scripts" %s' % (extracted_app_folder, extracted_app_folder, app_setting_path))
|
||||||
|
|
||||||
|
if os.path.exists(os.path.join(extracted_app_folder, "conf")):
|
||||||
|
os.system('cp -R %s/conf %s' % (extracted_app_folder, app_setting_path))
|
||||||
|
|
||||||
# So much win
|
# So much win
|
||||||
upgraded_apps.append(app_instance_name)
|
upgraded_apps.append(app_instance_name)
|
||||||
logger.success(m18n.n('app_upgraded', app=app_instance_name))
|
logger.success(m18n.n('app_upgraded', app=app_instance_name))
|
||||||
|
|
||||||
|
hook_callback('post_app_upgrade', args=args_list, env=env_dict)
|
||||||
|
|
||||||
|
|
||||||
if not upgraded_apps:
|
if not upgraded_apps:
|
||||||
raise MoulinetteError(errno.ENODATA, m18n.n('app_no_upgrade'))
|
raise MoulinetteError(errno.ENODATA, m18n.n('app_no_upgrade'))
|
||||||
|
|
||||||
|
@ -652,7 +670,7 @@ def app_install(auth, app, label=None, args=None, no_remove_on_failure=False):
|
||||||
no_remove_on_failure -- Debug option to avoid removing the app on a failed installation
|
no_remove_on_failure -- Debug option to avoid removing the app on a failed installation
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from yunohost.hook import hook_add, hook_remove, hook_exec
|
from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback
|
||||||
|
|
||||||
# Fetch or extract sources
|
# Fetch or extract sources
|
||||||
try:
|
try:
|
||||||
|
@ -683,7 +701,7 @@ def app_install(auth, app, label=None, args=None, no_remove_on_failure=False):
|
||||||
app_id = manifest['id']
|
app_id = manifest['id']
|
||||||
|
|
||||||
# Check requirements
|
# Check requirements
|
||||||
_check_manifest_requirements(manifest)
|
_check_manifest_requirements(manifest, app_id)
|
||||||
|
|
||||||
# Check if app can be forked
|
# Check if app can be forked
|
||||||
instance_number = _installed_instance_number(app_id, last=True) + 1
|
instance_number = _installed_instance_number(app_id, last=True) + 1
|
||||||
|
@ -733,6 +751,9 @@ def app_install(auth, app, label=None, args=None, no_remove_on_failure=False):
|
||||||
os.system('cp %s/manifest.json %s' % (extracted_app_folder, app_setting_path))
|
os.system('cp %s/manifest.json %s' % (extracted_app_folder, app_setting_path))
|
||||||
os.system('cp -R %s/scripts %s' % (extracted_app_folder, app_setting_path))
|
os.system('cp -R %s/scripts %s' % (extracted_app_folder, app_setting_path))
|
||||||
|
|
||||||
|
if os.path.exists(os.path.join(extracted_app_folder, "conf")):
|
||||||
|
os.system('cp -R %s/conf %s' % (extracted_app_folder, app_setting_path))
|
||||||
|
|
||||||
# Execute the app install script
|
# Execute the app install script
|
||||||
install_retcode = 1
|
install_retcode = 1
|
||||||
try:
|
try:
|
||||||
|
@ -791,6 +812,8 @@ def app_install(auth, app, label=None, args=None, no_remove_on_failure=False):
|
||||||
|
|
||||||
logger.success(m18n.n('installation_complete'))
|
logger.success(m18n.n('installation_complete'))
|
||||||
|
|
||||||
|
hook_callback('post_app_install', args=args_list, env=env_dict)
|
||||||
|
|
||||||
|
|
||||||
def app_remove(auth, app):
|
def app_remove(auth, app):
|
||||||
"""
|
"""
|
||||||
|
@ -800,7 +823,7 @@ def app_remove(auth, app):
|
||||||
app -- App(s) to delete
|
app -- App(s) to delete
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from yunohost.hook import hook_exec, hook_remove
|
from yunohost.hook import hook_exec, hook_remove, hook_callback
|
||||||
|
|
||||||
if not _is_installed(app):
|
if not _is_installed(app):
|
||||||
raise MoulinetteError(errno.EINVAL,
|
raise MoulinetteError(errno.EINVAL,
|
||||||
|
@ -829,6 +852,8 @@ def app_remove(auth, app):
|
||||||
if hook_exec('/tmp/yunohost_remove/scripts/remove', args=args_list, env=env_dict, user="root") == 0:
|
if hook_exec('/tmp/yunohost_remove/scripts/remove', args=args_list, env=env_dict, user="root") == 0:
|
||||||
logger.success(m18n.n('app_removed', app=app))
|
logger.success(m18n.n('app_removed', app=app))
|
||||||
|
|
||||||
|
hook_callback('post_app_remove', args=args_list, env=env_dict)
|
||||||
|
|
||||||
if os.path.exists(app_setting_path):
|
if os.path.exists(app_setting_path):
|
||||||
shutil.rmtree(app_setting_path)
|
shutil.rmtree(app_setting_path)
|
||||||
shutil.rmtree('/tmp/yunohost_remove')
|
shutil.rmtree('/tmp/yunohost_remove')
|
||||||
|
@ -1016,7 +1041,9 @@ def app_makedefault(auth, app, domain=None):
|
||||||
|
|
||||||
if '/' in app_map(raw=True)[domain]:
|
if '/' in app_map(raw=True)[domain]:
|
||||||
raise MoulinetteError(errno.EEXIST,
|
raise MoulinetteError(errno.EEXIST,
|
||||||
m18n.n('app_location_already_used'))
|
m18n.n('app_make_default_location_already_used',
|
||||||
|
app=app, domain=app_domain,
|
||||||
|
other_app=app_map(raw=True)[domain]["/"]["id"]))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open('/etc/ssowat/conf.json.persistent') as json_conf:
|
with open('/etc/ssowat/conf.json.persistent') as json_conf:
|
||||||
|
@ -1138,6 +1165,9 @@ def app_checkurl(auth, url, app=None):
|
||||||
app -- Write domain & path to app settings for further checks
|
app -- Write domain & path to app settings for further checks
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
logger.warning(m18n.n("app_checkurl_is_deprecated"))
|
||||||
|
|
||||||
from yunohost.domain import domain_list
|
from yunohost.domain import domain_list
|
||||||
|
|
||||||
if "https://" == url[:8]:
|
if "https://" == url[:8]:
|
||||||
|
@ -1169,10 +1199,13 @@ def app_checkurl(auth, url, app=None):
|
||||||
continue
|
continue
|
||||||
if path == p:
|
if path == p:
|
||||||
raise MoulinetteError(errno.EINVAL,
|
raise MoulinetteError(errno.EINVAL,
|
||||||
m18n.n('app_location_already_used'))
|
m18n.n('app_location_already_used',
|
||||||
|
app=a["id"], path=path))
|
||||||
|
# can't install "/a/b/" if "/a/" exists
|
||||||
elif path.startswith(p) or p.startswith(path):
|
elif path.startswith(p) or p.startswith(path):
|
||||||
raise MoulinetteError(errno.EPERM,
|
raise MoulinetteError(errno.EPERM,
|
||||||
m18n.n('app_location_install_failed'))
|
m18n.n('app_location_install_failed',
|
||||||
|
other_path=p, other_app=a['id']))
|
||||||
|
|
||||||
if app is not None and not installed:
|
if app is not None and not installed:
|
||||||
app_setting(app, 'domain', value=domain)
|
app_setting(app, 'domain', value=domain)
|
||||||
|
@ -1306,6 +1339,17 @@ def app_ssowatconf(auth):
|
||||||
logger.success(m18n.n('ssowat_conf_generated'))
|
logger.success(m18n.n('ssowat_conf_generated'))
|
||||||
|
|
||||||
|
|
||||||
|
def app_change_label(auth, app, new_label):
|
||||||
|
installed = _is_installed(app)
|
||||||
|
if not installed:
|
||||||
|
raise MoulinetteError(errno.ENOPKG,
|
||||||
|
m18n.n('app_not_installed', app=app))
|
||||||
|
|
||||||
|
app_setting(app, "label", value=new_label)
|
||||||
|
|
||||||
|
app_ssowatconf(auth)
|
||||||
|
|
||||||
|
|
||||||
def _get_app_settings(app_id):
|
def _get_app_settings(app_id):
|
||||||
"""
|
"""
|
||||||
Get settings of an installed app
|
Get settings of an installed app
|
||||||
|
@ -1689,7 +1733,7 @@ def _encode_string(value):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def _check_manifest_requirements(manifest):
|
def _check_manifest_requirements(manifest, app_instance_name):
|
||||||
"""Check if required packages are met from the manifest"""
|
"""Check if required packages are met from the manifest"""
|
||||||
requirements = manifest.get('requirements', dict())
|
requirements = manifest.get('requirements', dict())
|
||||||
|
|
||||||
|
@ -1707,12 +1751,12 @@ def _check_manifest_requirements(manifest):
|
||||||
if (not yunohost_req or
|
if (not yunohost_req or
|
||||||
not packages.SpecifierSet(yunohost_req) & '>= 2.3.6'):
|
not packages.SpecifierSet(yunohost_req) & '>= 2.3.6'):
|
||||||
raise MoulinetteError(errno.EINVAL, '{0}{1}'.format(
|
raise MoulinetteError(errno.EINVAL, '{0}{1}'.format(
|
||||||
m18n.g('colon', m18n.n('app_incompatible')),
|
m18n.g('colon', m18n.n('app_incompatible'), app=app_instance_name),
|
||||||
m18n.n('app_package_need_update')))
|
m18n.n('app_package_need_update', app=app_instance_name)))
|
||||||
elif not requirements:
|
elif not requirements:
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info(m18n.n('app_requirements_checking'))
|
logger.info(m18n.n('app_requirements_checking', app=app_instance_name))
|
||||||
|
|
||||||
# Retrieve versions of each required package
|
# Retrieve versions of each required package
|
||||||
try:
|
try:
|
||||||
|
@ -1721,7 +1765,7 @@ def _check_manifest_requirements(manifest):
|
||||||
except packages.PackageException as e:
|
except packages.PackageException as e:
|
||||||
raise MoulinetteError(errno.EINVAL,
|
raise MoulinetteError(errno.EINVAL,
|
||||||
m18n.n('app_requirements_failed',
|
m18n.n('app_requirements_failed',
|
||||||
error=str(e)))
|
error=str(e), app=app_instance_name))
|
||||||
|
|
||||||
# Iterate over requirements
|
# Iterate over requirements
|
||||||
for pkgname, spec in requirements.items():
|
for pkgname, spec in requirements.items():
|
||||||
|
@ -1730,7 +1774,7 @@ def _check_manifest_requirements(manifest):
|
||||||
raise MoulinetteError(
|
raise MoulinetteError(
|
||||||
errno.EINVAL, m18n.n('app_requirements_unmeet',
|
errno.EINVAL, m18n.n('app_requirements_unmeet',
|
||||||
pkgname=pkgname, version=version,
|
pkgname=pkgname, version=version,
|
||||||
spec=spec))
|
spec=spec, app=app_instance_name))
|
||||||
|
|
||||||
|
|
||||||
def _parse_args_from_manifest(manifest, action, args={}, auth=None):
|
def _parse_args_from_manifest(manifest, action, args={}, auth=None):
|
||||||
|
|
|
@ -1543,9 +1543,13 @@ class BackupMethod(object):
|
||||||
# Can create a hard link only if files are on the same fs
|
# Can create a hard link only if files are on the same fs
|
||||||
# (i.e. we can't if it's on a different fs)
|
# (i.e. we can't if it's on a different fs)
|
||||||
if os.stat(src).st_dev == os.stat(dest_dir).st_dev:
|
if os.stat(src).st_dev == os.stat(dest_dir).st_dev:
|
||||||
os.link(src, dest)
|
# Don't hardlink /etc/cron.d files to avoid cron bug
|
||||||
# Success, go to next file to organize
|
# 'NUMBER OF HARD LINKS > 1' see #1043
|
||||||
continue
|
cron_path = os.path.abspath('/etc/cron') + '.'
|
||||||
|
if not os.path.abspath(src).startswith(cron_path):
|
||||||
|
os.link(src, dest)
|
||||||
|
# Success, go to next file to organize
|
||||||
|
continue
|
||||||
|
|
||||||
# If mountbind or hardlink couldnt be created,
|
# If mountbind or hardlink couldnt be created,
|
||||||
# prepare a list of files that need to be copied
|
# prepare a list of files that need to be copied
|
||||||
|
@ -2301,6 +2305,11 @@ def backup_delete(name):
|
||||||
def _create_archive_dir():
|
def _create_archive_dir():
|
||||||
""" Create the YunoHost archives directory if doesn't exist """
|
""" Create the YunoHost archives directory if doesn't exist """
|
||||||
if not os.path.isdir(ARCHIVES_PATH):
|
if not os.path.isdir(ARCHIVES_PATH):
|
||||||
|
if os.path.lexists(ARCHIVES_PATH):
|
||||||
|
raise MoulinetteError(errno.EINVAL,
|
||||||
|
m18n.n('backup_output_symlink_dir_broken',
|
||||||
|
path=ARCHIVES_PATH))
|
||||||
|
|
||||||
os.mkdir(ARCHIVES_PATH, 0750)
|
os.mkdir(ARCHIVES_PATH, 0750)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@ from moulinette.core import MoulinetteError
|
||||||
from moulinette.utils.log import getActionLogger
|
from moulinette.utils.log import getActionLogger
|
||||||
|
|
||||||
import yunohost.domain
|
import yunohost.domain
|
||||||
|
from yunohost.utils.network import get_public_ip
|
||||||
|
|
||||||
from moulinette import m18n
|
from moulinette import m18n
|
||||||
from yunohost.app import app_ssowatconf
|
from yunohost.app import app_ssowatconf
|
||||||
|
@ -463,7 +464,7 @@ def _configure_for_acme_challenge(auth, domain):
|
||||||
nginx_conf_file = "%s/000-acmechallenge.conf" % nginx_conf_folder
|
nginx_conf_file = "%s/000-acmechallenge.conf" % nginx_conf_folder
|
||||||
|
|
||||||
nginx_configuration = '''
|
nginx_configuration = '''
|
||||||
location '/.well-known/acme-challenge'
|
location ^~ '/.well-known/acme-challenge'
|
||||||
{
|
{
|
||||||
default_type "text/plain";
|
default_type "text/plain";
|
||||||
alias %s;
|
alias %s;
|
||||||
|
@ -809,7 +810,7 @@ def _backup_current_cert(domain):
|
||||||
|
|
||||||
|
|
||||||
def _check_domain_is_ready_for_ACME(domain):
|
def _check_domain_is_ready_for_ACME(domain):
|
||||||
public_ip = yunohost.domain.get_public_ip()
|
public_ip = get_public_ip()
|
||||||
|
|
||||||
# Check if IP from DNS matches public IP
|
# Check if IP from DNS matches public IP
|
||||||
if not _dns_ip_match_public_ip(public_ip, domain):
|
if not _dns_ip_match_public_ip(public_ip, domain):
|
||||||
|
@ -856,14 +857,9 @@ def _regen_dnsmasq_if_needed():
|
||||||
"""
|
"""
|
||||||
Update the dnsmasq conf if some IPs are not up to date...
|
Update the dnsmasq conf if some IPs are not up to date...
|
||||||
"""
|
"""
|
||||||
try:
|
|
||||||
ipv4 = yunohost.domain.get_public_ip()
|
ipv4 = get_public_ip()
|
||||||
except:
|
ipv6 = get_public_ip(6)
|
||||||
ipv4 = None
|
|
||||||
try:
|
|
||||||
ipv6 = yunohost.domain.get_public_ip(6)
|
|
||||||
except:
|
|
||||||
ipv6 = None
|
|
||||||
|
|
||||||
do_regen = False
|
do_regen = False
|
||||||
|
|
||||||
|
|
91
src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py
Normal file
91
src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import base64
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import errno
|
||||||
|
|
||||||
|
from moulinette import m18n
|
||||||
|
from moulinette.core import MoulinetteError
|
||||||
|
from moulinette.utils.log import getActionLogger
|
||||||
|
|
||||||
|
from yunohost.tools import Migration
|
||||||
|
from yunohost.dyndns import _guess_current_dyndns_domain
|
||||||
|
|
||||||
|
logger = getActionLogger('yunohost.migration')
|
||||||
|
|
||||||
|
|
||||||
|
class MyMigration(Migration):
|
||||||
|
"Migrate Dyndns stuff from MD5 TSIG to SHA512 TSIG"
|
||||||
|
|
||||||
|
def backward(self):
|
||||||
|
# Not possible because that's a non-reversible operation ?
|
||||||
|
pass
|
||||||
|
|
||||||
|
def migrate(self, dyn_host="dyndns.yunohost.org", domain=None, private_key_path=None):
|
||||||
|
|
||||||
|
if domain is None or private_key_path is None:
|
||||||
|
try:
|
||||||
|
(domain, private_key_path) = _guess_current_dyndns_domain(dyn_host)
|
||||||
|
assert "+157" in private_key_path
|
||||||
|
except (MoulinetteError, AssertionError):
|
||||||
|
logger.warning(m18n.n("migrate_tsig_not_needed"))
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.warning(m18n.n('migrate_tsig_start', domain=domain))
|
||||||
|
public_key_path = private_key_path.rsplit(".private", 1)[0] + ".key"
|
||||||
|
public_key_md5 = open(public_key_path).read().strip().split(' ')[-1]
|
||||||
|
|
||||||
|
os.system('cd /etc/yunohost/dyndns && '
|
||||||
|
'dnssec-keygen -a hmac-sha512 -b 512 -r /dev/urandom -n USER %s' % domain)
|
||||||
|
os.system('chmod 600 /etc/yunohost/dyndns/*.key /etc/yunohost/dyndns/*.private')
|
||||||
|
|
||||||
|
# +165 means that this file store a hmac-sha512 key
|
||||||
|
new_key_path = glob.glob('/etc/yunohost/dyndns/*+165*.key')[0]
|
||||||
|
public_key_sha512 = open(new_key_path).read().strip().split(' ', 6)[-1]
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.put('https://%s/migrate_key_to_sha512/' % (dyn_host),
|
||||||
|
data={
|
||||||
|
'public_key_md5': base64.b64encode(public_key_md5),
|
||||||
|
'public_key_sha512': base64.b64encode(public_key_sha512),
|
||||||
|
}, timeout=30)
|
||||||
|
except requests.ConnectionError:
|
||||||
|
raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection'))
|
||||||
|
|
||||||
|
if r.status_code != 201:
|
||||||
|
try:
|
||||||
|
error = json.loads(r.text)['error']
|
||||||
|
except Exception:
|
||||||
|
# failed to decode json
|
||||||
|
error = r.text
|
||||||
|
|
||||||
|
import traceback
|
||||||
|
from StringIO import StringIO
|
||||||
|
stack = StringIO()
|
||||||
|
traceback.print_stack(file=stack)
|
||||||
|
logger.error(stack.getvalue())
|
||||||
|
|
||||||
|
# Migration didn't succeed, so we rollback and raise an exception
|
||||||
|
os.system("mv /etc/yunohost/dyndns/*+165* /tmp")
|
||||||
|
|
||||||
|
raise MoulinetteError(m18n.n('migrate_tsig_failed', domain=domain,
|
||||||
|
error_code=str(r.status_code), error=error))
|
||||||
|
|
||||||
|
# remove old certificates
|
||||||
|
os.system("mv /etc/yunohost/dyndns/*+157* /tmp")
|
||||||
|
|
||||||
|
# sleep to wait for dyndns cache invalidation
|
||||||
|
logger.warning(m18n.n('migrate_tsig_wait'))
|
||||||
|
time.sleep(60)
|
||||||
|
logger.warning(m18n.n('migrate_tsig_wait_2'))
|
||||||
|
time.sleep(60)
|
||||||
|
logger.warning(m18n.n('migrate_tsig_wait_3'))
|
||||||
|
time.sleep(30)
|
||||||
|
logger.warning(m18n.n('migrate_tsig_wait_4'))
|
||||||
|
time.sleep(30)
|
||||||
|
|
||||||
|
logger.warning(m18n.n('migrate_tsig_end'))
|
||||||
|
return
|
||||||
|
|
|
@ -30,8 +30,6 @@ import yaml
|
||||||
import errno
|
import errno
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from urllib import urlopen
|
|
||||||
|
|
||||||
from moulinette import m18n, msettings
|
from moulinette import m18n, msettings
|
||||||
from moulinette.core import MoulinetteError
|
from moulinette.core import MoulinetteError
|
||||||
from moulinette.utils.log import getActionLogger
|
from moulinette.utils.log import getActionLogger
|
||||||
|
@ -39,6 +37,7 @@ from moulinette.utils.log import getActionLogger
|
||||||
import yunohost.certificate
|
import yunohost.certificate
|
||||||
|
|
||||||
from yunohost.service import service_regen_conf
|
from yunohost.service import service_regen_conf
|
||||||
|
from yunohost.utils.network import get_public_ip
|
||||||
|
|
||||||
logger = getActionLogger('yunohost.domain')
|
logger = getActionLogger('yunohost.domain')
|
||||||
|
|
||||||
|
@ -188,17 +187,17 @@ def domain_dns_conf(domain, ttl=None):
|
||||||
|
|
||||||
result = ""
|
result = ""
|
||||||
|
|
||||||
result += "# Basic ipv4/ipv6 records"
|
result += "; Basic ipv4/ipv6 records"
|
||||||
for record in dns_conf["basic"]:
|
for record in dns_conf["basic"]:
|
||||||
result += "\n{name} {ttl} IN {type} {value}".format(**record)
|
result += "\n{name} {ttl} IN {type} {value}".format(**record)
|
||||||
|
|
||||||
result += "\n\n"
|
result += "\n\n"
|
||||||
result += "# XMPP"
|
result += "; XMPP"
|
||||||
for record in dns_conf["xmpp"]:
|
for record in dns_conf["xmpp"]:
|
||||||
result += "\n{name} {ttl} IN {type} {value}".format(**record)
|
result += "\n{name} {ttl} IN {type} {value}".format(**record)
|
||||||
|
|
||||||
result += "\n\n"
|
result += "\n\n"
|
||||||
result += "# Mail"
|
result += "; Mail"
|
||||||
for record in dns_conf["mail"]:
|
for record in dns_conf["mail"]:
|
||||||
result += "\n{name} {ttl} IN {type} {value}".format(**record)
|
result += "\n{name} {ttl} IN {type} {value}".format(**record)
|
||||||
|
|
||||||
|
@ -260,42 +259,6 @@ def domain_url_available(auth, domain, path):
|
||||||
return available
|
return available
|
||||||
|
|
||||||
|
|
||||||
def get_public_ip(protocol=4):
|
|
||||||
"""Retrieve the public IP address from ip.yunohost.org"""
|
|
||||||
if protocol == 4:
|
|
||||||
url = 'https://ip.yunohost.org'
|
|
||||||
elif protocol == 6:
|
|
||||||
url = 'https://ip6.yunohost.org'
|
|
||||||
else:
|
|
||||||
raise ValueError("invalid protocol version")
|
|
||||||
|
|
||||||
try:
|
|
||||||
return urlopen(url).read().strip()
|
|
||||||
except IOError:
|
|
||||||
logger.debug('cannot retrieve public IPv%d' % protocol, exc_info=1)
|
|
||||||
raise MoulinetteError(errno.ENETUNREACH,
|
|
||||||
m18n.n('no_internet_connection'))
|
|
||||||
|
|
||||||
def get_public_ips():
|
|
||||||
"""
|
|
||||||
Retrieve the public IPv4 and v6 from ip. and ip6.yunohost.org
|
|
||||||
|
|
||||||
Returns a 2-tuple (ipv4, ipv6). ipv4 or ipv6 can be None if they were not
|
|
||||||
found.
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
ipv4 = get_public_ip()
|
|
||||||
except:
|
|
||||||
ipv4 = None
|
|
||||||
try:
|
|
||||||
ipv6 = get_public_ip(6)
|
|
||||||
except:
|
|
||||||
ipv6 = None
|
|
||||||
|
|
||||||
return (ipv4, ipv6)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_maindomain():
|
def _get_maindomain():
|
||||||
with open('/etc/yunohost/current_host', 'r') as f:
|
with open('/etc/yunohost/current_host', 'r') as f:
|
||||||
maindomain = f.readline().rstrip()
|
maindomain = f.readline().rstrip()
|
||||||
|
@ -356,15 +319,8 @@ def _build_dns_conf(domain, ttl=3600):
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
ipv4 = get_public_ip()
|
||||||
ipv4 = get_public_ip()
|
ipv6 = get_public_ip(6)
|
||||||
except:
|
|
||||||
ipv4 = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
ipv6 = get_public_ip(6)
|
|
||||||
except:
|
|
||||||
ipv6 = None
|
|
||||||
|
|
||||||
basic = []
|
basic = []
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import os
|
||||||
import re
|
import re
|
||||||
import json
|
import json
|
||||||
import glob
|
import glob
|
||||||
|
import time
|
||||||
import base64
|
import base64
|
||||||
import errno
|
import errno
|
||||||
import requests
|
import requests
|
||||||
|
@ -38,7 +39,8 @@ from moulinette.utils.log import getActionLogger
|
||||||
from moulinette.utils.filesystem import read_file, write_to_file, rm
|
from moulinette.utils.filesystem import read_file, write_to_file, rm
|
||||||
from moulinette.utils.network import download_json
|
from moulinette.utils.network import download_json
|
||||||
|
|
||||||
from yunohost.domain import get_public_ips, _get_maindomain, _build_dns_conf
|
from yunohost.domain import _get_maindomain, _build_dns_conf
|
||||||
|
from yunohost.utils.network import get_public_ip
|
||||||
|
|
||||||
logger = getActionLogger('yunohost.dyndns')
|
logger = getActionLogger('yunohost.dyndns')
|
||||||
|
|
||||||
|
@ -46,6 +48,14 @@ OLD_IPV4_FILE = '/etc/yunohost/dyndns/old_ip'
|
||||||
OLD_IPV6_FILE = '/etc/yunohost/dyndns/old_ipv6'
|
OLD_IPV6_FILE = '/etc/yunohost/dyndns/old_ipv6'
|
||||||
DYNDNS_ZONE = '/etc/yunohost/dyndns/zone'
|
DYNDNS_ZONE = '/etc/yunohost/dyndns/zone'
|
||||||
|
|
||||||
|
RE_DYNDNS_PRIVATE_KEY_MD5 = re.compile(
|
||||||
|
r'.*/K(?P<domain>[^\s\+]+)\.\+157.+\.private$'
|
||||||
|
)
|
||||||
|
|
||||||
|
RE_DYNDNS_PRIVATE_KEY_SHA512 = re.compile(
|
||||||
|
r'.*/K(?P<domain>[^\s\+]+)\.\+165.+\.private$'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _dyndns_provides(provider, domain):
|
def _dyndns_provides(provider, domain):
|
||||||
"""
|
"""
|
||||||
|
@ -129,21 +139,22 @@ def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None
|
||||||
|
|
||||||
if key is None:
|
if key is None:
|
||||||
if len(glob.glob('/etc/yunohost/dyndns/*.key')) == 0:
|
if len(glob.glob('/etc/yunohost/dyndns/*.key')) == 0:
|
||||||
os.makedirs('/etc/yunohost/dyndns')
|
if not os.path.exists('/etc/yunohost/dyndns'):
|
||||||
|
os.makedirs('/etc/yunohost/dyndns')
|
||||||
|
|
||||||
logger.info(m18n.n('dyndns_key_generating'))
|
logger.info(m18n.n('dyndns_key_generating'))
|
||||||
|
|
||||||
os.system('cd /etc/yunohost/dyndns && '
|
os.system('cd /etc/yunohost/dyndns && '
|
||||||
'dnssec-keygen -a hmac-md5 -b 128 -r /dev/urandom -n USER %s' % domain)
|
'dnssec-keygen -a hmac-sha512 -b 512 -r /dev/urandom -n USER %s' % domain)
|
||||||
os.system('chmod 600 /etc/yunohost/dyndns/*.key /etc/yunohost/dyndns/*.private')
|
os.system('chmod 600 /etc/yunohost/dyndns/*.key /etc/yunohost/dyndns/*.private')
|
||||||
|
|
||||||
key_file = glob.glob('/etc/yunohost/dyndns/*.key')[0]
|
key_file = glob.glob('/etc/yunohost/dyndns/*.key')[0]
|
||||||
with open(key_file) as f:
|
with open(key_file) as f:
|
||||||
key = f.readline().strip().split(' ')[-1]
|
key = f.readline().strip().split(' ', 6)[-1]
|
||||||
|
|
||||||
# Send subscription
|
# Send subscription
|
||||||
try:
|
try:
|
||||||
r = requests.post('https://%s/key/%s' % (subscribe_host, base64.b64encode(key)), data={'subdomain': domain})
|
r = requests.post('https://%s/key/%s?key_algo=hmac-sha512' % (subscribe_host, base64.b64encode(key)), data={'subdomain': domain}, timeout=30)
|
||||||
except requests.ConnectionError:
|
except requests.ConnectionError:
|
||||||
raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection'))
|
raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection'))
|
||||||
if r.status_code != 201:
|
if r.status_code != 201:
|
||||||
|
@ -183,7 +194,8 @@ def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None,
|
||||||
old_ipv6 = read_file(OLD_IPV6_FILE).rstrip()
|
old_ipv6 = read_file(OLD_IPV6_FILE).rstrip()
|
||||||
|
|
||||||
# Get current IPv4 and IPv6
|
# Get current IPv4 and IPv6
|
||||||
(ipv4_, ipv6_) = get_public_ips()
|
ipv4_ = get_public_ip()
|
||||||
|
ipv6_ = get_public_ip(6)
|
||||||
|
|
||||||
if ipv4 is None:
|
if ipv4 is None:
|
||||||
ipv4 = ipv4_
|
ipv4 = ipv4_
|
||||||
|
@ -213,6 +225,19 @@ def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None,
|
||||||
|
|
||||||
key = keys[0]
|
key = keys[0]
|
||||||
|
|
||||||
|
# This mean that hmac-md5 is used
|
||||||
|
# (Re?)Trigger the migration to sha256 and return immediately.
|
||||||
|
# The actual update will be done in next run.
|
||||||
|
if "+157" in key:
|
||||||
|
from yunohost.tools import _get_migration_by_name
|
||||||
|
migration = _get_migration_by_name("migrate_to_tsig_sha256")
|
||||||
|
try:
|
||||||
|
migration["module"].MyMigration().migrate(dyn_host, domain, key)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(m18n.n('migrations_migration_has_failed', exception=e, **migration), exc_info=1)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
# Extract 'host', e.g. 'nohost.me' from 'foo.nohost.me'
|
# Extract 'host', e.g. 'nohost.me' from 'foo.nohost.me'
|
||||||
host = domain.split('.')[1:]
|
host = domain.split('.')[1:]
|
||||||
host = '.'.join(host)
|
host = '.'.join(host)
|
||||||
|
@ -313,15 +338,13 @@ def _guess_current_dyndns_domain(dyn_host):
|
||||||
dynette...)
|
dynette...)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
re_dyndns_private_key = re.compile(
|
|
||||||
r'.*/K(?P<domain>[^\s\+]+)\.\+157.+\.private$'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Retrieve the first registered domain
|
# Retrieve the first registered domain
|
||||||
for path in glob.iglob('/etc/yunohost/dyndns/K*.private'):
|
for path in glob.iglob('/etc/yunohost/dyndns/K*.private'):
|
||||||
match = re_dyndns_private_key.match(path)
|
match = RE_DYNDNS_PRIVATE_KEY_MD5.match(path)
|
||||||
if not match:
|
if not match:
|
||||||
continue
|
match = RE_DYNDNS_PRIVATE_KEY_SHA512.match(path)
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
_domain = match.group('domain')
|
_domain = match.group('domain')
|
||||||
|
|
||||||
# Verify if domain is registered (i.e., if it's available, skip
|
# Verify if domain is registered (i.e., if it's available, skip
|
||||||
|
|
|
@ -41,7 +41,8 @@ from moulinette import m18n
|
||||||
from moulinette.core import MoulinetteError
|
from moulinette.core import MoulinetteError
|
||||||
from moulinette.utils.log import getActionLogger
|
from moulinette.utils.log import getActionLogger
|
||||||
|
|
||||||
from yunohost.domain import get_public_ip, _get_maindomain
|
from yunohost.utils.network import get_public_ip
|
||||||
|
from yunohost.domain import _get_maindomain
|
||||||
|
|
||||||
logger = getActionLogger('yunohost.monitor')
|
logger = getActionLogger('yunohost.monitor')
|
||||||
|
|
||||||
|
@ -210,10 +211,7 @@ def monitor_network(units=None, human_readable=False):
|
||||||
else:
|
else:
|
||||||
logger.debug('interface name %s was not found', iname)
|
logger.debug('interface name %s was not found', iname)
|
||||||
elif u == 'infos':
|
elif u == 'infos':
|
||||||
try:
|
p_ipv4 = get_public_ip() or 'unknown'
|
||||||
p_ipv4 = get_public_ip()
|
|
||||||
except:
|
|
||||||
p_ipv4 = 'unknown'
|
|
||||||
|
|
||||||
l_ip = 'unknown'
|
l_ip = 'unknown'
|
||||||
for name, addrs in devices.items():
|
for name, addrs in devices.items():
|
||||||
|
|
|
@ -498,15 +498,12 @@ def _run_service_command(action, service):
|
||||||
raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', service=service))
|
raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', service=service))
|
||||||
|
|
||||||
cmd = None
|
cmd = None
|
||||||
if action in ['start', 'stop', 'restart', 'reload']:
|
if action in ['start', 'stop', 'restart', 'reload', 'enable', 'disable']:
|
||||||
cmd = 'service %s %s' % (service, action)
|
cmd = 'systemctl %s %s' % (action, service)
|
||||||
elif action in ['enable', 'disable']:
|
|
||||||
arg = 'defaults' if action == 'enable' else 'remove'
|
|
||||||
cmd = 'update-rc.d %s %s' % (service, arg)
|
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unknown action '%s'" % action)
|
raise ValueError("Unknown action '%s'" % action)
|
||||||
|
|
||||||
need_lock = (services[service].get('need_lock') or False) \
|
need_lock = services[service].get('need_lock', False) \
|
||||||
and action in ['start', 'stop', 'restart', 'reload']
|
and action in ['start', 'stop', 'restart', 'reload']
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
102
src/yunohost/ssh.py
Normal file
102
src/yunohost/ssh.py
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from moulinette.utils.filesystem import read_file, write_to_file, chown, chmod, mkdir
|
||||||
|
|
||||||
|
from yunohost.user import _get_user_for_ssh
|
||||||
|
|
||||||
|
|
||||||
|
def ssh_authorized_keys_list(auth, username):
|
||||||
|
user = _get_user_for_ssh(auth, username, ["homeDirectory"])
|
||||||
|
if not user:
|
||||||
|
raise Exception("User with username '%s' doesn't exists" % username)
|
||||||
|
|
||||||
|
authorized_keys_file = os.path.join(user["homeDirectory"][0], ".ssh", "authorized_keys")
|
||||||
|
|
||||||
|
if not os.path.exists(authorized_keys_file):
|
||||||
|
return []
|
||||||
|
|
||||||
|
keys = []
|
||||||
|
last_comment = ""
|
||||||
|
for line in read_file(authorized_keys_file).split("\n"):
|
||||||
|
# empty line
|
||||||
|
if not line.strip():
|
||||||
|
continue
|
||||||
|
|
||||||
|
if line.lstrip().startswith("#"):
|
||||||
|
last_comment = line.lstrip().lstrip("#").strip()
|
||||||
|
continue
|
||||||
|
|
||||||
|
# assuming a key per non empty line
|
||||||
|
key = line.strip()
|
||||||
|
keys.append({
|
||||||
|
"key": key,
|
||||||
|
"name": last_comment,
|
||||||
|
})
|
||||||
|
|
||||||
|
last_comment = ""
|
||||||
|
|
||||||
|
return {"keys": keys}
|
||||||
|
|
||||||
|
|
||||||
|
def ssh_authorized_keys_add(auth, username, key, comment):
|
||||||
|
user = _get_user_for_ssh(auth, username, ["homeDirectory", "uid"])
|
||||||
|
if not user:
|
||||||
|
raise Exception("User with username '%s' doesn't exists" % username)
|
||||||
|
|
||||||
|
authorized_keys_file = os.path.join(user["homeDirectory"][0], ".ssh", "authorized_keys")
|
||||||
|
|
||||||
|
if not os.path.exists(authorized_keys_file):
|
||||||
|
# ensure ".ssh" exists
|
||||||
|
mkdir(os.path.join(user["homeDirectory"][0], ".ssh"),
|
||||||
|
force=True, parents=True, uid=user["uid"][0])
|
||||||
|
|
||||||
|
# create empty file to set good permissions
|
||||||
|
write_to_file(authorized_keys_file, "")
|
||||||
|
chown(authorized_keys_file, uid=user["uid"][0])
|
||||||
|
chmod(authorized_keys_file, 0600)
|
||||||
|
|
||||||
|
authorized_keys_content = read_file(authorized_keys_file)
|
||||||
|
|
||||||
|
authorized_keys_content += "\n"
|
||||||
|
authorized_keys_content += "\n"
|
||||||
|
|
||||||
|
if comment and comment.strip():
|
||||||
|
if not comment.lstrip().startswith("#"):
|
||||||
|
comment = "# " + comment
|
||||||
|
authorized_keys_content += comment.replace("\n", " ").strip()
|
||||||
|
authorized_keys_content += "\n"
|
||||||
|
|
||||||
|
authorized_keys_content += key.strip()
|
||||||
|
authorized_keys_content += "\n"
|
||||||
|
|
||||||
|
write_to_file(authorized_keys_file, authorized_keys_content)
|
||||||
|
|
||||||
|
|
||||||
|
def ssh_authorized_keys_remove(auth, username, key):
|
||||||
|
user = _get_user(auth, username, ["homeDirectory", "uid"])
|
||||||
|
if not user:
|
||||||
|
raise Exception("User with username '%s' doesn't exists" % username)
|
||||||
|
|
||||||
|
authorized_keys_file = os.path.join(user["homeDirectory"][0], ".ssh", "authorized_keys")
|
||||||
|
|
||||||
|
if not os.path.exists(authorized_keys_file):
|
||||||
|
raise Exception("this key doesn't exists ({} dosesn't exists)".format(authorized_keys_file))
|
||||||
|
|
||||||
|
authorized_keys_content = read_file(authorized_keys_file)
|
||||||
|
|
||||||
|
if key not in authorized_keys_content:
|
||||||
|
raise Exception("Key '{}' is not present in authorized_keys".format(key))
|
||||||
|
|
||||||
|
# don't delete the previous comment because we can't verify if it's legit
|
||||||
|
|
||||||
|
# this regex approach failed for some reasons and I don't know why :(
|
||||||
|
# authorized_keys_content = re.sub("{} *\n?".format(key),
|
||||||
|
# "",
|
||||||
|
# authorized_keys_content,
|
||||||
|
# flags=re.MULTILINE)
|
||||||
|
|
||||||
|
authorized_keys_content = authorized_keys_content.replace(key, "")
|
||||||
|
|
||||||
|
write_to_file(authorized_keys_file, authorized_keys_content)
|
|
@ -33,8 +33,9 @@ import logging
|
||||||
import subprocess
|
import subprocess
|
||||||
import pwd
|
import pwd
|
||||||
import socket
|
import socket
|
||||||
from collections import OrderedDict
|
from xmlrpclib import Fault
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
import apt
|
import apt
|
||||||
import apt.progress
|
import apt.progress
|
||||||
|
@ -42,14 +43,16 @@ import apt.progress
|
||||||
from moulinette import msettings, msignals, m18n
|
from moulinette import msettings, msignals, m18n
|
||||||
from moulinette.core import MoulinetteError, init_authenticator
|
from moulinette.core import MoulinetteError, init_authenticator
|
||||||
from moulinette.utils.log import getActionLogger
|
from moulinette.utils.log import getActionLogger
|
||||||
|
from moulinette.utils.process import check_output
|
||||||
from moulinette.utils.filesystem import read_json, write_to_json
|
from moulinette.utils.filesystem import read_json, write_to_json
|
||||||
from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, app_list, _install_appslist_fetch_cron
|
from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, app_list, _install_appslist_fetch_cron
|
||||||
from yunohost.domain import domain_add, domain_list, get_public_ip, _get_maindomain, _set_maindomain
|
from yunohost.domain import domain_add, domain_list, _get_maindomain, _set_maindomain
|
||||||
from yunohost.dyndns import _dyndns_available, _dyndns_provides
|
from yunohost.dyndns import _dyndns_available, _dyndns_provides
|
||||||
from yunohost.firewall import firewall_upnp
|
from yunohost.firewall import firewall_upnp
|
||||||
from yunohost.service import service_status, service_regen_conf, service_log
|
from yunohost.service import service_status, service_regen_conf, service_log, service_start, service_enable
|
||||||
from yunohost.monitor import monitor_disk, monitor_system
|
from yunohost.monitor import monitor_disk, monitor_system
|
||||||
from yunohost.utils.packages import ynh_packages_version
|
from yunohost.utils.packages import ynh_packages_version
|
||||||
|
from yunohost.utils.network import get_public_ip
|
||||||
|
|
||||||
# FIXME this is a duplicate from apps.py
|
# FIXME this is a duplicate from apps.py
|
||||||
APPS_SETTING_PATH = '/etc/yunohost/apps/'
|
APPS_SETTING_PATH = '/etc/yunohost/apps/'
|
||||||
|
@ -398,8 +401,8 @@ def tools_postinstall(domain, password, ignore_dyndns=False):
|
||||||
os.system('touch /etc/yunohost/installed')
|
os.system('touch /etc/yunohost/installed')
|
||||||
|
|
||||||
# Enable and start YunoHost firewall at boot time
|
# Enable and start YunoHost firewall at boot time
|
||||||
os.system('update-rc.d yunohost-firewall enable')
|
service_enable("yunohost-firewall")
|
||||||
os.system('service yunohost-firewall start &')
|
service_start("yunohost-firewall")
|
||||||
|
|
||||||
service_regen_conf(force=True)
|
service_regen_conf(force=True)
|
||||||
logger.success(m18n.n('yunohost_configured'))
|
logger.success(m18n.n('yunohost_configured'))
|
||||||
|
@ -561,17 +564,19 @@ def tools_diagnosis(auth, private=False):
|
||||||
# Packages version
|
# Packages version
|
||||||
diagnosis['packages'] = ynh_packages_version()
|
diagnosis['packages'] = ynh_packages_version()
|
||||||
|
|
||||||
|
diagnosis["backports"] = check_output("dpkg -l |awk '/^ii/ && $3 ~ /bpo[6-8]/ {print $2}'").split()
|
||||||
|
|
||||||
# Server basic monitoring
|
# Server basic monitoring
|
||||||
diagnosis['system'] = OrderedDict()
|
diagnosis['system'] = OrderedDict()
|
||||||
try:
|
try:
|
||||||
disks = monitor_disk(units=['filesystem'], human_readable=True)
|
disks = monitor_disk(units=['filesystem'], human_readable=True)
|
||||||
except MoulinetteError as e:
|
except (MoulinetteError, Fault) as e:
|
||||||
logger.warning(m18n.n('diagnosis_monitor_disk_error', error=format(e)), exc_info=1)
|
logger.warning(m18n.n('diagnosis_monitor_disk_error', error=format(e)), exc_info=1)
|
||||||
else:
|
else:
|
||||||
diagnosis['system']['disks'] = {}
|
diagnosis['system']['disks'] = {}
|
||||||
for disk in disks:
|
for disk in disks:
|
||||||
if isinstance(disk, str):
|
if isinstance(disks[disk], str):
|
||||||
diagnosis['system']['disks'] = disk
|
diagnosis['system']['disks'][disk] = disks[disk]
|
||||||
else:
|
else:
|
||||||
diagnosis['system']['disks'][disk] = 'Mounted on %s, %s (%s free)' % (
|
diagnosis['system']['disks'][disk] = 'Mounted on %s, %s (%s free)' % (
|
||||||
disks[disk]['mnt_point'],
|
disks[disk]['mnt_point'],
|
||||||
|
@ -589,6 +594,14 @@ def tools_diagnosis(auth, private=False):
|
||||||
'swap': '%s (%s free)' % (system['memory']['swap']['total'], system['memory']['swap']['free']),
|
'swap': '%s (%s free)' % (system['memory']['swap']['total'], system['memory']['swap']['free']),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# nginx -t
|
||||||
|
try:
|
||||||
|
diagnosis['nginx'] = check_output("nginx -t").strip().split("\n")
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
logger.warning("Unable to check 'nginx -t', exception: %s" % e)
|
||||||
|
|
||||||
# Services status
|
# Services status
|
||||||
services = service_status()
|
services = service_status()
|
||||||
diagnosis['services'] = {}
|
diagnosis['services'] = {}
|
||||||
|
@ -610,23 +623,64 @@ def tools_diagnosis(auth, private=False):
|
||||||
# Private data
|
# Private data
|
||||||
if private:
|
if private:
|
||||||
diagnosis['private'] = OrderedDict()
|
diagnosis['private'] = OrderedDict()
|
||||||
|
|
||||||
# Public IP
|
# Public IP
|
||||||
diagnosis['private']['public_ip'] = {}
|
diagnosis['private']['public_ip'] = {}
|
||||||
try:
|
diagnosis['private']['public_ip']['IPv4'] = get_public_ip(4)
|
||||||
diagnosis['private']['public_ip']['IPv4'] = get_public_ip(4)
|
diagnosis['private']['public_ip']['IPv6'] = get_public_ip(6)
|
||||||
except MoulinetteError as e:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
diagnosis['private']['public_ip']['IPv6'] = get_public_ip(6)
|
|
||||||
except MoulinetteError as e:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Domains
|
# Domains
|
||||||
diagnosis['private']['domains'] = domain_list(auth)['domains']
|
diagnosis['private']['domains'] = domain_list(auth)['domains']
|
||||||
|
|
||||||
|
diagnosis['private']['regen_conf'] = service_regen_conf(with_diff=True, dry_run=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
diagnosis['security'] = {
|
||||||
|
"CVE-2017-5754": {
|
||||||
|
"name": "meltdown",
|
||||||
|
"vulnerable": _check_if_vulnerable_to_meltdown(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
logger.warning("Unable to check for meltdown vulnerability: %s" % e)
|
||||||
|
|
||||||
return diagnosis
|
return diagnosis
|
||||||
|
|
||||||
|
|
||||||
|
def _check_if_vulnerable_to_meltdown():
|
||||||
|
# meltdown CVE: https://security-tracker.debian.org/tracker/CVE-2017-5754
|
||||||
|
|
||||||
|
# script taken from https://github.com/speed47/spectre-meltdown-checker
|
||||||
|
# script commit id is store directly in the script
|
||||||
|
file_dir = os.path.split(__file__)[0]
|
||||||
|
SCRIPT_PATH = os.path.join(file_dir, "./vendor/spectre-meltdown-checker/spectre-meltdown-checker.sh")
|
||||||
|
|
||||||
|
# '--variant 3' corresponds to Meltdown
|
||||||
|
# example output from the script:
|
||||||
|
# [{"NAME":"MELTDOWN","CVE":"CVE-2017-5754","VULNERABLE":false,"INFOS":"PTI mitigates the vulnerability"}]
|
||||||
|
try:
|
||||||
|
call = subprocess.Popen("bash %s --batch json --variant 3" %
|
||||||
|
SCRIPT_PATH, shell=True,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT)
|
||||||
|
|
||||||
|
output, _ = call.communicate()
|
||||||
|
assert call.returncode == 0
|
||||||
|
|
||||||
|
CVEs = json.loads(output)
|
||||||
|
assert len(CVEs) == 1
|
||||||
|
assert CVEs[0]["NAME"] == "MELTDOWN"
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
logger.warning("Something wrong happened when trying to diagnose Meltdown vunerability, exception: %s" % e)
|
||||||
|
raise Exception("Command output for failed meltdown check: '%s'" % output)
|
||||||
|
|
||||||
|
return CVEs[0]["VULNERABLE"]
|
||||||
|
|
||||||
|
|
||||||
def tools_port_available(port):
|
def tools_port_available(port):
|
||||||
"""
|
"""
|
||||||
Check availability of a local port
|
Check availability of a local port
|
||||||
|
@ -717,30 +771,10 @@ def tools_migrations_migrate(target=None, skip=False):
|
||||||
|
|
||||||
# loading all migrations
|
# loading all migrations
|
||||||
for migration in tools_migrations_list()["migrations"]:
|
for migration in tools_migrations_list()["migrations"]:
|
||||||
logger.debug(m18n.n('migrations_loading_migration',
|
|
||||||
number=migration["number"],
|
|
||||||
name=migration["name"],
|
|
||||||
))
|
|
||||||
|
|
||||||
try:
|
|
||||||
# this is python builtin method to import a module using a name, we
|
|
||||||
# use that to import the migration as a python object so we'll be
|
|
||||||
# able to run it in the next loop
|
|
||||||
module = import_module("yunohost.data_migrations.{file_name}".format(**migration))
|
|
||||||
except Exception:
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
raise MoulinetteError(errno.EINVAL, m18n.n('migrations_error_failed_to_load_migration',
|
|
||||||
number=migration["number"],
|
|
||||||
name=migration["name"],
|
|
||||||
))
|
|
||||||
break
|
|
||||||
|
|
||||||
migrations.append({
|
migrations.append({
|
||||||
"number": migration["number"],
|
"number": migration["number"],
|
||||||
"name": migration["name"],
|
"name": migration["name"],
|
||||||
"module": module,
|
"module": _get_migration_module(migration),
|
||||||
})
|
})
|
||||||
|
|
||||||
migrations = sorted(migrations, key=lambda x: x["number"])
|
migrations = sorted(migrations, key=lambda x: x["number"])
|
||||||
|
@ -877,6 +911,47 @@ def _get_migrations_list():
|
||||||
return sorted(migrations)
|
return sorted(migrations)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_migration_by_name(migration_name, with_module=True):
|
||||||
|
"""
|
||||||
|
Low-level / "private" function to find a migration by its name
|
||||||
|
"""
|
||||||
|
|
||||||
|
migrations = tools_migrations_list()["migrations"]
|
||||||
|
|
||||||
|
matches = [ m for m in migrations if m["name"] == migration_name ]
|
||||||
|
|
||||||
|
assert len(matches) == 1, "Unable to find migration with name %s" % migration_name
|
||||||
|
|
||||||
|
migration = matches[0]
|
||||||
|
|
||||||
|
if with_module:
|
||||||
|
migration["module"] = _get_migration_module(migration)
|
||||||
|
|
||||||
|
return migration
|
||||||
|
|
||||||
|
|
||||||
|
def _get_migration_module(migration):
|
||||||
|
|
||||||
|
logger.debug(m18n.n('migrations_loading_migration',
|
||||||
|
number=migration["number"],
|
||||||
|
name=migration["name"],
|
||||||
|
))
|
||||||
|
|
||||||
|
try:
|
||||||
|
# this is python builtin method to import a module using a name, we
|
||||||
|
# use that to import the migration as a python object so we'll be
|
||||||
|
# able to run it in the next loop
|
||||||
|
return import_module("yunohost.data_migrations.{file_name}".format(**migration))
|
||||||
|
except Exception:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
raise MoulinetteError(errno.EINVAL, m18n.n('migrations_error_failed_to_load_migration',
|
||||||
|
number=migration["number"],
|
||||||
|
name=migration["name"],
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
class Migration(object):
|
class Migration(object):
|
||||||
|
|
||||||
def migrate(self):
|
def migrate(self):
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import pwd
|
||||||
import json
|
import json
|
||||||
import errno
|
import errno
|
||||||
import crypt
|
import crypt
|
||||||
|
@ -35,10 +36,13 @@ import subprocess
|
||||||
from moulinette import m18n
|
from moulinette import m18n
|
||||||
from moulinette.core import MoulinetteError
|
from moulinette.core import MoulinetteError
|
||||||
from moulinette.utils.log import getActionLogger
|
from moulinette.utils.log import getActionLogger
|
||||||
|
from moulinette.utils.filesystem import read_file
|
||||||
from yunohost.service import service_status
|
from yunohost.service import service_status
|
||||||
|
|
||||||
logger = getActionLogger('yunohost.user')
|
logger = getActionLogger('yunohost.user')
|
||||||
|
|
||||||
|
SSHD_CONFIG_PATH = "/etc/ssh/sshd_config"
|
||||||
|
|
||||||
|
|
||||||
def user_list(auth, fields=None):
|
def user_list(auth, fields=None):
|
||||||
"""
|
"""
|
||||||
|
@ -56,6 +60,8 @@ def user_list(auth, fields=None):
|
||||||
'cn': 'fullname',
|
'cn': 'fullname',
|
||||||
'mail': 'mail',
|
'mail': 'mail',
|
||||||
'maildrop': 'mail-forward',
|
'maildrop': 'mail-forward',
|
||||||
|
'loginShell': 'shell',
|
||||||
|
'homeDirectory': 'home_path',
|
||||||
'mailuserquota': 'mailbox-quota'
|
'mailuserquota': 'mailbox-quota'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +77,7 @@ def user_list(auth, fields=None):
|
||||||
raise MoulinetteError(errno.EINVAL,
|
raise MoulinetteError(errno.EINVAL,
|
||||||
m18n.n('field_invalid', attr))
|
m18n.n('field_invalid', attr))
|
||||||
else:
|
else:
|
||||||
attrs = ['uid', 'cn', 'mail', 'mailuserquota']
|
attrs = ['uid', 'cn', 'mail', 'mailuserquota', 'loginShell']
|
||||||
|
|
||||||
result = auth.search('ou=users,dc=yunohost,dc=org',
|
result = auth.search('ou=users,dc=yunohost,dc=org',
|
||||||
'(&(objectclass=person)(!(uid=root))(!(uid=nobody)))',
|
'(&(objectclass=person)(!(uid=root))(!(uid=nobody)))',
|
||||||
|
@ -81,6 +87,12 @@ def user_list(auth, fields=None):
|
||||||
entry = {}
|
entry = {}
|
||||||
for attr, values in user.items():
|
for attr, values in user.items():
|
||||||
if values:
|
if values:
|
||||||
|
if attr == "loginShell":
|
||||||
|
if values[0].strip() == "/bin/false":
|
||||||
|
entry["ssh_allowed"] = False
|
||||||
|
else:
|
||||||
|
entry["ssh_allowed"] = True
|
||||||
|
|
||||||
entry[user_attrs[attr]] = values[0]
|
entry[user_attrs[attr]] = values[0]
|
||||||
|
|
||||||
uid = entry[user_attrs['uid']]
|
uid = entry[user_attrs['uid']]
|
||||||
|
@ -435,6 +447,36 @@ def user_info(auth, username):
|
||||||
raise MoulinetteError(167, m18n.n('user_info_failed'))
|
raise MoulinetteError(167, m18n.n('user_info_failed'))
|
||||||
|
|
||||||
|
|
||||||
|
def user_allow_ssh(auth, username):
|
||||||
|
"""
|
||||||
|
Allow YunoHost user connect as ssh.
|
||||||
|
|
||||||
|
Keyword argument:
|
||||||
|
username -- User username
|
||||||
|
"""
|
||||||
|
# TODO it would be good to support different kind of shells
|
||||||
|
|
||||||
|
if not _get_user_for_ssh(auth, username):
|
||||||
|
raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=username))
|
||||||
|
|
||||||
|
auth.update('uid=%s,ou=users' % username, {'loginShell': '/bin/bash'})
|
||||||
|
|
||||||
|
|
||||||
|
def user_disallow_ssh(auth, username):
|
||||||
|
"""
|
||||||
|
Disallow YunoHost user connect as ssh.
|
||||||
|
|
||||||
|
Keyword argument:
|
||||||
|
username -- User username
|
||||||
|
"""
|
||||||
|
# TODO it would be good to support different kind of shells
|
||||||
|
|
||||||
|
if not _get_user_for_ssh(auth, username) :
|
||||||
|
raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=username))
|
||||||
|
|
||||||
|
auth.update('uid=%s,ou=users' % username, {'loginShell': '/bin/false'})
|
||||||
|
|
||||||
|
|
||||||
def _convertSize(num, suffix=''):
|
def _convertSize(num, suffix=''):
|
||||||
for unit in ['K', 'M', 'G', 'T', 'P', 'E', 'Z']:
|
for unit in ['K', 'M', 'G', 'T', 'P', 'E', 'Z']:
|
||||||
if abs(num) < 1024.0:
|
if abs(num) < 1024.0:
|
||||||
|
@ -470,3 +512,56 @@ def _hash_user_password(password):
|
||||||
|
|
||||||
salt = '$6$' + salt + '$'
|
salt = '$6$' + salt + '$'
|
||||||
return '{CRYPT}' + crypt.crypt(str(password), salt)
|
return '{CRYPT}' + crypt.crypt(str(password), salt)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_user_for_ssh(auth, username, attrs=None):
|
||||||
|
def ssh_root_login_status(auth):
|
||||||
|
# XXX temporary placed here for when the ssh_root commands are integrated
|
||||||
|
# extracted from https://github.com/YunoHost/yunohost/pull/345
|
||||||
|
# XXX should we support all the options?
|
||||||
|
# this is the content of "man sshd_config"
|
||||||
|
# PermitRootLogin
|
||||||
|
# Specifies whether root can log in using ssh(1). The argument must be
|
||||||
|
# “yes”, “without-password”, “forced-commands-only”, or “no”. The
|
||||||
|
# default is “yes”.
|
||||||
|
sshd_config_content = read_file(SSHD_CONFIG_PATH)
|
||||||
|
|
||||||
|
if re.search("^ *PermitRootLogin +(no|forced-commands-only) *$",
|
||||||
|
sshd_config_content, re.MULTILINE):
|
||||||
|
return {"PermitRootLogin": False}
|
||||||
|
|
||||||
|
return {"PermitRootLogin": True}
|
||||||
|
|
||||||
|
if username == "root":
|
||||||
|
root_unix = pwd.getpwnam("root")
|
||||||
|
return {
|
||||||
|
'username': 'root',
|
||||||
|
'fullname': '',
|
||||||
|
'mail': '',
|
||||||
|
'ssh_allowed': ssh_root_login_status(auth)["PermitRootLogin"],
|
||||||
|
'shell': root_unix.pw_shell,
|
||||||
|
'home_path': root_unix.pw_dir,
|
||||||
|
}
|
||||||
|
|
||||||
|
if username == "admin":
|
||||||
|
admin_unix = pwd.getpwnam("admin")
|
||||||
|
return {
|
||||||
|
'username': 'admin',
|
||||||
|
'fullname': '',
|
||||||
|
'mail': '',
|
||||||
|
'ssh_allowed': admin_unix.pw_shell.strip() != "/bin/false",
|
||||||
|
'shell': admin_unix.pw_shell,
|
||||||
|
'home_path': admin_unix.pw_dir,
|
||||||
|
}
|
||||||
|
|
||||||
|
# TODO escape input using https://www.python-ldap.org/doc/html/ldap-filter.html
|
||||||
|
user = auth.search('ou=users,dc=yunohost,dc=org',
|
||||||
|
'(&(objectclass=person)(uid=%s))' % username,
|
||||||
|
attrs)
|
||||||
|
|
||||||
|
assert len(user) in (0, 1)
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return user[0]
|
||||||
|
|
39
src/yunohost/utils/network.py
Normal file
39
src/yunohost/utils/network.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
""" License
|
||||||
|
|
||||||
|
Copyright (C) 2017 YUNOHOST.ORG
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program; if not, see http://www.gnu.org/licenses
|
||||||
|
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from urllib import urlopen
|
||||||
|
|
||||||
|
logger = logging.getLogger('yunohost.utils.network')
|
||||||
|
|
||||||
|
def get_public_ip(protocol=4):
|
||||||
|
"""Retrieve the public IP address from ip.yunohost.org"""
|
||||||
|
|
||||||
|
if protocol == 4:
|
||||||
|
url = 'https://ip.yunohost.org'
|
||||||
|
elif protocol == 6:
|
||||||
|
url = 'https://ip6.yunohost.org'
|
||||||
|
else:
|
||||||
|
raise ValueError("invalid protocol version")
|
||||||
|
|
||||||
|
try:
|
||||||
|
return urlopen(url).read().strip()
|
||||||
|
except IOError:
|
||||||
|
return None
|
|
@ -406,6 +406,7 @@ def get_installed_version(*pkgnames, **kwargs):
|
||||||
# Retrieve options
|
# Retrieve options
|
||||||
as_dict = kwargs.get('as_dict', False)
|
as_dict = kwargs.get('as_dict', False)
|
||||||
strict = kwargs.get('strict', False)
|
strict = kwargs.get('strict', False)
|
||||||
|
with_repo = kwargs.get('with_repo', False)
|
||||||
|
|
||||||
for pkgname in pkgnames:
|
for pkgname in pkgnames:
|
||||||
try:
|
try:
|
||||||
|
@ -414,13 +415,32 @@ def get_installed_version(*pkgnames, **kwargs):
|
||||||
if strict:
|
if strict:
|
||||||
raise UnknownPackage(pkgname)
|
raise UnknownPackage(pkgname)
|
||||||
logger.warning(m18n.n('package_unknown', pkgname=pkgname))
|
logger.warning(m18n.n('package_unknown', pkgname=pkgname))
|
||||||
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
version = pkg.installed.version
|
version = pkg.installed.version
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
if strict:
|
if strict:
|
||||||
raise UninstalledPackage(pkgname)
|
raise UninstalledPackage(pkgname)
|
||||||
version = None
|
version = None
|
||||||
versions[pkgname] = version
|
|
||||||
|
try:
|
||||||
|
# stable, testing, unstable
|
||||||
|
repo = pkg.installed.origins[0].component
|
||||||
|
except AttributeError:
|
||||||
|
if strict:
|
||||||
|
raise UninstalledPackage(pkgname)
|
||||||
|
repo = ""
|
||||||
|
|
||||||
|
if with_repo:
|
||||||
|
versions[pkgname] = {
|
||||||
|
"version": version,
|
||||||
|
# when we don't have component it's because it's from a local
|
||||||
|
# install or from an image (like in vagrant)
|
||||||
|
"repo": repo if repo else "local",
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
versions[pkgname] = version
|
||||||
|
|
||||||
if len(pkgnames) == 1 and not as_dict:
|
if len(pkgnames) == 1 and not as_dict:
|
||||||
return versions[pkgnames[0]]
|
return versions[pkgnames[0]]
|
||||||
|
@ -436,7 +456,11 @@ def meets_version_specifier(pkgname, specifier):
|
||||||
# YunoHost related methods ---------------------------------------------------
|
# YunoHost related methods ---------------------------------------------------
|
||||||
|
|
||||||
def ynh_packages_version(*args, **kwargs):
|
def ynh_packages_version(*args, **kwargs):
|
||||||
|
# from cli the received arguments are:
|
||||||
|
# (Namespace(_callbacks=deque([]), _tid='_global', _to_return={}), []) {}
|
||||||
|
# they don't seem to serve any purpose
|
||||||
"""Return the version of each YunoHost package"""
|
"""Return the version of each YunoHost package"""
|
||||||
return get_installed_version(
|
return get_installed_version(
|
||||||
'yunohost', 'yunohost-admin', 'moulinette', 'ssowat',
|
'yunohost', 'yunohost-admin', 'moulinette', 'ssowat',
|
||||||
|
with_repo=True
|
||||||
)
|
)
|
||||||
|
|
674
src/yunohost/vendor/spectre-meltdown-checker/LICENSE
vendored
Normal file
674
src/yunohost/vendor/spectre-meltdown-checker/LICENSE
vendored
Normal file
|
@ -0,0 +1,674 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
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 3 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, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
45
src/yunohost/vendor/spectre-meltdown-checker/README.md
vendored
Normal file
45
src/yunohost/vendor/spectre-meltdown-checker/README.md
vendored
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
Spectre & Meltdown Checker
|
||||||
|
==========================
|
||||||
|
|
||||||
|
A simple shell script to tell if your Linux installation is vulnerable against the 3 "speculative execution" CVEs that were made public early 2018.
|
||||||
|
|
||||||
|
Without options, it'll inspect your currently running kernel.
|
||||||
|
You can also specify a kernel image on the command line, if you'd like to inspect a kernel you're not running.
|
||||||
|
|
||||||
|
The script will do its best to detect mitigations, including backported non-vanilla patches, regardless of the advertised kernel version number.
|
||||||
|
|
||||||
|
## Example of script output
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Quick summary of the CVEs
|
||||||
|
|
||||||
|
**CVE-2017-5753** bounds check bypass (Spectre Variant 1)
|
||||||
|
|
||||||
|
- Impact: Kernel & all software
|
||||||
|
- Mitigation: recompile software *and* kernel with a modified compiler that introduces the LFENCE opcode at the proper positions in the resulting code
|
||||||
|
- Performance impact of the mitigation: negligible
|
||||||
|
|
||||||
|
**CVE-2017-5715** branch target injection (Spectre Variant 2)
|
||||||
|
|
||||||
|
- Impact: Kernel
|
||||||
|
- Mitigation 1: new opcode via microcode update that should be used by up to date compilers to protect the BTB (by flushing indirect branch predictors)
|
||||||
|
- Mitigation 2: introducing "retpoline" into compilers, and recompile software/OS with it
|
||||||
|
- Performance impact of the mitigation: high for mitigation 1, medium for mitigation 2, depending on your CPU
|
||||||
|
|
||||||
|
**CVE-2017-5754** rogue data cache load (Meltdown)
|
||||||
|
|
||||||
|
- Impact: Kernel
|
||||||
|
- Mitigation: updated kernel (with PTI/KPTI patches), updating the kernel is enough
|
||||||
|
- Performance impact of the mitigation: low to medium
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
This tool does its best to determine whether your system is immune (or has proper mitigations in place) for the collectively named "speculative execution" vulnerabilities. It doesn't attempt to run any kind of exploit, and can't guarantee that your system is secure, but rather helps you verifying whether your system has the known correct mitigations in place.
|
||||||
|
However, some mitigations could also exist in your kernel that this script doesn't know (yet) how to detect, or it might falsely detect mitigations that in the end don't work as expected (for example, on backported or modified kernels).
|
||||||
|
|
||||||
|
Your system exposure also depends on your CPU. As of now, AMD and ARM processors are marked as immune to some or all of these vulnerabilities (except some specific ARM models). All Intel processors manufactured since circa 1995 are thought to be vulnerable. Whatever processor one uses, one might seek more information from the manufacturer of that processor and/or of the device in which it runs.
|
||||||
|
|
||||||
|
The nature of the discovered vulnerabilities being quite new, the landscape of vulnerable processors can be expected to change over time, which is why this script makes the assumption that all CPUs are vulnerable, except if the manufacturer explicitly stated otherwise in a verifiable public announcement.
|
||||||
|
|
||||||
|
This tool has been released in the hope that it'll be useful, but don't use it to jump to conclusions about your security.
|
982
src/yunohost/vendor/spectre-meltdown-checker/spectre-meltdown-checker.sh
vendored
Executable file
982
src/yunohost/vendor/spectre-meltdown-checker/spectre-meltdown-checker.sh
vendored
Executable file
|
@ -0,0 +1,982 @@
|
||||||
|
#! /bin/sh
|
||||||
|
# Spectre & Meltdown checker
|
||||||
|
#
|
||||||
|
# Check for the latest version at:
|
||||||
|
# https://github.com/speed47/spectre-meltdown-checker
|
||||||
|
# git clone https://github.com/speed47/spectre-meltdown-checker.git
|
||||||
|
# or wget https://raw.githubusercontent.com/speed47/spectre-meltdown-checker/master/spectre-meltdown-checker.sh
|
||||||
|
#
|
||||||
|
# Stephane Lesimple
|
||||||
|
#
|
||||||
|
VERSION=0.29
|
||||||
|
|
||||||
|
# Script configuration
|
||||||
|
show_usage()
|
||||||
|
{
|
||||||
|
cat <<EOF
|
||||||
|
Usage:
|
||||||
|
Live mode: `basename $0` [options] [--live]
|
||||||
|
Offline mode: `basename $0` [options] [--kernel <vmlinux_file>] [--config <kernel_config>] [--map <kernel_map_file>]
|
||||||
|
|
||||||
|
Modes:
|
||||||
|
Two modes are available.
|
||||||
|
|
||||||
|
First mode is the "live" mode (default), it does its best to find information about the currently running kernel.
|
||||||
|
To run under this mode, just start the script without any option (you can also use --live explicitly)
|
||||||
|
|
||||||
|
Second mode is the "offline" mode, where you can inspect a non-running kernel.
|
||||||
|
You'll need to specify the location of the vmlinux file, and if possible, the corresponding config and System.map files:
|
||||||
|
|
||||||
|
--kernel vmlinux_file Specify a (possibly compressed) vmlinux file
|
||||||
|
--config kernel_config Specify a kernel config file
|
||||||
|
--map kernel_map_file Specify a kernel System.map file
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--no-color Don't use color codes
|
||||||
|
--verbose, -v Increase verbosity level
|
||||||
|
--no-sysfs Don't use the /sys interface even if present
|
||||||
|
--batch text Produce machine readable output, this is the default if --batch is specified alone
|
||||||
|
--batch json Produce JSON output formatted for Puppet, Ansible, Chef...
|
||||||
|
--batch nrpe Produce machine readable output formatted for NRPE
|
||||||
|
--variant [1,2,3] Specify which variant you'd like to check, by default all variants are checked
|
||||||
|
Can be specified multiple times (e.g. --variant 2 --variant 3)
|
||||||
|
|
||||||
|
|
||||||
|
IMPORTANT:
|
||||||
|
A false sense of security is worse than no security at all.
|
||||||
|
Please use the --disclaimer option to understand exactly what this script does.
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
show_disclaimer()
|
||||||
|
{
|
||||||
|
cat <<EOF
|
||||||
|
Disclaimer:
|
||||||
|
|
||||||
|
This tool does its best to determine whether your system is immune (or has proper mitigations in place) for the
|
||||||
|
collectively named "speculative execution" vulnerabilities. It doesn't attempt to run any kind of exploit, and can't guarantee
|
||||||
|
that your system is secure, but rather helps you verifying whether your system has the known correct mitigations in place.
|
||||||
|
However, some mitigations could also exist in your kernel that this script doesn't know (yet) how to detect, or it might
|
||||||
|
falsely detect mitigations that in the end don't work as expected (for example, on backported or modified kernels).
|
||||||
|
|
||||||
|
Your system exposure also depends on your CPU. As of now, AMD and ARM processors are marked as immune to some or all of these
|
||||||
|
vulnerabilities (except some specific ARM models). All Intel processors manufactured since circa 1995 are thought to be vulnerable.
|
||||||
|
Whatever processor one uses, one might seek more information from the manufacturer of that processor and/or of the device
|
||||||
|
in which it runs.
|
||||||
|
|
||||||
|
The nature of the discovered vulnerabilities being quite new, the landscape of vulnerable processors can be expected
|
||||||
|
to change over time, which is why this script makes the assumption that all CPUs are vulnerable, except if the manufacturer
|
||||||
|
explicitly stated otherwise in a verifiable public announcement.
|
||||||
|
|
||||||
|
This tool has been released in the hope that it'll be useful, but don't use it to jump to conclusions about your security.
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# parse options
|
||||||
|
opt_kernel=''
|
||||||
|
opt_config=''
|
||||||
|
opt_map=''
|
||||||
|
opt_live_explicit=0
|
||||||
|
opt_live=1
|
||||||
|
opt_no_color=0
|
||||||
|
opt_batch=0
|
||||||
|
opt_batch_format="text"
|
||||||
|
opt_verbose=1
|
||||||
|
opt_variant1=0
|
||||||
|
opt_variant2=0
|
||||||
|
opt_variant3=0
|
||||||
|
opt_allvariants=1
|
||||||
|
opt_no_sysfs=0
|
||||||
|
|
||||||
|
nrpe_critical=0
|
||||||
|
nrpe_unknown=0
|
||||||
|
nrpe_vuln=""
|
||||||
|
|
||||||
|
__echo()
|
||||||
|
{
|
||||||
|
opt="$1"
|
||||||
|
shift
|
||||||
|
_msg="$@"
|
||||||
|
if [ "$opt_no_color" = 1 ] ; then
|
||||||
|
# strip ANSI color codes
|
||||||
|
_msg=$(/bin/echo -e "$_msg" | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g")
|
||||||
|
fi
|
||||||
|
# explicitly call /bin/echo to avoid shell builtins that might not take options
|
||||||
|
/bin/echo $opt -e "$_msg"
|
||||||
|
}
|
||||||
|
|
||||||
|
_echo()
|
||||||
|
{
|
||||||
|
if [ $opt_verbose -ge $1 ]; then
|
||||||
|
shift
|
||||||
|
__echo '' "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
_echo_nol()
|
||||||
|
{
|
||||||
|
if [ $opt_verbose -ge $1 ]; then
|
||||||
|
shift
|
||||||
|
__echo -n "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
_warn()
|
||||||
|
{
|
||||||
|
_echo 0 "\033[31m${@}\033[0m" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
_info()
|
||||||
|
{
|
||||||
|
_echo 1 "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
_info_nol()
|
||||||
|
{
|
||||||
|
_echo_nol 1 "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
_verbose()
|
||||||
|
{
|
||||||
|
_echo 2 "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
_debug()
|
||||||
|
{
|
||||||
|
_echo 3 "\033[34m(debug) $@\033[0m"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_cpu_vulnerable()
|
||||||
|
{
|
||||||
|
# param: 1, 2 or 3 (variant)
|
||||||
|
# returns 0 if vulnerable, 1 if not vulnerable
|
||||||
|
# (note that in shell, a return of 0 is success)
|
||||||
|
# by default, everything is vulnerable, we work in a "whitelist" logic here.
|
||||||
|
# usage: is_cpu_vulnerable 2 && do something if vulnerable
|
||||||
|
variant1=0
|
||||||
|
variant2=0
|
||||||
|
variant3=0
|
||||||
|
|
||||||
|
if grep -q AMD /proc/cpuinfo; then
|
||||||
|
# AMD revised their statement about variant2 => vulnerable
|
||||||
|
# https://www.amd.com/en/corporate/speculative-execution
|
||||||
|
variant3=1
|
||||||
|
elif grep -qi 'CPU implementer\s*:\s*0x41' /proc/cpuinfo; then
|
||||||
|
# ARM
|
||||||
|
# reference: https://developer.arm.com/support/security-update
|
||||||
|
cpupart=$(awk '/CPU part/ {print $4;exit}' /proc/cpuinfo)
|
||||||
|
cpuarch=$(awk '/CPU architecture/ {print $3;exit}' /proc/cpuinfo)
|
||||||
|
if [ -n "$cpupart" -a -n "$cpuarch" ]; then
|
||||||
|
# Cortex-R7 and Cortex-R8 are real-time and only used in medical devices or such
|
||||||
|
# I can't find their CPU part number, but it's probably not that useful anyway
|
||||||
|
# model R7 R8 A9 A15 A17 A57 A72 A73 A75
|
||||||
|
# part ? ? 0xc09 0xc0f 0xc0e 0xd07 0xd08 0xd09 0xd0a
|
||||||
|
# arch 7? 7? 7 7 7 8 8 8 8
|
||||||
|
if [ "$cpuarch" = 7 ] && echo "$cpupart" | grep -Eq '^0x(c09|c0f|c0e)$'; then
|
||||||
|
# armv7 vulnerable chips
|
||||||
|
:
|
||||||
|
elif [ "$cpuarch" = 8 ] && echo "$cpupart" | grep -Eq '^0x(d07|d08|d09|d0a)$'; then
|
||||||
|
# armv8 vulnerable chips
|
||||||
|
:
|
||||||
|
else
|
||||||
|
variant1=1
|
||||||
|
variant2=1
|
||||||
|
fi
|
||||||
|
# for variant3, only A75 is vulnerable
|
||||||
|
if ! [ "$cpuarch" = 8 -a "$cpupart" = 0xd0a ]; then
|
||||||
|
variant3=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ "$1" = 1 ] && return $variant1
|
||||||
|
[ "$1" = 2 ] && return $variant2
|
||||||
|
[ "$1" = 3 ] && return $variant3
|
||||||
|
echo "$0: error: invalid variant '$1' passed to is_cpu_vulnerable()" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
show_header()
|
||||||
|
{
|
||||||
|
_info "\033[1;34mSpectre and Meltdown mitigation detection tool v$VERSION\033[0m"
|
||||||
|
_info
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_opt_file()
|
||||||
|
{
|
||||||
|
# parse_opt_file option_name option_value
|
||||||
|
option_name="$1"
|
||||||
|
option_value="$2"
|
||||||
|
if [ -z "$option_value" ]; then
|
||||||
|
show_header
|
||||||
|
show_usage
|
||||||
|
echo "$0: error: --$option_name expects one parameter (a file)" >&2
|
||||||
|
exit 1
|
||||||
|
elif [ ! -e "$option_value" ]; then
|
||||||
|
show_header
|
||||||
|
echo "$0: error: couldn't find file $option_value" >&2
|
||||||
|
exit 1
|
||||||
|
elif [ ! -f "$option_value" ]; then
|
||||||
|
show_header
|
||||||
|
echo "$0: error: $option_value is not a file" >&2
|
||||||
|
exit 1
|
||||||
|
elif [ ! -r "$option_value" ]; then
|
||||||
|
show_header
|
||||||
|
echo "$0: error: couldn't read $option_value (are you root?)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "$option_value"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
while [ -n "$1" ]; do
|
||||||
|
if [ "$1" = "--kernel" ]; then
|
||||||
|
opt_kernel=$(parse_opt_file kernel "$2")
|
||||||
|
[ $? -ne 0 ] && exit $?
|
||||||
|
shift 2
|
||||||
|
opt_live=0
|
||||||
|
elif [ "$1" = "--config" ]; then
|
||||||
|
opt_config=$(parse_opt_file config "$2")
|
||||||
|
[ $? -ne 0 ] && exit $?
|
||||||
|
shift 2
|
||||||
|
opt_live=0
|
||||||
|
elif [ "$1" = "--map" ]; then
|
||||||
|
opt_map=$(parse_opt_file map "$2")
|
||||||
|
[ $? -ne 0 ] && exit $?
|
||||||
|
shift 2
|
||||||
|
opt_live=0
|
||||||
|
elif [ "$1" = "--live" ]; then
|
||||||
|
opt_live_explicit=1
|
||||||
|
shift
|
||||||
|
elif [ "$1" = "--no-color" ]; then
|
||||||
|
opt_no_color=1
|
||||||
|
shift
|
||||||
|
elif [ "$1" = "--no-sysfs" ]; then
|
||||||
|
opt_no_sysfs=1
|
||||||
|
shift
|
||||||
|
elif [ "$1" = "--batch" ]; then
|
||||||
|
opt_batch=1
|
||||||
|
opt_verbose=0
|
||||||
|
shift
|
||||||
|
case "$1" in
|
||||||
|
text|nrpe|json) opt_batch_format="$1"; shift;;
|
||||||
|
--*) ;; # allow subsequent flags
|
||||||
|
'') ;; # allow nothing at all
|
||||||
|
*)
|
||||||
|
echo "$0: error: unknown batch format '$1'"
|
||||||
|
echo "$0: error: --batch expects a format from: text, nrpe, json"
|
||||||
|
exit 1 >&2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
elif [ "$1" = "-v" -o "$1" = "--verbose" ]; then
|
||||||
|
opt_verbose=$(expr $opt_verbose + 1)
|
||||||
|
shift
|
||||||
|
elif [ "$1" = "--variant" ]; then
|
||||||
|
if [ -z "$2" ]; then
|
||||||
|
echo "$0: error: option --variant expects a parameter (1, 2 or 3)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
case "$2" in
|
||||||
|
1) opt_variant1=1; opt_allvariants=0;;
|
||||||
|
2) opt_variant2=1; opt_allvariants=0;;
|
||||||
|
3) opt_variant3=1; opt_allvariants=0;;
|
||||||
|
*)
|
||||||
|
echo "$0: error: invalid parameter '$2' for --variant, expected either 1, 2 or 3" >&2;
|
||||||
|
exit 1;;
|
||||||
|
esac
|
||||||
|
shift 2
|
||||||
|
elif [ "$1" = "-h" -o "$1" = "--help" ]; then
|
||||||
|
show_header
|
||||||
|
show_usage
|
||||||
|
exit 0
|
||||||
|
elif [ "$1" = "--version" ]; then
|
||||||
|
opt_no_color=1
|
||||||
|
show_header
|
||||||
|
exit 1
|
||||||
|
elif [ "$1" = "--disclaimer" ]; then
|
||||||
|
show_header
|
||||||
|
show_disclaimer
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
show_header
|
||||||
|
show_usage
|
||||||
|
echo "$0: error: unknown option '$1'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
show_header
|
||||||
|
|
||||||
|
# print status function
|
||||||
|
pstatus()
|
||||||
|
{
|
||||||
|
if [ "$opt_no_color" = 1 ]; then
|
||||||
|
_info_nol "$2"
|
||||||
|
else
|
||||||
|
case "$1" in
|
||||||
|
red) col="\033[101m\033[30m";;
|
||||||
|
green) col="\033[102m\033[30m";;
|
||||||
|
yellow) col="\033[103m\033[30m";;
|
||||||
|
blue) col="\033[104m\033[30m";;
|
||||||
|
*) col="";;
|
||||||
|
esac
|
||||||
|
_info_nol "$col $2 \033[0m"
|
||||||
|
fi
|
||||||
|
[ -n "$3" ] && _info_nol " ($3)"
|
||||||
|
_info
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print the final status of a vulnerability (incl. batch mode)
|
||||||
|
# Arguments are: CVE UNK/OK/VULN description
|
||||||
|
pvulnstatus()
|
||||||
|
{
|
||||||
|
if [ "$opt_batch" = 1 ]; then
|
||||||
|
case "$opt_batch_format" in
|
||||||
|
text) _echo 0 "$1: $2 ($3)";;
|
||||||
|
nrpe)
|
||||||
|
case "$2" in
|
||||||
|
UKN) nrpe_unknown="1";;
|
||||||
|
VULN) nrpe_critical="1"; nrpe_vuln="$nrpe_vuln $1";;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
json)
|
||||||
|
case "$1" in
|
||||||
|
CVE-2017-5753) aka="SPECTRE VARIANT 1";;
|
||||||
|
CVE-2017-5715) aka="SPECTRE VARIANT 2";;
|
||||||
|
CVE-2017-5754) aka="MELTDOWN";;
|
||||||
|
esac
|
||||||
|
case "$2" in
|
||||||
|
UKN) is_vuln="unknown";;
|
||||||
|
VULN) is_vuln="true";;
|
||||||
|
OK) is_vuln="false";;
|
||||||
|
esac
|
||||||
|
json_output="${json_output:-[}{\"NAME\":\""$aka"\",\"CVE\":\""$1"\",\"VULNERABLE\":$is_vuln,\"INFOS\":\""$3"\"},"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
_info_nol "> \033[46m\033[30mSTATUS:\033[0m "
|
||||||
|
vulnstatus="$2"
|
||||||
|
shift 2
|
||||||
|
case "$vulnstatus" in
|
||||||
|
UNK) pstatus yellow UNKNOWN "$@";;
|
||||||
|
VULN) pstatus red 'VULNERABLE' "$@";;
|
||||||
|
OK) pstatus green 'NOT VULNERABLE' "$@";;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# The 3 below functions are taken from the extract-linux script, available here:
|
||||||
|
# https://github.com/torvalds/linux/blob/master/scripts/extract-vmlinux
|
||||||
|
# The functions have been modified for better integration to this script
|
||||||
|
# The original header of the file has been retained below
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# extract-vmlinux - Extract uncompressed vmlinux from a kernel image
|
||||||
|
#
|
||||||
|
# Inspired from extract-ikconfig
|
||||||
|
# (c) 2009,2010 Dick Streefland <dick@streefland.net>
|
||||||
|
#
|
||||||
|
# (c) 2011 Corentin Chary <corentin.chary@gmail.com>
|
||||||
|
#
|
||||||
|
# Licensed under the GNU General Public License, version 2 (GPLv2).
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
vmlinux=''
|
||||||
|
vmlinux_err=''
|
||||||
|
check_vmlinux()
|
||||||
|
{
|
||||||
|
readelf -h "$1" > /dev/null 2>&1 || return 1
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
try_decompress()
|
||||||
|
{
|
||||||
|
# The obscure use of the "tr" filter is to work around older versions of
|
||||||
|
# "grep" that report the byte offset of the line instead of the pattern.
|
||||||
|
|
||||||
|
# Try to find the header ($1) and decompress from here
|
||||||
|
for pos in `tr "$1\n$2" "\n$2=" < "$6" | grep -abo "^$2"`
|
||||||
|
do
|
||||||
|
_debug "try_decompress: magic for $3 found at offset $pos"
|
||||||
|
if ! which "$3" >/dev/null 2>&1; then
|
||||||
|
vmlinux_err="missing '$3' tool, please install it, usually it's in the '$5' package"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
pos=${pos%%:*}
|
||||||
|
tail -c+$pos "$6" 2>/dev/null | $3 $4 > $vmlinuxtmp 2>/dev/null
|
||||||
|
if check_vmlinux "$vmlinuxtmp"; then
|
||||||
|
vmlinux="$vmlinuxtmp"
|
||||||
|
_debug "try_decompress: decompressed with $3 successfully!"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
_debug "try_decompress: decompression with $3 did not work"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
extract_vmlinux()
|
||||||
|
{
|
||||||
|
[ -n "$1" ] || return 1
|
||||||
|
# Prepare temp files:
|
||||||
|
vmlinuxtmp="$(mktemp /tmp/vmlinux-XXXXXX)"
|
||||||
|
trap "rm -f $vmlinuxtmp" EXIT
|
||||||
|
|
||||||
|
# Initial attempt for uncompressed images or objects:
|
||||||
|
if check_vmlinux "$1"; then
|
||||||
|
cat "$1" > "$vmlinuxtmp"
|
||||||
|
vmlinux=$vmlinuxtmp
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# That didn't work, so retry after decompression.
|
||||||
|
try_decompress '\037\213\010' xy gunzip '' gunzip "$1" && return 0
|
||||||
|
try_decompress '\3757zXZ\000' abcde unxz '' xz-utils "$1" && return 0
|
||||||
|
try_decompress 'BZh' xy bunzip2 '' bzip2 "$1" && return 0
|
||||||
|
try_decompress '\135\0\0\0' xxx unlzma '' xz-utils "$1" && return 0
|
||||||
|
try_decompress '\211\114\132' xy 'lzop' '-d' lzop "$1" && return 0
|
||||||
|
try_decompress '\002\041\114\030' xyy 'lz4' '-d -l' liblz4-tool "$1" && return 0
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# end of extract-vmlinux functions
|
||||||
|
|
||||||
|
# check for mode selection inconsistency
|
||||||
|
if [ "$opt_live_explicit" = 1 ]; then
|
||||||
|
if [ -n "$opt_kernel" -o -n "$opt_config" -o -n "$opt_map" ]; then
|
||||||
|
show_usage
|
||||||
|
echo "$0: error: incompatible modes specified, use either --live or --kernel/--config/--map"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# root check (only for live mode, for offline mode, we already checked if we could read the files)
|
||||||
|
|
||||||
|
if [ "$opt_live" = 1 ]; then
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
|
_warn "Note that you should launch this script with root privileges to get accurate information."
|
||||||
|
_warn "We'll proceed but you might see permission denied errors."
|
||||||
|
_warn "To run it as root, you can try the following command: sudo $0"
|
||||||
|
_warn
|
||||||
|
fi
|
||||||
|
_info "Checking for vulnerabilities against running kernel \033[35m"$(uname -s) $(uname -r) $(uname -v) $(uname -m)"\033[0m"
|
||||||
|
_info "CPU is\033[35m"$(grep '^model name' /proc/cpuinfo | cut -d: -f2 | head -1)"\033[0m"
|
||||||
|
|
||||||
|
# try to find the image of the current running kernel
|
||||||
|
# first, look for the BOOT_IMAGE hint in the kernel cmdline
|
||||||
|
if [ -r /proc/cmdline ] && grep -q 'BOOT_IMAGE=' /proc/cmdline; then
|
||||||
|
opt_kernel=$(grep -Eo 'BOOT_IMAGE=[^ ]+' /proc/cmdline | cut -d= -f2)
|
||||||
|
_debug "found opt_kernel=$opt_kernel in /proc/cmdline"
|
||||||
|
# if we have a dedicated /boot partition, our bootloader might have just called it /
|
||||||
|
# so try to prepend /boot and see if we find anything
|
||||||
|
[ -e "/boot/$opt_kernel" ] && opt_kernel="/boot/$opt_kernel"
|
||||||
|
_debug "opt_kernel is now $opt_kernel"
|
||||||
|
# else, the full path is already there (most probably /boot/something)
|
||||||
|
fi
|
||||||
|
# if we didn't find a kernel, default to guessing
|
||||||
|
if [ ! -e "$opt_kernel" ]; then
|
||||||
|
[ -e /boot/vmlinuz-linux ] && opt_kernel=/boot/vmlinuz-linux
|
||||||
|
[ -e /boot/vmlinuz-linux-libre ] && opt_kernel=/boot/vmlinuz-linux-libre
|
||||||
|
[ -e /boot/vmlinuz-$(uname -r) ] && opt_kernel=/boot/vmlinuz-$(uname -r)
|
||||||
|
[ -e /boot/kernel-$( uname -r) ] && opt_kernel=/boot/kernel-$( uname -r)
|
||||||
|
[ -e /boot/bzImage-$(uname -r) ] && opt_kernel=/boot/bzImage-$(uname -r)
|
||||||
|
[ -e /boot/kernel-genkernel-$(uname -m)-$(uname -r) ] && opt_kernel=/boot/kernel-genkernel-$(uname -m)-$(uname -r)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# system.map
|
||||||
|
if [ -e /proc/kallsyms ] ; then
|
||||||
|
opt_map="/proc/kallsyms"
|
||||||
|
elif [ -e /boot/System.map-$(uname -r) ] ; then
|
||||||
|
opt_map=/boot/System.map-$(uname -r)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# config
|
||||||
|
if [ -e /proc/config.gz ] ; then
|
||||||
|
dumped_config="$(mktemp /tmp/config-XXXXXX)"
|
||||||
|
gunzip -c /proc/config.gz > $dumped_config
|
||||||
|
# dumped_config will be deleted at the end of the script
|
||||||
|
opt_config=$dumped_config
|
||||||
|
elif [ -e /boot/config-$(uname -r) ]; then
|
||||||
|
opt_config=/boot/config-$(uname -r)
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
_info "Checking for vulnerabilities against specified kernel"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$opt_kernel" ]; then
|
||||||
|
_verbose "Will use vmlinux image \033[35m$opt_kernel\033[0m"
|
||||||
|
else
|
||||||
|
_verbose "Will use no vmlinux image (accuracy might be reduced)"
|
||||||
|
bad_accuracy=1
|
||||||
|
fi
|
||||||
|
if [ -n "$dumped_config" ]; then
|
||||||
|
_verbose "Will use kconfig \033[35m/proc/config.gz\033[0m"
|
||||||
|
elif [ -n "$opt_config" ]; then
|
||||||
|
_verbose "Will use kconfig \033[35m$opt_config\033[0m"
|
||||||
|
else
|
||||||
|
_verbose "Will use no kconfig (accuracy might be reduced)"
|
||||||
|
bad_accuracy=1
|
||||||
|
fi
|
||||||
|
if [ -n "$opt_map" ]; then
|
||||||
|
_verbose "Will use System.map file \033[35m$opt_map\033[0m"
|
||||||
|
else
|
||||||
|
_verbose "Will use no System.map file (accuracy might be reduced)"
|
||||||
|
bad_accuracy=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$bad_accuracy" = 1 ]; then
|
||||||
|
_info "We're missing some kernel info (see -v), accuracy might be reduced"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -e "$opt_kernel" ]; then
|
||||||
|
if ! which readelf >/dev/null 2>&1; then
|
||||||
|
vmlinux_err="missing 'readelf' tool, please install it, usually it's in the 'binutils' package"
|
||||||
|
else
|
||||||
|
extract_vmlinux "$opt_kernel"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
vmlinux_err="couldn't find your kernel image in /boot, if you used netboot, this is normal"
|
||||||
|
fi
|
||||||
|
if [ -z "$vmlinux" -o ! -r "$vmlinux" ]; then
|
||||||
|
[ -z "$vmlinux_err" ] && vmlinux_err="couldn't extract your kernel from $opt_kernel"
|
||||||
|
fi
|
||||||
|
|
||||||
|
_info
|
||||||
|
|
||||||
|
# end of header stuff
|
||||||
|
|
||||||
|
# now we define some util functions and the check_*() funcs, as
|
||||||
|
# the user can choose to execute only some of those
|
||||||
|
|
||||||
|
mount_debugfs()
|
||||||
|
{
|
||||||
|
if [ ! -e /sys/kernel/debug/sched_features ]; then
|
||||||
|
# try to mount the debugfs hierarchy ourselves and remember it to umount afterwards
|
||||||
|
mount -t debugfs debugfs /sys/kernel/debug 2>/dev/null && mounted_debugfs=1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
umount_debugfs()
|
||||||
|
{
|
||||||
|
if [ "$mounted_debugfs" = 1 ]; then
|
||||||
|
# umount debugfs if we did mount it ourselves
|
||||||
|
umount /sys/kernel/debug
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
sys_interface_check()
|
||||||
|
{
|
||||||
|
[ "$opt_live" = 1 -a "$opt_no_sysfs" = 0 -a -r "$1" ] || return 1
|
||||||
|
_info_nol "* Checking whether we're safe according to the /sys interface: "
|
||||||
|
if grep -qi '^not affected' "$1"; then
|
||||||
|
# Not affected
|
||||||
|
status=OK
|
||||||
|
pstatus green YES "kernel confirms that your CPU is unaffected"
|
||||||
|
elif grep -qi '^mitigation' "$1"; then
|
||||||
|
# Mitigation: PTI
|
||||||
|
status=OK
|
||||||
|
pstatus green YES "kernel confirms that the mitigation is active"
|
||||||
|
elif grep -qi '^vulnerable' "$1"; then
|
||||||
|
# Vulnerable
|
||||||
|
status=VULN
|
||||||
|
pstatus red NO "kernel confirms your system is vulnerable"
|
||||||
|
else
|
||||||
|
status=UNK
|
||||||
|
pstatus yellow UNKNOWN "unknown value reported by kernel"
|
||||||
|
fi
|
||||||
|
msg=$(cat "$1")
|
||||||
|
_debug "sys_interface_check: $1=$msg"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
###################
|
||||||
|
# SPECTRE VARIANT 1
|
||||||
|
check_variant1()
|
||||||
|
{
|
||||||
|
_info "\033[1;34mCVE-2017-5753 [bounds check bypass] aka 'Spectre Variant 1'\033[0m"
|
||||||
|
|
||||||
|
status=UNK
|
||||||
|
sys_interface_available=0
|
||||||
|
msg=''
|
||||||
|
if sys_interface_check "/sys/devices/system/cpu/vulnerabilities/spectre_v1"; then
|
||||||
|
# this kernel has the /sys interface, trust it over everything
|
||||||
|
sys_interface_available=1
|
||||||
|
else
|
||||||
|
# no /sys interface (or offline mode), fallback to our own ways
|
||||||
|
_info_nol "* Checking count of LFENCE opcodes in kernel: "
|
||||||
|
if [ -n "$vmlinux_err" ]; then
|
||||||
|
msg="couldn't check ($vmlinux_err)"
|
||||||
|
status=UNK
|
||||||
|
pstatus yellow UNKNOWN
|
||||||
|
else
|
||||||
|
if ! which objdump >/dev/null 2>&1; then
|
||||||
|
msg="missing 'objdump' tool, please install it, usually it's in the binutils package"
|
||||||
|
status=UNK
|
||||||
|
pstatus yellow UNKNOWN
|
||||||
|
else
|
||||||
|
# here we disassemble the kernel and count the number of occurrences of the LFENCE opcode
|
||||||
|
# in non-patched kernels, this has been empirically determined as being around 40-50
|
||||||
|
# in patched kernels, this is more around 70-80, sometimes way higher (100+)
|
||||||
|
# v0.13: 68 found in a 3.10.23-xxxx-std-ipv6-64 (with lots of modules compiled-in directly), which doesn't have the LFENCE patches,
|
||||||
|
# so let's push the threshold to 70.
|
||||||
|
nb_lfence=$(objdump -d "$vmlinux" | grep -wc lfence)
|
||||||
|
if [ "$nb_lfence" -lt 70 ]; then
|
||||||
|
msg="only $nb_lfence opcodes found, should be >= 70, heuristic to be improved when official patches become available"
|
||||||
|
status=VULN
|
||||||
|
pstatus red NO
|
||||||
|
else
|
||||||
|
msg="$nb_lfence opcodes found, which is >= 70, heuristic to be improved when official patches become available"
|
||||||
|
status=OK
|
||||||
|
pstatus green YES
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# if we have the /sys interface, don't even check is_cpu_vulnerable ourselves, the kernel already does it
|
||||||
|
if [ "$sys_interface_available" = 0 ] && ! is_cpu_vulnerable 1; then
|
||||||
|
# override status & msg in case CPU is not vulnerable after all
|
||||||
|
msg="your CPU vendor reported your CPU model as not vulnerable"
|
||||||
|
status=OK
|
||||||
|
fi
|
||||||
|
|
||||||
|
# report status
|
||||||
|
pvulnstatus CVE-2017-5753 "$status" "$msg"
|
||||||
|
}
|
||||||
|
|
||||||
|
###################
|
||||||
|
# SPECTRE VARIANT 2
|
||||||
|
check_variant2()
|
||||||
|
{
|
||||||
|
_info "\033[1;34mCVE-2017-5715 [branch target injection] aka 'Spectre Variant 2'\033[0m"
|
||||||
|
|
||||||
|
status=UNK
|
||||||
|
sys_interface_available=0
|
||||||
|
msg=''
|
||||||
|
if sys_interface_check "/sys/devices/system/cpu/vulnerabilities/spectre_v2"; then
|
||||||
|
# this kernel has the /sys interface, trust it over everything
|
||||||
|
sys_interface_available=1
|
||||||
|
else
|
||||||
|
_info "* Mitigation 1"
|
||||||
|
_info_nol "* Hardware (CPU microcode) support for mitigation: "
|
||||||
|
if [ ! -e /dev/cpu/0/msr ]; then
|
||||||
|
# try to load the module ourselves (and remember it so we can rmmod it afterwards)
|
||||||
|
modprobe msr 2>/dev/null && insmod_msr=1
|
||||||
|
_debug "attempted to load module msr, ret=$insmod_msr"
|
||||||
|
fi
|
||||||
|
if [ ! -e /dev/cpu/0/msr ]; then
|
||||||
|
pstatus yellow UNKNOWN "couldn't read /dev/cpu/0/msr, is msr support enabled in your kernel?"
|
||||||
|
else
|
||||||
|
# the new MSR 'SPEC_CTRL' is at offset 0x48
|
||||||
|
# here we use dd, it's the same as using 'rdmsr 0x48' but without needing the rdmsr tool
|
||||||
|
# if we get a read error, the MSR is not there
|
||||||
|
dd if=/dev/cpu/0/msr of=/dev/null bs=8 count=1 skip=9 2>/dev/null
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
pstatus green YES
|
||||||
|
else
|
||||||
|
pstatus red NO
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$insmod_msr" = 1 ]; then
|
||||||
|
# if we used modprobe ourselves, rmmod the module
|
||||||
|
rmmod msr 2>/dev/null
|
||||||
|
_debug "attempted to unload module msr, ret=$?"
|
||||||
|
fi
|
||||||
|
|
||||||
|
_info_nol "* Kernel support for IBRS: "
|
||||||
|
if [ "$opt_live" = 1 ]; then
|
||||||
|
mount_debugfs
|
||||||
|
for ibrs_file in \
|
||||||
|
/sys/kernel/debug/ibrs_enabled \
|
||||||
|
/sys/kernel/debug/x86/ibrs_enabled \
|
||||||
|
/proc/sys/kernel/ibrs_enabled; do
|
||||||
|
if [ -e "$ibrs_file" ]; then
|
||||||
|
# if the file is there, we have IBRS compiled-in
|
||||||
|
# /sys/kernel/debug/ibrs_enabled: vanilla
|
||||||
|
# /sys/kernel/debug/x86/ibrs_enabled: RedHat (see https://access.redhat.com/articles/3311301)
|
||||||
|
# /proc/sys/kernel/ibrs_enabled: OpenSUSE tumbleweed
|
||||||
|
pstatus green YES
|
||||||
|
ibrs_supported=1
|
||||||
|
ibrs_enabled=$(cat "$ibrs_file" 2>/dev/null)
|
||||||
|
_debug "ibrs: found $ibrs_file=$ibrs_enabled"
|
||||||
|
break
|
||||||
|
else
|
||||||
|
_debug "ibrs: file $ibrs_file doesn't exist"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
if [ "$ibrs_supported" != 1 -a -n "$opt_map" ]; then
|
||||||
|
if grep -q spec_ctrl "$opt_map"; then
|
||||||
|
pstatus green YES
|
||||||
|
ibrs_supported=1
|
||||||
|
_debug "ibrs: found '*spec_ctrl*' symbol in $opt_map"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ "$ibrs_supported" != 1 ]; then
|
||||||
|
pstatus red NO
|
||||||
|
fi
|
||||||
|
|
||||||
|
_info_nol "* IBRS enabled for Kernel space: "
|
||||||
|
if [ "$opt_live" = 1 ]; then
|
||||||
|
# 0 means disabled
|
||||||
|
# 1 is enabled only for kernel space
|
||||||
|
# 2 is enabled for kernel and user space
|
||||||
|
case "$ibrs_enabled" in
|
||||||
|
"") [ "$ibrs_supported" = 1 ] && pstatus yellow UNKNOWN || pstatus red NO;;
|
||||||
|
0) pstatus red NO;;
|
||||||
|
1 | 2) pstatus green YES;;
|
||||||
|
*) pstatus yellow UNKNOWN;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
pstatus blue N/A "not testable in offline mode"
|
||||||
|
fi
|
||||||
|
|
||||||
|
_info_nol "* IBRS enabled for User space: "
|
||||||
|
if [ "$opt_live" = 1 ]; then
|
||||||
|
case "$ibrs_enabled" in
|
||||||
|
"") [ "$ibrs_supported" = 1 ] && pstatus yellow UNKNOWN || pstatus red NO;;
|
||||||
|
0 | 1) pstatus red NO;;
|
||||||
|
2) pstatus green YES;;
|
||||||
|
*) pstatus yellow UNKNOWN;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
pstatus blue N/A "not testable in offline mode"
|
||||||
|
fi
|
||||||
|
|
||||||
|
_info "* Mitigation 2"
|
||||||
|
_info_nol "* Kernel compiled with retpoline option: "
|
||||||
|
# We check the RETPOLINE kernel options
|
||||||
|
if [ -r "$opt_config" ]; then
|
||||||
|
if grep -q '^CONFIG_RETPOLINE=y' "$opt_config"; then
|
||||||
|
pstatus green YES
|
||||||
|
retpoline=1
|
||||||
|
_debug "retpoline: found "$(grep '^CONFIG_RETPOLINE' "$opt_config")" in $opt_config"
|
||||||
|
else
|
||||||
|
pstatus red NO
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
pstatus yellow UNKNOWN "couldn't read your kernel configuration"
|
||||||
|
fi
|
||||||
|
|
||||||
|
_info_nol "* Kernel compiled with a retpoline-aware compiler: "
|
||||||
|
# Now check if the compiler used to compile the kernel knows how to insert retpolines in generated asm
|
||||||
|
# For gcc, this is -mindirect-branch=thunk-extern (detected by the kernel makefiles)
|
||||||
|
# See gcc commit https://github.com/hjl-tools/gcc/commit/23b517d4a67c02d3ef80b6109218f2aadad7bd79
|
||||||
|
# In latest retpoline LKML patches, the noretpoline_setup symbol exists only if CONFIG_RETPOLINE is set
|
||||||
|
# *AND* if the compiler is retpoline-compliant, so look for that symbol
|
||||||
|
if [ -n "$opt_map" ]; then
|
||||||
|
# look for the symbol
|
||||||
|
if grep -qw noretpoline_setup "$opt_map"; then
|
||||||
|
retpoline_compiler=1
|
||||||
|
pstatus green YES "noretpoline_setup symbol found in System.map"
|
||||||
|
else
|
||||||
|
pstatus red NO
|
||||||
|
fi
|
||||||
|
elif [ -n "$vmlinux" ]; then
|
||||||
|
# look for the symbol
|
||||||
|
if which nm >/dev/null 2>&1; then
|
||||||
|
# the proper way: use nm and look for the symbol
|
||||||
|
if nm "$vmlinux" 2>/dev/null | grep -qw 'noretpoline_setup'; then
|
||||||
|
retpoline_compiler=1
|
||||||
|
pstatus green YES "noretpoline_setup found in vmlinux symbols"
|
||||||
|
else
|
||||||
|
pstatus red NO
|
||||||
|
fi
|
||||||
|
elif grep -q noretpoline_setup "$vmlinux"; then
|
||||||
|
# if we don't have nm, nevermind, the symbol name is long enough to not have
|
||||||
|
# any false positive using good old grep directly on the binary
|
||||||
|
retpoline_compiler=1
|
||||||
|
pstatus green YES "noretpoline_setup found in vmlinux"
|
||||||
|
else
|
||||||
|
pstatus red NO
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
pstatus yellow UNKNOWN "couldn't find your kernel image or System.map"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# if we have the /sys interface, don't even check is_cpu_vulnerable ourselves, the kernel already does it
|
||||||
|
if [ "$sys_interface_available" = 0 ] && ! is_cpu_vulnerable 2; then
|
||||||
|
# override status & msg in case CPU is not vulnerable after all
|
||||||
|
pvulnstatus CVE-2017-5715 OK "your CPU vendor reported your CPU model as not vulnerable"
|
||||||
|
elif [ -z "$msg" ]; then
|
||||||
|
# if msg is empty, sysfs check didn't fill it, rely on our own test
|
||||||
|
if [ "$retpoline" = 1 -a "$retpoline_compiler" = 1 ]; then
|
||||||
|
pvulnstatus CVE-2017-5715 OK "retpoline mitigate the vulnerability"
|
||||||
|
elif [ "$opt_live" = 1 ]; then
|
||||||
|
if [ "$ibrs_enabled" = 1 -o "$ibrs_enabled" = 2 ]; then
|
||||||
|
pvulnstatus CVE-2017-5715 OK "IBRS mitigates the vulnerability"
|
||||||
|
else
|
||||||
|
pvulnstatus CVE-2017-5715 VULN "IBRS hardware + kernel support OR kernel with retpoline are needed to mitigate the vulnerability"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ "$ibrs_supported" = 1 ]; then
|
||||||
|
pvulnstatus CVE-2017-5715 OK "offline mode: IBRS will mitigate the vulnerability if enabled at runtime"
|
||||||
|
else
|
||||||
|
pvulnstatus CVE-2017-5715 VULN "IBRS hardware + kernel support OR kernel with retpoline are needed to mitigate the vulnerability"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
pvulnstatus CVE-2017-5715 "$status" "$msg"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
########################
|
||||||
|
# MELTDOWN aka VARIANT 3
|
||||||
|
check_variant3()
|
||||||
|
{
|
||||||
|
_info "\033[1;34mCVE-2017-5754 [rogue data cache load] aka 'Meltdown' aka 'Variant 3'\033[0m"
|
||||||
|
|
||||||
|
status=UNK
|
||||||
|
sys_interface_available=0
|
||||||
|
msg=''
|
||||||
|
if sys_interface_check "/sys/devices/system/cpu/vulnerabilities/meltdown"; then
|
||||||
|
# this kernel has the /sys interface, trust it over everything
|
||||||
|
sys_interface_available=1
|
||||||
|
else
|
||||||
|
_info_nol "* Kernel supports Page Table Isolation (PTI): "
|
||||||
|
kpti_support=0
|
||||||
|
kpti_can_tell=0
|
||||||
|
if [ -n "$opt_config" ]; then
|
||||||
|
kpti_can_tell=1
|
||||||
|
if grep -Eq '^(CONFIG_PAGE_TABLE_ISOLATION|CONFIG_KAISER)=y' "$opt_config"; then
|
||||||
|
_debug "kpti_support: found option "$(grep -E '^(CONFIG_PAGE_TABLE_ISOLATION|CONFIG_KAISER)=y' "$opt_config")" in $opt_config"
|
||||||
|
kpti_support=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ "$kpti_support" = 0 -a -n "$opt_map" ]; then
|
||||||
|
# it's not an elif: some backports don't have the PTI config but still include the patch
|
||||||
|
# so we try to find an exported symbol that is part of the PTI patch in System.map
|
||||||
|
kpti_can_tell=1
|
||||||
|
if grep -qw kpti_force_enabled "$opt_map"; then
|
||||||
|
_debug "kpti_support: found kpti_force_enabled in $opt_map"
|
||||||
|
kpti_support=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ "$kpti_support" = 0 -a -n "$vmlinux" ]; then
|
||||||
|
# same as above but in case we don't have System.map and only vmlinux, look for the
|
||||||
|
# nopti option that is part of the patch (kernel command line option)
|
||||||
|
kpti_can_tell=1
|
||||||
|
if ! which strings >/dev/null 2>&1; then
|
||||||
|
pstatus yellow UNKNOWN "missing 'strings' tool, please install it, usually it's in the binutils package"
|
||||||
|
else
|
||||||
|
if strings "$vmlinux" | grep -qw nopti; then
|
||||||
|
_debug "kpti_support: found nopti string in $vmlinux"
|
||||||
|
kpti_support=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$kpti_support" = 1 ]; then
|
||||||
|
pstatus green YES
|
||||||
|
elif [ "$kpti_can_tell" = 1 ]; then
|
||||||
|
pstatus red NO
|
||||||
|
else
|
||||||
|
pstatus yellow UNKNOWN "couldn't read your kernel configuration nor System.map file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mount_debugfs
|
||||||
|
_info_nol "* PTI enabled and active: "
|
||||||
|
if [ "$opt_live" = 1 ]; then
|
||||||
|
dmesg_grep="Kernel/User page tables isolation: enabled"
|
||||||
|
dmesg_grep="$dmesg_grep|Kernel page table isolation enabled"
|
||||||
|
dmesg_grep="$dmesg_grep|x86/pti: Unmapping kernel while in userspace"
|
||||||
|
if grep ^flags /proc/cpuinfo | grep -qw pti; then
|
||||||
|
# vanilla PTI patch sets the 'pti' flag in cpuinfo
|
||||||
|
_debug "kpti_enabled: found 'pti' flag in /proc/cpuinfo"
|
||||||
|
kpti_enabled=1
|
||||||
|
elif grep ^flags /proc/cpuinfo | grep -qw kaiser; then
|
||||||
|
# kernel line 4.9 sets the 'kaiser' flag in cpuinfo
|
||||||
|
_debug "kpti_enabled: found 'kaiser' flag in /proc/cpuinfo"
|
||||||
|
kpti_enabled=1
|
||||||
|
elif [ -e /sys/kernel/debug/x86/pti_enabled ]; then
|
||||||
|
# RedHat Backport creates a dedicated file, see https://access.redhat.com/articles/3311301
|
||||||
|
kpti_enabled=$(cat /sys/kernel/debug/x86/pti_enabled 2>/dev/null)
|
||||||
|
_debug "kpti_enabled: file /sys/kernel/debug/x86/pti_enabled exists and says: $kpti_enabled"
|
||||||
|
elif dmesg | grep -Eq "$dmesg_grep"; then
|
||||||
|
# if we can't find the flag, grep dmesg output
|
||||||
|
_debug "kpti_enabled: found hint in dmesg: "$(dmesg | grep -E "$dmesg_grep")
|
||||||
|
kpti_enabled=1
|
||||||
|
elif [ -r /var/log/dmesg ] && grep -Eq "$dmesg_grep" /var/log/dmesg; then
|
||||||
|
# if we can't find the flag in dmesg output, grep in /var/log/dmesg when readable
|
||||||
|
_debug "kpti_enabled: found hint in /var/log/dmesg: "$(grep -E "$dmesg_grep" /var/log/dmesg)
|
||||||
|
kpti_enabled=1
|
||||||
|
else
|
||||||
|
_debug "kpti_enabled: couldn't find any hint that PTI is enabled"
|
||||||
|
kpti_enabled=0
|
||||||
|
fi
|
||||||
|
if [ "$kpti_enabled" = 1 ]; then
|
||||||
|
pstatus green YES
|
||||||
|
else
|
||||||
|
pstatus red NO
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
pstatus blue N/A "can't verify if PTI is enabled in offline mode"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# if we have the /sys interface, don't even check is_cpu_vulnerable ourselves, the kernel already does it
|
||||||
|
cve='CVE-2017-5754'
|
||||||
|
if [ "$sys_interface_available" = 0 ] && ! is_cpu_vulnerable 3; then
|
||||||
|
# override status & msg in case CPU is not vulnerable after all
|
||||||
|
pvulnstatus $cve OK "your CPU vendor reported your CPU model as not vulnerable"
|
||||||
|
elif [ -z "$msg" ]; then
|
||||||
|
# if msg is empty, sysfs check didn't fill it, rely on our own test
|
||||||
|
if [ "$opt_live" = 1 ]; then
|
||||||
|
if [ "$kpti_enabled" = 1 ]; then
|
||||||
|
pvulnstatus $cve OK "PTI mitigates the vulnerability"
|
||||||
|
else
|
||||||
|
pvulnstatus $cve VULN "PTI is needed to mitigate the vulnerability"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ "$kpti_support" = 1 ]; then
|
||||||
|
pvulnstatus $cve OK "offline mode: PTI will mitigate the vulnerability if enabled at runtime"
|
||||||
|
else
|
||||||
|
pvulnstatus $cve VULN "PTI is needed to mitigate the vulnerability"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
pvulnstatus $cve "$status" "$msg"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# now run the checks the user asked for
|
||||||
|
if [ "$opt_variant1" = 1 -o "$opt_allvariants" = 1 ]; then
|
||||||
|
check_variant1
|
||||||
|
_info
|
||||||
|
fi
|
||||||
|
if [ "$opt_variant2" = 1 -o "$opt_allvariants" = 1 ]; then
|
||||||
|
check_variant2
|
||||||
|
_info
|
||||||
|
fi
|
||||||
|
if [ "$opt_variant3" = 1 -o "$opt_allvariants" = 1 ]; then
|
||||||
|
check_variant3
|
||||||
|
_info
|
||||||
|
fi
|
||||||
|
|
||||||
|
_info "A false sense of security is worse than no security at all, see --disclaimer"
|
||||||
|
|
||||||
|
# this'll umount only if we mounted debugfs ourselves
|
||||||
|
umount_debugfs
|
||||||
|
|
||||||
|
# cleanup the temp decompressed config
|
||||||
|
[ -n "$dumped_config" ] && rm -f "$dumped_config"
|
||||||
|
|
||||||
|
if [ "$opt_batch" = 1 -a "$opt_batch_format" = "nrpe" ]; then
|
||||||
|
if [ ! -z "$nrpe_vuln" ]; then
|
||||||
|
echo "Vulnerable:$nrpe_vuln"
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
[ "$nrpe_critical" = 1 ] && exit 2 # critical
|
||||||
|
[ "$nrpe_unknown" = 1 ] && exit 3 # unknown
|
||||||
|
exit 0 # ok
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$opt_batch" = 1 -a "$opt_batch_format" = "json" ]; then
|
||||||
|
_echo 0 ${json_output%?}]
|
||||||
|
fi
|
Loading…
Add table
Reference in a new issue