Merge branch 'stretch-unstable' into migrate-pwd

This commit is contained in:
Alexandre Aubin 2018-11-04 16:02:59 +01:00 committed by GitHub
commit fb2d85b90d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 1178 additions and 279 deletions

View file

@ -56,7 +56,7 @@ else
echo "$LOGO_AND_FINGERPRINTS" echo "$LOGO_AND_FINGERPRINTS"
echo -e "\e[m Post-installation \e[0m" echo -e "\e[m Post-installation \e[0m"
echo "Congratulations! YunoHost has been successfully installed.\nTwo more steps are required to activate the services of your server." echo "Congratulations! YunoHost has been successfully installed.\nTwo more steps are required to activate the services of your server."
read -p "Proceed to post-installation? (y/n) " -n 1 read -p "Proceed to post-installation? (y/n)\nAlternatively, you can proceed the post-installation on https://${ip}" -n 1
RESULT=1 RESULT=1
while [ $RESULT -gt 0 ]; do while [ $RESULT -gt 0 ]; do
if [[ $REPLY =~ ^[Nn]$ ]]; then if [[ $REPLY =~ ^[Nn]$ ]]; then

View file

@ -105,7 +105,7 @@ user:
ask: ask_lastname ask: ask_lastname
required: True required: True
pattern: &pattern_lastname pattern: &pattern_lastname
- !!str ^([^\W\d_]{2,30}[ ,.']{0,3})+$ - !!str ^([^\W\d_]{2,30}[ ,.'-]{0,3})+$
- "pattern_lastname" - "pattern_lastname"
-m: -m:
full: --mail full: --mail
@ -125,6 +125,7 @@ user:
pattern: &pattern_password pattern: &pattern_password
- !!str ^.{3,}$ - !!str ^.{3,}$
- "pattern_password" - "pattern_password"
comment: good_practices_about_user_password
-q: -q:
full: --mailbox-quota full: --mailbox-quota
help: Mailbox size quota help: Mailbox size quota
@ -780,18 +781,18 @@ app:
### app_action_list() ### app_action_list()
list: list:
action_help: List app actions action_help: List app actions
api: GET /apps/<app_id>/actions api: GET /apps/<app>/actions
arguments: arguments:
app_id: app:
help: app id help: App name
### app_action_run() ### app_action_run()
run: run:
action_help: Run app action action_help: Run app action
api: PUT /apps/<app_id>/actions/<action> api: PUT /apps/<app>/actions/<action>
arguments: arguments:
app_id: app:
help: app id help: App name
action: action:
help: action id help: action id
-a: -a:
@ -805,18 +806,18 @@ app:
### app_config_show_panel() ### app_config_show_panel()
show-panel: show-panel:
action_help: show config panel for the application action_help: show config panel for the application
api: GET /apps/<app_id>/config-panel api: GET /apps/<app>/config-panel
arguments: arguments:
app_id: app:
help: App ID help: App name
### app_config_apply() ### app_config_apply()
apply: apply:
action_help: apply the new configuration action_help: apply the new configuration
api: POST /apps/<app_id>/config api: POST /apps/<app>/config
arguments: arguments:
app_id: app:
help: App ID help: App name
-a: -a:
full: --args full: --args
help: Serialized arguments for new configuration (i.e. "domain=domain.tld&path=/path") help: Serialized arguments for new configuration (i.e. "domain=domain.tld&path=/path")
@ -1136,6 +1137,13 @@ service:
full: --runlevel full: --runlevel
help: Runlevel priority of the service help: Runlevel priority of the service
type: int type: int
-n:
full: --need_lock
help: Use this option to prevent deadlocks if the service does invoke yunohost commands.
action: store_true
-d:
full: --description
help: Description of the service
### service_remove() ### service_remove()
remove: remove:
@ -1449,6 +1457,7 @@ tools:
password: ask_new_admin_password password: ask_new_admin_password
pattern: *pattern_password pattern: *pattern_password
required: True required: True
comment: good_practices_about_admin_password
### tools_maindomain() ### tools_maindomain()
maindomain: maindomain:
@ -1486,9 +1495,13 @@ tools:
password: ask_new_admin_password password: ask_new_admin_password
pattern: *pattern_password pattern: *pattern_password
required: True required: True
comment: good_practices_about_admin_password
--ignore-dyndns: --ignore-dyndns:
help: Do not subscribe domain to a DynDNS service help: Do not subscribe domain to a DynDNS service
action: store_true action: store_true
--force-password:
help: Use this if you really want to set a weak password
action: store_true
### tools_update() ### tools_update()
update: update:

View file

