Merge branch 'unstable' into backup_core_only

This commit is contained in:
Maniack Crudelis 2018-03-15 17:27:40 +01:00 committed by GitHub
commit 65dab2022c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 2779 additions and 273 deletions

22
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View 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 :

View file

@ -203,6 +203,30 @@ user:
extra:
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()
info:
action_help: Get user information
@ -647,6 +671,19 @@ app:
authenticate: all
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
addaccess:
action_help: Grant access right to users (everyone by default)
@ -1312,6 +1349,74 @@ dyndns:
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 #
#############################

View file

@ -2,7 +2,7 @@
#
# usage: ynh_use_logrotate [logfile] [--non-append]
# | 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}
# You can provide a path with the directory only or with the logfile.
@ -22,12 +22,12 @@ ynh_use_logrotate () {
fi
if [ $# -gt 0 ]; then
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
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
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
cat > ./${app}-logrotate << EOF # Build a config file for logrotate
$logfile {
@ -64,19 +64,24 @@ ynh_remove_logrotate () {
# Create a dedicated systemd config
#
# This will use a template in ../conf/systemd.service
# and will replace the following keywords with
# global variables that should be defined before calling
# usage: ynh_add_systemd_config [service] [template]
# | arg: service - Service name (optionnal, $app by default)
# | 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 :
#
# __APP__ by $app
# __FINALPATH__ by $final_path
#
# usage: 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"
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.
# 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"
sudo chown root: "$finalsystemdconf"
sudo systemctl enable $app
sudo systemctl enable $service_name
sudo systemctl daemon-reload
}
# 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 () {
finalsystemdconf="/etc/systemd/system/$app.service"
local service_name="${1:-$app}"
local finalsystemdconf="/etc/systemd/system/$service_name.service"
if [ -e "$finalsystemdconf" ]; then
sudo systemctl stop $app
sudo systemctl disable $app
sudo systemctl stop $service_name
sudo systemctl disable $service_name
ynh_secure_remove "$finalsystemdconf"
sudo systemctl daemon-reload
fi
}
# Create a dedicated nginx config
#
# usage: ynh_add_nginx_config
#
# This will use a template in ../conf/nginx.conf
# __PATH__ by $path_url
# __DOMAIN__ by $domain
@ -114,7 +126,6 @@ ynh_remove_systemd_config () {
# __NAME__ by $app
# __FINALPATH__ by $final_path
#
# usage: ynh_add_nginx_config
ynh_add_nginx_config () {
finalnginxconf="/etc/nginx/conf.d/$domain.d/$app.conf"
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
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=${path_url%/}
local path_url_slash_less=${path_url%/}
ynh_replace_string "__PATH__/" "$path_url_slash_less/" "$finalnginxconf"
ynh_replace_string "__PATH__" "$path_url" "$finalnginxconf"
fi
@ -157,7 +168,17 @@ ynh_remove_nginx_config () {
#
# usage: 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"
sudo cp ../conf/php-fpm.conf "$finalphpconf"
ynh_replace_string "__NAMETOCHANGE__" "$app" "$finalphpconf"
@ -168,21 +189,27 @@ ynh_add_fpm_config () {
if [ -e "../conf/php-fpm.ini" ]
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"
sudo cp ../conf/php-fpm.ini "$finalphpini"
sudo chown root: "$finalphpini"
ynh_store_file_checksum "$finalphpini"
fi
sudo systemctl reload php5-fpm
sudo systemctl reload $fpm_service
}
# Remove the dedicated php-fpm config
#
# usage: ynh_remove_fpm_config
ynh_remove_fpm_config () {
ynh_secure_remove "/etc/php5/fpm/pool.d/$app.conf"
ynh_secure_remove "/etc/php5/fpm/conf.d/20-$app.ini" 2>&1
sudo systemctl reload php5-fpm
local fpm_config_dir=$(ynh_app_setting_get $app fpm_config_dir)
local fpm_service=$(ynh_app_setting_get $app fpm_service)
# 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
}

View file

@ -144,6 +144,8 @@ ynh_restore () {
# Return the path in the archive where has been stocked the origin path
#
# [internal]
#
# usage: _get_archive_path ORIGIN_PATH
_get_archive_path () {
# For security reasons we use csv python library to read the CSV
@ -211,6 +213,9 @@ ynh_restore_file () {
}
# Deprecated helper since it's a dangerous one!
#
# [internal]
#
ynh_bind_or_cp() {
local AS_ROOT=${3:-0}
local NO_ROOT=0
@ -221,6 +226,8 @@ ynh_bind_or_cp() {
# Create a directory under /tmp
#
# [internal]
#
# Deprecated helper
#
# 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
if ! echo "$checksum_value $file" | sudo md5sum -c --status
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 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
@ -280,8 +287,8 @@ ynh_backup_if_checksum_is_different () {
# usage: ynh_secure_remove path_to_remove
# | arg: path_to_remove - File or directory to remove
ynh_secure_remove () {
path_to_remove=$1
forbidden_path=" \
local path_to_remove=$1
local forbidden_path=" \
/var/www \
/home/yunohost.app"

View file

@ -1,16 +1,16 @@
# 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
#
# usage: ynh_validate_ip <family> <ip_address>
#
# exit code : 0 for valid ip addresses, 1 otherwise
ynh_validate_ip()
{
# http://stackoverflow.com/questions/319279/how-to-validate-ip-address-in-python#319298
IP_ADDRESS_FAMILY=$1
IP_ADDRESS=$2
local IP_ADDRESS_FAMILY=$1
local IP_ADDRESS=$2
[ "$IP_ADDRESS_FAMILY" == "4" ] || [ "$IP_ADDRESS_FAMILY" == "6" ] || return 1
@ -31,8 +31,8 @@ EOF
# example: ynh_validate_ip4 111.222.333.444
#
# 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_ip 4 $1
@ -44,8 +44,8 @@ ynh_validate_ip4()
# example: ynh_validate_ip6 2000:dead:beef::1
#
# 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_ip 6 $1

View file

@ -8,7 +8,7 @@ MYSQL_ROOT_PWD_FILE=/etc/yunohost/mysql
# usage: ynh_mysql_connect_as user pwd [db]
# | arg: user - the user name to connect as
# | arg: pwd - the user password
# | arg: db - the database to connect to
# | arg: db - the database to connect to
ynh_mysql_connect_as() {
mysql -u "$1" --password="$2" -B "${3:-}"
}
@ -17,7 +17,7 @@ ynh_mysql_connect_as() {
#
# usage: ynh_mysql_execute_as_root sql [db]
# | 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_connect_as "root" "$(sudo cat $MYSQL_ROOT_PWD_FILE)" \
"${2:-}" <<< "$1"
@ -27,7 +27,7 @@ ynh_mysql_execute_as_root() {
#
# usage: ynh_mysql_execute_file_as_root file [db]
# | 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_connect_as "root" "$(sudo cat $MYSQL_ROOT_PWD_FILE)" \
"${2:-}" < "$1"
@ -35,14 +35,16 @@ ynh_mysql_execute_file_as_root() {
# Create a database and grant optionnaly privilegies to a user
#
# [internal]
#
# usage: ynh_mysql_create_db db [user [pwd]]
# | arg: db - the database name to create
# | arg: user - the user to grant privilegies
# | arg: pwd - the password to identify user by
ynh_mysql_create_db() {
db=$1
local db=$1
sql="CREATE DATABASE ${db};"
local sql="CREATE DATABASE ${db};"
# grant all privilegies to user
if [[ $# -gt 1 ]]; then
@ -56,6 +58,8 @@ ynh_mysql_create_db() {
# Drop a database
#
# [internal]
#
# If you intend to drop the database *and* the associated user,
# consider using ynh_mysql_remove_db instead.
#
@ -78,6 +82,8 @@ ynh_mysql_dump_db() {
# Create a user
#
# [internal]
#
# usage: ynh_mysql_create_user user pwd [host]
# | arg: user - the user name to create
# | arg: pwd - the password to identify user by
@ -90,7 +96,7 @@ ynh_mysql_create_user() {
#
# usage: ynh_mysql_user_exists user
# | arg: user - the user for which to check existence
function ynh_mysql_user_exists()
ynh_mysql_user_exists()
{
local user=$1
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
#
# [internal]
#
# usage: ynh_mysql_drop_user user
# | arg: user - the user name to drop
ynh_mysql_drop_user() {
@ -153,12 +161,12 @@ ynh_mysql_remove_db () {
# Sanitize a string intended to be the name of a database
# (More specifically : replace - and . by _)
#
# Exemple: dbname=$(ynh_sanitize_dbid $app)
# example: dbname=$(ynh_sanitize_dbid $app)
#
# usage: ynh_sanitize_dbid name
# | arg: name - name to correct/sanitize
# | ret: the corrected name
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
}

View file

@ -11,7 +11,7 @@
# usage: ynh_normalize_url_path path_to_normalize
# | arg: url_path_to_normalize - URL path to normalize before using it
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."
if [ "${path_url:0:1}" != "/" ]; then # If the first character is not a /
path_url="/$path_url" # Add / at begin of path variable
@ -29,7 +29,7 @@ ynh_normalize_url_path () {
# usage: ynh_find_port begin_port
# | arg: begin_port - port to start to search
ynh_find_port () {
port=$1
local port=$1
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
do

View file

@ -26,6 +26,8 @@ ynh_package_version() {
# APT wrapper for non-interactive operation
#
# [internal]
#
# usage: ynh_apt update
ynh_apt() {
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
#
# [internal]
#
# example: generate an empty control file with `equivs-control`, adjust its
# content and use helper to build and install the package:
# ynh_package_install_from_equivs /path/to/controlfile
@ -80,15 +84,15 @@ ynh_package_autopurge() {
# usage: ynh_package_install_from_equivs controlfile
# | arg: controlfile - path of the equivs control file
ynh_package_install_from_equivs () {
controlfile=$1
local controlfile=$1
# Check if the equivs package is installed. Or install it.
ynh_package_is_installed 'equivs' \
|| ynh_package_install equivs
# retrieve package information
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 pkgname=$(grep '^Package: ' $controlfile | cut -d' ' -f 2) # Retrieve the name of the debian package
local pkgversion=$(grep '^Version: ' $controlfile | cut -d' ' -f 2) # And its version number
[[ -z "$pkgname" || -z "$pkgversion" ]] \
&& 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
# Build and install the package
TMPDIR=$(mktemp -d)
local TMPDIR=$(mktemp -d)
# Note that the cd executes into a sub shell
# Create a fake deb package with equivs-build and the given control file
# 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 [...]]
# | arg: dep - the package name to install in dependence
ynh_install_app_dependencies () {
dependencies=$@
manifest_path="../manifest.json"
local dependencies=$@
local manifest_path="../manifest.json"
if [ ! -e "$manifest_path" ]; then
manifest_path="../settings/manifest.json" # Into the restore script, the manifest is not at the same place
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
Section: misc
@ -148,6 +156,6 @@ EOF
#
# usage: 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.
}

View file

@ -6,14 +6,18 @@ ynh_die() {
}
# Ignore the yunohost-cli log to prevent errors with conditionals commands
#
# [internal]
#
# usage: ynh_no_log 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...
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
eval $@
exit_code=$?
local exit_code=$?
sudo mv ${ynh_cli_log}-move ${ynh_cli_log}
return $?
}

View file

@ -47,13 +47,13 @@ ynh_replace_special_string () {
local replace_string=$2
local workfile=$3
# Escape any backslash to preserve them as simple backslash.
match_string=${match_string//\\/"\\\\"}
replace_string=${replace_string//\\/"\\\\"}
# Escape any backslash to preserve them as simple backslash.
match_string=${match_string//\\/"\\\\"}
replace_string=${replace_string//\\/"\\\\"}
# Escape the & character, who has a special function in sed.
match_string=${match_string//&/"\&"}
replace_string=${replace_string//&/"\&"}
# Escape the & character, who has a special function in sed.
match_string=${match_string//&/"\&"}
replace_string=${replace_string//&/"\&"}
ynh_replace_string "$match_string" "$replace_string" "$workfile"
}

View file

@ -1,20 +1,21 @@
# Manage a fail of the script
#
# Print a warning to inform that the script was failed
# Execute the ynh_clean_setup function if used in the app script
# [internal]
#
# usage of ynh_clean_setup function
# This function provide a way to clean some residual of installation that not managed by remove script.
# To use it, simply add in your script:
# usage:
# ynh_exit_properly is used only by the helper ynh_abort_if_errors.
# You should not use it directly.
# Instead, add to your script:
# ynh_clean_setup () {
# instructions...
# }
# This function is optionnal.
#
# Usage: ynh_exit_properly is used only by the helper ynh_abort_if_errors.
# You must not use it directly.
# This function provide a way to clean some residual of installation that not managed by remove script.
#
# 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 () {
exit_code=$?
local exit_code=$?
if [ "$exit_code" -eq 0 ]; then
exit 0 # Exit without error if the script ended correctly
fi
@ -31,13 +32,24 @@ ynh_exit_properly () {
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.
# The execution of the script is derivate to ynh_exit_properly function before exit.
# usage: ynh_abort_if_errors
#
# 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 () {
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
}
# 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)
}

View file

@ -1,4 +1,4 @@
# Check if a YunoHost user exists
# Check if a YunoHost user exists
#
# example: ynh_user_exists 'toto' || exit 1
#
@ -31,7 +31,7 @@ ynh_user_list() {
| 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
# | 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
then # If the user doesn't exist
if [ $# -ge 2 ]; then # If a home dir is mentioned
user_home_dir="-d $2"
local user_home_dir="-d $2"
else
user_home_dir="--no-create-home"
local user_home_dir="--no-create-home"
fi
sudo useradd $user_home_dir --system --user-group $1 --shell /usr/sbin/nologin || ynh_die "Unable to create $1 system account"
fi

View file

@ -5,9 +5,9 @@
# usage: ynh_get_plain_key key [subkey [subsubkey ...]]
# | ret: string - the key's value
ynh_get_plain_key() {
prefix="#"
founded=0
key=$1
local prefix="#"
local founded=0
local key=$1
shift
while read line; do
if [[ "$founded" == "1" ]] ; then
@ -36,7 +36,7 @@ ynh_get_plain_key() {
#
ynh_restore_upgradebackup () {
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.
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
sudo yunohost app remove $app
# 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."
fi
}
@ -65,8 +65,8 @@ ynh_backup_before_upgrade () {
return
fi
backup_number=1
old_backup_number=2
app_bck=${app//_/-} # Replace all '_' by '-'
local old_backup_number=2
local app_bck=${app//_/-} # Replace all '_' by '-'
# Check if a backup already exists with the prefix 1
if sudo yunohost backup list | grep -q $app_bck-pre-upgrade1
@ -77,7 +77,7 @@ ynh_backup_before_upgrade () {
fi
# 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 ]
then
# If the backup succeeded, remove the previous backup
@ -216,10 +216,11 @@ ynh_setup_source () {
# | arg: ... - (Optionnal) More POST keys and values
ynh_local_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
POST_data=""
local POST_data=""
local arg=""
for arg in "${@:2}"
do
POST_data="${POST_data}${arg}&"

View file

@ -53,6 +53,9 @@ do_pre_regen() {
else
sudo cp services.yml /etc/yunohost/services.yml
fi
mkdir -p "$pending_dir"/etc/etckeeper/
cp etckeeper.conf "$pending_dir"/etc/etckeeper/
}
_update_services() {

View file

@ -581,5 +581,6 @@ enabled = true
port = http,https
protocol = tcp
filter = yunohost
logpath = /var/log/nginx*/*error.log
logpath = /var/log/nginx/*error.log
/var/log/nginx/*access.log
maxretry = 6

View file

@ -14,8 +14,8 @@
# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
# Values: TEXT
#
failregex = helpers.lua:[1-9]+: authenticate\(\): Connection failed for: .*, client: <HOST>
^<HOST> -.*\"POST /yunohost/api/login HTTP/1.1\" 401 22
failregex = helpers.lua:[0-9]+: authenticate\(\): Connection failed for: .*, client: <HOST>
^<HOST> -.*\"POST /yunohost/api/login HTTP/1.1\" 401
# Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored.

View file

@ -1 +1,2 @@
server_tokens off;
gzip_types text/css text/javascript application/javascript;

View file

@ -37,7 +37,17 @@ server {
# > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048
#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 / {
return 302 https://$http_host/yunohost/admin;

View file

@ -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/;
default_type text/html;
index index.html;

View file

@ -42,7 +42,16 @@ server {
# > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048
#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;

View file

@ -66,6 +66,9 @@ PrintLastLog yes
TCPKeepAlive yes
#UseLogin no
# keep ssh sessions fresh
ClientAliveInterval 60
#MaxStartups 10:30:60
Banner /etc/issue.net

View 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
View file

@ -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
(Bumping version number for stable release)

2
debian/control vendored
View file

@ -18,7 +18,7 @@ Depends: ${python:Depends}, ${misc:Depends}
, ca-certificates, netcat-openbsd, iproute
, mariadb-server | mysql-server, php5-mysql | php5-mysqlnd
, 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-antispam, fail2ban
, nginx-extras (>=1.6.2), php5-fpm, php5-ldap, php5-intl

2
locales/ar.json Normal file
View file

@ -0,0 +1,2 @@
{
}

View file

@ -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_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_checkurl_is_deprecated": "Packagers /!\\ 'app checkurl' is deprecated ! Please use 'app register-url' instead !",
"app_extraction_failed": "Unable to extract installation files",
"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_location_already_used": "An app is already installed in this location",
"app_location_install_failed": "Unable to install the app in this location",
"app_location_already_used": "The app '{app}' is already installed on that location ({path})",
"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_manifest_invalid": "Invalid app manifest: {error}",
"app_no_upgrade": "No app to upgrade",
"app_not_correctly_installed": "{app:s} seems to be incorrectly installed",
"app_not_installed": "{app:s} is not installed",
"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_requirements_checking": "Checking required packages...",
"app_requirements_failed": "Unable to meet requirements: {error}",
"app_requirements_unmeet": "Requirements are not met, the package {pkgname} ({version}) must be {spec}",
"app_requirements_checking": "Checking required packages for {app}...",
"app_requirements_failed": "Unable to meet requirements for {app}: {error}",
"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_unknown": "Unknown 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_some_app_failed": "Unable to upgrade some applications",
"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_not_empty": "The output directory is not empty",
"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_hooks": "Running backup hooks...",
"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",
"maindomain_change_failed": "Unable to change the main domain",
"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_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",

View file

@ -13,7 +13,7 @@
"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_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_not_correctly_installed": "La aplicación {app:s} 8 parece estar incorrectamente 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_upgrade_failed": "No se pudo actualizar la aplicación {app:s}",
"app_upgraded": "{app:s} ha sido actualizada",
"appslist_fetched": "Lista de aplicaciones ha sido descargada",
"appslist_removed": "La lista de aplicaciones ha sido eliminada",
"appslist_retrieve_error": "No se pudo recuperar la lista remota de aplicaciones: {error}",
"appslist_fetched": "La lista de aplicaciones {appslist:s} ha sido descargada",
"appslist_removed": "La lista de aplicaciones {appslist:s} ha sido eliminada",
"appslist_retrieve_error": "No se pudo recuperar la lista remota de aplicaciones {appslist:s} : {error}",
"appslist_unknown": "Lista de aplicaciones desconocida",
"ask_current_admin_password": "Contraseña administrativa actual",
"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_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.",
"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",
"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'.",
@ -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_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",
"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_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_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"
}

View file

@ -10,21 +10,21 @@
"app_argument_required": "Le paramètre « {name:s} » est requis",
"app_extraction_failed": "Impossible d'extraire les fichiers d'installation",
"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_location_already_used": "Une application est déjà installée à cet emplacement",
"app_location_install_failed": "Impossible d'installer l'application à 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 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_no_upgrade": "Aucune application à mettre à jour",
"app_not_correctly_installed": "{app:s} semble être mal installé",
"app_not_installed": "{app:s} n'est pas installé",
"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_removed": "{app:s} a été supprimé",
"app_requirements_checking": "Vérification des paquets requis...",
"app_requirements_failed": "Impossible de satisfaire les pré-requis : {error}",
"app_requirements_unmeet": "Les pré-requis ne sont pas satisfaits, le paquet {pkgname} ({version}) doit être {spec}",
"app_requirements_checking": "Vérification des paquets requis pour {app}...",
"app_requirements_failed": "Impossible de satisfaire les pré-requis pour {app} : {error}",
"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_unknown": "Application inconnue",
"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",
"ask_path": "Chemin",
"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 darchives « {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": "Lalgorithme de génération des clefs nest 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 nest nécessaire !"
}

View file

@ -330,6 +330,9 @@ def app_info(app, show_status=False, raw=False):
if not _is_installed(app):
raise MoulinetteError(errno.EINVAL,
m18n.n('app_not_installed', app=app))
app_setting_path = APPS_SETTING_PATH + app
if raw:
ret = app_list(filter=app, raw=True)[app]
ret['settings'] = _get_app_settings(app)
@ -345,11 +348,10 @@ def app_info(app, show_status=False, raw=False):
upgradable = "no"
ret['upgradable'] = upgradable
ret['change_url'] = os.path.exists(os.path.join(app_setting_path, "scripts", "change_url"))
return ret
app_setting_path = APPS_SETTING_PATH + app
# Retrieve manifest and status
with open(app_setting_path + '/manifest.json') as f:
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
"""
from yunohost.hook import hook_exec
from yunohost.hook import hook_exec, hook_callback
installed = _is_installed(app)
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"),
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
os.system('chown -R admin: %s' % INSTALL_TMP)
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",
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):
"""
@ -530,7 +540,8 @@ def app_upgrade(auth, app=[], url=None, file=None):
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
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))
for app_instance_name in apps:
logger.warning(m18n.n('app_upgrade_app_name', app=app_instance_name))
installed = _is_installed(app_instance_name)
if not installed:
raise MoulinetteError(errno.ENOPKG,
@ -580,7 +592,7 @@ def app_upgrade(auth, app=[], url=None, file=None):
continue
# 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
@ -621,14 +633,20 @@ def app_upgrade(auth, app=[], url=None, file=None):
with open(app_setting_path + '/status.json', 'w+') as f:
json.dump(status, f)
# Replace scripts and manifest
os.system('rm -rf "%s/scripts" "%s/manifest.json"' % (app_setting_path, app_setting_path))
# Replace scripts and manifest and conf (if exists)
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))
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
upgraded_apps.append(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:
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
"""
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
try:
@ -683,7 +701,7 @@ def app_install(auth, app, label=None, args=None, no_remove_on_failure=False):
app_id = manifest['id']
# Check requirements
_check_manifest_requirements(manifest)
_check_manifest_requirements(manifest, app_id)
# Check if app can be forked
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 -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
install_retcode = 1
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'))
hook_callback('post_app_install', args=args_list, env=env_dict)
def app_remove(auth, app):
"""
@ -800,7 +823,7 @@ def app_remove(auth, app):
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):
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:
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):
shutil.rmtree(app_setting_path)
shutil.rmtree('/tmp/yunohost_remove')
@ -1016,7 +1041,9 @@ def app_makedefault(auth, app, domain=None):
if '/' in app_map(raw=True)[domain]:
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:
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
"""
logger.warning(m18n.n("app_checkurl_is_deprecated"))
from yunohost.domain import domain_list
if "https://" == url[:8]:
@ -1169,10 +1199,13 @@ def app_checkurl(auth, url, app=None):
continue
if path == p:
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):
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:
app_setting(app, 'domain', value=domain)
@ -1306,6 +1339,17 @@ def app_ssowatconf(auth):
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):
"""
Get settings of an installed app
@ -1689,7 +1733,7 @@ def _encode_string(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"""
requirements = manifest.get('requirements', dict())
@ -1707,12 +1751,12 @@ def _check_manifest_requirements(manifest):
if (not yunohost_req or
not packages.SpecifierSet(yunohost_req) & '>= 2.3.6'):
raise MoulinetteError(errno.EINVAL, '{0}{1}'.format(
m18n.g('colon', m18n.n('app_incompatible')),
m18n.n('app_package_need_update')))
m18n.g('colon', m18n.n('app_incompatible'), app=app_instance_name),
m18n.n('app_package_need_update', app=app_instance_name)))
elif not requirements:
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
try:
@ -1721,7 +1765,7 @@ def _check_manifest_requirements(manifest):
except packages.PackageException as e:
raise MoulinetteError(errno.EINVAL,
m18n.n('app_requirements_failed',
error=str(e)))
error=str(e), app=app_instance_name))
# Iterate over requirements
for pkgname, spec in requirements.items():
@ -1730,7 +1774,7 @@ def _check_manifest_requirements(manifest):
raise MoulinetteError(
errno.EINVAL, m18n.n('app_requirements_unmeet',
pkgname=pkgname, version=version,
spec=spec))
spec=spec, app=app_instance_name))
def _parse_args_from_manifest(manifest, action, args={}, auth=None):

View file

@ -1543,9 +1543,13 @@ class BackupMethod(object):
# 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)
if os.stat(src).st_dev == os.stat(dest_dir).st_dev:
os.link(src, dest)
# Success, go to next file to organize
continue
# Don't hardlink /etc/cron.d files to avoid cron bug
# 'NUMBER OF HARD LINKS > 1' see #1043
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,
# prepare a list of files that need to be copied
@ -2301,6 +2305,11 @@ def backup_delete(name):
def _create_archive_dir():
""" Create the YunoHost archives directory if doesn't exist """
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)

View file

@ -44,6 +44,7 @@ from moulinette.core import MoulinetteError
from moulinette.utils.log import getActionLogger
import yunohost.domain
from yunohost.utils.network import get_public_ip
from moulinette import m18n
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_configuration = '''
location '/.well-known/acme-challenge'
location ^~ '/.well-known/acme-challenge'
{
default_type "text/plain";
alias %s;
@ -809,7 +810,7 @@ def _backup_current_cert(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
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...
"""
try:
ipv4 = yunohost.domain.get_public_ip()
except:
ipv4 = None
try:
ipv6 = yunohost.domain.get_public_ip(6)
except:
ipv6 = None
ipv4 = get_public_ip()
ipv6 = get_public_ip(6)
do_regen = False

View 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

View file

@ -30,8 +30,6 @@ import yaml
import errno
import requests
from urllib import urlopen
from moulinette import m18n, msettings
from moulinette.core import MoulinetteError
from moulinette.utils.log import getActionLogger
@ -39,6 +37,7 @@ from moulinette.utils.log import getActionLogger
import yunohost.certificate
from yunohost.service import service_regen_conf
from yunohost.utils.network import get_public_ip
logger = getActionLogger('yunohost.domain')
@ -188,17 +187,17 @@ def domain_dns_conf(domain, ttl=None):
result = ""
result += "# Basic ipv4/ipv6 records"
result += "; Basic ipv4/ipv6 records"
for record in dns_conf["basic"]:
result += "\n{name} {ttl} IN {type} {value}".format(**record)
result += "\n\n"
result += "# XMPP"
result += "; XMPP"
for record in dns_conf["xmpp"]:
result += "\n{name} {ttl} IN {type} {value}".format(**record)
result += "\n\n"
result += "# Mail"
result += "; Mail"
for record in dns_conf["mail"]:
result += "\n{name} {ttl} IN {type} {value}".format(**record)
@ -260,42 +259,6 @@ def domain_url_available(auth, domain, path):
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():
with open('/etc/yunohost/current_host', 'r') as f:
maindomain = f.readline().rstrip()
@ -356,15 +319,8 @@ def _build_dns_conf(domain, ttl=3600):
}
"""
try:
ipv4 = get_public_ip()
except:
ipv4 = None
try:
ipv6 = get_public_ip(6)
except:
ipv6 = None
ipv4 = get_public_ip()
ipv6 = get_public_ip(6)
basic = []

View file

@ -27,6 +27,7 @@ import os
import re
import json
import glob
import time
import base64
import errno
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.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')
@ -46,6 +48,14 @@ OLD_IPV4_FILE = '/etc/yunohost/dyndns/old_ip'
OLD_IPV6_FILE = '/etc/yunohost/dyndns/old_ipv6'
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):
"""
@ -129,21 +139,22 @@ def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None
if key is None:
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'))
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')
key_file = glob.glob('/etc/yunohost/dyndns/*.key')[0]
with open(key_file) as f:
key = f.readline().strip().split(' ')[-1]
key = f.readline().strip().split(' ', 6)[-1]
# Send subscription
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:
raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection'))
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()
# Get current IPv4 and IPv6
(ipv4_, ipv6_) = get_public_ips()
ipv4_ = get_public_ip()
ipv6_ = get_public_ip(6)
if ipv4 is None:
ipv4 = ipv4_
@ -213,6 +225,19 @@ def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None,
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'
host = domain.split('.')[1:]
host = '.'.join(host)
@ -313,15 +338,13 @@ def _guess_current_dyndns_domain(dyn_host):
dynette...)
"""
re_dyndns_private_key = re.compile(
r'.*/K(?P<domain>[^\s\+]+)\.\+157.+\.private$'
)
# Retrieve the first registered domain
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:
continue
match = RE_DYNDNS_PRIVATE_KEY_SHA512.match(path)
if not match:
continue
_domain = match.group('domain')
# Verify if domain is registered (i.e., if it's available, skip

View file

@ -41,7 +41,8 @@ from moulinette import m18n
from moulinette.core import MoulinetteError
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')
@ -210,10 +211,7 @@ def monitor_network(units=None, human_readable=False):
else:
logger.debug('interface name %s was not found', iname)
elif u == 'infos':
try:
p_ipv4 = get_public_ip()
except:
p_ipv4 = 'unknown'
p_ipv4 = get_public_ip() or 'unknown'
l_ip = 'unknown'
for name, addrs in devices.items():

View file

@ -498,15 +498,12 @@ def _run_service_command(action, service):
raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', service=service))
cmd = None
if action in ['start', 'stop', 'restart', 'reload']:
cmd = 'service %s %s' % (service, action)
elif action in ['enable', 'disable']:
arg = 'defaults' if action == 'enable' else 'remove'
cmd = 'update-rc.d %s %s' % (service, arg)
if action in ['start', 'stop', 'restart', 'reload', 'enable', 'disable']:
cmd = 'systemctl %s %s' % (action, service)
else:
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']
try:

102
src/yunohost/ssh.py Normal file
View 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)

View file

@ -33,8 +33,9 @@ import logging
import subprocess
import pwd
import socket
from collections import OrderedDict
from xmlrpclib import Fault
from importlib import import_module
from collections import OrderedDict
import apt
import apt.progress
@ -42,14 +43,16 @@ import apt.progress
from moulinette import msettings, msignals, m18n
from moulinette.core import MoulinetteError, init_authenticator
from moulinette.utils.log import getActionLogger
from moulinette.utils.process import check_output
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.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.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.utils.packages import ynh_packages_version
from yunohost.utils.network import get_public_ip
# FIXME this is a duplicate from apps.py
APPS_SETTING_PATH = '/etc/yunohost/apps/'
@ -398,8 +401,8 @@ def tools_postinstall(domain, password, ignore_dyndns=False):
os.system('touch /etc/yunohost/installed')
# Enable and start YunoHost firewall at boot time
os.system('update-rc.d yunohost-firewall enable')
os.system('service yunohost-firewall start &')
service_enable("yunohost-firewall")
service_start("yunohost-firewall")
service_regen_conf(force=True)
logger.success(m18n.n('yunohost_configured'))
@ -561,17 +564,19 @@ def tools_diagnosis(auth, private=False):
# 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
diagnosis['system'] = OrderedDict()
try:
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)
else:
diagnosis['system']['disks'] = {}
for disk in disks:
if isinstance(disk, str):
diagnosis['system']['disks'] = disk
if isinstance(disks[disk], str):
diagnosis['system']['disks'][disk] = disks[disk]
else:
diagnosis['system']['disks'][disk] = 'Mounted on %s, %s (%s free)' % (
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']),
}
# 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 = service_status()
diagnosis['services'] = {}
@ -610,23 +623,64 @@ def tools_diagnosis(auth, private=False):
# Private data
if private:
diagnosis['private'] = OrderedDict()
# Public IP
diagnosis['private']['public_ip'] = {}
try:
diagnosis['private']['public_ip']['IPv4'] = get_public_ip(4)
except MoulinetteError as e:
pass
try:
diagnosis['private']['public_ip']['IPv6'] = get_public_ip(6)
except MoulinetteError as e:
pass
diagnosis['private']['public_ip']['IPv4'] = get_public_ip(4)
diagnosis['private']['public_ip']['IPv6'] = get_public_ip(6)
# 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
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):
"""
Check availability of a local port
@ -717,30 +771,10 @@ def tools_migrations_migrate(target=None, skip=False):
# loading all 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({
"number": migration["number"],
"name": migration["name"],
"module": module,
"module": _get_migration_module(migration),
})
migrations = sorted(migrations, key=lambda x: x["number"])
@ -877,6 +911,47 @@ def _get_migrations_list():
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):
def migrate(self):

View file

@ -25,6 +25,7 @@
"""
import os
import re
import pwd
import json
import errno
import crypt
@ -35,10 +36,13 @@ import subprocess
from moulinette import m18n
from moulinette.core import MoulinetteError
from moulinette.utils.log import getActionLogger
from moulinette.utils.filesystem import read_file
from yunohost.service import service_status
logger = getActionLogger('yunohost.user')
SSHD_CONFIG_PATH = "/etc/ssh/sshd_config"
def user_list(auth, fields=None):
"""
@ -56,6 +60,8 @@ def user_list(auth, fields=None):
'cn': 'fullname',
'mail': 'mail',
'maildrop': 'mail-forward',
'loginShell': 'shell',
'homeDirectory': 'home_path',
'mailuserquota': 'mailbox-quota'
}
@ -71,7 +77,7 @@ def user_list(auth, fields=None):
raise MoulinetteError(errno.EINVAL,
m18n.n('field_invalid', attr))
else:
attrs = ['uid', 'cn', 'mail', 'mailuserquota']
attrs = ['uid', 'cn', 'mail', 'mailuserquota', 'loginShell']
result = auth.search('ou=users,dc=yunohost,dc=org',
'(&(objectclass=person)(!(uid=root))(!(uid=nobody)))',
@ -81,6 +87,12 @@ def user_list(auth, fields=None):
entry = {}
for attr, values in user.items():
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]
uid = entry[user_attrs['uid']]
@ -435,6 +447,36 @@ def user_info(auth, username):
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=''):
for unit in ['K', 'M', 'G', 'T', 'P', 'E', 'Z']:
if abs(num) < 1024.0:
@ -470,3 +512,56 @@ def _hash_user_password(password):
salt = '$6$' + 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]

View 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

View file

@ -406,6 +406,7 @@ def get_installed_version(*pkgnames, **kwargs):
# Retrieve options
as_dict = kwargs.get('as_dict', False)
strict = kwargs.get('strict', False)
with_repo = kwargs.get('with_repo', False)
for pkgname in pkgnames:
try:
@ -414,13 +415,32 @@ def get_installed_version(*pkgnames, **kwargs):
if strict:
raise UnknownPackage(pkgname)
logger.warning(m18n.n('package_unknown', pkgname=pkgname))
continue
try:
version = pkg.installed.version
except AttributeError:
if strict:
raise UninstalledPackage(pkgname)
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:
return versions[pkgnames[0]]
@ -436,7 +456,11 @@ def meets_version_specifier(pkgname, specifier):
# YunoHost related methods ---------------------------------------------------
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 get_installed_version(
'yunohost', 'yunohost-admin', 'moulinette', 'ssowat',
with_repo=True
)

View 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>.

View 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
![checker](https://framapic.org/6O4v4AAwMenv/M6J4CFWwsB3z.png)
## 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.

View 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