commit 130cd2711b06cc8ba4b98b9c5aa78bf873412a5d Author: Adrien Bourmault Date: Wed May 4 14:44:59 2022 +0200 Initial commit diff --git a/captcha.sh b/captcha.sh new file mode 100755 index 0000000..8800e74 --- /dev/null +++ b/captcha.sh @@ -0,0 +1,181 @@ +#!/bin/bash + +# Copyright © 2021 Adrien Bourmault (neox@a-lec.org) +# Copyright © 2021 Echolib (echolib@dismail.de) + +# 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 script is an example captcha script. +# It takes the text to recognize in the captcha image as a parameter. +# It return the image binary as a result. ejabberd support PNG, JPEG and GIF. + +# The whole idea of the captcha script is to let server admins adapt it to +# their own needs. The goal is to be able to make the captcha generation as +# unique as possible, to make the captcha challenge difficult to bypass by +# a bot. +# Server admins are thus supposed to write and use their own captcha generators. + +# This script relies on ImageMagick. +# It is NOT compliant with ImageMagick forks like GraphicsMagick. + +INPUT=$1 +LENINPUT=${#INPUT} +DIGITS=(zéros uns deux trois quatres cinqs six septs huits neufs dix) +DIGIT=(zéro un deux trois quatre cinq six sept huit neuf dix) + + +# **************************************************************************** +# +# Tools +# +# **************************************************************************** + + +# Loops 10 times (should be enough) to check if a TARGET number is in INPUT +GENERATE__NOTIN() { +for i in {1..10} +do + FALSENBR=`seq 1 9 | sort -R | head -n 1` + ! [[ $INPUT =~ $FALSENBR ]] \ + && break +done +} + + +# **************************************************************************** +# +# Captcha shapes +# +# **************************************************************************** + + +# REPLACEs a random number in INPUT by another one randomized +REPLACE() { +NAME="REPLACE" +PLACE=`seq 1 6 | sort -R | head -n 1` +TARGET=`echo "$INPUT" | head -c $PLACE | tail -c 1` + +GENERATE__NOTIN +NAME+="-$FALSENBR-$TARGET" +NEWINPUT=${INPUT//$TARGET/$FALSENBR} +INSTRUCTIONS1="Recopiez ce nombre" +INSTRUCTIONS2="en changeant les ${DIGITS[FALSENBR]} en ${DIGITS[TARGET]}" +} + + +# REVERSEs INPUT. Ask to Add first or last number in random. +REVERSE() { +NAME="REVERSE" +RFL=`seq 1 2 | sort -R | head -n 1` # Rand First (1) or Last (2) +ADDNBR=${DIGITS[ADDNBR]} + +for ((i=$LENINPUT-1;i>=0;i--)) +do + NEWINPUT="$NEWINPUT${INPUT:$i:1}" +done + +if (( $RFL == 1 ));then + ADDNBR="${INPUT:0:1}" + NAME+="_FIRST" + NEWINPUT=${NEWINPUT:0:$LENINPUT-1} # Do not show first INPUT NBR + INSTRUCTIONS1="Écrivez le chiffre ${DIGIT[$ADDNBR]} puis le nombre " + INSTRUCTIONS2="en recopiant de DROITE à GAUCHE ←" +else + ADDNBR="${INPUT:$LENINPUT-1:1}" + NAME+="_LAST" + NEWINPUT=${NEWINPUT:1:$LENINPUT-1} # Do not show last INPUT NBR + INSTRUCTIONS1="Recopiez de DROITE à GAUCHE ←" + INSTRUCTIONS2="et terminez par le chiffre ${DIGIT[$ADDNBR]}" +fi +NAME+="-$ADDNBR" +} + + +# Add FALSE NBR to INPUT at three randomized place +NOTIN() { +NAME="NOTIN" +GENERATE__NOTIN +PLACE=(`seq 0 $((LENINPUT-1)) | sort -R | head -n 3 | sort -n`) +for i in `seq 0 $((LENINPUT-1))` +do + (( $i == ${PLACE[0]} )) \ + && NEWINPUT+="$FALSENBR" + (( $i == ${PLACE[1]} )) \ + && NEWINPUT+="$FALSENBR" + (( $i == ${PLACE[2]} )) \ + && NEWINPUT+="$FALSENBR" + NEWINPUT="$NEWINPUT${INPUT:$i:1}" +done +NAME+="-$FALSENBR" +INSTRUCTIONS1="Recopiez ce nombre sans les ${DIGITS[FALSENBR]}" +} + + +# Take a random NBR from INPUT. Calculate X +CALCUL() { +PLACE=`seq 1 $((LENINPUT)) | sort -R | head -n 1` +TARGET=`echo "$INPUT" | head -c $PLACE | tail -c 1` +NEWINPUT=${INPUT//$TARGET/X} + +RC=`seq 1 2 | sort -R | head -n 1` # 1 = - | 2 = + +if (( $RC == 1 ));then + FALSENBR=`seq $TARGET 10 | sort -R | head -n 1` + RESUME=$(( FALSENBR - TARGET )) + INSTRUCTIONS1="Calculez: ${DIGIT[FALSENBR]} moins ${DIGIT[RESUME]} et copiez" + NAME="CALCULM-$FALSENBR-$RESUME" +else + FALSENBR=`seq 1 4 | sort -R | head -n 1` + if (( $FALSENBR <= $TARGET ));then + RESULT=$(( TARGET + FALSENBR )) + RESUME=$(( TARGET - FALSENBR )) + INSTRUCTIONS1="Calculez: ${DIGIT[FALSENBR]} plus ${DIGIT[RESUME]} et copiez" + NAME="CALCULP-$FALSENBR-$RESUME" + else + RESULT=$(( FALSENBR - TARGET )) + RESUME=$(( TARGET - RESULT )) + INSTRUCTIONS1="Calculez: ${DIGIT[RESULT]} plus ${DIGIT[RESUME]} et copiez" + NAME="CALCULP-$RESULT-$RESUME" + fi +fi +INSTRUCTIONS2="en remplaçant chaque X par la somme" +} + + +# **************************************************************************** +# +# Random captcha shape choice (aka main) +# +# **************************************************************************** + +MODULES=(CALCUL NOTIN REPLACE REVERSE) +RMODULE=`seq 0 $(( ${#MODULES[@]} - 1)) | sort -R | head -n 1` +eval "${MODULES[RMODULE]}" + +TEXT="$NEWINPUT" +CHAPRILURI=/var/lib/ejabberd/chapril_captchas/$(date "+%Y-%m-%d-%H%M%S")_${NAME}_${INPUT}.png + +convert -size 300x70 xc:none -pointsize 20 \ + \( -clone 0 -fill black \ + -stroke black -strokewidth 1 \ + -font Helvetica-Narrow -annotate "0x0+0+0" "\n $INSTRUCTIONS1" \ + -font Helvetica-Bold -annotate "0x0+0+22" "\n $TEXT" \ + -font Helvetica-Narrow -annotate "0x0+0+44" "\n $INSTRUCTIONS2" \ + -roll +$ROLL_X+0 \ + -wave "$WAVE1_AMPLITUDE"x"$WAVE1_LENGTH" \ + -roll -$ROLL_X+0 \) \ + -flatten -crop 300x70 +repage -quality 500 \ + -depth 11 png:"$CHAPRILURI" + +convert -size 300x70 xc:none -pointsize 20 \ + \( -clone 0 -fill black \ + -stroke black -strokewidth 1 \ + -font Helvetica-Narrow -annotate "0x0+0+0" "\n $INSTRUCTIONS1" \ + -font Helvetica-Bold -annotate "0x0+0+22" "\n $TEXT" \ + -font Helvetica-Narrow -annotate "0x0+0+44" "\n $INSTRUCTIONS2" \ + -roll +$ROLL_X+0 \ + -wave "$WAVE1_AMPLITUDE"x"$WAVE1_LENGTH" \ + -roll -$ROLL_X+0 \) \ + -flatten -crop 300x70 +repage -quality 500 -depth 11 png:- diff --git a/exemples/CALCUL.png b/exemples/CALCUL.png new file mode 100644 index 0000000..09032e1 Binary files /dev/null and b/exemples/CALCUL.png differ diff --git a/exemples/NOTIN.png b/exemples/NOTIN.png new file mode 100644 index 0000000..fdfaf1a Binary files /dev/null and b/exemples/NOTIN.png differ diff --git a/exemples/REPLACE.png b/exemples/REPLACE.png new file mode 100644 index 0000000..a87c3c9 Binary files /dev/null and b/exemples/REPLACE.png differ diff --git a/exemples/REVERSE.png b/exemples/REVERSE.png new file mode 100644 index 0000000..0ac1995 Binary files /dev/null and b/exemples/REVERSE.png differ diff --git a/exemples/REVERSE2.png b/exemples/REVERSE2.png new file mode 100644 index 0000000..70cfc07 Binary files /dev/null and b/exemples/REVERSE2.png differ diff --git a/experimental.sh b/experimental.sh new file mode 100755 index 0000000..8fda410 --- /dev/null +++ b/experimental.sh @@ -0,0 +1,231 @@ +#!/bin/bash + +# Copyright © 2021 Adrien Bourmault (neox@a-lec.org) +# Copyright © 2021 Echolib (echolib@dismail.de) + +# 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 script is an example captcha script. +# It takes the text to recognize in the captcha image as a parameter. +# It return the image binary as a result. ejabberd support PNG, JPEG and GIF. + +# The whole idea of the captcha script is to let server admins adapt it to +# their own needs. The goal is to be able to make the captcha generation as +# unique as possible, to make the captcha challenge difficult to bypass by +# a bot. +# Server admins are thus supposed to write and use their own captcha generators. + +# This script relies on ImageMagick. +# It is NOT compliant with ImageMagick forks like GraphicsMagick. + +INPUT=$1 +LENINPUT=${#INPUT} +DIGITS=(zéros uns deux trois quatres cinqs six septs huits neufs dix) +DIGIT=(zéro un deux trois quatre cinq six sept huit neuf dix) + + +# **************************************************************************** +# +# Tools +# +# **************************************************************************** + + +# Generate a number that is not in INPUT. Loop 10 times (should be enough) +GENERATE__NOTIN() { +for i in {1..10} +do + FALSENBR=`seq 1 9 | sort -R | head -n 1` + ! [[ $INPUT =~ $FALSENBR ]] \ + && break +done +} + + +# **************************************************************************** +# +# Captcha shapes +# +# **************************************************************************** + + +# REPLACEs a random number in INPUT by another one randomized +REPLACE() { +NAME="REPLACE" +PLACE=`seq 1 6 | sort -R | head -n 1` +TARGET=`echo "$INPUT" | head -c $PLACE | tail -c 1` + +GENERATE__NOTIN +NAME+="-$FALSENBR-$TARGET" +NEWINPUT=${INPUT//$TARGET/$FALSENBR} +INSTRUCTIONS1="Recopiez ce nombre" +INSTRUCTIONS2="en changeant les ${DIGITS[FALSENBR]} en ${DIGITS[TARGET]}" +} + + +# REVERSEs INPUT. Ask to Add first or last number in random. +REVERSE() { +NAME="REVERSE" +RFL=`seq 1 2 | sort -R | head -n 1` # Rand First (1) or Last (2) +ADDNBR=${DIGITS[ADDNBR]} + +for ((i=$LENINPUT-1;i>=0;i--)) +do + NEWINPUT="$NEWINPUT${INPUT:$i:1}" +done + +if (( $RFL == 1 ));then + ADDNBR="${INPUT:0:1}" + NAME+="_FIRST" + NEWINPUT=${NEWINPUT:0:$LENINPUT-1} # Do not show first INPUT NBR + INSTRUCTIONS1="Écrivez le chiffre ${DIGIT[$ADDNBR]}" + INSTRUCTIONS2="et recopiez de DROITE à GAUCHE ←" +else + ADDNBR="${INPUT:$LENINPUT-1:1}" + NAME+="_LAST" + NEWINPUT=${NEWINPUT:1:$LENINPUT-1} # Do not show last INPUT NBR + INSTRUCTIONS1="Recopiez de DROITE à GAUCHE ←" + INSTRUCTIONS2="et terminez par le chiffre ${DIGIT[$ADDNBR]}" +fi +NAME+="-$ADDNBR" +} + + +# Add FALSE NBR to INPUT at three randomized place +NOTIN() { +NAME="NOTIN" +GENERATE__NOTIN +PLACE=(`seq 0 $((LENINPUT-1)) | sort -R | head -n 3 | sort -n`) +for i in `seq 0 $((LENINPUT-1))` +do + (( $i == ${PLACE[0]} )) \ + && NEWINPUT+="$FALSENBR" + (( $i == ${PLACE[1]} )) \ + && NEWINPUT+="$FALSENBR" + (( $i == ${PLACE[2]} )) \ + && NEWINPUT+="$FALSENBR" + NEWINPUT="$NEWINPUT${INPUT:$i:1}" +done +NAME+="-$FALSENBR" +INSTRUCTIONS1="Recopiez ce nombre sans les ${DIGITS[FALSENBR]}" +} + + +# Take a random NBR from INPUT. Calculate X +CALCUL() { +PLACE=`seq 1 $((LENINPUT)) | sort -R | head -n 1` +TARGET=`echo "$INPUT" | head -c $PLACE | tail -c 1` +NEWINPUT=${INPUT//$TARGET/X} + +RC=`seq 1 2 | sort -R | head -n 1` # 1 = - | 2 = + +if (( $RC == 1 ));then + FALSENBR=`seq $TARGET 10 | sort -R | head -n 1` + RESUME=$(( FALSENBR - TARGET )) + INSTRUCTIONS1="Calculez: ${DIGIT[FALSENBR]} moins ${DIGIT[RESUME]} et copiez" + NAME="CALCULM-$FALSENBR-$RESUME" +else + FALSENBR=`seq 1 4 | sort -R | head -n 1` + if (( $FALSENBR <= $TARGET ));then + RESULT=$(( TARGET + FALSENBR )) + RESUME=$(( TARGET - FALSENBR )) + INSTRUCTIONS1="Calculez: ${DIGIT[FALSENBR]} plus ${DIGIT[RESUME]} et copiez" + NAME="CALCULP-$FALSENBR-$RESUME" + else + RESULT=$(( FALSENBR - TARGET )) + RESUME=$(( TARGET - RESULT )) + INSTRUCTIONS1="Calculez: ${DIGIT[RESULT]} plus ${DIGIT[RESUME]} et copiez" + NAME="CALCULP-$RESULT-$RESUME" + fi +fi +INSTRUCTIONS2="en remplaçant chaque X par la somme" +} + +# Sum 2 LAST numbers. Hide LAST or BEFORE LAST +CALCEND() { +END1=${INPUT:$LENINPUT-2:1} +END2=${INPUT:$LENINPUT-1:1} +ENDS=$(( END1 + END2 )) +REND=`seq 1 2 | sort -R | head -n 1` +if (( $REND == 1 ));then + # Hide LAST + NEWINPUT=${INPUT:0:LENINPUT-1}X + NAME="END-${END1}X" +else + # Hide BEFORE LAST + NEWINPUT=${INPUT:0:LENINPUT-2}X$END2 + NAME="END-X$END2" +fi +INSTRUCTIONS1="La somme des 2 derniers chiffres est $ENDS" +INSTRUCTIONS2="Copiez en remplaçant X par sa valeur" +} + +# Replace 2 X (smaller > bigger | bigger > smaller) +LEMO() { +MAXNBR=${INPUT:0:1} +MINNBR=${INPUT:0:1} +for i in `seq 1 $((LENINPUT-1))` +do + (( ${INPUT:$i:1} > $MAXNBR )) \ + && MAXNBR=${INPUT:$i:1} + (( ${INPUT:$i:1} < $MINNBR )) \ + && MINNBR=${INPUT:$i:1} +done + +for i in `seq 0 $((LENINPUT-1))` +do + if (( ${INPUT:$i:1} == $MINNBR ));then + INSTRUCTIONS1="Remplacez les X par ${DIGIT[MAXNBR]} et ${DIGIT[MINNBR]}" + INSTRUCTIONS2="! le plus PETIT en premier" + NAME="LEMO-$MINNBR-$MAXNBR" + break + elif (( ${INPUT:$i:1} == $MAXNBR ));then + INSTRUCTIONS1="Remplacez les X par ${DIGIT[MINNBR]} et ${DIGIT[MAXNBR]}" + INSTRUCTIONS2="! Le plus GRAND en premier" + NAME="LEMO-$MAXNBR-$MINNBR" + break + fi +done + +NEWINPUT=`echo $INPUT | sed "s/$MINNBR/X/"` +NEWINPUT=`echo $NEWINPUT | sed "s/$MAXNBR/X/"` +} + +# **************************************************************************** +# +# Random captcha shape choice (aka main) +# +# **************************************************************************** + +MODULES=(CALCUL CALCEND LEMO NOTIN REPLACE REVERSE) +RMODULE=`seq 0 $(( ${#MODULES[@]} - 1)) | sort -R | head -n 1` +eval "${MODULES[RMODULE]}" + +TEXT="$NEWINPUT" +DATE="$(date "+%Y-%m-%d-%H%M%S")" +CHAPRILURI=/var/lib/ejabberd/chapril_captchas/${DATE}_${NAME}_${INPUT}.png + +convert -size 300x70 xc:none -pointsize 20 \ + \( -clone 0 -fill black \ + -stroke black -strokewidth 1 \ + -font Helvetica-Narrow -annotate "0x0+0+0" "\n $INSTRUCTIONS1" \ + -font Helvetica-Bold -annotate "0x0+0+22" "\n $TEXT" \ + -font Helvetica-Narrow -annotate "0x0+0+44" "\n $INSTRUCTIONS2" \ + -roll +$ROLL_X+0 \ + -wave "$WAVE1_AMPLITUDE"x"$WAVE1_LENGTH" \ + -roll -$ROLL_X+0 \) \ + -flatten -crop 300x70 +repage -quality 500 \ + -depth 11 png:"$CHAPRILURI" + +convert -size 300x70 xc:none -pointsize 20 \ + \( -clone 0 -fill black \ + -stroke black -strokewidth 1 \ + -font Helvetica-Narrow -annotate "0x0+0+0" "\n $INSTRUCTIONS1" \ + -font Helvetica-Bold -annotate "0x0+0+22" "\n $TEXT" \ + -font Helvetica-Narrow -annotate "0x0+0+44" "\n $INSTRUCTIONS2" \ + -roll +$ROLL_X+0 \ + -wave "$WAVE1_AMPLITUDE"x"$WAVE1_LENGTH" \ + -roll -$ROLL_X+0 \) \ + -flatten -crop 300x70 +repage -quality 500 -depth 11 png:- diff --git a/get_validated_captchas.sh b/get_validated_captchas.sh new file mode 100755 index 0000000..968d497 --- /dev/null +++ b/get_validated_captchas.sh @@ -0,0 +1,23 @@ +#! /bin/bash + +dates=$(zcat /var/log/ejabberd/ejabberd* 2> /dev/null | grep "was registered" \ + | cut -d[ -f 1 | cut -c 1-16) + +SAVEIFS=$IFS # Save current IFS +IFS=$'\n' # Change IFS to new line + +dates=($dates) # Split to array +IFS=$SAVEIFS # Restore IFS + +greppattern="" + +for (( i=0; i<${#dates[@]}; i++ )) +do + greppattern="$greppattern -e \"$(echo ${dates[$i]})\"" +done + +#echo -e ${greppattern} + +grepcommand="ls -lt --full-time /var/lib/ejabberd/chapril_captchas | grep $greppattern | cut -d ' ' -f 9" + +eval $grepcommand diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..46a529e --- /dev/null +++ b/readme.md @@ -0,0 +1,43 @@ +# Méthodes de génération de captcha pour ejabberd 2x.x + +## Réalisées dans le cadre du service XMPP du Chapril + +### Concept + +L'idée derrière ces méthodes de captchas est d'abord la simplicité de lecture pour l'utilisateur: +- Pas de déformation du texte +- Pas de coloration du texte +- Ecrit en français sous la forme d'une consigne se voulant "simple" tout en étant difficile à attaquer par force brute par un bot +- Compatible avec le système de génération de captcha de ejabberd (la XEP 0158) + +### Exemples + +Différentes méthodes sont utilisées. + +#### Remplacement (REPLACE) + +Une seule forme est disponible: + +![Exemple de remplacement](exemples/REPLACE.png) + +#### Inversion (REVERSE) + +Deux formes sont disponibles: + +![Premier exemple d'inversion](exemples/REVERSE.png) + +ou encore + +![Premier exemple d'inversion](exemples/REVERSE2.png) + +#### Intrus (NOTIN) + +Une seule forme est disponible: + +![Exemple d'intrus](exemples/NOTIN.png) + +#### Calcul (CALCUL) + +Une seule forme est disponible: + +![Exemple de calcul](exemples/CALCUL.png) \ No newline at end of file diff --git a/savecaptcha.sh b/savecaptcha.sh new file mode 100755 index 0000000..a1f196f --- /dev/null +++ b/savecaptcha.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# This script relies on ImageMagick. +# It is NOT compliant with ImageMagick forks like GraphicsMagick. + +RAND_ANGLE=$1 +RAND_ITALIC=$2 +INPUT=$3 +SOLUTION=$4 + +convert -size 300x60 xc:none -pointsize 20 \ + \( -clone 0 -fill black \ + -stroke black -strokewidth 1 \ + -annotate "${RAND_ANGLE}x${RAND_ITALIC}+0+0" "\n $INPUT" \ + -roll +$ROLL_X+0 \ + -wave "$WAVE1_AMPLITUDE"x"$WAVE1_LENGTH" \ + -roll -$ROLL_X+0 \) \ + -flatten -crop 300x60 +repage -quality 500 -depth 11 png:"/var/tmp/captcha/${SOLUTION}_$(date "+%Y-%m-%d-%H%M%S").png"