Add support for Argon2 password hashes (argon2id).

Also, make this the default for './unrealircd mkpasswd'.
The Windows version also works.. I just need to create a new library
package, will be done later today or tomorrow.
https://bugs.unrealircd.org/view.php?id=5116
pull/1/head
Bram Matthys 4 years ago
parent 459a55245a
commit a852b480d5
No known key found for this signature in database
GPG Key ID: BF8116B163EAAE98

@ -34,11 +34,11 @@ FROMDOS=/home/cmunk/bin/4dos
#
#XCFLAGS=-O -g -export-dynamic
IRCDLIBS=@IRCDLIBS@ @TRE_LIBS@ @PCRE2_LIBS@ @CARES_LIBS@ @PTHREAD_LIBS@
IRCDLIBS=@IRCDLIBS@ @TRE_LIBS@ @PCRE2_LIBS@ @ARGON2_LIBS@ @CARES_LIBS@ @PTHREAD_LIBS@
CRYPTOLIB=@CRYPTOLIB@
OPENSSLINCLUDES=
XCFLAGS=@PTHREAD_CFLAGS@ @TRE_CFLAGS@ @PCRE2_CFLAGS@ @CARES_CFLAGS@ @CFLAGS@ @HARDEN_CFLAGS@ @CPPFLAGS@
XCFLAGS=@PTHREAD_CFLAGS@ @TRE_CFLAGS@ @PCRE2_CFLAGS@ @ARGON2_CFLAGS@ @CARES_CFLAGS@ @CFLAGS@ @HARDEN_CFLAGS@ @CPPFLAGS@
#
# use the following on MIPS:
#CFLAGS= -systype bsd43 -DSYSTYPE_BSD43 -I$(INCLUDEDIR)

146
configure vendored