@ -1,8 +1,9 @@
# Use logrotate to manage the logfile # Use logrotate to manage the logfile
# #
# usage: ynh_use_logrotate [logfile] [--non-append] # usage: ynh_use_logrotate [logfile] [--non-append|--append] [specific_user/specific_group]
# | arg: logfile - absolute path of logfile # | arg: logfile - absolute path of logfile
# | arg: --non-append - (Option) Replace the config file instead of appending this new config. # | arg: --non-append - (Option) Replace the config file instead of appending this new config.
# | arg: specific_user : run logrotate as the specified user and group. If not specified logrotate is runned as root.
# #
# If no argument provided, a standard directory will be use. /var/log/${app} # If no argument provided, a standard directory will be use. /var/log/${app}
# You can provide a path with the directory only or with the logfile. # You can provide a path with the directory only or with the logfile.
@ -13,6 +14,7 @@
# Unless you use the option --non-append # Unless you use the option --non-append
ynh_use_logrotate () { ynh_use_logrotate () {
local customtee="tee -a" local customtee="tee -a"
local user_group="${3:-}"
if [ $# -gt 0 ] && [ "$1" == "--non-append" ]; then if [ $# -gt 0 ] && [ "$1" == "--non-append" ]; then
customtee="tee" customtee="tee"
# Destroy this argument for the next command. # Destroy this argument for the next command.
@ -29,6 +31,12 @@ ynh_use_logrotate () {
else else
local logfile="/var/log/${app}/*.log" # Without argument, use a defaut directory in /var/log local logfile="/var/log/${app}/*.log" # Without argument, use a defaut directory in /var/log
fi fi
local su_directive=""
if [[ -n $user_group ]]; then
su_directive=" # Run logorotate as specific user - group
su ${user_group%/*} ${user_group#*/}"
fi
cat > ./${app}-logrotate << EOF # Build a config file for logrotate cat > ./${app}-logrotate << EOF # Build a config file for logrotate
$logfile { $logfile {
# Rotate if the logfile exceeds 100Mo # Rotate if the logfile exceeds 100Mo
@ -47,6 +55,7 @@ $logfile {
notifempty notifempty
# Keep old logs in the same dir # Keep old logs in the same dir
noolddir noolddir
$su_directive
} }
EOF EOF
sudo mkdir -p $(dirname "$logfile") # Create the log directory, if not exist sudo mkdir -p $(dirname "$logfile") # Create the log directory, if not exist

View file

@ -299,6 +299,23 @@ ynh_backup_if_checksum_is_different () {
fi fi
} }
# Delete a file checksum from the app settings
#
# $app should be defined when calling this helper
#
# usage: ynh_remove_file_checksum file
# | arg: -f, --file= - The file for which the checksum will be deleted
ynh_delete_file_checksum () {
# Declare an array to define the options of this helper.
declare -Ar args_array=( [f]=file= )
local file
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_'
ynh_app_setting_delete $app $checksum_setting_name
}
# Remove a file or a directory securely # Remove a file or a directory securely
# #
# usage: ynh_secure_remove path_to_remove # usage: ynh_secure_remove path_to_remove

194
data/helpers.d/getopts Normal file
View file

@ -0,0 +1,194 @@
#!/bin/bash
# Internal helper design to allow helpers to use getopts to manage their arguments
#
# [internal]
#
# example: function my_helper()
# {
# declare -Ar args_array=( [a]=arg1= [b]=arg2= [c]=arg3 )
# local arg1
# local arg2
# local arg3
# ynh_handle_getopts_args "$@"
#
# [...]
# }
# my_helper --arg1 "val1" -b val2 -c
#
# usage: ynh_handle_getopts_args "$@"
# | arg: $@ - Simply "$@" to tranfert all the positionnal arguments to the function
#
# This helper need an array, named "args_array" with all the arguments used by the helper
# that want to use ynh_handle_getopts_args
# Be carreful, this array has to be an associative array, as the following example:
# declare -Ar args_array=( [a]=arg1 [b]=arg2= [c]=arg3 )
# Let's explain this array:
# a, b and c are short options, -a, -b and -c
# arg1, arg2 and arg3 are the long options associated to the previous short ones. --arg1, --arg2 and --arg3
# For each option, a short and long version has to be defined.
# Let's see something more significant
# declare -Ar args_array=( [u]=user [f]=finalpath= [d]=database )
#
# NB: Because we're using 'declare' without -g, the array will be declared as a local variable.
#
# Please keep in mind that the long option will be used as a variable to store the values for this option.
# For the previous example, that means that $finalpath will be fill with the value given as argument for this option.
#
# Also, in the previous example, finalpath has a '=' at the end. That means this option need a value.
# So, the helper has to be call with --finalpath /final/path, --finalpath=/final/path or -f /final/path, the variable $finalpath will get the value /final/path
# If there's many values for an option, -f /final /path, the value will be separated by a ';' $finalpath=/final;/path
# For an option without value, like --user in the example, the helper can be called only with --user or -u. $user will then get the value 1.
#
# To keep a retrocompatibility, a package can still call a helper, using getopts, with positional arguments.
# The "legacy mode" will manage the positional arguments and fill the variable in the same order than they are given in $args_array.
# e.g. for `my_helper "val1" val2`, arg1 will be filled with val1, and arg2 with val2.
ynh_handle_getopts_args () {
# Manage arguments only if there's some provided
set +x
if [ $# -ne 0 ]
then
# Store arguments in an array to keep each argument separated
local arguments=("$@")
# For each option in the array, reduce to short options for getopts (e.g. for [u]=user, --user will be -u)
# And built parameters string for getopts
# ${!args_array[@]} is the list of all keys in the array (A key is 'u' in [u]=user, user is a value)
local getopts_parameters=""
local key=""
for key in "${!args_array[@]}"
do
# Concatenate each keys of the array to build the string of arguments for getopts
# Will looks like 'abcd' for -a -b -c -d
# If the value of a key finish by =, it's an option with additionnal values. (e.g. --user bob or -u bob)
# Check the last character of the value associate to the key
if [ "${args_array[$key]: -1}" = "=" ]
then
# For an option with additionnal values, add a ':' after the letter for getopts.
getopts_parameters="${getopts_parameters}${key}:"
else
getopts_parameters="${getopts_parameters}${key}"
fi
# Check each argument given to the function
local arg=""
# ${#arguments[@]} is the size of the array
for arg in `seq 0 $(( ${#arguments[@]} - 1 ))`
do
# And replace long option (value of the key) by the short option, the key itself
# (e.g. for [u]=user, --user will be -u)
# Replace long option with =
arguments[arg]="${arguments[arg]//--${args_array[$key]}/-${key} }"
# And long option without =
arguments[arg]="${arguments[arg]//--${args_array[$key]%=}/-${key}}"
done
done
# Read and parse all the arguments
# Use a function here, to use standart arguments $@ and be able to use shift.
parse_arg () {
# Read all arguments, until no arguments are left
while [ $# -ne 0 ]
do
# Initialize the index of getopts
OPTIND=1
# Parse with getopts only if the argument begin by -, that means the argument is an option
# getopts will fill $parameter with the letter of the option it has read.
local parameter=""
getopts ":$getopts_parameters" parameter || true
if [ "$parameter" = "?" ]
then
ynh_die "Invalid argument: -${OPTARG:-}"
elif [ "$parameter" = ":" ]
then
ynh_die "-$OPTARG parameter requires an argument."
else
local shift_value=1
# Use the long option, corresponding to the short option read by getopts, as a variable
# (e.g. for [u]=user, 'user' will be used as a variable)
# Also, remove '=' at the end of the long option
# The variable name will be stored in 'option_var'
local option_var="${args_array[$parameter]%=}"
# If this option doesn't take values
# if there's a '=' at the end of the long option name, this option takes values
if [ "${args_array[$parameter]: -1}" != "=" ]
then
# 'eval ${option_var}' will use the content of 'option_var'
eval ${option_var}=1
else
# Read all other arguments to find multiple value for this option.
# Load args in a array
local all_args=("$@")
# If the first argument is longer than 2 characters,
# There's a value attached to the option, in the same array cell
if [ ${#all_args[0]} -gt 2 ]; then
# Remove the option and the space, so keep only the value itself.
all_args[0]="${all_args[0]#-${parameter} }"
# Reduce the value of shift, because the option has been removed manually
shift_value=$(( shift_value - 1 ))
fi
# Then read the array value per value
for i in `seq 0 $(( ${#all_args[@]} - 1 ))`
do
# If this argument is an option, end here.
if [ "${all_args[$i]:0:1}" == "-" ] || [ -z "${all_args[$i]}" ]
then
# Ignore the first value of the array, which is the option itself
if [ "$i" -ne 0 ]; then
break
fi
else
# Declare the content of option_var as a variable.
eval ${option_var}=""
# Else, add this value to this option
# Each value will be separated by ';'
if [ -n "${!option_var}" ]
then
# If there's already another value for this option, add a ; before adding the new value
eval ${option_var}+="\;"
fi
eval ${option_var}+=\"${all_args[$i]}\"
shift_value=$(( shift_value + 1 ))
fi
done
fi
fi
# Shift the parameter and its argument(s)
shift $shift_value
done
}
# LEGACY MODE
# Check if there's getopts arguments
if [ "${arguments[0]:0:1}" != "-" ]
then
# If not, enter in legacy mode and manage the arguments as positionnal ones.
echo "! Helper used in legacy mode !"
for i in `seq 0 $(( ${#arguments[@]} -1 ))`
do
# Use getopts_parameters as a list of key of the array args_array
# Remove all ':' in getopts_parameters
getopts_parameters=${getopts_parameters//:}
# Get the key from getopts_parameters, by using the key according to the position of the argument.
key=${getopts_parameters:$i:1}
# Use the long option, corresponding to the key, as a variable
# (e.g. for [u]=user, 'user' will be used as a variable)
# Also, remove '=' at the end of the long option
# The variable name will be stored in 'option_var'
local option_var="${args_array[$key]%=}"
# Store each value given as argument in the corresponding variable
# The values will be stored in the same order than $args_array
eval ${option_var}+=\"${arguments[$i]}\"
done
else
# END LEGACY MODE
# Call parse_arg and pass the modified list of args as an array of arguments.
parse_arg "${arguments[@]}"
fi
fi
set -x
}

View file

@ -8,12 +8,11 @@ ynh_die() {
# Display a message in the 'INFO' logging category # Display a message in the 'INFO' logging category
# #
# usage: ynh_info "Some message" # usage: ynh_info "Some message"
ynh_info() ynh_print_info() {
{
echo "$1" >> "$YNH_STDINFO" echo "$1" >> "$YNH_STDINFO"
} }
# Ignore the yunohost-cli log to prevent errors with conditionals commands # Ignore the yunohost-cli log to prevent errors with conditional commands
# #
# [internal] # [internal]
# #
@ -29,3 +28,99 @@ ynh_no_log() {
sudo mv ${ynh_cli_log}-move ${ynh_cli_log} sudo mv ${ynh_cli_log}-move ${ynh_cli_log}
return $? return $?
} }
# Main printer, just in case in the future we have to change anything about that.
#
# [internal]
#
ynh_print_log () {
echo -e "${1}"
}
# Print a warning on stderr
#
# usage: ynh_print_warn "Text to print"
# | arg: text - The text to print
ynh_print_warn () {
ynh_print_log "\e[93m\e[1m[WARN]\e[0m ${1}" >&2
}
# Print an error on stderr
#
# usage: ynh_print_err "Text to print"
# | arg: text - The text to print
ynh_print_err () {
ynh_print_log "\e[91m\e[1m[ERR]\e[0m ${1}" >&2
}
# Execute a command and print the result as an error
#
# usage: ynh_exec_err command to execute
# usage: ynh_exec_err "command to execute | following command"
# In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe.
#
# | arg: command - command to execute
ynh_exec_err () {
ynh_print_err "$(eval $@)"
}
# Execute a command and print the result as a warning
#
# usage: ynh_exec_warn command to execute
# usage: ynh_exec_warn "command to execute | following command"
# In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe.
#
# | arg: command - command to execute
ynh_exec_warn () {
ynh_print_warn "$(eval $@)"
}
# Execute a command and force the result to be printed on stdout
#
# usage: ynh_exec_warn_less command to execute
# usage: ynh_exec_warn_less "command to execute | following command"
# In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe.
#
# | arg: command - command to execute
ynh_exec_warn_less () {
eval $@ 2>&1
}
# Execute a command and redirect stdout in /dev/null
#
# usage: ynh_exec_quiet command to execute
# usage: ynh_exec_quiet "command to execute | following command"
# In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe.
#
# | arg: command - command to execute
ynh_exec_quiet () {
eval $@ > /dev/null
}
# Execute a command and redirect stdout and stderr in /dev/null
#
# usage: ynh_exec_fully_quiet command to execute
# usage: ynh_exec_fully_quiet "command to execute | following command"
# In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe.
#
# | arg: command - command to execute
ynh_exec_fully_quiet () {
eval $@ > /dev/null 2>&1
}
# Remove any logs for all the following commands.
#
# usage: ynh_print_OFF
# WARNING: You should be careful with this helper, and never forget to use ynh_print_ON as soon as possible to restore the logging.
ynh_print_OFF () {
set +x
}
# Restore the logging after ynh_print_OFF
#
# usage: ynh_print_ON
ynh_print_ON () {
set -x
# Print an echo only for the log, to be able to know that ynh_print_ON has been called.
echo ynh_print_ON > /dev/null
}

View file

@ -173,7 +173,7 @@ ynh_setup_source () {
then # Use the local source file if it is present then # Use the local source file if it is present
cp $local_src $src_filename cp $local_src $src_filename
else # If not, download the source else # If not, download the source
wget -nv -O $src_filename $src_url local out=`wget -nv -O $src_filename $src_url 2>&1` || ynh_print_err $out
fi fi
# Check the control sum # Check the control sum
@ -254,6 +254,9 @@ ynh_local_curl () {
POST_data="--data ${POST_data::-1}" POST_data="--data ${POST_data::-1}"
fi fi
# Wait untils nginx has fully reloaded (avoid curl fail with http2)
sleep 2
# Curl the URL # Curl the URL
curl --silent --show-error -kL -H "Host: $domain" --resolve $domain:443:127.0.0.1 $POST_data "$full_page_url" curl --silent --show-error -kL -H "Host: $domain" --resolve $domain:443:127.0.0.1 $POST_data "$full_page_url"
} }

View file

@ -12,6 +12,8 @@ backup_dir="${1}/data/home"
# Backup user home # Backup user home
for f in $(find /home/* -type d -prune | awk -F/ '{print $NF}'); do for f in $(find /home/* -type d -prune | awk -F/ '{print $NF}'); do
if [[ ! "$f" =~ ^yunohost|lost\+found ]]; then if [[ ! "$f" =~ ^yunohost|lost\+found ]]; then
if [ ! -e "/home/$f/.nobackup" ]; then
ynh_backup "/home/$f" "${backup_dir}/$f" 1 ynh_backup "/home/$f" "${backup_dir}/$f" 1
fi fi
fi
done done

View file

@ -2,6 +2,8 @@
set -e set -e
. /usr/share/yunohost/helpers.d/utils
do_init_regen() { do_init_regen() {
if [[ $EUID -ne 0 ]]; then if [[ $EUID -ne 0 ]]; then
echo "You must be root to run this script" 1>&2 echo "You must be root to run this script" 1>&2
@ -42,18 +44,18 @@ do_pre_regen() {
mkdir -p "$mail_autoconfig_dir" mkdir -p "$mail_autoconfig_dir"
# NGINX server configuration # NGINX server configuration
cat server.tpl.conf \ export domain
| sed "s/{{ domain }}/${domain}/g" \ export domain_cert_ca=$(yunohost domain cert-status $domain --json \
> "${nginx_conf_dir}/${domain}.conf" | jq ".certificates.\"$domain\".CA_type" \
| tr -d '"')
cat autoconfig.tpl.xml \
| sed "s/{{ domain }}/${domain}/g" \
> "${mail_autoconfig_dir}/config-v1.1.xml"
ynh_render_template "server.tpl.conf" "${nginx_conf_dir}/${domain}.conf"
ynh_render_template "autoconfig.tpl.xml" "${mail_autoconfig_dir}/config-v1.1.xml"
[[ $main_domain != $domain ]] \ [[ $main_domain != $domain ]] \
&& touch "${domain_conf_dir}/yunohost_local.conf" \ && touch "${domain_conf_dir}/yunohost_local.conf" \
|| cp yunohost_local.conf "${domain_conf_dir}/yunohost_local.conf" || cp yunohost_local.conf "${domain_conf_dir}/yunohost_local.conf"
done done
# remove old domain conf files # remove old domain conf files

Binary file not shown.

View file

@ -9,16 +9,64 @@
-- A table is a list of values, except each value has a name. An -- A table is a list of values, except each value has a name. An
-- example would be: -- example would be:
-- --
-- ssl = { key = "keyfile.key", certificate = "certificate.crt" } -- ssl = { key = "keyfile.key", certificate = "certificate.cert" }
-- --
-- Tip: You can check that the syntax of this file is correct when you have finished -- Tip: You can check that the syntax of this file is correct when you have finished
-- by running: luac -p metronome.cfg.lua -- by running: luac -p metronome.cfg.lua
-- If there are any errors, it will let you know what and where they are, otherwise it -- If there are any errors, it will let you know what and where they are, otherwise it
-- will keep quiet. -- will keep quiet.
---------- Server-wide settings ---------- -- Global settings go in this section
-- Settings in this section apply to the whole server and are the default settings
-- for any virtual hosts -- This is the list of modules Metronome will load on startup.
-- It looks for mod_modulename.lua in the plugins folder, so make sure that exists too.
modules_enabled = {
-- Generally required
"roster"; -- Allow users to have a roster. Recommended.
"saslauth"; -- Authentication for clients. Recommended if you want to log in.
"tls"; -- Add support for secure TLS on c2s/s2s connections
"disco"; -- Service discovery
-- Not essential, but recommended
"private"; -- Private XML storage (for room bookmarks, etc.)
"vcard"; -- Allow users to set vCards
"pep"; -- Allows setting of mood, tune, etc.
"posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
"bidi"; -- Enables Bidirectional Server-to-Server Streams.
-- Nice to have
"version"; -- Replies to server version requests
"uptime"; -- Report how long server has been running
"time"; -- Let others know the time here on this server
"ping"; -- Replies to XMPP pings with pongs
"register"; -- Allow users to register on this server using a client and change passwords
"stream_management"; -- Allows clients and servers to use Stream Management
"stanza_optimizations"; -- Allows clients to use Client State Indication and SIFT
"message_carbons"; -- Allows clients to enable carbon copies of messages
"mam"; -- Enable server-side message archives using Message Archive Management
"push"; -- Enable Push Notifications via PubSub using XEP-0357
"lastactivity"; -- Enables clients to know the last presence status of an user
"adhoc_cm"; -- Allow to set client certificates to login through SASL External via adhoc
"admin_adhoc"; -- administration adhoc commands
"bookmarks"; -- XEP-0048 Bookmarks synchronization between PEP and Private Storage
"sec_labels"; -- Allows to use a simplified version XEP-0258 Security Labels and related ACDFs.
"privacy"; -- Add privacy lists and simple blocking command support
-- Other specific functionality
--"admin_telnet"; -- administration console, telnet to port 5582
--"admin_web"; -- administration web interface
"bosh"; -- Enable support for BOSH clients, aka "XMPP over Bidirectional Streams over Synchronous HTTP"
--"compression"; -- Allow clients to enable Stream Compression
--"spim_block"; -- Require authorization via OOB form for messages from non-contacts and block unsollicited messages
--"gate_guard"; -- Enable config-based blacklisting and hit-based auto-banning features
--"incidents_handling"; -- Enable Incidents Handling support (can be administered via adhoc commands)
--"server_presence"; -- Enables Server Buddies extension support
--"service_directory"; -- Enables Service Directories extension support
--"public_service"; -- Enables Server vCard support for public services in directories and advertises in features
--"register_api"; -- Provides secure API for both Out-Of-Band and In-Band registration for E-Mail verification
"websocket"; -- Enable support for WebSocket clients, aka "XMPP over WebSockets"
};
-- Server PID -- Server PID
pidfile = "/var/run/metronome/metronome.pid" pidfile = "/var/run/metronome/metronome.pid"
@ -33,109 +81,25 @@ http_interfaces = { "127.0.0.1", "::1" }
-- Enable IPv6 -- Enable IPv6
use_ipv6 = true use_ipv6 = true
-- This is the list of modules Metronome will load on startup.
-- It looks for mod_modulename.lua in the plugins folder, so make sure that exists too.
modules_enabled = {
-- Generally required
"roster"; -- Allow users to have a roster. Recommended ;)
"saslauth"; -- Authentication for clients and servers. Recommended if you want to log in.
"tls"; -- Add support for secure TLS on c2s/s2s connections
"dialback"; -- s2s dialback support
"disco"; -- Service discovery
--"discoitems"; -- Service discovery items
--"extdisco"; -- External Service Discovery
-- Not essential, but recommended
"private"; -- Private XML storage (for room bookmarks, etc.)
"vcard"; -- Allow users to set vCards
"privacy"; -- Support privacy lists
-- These are commented by default as they have a performance impact
--"compression"; -- Stream compression (Debian: requires lua-zlib module to work)
-- Nice to have
"version"; -- Replies to server version requests
"uptime"; -- Report how long server has been running
"time"; -- Let others know the time here on this server
"ping"; -- Replies to XMPP pings with pongs
"pep"; -- Enables users to publish their mood, activity, playing music and more
"message_carbons"; -- Allow clients to keep in sync with messages send on other resources
"register"; -- Allow users to register on this server using a client and change passwords
"adhoc"; -- Support for "ad-hoc commands" that can be executed with an XMPP client
-- Admin interfaces
"admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands
"admin_telnet"; -- Opens telnet console interface on localhost port 5582
-- HTTP modules
"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
--"websockets"; -- Enable WebSocket clients
--"http_files"; -- Serve static files from a directory over HTTP
-- Other specific functionality
-- "bidi"; -- Bidirectional Streams for S2S connections
-- "stream_management"; -- Stream Management support
--"groups"; -- Shared roster support
--"announce"; -- Send announcement to all online users
--"welcome"; -- Welcome users who register accounts
--"watchregistrations"; -- Alert admins of registrations
--"motd"; -- Send a message to users when they log in
"mam"; -- Nice archive management
--"legacyauth"; -- Legacy authentication. Only used by some old clients and bots.
"offline"; -- Store offline messages
"c2s"; -- Handle client connections
"s2s"; -- Handle server-to-server connections
-- Debian: do not remove this module, or you lose syslog
-- support
"posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
};
-- Discovery items -- Discovery items
disco_items = { disco_items = {
{ "muc.{{ main_domain }}" }, { "muc.{{ main_domain }}" },
{ "pubsub.{{ main_domain }}" }, { "pubsub.{{ main_domain }}" },
{ "upload.{{ main_domain }}" },
{ "vjud.{{ main_domain }}" } { "vjud.{{ main_domain }}" }
}; };
-- BOSH configuration (mod_bosh) -- BOSH configuration (mod_bosh)
bosh_max_inactivity = 30
consider_bosh_secure = true consider_bosh_secure = true
cross_domain_bosh = true cross_domain_bosh = true
-- WebSocket configuration (mod_websocket)
consider_websocket_secure = true
cross_domain_websocket = true
-- Disable account creation by default, for security -- Disable account creation by default, for security
allow_registration = false allow_registration = false
-- SSL/TLS configuration
ssl = {
options = {
"no_sslv2",
"no_sslv3",
"no_ticket",
"no_compression",
"cipher_server_preference"
};
}
-- Force clients to use encrypted connections? This option will
-- prevent clients from authenticating unless they are using encryption.
c2s_require_encryption = true
-- Force servers to use encrypted connections? This option will
-- prevent servers from connecting unless they are using encryption.
s2s_require_encryption = true
-- Allow servers to use an unauthenticated encryption channel
s2s_allow_encryption = true
allow_unencrypted_plain_auth = false;
s2s_secure = true
s2s_secure_auth = false
--anonymous_login = false
-- Use LDAP storage backend for all stores -- Use LDAP storage backend for all stores
storage = "ldap" storage = "ldap"
@ -147,7 +111,6 @@ log = {
-- "*console"; -- Log to the console, useful for debugging with daemonize=false -- "*console"; -- Log to the console, useful for debugging with daemonize=false
} }
------ Components ------ ------ Components ------
-- You can specify components to add hosts that provide special services, -- You can specify components to add hosts that provide special services,
-- like multi-user conferences, and transports. -- like multi-user conferences, and transports.
@ -163,23 +126,28 @@ Component "muc.{{ main_domain }}" "muc"
modules_enabled = { modules_enabled = {
"muc_limits"; "muc_limits";
"muc_log"; "muc_log";
"muc_log_mam";
"muc_log_http"; "muc_log_http";
"muc_vcard";
} }
muc_event_rate = 0.5 muc_event_rate = 0.5
muc_burst_factor = 10 muc_burst_factor = 10
muc_log_http_config = {
url_base = "logs";
theme = "metronome";
}
---Set up a PubSub server ---Set up a PubSub server
Component "pubsub.{{ main_domain }}" "pubsub" Component "pubsub.{{ main_domain }}" "pubsub"
name = "{{ main_domain }} Publish/Subscribe" name = "{{ main_domain }} Publish/Subscribe"
unrestricted_node_creation = true -- Anyone can create a PubSub node (from any server) unrestricted_node_creation = true -- Anyone can create a PubSub node (from any server)
---Set up a HTTP Upload service
Component "upload.{{ main_domain }}" "http_upload"
name = "{{ main_domain }} Sharing Service"
http_file_size_limit = 6*1024*1024
http_file_quota = 60*1024*1024
---Set up a VJUD service ---Set up a VJUD service
Component "vjud.{{ main_domain }}" "vjud" Component "vjud.{{ main_domain }}" "vjud"
ud_disco_name = "{{ main_domain }} User Directory" ud_disco_name = "{{ main_domain }} User Directory"
@ -190,4 +158,3 @@ Component "vjud.{{ main_domain }}" "vjud"
-- Settings under each VirtualHost entry apply *only* to that host. -- Settings under each VirtualHost entry apply *only* to that host.
Include "conf.d/*.cfg.lua" Include "conf.d/*.cfg.lua"

View file

@ -73,6 +73,6 @@ server {
access_by_lua_file /usr/share/ssowat/access.lua; access_by_lua_file /usr/share/ssowat/access.lua;
} }
include conf.d/yunohost_admin.conf.inc; include /etc/nginx/conf.d/yunohost_admin.conf.inc;
include conf.d/yunohost_api.conf.inc; include /etc/nginx/conf.d/yunohost_api.conf.inc;
} }

View file

@ -4,6 +4,7 @@ location /yunohost/api/ {
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade"; proxy_set_header Connection "upgrade";
proxy_set_header Host $http_host;
# Custom 502 error page # Custom 502 error page
error_page 502 /yunohost/api/error/502; error_page 502 /yunohost/api/error/502;

View file

@ -5,7 +5,7 @@ server {
access_by_lua_file /usr/share/ssowat/access.lua; access_by_lua_file /usr/share/ssowat/access.lua;
include conf.d/{{ domain }}.d/*.conf; include /etc/nginx/conf.d/{{ domain }}.d/*.conf;
location /yunohost/admin { location /yunohost/admin {
return 301 https://$http_host$request_uri; return 301 https://$http_host$request_uri;
@ -68,12 +68,21 @@ server {
add_header X-Permitted-Cross-Domain-Policies none; add_header X-Permitted-Cross-Domain-Policies none;
add_header X-Frame-Options "SAMEORIGIN"; add_header X-Frame-Options "SAMEORIGIN";
{% if domain_cert_ca == "Let's Encrypt" %}
# OCSP settings
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/yunohost/certs/{{ domain }}/crt.pem;
resolver 127.0.0.1 127.0.1.1 valid=300s;
resolver_timeout 5s;
{% endif %}
access_by_lua_file /usr/share/ssowat/access.lua; access_by_lua_file /usr/share/ssowat/access.lua;
include conf.d/{{ domain }}.d/*.conf; include /etc/nginx/conf.d/{{ domain }}.d/*.conf;
include conf.d/yunohost_admin.conf.inc; include /etc/nginx/conf.d/yunohost_admin.conf.inc;
include conf.d/yunohost_api.conf.inc; include /etc/nginx/conf.d/yunohost_api.conf.inc;
access_log /var/log/nginx/{{ domain }}-access.log; access_log /var/log/nginx/{{ domain }}-access.log;
error_log /var/log/nginx/{{ domain }}-error.log; error_log /var/log/nginx/{{ domain }}-error.log;

View file

@ -5,8 +5,8 @@
# See ldap.conf(5) for details # See ldap.conf(5) for details
# This file should be world readable but not world writable. # This file should be world readable but not world writable.
#BASE dc=example,dc=com BASE dc=yunohost,dc=org
#URI ldap://ldap.example.com ldap://ldap-master.example.com:666 URI ldap://localhost:389
#SIZELIMIT 12 #SIZELIMIT 12
#TIMELIMIT 15 #TIMELIMIT 15

View file

@ -43,7 +43,7 @@ unique_subject = no # Set to 'no' to allow creation of
new_certs_dir = $dir/newcerts # default place for new certs. new_certs_dir = $dir/newcerts # default place for new certs.
certificate = $dir/ca/cacert.pem # The CA certificate certificate = $dir/ca/cacert.pem # The CA certificate
serial = $dir/serial # The current serial number #serial = $dir/serial # The current serial number
#crlnumber = $dir/crlnumber # the current crl number #crlnumber = $dir/crlnumber # the current crl number
# must be commented out to leave a V1 CRL # must be commented out to leave a V1 CRL
crl = $dir/crl.pem # The current CRL crl = $dir/crl.pem # The current CRL

36
debian/changelog vendored
View file

@ -1,3 +1,39 @@
yunohost (3.2.2) stable; urgency=low
* [hotfix] mod_auth_ldap: reflect SASL API changes in latest Metronome (#546)
* [enh] Add the internal helper ynh_handle_getopts_args (#520)
Thanks to all contributors (Maranda, Maniack) ! <3
-- Alexandre Aubin <alex.aubin@mailoo.org> Fri, 28 Sep 2018 23:04:00 +0000
yunohost (3.2.1) stable; urgency=low
* Don't send an email if no certificate needs to be renewed (#540)
* Fix an issue with home backups (#541)
* Fix an issue with installs on OVH VPS
* Tell the user about post-install available in browser in bootprompt (#544)
* Improve Arabic translation
Thanks to all contributors (BoF, ljf, Aleks) ! <3
-- Alexandre Aubin <alex.aubin@mailoo.org> Mon, 17 Sep 2018 18:06:00 +0000
yunohost (3.2.0) stable; urgency=low
* Add many print and exec helpers (#523)
* Add su directive as option for logrotate helper (#511)
* Add equivs, fake-hwclock and jq as base dependencies (#515, #514, #532)
* Allow to add a service description on "yunohost service add" (#529)
* Add option '--need-lock' to 'yunohost service add' (#530)
* Don't backup user home with .nobackup file (#536)
* Add a script to automatically generate helpers documentation (#538)
* [i18n] Improve Arabic translation
Thanks to all contributors (Bram, Maniack, irina11y, Josue, BoF, ljf, Aleks) ! <3
-- Alexandre Aubin <alex.aubin@mailoo.org> Tue, 11 Sep 2018 16:30:00 +0000
yunohost (3.2.0~testing1) testing; urgency=low yunohost (3.2.0~testing1) testing; urgency=low
* Add logging system of every unit operation (#165) * Add logging system of every unit operation (#165)

4
debian/control vendored
View file

@ -14,7 +14,7 @@ Depends: ${python:Depends}, ${misc:Depends}
, python-psutil, python-requests, python-dnspython, python-openssl , python-psutil, python-requests, python-dnspython, python-openssl
, python-apt, python-miniupnpc, python-dbus, python-jinja2 , python-apt, python-miniupnpc, python-dbus, python-jinja2
, glances , glances
, dnsutils, bind9utils, unzip, git, curl, cron, wget , dnsutils, bind9utils, unzip, git, curl, cron, wget, jq
, ca-certificates, netcat-openbsd, iproute , ca-certificates, netcat-openbsd, iproute
, mariadb-server, php-mysql | php-mysqlnd , mariadb-server, php-mysql | php-mysqlnd
, slapd, ldap-utils, sudo-ldap, libnss-ldapd, unscd , slapd, ldap-utils, sudo-ldap, libnss-ldapd, unscd
@ -25,7 +25,7 @@ Depends: ${python:Depends}, ${misc:Depends}
, dnsmasq, openssl, avahi-daemon, libnss-mdns, resolvconf, libnss-myhostname , dnsmasq, openssl, avahi-daemon, libnss-mdns, resolvconf, libnss-myhostname
, metronome , metronome
, rspamd (>= 1.6.0), redis-server, opendkim-tools , rspamd (>= 1.6.0), redis-server, opendkim-tools
, haveged , haveged, fake-hwclock
, equivs , equivs
Recommends: yunohost-admin Recommends: yunohost-admin
, openssh-server, ntp, inetutils-ping | iputils-ping , openssh-server, ntp, inetutils-ping | iputils-ping

1
debian/install vendored
View file

@ -4,6 +4,7 @@ data/bash-completion.d/yunohost /etc/bash_completion.d/
data/actionsmap/* /usr/share/moulinette/actionsmap/ data/actionsmap/* /usr/share/moulinette/actionsmap/
data/hooks/* /usr/share/yunohost/hooks/ data/hooks/* /usr/share/yunohost/hooks/
data/other/yunoprompt.service /etc/systemd/system/ data/other/yunoprompt.service /etc/systemd/system/
data/other/password/* /usr/share/yunohost/other/password/
data/other/* /usr/share/yunohost/yunohost-config/moulinette/ data/other/* /usr/share/yunohost/yunohost-config/moulinette/
data/templates/* /usr/share/yunohost/templates/ data/templates/* /usr/share/yunohost/templates/
data/helpers /usr/share/yunohost/ data/helpers /usr/share/yunohost/

171
doc/generate_helper_doc.py Normal file
View file

@ -0,0 +1,171 @@
#!/usr/env/python2.7
import os
import glob
import datetime
def render(data):
from jinja2 import Template
from ansi2html import Ansi2HTMLConverter
from ansi2html.style import get_styles
conv = Ansi2HTMLConverter()
shell_css = "\n".join(map(str, get_styles(conv.dark_bg, conv.scheme)))
def shell_to_html(shell):
return conv.convert(shell, False)
template = open("helper_doc_template.html", "r").read()
t = Template(template)
t.globals['now'] = datetime.datetime.utcnow
result = t.render(data=data, convert=shell_to_html, shell_css=shell_css)
open("helpers.html", "w").write(result)
##############################################################################
class Parser():
def __init__(self, filename):
self.file = open(filename, "r").readlines()
self.blocks = None
def parse_blocks(self):
self.blocks = []
current_reading = "void"
current_block = { "name": None,
"line": -1,
"comments": [],
"code": [] }
for i, line in enumerate(self.file):
line = line.rstrip().replace("\t", " ")
if current_reading == "void":
if is_global_comment(line):
# We start a new comment bloc
current_reading = "comments"
assert line.startswith("# ") or line == "#", malformed_error(i)
current_block["comments"].append(line[2:])
else:
pass
#assert line == "", malformed_error(i)
continue
elif current_reading == "comments":
if is_global_comment(line):
# We're still in a comment bloc
assert line.startswith("# ") or line == "#", malformed_error(i)
current_block["comments"].append(line[2:])
else:
# We're getting out of a comment bloc, we should find
# the name of the function
assert len(line.split()) >= 1
current_block["line"] = i
current_block["name"] = line.split()[0].strip("(){")
# Then we expect to read the function
current_reading = "code"
continue
elif current_reading == "code":
if line == "}":
# We're getting out of the function
current_reading = "void"
# Then we keep this bloc and start a new one
# (we ignore helpers containing [internal] ...)
if not "[internal]" in current_block["comments"]:
self.blocks.append(current_block)
current_block = { "name": None,
"line": -1,
"comments": [],
"code": [] }
else:
current_block["code"].append(line)
pass
continue
def parse_block(self, b):
b["brief"] = ""
b["details"] = ""
b["usage"] = ""
b["args"] = []
b["ret"] = ""
b["example"] = ""
subblocks = '\n'.join(b["comments"]).split("\n\n")
for i, subblock in enumerate(subblocks):
subblock = subblock.strip()
if i == 0:
b["brief"] = subblock
continue
elif subblock.startswith("example"):
b["example"] = " ".join(subblock.split()[1:])
continue
elif subblock.startswith("usage"):
for line in subblock.split("\n"):
if line.startswith("| arg"):
argname = line.split()[2]
argdescr = " ".join(line.split()[4:])
b["args"].append((argname, argdescr))
elif line.startswith("| ret"):
b["ret"] = " ".join(line.split()[2:])
else:
if line.startswith("usage"):
line = " ".join(line.split()[1:])
b["usage"] += line + "\n"
continue
elif subblock.startswith("| arg"):
for line in subblock.split("\n"):
if line.startswith("| arg"):
argname = line.split()[2]
argdescr = line.split()[4:]
b["args"].append((argname, argdescr))
continue
else:
b["details"] += subblock + "\n\n"
b["usage"] = b["usage"].strip()
def is_global_comment(line):
return line.startswith('#')
def malformed_error(line_number):
import pdb; pdb.set_trace()
return "Malformed file line {} ?".format(line_number)
def main():
helper_files = sorted(glob.glob("../data/helpers.d/*"))
helpers = []
for helper_file in helper_files:
category_name = os.path.basename(helper_file)
print "Parsing %s ..." % category_name
p = Parser(helper_file)
p.parse_blocks()
for b in p.blocks:
p.parse_block(b)
helpers.append((category_name, p.blocks))
render(helpers)
main()

View file

@ -0,0 +1,93 @@
<!-- NO_MARKDOWN_PARSING -->
<h1>App helpers</h1>
{% for category, helpers in data %}
<h3 style="text-transform: uppercase; font-weight: bold">{{ category }}</h3>
{% for h in helpers %}
<div class="helper-card">
<div class="helper-card-body">
<div data-toggle="collapse" href="#collapse-{{ h.name }}" style="cursor:pointer">
<h5 class="helper-card-title"><tt>{{ h.name }}</tt></h5>
<h6 class="helper-card-subtitle text-muted">{{ h.brief }}</h6>
</div>
<div id="collapse-{{ h.name }}" class="collapse" role="tabpanel">
<hr style="margin-top:25px; margin-bottom:25px;">
<p>
{% if not '\n' in h.usage %}
<strong>Usage</strong>: <code class="helper-code">{{ h.usage }}</code>
{% else %}
<strong>Usage</strong>: <code class="helper-code helper-usage">{{ h.usage }}</code>
{% endif %}
</p>
{% if h.args %}
<p>
<strong>Arguments</strong>:
<ul>
{% for name, descr in h.args %}
<li><code>{{ name }}</code> : {{ descr }}</li>
{% endfor %}
</ul>
</p>
{% endif %}
{% if h.ret %}
<p>
<strong>Returns</strong>: {{ h.ret }}
</p>
{% endif %}
{% if h.example %}
<p>
<strong>Example</strong>: <code class="helper-code">{{ h.example }}</code>
</p>
{% endif %}
{% if h.details %}
<p>
<strong>Details</strong>:
<p>
{{ h.details.replace('\n', '</br>') }}
</p>
</p>
{% endif %}
<p>
<a href="https://github.com/YunoHost/yunohost/blob/stretch-unstable/data/helpers.d/{{ category }}#L{{ h.line + 1 }}">Dude, show me the code !</a>
</p>
</div>
</div>
</div>
{% endfor %}
{% endfor %}
<style>
/*=================================================
Helper card
=================================================*/
.helper-card {
width:100%;
min-height: 1px;
margin-right: 10px;
margin-left: 10px;
border: 1px solid rgba(0,0,0,.125);
border-radius: 0.5rem;
word-wrap: break-word;
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
.helper-card-body {
padding: 1.25rem;
padding-top: 0.8rem;
padding-bottom: 0;
}
.helper-code {
word-wrap: break-word;
white-space: normal;
}
/*===============================================*/
</style>

View file

@ -56,8 +56,9 @@ function new_default_provider(host)
return nil, "Account creation/modification not available with LDAP."; return nil, "Account creation/modification not available with LDAP.";
end end
function provider.get_sasl_handler() function provider.get_sasl_handler(session)
local testpass_authentication_profile = { local testpass_authentication_profile = {
session = session,
plain_test = function(sasl, username, password, realm) plain_test = function(sasl, username, password, realm)
return provider.test_password(username, password), true; return provider.test_password(username, password), true;
end, end,

View file

@ -296,8 +296,8 @@
"restore_system_part_failed": "Unable to restore the '{part:s}' system part", "restore_system_part_failed": "Unable to restore the '{part:s}' system part",
"server_shutdown": "سوف ينطفئ الخادوم", "server_shutdown": "سوف ينطفئ الخادوم",
"server_shutdown_confirm": "سوف ينطفئ الخادوم حالا. متأكد ؟ [{answers:s}]", "server_shutdown_confirm": "سوف ينطفئ الخادوم حالا. متأكد ؟ [{answers:s}]",
"server_reboot": "The server will reboot", "server_reboot": "سيعاد تشغيل الخادوم",
"server_reboot_confirm": "The server will reboot immediatly, are you sure? [{answers:s}]", "server_reboot_confirm": "سيعاد تشغيل الخادوم في الحين. هل أنت متأكد ؟ [{answers:s}]",
"service_add_failed": "تعذرت إضافة خدمة '{service:s}'", "service_add_failed": "تعذرت إضافة خدمة '{service:s}'",
"service_added": "The service '{service:s}' has been added", "service_added": "The service '{service:s}' has been added",
"service_already_started": "Service '{service:s}' has already been started", "service_already_started": "Service '{service:s}' has already been started",

25
locales/ca.json Normal file
View file

@ -0,0 +1,25 @@
{
"action_invalid": "Acció '{action:s}' invàlida",
"admin_password": "Contrasenya d'administració",
"admin_password_change_failed": "No s'ha pogut canviar la contrasenya",
"admin_password_changed": "S'ha canviat la contrasenya d'administració",
"app_already_installed": "{app:s} ja està instal·lada",
"app_already_installed_cant_change_url": "Aquesta aplicació ja està instal·lada. La URL no és pot canviar únicament amb aquesta funció. Mireu a \"app changeurl\" si està disponible.",
"app_already_up_to_date": "{app:s} ja està actualitzada",
"app_argument_choice_invalid": "Aquesta opció no és vàlida per l'argument '{name:s}', ha de ser una de {choices:s}",
"app_argument_invalid": "Valor invàlid per l'argument '{name:s}':{error:s}",
"app_argument_required": "Es necessita l'argument '{name:s}'",
"app_change_no_change_url_script": "L'aplicació {app_name:s} encara no permet canviar la seva URL, es possible que s'hagi d'actualitzar.",
"app_change_url_failed_nginx_reload": "No s'ha pogut tornar a carregar nginx. Aquí teniu el resultat de \"nginx -t\":\n{nginx_errors:s}",
"app_change_url_identical_domains": "L'antic i el nou domini/camí són idèntics ('{domain:s}{path:s}'), no hi ha res per fer.",
"app_change_url_no_script": "Aquesta aplicació '{app_name:s}' encara no permet modificar la URL. Potser s'ha d'actualitzar l'aplicació.",
"app_change_url_success": "La URL de {app:s} s'ha canviat correctament a {domain:s}{path:s}",
"app_extraction_failed": "No s'han pogut extreure els fitxers d'instal·lació",
"app_id_invalid": "Id de l'aplicació incorrecte",
"app_incompatible": "L'aplicació {app} no és compatible amb la teva versió de YunoHost",
"app_install_files_invalid": "Fitxers d'instal·lació invàlids",
"app_location_already_used": "L'aplicació '{app}' ja està instal·lada en aquest camí ({path})",
"app_make_default_location_already_used": "No es pot fer l'aplicació '{app}' per defecte en el domini {domain} ja que ja és utilitzat per una altra aplicació '{other_app}'",
"app_location_install_failed": "No s'ha pogut instal·lar l'aplicació en aquest camí ja que entra en conflicte amb l'aplicació '{other_app}' ja instal·lada a '{other_path}'",
"app_location_unavailable": "Aquesta url no està disponible o entra en conflicte amb aplicacions ja instal·lades:\n{apps:s}"
}

View file

@ -130,7 +130,6 @@
"certmanager_hit_rate_limit": "Too many certificates already issued for exact set of domains {domain:s} recently. Please try again later. See https://letsencrypt.org/docs/rate-limits/ for more details", "certmanager_hit_rate_limit": "Too many certificates already issued for exact set of domains {domain:s} recently. Please try again later. See https://letsencrypt.org/docs/rate-limits/ for more details",
"certmanager_http_check_timeout": "Timed out when server tried to contact itself through HTTP using public IP address (domain {domain:s} with ip {ip:s}). You may be experiencing hairpinning issue or the firewall/router ahead of your server is misconfigured.", "certmanager_http_check_timeout": "Timed out when server tried to contact itself through HTTP using public IP address (domain {domain:s} with ip {ip:s}). You may be experiencing hairpinning issue or the firewall/router ahead of your server is misconfigured.",
"certmanager_no_cert_file": "Unable to read certificate file for domain {domain:s} (file: {file:s})", "certmanager_no_cert_file": "Unable to read certificate file for domain {domain:s} (file: {file:s})",
"certmanager_old_letsencrypt_app_detected": "\nYunohost detected that the 'letsencrypt' app is installed, which conflits with the new built-in certificate management features in Yunohost. If you wish to use the new built-in features, please run the following commands to migrate your installation:\n\n yunohost app remove letsencrypt\n yunohost domain cert-install\n\nN.B.: this will attempt to re-install certificates for all domains with a Let's Encrypt certificate or self-signed certificate",
"certmanager_self_ca_conf_file_not_found": "Configuration file not found for self-signing authority (file: {file:s})", "certmanager_self_ca_conf_file_not_found": "Configuration file not found for self-signing authority (file: {file:s})",
"certmanager_unable_to_parse_self_CA_name": "Unable to parse name of self-signing authority (file: {file:s})", "certmanager_unable_to_parse_self_CA_name": "Unable to parse name of self-signing authority (file: {file:s})",
"custom_app_url_required": "You must provide a URL to upgrade your custom app {app:s}", "custom_app_url_required": "You must provide a URL to upgrade your custom app {app:s}",
@ -197,6 +196,8 @@
"global_settings_setting_example_string": "Example string option", "global_settings_setting_example_string": "Example string option",
"global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discarding it and save it in /etc/yunohost/unkown_settings.json", "global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discarding it and save it in /etc/yunohost/unkown_settings.json",
"global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it's not a type supported by the system.", "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it's not a type supported by the system.",
"good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).",
"good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).",
"hook_exec_failed": "Script execution failed: {path:s}", "hook_exec_failed": "Script execution failed: {path:s}",
"hook_exec_not_terminated": "Script execution hasn\u2019t terminated: {path:s}", "hook_exec_not_terminated": "Script execution hasn\u2019t terminated: {path:s}",
"hook_list_by_invalid": "Invalid property to list hook by", "hook_list_by_invalid": "Invalid property to list hook by",
@ -256,6 +257,7 @@
"mail_domain_unknown": "Unknown mail address domain '{domain:s}'", "mail_domain_unknown": "Unknown mail address domain '{domain:s}'",
"mail_forward_remove_failed": "Unable to remove mail forward '{mail:s}'", "mail_forward_remove_failed": "Unable to remove mail forward '{mail:s}'",
"mailbox_used_space_dovecot_down": "Dovecot mailbox service need to be up, if you want to get mailbox used space", "mailbox_used_space_dovecot_down": "Dovecot mailbox service need to be up, if you want to get mailbox used space",
"mail_unavailable": "This email address is reserved and shall be automatically allocated to the very first user",
"maindomain_change_failed": "Unable to change the main domain", "maindomain_change_failed": "Unable to change the main domain",
"maindomain_changed": "The main domain has been changed", "maindomain_changed": "The main domain has been changed",
"migrate_tsig_end": "Migration to hmac-sha512 finished", "migrate_tsig_end": "Migration to hmac-sha512 finished",
@ -332,6 +334,11 @@
"packages_no_upgrade": "There is no package to upgrade", "packages_no_upgrade": "There is no package to upgrade",
"packages_upgrade_critical_later": "Critical packages ({packages:s}) will be upgraded later", "packages_upgrade_critical_later": "Critical packages ({packages:s}) will be upgraded later",
"packages_upgrade_failed": "Unable to upgrade all of the packages", "packages_upgrade_failed": "Unable to upgrade all of the packages",
"password_listed": "This password is among the most used password in the world. Please choose something a bit more unique.",
"password_too_simple_1": "Password needs to be at least 8 characters long",
"password_too_simple_2": "Password needs to be at least 8 characters long and contains digit, upper and lower characters",
"password_too_simple_3": "Password needs to be at least 8 characters long and contains digit, upper, lower and special characters",
"password_too_simple_4": "Password needs to be at least 12 characters long and contains digit, upper, lower and special characters",
"path_removal_failed": "Unable to remove path {:s}", "path_removal_failed": "Unable to remove path {:s}",
"pattern_backup_archive_name": "Must be a valid filename with max 30 characters, and alphanumeric and -_. characters only", "pattern_backup_archive_name": "Must be a valid filename with max 30 characters, and alphanumeric and -_. characters only",
"pattern_domain": "Must be a valid domain name (e.g. my-domain.org)", "pattern_domain": "Must be a valid domain name (e.g. my-domain.org)",

View file

@ -346,7 +346,8 @@ def app_info(app, show_status=False, raw=False):
ret['settings'] = _get_app_settings(app) ret['settings'] = _get_app_settings(app)
# Determine upgradability # Determine upgradability
local_update_time = ret['settings'].get('update_time', ret['settings']['install_time']) # In case there is neither update_time nor install_time, we assume the app can/has to be upgraded
local_update_time = ret['settings'].get('update_time', ret['settings'].get('install_time', 0))
if 'lastUpdate' not in ret or 'git' not in ret: if 'lastUpdate' not in ret or 'git' not in ret:
upgradable = "url_required" upgradable = "url_required"
@ -513,7 +514,7 @@ def app_change_url(operation_logger, auth, app, domain, path):
os.system('chmod +x %s' % os.path.join(os.path.join(APP_TMP_FOLDER, "scripts", "change_url"))) os.system('chmod +x %s' % os.path.join(os.path.join(APP_TMP_FOLDER, "scripts", "change_url")))
if hook_exec(os.path.join(APP_TMP_FOLDER, 'scripts/change_url'), if hook_exec(os.path.join(APP_TMP_FOLDER, 'scripts/change_url'),
args=args_list, env=env_dict, user="root") != 0: args=args_list, env=env_dict) != 0:
msg = "Failed to change '%s' url." % app msg = "Failed to change '%s' url." % app
logger.error(msg) logger.error(msg)
operation_logger.error(msg) operation_logger.error(msg)
@ -640,7 +641,7 @@ def app_upgrade(auth, app=[], url=None, file=None):
# Execute App upgrade script # Execute App upgrade script
os.system('chown -hR admin: %s' % INSTALL_TMP) os.system('chown -hR admin: %s' % INSTALL_TMP)
if hook_exec(extracted_app_folder + '/scripts/upgrade', if hook_exec(extracted_app_folder + '/scripts/upgrade',
args=args_list, env=env_dict, user="root") != 0: args=args_list, env=env_dict) != 0:
msg = m18n.n('app_upgrade_failed', app=app_instance_name) msg = m18n.n('app_upgrade_failed', app=app_instance_name)
logger.error(msg) logger.error(msg)
operation_logger.error(msg) operation_logger.error(msg)
@ -800,7 +801,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on
try: try:
install_retcode = hook_exec( install_retcode = hook_exec(
os.path.join(extracted_app_folder, 'scripts/install'), os.path.join(extracted_app_folder, 'scripts/install'),
args=args_list, env=env_dict, user="root" args=args_list, env=env_dict
) )
except (KeyboardInterrupt, EOFError): except (KeyboardInterrupt, EOFError):
install_retcode = -1 install_retcode = -1
@ -824,7 +825,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on
remove_retcode = hook_exec( remove_retcode = hook_exec(
os.path.join(extracted_app_folder, 'scripts/remove'), os.path.join(extracted_app_folder, 'scripts/remove'),
args=[app_instance_name], env=env_dict_remove, user="root" args=[app_instance_name], env=env_dict_remove
) )
if remove_retcode != 0: if remove_retcode != 0:
msg = m18n.n('app_not_properly_removed', msg = m18n.n('app_not_properly_removed',
@ -912,7 +913,7 @@ def app_remove(operation_logger, auth, app):
operation_logger.flush() operation_logger.flush()
if hook_exec('/tmp/yunohost_remove/scripts/remove', args=args_list, if hook_exec('/tmp/yunohost_remove/scripts/remove', args=args_list,
env=env_dict, user="root") == 0: env=env_dict) == 0:
logger.success(m18n.n('app_removed', app=app)) logger.success(m18n.n('app_removed', app=app))
hook_callback('post_app_remove', args=args_list, env=env_dict) hook_callback('post_app_remove', args=args_list, env=env_dict)
@ -1461,33 +1462,33 @@ def app_change_label(auth, app, new_label):
# actions todo list: # actions todo list:
# * docstring # * docstring
def app_action_list(app_id): def app_action_list(app):
logger.warning(m18n.n('experimental_feature')) logger.warning(m18n.n('experimental_feature'))
# this will take care of checking if the app is installed # this will take care of checking if the app is installed
app_info_dict = app_info(app_id) app_info_dict = app_info(app)
actions = os.path.join(APPS_SETTING_PATH, app_id, 'actions.json') actions = os.path.join(APPS_SETTING_PATH, app, 'actions.json')
return { return {
"app_id": app_id, "app": app,
"app_name": app_info_dict["name"], "app_name": app_info_dict["name"],
"actions": read_json(actions) if os.path.exists(actions) else [], "actions": read_json(actions) if os.path.exists(actions) else [],
} }
def app_action_run(app_id, action, args=None): def app_action_run(app, action, args=None):
logger.warning(m18n.n('experimental_feature')) logger.warning(m18n.n('experimental_feature'))
from yunohost.hook import hook_exec from yunohost.hook import hook_exec
import tempfile import tempfile
# will raise if action doesn't exist # will raise if action doesn't exist
actions = app_action_list(app_id)["actions"] actions = app_action_list(app)["actions"]
actions = {x["id"]: x for x in actions} actions = {x["id"]: x for x in actions}
if action not in actions: if action not in actions:
raise MoulinetteError(errno.EINVAL, "action '%s' not available for app '%s', available actions are: %s" % (action, app_id, ", ".join(actions.keys()))) raise MoulinetteError(errno.EINVAL, "action '%s' not available for app '%s', available actions are: %s" % (action, app, ", ".join(actions.keys())))
action_declaration = actions[action] action_declaration = actions[action]
@ -1496,8 +1497,12 @@ def app_action_run(app_id, action, args=None):
args_odict = _parse_args_for_action(actions[action], args=args_dict) args_odict = _parse_args_for_action(actions[action], args=args_dict)
args_list = args_odict.values() args_list = args_odict.values()
app_id, app_instance_nb = _parse_app_instance_name(app)
env_dict = _make_environment_dict(args_odict, prefix="ACTION_") env_dict = _make_environment_dict(args_odict, prefix="ACTION_")
env_dict["YNH_APP_ID"] = app_id env_dict["YNH_APP_ID"] = app_id
env_dict["YNH_APP_INSTANCE_NAME"] = app
env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb)
env_dict["YNH_ACTION"] = action env_dict["YNH_ACTION"] = action
_, path = tempfile.mkstemp() _, path = tempfile.mkstemp()
@ -1508,9 +1513,9 @@ def app_action_run(app_id, action, args=None):
os.chmod(path, 700) os.chmod(path, 700)
if action_declaration.get("cwd"): if action_declaration.get("cwd"):
cwd = action_declaration["cwd"].replace("$app_id", app_id) cwd = action_declaration["cwd"].replace("$app", app_id)
else: else:
cwd = "/etc/yunohost/apps/" + app_id cwd = "/etc/yunohost/apps/" + app
retcode = hook_exec( retcode = hook_exec(
path, path,
@ -1521,7 +1526,7 @@ def app_action_run(app_id, action, args=None):
) )
if retcode not in action_declaration.get("accepted_return_codes", [0]): if retcode not in action_declaration.get("accepted_return_codes", [0]):
raise MoulinetteError(retcode, "Error while executing action '%s' of app '%s': return code %s" % (action, app_id, retcode)) raise MoulinetteError(retcode, "Error while executing action '%s' of app '%s': return code %s" % (action, app, retcode))
os.remove(path) os.remove(path)
@ -1531,25 +1536,34 @@ def app_action_run(app_id, action, args=None):
# Config panel todo list: # Config panel todo list:
# * docstrings # * docstrings
# * merge translations on the json once the workflow is in place # * merge translations on the json once the workflow is in place
def app_config_show_panel(app_id): def app_config_show_panel(app):
logger.warning(m18n.n('experimental_feature')) logger.warning(m18n.n('experimental_feature'))
from yunohost.hook import hook_exec from yunohost.hook import hook_exec
# this will take care of checking if the app is installed # this will take care of checking if the app is installed
app_info_dict = app_info(app_id) app_info_dict = app_info(app)
config_panel = os.path.join(APPS_SETTING_PATH, app_id, 'config_panel.json') config_panel = os.path.join(APPS_SETTING_PATH, app, 'config_panel.json')
config_script = os.path.join(APPS_SETTING_PATH, app_id, 'scripts', 'config') config_script = os.path.join(APPS_SETTING_PATH, app, 'scripts', 'config')
app_id, app_instance_nb = _parse_app_instance_name(app)
if not os.path.exists(config_panel) or not os.path.exists(config_script): if not os.path.exists(config_panel) or not os.path.exists(config_script):
return { return {
"app_id": app_id,
"app": app,
"app_name": app_info_dict["name"],
"config_panel": [], "config_panel": [],
} }
config_panel = read_json(config_panel) config_panel = read_json(config_panel)
env = {"YNH_APP_ID": app_id} env = {
"YNH_APP_ID": app_id,
"YNH_APP_INSTANCE_NAME": app,
"YNH_APP_INSTANCE_NUMBER": str(app_instance_nb),
}
parsed_values = {} parsed_values = {}
# I need to parse stdout to communicate between scripts because I can't # I need to parse stdout to communicate between scripts because I can't
@ -1573,7 +1587,6 @@ def app_config_show_panel(app_id):
return_code = hook_exec(config_script, return_code = hook_exec(config_script,
args=["show"], args=["show"],
env=env, env=env,
user="root",
stdout_callback=parse_stdout, stdout_callback=parse_stdout,
) )
@ -1607,23 +1620,24 @@ def app_config_show_panel(app_id):
return { return {
"app_id": app_id, "app_id": app_id,
"app": app,
"app_name": app_info_dict["name"], "app_name": app_info_dict["name"],
"config_panel": config_panel, "config_panel": config_panel,
} }
def app_config_apply(app_id, args): def app_config_apply(app, args):
logger.warning(m18n.n('experimental_feature')) logger.warning(m18n.n('experimental_feature'))
from yunohost.hook import hook_exec from yunohost.hook import hook_exec
installed = _is_installed(app_id) installed = _is_installed(app)
if not installed: if not installed:
raise MoulinetteError(errno.ENOPKG, raise MoulinetteError(errno.ENOPKG,
m18n.n('app_not_installed', app=app_id)) m18n.n('app_not_installed', app=app))
config_panel = os.path.join(APPS_SETTING_PATH, app_id, 'config_panel.json') config_panel = os.path.join(APPS_SETTING_PATH, app, 'config_panel.json')
config_script = os.path.join(APPS_SETTING_PATH, app_id, 'scripts', 'config') config_script = os.path.join(APPS_SETTING_PATH, app, 'scripts', 'config')
if not os.path.exists(config_panel) or not os.path.exists(config_script): if not os.path.exists(config_panel) or not os.path.exists(config_script):
# XXX real exception # XXX real exception
@ -1631,7 +1645,12 @@ def app_config_apply(app_id, args):
config_panel = read_json(config_panel) config_panel = read_json(config_panel)
env = {"YNH_APP_ID": app_id} app_id, app_instance_nb = _parse_app_instance_name(app)
env = {
"YNH_APP_ID": app_id,
"YNH_APP_INSTANCE_NAME": app,
"YNH_APP_INSTANCE_NUMBER": str(app_instance_nb),
}
args = dict(urlparse.parse_qsl(args, keep_blank_values=True)) if args else {} args = dict(urlparse.parse_qsl(args, keep_blank_values=True)) if args else {}
for tab in config_panel.get("panel", []): for tab in config_panel.get("panel", []):
@ -1656,7 +1675,6 @@ def app_config_apply(app_id, args):
return_code = hook_exec(config_script, return_code = hook_exec(config_script,
args=["apply"], args=["apply"],
env=env, env=env,
user="root",
) )
if return_code != 0: if return_code != 0:
@ -2189,11 +2207,15 @@ def _parse_action_args_in_yunohost_format(args, action_args, auth=None):
for domain in domain_list(auth)['domains']: for domain in domain_list(auth)['domains']:
msignals.display("- {}".format(domain)) msignals.display("- {}".format(domain))
if arg_type == 'user': elif arg_type == 'user':
msignals.display(m18n.n('users_available')) msignals.display(m18n.n('users_available'))
for user in user_list(auth)['users'].keys(): for user in user_list(auth)['users'].keys():
msignals.display("- {}".format(user)) msignals.display("- {}".format(user))
elif arg_type == 'password':
msignals.display(m18n.n('good_practices_about_user_password'))
try: try:
input_string = msignals.prompt(ask_string, is_password) input_string = msignals.prompt(ask_string, is_password)
except NotImplementedError: except NotImplementedError:
@ -2251,6 +2273,9 @@ def _parse_action_args_in_yunohost_format(args, action_args, auth=None):
raise MoulinetteError(errno.EINVAL, raise MoulinetteError(errno.EINVAL,
m18n.n('app_argument_choice_invalid', m18n.n('app_argument_choice_invalid',
name=arg_name, choices='yes, no, y, n, 1, 0')) name=arg_name, choices='yes, no, y, n, 1, 0'))
elif arg_type == 'password':
from yunohost.utils.password import assert_password_is_strong_enough
assert_password_is_strong_enough('user', arg_value)
args_dict[arg_name] = arg_value args_dict[arg_name] = arg_value
# END loop over action_args... # END loop over action_args...

View file

@ -680,7 +680,7 @@ class BackupManager():
subprocess.call(['install', '-Dm555', app_script, tmp_script]) subprocess.call(['install', '-Dm555', app_script, tmp_script])
hook_exec(tmp_script, args=[tmp_app_bkp_dir, app], hook_exec(tmp_script, args=[tmp_app_bkp_dir, app],
raise_on_error=True, chdir=tmp_app_bkp_dir, env=env_dict, user="root") raise_on_error=True, chdir=tmp_app_bkp_dir, env=env_dict)
self._import_to_list_to_backup(env_dict["YNH_BACKUP_CSV"]) self._import_to_list_to_backup(env_dict["YNH_BACKUP_CSV"])
except: except:
@ -1310,8 +1310,7 @@ class RestoreManager():
args=[app_backup_in_archive, app_instance_name], args=[app_backup_in_archive, app_instance_name],
chdir=app_backup_in_archive, chdir=app_backup_in_archive,
raise_on_error=True, raise_on_error=True,
env=env_dict, env=env_dict)
user="root")
except: except:
msg = m18n.n('restore_app_failed',app=app_instance_name) msg = m18n.n('restore_app_failed',app=app_instance_name)
logger.exception(msg) logger.exception(msg)
@ -1336,7 +1335,7 @@ class RestoreManager():
# Execute remove script # Execute remove script
# TODO: call app_remove instead # TODO: call app_remove instead
if hook_exec(remove_script, args=[app_instance_name], if hook_exec(remove_script, args=[app_instance_name],
env=env_dict_remove, user="root") != 0: env=env_dict_remove) != 0:
msg = m18n.n('app_not_properly_removed', app=app_instance_name) msg = m18n.n('app_not_properly_removed', app=app_instance_name)
logger.warning(msg) logger.warning(msg)
operation_logger.error(msg) operation_logger.error(msg)
@ -2188,7 +2187,7 @@ def backup_list(with_info=False, human_readable=False):
except ValueError: except ValueError:
continue continue
result.append(name) result.append(name)
result.sort() result.sort(key=lambda x: os.path.getctime(os.path.join(ARCHIVES_PATH, x+".tar.gz")))
if result and with_info: if result and with_info:
d = OrderedDict() d = OrderedDict()

View file

@ -97,11 +97,6 @@ def certificate_status(auth, domain_list, full=False):
import yunohost.domain import yunohost.domain
# Check if old letsencrypt_ynh is installed
# TODO / FIXME - Remove this in the future once the letsencrypt app is
# not used anymore
_check_old_letsencrypt_app()
# If no domains given, consider all yunohost domains # If no domains given, consider all yunohost domains
if domain_list == []: if domain_list == []:
domain_list = yunohost.domain.domain_list(auth)['domains'] domain_list = yunohost.domain.domain_list(auth)['domains']
@ -144,11 +139,6 @@ def certificate_install(auth, domain_list, force=False, no_checks=False, self_si
self-signed -- Instal self-signed certificates instead of Let's Encrypt self-signed -- Instal self-signed certificates instead of Let's Encrypt
""" """
# Check if old letsencrypt_ynh is installed
# TODO / FIXME - Remove this in the future once the letsencrypt app is
# not used anymore
_check_old_letsencrypt_app()
if self_signed: if self_signed:
_certificate_install_selfsigned(domain_list, force) _certificate_install_selfsigned(domain_list, force)
else: else:
@ -328,11 +318,6 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal
import yunohost.domain import yunohost.domain
# Check if old letsencrypt_ynh is installed
# TODO / FIXME - Remove this in the future once the letsencrypt app is
# not used anymore
_check_old_letsencrypt_app()
# If no domains given, consider all yunohost domains with Let's Encrypt # If no domains given, consider all yunohost domains with Let's Encrypt
# certificates # certificates
if domain_list == []: if domain_list == []:
@ -355,7 +340,7 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal
domain_list.append(domain) domain_list.append(domain)
if len(domain_list) == 0: if len(domain_list) == 0 and not email:
logger.info("No certificate needs to be renewed.") logger.info("No certificate needs to be renewed.")
# Else, validate the domain list given # Else, validate the domain list given
@ -430,18 +415,6 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal
# Back-end stuff # # Back-end stuff #
############################################################################### ###############################################################################
def _check_old_letsencrypt_app():
import yunohost.domain
installedAppIds = [app["id"] for app in yunohost.app.app_list(installed=True)["apps"]]
if "letsencrypt" not in installedAppIds:
return
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_old_letsencrypt_app_detected'))
def _install_cron(): def _install_cron():
cron_job_file = "/etc/cron.daily/yunohost-certificate-renew" cron_job_file = "/etc/cron.daily/yunohost-certificate-renew"

View file

@ -94,7 +94,7 @@ class MyMigration(Migration):
# with /etc/lsb-release for instance -_-) # with /etc/lsb-release for instance -_-)
# Instead, we rely on /etc/os-release which should be the raw info from # Instead, we rely on /etc/os-release which should be the raw info from
# the distribution... # the distribution...
return int(check_output("grep VERSION_ID /etc/os-release | tr '\"' ' ' | cut -d ' ' -f2")) return int(check_output("grep VERSION_ID /etc/os-release | head -n 1 | tr '\"' ' ' | cut -d ' ' -f2"))
def yunohost_major_version(self): def yunohost_major_version(self):
return int(get_installed_version("yunohost").split('.')[0]) return int(get_installed_version("yunohost").split('.')[0])

View file

@ -209,6 +209,11 @@ def domain_dns_conf(domain, ttl=None):
for record in dns_conf["mail"]: for record in dns_conf["mail"]:
result += "\n{name} {ttl} IN {type} {value}".format(**record) result += "\n{name} {ttl} IN {type} {value}".format(**record)
result += "\n\n"
result += "; Extra"
for record in dns_conf["extra"]:
result += "\n{name} {ttl} IN {type} {value}".format(**record)
is_cli = True if msettings.get('interface') == 'cli' else False is_cli = True if msettings.get('interface') == 'cli' else False
if is_cli: if is_cli:
logger.info(m18n.n("domain_dns_conf_is_just_a_recommendation")) logger.info(m18n.n("domain_dns_conf_is_just_a_recommendation"))
@ -334,6 +339,9 @@ def _build_dns_conf(domain, ttl=3600):
{"type": "TXT", "name": "mail._domainkey", "value": "\"v=DKIM1; k=rsa; p=some-super-long-key\"", "ttl": 3600}, {"type": "TXT", "name": "mail._domainkey", "value": "\"v=DKIM1; k=rsa; p=some-super-long-key\"", "ttl": 3600},
{"type": "TXT", "name": "_dmarc", "value": "\"v=DMARC1; p=none\"", "ttl": 3600} {"type": "TXT", "name": "_dmarc", "value": "\"v=DMARC1; p=none\"", "ttl": 3600}
], ],
"extra": [
{"type": "CAA", "name": "@", "value": "128 issue 'letsencrypt.org", "ttl": 3600},
],
} }
""" """
@ -387,10 +395,16 @@ def _build_dns_conf(domain, ttl=3600):
["_dmarc", ttl, "TXT", '"v=DMARC1; p=none"'], ["_dmarc", ttl, "TXT", '"v=DMARC1; p=none"'],
] ]
# Extra
extra = [
["@", ttl, "CAA", "128 issue 'letsencrypt.org'"]
]
return { return {
"basic": [{"name": name, "ttl": ttl, "type": type_, "value": value} for name, ttl, type_, value in basic], "basic": [{"name": name, "ttl": ttl, "type": type_, "value": value} for name, ttl, type_, value in basic],
"xmpp": [{"name": name, "ttl": ttl, "type": type_, "value": value} for name, ttl, type_, value in xmpp], "xmpp": [{"name": name, "ttl": ttl, "type": type_, "value": value} for name, ttl, type_, value in xmpp],
"mail": [{"name": name, "ttl": ttl, "type": type_, "value": value} for name, ttl, type_, value in mail], "mail": [{"name": name, "ttl": ttl, "type": type_, "value": value} for name, ttl, type_, value in mail],
"extra": [{"name": name, "ttl": ttl, "type": type_, "value": value} for name, ttl, type_, value in extra],
} }

View file

@ -37,6 +37,8 @@ from moulinette.core import MoulinetteError
from moulinette.utils.log import getActionLogger from moulinette.utils.log import getActionLogger
from moulinette.utils.filesystem import read_file, write_to_file, rm from moulinette.utils.filesystem import read_file, write_to_file, rm
from moulinette.utils.network import download_json from moulinette.utils.network import download_json
from moulinette.utils.process import check_output
from yunohost.domain import _get_maindomain, _build_dns_conf from yunohost.domain import _get_maindomain, _build_dns_conf
from yunohost.utils.network import get_public_ip from yunohost.utils.network import get_public_ip
@ -44,8 +46,6 @@ from yunohost.log import is_unit_operation
logger = getActionLogger('yunohost.dyndns') logger = getActionLogger('yunohost.dyndns')
OLD_IPV4_FILE = '/etc/yunohost/dyndns/old_ip'
OLD_IPV6_FILE = '/etc/yunohost/dyndns/old_ipv6'
DYNDNS_ZONE = '/etc/yunohost/dyndns/zone' DYNDNS_ZONE = '/etc/yunohost/dyndns/zone'
RE_DYNDNS_PRIVATE_KEY_MD5 = re.compile( RE_DYNDNS_PRIVATE_KEY_MD5 = re.compile(
@ -96,7 +96,7 @@ def _dyndns_available(provider, domain):
domain -- The full domain that you'd like.. e.g. "foo.nohost.me" domain -- The full domain that you'd like.. e.g. "foo.nohost.me"
Returns: Returns:
True if the domain is avaible, False otherwise. True if the domain is available, False otherwise.
""" """
logger.debug("Checking if domain %s is available on %s ..." logger.debug("Checking if domain %s is available on %s ..."
% (domain, provider)) % (domain, provider))
@ -193,32 +193,6 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None,
old_ipv4, old_ipv6 = (None, None) # (default values) old_ipv4, old_ipv6 = (None, None) # (default values)
if os.path.isfile(OLD_IPV4_FILE):
old_ipv4 = read_file(OLD_IPV4_FILE).rstrip()
if os.path.isfile(OLD_IPV6_FILE):
old_ipv6 = read_file(OLD_IPV6_FILE).rstrip()
# Get current IPv4 and IPv6
ipv4_ = get_public_ip()
ipv6_ = get_public_ip(6)
if ipv4 is None:
ipv4 = ipv4_
if ipv6 is None:
ipv6 = ipv6_
logger.debug("Old IPv4/v6 are (%s, %s)" % (old_ipv4, old_ipv6))
logger.debug("Requested IPv4/v6 are (%s, %s)" % (ipv4, ipv6))
# no need to update
if old_ipv4 == ipv4 and old_ipv6 == ipv6:
logger.info("No updated needed.")
return
else:
logger.info("Updated needed, going on...")
# If domain is not given, try to guess it from keys available... # If domain is not given, try to guess it from keys available...
if domain is None: if domain is None:
(domain, key) = _guess_current_dyndns_domain(dyn_host) (domain, key) = _guess_current_dyndns_domain(dyn_host)
@ -262,7 +236,32 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None,
'zone %s' % host, 'zone %s' % host,
] ]
old_ipv4 = check_output("dig @%s +short %s" % (dyn_host, domain)).strip() or None
old_ipv6 = check_output("dig @%s +short aaaa %s" % (dyn_host, domain)).strip() or None
# Get current IPv4 and IPv6
ipv4_ = get_public_ip()
ipv6_ = get_public_ip(6)
if ipv4 is None:
ipv4 = ipv4_
if ipv6 is None:
ipv6 = ipv6_
logger.debug("Old IPv4/v6 are (%s, %s)" % (old_ipv4, old_ipv6))
logger.debug("Requested IPv4/v6 are (%s, %s)" % (ipv4, ipv6))
# no need to update
if old_ipv4 == ipv4 and old_ipv6 == ipv6:
logger.info("No updated needed.")
return
else:
logger.info("Updated needed, going on...")
dns_conf = _build_dns_conf(domain) dns_conf = _build_dns_conf(domain)
del dns_conf["extra"] # Ignore records from the 'extra' category
# Delete the old records for all domain/subdomains # Delete the old records for all domain/subdomains
@ -304,18 +303,11 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None,
command = ["/usr/bin/nsupdate", "-k", key, DYNDNS_ZONE] command = ["/usr/bin/nsupdate", "-k", key, DYNDNS_ZONE]
subprocess.check_call(command) subprocess.check_call(command)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
rm(OLD_IPV4_FILE, force=True) # Remove file (ignore if non-existent)
rm(OLD_IPV6_FILE, force=True) # Remove file (ignore if non-existent)
raise MoulinetteError(errno.EPERM, raise MoulinetteError(errno.EPERM,
m18n.n('dyndns_ip_update_failed')) m18n.n('dyndns_ip_update_failed'))
logger.success(m18n.n('dyndns_ip_updated')) logger.success(m18n.n('dyndns_ip_updated'))
if ipv4 is not None:
write_to_file(OLD_IPV4_FILE, ipv4)
if ipv6 is not None:
write_to_file(OLD_IPV6_FILE, ipv6)
def dyndns_installcron(): def dyndns_installcron():
""" """

View file

@ -281,7 +281,7 @@ def hook_callback(action, hooks=[], args=None, no_trace=False, chdir=None,
hook_args = pre_callback(name=name, priority=priority, hook_args = pre_callback(name=name, priority=priority,
path=path, args=args) path=path, args=args)
hook_exec(path, args=hook_args, chdir=chdir, env=env, hook_exec(path, args=hook_args, chdir=chdir, env=env,
no_trace=no_trace, raise_on_error=True, user="root") no_trace=no_trace, raise_on_error=True)
except MoulinetteError as e: except MoulinetteError as e:
state = 'failed' state = 'failed'
logger.error(e.strerror, exc_info=1) logger.error(e.strerror, exc_info=1)
@ -298,7 +298,7 @@ def hook_callback(action, hooks=[], args=None, no_trace=False, chdir=None,
def hook_exec(path, args=None, raise_on_error=False, no_trace=False, def hook_exec(path, args=None, raise_on_error=False, no_trace=False,
chdir=None, env=None, user="admin", stdout_callback=None, chdir=None, env=None, user="root", stdout_callback=None,
stderr_callback=None): stderr_callback=None):
""" """
Execute hook from a file with arguments Execute hook from a file with arguments

View file

@ -50,7 +50,7 @@ MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock"
logger = log.getActionLogger('yunohost.service') logger = log.getActionLogger('yunohost.service')
def service_add(name, status=None, log=None, runlevel=None): def service_add(name, status=None, log=None, runlevel=None, need_lock=False, description=None):
""" """
Add a custom service Add a custom service
@ -59,7 +59,8 @@ def service_add(name, status=None, log=None, runlevel=None):
status -- Custom status command status -- Custom status command
log -- Absolute path to log file to display log -- Absolute path to log file to display
runlevel -- Runlevel priority of the service runlevel -- Runlevel priority of the service
need_lock -- Use this option to prevent deadlocks if the service does invoke yunohost commands.
description -- description of the service
""" """
services = _get_services() services = _get_services()
@ -74,6 +75,12 @@ def service_add(name, status=None, log=None, runlevel=None):
if runlevel is not None: if runlevel is not None:
services[name]['runlevel'] = runlevel services[name]['runlevel'] = runlevel
if need_lock:
services[name]['need_lock'] = True
if description is not None:
services[name]['description'] = description
try: try:
_save_services(services) _save_services(services)
except: except:
@ -251,6 +258,9 @@ def service_status(names=[]):
else: else:
translation_key = "service_description_%s" % name translation_key = "service_description_%s" % name
if "description" in services[name] is not None:
description = services[name].get("description")
else:
description = m18n.n(translation_key) description = m18n.n(translation_key)
# that mean that we don't have a translation for this string # that mean that we don't have a translation for this string

View file

@ -29,12 +29,16 @@ SETTINGS_PATH_OTHER_LOCATION = "/etc/yunohost/settings-%s.json"
# * string # * string
# * enum (in form a python list) # * enum (in form a python list)
# we don't store the value in default options
DEFAULTS = OrderedDict([ DEFAULTS = OrderedDict([
("example.bool", {"type": "bool", "default": True}), ("example.bool", {"type": "bool", "default": True}),
("example.int", {"type": "int", "default": 42}), ("example.int", {"type": "int", "default": 42}),
("example.string", {"type": "string", "default": "yolo swag"}), ("example.string", {"type": "string", "default": "yolo swag"}),
("example.enum", {"type": "enum", "default": "a", "choices": ["a", "b", "c"]}), ("example.enum", {"type": "enum", "default": "a", "choices": ["a", "b", "c"]}),
# Password Validation
# -1 disabled, 0 alert if listed, 1 8-letter, 2 normal, 3 strong, 4 strongest
("security.password.admin.strength", {"type": "int", "default": 1}),
("security.password.user.strength", {"type": "int", "default": 1}),
]) ])
@ -90,6 +94,9 @@ def settings_set(key, value):
received_type=type(value).__name__, expected_type=key_type)) received_type=type(value).__name__, expected_type=key_type))
elif key_type == "int": elif key_type == "int":
if not isinstance(value, int) or isinstance(value, bool): if not isinstance(value, int) or isinstance(value, bool):
if isinstance(value, str):
value=int(value)
else:
raise MoulinetteError(errno.EINVAL, m18n.n( raise MoulinetteError(errno.EINVAL, m18n.n(
'global_settings_bad_type_for_setting', setting=key, 'global_settings_bad_type_for_setting', setting=key,
received_type=type(value).__name__, expected_type=key_type)) received_type=type(value).__name__, expected_type=key_type))

View file

@ -32,6 +32,7 @@ import logging
import subprocess import subprocess
import pwd import pwd
import socket import socket
import cracklib
from xmlrpclib import Fault from xmlrpclib import Fault
from importlib import import_module from importlib import import_module
from collections import OrderedDict from collections import OrderedDict
@ -127,8 +128,13 @@ def tools_adminpw(auth, new_password):
""" """
from yunohost.user import _hash_user_password from yunohost.user import _hash_user_password
from yunohost.utils.password import assert_password_is_strong_enough
import spwd import spwd
assert_password_is_strong_enough("admin", new_password)
new_hash = _hash_user_password(new_password) new_hash = _hash_user_password(new_password)
try: try:
auth.update("cn=admin", { "userPassword": new_hash, }) auth.update("cn=admin", { "userPassword": new_hash, })
except: except:
@ -263,7 +269,8 @@ def _is_inside_container():
@is_unit_operation() @is_unit_operation()
def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False): def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False,
force_password=False):
""" """
YunoHost post-install YunoHost post-install
@ -274,6 +281,8 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False):
password -- YunoHost admin password password -- YunoHost admin password
""" """
from yunohost.utils.password import assert_password_is_strong_enough
dyndns_provider = "dyndns.yunohost.org" dyndns_provider = "dyndns.yunohost.org"
# Do some checks at first # Do some checks at first
@ -281,6 +290,10 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False):
raise MoulinetteError(errno.EPERM, raise MoulinetteError(errno.EPERM,
m18n.n('yunohost_already_installed')) m18n.n('yunohost_already_installed'))
# Check password
if not force_password:
assert_password_is_strong_enough("admin", password)
if not ignore_dyndns: if not ignore_dyndns:
# Check if yunohost dyndns can handle the given domain # Check if yunohost dyndns can handle the given domain
# (i.e. is it a .nohost.me ? a .noho.st ?) # (i.e. is it a .nohost.me ? a .noho.st ?)
@ -312,6 +325,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False):
else: else:
dyndns = False dyndns = False
operation_logger.start() operation_logger.start()
logger.info(m18n.n('yunohost_installing')) logger.info(m18n.n('yunohost_installing'))
@ -371,7 +385,6 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False):
service_regen_conf(['ssl'], force=True) service_regen_conf(['ssl'], force=True)
ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA' ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA'
commands = [ commands = [
'echo "01" > %s/serial' % ssl_dir,
'rm %s/index.txt' % ssl_dir, 'rm %s/index.txt' % ssl_dir,
'touch %s/index.txt' % ssl_dir, 'touch %s/index.txt' % ssl_dir,
'cp %s/openssl.cnf %s/openssl.ca.cnf' % (ssl_dir, ssl_dir), 'cp %s/openssl.cnf %s/openssl.ca.cnf' % (ssl_dir, ssl_dir),
@ -1059,3 +1072,4 @@ class Migration(object):
@property @property
def description(self): def description(self):
return m18n.n("migration_description_%s" % self.id) return m18n.n("migration_description_%s" % self.id)

View file

@ -32,6 +32,7 @@ import crypt
import random import random
import string import string
import subprocess import subprocess
import cracklib
from moulinette import m18n from moulinette import m18n
from moulinette.core import MoulinetteError from moulinette.core import MoulinetteError
@ -116,6 +117,10 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas
from yunohost.domain import domain_list, _get_maindomain from yunohost.domain import domain_list, _get_maindomain
from yunohost.hook import hook_callback from yunohost.hook import hook_callback
from yunohost.app import app_ssowatconf from yunohost.app import app_ssowatconf
from yunohost.utils.password import assert_password_is_strong_enough
# Ensure sufficiently complex password
assert_password_is_strong_enough("user", password)
# Validate uniqueness of username and mail in LDAP # Validate uniqueness of username and mail in LDAP
auth.validate_uniqueness({ auth.validate_uniqueness({
@ -128,6 +133,17 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas
if username in all_existing_usernames: if username in all_existing_usernames:
raise MoulinetteError(errno.EEXIST, m18n.n('system_username_exists')) raise MoulinetteError(errno.EEXIST, m18n.n('system_username_exists'))
main_domain = _get_maindomain()
aliases = [
'root@' + main_domain,
'admin@' + main_domain,
'webmaster@' + main_domain,
'postmaster@' + main_domain,
]
if mail in aliases:
raise MoulinetteError(errno.EEXIST,m18n.n('mail_unavailable'))
# Check that the mail domain exists # Check that the mail domain exists
if mail.split("@")[1] not in domain_list(auth)['domains']: if mail.split("@")[1] not in domain_list(auth)['domains']:
raise MoulinetteError(errno.EINVAL, raise MoulinetteError(errno.EINVAL,
@ -166,13 +182,6 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas
# If it is the first user, add some aliases # If it is the first user, add some aliases
if not auth.search(base='ou=users,dc=yunohost,dc=org', filter='uid=*'): if not auth.search(base='ou=users,dc=yunohost,dc=org', filter='uid=*'):
main_domain = _get_maindomain()
aliases = [
'root@' + main_domain,
'admin@' + main_domain,
'webmaster@' + main_domain,
'postmaster@' + main_domain,
]
attr_dict['mail'] = [attr_dict['mail']] + aliases attr_dict['mail'] = [attr_dict['mail']] + aliases
# If exists, remove the redirection from the SSO # If exists, remove the redirection from the SSO
@ -279,6 +288,7 @@ def user_update(operation_logger, auth, username, firstname=None, lastname=None,
""" """
from yunohost.domain import domain_list from yunohost.domain import domain_list
from yunohost.app import app_ssowatconf from yunohost.app import app_ssowatconf
from yunohost.utils.password import assert_password_is_strong_enough
attrs_to_fetch = ['givenName', 'sn', 'mail', 'maildrop'] attrs_to_fetch = ['givenName', 'sn', 'mail', 'maildrop']
new_attr_dict = {} new_attr_dict = {}
@ -303,14 +313,27 @@ def user_update(operation_logger, auth, username, firstname=None, lastname=None,
new_attr_dict['cn'] = new_attr_dict['displayName'] = firstname + ' ' + lastname new_attr_dict['cn'] = new_attr_dict['displayName'] = firstname + ' ' + lastname
if change_password: if change_password:
# Ensure sufficiently complex password
assert_password_is_strong_enough("user", password)
new_attr_dict['userPassword'] = _hash_user_password(change_password) new_attr_dict['userPassword'] = _hash_user_password(change_password)
if mail: if mail:
main_domain = _get_maindomain()
aliases = [
'root@' + main_domain,
'admin@' + main_domain,
'webmaster@' + main_domain,
'postmaster@' + main_domain,
]
auth.validate_uniqueness({'mail': mail}) auth.validate_uniqueness({'mail': mail})
if mail[mail.find('@') + 1:] not in domains: if mail[mail.find('@') + 1:] not in domains:
raise MoulinetteError(errno.EINVAL, raise MoulinetteError(errno.EINVAL,
m18n.n('mail_domain_unknown', m18n.n('mail_domain_unknown',
domain=mail[mail.find('@') + 1:])) domain=mail[mail.find('@') + 1:]))
if mail in aliases:
raise MoulinetteError(errno.EEXIST,m18n.n('mail_unavailable'))
del user['mail'][0] del user['mail'][0]
new_attr_dict['mail'] = [mail] + user['mail'] new_attr_dict['mail'] = [mail] + user['mail']

View file

@ -0,0 +1,196 @@
# -*- coding: utf-8 -*-
""" License
Copyright (C) 2018 YunoHost
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 sys
import os
import json
import string
import subprocess
SMALL_PWD_LIST = ["yunohost", "olinuxino", "olinux", "raspberry", "admin",
"root", "test", "rpi"]
MOST_USED_PASSWORDS = '/usr/share/yunohost/other/password/100000-most-used.txt'
# Length, digits, lowers, uppers, others
STRENGTH_LEVELS = [
(8, 0, 0, 0, 0),
(8, 1, 1, 1, 0),
(8, 1, 1, 1, 1),
(12, 1, 1, 1, 1),
]
def assert_password_is_strong_enough(profile, password):
PasswordValidator(profile).validate(password)
class PasswordValidator(object):
def __init__(self, profile):
"""
Initialize a password validator.
The profile shall be either "user" or "admin"
and will correspond to a validation strength
defined via the setting "security.password.<profile>.strength"
"""
self.profile = profile
try:
# We do this "manually" instead of using settings_get()
# from settings.py because this file is also meant to be
# use as a script by ssowat.
# (or at least that's my understanding -- Alex)
settings = json.load(open('/etc/yunohost/settings.json', "r"))
setting_key = "security.password." + profile + ".strength"
self.validation_strength = int(settings[setting_key])
except Exception as e:
# Fallback to default value if we can't fetch settings for some reason
self.validation_strength = 1
def validate(self, password):
"""
Check the validation_summary and trigger an exception
if the password does not pass tests.
This method is meant to be used from inside YunoHost's code
(compared to validation_summary which is meant to be called
by ssowat)
"""
if self.validation_strength == -1:
return
# Note that those imports are made here and can't be put
# on top (at least not the moulinette ones)
# because the moulinette needs to be correctly initialized
# as well as modules available in python's path.
import errno
import logging
from moulinette import m18n
from moulinette.core import MoulinetteError
from moulinette.utils.log import getActionLogger
logger = logging.getLogger('yunohost.utils.password')
status, msg = self.validation_summary(password)
if status == "error":
raise MoulinetteError(1, m18n.n(msg))
def validation_summary(self, password):
"""
Check if a password is listed in the list of most used password
and if the overall strength is good enough compared to the
validation_strength defined in the constructor.
Produces a summary-tuple comprised of a level (succes or error)
and a message key describing the issues found.
"""
if self.validation_strength < 0:
return ("success", "")
listed = password in SMALL_PWD_LIST or self.is_in_most_used_list(password)
strength_level = self.strength_level(password)
if listed:
return ("error", "password_listed")
if strength_level < self.validation_strength:
return ("error", "password_too_simple_%s" % self.validation_strength)
return ("success", "")
def strength(self, password):
"""
Returns the strength of a password, defined as a tuple
containing the length of the password, the number of digits,
lowercase letters, uppercase letters, and other characters.
For instance, "PikachuDu67" is (11, 2, 7, 2, 0)
"""
length = len(password)
digits = 0
uppers = 0
lowers = 0
others = 0
for character in password:
if character in string.digits:
digits = digits + 1
elif character in string.ascii_uppercase:
uppers = uppers + 1
elif character in string.ascii_lowercase:
lowers = lowers + 1
else:
others = others + 1
return (length, digits, lowers, uppers, others)
def strength_level(self, password):
"""
Computes the strength of a password and compares
it to the STRENGTH_LEVELS.
Returns an int corresponding to the highest STRENGTH_LEVEL
satisfied by the password.
"""
strength = self.strength(password)
strength_level = 0
# Iterate over each level and its criterias
for level, level_criterias in enumerate(STRENGTH_LEVELS):
# Iterate simulatenously over the level criterias (e.g. [8, 1, 1, 1, 0])
# and the strength of the password (e.g. [11, 2, 7, 2, 0])
# and compare the values 1-by-1.
# If one False is found, the password does not satisfy the level
if False in [s>=c for s, c in zip(strength, level_criterias)]:
break
# Otherwise, the strength of the password is at least of the current level.
strength_level = level + 1
return strength_level
def is_in_most_used_list(self, password):
# Decompress file if compressed
if os.path.exists("%s.gz" % MOST_USED_PASSWORDS):
os.system("gzip -fd %s.gz" % MOST_USED_PASSWORDS)
# Grep the password in the file
# We use '-f -' to feed the pattern (= the password) through
# stdin to avoid it being shown in ps -ef --forest...
command = "grep -q -f - %s" % MOST_USED_PASSWORDS
p = subprocess.Popen(command.split(), stdin=subprocess.PIPE)
p.communicate(input=password)
return not bool(p.returncode)
# This file is also meant to be used as an executable by
# SSOwat to validate password from the portal when an user
# change its password.
if __name__ == '__main__':
if len(sys.argv) < 2:
import getpass
pwd = getpass.getpass("")
#print("usage: password.py PASSWORD")
else:
pwd = sys.argv[1]
status, msg = PasswordValidator('user').validation_summary(pwd)
print(msg)
sys.exit(0)