🐄 Jan(moo)uary Update 2022 - The U2F --> WebAuthn (2FA) Update
Images which get a new tag (on docker hub): unbound-mailcow (Tag 1.15) acme-mailcow (Tag 1.81) dockerapi-mailcow (Tag 1.41) netfilter-mailcow (Tag 1.46) watchdog-mailcow (Tag 1.96) These docker tag changes include the Alpine Update to 3.15 clamd-mailcow (Tag 1.43) sogo-mailcow (Tag 1.106) olefy-mailcow (Tag 1.8.1) dovecot-mailcow (Tag 1.159) solr-mailcow (Tag 1.8.1) Very important changes: U2F API Removal --> Replaced with WebAuthn API (TFA) [More Details here] (Thanks to @FreddleSpl0it) Important changes: Dovecot Update to 2.3.17.1 SOGO Update to 5.5.0 ClamAV Update to 0.103.5 (DDOS Fix) Solr Security Fix Fix (Removed breached log4j Class) Alpine Linux Update to 3.15 Better Acme SSL Path recognition for Alpine Versions after 3.15 (Thanks to @mkuron) --> Fixing issue: Possible regression in acme-mailcow 1.80 #4392 Olefy Ping Fix (Will fix: Olefy 1.8 broken #4401) (Thanks to @a16bitsysop for the Fix!) Netfilter GeoIP Fix (Thanks to @marcvorwerk) --> Fixing issue: Netfilter Python error #2668master
commit
2f9da5ae93
|
@ -29,6 +29,7 @@ jobs:
|
||||||
exempt-pr-labels: "pinned,security,enhancement,investigating"
|
exempt-pr-labels: "pinned,security,enhancement,investigating"
|
||||||
stale-issue-label: "stale"
|
stale-issue-label: "stale"
|
||||||
stale-pr-label: "stale"
|
stale-pr-label: "stale"
|
||||||
|
exempt-draft-pr: "true"
|
||||||
operations-per-run: "250"
|
operations-per-run: "250"
|
||||||
ascending: "true"
|
ascending: "true"
|
||||||
#DRY-RUN
|
#DRY-RUN
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM alpine:3.14
|
FROM alpine:3.15
|
||||||
|
|
||||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,7 @@ fi
|
||||||
printf "[SAN]\nsubjectAltName=" > /tmp/_SAN
|
printf "[SAN]\nsubjectAltName=" > /tmp/_SAN
|
||||||
printf "DNS:%s," "${CERT_DOMAINS[@]}" >> /tmp/_SAN
|
printf "DNS:%s," "${CERT_DOMAINS[@]}" >> /tmp/_SAN
|
||||||
sed -i '$s/,$//' /tmp/_SAN
|
sed -i '$s/,$//' /tmp/_SAN
|
||||||
openssl req -new -sha256 -key ${KEY} -subj "/" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf /tmp/_SAN) > ${CSR}
|
openssl req -new -sha256 -key ${KEY} -subj "/" -reqexts SAN -config <(cat "$(openssl version -d | sed 's/.*"\(.*\)"/\1/g')/openssl.cnf" /tmp/_SAN) > ${CSR}
|
||||||
|
|
||||||
# acme-tiny writes info to stderr and ceritifcate to stdout
|
# acme-tiny writes info to stderr and ceritifcate to stdout
|
||||||
# The redirects will do the following:
|
# The redirects will do the following:
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
FROM debian:buster-slim
|
FROM debian:bullseye-slim
|
||||||
|
|
||||||
LABEL maintainer "André Peters <andre.peters@servercow.de>"
|
LABEL maintainer "André Peters <andre.peters@servercow.de>"
|
||||||
|
|
||||||
ARG CLAMAV=0.103.4
|
ARG CLAMAV=0.103.5
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
zlib1g-dev \
|
zlib1g-dev \
|
||||||
|
@ -23,7 +23,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
dos2unix \
|
dos2unix \
|
||||||
netcat \
|
netcat \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& wget -O - https://fossies.org/linux/misc/clamav-${CLAMAV}.tar.gz | tar xfvz - \
|
&& wget -O - https://www.clamav.net/downloads/production/clamav-${CLAMAV}.tar.gz | tar xfvz - \
|
||||||
&& cd clamav-${CLAMAV} \
|
&& cd clamav-${CLAMAV} \
|
||||||
&& ./configure \
|
&& ./configure \
|
||||||
--prefix=/usr \
|
--prefix=/usr \
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM alpine:3.14
|
FROM alpine:3.15
|
||||||
|
|
||||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ FROM debian:buster-slim
|
||||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
ARG DOVECOT=2.3.17
|
ARG DOVECOT=2.3.17.1
|
||||||
ENV LC_ALL C
|
ENV LC_ALL C
|
||||||
ENV GOSU_VERSION 1.12
|
ENV GOSU_VERSION 1.12
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM alpine:3.14
|
FROM alpine:3.15
|
||||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
||||||
|
|
||||||
ENV XTABLES_LIBDIR /usr/lib/xtables
|
ENV XTABLES_LIBDIR /usr/lib/xtables
|
||||||
|
@ -13,10 +13,11 @@ RUN apk add --virtual .build-deps \
|
||||||
&& apk add -U python3 \
|
&& apk add -U python3 \
|
||||||
iptables \
|
iptables \
|
||||||
ip6tables \
|
ip6tables \
|
||||||
|
xtables-addons \
|
||||||
tzdata \
|
tzdata \
|
||||||
py3-pip \
|
py3-pip \
|
||||||
musl-dev \
|
musl-dev \
|
||||||
&& pip3 install --upgrade pip \
|
&& pip3 install --ignore-installed --upgrade pip \
|
||||||
python-iptables \
|
python-iptables \
|
||||||
redis \
|
redis \
|
||||||
ipaddress \
|
ipaddress \
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM alpine:3.14
|
FROM alpine:3.15
|
||||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
@ -12,7 +12,7 @@ RUN apk add --virtual .build-deps gcc musl-dev python3-dev libffi-dev openssl-de
|
||||||
&& apk del .build-deps
|
&& apk del .build-deps
|
||||||
# && sed -i 's/decompress_stream(bytearray(compressed_code))/bytes2str(decompress_stream(bytearray(compressed_code)))/g' /usr/lib/python3.8/site-packages/oletools/olevba.py
|
# && sed -i 's/decompress_stream(bytearray(compressed_code))/bytes2str(decompress_stream(bytearray(compressed_code)))/g' /usr/lib/python3.8/site-packages/oletools/olevba.py
|
||||||
|
|
||||||
ADD https://raw.githubusercontent.com/HeinleinSupport/olefy/master/olefy.py /app/
|
ADD olefy.py /app/
|
||||||
|
|
||||||
RUN chown -R nobody:nobody /app /tmp
|
RUN chown -R nobody:nobody /app /tmp
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,220 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2020, Dennis Kalbhen <d.kalbhen@heinlein-support.de>
|
||||||
|
# Copyright (c) 2020, Carsten Rosenberg <c.rosenberg@heinlein-support.de>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
###
|
||||||
|
#
|
||||||
|
# olefy is a little helper socket to use oletools with rspamd. (https://rspamd.com)
|
||||||
|
# Please find updates and issues here: https://github.com/HeinleinSupport/olefy
|
||||||
|
#
|
||||||
|
###
|
||||||
|
|
||||||
|
from subprocess import Popen, PIPE
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import asyncio
|
||||||
|
import time
|
||||||
|
import magic
|
||||||
|
import re
|
||||||
|
|
||||||
|
# merge variables from /etc/olefy.conf and the defaults
|
||||||
|
olefy_listen_addr_string = os.getenv('OLEFY_BINDADDRESS', '127.0.0.1,::1')
|
||||||
|
olefy_listen_port = int(os.getenv('OLEFY_BINDPORT', '10050'))
|
||||||
|
olefy_tmp_dir = os.getenv('OLEFY_TMPDIR', '/tmp')
|
||||||
|
olefy_python_path = os.getenv('OLEFY_PYTHON_PATH', '/usr/bin/python3')
|
||||||
|
olefy_olevba_path = os.getenv('OLEFY_OLEVBA_PATH', '/usr/local/bin/olevba3')
|
||||||
|
# 10:DEBUG, 20:INFO, 30:WARNING, 40:ERROR, 50:CRITICAL
|
||||||
|
olefy_loglvl = int(os.getenv('OLEFY_LOGLVL', 20))
|
||||||
|
olefy_min_length = int(os.getenv('OLEFY_MINLENGTH', 500))
|
||||||
|
olefy_del_tmp = int(os.getenv('OLEFY_DEL_TMP', 1))
|
||||||
|
olefy_del_tmp_failed = int(os.getenv('OLEFY_DEL_TMP_FAILED', 1))
|
||||||
|
|
||||||
|
# internal used variables
|
||||||
|
request_time = '0000000000.000000'
|
||||||
|
olefy_protocol = 'OLEFY'
|
||||||
|
olefy_ping = 'PING'
|
||||||
|
olefy_protocol_sep = '\n\n'
|
||||||
|
olefy_headers = {}
|
||||||
|
|
||||||
|
# init logging
|
||||||
|
logger = logging.getLogger('olefy')
|
||||||
|
logging.basicConfig(stream=sys.stdout, level=olefy_loglvl, format='olefy %(levelname)s %(funcName)s %(message)s')
|
||||||
|
|
||||||
|
logger.debug('olefy listen address string: {} (type {})'.format(olefy_listen_addr_string, type(olefy_listen_addr_string)))
|
||||||
|
|
||||||
|
if not olefy_listen_addr_string:
|
||||||
|
olefy_listen_addr = ""
|
||||||
|
else:
|
||||||
|
addr_re = re.compile('[\[" \]]')
|
||||||
|
olefy_listen_addr = addr_re.sub('', olefy_listen_addr_string.replace("'", "")).split(',')
|
||||||
|
|
||||||
|
# log runtime variables
|
||||||
|
logger.info('olefy listen address: {} (type: {})'.format(olefy_listen_addr, type(olefy_listen_addr)))
|
||||||
|
logger.info('olefy listen port: {}'.format(olefy_listen_port))
|
||||||
|
logger.info('olefy tmp dir: {}'.format(olefy_tmp_dir))
|
||||||
|
logger.info('olefy python path: {}'.format(olefy_python_path))
|
||||||
|
logger.info('olefy olvba path: {}'.format(olefy_olevba_path))
|
||||||
|
logger.info('olefy log level: {}'.format(olefy_loglvl))
|
||||||
|
logger.info('olefy min file length: {}'.format(olefy_min_length))
|
||||||
|
logger.info('olefy delete tmp file: {}'.format(olefy_del_tmp))
|
||||||
|
logger.info('olefy delete tmp file when failed: {}'.format(olefy_del_tmp_failed))
|
||||||
|
|
||||||
|
if not os.path.isfile(olefy_python_path):
|
||||||
|
logger.critical('python path not found: {}'.format(olefy_python_path))
|
||||||
|
exit(1)
|
||||||
|
if not os.path.isfile(olefy_olevba_path):
|
||||||
|
logger.critical('olevba path not found: {}'.format(olefy_olevba_path))
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# olefy protocol function
|
||||||
|
def protocol_split( olefy_line ):
|
||||||
|
header_lines = olefy_line.split('\n')
|
||||||
|
for line in header_lines:
|
||||||
|
if line == 'OLEFY/1.0':
|
||||||
|
olefy_headers['olefy'] = line
|
||||||
|
elif line != '':
|
||||||
|
kv = line.split(': ')
|
||||||
|
if kv[0] != '' and kv[1] != '':
|
||||||
|
olefy_headers[kv[0]] = kv[1]
|
||||||
|
logger.debug('olefy_headers: {}'.format(olefy_headers))
|
||||||
|
|
||||||
|
# calling oletools
|
||||||
|
def oletools( stream, tmp_file_name, lid ):
|
||||||
|
if olefy_min_length > stream.__len__():
|
||||||
|
logger.error('{} {} bytes (Not Scanning! File smaller than {!r})'.format(lid, stream.__len__(), olefy_min_length))
|
||||||
|
out = b'[ { "error": "File too small" } ]'
|
||||||
|
else:
|
||||||
|
tmp_file = open(tmp_file_name, 'wb')
|
||||||
|
tmp_file.write(stream)
|
||||||
|
tmp_file.close()
|
||||||
|
|
||||||
|
file_magic = magic.Magic(mime=True, uncompress=True)
|
||||||
|
file_mime = file_magic.from_file(tmp_file_name)
|
||||||
|
logger.info('{} {} (libmagic output)'.format(lid, file_mime))
|
||||||
|
|
||||||
|
# do the olefy
|
||||||
|
cmd_tmp = Popen([olefy_python_path, olefy_olevba_path, '-a', '-j' , '-l', 'error', tmp_file_name], stdout=PIPE, stderr=PIPE)
|
||||||
|
out, err = cmd_tmp.communicate()
|
||||||
|
out = bytes(out.decode('utf-8', 'ignore').replace(' ', ' ').replace('\t', '').replace('\n', '').replace('XLMMacroDeobfuscator: pywin32 is not installed (only is required if you want to use MS Excel)', ''), encoding="utf-8")
|
||||||
|
failed = False
|
||||||
|
if out.__len__() < 30:
|
||||||
|
logger.error('{} olevba returned <30 chars - rc: {!r}, response: {!r}, error: {!r}'.format(lid,cmd_tmp.returncode,
|
||||||
|
out.decode('utf-8', 'ignore'), err.decode('utf-8', 'ignore')))
|
||||||
|
out = b'[ { "error": "Unhandled error - too short olevba response" } ]'
|
||||||
|
failed = True
|
||||||
|
elif err.__len__() > 10 and cmd_tmp.returncode == 9:
|
||||||
|
logger.error("{} olevba stderr >10 chars - rc: {!r}, response: {!r}".format(lid, cmd_tmp.returncode, err.decode("utf-8", "ignore")))
|
||||||
|
out = b'[ { "error": "Decrypt failed" } ]'
|
||||||
|
failed = True
|
||||||
|
elif err.__len__() > 10 and cmd_tmp.returncode > 9:
|
||||||
|
logger.error('{} olevba stderr >10 chars - rc: {!r}, response: {!r}'.format(lid, cmd_tmp.returncode, err.decode('utf-8', 'ignore')))
|
||||||
|
out = b'[ { "error": "Unhandled oletools error" } ]'
|
||||||
|
failed = True
|
||||||
|
elif cmd_tmp.returncode != 0:
|
||||||
|
logger.error('{} olevba exited with code {!r}; err: {!r}'.format(lid, cmd_tmp.returncode, err.decode('utf-8', 'ignore')))
|
||||||
|
failed = True
|
||||||
|
|
||||||
|
if failed and olefy_del_tmp_failed == 0:
|
||||||
|
logger.debug('{} {} FAILED: not deleting tmp file'.format(lid, tmp_file_name))
|
||||||
|
elif olefy_del_tmp == 1:
|
||||||
|
logger.debug('{} {} deleting tmp file'.format(lid, tmp_file_name))
|
||||||
|
os.remove(tmp_file_name)
|
||||||
|
|
||||||
|
logger.debug('{} response: {}'.format(lid, out.decode('utf-8', 'ignore')))
|
||||||
|
return out + b'\t\n\n\t'
|
||||||
|
|
||||||
|
# Asyncio data handling, default AIO-Functions
|
||||||
|
class AIO(asyncio.Protocol):
|
||||||
|
def __init__(self):
|
||||||
|
self.extra = bytearray()
|
||||||
|
|
||||||
|
def connection_made(self, transport):
|
||||||
|
global request_time
|
||||||
|
peer = transport.get_extra_info('peername')
|
||||||
|
logger.debug('{} new connection was made'.format(peer))
|
||||||
|
self.transport = transport
|
||||||
|
request_time = str(time.time())
|
||||||
|
|
||||||
|
def data_received(self, request, msgid=1):
|
||||||
|
peer = self.transport.get_extra_info('peername')
|
||||||
|
logger.debug('{} data received from new connection'.format(peer))
|
||||||
|
self.extra.extend(request)
|
||||||
|
|
||||||
|
def eof_received(self):
|
||||||
|
peer = self.transport.get_extra_info('peername')
|
||||||
|
olefy_protocol_err = False
|
||||||
|
proto_ck = self.extra[0:2000].decode('utf-8', 'ignore')
|
||||||
|
|
||||||
|
headers = proto_ck[0:proto_ck.find(olefy_protocol_sep)]
|
||||||
|
|
||||||
|
if olefy_protocol == headers[0:5]:
|
||||||
|
self.extra = bytearray(self.extra[len(headers)+2:len(self.extra)])
|
||||||
|
protocol_split(headers)
|
||||||
|
else:
|
||||||
|
olefy_protocol_err = True
|
||||||
|
|
||||||
|
if olefy_ping == headers[0:4]:
|
||||||
|
is_ping = True
|
||||||
|
else:
|
||||||
|
is_ping = False
|
||||||
|
rspamd_id = olefy_headers['Rspamd-ID'][:6] or ''
|
||||||
|
lid = 'Rspamd-ID' in olefy_headers and '<'+rspamd_id+'>'
|
||||||
|
tmp_file_name = olefy_tmp_dir+'/'+request_time+'.'+str(peer[1])+'.'+rspamd_id
|
||||||
|
logger.debug('{} {} choosen as tmp filename'.format(lid, tmp_file_name))
|
||||||
|
|
||||||
|
if not is_ping or olefy_loglvl == 10:
|
||||||
|
logger.info('{} {} bytes (stream size)'.format(lid, self.extra.__len__()))
|
||||||
|
|
||||||
|
if olefy_ping == headers[0:4]:
|
||||||
|
logger.debug('{} PING request'.format(peer))
|
||||||
|
out = b'PONG'
|
||||||
|
elif olefy_protocol_err == True or olefy_headers['olefy'] != 'OLEFY/1.0':
|
||||||
|
logger.error('{} Protocol ERROR: no OLEFY/1.0 found'.format(lid))
|
||||||
|
out = b'[ { "error": "Protocol error" } ]'
|
||||||
|
elif 'Method' in olefy_headers:
|
||||||
|
if olefy_headers['Method'] == 'oletools':
|
||||||
|
out = oletools(self.extra, tmp_file_name, lid)
|
||||||
|
else:
|
||||||
|
logger.error('Protocol ERROR: Method header not found')
|
||||||
|
out = b'[ { "error": "Protocol error: Method header not found" } ]'
|
||||||
|
|
||||||
|
self.transport.write(out)
|
||||||
|
if not is_ping or olefy_loglvl == 10:
|
||||||
|
logger.info('{} {} response send: {!r}'.format(lid, peer, out))
|
||||||
|
self.transport.close()
|
||||||
|
|
||||||
|
|
||||||
|
# start the listeners
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
# each client connection will create a new protocol instance
|
||||||
|
coro = loop.create_server(AIO, olefy_listen_addr, olefy_listen_port)
|
||||||
|
server = loop.run_until_complete(coro)
|
||||||
|
for sockets in server.sockets:
|
||||||
|
logger.info('serving on {}'.format(sockets.getsockname()))
|
||||||
|
|
||||||
|
# XXX serve requests until KeyboardInterrupt, not needed for production
|
||||||
|
try:
|
||||||
|
loop.run_forever()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# graceful shutdown/reload
|
||||||
|
server.close()
|
||||||
|
loop.run_until_complete(server.wait_closed())
|
||||||
|
loop.close()
|
||||||
|
logger.info('stopped serving')
|
|
@ -1,4 +1,4 @@
|
||||||
@version: 3.19
|
@version: 3.28
|
||||||
@include "scl.conf"
|
@include "scl.conf"
|
||||||
options {
|
options {
|
||||||
chain_hostnames(off);
|
chain_hostnames(off);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
@version: 3.19
|
@version: 3.28
|
||||||
@include "scl.conf"
|
@include "scl.conf"
|
||||||
options {
|
options {
|
||||||
chain_hostnames(off);
|
chain_hostnames(off);
|
||||||
|
|
|
@ -16,10 +16,15 @@ RUN dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \
|
||||||
tzdata \
|
tzdata \
|
||||||
curl \
|
curl \
|
||||||
bash \
|
bash \
|
||||||
|
zip \
|
||||||
&& apt-get autoclean \
|
&& apt-get autoclean \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& chmod +x /solr.sh \
|
&& chmod +x /solr.sh \
|
||||||
&& sync \
|
&& sync \
|
||||||
&& bash /solr.sh --bootstrap
|
&& bash /solr.sh --bootstrap
|
||||||
|
|
||||||
|
RUN zip -q -d /opt/solr/server/lib/ext/log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class
|
||||||
|
|
||||||
|
RUN apt remove zip -y
|
||||||
|
|
||||||
CMD ["/solr.sh"]
|
CMD ["/solr.sh"]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM alpine:3.14
|
FROM alpine:3.15
|
||||||
|
|
||||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM alpine:3.14
|
FROM alpine:3.15
|
||||||
LABEL maintainer "André Peters <andre.peters@servercow.de>"
|
LABEL maintainer "André Peters <andre.peters@servercow.de>"
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
|
@ -16,8 +16,9 @@
|
||||||
<pre>docker-compose logs --tail=200 php-fpm-mailcow nginx-mailcow</pre>
|
<pre>docker-compose logs --tail=200 php-fpm-mailcow nginx-mailcow</pre>
|
||||||
<p>Make sure your SQL credentials in mailcow.conf (a link to .env) do fit your initialized SQL volume. If you see an access denied, you might have the wrong mailcow.conf:</p>
|
<p>Make sure your SQL credentials in mailcow.conf (a link to .env) do fit your initialized SQL volume. If you see an access denied, you might have the wrong mailcow.conf:</p>
|
||||||
<pre>source mailcow.conf ; docker-compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME}</pre>
|
<pre>source mailcow.conf ; docker-compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME}</pre>
|
||||||
<p>In case of a previous failed installation, remove all volumes and start over (<b>NEVER</b> do this with a production system, it will remove <b>ALL</b> data):</p>
|
<p>In case of a previous failed installation, create a backup of your existing data, followed by removing all volumes and starting over (<b>NEVER</b> do this with a production system, it will remove <b>ALL</b> data):</p>
|
||||||
<pre>docker-compose down -v ; docker-compose up -d</pre>
|
<pre>BACKUP_LOCATION=/tmp/ ./helper-scripts/backup_and_restore.sh backup all</pre>
|
||||||
|
<pre>docker-compose down --volumes ; docker-compose up -d</pre>
|
||||||
<p>Make sure your timezone is correct. Use "America/New_York" for example, do not use spaces. Check <a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">here</a> for a list.</p>
|
<p>Make sure your timezone is correct. Use "America/New_York" for example, do not use spaces. Check <a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">here</a> for a list.</p>
|
||||||
<br>Click to learn more about <a style="color:red;text-decoration:none;" href="https://mailcow.github.io/mailcow-dockerized-docs/#get-support" target="_blank">getting support.</a>
|
<br>Click to learn more about <a style="color:red;text-decoration:none;" href="https://mailcow.github.io/mailcow-dockerized-docs/#get-support" target="_blank">getting support.</a>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -17,7 +17,8 @@ if (is_array($alertbox_log_parser)) {
|
||||||
}
|
}
|
||||||
$alert = array_filter(array_unique($alerts));
|
$alert = array_filter(array_unique($alerts));
|
||||||
foreach($alert as $alert_type => $alert_msg) {
|
foreach($alert as $alert_type => $alert_msg) {
|
||||||
$alerts[$alert_type] = implode('<hr class="alert-hr">', $alert_msg);
|
// html breaks from mysql alerts, replace ` with '
|
||||||
|
$alerts[$alert_type] = implode('<hr class="alert-hr">', str_replace("`", "'", $alert_msg));
|
||||||
}
|
}
|
||||||
unset($_SESSION['return']);
|
unset($_SESSION['return']);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1140,7 +1140,6 @@ function is_valid_domain_name($domain_name) {
|
||||||
function set_tfa($_data) {
|
function set_tfa($_data) {
|
||||||
global $pdo;
|
global $pdo;
|
||||||
global $yubi;
|
global $yubi;
|
||||||
global $u2f;
|
|
||||||
global $tfa;
|
global $tfa;
|
||||||
$_data_log = $_data;
|
$_data_log = $_data;
|
||||||
!isset($_data_log['confirm_password']) ?: $_data_log['confirm_password'] = '*';
|
!isset($_data_log['confirm_password']) ?: $_data_log['confirm_password'] = '*';
|
||||||
|
@ -1183,6 +1182,8 @@ function set_tfa($_data) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
switch ($_data["tfa_method"]) {
|
switch ($_data["tfa_method"]) {
|
||||||
case "yubi_otp":
|
case "yubi_otp":
|
||||||
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
|
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
|
||||||
|
@ -1240,31 +1241,6 @@ function set_tfa($_data) {
|
||||||
'msg' => array('object_modified', htmlspecialchars($username))
|
'msg' => array('object_modified', htmlspecialchars($username))
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "u2f":
|
|
||||||
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
|
|
||||||
try {
|
|
||||||
$reg = $u2f->doRegister(json_decode($_SESSION['regReq']), json_decode($_data['token']));
|
|
||||||
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `authmech` != 'u2f'");
|
|
||||||
$stmt->execute(array(':username' => $username));
|
|
||||||
$stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`) VALUES (?, ?, 'u2f', ?, ?, ?, ?, '1')");
|
|
||||||
$stmt->execute(array($username, $key_id, $reg->keyHandle, $reg->publicKey, $reg->certificate, $reg->counter));
|
|
||||||
$_SESSION['return'][] = array(
|
|
||||||
'type' => 'success',
|
|
||||||
'log' => array(__FUNCTION__, $_data_log),
|
|
||||||
'msg' => array('object_modified', $username)
|
|
||||||
);
|
|
||||||
$_SESSION['regReq'] = null;
|
|
||||||
}
|
|
||||||
catch (Exception $e) {
|
|
||||||
$_SESSION['return'][] = array(
|
|
||||||
'type' => 'danger',
|
|
||||||
'log' => array(__FUNCTION__, $_data_log),
|
|
||||||
'msg' => array('u2f_verification_failed', $e->getMessage())
|
|
||||||
);
|
|
||||||
$_SESSION['regReq'] = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "totp":
|
case "totp":
|
||||||
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
|
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
|
||||||
if ($tfa->verifyCode($_POST['totp_secret'], $_POST['totp_confirm_token']) === true) {
|
if ($tfa->verifyCode($_POST['totp_secret'], $_POST['totp_confirm_token']) === true) {
|
||||||
|
@ -1286,6 +1262,29 @@ function set_tfa($_data) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "webauthn":
|
||||||
|
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `authmech` != 'webauthn'");
|
||||||
|
$stmt->execute(array(':username' => $username));
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`)
|
||||||
|
VALUES (?, ?, 'webauthn', ?, ?, ?, ?, '1')");
|
||||||
|
$stmt->execute(array(
|
||||||
|
$username,
|
||||||
|
$key_id,
|
||||||
|
base64_encode($_data['registration']->credentialId),
|
||||||
|
$_data['registration']->credentialPublicKey,
|
||||||
|
$_data['registration']->certificate,
|
||||||
|
0
|
||||||
|
));
|
||||||
|
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'success',
|
||||||
|
'log' => array(__FUNCTION__, $_data_log),
|
||||||
|
'msg' => array('object_modified', $username)
|
||||||
|
);
|
||||||
|
break;
|
||||||
case "none":
|
case "none":
|
||||||
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username");
|
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username");
|
||||||
$stmt->execute(array(':username' => $username));
|
$stmt->execute(array(':username' => $username));
|
||||||
|
@ -1360,8 +1359,8 @@ function fido2($_data) {
|
||||||
if (!isset($_data['cid']) || empty($_data['cid'])) {
|
if (!isset($_data['cid']) || empty($_data['cid'])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$stmt = $pdo->prepare("SELECT `certificateSubject`, `username`, `credentialPublicKey`, SHA2(`credentialId`, 256) AS `cid` FROM `fido2` WHERE TO_BASE64(`credentialId`) = :cid");
|
$stmt = $pdo->prepare("SELECT `certificateSubject`, `username`, `credentialPublicKey`, SHA2(`credentialId`, 256) AS `cid` FROM `fido2` WHERE `credentialId` = :cid");
|
||||||
$stmt->execute(array(':cid' => $_data['cid']));
|
$stmt->execute(array(':cid' => base64_decode($_data['cid'])));
|
||||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
if (empty($row) || empty($row['credentialPublicKey']) || empty($row['username'])) {
|
if (empty($row) || empty($row['credentialPublicKey']) || empty($row['username'])) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -1516,6 +1515,7 @@ function get_tfa($username = null) {
|
||||||
}
|
}
|
||||||
return $data;
|
return $data;
|
||||||
break;
|
break;
|
||||||
|
// u2f - deprecated, should be removed
|
||||||
case "u2f":
|
case "u2f":
|
||||||
$data['name'] = "u2f";
|
$data['name'] = "u2f";
|
||||||
$data['pretty'] = "Fido U2F";
|
$data['pretty'] = "Fido U2F";
|
||||||
|
@ -1547,6 +1547,19 @@ function get_tfa($username = null) {
|
||||||
}
|
}
|
||||||
return $data;
|
return $data;
|
||||||
break;
|
break;
|
||||||
|
case "webauthn":
|
||||||
|
$data['name'] = "webauthn";
|
||||||
|
$data['pretty'] = "WebAuthn";
|
||||||
|
$stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'webauthn' AND `username` = :username");
|
||||||
|
$stmt->execute(array(
|
||||||
|
':username' => $username,
|
||||||
|
));
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
while($row = array_shift($rows)) {
|
||||||
|
$data['additional'][] = $row;
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
$data['name'] = 'none';
|
$data['name'] = 'none';
|
||||||
$data['pretty'] = "-";
|
$data['pretty'] = "-";
|
||||||
|
@ -1560,7 +1573,7 @@ function get_tfa($username = null) {
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function verify_tfa_login($username, $token) {
|
function verify_tfa_login($username, $_data, $WebAuthn) {
|
||||||
global $pdo;
|
global $pdo;
|
||||||
global $yubi;
|
global $yubi;
|
||||||
global $u2f;
|
global $u2f;
|
||||||
|
@ -1572,7 +1585,7 @@ function verify_tfa_login($username, $token) {
|
||||||
|
|
||||||
switch ($row["authmech"]) {
|
switch ($row["authmech"]) {
|
||||||
case "yubi_otp":
|
case "yubi_otp":
|
||||||
if (!ctype_alnum($token) || strlen($token) != 44) {
|
if (!ctype_alnum($_data['token']) || strlen($_data['token']) != 44) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $username, '*'),
|
'log' => array(__FUNCTION__, $username, '*'),
|
||||||
|
@ -1580,7 +1593,7 @@ function verify_tfa_login($username, $token) {
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$yubico_modhex_id = substr($token, 0, 12);
|
$yubico_modhex_id = substr($_data['token'], 0, 12);
|
||||||
$stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa`
|
$stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa`
|
||||||
WHERE `username` = :username
|
WHERE `username` = :username
|
||||||
AND `authmech` = 'yubi_otp'
|
AND `authmech` = 'yubi_otp'
|
||||||
|
@ -1590,7 +1603,7 @@ function verify_tfa_login($username, $token) {
|
||||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
$yubico_auth = explode(':', $row['secret']);
|
$yubico_auth = explode(':', $row['secret']);
|
||||||
$yubi = new Auth_Yubico($yubico_auth[0], $yubico_auth[1]);
|
$yubi = new Auth_Yubico($yubico_auth[0], $yubico_auth[1]);
|
||||||
$yauth = $yubi->verify($token);
|
$yauth = $yubi->verify($_data['token']);
|
||||||
if (PEAR::isError($yauth)) {
|
if (PEAR::isError($yauth)) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
|
@ -1615,37 +1628,6 @@ function verify_tfa_login($username, $token) {
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
case "u2f":
|
|
||||||
try {
|
|
||||||
$reg = $u2f->doAuthenticate(json_decode($_SESSION['authReq']), get_u2f_registrations($username), json_decode($token));
|
|
||||||
$stmt = $pdo->prepare("SELECT `id` FROM `tfa` WHERE `keyHandle` = ?");
|
|
||||||
$stmt->execute(array($reg->keyHandle));
|
|
||||||
$row_key_id = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
$_SESSION['tfa_id'] = $row_key_id['id'];
|
|
||||||
$_SESSION['authReq'] = null;
|
|
||||||
$_SESSION['return'][] = array(
|
|
||||||
'type' => 'success',
|
|
||||||
'log' => array(__FUNCTION__, $username, '*'),
|
|
||||||
'msg' => 'verified_u2f_login'
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception $e) {
|
|
||||||
$_SESSION['return'][] = array(
|
|
||||||
'type' => 'danger',
|
|
||||||
'log' => array(__FUNCTION__, $username, '*'),
|
|
||||||
'msg' => array('u2f_verification_failed', $e->getMessage())
|
|
||||||
);
|
|
||||||
$_SESSION['regReq'] = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$_SESSION['return'][] = array(
|
|
||||||
'type' => 'danger',
|
|
||||||
'log' => array(__FUNCTION__, $username, '*'),
|
|
||||||
'msg' => array('u2f_verification_failed', 'unknown')
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
case "hotp":
|
case "hotp":
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
|
@ -1658,7 +1640,7 @@ function verify_tfa_login($username, $token) {
|
||||||
$stmt->execute(array(':username' => $username));
|
$stmt->execute(array(':username' => $username));
|
||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
foreach ($rows as $row) {
|
foreach ($rows as $row) {
|
||||||
if ($tfa->verifyCode($row['secret'], $_POST['token']) === true) {
|
if ($tfa->verifyCode($row['secret'], $_data['token']) === true) {
|
||||||
$_SESSION['tfa_id'] = $row['id'];
|
$_SESSION['tfa_id'] = $row['id'];
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
|
@ -1684,6 +1666,88 @@ function verify_tfa_login($username, $token) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
// u2f - deprecated, should be removed
|
||||||
|
case "u2f":
|
||||||
|
// delete old keys that used u2f
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `authmech` = :authmech AND `username` = :username");
|
||||||
|
$stmt->execute(array(':authmech' => 'u2f', ':username' => $username));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
case "webauthn":
|
||||||
|
$tokenData = json_decode($_data['token']);
|
||||||
|
$clientDataJSON = base64_decode($tokenData->clientDataJSON);
|
||||||
|
$authenticatorData = base64_decode($tokenData->authenticatorData);
|
||||||
|
$signature = base64_decode($tokenData->signature);
|
||||||
|
$id = base64_decode($tokenData->id);
|
||||||
|
$challenge = $_SESSION['challenge'];
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT `key_id`, `keyHandle`, `username`, `publicKey` FROM `tfa` WHERE `keyHandle` = :tokenId");
|
||||||
|
$stmt->execute(array(':tokenId' => $tokenData->id));
|
||||||
|
$process_webauthn = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (empty($process_webauthn) || empty($process_webauthn['publicKey']) || empty($process_webauthn['username'])) return false;
|
||||||
|
|
||||||
|
if ($process_webauthn['publicKey'] === false) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $username, '*'),
|
||||||
|
'msg' => array('webauthn_verification_failed', 'publicKey not found')
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$WebAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $process_webauthn['publicKey'], $challenge, null, $GLOBALS['WEBAUTHN_UV_FLAG_LOGIN'], $GLOBALS['WEBAUTHN_USER_PRESENT_FLAG']);
|
||||||
|
}
|
||||||
|
catch (Throwable $ex) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $username, '*'),
|
||||||
|
'msg' => array('webauthn_verification_failed', $ex->getMessage())
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT `superadmin` FROM `admin` WHERE `username` = :username");
|
||||||
|
$stmt->execute(array(':username' => $process_webauthn['username']));
|
||||||
|
$obj_props = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if ($obj_props['superadmin'] === 1) {
|
||||||
|
$_SESSION["mailcow_cc_role"] = "admin";
|
||||||
|
}
|
||||||
|
elseif ($obj_props['superadmin'] === 0) {
|
||||||
|
$_SESSION["mailcow_cc_role"] = "domainadmin";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :username");
|
||||||
|
$stmt->execute(array(':username' => $process_webauthn['username']));
|
||||||
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if ($row['username'] == $process_webauthn['username']) {
|
||||||
|
$_SESSION["mailcow_cc_role"] = "user";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ($process_webauthn['username'] != $_SESSION['pending_mailcow_cc_username']){
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $username, '*'),
|
||||||
|
'msg' => array('webauthn_verification_failed', 'user who requests does not match with sql entry')
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$_SESSION["mailcow_cc_username"] = $process_webauthn['username'];
|
||||||
|
$_SESSION['tfa_id'] = $process_webauthn['key_id'];
|
||||||
|
$_SESSION['authReq'] = null;
|
||||||
|
unset($_SESSION["challenge"]);
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'success',
|
||||||
|
'log' => array("webauthn_login"),
|
||||||
|
'msg' => array('logged_in_as', $process_webauthn['username'])
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
|
@ -1693,6 +1757,7 @@ function verify_tfa_login($username, $token) {
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
function admin_api($access, $action, $data = null) {
|
function admin_api($access, $action, $data = null) {
|
||||||
|
@ -1955,12 +2020,7 @@ function rspamd_ui($action, $data = null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function get_u2f_registrations($username) {
|
|
||||||
global $pdo;
|
|
||||||
$sel = $pdo->prepare("SELECT * FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = ? AND `active` = '1'");
|
|
||||||
$sel->execute(array($username));
|
|
||||||
return $sel->fetchAll(PDO::FETCH_OBJ);
|
|
||||||
}
|
|
||||||
function get_logs($application, $lines = false) {
|
function get_logs($application, $lines = false) {
|
||||||
if ($lines === false) {
|
if ($lines === false) {
|
||||||
$lines = $GLOBALS['LOG_LINES'] - 1;
|
$lines = $GLOBALS['LOG_LINES'] - 1;
|
||||||
|
|
|
@ -3,7 +3,7 @@ function init_db_schema() {
|
||||||
try {
|
try {
|
||||||
global $pdo;
|
global $pdo;
|
||||||
|
|
||||||
$db_version = "31102021_0620";
|
$db_version = "18012022_1020";
|
||||||
|
|
||||||
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
|
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
|
||||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||||
|
@ -696,7 +696,7 @@ function init_db_schema() {
|
||||||
"id" => "INT NOT NULL AUTO_INCREMENT",
|
"id" => "INT NOT NULL AUTO_INCREMENT",
|
||||||
"key_id" => "VARCHAR(255) NOT NULL",
|
"key_id" => "VARCHAR(255) NOT NULL",
|
||||||
"username" => "VARCHAR(255) NOT NULL",
|
"username" => "VARCHAR(255) NOT NULL",
|
||||||
"authmech" => "ENUM('yubi_otp', 'u2f', 'hotp', 'totp')",
|
"authmech" => "ENUM('yubi_otp', 'u2f', 'hotp', 'totp', 'webauthn')",
|
||||||
"secret" => "VARCHAR(255) DEFAULT NULL",
|
"secret" => "VARCHAR(255) DEFAULT NULL",
|
||||||
"keyHandle" => "VARCHAR(255) DEFAULT NULL",
|
"keyHandle" => "VARCHAR(255) DEFAULT NULL",
|
||||||
"publicKey" => "VARCHAR(255) DEFAULT NULL",
|
"publicKey" => "VARCHAR(255) DEFAULT NULL",
|
||||||
|
@ -1190,6 +1190,9 @@ function init_db_schema() {
|
||||||
// Mitigate imapsync pipemess issue
|
// Mitigate imapsync pipemess issue
|
||||||
$pdo->query("UPDATE `imapsync` SET `custom_params` = '' WHERE `custom_params` LIKE '%pipemess%';");
|
$pdo->query("UPDATE `imapsync` SET `custom_params` = '' WHERE `custom_params` LIKE '%pipemess%';");
|
||||||
|
|
||||||
|
// Migrate webauthn tfa
|
||||||
|
$stmt = $pdo->query("ALTER TABLE `tfa` MODIFY COLUMN `authmech` ENUM('yubi_otp', 'u2f', 'hotp', 'totp', 'webauthn')");
|
||||||
|
|
||||||
// Inject admin if not exists
|
// Inject admin if not exists
|
||||||
$stmt = $pdo->query("SELECT NULL FROM `admin`");
|
$stmt = $pdo->query("SELECT NULL FROM `admin`");
|
||||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace WebAuthn\Attestation;
|
namespace lbuchs\WebAuthn\Attestation;
|
||||||
use WebAuthn\WebAuthnException;
|
use lbuchs\WebAuthn\WebAuthnException;
|
||||||
use WebAuthn\CBOR\CborDecoder;
|
use lbuchs\WebAuthn\CBOR\CborDecoder;
|
||||||
use WebAuthn\Binary\ByteBuffer;
|
use lbuchs\WebAuthn\Binary\ByteBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Lukas Buchs
|
* @author Lukas Buchs
|
||||||
|
@ -12,6 +12,7 @@ use WebAuthn\Binary\ByteBuffer;
|
||||||
class AttestationObject {
|
class AttestationObject {
|
||||||
private $_authenticatorData;
|
private $_authenticatorData;
|
||||||
private $_attestationFormat;
|
private $_attestationFormat;
|
||||||
|
private $_attestationFormatName;
|
||||||
|
|
||||||
public function __construct($binary , $allowedFormats) {
|
public function __construct($binary , $allowedFormats) {
|
||||||
$enc = CborDecoder::decode($binary);
|
$enc = CborDecoder::decode($binary);
|
||||||
|
@ -29,13 +30,15 @@ class AttestationObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->_authenticatorData = new AuthenticatorData($enc['authData']->getBinaryString());
|
$this->_authenticatorData = new AuthenticatorData($enc['authData']->getBinaryString());
|
||||||
|
$this->_attestationFormatName = $enc['fmt'];
|
||||||
|
|
||||||
// Format ok?
|
// Format ok?
|
||||||
if (!in_array($enc['fmt'], $allowedFormats)) {
|
if (!in_array($this->_attestationFormatName, $allowedFormats)) {
|
||||||
throw new WebAuthnException('invalid atttestation format: ' . $enc['fmt'], WebAuthnException::INVALID_DATA);
|
throw new WebAuthnException('invalid atttestation format: ' . $this->_attestationFormatName, WebAuthnException::INVALID_DATA);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ($enc['fmt']) {
|
|
||||||
|
switch ($this->_attestationFormatName) {
|
||||||
case 'android-key': $this->_attestationFormat = new Format\AndroidKey($enc, $this->_authenticatorData); break;
|
case 'android-key': $this->_attestationFormat = new Format\AndroidKey($enc, $this->_authenticatorData); break;
|
||||||
case 'android-safetynet': $this->_attestationFormat = new Format\AndroidSafetyNet($enc, $this->_authenticatorData); break;
|
case 'android-safetynet': $this->_attestationFormat = new Format\AndroidSafetyNet($enc, $this->_authenticatorData); break;
|
||||||
case 'apple': $this->_attestationFormat = new Format\Apple($enc, $this->_authenticatorData); break;
|
case 'apple': $this->_attestationFormat = new Format\Apple($enc, $this->_authenticatorData); break;
|
||||||
|
@ -47,6 +50,14 @@ class AttestationObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the attestation format name
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getAttestationFormatName() {
|
||||||
|
return $this->_attestationFormatName;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns the attestation public key in PEM format
|
* returns the attestation public key in PEM format
|
||||||
* @return AuthenticatorData
|
* @return AuthenticatorData
|
||||||
|
@ -72,16 +83,19 @@ class AttestationObject {
|
||||||
$issuer = '';
|
$issuer = '';
|
||||||
if ($pem) {
|
if ($pem) {
|
||||||
$certInfo = \openssl_x509_parse($pem);
|
$certInfo = \openssl_x509_parse($pem);
|
||||||
if (\is_array($certInfo) && \is_array($certInfo['issuer'])) {
|
if (\is_array($certInfo) && \array_key_exists('issuer', $certInfo) && \is_array($certInfo['issuer'])) {
|
||||||
if ($certInfo['issuer']['CN']) {
|
|
||||||
$issuer .= \trim($certInfo['issuer']['CN']);
|
$cn = $certInfo['issuer']['CN'] ?? '';
|
||||||
|
$o = $certInfo['issuer']['O'] ?? '';
|
||||||
|
$ou = $certInfo['issuer']['OU'] ?? '';
|
||||||
|
|
||||||
|
if ($cn) {
|
||||||
|
$issuer .= $cn;
|
||||||
}
|
}
|
||||||
if ($certInfo['issuer']['O'] || $certInfo['issuer']['OU']) {
|
if ($issuer && ($o || $ou)) {
|
||||||
if ($issuer) {
|
$issuer .= ' (' . trim($o . ' ' . $ou) . ')';
|
||||||
$issuer .= ' (' . \trim($certInfo['issuer']['O'] . ' ' . $certInfo['issuer']['OU']) . ')';
|
|
||||||
} else {
|
} else {
|
||||||
$issuer .= \trim($certInfo['issuer']['O'] . ' ' . $certInfo['issuer']['OU']);
|
$issuer .= trim($o . ' ' . $ou);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,16 +112,19 @@ class AttestationObject {
|
||||||
$subject = '';
|
$subject = '';
|
||||||
if ($pem) {
|
if ($pem) {
|
||||||
$certInfo = \openssl_x509_parse($pem);
|
$certInfo = \openssl_x509_parse($pem);
|
||||||
if (\is_array($certInfo) && \is_array($certInfo['subject'])) {
|
if (\is_array($certInfo) && \array_key_exists('subject', $certInfo) && \is_array($certInfo['subject'])) {
|
||||||
if ($certInfo['subject']['CN']) {
|
|
||||||
$subject .= \trim($certInfo['subject']['CN']);
|
$cn = $certInfo['subject']['CN'] ?? '';
|
||||||
|
$o = $certInfo['subject']['O'] ?? '';
|
||||||
|
$ou = $certInfo['subject']['OU'] ?? '';
|
||||||
|
|
||||||
|
if ($cn) {
|
||||||
|
$subject .= $cn;
|
||||||
}
|
}
|
||||||
if ($certInfo['subject']['O'] || $certInfo['subject']['OU']) {
|
if ($subject && ($o || $ou)) {
|
||||||
if ($subject) {
|
$subject .= ' (' . trim($o . ' ' . $ou) . ')';
|
||||||
$subject .= ' (' . \trim($certInfo['subject']['O'] . ' ' . $certInfo['subject']['OU']) . ')';
|
|
||||||
} else {
|
} else {
|
||||||
$subject .= \trim($certInfo['subject']['O'] . ' ' . $certInfo['subject']['OU']);
|
$subject .= trim($o . ' ' . $ou);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace WebAuthn\Attestation;
|
namespace lbuchs\WebAuthn\Attestation;
|
||||||
use WebAuthn\WebAuthnException;
|
use lbuchs\WebAuthn\WebAuthnException;
|
||||||
use WebAuthn\CBOR\CborDecoder;
|
use lbuchs\WebAuthn\CBOR\CborDecoder;
|
||||||
use WebAuthn\Binary\ByteBuffer;
|
use lbuchs\WebAuthn\Binary\ByteBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Lukas Buchs
|
* @author Lukas Buchs
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace WebAuthn\Attestation\Format;
|
namespace lbuchs\WebAuthn\Attestation\Format;
|
||||||
use WebAuthn\WebAuthnException;
|
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
|
||||||
use WebAuthn\Binary\ByteBuffer;
|
use lbuchs\WebAuthn\WebAuthnException;
|
||||||
|
use lbuchs\WebAuthn\Binary\ByteBuffer;
|
||||||
|
|
||||||
class AndroidKey extends FormatBase {
|
class AndroidKey extends FormatBase {
|
||||||
private $_alg;
|
private $_alg;
|
||||||
private $_signature;
|
private $_signature;
|
||||||
private $_x5c;
|
private $_x5c;
|
||||||
|
|
||||||
public function __construct($AttestionObject, \WebAuthn\Attestation\AuthenticatorData $authenticatorData) {
|
public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
|
||||||
parent::__construct($AttestionObject, $authenticatorData);
|
parent::__construct($AttestionObject, $authenticatorData);
|
||||||
|
|
||||||
// check u2f data
|
// check u2f data
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
|
||||||
namespace WebAuthn\Attestation\Format;
|
namespace lbuchs\WebAuthn\Attestation\Format;
|
||||||
use WebAuthn\WebAuthnException;
|
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
|
||||||
use WebAuthn\Binary\ByteBuffer;
|
use lbuchs\WebAuthn\WebAuthnException;
|
||||||
|
use lbuchs\WebAuthn\Binary\ByteBuffer;
|
||||||
|
|
||||||
class AndroidSafetyNet extends FormatBase {
|
class AndroidSafetyNet extends FormatBase {
|
||||||
private $_signature;
|
private $_signature;
|
||||||
|
@ -11,7 +12,7 @@ class AndroidSafetyNet extends FormatBase {
|
||||||
private $_x5c;
|
private $_x5c;
|
||||||
private $_payload;
|
private $_payload;
|
||||||
|
|
||||||
public function __construct($AttestionObject, \WebAuthn\Attestation\AuthenticatorData $authenticatorData) {
|
public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
|
||||||
parent::__construct($AttestionObject, $authenticatorData);
|
parent::__construct($AttestionObject, $authenticatorData);
|
||||||
|
|
||||||
// check data
|
// check data
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
|
||||||
namespace WebAuthn\Attestation\Format;
|
namespace lbuchs\WebAuthn\Attestation\Format;
|
||||||
use WebAuthn\WebAuthnException;
|
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
|
||||||
use WebAuthn\Binary\ByteBuffer;
|
use lbuchs\WebAuthn\WebAuthnException;
|
||||||
|
use lbuchs\WebAuthn\Binary\ByteBuffer;
|
||||||
|
|
||||||
class Apple extends FormatBase {
|
class Apple extends FormatBase {
|
||||||
private $_x5c;
|
private $_x5c;
|
||||||
|
|
||||||
public function __construct($AttestionObject, \WebAuthn\Attestation\AuthenticatorData $authenticatorData) {
|
public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
|
||||||
parent::__construct($AttestionObject, $authenticatorData);
|
parent::__construct($AttestionObject, $authenticatorData);
|
||||||
|
|
||||||
// check packed data
|
// check packed data
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
|
||||||
namespace WebAuthn\Attestation\Format;
|
namespace lbuchs\WebAuthn\Attestation\Format;
|
||||||
use WebAuthn\WebAuthnException;
|
use lbuchs\WebAuthn\WebAuthnException;
|
||||||
|
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
|
||||||
|
|
||||||
|
|
||||||
abstract class FormatBase {
|
abstract class FormatBase {
|
||||||
|
@ -14,9 +15,9 @@ abstract class FormatBase {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param Array $AttestionObject
|
* @param Array $AttestionObject
|
||||||
* @param \WebAuthn\Attestation\AuthenticatorData $authenticatorData
|
* @param AuthenticatorData $authenticatorData
|
||||||
*/
|
*/
|
||||||
public function __construct($AttestionObject, \WebAuthn\Attestation\AuthenticatorData $authenticatorData) {
|
public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
|
||||||
$this->_attestationObject = $AttestionObject;
|
$this->_attestationObject = $AttestionObject;
|
||||||
$this->_authenticatorData = $authenticatorData;
|
$this->_authenticatorData = $authenticatorData;
|
||||||
}
|
}
|
||||||
|
@ -26,7 +27,7 @@ abstract class FormatBase {
|
||||||
*/
|
*/
|
||||||
public function __destruct() {
|
public function __destruct() {
|
||||||
// delete X.509 chain certificate file after use
|
// delete X.509 chain certificate file after use
|
||||||
if (\is_file($this->_x5c_tempFile)) {
|
if ($this->_x5c_tempFile && \is_file($this->_x5c_tempFile)) {
|
||||||
\unlink($this->_x5c_tempFile);
|
\unlink($this->_x5c_tempFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +37,7 @@ abstract class FormatBase {
|
||||||
* @return string|null
|
* @return string|null
|
||||||
*/
|
*/
|
||||||
public function getCertificateChain() {
|
public function getCertificateChain() {
|
||||||
if (\is_file($this->_x5c_tempFile)) {
|
if ($this->_x5c_tempFile && \is_file($this->_x5c_tempFile)) {
|
||||||
return \file_get_contents($this->_x5c_tempFile);
|
return \file_get_contents($this->_x5c_tempFile);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
|
||||||
namespace WebAuthn\Attestation\Format;
|
namespace lbuchs\WebAuthn\Attestation\Format;
|
||||||
use WebAuthn\WebAuthnException;
|
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
|
||||||
|
use lbuchs\WebAuthn\WebAuthnException;
|
||||||
|
|
||||||
class None extends FormatBase {
|
class None extends FormatBase {
|
||||||
|
|
||||||
|
|
||||||
public function __construct($AttestionObject, \WebAuthn\Attestation\AuthenticatorData $authenticatorData) {
|
public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
|
||||||
parent::__construct($AttestionObject, $authenticatorData);
|
parent::__construct($AttestionObject, $authenticatorData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,12 +29,13 @@ class None extends FormatBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* validates the certificate against root certificates
|
* validates the certificate against root certificates.
|
||||||
|
* Format 'none' does not contain any ca, so always false.
|
||||||
* @param array $rootCas
|
* @param array $rootCas
|
||||||
* @return boolean
|
* @return boolean
|
||||||
* @throws WebAuthnException
|
* @throws WebAuthnException
|
||||||
*/
|
*/
|
||||||
public function validateRootCertificate($rootCas) {
|
public function validateRootCertificate($rootCas) {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
|
||||||
namespace WebAuthn\Attestation\Format;
|
namespace lbuchs\WebAuthn\Attestation\Format;
|
||||||
use WebAuthn\WebAuthnException;
|
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
|
||||||
use WebAuthn\Binary\ByteBuffer;
|
use lbuchs\WebAuthn\WebAuthnException;
|
||||||
|
use lbuchs\WebAuthn\Binary\ByteBuffer;
|
||||||
|
|
||||||
class Packed extends FormatBase {
|
class Packed extends FormatBase {
|
||||||
private $_alg;
|
private $_alg;
|
||||||
private $_signature;
|
private $_signature;
|
||||||
private $_x5c;
|
private $_x5c;
|
||||||
|
|
||||||
public function __construct($AttestionObject, \WebAuthn\Attestation\AuthenticatorData $authenticatorData) {
|
public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
|
||||||
parent::__construct($AttestionObject, $authenticatorData);
|
parent::__construct($AttestionObject, $authenticatorData);
|
||||||
|
|
||||||
// check packed data
|
// check packed data
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
|
||||||
namespace WebAuthn\Attestation\Format;
|
namespace lbuchs\WebAuthn\Attestation\Format;
|
||||||
use WebAuthn\WebAuthnException;
|
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
|
||||||
use WebAuthn\Binary\ByteBuffer;
|
use lbuchs\WebAuthn\WebAuthnException;
|
||||||
|
use lbuchs\WebAuthn\Binary\ByteBuffer;
|
||||||
|
|
||||||
class Tpm extends FormatBase {
|
class Tpm extends FormatBase {
|
||||||
private $_TPM_GENERATED_VALUE = "\xFF\x54\x43\x47";
|
private $_TPM_GENERATED_VALUE = "\xFF\x54\x43\x47";
|
||||||
|
@ -19,7 +20,7 @@ class Tpm extends FormatBase {
|
||||||
private $_certInfo;
|
private $_certInfo;
|
||||||
|
|
||||||
|
|
||||||
public function __construct($AttestionObject, \WebAuthn\Attestation\AuthenticatorData $authenticatorData) {
|
public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
|
||||||
parent::__construct($AttestionObject, $authenticatorData);
|
parent::__construct($AttestionObject, $authenticatorData);
|
||||||
|
|
||||||
// check packed data
|
// check packed data
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
|
||||||
namespace WebAuthn\Attestation\Format;
|
namespace lbuchs\WebAuthn\Attestation\Format;
|
||||||
use WebAuthn\WebAuthnException;
|
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
|
||||||
use WebAuthn\Binary\ByteBuffer;
|
use lbuchs\WebAuthn\WebAuthnException;
|
||||||
|
use lbuchs\WebAuthn\Binary\ByteBuffer;
|
||||||
|
|
||||||
class U2f extends FormatBase {
|
class U2f extends FormatBase {
|
||||||
private $_alg = -7;
|
private $_alg = -7;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
|
||||||
namespace WebAuthn\Binary;
|
namespace lbuchs\WebAuthn\Binary;
|
||||||
use WebAuthn\WebAuthnException;
|
use lbuchs\WebAuthn\WebAuthnException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modified version of https://github.com/madwizard-thomas/webauthn-server/blob/master/src/Format/ByteBuffer.php
|
* Modified version of https://github.com/madwizard-thomas/webauthn-server/blob/master/src/Format/ByteBuffer.php
|
||||||
|
@ -39,7 +39,7 @@ class ByteBuffer implements \JsonSerializable, \Serializable {
|
||||||
/**
|
/**
|
||||||
* create a ByteBuffer from a base64 url encoded string
|
* create a ByteBuffer from a base64 url encoded string
|
||||||
* @param string $base64url
|
* @param string $base64url
|
||||||
* @return \WebAuthn\Binary\ByteBuffer
|
* @return ByteBuffer
|
||||||
*/
|
*/
|
||||||
public static function fromBase64Url($base64url) {
|
public static function fromBase64Url($base64url) {
|
||||||
$bin = self::_base64url_decode($base64url);
|
$bin = self::_base64url_decode($base64url);
|
||||||
|
@ -52,7 +52,7 @@ class ByteBuffer implements \JsonSerializable, \Serializable {
|
||||||
/**
|
/**
|
||||||
* create a ByteBuffer from a base64 url encoded string
|
* create a ByteBuffer from a base64 url encoded string
|
||||||
* @param string $hex
|
* @param string $hex
|
||||||
* @return \WebAuthn\Binary\ByteBuffer
|
* @return ByteBuffer
|
||||||
*/
|
*/
|
||||||
public static function fromHex($hex) {
|
public static function fromHex($hex) {
|
||||||
$bin = \hex2bin($hex);
|
$bin = \hex2bin($hex);
|
||||||
|
@ -65,7 +65,7 @@ class ByteBuffer implements \JsonSerializable, \Serializable {
|
||||||
/**
|
/**
|
||||||
* create a random ByteBuffer
|
* create a random ByteBuffer
|
||||||
* @param string $length
|
* @param string $length
|
||||||
* @return \WebAuthn\Binary\ByteBuffer
|
* @return ByteBuffer
|
||||||
*/
|
*/
|
||||||
public static function randomBuffer($length) {
|
public static function randomBuffer($length) {
|
||||||
if (\function_exists('random_bytes')) { // >PHP 7.0
|
if (\function_exists('random_bytes')) { // >PHP 7.0
|
||||||
|
@ -97,6 +97,14 @@ class ByteBuffer implements \JsonSerializable, \Serializable {
|
||||||
return \ord(\substr($this->_data, $offset, 1));
|
return \ord(\substr($this->_data, $offset, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getJson($jsonFlags=0) {
|
||||||
|
$data = \json_decode($this->getBinaryString(), null, 512, $jsonFlags);
|
||||||
|
if (\json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
throw new WebAuthnException(\json_last_error_msg(), WebAuthnException::BYTEBUFFER);
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
public function getLength() {
|
public function getLength() {
|
||||||
return $this->_length;
|
return $this->_length;
|
||||||
}
|
}
|
||||||
|
@ -203,7 +211,7 @@ class ByteBuffer implements \JsonSerializable, \Serializable {
|
||||||
/**
|
/**
|
||||||
* jsonSerialize interface
|
* jsonSerialize interface
|
||||||
* return binary data in RFC 1342-Like serialized string
|
* return binary data in RFC 1342-Like serialized string
|
||||||
* @return \stdClass
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function jsonSerialize() {
|
public function jsonSerialize() {
|
||||||
if (ByteBuffer::$useBase64UrlEncoding) {
|
if (ByteBuffer::$useBase64UrlEncoding) {
|
||||||
|
@ -231,6 +239,36 @@ class ByteBuffer implements \JsonSerializable, \Serializable {
|
||||||
$this->_length = \strlen($this->_data);
|
$this->_length = \strlen($this->_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (PHP 8 deprecates Serializable-Interface)
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function __serialize() {
|
||||||
|
return [
|
||||||
|
'data' => \serialize($this->_data)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* object to string
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function __toString() {
|
||||||
|
return $this->getHex();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (PHP 8 deprecates Serializable-Interface)
|
||||||
|
* @param array $data
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __unserialize($data) {
|
||||||
|
if ($data && isset($data['data'])) {
|
||||||
|
$this->_data = \unserialize($data['data']);
|
||||||
|
$this->_length = \strlen($this->_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------
|
// -----------------------
|
||||||
// PROTECTED STATIC
|
// PROTECTED STATIC
|
||||||
// -----------------------
|
// -----------------------
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
|
||||||
namespace WebAuthn\CBOR;
|
namespace lbuchs\WebAuthn\CBOR;
|
||||||
use WebAuthn\WebAuthnException;
|
use lbuchs\WebAuthn\WebAuthnException;
|
||||||
use WebAuthn\Binary\ByteBuffer;
|
use lbuchs\WebAuthn\Binary\ByteBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modified version of https://github.com/madwizard-thomas/webauthn-server/blob/master/src/Format/CborDecoder.php
|
* Modified version of https://github.com/madwizard-thomas/webauthn-server/blob/master/src/Format/CborDecoder.php
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright © 2019 Lukas Buchs
|
|
||||||
Copyright © 2018 Thomas Bleeker (CBOR & ByteBuffer part)
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
|
@ -1,7 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace WebAuthn;
|
namespace lbuchs\WebAuthn;
|
||||||
use WebAuthn\Binary\ByteBuffer;
|
use lbuchs\WebAuthn\Binary\ByteBuffer;
|
||||||
require_once 'WebAuthnException.php';
|
require_once 'WebAuthnException.php';
|
||||||
require_once 'Binary/ByteBuffer.php';
|
require_once 'Binary/ByteBuffer.php';
|
||||||
require_once 'Attestation/AttestationObject.php';
|
require_once 'Attestation/AttestationObject.php';
|
||||||
|
@ -69,16 +69,20 @@ class WebAuthn {
|
||||||
/**
|
/**
|
||||||
* add a root certificate to verify new registrations
|
* add a root certificate to verify new registrations
|
||||||
* @param string $path file path of / directory with root certificates
|
* @param string $path file path of / directory with root certificates
|
||||||
|
* @param array|null $certFileExtensions if adding a direction, all files with provided extension are added. default: pem, crt, cer, der
|
||||||
*/
|
*/
|
||||||
public function addRootCertificates($path) {
|
public function addRootCertificates($path, $certFileExtensions=null) {
|
||||||
if (!\is_array($this->_caFiles)) {
|
if (!\is_array($this->_caFiles)) {
|
||||||
$this->_caFiles = array();
|
$this->_caFiles = array();
|
||||||
}
|
}
|
||||||
|
if ($certFileExtensions === null) {
|
||||||
|
$certFileExtensions = array('pem', 'crt', 'cer', 'der');
|
||||||
|
}
|
||||||
$path = \rtrim(\trim($path), '\\/');
|
$path = \rtrim(\trim($path), '\\/');
|
||||||
if (\is_dir($path)) {
|
if (\is_dir($path)) {
|
||||||
foreach (\scandir($path) as $ca) {
|
foreach (\scandir($path) as $ca) {
|
||||||
if (\is_file($path . '/' . $ca)) {
|
if (\is_file($path . DIRECTORY_SEPARATOR . $ca) && \in_array(\strtolower(\pathinfo($ca, PATHINFO_EXTENSION)), $certFileExtensions)) {
|
||||||
$this->addRootCertificates($path . '/' . $ca);
|
$this->addRootCertificates($path . DIRECTORY_SEPARATOR . $ca);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (\is_file($path) && !\in_array(\realpath($path), $this->_caFiles)) {
|
} else if (\is_file($path) && !\in_array(\realpath($path), $this->_caFiles)) {
|
||||||
|
@ -273,10 +277,11 @@ class WebAuthn {
|
||||||
* @param string|ByteBuffer $challenge binary used challange
|
* @param string|ByteBuffer $challenge binary used challange
|
||||||
* @param bool $requireUserVerification true, if the device must verify user (e.g. by biometric data or pin)
|
* @param bool $requireUserVerification true, if the device must verify user (e.g. by biometric data or pin)
|
||||||
* @param bool $requireUserPresent false, if the device must NOT check user presence (e.g. by pressing a button)
|
* @param bool $requireUserPresent false, if the device must NOT check user presence (e.g. by pressing a button)
|
||||||
|
* @param bool $failIfRootMismatch false, if there should be no error thrown if root certificate doesn't match
|
||||||
* @return \stdClass
|
* @return \stdClass
|
||||||
* @throws WebAuthnException
|
* @throws WebAuthnException
|
||||||
*/
|
*/
|
||||||
public function processCreate($clientDataJSON, $attestationObject, $challenge, $requireUserVerification=false, $requireUserPresent=true) {
|
public function processCreate($clientDataJSON, $attestationObject, $challenge, $requireUserVerification=false, $requireUserPresent=true, $failIfRootMismatch=true) {
|
||||||
$clientDataHash = \hash('sha256', $clientDataJSON, true);
|
$clientDataHash = \hash('sha256', $clientDataJSON, true);
|
||||||
$clientData = \json_decode($clientDataJSON);
|
$clientData = \json_decode($clientDataJSON);
|
||||||
$challenge = $challenge instanceof ByteBuffer ? $challenge : new ByteBuffer($challenge);
|
$challenge = $challenge instanceof ByteBuffer ? $challenge : new ByteBuffer($challenge);
|
||||||
|
@ -318,18 +323,21 @@ class WebAuthn {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 15. If validation is successful, obtain a list of acceptable trust anchors
|
// 15. If validation is successful, obtain a list of acceptable trust anchors
|
||||||
if (is_array($this->_caFiles) && !$attestationObject->validateRootCertificate($this->_caFiles)) {
|
$rootValid = is_array($this->_caFiles) ? $attestationObject->validateRootCertificate($this->_caFiles) : null;
|
||||||
|
if ($failIfRootMismatch && is_array($this->_caFiles) && !$rootValid) {
|
||||||
throw new WebAuthnException('invalid root certificate', WebAuthnException::CERTIFICATE_NOT_TRUSTED);
|
throw new WebAuthnException('invalid root certificate', WebAuthnException::CERTIFICATE_NOT_TRUSTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 10. Verify that the User Present bit of the flags in authData is set.
|
// 10. Verify that the User Present bit of the flags in authData is set.
|
||||||
if ($requireUserPresent && !$attestationObject->getAuthenticatorData()->getUserPresent()) {
|
$userPresent = $attestationObject->getAuthenticatorData()->getUserPresent();
|
||||||
|
if ($requireUserPresent && !$userPresent) {
|
||||||
throw new WebAuthnException('user not present during authentication', WebAuthnException::USER_PRESENT);
|
throw new WebAuthnException('user not present during authentication', WebAuthnException::USER_PRESENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 11. If user verification is required for this registration, verify that the User Verified bit of the flags in authData is set.
|
// 11. If user verification is required for this registration, verify that the User Verified bit of the flags in authData is set.
|
||||||
if ($requireUserVerification && !$attestationObject->getAuthenticatorData()->getUserVerified()) {
|
$userVerified = $attestationObject->getAuthenticatorData()->getUserVerified();
|
||||||
throw new WebAuthnException('user not verificated during authentication', WebAuthnException::USER_VERIFICATED);
|
if ($requireUserVerification && !$userVerified) {
|
||||||
|
throw new WebAuthnException('user not verified during authentication', WebAuthnException::USER_VERIFICATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
$signCount = $attestationObject->getAuthenticatorData()->getSignCount();
|
$signCount = $attestationObject->getAuthenticatorData()->getSignCount();
|
||||||
|
@ -340,6 +348,7 @@ class WebAuthn {
|
||||||
// prepare data to store for future logins
|
// prepare data to store for future logins
|
||||||
$data = new \stdClass();
|
$data = new \stdClass();
|
||||||
$data->rpId = $this->_rpId;
|
$data->rpId = $this->_rpId;
|
||||||
|
$data->attestationFormat = $attestationObject->getAttestationFormatName();
|
||||||
$data->credentialId = $attestationObject->getAuthenticatorData()->getCredentialId();
|
$data->credentialId = $attestationObject->getAuthenticatorData()->getCredentialId();
|
||||||
$data->credentialPublicKey = $attestationObject->getAuthenticatorData()->getPublicKeyPem();
|
$data->credentialPublicKey = $attestationObject->getAuthenticatorData()->getPublicKeyPem();
|
||||||
$data->certificateChain = $attestationObject->getCertificateChain();
|
$data->certificateChain = $attestationObject->getCertificateChain();
|
||||||
|
@ -348,6 +357,9 @@ class WebAuthn {
|
||||||
$data->certificateSubject = $attestationObject->getCertificateSubject();
|
$data->certificateSubject = $attestationObject->getCertificateSubject();
|
||||||
$data->signatureCounter = $this->_signatureCounter;
|
$data->signatureCounter = $this->_signatureCounter;
|
||||||
$data->AAGUID = $attestationObject->getAuthenticatorData()->getAAGUID();
|
$data->AAGUID = $attestationObject->getAuthenticatorData()->getAAGUID();
|
||||||
|
$data->rootValid = $rootValid;
|
||||||
|
$data->userPresent = $userPresent;
|
||||||
|
$data->userVerified = $userVerified;
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -453,6 +465,92 @@ class WebAuthn {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads root certificates from FIDO Alliance Metadata Service (MDS) to a specific folder
|
||||||
|
* https://fidoalliance.org/metadata/
|
||||||
|
* @param string $certFolder Folder path to save the certificates in PEM format.
|
||||||
|
* @param bool $deleteCerts=true
|
||||||
|
* @return int number of cetificates
|
||||||
|
* @throws WebAuthnException
|
||||||
|
*/
|
||||||
|
public function queryFidoMetaDataService($certFolder, $deleteCerts=true) {
|
||||||
|
$url = 'https://mds.fidoalliance.org/';
|
||||||
|
$raw = null;
|
||||||
|
if (\function_exists('curl_init')) {
|
||||||
|
$ch = \curl_init($url);
|
||||||
|
\curl_setopt($ch, CURLOPT_HEADER, false);
|
||||||
|
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
\curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||||
|
\curl_setopt($ch, CURLOPT_USERAGENT, 'github.com/lbuchs/WebAuthn - A simple PHP WebAuthn server library');
|
||||||
|
$raw = \curl_exec($ch);
|
||||||
|
\curl_close($ch);
|
||||||
|
} else {
|
||||||
|
$raw = \file_get_contents($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
$certFolder = \rtrim(\realpath($certFolder), '\\/');
|
||||||
|
if (!is_dir($certFolder)) {
|
||||||
|
throw new WebAuthnException('Invalid folder path for query FIDO Alliance Metadata Service');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!\is_string($raw)) {
|
||||||
|
throw new WebAuthnException('Unable to query FIDO Alliance Metadata Service');
|
||||||
|
}
|
||||||
|
|
||||||
|
$jwt = \explode('.', $raw);
|
||||||
|
if (\count($jwt) !== 3) {
|
||||||
|
throw new WebAuthnException('Invalid JWT from FIDO Alliance Metadata Service');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($deleteCerts) {
|
||||||
|
foreach (\scandir($certFolder) as $ca) {
|
||||||
|
if (\substr($ca, -4) === '.pem') {
|
||||||
|
if (\unlink($certFolder . DIRECTORY_SEPARATOR . $ca) === false) {
|
||||||
|
throw new WebAuthnException('Cannot delete certs in folder for FIDO Alliance Metadata Service');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list($header, $payload, $hash) = $jwt;
|
||||||
|
$payload = Binary\ByteBuffer::fromBase64Url($payload)->getJson();
|
||||||
|
|
||||||
|
$count = 0;
|
||||||
|
if (\is_object($payload) && \property_exists($payload, 'entries') && \is_array($payload->entries)) {
|
||||||
|
foreach ($payload->entries as $entry) {
|
||||||
|
if (\is_object($entry) && \property_exists($entry, 'metadataStatement') && \is_object($entry->metadataStatement)) {
|
||||||
|
$description = $entry->metadataStatement->description ?? null;
|
||||||
|
$attestationRootCertificates = $entry->metadataStatement->attestationRootCertificates ?? null;
|
||||||
|
|
||||||
|
if ($description && $attestationRootCertificates) {
|
||||||
|
|
||||||
|
// create filename
|
||||||
|
$certFilename = \preg_replace('/[^a-z0-9]/i', '_', $description);
|
||||||
|
$certFilename = \trim(\preg_replace('/\_{2,}/i', '_', $certFilename),'_') . '.pem';
|
||||||
|
$certFilename = \strtolower($certFilename);
|
||||||
|
|
||||||
|
// add certificate
|
||||||
|
$certContent = $description . "\n";
|
||||||
|
$certContent .= \str_repeat('-', \mb_strlen($description)) . "\n";
|
||||||
|
|
||||||
|
foreach ($attestationRootCertificates as $attestationRootCertificate) {
|
||||||
|
$count++;
|
||||||
|
$certContent .= "\n-----BEGIN CERTIFICATE-----\n";
|
||||||
|
$certContent .= \chunk_split(\trim($attestationRootCertificate), 64, "\n");
|
||||||
|
$certContent .= "-----END CERTIFICATE-----\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\file_put_contents($certFolder . DIRECTORY_SEPARATOR . $certFilename, $certContent) === false) {
|
||||||
|
throw new WebAuthnException('unable to save certificate from FIDO Alliance Metadata Service');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------
|
// -----------------------------------------------
|
||||||
// PRIVATE
|
// PRIVATE
|
||||||
// -----------------------------------------------
|
// -----------------------------------------------
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
namespace WebAuthn;
|
namespace lbuchs\WebAuthn;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Lukas Buchs
|
* @author Lukas Buchs
|
||||||
|
|
|
@ -54,24 +54,16 @@ foreach ($css_dir as $css_file) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// U2F API + T/HOTP API
|
// U2F API + T/HOTP API
|
||||||
|
// u2f - deprecated, should be removed
|
||||||
$u2f = new u2flib_server\U2F('https://' . $_SERVER['HTTP_HOST']);
|
$u2f = new u2flib_server\U2F('https://' . $_SERVER['HTTP_HOST']);
|
||||||
$qrprovider = new RobThree\Auth\Providers\Qr\QRServerProvider();
|
$qrprovider = new RobThree\Auth\Providers\Qr\QRServerProvider();
|
||||||
$tfa = new RobThree\Auth\TwoFactorAuth($OTP_LABEL, 6, 30, 'sha1', $qrprovider);
|
$tfa = new RobThree\Auth\TwoFactorAuth($OTP_LABEL, 6, 30, 'sha1', $qrprovider);
|
||||||
|
|
||||||
// FIDO2
|
// FIDO2
|
||||||
$formats = $GLOBALS['FIDO2_FORMATS'];
|
$formats = $GLOBALS['FIDO2_FORMATS'];
|
||||||
$WebAuthn = new \WebAuthn\WebAuthn('WebAuthn Library', $_SERVER['HTTP_HOST'], $formats);
|
$WebAuthn = new lbuchs\WebAuthn\WebAuthn('WebAuthn Library', $_SERVER['HTTP_HOST'], $formats);
|
||||||
$WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/solo.pem');
|
// only include root ca's when needed
|
||||||
$WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/apple.pem');
|
if (getenv('WEBAUTHN_ONLY_TRUSTED_VENDORS') == 'y') $WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates');
|
||||||
$WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/nitro.pem');
|
|
||||||
$WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/yubico.pem');
|
|
||||||
$WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/hypersecu.pem');
|
|
||||||
$WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/globalSign.pem');
|
|
||||||
$WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/googleHardware.pem');
|
|
||||||
$WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/microsoftTpmCollection.pem');
|
|
||||||
$WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/huawei.pem');
|
|
||||||
$WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/trustkey.pem');
|
|
||||||
$WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates/bsi.pem');
|
|
||||||
|
|
||||||
// Redis
|
// Redis
|
||||||
$redis = new Redis();
|
$redis = new Redis();
|
||||||
|
|
|
@ -1,15 +1,28 @@
|
||||||
<?php
|
<?php
|
||||||
if (isset($_POST["verify_tfa_login"])) {
|
if (isset($_POST["verify_tfa_login"])) {
|
||||||
if (verify_tfa_login($_SESSION['pending_mailcow_cc_username'], $_POST["token"])) {
|
if (verify_tfa_login($_SESSION['pending_mailcow_cc_username'], $_POST, $WebAuthn)) {
|
||||||
$_SESSION['mailcow_cc_username'] = $_SESSION['pending_mailcow_cc_username'];
|
$_SESSION['mailcow_cc_username'] = $_SESSION['pending_mailcow_cc_username'];
|
||||||
$_SESSION['mailcow_cc_role'] = $_SESSION['pending_mailcow_cc_role'];
|
$_SESSION['mailcow_cc_role'] = $_SESSION['pending_mailcow_cc_role'];
|
||||||
unset($_SESSION['pending_mailcow_cc_username']);
|
unset($_SESSION['pending_mailcow_cc_username']);
|
||||||
unset($_SESSION['pending_mailcow_cc_role']);
|
unset($_SESSION['pending_mailcow_cc_role']);
|
||||||
unset($_SESSION['pending_tfa_method']);
|
unset($_SESSION['pending_tfa_method']);
|
||||||
|
|
||||||
header("Location: /user");
|
header("Location: /user");
|
||||||
|
} else {
|
||||||
|
unset($_SESSION['pending_mailcow_cc_username']);
|
||||||
|
unset($_SESSION['pending_mailcow_cc_role']);
|
||||||
|
unset($_SESSION['pending_tfa_method']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($_GET["cancel_tfa_login"])) {
|
||||||
|
unset($_SESSION['pending_mailcow_cc_username']);
|
||||||
|
unset($_SESSION['pending_mailcow_cc_role']);
|
||||||
|
unset($_SESSION['pending_tfa_method']);
|
||||||
|
|
||||||
|
header("Location: /");
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($_POST["quick_release"])) {
|
if (isset($_POST["quick_release"])) {
|
||||||
quarantine('quick_release', $_POST["quick_release"]);
|
quarantine('quick_release', $_POST["quick_release"]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,6 +175,9 @@ $MAILBOX_DEFAULT_ATTRIBUTES['pop3_access'] = true;
|
||||||
// Mailbox has SMTP access by default
|
// Mailbox has SMTP access by default
|
||||||
$MAILBOX_DEFAULT_ATTRIBUTES['smtp_access'] = true;
|
$MAILBOX_DEFAULT_ATTRIBUTES['smtp_access'] = true;
|
||||||
|
|
||||||
|
// Mailbox has sieve access by default
|
||||||
|
$MAILBOX_DEFAULT_ATTRIBUTES['sieve_access'] = true;
|
||||||
|
|
||||||
// Mailbox receives notifications about...
|
// Mailbox receives notifications about...
|
||||||
// "add_header" - mail that was put into the Junk folder
|
// "add_header" - mail that was put into the Junk folder
|
||||||
// "reject" - mail that was rejected
|
// "reject" - mail that was rejected
|
||||||
|
@ -192,11 +195,17 @@ $SHOW_LAST_LOGIN = true;
|
||||||
// true = required
|
// true = required
|
||||||
// false = preferred
|
// false = preferred
|
||||||
// string 'required' 'preferred' 'discouraged'
|
// string 'required' 'preferred' 'discouraged'
|
||||||
|
$WEBAUTHN_UV_FLAG_REGISTER = false;
|
||||||
|
$WEBAUTHN_UV_FLAG_LOGIN = false;
|
||||||
|
$WEBAUTHN_USER_PRESENT_FLAG = true;
|
||||||
|
|
||||||
$FIDO2_UV_FLAG_REGISTER = 'preferred';
|
$FIDO2_UV_FLAG_REGISTER = 'preferred';
|
||||||
$FIDO2_UV_FLAG_LOGIN = 'preferred'; // iOS ignores the key via NFC if required - known issue
|
$FIDO2_UV_FLAG_LOGIN = 'preferred'; // iOS ignores the key via NFC if required - known issue
|
||||||
$FIDO2_USER_PRESENT_FLAG = true;
|
$FIDO2_USER_PRESENT_FLAG = true;
|
||||||
|
|
||||||
$FIDO2_FORMATS = array('apple', 'android-key', 'android-safetynet', 'fido-u2f', 'none', 'packed', 'tpm');
|
$FIDO2_FORMATS = array('apple', 'android-key', 'android-safetynet', 'fido-u2f', 'none', 'packed', 'tpm');
|
||||||
|
|
||||||
|
|
||||||
// Set visible Rspamd maps in mailcow UI, do not change unless you know what you are doing
|
// Set visible Rspamd maps in mailcow UI, do not change unless you know what you are doing
|
||||||
$RSPAMD_MAPS = array(
|
$RSPAMD_MAPS = array(
|
||||||
'regex' => array(
|
'regex' => array(
|
||||||
|
|
|
@ -117,12 +117,12 @@ if (isset($_GET['query'])) {
|
||||||
echo isset($_SESSION['return']) ? json_encode($_SESSION['return']) : $generic_success;
|
echo isset($_SESSION['return']) ? json_encode($_SESSION['return']) : $generic_success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isset($_POST['attr']) && $category != "fido2-registration") {
|
if (!isset($_POST['attr']) && $category != "fido2-registration" && $category != "webauthn-tfa-registration") {
|
||||||
echo $request_incomplete;
|
echo $request_incomplete;
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if ($category != "fido2-registration") {
|
if ($category != "fido2-registration" && $category != "webauthn-tfa-registration") {
|
||||||
$attr = (array)json_decode($_POST['attr'], true);
|
$attr = (array)json_decode($_POST['attr'], true);
|
||||||
}
|
}
|
||||||
unset($attr['csrf_token']);
|
unset($attr['csrf_token']);
|
||||||
|
@ -170,6 +170,48 @@ if (isset($_GET['query'])) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "webauthn-tfa-registration":
|
||||||
|
if (isset($_SESSION["mailcow_cc_role"])) {
|
||||||
|
// parse post data
|
||||||
|
$post = trim(file_get_contents('php://input'));
|
||||||
|
if ($post) $post = json_decode($post);
|
||||||
|
|
||||||
|
// decode base64 strings
|
||||||
|
$clientDataJSON = base64_decode($post->clientDataJSON);
|
||||||
|
$attestationObject = base64_decode($post->attestationObject);
|
||||||
|
|
||||||
|
// process registration data from authenticator
|
||||||
|
try {
|
||||||
|
// processCreate($clientDataJSON, $attestationObject, $challenge, $requireUserVerification=false, $requireUserPresent=true, $failIfRootMismatch=true)
|
||||||
|
$data = $WebAuthn->processCreate($clientDataJSON, $attestationObject, $_SESSION['challenge'], false, true);
|
||||||
|
}
|
||||||
|
catch (Throwable $ex) {
|
||||||
|
// err
|
||||||
|
$return = new stdClass();
|
||||||
|
$return->success = false;
|
||||||
|
$return->msg = $ex->getMessage();
|
||||||
|
echo json_encode($return);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// safe authenticator in mysql `tfa` table
|
||||||
|
$_data['tfa_method'] = $post->tfa_method;
|
||||||
|
$_data['key_id'] = $post->key_id;
|
||||||
|
$_data['registration'] = $data;
|
||||||
|
set_tfa($_data);
|
||||||
|
|
||||||
|
// send response
|
||||||
|
$return = new stdClass();
|
||||||
|
$return->success = true;
|
||||||
|
echo json_encode($return);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// err - request incomplete
|
||||||
|
echo $request_incomplete;
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "time_limited_alias":
|
case "time_limited_alias":
|
||||||
process_add_return(mailbox('add', 'time_limited_alias', $attr));
|
process_add_return(mailbox('add', 'time_limited_alias', $attr));
|
||||||
break;
|
break;
|
||||||
|
@ -350,29 +392,13 @@ if (isset($_GET['query'])) {
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
switch ($category) {
|
switch ($category) {
|
||||||
case "u2f-registration":
|
// fido2
|
||||||
header('Content-Type: application/javascript');
|
|
||||||
if (isset($_SESSION["mailcow_cc_role"]) && $_SESSION["mailcow_cc_username"] == $object) {
|
|
||||||
list($req, $sigs) = $u2f->getRegisterData(get_u2f_registrations($object));
|
|
||||||
$_SESSION['regReq'] = json_encode($req);
|
|
||||||
$_SESSION['regSigs'] = json_encode($sigs);
|
|
||||||
echo 'var req = ' . json_encode($req) . ';';
|
|
||||||
echo 'var registeredKeys = ' . json_encode($sigs) . ';';
|
|
||||||
echo 'var appId = req.appId;';
|
|
||||||
echo 'var registerRequests = [{version: req.version, challenge: req.challenge}];';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
// fido2-registration via GET
|
|
||||||
case "fido2-registration":
|
case "fido2-registration":
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
if (isset($_SESSION["mailcow_cc_role"])) {
|
if (isset($_SESSION["mailcow_cc_role"])) {
|
||||||
// Exclude existing CredentialIds, if any
|
// Exclude existing CredentialIds, if any
|
||||||
$excludeCredentialIds = fido2(array("action" => "get_user_cids"));
|
$excludeCredentialIds = fido2(array("action" => "get_user_cids"));
|
||||||
$createArgs = $WebAuthn->getCreateArgs($_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], 30, true, $GLOBALS['FIDO2_UV_FLAG_REGISTER'], $excludeCredentialIds);
|
$createArgs = $WebAuthn->getCreateArgs($_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], 30, true, $GLOBALS['FIDO2_UV_FLAG_REGISTER'], null, $excludeCredentialIds);
|
||||||
print(json_encode($createArgs));
|
print(json_encode($createArgs));
|
||||||
$_SESSION['challenge'] = $WebAuthn->getChallenge();
|
$_SESSION['challenge'] = $WebAuthn->getChallenge();
|
||||||
return;
|
return;
|
||||||
|
@ -381,37 +407,63 @@ if (isset($_GET['query'])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "u2f-authentication":
|
case "fido2-get-args":
|
||||||
header('Content-Type: application/javascript');
|
header('Content-Type: application/json');
|
||||||
if (isset($_SESSION['pending_mailcow_cc_username']) && $_SESSION['pending_mailcow_cc_username'] == $object) {
|
// fetch allowed credentialIds
|
||||||
$auth_data = $u2f->getAuthenticateData(get_u2f_registrations($object));
|
$cids = fido2(array("action" => "get_all_cids"));
|
||||||
$challenge = $auth_data[0]->challenge;
|
if (count($cids) == 0) {
|
||||||
$appId = $auth_data[0]->appId;
|
print(json_encode(array(
|
||||||
foreach ($auth_data as $each) {
|
'type' => 'error',
|
||||||
$key = array(); // Empty array
|
'msg' => 'Cannot find matching credentialIds'
|
||||||
$key['version'] = $each->version;
|
)));
|
||||||
$key['keyHandle'] = $each->keyHandle;
|
|
||||||
$registeredKey[] = $key;
|
|
||||||
}
|
}
|
||||||
$_SESSION['authReq'] = json_encode($auth_data);
|
|
||||||
echo 'var appId = "' . $appId . '";';
|
$getArgs = $WebAuthn->getGetArgs($cids, 30, true, true, true, true, $GLOBALS['FIDO2_UV_FLAG_LOGIN']);
|
||||||
echo 'var challenge = ' . json_encode($challenge) . ';';
|
print(json_encode($getArgs));
|
||||||
echo 'var registeredKeys = ' . json_encode($registeredKey) . ';';
|
$_SESSION['challenge'] = $WebAuthn->getChallenge();
|
||||||
return;
|
return;
|
||||||
|
break;
|
||||||
|
// webauthn two factor authentication
|
||||||
|
case "webauthn-tfa-registration":
|
||||||
|
if (isset($_SESSION["mailcow_cc_role"])) {
|
||||||
|
// Exclude existing CredentialIds, if any
|
||||||
|
$stmt = $pdo->prepare("SELECT `keyHandle` FROM `tfa` WHERE username = :username");
|
||||||
|
$stmt->execute(array(':username' => $_SESSION['mailcow_cc_username']));
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
while($row = array_shift($rows)) {
|
||||||
|
$excludeCredentialIds[] = base64_decode($row['keyHandle']);
|
||||||
|
}
|
||||||
|
// getCreateArgs($userId, $userName, $userDisplayName, $timeout=20, $requireResidentKey=false, $requireUserVerification=false, $crossPlatformAttachment=null, $excludeCredentialIds=array())
|
||||||
|
// cross-platform: true, if type internal is not allowed
|
||||||
|
// false, if only internal is allowed
|
||||||
|
// null, if internal and cross-platform is allowed
|
||||||
|
$createArgs = $WebAuthn->getCreateArgs($_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], 30, false, $GLOBALS['WEBAUTHN_UV_FLAG_REGISTER'], null, $excludeCredentialIds);
|
||||||
|
|
||||||
|
print(json_encode($createArgs));
|
||||||
|
$_SESSION['challenge'] = $WebAuthn->getChallenge();
|
||||||
|
return;
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "fido2-get-args":
|
case "webauthn-tfa-get-args":
|
||||||
header('Content-Type: application/json');
|
$stmt = $pdo->prepare("SELECT `keyHandle` FROM `tfa` WHERE username = :username");
|
||||||
// Login without username, no ids!
|
$stmt->execute(array(':username' => $_SESSION['pending_mailcow_cc_username']));
|
||||||
// $ids = fido2(array("action" => "get_all_cids"));
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
// if (count($ids) == 0) {
|
while($row = array_shift($rows)) {
|
||||||
// return;
|
$cids[] = base64_decode($row['keyHandle']);
|
||||||
// }
|
}
|
||||||
$ids = NULL;
|
if (count($cids) == 0) {
|
||||||
$getArgs = $WebAuthn->getGetArgs($ids, 30, true, true, true, true, $GLOBALS['FIDO2_UV_FLAG_LOGIN']);
|
print(json_encode(array(
|
||||||
|
'type' => 'error',
|
||||||
|
'msg' => 'Cannot find matching credentialIds'
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$getArgs = $WebAuthn->getGetArgs($cids, 30, true, true, true, true, $GLOBALS['WEBAUTHN_UV_FLAG_LOGIN']);
|
||||||
|
$getArgs->publicKey->extensions = array('appid' => "https://".$getArgs->publicKey->rpId);
|
||||||
print(json_encode($getArgs));
|
print(json_encode($getArgs));
|
||||||
$_SESSION['challenge'] = $WebAuthn->getChallenge();
|
$_SESSION['challenge'] = $WebAuthn->getChallenge();
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -443,9 +443,9 @@
|
||||||
"set_tfa": "Definir el mètode d'autenticació de dos factors",
|
"set_tfa": "Definir el mètode d'autenticació de dos factors",
|
||||||
"tfa": "Autenticació de dos factors",
|
"tfa": "Autenticació de dos factors",
|
||||||
"totp": "OTP basat en temps (Google Authenticator etc.)",
|
"totp": "OTP basat en temps (Google Authenticator etc.)",
|
||||||
"u2f": "Autenticació U2F",
|
"webauthn": "Autenticació WebAuthn",
|
||||||
"waiting_usb_auth": "<i>Esperant el dispositiu USB...</i><br><br>Apreta el botó del teu dispositiu USB U2F ara.",
|
"waiting_usb_auth": "<i>Esperant el dispositiu USB...</i><br><br>Apreta el botó del teu dispositiu USB WebAuthn ara.",
|
||||||
"waiting_usb_register": "<i>Esperant el dispositiu USB...</i><br><br>Posa el teu password i confirma el registre del teu U2F apretant el botó del teu dispositiiu USB U2F.",
|
"waiting_usb_register": "<i>Esperant el dispositiu USB...</i><br><br>Posa el teu password i confirma el registre del teu WebAuthn apretant el botó del teu dispositiiu USB WebAuthn.",
|
||||||
"yubi_otp": "Autenticació OTP de Yubico"
|
"yubi_otp": "Autenticació OTP de Yubico"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
|
|
|
@ -454,7 +454,7 @@
|
||||||
"tls_policy_map_parameter_invalid": "Parametr pravidel TLS je neplatný",
|
"tls_policy_map_parameter_invalid": "Parametr pravidel TLS je neplatný",
|
||||||
"totp_verification_failed": "TOTP ověření selhalo",
|
"totp_verification_failed": "TOTP ověření selhalo",
|
||||||
"transport_dest_exists": "Transportní cíl \"%s\" již existuje",
|
"transport_dest_exists": "Transportní cíl \"%s\" již existuje",
|
||||||
"u2f_verification_failed": "U2F ověření selhalo: %s",
|
"webauthn_verification_failed": "WebAuthn ověření selhalo: %s",
|
||||||
"unknown": "Došlo k neznámé chybě",
|
"unknown": "Došlo k neznámé chybě",
|
||||||
"unknown_tfa_method": "Neznámá 2FA metoda",
|
"unknown_tfa_method": "Neznámá 2FA metoda",
|
||||||
"unlimited_quota_acl": "Neomeznou kvótu nepovoluje seznam oprávnění ACL",
|
"unlimited_quota_acl": "Neomeznou kvótu nepovoluje seznam oprávnění ACL",
|
||||||
|
@ -467,7 +467,7 @@
|
||||||
"chart_this_server": "Graf (tento server)",
|
"chart_this_server": "Graf (tento server)",
|
||||||
"containers_info": "Informace o kontejnerech",
|
"containers_info": "Informace o kontejnerech",
|
||||||
"disk_usage": "Využití disku",
|
"disk_usage": "Využití disku",
|
||||||
"docs": "Dokumentace",
|
"docs": "Dokumentů",
|
||||||
"external_logs": "Externí logy",
|
"external_logs": "Externí logy",
|
||||||
"history_all_servers": "Záznam (všechny servery)",
|
"history_all_servers": "Záznam (všechny servery)",
|
||||||
"in_memory_logs": "Logy v paměti",
|
"in_memory_logs": "Logy v paměti",
|
||||||
|
@ -972,7 +972,7 @@
|
||||||
"upload_success": "Soubor úspěšně nahrán",
|
"upload_success": "Soubor úspěšně nahrán",
|
||||||
"verified_fido2_login": "Ověřené FIDO2 přihlášení",
|
"verified_fido2_login": "Ověřené FIDO2 přihlášení",
|
||||||
"verified_totp_login": "TOTP přihlášení ověřeno",
|
"verified_totp_login": "TOTP přihlášení ověřeno",
|
||||||
"verified_u2f_login": "U2F přihlášení ověřeno",
|
"verified_webauthn_login": "WebAuthn přihlášení ověřeno",
|
||||||
"verified_yotp_login": "Yubico OTP přihlášení ověřeno"
|
"verified_yotp_login": "Yubico OTP přihlášení ověřeno"
|
||||||
},
|
},
|
||||||
"tfa": {
|
"tfa": {
|
||||||
|
@ -983,7 +983,7 @@
|
||||||
"disable_tfa": "Zakázat 2FA do příštího úspěšného přihlášení",
|
"disable_tfa": "Zakázat 2FA do příštího úspěšného přihlášení",
|
||||||
"enter_qr_code": "Kód TOTP, pokud zařízení neumí číst QR kódy",
|
"enter_qr_code": "Kód TOTP, pokud zařízení neumí číst QR kódy",
|
||||||
"error_code": "Kód chyby",
|
"error_code": "Kód chyby",
|
||||||
"init_u2f": "Probíhá inicializace, čekejte...",
|
"init_webauthn": "Probíhá inicializace, čekejte...",
|
||||||
"key_id": "Identifikátor YubiKey",
|
"key_id": "Identifikátor YubiKey",
|
||||||
"key_id_totp": "Identifikátor klíče",
|
"key_id_totp": "Identifikátor klíče",
|
||||||
"none": "Deaktivovat",
|
"none": "Deaktivovat",
|
||||||
|
@ -991,13 +991,13 @@
|
||||||
"scan_qr_code": "Prosím načtěte následující kód svou aplikací na ověření nebo zadejte kód ručně.",
|
"scan_qr_code": "Prosím načtěte následující kód svou aplikací na ověření nebo zadejte kód ručně.",
|
||||||
"select": "Prosím vyberte...",
|
"select": "Prosím vyberte...",
|
||||||
"set_tfa": "Nastavení způsobu dvoufaktorového ověření",
|
"set_tfa": "Nastavení způsobu dvoufaktorového ověření",
|
||||||
"start_u2f_validation": "Zahájit inicializaci",
|
"start_webauthn_validation": "Zahájit inicializaci",
|
||||||
"tfa": "Dvoufaktorové ověření (TFA)",
|
"tfa": "Dvoufaktorové ověření (TFA)",
|
||||||
"tfa_token_invalid": "Neplatný TFA token",
|
"tfa_token_invalid": "Neplatný TFA token",
|
||||||
"totp": "Časově založené OTP (Google Authenticator, Authy apod.)",
|
"totp": "Časově založené OTP (Google Authenticator, Authy apod.)",
|
||||||
"u2f": "U2F ověření",
|
"webauthn": "WebAuthn ověření",
|
||||||
"waiting_usb_auth": "<i>Čeká se na USB zařízení...</i><br><br>Prosím stiskněte tlačítko na svém U2F USB zařízení.",
|
"waiting_usb_auth": "<i>Čeká se na USB zařízení...</i><br><br>Prosím stiskněte tlačítko na svém WebAuthn USB zařízení.",
|
||||||
"waiting_usb_register": "<i>Čeká se na USB zařízení...</i><br><br>Prosím zadejte své heslo výše a potvrďte U2F registraci stiskem tlačítka na svém U2F USB zařízení.",
|
"waiting_usb_register": "<i>Čeká se na USB zařízení...</i><br><br>Prosím zadejte své heslo výše a potvrďte WebAuthn registraci stiskem tlačítka na svém WebAuthn USB zařízení.",
|
||||||
"yubi_otp": "Yubico OTP ověření"
|
"yubi_otp": "Yubico OTP ověření"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
|
|
|
@ -426,7 +426,7 @@
|
||||||
"tls_policy_map_parameter_invalid": "Politikparameter er ugyldig",
|
"tls_policy_map_parameter_invalid": "Politikparameter er ugyldig",
|
||||||
"totp_verification_failed": "Bekræftelse af TOTP mislykkedes",
|
"totp_verification_failed": "Bekræftelse af TOTP mislykkedes",
|
||||||
"transport_dest_exists": "Transport destination \"%s\" eksisterer",
|
"transport_dest_exists": "Transport destination \"%s\" eksisterer",
|
||||||
"u2f_verification_failed": "U2F-bekræftelse mislykkedes: %s",
|
"webauthn_verification_failed": "WebAuthn-bekræftelse mislykkedes: %s",
|
||||||
"fido2_verification_failed": "Bekræftelse af FIDO2 mislykkedes: %s",
|
"fido2_verification_failed": "Bekræftelse af FIDO2 mislykkedes: %s",
|
||||||
"unknown": "Der opstod en ukendt fejl",
|
"unknown": "Der opstod en ukendt fejl",
|
||||||
"unknown_tfa_method": "Ukendt TFA-metode",
|
"unknown_tfa_method": "Ukendt TFA-metode",
|
||||||
|
@ -881,7 +881,7 @@
|
||||||
"ui_texts": "Gemte ændringer til UI-tekster",
|
"ui_texts": "Gemte ændringer til UI-tekster",
|
||||||
"upload_success": "Filen blev uploadet",
|
"upload_success": "Filen blev uploadet",
|
||||||
"verified_totp_login": "Bekræftet TOTP-login",
|
"verified_totp_login": "Bekræftet TOTP-login",
|
||||||
"verified_u2f_login": "Bekræftet U2F-login",
|
"verified_webauthn_login": "Bekræftet WebAuthn-login",
|
||||||
"verified_fido2_login": "Bekræftet FIDO2-login",
|
"verified_fido2_login": "Bekræftet FIDO2-login",
|
||||||
"verified_yotp_login": "Bekræftet Yubico OTP-login"
|
"verified_yotp_login": "Bekræftet Yubico OTP-login"
|
||||||
},
|
},
|
||||||
|
@ -893,7 +893,7 @@
|
||||||
"disable_tfa": "Deaktiver TFA indtil næste vellykkede login",
|
"disable_tfa": "Deaktiver TFA indtil næste vellykkede login",
|
||||||
"enter_qr_code": "Din TOTP kode hvis din enhed ikke kan scanne QR-koder",
|
"enter_qr_code": "Din TOTP kode hvis din enhed ikke kan scanne QR-koder",
|
||||||
"error_code": "Fejl kode",
|
"error_code": "Fejl kode",
|
||||||
"init_u2f": "Initialiserer, vent venligst...",
|
"init_webauthn": "Initialiserer, vent venligst...",
|
||||||
"key_id": "En identifikator til din YubiKey",
|
"key_id": "En identifikator til din YubiKey",
|
||||||
"key_id_totp": "En identifikator for din nøgle",
|
"key_id_totp": "En identifikator for din nøgle",
|
||||||
"none": "Deaktivere",
|
"none": "Deaktivere",
|
||||||
|
@ -901,11 +901,11 @@
|
||||||
"scan_qr_code": "Scan venligst følgende kode med din godkendelsesapp, eller indtast koden manuelt.",
|
"scan_qr_code": "Scan venligst følgende kode med din godkendelsesapp, eller indtast koden manuelt.",
|
||||||
"select": "Vælg venligst",
|
"select": "Vælg venligst",
|
||||||
"set_tfa": "Set 2-faktor godkendelses metoden",
|
"set_tfa": "Set 2-faktor godkendelses metoden",
|
||||||
"start_u2f_validation": "Start validering",
|
"start_webauthn_validation": "Start validering",
|
||||||
"tfa": "2-faktor godkendelse",
|
"tfa": "2-faktor godkendelse",
|
||||||
"tfa_token_invalid": "TFA nøgle ugyldig",
|
"tfa_token_invalid": "TFA nøgle ugyldig",
|
||||||
"totp": "Tids-baseret OTP (Google Authenticator, Authy, etc.)",
|
"totp": "Tids-baseret OTP (Google Authenticator, Authy, etc.)",
|
||||||
"u2f": "U2F godkendelse",
|
"webauthn": "WebAuthn godkendelse",
|
||||||
"waiting_usb_auth": "<i>Venter på USB-enhed...</i><br><br>Tryk let på knappen på din USB-enhed nu.",
|
"waiting_usb_auth": "<i>Venter på USB-enhed...</i><br><br>Tryk let på knappen på din USB-enhed nu.",
|
||||||
"waiting_usb_register": "<i>Venter på USB-enhed...</i><br><br>Indtast din adgangskode ovenfor, og bekræft din registrering ved at trykke på knappen på din USB-enhed.",
|
"waiting_usb_register": "<i>Venter på USB-enhed...</i><br><br>Indtast din adgangskode ovenfor, og bekræft din registrering ved at trykke på knappen på din USB-enhed.",
|
||||||
"yubi_otp": "Yubico OTP godkendelse"
|
"yubi_otp": "Yubico OTP godkendelse"
|
||||||
|
|
|
@ -86,7 +86,7 @@
|
||||||
"public_comment": "Öffentlicher Kommentar",
|
"public_comment": "Öffentlicher Kommentar",
|
||||||
"quota_mb": "Speicherplatz (MiB)",
|
"quota_mb": "Speicherplatz (MiB)",
|
||||||
"relay_all": "Alle Empfänger-Adressen relayen",
|
"relay_all": "Alle Empfänger-Adressen relayen",
|
||||||
"relay_all_info": "↪ Wenn <b>nicht</b> alle Empfänger-Adressen relayt werden sollen, müssen \"blinde\" Mailboxen für jede Adresse, die relayt werden soll, erstellen werden.",
|
"relay_all_info": "↪ Wenn <b>nicht</b> alle Empfänger-Adressen relayt werden sollen, müssen \"blinde\" Mailboxen für jede Adresse, die relayt werden soll, erstellt werden.",
|
||||||
"relay_domain": "Diese Domain relayen",
|
"relay_domain": "Diese Domain relayen",
|
||||||
"relay_transport_info": "<div class=\"label label-info\">Info</div> Transport-Maps können erstellt werden, um individuelle Ziele für eine Relay-Domain zu definieren.",
|
"relay_transport_info": "<div class=\"label label-info\">Info</div> Transport-Maps können erstellt werden, um individuelle Ziele für eine Relay-Domain zu definieren.",
|
||||||
"relay_unknown_only": "Nur nicht-lokale Mailboxen relayen. Existente Mailboxen werden weiterhin lokal zugestellt.",
|
"relay_unknown_only": "Nur nicht-lokale Mailboxen relayen. Existente Mailboxen werden weiterhin lokal zugestellt.",
|
||||||
|
@ -455,7 +455,7 @@
|
||||||
"tls_policy_map_parameter_invalid": "Parameter ist ungültig",
|
"tls_policy_map_parameter_invalid": "Parameter ist ungültig",
|
||||||
"totp_verification_failed": "TOTP-Verifizierung fehlgeschlagen",
|
"totp_verification_failed": "TOTP-Verifizierung fehlgeschlagen",
|
||||||
"transport_dest_exists": "Transport-Maps-Ziel \"%s\" existiert bereits",
|
"transport_dest_exists": "Transport-Maps-Ziel \"%s\" existiert bereits",
|
||||||
"u2f_verification_failed": "U2F-Verifizierung fehlgeschlagen: %s",
|
"webauthn_verification_failed": "WebAuthn-Verifizierung fehlgeschlagen: %s",
|
||||||
"unknown": "Ein unbekannter Fehler trat auf",
|
"unknown": "Ein unbekannter Fehler trat auf",
|
||||||
"unknown_tfa_method": "Unbekannte TFA-Methode",
|
"unknown_tfa_method": "Unbekannte TFA-Methode",
|
||||||
"unlimited_quota_acl": "Unendliche Quota untersagt durch ACL",
|
"unlimited_quota_acl": "Unendliche Quota untersagt durch ACL",
|
||||||
|
@ -971,7 +971,7 @@
|
||||||
"upload_success": "Datei wurde erfolgreich hochgeladen",
|
"upload_success": "Datei wurde erfolgreich hochgeladen",
|
||||||
"verified_fido2_login": "FIDO2-Anmeldung verifiziert",
|
"verified_fido2_login": "FIDO2-Anmeldung verifiziert",
|
||||||
"verified_totp_login": "TOTP-Anmeldung verifiziert",
|
"verified_totp_login": "TOTP-Anmeldung verifiziert",
|
||||||
"verified_u2f_login": "U2F-Anmeldung verifiziert",
|
"verified_webauthn_login": "WebAuthn-Anmeldung verifiziert",
|
||||||
"verified_yotp_login": "Yubico-OTP-Anmeldung verifiziert"
|
"verified_yotp_login": "Yubico-OTP-Anmeldung verifiziert"
|
||||||
},
|
},
|
||||||
"tfa": {
|
"tfa": {
|
||||||
|
@ -982,19 +982,21 @@
|
||||||
"disable_tfa": "Deaktiviere 2FA bis zur nächsten erfolgreichen Anmeldung",
|
"disable_tfa": "Deaktiviere 2FA bis zur nächsten erfolgreichen Anmeldung",
|
||||||
"enter_qr_code": "Falls Sie den angezeigten QR-Code nicht scannen können, verwenden Sie bitte nachstehenden Sicherheitsschlüssel",
|
"enter_qr_code": "Falls Sie den angezeigten QR-Code nicht scannen können, verwenden Sie bitte nachstehenden Sicherheitsschlüssel",
|
||||||
"error_code": "Fehlercode",
|
"error_code": "Fehlercode",
|
||||||
"init_u2f": "Initialisiere, bitte warten...",
|
"init_webauthn": "Initialisiere, bitte warten...",
|
||||||
"key_id": "Ein Namen für diesen YubiKey",
|
"key_id": "Ein Namen für dieses Gerät",
|
||||||
"key_id_totp": "Ein eindeutiger Name",
|
"key_id_totp": "Ein eindeutiger Name",
|
||||||
"none": "Deaktiviert",
|
"none": "Deaktiviert",
|
||||||
"reload_retry": "- (bei persistierendem Fehler, bitte Browserfenster neu laden)",
|
"reload_retry": "- (bei persistierendem Fehler, bitte Browserfenster neu laden)",
|
||||||
"scan_qr_code": "Bitte scannen Sie jetzt den angezeigten QR-Code:",
|
"scan_qr_code": "Bitte scannen Sie jetzt den angezeigten QR-Code:",
|
||||||
"select": "Bitte auswählen",
|
"select": "Bitte auswählen",
|
||||||
"set_tfa": "Konfiguriere Zwei-Faktor-Authentifizierungsmethode",
|
"set_tfa": "Konfiguriere Zwei-Faktor-Authentifizierungsmethode",
|
||||||
"start_u2f_validation": "Starte Validierung",
|
"start_webauthn_validation": "Starte Validierung",
|
||||||
"tfa": "Zwei-Faktor-Authentifizierung",
|
"tfa": "Zwei-Faktor-Authentifizierung",
|
||||||
"tfa_token_invalid": "TFA-Token ungültig!",
|
"tfa_token_invalid": "TFA-Token ungültig!",
|
||||||
"totp": "Time-based-OTP (Google Authenticator etc.)",
|
"totp": "Time-based-OTP (Google Authenticator etc.)",
|
||||||
"u2f": "U2F-Authentifizierung",
|
"u2f_deprecated": "Es sieht so aus als wurde der Schlüssel mit der alten U2F Methode registriert. Wir werden die Zwei-Faktor-Authentifizierung deaktivieren und deinen Schlüssel löschen.",
|
||||||
|
"u2f_deprecated_important": "Bitte registriere den Schlüssel im Adminbereich mit der neuen WebAuthn Methode.",
|
||||||
|
"webauthn": "WebAuthn-Authentifizierung",
|
||||||
"waiting_usb_auth": "<i>Warte auf USB-Gerät...</i><br><br>Bitte jetzt den vorgesehenen Taster des USB-Gerätes berühren.",
|
"waiting_usb_auth": "<i>Warte auf USB-Gerät...</i><br><br>Bitte jetzt den vorgesehenen Taster des USB-Gerätes berühren.",
|
||||||
"waiting_usb_register": "<i>Warte auf USB-Gerät...</i><br><br>Bitte zuerst das obere Passwortfeld ausfüllen und erst dann den vorgesehenen Taster des USB-Gerätes berühren.",
|
"waiting_usb_register": "<i>Warte auf USB-Gerät...</i><br><br>Bitte zuerst das obere Passwortfeld ausfüllen und erst dann den vorgesehenen Taster des USB-Gerätes berühren.",
|
||||||
"yubi_otp": "Yubico OTP-Authentifizierung"
|
"yubi_otp": "Yubico OTP-Authentifizierung"
|
||||||
|
|
|
@ -455,7 +455,7 @@
|
||||||
"tls_policy_map_parameter_invalid": "Policy parameter is invalid",
|
"tls_policy_map_parameter_invalid": "Policy parameter is invalid",
|
||||||
"totp_verification_failed": "TOTP verification failed",
|
"totp_verification_failed": "TOTP verification failed",
|
||||||
"transport_dest_exists": "Transport destination \"%s\" exists",
|
"transport_dest_exists": "Transport destination \"%s\" exists",
|
||||||
"u2f_verification_failed": "U2F verification failed: %s",
|
"webauthn_verification_failed": "WebAuthn verification failed: %s",
|
||||||
"unknown": "An unknown error occurred",
|
"unknown": "An unknown error occurred",
|
||||||
"unknown_tfa_method": "Unknown TFA method",
|
"unknown_tfa_method": "Unknown TFA method",
|
||||||
"unlimited_quota_acl": "Unlimited quota prohibited by ACL",
|
"unlimited_quota_acl": "Unlimited quota prohibited by ACL",
|
||||||
|
@ -978,7 +978,7 @@
|
||||||
"upload_success": "File uploaded successfully",
|
"upload_success": "File uploaded successfully",
|
||||||
"verified_fido2_login": "Verified FIDO2 login",
|
"verified_fido2_login": "Verified FIDO2 login",
|
||||||
"verified_totp_login": "Verified TOTP login",
|
"verified_totp_login": "Verified TOTP login",
|
||||||
"verified_u2f_login": "Verified U2F login",
|
"verified_webauthn_login": "Verified WebAuthn login",
|
||||||
"verified_yotp_login": "Verified Yubico OTP login"
|
"verified_yotp_login": "Verified Yubico OTP login"
|
||||||
},
|
},
|
||||||
"tfa": {
|
"tfa": {
|
||||||
|
@ -989,19 +989,21 @@
|
||||||
"disable_tfa": "Disable TFA until next successful login",
|
"disable_tfa": "Disable TFA until next successful login",
|
||||||
"enter_qr_code": "Your TOTP code if your device cannot scan QR codes",
|
"enter_qr_code": "Your TOTP code if your device cannot scan QR codes",
|
||||||
"error_code": "Error code",
|
"error_code": "Error code",
|
||||||
"init_u2f": "Initializing, please wait...",
|
"init_webauthn": "Initializing, please wait...",
|
||||||
"key_id": "An identifier for your YubiKey",
|
"key_id": "An identifier for your Device",
|
||||||
"key_id_totp": "An identifier for your key",
|
"key_id_totp": "An identifier for your key",
|
||||||
"none": "Deactivate",
|
"none": "Deactivate",
|
||||||
"reload_retry": "- (reload browser if the error persists)",
|
"reload_retry": "- (reload browser if the error persists)",
|
||||||
"scan_qr_code": "Please scan the following code with your authenticator app or enter the code manually.",
|
"scan_qr_code": "Please scan the following code with your authenticator app or enter the code manually.",
|
||||||
"select": "Please select",
|
"select": "Please select",
|
||||||
"set_tfa": "Set two-factor authentication method",
|
"set_tfa": "Set two-factor authentication method",
|
||||||
"start_u2f_validation": "Start validation",
|
"start_webauthn_validation": "Start validation",
|
||||||
"tfa": "Two-factor authentication",
|
"tfa": "Two-factor authentication",
|
||||||
"tfa_token_invalid": "TFA token invalid",
|
"tfa_token_invalid": "TFA token invalid",
|
||||||
"totp": "Time-based OTP (Google Authenticator, Authy, etc.)",
|
"totp": "Time-based OTP (Google Authenticator, Authy, etc.)",
|
||||||
"u2f": "U2F authentication",
|
"u2f_deprecated": "It seems that your Key was registered using the deprecated U2F method. We will deactivate Two-Factor-Authenticaiton for you and delete your Key.",
|
||||||
|
"u2f_deprecated_important": "Please register your Key in the admin panel with the new WebAuthn method.",
|
||||||
|
"webauthn": "WebAuthn authentication",
|
||||||
"waiting_usb_auth": "<i>Waiting for USB device...</i><br><br>Please tap the button on your USB device now.",
|
"waiting_usb_auth": "<i>Waiting for USB device...</i><br><br>Please tap the button on your USB device now.",
|
||||||
"waiting_usb_register": "<i>Waiting for USB device...</i><br><br>Please enter your password above and confirm your registration by tapping the button on your USB device.",
|
"waiting_usb_register": "<i>Waiting for USB device...</i><br><br>Please enter your password above and confirm your registration by tapping the button on your USB device.",
|
||||||
"yubi_otp": "Yubico OTP authentication"
|
"yubi_otp": "Yubico OTP authentication"
|
||||||
|
|
|
@ -323,7 +323,7 @@
|
||||||
"tls_policy_map_parameter_invalid": "El parámetro de póliza no es válido.",
|
"tls_policy_map_parameter_invalid": "El parámetro de póliza no es válido.",
|
||||||
"totp_verification_failed": "Verificación TOTP fallida",
|
"totp_verification_failed": "Verificación TOTP fallida",
|
||||||
"transport_dest_exists": "Destino de la regla de transporte ya existe",
|
"transport_dest_exists": "Destino de la regla de transporte ya existe",
|
||||||
"u2f_verification_failed": "Verificación U2F fallida: %s",
|
"webauthn_verification_failed": "Verificación WebAuthn fallida: %s",
|
||||||
"unknown": "Se produjo un error desconocido",
|
"unknown": "Se produjo un error desconocido",
|
||||||
"unknown_tfa_method": "Método TFA desconocido",
|
"unknown_tfa_method": "Método TFA desconocido",
|
||||||
"unlimited_quota_acl": "Cuota ilimitada restringida por controles administrativos",
|
"unlimited_quota_acl": "Cuota ilimitada restringida por controles administrativos",
|
||||||
|
@ -649,7 +649,7 @@
|
||||||
"tls_policy_map_entry_deleted": "Regla de póliza de TLS con ID %s ha sido elimindada",
|
"tls_policy_map_entry_deleted": "Regla de póliza de TLS con ID %s ha sido elimindada",
|
||||||
"tls_policy_map_entry_saved": "Regla de póliza de TLS \"%s\" ha sido guardada",
|
"tls_policy_map_entry_saved": "Regla de póliza de TLS \"%s\" ha sido guardada",
|
||||||
"verified_totp_login": "Inicio de sesión TOTP verificado",
|
"verified_totp_login": "Inicio de sesión TOTP verificado",
|
||||||
"verified_u2f_login": "Inicio de sesión U2F verificado",
|
"verified_webauthn_login": "Inicio de sesión WebAuthn verificado",
|
||||||
"verified_yotp_login": "Inicio de sesión Yubico OTP verificado"
|
"verified_yotp_login": "Inicio de sesión Yubico OTP verificado"
|
||||||
},
|
},
|
||||||
"tfa": {
|
"tfa": {
|
||||||
|
@ -667,9 +667,9 @@
|
||||||
"set_tfa": "Establecer el método de autenticación de dos factores",
|
"set_tfa": "Establecer el método de autenticación de dos factores",
|
||||||
"tfa": "Autenticación de dos factores",
|
"tfa": "Autenticación de dos factores",
|
||||||
"totp": "OTP basado en tiempo (Google Authenticator, Authy, etc.)",
|
"totp": "OTP basado en tiempo (Google Authenticator, Authy, etc.)",
|
||||||
"u2f": "Autenticación U2F",
|
"webauthn": "Autenticación WebAuthn",
|
||||||
"waiting_usb_auth": "<i>Esperando al dispositivo USB...</i><br><br>Toque el botón en su dispositivo USB U2F ahora.",
|
"waiting_usb_auth": "<i>Esperando al dispositivo USB...</i><br><br>Toque el botón en su dispositivo USB WebAuthn ahora.",
|
||||||
"waiting_usb_register": "<i>Esperando al dispositivo USB....</i><br><br>Ingrese su contraseña arriba y confirme su registro U2F tocando el botón en su dispositivo USB U2F.",
|
"waiting_usb_register": "<i>Esperando al dispositivo USB....</i><br><br>Ingrese su contraseña arriba y confirme su registro WebAuthn tocando el botón en su dispositivo USB WebAuthn.",
|
||||||
"yubi_otp": "Yubico OTP"
|
"yubi_otp": "Yubico OTP"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
|
|
|
@ -370,7 +370,7 @@
|
||||||
"tls_policy_map_parameter_invalid": "Käytäntö parametri ei kelpaa",
|
"tls_policy_map_parameter_invalid": "Käytäntö parametri ei kelpaa",
|
||||||
"totp_verification_failed": "TOTP-vahvistus epäonnistui",
|
"totp_verification_failed": "TOTP-vahvistus epäonnistui",
|
||||||
"transport_dest_exists": "Kuljetuksen määränpää \"%s\" olemassa",
|
"transport_dest_exists": "Kuljetuksen määränpää \"%s\" olemassa",
|
||||||
"u2f_verification_failed": "U2F vahvistaminen epäonnistui: %s",
|
"webauthn_verification_failed": "WebAuthn vahvistaminen epäonnistui: %s",
|
||||||
"unknown": "Ilmeni tuntematon virhe",
|
"unknown": "Ilmeni tuntematon virhe",
|
||||||
"unknown_tfa_method": "Tuntematon TFA-menetelmä",
|
"unknown_tfa_method": "Tuntematon TFA-menetelmä",
|
||||||
"unlimited_quota_acl": "Rajoittamaton kiintiö kielletty ACL",
|
"unlimited_quota_acl": "Rajoittamaton kiintiö kielletty ACL",
|
||||||
|
@ -754,7 +754,7 @@
|
||||||
"ui_texts": "Tallennettu käyttöliittymätekstien muutokset",
|
"ui_texts": "Tallennettu käyttöliittymätekstien muutokset",
|
||||||
"upload_success": "Tiedosto ladattu onnistuneesti",
|
"upload_success": "Tiedosto ladattu onnistuneesti",
|
||||||
"verified_totp_login": "Vahvistettu TOTP-kirjautuminen",
|
"verified_totp_login": "Vahvistettu TOTP-kirjautuminen",
|
||||||
"verified_u2f_login": "Vahvistettu U2F kirjautuminen",
|
"verified_webauthn_login": "Vahvistettu WebAuthn kirjautuminen",
|
||||||
"verified_yotp_login": "Vahvistettu Yubico OTP kirjautuminen"
|
"verified_yotp_login": "Vahvistettu Yubico OTP kirjautuminen"
|
||||||
},
|
},
|
||||||
"tfa": {
|
"tfa": {
|
||||||
|
@ -765,7 +765,7 @@
|
||||||
"disable_tfa": "Poista TFA käytöstä seuraavaan onnistuneen kirjautumisen jälkeen",
|
"disable_tfa": "Poista TFA käytöstä seuraavaan onnistuneen kirjautumisen jälkeen",
|
||||||
"enter_qr_code": "TOTP-koodisi, jos laitteesi ei pysty tarkistamaan QR-koodeja",
|
"enter_qr_code": "TOTP-koodisi, jos laitteesi ei pysty tarkistamaan QR-koodeja",
|
||||||
"error_code": "Virhekoodi",
|
"error_code": "Virhekoodi",
|
||||||
"init_u2f": "Alustetaan, odota...",
|
"init_webauthn": "Alustetaan, odota...",
|
||||||
"key_id": "Tunniste YubiKey",
|
"key_id": "Tunniste YubiKey",
|
||||||
"key_id_totp": "Avaimen tunnus",
|
"key_id_totp": "Avaimen tunnus",
|
||||||
"none": "Poista",
|
"none": "Poista",
|
||||||
|
@ -773,12 +773,12 @@
|
||||||
"scan_qr_code": "Tarkista seuraava koodi Authenticator-sovelluksella tai Syötä koodi manuaalisesti.",
|
"scan_qr_code": "Tarkista seuraava koodi Authenticator-sovelluksella tai Syötä koodi manuaalisesti.",
|
||||||
"select": "Valitse",
|
"select": "Valitse",
|
||||||
"set_tfa": "Määritä kaksiosainen todennus menetelmä",
|
"set_tfa": "Määritä kaksiosainen todennus menetelmä",
|
||||||
"start_u2f_validation": "Aloita oikeellisuus tarkistus",
|
"start_webauthn_validation": "Aloita oikeellisuus tarkistus",
|
||||||
"tfa": "Kaksiosainen todennus",
|
"tfa": "Kaksiosainen todennus",
|
||||||
"totp": "Aikapohjainen OTP (Google Authenticator, Authy jne.)",
|
"totp": "Aikapohjainen OTP (Google Authenticator, Authy jne.)",
|
||||||
"u2f": "U2F todennus",
|
"webauthn": "WebAuthn todennus",
|
||||||
"waiting_usb_auth": "<i>Odotetaan USB-laitetta...</i><br><br>Napauta painiketta U2F USB-laitteessa nyt",
|
"waiting_usb_auth": "<i>Odotetaan USB-laitetta...</i><br><br>Napauta painiketta WebAuthn USB-laitteessa nyt",
|
||||||
"waiting_usb_register": "<i>Odotetaan USB-laitetta...</i><br><br>Anna salasanasi yltä ja vahvista U2F-rekisteröinti napauttamalla painiketta U2F USB-laitteessa.",
|
"waiting_usb_register": "<i>Odotetaan USB-laitetta...</i><br><br>Anna salasanasi yltä ja vahvista WebAuthn-rekisteröinti napauttamalla painiketta WebAuthn USB-laitteessa.",
|
||||||
"yubi_otp": "Yubico OTP-todennus"
|
"yubi_otp": "Yubico OTP-todennus"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
|
|
|
@ -259,7 +259,7 @@
|
||||||
"recipients": "Destinataires",
|
"recipients": "Destinataires",
|
||||||
"refresh": "Rafraîchir",
|
"refresh": "Rafraîchir",
|
||||||
"regen_api_key": "Regénérer la clé API",
|
"regen_api_key": "Regénérer la clé API",
|
||||||
"regex_maps": "Cartes Regex (Regex = expression régulière)",
|
"regex_maps": "Cartes Regex (expression régulière)",
|
||||||
"relay_from": "\"De:\" adresse",
|
"relay_from": "\"De:\" adresse",
|
||||||
"relay_run": "Lancer le test",
|
"relay_run": "Lancer le test",
|
||||||
"relayhosts": "Transports de l’expéditeur",
|
"relayhosts": "Transports de l’expéditeur",
|
||||||
|
@ -430,7 +430,7 @@
|
||||||
"tls_policy_map_parameter_invalid": "Le paramètre Policy est invalide",
|
"tls_policy_map_parameter_invalid": "Le paramètre Policy est invalide",
|
||||||
"totp_verification_failed": "Echec de la vérification TOTP",
|
"totp_verification_failed": "Echec de la vérification TOTP",
|
||||||
"transport_dest_exists": "La destination de transport \"%s\" existe",
|
"transport_dest_exists": "La destination de transport \"%s\" existe",
|
||||||
"u2f_verification_failed": "Echec de la vérification U2F: %s",
|
"webauthn_verification_failed": "Echec de la vérification WebAuthn: %s",
|
||||||
"fido2_verification_failed": "La vérification FIDO2 a échoué: %s",
|
"fido2_verification_failed": "La vérification FIDO2 a échoué: %s",
|
||||||
"unknown": "Une erreur inconnue est survenue",
|
"unknown": "Une erreur inconnue est survenue",
|
||||||
"unknown_tfa_method": "Methode TFA inconnue",
|
"unknown_tfa_method": "Methode TFA inconnue",
|
||||||
|
@ -699,7 +699,7 @@
|
||||||
"private_comment": "Commentaire privé",
|
"private_comment": "Commentaire privé",
|
||||||
"public_comment": "Commentaire public",
|
"public_comment": "Commentaire public",
|
||||||
"q_add_header": "Courriers indésirables",
|
"q_add_header": "Courriers indésirables",
|
||||||
"q_all": "Toutes les catégories",
|
"q_all": " quand déplacé dans le dossier spam ou rejeté",
|
||||||
"q_reject": "Rejecté",
|
"q_reject": "Rejecté",
|
||||||
"quarantine_notification": "Avis de quarantaine",
|
"quarantine_notification": "Avis de quarantaine",
|
||||||
"quarantine_category": "Catégorie de la notification de quarantaine",
|
"quarantine_category": "Catégorie de la notification de quarantaine",
|
||||||
|
@ -729,7 +729,7 @@
|
||||||
"sogo_visible": "Alias visible dans SOGo",
|
"sogo_visible": "Alias visible dans SOGo",
|
||||||
"sogo_visible_n": "Masquer alias dans SOGo",
|
"sogo_visible_n": "Masquer alias dans SOGo",
|
||||||
"sogo_visible_y": "Afficher alias dans SOGo",
|
"sogo_visible_y": "Afficher alias dans SOGo",
|
||||||
"spam_aliases": "Alias temp.",
|
"spam_aliases": "Alias temporaire",
|
||||||
"stats": "Statistiques",
|
"stats": "Statistiques",
|
||||||
"status": "Statut",
|
"status": "Statut",
|
||||||
"sync_jobs": "Tâches de synchronisation",
|
"sync_jobs": "Tâches de synchronisation",
|
||||||
|
@ -895,7 +895,7 @@
|
||||||
"ui_texts": "Enregistrement des modifications apportées aux textes de l’interface utilisateur",
|
"ui_texts": "Enregistrement des modifications apportées aux textes de l’interface utilisateur",
|
||||||
"upload_success": "Fichier téléchargé avec succès",
|
"upload_success": "Fichier téléchargé avec succès",
|
||||||
"verified_totp_login": "Authentification TOTP vérifiée",
|
"verified_totp_login": "Authentification TOTP vérifiée",
|
||||||
"verified_u2f_login": "Authentification U2F vérifiée",
|
"verified_webauthn_login": "Authentification WebAuthn vérifiée",
|
||||||
"verified_fido2_login": "Authentification FIDO2 vérifiée",
|
"verified_fido2_login": "Authentification FIDO2 vérifiée",
|
||||||
"verified_yotp_login": "Authentification Yubico OTP vérifiée"
|
"verified_yotp_login": "Authentification Yubico OTP vérifiée"
|
||||||
},
|
},
|
||||||
|
@ -907,7 +907,7 @@
|
||||||
"disable_tfa": "Désactiver TFA jusqu’à la prochaine ouverture de session réussie",
|
"disable_tfa": "Désactiver TFA jusqu’à la prochaine ouverture de session réussie",
|
||||||
"enter_qr_code": "Votre code TOTP si votre appareil ne peut pas scanner les codes QR",
|
"enter_qr_code": "Votre code TOTP si votre appareil ne peut pas scanner les codes QR",
|
||||||
"error_code": "Code d'erreur",
|
"error_code": "Code d'erreur",
|
||||||
"init_u2f": "Initialisation, veuillez patienter...",
|
"init_webauthn": "Initialisation, veuillez patienter...",
|
||||||
"key_id": "Un identifiant pour votre Yubikey",
|
"key_id": "Un identifiant pour votre Yubikey",
|
||||||
"key_id_totp": "Un identifiant pour votre clé",
|
"key_id_totp": "Un identifiant pour votre clé",
|
||||||
"none": "Désactiver",
|
"none": "Désactiver",
|
||||||
|
@ -915,13 +915,13 @@
|
||||||
"scan_qr_code": "Veuillez scanner le code suivant avec votre application d’authentification ou entrer le code manuellement.",
|
"scan_qr_code": "Veuillez scanner le code suivant avec votre application d’authentification ou entrer le code manuellement.",
|
||||||
"select": "Veuillez sélectionner",
|
"select": "Veuillez sélectionner",
|
||||||
"set_tfa": "Définir une méthode d’authentification à deux facteurs",
|
"set_tfa": "Définir une méthode d’authentification à deux facteurs",
|
||||||
"start_u2f_validation": "Début de la validation",
|
"start_webauthn_validation": "Début de la validation",
|
||||||
"tfa": "Authentification à deux facteurs",
|
"tfa": "Authentification à deux facteurs",
|
||||||
"tfa_token_invalid": "Token TFA invalide",
|
"tfa_token_invalid": "Token TFA invalide",
|
||||||
"totp": "OTP (One Time Password = Mot de passe à usage unique : Google Authenticator, Authy, etc.)",
|
"totp": "OTP (One Time Password = Mot de passe à usage unique : Google Authenticator, Authy, etc.)",
|
||||||
"u2f": "Authentification U2F",
|
"webauthn": "Authentification WebAuthn",
|
||||||
"waiting_usb_auth": "<i>En attente d’un périphérique USB...</i><br><br>S’il vous plaît appuyez maintenant sur le bouton de votre périphérique USB U2F.",
|
"waiting_usb_auth": "<i>En attente d’un périphérique USB...</i><br><br>S’il vous plaît appuyez maintenant sur le bouton de votre périphérique USB WebAuthn.",
|
||||||
"waiting_usb_register": "<i>En attente d’un périphérique USB...</i><br><br>Veuillez entrer votre mot de passe ci-dessus et confirmer votre inscription U2F en appuyant sur le bouton de votre périphérique USB U2F.",
|
"waiting_usb_register": "<i>En attente d’un périphérique USB...</i><br><br>Veuillez entrer votre mot de passe ci-dessus et confirmer votre inscription WebAuthn en appuyant sur le bouton de votre périphérique USB WebAuthn.",
|
||||||
"yubi_otp": "Authentification OTP Yubico"
|
"yubi_otp": "Authentification OTP Yubico"
|
||||||
},
|
},
|
||||||
"fido2": {
|
"fido2": {
|
||||||
|
@ -1070,7 +1070,7 @@
|
||||||
"warning": {
|
"warning": {
|
||||||
"cannot_delete_self": "Impossible de supprimer l’utilisateur connecté",
|
"cannot_delete_self": "Impossible de supprimer l’utilisateur connecté",
|
||||||
"domain_added_sogo_failed": "Ajout d’un domaine mais échec du redémarrage de Sogo, veuillez vérifier les journaux de votre serveur.",
|
"domain_added_sogo_failed": "Ajout d’un domaine mais échec du redémarrage de Sogo, veuillez vérifier les journaux de votre serveur.",
|
||||||
"dovecot_restart_failed": "Dovecot n’a pas pu redémarrer, veuillez vérifier les journaux de votre serveur.",
|
"dovecot_restart_failed": "Dovecot n’a pas pu redémarrer, veuillez vérifier les journaux",
|
||||||
"fuzzy_learn_error": "Erreur d’apprentissage du hachage flou: %s",
|
"fuzzy_learn_error": "Erreur d’apprentissage du hachage flou: %s",
|
||||||
"hash_not_found": "Hachage non trouvé ou déjà supprimé",
|
"hash_not_found": "Hachage non trouvé ou déjà supprimé",
|
||||||
"ip_invalid": "IP non valide ignorée: %s",
|
"ip_invalid": "IP non valide ignorée: %s",
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
"quarantine_attachments": "Allegati in quarantena",
|
"quarantine_attachments": "Allegati in quarantena",
|
||||||
"quarantine_category": "Modifica la categoria delle notifiche di quarantena",
|
"quarantine_category": "Modifica la categoria delle notifiche di quarantena",
|
||||||
"quarantine_notification": "Modifica notifiche quarantena",
|
"quarantine_notification": "Modifica notifiche quarantena",
|
||||||
"ratelimit": "Rate limit",
|
"ratelimit": "Limite di invio",
|
||||||
"recipient_maps": "Recipient maps",
|
"recipient_maps": "Mappe dei destinatari",
|
||||||
"smtp_ip_access": "Modifica gli host consentiti per SMTP",
|
"smtp_ip_access": "Modifica gli host consentiti per SMTP",
|
||||||
"sogo_access": "Consenti la gestione dell'accesso SOGo",
|
"sogo_access": "Consenti la gestione dell'accesso SOGo",
|
||||||
"sogo_profile_reset": "Ripristina profilo SOGo",
|
"sogo_profile_reset": "Ripristina profilo SOGo",
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
"spam_policy": "Blacklist/Whitelist",
|
"spam_policy": "Blacklist/Whitelist",
|
||||||
"spam_score": "Punteggio SPAM",
|
"spam_score": "Punteggio SPAM",
|
||||||
"syncjobs": "Processi di sync",
|
"syncjobs": "Processi di sync",
|
||||||
"tls_policy": "TLS policy",
|
"tls_policy": "Politica TLS",
|
||||||
"unlimited_quota": "Spazio illimitato per le caselle di posta"
|
"unlimited_quota": "Spazio illimitato per le caselle di posta"
|
||||||
},
|
},
|
||||||
"add": {
|
"add": {
|
||||||
|
@ -102,10 +102,11 @@
|
||||||
"target_domain": "Dominio di destinazione",
|
"target_domain": "Dominio di destinazione",
|
||||||
"timeout1": "Timeout per la connessione all'host remoto",
|
"timeout1": "Timeout per la connessione all'host remoto",
|
||||||
"timeout2": "Timeout per la connessione all'host locale",
|
"timeout2": "Timeout per la connessione all'host locale",
|
||||||
"username": "Username",
|
"username": "Nome utente",
|
||||||
"validate": "Convalida",
|
"validate": "Convalida",
|
||||||
"validation_success": "Convalidato con successo",
|
"validation_success": "Convalidato con successo",
|
||||||
"bcc_dest_format": "Il destinatario in copia nascosta, dev'essere un solo indirizzo email.<br>Se devi spedire una copia del messaggio a più destinatari, crea un alias ed utilizzalo per questa opzione."
|
"bcc_dest_format": "Il destinatario in copia nascosta deve essere un singolo indirizzo email.<br>Se si vuole spedire una copia del messaggio a più destinatari, bisogna creare un alias ed utilizzarlo per questa opzione.",
|
||||||
|
"app_passwd_protocols": "Protocolli consentiti per la password dell'app"
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"access": "Accedi",
|
"access": "Accedi",
|
||||||
|
@ -133,18 +134,18 @@
|
||||||
"advanced_settings": "Impostazioni avanzate",
|
"advanced_settings": "Impostazioni avanzate",
|
||||||
"api_allow_from": "Allow API access from these IPs/CIDR network notations",
|
"api_allow_from": "Allow API access from these IPs/CIDR network notations",
|
||||||
"api_info": "The API is a work in progress. The documentation can be found at <a href=\"/api\">/api</a>",
|
"api_info": "The API is a work in progress. The documentation can be found at <a href=\"/api\">/api</a>",
|
||||||
"api_key": "API key",
|
"api_key": "Chiave API",
|
||||||
"api_skip_ip_check": "Skip IP check for API",
|
"api_skip_ip_check": "Salta il controllo dell'IP per l'API",
|
||||||
"app_links": "App links",
|
"app_links": "App links",
|
||||||
"app_name": "App name",
|
"app_name": "Nome dell'app",
|
||||||
"apps_name": "\"mailcow Apps\" name",
|
"apps_name": "Nome \"mailcow Apps\"",
|
||||||
"arrival_time": "Arrival time (server time)",
|
"arrival_time": "Ora di arrivo (ora del server)",
|
||||||
"authed_user": "Auth. user",
|
"authed_user": "Auth. user",
|
||||||
"ays": "Sei sicuro di voler procedere?",
|
"ays": "Sei sicuro di voler procedere?",
|
||||||
"ban_list_info": "See a list of banned IPs below: <b>network (remaining ban time) - [actions]</b>.<br />IPs queued to be unbanned will be removed from the active ban list within a few seconds.<br />Red labels indicate active permanent bans by blacklisting.",
|
"ban_list_info": "See a list of banned IPs below: <b>network (remaining ban time) - [actions]</b>.<br />IPs queued to be unbanned will be removed from the active ban list within a few seconds.<br />Red labels indicate active permanent bans by blacklisting.",
|
||||||
"change_logo": "Cambia logo",
|
"change_logo": "Cambia logo",
|
||||||
"configuration": "Configurazione",
|
"configuration": "Configurazione",
|
||||||
"convert_html_to_text": "Convert HTML to plain text",
|
"convert_html_to_text": "Converti HTML in testo semplice",
|
||||||
"credentials_transport_warning": "<b>Warning</b>: Adding a new transport map entry will update the credentials for all entries with a matching next hop column.",
|
"credentials_transport_warning": "<b>Warning</b>: Adding a new transport map entry will update the credentials for all entries with a matching next hop column.",
|
||||||
"customer_id": "ID cliente",
|
"customer_id": "ID cliente",
|
||||||
"customize": "Personalizzare",
|
"customize": "Personalizzare",
|
||||||
|
@ -190,7 +191,7 @@
|
||||||
"forwarding_hosts_add_hint": "È possibile specificare indirizzi IPv4 / IPv6, reti nella notazione CIDR, nomi host (che verranno risolti in indirizzi IP) o nomi di dominio (che verranno risolti agli indirizzi IP richiamando i record SPF o, in assenza, i record MX) .",
|
"forwarding_hosts_add_hint": "È possibile specificare indirizzi IPv4 / IPv6, reti nella notazione CIDR, nomi host (che verranno risolti in indirizzi IP) o nomi di dominio (che verranno risolti agli indirizzi IP richiamando i record SPF o, in assenza, i record MX) .",
|
||||||
"forwarding_hosts_hint": "I messaggi in entrata sono accettati in maniera incondizionata da tutti gli host qui elencati. Questi host sono quindi non controllati tramite DNSBL o sottoposti a greylisting. Lo spam ricevuto da questi host non viene mai rifiutato, ma potrebbe essere archiviato nella cartella Posta indesiderata. L'utilizzo più comune è quello di specificare i server di posta elettronica su cui è stata impostata una regola che inoltra le email in arrivo al server mailcow.",
|
"forwarding_hosts_hint": "I messaggi in entrata sono accettati in maniera incondizionata da tutti gli host qui elencati. Questi host sono quindi non controllati tramite DNSBL o sottoposti a greylisting. Lo spam ricevuto da questi host non viene mai rifiutato, ma potrebbe essere archiviato nella cartella Posta indesiderata. L'utilizzo più comune è quello di specificare i server di posta elettronica su cui è stata impostata una regola che inoltra le email in arrivo al server mailcow.",
|
||||||
"from": "Da",
|
"from": "Da",
|
||||||
"generate": "generate",
|
"generate": "generare",
|
||||||
"guid": "GUID - ID istanza univoco",
|
"guid": "GUID - ID istanza univoco",
|
||||||
"guid_and_license": "GUID & Licenza",
|
"guid_and_license": "GUID & Licenza",
|
||||||
"hash_remove_info": "Removing a ratelimit hash (if still existing) will reset its counter completely.<br>\r\n Each hash is indicated by an individual color.",
|
"hash_remove_info": "Removing a ratelimit hash (if still existing) will reset its counter completely.<br>\r\n Each hash is indicated by an individual color.",
|
||||||
|
@ -205,7 +206,7 @@
|
||||||
"include_exclude_info": "Di default - se nessuna voce viene selezionata - <b>tutte le caselle di posta</b> risultano attivate",
|
"include_exclude_info": "Di default - se nessuna voce viene selezionata - <b>tutte le caselle di posta</b> risultano attivate",
|
||||||
"includes": "Includi questi destinatari",
|
"includes": "Includi questi destinatari",
|
||||||
"is_mx_based": "Basato sul record MX",
|
"is_mx_based": "Basato sul record MX",
|
||||||
"last_applied": "Last applied",
|
"last_applied": "Ultima applicazione",
|
||||||
"license_info": "Non è necessario essere in possesso di una licenza ma aiuta gli sviluppatori a far crescere il prodotto.<br><a href=\"https://www.servercow.de/mailcow?lang=en#sal\" target=\"_blank\" alt=\"SAL order\">Registra qui il tuo GUID </a> oppure <a href=\"https://www.servercow.de/mailcow?lang=en#support\" target=\"_blank\" alt=\"Support order\">acquista il supporto per la tua installazione di mailcow.</a>",
|
"license_info": "Non è necessario essere in possesso di una licenza ma aiuta gli sviluppatori a far crescere il prodotto.<br><a href=\"https://www.servercow.de/mailcow?lang=en#sal\" target=\"_blank\" alt=\"SAL order\">Registra qui il tuo GUID </a> oppure <a href=\"https://www.servercow.de/mailcow?lang=en#support\" target=\"_blank\" alt=\"Support order\">acquista il supporto per la tua installazione di mailcow.</a>",
|
||||||
"link": "Link",
|
"link": "Link",
|
||||||
"loading": "Caricamento in corso...",
|
"loading": "Caricamento in corso...",
|
||||||
|
@ -230,7 +231,7 @@
|
||||||
"optional": "facoltativo",
|
"optional": "facoltativo",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"password_length": "Lunghezza password",
|
"password_length": "Lunghezza password",
|
||||||
"password_policy": "Password policy",
|
"password_policy": "Criteri della password",
|
||||||
"password_policy_chars": "Deve contenere almeno un carattere alfabetico",
|
"password_policy_chars": "Deve contenere almeno un carattere alfabetico",
|
||||||
"password_policy_length": "La lunghezza minima della password è %d caratteri",
|
"password_policy_length": "La lunghezza minima della password è %d caratteri",
|
||||||
"password_policy_lowerupper": "Deve contenere caratteri minuscoli e maiuscoli",
|
"password_policy_lowerupper": "Deve contenere caratteri minuscoli e maiuscoli",
|
||||||
|
@ -282,7 +283,7 @@
|
||||||
"remove": "Rimuovi",
|
"remove": "Rimuovi",
|
||||||
"remove_row": "Elimina riga",
|
"remove_row": "Elimina riga",
|
||||||
"reset_default": "Riporta alle condizioni di default",
|
"reset_default": "Riporta alle condizioni di default",
|
||||||
"reset_limit": "Remove hash",
|
"reset_limit": "Rimuovi hash",
|
||||||
"routing": "Routing",
|
"routing": "Routing",
|
||||||
"rsetting_add_rule": "Aggiungi regola",
|
"rsetting_add_rule": "Aggiungi regola",
|
||||||
"rsetting_content": "Contenuto della regola",
|
"rsetting_content": "Contenuto della regola",
|
||||||
|
@ -334,10 +335,15 @@
|
||||||
"unban_pending": "unban pending",
|
"unban_pending": "unban pending",
|
||||||
"unchanged_if_empty": "Se immutato lasciare vuoto",
|
"unchanged_if_empty": "Se immutato lasciare vuoto",
|
||||||
"upload": "Upload",
|
"upload": "Upload",
|
||||||
"username": "Username",
|
"username": "Nome utente",
|
||||||
"validate_license_now": "Validate GUID against license server",
|
"validate_license_now": "Validate GUID against license server",
|
||||||
"verify": "Verifica",
|
"verify": "Verifica",
|
||||||
"yes": "✓"
|
"yes": "✓",
|
||||||
|
"api_read_only": "Accesso in sola lettura",
|
||||||
|
"api_read_write": "Accesso in lettura-scrittura",
|
||||||
|
"oauth2_apps": "App OAuth2",
|
||||||
|
"oauth2_add_client": "Aggiungere il client OAuth2",
|
||||||
|
"rsettings_preset_4": "Disattivare Rspamd per un dominio"
|
||||||
},
|
},
|
||||||
"danger": {
|
"danger": {
|
||||||
"access_denied": "Accesso negato o form di login non corretto",
|
"access_denied": "Accesso negato o form di login non corretto",
|
||||||
|
@ -449,14 +455,14 @@
|
||||||
"tls_policy_map_parameter_invalid": "Policy parameter is invalid",
|
"tls_policy_map_parameter_invalid": "Policy parameter is invalid",
|
||||||
"totp_verification_failed": "TOTP verification failed",
|
"totp_verification_failed": "TOTP verification failed",
|
||||||
"transport_dest_exists": "Transport destination \"%s\" exists",
|
"transport_dest_exists": "Transport destination \"%s\" exists",
|
||||||
"u2f_verification_failed": "U2F verification failed: %s",
|
"webauthn_verification_failed": "WebAuthn verification failed: %s",
|
||||||
"unknown": "An unknown error occurred",
|
"unknown": "An unknown error occurred",
|
||||||
"unknown_tfa_method": "Unknown TFA method",
|
"unknown_tfa_method": "Unknown TFA method",
|
||||||
"unlimited_quota_acl": "Unlimited quota prohibited by ACL",
|
"unlimited_quota_acl": "Unlimited quota prohibited by ACL",
|
||||||
"username_invalid": "Username %s non può essere utilizzato",
|
"username_invalid": "Username %s non può essere utilizzato",
|
||||||
"validity_missing": "Assegnare un periodo di validità",
|
"validity_missing": "Assegnare un periodo di validità",
|
||||||
"value_missing": "Si prega di fornire tutti i valori",
|
"value_missing": "Si prega di fornire tutti i valori",
|
||||||
"yotp_verification_failed": "Verifica Yubico OTP fallita: %s"
|
"yotp_verification_failed": "Verifica OTP Yubico fallita: %s"
|
||||||
},
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"chart_this_server": "Grafico (questo server)",
|
"chart_this_server": "Grafico (questo server)",
|
||||||
|
@ -601,7 +607,13 @@
|
||||||
"title": "Modifica oggetto",
|
"title": "Modifica oggetto",
|
||||||
"unchanged_if_empty": "Se immutato lasciare vuoto",
|
"unchanged_if_empty": "Se immutato lasciare vuoto",
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"validate_save": "Convalida e salva"
|
"validate_save": "Convalida e salva",
|
||||||
|
"pushover": "Pushover",
|
||||||
|
"sogo_access_info": "Il single-sign-on dall'interno dell'interfaccia di posta rimane funzionante. Questa impostazione non influisce sull'accesso a tutti gli altri servizi né cancella o modifica il profilo SOGo esistente dell'utente.",
|
||||||
|
"none_inherit": "Nessuno / Eredita",
|
||||||
|
"sogo_access": "Concedere l'accesso diretto a SOGo",
|
||||||
|
"acl": "ACL (autorizzazione)",
|
||||||
|
"app_passwd_protocols": "Protocolli consentiti per la password dell'app"
|
||||||
},
|
},
|
||||||
"fido2": {
|
"fido2": {
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
|
@ -615,7 +627,8 @@
|
||||||
"rename": "Rename",
|
"rename": "Rename",
|
||||||
"set_fido2": "Register FIDO2 device",
|
"set_fido2": "Register FIDO2 device",
|
||||||
"set_fn": "Set friendly name",
|
"set_fn": "Set friendly name",
|
||||||
"start_fido2_validation": "Start FIDO2 validation"
|
"start_fido2_validation": "Start FIDO2 validation",
|
||||||
|
"set_fido2_touchid": "Registrare il Touch ID su Apple M1"
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"cancel": "Annulla",
|
"cancel": "Annulla",
|
||||||
|
@ -629,7 +642,8 @@
|
||||||
"restart_container_info": "<b>Importante:</b> Il completamento di un normale riavvio potrebbe richiedere diversi minuti, ti consigliamo di attendere.",
|
"restart_container_info": "<b>Importante:</b> Il completamento di un normale riavvio potrebbe richiedere diversi minuti, ti consigliamo di attendere.",
|
||||||
"restart_now": "Riavvia adesso",
|
"restart_now": "Riavvia adesso",
|
||||||
"restarting_container": "Riavvia il container, potrebbe richiedere diversi minuti",
|
"restarting_container": "Riavvia il container, potrebbe richiedere diversi minuti",
|
||||||
"hibp_check": "Verifica con haveibeenpwned.com"
|
"hibp_check": "Verifica con haveibeenpwned.com",
|
||||||
|
"nothing_selected": "Niente di selezionato"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"administration": "Amministrazione",
|
"administration": "Amministrazione",
|
||||||
|
@ -694,7 +708,7 @@
|
||||||
"bcc_to_rcpt": "Switch to recipient map type",
|
"bcc_to_rcpt": "Switch to recipient map type",
|
||||||
"bcc_to_sender": "Switch to sender map type",
|
"bcc_to_sender": "Switch to sender map type",
|
||||||
"bcc_type": "BCC type",
|
"bcc_type": "BCC type",
|
||||||
"booking_null": "Always show as free",
|
"booking_null": "Mostra sempre come libero",
|
||||||
"booking_0_short": "Always free",
|
"booking_0_short": "Always free",
|
||||||
"booking_custom": "Hard-limit to a custom amount of bookings",
|
"booking_custom": "Hard-limit to a custom amount of bookings",
|
||||||
"booking_custom_short": "Hard limit",
|
"booking_custom_short": "Hard limit",
|
||||||
|
@ -802,7 +816,17 @@
|
||||||
"goto_spam": "Apprendi come <b>spam</b>",
|
"goto_spam": "Apprendi come <b>spam</b>",
|
||||||
"open_logs": "Apri i log",
|
"open_logs": "Apri i log",
|
||||||
"syncjob_check_log": "Controlla il log",
|
"syncjob_check_log": "Controlla il log",
|
||||||
"syncjob_last_run_result": "Risultato dell'ultima esecuzione"
|
"syncjob_last_run_result": "Risultato dell'ultima esecuzione",
|
||||||
|
"syncjob_EXIT_TLS_FAILURE": "Problema con la connessione criptata",
|
||||||
|
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problema di autenticazione",
|
||||||
|
"syncjob_EXIT_OVERQUOTA": "La casella di posta del destinatario ha superato la quota",
|
||||||
|
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Impossibile connettersi al server remoto",
|
||||||
|
"syncjob_EXIT_CONNECTION_FAILURE": "Problema di connessione",
|
||||||
|
"catch_all": "Catch-All",
|
||||||
|
"sender": "Mittente",
|
||||||
|
"all_domains": "Tutti i domini",
|
||||||
|
"recipient": "Destinatario",
|
||||||
|
"syncjob_EX_OK": "Successo"
|
||||||
},
|
},
|
||||||
"oauth2": {
|
"oauth2": {
|
||||||
"access_denied": "Effettua il login alla casella di posta per garantire l'accesso tramite OAuth2.",
|
"access_denied": "Effettua il login alla casella di posta per garantire l'accesso tramite OAuth2.",
|
||||||
|
@ -947,7 +971,7 @@
|
||||||
"upload_success": "File caricato con successo",
|
"upload_success": "File caricato con successo",
|
||||||
"verified_fido2_login": "Verified FIDO2 login",
|
"verified_fido2_login": "Verified FIDO2 login",
|
||||||
"verified_totp_login": "Verified TOTP login",
|
"verified_totp_login": "Verified TOTP login",
|
||||||
"verified_u2f_login": "Verified U2F login",
|
"verified_webauthn_login": "Verified WebAuthn login",
|
||||||
"verified_yotp_login": "Verified Yubico OTP login"
|
"verified_yotp_login": "Verified Yubico OTP login"
|
||||||
},
|
},
|
||||||
"tfa": {
|
"tfa": {
|
||||||
|
@ -958,7 +982,7 @@
|
||||||
"disable_tfa": "Disabilita TFA fino al prossimo accesso",
|
"disable_tfa": "Disabilita TFA fino al prossimo accesso",
|
||||||
"enter_qr_code": "Il codice TOTP se il tuo dispositivo non è in grado di acquisire i codici QR",
|
"enter_qr_code": "Il codice TOTP se il tuo dispositivo non è in grado di acquisire i codici QR",
|
||||||
"error_code": "Codice di errore",
|
"error_code": "Codice di errore",
|
||||||
"init_u2f": "Inizializzazione, attendere prego...",
|
"init_webauthn": "Inizializzazione, attendere prego...",
|
||||||
"key_id": "Identificatore per il tuo YubiKey",
|
"key_id": "Identificatore per il tuo YubiKey",
|
||||||
"key_id_totp": "Identificatore per la tua chiave",
|
"key_id_totp": "Identificatore per la tua chiave",
|
||||||
"none": "Disattivato",
|
"none": "Disattivato",
|
||||||
|
@ -966,12 +990,12 @@
|
||||||
"scan_qr_code": "Esegui la scansione del seguente codice con l'applicazione di autenticazione o inserisci manualmente il codice.",
|
"scan_qr_code": "Esegui la scansione del seguente codice con l'applicazione di autenticazione o inserisci manualmente il codice.",
|
||||||
"select": "Seleziona",
|
"select": "Seleziona",
|
||||||
"set_tfa": "Imposta il metodo di autenticazione a due fattori",
|
"set_tfa": "Imposta il metodo di autenticazione a due fattori",
|
||||||
"start_u2f_validation": "Avvia convalida",
|
"start_webauthn_validation": "Avvia convalida",
|
||||||
"tfa": "Autenticazione a due fattori",
|
"tfa": "Autenticazione a due fattori",
|
||||||
"totp": "Time-based OTP (Google Authenticator etc.)",
|
"totp": "Time-based OTP (Google Authenticator etc.)",
|
||||||
"u2f": "Autenticazione U2F",
|
"webauthn": "Autenticazione WebAuthn",
|
||||||
"waiting_usb_auth": "<i>In attesa del device USB...</i><br /><br />Tocca ora il pulsante sul dispositivo U2F USB.",
|
"waiting_usb_auth": "<i>In attesa del device USB...</i><br /><br />Tocca ora il pulsante sul dispositivo WebAuthn USB.",
|
||||||
"waiting_usb_register": "<i>In attesa del device USB...</i><br /><br />Inserisci la tua password qui sopra e conferma la tua registrazione U2F toccando il pulsante del dispositivo U2F USB.",
|
"waiting_usb_register": "<i>In attesa del device USB...</i><br /><br />Inserisci la tua password qui sopra e conferma la tua registrazione WebAuthn toccando il pulsante del dispositivo WebAuthn USB.",
|
||||||
"yubi_otp": "Autenticazione Yubico OTP",
|
"yubi_otp": "Autenticazione Yubico OTP",
|
||||||
"tfa_token_invalid": "Token TFA non valido"
|
"tfa_token_invalid": "Token TFA non valido"
|
||||||
},
|
},
|
||||||
|
@ -1123,7 +1147,18 @@
|
||||||
"change_password_hint_app_passwords": "Il tuo account ha {{number_of_app_passwords}} password delle app che non verranno modificate. Per gestirle, vai alla scheda App passwords.",
|
"change_password_hint_app_passwords": "Il tuo account ha {{number_of_app_passwords}} password delle app che non verranno modificate. Per gestirle, vai alla scheda App passwords.",
|
||||||
"syncjob_check_log": "Controlla i log",
|
"syncjob_check_log": "Controlla i log",
|
||||||
"syncjob_last_run_result": "Risultato dell'ultima esecuzione",
|
"syncjob_last_run_result": "Risultato dell'ultima esecuzione",
|
||||||
"open_logs": "Apri i log"
|
"open_logs": "Apri i log",
|
||||||
|
"apple_connection_profile_with_app_password": "Una nuova password dell'app è stata generata ed aggiunta al profilo così che non sia più necessario inserire alcuna password quando si imposta il dispositivo. Si prega di non condividere il file in quanto concede l'accesso completo alla tua casella di posta elettronica.",
|
||||||
|
"allowed_protocols": "Protocolli consentiti",
|
||||||
|
"syncjob_EX_OK": "Successo",
|
||||||
|
"syncjob_EXIT_CONNECTION_FAILURE": "Problema di connessione",
|
||||||
|
"syncjob_EXIT_TLS_FAILURE": "Problema con la connessione criptata",
|
||||||
|
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problema di autenticazione",
|
||||||
|
"syncjob_EXIT_OVERQUOTA": "La casella di posta del destinatario ha superato la quota",
|
||||||
|
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Impossibile connettersi al server remoto",
|
||||||
|
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nome utente o password errati",
|
||||||
|
"with_app_password": "con password dell'app",
|
||||||
|
"direct_protocol_access": "Questo utente della mailbox ha <b>accesso diretto ed esterno</b> ai seguenti protocolli e applicazioni. Questa impostazione è controllata dal tuo amministratore. Le password delle applicazioni possono essere create per garantire l'accesso ai singoli protocolli e applicazioni.<br>Il pulsante \"Accedi alla webmail\" fornisce un singolo accesso a SOGo ed è sempre disponibile."
|
||||||
},
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
"cannot_delete_self": "Cannot delete logged in user",
|
"cannot_delete_self": "Cannot delete logged in user",
|
||||||
|
@ -1141,6 +1176,8 @@
|
||||||
"ratelimit": {
|
"ratelimit": {
|
||||||
"minute": "messaggi / minuto",
|
"minute": "messaggi / minuto",
|
||||||
"disabled": "Disabilitato",
|
"disabled": "Disabilitato",
|
||||||
"second": "messaggi / secondo"
|
"second": "messaggi / secondo",
|
||||||
|
"hour": "messaggi / ora",
|
||||||
|
"day": "messaggi / giorno"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -417,7 +417,7 @@
|
||||||
"tls_policy_map_parameter_invalid": "유효하지 않은 정책 매개변수",
|
"tls_policy_map_parameter_invalid": "유효하지 않은 정책 매개변수",
|
||||||
"totp_verification_failed": "TOTP 확인 실패",
|
"totp_verification_failed": "TOTP 확인 실패",
|
||||||
"transport_dest_exists": "전송 목적지 \"%s\"가 존재합니다.",
|
"transport_dest_exists": "전송 목적지 \"%s\"가 존재합니다.",
|
||||||
"u2f_verification_failed": "U2F 검증 실패: %s",
|
"webauthn_verification_failed": "WebAuthn 검증 실패: %s",
|
||||||
"unknown": "알 수 없는 오류 발생",
|
"unknown": "알 수 없는 오류 발생",
|
||||||
"unknown_tfa_method": "알 수 없는 TFA 방식",
|
"unknown_tfa_method": "알 수 없는 TFA 방식",
|
||||||
"unlimited_quota_acl": "ACL에 따라 할당량을 무제한으로 둘 수 없습니다.",
|
"unlimited_quota_acl": "ACL에 따라 할당량을 무제한으로 둘 수 없습니다.",
|
||||||
|
@ -852,7 +852,7 @@
|
||||||
"ui_texts": "Saved changes to UI texts",
|
"ui_texts": "Saved changes to UI texts",
|
||||||
"upload_success": "File uploaded successfully",
|
"upload_success": "File uploaded successfully",
|
||||||
"verified_totp_login": "Verified TOTP login",
|
"verified_totp_login": "Verified TOTP login",
|
||||||
"verified_u2f_login": "Verified U2F login",
|
"verified_webauthn_login": "Verified WebAuthn login",
|
||||||
"verified_yotp_login": "Verified Yubico OTP login"
|
"verified_yotp_login": "Verified Yubico OTP login"
|
||||||
},
|
},
|
||||||
"tfa": {
|
"tfa": {
|
||||||
|
@ -863,7 +863,7 @@
|
||||||
"disable_tfa": "Disable TFA until next successful login",
|
"disable_tfa": "Disable TFA until next successful login",
|
||||||
"enter_qr_code": "Your TOTP code if your device cannot scan QR codes",
|
"enter_qr_code": "Your TOTP code if your device cannot scan QR codes",
|
||||||
"error_code": "Error code",
|
"error_code": "Error code",
|
||||||
"init_u2f": "Initializing, please wait...",
|
"init_webauthn": "Initializing, please wait...",
|
||||||
"key_id": "An identifier for your YubiKey",
|
"key_id": "An identifier for your YubiKey",
|
||||||
"key_id_totp": "An identifier for your key",
|
"key_id_totp": "An identifier for your key",
|
||||||
"none": "Deactivate",
|
"none": "Deactivate",
|
||||||
|
@ -871,12 +871,12 @@
|
||||||
"scan_qr_code": "Please scan the following code with your authenticator app or enter the code manually.",
|
"scan_qr_code": "Please scan the following code with your authenticator app or enter the code manually.",
|
||||||
"select": "Please select",
|
"select": "Please select",
|
||||||
"set_tfa": "Set two-factor authentication method",
|
"set_tfa": "Set two-factor authentication method",
|
||||||
"start_u2f_validation": "Start validation",
|
"start_webauthn_validation": "Start validation",
|
||||||
"tfa": "Two-factor authentication",
|
"tfa": "Two-factor authentication",
|
||||||
"totp": "Time-based OTP (Google Authenticator, Authy, etc.)",
|
"totp": "Time-based OTP (Google Authenticator, Authy, etc.)",
|
||||||
"u2f": "U2F authentication",
|
"webauthn": "WebAuthn authentication",
|
||||||
"waiting_usb_auth": "<i>Waiting for USB device...</i><br><br>Please tap the button on your U2F USB device now.",
|
"waiting_usb_auth": "<i>Waiting for USB device...</i><br><br>Please tap the button on your WebAuthn USB device now.",
|
||||||
"waiting_usb_register": "<i>Waiting for USB device...</i><br><br>Please enter your password above and confirm your U2F registration by tapping the button on your U2F USB device.",
|
"waiting_usb_register": "<i>Waiting for USB device...</i><br><br>Please enter your password above and confirm your WebAuthn registration by tapping the button on your WebAuthn USB device.",
|
||||||
"yubi_otp": "Yubico OTP authentication"
|
"yubi_otp": "Yubico OTP authentication"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
|
|
|
@ -450,9 +450,9 @@
|
||||||
"set_tfa": "Uzstādīt difi faktoru autentifik;acijas metodi",
|
"set_tfa": "Uzstādīt difi faktoru autentifik;acijas metodi",
|
||||||
"tfa": "Divu faktoru autentifikācija",
|
"tfa": "Divu faktoru autentifikācija",
|
||||||
"totp": "Uz laiku bāzēta vienreizēja parole (Google Autentifikātors utt.)",
|
"totp": "Uz laiku bāzēta vienreizēja parole (Google Autentifikātors utt.)",
|
||||||
"u2f": "U2F autentifikācija",
|
"webauthn": "WebAuthn autentifikācija",
|
||||||
"waiting_usb_auth": "<i>Gaida USB ierīci...</i><br><br>Lūdzu, tagad nospiežiet pogu uz Jūsu U2F USB ierīces.",
|
"waiting_usb_auth": "<i>Gaida USB ierīci...</i><br><br>Lūdzu, tagad nospiežiet pogu uz Jūsu WebAuthn USB ierīces.",
|
||||||
"waiting_usb_register": "<i>Gaida USB ierīci...</i><br><br>Lūdzu augšā ievadiet Jūsu paroli un apstipriniet U2F reģistrāciju nospiežot pogu uz Jūsu U2F USB ierīces.",
|
"waiting_usb_register": "<i>Gaida USB ierīci...</i><br><br>Lūdzu augšā ievadiet Jūsu paroli un apstipriniet WebAuthn reģistrāciju nospiežot pogu uz Jūsu WebAuthn USB ierīces.",
|
||||||
"yubi_otp": "Yubico OTP autentifikators"
|
"yubi_otp": "Yubico OTP autentifikators"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
|
|
|
@ -428,7 +428,7 @@
|
||||||
"tls_policy_map_parameter_invalid": "Beleidsparameter is ongeldig",
|
"tls_policy_map_parameter_invalid": "Beleidsparameter is ongeldig",
|
||||||
"totp_verification_failed": "TOTP-verificatie mislukt",
|
"totp_verification_failed": "TOTP-verificatie mislukt",
|
||||||
"transport_dest_exists": "Transportbestemming \"%s\" bestaat reeds",
|
"transport_dest_exists": "Transportbestemming \"%s\" bestaat reeds",
|
||||||
"u2f_verification_failed": "U2F-verificatie mislukt: %s",
|
"webauthn_verification_failed": "WebAuthn-verificatie mislukt: %s",
|
||||||
"fido2_verification_failed": "FIDO2-verificatie mislukt: %s",
|
"fido2_verification_failed": "FIDO2-verificatie mislukt: %s",
|
||||||
"unknown": "Er is een onbekende fout opgetreden",
|
"unknown": "Er is een onbekende fout opgetreden",
|
||||||
"unknown_tfa_method": "Onbekende tweefactorauthenticatiemethode",
|
"unknown_tfa_method": "Onbekende tweefactorauthenticatiemethode",
|
||||||
|
@ -891,7 +891,7 @@
|
||||||
"ui_texts": "Wijzigingen aan labels en teksten zijn opgeslagen",
|
"ui_texts": "Wijzigingen aan labels en teksten zijn opgeslagen",
|
||||||
"upload_success": "Bestand succesvol geupload",
|
"upload_success": "Bestand succesvol geupload",
|
||||||
"verified_totp_login": "TOTP succesvol geverifieerd",
|
"verified_totp_login": "TOTP succesvol geverifieerd",
|
||||||
"verified_u2f_login": "U2F succesvol geverifieerd",
|
"verified_webauthn_login": "WebAuthn succesvol geverifieerd",
|
||||||
"verified_fido2_login": "FIDO2 succesvol geverifieerd",
|
"verified_fido2_login": "FIDO2 succesvol geverifieerd",
|
||||||
"verified_yotp_login": "Yubico OTP succesvol geverifieerd"
|
"verified_yotp_login": "Yubico OTP succesvol geverifieerd"
|
||||||
},
|
},
|
||||||
|
@ -903,7 +903,7 @@
|
||||||
"disable_tfa": "Pauzeer tweefactorauthenticatie tot de eerstvolgende succesvolle login",
|
"disable_tfa": "Pauzeer tweefactorauthenticatie tot de eerstvolgende succesvolle login",
|
||||||
"enter_qr_code": "Voer deze code in als je apparaat geen QR-codes kan scannen:",
|
"enter_qr_code": "Voer deze code in als je apparaat geen QR-codes kan scannen:",
|
||||||
"error_code": "Errorcode",
|
"error_code": "Errorcode",
|
||||||
"init_u2f": "Even geduld aub...",
|
"init_webauthn": "Even geduld aub...",
|
||||||
"key_id": "Geef deze YubiKey een naam",
|
"key_id": "Geef deze YubiKey een naam",
|
||||||
"key_id_totp": "Geef deze key een naam",
|
"key_id_totp": "Geef deze key een naam",
|
||||||
"none": "Deactiveer",
|
"none": "Deactiveer",
|
||||||
|
@ -911,13 +911,13 @@
|
||||||
"scan_qr_code": "Scan de volgende QR-code met je authenticatie-app:",
|
"scan_qr_code": "Scan de volgende QR-code met je authenticatie-app:",
|
||||||
"select": "Selecteer...",
|
"select": "Selecteer...",
|
||||||
"set_tfa": "Kies methode voor tweefactorauthenticatie",
|
"set_tfa": "Kies methode voor tweefactorauthenticatie",
|
||||||
"start_u2f_validation": "Start validatie",
|
"start_webauthn_validation": "Start validatie",
|
||||||
"tfa": "Tweefactorauthenticatie",
|
"tfa": "Tweefactorauthenticatie",
|
||||||
"tfa_token_invalid": "Tweefactorauthenticatietoken is ongeldig",
|
"tfa_token_invalid": "Tweefactorauthenticatietoken is ongeldig",
|
||||||
"totp": "TOTP (Step Two, Authy, etc.)",
|
"totp": "TOTP (Step Two, Authy, etc.)",
|
||||||
"u2f": "U2F",
|
"webauthn": "WebAuthn",
|
||||||
"waiting_usb_auth": "<i>In afwachting van USB-apparaat...</i><br><br>Druk nu op de knop van je U2F-apparaat.",
|
"waiting_usb_auth": "<i>In afwachting van USB-apparaat...</i><br><br>Druk nu op de knop van je WebAuthn-apparaat.",
|
||||||
"waiting_usb_register": "<i>In afwachting van USB-apparaat...</i><br><br>Voer je wachtwoord hierboven in en bevestig de registratie van het U2F-apparaat door op de knop van het apparaat te drukken.",
|
"waiting_usb_register": "<i>In afwachting van USB-apparaat...</i><br><br>Voer je wachtwoord hierboven in en bevestig de registratie van het WebAuthn-apparaat door op de knop van het apparaat te drukken.",
|
||||||
"yubi_otp": "Yubico OTP"
|
"yubi_otp": "Yubico OTP"
|
||||||
},
|
},
|
||||||
"fido2": {
|
"fido2": {
|
||||||
|
|
|
@ -329,9 +329,9 @@
|
||||||
"set_tfa": "Ustaw metodę uwierzytelniania dwuetapowego",
|
"set_tfa": "Ustaw metodę uwierzytelniania dwuetapowego",
|
||||||
"tfa": "Uwierzytelnianie dwuetapowe",
|
"tfa": "Uwierzytelnianie dwuetapowe",
|
||||||
"totp": "Time-based OTP (Google Authenticator itd.)",
|
"totp": "Time-based OTP (Google Authenticator itd.)",
|
||||||
"u2f": "Uwierzytelnianie U2F",
|
"webauthn": "Uwierzytelnianie WebAuthn",
|
||||||
"waiting_usb_auth": "<i>Czekam na urządzenie USB...</i><br><br>Wciśnij teraz przycisk na urządzeniu U2F USB.",
|
"waiting_usb_auth": "<i>Czekam na urządzenie USB...</i><br><br>Wciśnij teraz przycisk na urządzeniu WebAuthn USB.",
|
||||||
"waiting_usb_register": "<i> Czekam na urządzenie USB...</i><br><br>Wprowadź swoje hasło powyżej i potwierdź rejestrację U2F przez naciśnięcie przycisku na urządzeniu U2F USB.",
|
"waiting_usb_register": "<i> Czekam na urządzenie USB...</i><br><br>Wprowadź swoje hasło powyżej i potwierdź rejestrację WebAuthn przez naciśnięcie przycisku na urządzeniu WebAuthn USB.",
|
||||||
"yubi_otp": "Uwierzytelnianie Yubico OTP"
|
"yubi_otp": "Uwierzytelnianie Yubico OTP"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
|
|
|
@ -42,8 +42,10 @@
|
||||||
"alias_domain_info": "<small>Doar nume de domenii valide (separate prin virgulă).</small>",
|
"alias_domain_info": "<small>Doar nume de domenii valide (separate prin virgulă).</small>",
|
||||||
"app_name": "Nume aplicație",
|
"app_name": "Nume aplicație",
|
||||||
"app_password": "Adaugă parolă aplicație",
|
"app_password": "Adaugă parolă aplicație",
|
||||||
|
"app_passwd_protocols": "Protocoale permise pentru parola aplicației",
|
||||||
"automap": "Încearcă maparea automată a folderelor (\"Obiecte trimise\", \"Trimise\" => \"Trimise\" etc.)",
|
"automap": "Încearcă maparea automată a folderelor (\"Obiecte trimise\", \"Trimise\" => \"Trimise\" etc.)",
|
||||||
"backup_mx_options": "Opțiuni backup MX",
|
"backup_mx_options": "Opțiuni backup MX",
|
||||||
|
"bcc_dest_format": "Destinația BCC trebuie să fie o singură adresă de email validă.",
|
||||||
"comment_info": "Un comentariu privat nu este vizibil pentru utilizator, în timp ce un comentariu public este afișat ca un tooltip când se trece peste el într-o privire de ansamblu asupra utilizatorilor",
|
"comment_info": "Un comentariu privat nu este vizibil pentru utilizator, în timp ce un comentariu public este afișat ca un tooltip când se trece peste el într-o privire de ansamblu asupra utilizatorilor",
|
||||||
"custom_params": "Parametri personalizați",
|
"custom_params": "Parametri personalizați",
|
||||||
"custom_params_hint": "Corect: --param=xy, greşit: --param xy",
|
"custom_params_hint": "Corect: --param=xy, greşit: --param xy",
|
||||||
|
@ -133,6 +135,8 @@
|
||||||
"api_allow_from": "Permite accesul API de la aceste adrese IP/CIDR",
|
"api_allow_from": "Permite accesul API de la aceste adrese IP/CIDR",
|
||||||
"api_info": "API-ul este în dezvoltare. Documentația se găsește la adresa: <a href=\"/api\">/api</a>",
|
"api_info": "API-ul este în dezvoltare. Documentația se găsește la adresa: <a href=\"/api\">/api</a>",
|
||||||
"api_key": "Cheie API",
|
"api_key": "Cheie API",
|
||||||
|
"api_read_only": "Acces doar pentru citire",
|
||||||
|
"api_read_write": "Acces pentru citire și scriere",
|
||||||
"api_skip_ip_check": "Săriți verificarea IP pentru API",
|
"api_skip_ip_check": "Săriți verificarea IP pentru API",
|
||||||
"app_links": "Linkuri aplicație",
|
"app_links": "Linkuri aplicație",
|
||||||
"app_name": "Nume aplicație",
|
"app_name": "Nume aplicație",
|
||||||
|
@ -220,6 +224,8 @@
|
||||||
"no_active_bans": "Nu există interdicții active",
|
"no_active_bans": "Nu există interdicții active",
|
||||||
"no_new_rows": "Nu există alte rânduri disponibile",
|
"no_new_rows": "Nu există alte rânduri disponibile",
|
||||||
"no_record": "Nici o înregistrare",
|
"no_record": "Nici o înregistrare",
|
||||||
|
"oauth2_apps": "Aplicații OAuth2",
|
||||||
|
"oauth2_add_client": "Adaugă client OAuth2",
|
||||||
"oauth2_client_id": "ID client",
|
"oauth2_client_id": "ID client",
|
||||||
"oauth2_client_secret": "Secret client",
|
"oauth2_client_secret": "Secret client",
|
||||||
"oauth2_info": "Implementarea OAuth2 suportă tipul de acces \"Cod de autorizare\" și emite jetoane de actualizare.<br>\r\nServerul emite automat noi jetoane de actualizare, după ce a fost folosit un jeton de actualizare.<br><br>\r\n→ Scopul implicit este <i>profil</i>. Doar utilizatorii căsuței poștale pot fi autentificați cu OAuth2. Dacă parametrul scop este omis, acesta revine la <i>profil</i>.<br>\r\n→ Parametrul <i>stare</i> trebuie să fie trimis de client ca parte a cererii de autorizare.<br><br>\r\nCăile pentru cereri către API-ul OAuth2:<br>\r\n<ul>\r\n <li>Calea de autorizare: <code>/oauth/autorize</code ></li>\r\n <li>Calea jetonului: <code>/oauth/token</code></li>\r\n <li>Calea pentru resursă: <code>/oauth/profile</code></li>\r\n</ul>\r\nRegenerarea secretului clientului nu va expira codurile de autorizare existente, dar vor eșua să-și reînnoiască jetonul.<br><br>\r\nRevocarea jetoanelor clientului va cauza încheierea imediată a tuturor sesiunilor active. Toți clienții trebuie să se autentifice din nou.",
|
"oauth2_info": "Implementarea OAuth2 suportă tipul de acces \"Cod de autorizare\" și emite jetoane de actualizare.<br>\r\nServerul emite automat noi jetoane de actualizare, după ce a fost folosit un jeton de actualizare.<br><br>\r\n→ Scopul implicit este <i>profil</i>. Doar utilizatorii căsuței poștale pot fi autentificați cu OAuth2. Dacă parametrul scop este omis, acesta revine la <i>profil</i>.<br>\r\n→ Parametrul <i>stare</i> trebuie să fie trimis de client ca parte a cererii de autorizare.<br><br>\r\nCăile pentru cereri către API-ul OAuth2:<br>\r\n<ul>\r\n <li>Calea de autorizare: <code>/oauth/autorize</code ></li>\r\n <li>Calea jetonului: <code>/oauth/token</code></li>\r\n <li>Calea pentru resursă: <code>/oauth/profile</code></li>\r\n</ul>\r\nRegenerarea secretului clientului nu va expira codurile de autorizare existente, dar vor eșua să-și reînnoiască jetonul.<br><br>\r\nRevocarea jetoanelor clientului va cauza încheierea imediată a tuturor sesiunilor active. Toți clienții trebuie să se autentifice din nou.",
|
||||||
|
@ -448,7 +454,7 @@
|
||||||
"tls_policy_map_parameter_invalid": "Parametrul politicii este invalid",
|
"tls_policy_map_parameter_invalid": "Parametrul politicii este invalid",
|
||||||
"totp_verification_failed": "Verificarea TOTP a eșuat",
|
"totp_verification_failed": "Verificarea TOTP a eșuat",
|
||||||
"transport_dest_exists": "Destinația transportului \"%s\" există",
|
"transport_dest_exists": "Destinația transportului \"%s\" există",
|
||||||
"u2f_verification_failed": "Verificarea U2F a eșuat: %s",
|
"webauthn_verification_failed": "Verificarea WebAuthn a eșuat: %s",
|
||||||
"fido2_verification_failed": "Verificarea FIDO2 a eșuat: %s",
|
"fido2_verification_failed": "Verificarea FIDO2 a eșuat: %s",
|
||||||
"unknown": "A apărut o eroare necunoscută",
|
"unknown": "A apărut o eroare necunoscută",
|
||||||
"unknown_tfa_method": "Metodă TFA necunoscută",
|
"unknown_tfa_method": "Metodă TFA necunoscută",
|
||||||
|
@ -497,6 +503,7 @@
|
||||||
"optional": "Această înregistrare este opțională."
|
"optional": "Această înregistrare este opțională."
|
||||||
},
|
},
|
||||||
"edit": {
|
"edit": {
|
||||||
|
"acl": "ACL (Permisiune)",
|
||||||
"active": "Activ",
|
"active": "Activ",
|
||||||
"admin": "Editează administrator",
|
"admin": "Editează administrator",
|
||||||
"advanced_settings": "Setări avansate",
|
"advanced_settings": "Setări avansate",
|
||||||
|
@ -506,9 +513,10 @@
|
||||||
"allowed_protocols": "Protocoale permise",
|
"allowed_protocols": "Protocoale permise",
|
||||||
"app_name": "Nume aplicație",
|
"app_name": "Nume aplicație",
|
||||||
"app_passwd": "Parolă apicație",
|
"app_passwd": "Parolă apicație",
|
||||||
|
"app_passwd_protocols": "Protocoale permise pentru parola aplicației",
|
||||||
"automap": "Încearcă maparea automată a folderelor (\"Obiecte trimise\", \"Trimise\" => \"Trimise\" etc.)",
|
"automap": "Încearcă maparea automată a folderelor (\"Obiecte trimise\", \"Trimise\" => \"Trimise\" etc.)",
|
||||||
"backup_mx_options": "Opțiuni backup MX",
|
"backup_mx_options": "Opțiuni backup MX",
|
||||||
"bcc_dest_format": "Destinația BCC trebuie să fie o singură adresă de email validă.",
|
"bcc_dest_format": "Destinația BCC trebuie să fie o singură adresă de email validă.<br>Dacă trebuie să trimiteți o copie la mai multe adrese, creați un alias și utilizați-l aici.",
|
||||||
"client_id": "ID Client",
|
"client_id": "ID Client",
|
||||||
"client_secret": "Secret client",
|
"client_secret": "Secret client",
|
||||||
"comment_info": "Un comentariu privat nu este vizibil pentru utilizator, în timp ce un comentariu public este afișat ca un tooltip când se trece peste el într-o privire de ansamblu asupra utilizatorilor",
|
"comment_info": "Un comentariu privat nu este vizibil pentru utilizator, în timp ce un comentariu public este afișat ca un tooltip când se trece peste el într-o privire de ansamblu asupra utilizatorilor",
|
||||||
|
@ -550,12 +558,14 @@
|
||||||
"mbox_rl_info": "Această limitare de rată se aplică pe numele de conectare SASL, se potrivește cu orice adresă „de la” folosită de către utilizatorul autentificat. O limitare a ratei căsuței poștale înlocuiește limitarea ratei la nivel de domeniu.",
|
"mbox_rl_info": "Această limitare de rată se aplică pe numele de conectare SASL, se potrivește cu orice adresă „de la” folosită de către utilizatorul autentificat. O limitare a ratei căsuței poștale înlocuiește limitarea ratei la nivel de domeniu.",
|
||||||
"mins_interval": "Interval (min)",
|
"mins_interval": "Interval (min)",
|
||||||
"multiple_bookings": "Rezervări multiple",
|
"multiple_bookings": "Rezervări multiple",
|
||||||
|
"none_inherit": "Nici una / Moștenește",
|
||||||
"nexthop": "Următorul hop",
|
"nexthop": "Următorul hop",
|
||||||
"password": "Parolă",
|
"password": "Parolă",
|
||||||
"password_repeat": "Confirmă parolă (repetă)",
|
"password_repeat": "Confirmă parolă (repetă)",
|
||||||
"previous": "Pagina precedentă",
|
"previous": "Pagina precedentă",
|
||||||
"private_comment": "Comentariu privat",
|
"private_comment": "Comentariu privat",
|
||||||
"public_comment": "Comentariu public",
|
"public_comment": "Comentariu public",
|
||||||
|
"pushover": "Pushover",
|
||||||
"pushover_evaluate_x_prio": "Escalează e-mailurile cu prioritate înaltă [<code>X-Priority: 1</code>]",
|
"pushover_evaluate_x_prio": "Escalează e-mailurile cu prioritate înaltă [<code>X-Priority: 1</code>]",
|
||||||
"pushover_info": "Setările de notificare push se vor aplica tuturor e-mailurilor curate (non-spam) livrate la <b>% s </b> inclusiv aliasuri (partajate, care nu sunt partajate, etichetate).",
|
"pushover_info": "Setările de notificare push se vor aplica tuturor e-mailurilor curate (non-spam) livrate la <b>% s </b> inclusiv aliasuri (partajate, care nu sunt partajate, etichetate).",
|
||||||
"pushover_only_x_prio": "Luați în considerare doar e-mailurile cu prioritate înaltă [<code>X-Priority: 1</code>]",
|
"pushover_only_x_prio": "Luați în considerare doar e-mailurile cu prioritate înaltă [<code>X-Priority: 1</code>]",
|
||||||
|
@ -586,6 +596,8 @@
|
||||||
"sieve_desc": "Descriere scurtă",
|
"sieve_desc": "Descriere scurtă",
|
||||||
"sieve_type": "Tip filtru",
|
"sieve_type": "Tip filtru",
|
||||||
"skipcrossduplicates": "Sari peste mesajele duplicate din toate folderele (primul venit, primul servit)",
|
"skipcrossduplicates": "Sari peste mesajele duplicate din toate folderele (primul venit, primul servit)",
|
||||||
|
"sogo_access": "Acordați acces direct de conectare la SOGo",
|
||||||
|
"sogo_access_info": "Conectarea unică din interfața de administrare a e-mailului continuă să funcționeze. Această setare nu afectează accesul la toate celelalte servicii și nici nu șterge sau modifică profilul SOGo existent al unui utilizator.",
|
||||||
"sogo_visible": "Aliasul este vizibil în SOGo",
|
"sogo_visible": "Aliasul este vizibil în SOGo",
|
||||||
"sogo_visible_info": "Această opțiune afectează doar obiecte, care pot fi afișate în SOGo (adrese alias partajate sau ne-partajate cu cel puțin o căsuță poștală locală). Dacă este ascuns, un alias nu va apărea ca expeditor selectabil în SOGo.",
|
"sogo_visible_info": "Această opțiune afectează doar obiecte, care pot fi afișate în SOGo (adrese alias partajate sau ne-partajate cu cel puțin o căsuță poștală locală). Dacă este ascuns, un alias nu va apărea ca expeditor selectabil în SOGo.",
|
||||||
"spam_alias": "Crează sau modifică adrese alias limitate în funcție de timp",
|
"spam_alias": "Crează sau modifică adrese alias limitate în funcție de timp",
|
||||||
|
@ -603,6 +615,21 @@
|
||||||
"username": "Nume de utilizator",
|
"username": "Nume de utilizator",
|
||||||
"validate_save": "Validează și salvează"
|
"validate_save": "Validează și salvează"
|
||||||
},
|
},
|
||||||
|
"fido2": {
|
||||||
|
"set_fn": "Setați un nume prietenos",
|
||||||
|
"fn": "Nume prietenos",
|
||||||
|
"rename": "redenumiți",
|
||||||
|
"confirm": "Confirmați",
|
||||||
|
"register_status": "Starea înregistrării",
|
||||||
|
"known_ids": "ID-uri cunoscute",
|
||||||
|
"none": "Dezactivat",
|
||||||
|
"set_fido2": "Înregistrați dispozitivul FIDO2",
|
||||||
|
"set_fido2_touchid": "Înregistrați Touch ID pe Apple M1",
|
||||||
|
"start_fido2_validation": "Începeți validarea FIDO2",
|
||||||
|
"fido2_auth": "Conectați-vă cu FIDO2",
|
||||||
|
"fido2_success": "Dispozitivul a fost înregistrat cu succes",
|
||||||
|
"fido2_validation_failed": "Validarea a eșuat"
|
||||||
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"cancel": "Anulează",
|
"cancel": "Anulează",
|
||||||
"confirm_delete": "Confirmă ștergerea",
|
"confirm_delete": "Confirmă ștergerea",
|
||||||
|
@ -664,6 +691,7 @@
|
||||||
"alias_domain_alias_hint": "Aliasurile <b>nu</b> sunt aplicate automat pe domeniile alias. O adresă alias <code>aliasul-meu@domeniu</code> <b>nu</b> acoperă adresa <code>aliasul-meu@domeniu-alias</code> (unde \"domeniu-alias\" este un domeniu alias imaginar pentru \"domeniu\").<br>Vă rugăm să utilizați un filtru sită pentru a redirecționa poșta către o căsuță poștală externă (consultați zona \"Filtre\" sau utilizați SOGo -> Redirecționare).",
|
"alias_domain_alias_hint": "Aliasurile <b>nu</b> sunt aplicate automat pe domeniile alias. O adresă alias <code>aliasul-meu@domeniu</code> <b>nu</b> acoperă adresa <code>aliasul-meu@domeniu-alias</code> (unde \"domeniu-alias\" este un domeniu alias imaginar pentru \"domeniu\").<br>Vă rugăm să utilizați un filtru sită pentru a redirecționa poșta către o căsuță poștală externă (consultați zona \"Filtre\" sau utilizați SOGo -> Redirecționare).",
|
||||||
"alias_domain_backupmx": "Alias domeniu inactiv pentru domeniu releu",
|
"alias_domain_backupmx": "Alias domeniu inactiv pentru domeniu releu",
|
||||||
"aliases": "Aliasuri",
|
"aliases": "Aliasuri",
|
||||||
|
"all_domains": "Toate Domeniile",
|
||||||
"allow_from_smtp": "Permiteți acestor adrese IP să utilizeze numai <b>SMTP</b>",
|
"allow_from_smtp": "Permiteți acestor adrese IP să utilizeze numai <b>SMTP</b>",
|
||||||
"allow_from_smtp_info": "Lăsați gol pentru a permite tuturor expeditorilor.<br>Adrese și rețele IPv4/IPv6.",
|
"allow_from_smtp_info": "Lăsați gol pentru a permite tuturor expeditorilor.<br>Adrese și rețele IPv4/IPv6.",
|
||||||
"allowed_protocols": "Protocoale permise",
|
"allowed_protocols": "Protocoale permise",
|
||||||
|
@ -687,6 +715,7 @@
|
||||||
"booking_custom_short": "Limită maximă",
|
"booking_custom_short": "Limită maximă",
|
||||||
"booking_ltnull": "Nelimitat, dar arată ca ocupat atunci când este rezervat",
|
"booking_ltnull": "Nelimitat, dar arată ca ocupat atunci când este rezervat",
|
||||||
"booking_lt0_short": "Limită redusă",
|
"booking_lt0_short": "Limită redusă",
|
||||||
|
"catch_all": "Prinde-Tot",
|
||||||
"daily": "Zilnic",
|
"daily": "Zilnic",
|
||||||
"deactivate": "Deactivează",
|
"deactivate": "Deactivează",
|
||||||
"description": "Descriere",
|
"description": "Descriere",
|
||||||
|
@ -704,6 +733,8 @@
|
||||||
"filter_table": "Tabel filtre",
|
"filter_table": "Tabel filtre",
|
||||||
"filters": "Filtre",
|
"filters": "Filtre",
|
||||||
"fname": "Nume complet",
|
"fname": "Nume complet",
|
||||||
|
"goto_ham": "Învață ca <b>ham</b>",
|
||||||
|
"goto_spam": "Învață ca <b>spam</b>",
|
||||||
"hourly": "Din oră în oră",
|
"hourly": "Din oră în oră",
|
||||||
"in_use": "În uz (%)",
|
"in_use": "În uz (%)",
|
||||||
"inactive": "Inactiv",
|
"inactive": "Inactiv",
|
||||||
|
@ -736,6 +767,7 @@
|
||||||
"quarantine_notification": "Notificări de carantină",
|
"quarantine_notification": "Notificări de carantină",
|
||||||
"quarantine_category": "Categoria notificărilor despre carantină",
|
"quarantine_category": "Categoria notificărilor despre carantină",
|
||||||
"quick_actions": "Acţiuni",
|
"quick_actions": "Acţiuni",
|
||||||
|
"recipient": "Destinatar",
|
||||||
"recipient_map": "Hartă destinatar",
|
"recipient_map": "Hartă destinatar",
|
||||||
"recipient_map_info": "Hărțile destinatarilor sunt folosite pentru a înlocui adresa de destinație a unui mesaj înainte de a fi livrat.",
|
"recipient_map_info": "Hărțile destinatarilor sunt folosite pentru a înlocui adresa de destinație a unui mesaj înainte de a fi livrat.",
|
||||||
"recipient_map_new": "Destinatar nou",
|
"recipient_map_new": "Destinatar nou",
|
||||||
|
@ -746,6 +778,7 @@
|
||||||
"remove": "Elimină",
|
"remove": "Elimină",
|
||||||
"resources": "Resurse",
|
"resources": "Resurse",
|
||||||
"running": "Rulare",
|
"running": "Rulare",
|
||||||
|
"sender": "Expeditor",
|
||||||
"set_postfilter": "Marchează ca postfiltru",
|
"set_postfilter": "Marchează ca postfiltru",
|
||||||
"set_prefilter": "Marchează ca prefiltru",
|
"set_prefilter": "Marchează ca prefiltru",
|
||||||
"sieve_info": "Poți stoca mai multe filtre pentru fiecare utilizator, dar numai un singur prefiltru și un singur postfiltru pot fi active în același timp.<br>\nFiecare filtru va fi procesat în ordinea descrisă. Nici un script eșuat, nici un \"keep;\" emis nu va opri procesarea altor scripturi.Modificările aduse scripturilor sită globale vor declanșa o repornire a Dovecot.<br>\nPrefilter → User scripts → Postfilter → <a href=\"https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/sieve_after\" target=\"_blank\">global sieve postfilter</a>",
|
"sieve_info": "Poți stoca mai multe filtre pentru fiecare utilizator, dar numai un singur prefiltru și un singur postfiltru pot fi active în același timp.<br>\nFiecare filtru va fi procesat în ordinea descrisă. Nici un script eșuat, nici un \"keep;\" emis nu va opri procesarea altor scripturi.Modificările aduse scripturilor sită globale vor declanșa o repornire a Dovecot.<br>\nPrefilter → User scripts → Postfilter → <a href=\"https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/dovecot/sieve_after\" target=\"_blank\">global sieve postfilter</a>",
|
||||||
|
@ -765,6 +798,15 @@
|
||||||
"stats": "Statistici",
|
"stats": "Statistici",
|
||||||
"status": "Stare",
|
"status": "Stare",
|
||||||
"sync_jobs": "Lucrări de sincronizare",
|
"sync_jobs": "Lucrări de sincronizare",
|
||||||
|
"syncjob_check_log": "Verificați jurnalul",
|
||||||
|
"syncjob_last_run_result": "Rezultatul ultimei rulări",
|
||||||
|
"syncjob_EX_OK": "Succes",
|
||||||
|
"syncjob_EXIT_CONNECTION_FAILURE": "Problemă de conectare",
|
||||||
|
"syncjob_EXIT_TLS_FAILURE": "Problemă cu conexiunea criptată",
|
||||||
|
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problemă de autentificare",
|
||||||
|
"syncjob_EXIT_OVERQUOTA": "Cutia poștală țintă depășește cota maximă",
|
||||||
|
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Nu se poate conecta la server",
|
||||||
|
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nume de utilizator sau parolă greșite",
|
||||||
"table_size": "Mărime tabel",
|
"table_size": "Mărime tabel",
|
||||||
"table_size_show_n": "Arată %s articole",
|
"table_size_show_n": "Arată %s articole",
|
||||||
"target_address": "Adresă goto",
|
"target_address": "Adresă goto",
|
||||||
|
@ -848,6 +890,13 @@
|
||||||
"text_plain_content": "Conținut (text/simplu)",
|
"text_plain_content": "Conținut (text/simplu)",
|
||||||
"toggle_all": "Comută toate"
|
"toggle_all": "Comută toate"
|
||||||
},
|
},
|
||||||
|
"ratelimit": {
|
||||||
|
"disabled": "Dezactivat",
|
||||||
|
"second": "mesaje / sec",
|
||||||
|
"minute": "mesaje / min",
|
||||||
|
"hour": "mesaje / ora",
|
||||||
|
"day": "mesaje / zi"
|
||||||
|
},
|
||||||
"start": {
|
"start": {
|
||||||
"help": "Afișează/Ascunde panoul de ajutor",
|
"help": "Afișează/Ascunde panoul de ajutor",
|
||||||
"imap_smtp_server_auth_info": "Utilizează adresa completă de email și mecanismul de autentificare SIMPLU.<br>\nDatele tale de conectare vor fi criptate prin criptarea obligatorie de la server.",
|
"imap_smtp_server_auth_info": "Utilizează adresa completă de email și mecanismul de autentificare SIMPLU.<br>\nDatele tale de conectare vor fi criptate prin criptarea obligatorie de la server.",
|
||||||
|
@ -928,7 +977,7 @@
|
||||||
"ui_texts": "Modificări salvate în textele UI",
|
"ui_texts": "Modificări salvate în textele UI",
|
||||||
"upload_success": "Fișier încărcat cu succes",
|
"upload_success": "Fișier încărcat cu succes",
|
||||||
"verified_totp_login": "Autentificarea TOTP verificată",
|
"verified_totp_login": "Autentificarea TOTP verificată",
|
||||||
"verified_u2f_login": "Autentificarea U2F verificată",
|
"verified_webauthn_login": "Autentificarea WebAuthn verificată",
|
||||||
"verified_fido2_login": "Conectare FIDO2 verificată",
|
"verified_fido2_login": "Conectare FIDO2 verificată",
|
||||||
"verified_yotp_login": "Autentificarea Yubico OTP verificată"
|
"verified_yotp_login": "Autentificarea Yubico OTP verificată"
|
||||||
},
|
},
|
||||||
|
@ -940,7 +989,7 @@
|
||||||
"disable_tfa": "Dezactivează TFA până la următoarea conectare reușită",
|
"disable_tfa": "Dezactivează TFA până la următoarea conectare reușită",
|
||||||
"enter_qr_code": "Codul tău TOTP dacă dispozitivul tău nu poate scana codurile QR",
|
"enter_qr_code": "Codul tău TOTP dacă dispozitivul tău nu poate scana codurile QR",
|
||||||
"error_code": "Cod de eroare",
|
"error_code": "Cod de eroare",
|
||||||
"init_u2f": "Inițializare, vă rugăm așteptați...",
|
"init_webauthn": "Inițializare, vă rugăm așteptați...",
|
||||||
"key_id": "Un identificator pentru YubiKey",
|
"key_id": "Un identificator pentru YubiKey",
|
||||||
"key_id_totp": "Un identificator pentru cheia ta",
|
"key_id_totp": "Un identificator pentru cheia ta",
|
||||||
"none": "Dezactivează",
|
"none": "Dezactivează",
|
||||||
|
@ -948,29 +997,15 @@
|
||||||
"scan_qr_code": "Scanează codul următor cu aplicația ta de autentificare sau introdu manual codul.",
|
"scan_qr_code": "Scanează codul următor cu aplicația ta de autentificare sau introdu manual codul.",
|
||||||
"select": "Te rog selectează",
|
"select": "Te rog selectează",
|
||||||
"set_tfa": "Setează metoda de autentificare cu doi factori",
|
"set_tfa": "Setează metoda de autentificare cu doi factori",
|
||||||
"start_u2f_validation": "Începi validarea",
|
"start_webauthn_validation": "Începi validarea",
|
||||||
"tfa": "Autentificare cu doi factori",
|
"tfa": "Autentificare cu doi factori",
|
||||||
"tfa_token_invalid": "Jeton TFA invalid",
|
"tfa_token_invalid": "Jeton TFA invalid",
|
||||||
"totp": "OTP pe bază de timp (Google Authenticator etc.)",
|
"totp": "OTP pe bază de timp (Google Authenticator etc.)",
|
||||||
"u2f": "Autentificare U2F",
|
"webauthn": "Autentificare WebAuthn",
|
||||||
"waiting_usb_auth": "<i>În așteptarea dispozitivului USB...</i><br><br>Apasă acum butonul de pe dispozitivul tău USB U2F.",
|
"waiting_usb_auth": "<i>În așteptarea dispozitivului USB...</i><br><br>Apasă acum butonul de pe dispozitivul tău USB WebAuthn.",
|
||||||
"waiting_usb_register": "<i>În așteptarea dispozitivului USB...</i><br><br>Introdu parola ta mai sus și confirmă înregistrarea ta U2F atingând butonul de pe dispozitivul tău USB U2F.",
|
"waiting_usb_register": "<i>În așteptarea dispozitivului USB...</i><br><br>Introdu parola ta mai sus și confirmă înregistrarea ta WebAuthn atingând butonul de pe dispozitivul tău USB WebAuthn.",
|
||||||
"yubi_otp": "Autentificare Yubico OTP"
|
"yubi_otp": "Autentificare Yubico OTP"
|
||||||
},
|
},
|
||||||
"fido2": {
|
|
||||||
"set_fn": "Setați un nume prietenos",
|
|
||||||
"fn": "Nume prietenos",
|
|
||||||
"rename": "redenumiți",
|
|
||||||
"confirm": "Confirmați",
|
|
||||||
"register_status": "Starea înregistrării",
|
|
||||||
"known_ids": "ID-uri cunoscute",
|
|
||||||
"none": "Dezactivat",
|
|
||||||
"set_fido2": "Înregistrați dispozitivul FIDO2",
|
|
||||||
"start_fido2_validation": "Începeți validarea FIDO2",
|
|
||||||
"fido2_auth": "Conectați-vă cu FIDO2",
|
|
||||||
"fido2_success": "Dispozitivul a fost înregistrat cu succes",
|
|
||||||
"fido2_validation_failed": "Validarea a esuat"
|
|
||||||
},
|
|
||||||
"user": {
|
"user": {
|
||||||
"action": "Acțiune",
|
"action": "Acțiune",
|
||||||
"active": "Activ",
|
"active": "Activ",
|
||||||
|
@ -987,12 +1022,15 @@
|
||||||
"aliases_also_send_as": "De asemenea, este permis să trimită ca utilizator",
|
"aliases_also_send_as": "De asemenea, este permis să trimită ca utilizator",
|
||||||
"aliases_send_as_all": "Nu se verifică accesul expeditorului pentru următorul(arele) domeniu(i) și domeniile sale alias",
|
"aliases_send_as_all": "Nu se verifică accesul expeditorului pentru următorul(arele) domeniu(i) și domeniile sale alias",
|
||||||
"app_hint": "Parolele aplicației sunt parole alternative pentru autentificarea IMAP, SMTP, CalDAV, CardDAV și EAS. Numele de utilizator rămâne neschimbat.<br>SOGo nu este disponibil prin parolele aplicației.",
|
"app_hint": "Parolele aplicației sunt parole alternative pentru autentificarea IMAP, SMTP, CalDAV, CardDAV și EAS. Numele de utilizator rămâne neschimbat.<br>SOGo nu este disponibil prin parolele aplicației.",
|
||||||
|
"allowed_protocols": "Protocoale permise",
|
||||||
"app_name": "Nume aplicație",
|
"app_name": "Nume aplicație",
|
||||||
"app_passwds": "Parole aplicație",
|
"app_passwds": "Parole aplicație",
|
||||||
"apple_connection_profile": "Profil de conexiune Apple",
|
"apple_connection_profile": "Profil de conexiune Apple",
|
||||||
"apple_connection_profile_complete": "Acest profil de conexiune include parametrii IMAP și SMTP, precum și calDAV (calendar) și carduri CardDAV (contacte) pentru dispozitivele Apple.",
|
"apple_connection_profile_complete": "Acest profil de conexiune include parametrii IMAP și SMTP, precum și calDAV (calendar) și carduri CardDAV (contacte) pentru dispozitivele Apple.",
|
||||||
"apple_connection_profile_mailonly": "Acest profil de conexiune include parametrii de configurare IMAP și SMTP pentru dispozitivele Apple.",
|
"apple_connection_profile_mailonly": "Acest profil de conexiune include parametrii de configurare IMAP și SMTP pentru dispozitivele Apple.",
|
||||||
|
"apple_connection_profile_with_app_password": "O nouă parolă pentru aplicație este generată și adăugată la profil, astfel încât să nu fie necesară introducerea unei parole la configurarea dispozitivului. Vă rugăm să nu partajați fișierul, deoarece oferă acces deplin la căsuța dvs. poștală.",
|
||||||
"change_password": "Schimbă parola",
|
"change_password": "Schimbă parola",
|
||||||
|
"change_password_hint_app_passwords": "Contul dvs. are {{number_of_app_passwords}} parole de aplicație care nu vor fi modificate. Pentru a le gestiona, accesați secțiunea Parole aplicație.",
|
||||||
"clear_recent_successful_connections": "Ștergeți conexiunile reușite văzute",
|
"clear_recent_successful_connections": "Ștergeți conexiunile reușite văzute",
|
||||||
"client_configuration": "Afișează ghidurile de configurare pentru clienții de email și smartphone-uri",
|
"client_configuration": "Afișează ghidurile de configurare pentru clienții de email și smartphone-uri",
|
||||||
"create_app_passwd": "Crează parola aplicației",
|
"create_app_passwd": "Crează parola aplicației",
|
||||||
|
@ -1003,6 +1041,7 @@
|
||||||
"delete_ays": "Vă rugăm să confirmați stergerea.",
|
"delete_ays": "Vă rugăm să confirmați stergerea.",
|
||||||
"direct_aliases": "Adrese alias directe",
|
"direct_aliases": "Adrese alias directe",
|
||||||
"direct_aliases_desc": "Adresele alias directe sunt afectate de setările filtrului de spam și ale politicii TLS.",
|
"direct_aliases_desc": "Adresele alias directe sunt afectate de setările filtrului de spam și ale politicii TLS.",
|
||||||
|
"direct_protocol_access": "Acest utilizator are <b>acces direct, extern</b> la următoarele protocoale și aplicații. Această setare este controlată de administratorul dvs. Parolele pentru aplicații pot fi create pentru a acorda acces la protocoale și aplicații individuale.<br>Butonul \"Conectați-vă la webmail\" oferă conectare unică la SOGo și este întotdeauna disponibil.",
|
||||||
"eas_reset": "Resetează memoria cache a dispozitivului ActiveSync",
|
"eas_reset": "Resetează memoria cache a dispozitivului ActiveSync",
|
||||||
"eas_reset_help": "În multe cazuri, resetarea cache-ului dispozitivului va ajuta la recuperarea unui profil ActiveSync defect.<br><b>Atenţie:</b> Toate elementele vor fi descărcate din nou!",
|
"eas_reset_help": "În multe cazuri, resetarea cache-ului dispozitivului va ajuta la recuperarea unui profil ActiveSync defect.<br><b>Atenţie:</b> Toate elementele vor fi descărcate din nou!",
|
||||||
"eas_reset_now": "Resetează acum",
|
"eas_reset_now": "Resetează acum",
|
||||||
|
@ -1096,6 +1135,15 @@
|
||||||
"spamfilter_yellow": "Galben: acest mesaj poate fi spam, va fi etichetat ca spam și va fi mutat în dosarul de junk",
|
"spamfilter_yellow": "Galben: acest mesaj poate fi spam, va fi etichetat ca spam și va fi mutat în dosarul de junk",
|
||||||
"status": "Stare",
|
"status": "Stare",
|
||||||
"sync_jobs": "Lucrări de sincronizare",
|
"sync_jobs": "Lucrări de sincronizare",
|
||||||
|
"syncjob_check_log": "Verificați jurnalul",
|
||||||
|
"syncjob_last_run_result": "Rezultatul ultimei rulări",
|
||||||
|
"syncjob_EX_OK": "Succes",
|
||||||
|
"syncjob_EXIT_CONNECTION_FAILURE": "Problemă de conectare",
|
||||||
|
"syncjob_EXIT_TLS_FAILURE": "Problemă cu conexiunea criptată",
|
||||||
|
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problemă de autentificare",
|
||||||
|
"syncjob_EXIT_OVERQUOTA": "Cutia poștală țintă depășește cota maximă",
|
||||||
|
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Nu se poate conecta la server",
|
||||||
|
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nume de utilizator sau parolă greșite",
|
||||||
"tag_handling": "Setează manevrarea pentru mailurile etichetate",
|
"tag_handling": "Setează manevrarea pentru mailurile etichetate",
|
||||||
"tag_help_example": "Exemplu pentru o adresă de email etichetată: me<b>+Facebook</b>@example.org",
|
"tag_help_example": "Exemplu pentru o adresă de email etichetată: me<b>+Facebook</b>@example.org",
|
||||||
"tag_help_explain": "În subfolder: un nou subfolder numit după etichetă va fi creat sub INBOX (\"INBOX/Facebook\").<br>\nÎn subiect: numele etichetelor va fi prefixat la subiectul mailurilor, de exemplu: \"[Facebook] My News\".",
|
"tag_help_explain": "În subfolder: un nou subfolder numit după etichetă va fi creat sub INBOX (\"INBOX/Facebook\").<br>\nÎn subiect: numele etichetelor va fi prefixat la subiectul mailurilor, de exemplu: \"[Facebook] My News\".",
|
||||||
|
@ -1115,6 +1163,7 @@
|
||||||
"week": "săptămână",
|
"week": "săptămână",
|
||||||
"weekly": "Săptămânal",
|
"weekly": "Săptămânal",
|
||||||
"weeks": "săptămâni",
|
"weeks": "săptămâni",
|
||||||
|
"with_app_password": "cu parola aplicație",
|
||||||
"year": "an",
|
"year": "an",
|
||||||
"years": "ani"
|
"years": "ani"
|
||||||
},
|
},
|
||||||
|
|
|
@ -454,7 +454,7 @@
|
||||||
"tls_policy_map_parameter_invalid": "Недопустимое значение параметра политики",
|
"tls_policy_map_parameter_invalid": "Недопустимое значение параметра политики",
|
||||||
"totp_verification_failed": "Ошибка валидации TOTP",
|
"totp_verification_failed": "Ошибка валидации TOTP",
|
||||||
"transport_dest_exists": "Назначение для отправки \"%s\" уже существует",
|
"transport_dest_exists": "Назначение для отправки \"%s\" уже существует",
|
||||||
"u2f_verification_failed": "Ошибка валидации U2F: %s",
|
"webauthn_verification_failed": "Ошибка валидации WebAuthn: %s",
|
||||||
"unknown": "Произошла неизвестная ошибка",
|
"unknown": "Произошла неизвестная ошибка",
|
||||||
"unknown_tfa_method": "Неизвестный метод TFA",
|
"unknown_tfa_method": "Неизвестный метод TFA",
|
||||||
"unlimited_quota_acl": "Неограниченная квота запрещена политикой доступа",
|
"unlimited_quota_acl": "Неограниченная квота запрещена политикой доступа",
|
||||||
|
@ -973,7 +973,7 @@
|
||||||
"upload_success": "Файл загружен успешно",
|
"upload_success": "Файл загружен успешно",
|
||||||
"verified_fido2_login": "Авторизация FIDO2 пройдена",
|
"verified_fido2_login": "Авторизация FIDO2 пройдена",
|
||||||
"verified_totp_login": "Авторизация TOTP пройдена",
|
"verified_totp_login": "Авторизация TOTP пройдена",
|
||||||
"verified_u2f_login": "Авторизация U2F пройдена",
|
"verified_webauthn_login": "Авторизация WebAuthn пройдена",
|
||||||
"verified_yotp_login": "Авторизация Yubico OTP пройдена"
|
"verified_yotp_login": "Авторизация Yubico OTP пройдена"
|
||||||
},
|
},
|
||||||
"tfa": {
|
"tfa": {
|
||||||
|
@ -984,7 +984,7 @@
|
||||||
"disable_tfa": "Отключить TFA до следующего успешного входа",
|
"disable_tfa": "Отключить TFA до следующего успешного входа",
|
||||||
"enter_qr_code": "Ваш код TOTP, если устройство не может отсканировать QR-код",
|
"enter_qr_code": "Ваш код TOTP, если устройство не может отсканировать QR-код",
|
||||||
"error_code": "Код ошибки",
|
"error_code": "Код ошибки",
|
||||||
"init_u2f": "Инициализация, пожалуйста, подождите...",
|
"init_webauthn": "Инициализация, пожалуйста, подождите...",
|
||||||
"key_id": "Идентификатор YubiKey ключа",
|
"key_id": "Идентификатор YubiKey ключа",
|
||||||
"key_id_totp": "Идентификатор TOTP ключа",
|
"key_id_totp": "Идентификатор TOTP ключа",
|
||||||
"none": "Отключить",
|
"none": "Отключить",
|
||||||
|
@ -992,11 +992,11 @@
|
||||||
"scan_qr_code": "Пожалуйста, отсканируйте QR-код с помощью приложения или введите его вручную.",
|
"scan_qr_code": "Пожалуйста, отсканируйте QR-код с помощью приложения или введите его вручную.",
|
||||||
"select": "Пожалуйста, выберите",
|
"select": "Пожалуйста, выберите",
|
||||||
"set_tfa": "Задать метод двухфакторной проверки",
|
"set_tfa": "Задать метод двухфакторной проверки",
|
||||||
"start_u2f_validation": "Начать проверку",
|
"start_webauthn_validation": "Начать проверку",
|
||||||
"tfa": "Двухфакторная проверка подлинности",
|
"tfa": "Двухфакторная проверка подлинности",
|
||||||
"tfa_token_invalid": "Неправильный TFA токен",
|
"tfa_token_invalid": "Неправильный TFA токен",
|
||||||
"totp": "OTP (Authy, Google Authenticator и др.)",
|
"totp": "OTP (Authy, Google Authenticator и др.)",
|
||||||
"u2f": "U2F аутентификация",
|
"webauthn": "WebAuthn аутентификация",
|
||||||
"waiting_usb_auth": "<i>Ожидание устройства USB...</i><br><br>Пожалуйста, нажмите кнопку на USB устройстве сейчас.",
|
"waiting_usb_auth": "<i>Ожидание устройства USB...</i><br><br>Пожалуйста, нажмите кнопку на USB устройстве сейчас.",
|
||||||
"waiting_usb_register": "<i>Ожидание устройства USB...</i><br><br>Пожалуйста, введите пароль выше и подтвердите регистрацию, нажав кнопку на USB устройстве.",
|
"waiting_usb_register": "<i>Ожидание устройства USB...</i><br><br>Пожалуйста, введите пароль выше и подтвердите регистрацию, нажав кнопку на USB устройстве.",
|
||||||
"yubi_otp": "Yubico OTP аутентификация"
|
"yubi_otp": "Yubico OTP аутентификация"
|
||||||
|
|
|
@ -80,7 +80,7 @@
|
||||||
"password": "Heslo",
|
"password": "Heslo",
|
||||||
"password_repeat": "Potvrdzovacie heslo (zopakovať)",
|
"password_repeat": "Potvrdzovacie heslo (zopakovať)",
|
||||||
"port": "Port",
|
"port": "Port",
|
||||||
"post_domain_add": "SOGo kontajner, \"sogo-mailcow\", musí byť reštartovaný po pridaní novej domény!<br>",
|
"post_domain_add": "SOGo kontajner, \"sogo-mailcow\", musí byť reštartovaný po pridaní novej domény!<br><br>Okrem toho by sa mala skontrolovať konfigurácia DNS domény. Ak je konfigurácia v poriadku, reštartujte \"acme-mailcow\", aby sa automaticky vygenerovali certifikáty pre vašu novú doménu (autoconfig.<domain>, autodiscover.<domain>).<br>Tento krok je voliteľný a bude sa opakovať každých 24 hodín.",
|
||||||
"private_comment": "Súkromný komentár",
|
"private_comment": "Súkromný komentár",
|
||||||
"public_comment": "Verejný komentár",
|
"public_comment": "Verejný komentár",
|
||||||
"quota_mb": "Kvóta (MiB)",
|
"quota_mb": "Kvóta (MiB)",
|
||||||
|
@ -323,7 +323,7 @@
|
||||||
"to_top": "Naspať navrch",
|
"to_top": "Naspať navrch",
|
||||||
"transport_dest_format": "Regulárny výraz alebo syntax: example.org, .example.org, *, box@example.org (viacero hodnôt môžu byť oddelené čiarkou)",
|
"transport_dest_format": "Regulárny výraz alebo syntax: example.org, .example.org, *, box@example.org (viacero hodnôt môžu byť oddelené čiarkou)",
|
||||||
"transport_maps": "Transportné Mapy",
|
"transport_maps": "Transportné Mapy",
|
||||||
"transport_test_rcpt_info": "• Na otestovanie odchádzajúcej pošty je možné použiť null@hosted.mailcow.de ako adresáta",
|
"transport_test_rcpt_info": "• Na otestovanie odchádzajúcej pošty je možné použiť null@hosted.mailcow.de ako adresáta.",
|
||||||
"transports_hint": "• Záznam v transportnej mape <b>prevažuje</b> nad transportnou mapou pre odosielanie</b>.<br>\r\n• Prenos na základe MX je preferovaná voľba.<br>\r\n• Nastavenie TLS pre používateľa je ignorované a môže byť vynútené TLS mapovaním.<br>\r\n• Transportná služba je definovaná vždy \"smtp:\" a použije TLS ak to bude umožnené. Wrapped TLS (SMTPS) nie je podporované.<br>\r\n• Adresy ktoré sa rovnajú hodnote \"/localhost$/\" budú vždy transportované cez \"local:\" a nebudú použité pre cieľový záznam \"*\".<br>\r\n• Po zadaní prihlasovacích údajov pre ďalší skok \"[host]:25\", Postfix <b>vždy</b> hľadá \"host\" a následne \"[host]:25\". Táto vlastnosť znemožňuje používať \"host\" a \"[host]:25\" naraz.",
|
"transports_hint": "• Záznam v transportnej mape <b>prevažuje</b> nad transportnou mapou pre odosielanie</b>.<br>\r\n• Prenos na základe MX je preferovaná voľba.<br>\r\n• Nastavenie TLS pre používateľa je ignorované a môže byť vynútené TLS mapovaním.<br>\r\n• Transportná služba je definovaná vždy \"smtp:\" a použije TLS ak to bude umožnené. Wrapped TLS (SMTPS) nie je podporované.<br>\r\n• Adresy ktoré sa rovnajú hodnote \"/localhost$/\" budú vždy transportované cez \"local:\" a nebudú použité pre cieľový záznam \"*\".<br>\r\n• Po zadaní prihlasovacích údajov pre ďalší skok \"[host]:25\", Postfix <b>vždy</b> hľadá \"host\" a následne \"[host]:25\". Táto vlastnosť znemožňuje používať \"host\" a \"[host]:25\" naraz.",
|
||||||
"ui_footer": "Pätička (HTML povolené)",
|
"ui_footer": "Pätička (HTML povolené)",
|
||||||
"ui_header_announcement": "Oznámenie",
|
"ui_header_announcement": "Oznámenie",
|
||||||
|
@ -394,9 +394,9 @@
|
||||||
"invalid_recipient_map_old": "Neplatná pôvodná mapa príjemcu: %s",
|
"invalid_recipient_map_old": "Neplatná pôvodná mapa príjemcu: %s",
|
||||||
"ip_list_empty": "Zoznam povolených IP nemôže byť prázdny",
|
"ip_list_empty": "Zoznam povolených IP nemôže byť prázdny",
|
||||||
"is_alias": "%s je už používané ako alias adresa",
|
"is_alias": "%s je už používané ako alias adresa",
|
||||||
"is_alias_or_mailbox": "%s je už používaná ako adresa aliasu, mailovej schránky alebo aliasu odvodeného z aliasu domény",
|
"is_alias_or_mailbox": "%s je už používaná ako adresa aliasu, mailovej schránky alebo aliasu odvodeného z aliasu domény.",
|
||||||
"is_spam_alias": "%s je už používaná ako adresa dočasného aliasu (spam alias adresa)",
|
"is_spam_alias": "%s je už používaná ako adresa dočasného aliasu (spam alias adresa)",
|
||||||
"last_key": "Posledný kľúč nemôže byť vymazaný, deaktivujte najprv TFA",
|
"last_key": "Posledný kľúč nemôže byť vymazaný, deaktivujte najprv TFA.",
|
||||||
"login_failed": "Prihlásenie zlyhalo",
|
"login_failed": "Prihlásenie zlyhalo",
|
||||||
"mailbox_defquota_exceeds_mailbox_maxquota": "Predvolená kvóta presahuje max. kvótu mailovej schránky",
|
"mailbox_defquota_exceeds_mailbox_maxquota": "Predvolená kvóta presahuje max. kvótu mailovej schránky",
|
||||||
"mailbox_invalid": "Meno mailovej schránky je neplatné",
|
"mailbox_invalid": "Meno mailovej schránky je neplatné",
|
||||||
|
@ -454,7 +454,7 @@
|
||||||
"tls_policy_map_parameter_invalid": "Podmienkový parameter mapy TLS pravidiel je neplatný",
|
"tls_policy_map_parameter_invalid": "Podmienkový parameter mapy TLS pravidiel je neplatný",
|
||||||
"totp_verification_failed": "TOTP overenie zlyhalo",
|
"totp_verification_failed": "TOTP overenie zlyhalo",
|
||||||
"transport_dest_exists": "Transportný cieľ \"%s\" už existuje",
|
"transport_dest_exists": "Transportný cieľ \"%s\" už existuje",
|
||||||
"u2f_verification_failed": "U2F overenie zlyhalo: %s",
|
"webauthn_verification_failed": "WebAuthn overenie zlyhalo: %s",
|
||||||
"unknown": "Nastala neznáma chyba",
|
"unknown": "Nastala neznáma chyba",
|
||||||
"unknown_tfa_method": "Neznáma TFA metóda",
|
"unknown_tfa_method": "Neznáma TFA metóda",
|
||||||
"unlimited_quota_acl": "Neobmedzené kvóta je zakázaná cez ACL",
|
"unlimited_quota_acl": "Neobmedzené kvóta je zakázaná cez ACL",
|
||||||
|
@ -638,7 +638,7 @@
|
||||||
"restart_container": "Reštartovať kontajner",
|
"restart_container": "Reštartovať kontajner",
|
||||||
"restart_container_info": "<b>Dôležité:</b> Reštartovanie môže trvať dlhšie, čakajte prosím ...",
|
"restart_container_info": "<b>Dôležité:</b> Reštartovanie môže trvať dlhšie, čakajte prosím ...",
|
||||||
"restart_now": "Reštartuj teraz",
|
"restart_now": "Reštartuj teraz",
|
||||||
"restarting_container": "Prebieha reštartovanie kontajnera, čakajte prosím ..."
|
"restarting_container": "Prebieha reštartovanie kontajnera, čakajte prosím"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"administration": "Konfigurácia & Detaily",
|
"administration": "Konfigurácia & Detaily",
|
||||||
|
@ -741,7 +741,7 @@
|
||||||
"last_run_reset": "Znovu naplánovať",
|
"last_run_reset": "Znovu naplánovať",
|
||||||
"mailbox": "Mailová schránka",
|
"mailbox": "Mailová schránka",
|
||||||
"mailbox_defaults": "Predvolené nastavenia",
|
"mailbox_defaults": "Predvolené nastavenia",
|
||||||
"mailbox_defaults_info": "Definuje predvolené nastavenia pre nové schránky",
|
"mailbox_defaults_info": "Definuje predvolené nastavenia pre nové schránky.",
|
||||||
"mailbox_defquota": "Predvolená veľkosť schránky",
|
"mailbox_defquota": "Predvolená veľkosť schránky",
|
||||||
"mailbox_quota": "Max. veľkosť schránky",
|
"mailbox_quota": "Max. veľkosť schránky",
|
||||||
"mailboxes": "Mailové schránky",
|
"mailboxes": "Mailové schránky",
|
||||||
|
@ -815,7 +815,7 @@
|
||||||
"tls_map_policy": "Podmienky",
|
"tls_map_policy": "Podmienky",
|
||||||
"tls_policy_maps": "Mapy TLS pravidiel",
|
"tls_policy_maps": "Mapy TLS pravidiel",
|
||||||
"tls_policy_maps_enforced_tls": "Tieto politiky prepisujú používateľské nastavenia pre mailové schránky ktoré majú vynútene odchodzie TLS pripojenie. Ak nižšie nie sú uvedené žiadne pravidlá, budú použité ako východzie pravidlá <code>smtp_tls_mandatory_protocols</code> a <code>smtp_tls_mandatory_ciphers</code>.",
|
"tls_policy_maps_enforced_tls": "Tieto politiky prepisujú používateľské nastavenia pre mailové schránky ktoré majú vynútene odchodzie TLS pripojenie. Ak nižšie nie sú uvedené žiadne pravidlá, budú použité ako východzie pravidlá <code>smtp_tls_mandatory_protocols</code> a <code>smtp_tls_mandatory_ciphers</code>.",
|
||||||
"tls_policy_maps_info": "Táto mapa prevažuje nad TLS pravidlami nezávisle od TLS pravidiel jednotlivých používateľov.<br>\r\n Viac informácií navštívte <a href=\"http://www.postfix.org/postconf.5.html#smtp_tls_policy_maps\" target=\"_blank\"> info k \"smtp_tls_policy_maps\" </a>",
|
"tls_policy_maps_info": "Táto mapa prevažuje nad TLS pravidlami nezávisle od TLS pravidiel jednotlivých používateľov.<br>\nPre viac informácií navštívte <a href=\"http://www.postfix.org/postconf.5.html#smtp_tls_policy_maps\" target=\"_blank\"> postfix \"smtp_tls_policy_maps\"</a>.",
|
||||||
"tls_policy_maps_long": "Prepisovanie TLS pravidiel pre odosielanie",
|
"tls_policy_maps_long": "Prepisovanie TLS pravidiel pre odosielanie",
|
||||||
"toggle_all": "Označiť všetky",
|
"toggle_all": "Označiť všetky",
|
||||||
"username": "Používateľské meno",
|
"username": "Používateľské meno",
|
||||||
|
@ -973,7 +973,7 @@
|
||||||
"upload_success": "Súbor úspešne nahratý",
|
"upload_success": "Súbor úspešne nahratý",
|
||||||
"verified_fido2_login": "Overené FIDO2 prihlásenie",
|
"verified_fido2_login": "Overené FIDO2 prihlásenie",
|
||||||
"verified_totp_login": "Overené TOTP prihlásenie",
|
"verified_totp_login": "Overené TOTP prihlásenie",
|
||||||
"verified_u2f_login": "Overené U2F prihlásenie",
|
"verified_webauthn_login": "Overené WebAuthn prihlásenie",
|
||||||
"verified_yotp_login": "Overené Yubico OTP prihlásenie"
|
"verified_yotp_login": "Overené Yubico OTP prihlásenie"
|
||||||
},
|
},
|
||||||
"tfa": {
|
"tfa": {
|
||||||
|
@ -984,7 +984,7 @@
|
||||||
"disable_tfa": "Vypnúť TFA do ďalšieho úspešného prihlásenia",
|
"disable_tfa": "Vypnúť TFA do ďalšieho úspešného prihlásenia",
|
||||||
"enter_qr_code": "Zadajte váš TOTP kód, ak vaše zariadenie nedokáže skenovať QR kódy",
|
"enter_qr_code": "Zadajte váš TOTP kód, ak vaše zariadenie nedokáže skenovať QR kódy",
|
||||||
"error_code": "Chyba kódu",
|
"error_code": "Chyba kódu",
|
||||||
"init_u2f": "Inicializácia, prosím čakajte...",
|
"init_webauthn": "Inicializácia, prosím čakajte...",
|
||||||
"key_id": "Identifikátor pre váš YubiKey",
|
"key_id": "Identifikátor pre váš YubiKey",
|
||||||
"key_id_totp": "Identifikátor pre váš kľúč",
|
"key_id_totp": "Identifikátor pre váš kľúč",
|
||||||
"none": "Deaktivovať",
|
"none": "Deaktivovať",
|
||||||
|
@ -992,11 +992,11 @@
|
||||||
"scan_qr_code": "Prosím oskenujte nasledovný kód pomocou vašej autentizačnej aplikácie alebo zadajte kód manuálne.",
|
"scan_qr_code": "Prosím oskenujte nasledovný kód pomocou vašej autentizačnej aplikácie alebo zadajte kód manuálne.",
|
||||||
"select": "Prosím vyberte",
|
"select": "Prosím vyberte",
|
||||||
"set_tfa": "Nastaviť dvojúrovňovú autentifikačnú metódu",
|
"set_tfa": "Nastaviť dvojúrovňovú autentifikačnú metódu",
|
||||||
"start_u2f_validation": "Spustiť validáciu",
|
"start_webauthn_validation": "Spustiť validáciu",
|
||||||
"tfa": "Dvojúrovňová autentifikácia (TFA)",
|
"tfa": "Dvojúrovňová autentifikácia (TFA)",
|
||||||
"tfa_token_invalid": "Neplatný TFA token",
|
"tfa_token_invalid": "Neplatný TFA token",
|
||||||
"totp": "Časovo-založený OTP (Google Authenticator, Authy, atď.)",
|
"totp": "Časovo-založený OTP (Google Authenticator, Authy, atď.)",
|
||||||
"u2f": "U2F autentifikácia",
|
"webauthn": "WebAuthn autentifikácia",
|
||||||
"waiting_usb_auth": "<i>Čakanie na USB zariadenie...</i><br><br>Prosím stlačte tlačidlo na vašom USB zariadení.",
|
"waiting_usb_auth": "<i>Čakanie na USB zariadenie...</i><br><br>Prosím stlačte tlačidlo na vašom USB zariadení.",
|
||||||
"waiting_usb_register": "<i>Čakanie na USB zariadenie...</i><br><br>Prosím zadajte vaše heslo a potvrďte registráciu stlačením tlačidla na vašom USB zariadení.",
|
"waiting_usb_register": "<i>Čakanie na USB zariadenie...</i><br><br>Prosím zadajte vaše heslo a potvrďte registráciu stlačením tlačidla na vašom USB zariadení.",
|
||||||
"yubi_otp": "Yubico OTP autentifikácia"
|
"yubi_otp": "Yubico OTP autentifikácia"
|
||||||
|
@ -1069,8 +1069,8 @@
|
||||||
"never": "Nikdy",
|
"never": "Nikdy",
|
||||||
"new_password": "Nové heslo",
|
"new_password": "Nové heslo",
|
||||||
"new_password_repeat": "Potvrdiť heslo (opakovať)",
|
"new_password_repeat": "Potvrdiť heslo (opakovať)",
|
||||||
"no_active_filter": "Nie je dostupný žiadny aktívny filter.",
|
"no_active_filter": "Nie je dostupný žiadny aktívny filter",
|
||||||
"no_last_login": "Žiadne informácie o UI prihlásení.",
|
"no_last_login": "Žiadny záznam o prihlásení cez web",
|
||||||
"no_record": "Žiaden záznam",
|
"no_record": "Žiaden záznam",
|
||||||
"open_logs": "Otvoriť záznam",
|
"open_logs": "Otvoriť záznam",
|
||||||
"open_webmail_sso": "Prihláste sa do webmailu",
|
"open_webmail_sso": "Prihláste sa do webmailu",
|
||||||
|
|
|
@ -441,8 +441,8 @@
|
||||||
"tls_policy_map_parameter_invalid": "Policy parameter är ogiltig",
|
"tls_policy_map_parameter_invalid": "Policy parameter är ogiltig",
|
||||||
"totp_verification_failed": "TOTP-verifiering misslyckades",
|
"totp_verification_failed": "TOTP-verifiering misslyckades",
|
||||||
"transport_dest_exists": "Transportdestinationen \"%s\" existerar redan",
|
"transport_dest_exists": "Transportdestinationen \"%s\" existerar redan",
|
||||||
"u2f_verification_failed": "U2F-verifiering misslyckades: %s",
|
"webauthn_verification_failed": "WebAuthn-verifiering misslyckades: %s",
|
||||||
"fido2_verification_failed": "U2F-verifiering misslyckades: %s",
|
"fido2_verification_failed": "WebAuthn-verifiering misslyckades: %s",
|
||||||
"unknown": "Ett fel har inträffat",
|
"unknown": "Ett fel har inträffat",
|
||||||
"unknown_tfa_method": "Okänd TFA method",
|
"unknown_tfa_method": "Okänd TFA method",
|
||||||
"unlimited_quota_acl": "På grund av en åtkomstlista tillåts inte en obegränsad kvot",
|
"unlimited_quota_acl": "På grund av en åtkomstlista tillåts inte en obegränsad kvot",
|
||||||
|
@ -911,7 +911,7 @@
|
||||||
"ui_texts": "Ändringarna på texter och rubriker i gränssnittet sparade",
|
"ui_texts": "Ändringarna på texter och rubriker i gränssnittet sparade",
|
||||||
"upload_success": "Filen har laddats upp",
|
"upload_success": "Filen har laddats upp",
|
||||||
"verified_totp_login": "Verifierad TOTP inloggning",
|
"verified_totp_login": "Verifierad TOTP inloggning",
|
||||||
"verified_u2f_login": "Verifierad U2F inloggning",
|
"verified_webauthn_login": "Verifierad WebAuthn inloggning",
|
||||||
"verified_fido2_login": "Verifierad FIDO2 inloggning",
|
"verified_fido2_login": "Verifierad FIDO2 inloggning",
|
||||||
"verified_yotp_login": "Verifierad Yubico OTP inloggning"
|
"verified_yotp_login": "Verifierad Yubico OTP inloggning"
|
||||||
},
|
},
|
||||||
|
@ -923,7 +923,7 @@
|
||||||
"disable_tfa": "Inaktivera tvåfaktorsautentisering tills nästa lyckade inloggning",
|
"disable_tfa": "Inaktivera tvåfaktorsautentisering tills nästa lyckade inloggning",
|
||||||
"enter_qr_code": "Om du inte kan skanna den QR-kod som visas, använd säkerhetsnyckeln som visas nedan",
|
"enter_qr_code": "Om du inte kan skanna den QR-kod som visas, använd säkerhetsnyckeln som visas nedan",
|
||||||
"error_code": "Felkod",
|
"error_code": "Felkod",
|
||||||
"init_u2f": "Initierar, vänta...",
|
"init_webauthn": "Initierar, vänta...",
|
||||||
"key_id": "En identifierare för din YubiKey",
|
"key_id": "En identifierare för din YubiKey",
|
||||||
"key_id_totp": "En identifierare för din nyckel",
|
"key_id_totp": "En identifierare för din nyckel",
|
||||||
"none": "Avaktivera",
|
"none": "Avaktivera",
|
||||||
|
@ -931,11 +931,11 @@
|
||||||
"scan_qr_code": "Skanna nu den QR-kod som visas på skärmen.",
|
"scan_qr_code": "Skanna nu den QR-kod som visas på skärmen.",
|
||||||
"select": "Välj",
|
"select": "Välj",
|
||||||
"set_tfa": "Metod för tvåfaktorsautentisering",
|
"set_tfa": "Metod för tvåfaktorsautentisering",
|
||||||
"start_u2f_validation": "Startar validering",
|
"start_webauthn_validation": "Startar validering",
|
||||||
"tfa": "Tvåfaktorsautentisering",
|
"tfa": "Tvåfaktorsautentisering",
|
||||||
"tfa_token_invalid": "TFA nyckeln är ogiltig!",
|
"tfa_token_invalid": "TFA nyckeln är ogiltig!",
|
||||||
"totp": "Tidsbaserad OTP (Google Authenticator, Authy, mm)",
|
"totp": "Tidsbaserad OTP (Google Authenticator, Authy, mm)",
|
||||||
"u2f": "U2F-autentisering",
|
"webauthn": "WebAuthn-autentisering",
|
||||||
"waiting_usb_auth": "<i>Väntar på USB-enhet...</i><br><br>Tryck på knappen på USB-enheten nu.",
|
"waiting_usb_auth": "<i>Väntar på USB-enhet...</i><br><br>Tryck på knappen på USB-enheten nu.",
|
||||||
"waiting_usb_register": "<i>Väntar på USB-enhet...</i><br><br>Vänligen fyll i det övre lösenordsfältet först och tryck sedan på knappen på USB-enheten.",
|
"waiting_usb_register": "<i>Väntar på USB-enhet...</i><br><br>Vänligen fyll i det övre lösenordsfältet först och tryck sedan på knappen på USB-enheten.",
|
||||||
"yubi_otp": "Yubico OTP-autentisering"
|
"yubi_otp": "Yubico OTP-autentisering"
|
||||||
|
|
|
@ -424,7 +424,7 @@
|
||||||
"tls_policy_map_parameter_invalid": "策略参数非法",
|
"tls_policy_map_parameter_invalid": "策略参数非法",
|
||||||
"totp_verification_failed": "TOTP认证失败",
|
"totp_verification_failed": "TOTP认证失败",
|
||||||
"transport_dest_exists": "传输目标 \"%s\" 已存在",
|
"transport_dest_exists": "传输目标 \"%s\" 已存在",
|
||||||
"u2f_verification_failed": "U2F认证失败: %s",
|
"webauthn_verification_failed": "WebAuthn认证失败: %s",
|
||||||
"unknown": "发生未知错误",
|
"unknown": "发生未知错误",
|
||||||
"unknown_tfa_method": "未知TFA方法",
|
"unknown_tfa_method": "未知TFA方法",
|
||||||
"unlimited_quota_acl": "ACL设置禁止了无限配额",
|
"unlimited_quota_acl": "ACL设置禁止了无限配额",
|
||||||
|
@ -875,7 +875,7 @@
|
||||||
"ui_texts": "已保存UI文本更改",
|
"ui_texts": "已保存UI文本更改",
|
||||||
"upload_success": "成功上传文件",
|
"upload_success": "成功上传文件",
|
||||||
"verified_totp_login": "TOTP登录验证成功",
|
"verified_totp_login": "TOTP登录验证成功",
|
||||||
"verified_u2f_login": "U2F登录验证成功",
|
"verified_webauthn_login": "WebAuthn登录验证成功",
|
||||||
"verified_yotp_login": "Yubico OTP登录验证成功"
|
"verified_yotp_login": "Yubico OTP登录验证成功"
|
||||||
},
|
},
|
||||||
"tfa": {
|
"tfa": {
|
||||||
|
@ -886,7 +886,7 @@
|
||||||
"disable_tfa": "在下一次成功登录前关闭两步验证",
|
"disable_tfa": "在下一次成功登录前关闭两步验证",
|
||||||
"enter_qr_code": "如果你的设备不能扫描QR码,输入此TOTP码",
|
"enter_qr_code": "如果你的设备不能扫描QR码,输入此TOTP码",
|
||||||
"error_code": "错误码",
|
"error_code": "错误码",
|
||||||
"init_u2f": "初始化中,请等待...",
|
"init_webauthn": "初始化中,请等待...",
|
||||||
"key_id": "你的YubiKey的标识",
|
"key_id": "你的YubiKey的标识",
|
||||||
"key_id_totp": "你的密钥的标识",
|
"key_id_totp": "你的密钥的标识",
|
||||||
"none": "禁用",
|
"none": "禁用",
|
||||||
|
@ -894,12 +894,12 @@
|
||||||
"scan_qr_code": "请用你认证应用扫描或手动输入此码。",
|
"scan_qr_code": "请用你认证应用扫描或手动输入此码。",
|
||||||
"select": "请选择",
|
"select": "请选择",
|
||||||
"set_tfa": "设置两步验证方法",
|
"set_tfa": "设置两步验证方法",
|
||||||
"start_u2f_validation": "开始认证",
|
"start_webauthn_validation": "开始认证",
|
||||||
"tfa": "两步验证(2FA)",
|
"tfa": "两步验证(2FA)",
|
||||||
"totp": "TOTP认证 (Google Authenticator、Authy等)",
|
"totp": "TOTP认证 (Google Authenticator、Authy等)",
|
||||||
"u2f": "U2F认证",
|
"webauthn": "WebAuthn认证",
|
||||||
"waiting_usb_auth": "<i>等待USB设备...</i><br><br>现在请触碰你的U2F USB设备上的按钮。",
|
"waiting_usb_auth": "<i>等待USB设备...</i><br><br>现在请触碰你的WebAuthn USB设备上的按钮。",
|
||||||
"waiting_usb_register": "<i>等待USB设备...</i><br><br>请在上方输入你的密码并请触碰你的U2F USB设备上的按钮以确认注册U2F设备。",
|
"waiting_usb_register": "<i>等待USB设备...</i><br><br>请在上方输入你的密码并请触碰你的WebAuthn USB设备上的按钮以确认注册WebAuthn设备。",
|
||||||
"yubi_otp": "Yubico OTP认证"
|
"yubi_otp": "Yubico OTP认证"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
<div class="col-sm-9 col-xs-7">
|
<div class="col-sm-9 col-xs-7">
|
||||||
<select data-style="btn btn-sm dropdown-toggle bs-placeholder btn-default" data-width="fit" id="selectTFA" class="selectpicker" title="{{ lang.tfa.select }}">
|
<select data-style="btn btn-sm dropdown-toggle bs-placeholder btn-default" data-width="fit" id="selectTFA" class="selectpicker" title="{{ lang.tfa.select }}">
|
||||||
<option value="yubi_otp">{{ lang.tfa.yubi_otp }}</option>
|
<option value="yubi_otp">{{ lang.tfa.yubi_otp }}</option>
|
||||||
<option value="u2f">{{ lang.tfa.u2f }}</option>
|
<option value="webauthn">{{ lang.tfa.webauthn }}</option>
|
||||||
<option value="totp">{{ lang.tfa.totp }}</option>
|
<option value="totp">{{ lang.tfa.totp }}</option>
|
||||||
<option value="none">{{ lang.tfa.none }}</option>
|
<option value="none">{{ lang.tfa.none }}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
|
@ -181,34 +181,52 @@ function recursiveBase64StrToArrayBuffer(obj) {
|
||||||
backdrop: 'static',
|
backdrop: 'static',
|
||||||
keyboard: false
|
keyboard: false
|
||||||
});
|
});
|
||||||
$('#u2f_status_auth').html('<p><i class="bi bi-arrow-repeat icon-spin"></i> ' + lang_tfa.init_u2f + '</p>');
|
|
||||||
$('#ConfirmTFAModal').on('shown.bs.modal', function(){
|
// validate WebAuthn tfa
|
||||||
|
$('#start_webauthn_confirmation').click(function(){
|
||||||
|
$('#webauthn_status_auth').html('<p><i class="bi bi-arrow-repeat icon-spin"></i> ' + lang_tfa.init_webauthn + '</p>');
|
||||||
|
|
||||||
$(this).find('input[name=token]').focus();
|
$(this).find('input[name=token]').focus();
|
||||||
// If U2F
|
if(document.getElementById("webauthn_auth_data") !== null) {
|
||||||
if(document.getElementById("u2f_auth_data") !== null) {
|
// Check Browser support
|
||||||
$.ajax({
|
if (!window.fetch || !navigator.credentials || !navigator.credentials.create) {
|
||||||
type: "GET",
|
window.alert('Browser not supported for WebAuthn.');
|
||||||
cache: false,
|
return;
|
||||||
dataType: 'script',
|
|
||||||
url: "/api/v1/get/u2f-authentication/{{ pending_mailcow_cc_username|url_encode(true)|default('null') }}",
|
|
||||||
complete: function(data){
|
|
||||||
$('#u2f_status_auth').html(lang_tfa.waiting_usb_auth);
|
|
||||||
data;
|
|
||||||
setTimeout(function() {
|
|
||||||
console.log("Ready to authenticate");
|
|
||||||
u2f.sign(appId, challenge, registeredKeys, function(data) {
|
|
||||||
var form = document.getElementById('u2f_auth_form');
|
|
||||||
var auth = document.getElementById('u2f_auth_data');
|
|
||||||
console.log("Authenticate callback", data);
|
|
||||||
auth.value = JSON.stringify(data);
|
|
||||||
form.submit();
|
|
||||||
});
|
|
||||||
}, 1000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fetch webauthn auth args
|
||||||
|
window.fetch("/api/v1/get/webauthn-tfa-get-args", {method:'GET',cache:'no-cache'}).then(response => {
|
||||||
|
return response.json();
|
||||||
|
}).then(json => {
|
||||||
|
if (json.success === false) throw new Error();
|
||||||
|
|
||||||
|
recursiveBase64StrToArrayBuffer(json);
|
||||||
|
return json;
|
||||||
|
}).then(getCredentialArgs => {
|
||||||
|
// get credentials
|
||||||
|
return navigator.credentials.get(getCredentialArgs);
|
||||||
|
}).then(cred => {
|
||||||
|
return {
|
||||||
|
id: cred.rawId ? arrayBufferToBase64(cred.rawId) : null,
|
||||||
|
clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null,
|
||||||
|
authenticatorData: cred.response.authenticatorData ? arrayBufferToBase64(cred.response.authenticatorData) : null,
|
||||||
|
signature : cred.response.signature ? arrayBufferToBase64(cred.response.signature) : null
|
||||||
|
};
|
||||||
|
}).then(JSON.stringify).then(function(AuthenticatorAttestationResponse) {
|
||||||
|
// send request by submit
|
||||||
|
var form = document.getElementById('webauthn_auth_form');
|
||||||
|
var auth = document.getElementById('webauthn_auth_data');
|
||||||
|
auth.value = AuthenticatorAttestationResponse;
|
||||||
|
form.submit();
|
||||||
|
}).catch(function(err) {
|
||||||
|
var webauthn_return_code = document.getElementById('webauthn_return_code');
|
||||||
|
webauthn_return_code.style.display = webauthn_return_code.style.display === 'none' ? '' : null;
|
||||||
|
webauthn_return_code.innerHTML = lang_tfa.error_code + ': ' + err + ' ' + lang_tfa.reload_retry;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$('#ConfirmTFAModal').on('hidden.bs.modal', function(){
|
$('#ConfirmTFAModal').on('hidden.bs.modal', function(){
|
||||||
|
// cancel pending login
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "GET",
|
type: "GET",
|
||||||
cache: false,
|
cache: false,
|
||||||
|
@ -327,44 +345,55 @@ function recursiveBase64StrToArrayBuffer(obj) {
|
||||||
});
|
});
|
||||||
$("option:selected").prop("selected", false);
|
$("option:selected").prop("selected", false);
|
||||||
}
|
}
|
||||||
if ($(this).val() == "u2f") {
|
if ($(this).val() == "webauthn") {
|
||||||
$('#U2FModal').modal('show');
|
// check if Browser is supported
|
||||||
$("option:selected").prop("selected", false);
|
if (!window.fetch || !navigator.credentials || !navigator.credentials.create) {
|
||||||
$("#start_u2f_register").click(function(){
|
window.alert('Browser not supported.');
|
||||||
$('#u2f_return_code').html('');
|
|
||||||
$('#u2f_return_code').hide();
|
|
||||||
$('#u2f_status_reg').html('<p><i class="bi bi-arrow-repeat icon-spin"></i> ' + lang_tfa.init_u2f + '</p>');
|
|
||||||
$.ajax({
|
|
||||||
type: "GET",
|
|
||||||
cache: false,
|
|
||||||
dataType: 'script',
|
|
||||||
url: "/api/v1/get/u2f-registration/{{ mailcow_cc_username|url_encode(true)|default('null') }}",
|
|
||||||
complete: function(data){
|
|
||||||
data;
|
|
||||||
setTimeout(function() {
|
|
||||||
console.log("Ready to register");
|
|
||||||
$('#u2f_status_reg').html(lang_tfa.waiting_usb_register);
|
|
||||||
u2f.register(appId, registerRequests, registeredKeys, function(deviceResponse) {
|
|
||||||
var form = document.getElementById('u2f_reg_form');
|
|
||||||
var reg = document.getElementById('u2f_register_data');
|
|
||||||
console.log("Register callback: ", data);
|
|
||||||
if (deviceResponse.errorCode && deviceResponse.errorCode != 0) {
|
|
||||||
var u2f_return_code = document.getElementById('u2f_return_code');
|
|
||||||
u2f_return_code.style.display = u2f_return_code.style.display === 'none' ? '' : null;
|
|
||||||
if (deviceResponse.errorCode == "4") {
|
|
||||||
deviceResponse.errorCode = "4 - The presented device is not eligible for this request. For a registration request this may mean that the token is already registered, and for a sign request it may mean that the token does not know the presented key handle";
|
|
||||||
}
|
|
||||||
else if (deviceResponse.errorCode == "5") {
|
|
||||||
deviceResponse.errorCode = "5 - Timeout reached before request could be satisfied.";
|
|
||||||
}
|
|
||||||
u2f_return_code.innerHTML = lang_tfa.error_code + ': ' + deviceResponse.errorCode + ' ' + lang_tfa.reload_retry;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
reg.value = JSON.stringify(deviceResponse);
|
|
||||||
form.submit();
|
// show modal
|
||||||
});
|
$('#WebAuthnModal').modal('show');
|
||||||
}, 1000);
|
$("option:selected").prop("selected", false);
|
||||||
|
|
||||||
|
$("#start_webauthn_register").click(() => {
|
||||||
|
var key_id = document.getElementsByName('key_id')[1].value;
|
||||||
|
|
||||||
|
// fetch WebAuthn create args
|
||||||
|
window.fetch("/api/v1/get/webauthn-tfa-registration/{{ mailcow_cc_username|url_encode(true)|default('null') }}", {method:'GET',cache:'no-cache'}).then(response => {
|
||||||
|
return response.json();
|
||||||
|
}).then(json => {
|
||||||
|
if (json.success === false) throw new Error(json.msg);
|
||||||
|
recursiveBase64StrToArrayBuffer(json);
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}).then(createCredentialArgs => {
|
||||||
|
// create credentials
|
||||||
|
return navigator.credentials.create(createCredentialArgs);
|
||||||
|
}).then(cred => {
|
||||||
|
return {
|
||||||
|
clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null,
|
||||||
|
attestationObject: cred.response.attestationObject ? arrayBufferToBase64(cred.response.attestationObject) : null,
|
||||||
|
key_id: key_id,
|
||||||
|
tfa_method: "webauthn"
|
||||||
|
};
|
||||||
|
}).then(JSON.stringify).then(AuthenticatorAttestationResponse => {
|
||||||
|
// send request
|
||||||
|
return window.fetch("/api/v1/add/webauthn-tfa-registration", {method:'POST', body: AuthenticatorAttestationResponse, cache:'no-cache'});
|
||||||
|
}).then(response => {
|
||||||
|
return response.json();
|
||||||
|
}).then(json => {
|
||||||
|
if (json.success) {
|
||||||
|
// reload on success
|
||||||
|
window.location = window.location.href.split("#")[0];
|
||||||
|
} else {
|
||||||
|
throw new Error(json.msg);
|
||||||
}
|
}
|
||||||
|
}).catch(function(err) {
|
||||||
|
console.log(err);
|
||||||
|
var webauthn_return_code = document.getElementById('webauthn_return_code');
|
||||||
|
webauthn_return_code.style.display = webauthn_return_code.style.display === 'none' ? '' : null;
|
||||||
|
webauthn_return_code.innerHTML = lang_tfa.error_code + ': ' + err + ' ' + lang_tfa.reload_retry;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,15 +37,15 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal fade" id="U2FModal" tabindex="-1" role="dialog" aria-labelledby="U2FModalLabel">
|
<div class="modal fade" id="WebAuthnModal" tabindex="-1" role="dialog" aria-labelledby="WebAuthnModalLabel">
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span></button>
|
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span></button>
|
||||||
<h3 class="modal-title">{{ lang.tfa.u2f }}</h3>
|
<h3 class="modal-title">{{ lang.tfa.webauthn }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form role="form" method="post" id="u2f_reg_form">
|
<form role="form" method="post" id="webauthn_reg_form">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" class="form-control" name="key_id" placeholder="{{ lang.tfa.key_id }}" autocomplete="off" required>
|
<input type="text" class="form-control" name="key_id" placeholder="{{ lang.tfa.key_id }}" autocomplete="off" required>
|
||||||
</div>
|
</div>
|
||||||
|
@ -54,18 +54,18 @@
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<center>
|
<center>
|
||||||
<div style="cursor:pointer" id="start_u2f_register">
|
<div style="cursor:pointer" id="start_webauthn_register">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24">
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24">
|
||||||
<path d="M17.81 4.47c-.08 0-.16-.02-.23-.06C15.66 3.42 14 3 12.01 3c-1.98 0-3.86.47-5.57 1.41-.24.13-.54.04-.68-.2-.13-.24-.04-.55.2-.68C7.82 2.52 9.86 2 12.01 2c2.13 0 3.99.47 6.03 1.52.25.13.34.43.21.67-.09.18-.26.28-.44.28zM3.5 9.72c-.1 0-.2-.03-.29-.09-.23-.16-.28-.47-.12-.7.99-1.4 2.25-2.5 3.75-3.27C9.98 4.04 14 4.03 17.15 5.65c1.5.77 2.76 1.86 3.75 3.25.16.22.11.54-.12.7-.23.16-.54.11-.7-.12-.9-1.26-2.04-2.25-3.39-2.94-2.87-1.47-6.54-1.47-9.4.01-1.36.7-2.5 1.7-3.4 2.96-.08.14-.23.21-.39.21zm6.25 12.07c-.13 0-.26-.05-.35-.15-.87-.87-1.34-1.43-2.01-2.64-.69-1.23-1.05-2.73-1.05-4.34 0-2.97 2.54-5.39 5.66-5.39s5.66 2.42 5.66 5.39c0 .28-.22.5-.5.5s-.5-.22-.5-.5c0-2.42-2.09-4.39-4.66-4.39-2.57 0-4.66 1.97-4.66 4.39 0 1.44.32 2.77.93 3.85.64 1.15 1.08 1.64 1.85 2.42.19.2.19.51 0 .71-.11.1-.24.15-.37.15zm7.17-1.85c-1.19 0-2.24-.3-3.1-.89-1.49-1.01-2.38-2.65-2.38-4.39 0-.28.22-.5.5-.5s.5.22.5.5c0 1.41.72 2.74 1.94 3.56.71.48 1.54.71 2.54.71.24 0 .64-.03 1.04-.1.27-.05.53.13.58.41.05.27-.13.53-.41.58-.57.11-1.07.12-1.21.12zM14.91 22c-.04 0-.09-.01-.13-.02-1.59-.44-2.63-1.03-3.72-2.1-1.4-1.39-2.17-3.24-2.17-5.22 0-1.62 1.38-2.94 3.08-2.94 1.7 0 3.08 1.32 3.08 2.94 0 1.07.93 1.94 2.08 1.94s2.08-.87 2.08-1.94c0-3.77-3.25-6.83-7.25-6.83-2.84 0-5.44 1.58-6.61 4.03-.39.81-.59 1.76-.59 2.8 0 .78.07 2.01.67 3.61.1.26-.03.55-.29.64-.26.1-.55-.04-.64-.29-.49-1.31-.73-2.61-.73-3.96 0-1.2.23-2.29.68-3.24 1.33-2.79 4.28-4.6 7.51-4.6 4.55 0 8.25 3.51 8.25 7.83 0 1.62-1.38 2.94-3.08 2.94s-3.08-1.32-3.08-2.94c0-1.07-.93-1.94-2.08-1.94s-2.08.87-2.08 1.94c0 1.71.66 3.31 1.87 4.51.95.94 1.86 1.46 3.27 1.85.27.07.42.35.35.61-.05.23-.26.38-.47.38z"></path>
|
<path d="M17.81 4.47c-.08 0-.16-.02-.23-.06C15.66 3.42 14 3 12.01 3c-1.98 0-3.86.47-5.57 1.41-.24.13-.54.04-.68-.2-.13-.24-.04-.55.2-.68C7.82 2.52 9.86 2 12.01 2c2.13 0 3.99.47 6.03 1.52.25.13.34.43.21.67-.09.18-.26.28-.44.28zM3.5 9.72c-.1 0-.2-.03-.29-.09-.23-.16-.28-.47-.12-.7.99-1.4 2.25-2.5 3.75-3.27C9.98 4.04 14 4.03 17.15 5.65c1.5.77 2.76 1.86 3.75 3.25.16.22.11.54-.12.7-.23.16-.54.11-.7-.12-.9-1.26-2.04-2.25-3.39-2.94-2.87-1.47-6.54-1.47-9.4.01-1.36.7-2.5 1.7-3.4 2.96-.08.14-.23.21-.39.21zm6.25 12.07c-.13 0-.26-.05-.35-.15-.87-.87-1.34-1.43-2.01-2.64-.69-1.23-1.05-2.73-1.05-4.34 0-2.97 2.54-5.39 5.66-5.39s5.66 2.42 5.66 5.39c0 .28-.22.5-.5.5s-.5-.22-.5-.5c0-2.42-2.09-4.39-4.66-4.39-2.57 0-4.66 1.97-4.66 4.39 0 1.44.32 2.77.93 3.85.64 1.15 1.08 1.64 1.85 2.42.19.2.19.51 0 .71-.11.1-.24.15-.37.15zm7.17-1.85c-1.19 0-2.24-.3-3.1-.89-1.49-1.01-2.38-2.65-2.38-4.39 0-.28.22-.5.5-.5s.5.22.5.5c0 1.41.72 2.74 1.94 3.56.71.48 1.54.71 2.54.71.24 0 .64-.03 1.04-.1.27-.05.53.13.58.41.05.27-.13.53-.41.58-.57.11-1.07.12-1.21.12zM14.91 22c-.04 0-.09-.01-.13-.02-1.59-.44-2.63-1.03-3.72-2.1-1.4-1.39-2.17-3.24-2.17-5.22 0-1.62 1.38-2.94 3.08-2.94 1.7 0 3.08 1.32 3.08 2.94 0 1.07.93 1.94 2.08 1.94s2.08-.87 2.08-1.94c0-3.77-3.25-6.83-7.25-6.83-2.84 0-5.44 1.58-6.61 4.03-.39.81-.59 1.76-.59 2.8 0 .78.07 2.01.67 3.61.1.26-.03.55-.29.64-.26.1-.55-.04-.64-.29-.49-1.31-.73-2.61-.73-3.96 0-1.2.23-2.29.68-3.24 1.33-2.79 4.28-4.6 7.51-4.6 4.55 0 8.25 3.51 8.25 7.83 0 1.62-1.38 2.94-3.08 2.94s-3.08-1.32-3.08-2.94c0-1.07-.93-1.94-2.08-1.94s-2.08.87-2.08 1.94c0 1.71.66 3.31 1.87 4.51.95.94 1.86 1.46 3.27 1.85.27.07.42.35.35.61-.05.23-.26.38-.47.38z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<p>{{ lang.tfa.start_u2f_validation }}</p>
|
<p>{{ lang.tfa.start_webauthn_validation }}</p>
|
||||||
<hr>
|
<hr>
|
||||||
</div>
|
</div>
|
||||||
</center>
|
</center>
|
||||||
<p id="u2f_status_reg"></p>
|
<p id="webauthn_status_reg"></p>
|
||||||
<div class="alert alert-danger" style="display:none" id="u2f_return_code"></div>
|
<div class="alert alert-danger" style="display:none" id="webauthn_return_code"></div>
|
||||||
<input type="hidden" name="token" id="u2f_register_data"/>
|
<input type="hidden" name="token" id="webauthn_register_data"/>
|
||||||
<input type="hidden" name="tfa_method" value="u2f">
|
<input type="hidden" name="tfa_method" value="webauthn">
|
||||||
<input type="hidden" name="set_tfa"/><br/>
|
<input type="hidden" name="set_tfa"/><br/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -154,24 +154,6 @@
|
||||||
<button class="btn btn-sm visible-xs-block visible-sm-inline visible-md-inline visible-lg-inline btn-sm btn-default" type="submit" name="verify_tfa_login">{{ lang.login.login }}</button>
|
<button class="btn btn-sm visible-xs-block visible-sm-inline visible-md-inline visible-lg-inline btn-sm btn-default" type="submit" name="verify_tfa_login">{{ lang.login.login }}</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if pending_tfa_method == 'u2f' %}
|
|
||||||
<form role="form" method="post" id="u2f_auth_form">
|
|
||||||
<center>
|
|
||||||
<div id="start_u2f_confirmation">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24">
|
|
||||||
<path d="M17.81 4.47c-.08 0-.16-.02-.23-.06C15.66 3.42 14 3 12.01 3c-1.98 0-3.86.47-5.57 1.41-.24.13-.54.04-.68-.2-.13-.24-.04-.55.2-.68C7.82 2.52 9.86 2 12.01 2c2.13 0 3.99.47 6.03 1.52.25.13.34.43.21.67-.09.18-.26.28-.44.28zM3.5 9.72c-.1 0-.2-.03-.29-.09-.23-.16-.28-.47-.12-.7.99-1.4 2.25-2.5 3.75-3.27C9.98 4.04 14 4.03 17.15 5.65c1.5.77 2.76 1.86 3.75 3.25.16.22.11.54-.12.7-.23.16-.54.11-.7-.12-.9-1.26-2.04-2.25-3.39-2.94-2.87-1.47-6.54-1.47-9.4.01-1.36.7-2.5 1.7-3.4 2.96-.08.14-.23.21-.39.21zm6.25 12.07c-.13 0-.26-.05-.35-.15-.87-.87-1.34-1.43-2.01-2.64-.69-1.23-1.05-2.73-1.05-4.34 0-2.97 2.54-5.39 5.66-5.39s5.66 2.42 5.66 5.39c0 .28-.22.5-.5.5s-.5-.22-.5-.5c0-2.42-2.09-4.39-4.66-4.39-2.57 0-4.66 1.97-4.66 4.39 0 1.44.32 2.77.93 3.85.64 1.15 1.08 1.64 1.85 2.42.19.2.19.51 0 .71-.11.1-.24.15-.37.15zm7.17-1.85c-1.19 0-2.24-.3-3.1-.89-1.49-1.01-2.38-2.65-2.38-4.39 0-.28.22-.5.5-.5s.5.22.5.5c0 1.41.72 2.74 1.94 3.56.71.48 1.54.71 2.54.71.24 0 .64-.03 1.04-.1.27-.05.53.13.58.41.05.27-.13.53-.41.58-.57.11-1.07.12-1.21.12zM14.91 22c-.04 0-.09-.01-.13-.02-1.59-.44-2.63-1.03-3.72-2.1-1.4-1.39-2.17-3.24-2.17-5.22 0-1.62 1.38-2.94 3.08-2.94 1.7 0 3.08 1.32 3.08 2.94 0 1.07.93 1.94 2.08 1.94s2.08-.87 2.08-1.94c0-3.77-3.25-6.83-7.25-6.83-2.84 0-5.44 1.58-6.61 4.03-.39.81-.59 1.76-.59 2.8 0 .78.07 2.01.67 3.61.1.26-.03.55-.29.64-.26.1-.55-.04-.64-.29-.49-1.31-.73-2.61-.73-3.96 0-1.2.23-2.29.68-3.24 1.33-2.79 4.28-4.6 7.51-4.6 4.55 0 8.25 3.51 8.25 7.83 0 1.62-1.38 2.94-3.08 2.94s-3.08-1.32-3.08-2.94c0-1.07-.93-1.94-2.08-1.94s-2.08.87-2.08 1.94c0 1.71.66 3.31 1.87 4.51.95.94 1.86 1.46 3.27 1.85.27.07.42.35.35.61-.05.23-.26.38-.47.38z"></path>
|
|
||||||
</svg>
|
|
||||||
<p>{{ lang.tfa.start_u2f_validation }}</p>
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
</center>
|
|
||||||
<p id="u2f_status_auth"></p>
|
|
||||||
<div class="alert alert-danger" style="display:none" id="u2f_return_code"></div>
|
|
||||||
<input type="hidden" name="token" id="u2f_auth_data"/>
|
|
||||||
<input type="hidden" name="tfa_method" value="u2f">
|
|
||||||
<input type="hidden" name="verify_tfa_login"/><br/>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
{% if pending_tfa_method == 'totp' %}
|
{% if pending_tfa_method == 'totp' %}
|
||||||
<form role="form" method="post">
|
<form role="form" method="post">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -187,6 +169,36 @@
|
||||||
{% if pending_tfa_method == 'hotp' %}
|
{% if pending_tfa_method == 'hotp' %}
|
||||||
<div class="empty"></div>
|
<div class="empty"></div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if pending_tfa_method == 'webauthn' %}
|
||||||
|
<form role="form" method="post" id="webauthn_auth_form">
|
||||||
|
<center>
|
||||||
|
<div style="cursor:pointer" id="start_webauthn_confirmation">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24">
|
||||||
|
<path d="M17.81 4.47c-.08 0-.16-.02-.23-.06C15.66 3.42 14 3 12.01 3c-1.98 0-3.86.47-5.57 1.41-.24.13-.54.04-.68-.2-.13-.24-.04-.55.2-.68C7.82 2.52 9.86 2 12.01 2c2.13 0 3.99.47 6.03 1.52.25.13.34.43.21.67-.09.18-.26.28-.44.28zM3.5 9.72c-.1 0-.2-.03-.29-.09-.23-.16-.28-.47-.12-.7.99-1.4 2.25-2.5 3.75-3.27C9.98 4.04 14 4.03 17.15 5.65c1.5.77 2.76 1.86 3.75 3.25.16.22.11.54-.12.7-.23.16-.54.11-.7-.12-.9-1.26-2.04-2.25-3.39-2.94-2.87-1.47-6.54-1.47-9.4.01-1.36.7-2.5 1.7-3.4 2.96-.08.14-.23.21-.39.21zm6.25 12.07c-.13 0-.26-.05-.35-.15-.87-.87-1.34-1.43-2.01-2.64-.69-1.23-1.05-2.73-1.05-4.34 0-2.97 2.54-5.39 5.66-5.39s5.66 2.42 5.66 5.39c0 .28-.22.5-.5.5s-.5-.22-.5-.5c0-2.42-2.09-4.39-4.66-4.39-2.57 0-4.66 1.97-4.66 4.39 0 1.44.32 2.77.93 3.85.64 1.15 1.08 1.64 1.85 2.42.19.2.19.51 0 .71-.11.1-.24.15-.37.15zm7.17-1.85c-1.19 0-2.24-.3-3.1-.89-1.49-1.01-2.38-2.65-2.38-4.39 0-.28.22-.5.5-.5s.5.22.5.5c0 1.41.72 2.74 1.94 3.56.71.48 1.54.71 2.54.71.24 0 .64-.03 1.04-.1.27-.05.53.13.58.41.05.27-.13.53-.41.58-.57.11-1.07.12-1.21.12zM14.91 22c-.04 0-.09-.01-.13-.02-1.59-.44-2.63-1.03-3.72-2.1-1.4-1.39-2.17-3.24-2.17-5.22 0-1.62 1.38-2.94 3.08-2.94 1.7 0 3.08 1.32 3.08 2.94 0 1.07.93 1.94 2.08 1.94s2.08-.87 2.08-1.94c0-3.77-3.25-6.83-7.25-6.83-2.84 0-5.44 1.58-6.61 4.03-.39.81-.59 1.76-.59 2.8 0 .78.07 2.01.67 3.61.1.26-.03.55-.29.64-.26.1-.55-.04-.64-.29-.49-1.31-.73-2.61-.73-3.96 0-1.2.23-2.29.68-3.24 1.33-2.79 4.28-4.6 7.51-4.6 4.55 0 8.25 3.51 8.25 7.83 0 1.62-1.38 2.94-3.08 2.94s-3.08-1.32-3.08-2.94c0-1.07-.93-1.94-2.08-1.94s-2.08.87-2.08 1.94c0 1.71.66 3.31 1.87 4.51.95.94 1.86 1.46 3.27 1.85.27.07.42.35.35.61-.05.23-.26.38-.47.38z"></path>
|
||||||
|
</svg>
|
||||||
|
<p>{{ lang.tfa.start_webauthn_validation }}</p>
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
</center>
|
||||||
|
<p id="webauthn_status_auth"></p>
|
||||||
|
<div class="alert alert-danger" style="display:none" id="webauthn_return_code"></div>
|
||||||
|
<input type="hidden" name="token" id="webauthn_auth_data"/>
|
||||||
|
<input type="hidden" name="tfa_method" value="webauthn">
|
||||||
|
<input type="hidden" name="verify_tfa_login"/><br/>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
{# leave this here to inform users that u2f is deprecated #}
|
||||||
|
{% if pending_tfa_method == 'u2f' %}
|
||||||
|
<form role="form" method="post" id="u2f_auth_form">
|
||||||
|
<p>{{ lang.tfa.u2f_deprecated }}</p>
|
||||||
|
<p><b>{{ lang.tfa.u2f_deprecated_important }}</b></p>
|
||||||
|
<input type="hidden" name="token" value="destroy" />
|
||||||
|
<input type="hidden" name="tfa_method" value="u2f">
|
||||||
|
<input type="hidden" name="verify_tfa_login"/><br/>
|
||||||
|
<button type="submit" class="btn btn-xs-lg btn-success" value="Login">{{ lang.login.login }}</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="text">{{ lang.user.pushover_sender_array|raw }}</label>
|
<label for="text">{{ lang.user.pushover_sender_array|raw }}</label>
|
||||||
<input type="text" class="form-control" name="senders" value="{{ pushover_data.token }}" placeholder="sender1@example.com, sender2@example.com">
|
<input type="text" class="form-control" name="senders" value="{{ pushover_data.senders }}" placeholder="sender1@example.com, sender2@example.com">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
|
|
|
@ -2,7 +2,7 @@ version: '2.1'
|
||||||
services:
|
services:
|
||||||
|
|
||||||
unbound-mailcow:
|
unbound-mailcow:
|
||||||
image: mailcow/unbound:1.14
|
image: mailcow/unbound:1.15
|
||||||
environment:
|
environment:
|
||||||
- TZ=${TZ}
|
- TZ=${TZ}
|
||||||
volumes:
|
volumes:
|
||||||
|
@ -58,7 +58,7 @@ services:
|
||||||
- redis
|
- redis
|
||||||
|
|
||||||
clamd-mailcow:
|
clamd-mailcow:
|
||||||
image: mailcow/clamd:1.42
|
image: mailcow/clamd:1.43
|
||||||
restart: always
|
restart: always
|
||||||
dns:
|
dns:
|
||||||
- ${IPV4_NETWORK:-172.22.1}.254
|
- ${IPV4_NETWORK:-172.22.1}.254
|
||||||
|
@ -157,6 +157,7 @@ services:
|
||||||
- ALLOW_ADMIN_EMAIL_LOGIN=${ALLOW_ADMIN_EMAIL_LOGIN:-n}
|
- ALLOW_ADMIN_EMAIL_LOGIN=${ALLOW_ADMIN_EMAIL_LOGIN:-n}
|
||||||
- MASTER=${MASTER:-y}
|
- MASTER=${MASTER:-y}
|
||||||
- DEV_MODE=${DEV_MODE:-n}
|
- DEV_MODE=${DEV_MODE:-n}
|
||||||
|
- WEBAUTHN_ONLY_TRUSTED_VENDORS=${WEBAUTHN_ONLY_TRUSTED_VENDORS:-n}
|
||||||
restart: always
|
restart: always
|
||||||
networks:
|
networks:
|
||||||
mailcow-network:
|
mailcow-network:
|
||||||
|
@ -164,7 +165,7 @@ services:
|
||||||
- phpfpm
|
- phpfpm
|
||||||
|
|
||||||
sogo-mailcow:
|
sogo-mailcow:
|
||||||
image: mailcow/sogo:1.104
|
image: mailcow/sogo:1.106
|
||||||
environment:
|
environment:
|
||||||
- DBNAME=${DBNAME}
|
- DBNAME=${DBNAME}
|
||||||
- DBUSER=${DBUSER}
|
- DBUSER=${DBUSER}
|
||||||
|
@ -211,7 +212,7 @@ services:
|
||||||
- sogo
|
- sogo
|
||||||
|
|
||||||
dovecot-mailcow:
|
dovecot-mailcow:
|
||||||
image: mailcow/dovecot:1.158
|
image: mailcow/dovecot:1.159
|
||||||
depends_on:
|
depends_on:
|
||||||
- mysql-mailcow
|
- mysql-mailcow
|
||||||
dns:
|
dns:
|
||||||
|
@ -384,7 +385,7 @@ services:
|
||||||
acme-mailcow:
|
acme-mailcow:
|
||||||
depends_on:
|
depends_on:
|
||||||
- nginx-mailcow
|
- nginx-mailcow
|
||||||
image: mailcow/acme:1.80
|
image: mailcow/acme:1.81
|
||||||
dns:
|
dns:
|
||||||
- ${IPV4_NETWORK:-172.22.1}.254
|
- ${IPV4_NETWORK:-172.22.1}.254
|
||||||
environment:
|
environment:
|
||||||
|
@ -420,7 +421,7 @@ services:
|
||||||
- acme
|
- acme
|
||||||
|
|
||||||
netfilter-mailcow:
|
netfilter-mailcow:
|
||||||
image: mailcow/netfilter:1.45
|
image: mailcow/netfilter:1.46
|
||||||
stop_grace_period: 30s
|
stop_grace_period: 30s
|
||||||
depends_on:
|
depends_on:
|
||||||
- dovecot-mailcow
|
- dovecot-mailcow
|
||||||
|
@ -443,7 +444,7 @@ services:
|
||||||
- /lib/modules:/lib/modules:ro
|
- /lib/modules:/lib/modules:ro
|
||||||
|
|
||||||
watchdog-mailcow:
|
watchdog-mailcow:
|
||||||
image: mailcow/watchdog:1.95
|
image: mailcow/watchdog:1.96
|
||||||
dns:
|
dns:
|
||||||
- ${IPV4_NETWORK:-172.22.1}.254
|
- ${IPV4_NETWORK:-172.22.1}.254
|
||||||
tmpfs:
|
tmpfs:
|
||||||
|
@ -505,7 +506,7 @@ services:
|
||||||
- watchdog
|
- watchdog
|
||||||
|
|
||||||
dockerapi-mailcow:
|
dockerapi-mailcow:
|
||||||
image: mailcow/dockerapi:1.40
|
image: mailcow/dockerapi:1.41
|
||||||
security_opt:
|
security_opt:
|
||||||
- label=disable
|
- label=disable
|
||||||
restart: always
|
restart: always
|
||||||
|
@ -523,7 +524,7 @@ services:
|
||||||
- dockerapi
|
- dockerapi
|
||||||
|
|
||||||
solr-mailcow:
|
solr-mailcow:
|
||||||
image: mailcow/solr:1.8
|
image: mailcow/solr:1.8.1
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- solr-vol-1:/opt/solr/server/solr/dovecot-fts/data:Z
|
- solr-vol-1:/opt/solr/server/solr/dovecot-fts/data:Z
|
||||||
|
@ -539,7 +540,7 @@ services:
|
||||||
- solr
|
- solr
|
||||||
|
|
||||||
olefy-mailcow:
|
olefy-mailcow:
|
||||||
image: mailcow/olefy:1.8
|
image: mailcow/olefy:1.8.1
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
- TZ=${TZ}
|
- TZ=${TZ}
|
||||||
|
@ -560,6 +561,7 @@ services:
|
||||||
image: mcuadros/ofelia:latest
|
image: mcuadros/ofelia:latest
|
||||||
restart: always
|
restart: always
|
||||||
command: daemon --docker
|
command: daemon --docker
|
||||||
|
environment:
|
||||||
- TZ=${TZ}
|
- TZ=${TZ}
|
||||||
depends_on:
|
depends_on:
|
||||||
- sogo-mailcow
|
- sogo-mailcow
|
||||||
|
|
|
@ -344,6 +344,11 @@ DOVECOT_MASTER_PASS=
|
||||||
# https://mailcow.github.io/mailcow-dockerized-docs/debug-reset_tls/
|
# https://mailcow.github.io/mailcow-dockerized-docs/debug-reset_tls/
|
||||||
ACME_CONTACT=
|
ACME_CONTACT=
|
||||||
|
|
||||||
|
# WebAuthn device manufacturer verification
|
||||||
|
# After setting WEBAUTHN_ONLY_TRUSTED_VENDORS=y only devices from trusted manufacturers are allowed
|
||||||
|
# root certificates can be placed for validation under mailcow-dockerized/data/web/inc/lib/WebAuthn/rootCertificates
|
||||||
|
WEBAUTHN_ONLY_TRUSTED_VENDORS=n
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
mkdir -p data/assets/ssl
|
mkdir -p data/assets/ssl
|
||||||
|
|
|
@ -307,6 +307,7 @@ CONFIG_ARRAY=(
|
||||||
"ADDITIONAL_SERVER_NAMES"
|
"ADDITIONAL_SERVER_NAMES"
|
||||||
"ACME_CONTACT"
|
"ACME_CONTACT"
|
||||||
"WATCHDOG_VERBOSE"
|
"WATCHDOG_VERBOSE"
|
||||||
|
"WEBAUTHN_ONLY_TRUSTED_VENDORS"
|
||||||
)
|
)
|
||||||
|
|
||||||
sed -i --follow-symlinks '$a\' mailcow.conf
|
sed -i --follow-symlinks '$a\' mailcow.conf
|
||||||
|
@ -514,6 +515,13 @@ for option in ${CONFIG_ARRAY[@]}; do
|
||||||
echo '# https://mailcow.github.io/mailcow-dockerized-docs/debug-reset-tls/' >> mailcow.conf
|
echo '# https://mailcow.github.io/mailcow-dockerized-docs/debug-reset-tls/' >> mailcow.conf
|
||||||
echo 'ACME_CONTACT=' >> mailcow.conf
|
echo 'ACME_CONTACT=' >> mailcow.conf
|
||||||
fi
|
fi
|
||||||
|
elif [[ ${option} == "WEBAUTHN_ONLY_TRUSTED_VENDORS" ]]; then
|
||||||
|
if ! grep -q ${option} mailcow.conf; then
|
||||||
|
echo "# WebAuthn device manufacturer verification" >> mailcow.conf
|
||||||
|
echo '# After setting WEBAUTHN_ONLY_TRUSTED_VENDORS=y only devices from trusted manufacturers are allowed' >> mailcow.conf
|
||||||
|
echo '# root certificates can be placed for validation under mailcow-dockerized/data/web/inc/lib/WebAuthn/rootCertificates' >> mailcow.conf
|
||||||
|
echo 'WEBAUTHN_ONLY_TRUSTED_VENDORS=n' >> mailcow.conf
|
||||||
|
fi
|
||||||
elif [[ ${option} == "WATCHDOG_VERBOSE" ]]; then
|
elif [[ ${option} == "WATCHDOG_VERBOSE" ]]; then
|
||||||
if ! grep -q ${option} mailcow.conf; then
|
if ! grep -q ${option} mailcow.conf; then
|
||||||
echo '# Enable watchdog verbose logging' >> mailcow.conf
|
echo '# Enable watchdog verbose logging' >> mailcow.conf
|
||||||
|
|
Loading…
Reference in New Issue