@ -640,6 +640,8 @@ build_cpu
build
CARES_LIBS
CARES_CFLAGS
ARGON2_LIBS
ARGON2_CFLAGS
PCRE2_LIBS
PCRE2_CFLAGS
PKG_CONFIG_LIBDIR
@ -757,6 +759,7 @@ with_operoverride_verify
with_disable_extendedban_stacking
with_system_tre
with_system_pcre2
with_system_argon2
with_system_cares
enable_ssl
enable_dynamic_linking
@ -779,6 +782,8 @@ TRE_CFLAGS
TRE_LIBS
PCRE2_CFLAGS
PCRE2_LIBS
ARGON2_CFLAGS
ARGON2_LIBS
CARES_CFLAGS
CARES_LIBS'
@ -1458,6 +1463,8 @@ Optional Packages:
discovered using pkg-config
--with-system-pcre2 Use the system pcre2 package instead of bundled,
discovered using pkg-config
--without-system-argon2 Use bundled version instead of system argon2
library. Normally autodetected via pkg-config
--without-system-cares Use bundled version instead of system c-ares.
Normally autodetected via pkg-config.
@ -1480,6 +1487,9 @@ Some influential environment variables:
PCRE2_CFLAGS
C compiler flags for PCRE2, overriding pkg-config
PCRE2_LIBS linker flags for PCRE2, overriding pkg-config
ARGON2_CFLAGS
C compiler flags for ARGON2, overriding pkg-config
ARGON2_LIBS linker flags for ARGON2, overriding pkg-config
CARES_CFLAGS
C compiler flags for CARES, overriding pkg-config
CARES_LIBS linker flags for CARES, overriding pkg-config
@ -7294,6 +7304,14 @@ else
fi
# Check whether --with-system-argon2 was given.
if test "${with_system_argon2+set}" = set; then :
withval=$with_system_argon2;
else
with_system_argon2=yes
fi
# Check whether --with-system-cares was given.
if test "${with_system_cares+set}" = set; then :
withval=$with_system_cares;
@ -8040,6 +8058,134 @@ fi
fi
if test "x$with_system_argon2" = "xno"; then :
argon2_version="20181209"
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: extracting Argon2 library" >&5
$as_echo "extracting Argon2 library" >&6; }
cur_dir=`pwd`
cd extras
rm -rf argon2-$argon2_version argon2
if test "x$ac_cv_path_GUNZIP" = "x" ; then
tar xfz argon2-$argon2_version.tar.gz
else
cp argon2-$argon2_version.tar.gz argon2-$argon2_version.tar.gz.bak
gunzip -f argon2-$argon2_version.tar.gz
cp argon2-$argon2_version.tar.gz.bak argon2-$argon2_version.tar.gz
tar xf argon2-$argon2_version.tar
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: compiling Argon2 library" >&5
$as_echo "compiling Argon2 library" >&6; }
cd argon2-$argon2_version
$ac_cv_prog_MAKER || exit 1
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: installing Argon2 library" >&5
$as_echo "installing Argon2 library" >&6; }
$ac_cv_prog_MAKER install PREFIX=$cur_dir/extras/argon2 || exit 1
# We need to manually copy the libs to PRIVATELIBDIR because
# there is no way to tell make install in libargon2 to do so.
cp -av $cur_dir/extras/argon2/lib/* $PRIVATELIBDIR/
ARGON2_CFLAGS="-I$cur_dir/extras/argon2/include"
ARGON2_LIBS="-L$PRIVATELIBDIR -largon2 -lrt -ldl"
cd $cur_dir
else
pkg_failed=no
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ARGON2" >&5
$as_echo_n "checking for ARGON2... " >&6; }
if test -n "$ARGON2_CFLAGS"; then
pkg_cv_ARGON2_CFLAGS="$ARGON2_CFLAGS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libargon2 >= 0\""; } >&5
($PKG_CONFIG --exists --print-errors "libargon2 >= 0") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_ARGON2_CFLAGS=`$PKG_CONFIG --cflags "libargon2 >= 0" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
fi
else
pkg_failed=untried
fi
if test -n "$ARGON2_LIBS"; then
pkg_cv_ARGON2_LIBS="$ARGON2_LIBS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libargon2 >= 0\""; } >&5
($PKG_CONFIG --exists --print-errors "libargon2 >= 0") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_ARGON2_LIBS=`$PKG_CONFIG --libs "libargon2 >= 0" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
fi
else
pkg_failed=untried
fi
if test $pkg_failed = yes; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
_pkg_short_errors_supported=yes
else
_pkg_short_errors_supported=no
fi
if test $_pkg_short_errors_supported = yes; then
ARGON2_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libargon2 >= 0" 2>&1`
else
ARGON2_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libargon2 >= 0" 2>&1`
fi
# Put the nasty error message in config.log where it belongs
echo "$ARGON2_PKG_ERRORS" >&5
as_fn_error $? "Package requirements (libargon2 >= 0) were not met:
$ARGON2_PKG_ERRORS
Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.
Alternatively, you may set the environment variables ARGON2_CFLAGS
and ARGON2_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details." "$LINENO" 5
elif test $pkg_failed = untried; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it
is in your PATH or set the PKG_CONFIG environment variable to the full
path to pkg-config.
Alternatively, you may set the environment variables ARGON2_CFLAGS
and ARGON2_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.
To get pkg-config, see <http://pkg-config.freedesktop.org/>.
See \`config.log' for more details" "$LINENO" 5; }
else
ARGON2_CFLAGS=$pkg_cv_ARGON2_CFLAGS
ARGON2_LIBS=$pkg_cv_ARGON2_LIBS
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
fi
fi
has_system_cares="no"
if test "x$with_system_cares" = "xyes"; then :

@ -701,6 +701,7 @@ AC_ARG_WITH(disable-extendedban-stacking, [AS_HELP_STRING([--with-disable-extend
[AC_DEFINE([DISABLE_STACKED_EXTBANS], [], [Define to disable extended ban stacking (~q:~c:\#chan, etc)])])])
AC_ARG_WITH(system-tre, [AS_HELP_STRING([--with-system-tre], [Use the system tre package instead of bundled, discovered using pkg-config])], [], [with_system_tre=no])
AC_ARG_WITH(system-pcre2, [AS_HELP_STRING([--with-system-pcre2], [Use the system pcre2 package instead of bundled, discovered using pkg-config])], [], [with_system_pcre2=no])
AC_ARG_WITH(system-argon2, [AS_HELP_STRING([--without-system-argon2], [Use bundled version instead of system argon2 library. Normally autodetected via pkg-config])], [], [with_system_argon2=yes])
AC_ARG_WITH(system-cares, [AS_HELP_STRING([--without-system-cares], [Use bundled version instead of system c-ares. Normally autodetected via pkg-config.])], [], [with_system_cares=yes])
CHECK_SSL
CHECK_SSL_CTX_SET1_CURVES_LIST
@ -837,6 +838,41 @@ dnl use pkgconfig for pcre2:
PKG_CHECK_MODULES([PCRE2], libpcre2-8 >= 10.00)
])
AS_IF([test "x$with_system_argon2" = "xno"],[
dnl REMEMBER TO CHANGE WITH A NEW ARGON2 RELEASE!
argon2_version="20181209"
AC_MSG_RESULT(extracting Argon2 library)
cur_dir=`pwd`
cd extras
dnl remove old argon2 directory to force a recompile...
dnl and remove its installation prefix just to clean things up.
rm -rf argon2-$argon2_version argon2
if test "x$ac_cv_path_GUNZIP" = "x" ; then
tar xfz argon2-$argon2_version.tar.gz
else
cp argon2-$argon2_version.tar.gz argon2-$argon2_version.tar.gz.bak
gunzip -f argon2-$argon2_version.tar.gz
cp argon2-$argon2_version.tar.gz.bak argon2-$argon2_version.tar.gz
tar xf argon2-$argon2_version.tar
fi
AC_MSG_RESULT(compiling Argon2 library)
cd argon2-$argon2_version
$ac_cv_prog_MAKER || exit 1
AC_MSG_RESULT(installing Argon2 library)
$ac_cv_prog_MAKER install PREFIX=$cur_dir/extras/argon2 || exit 1
# We need to manually copy the libs to PRIVATELIBDIR because
# there is no way to tell make install in libargon2 to do so.
cp -av $cur_dir/extras/argon2/lib/* $PRIVATELIBDIR/
ARGON2_CFLAGS="-I$cur_dir/extras/argon2/include"
AC_SUBST(ARGON2_CFLAGS)
ARGON2_LIBS="-L$PRIVATELIBDIR -largon2 -lrt -ldl"
AC_SUBST(ARGON2_LIBS)
cd $cur_dir
],[
dnl use pkgconfig for argon2:
PKG_CHECK_MODULES([ARGON2], libargon2 >= 0)
])
dnl Use system c-ares when available, unless --without-system-cares.
has_system_cares="no"
AS_IF([test "x$with_system_cares" = "xyes"],[

Binary file not shown.

@ -33,6 +33,7 @@ typedef struct {
#define AUTHTYPE_SSL_CLIENTCERTFP 6
#define AUTHTYPE_BCRYPT 7
#define AUTHTYPE_SPKIFP 8
#define AUTHTYPE_ARGON2 9
#ifndef HAVE_CRYPT
#define crypt DES_crypt

@ -24,6 +24,11 @@ MT=mt
#PCRE2_INC_DIR="C:\dev\pcre2"
#PCRE2LIB="pcre2-8.lib"
### ARGON2 ###
#ARGON2_LIB_DIR="C:\dev\argon2\vs2015\build"
#ARGON2_INC_DIR="C:\dev\argon2\include"
#ARGON2LIB="Argon2RefDll.lib"
### C-ARES ####
#CARES_LIB_DIR="C:\dev\c-ares\vc\cares\dll-release"
#CARES_INC_DIR="C:\dev\c-ares"
@ -94,6 +99,13 @@ PCRE2_INC=/I "$(PCRE2_INC_DIR)"
PCRE2_LIB=/LIBPATH:"$(PCRE2_LIB_DIR)"
!ENDIF
!IFDEF ARGON2_INC_DIR
ARGON2_INC=/I "$(ARGON2_INC_DIR)"
!ENDIF
!IFDEF ARGON2_LIB_DIR
ARGON2_LIB=/LIBPATH:"$(ARGON2_LIB_DIR)"
!ENDIF
!IFDEF USE_REMOTEINC
CURLCFLAGS=/D USE_LIBCURL
CURLOBJ=SRC/URL.OBJ
@ -126,19 +138,19 @@ MODDBGCFLAG=/LDd /MD /Zi
!ENDIF
FD_SETSIZE=/D FD_SETSIZE=16384
CFLAGS=$(DBGCFLAG) $(TRE_INC) $(PCRE2_INC) $(CARES_INC) $(LIBCURL_INC) $(LIBRESSL_INC) /J /I ./INCLUDE /I ./INCLUDE/WIN32/ARES /Fosrc/ /nologo \
CFLAGS=$(DBGCFLAG) $(TRE_INC) $(PCRE2_INC) $(ARGON2_INC) $(CARES_INC) $(LIBCURL_INC) $(LIBRESSL_INC) /J /I ./INCLUDE /I ./INCLUDE/WIN32/ARES /Fosrc/ /nologo \
$(CURLCFLAGS) $(FD_SETSIZE) $(SSLCFLAGS) /D NOSPOOF=1 /c /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _USE_32BIT_TIME_T
CFLAGSST=$(DBGCFLAGST) $(TRE_INC) $(PCRE2_INC) $(CARES_INC) $(LIBCURL_INC) $(LIBRESSL_INC) /J /I ./INCLUDE /I ./INCLUDE/WIN32/ARES /Fosrc/ /nologo \
CFLAGSST=$(DBGCFLAGST) $(TRE_INC) $(PCRE2_INC) $(ARGON2_INC) $(CARES_INC) $(LIBCURL_INC) $(LIBRESSL_INC) /J /I ./INCLUDE /I ./INCLUDE/WIN32/ARES /Fosrc/ /nologo \
$(CURLCFLAGS) $(FD_SETSIZE) $(SSLCFLAGS) /D NOSPOOF=1 /c /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _USE_32BIT_TIME_T
LFLAGS=kernel32.lib user32.lib gdi32.lib shell32.lib ws2_32.lib advapi32.lib \
dbghelp.lib oldnames.lib comctl32.lib comdlg32.lib $(CARES_LIB) $(CARESLIB) $(TRE_LIB) $(TRELIB) \
$(PCRE2_LIB) $(PCRE2LIB) $(LIBRESSL_LIB) $(SSLLIB) $(LIBCURL_LIB) $(CURLLIB) /def:UnrealIRCd.def /implib:UnrealIRCd.lib \
$(PCRE2_LIB) $(PCRE2LIB) $(ARGON2_LIB) $(ARGON2LIB) $(LIBRESSL_LIB) $(SSLLIB) $(LIBCURL_LIB) $(CURLLIB) /def:UnrealIRCd.def /implib:UnrealIRCd.lib \
/nologo $(DBGLFLAG) /out:UnrealIRCd.exe
MODCFLAGS=$(MODDBGCFLAG) $(SSLCFLAGS) $(CURLCFLAGS) /J /Fesrc/modules/ \
/Fosrc/modules/ /nologo $(TRE_INC) $(PCRE2_INC) $(CARES_INC) $(LIBCURL_INC) $(LIBRESSL_INC) /I ./INCLUDE /D \
/Fosrc/modules/ /nologo $(TRE_INC) $(PCRE2_INC) $(ARGON2_INC) $(CARES_INC) $(LIBCURL_INC) $(LIBRESSL_INC) /I ./INCLUDE /D \
DYNAMIC_LINKING /D NOSPOOF /D MODULE_COMPILE /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _USE_32BIT_TIME_T
MODLFLAGS=/link /def:src/modules/module.def UnrealIRCd.lib ws2_32.lib $(TRE_LIB) $(TRELIB) \
$(PCRE2_LIB) $(PCRE2LIB) $(CARES_LIB) $(LIBRESSL_LIB) $(SSLLIB) \
$(PCRE2_LIB) $(PCRE2LIB) $(ARGON2_LIB) $(ARGON2LIB) $(CARES_LIB) $(LIBRESSL_LIB) $(SSLLIB) \
$(LIBCURL_LIB) $(CURLLIB)
INCLUDES=./include/struct.h ./include/config.h ./include/sys.h \

@ -19,6 +19,7 @@
#include "unrealircd.h"
#include "crypt_blowfish.h"
#include <argon2.h>
anAuthStruct MODVAR AuthTypes[] = {
{"plain", AUTHTYPE_PLAINTEXT},
@ -34,6 +35,7 @@ anAuthStruct MODVAR AuthTypes[] = {
{"sslclientcertfp", AUTHTYPE_SSL_CLIENTCERTFP},
{"certfp", AUTHTYPE_SSL_CLIENTCERTFP},
{"spkifp", AUTHTYPE_SPKIFP},
{"argon2", AUTHTYPE_ARGON2},
{NULL, 0}
};
@ -83,6 +85,9 @@ int Auth_AutoDetectHashType(char *hash)
if (!strncmp(hash, "$2a$", 4) || !strncmp(hash, "$2b$", 4) || !strncmp(hash, "$2y$", 4))
return AUTHTYPE_BCRYPT;
if (!strncmp(hash, "$argon2", 7))
return AUTHTYPE_ARGON2;
/* Now handle UnrealIRCd-style password hashes.. */
if (parsepass(hash, &saltstr, &hashstr) == 0)
return AUTHTYPE_PLAINTEXT; /* old method (pre-3.2.1) or could not detect, fallback. */
@ -272,6 +277,31 @@ int max;
return 1;
}
static int authcheck_argon2(aClient *cptr, anAuthStruct *as, char *para)
{
argon2_type hashtype;
if (!para)
return -1;
/* Find out the hashtype. Why do we need to do this, why is this
* not in the library or irrelevant by using some generic function?
*/
if (!strncmp(as->data, "$argon2id", 9))
hashtype = Argon2_id;
else if (!strncmp(as->data, "$argon2i", 8))
hashtype = Argon2_i;
else if (!strncmp(as->data, "$argon2d", 8))
hashtype = Argon2_d;
else
return -1; /* unknown argon2 type */
if (argon2_verify(as->data, para, strlen(para), hashtype) == ARGON2_OK)
return 2; /* MATCH */
return -1; /* NO MATCH or error */
}
static int authcheck_bcrypt(aClient *cptr, anAuthStruct *as, char *para)
{
char data[512]; /* NOTE: only 64 required by BF_crypt() */
@ -500,6 +530,9 @@ int Auth_Check(aClient *cptr, anAuthStruct *as, char *para)
return 2;
return -1;
case AUTHTYPE_ARGON2:
return authcheck_argon2(cptr, as, para);
case AUTHTYPE_BCRYPT:
return authcheck_bcrypt(cptr, as, para);
@ -603,6 +636,44 @@ int Auth_Check(aClient *cptr, anAuthStruct *as, char *para)
return -1;
}
#define UNREALIRCD_ARGON2_DEFAULT_TIME_COST 3
#define UNREALIRCD_ARGON2_DEFAULT_MEMORY_COST 8192
#define UNREALIRCD_ARGON2_DEFAULT_PARALLELISM_COST 2
#define UNREALIRCD_ARGON2_DEFAULT_HASH_LENGTH 32
#define UNREALIRCD_ARGON2_DEFAULT_SALT_LENGTH (128/8)
static char *mkpass_argon2(char *para)
{
static char buf[512];
char salt[UNREALIRCD_ARGON2_DEFAULT_SALT_LENGTH];
int ret, i;
if (!para)
return NULL;
/* Initialize salt */
for (i=0; i < sizeof(salt); i++)
salt[i] = getrandom8();
*buf = '\0';
ret = argon2id_hash_encoded(UNREALIRCD_ARGON2_DEFAULT_TIME_COST,
UNREALIRCD_ARGON2_DEFAULT_MEMORY_COST,
UNREALIRCD_ARGON2_DEFAULT_PARALLELISM_COST,
para,
strlen(para),
salt,
sizeof(salt),
UNREALIRCD_ARGON2_DEFAULT_HASH_LENGTH,
buf,
sizeof(buf));
if (ret != ARGON2_OK)
return NULL; /* internal error */
return buf;
}
static char *mkpass_bcrypt(char *para)
{
static char buf[128];
@ -801,6 +872,9 @@ char *Auth_Make(short type, char *para)
case AUTHTYPE_PLAINTEXT:
return (para);
case AUTHTYPE_ARGON2:
return mkpass_argon2(para);
case AUTHTYPE_BCRYPT:
return mkpass_bcrypt(para);

@ -1137,7 +1137,7 @@ int InitUnrealIRCd(int argc, char *argv[])
type = Auth_FindType(NULL, p);
if (type == -1)
{
type = AUTHTYPE_BCRYPT;
type = AUTHTYPE_ARGON2;
} else {
p = *++argv;
argc--;
@ -1155,7 +1155,7 @@ int InitUnrealIRCd(int argc, char *argv[])
{
/* Hmmm.. is this warning really still true (and always) ?? */
printf("WARNING: Password truncated to 8 characters due to 'crypt' algorithm. "
"You are suggested to use the 'bcrypt' algorithm instead.");
"You are suggested to use the 'argon2' algorithm instead.");
p[8] = '\0';
}
if (!(result = Auth_Make(type, p))) {

@ -103,7 +103,7 @@ CMD_FUNC(m_mkpasswd)
{
/* TODO: is this still a valid warning ? */
sendnotice(sptr, "WARNING: Password truncated to 8 characters due to 'crypt' algorithm. "
"You are suggested to use the 'md5' algorithm instead.");
"You are suggested to use the 'argon2' algorithm instead.");
parv[2][8] = '\0';
}

@ -73,6 +73,7 @@ Source: "src\modules\cap\*.dll"; DestDir: "{app}\modules\cap"; Flags: ignorevers
Source: "c:\dev\tre\win32\release\tre.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "c:\dev\pcre2\bin\pcre*.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "c:\dev\argon2\vs2015\build\*.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "c:\dev\c-ares\msvc\cares\dll-release\cares.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "c:\dev\libressl\bin\openssl.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "c:\dev\libressl\bin\*.dll"; DestDir: "{app}"; Flags: ignoreversion

Loading…
Cancel
Save